Search code examples
wpfc#-4.0dynamic-data-display

Dynamic data display smooth scrolling of X axis


I'm trying to create a real time chart using Dynamic data display plotting DateTime vs Integer values. What I need is a smooth scrolling of X-axis with fixed axis values. Just like panning, ie when you drag the plot from center, it flows smoothly with mouse cursor. I would like to have a similar behavior automatically.

Currently when changing the data source in a timer, the axis range changes dynamically and scrolling is not smooth. Is it possible to change this behavior. Any idea, please help.

I have tried to update the date and integer datasource dynamically in a 400 ms timer, which results in a real time graph with axis range changes based on datasource.

Here is the testing code:

On Window Load

 timer = new Timer(400);
 ds = new ObservableDataSource<VoltagePoint>();
 ds.SetXMapping(x => dateAxis.ConvertToDouble(x.Date));
 ds.SetYMapping(y => y.Voltage);
 plotter.AddLineGraph(ds, 2, "Sample");
 timer.Start();

On Timer tick

 Random rnd = new Random();
 TestPoint pt = new TestPoint (rnd.Next(1, 500), DateTime.Now);
 ds.AppendAsync(plotter.Viewport.Dispatcher, pt);

Solution

  • I believe you're after something like this:

    // Your code (unchanged)
    timer = new Timer(400);
    ds = new ObservableDataSource<VoltagePoint>();
    ds.SetXMapping(x => dateAxis.ConvertToDouble(x.Date));
    ds.SetYMapping(y => y.Voltage);
    plotter.AddLineGraph(ds, 2, "Sample");
    timer.Start();
    
    // Make the Viewport not move when items are added.
    plotter.Viewport.AutoFitToView = false;
    
    // Put in your initial viewing dimensions
    var xMin = dateAxis.ConvertToDouble(DateTime.Now);
    var startXMax = dateAxis.ConvertToDouble(DateTime.Now.AddMinutes(1));
    var startYMin = -20;
    var startYMax = 520;
    
    // Set the initial visible area.
    plotter.Visible = new Rect { X = xMin, Width = startXMax - xMin, Y = startYMin, Height = startYMax - startYMin };
    
    // If you wish, you can also restrict where the user can scroll:
    plotter.Viewport.Restrictions.Add(new CustomAxisRestriction(xMin));
    

    Where the restriction is another way of controlling what the user sees, a very basic example is below:

        public class CustomAxisRestriction : ViewportRestrictionBase
        {
            private double xMin;
            public CustomAxisRestriction(double xMin)
            {
                this.xMin = xMin;
            }
            public override Rect Apply(Rect oldDataRect, Rect newDataRect, Viewport2D viewport)
            {
                newDataRect.X = Math.Max(newDataRect.X, xMin);
                return newDataRect;
            }
        }
    

    Basically, all you need in a restriction is an override of the Apply method, with the above signature.

    In your case, you might also want to add restrictions on newDataRect.Y and newDataRect.Height, if you wish to constrain them in -20 <-> 520 (or however), but I'll leave that up to you - the basic idea is above.

    Hope this helps! Let me know if any of the above doesn't make sense :).

    A (not necessarily fantastic) method for smooth scrolling:

    Add another timer, eg, on initializing:

            animationTimer = new DispatcherTimer { Interval = TimeSpan.FromMilliseconds(20) };
            animationTimer.Tick += animationTimer_Tick;
            animationTimer.Start();
    

    I use a DispatcherTimer instead of a Timer because the moving of the ViewPort needs to be done on the UI Thread. Then just have:

        private void animationTimer_Tick(object sender, EventArgs e)
        {
            var oldRect = plotter.Visible;
            oldRect.X = Math.Max(oldRect.X, dateAxis.ConvertToDouble(DateTime.Now.AddMinutes(-0.1)));
            plotter.Visible = oldRect;
        }
    

    Of course, you need to think carefully from a UX perspective on how / whether the user should be able to interrupt this scrolling, and re-enable it. But I'll leave that down to you..!

    But all the code I've given above should work (or at least was working locally!) Let me know if you have any problems with it.

    I did have a few problems with the axis behaving oddly during animation, I think this is basically a bit of a bug with the axis TicksProvider, so if necessary, you might need to implement your own: dateAxis.TicksProvider = new CustomTicksProvider();, with CustomTicksProvider inheriting from TimeTicksProviderBase<DateTime>, but that should be relatively straightforward to implement with a google search.

    Good luck!