com.appnativa.rare.ui.chart.jfreechart.ChartHandler.java Source code

Java tutorial

Introduction

Here is the source code for com.appnativa.rare.ui.chart.jfreechart.ChartHandler.java

Source

/*
 * Copyright appNativa Inc. All Rights Reserved.
 *
 * This file is part of the Real-time Application Rendering Engine (RARE).
 *
 * RARE is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program 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 General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 *
 */

package com.appnativa.rare.ui.chart.jfreechart;

import com.appnativa.rare.Platform;
import com.appnativa.rare.platform.EventHelper;
import com.appnativa.rare.platform.swing.ui.util.SwingGraphics;
import com.appnativa.rare.platform.swing.ui.util.SwingHelper;
import com.appnativa.rare.platform.swing.ui.view.JToolTipEx;
import com.appnativa.rare.platform.swing.ui.view.iView;
import com.appnativa.rare.ui.Container;
import com.appnativa.rare.ui.RenderableDataItem;
import com.appnativa.rare.ui.UIColor;
import com.appnativa.rare.ui.UIColorShade;
import com.appnativa.rare.ui.UIDimension;
import com.appnativa.rare.ui.UIFont;
import com.appnativa.rare.ui.UIFontMetrics;
import com.appnativa.rare.ui.UIImage;
import com.appnativa.rare.ui.UIInsets;
import com.appnativa.rare.ui.UIPopupMenu;
import com.appnativa.rare.ui.UIStroke;
import com.appnativa.rare.ui.chart.ChartAxis;
import com.appnativa.rare.ui.chart.ChartDataItem;
import com.appnativa.rare.ui.chart.ChartDefinition;
import com.appnativa.rare.ui.chart.ChartDefinition.ChartType;
import com.appnativa.rare.ui.chart.PlotInformation;
import com.appnativa.rare.ui.chart.PlotInformation.ShapeStyle;
import com.appnativa.rare.ui.chart.aChartHandler;
import com.appnativa.rare.ui.chart.jfreechart.ChartHelper.DefaultPieDatasetEx;
import com.appnativa.rare.ui.chart.jfreechart.ChartHelper.PieCollection;
import com.appnativa.rare.ui.chart.jfreechart.ChartHelper.PieLabelGenerator;
import com.appnativa.rare.ui.chart.jfreechart.ChartHelper.PieToolTipLabelGenerator;
import com.appnativa.rare.ui.chart.jfreechart.ChartHelper.RareBarPainter;
import com.appnativa.rare.ui.chart.jfreechart.ChartHelper.XYAreaRendererEx;
import com.appnativa.rare.ui.chart.jfreechart.ChartHelper.XYAreaSplineRendererEx;
import com.appnativa.rare.ui.chart.jfreechart.ChartHelper.XYClusteredBarRendererEx;
import com.appnativa.rare.ui.chart.jfreechart.ChartHelper.XYLine3DRendererEx;
import com.appnativa.rare.ui.chart.jfreechart.ChartHelper.XYLineAndShapeRendererEx;
import com.appnativa.rare.ui.chart.jfreechart.ChartHelper.XYRangeAreaRendererEx;
import com.appnativa.rare.ui.chart.jfreechart.ChartHelper.XYRangeBarRendererEx;
import com.appnativa.rare.ui.chart.jfreechart.ChartHelper.XYSeriesCollectionEx;
import com.appnativa.rare.ui.chart.jfreechart.ChartHelper.XYSplineRendererEx;
import com.appnativa.rare.ui.chart.jfreechart.ChartHelper.XYStackedAreaRendererEx;
import com.appnativa.rare.ui.chart.jfreechart.ChartHelper.XYStackedBarRendererEx;
import com.appnativa.rare.ui.chart.jfreechart.ChartHelper.XYToolTipLabelGenerator;
import com.appnativa.rare.ui.iImageObserver;
import com.appnativa.rare.ui.iPlatformBorder;
import com.appnativa.rare.ui.iPlatformComponent;
import com.appnativa.rare.ui.iPlatformPaint;
import com.appnativa.rare.ui.painter.UISimpleBackgroundPainter;
import com.appnativa.rare.ui.painter.iComponentPainter;
import com.appnativa.rare.ui.painter.iPainter;
import com.appnativa.rare.ui.painter.iPainterSupport;
import com.appnativa.rare.ui.painter.iPlatformComponentPainter;
import com.appnativa.rare.viewer.aChartViewer;
import com.appnativa.util.SNumber;

import org.jfree.chart.ChartFactory;
import org.jfree.chart.ChartMouseEvent;
import org.jfree.chart.ChartMouseListener;
import org.jfree.chart.ChartPanel;
import org.jfree.chart.JFreeChart;
import org.jfree.chart.StandardChartTheme;
import org.jfree.chart.axis.Axis;
import org.jfree.chart.axis.AxisState;
import org.jfree.chart.axis.NumberAxis;
import org.jfree.chart.axis.NumberAxis3D;
import org.jfree.chart.axis.NumberTick;
import org.jfree.chart.axis.NumberTickUnit;
import org.jfree.chart.axis.Tick;
import org.jfree.chart.axis.TickType;
import org.jfree.chart.axis.ValueAxis;
import org.jfree.chart.entity.ChartEntity;
import org.jfree.chart.entity.LegendItemEntity;
import org.jfree.chart.entity.PieSectionEntity;
import org.jfree.chart.entity.XYAnnotationEntity;
import org.jfree.chart.entity.XYItemEntity;
import org.jfree.chart.labels.PieSectionLabelGenerator;
import org.jfree.chart.labels.StandardXYItemLabelGenerator;
import org.jfree.chart.plot.DatasetRenderingOrder;
import org.jfree.chart.plot.IntervalMarker;
import org.jfree.chart.plot.PiePlot;
import org.jfree.chart.plot.PiePlot3D;
import org.jfree.chart.plot.Plot;
import org.jfree.chart.plot.PlotOrientation;
import org.jfree.chart.plot.XYPlot;
import org.jfree.chart.renderer.AbstractRenderer;
import org.jfree.chart.renderer.category.BarRenderer;
import org.jfree.chart.renderer.xy.AbstractXYItemRenderer;
import org.jfree.chart.renderer.xy.StackedXYBarRenderer;
import org.jfree.chart.renderer.xy.XYBarRenderer;
import org.jfree.chart.renderer.xy.XYItemRenderer;
import org.jfree.chart.renderer.xy.XYLineAndShapeRenderer;
import org.jfree.chart.title.LegendTitle;
import org.jfree.data.xy.XYDataset;
import org.jfree.data.xy.XYSeries;
import org.jfree.data.xy.XYSeriesCollection;
import org.jfree.ui.RectangleEdge;
import org.jfree.ui.RectangleInsets;
import org.jfree.ui.TextAnchor;
import org.jfree.util.ShapeUtilities;

import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Component;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Paint;
import java.awt.Rectangle;
import java.awt.Shape;
import java.awt.Stroke;
import java.awt.event.MouseWheelEvent;
import java.awt.event.MouseWheelListener;
import java.awt.geom.AffineTransform;
import java.awt.geom.Rectangle2D;
import java.awt.geom.RoundRectangle2D;

import java.security.InvalidParameterException;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

import javax.swing.JComponent;
import javax.swing.JPopupMenu;
import javax.swing.JToolTip;
import javax.swing.JViewport;
import javax.swing.Scrollable;
import javax.swing.border.Border;

/**
 * Chart Handler class for JFreeChart
 *
 * @author Don DeCoteau
 */
public class ChartHandler extends aChartHandler {
    private ArrayList<Shape> baseShapes;
    private int tickSize;

    /** Creates a new instance of JFreeChartHandler */
    public ChartHandler() {
        ChartFactory.setChartTheme(StandardChartTheme.createLegacyTheme());
    }

