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 * XYBezierRenderer.java
029 * ---------------------
030 * (C) Copyright, by Javier Robes and Contributors.
031 *
032 * Original Author:  Javier Robes;
033 *
034 */
035
036package org.jfree.chart.renderer.xy;
037
038import java.awt.GradientPaint;
039import java.awt.Graphics2D;
040import java.awt.Paint;
041import java.awt.geom.GeneralPath;
042import java.awt.geom.Point2D;
043import java.awt.geom.Rectangle2D;
044import java.util.ArrayList;
045import java.util.List;
046import java.util.Objects;
047
048import org.jfree.chart.axis.ValueAxis;
049import org.jfree.chart.event.RendererChangeEvent;
050import org.jfree.chart.plot.PlotOrientation;
051import org.jfree.chart.plot.PlotRenderingInfo;
052import org.jfree.chart.plot.XYPlot;
053import org.jfree.chart.ui.GradientPaintTransformer;
054import org.jfree.chart.ui.RectangleEdge;
055import org.jfree.chart.ui.StandardGradientPaintTransformer;
056import org.jfree.chart.util.Args;
057import org.jfree.data.xy.XYDataset;
058
059
060/**
061 * A renderer that connects data points with Bezier cubic curves and/or
062 * draws shapes at each data point.  This renderer is designed for use with
063 * the {@link XYPlot} class.
064 * <br><br>
065 *
066 * @since
067 */
068public class XYBezierRenderer extends XYLineAndShapeRenderer {
069
070    /**
071     * An enumeration of the fill types for the renderer.
072     *
073     * @since 1.0.17
074     */
075    public static enum FillType {
076
077        /** No fill. */
078        NONE,
079
080        /** Fill down to zero. */
081        TO_ZERO,
082
083        /** Fill to the lower bound. */
084        TO_LOWER_BOUND,
085
086        /** Fill to the upper bound. */
087        TO_UPPER_BOUND
088    }
089
090    /**
091     * Represents state information that applies to a single rendering of
092     * a chart.
093     */
094    public static class XYBezierState extends State {
095
096        /** The area to fill under the curve. */
097        public GeneralPath fillArea;
098
099        /** The points. */
100        public List<Point2D> points;
101
102        /**
103         * Creates a new state instance.
104         *
105         * @param info  the plot rendering info. 
106         */
107        public XYBezierState(PlotRenderingInfo info) {
108            super(info);
109            this.fillArea = new GeneralPath();
110            this.points = new ArrayList<>();
111        }
112    }
113
114    /**
115     * Resolution of Bezier curves (number of line segments between points)
116     */
117    private int precision;
118
119    /**
120     *  Tension defines how sharply does the curve bends
121     */
122    private double tension;
123
124    /**
125     * A flag that can be set to specify 
126     * to fill the area under the Bezier curve.
127     */
128    private FillType fillType;
129
130    private GradientPaintTransformer gradientPaintTransformer;
131
132    /**
133     * Creates a new instance with the precision attribute defaulting to 5,
134     * the tension attribute defaulting to 2  
135     * and no fill of the area 'under' the Bezier curve.
136     */
137    public XYBezierRenderer() {
138        this(5, 25, FillType.NONE);
139    }
140
141    /**
142     * Creates a new renderer with the specified precision and tension
143     * and no fill of the area 'under' (between '0' and) the Bezier curve.
144     *
145     * @param precision  the number of points between data items.
146     * @param tension  value to define how sharply the curve bends
147     */
148    public XYBezierRenderer(int precision, double tension) {
149        this(precision, tension ,FillType.NONE);
150    }
151
152    /**
153     * Creates a new renderer with the specified precision
154     * and specified fill of the area 'under' (between '0' and) the Bezier curve.
155     *
156     * @param precision  the number of points between data items.
157     * @param tension  value to define how sharply the Bezier curve bends
158     * @param fillType  the type of fill beneath the curve ({@code null}
159     *     not permitted).
160     *
161     * @since 1.0.17
162     */
163    public XYBezierRenderer(int precision, double tension, FillType fillType) {
164        super();
165        if (precision <= 0) {
166            throw new IllegalArgumentException("Requires precision > 0.");
167        }
168        if (tension <= 0) {
169            throw new IllegalArgumentException("Requires precision > 0.");
170        }
171        Args.nullNotPermitted(fillType, "fillType");
172        this.precision = precision;
173        this.tension = tension;
174        this.fillType = fillType;
175        this.gradientPaintTransformer = new StandardGradientPaintTransformer();
176    }
177
178    /**
179     * Returns the number of line segments used to approximate the Bezier
180     * curve between data points.
181     *
182     * @return The number of line segments.
183     *
184     * @see #setPrecision(int)
185     */
186    public int getPrecision() {
187        return this.precision;
188    }
189
190    /**
191     * Set the resolution of Bezier curves and sends a {@link RendererChangeEvent}
192     * to all registered listeners.
193     *
194     * @param p  number of line segments between points (must be &gt; 0).
195     *
196     * @see #getPrecision()
197     */
198    public void setPrecision(int p) {
199        if (p <= 0) {
200            throw new IllegalArgumentException("Requires p > 0.");
201        }
202        this.precision = p;
203        fireChangeEvent();
204    }
205
206    /**
207     * Returns the value of the tension which defines how sharply 
208     * does the curve bends
209     *
210     * @return The value of tesion.
211     *
212     * @see #setTension(double)
213     */
214    public double getTension() {
215        return this.tension;
216    }
217
218    /**
219     * Set the value of the tension which defines how sharply 
220     * does the curve bends and sends a {@link RendererChangeEvent}
221     * to all registered listeners.
222     *
223     * @param t  value of tension (must be &gt; 0).
224     *
225     * @see #getTension()
226     */
227    public void setTension(double t) {
228        if (t <= 0) {
229            throw new IllegalArgumentException("Requires tension > 0.");
230        }
231        this.tension = t;
232        fireChangeEvent();
233    }
234
235    /**
236     * Returns the type of fill that the renderer draws beneath the curve.
237     *
238     * @return The type of fill (never {@code null}).
239     *
240     * @see #setFillType(FillType)
241     *
242     * @since 1.0.17
243     */
244    public FillType getFillType() {
245        return this.fillType;
246    }
247
248    /**
249     * Set the fill type and sends a {@link RendererChangeEvent}
250     * to all registered listeners.
251     *
252     * @param fillType   the fill type ({@code null} not permitted).
253     *
254     * @see #getFillType()
255     *
256     * @since 1.0.17
257     */
258    public void setFillType(FillType fillType) {
259        this.fillType = fillType;
260        fireChangeEvent();
261    }
262
263    /**
264     * Returns the gradient paint transformer, or {@code null}.
265     *
266     * @return The gradient paint transformer (possibly {@code null}).
267     *
268     * @since 1.0.17
269     */
270    public GradientPaintTransformer getGradientPaintTransformer() {
271        return this.gradientPaintTransformer;
272    }
273
274    /**
275     * Sets the gradient paint transformer and sends a 
276     * {@link RendererChangeEvent} to all registered listeners.
277     *
278     * @param gpt  the transformer ({@code null} permitted).
279     *
280     * @since 1.0.17
281     */
282    public void setGradientPaintTransformer(GradientPaintTransformer gpt) {
283        this.gradientPaintTransformer = gpt;
284        fireChangeEvent();
285    }
286
287    /**
288     * Initialises the renderer.
289     * <P>
290     * This method will be called before the first item is rendered, giving the
291     * renderer an opportunity to initialise any state information it wants to
292     * maintain.  The renderer can do nothing if it chooses.
293     *
294     * @param g2  the graphics device.
295     * @param dataArea  the area inside the axes.
296     * @param plot  the plot.
297     * @param data  the data.
298     * @param info  an optional info collection object to return data back to
299     *              the caller.
300     *
301     * @return The renderer state.
302     */
303    @Override
304    public XYItemRendererState initialise(Graphics2D g2, Rectangle2D dataArea,
305                                          XYPlot plot, XYDataset data, PlotRenderingInfo info) {
306
307        setDrawSeriesLineAsPath(true);
308        XYBezierState state = new XYBezierState(info);
309        state.setProcessVisibleItemsOnly(false);
310        return state;
311    }
312
313
314    /**
315     * Draws the item (first pass). This method draws the lines
316     * connecting the items. Instead of drawing separate lines,
317     * a GeneralPath is constructed and drawn at the end of
318     * the series painting.
319     *
320     * @param g2  the graphics device.
321     * @param state  the renderer state.
322     * @param plot  the plot (can be used to obtain standard color information
323     *              etc).
324     * @param dataset  the dataset.
325     * @param pass  the pass.
326     * @param series  the series index (zero-based).
327     * @param item  the item index (zero-based).
328     * @param xAxis  the domain axis.
329     * @param yAxis  the range axis.
330     * @param dataArea  the area within which the data is being drawn.
331     */
332    @Override
333    protected void drawPrimaryLineAsPath(XYItemRendererState state,
334                                         Graphics2D g2, XYPlot plot, XYDataset dataset, int pass,
335                                         int series, int item, ValueAxis xAxis, ValueAxis yAxis,
336                                         Rectangle2D dataArea) {
337
338        XYBezierState s = (XYBezierState) state;
339        RectangleEdge xAxisLocation = plot.getDomainAxisEdge();
340        RectangleEdge yAxisLocation = plot.getRangeAxisEdge();
341
342        // get the data points
343        double x1 = dataset.getXValue(series, item);
344        double y1 = dataset.getYValue(series, item);
345        double transX1 = xAxis.valueToJava2D(x1, dataArea, xAxisLocation);
346        double transY1 = yAxis.valueToJava2D(y1, dataArea, yAxisLocation);
347
348        // Collect points
349        if (!Double.isNaN(transX1) && !Double.isNaN(transY1)) {
350            Point2D p = plot.getOrientation() == PlotOrientation.HORIZONTAL
351                    ? new Point2D.Float((float) transY1, (float) transX1)
352                    : new Point2D.Float((float) transX1, (float) transY1);
353            if (!s.points.contains(p))
354                s.points.add(p);
355        }
356
357        if (item == dataset.getItemCount(series) - 1) {     // construct path
358            if (s.points.size() > 1) {
359                Point2D origin;
360                if (this.fillType == FillType.TO_ZERO) {
361                    float xz = (float) xAxis.valueToJava2D(0, dataArea,
362                            yAxisLocation);
363                    float yz = (float) yAxis.valueToJava2D(0, dataArea,
364                            yAxisLocation);
365                    origin = plot.getOrientation() == PlotOrientation.HORIZONTAL
366                            ? new Point2D.Float(yz, xz)
367                            : new Point2D.Float(xz, yz);
368                } else if (this.fillType == FillType.TO_LOWER_BOUND) {
369                    float xlb = (float) xAxis.valueToJava2D(
370                            xAxis.getLowerBound(), dataArea, xAxisLocation);
371                    float ylb = (float) yAxis.valueToJava2D(
372                            yAxis.getLowerBound(), dataArea, yAxisLocation);
373                    origin = plot.getOrientation() == PlotOrientation.HORIZONTAL
374                            ? new Point2D.Float(ylb, xlb)
375                            : new Point2D.Float(xlb, ylb);
376                } else {// fillType == TO_UPPER_BOUND
377                    float xub = (float) xAxis.valueToJava2D(
378                            xAxis.getUpperBound(), dataArea, xAxisLocation);
379                    float yub = (float) yAxis.valueToJava2D(
380                            yAxis.getUpperBound(), dataArea, yAxisLocation);
381                    origin = plot.getOrientation() == PlotOrientation.HORIZONTAL
382                            ? new Point2D.Float(yub, xub)
383                            : new Point2D.Float(xub, yub);
384                }
385
386                // we need at least two points to draw something
387                Point2D cp0 = s.points.get(0);
388                s.seriesPath.moveTo(cp0.getX(), cp0.getY());
389                if (this.fillType != FillType.NONE) {
390                    if (plot.getOrientation() == PlotOrientation.HORIZONTAL) {
391                        s.fillArea.moveTo(origin.getX(), cp0.getY());
392                    } else {
393                        s.fillArea.moveTo(cp0.getX(), origin.getY());
394                    }
395                    s.fillArea.lineTo(cp0.getX(), cp0.getY());
396                }
397                if (s.points.size() == 2) {
398                    // we need at least 3 points to Bezier. Draw simple line
399                    // for two points
400                    Point2D cp1 = s.points.get(1);
401                    if (this.fillType != FillType.NONE) {
402                        s.fillArea.lineTo(cp1.getX(), cp1.getY());
403                        s.fillArea.lineTo(cp1.getX(), origin.getY());
404                        s.fillArea.closePath();
405                    }
406                    s.seriesPath.lineTo(cp1.getX(), cp1.getY());
407                }
408                else if (s.points.size() == 3) {
409                    // with 3 points only initial and end Bezier curves are required.
410
411                    Point2D[] pInitial = getInitalPoints(s);
412                    addBezierPointsToSeriesPath(pInitial, s);
413                    Point2D[] pFinal = getFinalPoints(s);
414                    addBezierPointsToSeriesPath(pFinal, s);
415
416                }
417                else {
418                    // construct Bezier curve
419                    int np = s.points.size(); // number of points
420                    for(int i = 0; i < np - 1; i++) {
421                        if(i == 0) {
422                            // 3 points, 2 lines (initial an final Bezier curves)
423                            Point2D[] initial3Points = new Point2D[3];
424                            initial3Points[0] = s.points.get(0);
425                            initial3Points[1] = s.points.get(1);
426                            initial3Points[2] = s.points.get(2);
427                            Point2D[] pInitial = calcSegmentPointsInitial(initial3Points);
428                            addBezierPointsToSeriesPath(pInitial, s);
429                        }
430                        if(i == np - 2) {
431                            Point2D[] final3Points = new Point2D[4];
432                            final3Points[1] = s.points.get(np-3);
433                            final3Points[2] = s.points.get(np-2);
434                            final3Points[3] = s.points.get(np-1);
435                            // No need for final3Points[0]. Not required
436                            Point2D[] pFinal = calcSegmentPointsFinal(final3Points);
437                            addBezierPointsToSeriesPath(pFinal, s);
438                        }
439                        if ((i != 0) && (i != (np - 2))){
440                            Point2D[] original4Points = new Point2D[4];
441                            original4Points[0] = s.points.get(i - 1);
442                            original4Points[1] = s.points.get(i);
443                            original4Points[2] = s.points.get(i + 1);
444                            original4Points[3] = s.points.get(i + 2);
445                            Point2D[] pMedium = calculateSegmentPoints(original4Points);
446                            addBezierPointsToSeriesPath(pMedium, s);
447                        }
448                    }
449                }
450                // Add last point @ y=0 for fillPath and close path
451                if (this.fillType != FillType.NONE) {
452                    if (plot.getOrientation() == PlotOrientation.HORIZONTAL) {
453                        s.fillArea.lineTo(origin.getX(), s.points.get(
454                                s.points.size() - 1).getY());
455                    } else {
456                        s.fillArea.lineTo(s.points.get(
457                                s.points.size() - 1).getX(), origin.getY());
458                    }
459                    s.fillArea.closePath();
460                }
461                // fill under the curve...
462                if (this.fillType != FillType.NONE) {
463                    Paint fp = getSeriesFillPaint(series);
464                    if (this.gradientPaintTransformer != null
465                            && fp instanceof GradientPaint) {
466                        GradientPaint gp = this.gradientPaintTransformer
467                                .transform((GradientPaint) fp, s.fillArea);
468                        g2.setPaint(gp);
469                    } else {
470                        g2.setPaint(fp);
471                    }
472                    g2.fill(s.fillArea);
473                    s.fillArea.reset();
474                }
475                // then draw the line...
476                drawFirstPassShape(g2, pass, series, item, s.seriesPath);
477            }
478            // reset points vector
479            s.points = new ArrayList<>();
480        }
481    }
482
483    private void addBezierPointsToSeriesPath(Point2D[] segmentPoints, XYBezierState s) {
484        double x;
485        double y;
486        for (int t = 0 ; t <= this.precision; t++) {
487            double k = (double)t / this.precision;
488            double r = 1- k;
489
490            x = Math.pow(r, 3) * segmentPoints[0].getX() + 3 * k * Math.pow(r, 2) * segmentPoints[1].getX()
491                    + 3 * Math.pow(k, 2) * (1 - k) * segmentPoints[2].getX() + Math.pow(k, 3) * segmentPoints[3].getX();
492            y = Math.pow(r, 3) * segmentPoints[0].getY() + 3 * k * Math.pow(r, 2) * segmentPoints[1].getY()
493                    + 3 * Math.pow(k, 2) * (1 - k) * segmentPoints[2].getY() + Math.pow(k, 3) * segmentPoints[3].getY();
494            s.seriesPath.lineTo(x, y);
495            if (this.fillType != FillType.NONE) {
496                s.fillArea.lineTo(x, y);
497            }
498        }
499    }
500
501    private Point2D[] getFinalPoints(XYBezierState s) {
502        Point2D[] final3Points = new Point2D[4];
503        final3Points[1] = s.points.get(0);
504        final3Points[2] = s.points.get(1);
505        final3Points[3] = s.points.get(2);
506        // No need for final3Points[0]. Not required
507        Point2D[] pFinal = calcSegmentPointsFinal(final3Points);//TENSION = 1.5
508        return pFinal;
509    }
510
511    private Point2D[] getInitalPoints(XYBezierState s) {
512        Point2D[] initial3Points = new Point2D[3];
513        initial3Points[0] = s.points.get(0);
514        initial3Points[1] = s.points.get(1);
515        initial3Points[2] = s.points.get(2);
516        Point2D[] pInitial = calcSegmentPointsInitial(initial3Points);// TENSION = 1.5
517        return pInitial;
518    }
519
520    private Point2D[] calculateSegmentPoints(Point2D[] original4Points) {
521        Point2D[] points = new Point2D[4];
522        points[0] = original4Points[1];
523        points[3] = original4Points[2];
524        for(int i = 1; i < 3; i++) {
525            Point2D aux1 = calcUnitaryVector(original4Points[i-1], original4Points[i]);
526            Point2D aux2 = calcUnitaryVector(original4Points[i+1], original4Points[i]);
527            Point2D aux3 = calcUnitaryVector(aux2, aux1);
528
529            double x = original4Points[i].getX() + Math.pow(-1.0, i+1) * tension * aux3.getX();
530            double y = original4Points[i].getY() + Math.pow(-1.0, i+1) * tension * aux3.getY();
531            points[i] = new Point2D.Double(x, y);
532        }
533        return points;
534    }
535
536    private Point2D[] calcSegmentPointsInitial(Point2D[] original3P) {
537        Point2D[] points = new Point2D[4];
538        points[0] = original3P[0];// Endpoint 1
539        points[3] = original3P[1];// Endpoint 2
540        // Control point 1
541        Point2D auxInitial = calcUnitaryVector(original3P[0], original3P[1]);
542        points[1] = original3P[0];// new Point2D.Double(x0, y0);
543        // Control point 2
544        Point2D aux2 = calcUnitaryVector(original3P[2], original3P[1]);
545        Point2D aux3 = calcUnitaryVector(auxInitial, aux2);
546        double x = original3P[1].getX() + tension * aux3.getX();
547        double y = original3P[1].getY() + tension * aux3.getY();
548        points[2] = new Point2D.Double(x, y);
549        return points;
550    }
551
552    private Point2D[] calcSegmentPointsFinal(Point2D[] original3P) {
553        /*
554         * Each segment is defined by its two endpoints and two control points. A
555         * control point determines the tangent at the corresponding endpoint.
556         */
557        Point2D[] points = new Point2D[4];
558        points[0] = original3P[2];// Endpoint 1
559        points[3] = original3P[3];// Endpoint 2
560        // Control point 2: points[2]
561        Point2D auxInitial = calcUnitaryVector(original3P[3], original3P[2]);
562        points[2] = original3P[3];// new Point2D.Double(x0, y0);
563        // Control point 1
564        Point2D aux1 = calcUnitaryVector(original3P[3], original3P[2]);
565        Point2D aux2 = calcUnitaryVector(original3P[1], original3P[2]);
566        Point2D aux3 = calcUnitaryVector(aux1, aux2);
567        double x = original3P[2].getX() + tension * aux3.getX();
568        double y = original3P[2].getY() + tension * aux3.getY();
569        points[1] = new Point2D.Double(x, y);
570        return points;
571    }
572
573    private Point2D calcUnitaryVector(Point2D pOrigin, Point2D pEnd) {
574        double module = Math.sqrt(Math.pow(pEnd.getX() - pOrigin.getX(), 2) +
575                Math.pow(pEnd.getY() - pOrigin.getY(), 2));
576        if (module == 0) {
577            return null;
578        }
579        return new Point2D.Double((pEnd.getX() - pOrigin.getX()) / module,
580                (pEnd.getY() - pOrigin.getY()) /module);
581    }
582
583
584    /**
585     * Tests this renderer for equality with an arbitrary object.
586     *
587     * @param obj  the object ({@code null} permitted).
588     *
589     * @return A boolean.
590     */
591    @Override
592    public boolean equals(Object obj) {
593        if (obj == this) {
594            return true;
595        }
596        if (!(obj instanceof XYBezierRenderer)) {
597            return false;
598        }
599        XYBezierRenderer that = (XYBezierRenderer) obj;
600        if (this.precision != that.precision) {
601            return false;
602        }
603        if (this.fillType != that.fillType) {
604            return false;
605        }
606        if (!Objects.equals(this.gradientPaintTransformer, that.gradientPaintTransformer)) {
607            return false;
608        }
609        return super.equals(obj);
610    }
611}