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).
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.
Wow, great! Beats the s**t out of DynamicDataDisplay! There is some flickering though, and you should really use INotifyCollectionChanged instead of IBindingList. See http://stackoverflow.com/questions/4284663/difference-between-observablecollection-and-bindinglist
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.
Do you have a goal or deadline for the product? Just curious 🙂
Richard
my blog / psoriasiswarning.com
Not at this time, sorry.
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
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
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).
Hi Miha, src link is broken. Can you fix it?
Hm, looks like something happened during the transition from Blogengine 2.5 -> 2.6. Hopefully I'll fix it. Thanks for noticing
It should be fixed now. Let me know if you still experience problems.
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
I am still getting rsize has not been called
Care to share more about the problem?
thank you such nice article,
is there any way to hide vertical labels?
Out of my head – no. I might add this feature though.
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.
Hi,
What VS version are you using? About flickering: sorry, didn't have time.
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
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.
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