Search Results for

    Show / Hide Table of Contents

    The Surface Mesh 3D Chart Type

    The surface mesh renders a two-dimensional array as a heightmap. In SciChart it's defined by the SurfaceMeshRenderableSeries3D class and provides a number of configurable chart types in SciChart 3D, including:

    • dynamic real-time Surfaces (terrains or height maps).
    • texturing of surfaces or terrains or height maps.
    • Non-uniform or uniform grid spacing.
    • Contour mapping or wireframe on terrain or height maps.

    Surface Mesh 3D Series Type

    Note

    Examples of the Surface Mesh 3D Series can be found in the SciChart Android Examples Suite as well as on GitHub:

    • Native Example
    • Xamarin Example

    In the Surface Mesh 3D Series, the data is stored in the UniformGridDataSeries3D<TX,TY,TZ>. This represents a 2-dimensional grid, typically of type double.

    Some important points which is mandatory to understand while configuring the Surface Meshes:

    • The double values which are stored in the UniformGridDataSeries3D<TX,TY,TZ> correspond to the heights on the chart (the Y-Axis). They are transformed into chart World Coordinates via the yAxis.
    • The Z and X Data-Value are defined by the startX, stepX, startZ and stepZ properties on UniformGridDataSeries3D<TX,TY,TZ>. These are transformed into World Coordinates via the xAxis and zAxis respectively.
    • The Colours on the SurfaceMesh are defined by the MeshColorPalette. More on this in the following sections.

    The SurfaceMeshRenderableSeries3D is highly configurable, so please read on the Configuring Surface Mesh 3D Series section.

    Create a Surface Mesh Series 3D

    In order to create Uniform Surface Mesh 3D - you will need to provide the UniformGridDataSeries3D<TX,TY,TZ> with N x M array of points.

    The above graph is rendered with the following code:

    • Java
    • Java with Builders API
    • Kotlin
    final NumericAxis3D xAxis = new NumericAxis3D();
    xAxis.setGrowBy(new DoubleRange(.1, .1));
    
    final NumericAxis3D yAxis = new NumericAxis3D();
    yAxis.setVisibleRange(new DoubleRange(0.0, .3));
    yAxis.setGrowBy(new DoubleRange(.1, .1));
    
    final NumericAxis3D zAxis = new NumericAxis3D();
    zAxis.setGrowBy(new DoubleRange(.1, .1));
    
    final UniformGridDataSeries3D<Double, Double, Double> ds = new UniformGridDataSeries3D<>(Double.class, Double.class, Double.class, COUNT, COUNT);
    
    for (int x = 0; x < COUNT; x++) {
        for (int z = 0; z < COUNT; z++) {
            final double xVal = 25 * x / COUNT;
            final double zVal = 25 * z / COUNT;
            final double y = Math.sin(xVal * 0.2) / ((zVal + 1.0) * 2.0);
            ds.updateYAt(x, z, y);
        }
    }
    
    final int[] colors = new int[]{0xFF1D2C6B, 0xFF0000FF, 0xFF00FFFF, 0xFFADFF2F, 0xFFFFFF00, 0xFFFF0000, 0xFF8B0000};
    final float[] stops = new float[]{0, .1f, .3f, .5f, .7f, .9f, 1};
    final GradientColorPalette palette = new GradientColorPalette(colors, stops);
    
    final SurfaceMeshRenderableSeries3D rs = new SurfaceMeshRenderableSeries3D();
    rs.setDataSeries(ds);
    rs.setDrawMeshAs(DrawMeshAs.SolidWireframe);
    rs.setStroke(0x77228B22);
    rs.setContourStroke(0x77228B22);
    rs.setStrokeThickness(convertValueToDp(2f));
    rs.setDrawSkirt(false);
    rs.setMeshColorPalette(palette);
    
    UpdateSuspender.using(surface, () -> {
        surface.setXAxis(xAxis);
        surface.setYAxis(yAxis);
        surface.setZAxis(zAxis);
        surface.getRenderableSeries().add(rs);
        surface.getChartModifiers().add(createDefaultModifiers3D());
    });
    
    final NumericAxis3D xAxis = sciChart3DBuilder.newNumericAxis3D().withGrowBy(.1, .1).build();
    final NumericAxis3D yAxis = sciChart3DBuilder.newNumericAxis3D().withGrowBy(.1, .1).withVisibleRange(0.0, .3).build();
    final NumericAxis3D zAxis = sciChart3DBuilder.newNumericAxis3D().withGrowBy(.1, .1).build();
    
    final UniformGridDataSeries3D<Double, Double, Double> ds = new UniformGridDataSeries3D<>(Double.class, Double.class, Double.class, COUNT, COUNT);
    
    for (int x = 0; x < COUNT; x++) {
        for (int z = 0; z < COUNT; z++) {
            final double xVal = 25 * x / COUNT;
            final double zVal = 25 * z / COUNT;
            final double y = Math.sin(xVal * 0.2) / ((zVal + 1.0) * 2.0);
            ds.updateYAt(x, z, y);
        }
    }
    
    final int[] colors = new int[]{0xFF1D2C6B, 0xFF0000FF, 0xFF00FFFF, 0xFFADFF2F, 0xFFFFFF00, 0xFFFF0000, 0xFF8B0000};
    final float[] stops = new float[]{0, .1f, .3f, .5f, .7f, .9f, 1};
    final GradientColorPalette palette = new GradientColorPalette(colors, stops);
    
    final SurfaceMeshRenderableSeries3D rs = sciChart3DBuilder.newSurfaceMeshSeries3D()
            .withDataSeries(ds)
            .withDrawMeshAs(DrawMeshAs.SolidWireframe)
            .withStroke(0x77228B22)
            .withContourStroke(0x77228B22)
            .withStrokeThicknes(2f)
            .withDrawSkirt(false)
            .withMeshColorPalette(palette)
            .build();
    
    UpdateSuspender.using(surface, () -> {
        surface.setXAxis(xAxis);
        surface.setYAxis(yAxis);
        surface.setZAxis(zAxis);
        surface.getRenderableSeries().add(rs);
        surface.getChartModifiers().add(createDefaultModifiers3D());
    });
    
    val xAxis = NumericAxis3D()
    xAxis.growBy = DoubleRange(.1, .1)
    
    val yAxis = NumericAxis3D()
    yAxis.setVisibleRange(DoubleRange(0.0, .3))
    yAxis.growBy = DoubleRange(.1, .1)
    
    val zAxis = NumericAxis3D()
    zAxis.growBy = DoubleRange(.1, .1)
    
    val ds = UniformGridDataSeries3D(Double::class.java, Double::class.java, Double::class.java, COUNT, COUNT
    )
    for (x in 0 until COUNT) {
        for (z in 0 until COUNT) {
            val xVal = (25 * x / COUNT).toDouble()
            val zVal = (25 * z / COUNT).toDouble()
            val y = sin(xVal * 0.2) / ((zVal + 1.0) * 2.0)
            ds.updateYAt(x, z, y)
        }
    }
    val colors = intArrayOf(0xFF00008B.toInt(), 0xFF0000FF.toInt(), 0xFF00FFFF.toInt(), 0xFFADFF2F.toInt(), 0xFFFFFF00.toInt(), 0xFFFF0000.toInt(), 0xFF8B0000.toInt())
    val stops = floatArrayOf(0f, .1f, .3f, .5f, .7f, .9f, 1f)
    val palette = GradientColorPalette(colors, stops)
    
    val rs = SurfaceMeshRenderableSeries3D()
    rs.dataSeries = ds
    rs.drawMeshAs = DrawMeshAs.SolidWireframe
    rs.stroke = 0x77228B22
    rs.contourStroke = 0x77228B22
    rs.strokeThickness = convertValueToDp(2f)
    rs.drawSkirt = false
    rs.meshColorPalette = palette
    
    UpdateSuspender.using(surface) {
        surface.xAxis = xAxis
        surface.yAxis = yAxis
        surface.zAxis = zAxis
        surface.renderableSeries.add(rs)
        surface.chartModifiers.add(createDefaultModifiers3D())
    }
    

    Configuring Surface Mesh 3D Series

    There are several properties which affect rendering of the SurfaceMeshRenderableSeries3D and those are listed below:

    Property Description
    highlight Changes the lighting algorithm to make cells appear lighter.
    meshColorPalette defines the MeshColorPalette which is used to calculate color from data value. See the Applying Palettes to the 3D Surface Meshes section for more information.
    meshPaletteMode Changes how the heightmap is applied. See the Applying Palettes to the 3D Surface Meshes section for more information.
    heightScaleFactor Applies a constant scaling factor to the heights, e.g. setting to 0 will make the surface mesh flat.
    yOffset Applies a constant offset heights, e.g. setting to 1 will move the SurfaceMesh 1-Data-Value in the Y-direction.

    The effect of these properties are demonstrated in the images below.

    Highlight = 0 Highlight = 1
    Surface Mesh Highlight = 0 Surface Mesh Highlight = 1
    HeightScaleFactor = 0 meshPaletteMode = HeightMapSolidCells
    Surface Mesh HeightScaleFactor = 0 Surface Mesh MeshPaletteMode = HeightMapSolidCells

    Applying Palettes to the 3D Surface Meshes

    The SurfaceMeshRenderableSeries3D accepts color palettes via the meshColorPalette property. There are several palettes available out of the box, including the following:

    • SolidColorBrushPalette - applies a solid color to all cells in the mesh
    • GradientColorPalette - maps a linear gradient to the mesh.
    • BrushColorPalette - maps a BrushStyle to the mesh. For example, this can be used to map an image via the TextureBrushStyle type.
    • Custom Palette - maps a custom Texture to the mesh.

    Read on to learn more about each of these options.

    In addition, rendering all of the above palettes can be affected by the MeshPaletteMode. See the Mesh Palette Modes section below for more information.

    Mesh Palette Modes

    The meshPaletteMode property changes how the palette is applied to the Mesh. Possible MeshPaletteMode are listed in the table below:

    Mode Description
    HeightMapInterpolated the palette is applied in the Y-direction (vertically).
    HeightMapSolidCells same as HeightMapInterpolated but no interpolation. Use this if you want each cell have a separate colour.
    Textured palette is applied to the mesh in the XZ plane. Imagine the palette is stretched over the mesh itself.
    TexturedSolidCells same as Textured but no interpolation. Use this if you want each cell have a separate colour.

    Solid Color Palette

    The Solid palette if provided by the SolidColorBrushPalette class. It's quite simple, let's dig into declaration:

    • Java
    • Java with Builders API
    • Kotlin
    final SurfaceMeshRenderableSeries3D rSeries = new SurfaceMeshRenderableSeries3D();
    rSeries.setMeshColorPalette(new SolidColorBrushPalette(0xFF006400));
    
    final SurfaceMeshRenderableSeries3D rSeries = sciChart3DBuilder.newSurfaceMeshSeries3D()
            .withMeshColorPalette(new SolidColorBrushPalette(0xFF006400))
            .build();
    
    val rSeries = SurfaceMeshRenderableSeries3D()
    rSeries.meshColorPalette = SolidColorBrushPalette(-0xff9c00)
    

    Surface Mesh Solid Palette

    Gradient Color Palette

    The GradientColorPalette can be used to map a Gradient Brush set of colors to heights in the Surface Mesh. The mapping is similar to that performed by the Heatmap Series in 2D Charts.

    Given the following code:

    • Java
    • Java with Builders API
    • Kotlin
    final int[] colors = new int[]{0xFF1D2C6B, 0xFF0000FF, 0xFF00FFFF, 0xFFADFF2F, 0xFFFFFF00, 0xFFFF0000, 0xFF8B0000};
    final float[] stops = new float[]{0, .1f, .3f, .5f, .7f, .9f, 1};
    final GradientColorPalette palette = new GradientColorPalette(colors, stops);
    
    final int[] colors = new int[]{0xFF1D2C6B, 0xFF0000FF, 0xFF00FFFF, 0xFFADFF2F, 0xFFFFFF00, 0xFFFF0000, 0xFF8B0000};
    final float[] stops = new float[]{0, .1f, .3f, .5f, .7f, .9f, 1};
    final GradientColorPalette palette = new GradientColorPalette(colors, stops);
    
    val colors = intArrayOf(0xFF00008B.toInt(), 0xFF0000FF.toInt(), 0xFF00FFFF.toInt(), 0xFFADFF2F.toInt(), 0xFFFFFF00.toInt(), 0xFFFF0000.toInt(), 0xFF8B0000.toInt())
    val stops = floatArrayOf(0f, .1f, .3f, .5f, .7f, .9f, 1f)
    val palette = GradientColorPalette(colors, stops)
    

    Colors are mapped onto Y-values as follows:

    • minimum are drawn with the gradient stop color at offset 0.
    • maximum are drawn with the gradient stop color at offset 1.
    • All other values are linearly interpolated (including Y-Values outside of minimum and maximum values).

    It's also possible to specify whether gradient is stepped or not. See code which creates stepped palette below:

    • Java
    • Java with Builders API
    • Kotlin
    final GradientColorPalette palette = new GradientColorPalette(colors, stops, true);
    
    final GradientColorPalette palette = new GradientColorPalette(colors, stops, true);
    
    val palette = GradientColorPalette(colors, stops, true)
    

    And the difference is showed below:

    Linear Gradient Stepped Gradient (isStepped = YES)
    Surface Mesh Gradient Palette Surface Mesh Stepped Gradient Palette

    Brush Color Palette

    A texture can be applied to the SurfaceMesh and mapped over it in the XZ plane plane by using a combination of the following:

    • BrushColorPalette
    • Textured
    • meshColorPaletteSize

    See the code below:

    • Java
    • Java with Builders API
    • Kotlin
    final Bitmap bitmap = BitmapFactory.decodeResource(getContext().getResources(), R.drawable.example_weather_storm);
    CustomPointMarker3D pointMarker = new CustomPointMarker3D(bitmap);
    pointMarker.setSize(10);
    
    final TextureBrushStyle brushStyle = new TextureBrushStyle(bitmap);
    final BrushColorPalette palette = new BrushColorPalette(brushStyle);
    
    final SurfaceMeshRenderableSeries3D rSeries = new SurfaceMeshRenderableSeries3D();
    rSeries.setMeshColorPalette(palette);
    rSeries.setMeshPaletteMode(MeshPaletteMode.Textured);
    rSeries.setMeshColorPaletteSize(new Size(bitmap.getWidth(), bitmap.getHeight()));
    
    final Bitmap bitmap = BitmapFactory.decodeResource(getContext().getResources(), R.drawable.example_weather_storm);
    CustomPointMarker3D pointMarker = new CustomPointMarker3D(bitmap);
    pointMarker.setSize(10);
    
    final TextureBrushStyle brushStyle = new TextureBrushStyle(bitmap);
    final BrushColorPalette palette = new BrushColorPalette(brushStyle);
    
    final SurfaceMeshRenderableSeries3D rSeries = sciChart3DBuilder.newSurfaceMeshSeries3D()
            .withMeshColorPalette(palette)
            .withMeshPaletteMode(MeshPaletteMode.Textured)
            .withMeshColorPaletteSize(new Size(bitmap.getWidth(), bitmap.getHeight()))
            .build();
    
    val bitmap = BitmapFactory.decodeResource(requireContext().resources, R.drawable.example_weather_storm)
    val pointMarker = CustomPointMarker3D(bitmap)
    pointMarker.size = 10f
    
    val brushStyle = TextureBrushStyle(bitmap)
    val palette = BrushColorPalette(brushStyle)
    
    val rSeries = SurfaceMeshRenderableSeries3D()
    rSeries.meshColorPalette = palette
    rSeries.meshPaletteMode = MeshPaletteMode.Textured
    rSeries.meshColorPaletteSize = Size(bitmap.width, bitmap.height)
    

    Surface Mesh Textured Palette

    Create a Custom Palette

    In addition to all of the above, you can create your own custom Color Palette by inheriting MeshColorPalette and overriding the getTexture(int width, int height) method.

    For example, see the following code snippet:

    • Java
    • Java with Builders API
    • Kotlin
    class CustomPalette extends MeshColorPalette {
        @Override
        public Texture2D getTexture(int width, int height) {
            final Bitmap bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
            try {
                return Texture2D.fromBitmap(bitmap);
            } finally {
                bitmap.recycle();
            }
        }
    }
    
    class CustomPalette extends MeshColorPalette {
        @Override
        public Texture2D getTexture(int width, int height) {
            final Bitmap bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
            try {
                return Texture2D.fromBitmap(bitmap);
            } finally {
                bitmap.recycle();
            }
        }
    }
    
    class CustomPalette : MeshColorPalette() {
        override fun getTexture(width: Int, height: Int): Texture2D {
            val bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888)
            return try {
                Texture2D.fromBitmap(bitmap)
            } finally {
                bitmap.recycle()
            }
        }
    }
    

    The palette is applied to a SurfaceMeshRenderableSeries3D as in the above examples.

    Note

    You might noticed helped extension, which allows to create ITexture2D directly from your Bitmap - fromBitmap(Bitmap bitmap).

    Surface Mesh 3D Wireframe and Contours

    In SciChart, the 3D Surface Mesh can be configured to draw with Contours and/or Wireframe. That is added optionally ans configured via the drawMeshAs property. See the possible options in the table below:

    Option Surface Wireframe Contours
    SolidMesh +
    Wireframe +
    Contours +
    SolidWireframe + +
    SolidWithContours + +
    SolidWireframeWithContours + + +

    Wireframe and Contours can be configured via the following properties:

    • contourStrokeThickness - defines the thickness of the contour line.
    • contourStroke - defines the stroke color of the contours, which may optionally include opacity.
    • contourOffset - defines the offset of contours from Y-values, defaults to 0.
    • contourInterval - defines the Y-value intervals between contours.
    • strokeThickness - defines the thickness of the wireframe line.
    • stroke - defines the stroke color of the wireframe, which may optionally include opacity.

    Some examples are shown below:

    Bare Wireframe Solid Surface With Contours
    Surface Mesh Gradient Palette Surface Mesh Stepped Gradient Palette

    Overriding Surface Mesh 3D Specific Cell Colors

    In addition to the custom palettes, heightmaps, textures - you can override a specific cell or cells in the SurfaceMeshRenderableSeries3D by using the MetadataProvider API.

    For example it can be used for one of the following:

    • remove specific cells or mark them as NULL by overriding the cell color to be Transparent.
    • mark regions of interest, say certain cells in a value range, or with index must be colored differently.
    • with higher resolution meshes, you can change the shape of the mesh to circular (approx) by removing cells outside of a region.

    It is showcased in our Surface mesh 3D with Metadata provider example. Let's see a code snippet from it, which shows how to implement the custom MetadataProvider:

    • Java
    • Java with Builders API
    • Kotlin
    class SurfaceMeshMetadataProvider3D extends MetadataProvider3DBase<SurfaceMeshRenderableSeries3D> implements ISurfaceMeshMetadataProvider3D {
        SurfaceMeshMetadataProvider3D() {
            super(SurfaceMeshRenderableSeries3D.class);
        }
    
        @Override
        public void updateMeshColors(IntegerValues cellColors) {
            final SurfaceMeshRenderPassData3D currentRenderPassData = (SurfaceMeshRenderPassData3D) renderableSeries.getCurrentRenderPassData();
    
            final int countX = currentRenderPassData.countX - 1;
            final int countZ = currentRenderPassData.countZ - 1;
            cellColors.setSize(currentRenderPassData.getPointsCount());
    
            final int[] items = cellColors.getItemsArray();
            for (int x = 0; x < countX; x++) {
                for (int z = 0; z < countZ; z++) {
                    final int index = x * countZ + z;
    
                    final int color;
                    if ((x >= 20 && x <= 26 && z > 0 && z < 47) || (z >= 20 && z <= 26 && x > 0 && x < 47)) {
                        color = TRANSPARENT;
                    } else {
                        color = dataManager.getRandomColor();
                    }
    
                    items[index] = color;
                }
            }
        }
    }
    
    class SurfaceMeshMetadataProvider3D extends MetadataProvider3DBase<SurfaceMeshRenderableSeries3D> implements ISurfaceMeshMetadataProvider3D {
        SurfaceMeshMetadataProvider3D() {
            super(SurfaceMeshRenderableSeries3D.class);
        }
    
        @Override
        public void updateMeshColors(IntegerValues cellColors) {
            final SurfaceMeshRenderPassData3D currentRenderPassData = (SurfaceMeshRenderPassData3D) renderableSeries.getCurrentRenderPassData();
    
            final int countX = currentRenderPassData.countX - 1;
            final int countZ = currentRenderPassData.countZ - 1;
            cellColors.setSize(currentRenderPassData.getPointsCount());
    
            final int[] items = cellColors.getItemsArray();
            for (int x = 0; x < countX; x++) {
                for (int z = 0; z < countZ; z++) {
                    final int index = x * countZ + z;
    
                    final int color;
                    if ((x >= 20 && x <= 26 && z > 0 && z < 47) || (z >= 20 && z <= 26 && x > 0 && x < 47)) {
                        color = TRANSPARENT;
                    } else {
                        color = dataManager.getRandomColor();
                    }
    
                    items[index] = color;
                }
            }
        }
    }
    
    class SurfaceMeshMetadataProvider3D :
        MetadataProvider3DBase<SurfaceMeshRenderableSeries3D>(SurfaceMeshRenderableSeries3D::class.java),
        ISurfaceMeshMetadataProvider3D {
        override fun updateMeshColors(cellColors: IntegerValues) {
            val currentRenderPassData =
                renderableSeries!!.currentRenderPassData as SurfaceMeshRenderPassData3D
            val countX = currentRenderPassData.countX - 1
            val countZ = currentRenderPassData.countZ - 1
            cellColors.setSize(currentRenderPassData.pointsCount)
            val items = cellColors.itemsArray
            for (x in 0 until countX) {
                for (z in 0 until countZ) {
                    val index = x * countZ + z
                    val color: Int
                    color =
                        if (x >= 20 && x <= 26 && z > 0 && z < 47 || z >= 20 && z <= 26 && x > 0 && x < 47) {
                            TRANSPARENT
                        } else {
                            dataManager.getRandomColor()
                        }
                    items[index] = color
                }
            }
        }
    }
    
    Note

    For more information about custom MetadataProviders - please refer to the MetadataProvider API article.

    MetadataProvider API

    Note

    Examples of using MetadataProvider API can be found in the SciChart Android Examples Suite as well as on GitHub:

    • Native Example
    • Xamarin Example
    Back to top © 2011-2025 SciChart. All rights reserved. | sitemap.xml