Wpf, Scrolling Graph and Performance

Started by
7 comments, last by Dzogchener 14 years ago
Hi- I posted the following message to the .Net forum, but I am also interested in how this could be done differently, possibly agnostic to wpf per se. From a graphics programming perspective how could this be achieved and not cost the earth in cpu: Thanks for any advice: Hi, I am looking for some design/graphics advice. I have built a little graph in Wpf, which is a descendant of Canvas. The graphs core design is that it scrolls from right to left, exactly like the CPU performance graph does in the Windows task manager. I achieved this by running through the data points, drawing from left to right. Each time graph is updated I essentially move one data point in, and draw a little from the left of the previous. What this essentially means is that when I have enough data points to fill the actual graph visually, I run from the left position to the right position, pulling out the data points as I go. I have to do this for every update. So if the graph is visually 600 pixels wide, and the refresh rate is every 1 second, that involves drawing each line from left to right, every single time. I am wondering if there is a smarter approach to this type of problem. I knocked up the logic for this as it stands today, so I expect I am doing this completely wrong. This approach seems to be burning CPU time like nobodies business. Any advice? Thanks, D
Advertisement
I don´t know a thing about wpf, but my method (only theory) to achieve what you want would be like this:

Suppose you have an offscreen buffer with n-1 points already drawn and you want to draw the nth point, moving everything 1 pixel to the left:
Draw/blit the offscreen buffer in your canvas, but moved 1 pixel to the left, then draw the nth pixel in its position, finally grab the current canvas content as your new buffer, repeat everything the next time you want to refresh the graph. This way you only have to worry about the last point.

NOTE: i don´t know if this is possible to do in wpf, no idea if it allows you to have buffers or stuff like that. But i hope you get the idea.
Hi-

That does make total sense to me - many years ago I did Masm+watcom c graphics programming in mode-x (320x240) on the PC. We used to write offline to a 64k buffer and bitblit onto the vgascreen when the crt beam was on its way back to the top (cant remember the term for that). So I was thinking about that approach, perhaps drawing to an offline buffer and then somehow just pasting in the OnRender - I guess its hard for you to know what this is given you dont know wpf.

One wrinkle is the window has some transparency, which might not be a major issue. Possibly more of an issue is that I have a rendered grid, and then the lines are drawn onto it. Today on each call to render, I draw the grid, then draw each data series onto the canvas. If I were to look into double-buffering in the theme of above, I think I would need to have one bitmap for the grid, one for the data series, and then xor them onto the same canvas, or something.

Thanks for the feedback/food for thought.

D
I would do it a bit differently. I made something similar in WPF: a tickertape.

Here is what I would do:
Create two canvases, both equally sized as the graph.
Place one canvas in view and the second one to the right of the first one but make sure it is clipped off.
Start drawing on the first canvas (the one in view) from left to right.
When you hit the final column of the first canvas:
*- move BOTH canvases 1 unit to the left
- draw in the first colum of the second canvas
Keep on moving both canvases to the left until the first canvas is completely out of sight then:
- clear it and move it to the right of the second canvas; the fist canvas now becomes the second one.
repeat from *

this allows 'animation' without creating loads of objects.

[Edited by - ernow on April 22, 2010 1:01:54 PM]
Hi ernow-

Thats a very interesting idea - I would need to do it from right to left rather than the inverse, but I think it would be possible. I havent played with clipping, so will take a look at that approach. you wouldnt happen to know of any examples of this technique online would you?

Thanks alot!

D.
I'll see if I can make a little example. I'll post here again within 24 hrs.
Thanks ernow. I am in th emean time doing some research on clipping in wpf....
I quickly made this. With a little adjustment you can make it into a control (and aware of resizing and such)
The magic is in the PlotValue method. That is where I adjust the canvases and when needed swap them.
I colored the canvases differently so you can see what is going on. To see it even better you could switch off clipping (ClipToBounds on the Grid to False.

XAML:
<Window x:Class="Client.Window1"    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"    Title="Window1" Height="300" Width="300" Loaded="Window_Loaded">    <Grid>        <Grid x:Name="_chart" Width="200" Height="150" ClipToBounds="True">            <Canvas x:Name="_canvas1" Background="LightBlue"/>            <Canvas x:Name="_canvas2" Background="LightCoral"/>        </Grid>    </Grid></Window>


Code behind:
public partial class Window1 : Window{    private Timer _timer;    private Random _randomizer;    private double _pointSize = 3;    private Canvas _leftCanvas;    private Canvas _rightCanvas;    private double _currentColumn;    public Window1()    {        InitializeComponent();    }    private void Window_Loaded(object sender, RoutedEventArgs e)    {        _leftCanvas = _canvas1;        _rightCanvas = _canvas2;        _leftCanvas.RenderTransform = new TranslateTransform(0, 0);        _rightCanvas.RenderTransform = new TranslateTransform(_leftCanvas.ActualWidth, 0);                _randomizer = new Random();        _timer = new Timer(20);        _timer.Elapsed += new ElapsedEventHandler(Timer_Elapsed);        _timer.Start();    }    void Timer_Elapsed(object sender, ElapsedEventArgs e)    {        double value = GenerateNewChartValue();        Dispatcher.Invoke(new Action<double>(PlotValue), new object[]{value});    }    private double GenerateNewChartValue()    {        return _randomizer.NextDouble() * _chart.ActualHeight;    }    private void PlotValue(double value)    {        //reached the right handside of the righthand canvas so swap canvases        if (_currentColumn >= _rightCanvas.ActualWidth)        {            _currentColumn = _currentColumn - _rightCanvas.ActualWidth;            Canvas temp = _leftCanvas;            _leftCanvas = _rightCanvas;            _rightCanvas = temp;            _leftCanvas.RenderTransform = new TranslateTransform(0, 0);            _rightCanvas.Children.Clear();            _rightCanvas.RenderTransform = new TranslateTransform(_leftCanvas.ActualWidth, 0);        }        Ellipse point = new Ellipse { Width = _pointSize, Height = _pointSize, Fill = Brushes.Blue };        Canvas.SetTop(point, value);        Canvas.SetLeft(point, _currentColumn);        _rightCanvas.Children.Add(point);        _currentColumn += _pointSize;        ((TranslateTransform)_leftCanvas.RenderTransform).X -= _pointSize;        ((TranslateTransform)_rightCanvas.RenderTransform).X -= _pointSize;    }}
Thank you very much for this example. It is major food for thought, thank you.

D

This topic is closed to new replies.

Advertisement