001/* ================================================
002 * JFreeChart-FX : JavaFX extensions for JFreeChart
003 * ================================================
004 *
005 * (C) Copyright 2017, by Object Refinery Limited and Contributors.
006 *
007 * Project Info:  https://github.com/jfree/jfreechart-fx
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, 2017, by Object Refinery Limited and Contributors.
031 *
032 * Original Author:  David Gilbert (for Object Refinery Limited);
033 * Contributor(s):   -;
034 *
035 */
036
037package org.jfree.chart.fx.interaction;
038
039import java.awt.geom.Point2D;
040import java.awt.geom.Rectangle2D;
041import javafx.scene.input.MouseEvent;
042import org.jfree.chart.fx.ChartCanvas;
043import org.jfree.chart.fx.ChartViewer;
044import org.jfree.chart.plot.Plot;
045import org.jfree.chart.plot.PlotRenderingInfo;
046import org.jfree.chart.plot.Zoomable;
047import org.jfree.chart.util.ShapeUtils;
048
049/**
050 * Handles drag zooming of charts on a {@link ChartCanvas}.  This 
051 * handler should be configured with the required modifier keys and installed 
052 * as a live handler (not an auxiliary handler).  This handler only works for
053 * a <b>ChartCanvas</b> that is embedded in a {@link ChartViewer}, since it 
054 * relies on the <b>ChartViewer</b> for drawing the zoom rectangle.
055 */
056public class ZoomHandlerFX extends AbstractMouseHandlerFX {
057
058    /** The viewer is used to overlay the zoom rectangle. */
059    private ChartViewer viewer;
060    
061    /** The starting point for the zoom. */
062    private Point2D startPoint;
063    
064    /**
065     * Creates a new instance with no modifier keys required.
066     * 
067     * @param id  the handler ID ({@code null} not permitted).
068     * @param parent  the chart viewer.
069     */
070    public ZoomHandlerFX(String id, ChartViewer parent) { 
071        this(id, parent, false, false, false, false);
072    }
073    
074    /**
075     * Creates a new instance that will be activated using the specified 
076     * combination of modifier keys.
077     * 
078     * @param id  the handler ID ({@code null} not permitted).
079     * @param parent  the chart viewer.
080     * @param altKey  require ALT key?
081     * @param ctrlKey  require CTRL key?
082     * @param metaKey  require META key?
083     * @param shiftKey   require SHIFT key?
084     */
085    public ZoomHandlerFX(String id, ChartViewer parent, boolean altKey, 
086            boolean ctrlKey, boolean metaKey, boolean shiftKey) {
087        super(id, altKey, ctrlKey, metaKey, shiftKey);
088        this.viewer = parent;
089    }
090    
091    /**
092     * Handles a mouse pressed event by recording the initial mouse pointer
093     * location.
094     * 
095     * @param canvas  the JavaFX canvas ({@code null} not permitted).
096     * @param e  the mouse event ({@code null} not permitted).
097     */
098    @Override
099    public void handleMousePressed(ChartCanvas canvas, MouseEvent e) {
100        Point2D pt = new Point2D.Double(e.getX(), e.getY());
101        Rectangle2D dataArea = canvas.findDataArea(pt);
102        if (dataArea != null) {
103            this.startPoint = ShapeUtils.getPointInRectangle(e.getX(),
104                    e.getY(), dataArea);
105        } else {
106            this.startPoint = null;
107            canvas.clearLiveHandler();
108        }
109    }
110    
111    /**
112     * Handles a mouse dragged event by updating the zoom rectangle displayed
113     * in the ChartViewer.
114     * 
115     * @param canvas  the JavaFX canvas ({@code null} not permitted).
116     * @param e  the mouse event ({@code null} not permitted).
117     */
118    @Override
119    public void handleMouseDragged(ChartCanvas canvas, MouseEvent e) {
120        if (this.startPoint == null) {
121            //no initial zoom rectangle exists but the handler is set
122            //as life handler unregister
123            canvas.clearLiveHandler();
124            return;
125        }
126
127        boolean hZoom, vZoom;
128        Plot p = canvas.getChart().getPlot();
129        if (!(p instanceof Zoomable)) {
130            return;
131        }
132        Zoomable z = (Zoomable) p;
133        if (z.getOrientation().isHorizontal()) {
134            hZoom = z.isRangeZoomable();
135            vZoom = z.isDomainZoomable();
136        } else {
137            hZoom = z.isDomainZoomable();
138            vZoom = z.isRangeZoomable();
139        }
140        Rectangle2D dataArea = canvas.findDataArea(this.startPoint);
141        
142        double x = this.startPoint.getX();
143        double y = this.startPoint.getY();
144        double w = 0;
145        double h = 0;
146        if (hZoom && vZoom) {
147            // selected rectangle shouldn't extend outside the data area...
148            double xmax = Math.min(e.getX(), dataArea.getMaxX());
149            double ymax = Math.min(e.getY(), dataArea.getMaxY());
150            w = xmax - this.startPoint.getX();
151            h = ymax - this.startPoint.getY();
152        }
153        else if (hZoom) {
154            double xmax = Math.min(e.getX(), dataArea.getMaxX());
155            y = dataArea.getMinY();
156            w = xmax - this.startPoint.getX();
157            h = dataArea.getHeight();
158        }
159        else if (vZoom) {
160            double ymax = Math.min(e.getY(), dataArea.getMaxY());
161            x = dataArea.getMinX();
162            w = dataArea.getWidth();
163            h = ymax - this.startPoint.getY();
164        }
165        this.viewer.showZoomRectangle(x, y, w, h);
166    }
167
168    @Override
169    public void handleMouseReleased(ChartCanvas canvas, MouseEvent e) {  
170        Plot p = canvas.getChart().getPlot();
171        if (!(p instanceof Zoomable)) {
172            return;
173        }
174        boolean hZoom, vZoom;
175        Zoomable z = (Zoomable) p;
176        if (z.getOrientation().isHorizontal()) {
177            hZoom = z.isRangeZoomable();
178            vZoom = z.isDomainZoomable();
179        } else {
180            hZoom = z.isDomainZoomable();
181            vZoom = z.isRangeZoomable();
182        }
183
184        boolean zoomTrigger1 = hZoom && Math.abs(e.getX()
185                - this.startPoint.getX()) >= 10;
186        boolean zoomTrigger2 = vZoom && Math.abs(e.getY()
187                - this.startPoint.getY()) >= 10;
188        if (zoomTrigger1 || zoomTrigger2) {
189            Point2D endPoint = new Point2D.Double(e.getX(), e.getY());
190            PlotRenderingInfo pri = canvas.getRenderingInfo().getPlotInfo();
191            if ((hZoom && (e.getX() < this.startPoint.getX()))
192                    || (vZoom && (e.getY() < this.startPoint.getY()))) {
193                boolean saved = p.isNotify();
194                p.setNotify(false);
195                z.zoomDomainAxes(0, pri, endPoint);
196                z.zoomRangeAxes(0, pri, endPoint);
197                p.setNotify(saved);
198            } else {
199                double x = this.startPoint.getX();
200                double y = this.startPoint.getY();
201                double w = e.getX() - x;
202                double h = e.getY() - y;
203                Rectangle2D dataArea = canvas.findDataArea(this.startPoint);
204                double maxX = dataArea.getMaxX();
205                double maxY = dataArea.getMaxY();
206                // for mouseReleased event, (horizontalZoom || verticalZoom)
207                // will be true, so we can just test for either being false;
208                // otherwise both are true
209                if (!vZoom) {
210                    y = dataArea.getMinY();
211                    w = Math.min(w, maxX - this.startPoint.getX());
212                    h = dataArea.getHeight();
213                }
214                else if (!hZoom) {
215                    x = dataArea.getMinX();
216                    w = dataArea.getWidth();
217                    h = Math.min(h, maxY - this.startPoint.getY());
218                }
219                else {
220                    w = Math.min(w, maxX - this.startPoint.getX());
221                    h = Math.min(h, maxY - this.startPoint.getY());
222                }
223                Rectangle2D zoomArea = new Rectangle2D.Double(x, y, w, h);
224                
225                boolean saved = p.isNotify();
226                p.setNotify(false);
227                double pw0 = percentW(x, dataArea);
228                double pw1 = percentW(x + w, dataArea);
229                double ph0 = percentH(y, dataArea);
230                double ph1 = percentH(y + h, dataArea);
231                PlotRenderingInfo info 
232                        = this.viewer.getRenderingInfo().getPlotInfo();
233                if (z.getOrientation().isVertical()) {
234                    z.zoomDomainAxes(pw0, pw1, info, endPoint);
235                    z.zoomRangeAxes(1 - ph1, 1 - ph0, info, endPoint);
236                } else {
237                    z.zoomRangeAxes(pw0, pw1, info, endPoint);
238                    z.zoomDomainAxes(1 - ph1, 1 - ph0, info, endPoint);
239                }
240                p.setNotify(saved);
241                
242            }
243        }
244        this.viewer.hideZoomRectangle();
245        this.startPoint = null;
246        canvas.clearLiveHandler();
247    }
248
249    private double percentW(double x, Rectangle2D r) {
250        return (x - r.getMinX()) / r.getWidth();
251    }
252    
253    private double percentH(double y, Rectangle2D r) {
254        return (y - r.getMinY()) / r.getHeight();
255    }
256}