Pre loader

WPF 3D Chart Add Geometry to a 3D Scene

WPF Chart - Examples

SciChart WPF ships with hundreds of WPF Chart Examples which you can browse, play with, view the source-code and even export each WPF Chart Example to a stand-alone Visual Studio solution. All of this is possible with the new and improved SciChart WPF Examples Suite, which ships as part of the SciChart WPF SDK.

Download the SDK

This 3D Chart demo shows how to add a custom Cube Geometry to the SciChart3DSurface using our BaseSceneEntity type

Entities may be added to the SciChart3DSurface.Viewport3D.RootEntity, which is a type of BaseSceneEntity. Several cubes are created and added to the scene.

We also use the same example to demonstrate drawing 3D Text (Billboarded, facing camera always) at specific locations in the 3D scene. This uses the Font3D type and again BaseSceneEntity to custom draw text of a specific font, color and string in the 3D scene.

The C#/WPF source code for the WPF 3D Chart Add Geometry to a 3D Scene example is included below (Scroll down!).

Did you know you can also view the source code from one of the following sources as well?

  1. Clone the SciChart.WPF.Examples from Github.
  2. Or, view source in the SciChart WPF Examples suite.
  3. Also the SciChart WPF Trial contains the full source for the examples (link below).

DOWNLOAD THE WPF CHART EXAMPLES

AddObjectsToA3DChart.xaml
View source code
<UserControl x:Class="SciChart.Examples.Examples.Charts3D.Customize3DChart.AddGeometry3D.AddGeometryTo3DChart"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
             xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
             xmlns:s3D="http://schemas.abtsoftware.co.uk/scichart3D"
             mc:Ignorable="d"
             d:DesignHeight="400" d:DesignWidth="600">
    <Grid>
    <s3D:SciChart3DSurface x:Name="sciChart3DSurface"
                           WorldDimensions="400,400,400"
                           BorderThickness="0"
                           s3D:ZxAxisPlane.AxisPlaneVisibilityMode="NegativeSide"
                           s3D:XyAxisPlane.IsPlaneVisible="False"
                           s3D:ZyAxisPlane.IsPlaneVisible="False">
      <s3D:SciChart3DSurface.Camera>
        <s3D:Camera3D Target="0,50,0" Position="-400,200,400" />
      </s3D:SciChart3DSurface.Camera>
      <s3D:SciChart3DSurface.XAxis>
        <s3D:NumericAxis3D/>
      </s3D:SciChart3DSurface.XAxis>
      <s3D:SciChart3DSurface.YAxis>
        <s3D:NumericAxis3D />
      </s3D:SciChart3DSurface.YAxis>
      <s3D:SciChart3DSurface.ZAxis>
        <s3D:NumericAxis3D/>
      </s3D:SciChart3DSurface.ZAxis>
      <s3D:SciChart3DSurface.ChartModifier>
        <s3D:ModifierGroup3D>
          <s3D:FreeLookModifier3D ExecuteOn="MouseLeftButton" ExecuteWhen="Shift"/>
          <s3D:OrbitModifier3D ExecuteOn="MouseLeftButton"/>
          <s3D:MouseWheelZoomModifier3D/>
          <s3D:ZoomExtentsModifier3D ResetPosition="-400,200,400" ResetTarget="0,50,0"  AnimateDurationMs="500"/>
        </s3D:ModifierGroup3D>
      </s3D:SciChart3DSurface.ChartModifier>
    </s3D:SciChart3DSurface>
  </Grid>
</UserControl>
AddObjectsToA3DChart.xaml.cs
View source code
// *************************************************************************************
// SCICHART® Copyright SciChart Ltd. 2011-2025. All rights reserved.
//  
// Web: http://www.scichart.com
//   Support: support@scichart.com
//   Sales:   sales@scichart.com
// 
// AddGeometryTo3DChart.xaml.cs is part of the SCICHART® Examples. Permission is hereby granted
// to modify, create derivative works, distribute and publish any part of this source
// code whether for commercial, private or personal use. 
// 
// The SCICHART® examples are distributed in the hope that they will be useful, but
// without any warranty. It is provided "AS IS" without warranty of any kind, either
// expressed or implied. 
// *************************************************************************************
using System.Windows.Controls;
using System.Windows.Media;
using SciChart.Charting3D;
using SciChart.Charting3D.Interop;

