ucar.unidata.idv.control.chart.XYChartManager.java Source code

Java tutorial

Introduction

Here is the source code for ucar.unidata.idv.control.chart.XYChartManager.java

Source

/*
 * Copyright 1997-2019 Unidata Program Center/University Corporation for
 * Atmospheric Research, P.O. Box 3000, Boulder, CO 80307,
 * support@unidata.ucar.edu.
 * 
 * This library is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation; either version 2.1 of the License, or (at
 * your option) any later version.
 * 
 * This library is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Lesser
 * General Public License for more details.
 * 
 * You should have received a copy of the GNU Lesser General Public License
 * along with this library; if not, write to the Free Software Foundation,
 * Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
 */

package ucar.unidata.idv.control.chart;

import org.jfree.chart.LegendItem;
import org.jfree.chart.annotations.XYAnnotation;
import org.jfree.chart.axis.ValueAxis;
import org.jfree.chart.entity.EntityCollection;
import org.jfree.chart.entity.XYItemEntity;
import org.jfree.chart.event.AnnotationChangeListener;
import org.jfree.chart.labels.XYItemLabelGenerator;
import org.jfree.chart.labels.XYToolTipGenerator;
import org.jfree.chart.plot.CrosshairState;
import org.jfree.chart.plot.PlotOrientation;
import org.jfree.chart.plot.PlotRenderingInfo;
import org.jfree.chart.plot.PlotState;
import org.jfree.chart.plot.XYPlot;
import org.jfree.chart.renderer.xy.AbstractXYItemRenderer;
import org.jfree.chart.renderer.xy.XYAreaRenderer;
import org.jfree.chart.renderer.xy.XYBarRenderer;
import org.jfree.chart.renderer.xy.XYItemRenderer;
import org.jfree.chart.renderer.xy.XYItemRendererState;
import org.jfree.data.general.Series;
import org.jfree.data.time.TimeSeries;
import org.jfree.data.xy.IntervalXYDataset;
import org.jfree.data.xy.XYDataset;
import org.jfree.data.xy.XYSeries;
import org.jfree.ui.RectangleEdge;

import ucar.unidata.idv.control.DisplayControlImpl;
import ucar.unidata.ui.symbol.WindBarbSymbol;
import ucar.unidata.util.Misc;

import ucar.visad.quantities.CommonUnits;

import visad.Unit;
import visad.VisADException;

import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.FontMetrics;
import java.awt.GradientPaint;
import java.awt.Graphics2D;
import java.awt.Paint;
import java.awt.Rectangle;
import java.awt.Shape;
import java.awt.Stroke;
import java.awt.geom.Ellipse2D;
import java.awt.geom.GeneralPath;
import java.awt.geom.Point2D;
import java.awt.geom.Rectangle2D;

import java.util.List;

/**
 * A time series chart
 *
 * @author MetApps Development Team
 * @version $Revision: 1.4 $
 */

public abstract class XYChartManager extends ChartManager {

    /** For showing when chart is empty */
    private XYAnnotation emptyChartAnnotation;

    /** Label to show when chart is empty */
    private String emptyChartLabel = "";

    /**
     * ctor
     */
    public XYChartManager() {
    }

    /**
     * Default constructor.
     *
     * @param control my control
     */
    public XYChartManager(DisplayControlImpl control) {
        super(control);
    }

    /**
     * Default constructor.
     *
     * @param control my control
     * @param chartName my name
     */
    public XYChartManager(DisplayControlImpl control, String chartName) {
        super(control, chartName);
    }

    /**
     * Get the renderer for the given line
     *
     * @param lineState The line
     *
     * @return renderer
     */
    protected XYItemRenderer getRenderer(LineState lineState) {
        return getRenderer(lineState, true);
    }

