Datenvisualisierung/Balken- und Kreisdiagramme

Aus SELFHTML-Wiki
Wechseln zu: Navigation, Suche

Im Selfhtml-aktuell-Bereich stellte Lutz Tautenhahn mit dem JavaScript Diagram Builder 2002 eine Bibliothek zur Verfügung, mit der man einfache Diagramme erstellen konnte.

SVG eignet sich für Diagramme, da die fertigen Grafiken im Gegensatz zu Rastergrafiken (jpg) oder canvas aus einzelnen Elementen bestehen, denen Sie zusätzliche, mit CSS formatierbare, Textinformationen hinzufügen können.

Dieses Kapitel zeigt, wie Diagramme mit wenigen Grundformen gezeichnet werden können. Am Ende des Kapitels entwerfen wir einen bequemen Diagramm-Generator, der aus Einzeldaten Balken- oder Kreisdiagramme erstellt, die Sie dann auf Ihren Webseiten verwenden können.

Balkendiagramm

Das Balkendiagramm ist einer der häufigsten Diagrammtypen, das durch nicht aneinandergrenzende Säulen (Rechtecke mit bedeutungsloser Breite) die Häufigkeitsverteilung einer Variablen veranschaulicht. Balkendiagramme mit horizontalen Balken eignen sich besonders für Rangfolgen.[1]

Diagramme aus auf der x-Achse senkrecht stehenden Rechtecken nennt man Säulendiagramm. Sie eignen sich besonders, um wenige Ausprägungen zu veranschaulichen. Bei mehr Kategorien leidet die Anschaulichkeit und es sind Liniendiagramme zu bevorzugen. Auch im Falle von metrisch stetigen Daten eignet sich das Balkendiagramm nicht, es ist ein Histogramm zu bevorzugen.


Balkendiagramm - SVG-Markup ansehen …
<svg class="chart" width="450" height="300" aria-labelledby="chartinfo" viewBox="0 0 450 300">
	<desc id="chartinfo">Ein Diagramm unserer Ernte</desc>

	<g class="bar purple" tabindex="0">
		<rect width="40" height="45" />
		<text x="55" y="40">4 Pflaumen</text>
	</g>
	<g class="bar green" tabindex="0">
		<rect width="80" height="45" y="50"/>
		<text x="95" y="90">8 Kiwis</text>
	</g>
	<g class="bar yellow" tabindex="0">
		<rect width="150" height="45" y="100" />
		<text x="160" y="140">15 Zitronen</text>
	</g>
	<g class="bar orange" tabindex="0">
		<rect width="160" height="45" y="150" />
		<text x="170" y="190">16 Orangen</text>
	</g>
	<g class="bar red" tabindex="0">
		<rect width="230" height="45" y="200" />
		<text x="245" y="240">23 Äpfel</text>
	</g>
</svg>

Für jeden Datensatz wird nun ein g-Element angelegt. Dies beinhaltet je ein …

  • rect-Element. (Wenn Sie ein title-Element als Tooltipp einbauen wollen, erhält das rect-Element Start-und End-Tag.)
  • text-Element, das eine Legende enthält, die auch vorgelesen wird. Der Text ist über das g-Element semantisch mit dem dem rechteckigen Grafik-Objekt verbunden.

Jedes rect und text-Element enthält mehrere XML-Attribute, die Breite und Höhe, aber auch die Position mithilfe der x- und y-Koordinaten festlegen. (In diesem Beispiel kann auf das x-Attribut verzichtet werden, da alle Balken links starten!)

Säulendiagramm

Ein Säulendiagramm ist eigentlich das Gleiche in grün - einziger Unterschied sind die vertauschten Werte für width und height des rect-Elements.

Zeit für einige grundsätzliche Gedanken:

  • das SVG-Markup für beide Diagramme ist eigentlich identisch.
  • das Aussehen kann über CSS und entsprechende Regelsätze für die Geometrieattribute gesetzt werden.
  • nur die Werte an sich, also die Breiten beim Balken-, bzw die Höhen beim Säulendiagramm unterscheiden sich.
