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 * MarkerAxisBand.java
029 * -------------------
030 * (C) Copyright 2000-2022, by David Gilbert.
031 *
032 * Original Author:  David Gilbert;
033 * Contributor(s):   -;
034 *
035 * Changes
036 * -------
037 * 03-Sep-2002 : Updated Javadoc comments (DG);
038 * 01-Oct-2002 : Fixed errors reported by Checkstyle (DG);
039 * 08-Nov-2002 : Moved to new package com.jrefinery.chart.axis (DG);
040 * 26-Mar-2003 : Implemented Serializable (DG);
041 * 13-May-2003 : Renamed HorizontalMarkerAxisBand --> MarkerAxisBand (DG);
042 * 29-Oct-2003 : Added workaround for font alignment in PDF output (DG);
043 * 21-Jan-2004 : Update for renamed method in ValueAxis (DG);
044 * 07-Apr-2004 : Changed text bounds calculation (DG);
045 *
046 */
047
048package org.jfree.chart.axis;
049
050import org.jfree.chart.api.RectangleEdge;
051import org.jfree.chart.plot.IntervalMarker;
052import org.jfree.chart.text.TextUtils;
053
054import java.awt.*;
055import java.awt.font.LineMetrics;
056import java.awt.geom.Rectangle2D;
057import java.io.Serializable;
058import java.util.List;
059import java.util.Objects;
060
061/**
062 * A band that can be added to a number axis to display regions.
063 */
064public class MarkerAxisBand implements Serializable {
065
066    /** For serialization. */
067    private static final long serialVersionUID = -1729482413886398919L;
068
069    /** The axis that the band belongs to. */
070    private NumberAxis axis;
071
072    /** The top outer gap. */
073    private double topOuterGap;
074
075    /** The top inner gap. */
076    private double topInnerGap;
077
078    /** The bottom outer gap. */
079    private double bottomOuterGap;
080
081    /** The bottom inner gap. */
082    private double bottomInnerGap;
083
084    /** The font. */
085    private Font font;
086
087    /** Storage for the markers. */
088    private List markers;
089
090    /**
091     * Constructs a new axis band.
092     *
093     * @param axis  the owner.
094     * @param topOuterGap  the top outer gap.
095     * @param topInnerGap  the top inner gap.
096     * @param bottomOuterGap  the bottom outer gap.
097     * @param bottomInnerGap  the bottom inner gap.
098     * @param font  the font.
099     */
100    public MarkerAxisBand(NumberAxis axis,
101                          double topOuterGap, double topInnerGap,
102                          double bottomOuterGap, double bottomInnerGap,
103                          Font font) {
104        this.axis = axis;
105        this.topOuterGap = topOuterGap;
106        this.topInnerGap = topInnerGap;
107        this.bottomOuterGap = bottomOuterGap;
108        this.bottomInnerGap = bottomInnerGap;
109        this.font = font;
110        this.markers = new java.util.ArrayList();
111    }
112
113    /**
114     * Adds a marker to the band.
115     *
116     * @param marker  the marker.
117     */
118    public void addMarker(IntervalMarker marker) {
119        this.markers.add(marker);
120    }
121
122    /**
123     * Returns the height of the band.
124     *
125     * @param g2  the graphics device.
126     *
127     * @return The height of the band.
128     */
129    public double getHeight(Graphics2D g2) {
130
131        double result = 0.0;
132        if (this.markers.size() > 0) {
133            LineMetrics metrics = this.font.getLineMetrics(
134                "123g", g2.getFontRenderContext()
135            );
136            result = this.topOuterGap + this.topInnerGap + metrics.getHeight()
137                     + this.bottomInnerGap + this.bottomOuterGap;
138        }
139        return result;
140
141    }
142
143    /**
144     * A utility method that draws a string inside a rectangle.
145     *
146     * @param g2  the graphics device.
147     * @param bounds  the rectangle.
148     * @param font  the font.
149     * @param text  the text.
150     */
151    private void drawStringInRect(Graphics2D g2, Rectangle2D bounds, Font font,
152                                  String text) {
153
154        g2.setFont(font);
155        FontMetrics fm = g2.getFontMetrics(font);
156        Rectangle2D r = TextUtils.getTextBounds(text, g2, fm);
157        double x = bounds.getX();
158        if (r.getWidth() < bounds.getWidth()) {
159            x = x + (bounds.getWidth() - r.getWidth()) / 2;
160        }
161        LineMetrics metrics = font.getLineMetrics(
162            text, g2.getFontRenderContext()
163        );
164        g2.drawString(
165            text, (float) x, (float) (bounds.getMaxY()
166                - this.bottomInnerGap - metrics.getDescent())
167        );
168    }
169
170    /**
171     * Draws the band.
172     *
173     * @param g2  the graphics device.
174     * @param plotArea  the plot area.
175     * @param dataArea  the data area.
176     * @param x  the x-coordinate.
177     * @param y  the y-coordinate.
178     */
179    public void draw(Graphics2D g2, Rectangle2D plotArea, Rectangle2D dataArea,
180                     double x, double y) {
181
182        double h = getHeight(g2);
183        for (Object o : this.markers) {
184            IntervalMarker marker = (IntervalMarker) o;
185            double start = Math.max(
186                    marker.getStartValue(), this.axis.getRange().getLowerBound()
187            );
188            double end = Math.min(
189                    marker.getEndValue(), this.axis.getRange().getUpperBound()
190            );
191            double s = this.axis.valueToJava2D(
192                    start, dataArea, RectangleEdge.BOTTOM
193            );
194            double e = this.axis.valueToJava2D(
195                    end, dataArea, RectangleEdge.BOTTOM
196            );
197            Rectangle2D r = new Rectangle2D.Double(
198                    s, y + this.topOuterGap, e - s,
199                    h - this.topOuterGap - this.bottomOuterGap
200            );
201
202            Composite originalComposite = g2.getComposite();
203            g2.setComposite(AlphaComposite.getInstance(
204                    AlphaComposite.SRC_OVER, marker.getAlpha())
205            );
206            g2.setPaint(marker.getPaint());
207            g2.fill(r);
208            g2.setPaint(marker.getOutlinePaint());
209            g2.draw(r);
210            g2.setComposite(originalComposite);
211
212            g2.setPaint(Color.BLACK);
213            drawStringInRect(g2, r, this.font, marker.getLabel());
214        }
215
216    }
217
218    /**
219     * Tests this axis for equality with another object.  Note that the axis
220     * that the band belongs to is ignored in the test.
221     *
222     * @param obj  the object ({@code null} permitted).
223     *
224     * @return {@code true} or {@code false}.
225     */
226    @Override
227    public boolean equals(Object obj) {
228        if (obj == this) {
229            return true;
230        }
231        if (!(obj instanceof MarkerAxisBand)) {
232            return false;
233        }
234        MarkerAxisBand that = (MarkerAxisBand) obj;
235        if (this.topOuterGap != that.topOuterGap) {
236            return false;
237        }
238        if (this.topInnerGap != that.topInnerGap) {
239            return false;
240        }
241        if (this.bottomInnerGap != that.bottomInnerGap) {
242            return false;
243        }
244        if (this.bottomOuterGap != that.bottomOuterGap) {
245            return false;
246        }
247        if (!Objects.equals(this.font, that.font)) {
248            return false;
249        }
250        if (!Objects.equals(this.markers, that.markers)) {
251            return false;
252        }
253        return true;
254    }
255
256    /**
257     * Returns a hash code for the object.
258     *
259     * @return A hash code.
260     */
261    @Override
262    public int hashCode() {
263        int result = 37;
264        result = 19 * result + this.font.hashCode();
265        result = 19 * result + this.markers.hashCode();
266        return result;
267    }
268
269}