namespace SciChart.Examples.Examples.Charts3D.Customize3DChart.AddGeometry3D
{
    public partial class AddGeometryTo3DChart : UserControl
    {
        public AddGeometryTo3DChart()
        {
            InitializeComponent();

            Loaded += OnExampleLoaded;
        }

        private void OnExampleLoaded(object sender, System.Windows.RoutedEventArgs e)
        {
            // Create some SceneInstances and add to the 3D scene

            // Requires Coordinate transformation from Data Coordinates to World Coordinates,
            // which requires Axes3D to be created
            CreateCubes();
            CreateLabels();
        }

        private void CreateLabels()
        {
            // Add text labels to the scene 
            var textA = new TextSceneEntity("Teeny Block", Color.FromArgb(255, 0xDC, 0x79, 0x69), TransformToWorldCoordinates(new Vector3(6.5f, 2.0f, 6.5f)), 9, "Segoe UI");
            var textB = new TextSceneEntity("Big Block", Color.FromArgb(255, 0xF4, 0x84, 0x0B), TransformToWorldCoordinates(new Vector3(4.0f, 4.0f, 4.0f)), 10, "Tahoma");

            sciChart3DSurface.Viewport3D.RootEntity.Children.Add(textA);
            sciChart3DSurface.Viewport3D.RootEntity.Children.Add(textB);
        }

        private Vector3 TransformToWorldCoordinates(Vector3 chartPoint3D)
        {
            var worldPoint3D = new Vector3
            {
                X = (float)sciChart3DSurface.XAxis.GetCurrentCoordinateCalculator().GetCoordinate(chartPoint3D.X) - sciChart3DSurface.WorldDimensions.X / 2.0f,
                Y = (float)sciChart3DSurface.YAxis.GetCurrentCoordinateCalculator().GetCoordinate(chartPoint3D.Y),
                Z = (float)sciChart3DSurface.ZAxis.GetCurrentCoordinateCalculator().GetCoordinate(chartPoint3D.Z) - sciChart3DSurface.WorldDimensions.Z / 2.0f
            };

            return worldPoint3D;
        }

        private void CreateCubes()
        {
            // Create a cubes in 3D World space with TopLeft and BottomRight coordinates
            // We set some cubes to transparent colors to demonstrate Order Independent Transparency 

            // Create CubeGeometries defined by chart coordinates
            var cubeA = CreateCubeGeometry(new Vector3(4.5f, 0.0f, 4.5f), new Vector3(6.0f, 1.5f, 6.0f), Color.FromArgb(128, 0x64, 0xBA, 0xE4));
            var cubeB = CreateCubeGeometry(new Vector3(6.0f, 0.0f, 6.0f), new Vector3(7.0f, 2.5f, 7.0f), Color.FromArgb(128, 0xDC, 0x79, 0x69));
            var cubeC = CreateCubeGeometry(new Vector3(7.0f, 0.0f, 7.0f), new Vector3(8.0f, 3.5f, 8.0f), Color.FromArgb(255, 0x6B, 0xC4, 0xA9));
            var cubeD = CreateCubeGeometry(new Vector3(2.5f, 0.0f, 2.5f), new Vector3(4.5f, 3.5f, 4.5f), Color.FromArgb(255, 0x5C, 0x43, 0x9B));
            var cubeE = CreateCubeGeometry(new Vector3(1.0f, 0.0f, 1.0f), new Vector3(6.0f, 5.0f, 6.0f), Color.FromArgb(128, 0xF4, 0x84, 0x0B));
            var cubeF = CreateCubeGeometry(new Vector3(1.0f, 0.0f, 1.0f), new Vector3(2.5f, 1.5f, 2.5f), Color.FromArgb(128, 0xF6, 0x08, 0x6C));

            // force a far position on this cube, in world coordinates ( user can do this in cases where a box inside another )
            var farPosition = new TSRVector3(20000.0f, 20000.0f, 2000.0f);
            cubeF.SetPosition(farPosition);

            // Add the cubes to the 3D Scene 
            sciChart3DSurface.Viewport3D.RootEntity.Children.Add(cubeA);
            sciChart3DSurface.Viewport3D.RootEntity.Children.Add(cubeB);
            sciChart3DSurface.Viewport3D.RootEntity.Children.Add(cubeC);
            sciChart3DSurface.Viewport3D.RootEntity.Children.Add(cubeD);
            sciChart3DSurface.Viewport3D.RootEntity.Children.Add(cubeE);
            sciChart3DSurface.Viewport3D.RootEntity.Children.Add(cubeF);
        }

