Wie lässt sich ein Flächendiagramm erstellen?

Einem Flächendiagramm liegt ein zweidimensionales Koordinatensystem zugrunde. In diesem werden wie in einem Liniendiagramm durch mit Linien verbundene Punkte erfasst. Im Gegensatz zu jenem wird die Fläche unter der Kurve jedoch farbig ausgefüllt, sodass Überschneidungen mehrerer Datensätze besser verdeutlicht werden. In JavaFX kann ein Flächendiagramm mit Hilfe der Klasse javafx.scene.chart.AreaChart erstellt werden.

Das Beispiel demonstriert das Zeichnen eines Flächendiagramms am Beispiel eines fiktiven Gasverbrauchs in den Jahren 2020 und 2021. Für jeden Monat ist die jeweilige Menge des Verbrauchs notiert.

Die X-Achse wird zur Erfassung des Zeitstrahls durch ein Objekt der Klasse CategoryAxis, die Y-Achse zur Darstellung der Mengen als NumberAxis realisiert. Eine CategoryAxis kann beliebige Wertebezeichner als Strings darstellen, eine NumberAxis visualisiert immer einen linearen numerischen Verlauf beginnend bei einem unteren und endend bei einem oberen Wert. Für beide Achsen wird mittles setLabel() die Anzeige der Dimension initiiert. Bei der Erzeugung der AreaChart werden beide Axis-Objekte dem Konstruktor als Argumente übergeben.
Die Methode setCreateSymbols(false) bewirkt dann, dass die oberen Grenzlinien der Diagrammflächen ohne eine gesonderte Markierung der Datenpunkte gezeichnet wird. Wird hier true übergeben oder verzichtet man auf den Methodenaufruf, so werden an den Datenpunkten, wie bei Liniendiagrammen auch, Marker in Form kleiner Kreise dargestellt. Ein Beispiel hierzu findet sich im Abschnitt über negative Werte.

Für jedes Jahr wird eine Datengruppe vom Typ XYChart.Series erstellt. Sie speichert die Verbrauchswerte in Form mehrerer XYChart.Data-Objekte. Hierbei werden in jedem jeweils der String des Monatsnamens und der Verbrauch in diesem Zeitraum gespeichert. Die Werte werden dem Konstruktor übergeben.
Im Einzelnen geschieht dies so, dass durch getData() die ObservableList des Series-Objektes ermittelt und dieser dann durch add() das Data-Objekt übergeben wird. Auf diese Weise werden die Werte für jedes Jahr in einer separaten Liste abgelegt.

import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.chart.AreaChart;
import javafx.scene.chart.CategoryAxis;
import javafx.scene.chart.NumberAxis;
import javafx.scene.chart.StackedAreaChart;
import javafx.scene.chart.XYChart;
import javafx.stage.Stage;
 
 
public class AreaChartBsp extends Application {
 
    @Override public void start(Stage stage) {
        stage.setTitle("Area Chart Beispiel");
        final CategoryAxis xAxis = new CategoryAxis();
        final NumberAxis yAxis = new NumberAxis();
        xAxis.setLabel("Monat");
        yAxis.setLabel("Menge [m\u00b3]");
        final AreaChart<String, Number> ac = new AreaChart<>(xAxis,yAxis);
        ac.setCreateSymbols(false);
        ac.setTitle("Gasverbrauch [m\u00b3]");
 
        XYChart.Series series20= new XYChart.Series();
        series20.setName("2020");
        series20.getData().add(new XYChart.Data("Januar", 129));
        series20.getData().add(new XYChart.Data("Februar", 88));
        series20.getData().add(new XYChart.Data("M\u00e4rz", 69));
        series20.getData().add(new XYChart.Data("April", 15));
        series20.getData().add(new XYChart.Data("Mai", 3));
        series20.getData().add(new XYChart.Data("Juni", 1));
        series20.getData().add(new XYChart.Data("Juli", 0));
        series20.getData().add(new XYChart.Data("August", 0));
        series20.getData().add(new XYChart.Data("September", 0));
        series20.getData().add(new XYChart.Data("Oktober", 42));
        series20.getData().add(new XYChart.Data("November", 69));
        series20.getData().add(new XYChart.Data("Dezember", 133));
        
        XYChart.Series series21 = new XYChart.Series();
        series21.setName("2021");
        series21.getData().add(new XYChart.Data("Januar", 162));
        series21.getData().add(new XYChart.Data("Februar", 105));
        series21.getData().add(new XYChart.Data("M\u00e4rz", 71));
        series21.getData().add(new XYChart.Data("April", 39));
        series21.getData().add(new XYChart.Data("Mai", 0));
        series21.getData().add(new XYChart.Data("Juni", 0));
        series21.getData().add(new XYChart.Data("Juli", 1));
        series21.getData().add(new XYChart.Data("August", 0));
        series21.getData().add(new XYChart.Data("September", 0));
        series21.getData().add(new XYChart.Data("Oktober", 27));
        series21.getData().add(new XYChart.Data("November", 90));
        series21.getData().add(new XYChart.Data("Dezember", 111));
        
        Scene scene  = new Scene(ac,800,600);
        ac.getData().addAll(series20, series21);
        stage.setScene(scene);
        stage.show();
    }
 
