de.unibayreuth.bayeos.goat.panels.timeseries.JPanelChart.java Source code

Java tutorial

Introduction

Here is the source code for de.unibayreuth.bayeos.goat.panels.timeseries.JPanelChart.java

Source

/*******************************************************************************
 * Copyright (c) 2011 University of Bayreuth - BayCEER.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the GNU Public License v2.0
 * which accompanies this distribution, and is available at
 * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
 * 
 * Contributors:
 *     University of Bayreuth - BayCEER - initial API and implementation
 ******************************************************************************/
/*
 * JPanelChart.java
 *
 * Created on 24. Oktober 2002, 15:46
 */

package de.unibayreuth.bayeos.goat.panels.timeseries;

import java.awt.BorderLayout;
import java.awt.Cursor;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.awt.event.MouseMotionListener;
import java.awt.geom.Point2D;
import java.awt.geom.Rectangle2D;
import java.io.IOException;
import java.util.ResourceBundle;

import javax.swing.AbstractButton;
import javax.swing.BoundedRangeModel;
import javax.swing.ButtonGroup;
import javax.swing.DefaultBoundedRangeModel;
import javax.swing.JButton;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JScrollBar;
import javax.swing.JToggleButton;
import javax.swing.JToolBar;
import javax.swing.SwingUtilities;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;

import org.jfree.chart.ChartPanel;
import org.jfree.chart.ChartRenderingInfo;
import org.jfree.chart.JFreeChart;
import org.jfree.chart.Legend;
import org.jfree.chart.axis.ValueAxis;
import org.jfree.chart.event.ChartChangeEvent;
import org.jfree.chart.event.ChartChangeListener;
import org.jfree.chart.plot.Plot;
import org.jfree.chart.plot.ValueAxisPlot;
import org.jfree.chart.plot.XYPlot;
import org.jfree.chart.ui.ChartPropertyEditPanel;
import org.jfree.data.Range;
import org.jfree.ui.RefineryUtilities;

import de.unibayreuth.bayeos.utils.ImageFactory;
import de.unibayreuth.bayeos.utils.ImageFactoryException;
import de.unibayreuth.bayeos.utils.MsgBox;

/**
 *
 * @author  oliver
 */
