Временные диаграммы, TimeSeries

Временные диаграммы очень похожи на линейные, за исключением того, что вместо шкалы DomainAxis используется временная шкала DateAxis. C временной шкалой связан и набор данных, у которых одним из значений является время (дата, месяц, час, минута и т.д).

Временная шкала DateAxis

Специальный класс временной шкалой DateAxis преобразует милисекунды в даты и наоборот, обеспечивая представление осевых меток в нужном формате. Используя метод setDateFormatOverride и класс SimpleDateFormat можно представить значения осевых временных меток в заданном формате.

DateAxis axis = (DateAxis) plot.getDomainAxis();
axis.setDateFormatOverride(new SimpleDateFormat("dd.MM"));

Рассмотренные ниже примеры демонстрируют способности временной шкалы DateAxis представлять значения осевых временных меток в нужном формате и перестраивать значения при «переходе» к выделенной области графика (см. пример TimeSeriesRandow.java).

Временной набор данных, TimeSeriesCollection

Для временной диаграммы используются коллекция TimeSeriesCollection и набор данных TimeSeries. При добавлении значения в набор данных TimeSeries выполняется проверка на соответствие типу и отсутствие дублирования. Например, если в набор данных первым значением был внесен тип Day, то и все последующие значения также должны быть типом Day. В противном случае будет вызвано исключение в режиме RunTime. Пример создания набора данных :

TimeSeries s1 = new TimeSeries("Курс USD");
s1.add(new Day(11, 5, 2017), 58.0824);
s1.add(new Day(12, 5, 2017), 57.1161);

TimeSeries s2 = new TimeSeries("Курс EUR");
s2.add(new Day(11, 5, 2017), 63.2634);
s2.add(new Day(12, 5, 2017), 62.1595);

TimeSeriesCollection dataset = new TimeSeriesCollection();
dataset.addSeries(s1);
dataset.addSeries(s2);

Временные классы

Временные классы, используемые для построения временных диаграмм, располагаются в пакете org.jfree.data.time библиотеки JFreeChart и включают Month, Week, Day, Hour, Minute, Second и Milisecond. Основой (родительским классом) временных классов является RegularTimePeriod, определяющий свойства и методы получения следующего и предыдущего значений, периода значений и т.д. Ниже представлены конструкторы наиболее используемых временных классов.

Конструкторы класса Month

public Month()
public Month(int month, int year)
public Month(int month, Year year)
public Month(Date time, TimeZone zone, Locale locale)

Конструкторы класса Day

public Day();
public Day(Date time);
public Day(int day, int month, int year);
public Day(Date time, TimeZone zone, Locale locale);

Конструкторы класса Hour

public Hour();
public Hour(Date time);
public Hour(int hour, Day day);
public Hour(int hour, int day, int month, int year);
public Hour(Date time, TimeZone zone, Locale locale);

Конструкторы класса Minute

public Minute();
public Minute(Date time);
public Minute(int minute, Hour hour);
public Minute(Date time, TimeZone zone, Locale locale);
public Minute(int minute, int hour, int day, int month, int year);

Методы создания веременной диаграммы

Класс ChartFactory включает два перегруженных метода createTimeSeriesChart создания линейной временной диаграммы :

JFreeChart createTimeSeriesChart(String title, 
                                 String timeAxisLabel,
                                 String valueAxisLabel,
                                 XYDataset dataset);
JFreeChart createTimeSeriesChart(String title,
                                 String timeAxisLabel,
                                 String valueAxisLabel,
                                 XYDataset dataset,
                                 boolean legend,
                                 boolean tooltips,
                                 boolean urls);

Класс XYDataset наследует (extends) свйства и методы класса SeriesDataset. С описанием параметров при желании можно познакомиться здесь.

Настройка временной диаграммы

Настройка внешнего вида временной диаграммы ничем не отличается от настройки линейной диаграммы. Желающие могут перейти по указанной ссылке.

Примеры построения временных диаграмм

TimeSeriesDay.java Пример временной диаграммы с отображением курса валют и цены нефти марки Brent за определенный период.
TimeSeriesRandow.java Выделение области временной диаграммы, «переход» к выделенной области.
TimeSeriesMarker.java Нанесение маркеров и аннотаций на временную диаграмму.

Первый и третий примеры используют наборы данных типа XYDataset, которые вынесены в отдельный модуль Dataset.java. Таким образом, исходные данные отделены от кода формирования интерфейса диаграмм для облегчения восприятия. В конце страницы можно скачать примеры.

Листинг Dataset.java

