Datenvisualisierung/Liniendiagramm
Ein Liniendiagramm auch Kurvendiagramm, ist die graphische Darstellung des funktionellen Zusammenhangs mehrerer Merkmale als Diagramm in Linienform, wodurch Veränderungen bzw. Entwicklungen (etwa innerhalb eines bestimmten Zeitabschnitts) dargestellt werden können.[1]
Meist dient als Hintergrund ein Koordinatensystem zur eindeutigen Bezeichnung der Position von Punkten und Objekten in einem geometrischen Raum.
In diesem Kapitel lernst Du, wie man Daten (aus Messungen) in SVG visualisieren kann. Dabei liegt der Schwerpunkt auf den passenden SVG-Elementen und best practices.
Inhaltsverzeichnis
Koordinatensystem mit Raster
Ein kartesisches Koordinatensystem ist ein orthogonales (rechtwinkliges) Koordinatensystem. Im zwei- und dreidimensionalen Raum handelt es sich um das am häufigsten verwendete Koordinatensystem, da sich in diesem viele geometrische Sachverhalte anschaulich und übersichtlich beschreiben lassen.
<defs>
<pattern id="grid" patternUnits="userSpaceOnUse" width="10" height="10" x="0" y="0">
<path d="M0,0 v10 h10" stroke="#57c4ff" fill="none" />
</pattern>
</defs>
<rect x="0" y="0" width="700" height="400" fill="url(#grid)"></rect>
In diesem Beispiel wird der Hintergrund mit einem Muster versehen, das das Rechteck mit einem Raster versieht, sodass das darüber gezeichnete Koordinatensystem wie auf Millimeterpapier gezeichnet aussieht und leichter abzulesen ist.
.axis {
stroke: #999;
marker-end: url(#markerArrow);
}
Elemente mit der Klasse axis
erhalten eine graue Randlinie und eine Markierung am Ende.
<defs>
<pattern id="grid" patternUnits="userSpaceOnUse" width="10" height="10" x="0" y="0">
<path d="M0,0 v10 h10" stroke="#57c4ff" fill="none" />
</pattern>
<marker id="markerArrow" markerWidth="130" markerHeight="13" refx="2" refy="6" orient="auto">
<path d="M0,2 l8,4 l-8,4" fill="none" stroke="#999" stroke-width="1"/>
</marker>
</defs>
<rect x="0" y="0" width="700" height="400" fill="url(#grid)"></rect>
<path class="axis" d="M200,400 v-395"/>
<path class="axis" d="M0,200 h695"/>
Im Definitionsabschnitt wird nun eine Markierung markerArrow
festgelegt. Sie besteht aus einem Pfad, der die Form eines Winkels oder Pfeils einnimmt.
Durch den Ursprung des Koordinatensystems werden nun die zwei Achsen mit der Klasse axis
in Form eines Pfads gezogen. Über die CSS-Festlegung marker-end: url(#markerArrow);
wird diese Markierung an das Ende jeder Achse angefügt.
Über die Eigenschaft orient="auto"
wird die Richtung des Pfeils an den Verlauf der Achse angepasst.
Das Koordinatensystem von SVG hat seinen Ursprung oben links und so läuft die y-Achse von oben nach unten. Um zu einem Kartesischen Koordinatensystem zu kommen, müssen wir unsere Leinwand (und die Daten) nun mit transform="scale(1,-1)"
spiegeln (oder alle Koordinaten umrechnen). [2]
<defs>
<pattern id="grid" patternUnits="userSpaceOnUse" width="10" height="10" x="0" y="0">
<path d="M0,0 v10 h10" stroke="#57c4ff" fill="none" />
</pattern>
<marker id="markerArrow" markerWidth="130" markerHeight="13" refx="2" refy="6" orient="auto">
<path d="M0,2 l8,4 l-8,4" fill="none" stroke="#999" stroke-width="1"/>
</marker>
</defs>
<rect x="0" y="0" width="700" height="400" fill="url(#grid)"></rect>
<path class="axis" d="M200,400 v-395"/>
<path class="axis" d="M0,200 h695"/>
Wetterdaten im Monatsvergleich
Dieses Beispiel soll Daten aus einer Wetterstation grafisch präsentieren und mit denen der Vormonate oder des Vorjahresmonats vergleichen. Aus Gründen der didaktischen Reduktion arbeiten wir mit einem Array, den wir im Realeinsatz z. B. aus einer entsprechenden csv-Datei oder Datenbank erhalten.
const 2023-09 = [
{ day: 01, temp: 21 },
{ day: 02, temp: 24 },
{ day: 03, temp: 22 },
{ day: 04, temp: 25 },
{ day: 05, temp: 29 },
{ day: 06, temp: 17 },
{ day: 07, temp: 21 },
{ day: 08, temp: 24 },
{ day: 09, temp: 22 },
{ day: 10, temp: 17 },
...
];
Jede dieser Messungen hat ein Datum und eine Temperatur. (Im Realeinsatz würde eine Wetterstation diese Daten alle fünf Minuten erheben. Um dann Tageshöchsttemperaturen zu ermitteln, müsste vorher schon ausgewertet werden.)
Streudiagramm: Messpunkte darstellen
Zuerst wollen wir unsere Messwerte in das oben erklärte Diagramm eintragen. Dafür soll unser JavaScript für jeden Messwert einen Punkt zeichnen. Ein solches Diagramm nennt man Streudiagramm oder Punktwolke (engl. scatterplot).
Jede dieser Temperaturmessungen tragen wir mit einem dynamisch erzeugten circle-Element in unser Koordinatensystem ein. Dabei werden die jeweils ersten Werte in das cx- , die Temperaturen in die cy-Attribute eingetragen.
circle {
/* r: 3px; Pixelangabe für Firefox nötig */
fill: var(--yellow-lighter);
stroke: #333;
stroke-width: 1;
}
Das Aussehen des Punkts wird mit CSS festgelegt.
Alternativ könnte der Messwert auch andere Symbole (z. B. ■, ◆, 🞭, ...) erhalten, die ebenfalls als SVG erzeugt werden können.
function drawPoints() {
const xScale = (value) => (value - 1) * (600 / (data.length - 1) );
const yScale = (value) => 400 - (value / 30) * 400;
const chart = svg.appendChild(document.createElementNS('http://www.w3.org/2000/svg', 'g'));
//chart.setAttribute('transform', `translate(${margin.left}, ${margin.top})`);
data.forEach((point) => {
const circle = chart.appendChild(document.createElementNS('http://www.w3.org/2000/svg', 'circle'));
circle.setAttribute('cx', xScale(point.day));
circle.setAttribute('cy', yScale(point.temp));
circle.setAttribute('r', 3); // Radius of the circle
});
}
Das Datum wird als Wert für die X-Achse; die jeweiligen Messwerte als Wert für die Y-Achse verwendet.
Schöner wäre ein größerer Klickbereich, der auch auf mobilen Bildschirmen gut zu erfassen ist. So könnte man den Radius vergrößern und einen unsichtbaren Rand hinzufügen:
.messpunkt {
r: 8px; /*Pixelangabe für Firefox nötig */
fill: var(--yellow);
stroke: transparent;
stroke-width: 8;
Der Messpunkt erhält nun einen Radius von 8 Einheiten; Da sich die unsichtbare Randlinie mit einer Breite von 8 Einheiten gleichmäßig verteilt, hat der Messpunkt einen scheinbaren Radius von 4 - der Klickbereich jedoch von 12 Einheiten.
Beschriftung durch Tooltips
Grafiken liefern eine gute Übersicht - manchmal will man aber auch die genauen Werte wieder ablesen können. Aus diesem Grund wäre es gut, wenn man beim :hovern oder Anklicken einen kleinen Tooltip mit den Werten anzeigen könnte.
Die einfachste Lösung ist das Einfügen eines Kindelements:
<circle x="2" y="26" class="data">
<title>(05.09.) 26° Celsius</title>
</circle>
Das circle-Element wird vom inhaltsleeren Element zu einem „normalen“ XML-Element mit einem title-Element als Kind. Anders als in HTML sind in einem SVG-Dokument, auch in einem inline-SVG-Fragment in HTML, beliebig viele title-Elemente möglich.
function drawPoints() {
...
data.forEach((point) => {
const circle = chart.appendChild(document.createElementNS('http://www.w3.org/2000/svg', 'circle'));
...
// Tooltipp
const tooltipp = circle.appendChild(document.createElementNS('http://www.w3.org/2000/svg', 'title'));
tooltipp.textContent = `( ${point.day}.09.): ${point.temp}° Celsius`;
});
}
Für jeden Messpunkt wird ein title-Element erzeugt in den circle-Element eingehängt. Mit Node.textContent werden die Werte als Template-literal hinzugefügt.
Alternativ könntest du die Textinformation in einen eigenen Tooltipp legen, der bei :hover oder: focus aktiviert werden kann.
<defs>
<symbol id="tooltipp">
<path d="..." fill="#666" stroke="black" stroke-width=".25" />
</symbol>
</defs>
...
<g class="info" id="NswInfo" transform="translate(200,120)">
<use href="#tooltipp" />
<text x="5" y="12">New South Wales</text>
</g>
Die Form des Tooltips wird in einem wiederverwendbaren symbol angelegt und dann innerhalb eines g-Elements mit use wieder aufgerufen. Der Text wird nicht wie in HTML als Kindelement, sondern einfach als nächstes Geschwisterelement notiert.
Hauptartikel: Datenvisualisierung/interaktive Landkarten#frei positionierbare Tooltipps
von der Punktkette zur Linie
In einem zweiten Schritt verbinden wir die Punkte mit einer Linie. Als Linie bietet sich ein path-Element an. Einfacher ist eine polyline, deren points-Attribut mit unseren Koordinaten gefüllt wird.
// Scales for positioning
const xScale = (value) => (value - 1) * (600 / (data.length -1));
const yScale = (value) => 400 - (value / 30) * 400;
function drawPolyline() {
const points = data.map((item) => `${xScale(item.day)},${yScale(item.temp)}`).join(' ');
const polyline = document.createElementNS('http://www.w3.org/2000/svg', 'polyline');
polyline.setAttribute('points', points);
polyline.setAttribute('fill', 'none');
polyline.setAttribute('stroke', '#666');
polyline.setAttribute('stroke-width', 1);
svg.appendChild(polyline);
}
In der Funktion drawPolyline()
werden zuerst alle Messpunkte mit join zu einer Zeichenkette verbunden. Dann wird ein neues Element polyline erstellt, das die entsprechenden XML-Attribute zur Gestaltung erhält. Anschließend wird es mit appendChild in das SVG eingehängt.
Es wäre möglich, die einzelnen Punkte der polyline
mit dem marker-Element sichtbar zu machen und so auf die circle
-Elemente zu verzichten. Dann könnte man aber die einzelnen Zwischenpunkte nicht mit title-Elementen versehen.
Mit einem Klick auf den Button kannst du eine Regressionslinie zeichnen, um einen Trend zu erkennen.
Fazit
Dieses Tutorial dient als Einführung in den Aufbau von Diagrammen in SVG und welche SVG-Elemente für eine übersichtliche Darstellung, aber auch für beschreibende Texte verwendet werden können.
Im Regelfall stehen aber die darzustellenden Daten im Vordergrund und das Diagramm bildet nur das Gerüst. Deshalb wird im nächsten Kapitel gezeigt, wie man mit dem Framework D3.js externe Daten laden und in Webseiten darstellen kann.
Das Framework bietet auch Möglichkeiten, Daten noch anschaulicher aufzubereiten und z.B. Tageshöchst- und Minimalwerte zu kennzeichnen.
Siehe auch
D3.js ist das am weitesten verbreiteste Framework mit vielen Funktionen, die man mit wenigen Zeilen Code einbinden kann.
zum Zeichnen von Diagrammen und Funktionskurven
Weblinks
- ↑ Liniendiagramm (wikipedia)
- ↑ Kartesisches_Koordinatensystem (wikipedia)