Datenvisualisierung/Liniendiagramm

Aus SELFHTML-Wiki
Wechseln zu: Navigation, Suche

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.

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.

Raster mit Karos ansehen …
  <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.

X- und Y-Achse ansehen …
.axis {
  stroke: #999;
  marker-end: url(#markerArrow);
}

Elemente mit der Klasse axis erhalten eine graue Randlinie und eine Markierung am Ende.

X- und Y-Achse ansehen …
<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]

gespiegelte Leinwand ansehen …
<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"/>
Beachte, dass die Achsenbeschriftung und eventuelle Labels nicht gespiegelt werden sollten.

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.

Beachte, eine Angabe für den Radius mit der Einheit px notiert werden muss, damit der Firefox dies rendert. Im SVG selbst ist diese Angabe nicht nötig - gerechnet wird in SVG sowieso in dimensionslosen Größen.

Alternativ könnte der Messwert auch andere Symbole (z. B. ■, ◆, 🞭, ...) erhalten, die ebenfalls als SVG erzeugt werden können.

Messpunkte in einem Streudiagramm darstellen ansehen …
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.

Messpunkte mit title-Tooltipp ansehen …
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.

Beispiel
  <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.

Liniendiagramm der (Tageshöchst)temperatur ansehen …
  // 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 D3.svg

D3.js ist das am weitesten verbreiteste Framework mit vielen Funktionen, die man mit wenigen Zeilen Code einbinden kann.

Funktionsplotter Plotter-icon.svg

zum Zeichnen von Diagrammen und Funktionskurven

Weblinks

  1. Liniendiagramm (wikipedia)
  2. Kartesisches_Koordinatensystem (wikipedia)