Dzielimy się z Wami bezpłatnie wiedzą programistyczną.
Jeżeli doceniacie naszą pracę możecie nas wesprzeć dobrowolną wpłatą:
Wykresy w WPF C# – OxyPlot
Nieodłącznym elementem pracy na danych liczbowych jest ich analiza. A jaka forma interpretacji danych mogłaby być przyjemniejsza dla oka niż wykres zależności? Dlatego wpis dotyczył będzie narzędzia pozwalającego rysować wykresy w WPF C# – OxyPlot ułatwiającego to zadanie.
W projektach, które wymagają wyświetlenia kilku punktów w prosty sposób, spokojnie można to zrealizować za pomocą własnych klas. Czasem jednak ilość danych wzrasta oraz komplikuje się sposób wyświetlania np. poprzez skalę logarytmiczną, możliwość przybliżania fragmentu wykresu. W takich sytuacjach zaprogramowanie takiego narzędzia może zająć znaczną część czasu przeznaczonego na pierwotny projekt. Warto wtedy skorzystać z rozbudowanych narzędzi, które na pewno spełnią nasze wymagania.
Na rynku znajduje się wiele narzędzi wspomagających wyświetlanie wykresów np. XPlot, Chart, ZedGraph. Większość narzędzi napisana jest pod WinForms i tam znakomicie się spełniają. Co w sytuacji, gdy projekt od początku do końca projektowany jest w WPF? Można oczywiście połączyć projekt WPF z WinForms, ale istnieje narzędzie, które rozwiązuje ten problem.
OxyPlot jest biblioteką pozwalającą na bezpośrednią implementację w WPF, Windows 8, Windows Phone, Windows Phone Silverlight, Windows Forms, Silverlight, GTK#, Xwt, Xamarin.iOS, Xamarin.Android, Xamarin.Forms and Xamarin.Mac. Udostępniona jest na licencji MIT, która pozwala na swobodne korzystanie, dopóki w programach pochodnych zamieszczana jest treść licencji.
Spis treści
Od czego zacząć
Pracę z OxyPlot należy rozpocząć od dodania bibliotek do własnego projektu WPF. Dostępne są trzy sposoby dodania odpowiednich referencji:
1. Najłatwiejszym sposobem na to jest skorzystanie z nuget. Po uruchomieniu Package Manager Console należy wpisać:
- Install-Package Oxyplot.Core
- Install-Package Oxyplot.Wpf
2. Drugim sposobem jest wyszukanie bibliotek Oxyplot.Core oraz Oxyplot.Wpf poprzez interfejs nuget – Manage Nuget Packages w sposób zaprezentowany poniżej:
3. Trzeci sposób jest dla tych, którym nie działają poprzednie sposoby.
Z tego sposobu należy skorzystać jeżeli podczas instalacji wystąpił problem, nie działają pewne funkcje OxyPlot lub z jakiegoś powodu nie chcesz korzystać z nuget.
UWAGA
Zarówno poniższy opis, jak i przykłady podawane przez producenta odnoszą się do wersji 2013.1.38.1, niestety wersja obecnie znajdująca się w nuget to 2014.1.546 i może powodować to błędy. Jednym z nich jest:
The name „plot” does not exist in the namespace „http://oxyplot.codeplex.com”
Rozwiązaniem tego problemu jest dodanie referencji do starszej wersji plików, które można pobrać stąd: OxyPlot
Pobrany plik należy rozpakować, a następnie dodać referencję do nich w sposób przedstawiony poniżej.
1. W Solution Explorer należy znaleźć referencję, kliknąć prawym przyciskiem myszy i wybrać Add Reference.
2. W nowo otwartym oknie należy kliknąć Browse i znaleźć pobrane pliki, następnie kliknąć Add.
3. Ostatecznie należy sprawdzić czy są zaznaczone na liście i kliknąć OK.
UWAGA
Dodanie dowolnego pliku przez nuget spowoduje aktualizację plików do nowszej wersji. W przypadku występowania wymienionego wcześniej błędu, pojawi się on ponownie.
Deklaracja kontrolki wykresu
Kontrolkę należy zadeklarować w kodzie XAML projektu. W nagłówku Window należy dopisać linię:
xmlns:oxy="http://oxyplot.codeplex.com"
Następnie w miejscu, w którym ma się pojawić wykres wpisać kod:
<oxy:Plot x:Name="Plot1" Title="A Graph" Model="{Binding PlotModel}" Margin="10" Grid.Row="1"> </oxy:Plot>
Parametrem wartym wyjaśnienia jest tu Model, do którego został ustawiony Binding. Jest to parametr, pozwalający na ustawienie wyglądu, przedziału, kolorów, serii danych itd.
OxyPlot pomimo funkcjonowania w WPF i bindowanego Model’u nie wspiera w pełni modelu MVVM (Model View ViewModel). Oznacza to, że wyświetlenie zmian na wykresie wymaga uaktualnienia danych w obiekcie PlotModel w code-behind.
Implementacja obiektu PlotModel wykresu
Teraz, gdy w projekcie znajduje się już kontrolka, w której wyświetlany będzie wykres, należy zająć się jej wyglądem.
W modelu został ustawiony Binding, a więc konieczna jest deklaracja klasy z polem, którego zmiana będzie wywoływała reakcję poprzez akcesory. Taka klasa musi dziedziczyć z INotifyPropertyChanged i nadpisywać metodę OnPropertyChanged:
public class OxyPlotModel : INotifyPropertyChanged
{
private OxyPlot.PlotModel plotModel;
public OxyPlot.PlotModel PlotModel
{
get
{
return plotModel;
}
set
{
plotModel = value;
OnPropertyChanged("PlotModel");
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(string name)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(name));
}
}
}
Obiekt tak zdefiniowanej klasy należy podpiąć pod interfejs z kontrolką wykresu.
//Deklaracja obiektu
private OxyPlotModel oxyPlotModel;
//W konstruktorze
oxyPlotModel = new OxyPlotModel();
this.DataContext = oxyPlotModel; // To pozwala połączyć kontrolki z polami klasy OxyPlotModel
Ustawianie parametrów wykresu
Teraz gdy wszystko jest już połączone ze sobą, pozostaje ustawienie odpowiednich parametrów polu PlotModel. W tym celu klasa OxyPlotModel zostanie rozbudowana. Sposób uzupełniania tych parametrów jest dowolny, ale w poniższym przykładzie zostało to rozdzielone na dwa etapy. Parametry, które się nie zmieniają uzupełniane są na początku, a te zależne od wykresu dopisywane są przed jego wyświetleniem. Stałymi parametrami są tu tylko te dotyczące legendy wykresu:
private void SetUpLegend()
{
plotModel.LegendTitle = "Legenda";
plotModel.LegendOrientation = OxyPlot.LegendOrientation.Horizontal;
//Orientacja pozioma
plotModel.LegendPlacement = OxyPlot.LegendPlacement.Outside; //Poza planszą wykresu
plotModel.LegendPosition = OxyPlot.LegendPosition.TopRight; //Pozycja: góra, prawo
plotModel.LegendBackground = OxyPlot.OxyColor.FromAColor(200, OxyPlot.OxyColors.White);//Tło białe
plotModel.LegendBorder = OxyPlot.OxyColors.Black; //Ramka okna czarna
}
Nie zalecam wstępnej deklaracji osi wykresu, ponieważ mogą się wartości liczbowe z deklaracji i z samych danych wejściowych na siebie nachodzić i spowoduje to wizualny bałagan.
Drugą metodą klasy, wywoływaną zawsze gdy należy zmienić zawartość wykresu. Najprostszym przykładem pozwalającym narysować kosinusoidę jest metoda:
public void PodajDaneDoWykresu()
{
this.MyModel = new PlotModel { Title = "Przykład 1" };
this.MyModel.Series.Add(new FunctionSeries(Math.Cos, 0, 10, 0.1, "cos(x)"));
}
W przypadku wykresu na podstawie własnych punktów X i Y, ta sama klasa wyglądałaby tak:
public IList<OxyPlot.DataPoint> Points { get; private set; }
public void PodajDaneDoWykresu()
{
this.plotModel.Title = "Przykład 2";
this.Points = new List<OxyPlot.DataPoint>
{
new OxyPlot.DataPoint(5, 3),
new OxyPlot.DataPoint(15, 17),
new OxyPlot.DataPoint(25, 12),
new OxyPlot.DataPoint(35, 4),
new OxyPlot.DataPoint(45, 15),
new OxyPlot.DataPoint(55, 10)
};
}
lub:
OxyPlot.Series.LineSeries punktySerii;
public void PodajDaneDoWykresu()
{
this.plotModel.Title = "Przykład 3";
punktySerii.Points.Add(new OxyPlot.DataPoint(5, 3));
punktySerii.Points.Add(new OxyPlot.DataPoint(15, 17));
punktySerii.Points.Add(new OxyPlot.DataPoint(25, 12));
punktySerii.Points.Add(new OxyPlot.DataPoint(35, 4));
punktySerii.Points.Add(new OxyPlot.DataPoint(45, 15));
punktySerii.Points.Add(new OxyPlot.DataPoint(55, 10));
plotModel.Series.Add(punktySerii);
}
Dodatkowo w tej metodzie można uzupełnić informację dotyczące parametrów osi wykresu takie jak liniowe/logarytmiczne, zakres danych, wartość minimalna/maksymalna:
var xAxis = new OxyPlot.Axes.LinearAxis(OxyPlot.Axes.AxisPosition.Bottom, "a - offered traffic [Erl]")
{
MajorGridlineStyle = OxyPlot.LineStyle.Solid,
MinorGridlineStyle = OxyPlot.LineStyle.Dot
};
plotModel.Axes.Add(xAxis); var yAxis = new OxyPlot.Axes.LogarithmicAxis(OxyPlot.Axes.AxisPosition.Left, "Ei")
{
MajorGridlineStyle = OxyPlot.LineStyle.Solid,
MinorGridlineStyle = OxyPlot.LineStyle.Dot
};
plotModel.Axes.Add(yAxis);
Jeżeli serii danych jest więcej to warto by wyraźnie się różniły od siebie, dlatego należy zdefiniować parametry serii:
//deklaracja poza metodą
OxyPlot.Series.LineSeries punktySerii;
//implementacja w metodzie
punktySerii = new OxyPlot.Series.LineSeries
{
MarkerType = OxyPlot.MarkerType.Plus, //oznaczenie punktów
MarkerSize = 4, //wielkość punktów
MarkerStroke = OxyPlot.OxyColors.Tomato, //Kolor linii wykresu
Title = "Seria danych" //tytuł serii
};
Zbierając wszystkie omówione informacje, poniżej został zaprezentowany kod dla serii danych, podanych jako listy w parametrze:
public void PodajDaneDoWykresu(List<double> X, List<double> Y)//Lista X i Y podana jako parametr metody
{
this.PlotModel = new OxyPlot.PlotModel();
//Usunięcie ustawionych parametrów z poprzedniego uruchomienia metody
plotModel.Series = new System.Collections.ObjectModel.Collection<OxyPlot.Series.Series> { };
plotModel.Axes = new System.Collections.ObjectModel.Collection<OxyPlot.Axes.Axis> { };
//Graficzne ustawienia wykresów
punktySerii = new OxyPlot.Series.LineSeries
{
MarkerType = ksztaltPunktowWykresu[0], //oznaczenie punktów - definicja poniżej
MarkerSize = 4, //wielkość punktów
MarkerStroke = koloryWykresow[0], //Kolor linii wykresu - definicja poniżej
Title = "Seria nr: " + (1).ToString() //tytuł serii
};
//Uzupełnianie danych
for (int n = 0; n < X.Count; n++)
punktySerii.Points.Add(new OxyPlot.DataPoint(X[n], Y[n]));//dodanie wszystkich serii do wykresu
plotModel.Series.Add(punktySerii);
//Opis i parametry osi wykresu
var xAxis = new OxyPlot.Axes.LinearAxis(OxyPlot.Axes.AxisPosition.Bottom, "a - offered traffic [Erl]")
{
MajorGridlineStyle = OxyPlot.LineStyle.Solid,
MinorGridlineStyle = OxyPlot.LineStyle.Dot
};
plotModel.Axes.Add(xAxis);
var yAxis = new OxyPlot.Axes.LogarithmicAxis(OxyPlot.Axes.AxisPosition.Left, "Ei")
{
MajorGridlineStyle = OxyPlot.LineStyle.Solid,
MinorGridlineStyle = OxyPlot.LineStyle.Dot
};
plotModel.Axes.Add(yAxis);
}
W powyższym kodzie dodawane serie korzystały z kolejnych wartości tablic ksztaltPunktowWykresu i koloryWykresow. Są to listy z wypisanymi wartościami tych parametrów, po to by można było korzystać z nich w pętli. Przykładowa implementacja takich tablic znajduje się poniżej:
//Wypisane po to, by zmieniać kolor i kształt wraz z numerem klasy
private readonly List<OxyPlot.OxyColor> koloryWykresow = new List<OxyPlot.OxyColor>
{
OxyPlot.OxyColors.Green,
OxyPlot.OxyColors.IndianRed,
OxyPlot.OxyColors.Coral,
OxyPlot.OxyColors.Chartreuse,
OxyPlot.OxyColors.Peru
};
private readonly List<OxyPlot.MarkerType> ksztaltPunktowWykresu = new List<OxyPlot.MarkerType>
{
OxyPlot.MarkerType.Plus,
OxyPlot.MarkerType.Star,
OxyPlot.MarkerType.Cross,
OxyPlot.MarkerType.Custom,
OxyPlot.MarkerType.Square
};
Zapis wykresu do pliku
Gdy już się tak napracowaliśmy by wyświetlić wykres, to dobrze byłoby zachować go w formie pliku, do późniejszego odtworzenia. OxyPlot umożliwia skorzystania ze skrótu klawiszowego ctrl+c, by zapisać jego obraz w pamięci tymczasowej, następnie wystarczy go wkleić w dowolnym edytorze graficznym.
Wygodniejszą opcją jest jednak skorzystanie z metody, która pozwala na taki zapis do pliku:
OxyPlot.Wpf.PngExporter.Export(PlotModel, filename, 1600, 1600, OxyPlot.OxyColors.White, 96);
Parametry metody to kolejno:
- Obiekt z modelem wykresu;
- Nazwa pliku, wraz z lokalizacją i rozszerzeniem;
- Szerokość w pikselach;
- Wysokość w pikselach;
- Kolor tła wykresu;
- Liczba pikseli przypadająca na cal długości;