    /**
     * Get the renderer for the given line
     *
     * @param lineState The line
     * @param showLegend And show the legend
     *
     * @return renderer
     */
    protected XYItemRenderer getRenderer(LineState lineState, boolean showLegend) {
        int lineType = lineState.getLineType();
        XYItemRenderer renderer = null;
        if (lineType == LineState.LINETYPE_BAR) {
            return new MyXYBarRenderer();
        } else if (lineType == LineState.LINETYPE_SHAPES) {
            renderer = new MyXYAreaRenderer(lineState, XYAreaRenderer.SHAPES, showLegend);
        } else if (lineType == LineState.LINETYPE_LINES) {
            return new MyXYAreaRenderer(lineState, XYAreaRenderer.LINES, showLegend);
        } else if (lineType == LineState.LINETYPE_AREA) {
            return new MyXYAreaRenderer(lineState, XYAreaRenderer.AREA, showLegend);
        } else if (lineType == LineState.LINETYPE_AREA_AND_SHAPES) {
            renderer = new MyXYAreaRenderer(lineState, XYAreaRenderer.AREA_AND_SHAPES, showLegend);
        } else {
            renderer = new MyXYAreaRenderer(lineState, XYAreaRenderer.SHAPES_AND_LINES);
        }

        Shape shape = lineState.getPaintShape();
        if (shape != null) {
            renderer.setShape(shape);
            renderer.setBaseShape(shape);
            if (renderer instanceof XYAreaRenderer) {
                ((XYAreaRenderer) renderer).setLegendArea(shape);
            }
        }
        return renderer;

    }

    /**
     * Class MyXYAreaRenderer for rendering areas
     *
     *
     * @author IDV Development Team
     * @version $Revision: 1.4 $
     */
    protected class MyXYAreaRenderer extends XYAreaRenderer {

        /** the line */
        LineState lineState;

        /** shape to draw */
        Shape shape;

        /** Do we have a shape to draw */
        boolean hasShape;

        /** _more_ */
        boolean showLegend = true;

        /**
         * ctor
         *
         * @param lineState line
         * @param type type
         */
        public MyXYAreaRenderer(LineState lineState, int type) {
            this(lineState, type, true);
        }

        /**
         * _more_
         *
         * @param lineState _more_
         * @param type _more_
         * @param showLegend _more_
         */
        public MyXYAreaRenderer(LineState lineState, int type, boolean showLegend) {
            super(type);
            this.showLegend = showLegend;
            this.lineState = lineState;
            shape = lineState.getPaintShape();
            int lineType = lineState.getLineType();
            hasShape = (lineType == LineState.LINETYPE_SHAPES) || (lineType == LineState.LINETYPE_AREA_AND_SHAPES)
                    || (lineType == LineState.LINETYPE_SHAPES_AND_LINES);
        }

        /**
         * Get item for legend
         *
         * @param datasetIndex Which data set
         * @param series Which time series
         *
         * @return legend item
         */
        public LegendItem getLegendItem(int datasetIndex, int series) {
            if (!showLegend) {
                return null;
            }
            LegendItem l = super.getLegendItem(datasetIndex, series);
            if (!hasShape) {
                return l;
            }
            Paint p = l.getFillPaint();
            l = new LegendItem(l.getLabel(), l.getDescription(), l.getToolTipText(), l.getURLText(), true, shape,
                    false, p, true, p, l.getOutlineStroke(), true, shape, l.getLineStroke(), p);
            return l;
        }
    }

    ;

    /**
     *   a cut and paste so we can draw bars with a fixed width
     */
    protected static class MyXYBarRenderer extends XYBarRenderer {

