com.rapidminer.gui.new_plotter.engine.jfreechart.link_and_brush.LinkAndBrushChartPanel.java Source code

Java tutorial

Introduction

Here is the source code for com.rapidminer.gui.new_plotter.engine.jfreechart.link_and_brush.LinkAndBrushChartPanel.java

Source

/**
 * Copyright (C) 2001-2015 by RapidMiner and the contributors
 *
 * Complete list of developers available at our web site:
 *
 *      http://rapidminer.com
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero 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 Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program.  If not, see http://www.gnu.org/licenses/.
 */
package com.rapidminer.gui.new_plotter.engine.jfreechart.link_and_brush;

import com.rapidminer.gui.new_plotter.engine.jfreechart.link_and_brush.listener.LinkAndBrushSelection;
import com.rapidminer.gui.new_plotter.engine.jfreechart.link_and_brush.listener.LinkAndBrushSelection.SelectionType;
import com.rapidminer.gui.new_plotter.engine.jfreechart.link_and_brush.listener.LinkAndBrushSelectionListener;
import com.rapidminer.gui.new_plotter.engine.jfreechart.link_and_brush.plots.LinkAndBrushPlot;
import com.rapidminer.gui.plotter.CoordinateTransformation;
import com.rapidminer.gui.plotter.NullCoordinateTransformation;
import com.rapidminer.tools.container.Pair;

import java.awt.Color;
import java.awt.Cursor;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.GraphicsConfiguration;
import java.awt.Image;
import java.awt.Insets;
import java.awt.Paint;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.Transparency;
import java.awt.event.MouseEvent;
import java.awt.geom.AffineTransform;
import java.awt.geom.Point2D;
import java.awt.geom.Rectangle2D;
import java.lang.ref.WeakReference;
import java.lang.reflect.Field;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;

import javax.swing.JPopupMenu;
import javax.swing.SwingUtilities;

import org.jfree.chart.ChartPanel;
import org.jfree.chart.JFreeChart;
import org.jfree.chart.panel.Overlay;
import org.jfree.chart.plot.CategoryPlot;
import org.jfree.chart.plot.Pannable;
import org.jfree.chart.plot.Plot;
import org.jfree.chart.plot.PlotOrientation;
import org.jfree.chart.plot.PlotRenderingInfo;
import org.jfree.chart.plot.XYPlot;
import org.jfree.data.Range;

/**
 * A Swing GUI component for displaying a {@link JFreeChart} object. The chart will be buffered.
 * <P>
 * 
 * @author Nils Woehler
 * 
 */
public class LinkAndBrushChartPanel extends ChartPanel {

    private static final long serialVersionUID = 1L;
    private boolean zoomOnLinkAndBrushSelection;
    private boolean blockSelectionOrZoom = false;

    /**
     * This is a transformation which transforms the components coordinates to screen coordinates.
     * If is null, no transformation is needed.
     */
    private transient CoordinateTransformation coordinateTransformation = new NullCoordinateTransformation();

    private transient List<WeakReference<LinkAndBrushSelectionListener>> listeners = new LinkedList<WeakReference<LinkAndBrushSelectionListener>>();

    public LinkAndBrushChartPanel(JFreeChart chart, boolean zoomOnLinkAndBrushSelection) {
        super(chart, 600, 400, 200, 133, DEFAULT_MAXIMUM_DRAW_WIDTH, DEFAULT_MAXIMUM_DRAW_HEIGHT,
                DEFAULT_BUFFER_USED, false, // copy
                false, // properties
                false, // save
                false, // print
                false, // zoom
                true); // tooltips

        this.zoomOnLinkAndBrushSelection = zoomOnLinkAndBrushSelection;
        setInitialDelay(200);

        setMouseWheelEnabled(false);
    }

    public LinkAndBrushChartPanel(JFreeChart chart, int defaultWidth, int defaultHeigth, int minDrawWidth,
            int minDrawHeigth, boolean zoomOnLinkAndBrush) {
        super(chart, defaultWidth, defaultHeigth, minDrawWidth, minDrawHeigth, DEFAULT_MAXIMUM_DRAW_WIDTH,
                DEFAULT_MAXIMUM_DRAW_HEIGHT, DEFAULT_BUFFER_USED, false, // copy
                false, // properties
                false, // save
                false, // print
                false, // zoom
                true); // tooltips

        this.zoomOnLinkAndBrushSelection = zoomOnLinkAndBrush;
        setInitialDelay(200);

        setMouseWheelEnabled(false);
    }

