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:
- The displacement along the Y-axis is computed
- The new Y value is calculated using the Camera3D API and Coordinate Transformation API
- The PointDragDelta event is triggered to reflect the change
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:
Was this article helpful?
That’s Great!
Thank you for your feedback
Sorry! We couldn't be helpful
Thank you for your feedback
Feedback sent
We appreciate your effort and will try to fix the article