        /**
         * draw
         *
         * @param g2 param
         * @param state param
         * @param dataArea param
         * @param info param
         * @param plot param
         * @param domainAxis param
         * @param rangeAxis param
         * @param dataset param
         * @param series param
         * @param item param
         * @param crosshairState param
         * @param pass param
         */
        public void drawItem(Graphics2D g2, XYItemRendererState state, Rectangle2D dataArea, PlotRenderingInfo info,
                XYPlot plot, ValueAxis domainAxis, ValueAxis rangeAxis, XYDataset dataset, int series, int item,
                CrosshairState crosshairState, int pass) {

            if (!getItemVisible(series, item)) {
                return;
            }
            IntervalXYDataset intervalDataset = (IntervalXYDataset) dataset;

            double value0;
            double value1;
            if (this.getUseYInterval()) {
                value0 = intervalDataset.getStartYValue(series, item);
                value1 = intervalDataset.getEndYValue(series, item);
            } else {
                value0 = this.getBase();
                value1 = intervalDataset.getYValue(series, item);
            }
            if (Double.isNaN(value0) || Double.isNaN(value1)) {
                return;
            }

            double translatedValue0 = rangeAxis.valueToJava2D(value0, dataArea, plot.getRangeAxisEdge());
            double translatedValue1 = rangeAxis.valueToJava2D(value1, dataArea, plot.getRangeAxisEdge());

            RectangleEdge location = plot.getDomainAxisEdge();
            double startX = intervalDataset.getStartXValue(series, item);
            if (Double.isNaN(startX)) {
                return;
            }
            double translatedStartX = domainAxis.valueToJava2D(startX, dataArea, location);

            double endX = intervalDataset.getEndXValue(series, item);
            if (Double.isNaN(endX)) {
                return;
            }
            double translatedEndX = domainAxis.valueToJava2D(endX, dataArea, location);

            double translatedWidth = Math.max(1, Math.abs(translatedEndX - translatedStartX));
            double translatedHeight = Math.abs(translatedValue1 - translatedValue0);

            if (getMargin() > 0.0) {
                double cut = translatedWidth * getMargin();
                translatedWidth = translatedWidth - cut;
                translatedStartX = translatedStartX + cut / 2;
            }

            translatedStartX -= 4;
            translatedWidth += 8;
            Rectangle2D bar = null;
            PlotOrientation orientation = plot.getOrientation();
            if (orientation == PlotOrientation.HORIZONTAL) {
                bar = new Rectangle2D.Double(Math.min(translatedValue0, translatedValue1),
                        Math.min(translatedStartX, translatedEndX), translatedHeight, translatedWidth);
            } else if (orientation == PlotOrientation.VERTICAL) {
                bar = new Rectangle2D.Double(Math.min(translatedStartX, translatedEndX),
                        Math.min(translatedValue0, translatedValue1), translatedWidth, translatedHeight);
            }

            Paint itemPaint = getItemPaint(series, item);
            if ((getGradientPaintTransformer() != null) && (itemPaint instanceof GradientPaint)) {
                GradientPaint gp = (GradientPaint) itemPaint;
                itemPaint = getGradientPaintTransformer().transform(gp, bar);
            }
            g2.setPaint(itemPaint);
            g2.fill(bar);
            if (isDrawBarOutline() && (Math.abs(translatedEndX - translatedStartX) > 3)) {
                Stroke stroke = getItemOutlineStroke(series, item);
                Paint paint = getItemOutlinePaint(series, item);
                if ((stroke != null) && (paint != null)) {
                    g2.setStroke(stroke);
                    g2.setPaint(paint);
                    g2.draw(bar);
                }
            }

            if (isItemLabelVisible(series, item)) {
                XYItemLabelGenerator generator = getItemLabelGenerator(series, item);
                drawItemLabel(g2, dataset, series, item, plot, generator, bar, value1 < 0.0);
            }

            // update the crosshair point
            double x1 = (startX + endX) / 2.0;
            double y1 = dataset.getYValue(series, item);
            double transX1 = domainAxis.valueToJava2D(x1, dataArea, location);
            double transY1 = rangeAxis.valueToJava2D(y1, dataArea, plot.getRangeAxisEdge());
            updateCrosshairValues(crosshairState, x1, y1, transX1, transY1, plot.getOrientation());

            // add an entity for the item...
            if (info != null) {
                EntityCollection entities = info.getOwner().getEntityCollection();
                if (entities != null) {
                    String tip = null;
                    XYToolTipGenerator generator = getToolTipGenerator(series, item);
                    if (generator != null) {
                        tip = generator.generateToolTip(dataset, series, item);
                    }
                    String url = null;
                    if (getURLGenerator() != null) {
                        url = getURLGenerator().generateURL(dataset, series, item);
                    }
                    XYItemEntity entity = new XYItemEntity(bar, dataset, series, item, tip, url);
                    entities.add(entity);
                }
            }

        }

    }