    public LinkAndBrushChartPanel(JFreeChart chart, int defaultWidth, int defaultHeigth, int minDrawWidth,
            int minDrawHeigth, boolean zoomOnLinkAndBrush, boolean useBuffer) {
        super(chart, defaultWidth, defaultHeigth, minDrawWidth, minDrawHeigth, DEFAULT_MAXIMUM_DRAW_WIDTH,
                DEFAULT_MAXIMUM_DRAW_HEIGHT, useBuffer, false, // copy
                false, // properties
                false, // save
                false, // print
                false, // zoom
                true); // tooltips

        this.zoomOnLinkAndBrushSelection = zoomOnLinkAndBrush;

        setMouseWheelEnabled(false);
    }

    @Override
    public void restoreAutoBounds() {
        Plot plot = getChart().getPlot();
        if (plot == null) {
            return;
        }
        // here we tweak the notify flag on the plot so that only
        // one notification happens even though we update multiple
        // axes...
        boolean savedNotify = plot.isNotify();
        plot.setNotify(false);

        if (plot instanceof LinkAndBrushPlot) {

            LinkAndBrushPlot LABPlot = (LinkAndBrushPlot) plot;

            List<Pair<Integer, Range>> zoomedDomainAxisRanges = new LinkedList<Pair<Integer, Range>>();
            List<Pair<Integer, Range>> zoomedRangeAxisRanges = new LinkedList<Pair<Integer, Range>>();

            zoomedDomainAxisRanges.addAll(LABPlot.restoreAutoDomainAxisBounds(zoomOnLinkAndBrushSelection));
            zoomedRangeAxisRanges.addAll(LABPlot.restoreAutoRangeAxisBounds(zoomOnLinkAndBrushSelection));

            if (zoomOnLinkAndBrushSelection) {
                informLinkAndBrushSelectionListeners(new LinkAndBrushSelection(SelectionType.RESTORE_AUTO_BOUNDS,
                        zoomedDomainAxisRanges, zoomedRangeAxisRanges));
            } else {
                informLinkAndBrushSelectionListeners(new LinkAndBrushSelection(SelectionType.RESTORE_SELECTION,
                        zoomedDomainAxisRanges, zoomedRangeAxisRanges));
            }

        } else {
            restoreAutoDomainBounds();
            restoreAutoRangeBounds();
        }

        plot.setNotify(savedNotify);
    }

    @Override
    public void zoom(Rectangle2D selection) {
        // get the origin of the zoom selection in the Java2D space used for
        // drawing the chart (that is, before any scaling to fit the panel)
        Point2D selectOrigin = translateScreenToJava2D(
                new Point((int) Math.ceil(selection.getX()), (int) Math.ceil(selection.getY())));
        PlotRenderingInfo plotInfo = getChartRenderingInfo().getPlotInfo();
        Rectangle2D scaledDataArea = getScreenDataArea((int) selection.getCenterX(), (int) selection.getCenterY());
        if ((selection.getHeight() > 0) && (selection.getWidth() > 0)) {

            double hLower = (selection.getMinX() - scaledDataArea.getMinX()) / scaledDataArea.getWidth();
            double hUpper = (selection.getMaxX() - scaledDataArea.getMinX()) / scaledDataArea.getWidth();
            double vLower = (scaledDataArea.getMaxY() - selection.getMaxY()) / scaledDataArea.getHeight();
            double vUpper = (scaledDataArea.getMaxY() - selection.getMinY()) / scaledDataArea.getHeight();

            Plot p = getChart().getPlot();
            if (p instanceof LinkAndBrushPlot) {

                PlotOrientation orientation = null;
                if (p instanceof XYPlot) {
                    XYPlot xyPlot = (XYPlot) p;
                    orientation = xyPlot.getOrientation();
                }
                if (p instanceof CategoryPlot) {
                    CategoryPlot categoryPlot = (CategoryPlot) p;
                    orientation = categoryPlot.getOrientation();
                }

                // here we tweak the notify flag on the plot so that only
                // one notification happens even though we update multiple
                // axes...

                boolean savedNotify = p.isNotify();
                p.setNotify(false);
                LinkAndBrushPlot LABPlot = (LinkAndBrushPlot) p;

                List<Pair<Integer, Range>> zoomedDomainAxisRanges = new LinkedList<Pair<Integer, Range>>();
                List<Pair<Integer, Range>> zoomedRangeAxisRanges = new LinkedList<Pair<Integer, Range>>();

                if (orientation == PlotOrientation.HORIZONTAL) {
                    zoomedDomainAxisRanges
                            .addAll(LABPlot.calculateDomainAxesZoom(vLower, vUpper, zoomOnLinkAndBrushSelection));
                    zoomedRangeAxisRanges.addAll(LABPlot.calculateRangeAxesZoom(hLower, hUpper, plotInfo,
                            selectOrigin, zoomOnLinkAndBrushSelection));
                } else {
                    zoomedDomainAxisRanges
                            .addAll(LABPlot.calculateDomainAxesZoom(hLower, hUpper, zoomOnLinkAndBrushSelection));
                    zoomedRangeAxisRanges.addAll(LABPlot.calculateRangeAxesZoom(vLower, vUpper, plotInfo,
                            selectOrigin, zoomOnLinkAndBrushSelection));
                }
                p.setNotify(savedNotify);

                if (zoomOnLinkAndBrushSelection) {
                    informLinkAndBrushSelectionListeners(new LinkAndBrushSelection(SelectionType.ZOOM_IN,
                            zoomedDomainAxisRanges, zoomedRangeAxisRanges));
                } else {
                    informLinkAndBrushSelectionListeners(new LinkAndBrushSelection(SelectionType.SELECTION,
                            zoomedDomainAxisRanges, zoomedRangeAxisRanges));
                }

            } else {
                super.zoom(selection);
            }
        }
    }

