001/* ===========================================================
002 * JFreeChart : a free chart library for the Java(tm) platform
003 * ===========================================================
004 *
005 * (C) Copyright 2000-2014, by Object Refinery Limited and Contributors.
006 *
007 * Project Info:  http://www.jfree.org/jfreechart/index.html
008 *
009 * This library is free software; you can redistribute it and/or modify it
010 * under the terms of the GNU Lesser General Public License as published by
011 * the Free Software Foundation; either version 2.1 of the License, or
012 * (at your option) any later version.
013 *
014 * This library is distributed in the hope that it will be useful, but
015 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
016 * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
017 * License for more details.
018 *
019 * You should have received a copy of the GNU Lesser General Public
020 * License along with this library; if not, write to the Free Software
021 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301,
022 * USA.
023 *
024 * [Oracle and Java are registered trademarks of Oracle and/or its affiliates. 
025 * Other names may be trademarks of their respective owners.]
026 *
027 * ------------------
028 * ZoomHandlerFX.java
029 * ------------------
030 * (C) Copyright 2014, by Object Refinery Limited and Contributors.
031 *
032 * Original Author:  David Gilbert (for Object Refinery Limited);
033 * Contributor(s):   -;
034 *
035 * Changes:
036 * --------
037 * 25-Jun-2014 : Version 1 (DG);
038 *
039 */
040
041package org.jfree.chart.fx.interaction;
042
043import java.awt.geom.Point2D;
044import java.awt.geom.Rectangle2D;
045import javafx.scene.input.MouseEvent;
046import org.jfree.chart.fx.ChartCanvas;
047import org.jfree.chart.fx.ChartViewer;
048import org.jfree.chart.plot.Plot;
049import org.jfree.chart.plot.PlotRenderingInfo;
050import org.jfree.chart.plot.Zoomable;
051import org.jfree.util.ShapeUtilities;
052
053/**
054 * Handles drag zooming of charts on a {@link ChartCanvas}.  This 
055 * handler should be configured with the required modifier keys and installed 
056 * as a live handler (not an auxiliary handler).  This handler only works for
057 * a <b>ChartCanvas</b> that is embedded in a {@link ChartViewer}, since it 
058 * relies on the <b>ChartViewer</b> for drawing the zoom rectangle.
059 * 
060 * <p>THE API FOR THIS CLASS IS SUBJECT TO CHANGE IN FUTURE RELEASES.  This is
061 * so that we can incorporate feedback on the (new) JavaFX support in 
062 * JFreeChart.</p>
063 * 
064 * @since 1.0.18
065 */
066public class ZoomHandlerFX extends AbstractMouseHandlerFX {
067
068    /** The viewer is used to overlay the zoom rectangle. */
069    private ChartViewer viewer;
070    
071    /** The starting point for the zoom. */
072    private Point2D startPoint;
073    
074    /**
075     * Creates a new instance with no modifier keys required.
076     * 
077     * @param id  the handler ID (<code>null</code> not permitted).
078     * @param parent  the chart viewer.
079     */
080    public ZoomHandlerFX(String id, ChartViewer parent) { 
081        this(id, parent, false, false, false, false);
082    }
083    
084    /**
085     * Creates a new instance that will be activated using the specified 
086     * combination of modifier keys.
087     * 
088     * @param id  the handler ID (<code>null</code> not permitted).
089     * @param parent  the chart viewer.
090     * @param altKey  require ALT key?
091     * @param ctrlKey  require CTRL key?
092     * @param metaKey  require META key?
093     * @param shiftKey   require SHIFT key?
094     */
095    public ZoomHandlerFX(String id, ChartViewer parent, boolean altKey, 
096            boolean ctrlKey, boolean metaKey, boolean shiftKey) {
097        super(id, altKey, ctrlKey, metaKey, shiftKey);
098        this.viewer = parent;
099    }
100    
101    /**
102     * Handles a mouse pressed event by recording the initial mouse pointer
103     * location.
104     * 
105     * @param canvas  the JavaFX canvas (<code>null</code> not permitted).
106     * @param e  the mouse event (<code>null</code> not permitted).
107     */
108    @Override
109    public void handleMousePressed(ChartCanvas canvas, MouseEvent e) {
110        Point2D pt = new Point2D.Double(e.getX(), e.getY());
111        Rectangle2D dataArea = canvas.findDataArea(pt);
112        if (dataArea != null) {
113            this.startPoint = ShapeUtilities.getPointInRectangle(e.getX(),
114                    e.getY(), dataArea);
115        } else {
116            this.startPoint = null;
117            canvas.clearLiveHandler();
118        }
119    }
120    
121    /**
122     * Handles a mouse dragged event by updating the zoom rectangle displayed
123     * in the ChartViewer.
124     * 
125     * @param canvas  the JavaFX canvas (<code>null</code> not permitted).
126     * @param e  the mouse event (<code>null</code> not permitted).
127     */
128    @Override
129    public void handleMouseDragged(ChartCanvas canvas, MouseEvent e) {
130        if (this.startPoint == null) {
131            //no initial zoom rectangle exists but the handler is set
132            //as life handler unregister
133            canvas.clearLiveHandler();
134            return;
135        }
136
137        boolean hZoom, vZoom;
138        Plot p = canvas.getChart().getPlot();
139        if (!(p instanceof Zoomable)) {
140            return;
141        }
142        Zoomable z = (Zoomable) p;
143        if (z.getOrientation().isHorizontal()) {
144            hZoom = z.isRangeZoomable();
145            vZoom = z.isDomainZoomable();
146        } else {
147            hZoom = z.isDomainZoomable();
148            vZoom = z.isRangeZoomable();
149        }
150        Rectangle2D dataArea = canvas.findDataArea(this.startPoint);
151        
152        double x = this.startPoint.getX();
153        double y = this.startPoint.getY();
154        double w = 0;
155        double h = 0;
156        if (hZoom && vZoom) {
157            // selected rectangle shouldn't extend outside the data area...
158            double xmax = Math.min(e.getX(), dataArea.getMaxX());
159            double ymax = Math.min(e.getY(), dataArea.getMaxY());
160            w = xmax - this.startPoint.getX();
161            h = ymax - this.startPoint.getY();
162        }
163        else if (hZoom) {
164            double xmax = Math.min(e.getX(), dataArea.getMaxX());
165            y = dataArea.getMinY();
166            w = xmax - this.startPoint.getX();
167            h = dataArea.getHeight();
168        }
169        else if (vZoom) {
170            double ymax = Math.min(e.getY(), dataArea.getMaxY());
171            x = dataArea.getMinX();
172            w = dataArea.getWidth();
173            h = ymax - this.startPoint.getY();
174        }
175        viewer.showZoomRectangle(x, y, w, h);
176    }
177
178    @Override
179    public void handleMouseReleased(ChartCanvas canvas, MouseEvent e) {  
180        Plot p = canvas.getChart().getPlot();
181        if (!(p instanceof Zoomable)) {
182            return;
183        }
184        boolean hZoom, vZoom;
185        Zoomable z = (Zoomable) p;
186        if (z.getOrientation().isHorizontal()) {
187            hZoom = z.isRangeZoomable();
188            vZoom = z.isDomainZoomable();
189        } else {
190            hZoom = z.isDomainZoomable();
191            vZoom = z.isRangeZoomable();
192        }
193
194        boolean zoomTrigger1 = hZoom && Math.abs(e.getX()
195                - this.startPoint.getX()) >= 10;
196        boolean zoomTrigger2 = vZoom && Math.abs(e.getY()
197                - this.startPoint.getY()) >= 10;
198        if (zoomTrigger1 || zoomTrigger2) {
199            Point2D endPoint = new Point2D.Double(e.getX(), e.getY());
200            PlotRenderingInfo pri = canvas.getRenderingInfo().getPlotInfo();
201            if ((hZoom && (e.getX() < this.startPoint.getX()))
202                    || (vZoom && (e.getY() < this.startPoint.getY()))) {
203                boolean saved = p.isNotify();
204                p.setNotify(false);
205                z.zoomDomainAxes(0, pri, endPoint);
206                z.zoomRangeAxes(0, pri, endPoint);
207                p.setNotify(saved);
208            } else {
209                double x = this.startPoint.getX();
210                double y = this.startPoint.getY();
211                double w = e.getX() - x;
212                double h = e.getY() - y;
213                Rectangle2D dataArea = canvas.findDataArea(this.startPoint);
214                double maxX = dataArea.getMaxX();
215                double maxY = dataArea.getMaxY();
216                // for mouseReleased event, (horizontalZoom || verticalZoom)
217                // will be true, so we can just test for either being false;
218                // otherwise both are true
219                if (!vZoom) {
220                    y = dataArea.getMinY();
221                    w = Math.min(w, maxX - this.startPoint.getX());
222                    h = dataArea.getHeight();
223                }
224                else if (!hZoom) {
225                    x = dataArea.getMinX();
226                    w = dataArea.getWidth();
227                    h = Math.min(h, maxY - this.startPoint.getY());
228                }
229                else {
230                    w = Math.min(w, maxX - this.startPoint.getX());
231                    h = Math.min(h, maxY - this.startPoint.getY());
232                }
233                Rectangle2D zoomArea = new Rectangle2D.Double(x, y, w, h);
234                
235                boolean saved = p.isNotify();
236                p.setNotify(false);
237                double pw0 = percentW(x, dataArea);
238                double pw1 = percentW(x + w, dataArea);
239                double ph0 = percentH(y, dataArea);
240                double ph1 = percentH(y + h, dataArea);
241                PlotRenderingInfo info 
242                        = this.viewer.getRenderingInfo().getPlotInfo();
243                if (z.getOrientation().isVertical()) {
244                    z.zoomDomainAxes(pw0, pw1, info, endPoint);
245                    z.zoomRangeAxes(1 - ph1, 1 - ph0, info, endPoint);
246                } else {
247                    z.zoomRangeAxes(pw0, pw1, info, endPoint);
248                    z.zoomDomainAxes(1 - ph1, 1 - ph0, info, endPoint);
249                }
250                p.setNotify(saved);
251                
252            }
253        }
254        viewer.hideZoomRectangle();
255        this.startPoint = null;
256        canvas.clearLiveHandler();
257    }
258
259    private double percentW(double x, Rectangle2D r) {
260        return (x - r.getMinX()) / r.getWidth();
261    }
262    
263    private double percentH(double y, Rectangle2D r) {
264        return (y - r.getMinY()) / r.getHeight();
265    }
266}