001/* ===========================================================
002 * JFreeChart : a free chart library for the Java(tm) platform
003 * ===========================================================
004 *
005 * (C) Copyright 2000-present, 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-present, 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.ui.RectangleEdge;
061import org.jfree.chart.util.PublicCloneable;
062import org.jfree.chart.util.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    private XYItemLabelGenerator additionalItemLabelGenerator;
088
089    /**
090     * The default constructor.
091     */
092    public YIntervalRenderer() {
093        super();
094        this.additionalItemLabelGenerator = null;
095    }
096
097    /**
098     * Returns the generator for the item labels that appear near the lower
099     * y-value.
100     *
101     * @return The generator (possibly {@code null}).
102     *
103     * @see #setAdditionalItemLabelGenerator(XYItemLabelGenerator)
104     */
105    public XYItemLabelGenerator getAdditionalItemLabelGenerator() {
106        return this.additionalItemLabelGenerator;
107    }
108
109    /**
110     * Sets the generator for the item labels that appear near the lower
111     * y-value and sends a {@link RendererChangeEvent} to all registered
112     * listeners.  If this is set to {@code null}, no item labels will be
113     * drawn.
114     *
115     * @param generator  the generator ({@code null} permitted).
116     *
117     * @see #getAdditionalItemLabelGenerator()
118     */
119    public void setAdditionalItemLabelGenerator(
120            XYItemLabelGenerator generator) {
121        this.additionalItemLabelGenerator = generator;
122        fireChangeEvent();
123    }
124
125    /**
126     * Returns the range of values the renderer requires to display all the
127     * items from the specified dataset.
128     *
129     * @param dataset  the dataset ({@code null} permitted).
130     *
131     * @return The range ({@code null} if the dataset is {@code null} or empty).
132     */
133    @Override
134    public Range findRangeBounds(XYDataset dataset) {
135        return findRangeBounds(dataset, true);
136    }
137
138    /**
139     * Draws the visual representation of a single data item.
140     *
141     * @param g2  the graphics device.
142     * @param state  the renderer state.
143     * @param dataArea  the area within which the plot is being drawn.
144     * @param info  collects information about the drawing.
145     * @param plot  the plot (can be used to obtain standard color
146     *              information etc).
147     * @param domainAxis  the domain axis.
148     * @param rangeAxis  the range axis.
149     * @param dataset  the dataset.
150     * @param series  the series index (zero-based).
151     * @param item  the item index (zero-based).
152     * @param crosshairState  crosshair information for the plot
153     *                        ({@code null} permitted).
154     * @param pass  the pass index (ignored here).
155     */
156    @Override
157    public void drawItem(Graphics2D g2, XYItemRendererState state,
158            Rectangle2D dataArea, PlotRenderingInfo info, XYPlot plot,
159            ValueAxis domainAxis, ValueAxis rangeAxis, XYDataset dataset,
160            int series, int item, CrosshairState crosshairState, int pass) {
161
162        // do nothing if item is not visible
163        if (!getItemVisible(series, item)) {
164            return;
165        }
166
167        // setup for collecting optional entity info...
168        EntityCollection entities = null;
169        if (info != null) {
170            entities = info.getOwner().getEntityCollection();
171        }
172
173        IntervalXYDataset intervalDataset = (IntervalXYDataset) dataset;
174
175        double x = intervalDataset.getXValue(series, item);
176        double yLow   = intervalDataset.getStartYValue(series, item);
177        double yHigh  = intervalDataset.getEndYValue(series, item);
178
179        RectangleEdge xAxisLocation = plot.getDomainAxisEdge();
180        RectangleEdge yAxisLocation = plot.getRangeAxisEdge();
181
182        double xx = domainAxis.valueToJava2D(x, dataArea, xAxisLocation);
183        double yyLow = rangeAxis.valueToJava2D(yLow, dataArea, yAxisLocation);
184        double yyHigh = rangeAxis.valueToJava2D(yHigh, dataArea, yAxisLocation);
185
186        Paint p = getItemPaint(series, item);
187        Stroke s = getItemStroke(series, item);
188
189        Line2D line = null;
190        Shape shape = getItemShape(series, item);
191        Shape top = null;
192        Shape bottom = null;
193        PlotOrientation orientation = plot.getOrientation();
194        if (orientation == PlotOrientation.HORIZONTAL) {
195            line = new Line2D.Double(yyLow, xx, yyHigh, xx);
196            top = ShapeUtils.createTranslatedShape(shape, yyHigh, xx);
197            bottom = ShapeUtils.createTranslatedShape(shape, yyLow, xx);
198        }
199        else if (orientation == PlotOrientation.VERTICAL) {
200            line = new Line2D.Double(xx, yyLow, xx, yyHigh);
201            top = ShapeUtils.createTranslatedShape(shape, xx, yyHigh);
202            bottom = ShapeUtils.createTranslatedShape(shape, xx, yyLow);
203        } else {
204            throw new IllegalStateException();
205        }
206        g2.setPaint(p);
207        g2.setStroke(s);
208        g2.draw(line);
209
210        g2.fill(top);
211        g2.fill(bottom);
212
213        // for item labels, we have a special case because there is the
214        // possibility to draw (a) the regular item label near to just the
215        // upper y-value, or (b) the regular item label near the upper y-value
216        // PLUS an additional item label near the lower y-value.
217        if (isItemLabelVisible(series, item)) {
218            drawItemLabel(g2, orientation, dataset, series, item, xx, yyHigh,
219                    false);
220            drawAdditionalItemLabel(g2, orientation, dataset, series, item,
221                    xx, yyLow);
222        }
223
224        // add an entity for the item...
225        Shape hotspot = ShapeUtils.createLineRegion(line, 4.0f);
226        if (entities != null && hotspot.intersects(dataArea)) {
227            addEntity(entities, hotspot, dataset, series, item, 0.0, 0.0);
228        }
229
230    }
231
232    /**
233     * Draws an item label.
234     *
235     * @param g2  the graphics device.
236     * @param orientation  the orientation.
237     * @param dataset  the dataset.
238     * @param series  the series index (zero-based).
239     * @param item  the item index (zero-based).
240     * @param x  the x coordinate (in Java2D space).
241     * @param y  the y coordinate (in Java2D space).
242     */
243    private void drawAdditionalItemLabel(Graphics2D g2,
244            PlotOrientation orientation, XYDataset dataset, int series,
245            int item, double x, double y) {
246
247        if (this.additionalItemLabelGenerator == null) {
248            return;
249        }
250
251        Font labelFont = getItemLabelFont(series, item);
252        Paint paint = getItemLabelPaint(series, item);
253        g2.setFont(labelFont);
254        g2.setPaint(paint);
255        String label = this.additionalItemLabelGenerator.generateLabel(dataset,
256                series, item);
257
258        ItemLabelPosition position = getNegativeItemLabelPosition(series, item);
259        Point2D anchorPoint = calculateLabelAnchorPoint(
260                position.getItemLabelAnchor(), x, y, orientation);
261        TextUtils.drawRotatedString(label, g2,
262                (float) anchorPoint.getX(), (float) anchorPoint.getY(),
263                position.getTextAnchor(), position.getAngle(),
264                position.getRotationAnchor());
265    }
266
267    /**
268     * Tests this renderer for equality with an arbitrary object.
269     *
270     * @param obj  the object ({@code null} permitted).
271     *
272     * @return A boolean.
273     */
274    @Override
275    public boolean equals(Object obj) {
276        if (obj == this) {
277            return true;
278        }
279        if (!(obj instanceof YIntervalRenderer)) {
280            return false;
281        }
282        YIntervalRenderer that = (YIntervalRenderer) obj;
283        if (!Objects.equals(this.additionalItemLabelGenerator,
284                that.additionalItemLabelGenerator)) {
285            return false;
286        }
287        return super.equals(obj);
288    }
289
290    /**
291     * Returns a clone of the renderer.
292     *
293     * @return A clone.
294     *
295     * @throws CloneNotSupportedException  if the renderer cannot be cloned.
296     */
297    @Override
298    public Object clone() throws CloneNotSupportedException {
299        return super.clone();
300    }
301
302}