Säulendiagramm - SVG-Markup ansehen …
  <g class="bar" tabindex="0" style="--value: 4; --color: var(--purple);">
    <rect />
    <text x="-25"  y="250">4 Pflaumen</text>
  </g>
  <g class="bar" tabindex="0" style="--value: 8;--color: var(--green);">
    <rect />
    <text x="50" y="200" >8 Kiwis</text>
  </g>
  <g class="bar" tabindex="0" style="--value: 15; --color: var(--yellow);">
    <rect />
    <text x="70"  y="140" >15 Zitronen</text>
  </g>

Gegenüber dem oberen Balkendiagramm fällt auf, dass unser Markup viel weniger XML-Attribute hat.

Wichtigste Neuerung ist ein style-Attribut, in dem nun je ein --value- und ein --color-Wert gesetzt wird. Diese custom properties (CSS-Variablen) finden sich im CSS wieder:

custom properties berechnen unser Diagramm ansehen …
.bar > rect {
	width: 45px;
	height: calc(var(--value) * 10px); 
	x: calc(var(--n) * 50px);
	y: calc((30 - var(--value)) * 10px);	
}

.bar {
	fill: var(--color);
	stroke: var(--black);
	stroke-width: 1;
	transition: fill .3s ease;
	cursor: pointer;
}

.bar:hover,
.bar:focus {
  fill: var(--bgcolor);
}

Das rect-Element <rect /> verzichtet auf alle XML-Attribute. Mit CSS werden nun Werte berechnet für:

  • width - hier wird mit 45px (die px-Angaben sind im Firefox nötig!) noch eine Magic Number verwendet.
  • height: hier wird der --value-Wert verzehnfacht. Durch die Multiplikation mit einem Pixelwert entsteht ein für CSS gültiger Wert.
  • x: Jede Säule soll neben der anderen platziert werden. Dies ist leider noch nicht automagisch möglich, weshalb diese Werte im CSS mit .bar:nth-of-type(1) { --n: 1; } usw. von Hand gesetzt werden.
  • y: Anders als beim Balkendiagramm, bei dem alle Balken bei x=0 beginnen; müssen die Säulen, die ja alle am Ende des SVG bei y=300 enden, einen unterschiedlichen y-Wert für den oberen Rand haben. Dieser wird mit calc((30 - var(--value)) * 10px); berechnet - die Höhe des Rechtecks wird vom unteren Rand des SVG abgezogen. (Da das gesamte SVG die --value-Werte um das Zehnfache skaliert, wird hier die Magic Number 30 verwendet; besser wäre eine weitere CSS-Variable.)

Was (noch) nicht funktioniert: Das X-Attribut des text-Elements ist in SVG2 noch kein Präsentationsattribut und kann daher nicht über custom properties gesetzt werden.[2]


Warum sollte man die Werte nicht in einem Datenteil vorhalten und per JavaScript in die vorhandene Struktur dynamisch einfügen? Die Art des Diagramms könnte dann über eine Klasse gesetzt werden.

Bevor wir einen solchen Diagramm-Generator bauen, sollten wir uns noch den Aufbau eines Tortendiagramms anschauen:

Gestapelte Balken/Säulen

Gestapelte oder „stacked bar charts“ sind mit SVG vollkommen analog zu erstellen: Statt nebeneinander (Säulen)/übereinander (Balken) werden die Balken in einer Säule/einem Balken dargestellt, im Fall des

  • Säulendiagramms mit gleicher x-Koordinate und y-Koordinate, die der Höhe des vorherigen Elements plus dessen y-Koordinate entspricht,
  • Balkendiagramms (siehe Beispiel) wie zuvor, nur mit vertauschten Koordinaten (gleiche y-Koordinate, zunehmende x-Koordinate).
Gestapeltes Balkendiagramm - SVG-Markup ansehen …
<svg class="chart" width="450" height="300" aria-labelledby="chartinfo" viewBox="0 0 450 300">
	<desc id="chartinfo">Ein Diagramm unserer Ernten</desc>

	<g class="bar">
		<title>2022</title>
		<rect width="660" height="45" />
		<text x="680" y="40">2022</text>
		
		<g class="bar purple" tabindex="0">
			<rect width="40" height="45" />
			<text x="5" y="40">4 Pflaumen</text>
		</g>
		<g class="bar green" tabindex="0">
			<rect width="80" height="45" x="40"/>
			<text x="45" y="40">8 Kiwis</text>
		</g>
		<g class="bar yellow" tabindex="0">
			<rect width="150" height="45" x="120" />
			<text x="125" y="40">15 Zitronen</text>
		</g>
		<g class="bar orange" tabindex="0">
			<rect width="160" height="45" x="270" />
			<text x="275" y="40">16 Orangen</text>
		</g>
		<g class="bar red" tabindex="0">
			<rect width="230" height="45" x="430" />
			<text x="435" y="40">23 Äpfel</text>
		</g>
	</g>

	<!-- und weitere Balken für frühere oder spätere Jahrgänge -->