    /**
     * If set to <code>true</code>, will zoom on selection, otherwise will do a selection.
     * 
     * @param zoomOnLinkAndBrushSelection
     */
    public void setZoomOnLinkAndBrushSelection(boolean zoomOnLinkAndBrushSelection) {
        this.zoomOnLinkAndBrushSelection = zoomOnLinkAndBrushSelection;
    }

    /**
     * Returns whether the panel will zoom on selection or not.
     * 
     * @return
     */
    public boolean getZoomOnLinkAndBrushSelection() {
        return zoomOnLinkAndBrushSelection;
    }

    @Override
    public void paintComponent(Graphics g) {
        super.paintComponent(g);
        if (getChart() == null) {
            return;
        }
        Graphics2D g2 = (Graphics2D) g.create();

        // first determine the size of the chart rendering area...
        Dimension size = getSize();
        Insets insets = getInsets();
        Rectangle2D available = new Rectangle2D.Double(insets.left, insets.top,
                size.getWidth() - insets.left - insets.right, size.getHeight() - insets.top - insets.bottom);

        // work out if scaling is required...
        boolean scale = false;
        double drawWidth = available.getWidth();
        double drawHeight = available.getHeight();
        setChartFieldValue(getChartFieldByName("scaleX"), 1.0);
        // this.scaleX = 1.0;
        setChartFieldValue(getChartFieldByName("scaleY"), 1.0);
        // this.scaleY = 1.0;

        if (drawWidth < getMinimumDrawWidth()) {
            setChartFieldValue(getChartFieldByName("scaleX"), drawWidth / getMinimumDrawWidth());
            // this.scaleX = drawWidth / getMinimumDrawWidth();
            drawWidth = getMinimumDrawWidth();
            scale = true;
        } else if (drawWidth > getMaximumDrawWidth()) {
            setChartFieldValue(getChartFieldByName("scaleX"), drawWidth / getMaximumDrawWidth());
            // this.scaleX = drawWidth / getMaximumDrawWidth();
            drawWidth = getMaximumDrawWidth();
            scale = true;
        }

        if (drawHeight < getMinimumDrawHeight()) {
            setChartFieldValue(getChartFieldByName("scaleY"), drawHeight / getMinimumDrawHeight());
            // this.scaleY = drawHeight / getMinimumDrawHeight();
            drawHeight = getMinimumDrawHeight();
            scale = true;
        } else if (drawHeight > getMaximumDrawHeight()) {
            setChartFieldValue(getChartFieldByName("scaleY"), drawHeight / getMaximumDrawHeight());
            // this.scaleY = drawHeight / getMaximumDrawHeight();
            drawHeight = getMaximumDrawHeight();
            scale = true;
        }

        Rectangle2D chartArea = new Rectangle2D.Double(0.0, 0.0, drawWidth, drawHeight);

        // are we using the chart buffer?
        if ((Boolean) getChartFieldValueByName("useBuffer")) {

            // do we need to resize the buffer?
            if ((getChartFieldValueByName("chartBuffer") == null)
                    || ((Integer) getChartFieldValueByName("chartBufferWidth") != available.getWidth())
                    || ((Integer) getChartFieldValueByName("chartBufferHeight") != available.getHeight())) {
                setChartFieldValue(getChartFieldByName("chartBufferWidth"), (int) available.getWidth());
                // this.chartBufferWidth = (int) available.getWidth();
                setChartFieldValue(getChartFieldByName("chartBufferHeight"), (int) available.getHeight());
                // this.chartBufferHeight = (int) available.getHeight();
                GraphicsConfiguration gc = g2.getDeviceConfiguration();
                setChartFieldValue(getChartFieldByName("chartBuffer"),
                        gc.createCompatibleImage((Integer) getChartFieldValueByName("chartBufferWidth"),
                                (Integer) getChartFieldValueByName("chartBufferHeight"), Transparency.TRANSLUCENT));
                // this.chartBuffer = gc.createCompatibleImage(this.chartBufferWidth,
                // this.chartBufferHeight, Transparency.TRANSLUCENT);
                setRefreshBuffer(true);
            }

            // do we need to redraw the buffer?
            if (getRefreshBuffer()) {

                setRefreshBuffer(false); // clear the flag

                Rectangle2D bufferArea = new Rectangle2D.Double(0, 0,
                        (Integer) getChartFieldValueByName("chartBufferWidth"),
                        (Integer) getChartFieldValueByName("chartBufferHeight"));

                Graphics2D bufferG2 = (Graphics2D) ((Image) getChartFieldValueByName("chartBuffer")).getGraphics();
                Rectangle r = new Rectangle(0, 0, (Integer) getChartFieldValueByName("chartBufferWidth"),
                        (Integer) getChartFieldValueByName("chartBufferHeight"));
                bufferG2.setPaint(getBackground());
                bufferG2.fill(r);
                if (scale) {
                    AffineTransform saved = bufferG2.getTransform();
                    AffineTransform st = AffineTransform.getScaleInstance(
                            (Double) getChartFieldValueByName("scaleX"),
                            (Double) getChartFieldValueByName("scaleY"));
                    bufferG2.transform(st);
                    getChart().draw(bufferG2, chartArea, getAnchor(), getChartRenderingInfo());
                    bufferG2.setTransform(saved);
                } else {
                    getChart().draw(bufferG2, bufferArea, getAnchor(), getChartRenderingInfo());
                }

            }

            // zap the buffer onto the panel...
            g2.drawImage((Image) getChartFieldValueByName("chartBuffer"), insets.left, insets.top, this);

        }

        // or redrawing the chart every time...
        else {

            AffineTransform saved = g2.getTransform();
            g2.translate(insets.left, insets.top);
            if (scale) {
                AffineTransform st = AffineTransform.getScaleInstance((Double) getChartFieldValueByName("scaleX"),
                        (Double) getChartFieldValueByName("scaleY"));
                g2.transform(st);
            }
            getChart().draw(g2, chartArea, getAnchor(), getChartRenderingInfo());
            g2.setTransform(saved);

        }

        Iterator iterator = ((List) getChartFieldValueByName("overlays")).iterator();
        while (iterator.hasNext()) {
            Overlay overlay = (Overlay) iterator.next();
            overlay.paintOverlay(g2, this);
        }

        // redraw the zoom rectangle (if present) - if useBuffer is false,
        // we use XOR so we can XOR the rectangle away again without redrawing
        // the chart
        drawZoomRectangle(g2, !(Boolean) getChartFieldValueByName("useBuffer"));

        g2.dispose();

        setAnchor(null);
        setVerticalTraceLine(null);
        setHorizontalTraceLine(null);
    }

