Question

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() }
                }
            }); 
        }
    }
    }
}

surface graph without marked values

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?

Was it helpful?

Solution

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: enter image description here

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top