Realtime graph for WPF

You know the Task Manager’s CPU Usage History graph that shows CPU utilization over time? Try that type of the graph in WPF and you’ll realize that it isn’t as easy as it should be due to the WPF’s performance for real time graphs. Which sucks due to the way WPF works. Even with 3rd party you’ll have hard time to find a graph fast enough to cope with even more than few hundred samples.

Hence I’ve decide to build my own real time graph. It is based on Direct2D because Direct2D is much faster when it comes to presenting the graphical result. However, merging Direct2D into WPF is, again, not an easy task as it should be (hint to MS – there should be native WPF support for hosting Direct2D output). Anyway, I found the article Using Direct2D with WPF on CodePlex. It comes with sources which I use. Those sources require Windows API CodePack(WACP), a bug ridden set of managed API’s to various, otherwise unsupported, features (MS went with this approach (if it works, it works, if it doesn’t fix it by yourself) instead of providing an official support). The WACP binaries I provide are compiled from 1.1. sources with some bug fixed regarding DirectCompute.

Here is a snapshot from a graph showing a sinusoide as a product of many samples (the attached example contains required code and binaries).

realtimechart

I utilize a combination of Direct2D and samples optimization which yields smooth result even with million and more samples.

 

Required code

Create a class that implements IGraphItem interface. This class will hold a single sample through Time and Value property. Time typically represents a time elapsed since the start and it is expressed in millisecond while Value is a double and holds whatever value.

public class RealtimeGraphItem : IGraphItem
{    
    public int Time { get; set; }
    public double Value { get; set; }
}

Configure RealtimeGraphControl like

xmlns:rg="clr-namespace:Righthand.RealtimeGraph;assembly=Righthand.RealtimeGraph"
...
<rg:RealtimeGraphControl x:Name="Graph" AxisColor="Blue" AxisWidth="1"      VerticalLinesInterval="5" VerticalLabelsStep="2"  SpanX="100"
      MaxY="250" MinY="50"  HorizontalLinesInterval="10"/>

where SpanX is time span expressed in seconds telling the graph how many seconds are shown.

Finally create a BindingList<RealtimeGraphItem> source and bind it to Graph.SeriesSource property (BindingList is required as it has the event ListChanged that is used to trigger rendering update of the graph). And that’s it. When you add new items to the source the graph will automatically reflects the new state.

 

Requirements

.net 4.0

Dowload

Attached are binaries and the sources for the Example project.

Let me know what you think.

RealtimeGraph_1_0.zip (1.31 mb)