    @SuppressWarnings("unchecked")
    public List<Overlay> getOverlayList() {
        return ((List<Overlay>) getChartFieldValueByName("overlays"));
    }

    public void setOverlayList(List<Overlay> list) {
        setChartFieldValue(getChartFieldByName("overlays"), list);
    }

    @Override
    public void mousePressed(MouseEvent e) {
        // this is used to only allow left mouse button zoom / selection
        if (!SwingUtilities.isLeftMouseButton(e)) {
            blockSelectionOrZoom = true;
        } else {
            blockSelectionOrZoom = false;
        }
        super.mousePressed(e);
    }

    @Override
    public void mouseDragged(MouseEvent e) {
        // when not allowed to zoom / select, return
        if (blockSelectionOrZoom) {
            return;
        }
        // if the popup menu has already been triggered, then ignore dragging...
        if (getChartFieldValueByName("popup") != null
                && ((JPopupMenu) getChartFieldValueByName("popup")).isShowing()) {
            return;
        }

        // handle panning if we have a start point
        if (getChartFieldValueByName("panLast") != null) {
            double dx = e.getX() - ((Point) getChartFieldValueByName("panLast")).getX();
            double dy = e.getY() - ((Point) getChartFieldValueByName("panLast")).getY();
            if (dx == 0.0 && dy == 0.0) {
                return;
            }
            double wPercent = -dx / ((Double) getChartFieldValueByName("panW"));
            double hPercent = dy / ((Double) getChartFieldValueByName("panH"));
            boolean old = getChart().getPlot().isNotify();
            getChart().getPlot().setNotify(false);
            Pannable p = (Pannable) getChart().getPlot();
            if (p.getOrientation() == PlotOrientation.VERTICAL) {
                p.panDomainAxes(wPercent, getChartRenderingInfo().getPlotInfo(),
                        (Point) getChartFieldValueByName("panLast"));
                p.panRangeAxes(hPercent, getChartRenderingInfo().getPlotInfo(),
                        (Point) getChartFieldValueByName("panLast"));
            } else {
                p.panDomainAxes(hPercent, getChartRenderingInfo().getPlotInfo(),
                        (Point) getChartFieldValueByName("panLast"));
                p.panRangeAxes(wPercent, getChartRenderingInfo().getPlotInfo(),
                        (Point) getChartFieldValueByName("panLast"));
            }
            setChartFieldValue((getChartFieldByName("panLast")), e.getPoint());
            getChart().getPlot().setNotify(old);
            return;
        }

        // if no initial zoom point was set, ignore dragging...
        if (getChartFieldValueByName("zoomPoint") == null) {
            return;
        }
        Graphics2D g2 = (Graphics2D) getGraphics();

        // erase the previous zoom rectangle (if any). We only need to do
        // this is we are using XOR mode, which we do when we're not using
        // the buffer (if there is a buffer, then at the end of this method we
        // just trigger a repaint)
        if (!(Boolean) getChartFieldValueByName("useBuffer")) {
            drawZoomRectangle(g2, true);
        }

        boolean hZoom = false;
        boolean vZoom = false;
        if ((PlotOrientation) getChartFieldValueByName("orientation") == PlotOrientation.HORIZONTAL) {
            hZoom = (Boolean) getChartFieldValueByName("rangeZoomable");
            vZoom = (Boolean) getChartFieldValueByName("domainZoomable");
        } else {
            hZoom = (Boolean) getChartFieldValueByName("domainZoomable");
            vZoom = (Boolean) getChartFieldValueByName("rangeZoomable");
        }
        Point2D zoomPoint = (Point2D) getChartFieldValueByName("zoomPoint");
        Rectangle2D scaledDataArea = getScreenDataArea((int) zoomPoint.getX(), (int) zoomPoint.getY());
        if (hZoom && vZoom) {
            // selected rectangle shouldn't extend outside the data area...
            double xmax = Math.min(e.getX(), scaledDataArea.getMaxX());
            double ymax = Math.min(e.getY(), scaledDataArea.getMaxY());
            setChartFieldValue(getChartFieldByName("zoomRectangle"), new Rectangle2D.Double(zoomPoint.getX(),
                    zoomPoint.getY(), xmax - zoomPoint.getX(), ymax - zoomPoint.getY()));
        } else if (hZoom) {
            double xmax = Math.min(e.getX(), scaledDataArea.getMaxX());
            setChartFieldValue(getChartFieldByName("zoomRectangle"), new Rectangle2D.Double(zoomPoint.getX(),
                    scaledDataArea.getMinY(), xmax - zoomPoint.getX(), scaledDataArea.getHeight()));
        } else if (vZoom) {
            double ymax = Math.min(e.getY(), scaledDataArea.getMaxY());
            setChartFieldValue(getChartFieldByName("zoomRectangle"),
                    new Rectangle2D.Double(scaledDataArea.getMinX(), zoomPoint.getY(), scaledDataArea.getWidth(),
                            ymax - zoomPoint.getY()));
        }

        // Draw the new zoom rectangle...
        if ((Boolean) getChartFieldValueByName("useBuffer")) {
            repaint();
        } else {
            // with no buffer, we use XOR to draw the rectangle "over" the
            // chart...
            drawZoomRectangle(g2, true);
        }
        g2.dispose();

    }