public class JPanelChart extends JPanel
        implements ChartChangeListener, ChangeListener, MouseListener, MouseMotionListener, ActionListener {

    protected ChartPanel chartPanel;
    protected JFreeChart jFreeChart;
    protected JScrollBar chartScrollBar;

    private Point2D panStartPoint;
    private double scrollFactor = 1000;

    /** The min/max values for the y axis. */
    protected double yMin;
    protected double yMax;

    /** Action commands for the chart. */
    private static final String ACTION_CHART_PAN = "chart_pan";
    private static final String ACTION_CHART_ZOOM_BOX = "chart_zoombox";
    private static final String ACTION_CHART_ZOOM_TO_FIT = "chart_zoomfit";
    private static final String ACTION_CHART_ZOOM_IN = "chart_zoomin";
    private static final String ACTION_CHART_ZOOM_OUT = "chart_zoomout";
    private static final String ACTION_CHART_EXPORT = "chart_export";
    private static final String ACTION_CHART_PRINT = "chart_print";
    private static final String ACTION_CHART_PROPERTIES = "chart_properties";

    private static final String ACTION_TABLE_EXPORT = "table_export";

    protected AbstractButton chartZoomButton, chartPanButton, chartZoomInButton, chartZoomOutButton, chartFitButton,
            chartExportButton, chartPrintButton, chartPropertiesButton;

    protected static ResourceBundle localizationResources = ResourceBundle
            .getBundle("org.jfree.chart.LocalizationBundle");

    /** The zoom factor. */
    private static final double ZOOM_FACTOR = 0.8;

    /** The toolbar. */
    protected JToolBar toolBarChart;

    /** Creates a new instance of JDetailPanel */
    public JPanelChart() {
        setLayout(new BorderLayout());
        toolBarChart = createChartToolbar();
        add(toolBarChart, BorderLayout.NORTH);

        chartScrollBar = createJScrollBar();
        add(chartScrollBar, BorderLayout.SOUTH);

    }

    private JScrollBar createJScrollBar() {
        JScrollBar b = new JScrollBar(JScrollBar.HORIZONTAL);
        b.setModel(new DefaultBoundedRangeModel());
        b.setEnabled(false);
        return b;
    }

    private JToolBar createChartToolbar() {
        JToolBar toolbar = new JToolBar();

        ButtonGroup groupedButtons = new ButtonGroup();

        // ACTION_CMD_PAN
        chartPanButton = new JToggleButton();
        prepareButton(chartPanButton, ACTION_CHART_PAN, "de/unibayreuth/bayeos/goat/panels/Pan16.gif", "Pan mode");
        groupedButtons.add(chartPanButton);
        toolbar.add(chartPanButton);

        // ACTION_CMD_ZOOM_BOX
        chartZoomButton = new JToggleButton();
        prepareButton(chartZoomButton, ACTION_CHART_ZOOM_BOX, "de/unibayreuth/bayeos/goat/panels/Zoom16.gif",
                "Zoom mode");
        groupedButtons.add(chartZoomButton);
        chartZoomButton.setSelected(true); // no other makes sense after startup
        toolbar.add(chartZoomButton);

        // end of toggle-button group for select/pan/zoom-box
        toolbar.addSeparator();

        // ACTION_CMD_ZOOM_IN
        chartZoomInButton = new JButton();
        prepareButton(chartZoomInButton, ACTION_CHART_ZOOM_IN, "de/unibayreuth/bayeos/goat/panels/ZoomIn16.gif",
                "Zoom in");
        toolbar.add(chartZoomInButton);

        // ACTION_CMD_ZOOM_OUT
        chartZoomOutButton = new JButton();
        prepareButton(chartZoomOutButton, ACTION_CHART_ZOOM_OUT, "de/unibayreuth/bayeos/goat/panels/ZoomOut16.gif",
                "Zoom out");
        toolbar.add(chartZoomOutButton);

        // ACTION_CMD_ZOOM_TO_FIT
        chartFitButton = new JButton();
        prepareButton(chartFitButton, ACTION_CHART_ZOOM_TO_FIT,
                "de/unibayreuth/bayeos/goat/panels/ZoomExtent16.gif", "Zoom to extent");
        toolbar.add(chartFitButton);

        toolbar.addSeparator();

        chartExportButton = new JButton();
        prepareButton(chartExportButton, ACTION_CHART_EXPORT, "de/unibayreuth/bayeos/goat/panels/Export16.gif",
                "Export chart image ...");
        toolbar.add(chartExportButton);

        // ACTION_CMD_PRINT
        chartPrintButton = new JButton();
        prepareButton(chartPrintButton, ACTION_CHART_PRINT, "de/unibayreuth/bayeos/goat/panels/Print16.gif",
                "Print chart ...");
        toolbar.add(chartPrintButton);

        toolbar.addSeparator();
        // ACTION_CMD_PROPERTIES
        chartPropertiesButton = new JButton();
        prepareButton(chartPropertiesButton, ACTION_CHART_PROPERTIES,
                "de/unibayreuth/bayeos/goat/panels/Properties16.gif", "Chart properties ...");
        toolbar.add(chartPropertiesButton);

        chartZoomOutButton.setEnabled(false);
        chartFitButton.setEnabled(false);

        return toolbar;
    }

    /**
     * Handles an action event.
     * 
     * @param evt
     *            the event.
     */
    public void actionPerformed(ActionEvent evt) {
        try {
            String acmd = evt.getActionCommand();

            if (acmd.equals(ACTION_CHART_ZOOM_BOX)) {
                setPanMode(false);
            } else if (acmd.equals(ACTION_CHART_PAN)) {
                setPanMode(true);
            } else if (acmd.equals(ACTION_CHART_ZOOM_IN)) {
                ChartRenderingInfo info = this.chartPanel.getChartRenderingInfo();
                Rectangle2D rect = info.getPlotInfo().getDataArea();
                zoomBoth(rect.getCenterX(), rect.getCenterY(), ZOOM_FACTOR);
            } else if (acmd.equals(ACTION_CHART_ZOOM_OUT)) {
                ChartRenderingInfo info = this.chartPanel.getChartRenderingInfo();
                Rectangle2D rect = info.getPlotInfo().getDataArea();
                zoomBoth(rect.getCenterX(), rect.getCenterY(), 1 / ZOOM_FACTOR);
            } else if (acmd.equals(ACTION_CHART_ZOOM_TO_FIT)) {

                // X-axis (has no fixed borders)
                chartPanel.autoRangeHorizontal();
                Plot plot = chartPanel.getChart().getPlot();
                if (plot instanceof ValueAxisPlot) {
                    XYPlot vvPlot = (XYPlot) plot;
                    ValueAxis axis = vvPlot.getRangeAxis();
                    if (axis != null) {
                        axis.setLowerBound(yMin);
                        axis.setUpperBound(yMax);
                    }
                }
            } else if (acmd.equals(ACTION_CHART_EXPORT)) {
                try {
                    chartPanel.doSaveAs();
                } catch (IOException i) {
                    MsgBox.error(i.getMessage());
                }
            } else if (acmd.equals(ACTION_CHART_PRINT)) {
                chartPanel.createChartPrintJob();

            } else if (acmd.equals(ACTION_CHART_PROPERTIES)) {
                ChartPropertyEditPanel panel = new ChartPropertyEditPanel(jFreeChart);
                int result = JOptionPane.showConfirmDialog(this, panel,
                        localizationResources.getString("Chart_Properties"), JOptionPane.OK_CANCEL_OPTION,
                        JOptionPane.PLAIN_MESSAGE);
                if (result == JOptionPane.OK_OPTION) {
                    panel.updateChartProperties(jFreeChart);
                }
            }

        } catch (Exception e) {
            MsgBox.error(e.getMessage());
        }
    }

    private void setPanMode(boolean val) {

        this.chartPanel.setHorizontalZoom(!val);
        // chartPanel.setHorizontalAxisTrace(! val);
        this.chartPanel.setVerticalZoom(!val);
        // chartPanel.setVerticalAxisTrace(! val);

        if (val) {
            this.chartPanel.setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR));
        } else {
            this.chartPanel.setCursor(Cursor.getDefaultCursor());
        }
    }

    protected void prepareButton(AbstractButton button, String actionKey, String iconResource, String toolTipText) {
        button.setActionCommand(actionKey);
        button.setToolTipText(toolTipText);
        button.addActionListener(this);
        try {
            button.setIcon(ImageFactory.getIcon(iconResource));
        } catch (ImageFactoryException i) {
            MsgBox.error(i.getMessage());
        }
    }

    public void setChart(JFreeChart chart) {
        this.jFreeChart = chart;
        recalcScrollBar(jFreeChart.getPlot());
        if (chartPanel != null) {
            remove(chartPanel);
        }
        this.chartPanel = new ChartPanel(jFreeChart);
        jFreeChart.addChangeListener(this);

        // enable zoom
        actionPerformed(new ActionEvent(this, 0, ACTION_CHART_ZOOM_BOX));

        // MouseListeners for pan function
        this.chartPanel.addMouseListener(this);
        this.chartPanel.addMouseMotionListener(this);

        // remove popup menu to allow panning
        // with right mouse pressed
        this.chartPanel.setPopupMenu(null);

        XYPlot vvPlot = (XYPlot) chart.getXYPlot();
        ValueAxis axis = vvPlot.getRangeAxis();
        yMax = axis.getMaximumAxisValue();
        yMin = axis.getMinimumAxisValue();

        add(chartPanel, BorderLayout.CENTER);

    }

    public void setLegend(Legend legend) {
        jFreeChart.setLegend(legend);
    }

    /** Getter for property chartPanel.
     * @return Value of property chartPanel.
     *
     */
    public ChartPanel getChartPanel() {
        return chartPanel;
    }

    /**
         * Zooms in on an anchor point (measured in Java2D coordinates).
         * 
         * @param x  the x value.
         * @param y  the y value.
         * @param zoomFactor  the zoomFactor < 1 == zoom in; else out.
         */
    private void zoomBoth(double x, double y, double zoomFactor) {
        zoomHorizontal(x, zoomFactor);
        zoomVertical(y, zoomFactor);
    }

    /**
     * Decreases the range on the horizontal axis, centered about a Java2D x coordinate.
     * <P>
     * The range on the x axis is multiplied by zoomFactor
     * 
     * @param x  the x coordinate in Java2D space.
     * @param zoomFactor  the zoomFactor < 1 == zoom in; else out.
     */
    private void zoomHorizontal(double x, double zoomFactor) {

        JFreeChart chart = this.chartPanel.getChart();
        ChartRenderingInfo info = this.chartPanel.getChartRenderingInfo();
        if (chart.getPlot() instanceof XYPlot) {
            XYPlot hvp = (XYPlot) chart.getPlot();
            ValueAxis axis = hvp.getDomainAxis();
            if (axis != null) {
                double anchorValue = axis.java2DToValue((float) x, info.getPlotInfo().getDataArea(),
                        hvp.getDomainAxisEdge());
                if (zoomFactor < 1.0) {
                    axis.resizeRange(zoomFactor, anchorValue);
                } else if (zoomFactor > 1.0) {
                    Range range = hvp.getDataRange(axis);
                    adjustRange(axis, range, zoomFactor, anchorValue);
                }
            }
        }
    }

    /**
     * Decreases the range on the vertical axis, centered about a Java2D y coordinate.
     * <P>
     * The range on the y axis is multiplied by zoomFactor
     * 
     * @param y  the y coordinate in Java2D space.
     * @param zoomFactor  the zoomFactor < 1 == zoom in; else out.
     */
    private void zoomVertical(double y, double zoomFactor) {

        JFreeChart chart = this.chartPanel.getChart();
        ChartRenderingInfo info = this.chartPanel.getChartRenderingInfo();

        if (chart.getPlot() instanceof XYPlot) {
            XYPlot vvp = (XYPlot) chart.getPlot();
            ValueAxis primYAxis = vvp.getRangeAxis();
            if (primYAxis != null) {
                double anchorValue = primYAxis.java2DToValue((float) y, info.getPlotInfo().getDataArea(),
                        vvp.getRangeAxisEdge());
                if (zoomFactor < 1.0) {
                    // zoom in
                    primYAxis.resizeRange(zoomFactor, anchorValue);

                } else if (zoomFactor > 1.0) {
                    // zoom out
                    Range range = new Range(yMin, yMax);
                    adjustRange(primYAxis, range, zoomFactor, anchorValue);
                }
            }

        }
    }

    /**
     * used for zooming
     * 
     * @param axis  the axis.
     * @param range  the range.
     * @param zoomFactor  the zoom factor.
     * @param anchorValue  the anchor value.
     */
    private void adjustRange(ValueAxis axis, Range range, double zoomFactor, double anchorValue) {

        if (axis == null || range == null) {
            return;
        }

        double rangeMinVal = range.getLowerBound() - range.getLength() * axis.getLowerMargin();
        double rangeMaxVal = range.getUpperBound() + range.getLength() * axis.getUpperMargin();
        double halfLength = axis.getRange().getLength() * zoomFactor / 2;
        double zoomedMinVal = anchorValue - halfLength;
        double zoomedMaxVal = anchorValue + halfLength;
        double adjMinVal = zoomedMinVal;
        if (zoomedMinVal < rangeMinVal) {
            adjMinVal = rangeMinVal;
            zoomedMaxVal += rangeMinVal - zoomedMinVal;
        }
        double adjMaxVal = zoomedMaxVal;
        if (zoomedMaxVal > rangeMaxVal) {
            adjMaxVal = rangeMaxVal;
            zoomedMinVal -= zoomedMaxVal - rangeMaxVal;
            adjMinVal = Math.max(zoomedMinVal, rangeMinVal);
        }

        Range adjusted = new Range(adjMinVal, adjMaxVal);
        axis.setRange(adjusted);
    }

    private void recalcScrollBar(Plot plot) {
        if (plot instanceof XYPlot) {
            XYPlot hvp = (XYPlot) plot;
            ValueAxis axis = hvp.getDomainAxis();

            axis.setLowerMargin(0);
            axis.setUpperMargin(0);

            Range rng = axis.getRange();

            BoundedRangeModel scrollBarModel = this.chartScrollBar.getModel();
            int len = scrollBarModel.getMaximum() - scrollBarModel.getMinimum();
            if (rng.getLength() > 0) {
                scrollFactor = len / rng.getLength();
            }

            double dblow = rng.getLowerBound();
            int ilow = (int) (dblow * scrollFactor);
            scrollBarModel.setMinimum(ilow);
            int val = ilow;
            scrollBarModel.setValue(val);

            double dbup = rng.getUpperBound();
            int iup = (int) (dbup * scrollFactor);
            scrollBarModel.setMaximum(iup);
            int ext = iup - ilow;
            scrollBarModel.setExtent(ext);

            scrollBarModel.addChangeListener(this);
        }
    }

    public void chartChanged(ChartChangeEvent event) {
        try {
            if (event.getChart() == null) {
                return;
            }

            BoundedRangeModel scrollBarModel = this.chartScrollBar.getModel();
            if (scrollBarModel == null) {
                return;
            }

            boolean chartIsZoomed = false;

            Plot plot = event.getChart().getPlot();
            if (plot instanceof XYPlot) {
                XYPlot hvp = (XYPlot) plot;
                ValueAxis xAxis = hvp.getDomainAxis();
                Range xAxisRange = xAxis.getRange();

                // avoid recursion
                scrollBarModel.removeChangeListener(this);

                int low = (int) (xAxisRange.getLowerBound() * scrollFactor);
                scrollBarModel.setValue(low);
                int ext = (int) (xAxisRange.getUpperBound() * scrollFactor - low);
                scrollBarModel.setExtent(ext);

                // restore
                scrollBarModel.addChangeListener(this);

                // check if zoomed horizontally
                //Range hdr = hvp.getHorizontalDataRange(xAxis);
                Range hdr = hvp.getDataRange(xAxis);

                double len = hdr == null ? 0 : hdr.getLength();
                chartIsZoomed |= xAxisRange.getLength() < len;
            }

            if (!chartIsZoomed && plot instanceof XYPlot) {
                // check if zoomed vertically
                XYPlot vvp = (XYPlot) plot;
                ValueAxis yAxis = vvp.getRangeAxis();
                if (yAxis != null) {
                    chartIsZoomed = yAxis.getLowerBound() > yMin || yAxis.getUpperBound() < yMax;
                }
            }

            // enable "zoom-out-buttons" if chart is zoomed
            // otherwise disable them
            chartPanButton.setEnabled(chartIsZoomed);
            chartZoomOutButton.setEnabled(chartIsZoomed);
            chartFitButton.setEnabled(chartIsZoomed);
            chartScrollBar.setEnabled(chartIsZoomed);
            if (!chartIsZoomed) {
                setPanMode(false);
                chartZoomButton.setSelected(true);
            }
        } catch (Exception e) {
            MsgBox.error(e.getMessage());
        }
    }

    public void stateChanged(ChangeEvent event) {
        try {
            Object src = event.getSource();
            BoundedRangeModel scrollBarModel = this.chartScrollBar.getModel();
            if (src == scrollBarModel) {
                int val = scrollBarModel.getValue();
                int ext = scrollBarModel.getExtent();

                Plot plot = this.chartPanel.getChart().getPlot();
                if (plot instanceof XYPlot) {
                    XYPlot hvp = (XYPlot) plot;
                    ValueAxis axis = hvp.getDomainAxis();

                    // avoid problems
                    this.chartPanel.getChart().removeChangeListener(this);

                    axis.setRange(val / scrollFactor, (val + ext) / scrollFactor);

                    // restore chart listener
                    this.chartPanel.getChart().addChangeListener(this);
                }
            }
        } catch (Exception e) {
            MsgBox.error(e.getMessage());
        }
    }

    public void mouseDragged(MouseEvent event) {
        try {
            if (this.panStartPoint != null) {
                Rectangle2D scaledDataArea = this.chartPanel.getScaledDataArea();

                this.panStartPoint = RefineryUtilities.getPointInRectangle(this.panStartPoint.getX(),
                        this.panStartPoint.getY(), scaledDataArea);
                Point2D panEndPoint = RefineryUtilities.getPointInRectangle(event.getX(), event.getY(),
                        scaledDataArea);

                // horizontal pan

                Plot plot = this.chartPanel.getChart().getPlot();
                if (plot instanceof XYPlot) {
                    XYPlot hvp = (XYPlot) plot;
                    ValueAxis xAxis = hvp.getDomainAxis();

                    if (xAxis != null) {
                        double translatedStartPoint = xAxis.java2DToValue((float) panStartPoint.getX(),
                                scaledDataArea, hvp.getDomainAxisEdge());
                        double translatedEndPoint = xAxis.java2DToValue((float) panEndPoint.getX(), scaledDataArea,
                                hvp.getDomainAxisEdge());
                        double dX = translatedStartPoint - translatedEndPoint;

                        double oldMin = xAxis.getLowerBound();
                        double newMin = oldMin + dX;

                        double oldMax = xAxis.getUpperBound();
                        double newMax = oldMax + dX;

                        // do not pan out of range
                        if (newMin >= hvp.getDataRange(xAxis).getLowerBound()
                                && newMax <= hvp.getDataRange(xAxis).getUpperBound()) {
                            xAxis.setLowerBound(newMin);
                            xAxis.setUpperBound(newMax);
                        }
                    }
                }

                // vertical pan (1. Y-Axis)

                if (plot instanceof XYPlot) {
                    XYPlot vvp = (XYPlot) plot;
                    ValueAxis yAxis = vvp.getRangeAxis();

                    if (yAxis != null) {
                        double translatedStartPoint = yAxis.java2DToValue((float) panStartPoint.getY(),
                                scaledDataArea, vvp.getRangeAxisEdge());
                        double translatedEndPoint = yAxis.java2DToValue((float) panEndPoint.getY(), scaledDataArea,
                                vvp.getRangeAxisEdge());
                        double dY = translatedStartPoint - translatedEndPoint;

                        double oldMin = yAxis.getLowerBound();
                        double newMin = oldMin + dY;

                        double oldMax = yAxis.getUpperBound();
                        double newMax = oldMax + dY;

                        // do not pan out of range
                        if (newMin >= yMin && newMax <= yMax) {
                            yAxis.setLowerBound(newMin);
                            yAxis.setUpperBound(newMax);
                        }
                    }
                }

                // for the next time
                this.panStartPoint = panEndPoint;
            }
        } catch (Exception e) {
            MsgBox.error(e.getMessage());
        }
    }

    public void mousePressed(MouseEvent event) {
        try {
            if (chartPanButton.isSelected()
                    || chartPanButton.isEnabled() && SwingUtilities.isRightMouseButton(event)) {
                Rectangle2D dataArea = this.chartPanel.getScaledDataArea();
                Point2D point = event.getPoint();
                if (dataArea.contains(point)) {
                    setPanMode(true);
                    panStartPoint = point;
                }
            }
        } catch (Exception e) {
            MsgBox.error(e.getMessage());
        }
    }

    /**
     * Handles a mouse released event (stops panning).
     * 
     * @param event  the event.
     */
    public void mouseReleased(MouseEvent event) {
        try {
            this.panStartPoint = null; // stop panning
            if (!chartPanButton.isSelected()) {
                setPanMode(false);
            }
        } catch (Exception e) {
            MsgBox.error(e.getMessage());
        }
    }

    /** 
     * Handles a mouse clicked event, in this case by ignoring it.
     * 
     * @param event  the event.
     */
    public void mouseClicked(MouseEvent event) {
        // ignored
    }

    /** 
     * Handles a mouse moved event, in this case by ignoring it.
     * 
     * @param event  the event.
     */
    public void mouseMoved(MouseEvent event) {
        // ignored
    }

    /** 
     * Handles a mouse entered event, in this case by ignoring it.
     * 
     * @param event  the event.
     */
    public void mouseEntered(MouseEvent event) {
        // ignored
    }

    /** 
     * Handles a mouse exited event, in this case by ignoring it.
     * 
     * @param event  the event.
     */
    public void mouseExited(MouseEvent event) {
        // ignored
    }

}