</svg>

Kreisdiagramm

Etwas anders zeigen sich Kreis- oder Tortendiagramme. Kreissektoren kann man gemeinhin mit einem Pfad-Element mit einem Kreisbogen in drei Zügen zeichnen.

Tortendiagramm.svg

Wie schon bei der SVG-Uhr und dem Pie Timer wird die viewBox am Mittelpunkt des Kreises ausgerichtet; enthält rechts aber noch Platz für eine Legende.

Zuerst wird der Zeichenstift mit M 0,0 auf den Startpunkt (Koordinatenursprung) gelegt. Dann wird der Radius vertikal nach oben gezogen. Daran schließt das Kreisbogen-Segment mit a für Arc an, wobei große und kleine Halbachse gleich lang sind und dem Kreisradius entsprechen. Die Berechnung des Endpunkts des Kreissegments ist im folgenden Unterabschnitt #Konstruktion beschrieben. Mit Z wird der Pfad geschlossen. Diese Pfadkommandos werden im d-Attribut notiert.

Als Hintergrund für unser Diagramm wird ein grauer Kreis gezeichnet.

Kreisdiagramm - SVG-Markup ansehen …
<svg class="chart" width="500" height="400" aria-labelledby="chartinfo" viewBox="-105 -100 250 200">
	<title>Tortenstück eines Tortendiagrams</title>
	<desc id="chartinfo">Die Konstruktion eines Tortenstücks mit 60 °</desc>
<g>
	<circle id="kreis" r="80" style="fill:none;stroke:grey;stroke-width:0.5;stroke-dasharray: 2 2"/>

	<g>
		<title>Tortenstück 60 ° entsprechend 1/6 ≈ 16.66… %</title>

		<!-- text-Elemente der Übersichtlichkeit halber weggelassen -->
		<path d="M0,0 v-80" class="cons" stroke="#c32e04"/>
		<path d="M0,-80 a80,80 0 0,1 69.282032302755091741097853660235,40" class="cons" stroke="#dfac20" fill="none"/>
		<path d="M69.282032302755091741097853660235,-40 L0,0" class="cons pointing" style="stroke:#8db243"/>

		<!-- komplettes Kreissegment: -->
		<path d="M0,0 v-80 a80,80 0 0,1 69.282032302755091741097853660235,40z"/>
	</g>
</g>
</svg>

Ein Kreisdiagramm besteht nun aus mehreren Kreissektoren, die zusammen einen kompletten Kreis ergeben. Die Gesamtsumme aller Anteile entspricht daher einem Winkel von 360°, womit sich der Kreisbogen jedes Sektors als Anteil dieser 360° berechnet.

Jeder Kreissektor enthält ein path- und ein text-Element. Hier verbietet sich ein symbol, da die Werte für das d-Attribut zu unterschiedlich sind.

Konstruktion

Eine einfache Konstruktion eines Kreisdiagramms ist möglich, indem die einzelnen Kreissektoren wie oben erzeugt und dann um die Winkelsumme ihrer Vorgänger rotiert werden. In der folgenden Abbildung ist dies dargestellt:

Tortendiagramm-backen.svg

Der Kreissektor alpha entspricht 60°/16.66...%, die Vorgänger-Sektoren betragen insgesamt 200°/55.55...%, um die der Sektor noch rotiert werden muss.

Der Endpunkt des Kreisbogens a(rc) berechnet sich dabei aus der Konstruktion eines gleichschenkligen Dreiecks, wobei die Länge der beiden Schenkel dem Radius entspricht:

Kreisbogensegment.svg

sin(α) = x/R ⇔ x = R ⋅ sin(α)     cos(α) = y/R ⇔ y = R ⋅ cos(α) ⇒ y' = R - R ⋅ cos(α) = R ⋅ (1 - cos(α))