    @Override
    public iPlatformComponent createChart(iPlatformComponent chartComponent, final ChartDefinition cd) {
        ChartInfo ci = (ChartInfo) cd.getChartHandlerInfo();

        if (ci != null) {
            ci.dispose();
        }

        ci = new ChartInfo();
        cd.setChartHandlerInfo(ci);

        ChartPanelEx chartPanel = (ChartPanelEx) ((chartComponent == null) ? null : chartComponent.getView());

        if (chartPanel == null) {
            chartPanel = createChartPanel(null, cd);
            chartComponent = new Container(chartPanel);

            if (chartForeground != null) {
                chartComponent.setForeground(chartForeground);
            }

            if (chartFont != null) {
                chartComponent.setFont(chartFont);
            }

            if (chartBackground != null) {
                chartComponent.setBackground(chartBackground);
            }
        }

        ci.popularSeriesDataAndCaluclateRanges(this, cd);

        JFreeChart chart = createCharts(chartPanel, cd);

        chartPanel.setChart(chart);
        chart.setAntiAlias(true);
        chart.setBackgroundPaint(null);
        chart.setBorderVisible(false);

        if (!cd.isAllowZooming()) {
            chartPanel.setRangeZoomable(false);
            chartPanel.setDomainZoomable(false);
        }

        ci.chart = chart;
        ChartHelper.setChartTitle(chart, cd.getTitle());

        if (cd.isShowLegends()) {
            LegendTitle l = new LegendTitle(chart.getPlot());

            l.setItemPaint(cd.getTextColor(legendLabelColor));
            l.setItemFont(cd.getTextFont(legendLabelFont));

            switch (cd.getLegendSide()) {
            case TOP:
                l.setPosition(RectangleEdge.TOP);

                break;

            case BOTTOM:
                l.setPosition(RectangleEdge.BOTTOM);

                break;

            case LEFT:
                l.setPosition(RectangleEdge.LEFT);

                break;

            default:
                l.setPosition(RectangleEdge.RIGHT);

                break;
            }

            chart.addSubtitle(l);
        }

        ChartFactory.getChartTheme().apply(chart);
        ((ChartInfo) cd.getChartHandlerInfo()).chartPanel = chartPanel;

        if ((cd.getSeriesCount() > 0) && (chartPanel.getHeight() > 0)) {
            chartPanel.updateTickmarks(chartPanel.getWidth(), chartPanel.getHeight());
        }

        return chartComponent;
    }

    public LabelData[] createNonCategoryLabels(ChartDefinition cd, double width, NumberAxis axis, boolean domain,
            double widthDivisor) {
        LabelData[] labels;
        double startValue = axis.getLowerBound();
        double endValue = axis.getUpperBound();
        double increment = axis.getTickUnit().getSize();

        if (domain) {
            labels = createLabelData(cd, domain, width, startValue, endValue + increment, increment);

            if (labels.length > 0) {
                labels[labels.length - 1].label = "";
            }
        } else {
            labels = createNumericLabelsData(cd, width, startValue, endValue, increment, domain, widthDivisor);
        }

        return labels;
    }

    @Override
    public iPlatformComponent dataChanged(iPlatformComponent chartComponent, ChartDefinition cd) {
        ChartInfo info = (ChartInfo) cd.getChartHandlerInfo();
        boolean show = true;

        if ((info.seriesData != null) && !info.seriesData.isEmpty()) {
            show = info.seriesData.get(0).showPointLabels;
        }

        iPlatformComponent c = createChart(chartComponent, cd);

        info = (ChartInfo) cd.getChartHandlerInfo();
        info.setShowPointLabels(show);

        return c;
    }

    @Override
    public UIImage getChartImage(iPlatformComponent chartPanel, ChartDefinition cd) {
        if (chartPanel == null) {
            return null;
        }

        ChartPanel cp = (ChartPanel) chartPanel.getView();

        return new UIImage(cp.getChart().createBufferedImage(cp.getWidth(), cp.getHeight()));
    }

    @Override
    public UIDimension getPlotAreaSize(iPlatformComponent chartPanel, ChartDefinition cd) {
        if (chartPanel == null) {
            return new UIDimension();
        }

        ChartPanelEx cp = (ChartPanelEx) chartPanel.getView();

        return cp.getPlotAreaSize();
    }

    @Override
    public void itemChanged(iPlatformComponent chartComponent, ChartDefinition cd, ChartDataItem item) {
        ChartInfo info = (ChartInfo) cd.getChartHandlerInfo();

        switch (item.getItemType()) {
        case SERIES:
            dataChanged(chartComponent, cd);

            return;

        case DOMAIN_MARKER:
            if ((info.chart != null) && (info.chart.getPlot() instanceof XYPlot)) {
                updateMarkers(cd, (XYPlot) info.chart.getPlot(), false);
            }

            break;

        case RANGE_MARKER:
            if ((info.chart != null) && (info.chart.getPlot() instanceof XYPlot)) {
                updateMarkers(cd, (XYPlot) info.chart.getPlot(), true);
            }

            break;

        case POINT:
            break;

        default:
            break;
        }

        SeriesData data = info.updateSeries(cd, item);
        Plot plot = info.chart.getPlot();

        if ((data == null) || (plot instanceof PiePlot)) {
            createChart(chartComponent, cd);

            return;
        }

        int series = data.seriesIndex;

        try {
            if (plot instanceof XYPlot) {
                XYPlot xyplot = (XYPlot) plot;
                int sn = series;
                XYDataset set;

                if (xyplot.getDatasetCount() == 1) {
                    set = xyplot.getDataset(0);
                } else {
                    set = xyplot.getDataset(series);
                    sn = 0;
                }

                if (set.getSeriesCount() > sn) {
                    if (set instanceof XYSeriesCollection) {
                        XYSeries s = ((XYSeriesCollection) set).getSeries(sn);

                        if (s != null) {
                            s.clear();
                            ChartHelper.populateXYSeries(s, data);
                        }
                    }
                }
            }
        } catch (Exception e) {
            Platform.ignoreException("can't update chart series", e);
            createChart(chartComponent, cd);
        }
    }

    @Override
    public ChartDataItem itemFromLocation(ChartDefinition cd, int x, int y) {
        ChartDataItem item = null;

        if (cd != null) {
            ChartInfo info = (ChartInfo) cd.getChartHandlerInfo();

            if (info.chartPanel != null) {
                item = itemFromEntity(cd, info.chartPanel.getEntityForPoint(x, y), false);
            }
        }

        return item;
    }

    @Override
    public void setDomainLabel(iPlatformComponent chartPanel, ChartDefinition cd) {
        Axis a = getAxis(cd, false);

        if (a != null) {
            a.setLabel(cd.getDomainAxis().getLabel());
        }
    }

    @Override
    public void setDomainLabelsAngle(iPlatformComponent chartPanel, ChartDefinition cd) {
        setLabelAngel(chartPanel, cd, false);
    }

    @Override
    public void setRangeLabel(iPlatformComponent chartPanel, ChartDefinition cd) {
        Axis a = getAxis(cd, true);

        if (a != null) {
            a.setLabel(cd.getRangeAxis().getLabel());
        }
    }

    @Override
    public void setShowDomainLabels(iPlatformComponent chartPanel, ChartDefinition cd) {
        Axis a = getAxis(cd, false);

        if (a != null) {
            a.setTickLabelsVisible(cd.getDomainAxis().isLabelsVisible());
        }
    }

    @Override
    public void setShowPlotValues(iPlatformComponent chartPanel, ChartDefinition cd) {
        boolean show = cd.isShowPlotLabels();
        ChartInfo info = (ChartInfo) cd.getChartHandlerInfo();

        info.setShowPointLabels(show);

        Plot plot = info.chart.getPlot();

        if (plot instanceof XYPlot) {
            ((XYPlot) plot).getRenderer().setBaseItemLabelsVisible(show, true);
        } else if (plot instanceof PiePlot) {
            PiePlot p = (PiePlot) plot;

            if (show) {
                p.setLabelGenerator((PieSectionLabelGenerator) info.labelGenerator);
            } else {
                info.labelGenerator = p.getLabelGenerator();
                p.setLabelGenerator(null);
            }
        }
    }

    @Override
    public void setShowRangeLabels(iPlatformComponent chartPanel, ChartDefinition cd) {
        Axis a = getAxis(cd, true);

        if (a != null) {
            a.setTickLabelsVisible(cd.getRangeAxis().isLabelsVisible());
        }
    }

    @Override
    public void unzoom(iPlatformComponent chartComponent, ChartDefinition cd) {
        JComponent chartPanel = chartComponent.getView();

        ((ChartPanel) chartPanel).restoreAutoBounds();
    }

    @Override
    public void updateRangeAxis(iPlatformComponent chartComponent, ChartDefinition cd) {
        Axis a = getAxis(cd, true);

        customizeAxisEx(cd, cd.getRangeAxis(), a);
    }

    @Override
    public void updatesCompleted(iPlatformComponent chartComponent, ChartDefinition cd) {
        createChart(chartComponent, cd);
    }

    @Override
    public void updatesPending(iPlatformComponent chartComponent, ChartDefinition cd) {
    }

    protected ChartPanelEx createChartPanel(JFreeChart chart, final ChartDefinition cd) {
        ChartPanelEx cp = new ChartPanelEx(chart, cd.isScrollWheelZoomingAllowed()) {
            @Override
            protected JPopupMenu createPopupMenu(boolean properties, boolean copy, boolean save, boolean print,
                    boolean zoom) {
                JPopupMenu menu;
                aChartViewer cv = cd.getChartViewer();

                menu = super.createPopupMenu(false, cd.getChartViewer().canCopy(), cv.canSave(), cv.canPrint(),
                        cd.isAllowZooming());

                UIPopupMenu sm = cv.getPopupMenu();

                if (sm != null) {
                    menu.addSeparator();
                    sm.copyTo(menu);
                }

                return menu;
            }
        };

        cp.maxZoom = cd.getMaxZoomFactor();

        if (cd.wantsMouseEvents()) {
            cp.addChartMouseListener(new ChartMouseHandler(cd));
        }

        return cp;
    }

    protected JFreeChart createCharts(ChartPanel chartPanel, ChartDefinition cd) {
        List<DataSetValue> datasets = createDataSets(cd);
        int len = datasets.size();
        UIFont f = getAxisLabelFont(cd.getRangeAxis());
        UIFontMetrics fm = UIFontMetrics.getMetrics(f);

        tickSize = (int) fm.getHeight();

        if (cd.getChartType() == ChartType.PIE) {
            PiePlot plot;
            PieCollection pie = (PieCollection) datasets.get(0).dataset;

            if (cd.isDraw3D()) {
                plot = new PiePlot3D(pie);
            } else {
                plot = new PiePlot(pie);
            }

            customizePiePlot(cd, plot, pie);

            return new JFreeChart(null, getChartFont(), plot, false);
        }

        ChartInfo ci = (ChartInfo) cd.getChartHandlerInfo();
        XYPlot xyplot = null;
        NumberAxis yAxis = cd.isDraw3D() ? new NumberAxis3DEx(cd, true, cd.getRangeLabel())
                : new NumberAxisEx(cd, false, cd.getRangeLabel());

        yAxis.setAutoRangeIncludesZero(false);
        yAxis.setAutoRange(false);

        NumberAxis xAxis;

        if (ci.categoryDomain) {
            LabelData[] labels = ci.createLabelData(cd, fm, true);

            xAxis = cd.isDraw3D() ? new NumberAxis3DEx(cd, true, cd.getDomainLabel())
                    : new NumberAxisEx(cd, labels, cd.getDomainLabel());
        } else {
            xAxis = cd.isDraw3D() ? new NumberAxis3DEx(cd, true, cd.getDomainLabel())
                    : new NumberAxisEx(cd, true, cd.getDomainLabel());
        }

        xAxis.setAutoRangeIncludesZero(false);
        xAxis.setAutoRange(false);
        xyplot = new XYPlotEx(null, xAxis, yAxis, null);

        for (int i = 0; i < len; i++) {
            DataSetValue dv = datasets.get(i);

            xyplot.setDataset(i, (XYDataset) dv.dataset);

            switch (dv.chartType) {
            case SPLINE:
                xyplot.setRenderer(i, new XYSplineRendererEx(ci.seriesData));

                break;

            case LINE:
                xyplot.setRenderer(i, cd.isDraw3D() ? new XYLine3DRendererEx(ci.seriesData)
                        : new XYLineAndShapeRendererEx(ci.seriesData));

                break;

            case BAR: {
                XYClusteredBarRendererEx renderer = new XYClusteredBarRendererEx(ci.seriesData);

                renderer.setShadowVisible(false);
                xyplot.setRenderer(i, renderer);
            }

                break;

            case STACKED_BAR: {
                StackedXYBarRenderer renderer = new XYStackedBarRendererEx(0.10, ci.seriesData);

                renderer.setDrawBarOutline(false);
                renderer.setShadowVisible(false);
                xyplot.setRenderer(i, renderer);
            }

                break;

            case RANGE_AREA:
                xyplot.setRenderer(i, new XYRangeAreaRendererEx(ci.seriesData));

                break;

            case RANGE_BAR:
                xyplot.setRenderer(i, new XYRangeBarRendererEx(ci.seriesData));

                break;

            case STACKED_AREA:
                XYStackedAreaRendererEx renderer = new XYStackedAreaRendererEx(ci.seriesData);

                renderer.setOutline(false);
                xyplot.setRenderer(i, renderer);

                break;

            case SPLINE_AREA:
            case AREA:
                XYItemRenderer r;

                if (dv.chartType == ChartType.SPLINE_AREA) {
                    r = new XYAreaSplineRendererEx(ci.seriesData);
                } else {
                    r = new XYAreaRendererEx(ci.seriesData);
                }

                xyplot.setRenderer(r);

                break;

            default:
                throw new InvalidParameterException("Unsupported chart type");
            }
        }

        customizeXYPlot(chartPanel, cd, xyplot);

        JFreeChart chart = new JFreeChart(null, getChartFont(), xyplot, false);

        return chart;
    }

    protected List<DataSetValue> createDataSets(ChartDefinition cd) {
        ChartInfo ci = (ChartInfo) cd.getChartHandlerInfo();
        List<SeriesData> seriesData = ci.seriesData;
        int len = (seriesData == null) ? 0 : seriesData.size();
        ChartType ct;

        if (len == 0) {
            ct = cd.getChartType();

            switch (ct) {
            case PIE:
                return Arrays.asList(new DataSetValue(ct, new DefaultPieDatasetEx()));

            case RANGE_BAR:
            case RANGE_AREA:
            case STACKED_BAR:
            case STACKED_AREA:
            case AREA:
            case BAR:
            case LINE:
            case SPLINE:
            case SPLINE_AREA: {
                return Arrays.asList(new DataSetValue(ct, new XYSeriesCollectionEx()));
            }

            default:
                throw new InvalidParameterException("Unsupported chart type:" + ct.toString());
            }
        }

        boolean allSameType = areAllTheSameType(cd);
        List<DataSetValue> list = new ArrayList<DataSetValue>(len);
        XYSeriesCollectionEx xysc = null;

        for (int i = 0; i < len; i++) {
            SeriesData data = seriesData.get(i);

            ct = data.chartType;

            switch (ct) {
            case PIE:
                list.add(new DataSetValue(ct, ChartHelper.createPieDataset(cd)));

                if (!allSameType) {
                    throw new UnsupportedOperationException("Can include a Pie Chart with other chart types");
                }

                break;

            case RANGE_BAR:
            case RANGE_AREA:
                XYSeriesCollectionEx rxysc = new XYSeriesCollectionEx(true);

                list.add(new DataSetValue(ct, rxysc));
                rxysc.addSeries(ChartHelper.populateXYSeries(null, data));

                break;

            case STACKED_BAR:
            case STACKED_AREA:
            case AREA:
            case BAR:
            case LINE:
            case SPLINE:
            case SPLINE_AREA: {
                if ((xysc == null) || !allSameType) {
                    xysc = new XYSeriesCollectionEx();
                    list.add(new DataSetValue(ct, xysc));
                }

                xysc.addSeries(ChartHelper.populateXYSeries(null, data));

                break;
            }

            default:
                throw new InvalidParameterException("Unsupported chart type:" + ct.toString());
            }
        }

        return list;
    }

    protected void customizeBasicPlot(final Plot plot, PlotInformation pi) {
        UIColor bg = null;

        if (pi != null) {
            bg = pi.getBackgroundColor();
            plot.setForegroundAlpha(pi.getForegroundAlpha());

            if (pi.getBackgroundImage() != null) {
                UIImage img = pi.getBackgroundImage();

                if (img.isLoaded()) {
                    plot.setBackgroundImage(img.getImage());
                } else {
                    iImageObserver io = new iImageObserver() {
                        @Override
                        public void imageLoaded(UIImage image) {
                            try {
                                plot.setBackgroundImage(image.getImage());
                            } catch (Exception ignore) {
                            }
                        }
                    };

                    if (img.isImageLoaded(io)) {
                        plot.setBackgroundImage(img.getImage());
                    }
                }
            }

            UIInsets in = pi.getMargin();

            if (in != null) {
                plot.setInsets(new RectangleInsets(in.top, in.left, in.bottom, in.right));
            }
        }

        if (bg == null) {
            bg = plotBackground;
        }

        if (bg != null) {
            plot.setBackgroundPaint(ChartHelper.getPaint(bg));
        }

        plot.setInsets(new RectangleInsets(12, 12, 12, 12));
    }

    protected void customizeSeriesAttributes(ChartPanel chartPanel, ChartDefinition cd, Plot plot,
            boolean multiset) {
        List<RenderableDataItem> rows = cd.getSeries();
        ChartInfo ci = (ChartInfo) cd.getChartHandlerInfo();
        List<SeriesData> seriesData = ci.seriesData;
        int len = (seriesData == null) ? 0 : seriesData.size();

        if (len == 0) {
            return;
        }

        ChartDataItem di;
        Paint p = null;
        iPainter painter;
        BasicStroke stroke = null;
        BasicStroke ostroke = null;
        RareBarPainter barPainter = null;
        AbstractRenderer r;
        Object o;
        UIColor c;
        PlotInformation pi = cd.getPlotInformation();

        if (pi != null) {
            float lt = pi.getLineThickness();
            float ot = pi.getOutlineThickness();

            if (lt > 1) {
                stroke = new BasicStroke(lt);
            }

            if (ot > 1) {
                ostroke = (ot == lt) ? stroke : new BasicStroke(ot);
            }
        }

        int n = 0;

        for (int i = 0; i < len; i++) {
            SeriesData data = seriesData.get(i);

            di = (ChartDataItem) rows.get(data.seriesIndex);

            if ((di == null) || di.isEmpty()) {
                continue;
            }

            p = null;
            o = ((XYPlot) plot).getRenderer(multiset ? i : 0);

            if (!(o instanceof AbstractRenderer)) {
                continue;
            }

            UIColor fc = data.fillColor;
            UIColor oc = data.outlineColor;

            r = (AbstractRenderer) o;
            painter = di.getComponentPainter();

            if (painter == null) {
                c = di.getBackground();

                if (c instanceof UIColorShade) {
                    painter = ((UIColorShade) c).getBackgroundPainter();

                    if (painter instanceof UISimpleBackgroundPainter) {
                        p = painter.getBackgroundColor();
                        painter = null;
                    }
                } else {
                    p = c;
                }
            }

            if ((painter == null) && (p == null)) {
                painter = cd.getRangeAxis().getComponentPainter();

                if (painter == null) {
                    c = cd.getRangeAxis().getBackground();

                    if (c instanceof UIColorShade) {
                        painter = ((UIColorShade) c).getBackgroundPainter();

                        if (painter instanceof UISimpleBackgroundPainter) {
                            p = painter.getBackgroundColor();
                            painter = null;
                        }
                    } else {
                        p = c;
                    }
                }
            }

            if (painter != null) {
                if (r instanceof XYBarRenderer) {
                    if (barPainter == null) {
                        barPainter = new RareBarPainter(chartPanel);
                    }

                    XYBarRenderer xr = (XYBarRenderer) r;

                    xr.setBarPainter(barPainter);
                    p = ChartHelper.getPaint(painter);
                } else if (r instanceof BarRenderer) {
                    if (barPainter == null) {
                        barPainter = new RareBarPainter(chartPanel);
                    }

                    BarRenderer xr = (BarRenderer) r;

                    xr.setItemMargin(0);
                    xr.setBarPainter(barPainter);
                    p = ChartHelper.getPaint(painter);
                } else {
                    iPlatformPaint pp = painter.getPaint(50, 50);

                    p = (pp == null) ? null : pp.getPaint();
                }
            }

            if (p == null) {
                p = di.getBackground();
            }

            if (p == null) {
                p = getDefaultColor(i);
            }

            n = multiset ? 0 : i;
            r.setSeriesPaint(n, p);

            if (r instanceof XYAreaSplineRendererEx) {
                r.setSeriesFillPaint(n, p);
            }

            float lt = getSeriesLineThickness(di, -1);

            if (lt != -1) {
                r.setSeriesStroke(n, new BasicStroke(lt));
            } else if (stroke != null) {
                r.setSeriesStroke(n, stroke);
            }

            lt = getSeriesOutlineLineThickness(di, -1);

            if (lt != -1) {
                r.setSeriesOutlineStroke(n, ostroke);
            } else if (ostroke != null) {
                r.setSeriesOutlineStroke(n, ostroke);
            }

            if (r instanceof XYLineAndShapeRenderer) {
                ShapeStyle ss = getSeriesShapeStyle(pi, di);
                XYLineAndShapeRenderer renderer = (XYLineAndShapeRenderer) r;

                switch (ss) {
                case FILLED:
                    renderer.setBaseShapesVisible(true);
                    renderer.setBaseShapesFilled(true);
                    renderer.setUseFillPaint(true);
                    renderer.setSeriesFillPaint(n, (fc == null) ? p : fc.getPaint());

                    break;

                case OUTLINED:
                    renderer.setBaseShapesVisible(true);
                    renderer.setUseOutlinePaint(true);
                    renderer.setDrawOutlines(true);
                    renderer.setSeriesOutlinePaint(n, (oc == null) ? p : oc.getPaint());

                    break;

                case FILLED_AND_OUTLINED:
                    renderer.setSeriesOutlinePaint(n, (oc == null) ? p : oc.getPaint());
                    renderer.setSeriesFillPaint(n, (fc == null) ? UIColor.WHITE.getPaint() : fc.getPaint());
                    renderer.setBaseShapesVisible(true);
                    renderer.setDrawOutlines(true);
                    renderer.setBaseShapesFilled(true);
                    renderer.setUseFillPaint(true);
                    renderer.setUseOutlinePaint(true);

                    break;

                default:
                    renderer.setBaseShapesVisible(false);

                    break;
                }

                if (renderer.getBaseShapesVisible()) {
                    renderer.setSeriesShape(n, getDefaultShape(i));
                }
            }
        }
    }

    protected void customizeXYLineAndShapeRenderer(ChartDefinition cd, XYPlot plot, PlotInformation pi) {
        AbstractXYItemRenderer renderer = (AbstractXYItemRenderer) plot.getRenderer();

        if (renderer instanceof XYLineAndShapeRenderer) {
            XYLineAndShapeRenderer xrenderer = (XYLineAndShapeRenderer) renderer;

            if (pi != null) {
                ShapeStyle fs = pi.getShapeStyle();

                if (renderer instanceof XYAreaSplineRendererEx) {
                    fs = pi.getShapeStyleEx();
                }

                switch (fs) {
                case FILLED:
                    xrenderer.setBaseShapesVisible(true);
                    xrenderer.setBaseShapesFilled(true);
                    xrenderer.setUseFillPaint(true);

                    break;

                case OUTLINED:
                    xrenderer.setBaseShapesVisible(true);
                    xrenderer.setDrawOutlines(true);
                    xrenderer.setUseOutlinePaint(true);

                    break;

                case FILLED_AND_OUTLINED:
                    xrenderer.setBaseShapesVisible(true);
                    xrenderer.setDrawOutlines(true);
                    xrenderer.setBaseShapesFilled(true);
                    xrenderer.setUseOutlinePaint(true);
                    xrenderer.setUseFillPaint(true);

                    break;

                default:
                    xrenderer.setBaseShapesVisible(false);

                    break;
                }
            }

            if (xrenderer.getBaseShapesVisible()) {
                xrenderer.setBaseShape(getDefaultShape(0));
            }
        }

        StandardXYItemLabelGenerator lg = null;

        renderer.setBaseItemLabelsVisible(cd.isShowPlotLabels());
        lg = new XYToolTipLabelGenerator(((ChartInfo) cd.getChartHandlerInfo()).seriesData);

        NumberAxis rangeAxis = (NumberAxis) plot.getRangeAxis();

        renderer.setBaseItemLabelGenerator(lg);
        rangeAxis.setUpperMargin(0.25);

        if (cd.isShowToolTips()) {
            renderer.setBaseToolTipGenerator((XYToolTipLabelGenerator) lg);
        }
    }

    protected void customizeXYPlot(ChartPanel chartPanel, ChartDefinition cd, XYPlot plot) {
        PlotInformation pi = cd.getPlotInformation();

        customizeBasicPlot(plot, pi);
        plot.clearRangeMarkers();
        plot.clearDomainMarkers();

        PlotOrientation po = cd.isVertical() ? PlotOrientation.VERTICAL : PlotOrientation.HORIZONTAL;

        plot.setOrientation(po);

        boolean showGrid = (pi == null) ? true : pi.isShowGridLines();

        if (showGrid) {
            Color c = getGridColor(pi);
            UIStroke stroke = getGridStroke(pi);

            plot.setRangeGridlinePaint(c);
            plot.setDomainGridlinePaint(c);

            Stroke s = SwingHelper.getStroke(stroke);

            plot.setRangeGridlineStroke(s);
            plot.setDomainGridlineStroke(s);
        } else {
            plot.setRangeGridlinesVisible(false);
            plot.setDomainGridlinesVisible(false);
        }

        if (pi != null) {
            Color c = pi.getBorderColor();

            if (c != null) {
                plot.setOutlinePaint(c);
            }
        }

        int angle = cd.getDomainAxis().getAngle();

        if ((angle != 0) && (angle != 180)) {
            plot.getDomainAxis().setLabelAngle(((angle) / 180f) * Math.PI);
        } else {
            plot.getDomainAxis().setLabelAngle(0);
        }

        angle = cd.getRangeAxis().getAngle();

        if ((angle > 0) && (angle != 180)) {
            plot.getRangeAxis().setLabelAngle(((angle) / 180f) * Math.PI);
        } else {
            plot.getRangeAxis().setLabelAngle(0);
        }

        updateMarkers(cd, plot, true);
        updateMarkers(cd, plot, false);
        customizeXYLineAndShapeRenderer(cd, plot, pi);
        customizeSeriesAttributes(chartPanel, cd, plot, plot.getDatasetCount() > 1);
        plot.setDatasetRenderingOrder(DatasetRenderingOrder.FORWARD);
    }

    protected void updateMarkers(ChartDefinition cd, XYPlot plot, boolean range) {
        List<ChartDataItem> markers = range ? cd.getRangeMarkers() : cd.getDomainMarkers();
        int len = (markers == null) ? 0 : markers.size();

        len = (markers == null) ? 0 : markers.size();

        if (range) {
            plot.clearRangeMarkers();

            for (int i = 0; i < len; i++) {
                plot.addRangeMarker(ChartHelper.createIntervalMarker(markers.get(i)));
            }
        } else {
            plot.clearDomainMarkers();

            IntervalMarker marker;
            ChartAxis.TimeUnit tm = cd.getDomainAxis().getTimeUnit();
            boolean time = cd.isDateTimeType();

            customizeAxis(cd, cd.getRangeAxis(), (NumberAxis) plot.getRangeAxis());
            customizeAxis(cd, cd.getDomainAxis(), (NumberAxis) plot.getDomainAxis());

            for (int i = 0; i < len; i++) {
                marker = time ? ChartHelper.createIntervalMarker(markers.get(i), tm)
                        : ChartHelper.createIntervalMarker(markers.get(i));
                plot.addDomainMarker(marker);
            }
        }
    }

    protected Axis getAxis(ChartDefinition cd, boolean range) {
        ChartInfo info = (ChartInfo) cd.getChartHandlerInfo();

        if (range) {
            return ((XYPlot) info.chart.getPlot()).getRangeAxis();
        } else {
            return ((XYPlot) info.chart.getPlot()).getDomainAxis();
        }
    }

    protected Shape getDefaultShape(int index) {
        if (baseShapes == null) {
            baseShapes = new ArrayList<Shape>();
        }

        return getDefaultShape(index, baseShapes);
    }

    protected Shape getDefaultShape(int index, ArrayList<Shape> markers) {
        Shape s;

        index = index % 4;

        while (markers.size() <= index) {
            switch (index) {
            case 0:
                s = new RoundRectangle2D.Double(-3, -3, 6, 6, 6, 6);

                break;

            case 1:
                s = new Rectangle2D.Double(-3, -3, 6, 6);

                break;

            case 2: {
                s = ShapeUtilities.createDiamond(4);

                break;
            }

            default: {
                s = ShapeUtilities.createUpTriangle(4);
            }
            }

            markers.add(s);
        }

        s = markers.get(index);

        return s;
    }

    @SuppressWarnings("deprecation")
    protected ChartDataItem itemFromEntity(ChartDefinition cd, ChartEntity e, boolean setVisited) {
        if (e instanceof XYItemEntity) {
            XYItemEntity ie = (XYItemEntity) e;

            if (setVisited) {
                cd.setVisitedItem(ie.getSeriesIndex(), ie.getItem());

                return cd.getVisitedItem();
            } else {
                return cd.getSeriesItem(ie.getSeriesIndex(), ie.getItem());
            }
        }

        if (e instanceof PieSectionEntity) {
            PieSectionEntity ie = (PieSectionEntity) e;

            if (setVisited) {
                cd.setVisitedItem(ie.getSectionIndex(), 0);

                return cd.getVisitedItem();
            } else {
                return cd.getSeriesItem(ie.getSectionIndex(), 0);
            }
        }

        if (e instanceof LegendItemEntity) {
            LegendItemEntity ie = (LegendItemEntity) e;

            if (setVisited) {
                cd.setVisitedItem(-1, -1);
            }

            return cd.getSeries(ie.getSeriesIndex());
        }

        if (e instanceof XYAnnotationEntity) {
            XYAnnotationEntity ie = (XYAnnotationEntity) e;

            if (setVisited) {
                cd.setVisitedItem(-1, -1);
            }

            return cd.getAnnotation(ie.getRendererIndex());
        }

        if (setVisited) {
            cd.setVisitedItem(-1, -1);
        }

        return null;
    }

    protected void setLabelAngel(iPlatformComponent chartPanel, ChartDefinition cd, boolean range) {
        Axis a = getAxis(cd, range);
        int angle = range ? cd.getRangeAxis().getAngle() : cd.getDomainAxis().getAngle();

        if (a != null) {
            float ang = (float) Math.toRadians(angle);

            if (!SNumber.isEqual(ang, a.getLabelAngle())) {
                a.setLabelAngle(ang);

                LabelData[] labels = null;

                if (a instanceof NumberAxisEx) {
                    labels = ((NumberAxisEx) a).categoryLabels;
                } else if (a instanceof NumberAxis3DEx) {
                    labels = ((NumberAxis3DEx) a).categoryLabels;
                }

                if (labels != null) {
                    UIFont f = getAxisLabelFont(cd.getRangeAxis());

                    remeasureLabels(labels, UIFontMetrics.getMetrics(f), angle);
                }
            }
        }
    }

    private void customizeAxis(ChartDefinition cd, ChartAxis ai, NumberAxis axis) {
        customizeAxisEx(cd, ai, axis);

        ChartInfo ci = (ChartInfo) cd.getChartHandlerInfo();
        double[] bounds = null;

        if (cd.getSeriesCount() > 0) {
            if (ai.isRangeAxis()) {
                bounds = ci.yAxisValues;
            } else {
                bounds = ci.xAxisValues;
            }
        }

        if (bounds != null) {
            axis.setLowerBound(bounds[0]);
            axis.setUpperBound(bounds[1]);
            axis.setTickUnit(new NumberTickUnit(bounds[2]));
        } else {
            RenderableDataItem u = ai.getUpperBounds();
            RenderableDataItem l = ai.getLowerBounds();
            double inc = ai.getIncrement();

            if (u != null) {
                axis.setUpperBound(u.doubleValue());
            }

            if (l != null) {
                axis.setLowerBound(l.doubleValue());
            }

            if (inc > 0) {
                axis.setTickUnit(new NumberTickUnit(inc));
            }
        }

        axis.setVisible(ai.isVisible());
    }

    private void customizeAxisEx(ChartDefinition cd, ChartAxis ai, Axis axis) {
        axis.setLabelPaint(getAxisTitleColor(ai));
        axis.setTickLabelPaint(getAxisLabelColor(ai));
        axis.setLabelFont(getAxisTitleFont(ai));
        axis.setTickLabelFont(getAxisLabelFont(ai));
        axis.setVisible(ai.isVisible());
        axis.setTickLabelsVisible(ai.isLabelsVisible());
    }

