I want to create a 3D surface graph as shown in the picture. I am using ILNumerics, recent RC from the website. So far I could create the surface (the data are only for testing) using this code:
using System;
using System.Drawing;
using System.Windows.Forms;
using ILNumerics;
using ILNumerics.Drawing;
using ILNumerics.Drawing.Plotting;
namespace WindowsFormsApplication1 {
public partial class Form1 : Form {
public Form1() {
InitializeComponent();
}
private void ilPanel1_Load(object sender, EventArgs e) {
using (ILScope.Enter()) {
ILArray<float> R = ILMath.linspace<float>(-4, 4, 100);
ILArray<float> y = 1;
ILArray<float> x = ILMath.meshgrid(R, R, y);
ILArray<float> Z = ILMath.zeros<float>(x.S[0], x.S[1], 3);
Z[":;:;1"] = x; Z[":;:;2"] = y;
Z[":;:;0"] = 0.4f * x * x - 0.2f * y * y * y;
ilPanel1.Scene.Add(new ILPlotCube(twoDMode: false) {
new ILSurface(Z, colormap: Colormaps.Cool) {
Colors = 1.4f * x * x * x + 0.13f * y * y,
Childs = { new ILColorbar() }
}
});
}
}
}
}
What is the best way to mark all facettes with a data (color) value of 0 in a different color? Probably using the colorbar would be the way to go? But how exactly is this done?
This can be achieved using a custom colormap. But one must take care, to do it reliably. Simply placing a new red keypoint in the middle of the colormap would give very incorrect results... :|
I modified and documented your example slightly. Basically, colormaps contain keypoints with a position and a color. Positions commonly (but not necessarily) range from [0..1]. Colors are always in range [0..1]. The challenge here is to find the correct position of your '0' cdata value in the colormap range.
First, create the default Cool colormap. It has the following two keypoints (in rows):
colorkeys
<Single> [2,5]
[0]: 0,00000 0,00000 1,00000 1,00000 1,00000 <- position: 0, color: RGBA
[1]: 1,00000 1,00000 0,00000 1,00000 1,00000 <- position: 1
You have to add at least 3 new keypoints: the first two sample the original color at the edges of the marked area. The third gives the marked area the red color. It would not work to simply add the red keypoint, since this would influence all values in the map, not only data values around 0.
At the end the keypoints look as follows:
colorkeys
<Single> [5,5]
[0]: 0,00000 0,00000 1,00000 1,00000 1,00000
[1]: 1,00000 1,00000 0,00000 1,00000 1,00000
[2]: 0,49150 0,49150 0,50850 1,00000 1,00000
[3]: 0,49702 0,49702 0,50298 1,00000 1,00000
[4]: 0,49426 1,00000 0,00000 0,00000 1,00000
Note that the order of keypoints is not important. They are sorted anyway when creating a new colormap. The new map is simply provided to ILSurface:
private void ilPanel1_Load(object sender, EventArgs e) {
// create some X/Y meshgrid data
ILArray<float> y = 1, R = ILMath.linspace<float>(-4, 4, 100);
ILArray<float> x = ILMath.meshgrid(R, ILMath.linspace<float>(-4, 4, 100), y);
// precreate the surface data array
ILArray<float> Z = ILMath.zeros<float>(x.S[0], x.S[1], 3);
// surface expects Z, X and Y coords (in that order)
Z[":;:;2"] = y; Z[":;:;1"] = x;
Z[":;:;0"] = 0.4f * x * x - 0.2f * y * y * y;
// our color data are based on another function
ILArray<float> cdata = 1.4f * x * x * x + 0.13f * y * y;
// we need cdatas limits for creating a new colormap
float min, max; cdata.GetLimits(out min, out max);
// get default 'Cool' colormap
var colormap = new ILColormap(Colormaps.Cool);
// get colormap keys as array: [position,R,G,B,A]
ILArray<float> colorkeys = colormap.Data;
// helper function to map true values to 0..1 range
Func<float, float> map = a => { return (a - min) / (max - min); };
// the value to mark and +/- tolerance width (to make it a visible strip at least)
float markValue = 0, tolerance = 0.5f;
// sample the colormap at the marked edges
Vector4 key1 = colormap.Map(markValue - tolerance, new Tuple<float, float>(min, max));
Vector4 key2 = colormap.Map(markValue + tolerance, new Tuple<float, float>(min, max));
// create new keypoints at the edges of marked area
colorkeys[ILMath.end + 1, ":"] = ILMath.array(map(markValue - tolerance), key1.X, key1.Y, key1.Z, 1f);
colorkeys[ILMath.end + 1, ":"] = ILMath.array(map(markValue + tolerance), key2.X, key2.Y, key2.Z, 1f);
// create new keypoint for the marked area itself; color red
colorkeys[ILMath.end + 1, ":"] = ILMath.array(map(markValue), 1f, 0f, 0f, 1f); // red
// make a new colormap out of it
colormap = new ILColormap(colorkeys);
// create & add a plot cube
ilPanel1.Scene.Add(new ILPlotCube(twoDMode: false) {
// add surface plot, give custom colormap & colormap data ...
new ILSurface(Z, colormap: colormap, C: cdata) {
// add colorbar
Childs = { new ILColorbar() }
}
});
}
This produces this output: