The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.

NAME

Quiq::ChartJs::TimeSeries - Erzeuge Zeitreihen-Plot auf Basis von Chart.js

BASE CLASS

Quiq::Hash

SYNOPSIS

Modul laden

  use Quiq::ChartJs::TimeSeries;

Objekt instantiieren

  my $ch = Quiq::ChartJs::TimeSeries->new(
      parameter => 'Windspeed',
      unit => 'm/s',
      points => \@rows,
      pointCallback => sub {
           my ($point,$i) = @_;
           my ($iso,$val) = split /\t/,$point,2;
           return [Quiq::Epoch->new($iso)->epoch*1000,$val];
      },
  );

HTML-Seite mit Diagramm generieren

  my $h = Quiq::Html::Producer->new;
  
  my $html = Quiq::Html::Page->html($h,
      title => 'Chart.js testpage',
      load => [
          js => $ch->cdnUrl('2.8.0'),
      ],
      body => $ch->html($h),
  );

Diagramm

(Folgendes Diagramm erscheint nur in HTML - außer auf meta::cpan, da der HTML-Code dort gestrippt wird. Es zeigt 720 Messwerte einer Windgeschwindigkeits-Messung)

DESCRIPTION

Diese Klasse ist ein Perl-Wrapper für die Erzeugung für Zeitreihen-Plots auf Basis von Chart.js. Chart.js ist eine JavaScript-Bibliothek, die Diagramme auf einem HTML5 <canvas> darstellt. Chart.js bietet viele Möglichkeiten der Diagramm-Generierung. Die Einstellungen werden per Datenstruktur an den Chart-Konstruktor übergeben. Die Perl-Klasse ist darauf optimiert, einen speziellen Typ von Diagramm zu erzeugen: einen Zeitreihen-Plot. In einem Zeitreihen-Plot werden die Werte eines Parameters einer bestimmten Einheit (unit) gegen die Zeit geplottet. Die X-Achse ist die Zeitachse und die Y-Achse die Werteachse.

Diagramm-Eigenschaften und wie sie in Chart.js konfiguriert werden

Übergabe der Daten

Zeitreihendaten werden als Array von Punkten übergeben:

  data: {
      datasets: [{
          type: 'line',
          data: [__POINTS__],
      }],
  }

Jeder Punkt in __POINTS__ ist ein JS-Objekt mit der Struktur:

  {
      t: __JAVASCRIPT_EPOCH__,
      y: __VALUE__,
  }

Hierbei ist __JAVASCRIPT_EPOCH__ der Zeitpunkt in Unix Epoch mal 1000 (also in Millisekunden-Auflösung).

Diagramm mit konstanter Höhe und variabler Breite

Das <canvas>-Element wird in ein Parent-Element eingebettet, welches die Höhe __HEIGHT__ zugewiesen bekommt:

  <div style="height: __HEIGHT__px">
      <canvas id="__NAME__"></canvas>
  </div>

In den Chart-Optionen wird definiert:

  options: {
       maintainAspectRatio: false,
  }

Damit ist das Diagramm fest auf __HEIGHT__ Pixel Höhe eingestellt, passt sich in der Breite aber dem zur Verfügung stehenden Raum an, auch nach einem Resize.

Konfiguration der Zeitachse