    private void customizePiePlot(ChartDefinition cd, PiePlot plot, PieCollection pie) {
        PlotInformation pi = cd.getPlotInformation();

        plot.setBackgroundPaint(null);
        plot.setOutlineVisible(false);
        customizeBasicPlot(plot, pi);

        List<RenderableDataItem> rows = cd.getSeries();
        int len = (rows == null) ? 0 : rows.size();
        ChartDataItem di;
        iComponentPainter bp;
        Paint p = null;

        if (len > 0) {
            rows = rows.get(0).getItems();
            len = rows.size();
        }

        for (int i = 0; i < len; i++) {
            di = (ChartDataItem) rows.get(i);
            bp = di.getComponentPainter();

            iPlatformPaint pp;

            if (bp == null) {
                p = di.getBackground();
            } else if (bp.getBackgroundPainter() != null) {
                pp = bp.getBackgroundPainter().getPaint(100, 100);
                p = (pp == null) ? null : pp.getPaint();
            }

            if (p == null) {
                p = getDefaultColor(i);
            }

            plot.setSectionPaint(di.getDomainValue().toString(), p);
        }

        if (pie.getExplodedPiece() != null) {
            plot.setExplodePercent(pie.getExplodedPiece(), 0.50);
        }

        plot.setLabelBackgroundPaint(null);
        plot.setLabelOutlinePaint(null);
        plot.setLabelShadowPaint(null);

        UIColor fg = cd.getTextColor(plotLabelColor);
        UIFont font = cd.getTextFont(plotLabelFont);

        plot.setLabelFont(font);
        plot.setLabelPaint(fg);

        if (cd.isShowPlotLabels()) {
            String format = (pi == null) ? null : pi.getLabelsFormat();

            plot.setLabelGenerator(new PieLabelGenerator(cd, format));
        }

        if (cd.isShowToolTips()) {
            plot.setToolTipGenerator(new PieToolTipLabelGenerator(cd, false));
        }
    }

    public static List refreshTicks(LabelData[] labels, TextAnchor anchor, TextAnchor rotationAnchor, double width,
            double angle) {
        Tick tick;
        int len = (labels == null) ? 0 : labels.length;
        List result = new ArrayList((len == 0) ? 1 : len);
        int mod = getLabelsMod(labels, (float) width, LABELS_PADDING);

        for (int i = 0; i < len; i++) {
            LabelData l = labels[i];

            if ((mod == 0) || (i % mod == 0)) {
                tick = new NumberTick(new Double(l.position), l.label, anchor, rotationAnchor, angle);
            } else {
                tick = new NumberTick(TickType.MINOR, new Double(l.position), "", TextAnchor.TOP_CENTER,
                        TextAnchor.CENTER, 0.0);
            }

            result.add(tick);
        }

        return result;
    }

    protected class ChartPanelEx extends ChartPanel
            implements Scrollable, MouseWheelListener, iPainterSupport, iView {
        int domainZoomScale = 1;
        int maxZoom = 5;
        int rangeZoomScale = 1;
        boolean paintComponentCalled;
        protected iPlatformComponentPainter componentPainter;
        protected SwingGraphics graphics;
        protected boolean shapedBorder;
        protected AffineTransform transform;
        private boolean trackWidth = false;
        private boolean trackHeight = false;
        boolean checkSize = true;
        double[] xAxisValues;
        double[] yAxisValues;

        protected ChartPanelEx(JFreeChart chart, boolean wheelscroll) {
            super(chart);

            if (wheelscroll) {
                addMouseWheelListener(this);
            }
        }

        @Override
        public JToolTip createToolTip() {
            return JToolTipEx.createNewToolTip(this);
        }

        public void decreaseZoom() {
            zoomChartAxis(false);
        }

        @Override
        public iPlatformComponentPainter getComponentPainter() {
            return componentPainter;
        }

        @Override
        public Dimension getMinimumSize() {
            return new Dimension(0, 0);
        }

        @Override
        public void getMinimumSize(UIDimension size, int maxWidth) {
            size.width = 0;
            size.height = 0;
        }

        @Override
        public Dimension getPreferredScrollableViewportSize() {
            return getPreferredSize();
        }

        @Override
        public void getPreferredSize(UIDimension size, int maxWidth) {
            size.width = 0;
            size.height = 0;
        }

        @Override
        public int getScrollableBlockIncrement(Rectangle visibleRect, int orientation, int direction) {
            int size = visibleRect.width / 4;

            return (size < 10) ? 10 : size;
        }

        @Override
        public boolean getScrollableTracksViewportHeight() {
            if (trackHeight) {
                return true;
            }

            Component parent = getParent();

            if (parent instanceof JViewport) {
                return parent.getHeight() > getPreferredSize().height;
            }

            return false;
        }

        @Override
        public boolean getScrollableTracksViewportWidth() {
            if (trackWidth) {
                return true;
            }

            Component parent = getParent();

            if (parent instanceof JViewport) {
                return parent.getWidth() > getPreferredSize().width;
            }

            return false;
        }

        @Override
        public int getScrollableUnitIncrement(Rectangle visibleRect, int orientation, int direction) {
            return 10;
        }

        @Override
        public AffineTransform getTransformEx() {
            return transform;
        }

        public void increaseZoom() {
            zoomChartAxis(true);
        }

        @Override
        public boolean isOpaque() {
            return false;
        }

        @Override
        public void mouseWheelMoved(MouseWheelEvent e) {
            if (e.getScrollType() != MouseWheelEvent.WHEEL_UNIT_SCROLL) {
                return;
            }

            if (e.getWheelRotation() < 0) {
                increaseZoom();
            } else {
                decreaseZoom();
            }
        }

        @Override
        public void paint(Graphics g) {
            if ((getWidth() > 16) && (getHeight() > 16)) {
                paintComponentCalled = false;

                Graphics2D g2 = (Graphics2D) g;
                AffineTransform tx = g2.getTransform();

                if (transform != null) {
                    g2.transform(transform);
                }

                graphics = SwingGraphics.fromGraphics(g2, this, graphics);
                super.paint(g2);

                if (tx != null) {
                    g2.setTransform(tx);
                }

                graphics.clear();
            }
        }

        @Override
        public void paintComponent(Graphics g) {
            paintComponentCalled = true;

            iPlatformComponentPainter cp = getComponentPainter();
            float height = getHeight();
            float width = getWidth();

            if (cp != null) {
                cp.paint(graphics, 0, 0, width, height, iPainter.HORIZONTAL, false);
            }

            super.paintComponent(g);
        }

        @Override
        public void restoreAutoBounds() {
            super.restoreAutoBounds();
            rangeZoomScale = 1;
            domainZoomScale = 1;
        }

        @Override
        public void setBorder(Border border) {
            super.setBorder(border);
            shapedBorder = false;

            if (border instanceof iPlatformBorder) {
                shapedBorder = !((iPlatformBorder) border).isRectangular();
            }
        }

        @Override
        public void setBounds(int x, int y, int width, int height) {
            setMaximumDrawHeight(height);
            setMaximumDrawWidth(width);
            setMinimumDrawWidth(width);
            setMinimumDrawHeight(height);
            updateTickmarks(width, height);
            super.setBounds(x, y, width, height);
        }

        @Override
        public void setComponentPainter(iPlatformComponentPainter cp) {
            this.componentPainter = cp;
        }

        @Override
        public void setTransformEx(AffineTransform tx) {
            transform = tx;
        }

        public UIDimension getPlotAreaSize() {
            Rectangle2D r = getScreenDataArea();

            return new UIDimension(r.getWidth(), r.getHeight());
        }

        public void updateTickmarks(int width, int height) {
            NumberAxis a = getNumberAxis(getChart().getPlot(), true);

            if (a instanceof NumberAxisEx) {
                ((NumberAxisEx) a).updateTickMarks(width, height);
            }

            if (a instanceof NumberAxis3DEx) {
                ((NumberAxis3DEx) a).updateTickMarks(width, height);
            }

            a = getNumberAxis(getChart().getPlot(), false);

            if (a instanceof NumberAxisEx) {
                ((NumberAxisEx) a).updateTickMarks(width, height);
            }

            if (a instanceof NumberAxis3DEx) {
                ((NumberAxis3DEx) a).updateTickMarks(width, height);
            }
        }

        @Override
        public void zoomInDomain(double x, double y) {
            if (domainZoomScale + 1 > maxZoom) {
                return;
            }

            domainZoomScale++;
            super.zoomInDomain(x, y);
        }

        @Override
        public void zoomInRange(double x, double y) {
            if (rangeZoomScale + 1 > maxZoom) {
                return;
            }

            rangeZoomScale++;
            super.zoomInRange(x, y);
        }

        @Override
        public void zoomOutDomain(double x, double y) {
            if (domainZoomScale - 1 < -maxZoom) {
                return;
            }

            domainZoomScale--;
            super.zoomOutDomain(x, y);
        }

        @Override
        public void zoomOutRange(double x, double y) {
            if (rangeZoomScale - 1 < -maxZoom) {
                return;
            }

            rangeZoomScale--;
            super.zoomOutRange(x, y);
        }

        @Override
        protected void paintBorder(Graphics g) {
            if (componentPainter == null) {
                super.paintBorder(g);
            }
        }

        @Override
        protected void paintChildren(Graphics g) {
            iPlatformComponentPainter cp = getComponentPainter();
            float height = getHeight();
            float width = getWidth();

            if (!paintComponentCalled && (cp != null)) {
                cp.paint(graphics, 0, 0, width, height, iPainter.HORIZONTAL, false);
            }

            super.paintChildren(graphics.getGraphics());

            if (cp != null) {
                cp.paint(graphics, 0, 0, width, height, iPainter.HORIZONTAL, true);
            }
        }

        private void zoomChartAxis(boolean increase) {
            int width = getMaximumDrawWidth() - getMinimumDrawWidth();
            int height = getMaximumDrawHeight() - getMinimumDrawWidth();

            if (increase) {
                zoomInBoth(width / 2, height / 2);
            } else {
                zoomOutBoth(width / 2, height / 2);
            }
        }

        NumberAxis getNumberAxis(Plot p, boolean range) {
            if (p instanceof XYPlot) {
                ValueAxis a = range ? ((XYPlot) p).getRangeAxis() : ((XYPlot) p).getDomainAxis();

                if (a instanceof NumberAxis) {
                    return (NumberAxis) a;
                }
            }

            return null;
        }
    }