    @Override
    public void mouseReleased(MouseEvent e) {

        // if we've been panning, we need to reset now that the mouse is
        // released...
        Rectangle2D zoomRectangle = (Rectangle2D) getChartFieldValueByName("zoomRectangle");
        Point2D zoomPoint = (Point2D) getChartFieldValueByName("zoomPoint");
        if (getChartFieldValueByName("panLast") != null) {
            setChartFieldValue((getChartFieldByName("panLast")), null);
            setCursor(Cursor.getDefaultCursor());
        } else if (zoomRectangle != null) {
            boolean hZoom = false;
            boolean vZoom = false;
            if ((PlotOrientation) getChartFieldValueByName("orientation") == PlotOrientation.HORIZONTAL) {
                hZoom = (Boolean) getChartFieldValueByName("rangeZoomable");
                vZoom = (Boolean) getChartFieldValueByName("domainZoomable");
            } else {
                hZoom = (Boolean) getChartFieldValueByName("domainZoomable");
                vZoom = (Boolean) getChartFieldValueByName("rangeZoomable");
            }

            boolean zoomTrigger1 = hZoom && Math
                    .abs(e.getX() - zoomPoint.getX()) >= (Integer) getChartFieldValueByName("zoomTriggerDistance");
            boolean zoomTrigger2 = vZoom && Math
                    .abs(e.getY() - zoomPoint.getY()) >= (Integer) getChartFieldValueByName("zoomTriggerDistance");
            if (zoomTrigger1 || zoomTrigger2) {
                if ((hZoom && (e.getX() < zoomPoint.getX())) || (vZoom && (e.getY() < zoomPoint.getY()))) {
                    restoreAutoBounds();
                } else {
                    double x, y, w, h;
                    Rectangle2D screenDataArea = getScreenDataArea((int) zoomPoint.getX(), (int) zoomPoint.getY());
                    double maxX = screenDataArea.getMaxX();
                    double maxY = screenDataArea.getMaxY();
                    // for mouseReleased event, (horizontalZoom || verticalZoom)
                    // will be true, so we can just test for either being false;
                    // otherwise both are true
                    if (!vZoom) {
                        x = zoomPoint.getX();
                        y = screenDataArea.getMinY();
                        w = Math.min(zoomRectangle.getWidth(), maxX - zoomPoint.getX());
                        h = screenDataArea.getHeight();
                    } else if (!hZoom) {
                        x = screenDataArea.getMinX();
                        y = zoomPoint.getY();
                        w = screenDataArea.getWidth();
                        h = Math.min(zoomRectangle.getHeight(), maxY - zoomPoint.getY());
                    } else {
                        x = zoomPoint.getX();
                        y = zoomPoint.getY();
                        w = Math.min(zoomRectangle.getWidth(), maxX - zoomPoint.getX());
                        h = Math.min(zoomRectangle.getHeight(), maxY - zoomPoint.getY());
                    }
                    Rectangle2D zoomArea = new Rectangle2D.Double(x, y, w, h);
                    zoom(zoomArea);
                }
                setChartFieldValue(getChartFieldByName("zoomPoint"), null);
                setChartFieldValue(getChartFieldByName("zoomRectangle"), null);
            } else {
                // erase the zoom rectangle
                Graphics2D g2 = (Graphics2D) getGraphics();
                if ((Boolean) getChartFieldValueByName("useBuffer")) {
                    repaint();
                } else {
                    drawZoomRectangle(g2, true);
                }
                g2.dispose();
                setChartFieldValue(getChartFieldByName("zoomPoint"), null);
                setChartFieldValue(getChartFieldByName("zoomRectangle"), null);
            }

        }

        else if (e.isPopupTrigger()) {
            if (getChartFieldValueByName("popup") != null) {
                displayPopupMenu(e.getX(), e.getY());
            }
        }

    }