        private CubeGeometry CreateCubeGeometry(Vector3 topLeft, Vector3 bottomRight, Color color)
        {
            // Transform the chart coordinates to world coordinates
            var topLeftWorld = TransformToWorldCoordinates(topLeft);
            var bottomRightWorld = TransformToWorldCoordinates(bottomRight);

            // Create a CubeGeometry from the world coordinates
            var cubeGeometry = new CubeGeometry(topLeftWorld, bottomRightWorld, color);

            return cubeGeometry;
        }
    }
}
CubeGeometry.cs
View source code
// *************************************************************************************
// SCICHART® Copyright SciChart Ltd. 2011-2025. All rights reserved.
//  
// Web: http://www.scichart.com
//   Support: support@scichart.com
//   Sales:   sales@scichart.com
// 
// CubeGeometry.cs is part of the SCICHART® Examples. Permission is hereby granted
// to modify, create derivative works, distribute and publish any part of this source
// code whether for commercial, private or personal use. 
// 
// The SCICHART® examples are distributed in the hope that they will be useful, but
// without any warranty. It is provided "AS IS" without warranty of any kind, either
// expressed or implied. 
// *************************************************************************************
using System.Linq;
using System.Windows.Media;
using SciChart.Charting3D;
using SciChart.Charting3D.Interop;
using SciChart.Charting3D.Primitives;

namespace SciChart.Examples.Examples.Charts3D.Customize3DChart.AddGeometry3D
{
    /// <summary>
    /// A class to demonstrate a 3D Geometry added to the SciChart3D Scene. Created using our BaseSceneEntity and Mesh APIs
    /// </summary>
    public class CubeGeometry : BaseSceneEntity<SCRTSceneEntity>
    {
        private readonly Vector3 _bottomRight;
        private readonly Vector3 _topLeft;
        private readonly Color _cubeColor;

        /// <summary>
        /// Creates a representation of a Cube in the 3D space, defined in world coordinates
        /// </summary>
        /// <param name="topLeft">Point in the 3D space that determines the top-left corner of a cube</param>
        /// <param name="bottomRight">Point in the 3D space that determines the bottom-right corner of a cube</param>
        /// <param name="cubeColor">Color of the cube surface</param>
        public CubeGeometry(Vector3 topLeft, Vector3 bottomRight, Color cubeColor) : base(new SCRTSceneEntity())
        {
            // Setting the position of scene entities will be used back when sorting them from camera perspective back to front
            using (TSRVector3 centerPosition = new TSRVector3(
                    0.5f*(topLeft.x + bottomRight.x),
                    0.5f*(topLeft.y + bottomRight.y), 
                    0.5f*(topLeft.z + bottomRight.z)))
            {
                SetPosition(centerPosition);
            }                        

            this._topLeft = topLeft;
            this._bottomRight = bottomRight;
            this._cubeColor = cubeColor;
        }

        /// <summary>
        /// Determines a kind of the entity. If SCRT_SCENE_ENTITY_KIND_TRANSPARENT then the 3D Engine must make some internal adjustments to allow order independent transparency
        /// </summary>
        public override eSCRTSceneEntityKind GetKind()
        {
            return _cubeColor.A == 255 ? eSCRTSceneEntityKind.SCRT_SCENE_ENTITY_KIND_OPAQUE : eSCRTSceneEntityKind.SCRT_SCENE_ENTITY_KIND_TRANSPARENT;
        }

