How to have a Fixed Scrolling Time-Range on the XAxis that works with Modifiers
Posted by Andrew BT on 11 February 2019 05:03 PM
|
|
The ProblemWe were recently asked the question how to have a Fixed-Size scrolling window, without using the FIFO Series (e.g. do not discard old data):
Since this is now an FAQ we thought we would create an article for you! The SolutionSciChart provides many ways to achieve this, but perhaps the simplest is to update the VisibleRange of the XAxis when you append data. For example: DateTime latestXValue = DataSeries.XValues.Last(); DateTime tenSecondsAgo = new DateTime(latestXValue.Ticks - TimeSpan.FromSeconds(10).Ticks); SciChartSurface.XAxis.VisibleRange = new DateTimeRange(tenSecondsAgo, latestXValue); Where to Apply the Solution?Where to do this? Well there are two ways.
Ok so what was that about the ViewportManager?There is a property called SciChartSurface.ViewportManager (expects IViewportManager) which is queried on render to get the X and Y axis VisibleRange. See below the implementation of DefaultViewportManager: using System.Diagnostics; /// <summary> /// The DefaultViewportManager performs a naive calculation for X and Y Axis VisibleRange. /// On each render of the parent SciChartSurface, either autorange to fit the data (depending on the Axis.AutoRange property value), /// or return the original axis range (no change) /// </summary> public class DefaultViewportManager : ViewportManagerBase { /// <summary> /// Called when the <see cref="IAxis.VisibleRange"/> changes for an axis. Override in derived types to get a notification of this occurring /// </summary> /// <param name="axis">The <see cref="IAxis"/>instance</param> public override void OnVisibleRangeChanged(IAxis axis) { } /// <summary> /// Called when the <see cref="ISciChartSurface" /> is rendered. /// </summary> /// <param name="sciChartSurface">The SciChartSurface instance</param> public override void OnParentSurfaceRendered(ISciChartSurface sciChartSurface) { } /// <summary> /// Overridden by derived types, called when the parent <see cref="SciChartSurface" /> requests the XAxis VisibleRange. /// The Range returned by this method will be applied to the chart on render /// </summary> /// <param name="xAxis">The XAxis</param> /// <returns> /// The new VisibleRange for the XAxis /// </returns> protected override IRange OnCalculateNewXRange(IAxis xAxis) { // Calculate the VisibleRange of X Axis, depending on AutoRange property if (xAxis.AutoRange) { var newXRange = xAxis.GetMaximumRange(); if (newXRange != null && newXRange.IsDefined) return newXRange; } return xAxis.VisibleRange; } /// <summary> /// Overridden by derived types, called when the parent <see cref="SciChartSurface" /> requests a YAxis VisibleRange. /// The Range returned by this method will be applied to the chart on render /// </summary> /// <param name="yAxis">The YAxis</param> /// <param name="renderPassInfo"></param> /// <returns> /// The new VisibleRange for the YAxis /// </returns> protected override IRange OnCalculateNewYRange(IAxis yAxis, RenderPassInfo renderPassInfo) { if (yAxis.AutoRange && renderPassInfo.PointSeries != null && renderPassInfo.RenderableSeries != null) { var newYRange = yAxis.CalculateYRange(renderPassInfo); if (newYRange != null && newYRange.IsDefined) { return newYRange; } } return yAxis.VisibleRange; } } If you create a class like the above and change the behaviour of OnCalculateNewYRange to return a VisibleRange with a fixed time window, then you you can effectively do anything with the behaviour of the chart. That's great! But now the Zoom and Pan Modifiers don't work!Well gosh don't you want everything? :P Just kidding. Yes, your custom ViewportManager will now override the zoom and pan modifiers applied to the chart. Mouse input will do nothing. How can we allow both? Well the Real Time Ticking Stock Charts example handles this use-case very well. Try it out before proceeding - How to Implement Scrolling Behaviour which is compatible with Zoom and Pan?Well, its really simple. The example has this one rule that does the scrolling only if the latest XValue is inside the viewport, otherwise, it allows the user to interact with the chart. /// INSIDE CreateRealtimeTickingStockChartsViewModel.OnNewPrice method IOhlcDataSeries<DateTime, double> dataSeries; // Already declared data series int latestXIndex = dataSeries.Count; // If the latest appending point is inside the viewport (i.e. not off the edge of the screen) // then scroll the viewport 1 bar, to keep the latest bar at the same place if (XVisibleRange.Max > latestXIndex) { var existingRange = _xVisibleRange; var newRange = new IndexRange(existingRange.Min + 1, existingRange.Max + 1); XVisibleRange = newRange; } You can try something like this in your ViewportManager.OnCalculateNewYRange() method, or, just in code or a ViewModel. It should work! For DateTimeAxis you will need to use actual date values not indices. Code to scroll a fixed-time window of 10 seconds would look like this: public void CallThisWhenYouAppendData() { IXyDataSeries<DateTime, double> dataSeries; // Already declared and filled data series DateTime latestXValue = dataSeries.XValues.Last(); // If the last point in the DataSeries is in the viewport if (SciChartSurface.XAxis.VisibleRange.Max > latestXValue) { // Scroll the viewport to show the last ten seconds, else allow user interaction DateTime latestXValue = DataSeries.XValues.Last(); DateTime tenSecondsAgo = new DateTIme(latestXValue.Ticks - TimeSpan.FromSeconds(10).Ticks); SciChartSurface.XAxis.VisibleRange = new DateTimeRange(tenSecondsAgo, latestXValue); } } Notes:
Further ReadingPlease find a Tutorial that goes into more details on this topic in our documentation Tutorial 06 - Adding Realtime Updates. For further info on various SciChart APIs please refer to the following documentation articles: Finally, the example can be found in SciChart Examples Suite at Create Stock Charts -> Realtime Ticking Stock Charts. Full source code of the example is available online at this link. | |
|
I am a total newcomer to SciChart and am evaluating this for integrationg into our product, and have the exact same requirement that is mentioned here by the user.
I'm not able to adapt your suggestions into my code properly. Could you post a link to a sample (with source code) for the above requirement?
It's very simple. After updating the data, you calculate the XAxis.VisibleRange you want to show, and show it.
If the user scrolls back in time, you stop updating the XAxis.VisibleRange.
Hope this helps!
Sometimes the operator wants to freeze the graph and inspect the data - zoom and pan.
The live data should continue to be added to the series, but should not be seen in the graph.
When the operator unfreezes the graph, he should see the latest values (added while freezed) and all new values will be added "live".
I have tried to achieve this. I can freeze the data by using SuspendUpdates. But then I can't zoom and pan. Do you know how this could be done?
If I don't freeze the graph, I just set the AutoRange to Never and it's possible to zoom and pan. But it's not possible when I use SuspendUpdates (of course).
Is there another way to "freeze" the update?
Is there another way to zoom and pan while SuspendUpdates is on?
Sometimes the operator wants to freeze the graph and inspect the data - zoom and pan.
The live data should continue to be added to the series, but should not be seen in the graph.
When the operator unfreezes the graph, he should see the latest values (added while freezed) and all new values will be added "live".
I have tried to achieve this. I can freeze the data by using SuspendUpdates. But then I can't zoom and pan. Do you know how this could be done?
If I don't freeze the graph, I just set the AutoRange to Never and it's possible to zoom and pan. But it's not possible when I use SuspendUpdates (of course).
Is there another way to "freeze" the update?
Is there another way to zoom and pan while SuspendUpdates is on?