com.bdb.weather.display.summary.HighLowPanel.java Source code

Java tutorial

Introduction

Here is the source code for com.bdb.weather.display.summary.HighLowPanel.java

Source

/* 
 * Copyright (C) 2016 Bruce Beisel
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
package com.bdb.weather.display.summary;

import java.text.NumberFormat;
import java.time.Instant;
import java.time.LocalDate;
import java.util.Arrays;
import java.util.List;
import java.util.function.Function;
import java.util.logging.Logger;

import javafx.beans.property.ReadOnlyStringWrapper;
import javafx.beans.value.ObservableValue;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableView;
import javafx.util.Callback;

import org.jfree.chart.ChartFactory;
import org.jfree.chart.JFreeChart;
import org.jfree.chart.axis.DateAxis;
import org.jfree.chart.axis.DateTickMarkPosition;
import org.jfree.chart.axis.ValueAxis;
import org.jfree.chart.entity.ChartEntity;
import org.jfree.chart.entity.XYItemEntity;
import org.jfree.chart.fx.ChartViewer;
import org.jfree.chart.fx.interaction.ChartMouseEventFX;
import org.jfree.chart.fx.interaction.ChartMouseListenerFX;
import org.jfree.chart.labels.HighLowItemLabelGenerator;
import org.jfree.chart.plot.XYPlot;
import org.jfree.chart.renderer.xy.HighLowRenderer;
import org.jfree.data.time.Day;
import org.jfree.data.time.Hour;
import org.jfree.data.time.RegularTimePeriod;
import org.jfree.data.time.Year;
import org.jfree.data.time.ohlc.OHLCSeries;
import org.jfree.data.time.ohlc.OHLCSeriesCollection;
import org.jfree.data.xy.XYDataset;
import org.jfree.ui.RectangleEdge;

import com.bdb.util.measurement.Measurement;

import com.bdb.weather.common.SummaryRecord;
import com.bdb.weather.display.ChartDataPane;
import com.bdb.weather.display.DisplayConstants;
import com.bdb.weather.display.ViewLauncher;

public abstract class HighLowPanel<T extends Measurement> extends ChartDataPane implements ChartMouseListenerFX {
    private static final String HIGH_COL_HEADING = "High";
    private static final String LOW_COL_HEADING = "Low";
    private static final String AVG_COL_HEADING = "Avg";

    public static class SeriesInfo<T extends Measurement>
            implements Callback<TableColumn.CellDataFeatures<SummaryRecord, String>, ObservableValue<String>> {
        private final String seriesName;
        private final Function<SummaryRecord, T> maxMethod;
        private final Function<SummaryRecord, T> minMethod;
        private final Function<SummaryRecord, T> avgMethod;

        public SeriesInfo(String seriesName, Function<SummaryRecord, T> max, Function<SummaryRecord, T> min,
                Function<SummaryRecord, T> avg) {
            this.seriesName = seriesName;
            this.maxMethod = max;
            this.minMethod = min;
            this.avgMethod = avg;
        }

        public String getSeriesName() {
            return seriesName;
        }

        public T getMaxValue(SummaryRecord record) {
            return maxMethod.apply(record);
        }

        public T getMinValue(SummaryRecord record) {
            return minMethod.apply(record);
        }

        public T getAvgValue(SummaryRecord record) {
            return avgMethod.apply(record);
        }

        @Override
        public ObservableValue<String> call(TableColumn.CellDataFeatures<SummaryRecord, String> cdf) {
            SummaryRecord r = cdf.getValue();
            Measurement m = null;
            switch ((String) cdf.getTableColumn().getUserData()) {
            case HIGH_COL_HEADING:
                m = getMaxValue(r);
                break;
            case LOW_COL_HEADING:
                m = getMinValue(r);
                break;
            case AVG_COL_HEADING:
                m = getAvgValue(r);
                break;
            }

            String value = DisplayConstants.UNKNOWN_VALUE_STRING;
            if (m != null)
                value = m.toString();

            return new ReadOnlyStringWrapper(value);
        }
    }

    private final OHLCSeriesCollection seriesCollection = new OHLCSeriesCollection();
    private final XYPlot plot;
    private final JFreeChart chart;
    private final HighLowRenderer renderer;
    private final DateAxis dateAxis;
    private final SeriesInfo<T>[] seriesInfo;
    private final OHLCSeries[] series;
    private final TableView<SummaryRecord> dataTable = new TableView<>();
    private final SummaryInterval interval;
    private final ViewLauncher launcher;
    private final SummarySupporter supporter;
    private final static Logger logger = Logger.getLogger(HighLowPanel.class.getName());

    @SuppressWarnings("LeakingThisInConstructor")
    public HighLowPanel(String title, SummaryInterval interval, ViewLauncher launcher, SummarySupporter supporter,
            ValueAxis rangeAxis, String domainAxisLabel, SeriesInfo<T>[] seriesList, NumberFormat format) {
        this.setPrefSize(500, 300);
        this.interval = interval;
        this.launcher = launcher;
        this.supporter = supporter;

        chart = ChartFactory.createHighLowChart(title, domainAxisLabel, "", seriesCollection, true);
        chart.getLegend().setPosition(RectangleEdge.RIGHT);

        plot = (XYPlot) chart.getPlot();
        renderer = (HighLowRenderer) plot.getRenderer();
        renderer.setDrawCloseTicks(false);

        plot.setRangeAxis(rangeAxis);

        dateAxis = (DateAxis) plot.getDomainAxis();
        dateAxis.setDateFormatOverride(interval.getLegacyFormat());
        dateAxis.setVerticalTickLabels(true);
        dateAxis.setTickMarkPosition(DateTickMarkPosition.START);
        //dateAxis.setTickUnit(interval.getDateTickUnit());
        //dateAxis.setTickUnit(new DateTickUnit(DateTickUnitType.MONTH, 2));

        ChartViewer chartViewer = new ChartViewer(chart);
        chartViewer.addChartMouseListener(this);
        chartViewer.setPrefSize(500, 300);

        series = new OHLCSeries[seriesList.length];

        for (int i = 0; i < seriesList.length; i++) {
            series[i] = new OHLCSeries(seriesList[i].getSeriesName());
            seriesCollection.addSeries(series[i]);
        }

        seriesInfo = Arrays.copyOf(seriesList, seriesList.length);

        TableColumn<SummaryRecord, String> column = new TableColumn<>("Date");
        column.setCellValueFactory(
                (rec) -> new ReadOnlyStringWrapper(DisplayConstants.formatDate(rec.getValue().getDate())));

        dataTable.getColumns().add(column);

        String headingPrefix[] = { HIGH_COL_HEADING, LOW_COL_HEADING, AVG_COL_HEADING };

        for (SeriesInfo<T> seriesColumn : seriesList) {
            for (String heading : headingPrefix) {
                column = new TableColumn<>(heading + " - " + seriesColumn.getSeriesName());
                column.setCellValueFactory(seriesColumn);
                column.setUserData(heading);
                dataTable.getColumns().add(column);
            }
        }

        this.setTabContents(chartViewer, dataTable);

        HighLowItemLabelGenerator ttg = new HiLoItemLabelGenerator(interval.getLegacyFormat(), format);
        plot.getRenderer().setBaseToolTipGenerator(ttg);
    }

    protected XYPlot getPlot() {
        return plot;
    }

    public void loadData(List<SummaryRecord> records) {
        for (int i = 0; i < seriesInfo.length; i++) {
            series[i].clear();
            loadData(series[i], seriesInfo[i], records, i);
        }
    }

    private void loadData(OHLCSeries series, SeriesInfo<T> info, List<SummaryRecord> records, int seriesIndex) {
        ObservableList<SummaryRecord> dataModel = FXCollections.observableList(records);
        dataTable.setItems(dataModel);
        for (SummaryRecord record : records) {
            T avg = info.getAvgValue(record);
            T min = info.getMinValue(record);
            T max = info.getMaxValue(record);

            LocalDate date = record.getDate();
            // TODO: Figure out how to create a time period based on the specified interval
            RegularTimePeriod period = null;
            switch (interval) {
            case DAY_INTERVAL:
                period = new Hour(seriesIndex * 4, date.getDayOfMonth(), date.getMonth().getValue(),
                        date.getYear());
                break;
            case MONTH_INTERVAL:
                period = new Day(seriesIndex * 4 + 1, date.getMonth().getValue(), date.getYear());
                break;
            case YEAR_INTERVAL:
                period = new Year(date.getYear());
                break;
            default:
                period = null;
                break;
            }

            if (avg != null && min != null && max != null) {
                series.add(period, avg.get(), max.get(), min.get(), min.get());
            }
        }
    }

    /* (non-Javadoc)
     * @see org.jfree.chart.ChartMouseListener#chartMouseClicked(org.jfree.chart.ChartMouseEvent)
     */
    @Override
    public void chartMouseClicked(ChartMouseEventFX event) {
        ChartEntity entity = event.getEntity();
        //
        // Was a point on the plot selected?
        //
        if (entity instanceof XYItemEntity) {
            XYItemEntity itemEntity = (XYItemEntity) entity;
            XYDataset dataset = itemEntity.getDataset();
            Number x = dataset.getXValue(itemEntity.getSeriesIndex(), itemEntity.getItem());
            LocalDate date = LocalDate.from(Instant.ofEpochMilli(x.longValue()));
            boolean doubleClick = event.getTrigger().getClickCount() == 2;
            if (doubleClick) {
                supporter.launchView(launcher, date);
            }
        }
    }

    /* (non-Javadoc)
     * @see org.jfree.chart.ChartMouseListener#chartMouseMoved(org.jfree.chart.ChartMouseEvent)
     */
    @Override
    public void chartMouseMoved(ChartMouseEventFX event) {
        // Ignore mouse movement
    }

}