Comments (20) -

  • m0sa

    10.1.2012 11:35:46 | Reply

    Wow, great! Beats the s**t out of DynamicDataDisplay! There is some flickering though, and you should really use INotifyCollectionChanged instead of IBindingList. See stackoverflow.com/.../difference-between-observablecollection-and-bindinglist

    • Miha Markic

      10.1.2012 11:49:23 | Reply

      Glad you like it. Well, yes, it is not a finished complete product yet.
      Thanks for the feedback, will definitely add support for INotifyCollectionChanged and check the flickering issue.

      • Richard

        13.5.2013 12:47:28 | Reply

        Do you have a goal or deadline for the product? Just curious Smile

        Richard
        my blog / psoriasiswarning.com

        • miha markic

          13.5.2013 16:03:44 | Reply

          Not at this time, sorry.

  • Antonio Morejon

    26.2.2012 22:37:31 | Reply

    It's look great solution for WPF drawing performance. We want to apply a similar solutions in a project we are development. Please, write us if you consider we could cooperate. Thank you

  • Eric Ouellet

    20.4.2012 21:18:33 | Reply

    I have 2 issues.

    First, when I try to see the xaml in design, I receive:


    System.InvalidCastException
    Specified cast is not valid.
       at Microsoft.WindowsAPICodePack.DirectX.Direct3D10.D3DDevice1.CreateDevice1(Adapter adapter, DriverType driverType, String softwareRasterizerLibrary, CreateDeviceOptions options, FeatureLevel hardwareLevel)
       at Direct2D.Scene.TryCreateDevice1(DriverType type)
       at Direct2D.Scene.CreateResources()
       at Direct2D.Direct2DControl.OnSceneChanged()
       at Direct2D.Direct2DControl.SceneChangedCallback(DependencyObject d, DependencyPropertyChangedEventArgs e)
       at System.Windows.DependencyObject.OnPropertyChanged(DependencyPropertyChangedEventArgs e)
       at System.Windows.FrameworkElement.OnPropertyChanged(DependencyPropertyChangedEventArgs e)
       at System.Windows.DependencyObject.NotifyPropertyChange(DependencyPropertyChangedEventArgs args)
       at System.Windows.DependencyObject.UpdateEffectiveValue(EntryIndex entryIndex, DependencyProperty dp, PropertyMetadata metadata, EffectiveValueEntry oldEntry, EffectiveValueEntry& newEntry, Boolean coerceWithDeferredReference, Boolean coerceWithCurrentValue, OperationType operationType)
       at System.Windows.DependencyObject.SetValueCommon(DependencyProperty dp, Object value, PropertyMetadata metadata, Boolean coerceWithDeferredReference, Boolean coerceWithCurrentValue, OperationType operationType, Boolean isInternal)
       at System.Windows.DependencyObject.SetValue(DependencyProperty dp, Object value)
       at Righthand.RealtimeGraph.RealtimeGraphControl.RealtimeGraph_Loaded(Object sender, RoutedEventArgs e)
       at System.Windows.RoutedEventHandlerInfo.InvokeHandler(Object target, RoutedEventArgs routedEventArgs)
       at System.Windows.EventRoute.InvokeHandlersImpl(Object source, RoutedEventArgs args, Boolean reRaised)
       at System.Windows.UIElement.RaiseEventImpl(DependencyObject sender, RoutedEventArgs args)
       at System.Windows.UIElement.RaiseEvent(RoutedEventArgs e)
       at System.Windows.BroadcastEventHelper.BroadcastEvent(DependencyObject root, RoutedEvent routedEvent)
       at System.Windows.BroadcastEventHelper.BroadcastLoadedEvent(Object root)
       at MS.Internal.LoadedOrUnloadedOperation.DoWork()
       at System.Windows.Media.MediaContext.FireLoadedPendingCallbacks()
       at System.Windows.Media.MediaContext.FireInvokeOnRenderCallbacks()
       at System.Windows.Media.MediaContext.RenderMessageHandlerCore(Object resizedCompositionTarget)
       at System.Windows.Media.MediaContext.AnimatedRenderMessageHandler(Object resizedCompositionTarget)
       at System.Windows.Threading.ExceptionWrapper.InternalRealCall(Delegate callback, Object args, Int32 numArgs)
       at MS.Internal.Threading.ExceptionFilterHelper.TryCatchWhen(Object source, Delegate method, Object args, Int32 numArgs, Delegate catchHandler)



    Second:
    When I execute, I receive: InvalidOperation Exception (Resize has not been called.) at line
    ==>   items.Add(newItem);  in timer_Tick function

    For me, it's not a real success.

    Good luck !
    Eric

    • Miha Markic

      21.4.2012 12:57:29 | Reply

      Hi Eric,

      What OS and graphic card are you using?
      FYI XP isn't supported while Vista requires a service pack or something (see Direct2D requirements).

  • Hari

    31.8.2012 5:32:20 | Reply

    Hi Miha, src link is broken. Can you fix it?

    • Miha Markic

      3.9.2012 10:04:01 | Reply

      Hm, looks like something happened during the transition from Blogengine 2.5 -> 2.6. Hopefully I'll fix it. Thanks for noticing

    • Miha Markic

      3.9.2012 11:02:35 | Reply

      It should be fixed now. Let me know if you still experience problems.

  • elwov

    8.9.2012 18:50:21 | Reply

    hello,  I am trying to find a DirectCompute solution in C#, simply to avoid the c++ syntax and pointers in general in favor of indexed arrays and numerical strings.
    - Could you calrify the status of .net with respect DX11 since DX9 no longer usable with DirectCompute.  
    -  the WACP 1.1 sdk has a .net wrapper, but the samples does build but has problems, like TestSimple in creating device in Main.  could you exaplin the origin if the this API and why it is not fully mamanged by MS to act as .net-DirectX outlet?
    -  is there rworked TestSimple fro current WACP version that actually compile and run on Window 7, and 1.1 WACP?
    thank you

  • Yazid

    1.12.2012 18:02:13 | Reply

    I am still getting rsize has not been called

    • Miha Markic

      2.12.2012 17:06:49 | Reply

      Care to share more about the problem?

  • ted

    1.3.2013 8:10:08 | Reply

    thank you such nice article,

    is there any way to hide vertical labels?

    • Miha Markic

      2.3.2013 18:54:02 | Reply

      Out of my head - no. I might add this feature though.

  • JP Talusan

    23.3.2013 12:19:36 | Reply

    Hi,

    I have 2 questions.
    1) When I look at the XAML i receive this error:
    "Error  1  The name "RealtimeGraphControl" does not exist in the namespace "clr-namespace:Righthand.RealtimeGraph;assembly=Righthand.RealtimeGraph"."

    But the solution still compiles correctly. I just cannot view the design.

    2) Do you have any updated code which removes the flickering?

    Thank you very much.

    • Miha Markic

      28.3.2013 16:45:17 | Reply

      Hi,

      What VS version are you using? About flickering: sorry, didn't have time.

  • Chris

    2.1.2014 18:38:20 | Reply

    Hey, this is pretty cool. I'd quite like to make use of it in one of my projects, but I'm having some trouble including it in a XAML template. I've included it inside a Grid, like so:

    <rg:RealtimeGraphControl SeriesSource="{Binding PercentageGraphSource}" Grid.Row="3" Grid.ColumnSpan="2" AxisColor="Black" AxisWidth="1" VerticalLinesInterval="10" VerticalLabelsStep="2" SpanX="100" MaxY="100" MinY="0" HorizontalLinesInterval="25"/>

    This is part of a DataTemplate, templating my views automatically, which is the main difference I can see compared to the example app included with the download.

    The graph displays, and the binding to the data also appears to work (I can see it animating new points), but none of the options seem to be picked up, e.g. the line intervals don't get set, if I change the MaxY/MinY it doesn't alter the apperance of the graph, changing the axis colour to black doesn't work.

    The points being graphed appear to go off the scale (which I set to 100 above). I hard coded the Y-value for each data point to 25 to make sure I wasn't putting the wrong numbers in but it's still off the scale!

    Any ideas why the properties I've set on the RealtimeGraphControl in my data template might not be respecting what I set? I've made the same changes to the control in the example app in this post and it appears to work fine.

    Cheers,
    Chris

  • Alex Su

    21.8.2014 11:45:46 | Reply

    Hi guys, for people who has got error "The name "RealtimeGraphControl" does not exist in the namespace "clr-namespace:Righthand.RealtimeGraph;assembly=Righthand.RealtimeGraph".".

    Please find Righthand.RealtimeGraph dll, right click it, select properties, under general tab, select unblock link.

  • Stephane

    24.1.2018 17:20:38 | Reply

    Hi Miha,
    i just test your code and it works really well. i'll be very thankfull if i can have the binaries in orther to adapt it to my personals uses.
    Thanks for the help

Loading