0

I'm creating a real-time WPF graphical plotter that will plot data points as it is received. It utilizes the Dynamic Data Display library. (http://dynamicdatadisplay.codeplex.com/)

Currently, the Application is set up so that it:

  • Updates the graphical plotter every time there is a change to the ObservableCollection that I have instantiated inside the WPF Application.

  • Adds data points using a custom method called AddDataPoint(...) which modifies the ObservableCollection.

The application runs as expected within the environment (when I hit F5 to Debug my Solution), but that is only because I am passing in "fake" data points to test the application with. I utilize an internal DispatchTimer/Random.Next(..) to continually feed in generated data points to the internally instantiated ObservableCollection; however, I have no idea how to allow for an external class or external data source to feed in "real" data to be graphed.

I'm really new to WPF and C# in general, so while I did do a lot of Google-ing on the subject I couldn't find a concrete answer (i.e. Data Binding -- it seems to me that it is only for use within the Application as well). Im stuck when it comes to passing in real-time data from an external source to my WPF Application.

I tried adding the WPF's .exe file as a resource to the external class/solution that will provide the data and starting an instance of the WPF by using:

      "WPFAppNameSpace".MainWindow w = new "WPFAppNameSpace".MainWindow();  
      w.Show();
      w.AddDataPoint(...);

But, it didn't work. The Window does not even show up! Can I have an external class (not a WPF App) pass in data to my WPF Graphing Application? If so, how can I go about doing this and what should I research?

Here are some code snippets from my Project:
XAML

<Window x:Class="WpfApplication1.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d3="http://research.microsoft.com/DynamicDataDisplay/1.0"
        Title="MainWindow" Height="480" Width="660" Loaded="Window_Loaded" WindowState="Maximized">
    <Grid>     
        <d3:ChartPlotter Name="plotter" Margin="12,10,12,14">

            <d3:ChartPlotter.MainHorizontalAxis>
                <d3:HorizontalAxis Name="xAxis"></d3:HorizontalAxis>
            </d3:ChartPlotter.MainHorizontalAxis>

            <d3:ChartPlotter.MainVerticalAxis>
                <d3:VerticalAxis Name="yAxis"></d3:VerticalAxis>
            </d3:ChartPlotter.MainVerticalAxis>

            <d3:VerticalAxisTitle Content="Voltage"/>
            <d3:HorizontalAxisTitle Content="Test Identifier"/>
            <d3:Header TextBlock.FontSize="20" Content="Dynamically Updated Graph"/>

        </d3:ChartPlotter>

    </Grid>

</Window>

MainWindow.xaml.cs

namespace WpfApplication1
{
    /// <summary>
    /// Interaction logic for MainWindow.xaml
    /// </summary>
    public partial class MainWindow : Window
    {
        //Dummy Variables for Testing
        private readonly Random rand = new Random();
        private DispatcherTimer dt = new DispatcherTimer();

        ...

        //Data Sources for Graph
        private List<EnumerableDataSource<DataPoint>> enumSources;
        private List<ObservableCollection<DataPoint>> observableSources;
        private int dataSourceIndex = 0;

        public MainWindow()
        {
            InitializeComponent();
        }

        //Automatically Called after MainWindow() Finishes. Initlialize Graph.
        private void Window_Loaded(object sender, RoutedEventArgs e)
        {
            //Initlizes Data Sources for Graph
            enumSources = new List<EnumerableDataSource<DataPoint>>();
            observableSources = new List<ObservableCollection<DataPoint>>();

            //Adds a new Source to the Graph (New Line on Graph)
            addNewSource("Test Data " + dataSourceIndex);

            //TESTING PURPOSES
            dt.Interval = new TimeSpan(25000);
            dt.Tick += new EventHandler(timerAction);
            dt.IsEnabled = true;
            dt.Start();
        }

        //Adds data into the observableSource and alerts the enumSource to add point into Graph
        private void AsyncAppend(...) {...}

        //Adds a new DataPoint onto the Graph and Specifies if it starts a new Line.
        public void AddDataPoint(..., bool newLine) {...}

