001/* ================================================ 002 * JFreeChart-FX : JavaFX extensions for JFreeChart 003 * ================================================ 004 * 005 * (C) Copyright 2017-2021, 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-2021, 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 if (canvas.getChart() == null) { 101 return; 102 } 103 Point2D pt = new Point2D.Double(e.getX(), e.getY()); 104 Rectangle2D dataArea = canvas.findDataArea(pt); 105 if (dataArea != null) { 106 this.startPoint = ShapeUtils.getPointInRectangle(e.getX(), 107 e.getY(), dataArea); 108 } else { 109 this.startPoint = null; 110 canvas.clearLiveHandler(); 111 } 112 } 113 114 /** 115 * Handles a mouse dragged event by updating the zoom rectangle displayed 116 * in the ChartViewer. 117 * 118 * @param canvas the JavaFX canvas ({@code null} not permitted). 119 * @param e the mouse event ({@code null} not permitted). 120 */ 121 @Override 122 public void handleMouseDragged(ChartCanvas canvas, MouseEvent e) { 123 if (this.startPoint == null) { 124 //no initial zoom rectangle exists but the handler is set 125 //as life handler unregister 126 canvas.clearLiveHandler(); 127 return; 128 } 129 if (canvas.getChart() == null) { 130 return; 131 } 132 133 boolean hZoom, vZoom; 134 Plot p = canvas.getChart().getPlot(); 135 if (!(p instanceof Zoomable)) { 136 return; 137 } 138 Zoomable z = (Zoomable) p; 139 if (z.getOrientation().isHorizontal()) { 140 hZoom = z.isRangeZoomable(); 141 vZoom = z.isDomainZoomable(); 142 } else { 143 hZoom = z.isDomainZoomable(); 144 vZoom = z.isRangeZoomable(); 145 } 146 Rectangle2D dataArea = canvas.findDataArea(this.startPoint); 147 148 double x = this.startPoint.getX(); 149 double y = this.startPoint.getY(); 150 double w = 0; 151 double h = 0; 152 if (hZoom && vZoom) { 153 // selected rectangle shouldn't extend outside the data area... 154 double xmax = Math.min(e.getX(), dataArea.getMaxX()); 155 double ymax = Math.min(e.getY(), dataArea.getMaxY()); 156 w = xmax - this.startPoint.getX(); 157 h = ymax - this.startPoint.getY(); 158 } 159 else if (hZoom) { 160 double xmax = Math.min(e.getX(), dataArea.getMaxX()); 161 y = dataArea.getMinY(); 162 w = xmax - this.startPoint.getX(); 163 h = dataArea.getHeight(); 164 } 165 else if (vZoom) { 166 double ymax = Math.min(e.getY(), dataArea.getMaxY()); 167 x = dataArea.getMinX(); 168 w = dataArea.getWidth(); 169 h = ymax - this.startPoint.getY(); 170 } 171 this.viewer.showZoomRectangle(x, y, w, h); 172 } 173 174 @Override 175 public void handleMouseReleased(ChartCanvas canvas, MouseEvent e) { 176 if (canvas.getChart() == null) { 177 return; 178 } 179 Plot p = canvas.getChart().getPlot(); 180 if (!(p instanceof Zoomable)) { 181 return; 182 } 183 boolean hZoom, vZoom; 184 Zoomable z = (Zoomable) p; 185 if (z.getOrientation().isHorizontal()) { 186 hZoom = z.isRangeZoomable(); 187 vZoom = z.isDomainZoomable(); 188 } else { 189 hZoom = z.isDomainZoomable(); 190 vZoom = z.isRangeZoomable(); 191 } 192 193 boolean zoomTrigger1 = hZoom && Math.abs(e.getX() 194 - this.startPoint.getX()) >= 10; 195 boolean zoomTrigger2 = vZoom && Math.abs(e.getY() 196 - this.startPoint.getY()) >= 10; 197 if (zoomTrigger1 || zoomTrigger2) { 198 Point2D endPoint = new Point2D.Double(e.getX(), e.getY()); 199 PlotRenderingInfo pri = canvas.getRenderingInfo().getPlotInfo(); 200 if ((hZoom && (e.getX() < this.startPoint.getX())) 201 || (vZoom && (e.getY() < this.startPoint.getY()))) { 202 boolean saved = p.isNotify(); 203 p.setNotify(false); 204 z.zoomDomainAxes(0, pri, endPoint); 205 z.zoomRangeAxes(0, pri, endPoint); 206 p.setNotify(saved); 207 } else { 208 double x = this.startPoint.getX(); 209 double y = this.startPoint.getY(); 210 double w = e.getX() - x; 211 double h = e.getY() - y; 212 Rectangle2D dataArea = canvas.findDataArea(this.startPoint); 213 double maxX = dataArea.getMaxX(); 214 double maxY = dataArea.getMaxY(); 215 // for mouseReleased event, (horizontalZoom || verticalZoom) 216 // will be true, so we can just test for either being false; 217 // otherwise both are true 218 if (!vZoom) { 219 y = dataArea.getMinY(); 220 w = Math.min(w, maxX - this.startPoint.getX()); 221 h = dataArea.getHeight(); 222 } 223 else if (!hZoom) { 224 x = dataArea.getMinX(); 225 w = dataArea.getWidth(); 226 h = Math.min(h, maxY - this.startPoint.getY()); 227 } 228 else { 229 w = Math.min(w, maxX - this.startPoint.getX()); 230 h = Math.min(h, maxY - this.startPoint.getY()); 231 } 232 Rectangle2D zoomArea = new Rectangle2D.Double(x, y, w, h); 233 if (!zoomArea.isEmpty()) { 234 boolean saved = p.isNotify(); 235 p.setNotify(false); 236 double pw0 = percentW(x, dataArea); 237 double pw1 = percentW(x + w, dataArea); 238 double ph0 = percentH(y, dataArea); 239 double ph1 = percentH(y + h, dataArea); 240 PlotRenderingInfo info 241 = this.viewer.getRenderingInfo().getPlotInfo(); 242 if (z.getOrientation().isVertical()) { 243 z.zoomDomainAxes(pw0, pw1, info, endPoint); 244 z.zoomRangeAxes(1 - ph1, 1 - ph0, info, endPoint); 245 } else { 246 z.zoomRangeAxes(pw0, pw1, info, endPoint); 247 z.zoomDomainAxes(1 - ph1, 1 - ph0, info, endPoint); 248 } 249 p.setNotify(saved); 250 } 251 } 252 } 253 this.viewer.hideZoomRectangle(); 254 this.startPoint = null; 255 canvas.clearLiveHandler(); 256 } 257 258 private double percentW(double x, Rectangle2D r) { 259 return (x - r.getMinX()) / r.getWidth(); 260 } 261 262 private double percentH(double y, Rectangle2D r) { 263 return (y - r.getMinY()) / r.getHeight(); 264 } 265}