Класс Dataset включает два метода, формирующих значения типа XYDataset. Метод createDataset() в качестве временного класса использует день Day, т.е. значения «привязываются» к суткам. В методе createSuppliersBids() в качестве временного класса используется минута Minute.

Два вспомогательных класса getDay() и getHour() используются в 3-м примере и «привязывают» значения к определенному часу суток. Метод next() класса Hour обеспечивает переход к следующему часу.

import org.jfree.data.time.Day;
import org.jfree.data.time.Hour;
import org.jfree.data.time.Minute;
import org.jfree.data.xy.XYDataset;
import org.jfree.data.time.TimeSeries;
import org.jfree.data.time.TimeSeriesCollection;

public class Dataset 
{
    public static XYDataset createDataset() 
    {
        final TimeSeries s1 = new TimeSeries("Курс USD");
        s1.add(new Day(11, 5, 2017), 58.0824);
        s1.add(new Day(12, 5, 2017), 57.1161);
        s1.add(new Day(13, 5, 2017), 57.1640);
        s1.add(new Day(16, 5, 2017), 56.5258);
        s1.add(new Day(17, 5, 2017), 56.2603);
        . . .
        final TimeSeries s2 = new TimeSeries("Курс EUR");
        s2.add(new Day(11, 5, 2017), 63.2634);
        s2.add(new Day(12, 5, 2017), 62.1595);
        s2.add(new Day(13, 5, 2017), 62.0915);
        s2.add(new Day(16, 5, 2017), 61.8449);
        s2.add(new Day(17, 5, 2017), 62.0382);
        . . .
        final TimeSeries s3 = new TimeSeries("Нефть марки Brent");
        s3.add(new Day(11, 5, 2017), 50.78);
        s3.add(new Day(12, 5, 2017), 50.82);
        s3.add(new Day(13, 5, 2017), 51.77);
        s3.add(new Day(16, 5, 2017), 51.30);
        s3.add(new Day(17, 5, 2017), 52.14);
        . . .
        final TimeSeriesCollection dataset = new TimeSeriesCollection();
        dataset.addSeries(s1);
        dataset.addSeries(s2);
        dataset.addSeries(s3);
        return dataset;
    }
    public static Hour getHour(final int value)
    {
    	return new Hour(value, getDay());
    }
    public static Day getDay()
    {
    	return new Day(15, 8, 2017);
    }
    public static XYDataset createSuppliersBids()
    {
        final Hour hour  = getHour(1);
        final Hour hour1 = getHour(1);
        final Hour hour2 = (Hour) hour1.next();

        final TimeSeries series1 = new TimeSeries("Поставщик 1");
        series1.add(new Minute(13, hour), 200.0);
        series1.add(new Minute(14, hour), 195.0);
        . . .
        final TimeSeries series2 = new TimeSeries("Поставщик 2");
        series2.add(new Minute(25, hour1), 185.0);
        series2.add(new Minute( 0, hour2), 175.0);
        series2.add(new Minute( 5, hour2), 170.0);
        . . .
        TimeSeriesCollection result = new TimeSeriesCollection();
        result.addSeries(series1);
        result.addSeries(series2);
        return result;
    }
}

Пример временной диаграммы TimeSeriesDay.java

В примере TimeSeriesDay.java представлены графики курса валют и цены нефти марки Brent за определенный период. Интерфейс диаграммы настраивается : изменяется фон графика и цвет сетки, удаляются осевые линии. Интерфейс примера представлен на следующем скриншоте.

Графики представлены в виде «кусочно-линейных функций». Их можно сгладить с использованием слайн-класса XYSplineRenderer. Следующий код можно вставить в пример и линии графиков будут гладкими :

XYSplineRenderer spline = new XYSplineRenderer();
spline.setSeriesShapesVisible (0, false);
plot.setRenderers(new XYItemRenderer[] {spline});

Листинг примера TimeSeriesDay.java

Для скрытия наименования осей в метод createTimeSeriesChart передаются соответствующие нулевые параметры. Цвет фона диаграммы и графика определяются методом setBackgroundPaint (Color). Цвет сетки графика устанавливается методами setDomainGridlinePaint(Color) и setRangeGridlinePaint (Color). Чтобы скрыть линии графика используется метод setAxisLineVisible (boolean). У третьего графика изменяется цвет линии методом setSeriesPaint.

Для временной оси DateAxis определяется формат представления осевых меток "dd.MM".

public class TimeSeriesDay extends ApplicationFrame
{
    private static final long serialVersionUID = 1L;
    static String TITLE = "Курс валюты, цена нефти марки Brent";