        //Tests Function for Adding New Points/New Lines to Graph; Called by Timer
        private void timerAction(object sender, EventArgs e)
        { 
            //current count of points in a particular line
            var count = observableSources[dataSourceIndex].Count;
            if (count < 100)
            {
                //Adds Data to Current Line
                AddDataPoint(...);
            }
            else
            {
                //Starts New Line and Adds Data
                AddDataPoint(..., true);
            }
        }

        //Adds a new Data Source onto the Graph (Starts a New Line)
        private void addNewSource(string legendKey){...}

        //DataPoint Object to Pass into the Graph
        private class DataPoint
        {
            //X-Coord of the Point
            public double xCoord { get; set; }

            //Y-Coord of the Point
            public double yCoord { get; set; }

            //DataPoint's Label Name
            public string labelName { get; set; }

            //Constructor for DataPoint
            public DataPoint(double x, double y, string label = "MISSNG LBL")
            {
                xCoord = x;
                yCoord = y;
                labelName = label;
            }
        }
}
Kevin Xie
  • 23
  • 1
  • 5
  • I had the same problem plotting real-time LIBOR rates. I solved it by using Reactive Extensions to wrap the data acquisition component, and that worked like a charm. It's what Rx was made for. If you didn't want to use Rx, you could probably accomplish the same thing via an internal publish/subscribe pattern. – Gayot Fow Jun 05 '13 at 19:11
  • Hi Garry, thanks for the response, could you elaborate a little bit more on how you managed to use Reactive Extensions to wrap the data acquisition component? I feel like there should be a simpler fix that I'm just not seeing. If possible could you post a snippet of how you got that to work? – Kevin Xie Jun 05 '13 at 20:31

1 Answers1

1

As an example of Reactive Extensions, here's a class that acquires data by simulation at a random interval between 0 and 5 seconds

public class DataAquisitionSimulator:IObservable<int>
{
    private static readonly Random RealTimeMarketData = new Random();
    public IDisposable Subscribe(IObserver<int> observer)
    {
        for (int i = 0; i < 10; i++)
        {
            int data = RealTimeMarketData.Next();
            observer.OnNext(data);
            Thread.Sleep(RealTimeMarketData.Next(5000));
        }
        observer.OnCompleted();
        return Disposable.Create(() => Console.WriteLine("cleaning up goes here"));
    }
}

It simulates acquiring market data (just random integers in this case) and posting them to an observer. It sleeps for a while between observations to simulate market latency.

Here's a skeletal class that's been set up as a consumer...

public class DataConsumer : IObserver<int>
{
    private readonly IDisposable _disposable;
    public DataConsumer(DataAquisitionSimulator das)
    {
        _disposable = das.Subscribe(this);
        _disposable.Dispose();
    }
    public void OnCompleted()
    {
        Console.WriteLine("all done");
    }
    public void OnError(Exception error)
    {
        throw error;
    }
    public void OnNext(int value)
    {
        Console.WriteLine("New data " + value + " at " + DateTime.Now.ToLongTimeString());
    }
}

It implements the three methods needed and note that the 'OnNext' gets called upon each occurrence of new data. Presumably this class would be implemented in your VM and immediately inserts the new data into the binding pipeline so that users could visualize it.

To see these two classes interacting, you can add this to a console app...

static void Main(string[] args)
{
    DataAquisitionSimulator v = new DataAquisitionSimulator();
    DataConsumer c = new DataConsumer(v);
}

Designing your threads is key, but otherwise this is a sample of how external data that has latency and irregular observations can be captured in a structured way.

Gayot Fow
  • 8,710
  • 1
  • 35
  • 48
  • 1
    Thanks Garry, it wasn't the exact solution I used, but it helped point me in the right direction. Thanks! Also I decided to consolidate all applications and classes into one single entity. – Kevin Xie Jun 06 '13 at 18:17
  • Glad it all worked out for you. Hopefully your integration project will incorporate some Rx that you got here! – Gayot Fow Jun 06 '13 at 19:50
  • Hi, I have to similar situation, but the data provider and consumer are separate WPF apps. Could you pls elaborate on how to make this communication work with RX. thanks – Jim Jul 15 '13 at 07:45
  • @Jim, that falls into a different realm that this question. Please open a new question – Gayot Fow Jul 15 '13 at 08:24
  • Thanks, I did that http://stackoverflow.com/questions/17650229/realtime-data-wpf-wcf-and-reactive-extensions – Jim Jul 15 '13 at 08:53