    ;

    /** a cut and paste so we can draw bars with a fixed width */
    protected static class CloudCoverageRenderer extends AbstractXYItemRenderer {

        /** scale */
        private double scale = 0;

        /** line info */
        LineState lineState;

        /**
         * ctor
         *
         * @param lineState line
         * @param scale scale
         */
        public CloudCoverageRenderer(LineState lineState, double scale) {
            this.lineState = lineState;
            this.scale = scale;
        }

        /**
         * Get item for legend
         *
         * @param datasetIndex Which data set
         * @param series Which time series
         *
         * @return legend item
         */
        public LegendItem getLegendItem(int datasetIndex, int series) {
            GeneralPath path = new GeneralPath();
            path.append(new Ellipse2D.Double(0, 0, 10, 10), false);
            path.moveTo(5.0f, 0.0f);
            path.lineTo(5.0f, 10.0f);
            path.closePath();
            LegendItem l = super.getLegendItem(datasetIndex, series);
            l = new LegendItem(l.getLabel(), l.getDescription(), l.getToolTipText(), l.getURLText(), true, path,
                    false, Color.black, true, Color.black, l.getOutlineStroke(), false, l.getLine(),
                    l.getLineStroke(), Color.black);
            return l;
        }

        /**
         * draw
         *
         * @param g2 param
         * @param state param
         * @param dataArea param
         * @param info param
         * @param plot param
         * @param domainAxis param
         * @param rangeAxis param
         * @param dataset param
         * @param series param
         * @param item param
         * @param crosshairState param
         * @param pass param
         */
        public void drawItem(Graphics2D g2, XYItemRendererState state, Rectangle2D dataArea, PlotRenderingInfo info,
                XYPlot plot, ValueAxis domainAxis, ValueAxis rangeAxis, XYDataset dataset, int series, int item,
                CrosshairState crosshairState, int pass) {

            if (!getItemVisible(series, item)) {
                return;
            }
            double x = dataset.getXValue(series, item);
            double value = dataset.getYValue(series, item);
            if (scale != 0) {
                value = value * scale;
            }

            if (Double.isNaN(x) || Double.isNaN(value)) {
                return;
            }
            int sX = (int) domainAxis.valueToJava2D(x, dataArea, plot.getDomainAxisEdge());
            try {
                int top = (int) (dataArea.getY());
                int bottom = (int) (top + dataArea.getHeight());
                int mid = top + (bottom - top) / 2;
                int vAnchor;
                int w = 16;
                int w2 = w / 2;

                int verticalPosition = lineState.getVerticalPosition();
                if (verticalPosition == LineState.VPOS_TOP) {
                    vAnchor = top;
                } else if (verticalPosition == LineState.VPOS_MIDDLE) {
                    vAnchor = mid - w2;
                } else {
                    vAnchor = bottom - w;
                }

                int angle = 0;
                if (value == 0) {
                } else if (value < 2) {
                    angle = 90;
                } else if (value < 4) {
                    angle = 180;
                } else if (value < 6) {
                    angle = 270;
                } else {
                    angle = 360;
                }
                g2.setColor(Color.black);
                g2.setStroke(new BasicStroke());
                g2.fillArc(sX - w2, vAnchor, w, w, 90, -angle);
                g2.drawArc(sX - w2, vAnchor, w, w, 0, 360);
            } catch (Exception exc) {
                System.err.println("oops:" + exc);
            }

        }
    }

    ;

    /** a cut and paste so we can draw bars with a fixed width */
    protected static class TextRenderer extends AbstractXYItemRenderer {

        /** List of strings to draw */
        List textList;

        /** line state */
        LineState lineState;

        /**
         * ctor
         *
         * @param textList List of strings to draw
         * @param lineState line state
         */
        public TextRenderer(List textList, LineState lineState) {
            this.textList = textList;
            this.lineState = lineState;
        }