        /// <summary>
        /// Called when the 3D Engine wishes to render this element. This is where geometry must be drawn to the 3D scene
        /// </summary>
        /// <param name="rpi">The <see cref="IRenderPassInfo3D" /> containing parameters for the current render pass.</param>
        public override void RenderScene(IRenderPassInfo3D rpi)
        {
            float bottomRightCoordX = _bottomRight.X;
            float bottomRightCoordY = _bottomRight.Y;
            float bottomRightCoordZ = _bottomRight.Z;
            float topLeftCoordX = _topLeft.X;
            float topLeftCoordY = _topLeft.Y;
            float topLeftCoordZ = _topLeft.Z;

            // y          1--------0
            // |         /|       /|
            // |       5--------4  |
            // |       |  |     |  |
            // |       |  |     |  |
            // |       |  2--------3
            // |  z    | /      |/    
            // | /     6--------7        
            // |/
            // ----------- X
            Vector3[] corners = {
                new Vector3(topLeftCoordX, topLeftCoordY, topLeftCoordZ), //0
                new Vector3(bottomRightCoordX, topLeftCoordY, topLeftCoordZ), //1
                new Vector3(bottomRightCoordX, bottomRightCoordY, topLeftCoordZ), //2
                new Vector3(topLeftCoordX, bottomRightCoordY, topLeftCoordZ), //3
                new Vector3(topLeftCoordX, topLeftCoordY, bottomRightCoordZ), //4
                new Vector3(bottomRightCoordX, topLeftCoordY, bottomRightCoordZ), //5
                new Vector3(bottomRightCoordX, bottomRightCoordY, bottomRightCoordZ), //6
                new Vector3(topLeftCoordX, bottomRightCoordY, bottomRightCoordZ), //7
            };

            Vector3[] normals = {
                new Vector3(+0.0f, +0.0f, -1.0f), //front
                new Vector3(+0.0f, +0.0f, +1.0f), //back
                new Vector3(+1.0f, +0.0f, +0.0f), //right
                new Vector3(-1.0f, +0.0f, +0.0f), //left
                new Vector3(+0.0f, +1.0f, +0.0f), //top
                new Vector3(+0.0f, -1.0f, +0.0f), //bottom
            };

            eSCRTUpAxis upaxis = VXccelEngine3D.GetUpAxis();
            // We create a mesh context. There are various mesh render modes. The simplest is Triangles
            // For this mode we have to draw a single triangle (three vertices) for each corner of the cube
            using (var meshContext = BeginLitMesh(TSRRenderMode.TRIANGLES))
            {
                // Set the Rasterizer State for this entity 
                if (upaxis == eSCRTUpAxis.Z_UP)
                {
                    VXccelEngine3D.PushRasterizerState(RasterizerStates.Default.TSRRasterizerState);
                }
                else
                {
                    VXccelEngine3D.PushRasterizerState(RasterizerStates.CullBackFacesState.TSRRasterizerState);
                }
                
                // Set the color before drawing vertices
                meshContext.SetVertexColor(_cubeColor);
            
                // Pass Entity ID value for a hit test purpose
                ulong selectionColor = VXccelEngine3D.EncodeSelectionId(EntityId, 0);
                meshContext.SetSelectionId(selectionColor);

                // Now draw the triangles. Each face of the cube is made up of two triangles
                // Front face
                SetNormal(meshContext, normals[0]);
                SetVertex(meshContext, corners[0]);
                SetVertex(meshContext, corners[2]);
                SetVertex(meshContext, corners[1]);
                SetVertex(meshContext, corners[2]);
                SetVertex(meshContext, corners[0]);
                SetVertex(meshContext, corners[3]);
            
                // Right side face
                SetNormal(meshContext, normals[2]);
                SetVertex(meshContext, corners[1]);
                SetVertex(meshContext, corners[2]);
                SetVertex(meshContext, corners[6]);
                SetVertex(meshContext, corners[1]);
                SetVertex(meshContext, corners[6]);
                SetVertex(meshContext, corners[5]);
            
                // Top face
                SetNormal(meshContext, normals[4]);
                SetVertex(meshContext, corners[2]);
                SetVertex(meshContext, corners[7]);
                SetVertex(meshContext, corners[6]);
                SetVertex(meshContext, corners[7]);
                SetVertex(meshContext, corners[2]);
                SetVertex(meshContext, corners[3]);
            
                // Left side face
                SetNormal(meshContext, normals[3]);
                SetVertex(meshContext, corners[3]);
                SetVertex(meshContext, corners[0]);
                SetVertex(meshContext, corners[4]);
                SetVertex(meshContext, corners[3]);
                SetVertex(meshContext, corners[4]);
                SetVertex(meshContext, corners[7]);
            
                // Back face
                SetNormal(meshContext, normals[1]);
                SetVertex(meshContext, corners[7]);
                SetVertex(meshContext, corners[5]);
                SetVertex(meshContext, corners[6]);
                SetVertex(meshContext, corners[7]);
                SetVertex(meshContext, corners[4]);
                SetVertex(meshContext, corners[5]);
            
                // Bottom face 
                SetNormal(meshContext, normals[5]);
                SetVertex(meshContext, corners[0]);
                SetVertex(meshContext, corners[1]);
                SetVertex(meshContext, corners[5]);
                SetVertex(meshContext, corners[0]);
                SetVertex(meshContext, corners[5]);
                SetVertex(meshContext, corners[4]);
            }

            // Revert raster state
            VXccelEngine3D.PopRasterizerState();

            // Set the Rasterizer State for the cube mesh wireframe 
            VXccelEngine3D.PushRasterizerState(RasterizerStates.WireframeState.TSRRasterizerState);

            // Create a Line Context for a continuous line and draw the outline of the cube 
            var lineColor = Color.FromArgb(0xFF, _cubeColor.R, _cubeColor.G, _cubeColor.B);

            CreateSquare(2.0f, true, lineColor, new[] { corners[0], corners[1], corners[2], corners[3] });
            CreateSquare(2.0f, true, lineColor, new[] { corners[4], corners[5], corners[6], corners[7] });
            CreateSquare(2.0f, true, lineColor, new[] { corners[0], corners[4], corners[7], corners[3] });
            CreateSquare(2.0f, true, lineColor, new[] { corners[5], corners[1], corners[2], corners[6] });

            // Revert raster state
            VXccelEngine3D.PopRasterizerState();
        }

        private void CreateSquare(float lineThickness, bool isAntiAlias, Color lineColor, Vector3[] vertices)
        {
            using (var lineContext = BeginLineStrips(lineThickness, isAntiAlias))
            {
                lineContext.SetVertexColor(lineColor);

                // Pass Entity ID value for a hit test purpose
                ulong selectionColor = VXccelEngine3D.EncodeSelectionId(EntityId, 0);
                lineContext.SetSelectionId(selectionColor);

                foreach (var v in vertices)
                {
                    SetVertex(lineContext, v);
                }
                SetVertex(lineContext, vertices.First());
                lineContext.Freeze();
                lineContext.Draw();
            }
        }

        private void SetVertex(IImmediateLitMeshContext meshContext, Vector3 vector3)
        {
            meshContext.SetVertex3(vector3.X, vector3.Y, vector3.Z);
        }

        private void SetVertex(ILinesMesh linesContext, Vector3 vector3)
        {
            linesContext.SetVertex3(vector3.X, vector3.Y, vector3.Z);
        }

        private void SetNormal(IImmediateLitMeshContext meshContext, Vector3 vector3)
        {
            meshContext.Normal3(vector3.X, vector3.Y, vector3.Z);
        }
    }
}
TextSceneEntity.cs
View source code
// *************************************************************************************
// SCICHART® Copyright SciChart Ltd. 2011-2025. All rights reserved.
//  
// Web: http://www.scichart.com
//   Support: support@scichart.com
//   Sales:   sales@scichart.com
// 
// TextSceneEntity.cs is part of the SCICHART® Examples. Permission is hereby granted
// to modify, create derivative works, distribute and publish any part of this source
// code whether for commercial, private or personal use. 
// 
// The SCICHART® examples are distributed in the hope that they will be useful, but
// without any warranty. It is provided "AS IS" without warranty of any kind, either
// expressed or implied. 
// *************************************************************************************
using System;
using System.ComponentModel;
using System.Windows.Media;
using SciChart.Charting3D;
using SciChart.Charting3D.Interop;
using SciChart.Charting3D.Primitives;
using SciChart.Charting3D.Visuals.Primitives;
using SciChart.Core.Extensions;