Eine Zeitachse bedarf einiger Konfigurationsarbeit, da die Defaults von Chart.js nicht besonders sinnvoll sind.

  options: {
      scales: {
          xAxes: [{
              type: 'time',
              ticks: {
                  minRotation: 30,
                  maxRotation: 60,
              },
              time: {
                  min: __T_MIN__,
                  max: __T_MAX__,
                  minUnit: 'second',
                  displayFormats: {
                      second: 'YYYY-MM-DD HH:mm:ss',
                      minute: 'YYYY-MM-DD HH:mm',
                      hour: 'YYYY-MM-DD HH',
                      day: 'YYYY-MM-DD',
                      week: 'YYYY-MM-DD',
                      month: 'YYYY-MM',
                      quarter: 'YYYY [Q]Q',
                      year: 'YYYY',
                  },
                  tooltipFormat: 'YYYY-MM-DD HH:mm:ss',
              },
          }],
      },
  }
  • Die X-Achse wird zu einer Zeitachse, wenn type: 'time' gesetzt ist.

  • Eine Zeitachse wird speziell über die Unterstruktur time: ... konfiguriert.

  • Anders als bei numerischen Achsen werden Minimum und Maximum dort und nicht in der Unterstruktur ticks: ... festgelegt.

  • Werden min: und max: nicht oder auf undefined gesetzt, werden die Grenzen aus den Daten ermittelt.

  • Die Skalierung ergibt sich aus dem zur Verfügung stehenden Raum. Chart.js entscheidet sich für eine Auflösung aus den 9 Kategorien millisecond .. year.

  • Die Tick-Beschriftung für die einzelnen Kategorien wird durch die Substuktur displayFormats: ... definiert. Diese sollte komplett durchdefiniert werden, da Defaults von Chart.js nicht besonders sinnvoll sind.

  • Wie die Zeit im Tooltip dargestellt wird, definiert tooltipFormat:.

  • Bei längeren Tick-Beschriftungen kann Chart.js diese gekippt darstellen. Da Zeitangaben länger sind, sollte die gekippte Darstellung forciert werden. Die gekippte Darstellung wird forciert, wenn minRotation: und maxRotation definiert werden. Dann unterbleibt eine gerade Beschriftung, denn ein Wechsel zwischen gerader und gekippter Beschriftung wirkt uneinheitlich.

Raum unter dem Graph einfärben

  data: {
      datasets: [{
          fill: true;
      }],
  }

Konfigurations-Datenstruktur insgesamt

Die Perl-Klasse übergibt folgende Datenstruktur an den Konstruktor der JavaScript-Klasse Chart, wobei die Platzhalter __XXXX__ ersetzt werden, meist durch den Wert des betreffenden Klassen-Attributs xxxx. Die Liste aller Attribute siehe Abschnitt Attributes.

  type: 'line',
  data: {
      datasets: [{
          type: 'line',
          lineTension: __LINE_TENSION__,
          fill: true,
          borderColor: '__LINE_COLOR__',
          borderWidth: 1,
          pointRadius: __POINT_RADIUS__,
          data: [__POINTS__],
      }],
  },
  options: {
      maintainAspectRatio: false,
      title: {
          display: true,
          text: '__TITLE__',
          fontSize: 16,
          fontStyle: 'normal',
      },
      tooltips: {
          intersect: false,
          displayColors: false,
          backgroundColor: 'rgb(0,0,0,0.6)',
          titleMarginBottom: 2,
          callbacks: {
              label: function(tooltipItem,data) {
                  return '__PARAMETER__: ' + tooltipItem.value + ' __UNIT__';
              },
          },
      },
      legend: {
          display: false,
      },
      scales: {
          xAxes: [{
              type: 'time',
              ticks: {
                  minRotation: 30,
                  maxRotation: 60,
              },
              time: {
                  min: __T_MIN__,
                  max: __T_MAX__,
                  minUnit: 'second',
                  displayFormats: {
                      second: 'YYYY-MM-DD HH:mm:ss',
                      minute: 'YYYY-MM-DD HH:mm',
                      hour: 'YYYY-MM-DD HH',
                      day: 'YYYY-MM-DD',
                      week: 'YYYY-MM-DD',
                      month: 'YYYY-MM',
                      quarter: 'YYYY [Q]Q',
                      year: 'YYYY',
                  },
                  tooltipFormat: 'YYYY-MM-DD HH:mm:ss',
              },
          }],
          yAxes: [{
              ticks: {
                  min: __Y_MIN__,
                  max: __Y_MAX__,
              },
              scaleLabel: {
                  display: true,
                  labelString: '__UNIT__',
              },
          }],
      },
  }

SEE ALSO

METHODS

Konstruktor

new() - Instantiiere Objekt

Synopsis

  $ch = $class->new(@attVal);

Attributes

t => \@y (Default: [])

Referenz auf Array der Zeit-Werte (in JavaScript-Epoch).

y => \@y (Default: [])

Referenz auf Array der Y-Werte (Weltkoordinaten).

tMin => $jsEpoch (Default: 'undefined')