    /**
     * Draws zoom rectangle (if present). The drawing is performed in XOR mode, therefore when this
     * method is called twice in a row, the second call will completely restore the state of the
     * canvas.
     * 
     * @param g2
     *            the graphics device.
     * @param xor
     *            use XOR for drawing?
     */
    private void drawZoomRectangle(Graphics2D g2, boolean xor) {
        Rectangle2D zoomRectangle = (Rectangle2D) getChartFieldValueByName("zoomRectangle");
        if (zoomRectangle != null) {
            // fix rectangle parameters when chart is transformed
            zoomRectangle = coordinateTransformation.transformRectangle(zoomRectangle, this);
            if (!(coordinateTransformation instanceof NullCoordinateTransformation)) {
                g2 = coordinateTransformation.getTransformedGraphics(this);
            }
            if (xor) {
                // Set XOR mode to draw the zoom rectangle
                g2.setXORMode(Color.gray);
            }
            if ((Boolean) getChartFieldValueByName("fillZoomRectangle")) {
                g2.setPaint((Paint) getChartFieldValueByName("zoomFillPaint"));
                g2.fill(zoomRectangle);
            } else {
                g2.setPaint((Paint) getChartFieldValueByName("zoomOutlinePaint"));
                g2.draw(zoomRectangle);
            }
            if (xor) {
                // Reset to the default 'overwrite' mode
                g2.setPaintMode();
            }
        }
    }

