    001    /* ===========================================================
    002     * JFreeChart : a free chart library for the Java(tm) platform
    003     * ===========================================================
    004     *
    005     * (C) Copyright 2000-2005, by Object Refinery Limited 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     * [Java is a trademark or registered trademark of Sun Microsystems, Inc.
    025     * in the United States and other countries.]
    026     *
    027     * -------------------
    028     * XYAreaRenderer.java
    029     * -------------------
    030     * (C) Copyright 2002-2005, by Hari and Contributors.
    031     *
    032     * Original Author:  Hari (ourhari@hotmail.com);
    033     * Contributor(s):   David Gilbert (for Object Refinery Limited);
    034     *                   Richard Atkinson;
    035     *                   Christian W. Zuckschwerdt;
    036     *
    037     * $Id: XYAreaRenderer.java,v 1.12.2.4 2005/12/02 11:59:43 mungady Exp $
    038     *
    039     * Changes:
    040     * --------
    041     * 03-Apr-2002 : Version 1, contributed by Hari.  This class is based on the
    042     *               StandardXYItemRenderer class (DG);
    043     * 09-Apr-2002 : Removed the translated zero from the drawItem method -
    044     *               overridden the initialise() method to calculate it (DG);
    045     * 30-May-2002 : Added tool tip generator to constructor to match super
    046     *               class (DG);
    047     * 25-Jun-2002 : Removed unnecessary local variable (DG);
    048     * 05-Aug-2002 : Small modification to drawItem method to support URLs for HTML
    049     *               image maps (RA);
    050     * 01-Oct-2002 : Fixed errors reported by Checkstyle (DG);
    051     * 07-Nov-2002 : Renamed AreaXYItemRenderer --> XYAreaRenderer (DG);
    052     * 25-Mar-2003 : Implemented Serializable (DG);
    053     * 01-May-2003 : Modified drawItem() method signature (DG);
    054     * 27-Jul-2003 : Made line and polygon properties protected rather than
    055     *               private (RA);
    056     * 30-Jul-2003 : Modified entity constructor (CZ);
    057     * 20-Aug-2003 : Implemented Cloneable and PublicCloneable (DG);
    058     * 16-Sep-2003 : Changed ChartRenderingInfo --> PlotRenderingInfo (DG);
    059     * 07-Oct-2003 : Added renderer state (DG);
    060     * 08-Dec-2003 : Modified hotspot for chart entity (DG);
    061     * 10-Feb-2004 : Changed the drawItem() method to make cut-and-paste overriding
    062     *               easier.  Also moved state class into this class (DG);
    063     * 25-Feb-2004 : Replaced CrosshairInfo with CrosshairState.  Renamed
    064     *               XYToolTipGenerator --> XYItemLabelGenerator (DG);
    065     * 15-Jul-2004 : Switched getX() with getXValue() and getY() with
    066     *               getYValue() (DG);
    067     * 11-Nov-2004 : Now uses ShapeUtilities to translate shapes (DG);
    068     * 19-Jan-2005 : Now accesses primitives only from dataset (DG);
    069     * 21-Mar-2005 : Override getLegendItem() and equals() methods (DG);
    070     * 20-Apr-2005 : Use generators for legend tooltips and URLs (DG);
    071     *
    072     */
    073
    074    package org.jfree.chart.renderer.xy;
    075
    076    import java.awt.Graphics2D;
    077    import java.awt.Paint;
    078    import java.awt.Polygon;
    079    import java.awt.Shape;
    080    import java.awt.Stroke;
    081    import java.awt.geom.GeneralPath;
    082    import java.awt.geom.Line2D;
    083    import java.awt.geom.Rectangle2D;
    084    import java.io.IOException;
    085    import java.io.ObjectInputStream;
    086    import java.io.ObjectOutputStream;
    087    import java.io.Serializable;
    088
    089    import org.jfree.chart.LegendItem;
    090    import org.jfree.chart.axis.ValueAxis;
    091    import org.jfree.chart.entity.EntityCollection;
    092    import org.jfree.chart.entity.XYItemEntity;
    093    import org.jfree.chart.event.RendererChangeEvent;
    094    import org.jfree.chart.labels.XYSeriesLabelGenerator;
    095    import org.jfree.chart.labels.XYToolTipGenerator;
    096    import org.jfree.chart.plot.CrosshairState;
    097    import org.jfree.chart.plot.PlotOrientation;
    098    import org.jfree.chart.plot.PlotRenderingInfo;
    099    import org.jfree.chart.plot.XYPlot;
    100    import org.jfree.chart.urls.XYURLGenerator;
    101    import org.jfree.data.xy.XYDataset;
    102    import org.jfree.io.SerialUtilities;
    103    import org.jfree.util.PublicCloneable;
    104    import org.jfree.util.ShapeUtilities;
    105
    106    /**
    107     * Area item renderer for an {@link XYPlot}.  This class can draw (a) shapes at
    108     * each point, or (b) lines between points, or (c) both shapes and lines,
    109     * or (d) filled areas, or (e) filled areas and shapes.
    110     */
    111    public class XYAreaRenderer extends AbstractXYItemRenderer
    112                                implements XYItemRenderer,
    113                                           Cloneable,
    114                                           PublicCloneable,
    115                                           Serializable {
    116
    117        /** For serialization. */
    118        private static final long serialVersionUID = -4481971353973876747L;
    119
    120        /**
    121         * A state object used by this renderer.
    122         */
    123        static class XYAreaRendererState extends XYItemRendererState {
    124
    125            /** Working storage for the area under one series. */
    126            public Polygon area;
    127
    128            /** Working line that can be recycled. */
    129            public Line2D line;
    130
    131            /**
    132             * Creates a new state.
    133             *
    134             * @param info  the plot rendering info.
    135             */
    136            public XYAreaRendererState(PlotRenderingInfo info) {
    137                super(info);
    138                this.area = new Polygon();
    139                this.line = new Line2D.Double();
    140            }
    141
    142        }
    143
    144        /** Useful constant for specifying the type of rendering (shapes only). */
    145        public static final int SHAPES = 1;
    146
    147        /** Useful constant for specifying the type of rendering (lines only). */
    148        public static final int LINES = 2;
    149
    150        /**
    151         * Useful constant for specifying the type of rendering (shapes and lines).
    152         */
    153        public static final int SHAPES_AND_LINES = 3;
    154
    155        /** Useful constant for specifying the type of rendering (area only). */
    156        public static final int AREA = 4;
    157
    158        /**
    159         * Useful constant for specifying the type of rendering (area and shapes).
    160         */
    161        public static final int AREA_AND_SHAPES = 5;
    162
    163        /** A flag indicating whether or not shapes are drawn at each XY point. */
    164        private boolean plotShapes;
    165
    166        /** A flag indicating whether or not lines are drawn between XY points. */
    167        private boolean plotLines;
    168
    169        /** A flag indicating whether or not Area are drawn at each XY point. */
    170        private boolean plotArea;
    171
    172        /** A flag that controls whether or not the outline is shown. */
    173        private boolean showOutline;
    174
    175        /**
    176         * The shape used to represent an area in each legend item (this should
    177         * never be <code>null</code>).
    178         */
    179        private transient Shape legendArea;
    180
    181        /**
    182         * Constructs a new renderer.
    183         */
    184        public XYAreaRenderer() {
    185            this(AREA);
    186        }
    187
    188        /**
    189         * Constructs a new renderer.
    190         *
    191         * @param type  the type of the renderer.
    192         */
    193        public XYAreaRenderer(int type) {
    194            this(type, null, null);
    195        }
    196
    197        /**
    198         * Constructs a new renderer.
    199         * <p>
    200         * To specify the type of renderer, use one of the constants: SHAPES, LINES,
    201         * SHAPES_AND_LINES, AREA or AREA_AND_SHAPES.
    202         *
    203         * @param type  the type of renderer.
    204         * @param toolTipGenerator  the tool tip generator to use
    205         *                          (<code>null</code> permitted).
    206         * @param urlGenerator  the URL generator (<code>null</code> permitted).
    207         */
    208        public XYAreaRenderer(int type, XYToolTipGenerator toolTipGenerator,
    209                              XYURLGenerator urlGenerator) {
    210
    211            super();
    212            setBaseToolTipGenerator(toolTipGenerator);
    213            setURLGenerator(urlGenerator);
    214
    215            if (type == SHAPES) {
    216                this.plotShapes = true;
    217            }
    218            if (type == LINES) {
    219                this.plotLines = true;
    220            }
    221            if (type == SHAPES_AND_LINES) {
    222                this.plotShapes = true;
    223                this.plotLines = true;
    224            }
    225            if (type == AREA) {
    226                this.plotArea = true;
    227            }
    228            if (type == AREA_AND_SHAPES) {
    229                this.plotArea = true;
    230                this.plotShapes = true;
    231            }
    232            this.showOutline = false;
    233            GeneralPath area = new GeneralPath();
    234            area.moveTo(0.0f, -4.0f);
    235            area.lineTo(3.0f, -2.0f);
    236            area.lineTo(4.0f, 4.0f);
    237            area.lineTo(-4.0f, 4.0f);
    238            area.lineTo(-3.0f, -2.0f);
    239            area.closePath();
    240            this.legendArea = area;
    241
    242        }
    243
    244        /**
    245         * Returns a flag that controls whether or not outlines of the areas are
    246         * drawn.
    247         *
    248         * @return The flag.
    249         */
    250        public boolean isOutline() {
    251            return this.showOutline;
    252        }
    253
    254        /**
    255         * Sets a flag that controls whether or not outlines of the areas are drawn.
    256         *
    257         * @param show  the flag.
    258         */
    259        public void setOutline(boolean show) {
    260            this.showOutline = show;
    261        }
    262
    263        /**
    264         * Returns true if shapes are being plotted by the renderer.
    265         *
    266         * @return <code>true</code> if shapes are being plotted by the renderer.
    267         */
    268        public boolean getPlotShapes() {
    269            return this.plotShapes;
    270        }
    271
    272        /**
    273         * Returns true if lines are being plotted by the renderer.
    274         *
    275         * @return <code>true</code> if lines are being plotted by the renderer.
    276         */
    277        public boolean getPlotLines() {
    278            return this.plotLines;
    279        }
    280
    281        /**
    282         * Returns true if Area is being plotted by the renderer.
    283         *
    284         * @return <code>true</code> if Area is being plotted by the renderer.
    285         */
    286        public boolean getPlotArea() {
    287            return this.plotArea;
    288        }
    289
    290        /**
    291         * Returns the shape used to represent an area in the legend.
    292         *
    293         * @return The legend area (never <code>null</code>).
    294         */
    295        public Shape getLegendArea() {
    296            return this.legendArea;
    297        }
    298
    299        /**
    300         * Sets the shape used as an area in each legend item and sends a
    301         * {@link RendererChangeEvent} to all registered listeners.
    302         *
    303         * @param area  the area (<code>null</code> not permitted).
    304         */
    305        public void setLegendArea(Shape area) {
    306            if (area == null) {
    307                throw new IllegalArgumentException("Null 'area' argument.");
    308            }
    309            this.legendArea = area;
    310            notifyListeners(new RendererChangeEvent(this));
    311        }
    312
    313        /**
    314         * Initialises the renderer and returns a state object that should be
    315         * passed to all subsequent calls to the drawItem() method.
    316         *
    317         * @param g2  the graphics device.
    318         * @param dataArea  the area inside the axes.
    319         * @param plot  the plot.
    320         * @param data  the data.
    321         * @param info  an optional info collection object to return data back to
    322         *              the caller.
    323         *
    324         * @return A state object for use by the renderer.
    325         */
    326        public XYItemRendererState initialise(Graphics2D g2,
    327                                              Rectangle2D dataArea,
    328                                              XYPlot plot,
    329                                              XYDataset data,
    330                                              PlotRenderingInfo info) {
    331            XYAreaRendererState state = new XYAreaRendererState(info);
    332            return state;
    333        }
    334
    335        /**
    336         * Returns a default legend item for the specified series.  Subclasses
    337         * should override this method to generate customised items.
    338         *
    339         * @param datasetIndex  the dataset index (zero-based).
    340         * @param series  the series index (zero-based).
    341         *
    342         * @return A legend item for the series.
    343         */
    344        public LegendItem getLegendItem(int datasetIndex, int series) {
    345            LegendItem result = null;
    346            XYPlot xyplot = getPlot();
    347            if (xyplot != null) {
    348                XYDataset dataset = xyplot.getDataset(datasetIndex);
    349                if (dataset != null) {
    350                    XYSeriesLabelGenerator lg = getLegendItemLabelGenerator();
    351                    String label = lg.generateLabel(dataset, series);
    352                    String description = label;
    353                    String toolTipText = null;
    354                    if (getLegendItemToolTipGenerator() != null) {
    355                        toolTipText = getLegendItemToolTipGenerator().generateLabel(
    356                            dataset, series
    357                        );
    358                    }
    359                    String urlText = null;
    360                    if (getLegendItemURLGenerator() != null) {
    361                        urlText = getLegendItemURLGenerator().generateLabel(
    362                            dataset, series
    363                        );
    364                    }
    365                    Paint paint = getSeriesPaint(series);
    366                    result = new LegendItem(label, description, toolTipText,
    367                            urlText, this.legendArea, paint);
    368                }
    369            }
    370            return result;
    371        }
    372
    373        /**
    374         * Draws the visual representation of a single data item.
    375         *
    376         * @param g2  the graphics device.
    377         * @param state  the renderer state.
    378         * @param dataArea  the area within which the data is being drawn.
    379         * @param info  collects information about the drawing.
    380         * @param plot  the plot (can be used to obtain standard color information
    381         *              etc).
    382         * @param domainAxis  the domain axis.
    383         * @param rangeAxis  the range axis.
    384         * @param dataset  the dataset.
    385         * @param series  the series index (zero-based).
    386         * @param item  the item index (zero-based).
    387         * @param crosshairState  crosshair information for the plot
    388         *                        (<code>null</code> permitted).
    389         * @param pass  the pass index.
    390         */
    391        public void drawItem(Graphics2D g2,
    392                             XYItemRendererState state,
    393                             Rectangle2D dataArea,
    394                             PlotRenderingInfo info,
    395                             XYPlot plot,
    396                             ValueAxis domainAxis,
    397                             ValueAxis rangeAxis,
    398                             XYDataset dataset,
    399                             int series,
    400                             int item,
    401                             CrosshairState crosshairState,
    402                             int pass) {
    403
    404            if (!getItemVisible(series, item)) {
    405                return;
    406            }
    407            XYAreaRendererState areaState = (XYAreaRendererState) state;
    408
    409            // get the data point...
    410            double x1 = dataset.getXValue(series, item);
    411            double y1 = dataset.getYValue(series, item);
    412            if (Double.isNaN(y1)) {
    413                y1 = 0.0;
    414            }
    415            double transX1 = domainAxis.valueToJava2D(
    416                x1, dataArea, plot.getDomainAxisEdge()
    417            );
    418            double transY1 = rangeAxis.valueToJava2D(
    419                y1, dataArea, plot.getRangeAxisEdge()
    420            );
    421
    422            // get the previous point and the next point so we can calculate a
    423            // "hot spot" for the area (used by the chart entity)...
    424            int itemCount = dataset.getItemCount(series);
    425            double x0 = dataset.getXValue(series, Math.max(item - 1, 0));
    426            double y0 = dataset.getYValue(series, Math.max(item - 1, 0));
    427            if (Double.isNaN(y0)) {
    428                y0 = 0.0;
    429            }
    430            double transX0 = domainAxis.valueToJava2D(
    431                x0, dataArea, plot.getDomainAxisEdge()
    432            );
    433            double transY0 = rangeAxis.valueToJava2D(
    434                y0, dataArea, plot.getRangeAxisEdge()
    435            );
    436
    437            double x2 = dataset.getXValue(
    438                series, Math.min(item + 1, itemCount - 1)
    439            );
    440            double y2 = dataset.getYValue(
    441                series, Math.min(item + 1, itemCount - 1)
    442            );
    443            if (Double.isNaN(y2)) {
    444                y2 = 0.0;
    445            }
    446            double transX2 = domainAxis.valueToJava2D(
    447                x2, dataArea, plot.getDomainAxisEdge()
    448            );
    449            double transY2 = rangeAxis.valueToJava2D(
    450                y2, dataArea, plot.getRangeAxisEdge()
    451            );
    452
    453            double transZero = rangeAxis.valueToJava2D(
    454                0.0, dataArea, plot.getRangeAxisEdge()
    455            );
    456            Polygon hotspot = null;
    457            if (plot.getOrientation() == PlotOrientation.HORIZONTAL) {
    458                hotspot = new Polygon();
    459                hotspot.addPoint(
    460                    (int) transZero, (int) ((transX0 + transX1) / 2.0)
    461                );
    462                hotspot.addPoint(
    463                    (int) ((transY0 + transY1) / 2.0),
    464                    (int) ((transX0 + transX1) / 2.0)
    465                );
    466                hotspot.addPoint((int) transY1, (int) transX1);
    467                hotspot.addPoint(
    468                    (int) ((transY1 + transY2) / 2.0),
    469                    (int) ((transX1 + transX2) / 2.0)
    470                );
    471                hotspot.addPoint(
    472                    (int) transZero, (int) ((transX1 + transX2) / 2.0)
    473                );
    474            }
    475            else {  // vertical orientation
    476                hotspot = new Polygon();
    477                hotspot.addPoint(
    478                    (int) ((transX0 + transX1) / 2.0), (int) transZero
    479                );
    480                hotspot.addPoint(
    481                    (int) ((transX0 + transX1) / 2.0),
    482                    (int) ((transY0 + transY1) / 2.0)
    483                );
    484                hotspot.addPoint((int) transX1, (int) transY1);
    485                hotspot.addPoint(
    486                    (int) ((transX1 + transX2) / 2.0),
    487                    (int) ((transY1 + transY2) / 2.0)
    488                );
    489                hotspot.addPoint(
    490                    (int) ((transX1 + transX2) / 2.0), (int) transZero
    491                );
    492            }
    493
    494            if (item == 0) {  // create a new area polygon for the series
    495                areaState.area = new Polygon();
    496                // the first point is (x, 0)
    497                double zero = rangeAxis.valueToJava2D(
    498                    0.0, dataArea, plot.getRangeAxisEdge()
    499                );
    500                if (plot.getOrientation() == PlotOrientation.VERTICAL) {
    501                    areaState.area.addPoint((int) transX1, (int) zero);
    502                }
    503                else if (plot.getOrientation() == PlotOrientation.HORIZONTAL) {
    504                    areaState.area.addPoint((int) zero, (int) transX1);
    505                }
    506            }
    507
    508            // Add each point to Area (x, y)
    509            if (plot.getOrientation() == PlotOrientation.VERTICAL) {
    510                areaState.area.addPoint((int) transX1, (int) transY1);
    511            }
    512            else if (plot.getOrientation() == PlotOrientation.HORIZONTAL) {
    513                areaState.area.addPoint((int) transY1, (int) transX1);
    514            }
    515
    516            PlotOrientation orientation = plot.getOrientation();
    517            Paint paint = getItemPaint(series, item);
    518            Stroke stroke = getItemStroke(series, item);
    519            g2.setPaint(paint);
    520            g2.setStroke(stroke);
    521
    522            Shape shape = null;
    523            if (getPlotShapes()) {
    524                shape = getItemShape(series, item);
    525                if (orientation == PlotOrientation.VERTICAL) {
    526                    shape = ShapeUtilities.createTranslatedShape(
    527                        shape, transX1, transY1
    528                    );
    529                }
    530                else if (orientation == PlotOrientation.HORIZONTAL) {
    531                    shape = ShapeUtilities.createTranslatedShape(
    532                        shape, transY1, transX1
    533                    );
    534                }
    535                g2.draw(shape);
    536            }
    537
    538            if (getPlotLines()) {
    539                if (item > 0) {
    540                    if (plot.getOrientation() == PlotOrientation.VERTICAL) {
    541                        areaState.line.setLine(transX0, transY0, transX1, transY1);
    542                    }
    543                    else if (plot.getOrientation() == PlotOrientation.HORIZONTAL) {
    544                        areaState.line.setLine(transY0, transX0, transY1, transX1);
    545                    }
    546                    g2.draw(areaState.line);
    547                }
    548            }
    549
    550            // Check if the item is the last item for the series.
    551            // and number of items > 0.  We can't draw an area for a single point.
    552            if (getPlotArea() && item > 0 && item == (itemCount - 1)) {
    553
    554                if (orientation == PlotOrientation.VERTICAL) {
    555                    // Add the last point (x,0)
    556                    areaState.area.addPoint((int) transX1, (int) transZero);
    557                }
    558                else if (orientation == PlotOrientation.HORIZONTAL) {
    559                    // Add the last point (x,0)
    560                    areaState.area.addPoint((int) transZero, (int) transX1);
    561                }
    562
    563                g2.fill(areaState.area);
    564
    565                // draw an outline around the Area.
    566                if (isOutline()) {
    567                    g2.setStroke(getItemOutlineStroke(series, item));
    568                    g2.setPaint(getItemOutlinePaint(series, item));
    569                    g2.draw(areaState.area);
    570                }
    571            }
    572
    573            updateCrosshairValues(
    574                crosshairState, x1, y1, transX1, transY1, orientation
    575            );
    576
    577            // collect entity and tool tip information...
    578            if (state.getInfo() != null) {
    579                EntityCollection entities = state.getEntityCollection();
    580                if (entities != null && hotspot != null) {
    581                    String tip = null;
    582                    XYToolTipGenerator generator
    583                        = getToolTipGenerator(series, item);
    584                    if (generator != null) {
    585                        tip = generator.generateToolTip(dataset, series, item);
    586                    }
    587                    String url = null;
    588                    if (getURLGenerator() != null) {
    589                        url = getURLGenerator().generateURL(dataset, series, item);
    590                    }
    591                    XYItemEntity entity = new XYItemEntity(
    592                        hotspot, dataset, series, item, tip, url
    593                    );
    594                    entities.add(entity);
    595                }
    596            }
    597
    598        }
    599
    600        /**
    601         * Returns a clone of the renderer.
    602         *
    603         * @return A clone.
    604         *
    605         * @throws CloneNotSupportedException  if the renderer cannot be cloned.
    606         */
    607        public Object clone() throws CloneNotSupportedException {
    608            return super.clone();
    609        }
    610
    611        /**
    612         * Tests this renderer for equality with an arbitrary object.
    613         *
    614         * @param obj  the object (<code>null</code> permitted).
    615         *
    616         * @return A boolean.
    617         */
    618        public boolean equals(Object obj) {
    619            if (obj == this) {
    620                return true;
    621            }
    622            if (!(obj instanceof XYAreaRenderer)) {
    623                return false;
    624            }
    625            XYAreaRenderer that = (XYAreaRenderer) obj;
    626            if (this.plotArea != that.plotArea) {
    627                return false;
    628            }
    629            if (this.plotLines != that.plotLines) {
    630                return false;
    631            }
    632            if (this.plotShapes != that.plotShapes) {
    633                return false;
    634            }
    635            if (this.showOutline != that.showOutline) {
    636                return false;
    637            }
    638            if (!ShapeUtilities.equal(this.legendArea, that.legendArea)) {
    639                return false;
    640            }
    641            return true;
    642        }
    643
    644        /**
    645         * Provides serialization support.
    646         *
    647         * @param stream  the input stream.
    648         *
    649         * @throws IOException  if there is an I/O error.
    650         * @throws ClassNotFoundException  if there is a classpath problem.
    651         */
    652        private void readObject(ObjectInputStream stream)
    653                throws IOException, ClassNotFoundException {
    654            stream.defaultReadObject();
    655            this.legendArea = SerialUtilities.readShape(stream);
    656        }
    657
    658        /**
    659         * Provides serialization support.
    660         *
    661         * @param stream  the output stream.
    662         *
    663         * @throws IOException  if there is an I/O error.
    664         */
    665        private void writeObject(ObjectOutputStream stream) throws IOException {
    666            stream.defaultWriteObject();
    667            SerialUtilities.writeShape(this.legendArea, stream);
    668        }
    669    }
    670