Einen Großteil der letzten Jahre habe ich mit dem Erstellen von Reportinglösungen und demnach mit dem „Zeichnen“ von Diagrammen zugebracht. Mal wollte man die Geschwindigkeit eines Fahrzeugs live mitschneiden, mal die produzierten Endprodukte einer Fertigung in Qualitätsklassen eingeteilt sehen. Immer hat es mich genervt, dass ich selbst bei den kleinsten Aufgabenstellungen auf externe Tools wie ZedGraph (LGPL), TeeChart (rein kommerziell) o.ä. zurückgreifen musste, da das Framework keine eigene Bibliothek mitbrachte und somit zumindest eine gewisse Gefahr von Lizenzkonflikten bzw. zusätzlichen Kosten entstanden.
2008 hat sich dies zumindest ansatzweise geändert, da Microsoft MSChart bzw. die Microsoft Chart Controls für .Net Framework 3.5 veröffentlichte. Ein kleiner Nachteil an dieser Stelle war immer noch, dass man es quasi mit einem extra Paket zu tun hatte, denn um integraler Bestandteil von .Net 3.5 zu sein kam es „etwas“ spät. Jetzt mit .Net 4.0 ist dies aber Geschichte und alles wird gut, oder etwa nicht?
Vorbereitende Maßnahmen
Darf / kann man nicht mit .Net 4.0 arbeiten, muss man sich zunächst das Objekt der Begierde bei Microsoft herunter laden und installieren. Benutzt man Visual Studio 2008 wird es unter Umständen weiterhin notwendig jenes mit einem entsprechenden Add-On zu erweitern. Danach findet man in der Visual Studio Toolbox bei jedem WindowsForms oder ASP.Net Projekt einen neuen Eintrag Namens Chart.
Dieser repräsentiert – naheliegend - eine von Control abgeleitete Klasse welche die Darstellung der Diagramme übernimmt und aus dem System.Windows.Forms.DataVisualization.Charting Namespace stammt. Dieses Control ist dann einfach auf ein beliebiges Panel zu ziehen und nach Belieben mit Daten zu befüllen.
Die Akteure
Die Bibliothek umfasst eine ganze Reihe von Klassen, von denen im ersten Moment jedoch nur 4 von tatsächlichem Interesse sein sollen. Chart als grundlegendste ist schon bekannt. Dieses Control enthält 1 bis n ChartAreas, welche die Bereiche repräsentieren in welche die eigentlichen Diagramme gezeichnet werden. Die Graphen wiederum werden mit der Klasse Sieries repräsentiert, welche neben Farb- und Darstellungsdefinitionen auch die eigentlichen Daten in Form von DataPoints enthält.
Oder in anderen Worten:
- Chart – Basiskomponente zur Verwaltung von Diagrammen in WindowsForms
- ChartArea – Zeichnungsbereich eines Diagrams mit Achsenbeschriftung usw.
- Series – Definition einer Datenreihe inklusive Färbung und Darstellungstyp
- DataPoint – Repräsentation eines Datenwertepaares
Ganz richtig ist diese Aufzählung nicht, denn bei der Verknüpfung der einzelnen Bestandteile geht man nicht selten einen ungewöhnlichen Weg. So werden meist nicht Referenzen von einander abhängigen Bestandteilen getauscht, sondern ihre Namen. Den eigentlichen Zusammenbau übernimmt dann das Chart Control.
Erste Daten darstellen
Mit den Standardeinstellungen ist es sofort möglich Diagramme zu zeichnen. Dazu legt man ein neues Series Objekt an, dessen Points Property über Add die jeweiligen Werte hinzugefügt werden. Um eine Sinuskurve zu zeichnen, kann man sich beispielsweisen folgenden Codes bedienen.
// delete previouse data _chart.Series.Clear(); Series sinus = new Series("Sinus"); for (double i = 0; i <= 7.5; i += 0.2) sinus.Points.AddXY(i, Math.Sin(i)); // add new graph _chart.Series.Add(sinus);
Das Ergebnis:
Wie man sieht wird der Serienname automatisch in einer Legende verwendet und auch die Achseneinteilung skaliert von selbst. Unschön wirkt hingegen die Darstellung als Säulendiagram.
Darstellungsformen von Graphen
Die Darstellung einer Datenreihe wird von der Property ChartType der Klasse Series bestimmt und kann für verschiene Datenreihen innerhalb des gleichen Diagrams auch unterschiedlich gesetzt werden. Nachfolgend sieht man zum Beispiel zwei Serien mit den gleichen Daten. Wobei die erste als Spline und die zweite als Point gezeichnet werden.
for (int j = 0; j < 2; j++) { Series sinus = new Series("Sinus"+j); sinus.IsVisibleInLegend = false; if (j == 0) sinus.ChartType = SeriesChartType.Spline; else sinus.ChartType = SeriesChartType.Point; for (double i = 0; i <= 7.5; i += 0.2) sinus.Points.AddXY(i, Math.Sin(i)); _chart.Series.Add(sinus); }
An Darstellungen ist so ziemlich alles dabei was man sich wünschen kann. Von der einfachen Linie, über Kreisdiagramme bis hin zu exotischeren Dingen wie einer Pyramiden- oder Radarddarstellung. Mit Hilfe des Propertygrids von Visual Studio kann man sich diese ansehen in dem man den ChartType einer Serie ändert.
Nutzung unterschiedlicher ChartAreas
Möchte man die Datenreihen nicht geballt in einem Diagram anzeigen, kann man zusätzliche ChartAreas erstellen die diesen Diagrammen dann zugewiesen werden. Auch hier geschieht die Achseneinteilung für jedes Diagram separat und jede Serie wird, wenn gewünscht, in der einheitlichen Legende angezeigt.
Die Zuweisung der ChartArea zu einer Series geschieht dabei ungewöhnlicherweise über ihren Namen. Im Gegensatz zum folgenden Beispiel kann man eine Area auch erstellen indem man der Add-Methode der ChartAreas Property den entsprechenden Namen übergibt.
string sinusArea = "sinus chart"; ChartArea area = new ChartArea(sinusArea); _chart.ChartAreas.Add(area); ... series.ChartArea = sinusArea;
Verwenden von Titeln…
Den Titel erstellt man, wer hätte es gedacht, mit einer Klasse Namens Title. Dieser kann man den entsprechenden Text, Position (Docking), Schrift (Font) usw. zuordnen. Hat man dies getan, muss man sie der Titles-Collection des Chart Controls hinzufügen.
string sinusArea = "sinus chart"; ChartArea area = new ChartArea(sinusArea); _chart.ChartAreas.Add(area); ... series.ChartArea = sinusArea;
Um einen Titel in einer ChartArea zu benutzen, wird wieder von der Referenzierung über den Namen Gebrauch gemacht. Hierbei ist darauf zu achten, dass der Diagramtitel im Diagram enthalten ist. Um ihn außerhalb zu zeichnen bedarf es eines DockingOffsets. Da es keine Kollisionsabfrage zwischen den Bestandteilen gibt, kann es hier auch zu Überdeckungen kommen. Ein Problem das weiterhin durch die Tatsache verstärkt wird, dass jeder Diagramtyp andere Abmessungen hat und daher im Grunde ein anderes Offset für den Titel benötigt. Sprich, eine einfachen Lösung dürfte man hier lange suchen.
Title mainTitle = new Title("trigonometrical functions"); mainTitle.Docking = Docking.Bottom; mainTitle.Font = new Font(FontFamily.GenericMonospace, 12, FontStyle.Bold); _chart.Titles.Add(mainTitle);
… und Achsenbeschriftungen
Rein von den voran gegangenen Erkenntnissen, wäre zu erwarten gewesen, dass die Achsenbeschriftung auf eine ähnliche Art gesetzt wird wie bei den Diagramtiteln. Dies ist jedoch nicht der Fall. Bei Achsenbeschriftungen handelt es sich um einfache Strings die der jeweiligen Achse der ChartArea zugewiesen werden und eben jene Achse enthält die notwendigen Properties zur Formatierung.
ChartArea area = new ChartArea(dict.Key); _chart.ChartAreas.Add(area); Font font = new Font("Arial", 6); area.AxisX.Title = "X-Value"; area.AxisX.TitleFont = font; area.AxisX.TitleAlignment = StringAlignment.Far; area.AxisY.Title = "Y-Value"; area.AxisY.TitleFont = font;
Interessant an dieser Stelle ist die Nutzung der StringAlignment, welche bei Diagramtiteln nicht zur Verfügung stehen. Dort kann man nur zwischen Top, Bottom, Left und Right unterscheiden. Es ist aber unmöglich einen Diagramtitel am oberen linken Rand auszurichten.
Weiterhin wird es über die gleiche Eigenschaft der ChartArea möglich das Aussehen, die Einteilung und die Bezeichnung der Achsen steuern. Man kann also beispielweise Labels drehen und deren Werte mit Pre- oder Postfixen versehen.
// Achsenbeschriftung hat zwei Nachkommastellen und ist leicht gedreht area.AxisX.LabelStyle.Angle = 30; area.AxisX.LabelStyle.Interval = Math.PI / 4; area.AxisX.LabelStyle.Format = "{0:0.00}..."; // Hauptschritte in rot darstellen area.AxisX.MajorGrid.Interval = Math.PI; area.AxisX.MajorGrid.LineColor = Color.Red; // Zwischenschritte gepunktet darstellen area.AxisX.MinorGrid.Interval = Math.PI/4; area.AxisX.MinorGrid.Enabled = true; area.AxisX.MinorGrid.LineDashStyle = ChartDashStyle.Dot;
Somit ist es ohne Weiteres möglich alle Einstellungen, die automatisch anhand der Datenreihen vorgenommen wurden zu überschreiben und die Diagramme an eigene Anforderungen anzupassen.
Auf in die dritte Dimension
ChartAreas sind auch der Ansatzpunkt wenn man statt einer zwei dimensionalen Ansicht, eine drei dimensionale verwenden möchte. Hierzu bedient man sich der Porperty Area3DStyle. Mit dieser schaltet man über Enable3D zunächst das 3D Aussehen ein und kann es über weitere Eigenschaften drehen, kippen oder die Belichtung ändern.
ChartArea area = _chart.ChartAreas.Add("Rotation 45°"); area.Area3DStyle.Enable3D = true; area.Area3DStyle.Rotation = 45;
Interessant ist dies vor allem, wenn man jene Eigenschaften an Mausbewegungen koppelt damit der Nutzer selbst die Perspektive ändern kann. Eine dreidimensionale Darstellung von Daten in Form einer sichtbaren Z-Achse mit eigenen Werten gibt es jedoch nicht. Es ist demnach nicht möglich Flächen oder Körper zu zeichnen.
Das Spiel mit der Farbe
In Zeiten von WPF und Co. sehen die bisherigen Diagramme zugegebener Maßen nicht sehr überzeugend aus. Wie könnte man sie also etwas aufpeppen? Der einfachste Weg ist die Änderung der Hintergrundfarbe. So kann man neben dem eigentlichen Chart Control auch jede ChartArea, jede Legende und jeden Titel eigens einfärben. Zudem kann ein Farbverlauf über BackGradientStyle definiert werden und BackHatchStyle zeichnet ein vorgegebenes Muster durch welches eine Art Körnung entsteht.
foreach (var area in _chart.ChartAreas) { area.BackColor = Color.AliceBlue; area.BackHatchStyle = ChartHatchStyle.Wave; } foreach (var legend in _chart.Legends) { legend.BackColor = Color.Transparent; } _chart.BackGradientStyle = GradientStyle.TopBottom; _chart.BackColor = Color.Salmon;
Soll der Hintergrund eines Elements, beispielsweise der Legende, nicht sichtbar sein, überschreibt man dessen Hintergrundfarbe mit einer transparenten und freut sich über das Ergebnis.
Zusammenfassung
Ich denke mit dem hier gezeigten kann man gut in die Nutzung der Chart Controls starten. Ich bin mir sicher, dass ich in Zukunft auch noch weitere Artikel dazu schreiben werde, denn im Grunde behandelt dieser Artikel nur die absoluten Grundlagen. So fehlt beispielsweise noch das Zooming, Annotations oder die besonderen Diagramme wie der Poxplott oder das Radar. Viel Stoff also der beschrieben werden möchte.
Kommentare