    /**
     * Add a {@link LinkAndBrushSelectionListener}. The listener is saved as a {@link WeakReference}
     * . Thus listener must not be hidden (anonymous) classes!
     */
    public void addLinkAndBrushSelectionListener(LinkAndBrushSelectionListener l) {
        listeners.add(new WeakReference<LinkAndBrushSelectionListener>(l));
    }

    /**
     * Remove a {@link LinkAndBrushSelectionListener}.
     * 
     * @param l
     */
    public void removeLinkAndBrushSelectionListener(LinkAndBrushSelectionListener l) {
        Iterator<WeakReference<LinkAndBrushSelectionListener>> it = listeners.iterator();
        while (it.hasNext()) {
            WeakReference<LinkAndBrushSelectionListener> wrl = it.next();
            LinkAndBrushSelectionListener listener = wrl.get();
            if (listener == l || listener == null) {
                it.remove();
            }
        }
    }

    /**
     * Informs all {@link LinkAndBrushSelectionListener} of a {@link LinkAndBrushSelection}.
     * 
     * @param e
     */
    public void informLinkAndBrushSelectionListeners(LinkAndBrushSelection e) {
        // create a copy to avoid ConcurrentModificationException when informing and remove listener
        // happens
        List<WeakReference<LinkAndBrushSelectionListener>> listenersCopy = new LinkedList<WeakReference<LinkAndBrushSelectionListener>>();
        listenersCopy.addAll(listeners);
        Iterator<WeakReference<LinkAndBrushSelectionListener>> it = listenersCopy.iterator();
        while (it.hasNext()) {
            WeakReference<LinkAndBrushSelectionListener> wrl = it.next();
            LinkAndBrushSelectionListener l = wrl.get();
            if (l != null) {
                l.selectedLinkAndBrushRectangle(e);
            } else {
                it.remove();
            }
        }
    }

    /**
     * Returns the value of a {@link Field} of the {@link ChartPanel}. If the field can not be
     * found, returns {@code null}.
     * 
     * @param name
     *            the name of the {@link Field}
     * @return
     */
    private Object getChartFieldValueByName(String name) {
        try {
            Field field = ChartPanel.class.getDeclaredField(name);
            field.setAccessible(true);
            Object value = field.get(this);
            field.setAccessible(false);
            return value;
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }

    /**
     * Returns the {@link Field} of the {@link ChartPanel}. If the field can not be found, returns
     * {@code null}.
     * 
     * @param name
     *            the name of the {@link Field}
     * @return
     */
    private Field getChartFieldByName(String name) {
        try {
            Field field = ChartPanel.class.getDeclaredField(name);
            field.setAccessible(true);
            return field;
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }

    /**
     * Sets the given value for the given {@link Field}.
     * 
     * @param field
     *            the {@link Field} to change
     * @param value
     *            the new value for the {@link Field}
     */
    private void setChartFieldValue(Field field, Object value) {
        try {
            field.setAccessible(true);
            field.set(this, value);
            field.setAccessible(false);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * This method sets the coordinate transformation for this component.
     */
    public void setCoordinateTransformation(CoordinateTransformation transformation) {
        this.coordinateTransformation = transformation;
    }
}