    public TimeSeriesDay(final String title) {
        
        super(title);
        final XYDataset dataset = Dataset.createDataset();
        final JFreeChart chart = createChart(dataset);
        final ChartPanel chartPanel = new ChartPanel(chart);
        chartPanel.setPreferredSize(new java.awt.Dimension(560, 480));
        chartPanel.setMouseZoomable(true, false);
        setContentPane(chartPanel);
    }

    private JFreeChart createChart(final XYDataset dataset) 
    {
        JFreeChart chart = ChartFactory.createTimeSeriesChart (
            "Валюта, нефть с 11.05.2017 по 25.05.2017", null, null,
            dataset, true, true, false);

        chart.setBackgroundPaint(Color.white);

        XYPlot plot = chart.getXYPlot();
        
        plot.setBackgroundPaint(new Color(232, 232, 232));
        plot.setDomainGridlinePaint(Color.lightGray);
        plot.setRangeGridlinePaint (Color.lightGray);
        plot.setDomainCrosshairVisible(true);
        plot.setRangeCrosshairVisible (true);
        
        // Скрытие осевых линий
        ValueAxis vaxis = plot.getDomainAxis();
        vaxis.setAxisLineVisible (false);
        vaxis = plot.getRangeAxis();
        vaxis.setAxisLineVisible (false);

        plot.getRenderer().setSeriesPaint(2, new Color(64, 255, 64));
        // Определение временной оси
        DateAxis axis = (DateAxis) plot.getDomainAxis();
        // Формат отображения осевых меток
        axis.setDateFormatOverride(new SimpleDateFormat("dd.MM"));
        return chart;
    }
    public static void main(final String[] args)
    {
        TimeSeriesDay demo = new TimeSeriesDay(TITLE);
        demo.pack();
        RefineryUtilities.centerFrameOnScreen(demo);
        demo.setVisible(true);
    }
}

Пример TimeSeriesRandow.java

В примере TimeSeriesRandow.java случайным образом генерятся значения с привязкой к суткам. На основании сгенерированного набора данных XYDataset создается график. После этого можно выделить определенную область значений и JFreeChart перерисует график для новой области (выделенного набора значений).

Интерфейс диаграммы и графика настраивается также, как и в первом примере. На следующем скриншоте представлен интерфейс диаграммы.

Чтобы выделить область значений необходимо нажать левую клавишу мыши на начальной точке и не отпуская ее переместить курсор мыши в конечную точку. После отпускания мыши JFreeChart перерисует диаграмму (график, шкалу) с учетом новых начальных и конечных значений.

Для выделения области значений на графике необходимо вызвать метод setMouseZoomable (boolean flag, boolean fillRectangle) класса ChartPanel. Первый параметр определяет возможность выделения области значений. Второй параметр указывает на необходимость заполнения выделяемой области.

На следующем скриншоте представлена перестроенная диаграмма для выделенной области значений.

Листинг примера TimeSeriesRandow.java

Для генерации данных используется Math.random(), значения привязываются к суткам Day. Метод createDataset возвращает набор данных из 2200 значений.

Временная ось DateAxis форматируется значением "MM.yyyy".

public class TimeSeriesRandow extends ApplicationFrame
{
    private static final long serialVersionUID = 1L;
    private static final String TITLE = "Пример TimeSeries";

    public TimeSeriesRandow(final String title)
    {
        super(title);
        
        XYDataset  dataset    = createDataset();
        JFreeChart chart      = createChart(dataset);
        ChartPanel chartPanel = new ChartPanel(chart);
        
        chartPanel.setPreferredSize(new java.awt.Dimension(560, 360));
        chartPanel.setMouseZoomable(true, true);
        setContentPane(chartPanel);
    }
     private XYDataset createDataset() 
    {
        TimeSeries series = new TimeSeries("Random Data");
        Day current = new Day(1, 1, 2011);
        double value = 100.0;
        for (int i = 0; i < 2200; i++) {
            try {
                value = value + Math.random() - 0.5;
                series.add(current, new Double(value));
                current = (Day) current.next();
            } catch (SeriesException e) {}
        }
        return new TimeSeriesCollection(series);
    }
    private JFreeChart createChart(final XYDataset dataset) 
    {
        JFreeChart chart = ChartFactory.createTimeSeriesChart(
                TITLE, null, null, dataset, false, false, false);
        XYPlot plot = chart.getXYPlot();
        // Определение отступа меток делений
        plot.setAxisOffset(new RectangleInsets (1.0, 1.0, 1.0, 1.0));
        // Скрытие осевых линий
        ValueAxis axis = plot.getDomainAxis();
        axis.setAxisLineVisible (false);
        axis = plot.getRangeAxis();
        axis.setAxisLineVisible (false);
        // Фон и цвет сетки графика 
        plot.setBackgroundPaint(new Color(232, 232, 232));
        plot.setDomainGridlinePaint(Color.lightGray);
        plot.setRangeGridlinePaint (Color.lightGray);

        // Определение временной оси
        DateAxis daxis = (DateAxis) plot.getDomainAxis();
        // Формат отображения осевых меток
        daxis.setDateFormatOverride(new SimpleDateFormat("MM.yyyy"));
        return chart;
    }
    public static void main(final String[] args)
    {
        final TimeSeriesRandow demo = new TimeSeriesRandow(TITLE);
        demo.pack();
        RefineryUtilities.positionFrameRandomly(demo);
        demo.setVisible(true);
    }
}

