001/* ===========================================================
002 * JFreeChart : a free chart library for the Java(tm) platform
003 * ===========================================================
004 *
005 * (C) Copyright 2000-2022, by David Gilbert 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 * DefaultSelectionZoomStrategy.java
029 * ---------------------------------
030 * (C) Copyright 2021-2022 by David Gilbert and Contributors.
031 *
032 * Original Author:  -;
033 * Contributor(s):   David Gilbert;
034 *              
035 *
036 */
037
038package org.jfree.chart.swing;
039
040import java.awt.Color;
041import java.awt.Graphics2D;
042import java.awt.Paint;
043import java.awt.event.MouseEvent;
044import java.awt.geom.Point2D;
045import java.awt.geom.Rectangle2D;
046import java.io.IOException;
047import java.io.ObjectInputStream;
048import java.io.ObjectOutputStream;
049import org.jfree.chart.internal.SerialUtils;
050
051/**
052 * {@inheritDoc}
053 *
054 * This implementation can be extended to override default behavior.
055 */
056public class DefaultSelectionZoomStrategy implements SelectionZoomStrategy {
057
058    private static final long serialVersionUID = -8042265475645652131L;
059
060    /** The minimum size required to perform a zoom on a rectangle */
061    public static final int DEFAULT_ZOOM_TRIGGER_DISTANCE = 10;
062
063    /**
064     * The zoom rectangle starting point (selected by the user with a mouse
065     * click).  This is a point on the screen, not the chart (which may have
066     * been scaled up or down to fit the panel).
067     */
068    protected Point2D zoomPoint = null;
069
070    /**
071     * The zoom rectangle (selected by the user with the mouse).
072     */
073    protected transient Rectangle2D zoomRectangle = null;
074
075    /**
076     * Controls if the zoom rectangle is drawn as an outline or filled.
077     */
078    private boolean fillZoomRectangle = true;
079
080    /**
081     * The minimum distance required to drag the mouse to trigger a zoom.
082     */
083    private int zoomTriggerDistance;
084
085    /**
086     * The paint used to draw the zoom rectangle outline.
087     */
088    private transient Paint zoomOutlinePaint;
089
090    /**
091     * The zoom fill paint (should use transparency).
092     */
093    private transient Paint zoomFillPaint;
094
095    public DefaultSelectionZoomStrategy() {
096        zoomTriggerDistance = DEFAULT_ZOOM_TRIGGER_DISTANCE;
097        this.zoomOutlinePaint = Color.BLUE;
098        this.zoomFillPaint = new Color(0, 0, 255, 63);
099    }
100
101    @Override
102    public boolean isActivated() {
103        return zoomRectangle != null;
104    }
105
106    @Override
107    public Point2D getZoomPoint() {
108        return zoomPoint;
109    }
110
111    @Override
112    public void setZoomPoint(Point2D zoomPoint) {
113        this.zoomPoint = zoomPoint;
114    }
115
116    @Override
117    public void setZoomTriggerDistance(int distance) {
118        this.zoomTriggerDistance = distance;
119    }
120
121    @Override
122    public int getZoomTriggerDistance() {
123        return zoomTriggerDistance;
124    }
125
126    @Override
127    public Paint getZoomOutlinePaint() {
128        return zoomOutlinePaint;
129    }
130
131    @Override
132    public void setZoomOutlinePaint(Paint paint) {
133        this.zoomOutlinePaint = paint;
134    }
135
136    @Override
137    public Paint getZoomFillPaint() {
138        return zoomFillPaint;
139    }
140
141    @Override
142    public void setZoomFillPaint(Paint paint) {
143        this.zoomFillPaint = paint;
144    }
145
146    @Override
147    public boolean getFillZoomRectangle() {
148        return this.fillZoomRectangle;
149    }
150
151    @Override
152    public void setFillZoomRectangle(boolean flag) {
153        this.fillZoomRectangle = flag;
154    }
155
156    @Override
157    public void updateZoomRectangleSelection(MouseEvent e, boolean hZoom, boolean vZoom, Rectangle2D scaledDataArea) {
158        if (hZoom && vZoom) {
159            // selected rectangle shouldn't extend outside the data area...
160            double xMax = Math.min(e.getX(), scaledDataArea.getMaxX());
161            double yMax = Math.min(e.getY(), scaledDataArea.getMaxY());
162            zoomRectangle = new Rectangle2D.Double(
163                    zoomPoint.getX(), zoomPoint.getY(),
164                    xMax - zoomPoint.getX(), yMax - zoomPoint.getY());
165        }
166        else if (hZoom) {
167            double xMax = Math.min(e.getX(), scaledDataArea.getMaxX());
168            zoomRectangle = new Rectangle2D.Double(
169                    zoomPoint.getX(), scaledDataArea.getMinY(),
170                    xMax - zoomPoint.getX(), scaledDataArea.getHeight());
171        }
172        else if (vZoom) {
173            double yMax = Math.min(e.getY(), scaledDataArea.getMaxY());
174            zoomRectangle = new Rectangle2D.Double(
175                    scaledDataArea.getMinX(), zoomPoint.getY(),
176                    scaledDataArea.getWidth(), yMax - zoomPoint.getY());
177        }
178    }
179
180    @Override
181    public Rectangle2D getZoomRectangle(boolean hZoom, boolean vZoom, Rectangle2D screenDataArea) {
182        double x, y, w, h;
183        double maxX = screenDataArea.getMaxX();
184        double maxY = screenDataArea.getMaxY();
185        // for mouseReleased event, (horizontalZoom || verticalZoom)
186        // will be true, so we can just test for either being false;
187        // otherwise both are true
188        if (!vZoom) {
189            x = zoomPoint.getX();
190            y = screenDataArea.getMinY();
191            w = Math.min(zoomRectangle.getWidth(),
192                    maxX - zoomPoint.getX());
193            h = screenDataArea.getHeight();
194        }
195        else if (!hZoom) {
196            x = screenDataArea.getMinX();
197            y = zoomPoint.getY();
198            w = screenDataArea.getWidth();
199            h = Math.min(zoomRectangle.getHeight(),
200                    maxY - zoomPoint.getY());
201        }
202        else {
203            x = zoomPoint.getX();
204            y = zoomPoint.getY();
205            w = Math.min(zoomRectangle.getWidth(),
206                    maxX - zoomPoint.getX());
207            h = Math.min(zoomRectangle.getHeight(),
208                    maxY - zoomPoint.getY());
209        }
210        return new Rectangle2D.Double(x, y, w, h);
211    }
212
213    @Override
214    public void reset() {
215        zoomPoint = null;
216        zoomRectangle = null;
217    }
218
219    @Override
220    public void drawZoomRectangle(Graphics2D g2, boolean xor) {
221        if (zoomRectangle != null) {
222            if (xor) {
223                 // Set XOR mode to draw the zoom rectangle
224                g2.setXORMode(Color.GRAY);
225            }
226            if (fillZoomRectangle) {
227                g2.setPaint(zoomFillPaint);
228                g2.fill(zoomRectangle);
229            }
230            else {
231                g2.setPaint(zoomOutlinePaint);
232                g2.draw(zoomRectangle);
233            }
234            if (xor) {
235                // Reset to the default 'overwrite' mode
236                g2.setPaintMode();
237            }
238        }
239    }
240
241    /**
242     * Provides serialization support.
243     *
244     * @param stream  the output stream.
245     *
246     * @throws IOException  if there is an I/O error.
247     */
248    private void writeObject(ObjectOutputStream stream) throws IOException {
249        stream.defaultWriteObject();
250        SerialUtils.writePaint(this.zoomFillPaint, stream);
251        SerialUtils.writePaint(this.zoomOutlinePaint, stream);
252    }
253
254    /**
255     * Provides serialization support.
256     *
257     * @param stream  the input stream.
258     *
259     * @throws IOException  if there is an I/O error.
260     * @throws ClassNotFoundException  if there is a classpath problem.
261     */
262    private void readObject(ObjectInputStream stream)
263            throws IOException, ClassNotFoundException {
264        stream.defaultReadObject();
265        this.zoomFillPaint = SerialUtils.readPaint(stream);
266        this.zoomOutlinePaint = SerialUtils.readPaint(stream);
267    }
268}