        /**
         * Get item for legend
         *
         * @param datasetIndex Which data set
         * @param series Which time series
         *
         * @return legend item
         */
        public LegendItem getLegendItem(int datasetIndex, int series) {
            LegendItem l = super.getLegendItem(datasetIndex, series);
            l = new LegendItem(l.getLabel(), l.getDescription(), l.getToolTipText(), l.getURLText(), true,
                    new Rectangle(0, 0, 8, 8), true, getSeriesPaint(series), false, Color.black,
                    l.getOutlineStroke(), false, l.getLine(), l.getLineStroke(), Color.black);
            return l;
        }

        /**
         * draw
         *
         * @param g2 param
         * @param state param
         * @param dataArea param
         * @param info param
         * @param plot param
         * @param domainAxis param
         * @param rangeAxis param
         * @param dataset param
         * @param series param
         * @param item param
         * @param crosshairState param
         * @param pass param
         */
        public void drawItem(Graphics2D g2, XYItemRendererState state, Rectangle2D dataArea, PlotRenderingInfo info,
                XYPlot plot, ValueAxis domainAxis, ValueAxis rangeAxis, XYDataset dataset, int series, int item,
                CrosshairState crosshairState, int pass) {

            if (!getItemVisible(series, item)) {
                return;
            }
            String text = (String) textList.get(item);
            double x = dataset.getXValue(series, item);
            if (Double.isNaN(x)) {
                return;
            }
            int sX = (int) domainAxis.valueToJava2D(x, dataArea, plot.getDomainAxisEdge());
            try {
                int top = (int) (dataArea.getY());
                int bottom = (int) (top + dataArea.getHeight());
                int mid = top + (bottom - top) / 2;
                int vAnchor;
                FontMetrics fm = g2.getFontMetrics();

                int width = fm.stringWidth(text);
                int height = (fm.getMaxDescent() + fm.getMaxAscent());

                int verticalPosition = lineState.getVerticalPosition();
                if (verticalPosition == LineState.VPOS_TOP) {
                    vAnchor = top + height;
                } else if (verticalPosition == LineState.VPOS_MIDDLE) {
                    vAnchor = mid - height / 2;
                } else {
                    vAnchor = bottom;
                }

                g2.setPaint(getSeriesPaint(series));
                g2.drawString(text, sX - width / 2, vAnchor);
            } catch (Exception exc) {
                System.err.println("oops:" + exc);
            }

        }
    }

    ;

    /** displays windw barbs */
    protected static class WindbarbRenderer extends AbstractXYItemRenderer {

        /** drawer */
        WindBarbSymbol.WindDrawer drawer;

        /** speed data */
        Series speedSeries;

        /** direction data */
        Series dirSeries;

        /** unit */
        Unit speedUnit;

        /** line state */
        LineState lineState;

        /** Speed,Dir or U,V */
        boolean polarWind = true;

        /** Is in Southern hemisphere? */
        boolean isSouth = false;

        /**
         * ctor
         *
         * @param lineState line state
         * @param speedSeries speed data
         * @param dirSeries dir data
         * @param unit speed unit
         */
        public WindbarbRenderer(LineState lineState, Series speedSeries, Series dirSeries, Unit unit) {
            this(lineState, speedSeries, dirSeries, unit, true);
        }

        /**
         * ctor
         *
         * @param lineState line state
         * @param speedSeries speed data
         * @param dirSeries dir data
         * @param unit speed unit
         * @param polarWind true if polar coords
         */
        public WindbarbRenderer(LineState lineState, Series speedSeries, Series dirSeries, Unit unit,
                boolean polarWind) {
            this.lineState = lineState;
            this.speedUnit = unit;
            this.speedSeries = speedSeries;
            this.dirSeries = dirSeries;
            this.polarWind = polarWind;
        }