Пример TimeSeriesMarker.java

В примере TimeSeriesMarker.java на диаграмму наносятся маркеры и аннотации. Маркеры в виде прямых линий могут быть как горизонтальными, так и вертикальными, выделяться цветом и иметь подписи. Аннотации могут представлять либо простой текст с поворотом на определенный угол, либо текст с привязкой к графику в виде стрелки, которую можно привязать к определенной части текста и повернуть на заданный угол. Кроме этого, аннотация может быть представлена в виде некоторого графического объекта, размещаемого на графике.

Интерфейс примера представлен на следующем скриншоте.

Аннотации с привязкой и без привязки к графику с поворотом на заданный угол рассмотрены в примере построения графиков нормального распределения при описании линейных диаграмм.

Листинг примера TimeSeriesMarker.java

В примере для создания маркера используется метод createRangeMarker, которому передаются следующие параметры : значение по оси, текстовая метка, параметры привязки текста к маркеру и цвет маркера. Для горизотального размещения маркера на графике используется метод addRangeMarker. Для вертикально располагаемого маркера вызывается метод addDomainMarker.

Для нанесения на график аннотации с привязкой используется класс XYPointerAnnotation. Создаваемый объект данного класса можно настроить : определить цвет текста, место привязки стрелки к тексту и угол поворота стрелки, а также шрифт надписи.

В примере график представляет набор различных предложений во время торговой сессии на биржи. Лучшие предложения ограничены маркерами, а наиболее выгодное предложение выделено красным кружком. Для этого используется класс CircleDrawer, реализующий интерфейс Drawable. Листинг кода CircleDrawer представлен ниже. Для нанесения кружка на диаграмму создается аннотация XYDrawableAnnotation.

public class TimeSeriesMarker extends ApplicationFrame
{
    private static final long serialVersionUID = 1L;
    private static final String TITLE  = "Пример маркеров";
	
