How-To: Charts mit WPF und Silverlight

Bei WPF bzw. Silverlight gibt es grundsätzlich die gleiche „Problematik“ wie bei Windows Forms bis .Net 4.0: Diagramme gehören einfach nicht zum Lieferumfang des Frameworks. Deshalb muss man in der Regel zu Dritt-Herstellern greifen, bindet die Charts von Windows Forms ein oder sucht sich kostenlose Alternativen.

In diesem Artikel soll es zunächst um die letztere Fassung gehen wobei in dem Fall zwischen Dynamic Data Display und dem WPF- bzw. Silverlight Toolkit gewählt werden kann. Nachfolgend werden die beiden Fassungen aus den Toolkits näher beschrieben.

Grundlagen

Nach der Installation des Toolkits finden sich diverse Dlls auf der Festplatte, von denen insbesondere die System.Windows.Controls.DataVisualization.Toolkit.dll für uns von Interesse ist. Nachdem diese vom entsprechenden Projekt referenziert wurde, müssen wir zunächst noch den dazugehörigen Namensraum im XAML hinzufügen.

<xmlns:c="clr-namespace:System.Windows.Controls.DataVisualization.Charting;assembly=System.Windows.Controls.DataVisualization.Toolkit" />

Ab jetzt steht uns das Chart Control zu Verfügung, welches grundsätzlich noch nicht viel macht, denn sein einziger Zweck ist es, den Bereich vorzugeben in dem die Charts gezeichnet werden. Diese wiederum werden durch Serien bestimmt von denen es unter WPF 7 verschiedene gibt, die in Silverlight noch durch einige Sonderformen erweitert werden. Welche das sind kann man sich in der Kategorie „Data Visualization“ im „Showroom“ des Toolkits ansehen.

Die Serien werden dem Chart-Objekt über die Series Collection hinzugefügt. Dabei ist jeder Eintrag durch einen unabhängigen (IndependentValue) und einen abhängigen (DependentValue) Wert gekennzeichnet. In einem Säulendiagramm werden dabei bspw. der unabhängige Wert auf der X- und der abhängige auf der Y-Achse eingetragen. Bei einem Kreisdiagramm hingegen bestimmt der unabhängige Wert die Beschriftung in der Legende und der abhängige die Größe des entsprechenden Kreisanteils.

Die tatsächliche Datenquelle wird als ItemsSource angegeben, welche Objekte mit den entsprechenden Daten enthält. Die jeweiligen Properties können dann über die Eigenschaften DependentValueBinding und IndependentValueBinding gebunden werden.

<c:Chart Title="Chart Title" LegendTitle="Legend">
 <c:Chart.Series>
   <c:ColumnSeries Title="Series Title"
       DependentValueBinding="{Binding DependentValue}"
       IndependentValueBinding="{Binding IndependentValue}"
       ItemsSource="{Binding Data}" />
   <c:PieSeries DependentValueBinding="{Binding DependentValue}"
       IndependentValueBinding="{Binding IndependentValue}"
       ItemsSource="{Binding Data}" />
 </c:Chart.Series>
</c:Chart>

DataBinding etwas einfacher

Hier liegt auch ein kleines Problem, denn in aller Regel zeigen Diagramme zuvor berechnete Wertepaare an. Der Aufbau dieser Datenquelle ist also meist gleich, eine entsprechende Klassenkombination die man gerade bei MVVM gut gebrauchen könnte gibt es so aber nicht.

Die einfachste Lösung dies zu umgehen ist die Nutzung der ObservableCollection die mit entsprechenden KeyValuePairs gefüllt wird oder eine Liste einer LINQ-Query.

Ganz gelungen finde ich das jedoch nicht, weshalb ich mir nachfolgende zwei Klassen geschrieben habe die sich meiner Meinung nach etwas eingängiger nutzen lassen und mögliche Missverständnisse im Vorfeld vermeiden sollten.

Die ChartingCollection stellt eine erweiterte ObservableCollection dar. Mit ihr kann ein Wertepaar typsicher und einfach in das Diagramm eingefügt und zur Laufzeit verändert werden.

public class ChartingCollection<TIndepend, TDepend>
       : ObservableCollection<Chartset<TIndepend, TDepend>>
{
   public void Add(TIndepend independentValue, TDepend dependentValue)
   {
      Add(new ChartSet(independentValue, dependentValue));
   }
}