        /**
         * Get item for legend
         *
         * @param datasetIndex Which data set
         * @param series Which time series
         *
         * @return legend item
         */
        public LegendItem getLegendItem(int datasetIndex, int series) {
            GeneralPath path = new GeneralPath();
            path.moveTo(0.0f, 0.0f);
            path.lineTo(10.f, 10.f);

            path.moveTo(0.f, 0.f);
            path.lineTo(3.0f, -3.0f);

            path.moveTo(3.f, 3.f);
            path.lineTo(3.f + 2.0f, 3.f - 2.0f);
            path.closePath();
            LegendItem l = super.getLegendItem(datasetIndex, series);
            l = new LegendItem(l.getLabel(), l.getDescription(), l.getToolTipText(), l.getURLText(), true, path,
                    false, Color.black, true, Color.black, l.getOutlineStroke(), false, l.getLine(),
                    l.getLineStroke(), Color.black);
            return l;
        }

        /**
         * draw
         *
         * @param g2 param
         * @param state param
         * @param dataArea param
         * @param info param
         * @param plot param
         * @param domainAxis param
         * @param rangeAxis param
         * @param dataset param
         * @param series param
         * @param item param
         * @param crosshairState param
         * @param pass param
         */
        public void drawItem(Graphics2D g2, XYItemRendererState state, Rectangle2D dataArea, PlotRenderingInfo info,
                XYPlot plot, ValueAxis domainAxis, ValueAxis rangeAxis, XYDataset dataset, int series, int item,
                CrosshairState crosshairState, int pass) {

            if (!getItemVisible(series, item)) {
                return;
            }
            if (item >= speedSeries.getItemCount()) {
                return;
            }
            if (item >= dirSeries.getItemCount()) {
                return;
            }
            double speed = Double.NaN;
            double dir = Double.NaN;
            boolean isXY = speedSeries instanceof XYSeries;

            if (isXY) {
                // axes are switched in VertProf - X is alt, Y is value
                speed = ((XYSeries) speedSeries).getY(item).doubleValue();
                dir = ((XYSeries) dirSeries).getY(item).doubleValue();
            } else {
                speed = ((TimeSeries) speedSeries).getValue(item).doubleValue();
                dir = ((TimeSeries) dirSeries).getValue(item).doubleValue();
            }
            if (!polarWind) {
                double u = speed;
                double v = dir;
                speed = Math.sqrt(u * u + v * v);
                dir = Math.toDegrees(Math.atan2(-u, -v));
                if (dir < 0) {
                    dir += 360;
                }
            }
            //System.err.println("spd/dir = " + speed+ "/" + dir);

            int xPos, yPos;
            double x = (isXY) ? dataset.getYValue(series, item) : dataset.getXValue(series, item);
            //System.out.println("x = " + x);

            if (Double.isNaN(x) || Double.isNaN(speed) || Double.isNaN(dir)) {
                return;
            }
            int top = (int) (dataArea.getY());
            int bottom = (int) (top + dataArea.getHeight());
            int left = (int) (dataArea.getX());
            int right = (int) (left + dataArea.getWidth());
            int midW = left + (right - left) / 2;
            int mid = top + (bottom - top) / 2;
            int vAnchor;
            int hAnchor;
            int w = 20;
            int w2 = w / 2;
            int wiggle = 15;

            if (isXY) {

                double y = dataset.getXValue(series, item);
                //xPos = (int) rangeAxis.valueToJava2D(x, dataArea,
                //             plot.getRangeAxisEdge());
                int horizontalPosition = lineState.getHorizontalPosition();
                if (horizontalPosition == LineState.HPOS_LEFT) {
                    hAnchor = left + wiggle;
                } else if (horizontalPosition == LineState.HPOS_MIDDLE) {
                    hAnchor = midW;
                } else if (horizontalPosition == LineState.HPOS_RIGHT) {
                    hAnchor = right - wiggle;
                } else {
                    hAnchor = midW;
                }
                xPos = hAnchor;
                yPos = (int) domainAxis.valueToJava2D(y, dataArea, plot.getDomainAxisEdge());

            } else {

                int sX = (int) domainAxis.valueToJava2D(x, dataArea, plot.getDomainAxisEdge());
                int sY = (int) rangeAxis.valueToJava2D(speed, dataArea, plot.getRangeAxisEdge());
                //System.out.println("sX/sY = " + sX + "/" + sY);

                int verticalPosition = lineState.getVerticalPosition();
                if (verticalPosition == LineState.VPOS_TOP) {
                    vAnchor = top + wiggle;
                } else if (verticalPosition == LineState.VPOS_MIDDLE) {
                    vAnchor = mid - w2;
                } else if (verticalPosition == LineState.VPOS_BOTTOM) {
                    vAnchor = bottom - w - wiggle;
                } else {
                    vAnchor = sY;
                }
                xPos = sX - w2;
                yPos = vAnchor;
            }

            try {
                speed = CommonUnits.KNOT.toThis(speed, speedUnit);
            } catch (VisADException vex) {
                System.err.println("error:" + vex);
            }

            if (drawer == null) {
                drawer = new WindBarbSymbol.WindDrawer(isSouth);
            }
            // System.err.println ("speed: " + speed +", dir: " + dir + ", X: " + xPos + ", Y: " + yPos);
            try {
                g2.setColor(Color.black);
                g2.setStroke(new BasicStroke());
                //drawer.draw(g2, sX - w2, vAnchor, w, w, speed, dir);
                drawer.draw(g2, xPos, yPos, w, w, speed, dir);
            } catch (Exception exc) {
                System.err.println("oops:" + exc);
            }
        }
    }

    /**
     * Set the label to use when we have an empty chart
     *
     * @param label empty chart label
     */
    public void setEmptyChartLabel(String label) {
        boolean signalChange = !Misc.equals(emptyChartLabel, label);
        if (emptyChartAnnotation == null) {
            signalChange = true;
            emptyChartAnnotation = new XYAnnotation() {
                public void draw(Graphics2D g2, XYPlot plot, Rectangle2D dataArea, ValueAxis domainAxis,
                        ValueAxis rangeAxis, int rendererIndex, PlotRenderingInfo info) {
                    if (!hasStuff()) {
                        g2.setColor(Color.black);
                        g2.drawString(emptyChartLabel, 100, 50);
                    }
                }

                @Override
                public void addChangeListener(AnnotationChangeListener arg0) {
                }

                @Override
                public void removeChangeListener(AnnotationChangeListener arg0) {
                }
            };
            for (int plotIdx = 0; plotIdx < chartHolders.size(); plotIdx++) {
                ChartHolder chartHolder = (ChartHolder) chartHolders.get(plotIdx);
                ((XYPlot) chartHolder.getPlot()).addAnnotation(emptyChartAnnotation);
            }
        }
        emptyChartLabel = label;
        if (signalChange) {
            signalChartChanged();
        }
    }

    /**
     * Add chart
     *
     * @param chartHolder new chart
     */
    protected void addChart(ChartHolder chartHolder) {
        super.addChart(chartHolder);
        if (emptyChartAnnotation != null) {
            ((XYPlot) chartHolder.getPlot()).addAnnotation(emptyChartAnnotation);
        }

    }

    /**
     * Class MyXYPlot is an xyplot with some special sauce to synchronize drawing
     *
     *
     * @author IDV Development Team
     * @version $Revision: 1.4 $
     */
    public class MyXYPlot extends XYPlot {

        /**
         * ctor
         *
         * @param dataset  dataset
         * @param domainAxis axis
         * @param rangeAxis axis
         * @param renderer renderer
         */
        public MyXYPlot(XYDataset dataset, ValueAxis domainAxis, ValueAxis rangeAxis, XYItemRenderer renderer) {
            super(dataset, domainAxis, rangeAxis, renderer);
        }

        /**
         * draw synchronized
         *
         * @param g2 param
         * @param area param
         * @param anchor param
         * @param parentState param
         * @param info param
         */
        public void draw(Graphics2D g2, Rectangle2D area, Point2D anchor, PlotState parentState,
                PlotRenderingInfo info) {
            try {
                if (!getSettingData()) {
                    synchronized (getMutex()) {
                        super.draw(g2, area, anchor, parentState, info);
                    }
                }
            } catch (Exception exc) {
                exc.printStackTrace();
            }
        }

    }

    ;

}