    protected static class DataSetValue {
        public ChartType chartType;
        public Object dataset;

        DataSetValue(ChartType ct, Object set) {
            chartType = ct;
            dataset = set;
        }
    }

    static class ChartInfo extends aChartInfo {
        JFreeChart chart;
        ChartPanel chartPanel;
        Object labelGenerator;

        public ChartInfo(JFreeChart chart) {
            this.chart = chart;
        }

        /**
         * Constructs a new instance
         */
        ChartInfo() {
        }
    }

    class ChartMouseHandler implements ChartMouseListener {
        ChartDefinition chartDefinition;

        public ChartMouseHandler(ChartDefinition cd) {
            super();
            chartDefinition = cd;
        }

        @Override
        public void chartMouseClicked(ChartMouseEvent event) {
            ChartEntity e = event.getEntity();
            ChartDataItem item = itemFromEntity(chartDefinition, e, true);

            if (item != null) {
                chartDefinition.mouseClicked(EventHelper.createMouseEvent(event.getSource(), event.getTrigger()),
                        item);
            }
        }

        @Override
        public void chartMouseMoved(ChartMouseEvent event) {
            if ((chartDefinition.getMouseHandler() != null)
                    && chartDefinition.getMouseHandler().wantsMouseMoveEvents()) {
                ChartEntity e = event.getEntity();
                ChartDataItem item = itemFromEntity(chartDefinition, e, true);

                if (item != null) {
                    chartDefinition.mouseMoved(EventHelper.createMouseEvent(event.getSource(), event.getTrigger()),
                            item);
                }
            }
        }
    }

    class NumberAxis3DEx extends NumberAxis3D {
        ChartDefinition chartDefinition;
        boolean domain;
        LabelData[] categoryLabels;
        boolean staticLabels;
        TickUpdater tickUpdater;
        boolean zeroAngle;

        public NumberAxis3DEx() {
            super();
        }

        public NumberAxis3DEx(ChartDefinition cd, boolean domain, String label) {
            super(label);
            this.domain = domain;
            this.chartDefinition = cd;

            ChartInfo ci = (ChartInfo) cd.getChartHandlerInfo();
            boolean fixed = domain ? ci.xIncrementFixed : ci.yIncrementFixed;

            if (!fixed) {
                tickUpdater = new TickUpdater(domain);
            }

            setMinorTickMarksVisible(true);
            setTickMarkOutsideLength(5);
        }

        public NumberAxis3DEx(ChartDefinition cd, LabelData[] labels, String label) {
            super(label);
            this.domain = true;
            this.chartDefinition = cd;

            ChartInfo ci = (ChartInfo) cd.getChartHandlerInfo();
            boolean fixed = domain ? ci.xIncrementFixed : ci.yIncrementFixed;

            if (!fixed) {
                tickUpdater = new TickUpdater(domain);
            }

            this.categoryLabels = labels;
            setTickMarkOutsideLength(5);
            setMinorTickMarksVisible(true);
        }

        @Override
        public double getLabelAngle() {
            return zeroAngle ? 0 : super.getLabelAngle();
        }

        @Override
        public List refreshTicks(Graphics2D g2, AxisState state, Rectangle2D dataArea, RectangleEdge edge) {
            if (chartDefinition.getSeriesCount() == 0) {
                ChartAxis ai = domain ? chartDefinition.getDomainAxis() : chartDefinition.getRangeAxis();

                if ((ai.getUpperBounds() == null) && (ai.getLowerBounds() == null)) {
                    return new ArrayList(0);
                }
            }

            List result = null;

            if (RectangleEdge.isTopOrBottom(edge)) {
                result = refreshTicksHorizontal(g2, dataArea, edge);
            } else if (RectangleEdge.isLeftOrRight(edge)) {
                result = refreshTicksVertical(g2, dataArea, edge);
            }

            return (result == null) ? new ArrayList(0) : result;
        }

        @Override
        public void setLabelAngle(double angle) {
            super.setLabelAngle(angle);
            setVerticalTickLabels(angle != 0);
        }

        public void updateTickMarks(int width, int height) {
            if (tickUpdater != null) {
                tickUpdater.updateTickMarks(chartDefinition, this, width, height);
            }
        }

        @Override
        protected AxisState drawLabel(String label, Graphics2D g2, Rectangle2D plotArea, Rectangle2D dataArea,
                RectangleEdge edge, AxisState state) {
            try {
                zeroAngle = true;

                return super.drawLabel(label, g2, plotArea, dataArea, edge, state);
            } finally {
                zeroAngle = false;
            }
        }

        @Override
        protected Rectangle2D getLabelEnclosure(Graphics2D g2, RectangleEdge edge) {
            try {
                zeroAngle = true;

                return super.getLabelEnclosure(g2, edge);
            } finally {
                zeroAngle = false;
            }
        }

        @Override
        protected List refreshTicksHorizontal(Graphics2D g2, Rectangle2D dataArea, RectangleEdge edge) {
            double width = dataArea.getWidth();
            TextAnchor anchor, rotationAnchor;
            double angle = 0.0;

            if (isVerticalTickLabels()) {
                anchor = TextAnchor.CENTER_RIGHT;
                rotationAnchor = TextAnchor.CENTER_RIGHT;
                angle = getLabelAngle();
                // if (edge == RectangleEdge.TOP) {
                // angle = Math.PI / 2.0;
                // } else {
                // angle = -Math.PI / 2.0;
                // }
            } else {
                if (edge == RectangleEdge.TOP) {
                    anchor = TextAnchor.BOTTOM_CENTER;
                    rotationAnchor = TextAnchor.BOTTOM_CENTER;
                } else {
                    anchor = TextAnchor.TOP_CENTER;
                    rotationAnchor = TextAnchor.TOP_CENTER;
                }
            }

            LabelData[] labels = (categoryLabels != null) ? categoryLabels
                    : createNonCategoryLabels(chartDefinition, width, this, domain, tickSize);

            return ChartHandler.refreshTicks(labels, anchor, rotationAnchor, width, angle);
        }

