1

I'm developing a WPF app with C#, .NET Framework 4.7. and Oxyplot 1.0.

I'm trying to update the graphic at runtime, but it doesn't do anything.

I have tried to use ObsevableCollection and InvalidateFlag but without success.

This is the XAML:

<oxy:Plot Title="{Binding Title}" InvalidateFlag="{Binding InvalidateFlag}">
    <oxy:Plot.Series>
        <oxy:LineSeries ItemsSource="{Binding BestFitness}"/>
        <oxy:LineSeries ItemsSource="{Binding WorstFitness}"/>
        <oxy:LineSeries ItemsSource="{Binding AverageFitness}"/>
    </oxy:Plot.Series>
</oxy:Plot>

And this is the view model:

public class MainViewModel : ObservableObject
{
    private int count;
    private int _invalidateFlag;

    public string Title { get; set; }

    public int InvalidateFlag
    {
        get { return _invalidateFlag; }
        set
        {
            _invalidateFlag = value;
            RaisePropertyChangedEvent("InvalidateFlag");
        }
    }

    public ObservableCollection<DataPoint> BestFitness { get; set; }
    public ObservableCollection<DataPoint> WorstFitness { get; set; }
    public ObservableCollection<DataPoint> AverageFitness { get; set; }

    public ICommand StartCommand
    {
        get { return new DelegateCommand(Start); }
    }

    public ICommand RefereshCommand
    {
        get { return new DelegateCommand(Refresh); }
    }

    public MainViewModel()
    {
        this.Title = "Example 2";
        this.BestFitness = new ObservableCollection<DataPoint>
        {
            new DataPoint(0, 4),
            new DataPoint(10, 13),
            new DataPoint(20, 15),
            new DataPoint(30, 16),
            new DataPoint(40, 12),
            new DataPoint(50, 12)
        };
    }

    private void Start()
    {
        Random rnd = new Random((int)DateTime.Now.Ticks);

        Program program = new Program(rnd);

        program.Algorithm.EvolutionEnded += Algorithm_EvolutionEnded;

        count = 0;
        this.BestFitness = new ObservableCollection<DataPoint>();
        this.WorstFitness = new ObservableCollection<DataPoint>();
        this.AverageFitness = new ObservableCollection<DataPoint>();

        Task.Run(() => program.Run(null));
    }

    private void Refresh()
    {
        this.BestFitness.Clear();
    }

    private void Algorithm_EvolutionEnded(object sender, EventArgs e)
    {
        EvolutionEndedEventArgs args = (EvolutionEndedEventArgs)e;

        BestFitness.Add(new DataPoint(count, args.BestFitness));
        WorstFitness.Add(new DataPoint(count, args.WorstFitness));
        AverageFitness.Add(new DataPoint(count, args.AverageFitness));

        InvalidateFlag++;
    }
}

Do I need to do anything else?

VansFannel
  • 45,055
  • 107
  • 359
  • 626

2 Answers2

2

With

this.BestFitness = new ObservableCollection<DataPoint>();
...

you are replacing the complete ItemsSource of the plot. Since there is no notification of the view by calling RaisePropertyChangedEvent afterwards, the bound plot will not recognize the change and the plot will not update it's points.

There are two possible solutions:

1. Use the INotifyPropertychanged by calling RaisePropertyChangedEvent after replacing the collection. Therefore

public ObservableCollection<DataPoint> BestFitness { get; set; }

should be extended to

private ObservableCollection<DataPoint> _BestFitness;
public ObservableCollection<DataPoint> BestFintess
{
    get
    {
        return _BestFitness;
    }
    private set
    {
        _BestFitness = value;
        RaisePropertyChangedEvent(nameof(BestFintess));
    }
}

2. Don't replace the whole ObservableCollection. Simply clear the existing collections and use them again. This means use

this.BestFitniss.Clear();

instead of

this.BestFitness = new ObservableCollection<DataPoint>();

Both solutions notifying the view about changes and the plot will update it's points without using the InvalidateFlag.

Note that it is required to use the UI thread to change the items of an ObservableCollection as described in this question. Since you are using an other thread to add values invoking the UI like

Application.Current.Dispatcher.BeginInvoke(() =>
    {
        BestFitness.Add(new DataPoint(count, args.BestFitness));
    });

is required.

Fruchtzwerg
  • 10,999
  • 12
  • 40
  • 49
  • I've done what you said, and now I get the exception `This type of CollectionView does not support changes to the SourceCollection of a subprocess other than the Dispatcher thread.` when I do `BestFitness.Add(new DataPoint(count, args.BestFitness));`. – VansFannel Jun 02 '18 at 07:15
  • This is because you are changing the `ObservableCollection` out of a non UI thread. Check out [this question](https://stackoverflow.com/questions/2091988/how-do-i-update-an-observablecollection-via-a-worker-thread) and invoke your UI thread like `Application.Current.Dispatcher.BeginInvoke(() => { BestFitness.Add(new DataPoint(count, args.BestFitness)); });` – Fruchtzwerg Jun 02 '18 at 07:48
  • I am doing this, but sometimes I got exception `This PlotModel is already in use by some other PlotView control.` from OxyPlot: `UIItems.Clear(); DataLoaderService.OnDataLoaded += (index, uiItem) => { Dispatcher.BeginInvoke(new Action(() => { UIItems.Insert(index, uiItem); })); }; // Load data` – Krusty Aug 02 '19 at 09:38
  • Yeah, I know that error, but I am wondering why it is thrown in my case (very similar to the one provided into the solution answer) and at the second iteration rather than the first – Krusty Aug 02 '19 at 10:34
0

I have created this small example to show how to update the graphic at runtime. I hope it helps!

ViewModel:

using System;
using System.Timers;
using OxyPlot;
using OxyPlot.Series;

namespace WpfApp1
{
    public class MainViewModel
    {
        private LineSeries lineSeries;
        private int count;

        public MainViewModel()
        {
            this.MyModel = new PlotModel { Title = "Example 1" };
            //this.MyModel.Series.Add(new FunctionSeries(Math.Cos, 0, 10, 0.1, "cos(x)"));
            //this.MyModel.Series.Add(new FunctionSeries(Math.Sin, 0, 10, 0.1, "sin(x)"));

            lineSeries = new LineSeries();
            lineSeries.LineStyle = LineStyle.Solid;
            lineSeries.StrokeThickness = 2.0;
            lineSeries.Color = OxyColor.FromRgb(0, 0, 0);

            this.MyModel.Series.Add(lineSeries);

            Timer timer = new Timer(1000);
            timer.Elapsed += Timer_Elapsed;
            timer.Start();

            count = 0;
        }

        private void Timer_Elapsed(object sender, ElapsedEventArgs e)
        {
            lineSeries.Points.Add(new DataPoint(count, Math.Pow(count, 2)));
            this.MyModel.InvalidatePlot(true);

            count++;
        }

        public PlotModel MyModel { get; private set; }
    }
}

XAML:

<Window
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:WpfApp1"
        xmlns:oxy="http://oxyplot.org/wpf" x:Class="WpfApp1.MainWindow"
        mc:Ignorable="d"
        Title="MainWindow" Height="450" Width="800">
    <Window.DataContext>
        <local:MainViewModel/>
    </Window.DataContext>
    <Grid>
        <oxy:PlotView Model="{Binding MyModel}" />
    </Grid>
</Window>
VansFannel
  • 45,055
  • 107
  • 359
  • 626