    public static void main(String[] args) {
        launch(args);
    }
}

Das AreaChart-Objekt stellt ebenfalls eine ObservableList bereit. Ihr werden in Zeile 54 durch addAll() die einzelnen Gruppen übergeben. Ohne zusätzliche Formatierung bestimmt die hier verwendete Reihenfolge die Einfärbung der Kurven.

Gasverbrauch in den Jahren 2020 und 2021

Negative Werte

Die angegebenen Werte bestimmen im Standardfall den Darstellungsbereich des Diagramms. Will man diesen ändern, so besteht die Möglichkeit, dem Konstruktor einer NumberAxis den unteren und oberen Grenzwert, sowie die Schrittweite anzugeben. Die o.a. Abbildung ist somit äquivalent zum Konstruktoraufruf

NumberAxis yAxis = new NumberAxis(0, 170, 10);

Eine nachträgliche Änderung kann durch Aufrufe der Methoden setLowerBound(double d), setUpperBound(double d) und setTickUnit(double d) erfolgen.
Möchte man z.B. den 0-Wert nicht auf der Höhe der Y-Achse liegen haben, so ist dies durch die Erweiterung des Anzeigebereiches durch negative Werte möglich.

NumberAxis yAxis = new NumberAxis(-20, 170, 10);
Gasverbrauch in den Jahren 2020 und 2021

Umfasst der Bereich der übergebenen Daten den negativen Bereich, so erweitert sich die Darstellung automatisch. Das folgende Beispiel illustriert dies anhand von Abweichungen von einem Mittelwert.

import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.chart.AreaChart;
import javafx.scene.chart.CategoryAxis;
import javafx.scene.chart.NumberAxis;
import javafx.scene.chart.StackedAreaChart;
import javafx.scene.chart.XYChart;
import javafx.scene.chart.XYChart.Series;
import javafx.stage.Stage;
 
 
public class AreaChartBsp2 extends Application {
 
    @SuppressWarnings("unchecked")
    @Override public void start(Stage stage) {
        stage.setTitle("Area Chart Beispiel");
        final CategoryAxis xAxis = new CategoryAxis();
        final NumberAxis yAxis = new NumberAxis();
        xAxis.setLabel("Monat");
        yAxis.setLabel("Abweichung vom monatl.Durchschnitt [m\u00b3]");
        final AreaChart<String ,Number> ac = new AreaChart<>(xAxis,yAxis);
        ac.setTitle("Gasverbrauch [m\u00b3]");
 
        XYChart.Series series20= new XYChart.Series();
        series20.setName("2020");
        series20.getData().add(new XYChart.Data("Januar", computeFromAverage(50.5, 129)));
        series20.getData().add(new XYChart.Data("Februar", computeFromAverage(50.5, 88)));
        series20.getData().add(new XYChart.Data("M\u00e4rz", computeFromAverage(50.5, 69)));
        series20.getData().add(new XYChart.Data("April", computeFromAverage(50.5, 15)));
        series20.getData().add(new XYChart.Data("Mai", computeFromAverage(50.5, 3)));
        series20.getData().add(new XYChart.Data("Juni", computeFromAverage(50.5, 1)));
        series20.getData().add(new XYChart.Data("Juli", computeFromAverage(50.5, 0)));
        series20.getData().add(new XYChart.Data("August", computeFromAverage(50.5, 0)));
        series20.getData().add(new XYChart.Data("September", computeFromAverage(50.5, 0)));
        series20.getData().add(new XYChart.Data("Oktober", computeFromAverage(50.5, 42)));
        series20.getData().add(new XYChart.Data("November", computeFromAverage(50.5, 69)));
        series20.getData().add(new XYChart.Data("Dezember", computeFromAverage(50.5, 133)));
        
        Scene scene  = new Scene(ac,800,600);
        String stylesheet = getClass().getResource("/styles/areaChartStyles.css").toExternalForm();
        scene.getStylesheets().add(stylesheet);
        ac.getData().addAll(series20);
        stage.setScene(scene);
        stage.show();
    }
    
    private double computeFromAverage(double av, double value) {
        return value - av;
    }
 
    public static void main(String[] args) {
        launch(args);
    }
}

In Zeile 40 und 41 wird ein Stylesheet geladen, in dem über den Selektor .chart-horizontal-zero-line die Farbe der 0-Linie geändert wurde.

.chart-horizontal-zero-line {
    -fx-stroke: #dddddd;
}
Abweichungen vom Gas-Durchschnittsverbrauch in den Jahren 2020
Quellen
  1. https://docs.oracle.com/javase/8/javafx/user-interface-tutorial/area-chart.htm#CIHCFGBA