        @Override
        protected List refreshTicksVertical(Graphics2D g2, Rectangle2D dataArea, RectangleEdge edge) {
            double width = dataArea.getHeight();
            TextAnchor anchor;
            TextAnchor rotationAnchor;
            double angle = 0.0;

            if (isVerticalTickLabels()) {
                if (edge == RectangleEdge.LEFT) {
                    anchor = TextAnchor.BOTTOM_CENTER;
                    rotationAnchor = TextAnchor.BOTTOM_CENTER;
                    angle = -Math.PI / 2.0;
                } else {
                    anchor = TextAnchor.BOTTOM_CENTER;
                    rotationAnchor = TextAnchor.BOTTOM_CENTER;
                    angle = Math.PI / 2.0;
                }

                angle = getLabelAngle();
            } else {
                if (edge == RectangleEdge.LEFT) {
                    anchor = TextAnchor.CENTER_RIGHT;
                    rotationAnchor = TextAnchor.CENTER_RIGHT;
                } else {
                    anchor = TextAnchor.CENTER_LEFT;
                    rotationAnchor = TextAnchor.CENTER_LEFT;
                }

                angle = getLabelAngle();
            }

            LabelData[] labels = (categoryLabels != null) ? categoryLabels
                    : createNonCategoryLabels(chartDefinition, width, this, domain, tickSize);

            return ChartHandler.refreshTicks(labels, anchor, rotationAnchor, width, angle);
        }
    }

    class NumberAxisEx extends NumberAxis {
        ChartDefinition chartDefinition;
        boolean domain;
        LabelData[] categoryLabels;
        boolean staticLabels;
        TickUpdater tickUpdater;
        boolean zeroAngle;

        public NumberAxisEx() {
            super();
        }

        public NumberAxisEx(ChartDefinition cd, boolean domain, String label) {
            super(label);
            this.domain = domain;
            this.chartDefinition = cd;

            ChartInfo ci = (ChartInfo) cd.getChartHandlerInfo();
            boolean fixed = domain ? ci.xIncrementFixed : ci.yIncrementFixed;

            if (!fixed) {
                tickUpdater = new TickUpdater(domain);
            }

            setMinorTickMarksVisible(true);
            setTickMarkOutsideLength(5);
        }

        public NumberAxisEx(ChartDefinition cd, LabelData[] labels, String label) {
            super(label);
            this.domain = true;
            this.chartDefinition = cd;

            ChartInfo ci = (ChartInfo) cd.getChartHandlerInfo();
            boolean fixed = domain ? ci.xIncrementFixed : ci.yIncrementFixed;

            if (!fixed) {
                tickUpdater = new TickUpdater(domain);
            }

            this.categoryLabels = labels;
            setTickMarkOutsideLength(5);
            setMinorTickMarksVisible(true);
        }

        @Override
        public double getLabelAngle() {
            return zeroAngle ? 0 : super.getLabelAngle();
        }

        @Override
        public List refreshTicks(Graphics2D g2, AxisState state, Rectangle2D dataArea, RectangleEdge edge) {
            if (chartDefinition.getSeriesCount() == 0) {
                ChartAxis ai = domain ? chartDefinition.getDomainAxis() : chartDefinition.getRangeAxis();

                if ((ai.getUpperBounds() == null) && (ai.getLowerBounds() == null)) {
                    return new ArrayList(0);
                }
            }

            List result = null;

            if (RectangleEdge.isTopOrBottom(edge)) {
                result = refreshTicksHorizontal(g2, dataArea, edge);
            } else if (RectangleEdge.isLeftOrRight(edge)) {
                result = refreshTicksVertical(g2, dataArea, edge);
            }

            return (result == null) ? new ArrayList(0) : result;
        }

        @Override
        public void setLabelAngle(double angle) {
            super.setLabelAngle(angle);
            setVerticalTickLabels(angle != 0);
        }

        public void updateTickMarks(int width, int height) {
            if (tickUpdater != null) {
                tickUpdater.updateTickMarks(chartDefinition, this, width, height);
            }
        }

        @Override
        protected AxisState drawLabel(String label, Graphics2D g2, Rectangle2D plotArea, Rectangle2D dataArea,
                RectangleEdge edge, AxisState state) {
            try {
                zeroAngle = true;

                return super.drawLabel(label, g2, plotArea, dataArea, edge, state);
            } finally {
                zeroAngle = false;
            }
        }

        @Override
        protected Rectangle2D getLabelEnclosure(Graphics2D g2, RectangleEdge edge) {
            try {
                zeroAngle = true;

                return super.getLabelEnclosure(g2, edge);
            } finally {
                zeroAngle = false;
            }
        }

        @Override
        protected List refreshTicksHorizontal(Graphics2D g2, Rectangle2D dataArea, RectangleEdge edge) {
            double width = dataArea.getWidth();
            TextAnchor anchor, rotationAnchor;
            double angle = 0.0;

            if (isVerticalTickLabels()) {
                anchor = TextAnchor.CENTER_RIGHT;
                rotationAnchor = TextAnchor.CENTER_RIGHT;

                if (edge == RectangleEdge.TOP) {
                    angle = Math.PI / 2.0;
                } else {
                    angle = -Math.PI / 2.0;
                }
            } else {
                if (edge == RectangleEdge.TOP) {
                    anchor = TextAnchor.BOTTOM_CENTER;
                    rotationAnchor = TextAnchor.BOTTOM_CENTER;
                } else {
                    anchor = TextAnchor.TOP_CENTER;
                    rotationAnchor = TextAnchor.TOP_CENTER;
                }
            }

            LabelData[] labels = (categoryLabels != null) ? categoryLabels
                    : createNonCategoryLabels(chartDefinition, width, this, domain, tickSize);

            if (labels.length > 1) {
                width -= labels[0].width;
            }

            return ChartHandler.refreshTicks(labels, anchor, rotationAnchor, width, angle);
        }

        @Override
        protected List refreshTicksVertical(Graphics2D g2, Rectangle2D dataArea, RectangleEdge edge) {
            double width = dataArea.getHeight();
            TextAnchor anchor;
            TextAnchor rotationAnchor;
            double angle = 0.0;

            if (isVerticalTickLabels()) {
                if (edge == RectangleEdge.LEFT) {
                    anchor = TextAnchor.BOTTOM_CENTER;
                    rotationAnchor = TextAnchor.BOTTOM_CENTER;
                    angle = -Math.PI / 2.0;
                } else {
                    anchor = TextAnchor.BOTTOM_CENTER;
                    rotationAnchor = TextAnchor.BOTTOM_CENTER;
                    angle = Math.PI / 2.0;
                }
            } else {
                if (edge == RectangleEdge.LEFT) {
                    anchor = TextAnchor.CENTER_RIGHT;
                    rotationAnchor = TextAnchor.CENTER_RIGHT;
                } else {
                    anchor = TextAnchor.CENTER_LEFT;
                    rotationAnchor = TextAnchor.CENTER_LEFT;
                }
            }

            LabelData[] labels = (categoryLabels != null) ? categoryLabels
                    : createNonCategoryLabels(chartDefinition, width, this, domain, tickSize);

            return ChartHandler.refreshTicks(labels, anchor, rotationAnchor, width, angle);
        }
    }

    class TickUpdater {
        boolean updatingTickMarks;
        int oldHeight;
        int oldWidth;
        boolean domain;

        public TickUpdater(boolean domain) {
            this.domain = domain;
        }

        protected void updateTickMarks(ChartDefinition chartDefinition, NumberAxis axis, int width, int height) {
            if (updatingTickMarks || ((oldHeight == height) && (oldWidth == width))) {
                return;
            }

            updatingTickMarks = true;
            oldHeight = height;
            oldWidth = width;

            ChartInfo ci = (ChartInfo) chartDefinition.getChartHandlerInfo();

            try {
                if ((domain && ci.xIncrementFixed) || (!domain && ci.yIncrementFixed)) {
                    return;
                }

                double range[] = domain ? ci.xAxisValues : ci.yAxisValues;

                if (range != null) {
                    double increment = calculateIncrement(domain ? width : height, tickSize, range[0], range[1],
                            range[2]);

                    if (!SNumber.isEqual(increment, axis.getTickUnit().getSize())) {
                        axis.setTickUnit(new NumberTickUnit(increment));
                    }
                }
            } finally {
                updatingTickMarks = false;
            }
        }
    }
}