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>

Das SVG erhält eine Beschreibung in einem desc-Element, die über das aria-labelledby-Attribut mit diesen verbunden ist.

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 - ein weiteres aria-Attribut ist nicht nötig.

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

Die oben erworbenen Kenntnisse wollen wir nun in einem Diagramm-Generator einsetzen.[4] Er 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[5]


HTML-Markup

Die Eingabe der Datensätze und Einstellungen sollen über ein HTML-Formular realisiert werden.

Beispiel ansehen …
<form id="generator">
	<fieldset id="data">
		<legend>Daten</legend>
		<ol id="data-list">
			<li>
				<label for="legend">Text</label><input type="text" id="text" value="">
				<label for="value">Wert </label><input type="number" id="value" value="">
				<label for="color">Farbe</label><input type="color" id="color" value="#c82f04">
			</li>
		</ol>  
		<button id="add-input-button" name="add">add</button>
	</fieldset>
	...
</form>

Im ersten Schritt sollen die Daten eingegeben werden. Jeder Datensatz besteht aus einer Text-Beschreibung, einem Zahlenwert und einer selbst zu wählenden Farbe.

Ein leerer Datensatz wird zur Eingabe bereitgestellt, ein Script liefert auf Wunsch weitere Zeilen.

<template id="newDataset">
	<li>
		<label for="legend">Text</label><input type="text" id="text" value="">
		<label for="value">Wert </label><input type="number" id="value" value="">
		<label for="color">Farbe</label><input type="color" id="color" list="selfPalette" value="">
    </li>
</template>

Im zweiten Schritt wird der Typ des gewünschten Diagramms ausgewählt, sowie ein Titel vergeben.

<fieldset id="settings">
	<legend>Einstellungen</legend>
	<label for="title"> Titel: <input id="title" type="text" size="30" value="SVG-Kreisdiagramm"></label>
	<label for="bar"><input type="radio" name="format" id="bar" checked >Balkendiagramm</label>
	<label for="bar2"><input type="radio" name="format" id="bar2" >Säulendiagramm</label>
	<label for="pie"><input type="radio" name="format" id="pie" >Kreisdiagramm</label>
	<button name="generate" type="button">Diagramm erstellen</button>  
</fieldset>

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. ◔ 📈 📊 [6]

https://www.svgrepo.com/vectors/bar-chart-icon/multicolor/

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

Verarbeitung durch JavaScript

MIt einem Klick auf "Diagramm erstellen" werden die Werte in ein JSON-Objekt umgewandelt:


Pflaumen;Zitronen;Äpfel
4;15;23

Fazit

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


<pie-chart> Diagramme mit Web Components!

Während das oben vorgestellte Tutorial zeigt, wie Diagramme angelegt und auch berechnet werden sollten, stellte Danny Engelman 2021 ein <pie-chart>-Custom Element vor, dass mit JavaScript zu einem vollwertigen SVG-circle wurde.[7] Sein pie-meister ermöglicht es, Daten direkt in ein Web Component einzugeben. Das mit 999Bytes sehr kleine Script wandelt es dann in ein ansprechendes SVG direkt in der Webseite um:


Diagramm mit <pie-chart> ansehen …
<pie-chart pull="100" stroke="gold, #c82f04, steelBlue">
    <style>
      text {
font-size: 8em;
text-anchor: middle;
fill: #000;
      }
[part="slice2"] {
font-size: 1.5em;
fill: white;
      }			
    </style>
    <slice size="10" pull>10</slice>
    <slice size="20">20</slice>
    <slice size="40">$size</slice>
</pie-chart>


Fazit: Der Größenvergleich von 999Bytes gegenüber den 64KB von D3.js ist nicht 1:1 vergleichbar, da in D3.js ein Vielfaches an Funktionen und Festures vorhanden ist.

Trotzdem ein interessantes Projekt!

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. SVG-Diagramme mit PHP generieren von Thomas Meinike, 2002 (datenverdrahten.de)
    Beispiel in PHP - aus dem Jahre 2002, manches würde man heute anders machen.
  5. SELF-Blog: Ostereier selbst herstellen und mitnehmen von Robert B., 12.04.2024
  6. https://emojiterra.com/de/buero/
  7. What Web technologies are required to draw a Pie Chart in 2021? Danny Engelman (dev.to)