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 * YIntervalRenderer.java
029 * ----------------------
030 * (C) Copyright 2002-2022, by David Gilbert.
031 *
032 * Original Author:  David Gilbert;
033 * Contributor(s):   -;
034 *
035 */
036
037package org.jfree.chart.renderer.xy;
038
039import java.awt.Font;
040import java.awt.Graphics2D;
041import java.awt.Paint;
042import java.awt.Shape;
043import java.awt.Stroke;
044import java.awt.geom.Line2D;
045import java.awt.geom.Point2D;
046import java.awt.geom.Rectangle2D;
047import java.io.Serializable;
048import java.util.Objects;
049
050import org.jfree.chart.axis.ValueAxis;
051import org.jfree.chart.entity.EntityCollection;
052import org.jfree.chart.event.RendererChangeEvent;
053import org.jfree.chart.labels.ItemLabelPosition;
054import org.jfree.chart.labels.XYItemLabelGenerator;
055import org.jfree.chart.plot.CrosshairState;
056import org.jfree.chart.plot.PlotOrientation;
057import org.jfree.chart.plot.PlotRenderingInfo;
058import org.jfree.chart.plot.XYPlot;
059import org.jfree.chart.text.TextUtils;
060import org.jfree.chart.api.RectangleEdge;
061import org.jfree.chart.api.PublicCloneable;
062import org.jfree.chart.internal.ShapeUtils;
063import org.jfree.data.Range;
064import org.jfree.data.xy.IntervalXYDataset;
065import org.jfree.data.xy.XYDataset;
066
067/**
068 * A renderer that draws a line connecting the start and end Y values for an
069 * {@link XYPlot}.  The example shown here is generated by the
070 * {@code YIntervalRendererDemo1.java} program included in the JFreeChart
071 * demo collection:
072 * <br><br>
073 * <img src="doc-files/YIntervalRendererSample.png"
074 * alt="YIntervalRendererSample.png">
075 */
076public class YIntervalRenderer extends AbstractXYItemRenderer
077        implements XYItemRenderer, Cloneable, PublicCloneable, Serializable {
078
079    /** For serialization. */
080    private static final long serialVersionUID = -2951586537224143260L;
081
082    /**
083     * An additional item label generator.  If this is non-null, the item
084     * label generated will be displayed near the lower y-value at the
085     * position given by getNegativeItemLabelPosition().
086     *
087     * @since 1.0.10
088     */
089    private XYItemLabelGenerator additionalItemLabelGenerator;
090
091    /**
092     * The default constructor.
093     */
094    public YIntervalRenderer() {
095        super();
096        this.additionalItemLabelGenerator = null;
097    }
098
099    /**
100     * Returns the generator for the item labels that appear near the lower
101     * y-value.
102     *
103     * @return The generator (possibly {@code null}).
104     *
105     * @see #setAdditionalItemLabelGenerator(XYItemLabelGenerator)
106     *
107     * @since 1.0.10
108     */
109    public XYItemLabelGenerator getAdditionalItemLabelGenerator() {
110        return this.additionalItemLabelGenerator;
111    }
112
113    /**
114     * Sets the generator for the item labels that appear near the lower
115     * y-value and sends a {@link RendererChangeEvent} to all registered
116     * listeners.  If this is set to {@code null}, no item labels will be
117     * drawn.
118     *
119     * @param generator  the generator ({@code null} permitted).
120     *
121     * @see #getAdditionalItemLabelGenerator()
122     *
123     * @since 1.0.10
124     */
125    public void setAdditionalItemLabelGenerator(
126            XYItemLabelGenerator generator) {
127        this.additionalItemLabelGenerator = generator;
128        fireChangeEvent();
129    }
130
131    /**
132     * Returns the range of values the renderer requires to display all the
133     * items from the specified dataset.
134     *
135     * @param dataset  the dataset ({@code null} permitted).
136     *
137     * @return The range ({@code null} if the dataset is {@code null} or empty).
138     */
139    @Override
140    public Range findRangeBounds(XYDataset dataset) {
141        return findRangeBounds(dataset, true);
142    }
143
144    /**
145     * Draws the visual representation of a single data item.
146     *
147     * @param g2  the graphics device.
148     * @param state  the renderer state.
149     * @param dataArea  the area within which the plot is being drawn.
150     * @param info  collects information about the drawing.
151     * @param plot  the plot (can be used to obtain standard color
152     *              information etc).
153     * @param domainAxis  the domain axis.
154     * @param rangeAxis  the range axis.
155     * @param dataset  the dataset.
156     * @param series  the series index (zero-based).
157     * @param item  the item index (zero-based).
158     * @param crosshairState  crosshair information for the plot
159     *                        ({@code null} permitted).
160     * @param pass  the pass index (ignored here).
161     */
162    @Override
163    public void drawItem(Graphics2D g2, XYItemRendererState state,
164            Rectangle2D dataArea, PlotRenderingInfo info, XYPlot plot,
165            ValueAxis domainAxis, ValueAxis rangeAxis, XYDataset dataset,
166            int series, int item, CrosshairState crosshairState, int pass) {
167
168        // do nothing if item is not visible
169        if (!getItemVisible(series, item)) {
170            return;
171        }
172
173        // setup for collecting optional entity info...
174        EntityCollection entities = null;
175        if (info != null) {
176            entities = info.getOwner().getEntityCollection();
177        }
178
179        IntervalXYDataset intervalDataset = (IntervalXYDataset) dataset;
180
181        double x = intervalDataset.getXValue(series, item);
182        double yLow   = intervalDataset.getStartYValue(series, item);
183        double yHigh  = intervalDataset.getEndYValue(series, item);
184
185        RectangleEdge xAxisLocation = plot.getDomainAxisEdge();
186        RectangleEdge yAxisLocation = plot.getRangeAxisEdge();
187
188        double xx = domainAxis.valueToJava2D(x, dataArea, xAxisLocation);
189        double yyLow = rangeAxis.valueToJava2D(yLow, dataArea, yAxisLocation);
190        double yyHigh = rangeAxis.valueToJava2D(yHigh, dataArea, yAxisLocation);
191
192        Paint p = getItemPaint(series, item);
193        Stroke s = getItemStroke(series, item);
194
195        Line2D line = null;
196        Shape shape = getItemShape(series, item);
197        Shape top = null;
198        Shape bottom = null;
199        PlotOrientation orientation = plot.getOrientation();
200        if (orientation == PlotOrientation.HORIZONTAL) {
201            line = new Line2D.Double(yyLow, xx, yyHigh, xx);
202            top = ShapeUtils.createTranslatedShape(shape, yyHigh, xx);
203            bottom = ShapeUtils.createTranslatedShape(shape, yyLow, xx);
204        }
205        else if (orientation == PlotOrientation.VERTICAL) {
206            line = new Line2D.Double(xx, yyLow, xx, yyHigh);
207            top = ShapeUtils.createTranslatedShape(shape, xx, yyHigh);
208            bottom = ShapeUtils.createTranslatedShape(shape, xx, yyLow);
209        } else {
210            throw new IllegalStateException();
211        }
212        g2.setPaint(p);
213        g2.setStroke(s);
214        g2.draw(line);
215
216        g2.fill(top);
217        g2.fill(bottom);
218
219        // for item labels, we have a special case because there is the
220        // possibility to draw (a) the regular item label near to just the
221        // upper y-value, or (b) the regular item label near the upper y-value
222        // PLUS an additional item label near the lower y-value.
223        if (isItemLabelVisible(series, item)) {
224            drawItemLabel(g2, orientation, dataset, series, item, xx, yyHigh,
225                    false);
226            drawAdditionalItemLabel(g2, orientation, dataset, series, item,
227                    xx, yyLow);
228        }
229
230        // add an entity for the item...
231        Shape hotspot = ShapeUtils.createLineRegion(line, 4.0f);
232        if (entities != null && hotspot.intersects(dataArea)) {
233            addEntity(entities, hotspot, dataset, series, item, 0.0, 0.0);
234        }
235
236    }
237
238    /**
239     * Draws an item label.
240     *
241     * @param g2  the graphics device.
242     * @param orientation  the orientation.
243     * @param dataset  the dataset.
244     * @param series  the series index (zero-based).
245     * @param item  the item index (zero-based).
246     * @param x  the x coordinate (in Java2D space).
247     * @param y  the y coordinate (in Java2D space).
248     */
249    private void drawAdditionalItemLabel(Graphics2D g2,
250            PlotOrientation orientation, XYDataset dataset, int series,
251            int item, double x, double y) {
252
253        if (this.additionalItemLabelGenerator == null) {
254            return;
255        }
256
257        Font labelFont = getItemLabelFont(series, item);
258        Paint paint = getItemLabelPaint(series, item);
259        g2.setFont(labelFont);
260        g2.setPaint(paint);
261        String label = this.additionalItemLabelGenerator.generateLabel(dataset,
262                series, item);
263
264        ItemLabelPosition position = getNegativeItemLabelPosition(series, item);
265        Point2D anchorPoint = calculateLabelAnchorPoint(
266                position.getItemLabelAnchor(), x, y, orientation);
267        TextUtils.drawRotatedString(label, g2,
268                (float) anchorPoint.getX(), (float) anchorPoint.getY(),
269                position.getTextAnchor(), position.getAngle(),
270                position.getRotationAnchor());
271    }
272
273    /**
274     * Tests this renderer for equality with an arbitrary object.
275     *
276     * @param obj  the object ({@code null} permitted).
277     *
278     * @return A boolean.
279     */
280    @Override
281    public boolean equals(Object obj) {
282        if (obj == this) {
283            return true;
284        }
285        if (!(obj instanceof YIntervalRenderer)) {
286            return false;
287        }
288        YIntervalRenderer that = (YIntervalRenderer) obj;
289        if (!Objects.equals(this.additionalItemLabelGenerator, that.additionalItemLabelGenerator)) {
290            return false;
291        }
292        return super.equals(obj);
293    }
294
295    /**
296     * Returns a clone of the renderer.
297     *
298     * @return A clone.
299     *
300     * @throws CloneNotSupportedException  if the renderer cannot be cloned.
301     */
302    @Override
303    public Object clone() throws CloneNotSupportedException {
304        return super.clone();
305    }
306
307}