α = 360° ⋅ segment / 100     rad = 2 ⋅ π / 360° ⋅ 360° ⋅ segment / 100 = 2 ⋅ π ⋅ segment / 100

Für das obige Beispiel mit dem 60 °-Stück berechnen sich (der Radius R = 80) die im Beispielcode gezeigten relativen Koordinaten für den Bogen:

x  = 80 ⋅ sin(60 °)       ≈ 69.282...     y' = 80 ⋅ (1 - cos(60 °)) = 40

Beispiel

Das obige Beispiel mit der Obsternte lässt sich natürlich auch als Kreisdiagramm darstellen. Dazu müssen zuerst die Anteile berechnet werden, woraus sich dann die Winkel und Kreissegmente ergeben:

Obst Anzahl Anteil [%] Winkel [°]
Pflaumen 4 6,06 21,82
Kiwis 8 12,12 43,64
Zitronen 15 22,73 81,82
Orangen 16 24,24 87,27
Äpfel 23 34,85 125,45
Gesamt: 66 100 360

Mit den Winkeln und dem Radius werden nun die x- und y'-Koordinaten des Kreisbogenabschnitts (im Beispiel auf drei Nachkomma-Stellen genau) berechnet und die Pfade konstruiert.

Beispiel: Ernte als Kreisdiagramm - SVG-Markup ansehen …
<svg class="chart" width="500" height="400" aria-labelledby="chartinfo" viewBox="-105 -100 250 200">
	<title>Gesamternte als Kreissegmentm</title>
	<desc id="chartinfo">Beispiel mit der Gesamternte an Früchten als Kreissegmentm</desc>

	<path d="M0,0 v-80 a80,80 0 0 1 29.733,5.731z" transform="rotate(0)" class="purple">
		<title>4 Pflaumen</title>
	</path>

	<path d="M0,0 v-80 a80,80 0 0 1 55.206,22.101z" transform="rotate(21.818)" class="green">
		<title>8 Kiwis</title>
	</path>

	<path d="M0,0 v-80 a80,80 0 0 1 79.186,68.615z" transform="rotate(65.455)" class="yellow">
		<title>15 Zitronen</title>
	</path>

	<path d="M0,0 v-80 a80,80 0 0 1 79.909,76.193z" transform="rotate(147.273)" class="orange">
		<title>16 Orangen</title>
	</path>

	<path d="M0,0 v-80 a80,80 0 0 1 65.166,126.405z" transform="rotate(234.545)" class="red">
		<title>23 Äpfel</title>
	</path>
</svg>

Hervorhebung: Explodierte Darstellung

Bei :focus und :hover soll die Grundform optisch hervorgehoben werden. Eine Vergrößerung des Rands oder ein Schattenwurf wirkt besser, wenn der entsprechende Kreissektor etwas vom Mittelpunkt weg nach außen geschoben wird.

Diagramm-Generator

Unser Diagramm-Generator soll folgende Features haben:

  • manuelle Eingabe von Datensätzen
  • Importfunktion von CSV-Dateien aus Excel und Calc (dem OpenOffice-Pendant)
  • wahlweise Darstellung als Balken-, Säulen oder Kreisdiagramm
  • Exportfunktion des SVG-Markups (in Planung)

HTML-Markup

Die Eingaben und Einstellungen sollen über ein HTML-Formular realisiert werden. Als Inhalte der Menü-Elemente könnte man Unicode-Zeichen oder Emojis verwenden. Abgesehen von der mangelnden Zugänglichkeit besteht aber die Gefahr, dass die Zeichensätze nicht vorhanden sind. ◔ 📈 📊 [4]

Deshalb greifen wir auf die bewährten SVG-Grafiken als background-image zurück.


Beispiel Thomas Meinike, 2002


Fazit

Der Verzicht auf Bibliotheken erfordert einige Vorarbeit; mit einigen Tricks lassen sich so schnell barrierefreie und interaktive Diagramme erstellen.


Weblinks

  1. css-tricks: How to Make Charts with SVG 05.10.2015
  2. W3C: In SVG 2, the ‘text’ and ‘tspan’ ‘x’ and ‘y’ attributes are not presentation attributes and cannot be set via CSS. This may change in a future version of SVG.
  3. Simple pie charts with fallback, today (Lea Verou, 12.11.2020)
  4. https://emojiterra.com/de/buero/

Video-Tutorial: