Custom ChartModifiers - Part 7 – Select and Drag a Point on a 3D Chart

Created by Lex Smith, Modified on Fri, 14 Mar at 4:23 PM by Lex Smith

Overview


In this tutorial, we will create a custom ChartModifier3D in SciChart that enables users to drag a point along the Y-axis in 3D space in real-time. This modifier, called DragPointYAxisModifier3D, allows users to interactively select and move a data point with the mouse along the Y-axis. It provides callbacks that allow handlers to update the underlying DataSeries3D with the modified value.


This guide will walk you through the concept, implementation, and usage of this feature.



How Does It Work?


  • When the user clicks on a point, the modifier performs a hit test on the 3D scene to detect the vertex and the corresponding Scene3D entity.
  • As the dragging operation continues, a drag delta is calculated based on the mouse cursor movement.
  • The delta is then converted from screen coordinates to data coordinates using the Coordinate Transformation API.
  • A new Y value is computed, and the underlying DataSeries3D is updated, triggering a redraw of the 3D scene.
  • When the user releases the mouse, the drag operation ends and the final Y position is set in the DataSeries3D.


Implementation Details


The modifier extends ChartModifier3DBase to handle mouse events for selection and dragging.


Handling Mouse Down (Dragging Starts)


To begin with the dragging process, override the OnModifierMouseDown(ModifierMouseArgs) method. This method is triggered whenever the SciChartSurface3D receives a MouseDown event.


Inside this method:


  • The hit vertex is detected using the Viewport3D hit-test API by calling:

                Viewport3D.PickScene(e.MousePoint)

  • Once the hit vertex is identified, the associated 3D scene entity is determined.
  • The selected vertex is highlighted, and the PointDragStart event is triggered.


        public override void OnModifierMouseDown(ModifierMouseArgs e)
        {
            base.OnModifierMouseDown(e);

            if (!IsDragging && MatchesExecuteOn(e.MouseButtons, ExecuteOn) && MatchesExecuteWhen(e.Modifier))
            {
                CaptureMouse();

                IsDragging = true;
                StartPoint = e.MousePoint;

                if (Viewport3D?.RootEntity == null) return;

                var entityVertexId = Viewport3D.PickScene(e.MousePoint);
                if (entityVertexId.HasValue)
                {
                    _entityVertexId = entityVertexId.Value;

                    Viewport3D.RootEntity.VisitEntities(entity =>
                    {
                        if (entity is Points3DSceneEntity pointsEntity && entity.EntityId == _entityVertexId.EntityId)
                        {
                            entity.PerformSelection(true, new List<VertexId>
                            {
                                new VertexId { Id = _entityVertexId.VertexId }
                            });

                            _pointsEntity = pointsEntity;

                            PointDragStart?.Invoke(this, new DragPointEventArgs
                            {
                                PointIndex = (int)_entityVertexId.VertexId - 1,
                                YValue = GetPointVector(_entityVertexId).Y
                            });
                        }
                    });
                }
            }
        }


Handling Mouse Move (Dragging Continues)


To handle the continuation of dragging, override the OnModifierMouseMove(ModifierMouseArgs) method, which is called on every MouseMove event.


Inside this method:



        public override void OnModifierMouseMove(ModifierMouseArgs e)
        {
            base.OnModifierMouseMove(e);

            if (IsDragging && ParentSurface?.YAxis != null && _pointsEntity != null)
            {
                var pointVector = GetPointVector(_entityVertexId);
                var coordVector = GetCoordVector(pointVector);
                var dragDelta = GetYDragDelta(StartPoint, e.MousePoint, coordVector);

                if (!dragDelta.IsNaN())
                {
                    PointDragDelta?.Invoke(this, new DragPointEventArgs
                    {
                        PointIndex = (int)_entityVertexId.VertexId - 1,
                        YValue = ParentSurface.YAxis.GetDataValue(coordVector.y + dragDelta)
                    });

                    StartPoint = e.MousePoint;
                }
            }
        }

Handling Mouse Up (Dragging Ends)


To finalize the dragging operation, override the OnModifierMouseUp(ModifierMouseArgs) method, which is called when the MouseUp event occurs.


Inside this method:


  • The selected point is deselected.
  • The mouse capture is released.
  • The PointDragEnd event is triggered.


        public override void OnModifierMouseUp(ModifierMouseArgs e)
        {
            base.OnModifierMouseUp(e);

            _pointsEntity = null;
            Viewport3D.RootEntity.VisitEntities(entity => entity.PerformSelection(false, null));

            IsDragging = false;
            ReleaseMouseCapture();

            PointDragEnd?.Invoke(this, new DragPointEventArgs
            {
                PointIndex = (int)_entityVertexId.VertexId - 1,
                YValue = GetPointVector(_entityVertexId).Y
            });
        }


Updating Underlying DataSeries3D


The DragPointYAxisModifier3D does not directly modify the DataSeries3D. Instead, it triggers the following events:


  • PointDragStart (when dragging begins)
  • PointDragDelta (while dragging is in progress)
  • PointDragEnd (when dragging stops)


Event handlers are responsible for modifying the corresponding data point in the DataSeries3D. The EventArgs contain hit-test information, which allows finding the affected data point by index:


        private void OnPointDragDelta(object sender, DragPointEventArgs e)
        {
            if (e.PointIndex < xyzDataSeries.Count)
            {
                var y = (double)e.YValue;

                var x = xyzDataSeries.XValues[e.PointIndex];
                var z = xyzDataSeries.ZValues[e.PointIndex];
                var w = xyzDataSeries.WValues[e.PointIndex];

                var xIndex = xSteppings.FindIndex<double>(true, x, SearchMode.Exact);
                var zIndex = zSteppings.FindIndex<double>(true, z, SearchMode.Exact);

                meshDataSeries[zIndex, xIndex] = y;
                xyzDataSeries.Update(e.PointIndex, x, y, z, w);

                UpdatePointInfo(e.PointIndex, x, y, z);
            }
        }

The redraw of the 3D scene is triggered implicitly inside the xyzDataSeries.Update(e.PointIndex, x, y, z, w) call. However, if needed, a redraw can be forced by calling:

            InvalidateParentSurface(RangeMode.None)

on any DataSeries3D.


What else could you do?


While this tutorial demonstrates how to select and drag points, it only scratches the surface of what can be done with the ChartModifier3D API. You could extend this implementation to allow:


  • Dragging points in multiple directions (X, Y, and Z axes)
  • Real-time data constraints and snapping behavior
  • Moving data points with the Keyboard


If you have any ideas on improving this modifier or would like to see it as a part of the core SciChart library, feel free to share your thoughts!


Try It Yourself: Custom ChartModifier Sandbox


An example application demonstrating DragPointYAxisModifier3D is available in our open-source Sandbox at the link below:

 

SciChart 3D Sandbox - Custom ChartModifiers



Was this article helpful?

That’s Great!

Thank you for your feedback

Sorry! We couldn't be helpful

Thank you for your feedback

Let us know how can we improve this article!

Select at least one of the reasons
CAPTCHA verification is required.

Feedback sent

We appreciate your effort and will try to fix the article