    public TimeSeriesMarker(final String title)
    {
        super(title);
        final XYDataset  data       = Dataset.createSuppliersBids();
        final JFreeChart chart      = createChart(data);
        final ChartPanel chartPanel = new ChartPanel(chart);
        
        chartPanel.setPreferredSize(new java.awt.Dimension(560, 480));

        setContentPane(chartPanel);
    }
    private JFreeChart createChart(final XYDataset data)
    {
        final JFreeChart chart = ChartFactory.createScatterPlot(
                "Пример аннотаций и маркеров", null, null, data, 
                 PlotOrientation.VERTICAL, true, true, false);
        // Настройка диаграммы ...
        final XYPlot plot = chart.getXYPlot();

        // Определение временной оси
        final DateAxis domainAxis = new DateAxis(null);
        domainAxis.setUpperMargin(0.50);
        plot.setDomainAxis(domainAxis);

        // Скрытие осевых линий
        plot.setAxisOffset(new RectangleInsets (1.0, 1.0, 1.0, 1.0));
        ValueAxis axis = plot.getDomainAxis();
        axis.setAxisLineVisible (false);
        axis = plot.getRangeAxis();
        axis.setAxisLineVisible (false);      

        final ValueAxis rangeAxis = plot.getRangeAxis();
        rangeAxis.setUpperMargin(0.30);
        rangeAxis.setLowerMargin(0.50);
        
        Marker start = createRangeMarker(200.0, 
                            "Стартовая цена предложений",
                            RectangleAnchor.BOTTOM_RIGHT,
                            TextAnchor.TOP_RIGHT, Color.red);
        Marker target = createRangeMarker(175.0,
                            "Выгодная цена",
                            RectangleAnchor.TOP_RIGHT,
                            TextAnchor.BOTTOM_RIGHT, Color.blue);
        plot.addRangeMarker(start );
        plot.addRangeMarker(target);

        final Hour hour = Dataset.getHour(2);
        double millis = hour.getFirstMillisecond();
        
        Marker originalEnd = createRangeMarker(millis,
                            "Закрытие торговой сессии (02:00)",
                            RectangleAnchor.TOP_LEFT,
                            TextAnchor.TOP_RIGHT, Color.red);

        final Minute min = new Minute(15, hour);
        millis = min.getFirstMillisecond();
        Marker currentEnd = createRangeMarker(millis,
                            "Завершение сделок (02:15)",
                            RectangleAnchor.TOP_RIGHT,
                            TextAnchor.TOP_LEFT, Color.blue);
        plot.addDomainMarker(originalEnd);
        plot.addDomainMarker(currentEnd);

        // Аннотация лучшего предложения
        final Hour h = Dataset.getHour(2);
        final Minute m = new Minute(10, h);
        millis = m.getFirstMillisecond();
        CircleDrawer cd = new CircleDrawer(Color.red, 
                                    new BasicStroke(1.0f), null);
        XYAnnotation bestBid = new XYDrawableAnnotation(millis,
                                              163.0, 11, 11, cd);
        plot.addAnnotation(bestBid);
        
        plot.addAnnotation(createPtrAnnotation(millis));

        return chart;
    }
    private Marker createRangeMarker(final double value, 
                                     final String caption, 
                                     final RectangleAnchor ranchor,
                                     final TextAnchor tanchor,
                                     final Color color)
    {
        final Marker marker = new ValueMarker(value);
        marker.setPaint(color);
        marker.setLabel(caption);
        marker.setLabelAnchor(ranchor);
        marker.setLabelTextAnchor(tanchor);

        return marker;
    }
    private XYPointerAnnotation createPtrAnnotation(double millis)
    {
        XYPointerAnnotation pointer;
        pointer = new XYPointerAnnotation("Выгодное предложение",
                                           millis, 163.0,
                                           3.0 * Math.PI / 4.0);
        pointer.setBaseRadius(35.0);
        pointer.setTipRadius(10.0);
        pointer.setFont(new Font("SansSerif", Font.PLAIN, 9));
        pointer.setPaint(Color.blue);
        pointer.setTextAnchor(TextAnchor.HALF_ASCENT_RIGHT);
        return pointer;
    }
    
    public static void main(final String[] args)
    {
        TimeSeriesMarker demo = new TimeSeriesMarker(TITLE);
        demo.pack();
        RefineryUtilities.centerFrameOnScreen(demo);
        demo.setVisible(true);
    }
}

Листинг CircleDrawer.java

Класс создания графического изображения круга, используемого при формировании аннотации.

import java.awt.Color;
import java.awt.Paint;
import java.awt.Stroke;
import java.awt.Graphics2D;
import java.awt.BasicStroke;

import java.awt.geom.Line2D;
import java.awt.geom.Ellipse2D;
import java.awt.geom.Rectangle2D;

import org.jfree.ui.Drawable;

/**
 * Реализация интерфейса {@link Drawable} при использовании класса
 * {@link org.jfree.chart.annotations.XYDrawableAnnotation}.
 */
public class CircleDrawer implements Drawable
{
    private Paint  outlinePaint;
    private Stroke outlineStroke;
    private Paint  fillPaint;

    public CircleDrawer(final Paint outlinePaint, 
                        final Stroke outlineStroke, 
                        final Paint fillPaint) {
        this.outlinePaint = outlinePaint;
        this.outlineStroke = outlineStroke;
        this.fillPaint = fillPaint;
    }
    public void draw(final Graphics2D g2, final Rectangle2D area)
    {
        Ellipse2D ellipse = new Ellipse2D.Double(area.getX(), 
                                                 area.getY(),
                                                 area.getWidth(),
                                                 area.getHeight());
        if (this.fillPaint != null) {
            g2.setPaint(this.fillPaint);
            g2.fill(ellipse);
        }
        if (this.outlinePaint != null &&
            this.outlineStroke != null) {
            g2.setPaint(this.outlinePaint);
            g2.setStroke(this.outlineStroke);
            g2.draw(ellipse);
        }

        g2.setPaint(Color.black);
        g2.setStroke(new BasicStroke(1.0f));
        Line2D line1 = new Line2D.Double(area.getCenterX(),
                                         area.getMinY(),
                                         area.getCenterX(),
                                         area.getMaxY());
        Line2D line2 = new Line2D.Double(area.getMinX(),
                                         area.getCenterY(),
                                         area.getMaxX(),
                                         area.getCenterY());
        g2.draw(line1);
        g2.draw(line2);
    }
}

Скачать примеры

Исходные коды примеров создания временных диаграмм с нанесением маркеров и аннотаций в виде maven-проекта можно скачать здесь (22.9 Кб).

  Рейтинг@Mail.ru