Die Daten selbst sind in ChartSets zusammengefasst, welche sich im Grunde wie KeyValuePairs verhalten.

public class ChartSet<TIndepend, TDepend>
{
   public ChartSet(TIndepend independentValue, TDepend dependentValue)
   {
       DependentValue = dependentValue;
       IndependentValue = independentValue;
   }

   public ChartSet()
   {

   }

   public TDepend DependentValue { get; set; }

   public TIndepend IndependentValue { get; set; }
}

Im ViewModel können die Daten dann beispielsweise folgendermaßen über eine Property bereit gestellt werden.


// year is independent, amount is dependent
public ChartingCollection<string, int> Data { get; set; }

Daraus ergibt sich im Xaml folgende Schreibweise.

<c:ColumnSeries DependentValuePath="DependentValue"
       IndependentValuePath="IndependentValue"
       ItemsSource="{Binding Data}" />

Z-Ebene, Animationen und anderes

Neben all diesem hat das Control noch ein paar kleinere erwähnenswerte Eigenschaften. So ist es, wie im Beispiel gezeigt, möglich, Serien innerhalb eines Charts zu mischen. Hierbei ergibt sich eine künstliche Z-Ebene aus der Reihenfolge der Serien im Xaml. Demnach wird die zuerst geschriebene Serie als erstes gezeichnet und dann von der zweiten entsprechend verdeckt. „Echtes“ 3D ist damit jedoch nicht möglich, da keine Perspektivänderungen verfügbar sind.

Eine weitere nette Sache ergibt sich über die Property AnimationSequence des Serien-Objekts. Mit ihr wird das Zeichnen der Charts auf drei verschiedene Arten ermöglicht, wobei die Letztere den Standardfall darstellt:

  • FirstToLast – Beim einblenden von Werten wird erst der ersten, dann der zweite usw. angezeigt. Beim Ausblenden wird genauso verfahren.
  • LastToFirst – Verhält sich beim Einblenden wie FirstToLast, beim Ausblenden jedoch genau umgekehrt
  • Simultaneous – Alle Werte werden zeitgleich eingeblendet und ausgeblendet

Install Microsoft Silverlight


So gut dies aussieht, so schade ist es, dass es zumindest bei WPF mit den ersten beiden Möglichkeiten ein Bug zu geben scheint. Ändern sich nämlich die Daten während der Animation, bleibt diese unter Umständen stehen und wird nicht zu Ende geführt. Und das selbst dann wenn die Daten sich erneut ändern.

Die Schattenseiten

Damit wären wir bei den Schattenseiten angekommen. An sich ist das Chart-Control zwar sehr einfach zu bedienen, auf der anderen Seite aber leider auch etwas beschränkt im Umfang. Letztendlich handelt es sich zumindest bei der WPF-Version eher um eine Art Preview-Version, die seit Anfang 2010 nicht mehr aktualisiert wurde weshalb ich es dort auch nicht unbedingt für kommerzielle Anwendungen nutzen würde. Bei Silverlight hingegen habe ich diese bedenken nicht, da sie dort Teil des 5er Toolkits waren und sogar etwas umfangreicher als in WPF sind.

<c:Chart>
  <c:Chart.LegendStyle>
   <Style TargetType="v:Legend">
     <Setter Property="Width" Value="0" />
     <Setter Property="Height" Value="0" />
   </Style>
 </c:Chart.LegendStyle>
  ...
</c:Chart>

Weiterhin stört mich, dass zum Beispiel die Visibility-Eigenschaft der Legende nicht funktioniert, wodurch man jene nur unsichtbar machen kann in dem man die Größe per Style ändert. Weiterhin gibt es kein Zoom, es ist nicht möglich Labels einzuzeichnen, Trendlinien sucht man vergebens und was mich wirklich etwas gestört hat, ist die Abhängigkeit des Diagrammtyps vom Serientyp.

Möchte man also dem Nutzer bspw. die Möglichkeit geben zwischen einem Säulen und einem Kreisdiagramm umzuschalten, so muss man beide Serien in das Chart einfügen und je nach Auswahl das eine oder andere unsichtbar schalten. Hier wäre es schön wenn man den Diagrammtyp per Property steuern könnte.

Insofern diese Sachen nicht weh tun, sollte man sich die Charts aber auf alle Fälle einmal ansehen, denn letztendlich tun sie das was sie tun sollen, sind kostenlos und sehen dabei gut aus 🙂