namespace SciChart.Examples.Examples.Charts3D.Customize3DChart.AddGeometry3D
{
    /// <summary>
    /// A class to demonstrate a 3D Text Elements added to the SciChart3D Scene. Created using our BaseSceneEntity and Font3D APIs
    /// </summary>
    public class TextSceneEntity : BaseSceneEntity<SCRTSceneEntity>
    {
        private readonly Color _textColor;
        private readonly int _fontSize;
        private readonly string _fontFamily;
        private Font3D _font;

        public string Text { get; set; }

        [TypeConverter(typeof(StringToVector3TypeConverter))]
        public Vector3 Location { get; set; }

        static TextSceneEntity()
        {
            SCRTDllLoader.InitNativeLibs();
        }

        /// <summary>
        /// Creates a representation of a Text Label in the 3D space, defined in world coordinates
        /// </summary>
        /// <param name="location">Point in the 3D space that determines the top-left corner of a label</param>
        public TextSceneEntity(string text, Color textColor, Vector3 location,
            int fontSize = 8, string fontFamily = "Arial")
            : base(new SCRTSceneEntity())
        {
            Text = text;
            _textColor = textColor;
            Location = location;
            _fontSize = fontSize;
            _fontFamily = fontFamily;

            // Set requires SelectionId to False to exclude this item from selection, tooltips and also 
            // prevent issues with maximum number of selectable meshes 
            RequiresSelectionId = false;
        }

        /// <summary>
        /// Called when the 3D Engine wishes to render this element. This is where geometry must be drawn to the 3D scene
        /// </summary>
        /// <param name="rpi">The <see cref="IRenderPassInfo3D" /> containing parameters for the current render pass.</param>
        public override void RenderScene(IRenderPassInfo3D rpi)
        {
            var currentCamera = RootSceneEntity?.Viewport3D.CameraController;
            if (currentCamera == null || Location == null) return;

            // Display billboarded text using camera vectors                
            Vector3 cameraFwd = currentCamera.Forward;
            Vector3 cameraUp = currentCamera.Up;

            // Compute a side vector
            Vector3 cameraSide = cameraFwd ^ cameraUp;
            cameraSide.Normalize();

            // Compute orthogonal up vector
            cameraUp = cameraSide ^ cameraFwd;
            cameraUp.Normalize();

            // Display text billboarded using camera side, up vectors (text will always face camera) 
            _font.BeginBillboard(cameraSide, cameraUp);
            _font.AddText(Text, _textColor, Location.X, Location.Y, Location.Z);
            _font.End();
        }

        /// <summary>
        /// Called when the 3D Engine wishes to update the geometry in this element. This is where we need to cache geometry before draw.
        /// </summary>
        /// <param name="rpi">The <see cref="IRenderPassInfo3D" /> containing parameters for the current render pass.</param>
        public override void UpdateScene(IRenderPassInfo3D rpi)
        {
            _font ??= new Font3D(_fontFamily, (uint)_fontSize);
        }

        /// <summary>
        /// Called when the D3DEngine Restarts. Meshes and DirectX related objects should be recreated
        /// </summary>
        public override void OnEngineRestart()
        {
            DisposeFont();

            base.OnEngineRestart();
        }

        private void DisposeFont()
        {
            _font.SafeDispose();
            _font = null;
        }

        public override eSCRTSceneEntityKind GetKind()
        {
            return eSCRTSceneEntityKind.SCRT_SCENE_ENTITY_KIND_HUD3D;
        }

        /// <summary>
        /// Releases unmanaged and - optionally - managed resources.
        /// </summary>
        protected override void Dispose(bool disposing)
        {
            if (disposing)
            {
                DisposeFont();
            }

            base.Dispose(disposing);
        }
    }
}
Back to WPF Chart Examples