Kleinster Wert auf der Zeitachse. Der Default 'undefined' bedeutet, dass der Wert aus den Daten ermittelt wird.

tMax => $jsEpoch (Default: 'undefined')

Größter Wert auf der Zeitachse. Der Default 'undefined' bedeutet, dass der Wert aus den Daten ermittelt wird.

yMin => $val (Default: 'undefined')

Kleinster Wert auf der Y-Achse. Der Default 'undefined' bedeutet, dass der Wert aus den Daten ermittelt wird.

yMax => $val (Default: 'undefined')

Größter Wert auf der Y-Achse. Der Default 'undefined' bedeutet, dass der Wert aus den Daten ermittelt wird.

height => $height (Default: 300)

Die Höhe des Diagramms. Eine Breite wird nicht angegeben, diese passt sich dem zur Verfügung stehenden Raum an.

lineColor => $color (Default: 'rgb(255,0,0,1)')

Die Linienfarbe.

lineTension => $n (Default: 'undefined')

"Bezier curve tension of the line." Wenn 0, werden die Punkte gerade verbunden. Der Default 'undefined' bedeutet, dass der von Chart.js voreingestellte Wert 0.4 verwendet wird.

name => $name (Default: 'plot')

Name des Plot. Der Name wird als CSS-Id für die Zeichenfläche (Canvas) und als Variablenname für die Instanz verwendet.

parameter => $name

Der Name des dargestellten Parameters.

points => \@points (Default: [])

Liste der Datenpunkte oder - alternativ - der Elemente, aus denen die Datenpunkte mittels der Methode pointCallback (s.u.) gewonnen werden. Ein Datenpunkt ist ein Array mit zwei numerischen Werten [$x, $y], wobei $x ein JavaScript Epoch-Wert (Unix Epoch in Millisekunden) ist und $y ein beliebiger Y-Wert.

pointCallback => $sub (Default: undef)

Referenz auf eine Subroutine, die fr jedes Element der Liste @points einen Datenpunkt liefert, wie ihn die Klasse erwartet (s.o.). Ist kein rowCallback definiert, werden die Elemente aus @points unverändert verwendet.

pointRadius => $n (Default: 0)

Kennzeichne die Datenpunkte mit einem Kreis des Radius $n. 0 bedeutet, dass die Datenpunkte nicht gekennzeichnet werden.

showAverage => $bool (Default: 0)

Zeige das arithmetische Mittel an.

showMedian => $bool (Default: 0)

Zeige den Median an.

title => $str (Default: Name des Parameters)

Titel, der über das Diagramm geschrieben wird.

unit => $str

Einheit des Parameters. Mit der Einheit wird die Y-Achse beschriftet und sie erscheint im Tooltip.

Returns

Objekt

Description

Instantiiere ein Objekt der Klasse und liefere eine Referenz auf dieses Objekt zurück.

Klassenmethoden

cdnUrl() - Liefere CDN URL

Synopsis

  $url = $ch->cdnUrl($version);

Returns

URL (String)

Description

Liefere einen CDN URL für Chart.js in der Version $version.

Objektmethoden

html() - Generiere HTML

Synopsis

  $html = $ch->html($h);

Returns

HTML-Code (String)

Description

Liefere den HTML-Code der Chart-Instanz.

js() - Generiere JavaScript

Synopsis

  $js = $ch->js;

Returns

JavaScript-Code (String)

Description

Liefere den JavaScript-Code der Chart-Instanz.

IDEAS

  • minRotation, maxRotation auf einen festen Wert einstellen, z.B. 45, damit alle Diagramme gleich aussehen (?)

  • Höhe des Diagramms per JS setzen statt im HTML?

  • JS-Code in Ready-Handler setzen

  • Daten per Ajax laden

  • Tick-Label der Zeitachse berechnen, so dass wiederholende Teile ausgeblendet sind

  • Zoomen in die Daten. Wie? Plugin?

VERSION

1.204

AUTHOR

Frank Seitz, http://fseitz.de/

COPYRIGHT

Copyright (C) 2022 Frank Seitz

LICENSE

This code is free software; you can redistribute it and/or modify it under the same terms as Perl itself.