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}