VansFannel
VansFannel

Reputation: 45931

OxyPlot graphic doesn't update

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?

Upvotes: 1

Views: 1693

Answers (2)

VansFannel
VansFannel

Reputation: 45931

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>

Upvotes: 0

Fruchtzwerg
Fruchtzwerg

Reputation: 11399

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.

Upvotes: 2

Related Questions