com.rapidminer.gui.new_plotter.engine.jfreechart.JFreeChartPlotEngine.java Source code

Java tutorial

Introduction

Here is the source code for com.rapidminer.gui.new_plotter.engine.jfreechart.JFreeChartPlotEngine.java

Source

/**
 * Copyright (C) 2001-2015 by RapidMiner and the contributors
 *
 * Complete list of developers available at our web site:
 *
 *      http://rapidminer.com
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero 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 Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program.  If not, see http://www.gnu.org/licenses/.
 */
package com.rapidminer.gui.new_plotter.engine.jfreechart;

import com.rapidminer.gui.new_plotter.ChartPlottimeException;
import com.rapidminer.gui.new_plotter.ConfigurationChangeResponse;
import com.rapidminer.gui.new_plotter.MasterOfDesaster;
import com.rapidminer.gui.new_plotter.PlotConfigurationError;
import com.rapidminer.gui.new_plotter.PlotConfigurationQuickFix;
import com.rapidminer.gui.new_plotter.StaticDebug;
import com.rapidminer.gui.new_plotter.configuration.AxisParallelLineConfiguration;
import com.rapidminer.gui.new_plotter.configuration.DataTableColumn.ValueType;
import com.rapidminer.gui.new_plotter.configuration.DefaultDimensionConfig;
import com.rapidminer.gui.new_plotter.configuration.DimensionConfig;
import com.rapidminer.gui.new_plotter.configuration.DimensionConfig.PlotDimension;
import com.rapidminer.gui.new_plotter.configuration.LegendConfiguration;
import com.rapidminer.gui.new_plotter.configuration.LegendConfiguration.LegendPosition;
import com.rapidminer.gui.new_plotter.configuration.LineFormat.LineStyle;
import com.rapidminer.gui.new_plotter.configuration.PlotConfiguration;
import com.rapidminer.gui.new_plotter.configuration.RangeAxisConfig;
import com.rapidminer.gui.new_plotter.configuration.SeriesFormat;
import com.rapidminer.gui.new_plotter.configuration.SeriesFormat.IndicatorType;
import com.rapidminer.gui.new_plotter.configuration.SeriesFormat.ItemShape;
import com.rapidminer.gui.new_plotter.configuration.SeriesFormat.StackingMode;
import com.rapidminer.gui.new_plotter.configuration.SeriesFormat.VisualizationType;
import com.rapidminer.gui.new_plotter.configuration.ValueSource;
import com.rapidminer.gui.new_plotter.data.DimensionConfigData;
import com.rapidminer.gui.new_plotter.data.PlotInstance;
import com.rapidminer.gui.new_plotter.data.ValueSourceData;
import com.rapidminer.gui.new_plotter.engine.PlotEngine;
import com.rapidminer.gui.new_plotter.engine.jfreechart.actions.AddParallelLineAction;
import com.rapidminer.gui.new_plotter.engine.jfreechart.actions.ClearParallelLinesAction;
import com.rapidminer.gui.new_plotter.engine.jfreechart.actions.CopyChartAction;
import com.rapidminer.gui.new_plotter.engine.jfreechart.actions.ManageParallelLinesAction;
import com.rapidminer.gui.new_plotter.engine.jfreechart.actions.ManageZoomAction;
import com.rapidminer.gui.new_plotter.engine.jfreechart.dataset.ValueSourceToMultiValueCategoryDatasetAdapter;
import com.rapidminer.gui.new_plotter.engine.jfreechart.legend.ColoredBlockContainer;
import com.rapidminer.gui.new_plotter.engine.jfreechart.legend.SmartLegendTitle;
import com.rapidminer.gui.new_plotter.engine.jfreechart.link_and_brush.LinkAndBrushChartPanel;
import com.rapidminer.gui.new_plotter.engine.jfreechart.link_and_brush.plots.LinkAndBrushCategoryPlot;
import com.rapidminer.gui.new_plotter.engine.jfreechart.link_and_brush.plots.LinkAndBrushXYPlot;
import com.rapidminer.gui.new_plotter.listener.JFreeChartPlotEngineListener;
import com.rapidminer.gui.new_plotter.listener.PlotConfigurationListener;
import com.rapidminer.gui.new_plotter.listener.PlotConfigurationProcessingListener;
import com.rapidminer.gui.new_plotter.listener.events.DimensionConfigChangeEvent;
import com.rapidminer.gui.new_plotter.listener.events.DimensionConfigChangeEvent.DimensionConfigChangeType;
import com.rapidminer.gui.new_plotter.listener.events.LegendConfigurationChangeEvent;
import com.rapidminer.gui.new_plotter.listener.events.PlotConfigurationChangeEvent;
import com.rapidminer.gui.new_plotter.listener.events.PlotConfigurationChangeEvent.PlotConfigurationChangeType;
import com.rapidminer.gui.new_plotter.listener.events.RangeAxisConfigChangeEvent;
import com.rapidminer.gui.new_plotter.listener.events.RangeAxisConfigChangeEvent.RangeAxisConfigChangeType;
import com.rapidminer.gui.new_plotter.listener.events.SeriesFormatChangeEvent;
import com.rapidminer.gui.new_plotter.listener.events.ValueSourceChangeEvent;
import com.rapidminer.gui.new_plotter.listener.events.ValueSourceChangeEvent.ValueSourceChangeType;
import com.rapidminer.gui.plotter.CoordinateTransformation;
import com.rapidminer.gui.plotter.NullCoordinateTransformation;
import com.rapidminer.gui.tools.SwingTools;
import com.rapidminer.tools.I18N;

import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Font;
import java.awt.Point;
import java.awt.RenderingHints;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.lang.ref.WeakReference;
import java.text.DateFormat;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.TimeZone;
import java.util.Vector;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;

import javax.swing.JPopupMenu;
import javax.swing.SwingUtilities;
import javax.swing.SwingWorker;

import org.jfree.chart.ChartPanel;
import org.jfree.chart.JFreeChart;
import org.jfree.chart.LegendItemCollection;
import org.jfree.chart.LegendItemSource;
import org.jfree.chart.axis.CategoryAxis;
import org.jfree.chart.axis.DateAxis;
import org.jfree.chart.axis.ValueAxis;
import org.jfree.chart.block.BlockBorder;
import org.jfree.chart.block.ColumnArrangement;
import org.jfree.chart.block.FlowArrangement;
import org.jfree.chart.plot.CategoryPlot;
import org.jfree.chart.plot.Crosshair;
import org.jfree.chart.plot.Plot;
import org.jfree.chart.plot.PlotOrientation;
import org.jfree.chart.plot.XYPlot;
import org.jfree.chart.renderer.category.CategoryItemRenderer;
import org.jfree.chart.renderer.xy.XYItemRenderer;
import org.jfree.chart.title.LegendTitle;
import org.jfree.chart.title.TextTitle;
import org.jfree.data.category.CategoryDataset;
import org.jfree.data.general.Dataset;
import org.jfree.data.xy.XYDataset;
import org.jfree.ui.HorizontalAlignment;
import org.jfree.ui.RectangleEdge;
import org.jfree.ui.VerticalAlignment;

/**
 * This class creates a JFreeChart from a PlotConfiguration.
 * 
 * Using the listener mechanism, the chart is updated or recreated each time the plot configuration
 * changes.
 * 
 * Classes implementing the interface {@link JFreeChartPlotEngineListener} can register at the
 * Plotter2D via addListener() and will be informed when the chart has changed.
 * 
 * 
 * Dataset and Renderer Chart: Series type categorical grouped stacking error bars Renderer Dataset
 * Plot Constraints L - irrelevant n/a - XYLineAndShapeRenderer XYDataset, e.g. DefaultXYDataset
 * XYPlot L - irrelevant n/a bars XYErrorRenderer IntervalXYDataset XYPlot L - irrelevant n/a band
 * DeviationRenderer IntervalXYDataset XYPlot L - irrelevant n/a difference XYDifferenceRenderer
 * XYDataset with 2 series XYPlot distinct values only L X - n/a - ScatterRenderer
 * MultiValueCategoryDataset CategoryPlot no lines possible, color axis must be grouped (if present)
 * L X X n/a - LineAndShapeRenderer CategoryDataset CategoryPlot L X irrelevant n/a bars
 * StatisticalLineAndShapeRenderer StatisticalCategoryDataset CategoryPlot no duplicate values on
 * domain axis allowed, only symmetric error L X irrelevant n/a band not supported by JFreeChart n/a
 * n/a L X irrelevant n/a difference not supported by JFreeChart n/a n/a B - irrelevant none -
 * ClusteredXYBarRenderer IntervalXYDataset (XYSeriesCollection for fixed width binning/no binning,
 * DefaultIntervalXYDataset oXYPlot B - irrelevant absolute - StackedXYBarRenderer TableXYDataset
 * XYPlot B - irrelevant percentage - StackedXYBarRenderer TableXYDataset XYPlot B - irrelevant
 * irrelevant != none not supported by JFreeChart n/a n/a B X irrelevant none - BarRenderer
 * CategoryDataset CategoryPlot B X irrelevant absolute - StackedBarRenderer CategoryDataset
 * [(IntervalXYDataset && TableXYDataset), e.g. DefaultTableXYDataset] CategoryPlot B X irrelevant
 * percentage - StackedBarRenderer CategoryDataset [(IntervalXYDataset && TableXYDataset), e.g.
 * DefaultTableXYDataset] CategoryPlot B X irrelevant none bars StatisticalBarRenderer
 * StatisticalCategoryDataset CategoryPlot B X irrelevant none band, diff not supported by
 * JFreeChart n/a n/a B X irrelevant != none != none not supported by JFreeChart n/a n/a A -
 * irrelevant none - XYAreaRenderer2 XYDataset XYPlot A - irrelevant absolute -
 * StackedXYAreaRenderer2 TableXYDataset XYPlot A - irrelevant percentage - not supported by
 * JFreeChart n/a n/a A X irrelevant none - AreaRenderer CategoryDataset, e.g.
 * DefaultCategoryDataset CategoryPlot A X irrelevant absolute - StackedAreaRenderer
 * CategoryDataset, e.g. DefaultCategoryDataset CategoryPlot A X irrelevant percentage -
 * StackedAreaRenderer CategoryDataset, e.g. DefaultCategoryDataset CategoryPlot A irrelevant
 * irrelevant irrelevant != none not supported by JFreeChart n/a n/a
 * 
 * Series types: L -> Lines and Shapes B -> Bar A -> Area
 * 
 * 
 * @author Marius Helf, Nils Woehler
 */
public class JFreeChartPlotEngine
        implements PlotEngine, PlotConfigurationListener, PlotConfigurationProcessingListener, LegendItemSource {

    private boolean initializing = false;

    private List<WeakReference<JFreeChartPlotEngineListener>> listeners = new LinkedList<WeakReference<JFreeChartPlotEngineListener>>();

    private PlotInstance plotInstance;
    private PlotInstance nextPlotInstance = null;

    private final PlotInstanceLegendCreator legendCreator = new PlotInstanceLegendCreator();

    private transient LegendItemCollection cachedLegendItems = null;

    private final LinkAndBrushChartPanel chartPanel;

    private AtomicBoolean updatingChart;

    private boolean currentChartIsValid = false;

    private MultiAxesCrosshairOverlay crosshairOverlay = new MultiAxesCrosshairOverlay();

    private Object nextPlotInstanceLock = new Object();

    /** the popup menu shown after a popup action on the chart */
    private JPopupMenu popupMenuChart;

    /** the mouse listener for the popup menu */
    private MouseListener popupMenuListener;

    /** the add crosshair action */
    private AddParallelLineAction addParallelLineAction;

    /**
     * This is a transformation which transforms the components coordinates to screen coordinates.
     * If is null, no transformation is needed.
     */
    private transient CoordinateTransformation coordinateTransformation = new NullCoordinateTransformation();

    {
        // create popup menu for chart here
        popupMenuChart = new JPopupMenu();
        popupMenuChart.add(new CopyChartAction(this));
        popupMenuChart.addSeparator();
        popupMenuChart.add(new ManageZoomAction(this));
        popupMenuChart.addSeparator();
        addParallelLineAction = new AddParallelLineAction(this);
        popupMenuChart.add(addParallelLineAction);
        popupMenuChart.add(new ManageParallelLinesAction(this));
        popupMenuChart.addSeparator();
        popupMenuChart.add(new ClearParallelLinesAction(this));

        // popup listener for the chart panel
        popupMenuListener = new MouseAdapter() {

            @Override
            public void mousePressed(MouseEvent e) {
                maybeShowPopup(e);
            }

            @Override
            public void mouseReleased(MouseEvent e) {
                maybeShowPopup(e);
            }

            private void maybeShowPopup(MouseEvent e) {
                if (e.isPopupTrigger()) {
                    addParallelLineAction.setPopupLocation(e.getPoint());
                    coordinateTransformation.showPopupMenu(new Point(e.getX(), e.getY()), chartPanel,
                            popupMenuChart);
                }
            }
        };
    }

    public JFreeChartPlotEngine(PlotInstance plotInstanceForEngine, boolean zoomInOnSelection) {

        this.plotInstance = plotInstanceForEngine;
        updatingChart = new AtomicBoolean(false);

        chartPanel = new LinkAndBrushChartPanel(new JFreeChart(new CategoryPlot()), 50, 50, 50, 50,
                zoomInOnSelection);
        chartPanel.setMinimumDrawWidth(50);
        chartPanel.setMinimumDrawHeight(50);
        chartPanel.setMaximumDrawWidth(10000);
        chartPanel.setMaximumDrawHeight(10000);
        chartPanel.addMouseListener(popupMenuListener);

        subscribeAtPlotInstance(plotInstance);
    }

    public JFreeChartPlotEngine(PlotInstance plotInstanceForEngine, boolean zoomInOnSelection, boolean useBuffer) {

        this.plotInstance = plotInstanceForEngine;
        updatingChart = new AtomicBoolean(false);

        chartPanel = new LinkAndBrushChartPanel(new JFreeChart(new CategoryPlot()), 50, 50, 50, 50,
                zoomInOnSelection, useBuffer);
        chartPanel.setMinimumDrawWidth(50);
        chartPanel.setMinimumDrawHeight(50);
        chartPanel.setMaximumDrawWidth(10000);
        chartPanel.setMaximumDrawHeight(10000);
        chartPanel.addMouseListener(popupMenuListener);

        subscribeAtPlotInstance(plotInstance);
    }

    private void subscribeAtPlotInstance(PlotInstance plotInstance) {
        initializing = true;
        // register as listener
        PlotConfiguration masterPlotConfiguration = plotInstance.getMasterPlotConfiguration();
        masterPlotConfiguration.addPlotConfigurationListener(this);
        masterPlotConfiguration.addPlotConfigurationProcessingListener(this);
        chartPanel.addLinkAndBrushSelectionListener(masterPlotConfiguration);
        initializing = false;
    }

    public boolean updatingChart() {
        return updatingChart.get();
    }

    /**
     * Use to retrieve the current {@link JFreeChart} chart, stored and shown in the
     * {@link ChartPanel}. The chart may not reflect the current {@link PlotConfiguration} and may
     * be replaced by another chart shortly after it has been fetched.
     */
    public JFreeChart getCurrentChart() {
        JFreeChart currentChart = chartPanel.getChart();
        if (currentChart == null) {
            return new JFreeChart(new CategoryPlot());
        }
        return currentChart;
    }

    /**
     * Trigger an update of the {@link JFreeChart} that is stored in the {@link ChartPanel}. The
     * update is performed by using a {@link SwingWorker} thread. First the new Chart is created and
     * afterwards the new chart is stored in the {@link ChartPanel}.
     * 
     * @param informPlotConfigWhenDone
     *            should inform the {@link PlotConfiguration} that the worker thread is done?
     */
    private synchronized void updateChartPanelChart(final boolean informPlotConfigWhenDone) {
        updatingChart.getAndSet(true);

        StaticDebug.debug("######################### STARTING CHART UPDATE ######################");

        SwingWorker updateChartWorker = new SwingWorker<JFreeChart, Void>() {

            @Override
            public JFreeChart doInBackground() throws Exception {
                try {
                    if (!isPlotInstanceValid()) {
                        return null;
                    }
                    try {
                        invalidateCache();
                        JFreeChart createdChart = createChart();
                        updateLegendItems();
                        checkWarnings();
                        currentChartIsValid = true;
                        return createdChart;
                    } catch (ChartPlottimeException e) {
                        handlePlottimeException(e);
                        return null;
                    }
                } catch (Exception e) {
                    e.printStackTrace();
                    SwingTools.showFinalErrorMessage("generic_plotter_error", e, true, new Object[] {});
                    handlePlottimeException(new ChartPlottimeException("generic_plotter_error"));
                    return null;
                }
            }

            @Override
            public void done() {
                try {
                    JFreeChart chart = null;
                    try {
                        chart = get(60, TimeUnit.SECONDS);
                        updatingChartPanelChartDone();
                    } catch (Exception e) {
                        updatingChartPanelChartDone();
                        e.printStackTrace();
                        handlePlottimeException(new ChartPlottimeException("generic_plotter_error"));
                        return;
                    }
                    if (chart == null) {
                        currentChartIsValid = false;
                        chart = new JFreeChart(new CategoryPlot());
                    }
                    updateChartPanel(chart);

                    // informs plotConfig that the repaint event has been processed
                    if (informPlotConfigWhenDone) {
                        plotInstance.getMasterPlotConfiguration().plotConfigurationChangeEventProcessed();
                    }
                } catch (Exception e) {
                    e.printStackTrace();
                    SwingTools.showFinalErrorMessage("generic_plotter_error", e);
                }

            }

        };

        updateChartWorker.execute();
    }

    /**
     * Updates the chart panel to show the provided {@link JFreeChart}. If the chart is
     * <code>null</code> an empty Plot will be shown. If an overlay has been defined and the chart
     * is a {@link XYPlot} the overlay is also drawn.
     */
    private synchronized void updateChartPanel(final JFreeChart chart) {
        Runnable updateChartPanelRunnable = new Runnable() {

            @Override
            public void run() {
                if (chart != chartPanel.getChart()) {
                    if (chart == null) {
                        chartPanel.setChart(new JFreeChart(new CategoryPlot()));

                        fireChartChanged(new JFreeChart(new CategoryPlot()));
                    } else {
                        RenderingHints renderingHints = chart.getRenderingHints();

                        // enable antialiasing
                        renderingHints.add(new RenderingHints(RenderingHints.KEY_ANTIALIASING,
                                RenderingHints.VALUE_ANTIALIAS_ON));

                        // disable normalization (normalization tries to draw the center of strokes
                        // at whole pixels, which causes e.g.
                        // scaled shapes to appear more like potatoes than like circles)
                        renderingHints.add(new RenderingHints(RenderingHints.KEY_STROKE_CONTROL,
                                RenderingHints.VALUE_STROKE_PURE));
                        chart.setRenderingHints(renderingHints);

                        chartPanel.setChart(chart);
                        fireChartChanged(chart);
                    }
                }
                if (chart != null) {
                    chartPanel.removeOverlay(crosshairOverlay);
                    crosshairOverlay = new MultiAxesCrosshairOverlay();

                    if (chart.getPlot() instanceof XYPlot) {
                        // add overlays for range axes
                        int axisIdx = 0;
                        for (RangeAxisConfig rangeAxisConfig : plotInstance.getCurrentPlotConfigurationClone()
                                .getRangeAxisConfigs()) {
                            for (AxisParallelLineConfiguration line : rangeAxisConfig.getCrossHairLines()
                                    .getLines()) {
                                Crosshair crosshair = new Crosshair(line.getValue(), line.getFormat().getColor(),
                                        line.getFormat().getStroke());
                                crosshairOverlay.addRangeCrosshair(axisIdx, crosshair);
                            }
                            ++axisIdx;
                        }

                        // add overlays for domain axis
                        for (AxisParallelLineConfiguration line : plotInstance.getCurrentPlotConfigurationClone()
                                .getDomainConfigManager().getCrosshairLines().getLines()) {
                            Crosshair crosshair = new Crosshair(line.getValue(), line.getFormat().getColor(),
                                    line.getFormat().getStroke());
                            crosshairOverlay.addDomainCrosshair(crosshair);
                        }
                        chartPanel.addOverlay(crosshairOverlay);
                    }
                }
            }
        };

        if (SwingUtilities.isEventDispatchThread()) {
            updateChartPanelRunnable.run();
        } else {
            SwingUtilities.invokeLater(updateChartPanelRunnable);
        }

    }

    public void endInitializing() {
        if (initializing) {
            initializing = false;
            plotInstance.triggerReplot();
        }
    }

    public void startInitializing() {
        initializing = true;
    }

    /**
     * Creates a new {@link JFreeChart} using the plotConfiguration of this Plotter2D.
     */
    private JFreeChart createChart() throws ChartPlottimeException {
        Plot plot = createPlot();

        if (plot == null) {
            throw new ChartPlottimeException("The plot created was a NULL plot.");
        }

        JFreeChart chart = new JFreeChart(plot);
        formatChart(chart);
        return chart;
    }

    /**
     * Sets all the format options on the given chart like fonts, chart title, legend, legend
     * position etc.
     */
    private void formatChart(JFreeChart chart) {
        Plot plot = chart.getPlot();
        PlotConfiguration currentPlotConfigurationClone = plotInstance.getCurrentPlotConfigurationClone();

        // set plot background color
        plot.setBackgroundPaint(currentPlotConfigurationClone.getPlotBackgroundColor());

        formatLegend(chart);

        // set chart background color
        chart.setBackgroundPaint(currentPlotConfigurationClone.getChartBackgroundColor());

        // add title to chart
        String text = currentPlotConfigurationClone.getTitleText();
        if (text == null) {
            chart.setTitle(text);
        } else {
            Font font = currentPlotConfigurationClone.getTitleFont();
            if (font == null) {
                font = new Font("Dialog", Font.PLAIN, 10);
            }

            TextTitle textTitle = new TextTitle(text, font);
            textTitle.setPaint(currentPlotConfigurationClone.getTitleColor());

            chart.setTitle(textTitle);
        }
    }

    /**
     * @param chart
     */
    private void formatLegend(JFreeChart chart) {
        List<LegendTitle> legendTitles = createLegendTitles();

        while (chart.getLegend() != null) {
            chart.removeLegend();
        }
        for (LegendTitle legendTitle : legendTitles) {
            chart.addLegend(legendTitle);
        }

        // set legend font
        Font legendFont = plotInstance.getCurrentPlotConfigurationClone().getLegendConfiguration().getLegendFont();
        if (legendFont != null) {
            for (LegendTitle legendTitle : legendTitles) {
                legendTitle.setItemFont(legendFont);
            }
        }
    }

    private void invalidateCache() {
        cachedLegendItems = null;
    }

    /**
     * Is called to clear the {@link MasterOfDesaster}, invalidate the {@link JFreeChartPlotEngine}
     * cache and update the {@link ChartPanel}s chart. This should only be called if a
     * {@link PlotConfigurationChangeEvent} is processed. If initializing it returns
     * <code>true</code>, <code>false</code> otherwise.
     */
    public boolean replot() {
        if (initializing) {
            return true;
        }
        plotInstance.getMasterOfDesaster().clearAll();
        invalidateCache();

        updateChartPanelChart(true);
        return false;
    }

    private boolean isPlotInstanceValid() {
        plotInstance.getMasterOfDesaster().clearAll();
        if (!plotInstance.isValid()) {
            StaticDebug.debug("######## CAUTION: INVALID PLOT INSTANCE!!");

            if (!plotInstance.getCurrentPlotConfigurationClone().isValid()) {
                ConfigurationChangeResponse response = new ConfigurationChangeResponse();
                for (PlotConfigurationError error : plotInstance.getCurrentPlotConfigurationClone().getErrors()) {
                    response.addError(error);
                }
                plotInstance.getMasterOfDesaster().registerConfigurationChangeResponse(response);
                return false;
            } else {
                ConfigurationChangeResponse response = new ConfigurationChangeResponse();
                for (PlotConfigurationError error : plotInstance.getPlotData().getErrors()) {
                    response.addError(error);
                }
                plotInstance.getMasterOfDesaster().registerConfigurationChangeResponse(response);
                return false;
            }
        }
        return true;
    }

    private void setChartTitle() {
        JFreeChart chart = getCurrentChart();
        if (chart != null) {
            String text = plotInstance.getCurrentPlotConfigurationClone().getTitleText();
            if (text == null) {
                chart.setTitle(text);
                return;
            }

            Font font = plotInstance.getCurrentPlotConfigurationClone().getTitleFont();
            if (font == null) {
                font = new Font("Dialog", Font.PLAIN, 10);
            }

            TextTitle textTitle = new TextTitle(text, font);
            textTitle.setPaint(plotInstance.getCurrentPlotConfigurationClone().getTitleColor());

            chart.setTitle(textTitle);

        }

    }

    private void setDomainAxisLabel(String name) {
        JFreeChart chart = getCurrentChart();
        if (chart != null) {
            Plot plot = chart.getPlot();
            if (plot instanceof CategoryPlot) {
                CategoryPlot categoryPlot = (CategoryPlot) plot;
                CategoryAxis domainAxis = categoryPlot.getDomainAxis();
                if (domainAxis != null) {
                    domainAxis.setLabel(name);
                }
            } else {
                XYPlot xyPlot = (XYPlot) plot;
                ValueAxis domainAxis = xyPlot.getDomainAxis();
                if (domainAxis != null) {
                    domainAxis.setLabel(name);
                }
            }
        }
    }

    private void setDomainAxisDateFormat(DateFormat dateFormat) {
        checkWarnings();
        JFreeChart chart = getCurrentChart();
        if (chart != null) {
            Plot plot = chart.getPlot();
            if (plot instanceof CategoryPlot) {
                CategoryPlot categoryPlot = (CategoryPlot) plot;
                CategoryAxis domainAxis = categoryPlot.getDomainAxis();
                if (domainAxis != null) {

                }
            } else {
                XYPlot xyPlot = (XYPlot) plot;
                ValueAxis domainAxis = xyPlot.getDomainAxis();
                if (domainAxis != null && domainAxis instanceof DateAxis) {
                    DateAxis dateAxis = (DateAxis) domainAxis;
                    if (getPlotInstance().getCurrentPlotConfigurationClone().getDomainConfigManager()
                            .isUsingUserDefinedDateFormat()) {
                        dateAxis.setDateFormatOverride(dateFormat);
                        dateAxis.setTimeZone(TimeZone.getTimeZone("GMT"));
                    } else {
                        dateAxis.setDateFormatOverride(null);
                    }
                }
            }
        }
    }

    private void setRangeAxisLabel(String name, RangeAxisConfig source) {
        JFreeChart chart = getCurrentChart();
        if (chart != null) {
            Plot plot = chart.getPlot();
            int rangeAxisIdx = plotInstance.getCurrentPlotConfigurationClone()
                    .getIndexOfRangeAxisConfigById(source.getId());
            if (rangeAxisIdx == -1) {
                return;
            }

            if (plot instanceof CategoryPlot) {
                CategoryPlot categoryPlot = (CategoryPlot) plot;
                ValueAxis valueAxis = categoryPlot.getRangeAxis(rangeAxisIdx);
                if (valueAxis != null) {
                    valueAxis.setLabel(name);
                }
            } else {
                XYPlot xyPlot = (XYPlot) plot;
                ValueAxis valueAxis = xyPlot.getRangeAxis(rangeAxisIdx);
                if (valueAxis != null) {
                    valueAxis.setLabel(name);
                }
            }
        }
    }

    private void setPlotBackgroundColor(Color backgroundColor) {
        JFreeChart chart = getCurrentChart();
        if (chart != null) {
            Plot plot = chart.getPlot();
            if (plot instanceof CategoryPlot) {
                CategoryPlot categoryPlot = (CategoryPlot) plot;
                categoryPlot.setBackgroundPaint(backgroundColor);
            } else {
                XYPlot xyPlot = (XYPlot) plot;
                xyPlot.setBackgroundPaint(backgroundColor);
            }
        }
    }

    private void setChartBackgroundColor(Color backgroundColor) {
        JFreeChart chart = getCurrentChart();
        if (chart != null) {
            chart.setBackgroundPaint(backgroundColor);
        }
    }

    private void setAxesFont(Font axesFont) {
        JFreeChart chart = getCurrentChart();
        if (chart != null) {
            Plot plot = chart.getPlot();

            if (plot instanceof CategoryPlot) {
                CategoryPlot categoryPlot = (CategoryPlot) plot;

                // first change range axes font
                int rangeAxisCount = categoryPlot.getRangeAxisCount();
                for (int i = 0; i < rangeAxisCount; ++i) {
                    ValueAxis valueAxis = categoryPlot.getRangeAxis(i);
                    if (valueAxis != null) {
                        valueAxis.setLabelFont(axesFont);
                        valueAxis.setTickLabelFont(axesFont);
                    }
                }

                // then set domain axis font
                CategoryAxis domainAxis = categoryPlot.getDomainAxis();
                if (domainAxis != null) {
                    domainAxis.setLabelFont(axesFont);
                    domainAxis.setTickLabelFont(axesFont);
                }

            } else {
                XYPlot xyPlot = (XYPlot) plot;

                // first change range axes font
                int rangeAxisCount = xyPlot.getRangeAxisCount();
                for (int i = 0; i < rangeAxisCount; ++i) {
                    ValueAxis rangeAxis = xyPlot.getRangeAxis(i);
                    if (rangeAxis != null) {
                        rangeAxis.setLabelFont(axesFont);
                        rangeAxis.setTickLabelFont(axesFont);
                    }
                }

                // then set domain axis font
                ValueAxis domainAxis = xyPlot.getDomainAxis();
                if (domainAxis != null) {
                    domainAxis.setLabelFont(axesFont);
                    domainAxis.setTickLabelFont(axesFont);
                }

            }
        }
    }

    /**
     * Creates {@link LegendTitle}s for all dimensions from the PlotConfiguration of this Plotter2D.
     * Expects that all {@link ValueSource} s in the provided PlotConfiguration use the same
     * {@link DimensionConfig} s.
     */
    private List<LegendTitle> createLegendTitles() {
        List<LegendTitle> legendTitles = new LinkedList<LegendTitle>();
        LegendConfiguration legendConfiguration = plotInstance.getCurrentPlotConfigurationClone()
                .getLegendConfiguration();

        LegendTitle legendTitle = new SmartLegendTitle(this,
                new FlowArrangement(HorizontalAlignment.CENTER, VerticalAlignment.CENTER, 30, 2),
                new ColumnArrangement(HorizontalAlignment.LEFT, VerticalAlignment.CENTER, 0, 2));
        legendTitle.setItemPaint(legendConfiguration.getLegendFontColor());

        RectangleEdge position = legendConfiguration.getLegendPosition().getPosition();
        if (position == null) {
            return legendTitles;
        }
        legendTitle.setPosition(position);

        if (legendConfiguration.isShowLegendFrame()) {
            legendTitle.setFrame(new BlockBorder(legendConfiguration.getLegendFrameColor()));
        }
        ColoredBlockContainer wrapper = new ColoredBlockContainer(legendConfiguration.getLegendBackgroundColor());
        wrapper.add(legendTitle.getItemContainer());
        wrapper.setPadding(3, 3, 3, 3);
        legendTitle.setWrapper(wrapper);

        legendTitles.add(legendTitle);
        return legendTitles;
    }

    /**
     * Creates a new JFreeChart {@link Plot} from {@link PlotConfiguration} of this Plotter2D.
     */
    private Plot createPlot() throws ChartPlottimeException {

        PlotConfiguration currentPlotConfigurationClone = plotInstance.getCurrentPlotConfigurationClone();
        List<RangeAxisConfig> rangeAxes = currentPlotConfigurationClone.getRangeAxisConfigs();
        Plot plot = null;

        // Select item placement on domain axis
        ValueType domainAxisType = currentPlotConfigurationClone.getDomainConfigManager().getValueType();

        // create plot and domain axis
        if (domainAxisType == ValueType.NOMINAL) {
            CategoryPlot categoryPlot = new LinkAndBrushCategoryPlot();
            categoryPlot.setDomainAxis(ChartAxisFactory.createCategoryDomainAxis(currentPlotConfigurationClone));
            categoryPlot.setOrientation(currentPlotConfigurationClone.getOrientation());
            plot = categoryPlot;
        } else if (domainAxisType == ValueType.NUMERICAL) {
            LinkAndBrushXYPlot xyPlot = new LinkAndBrushXYPlot();
            xyPlot.setDomainAxis(ChartAxisFactory.createNumericalDomainAxis(plotInstance));
            xyPlot.setOrientation(currentPlotConfigurationClone.getOrientation());
            plot = xyPlot;
        } else if (domainAxisType == ValueType.DATE_TIME) {
            XYPlot xyPlot = new LinkAndBrushXYPlot();
            xyPlot.setDomainAxis(ChartAxisFactory.createDateDomainAxis(plotInstance));
            xyPlot.setOrientation(currentPlotConfigurationClone.getOrientation());
            plot = xyPlot;
        } else if (domainAxisType == ValueType.INVALID) {
            throw new ChartPlottimeException("illegal_domain_type", domainAxisType);
        } else if (domainAxisType == ValueType.UNKNOWN
                && currentPlotConfigurationClone.getAllValueSources().isEmpty()) {
            throw new ChartPlottimeException("no_value_source_defined");
        } else if (domainAxisType == ValueType.UNKNOWN) {
            throw new ChartPlottimeException("unknown_dimension_value_type");
        } else {
            throw new RuntimeException(
                    "Item placement is neither categorical, date or numerical. This cannot happen.");
        }

        int rangeAxisIdx = 0;
        for (RangeAxisConfig rangeAxisConfig : rangeAxes) {

            List<ValueSource> valueSources = rangeAxisConfig.getValueSources();

            for (ValueSource valueSource : valueSources) {

                // prepare call to recursivelyGetSeries
                Vector<PlotDimension> dimensionVector = new Vector<PlotDimension>();
                dimensionVector.addAll(currentPlotConfigurationClone.getDefaultDimensionConfigs().keySet());

                // create plot
                if (domainAxisType == ValueType.NOMINAL) {

                    if (!valueSource.isUsingRelativeIndicator()) {
                        throw new ChartPlottimeException("absolute_indicator_but_nominal_domain",
                                valueSource.getLabel());
                    }

                    CategoryPlot categoryPlot = (CategoryPlot) plot;
                    addDataAndRendererToCategoryPlot(valueSource, categoryPlot, rangeAxisIdx);
                } else if (domainAxisType == ValueType.NUMERICAL) {
                    XYPlot xyPlot = (XYPlot) plot;
                    addDataAndRendererToXYPlot(valueSource, xyPlot, rangeAxisIdx);
                } else if (domainAxisType == ValueType.DATE_TIME) {
                    XYPlot xyPlot = (XYPlot) plot;
                    addDataAndRendererToXYPlot(valueSource, xyPlot, rangeAxisIdx);
                } else {
                    throw new RuntimeException(
                            "Item placement is neither categorical nor numerical. This cannot happen.");
                }
            }

            // set range axis
            ValueAxis rangeAxis = ChartAxisFactory.createRangeAxis(rangeAxisConfig, plotInstance);
            if (rangeAxis != null) {
                if (domainAxisType == ValueType.NUMERICAL || domainAxisType == ValueType.DATE_TIME) {
                    try {
                        XYPlot xyPlot = (XYPlot) plot;
                        xyPlot.setRangeAxis(rangeAxisIdx, rangeAxis);
                    } catch (RuntimeException e) {
                        // probably this is because the domain axis contains
                        // values less then zero and the scaling is logarithmic.
                        // The shitty JFreeChart implementation does not throw a
                        // proper exception stating what happened,
                        // but just a RuntimeException with a string, so this is
                        // our best guess:
                        if (isProbablyZeroValuesOnLogScaleException(e)) {
                            String label = rangeAxisConfig.getLabel();
                            if (label == null) {
                                label = I18N.getGUILabel("plotter.unnamed_value_label");
                            }
                            throw new ChartPlottimeException("gui.plotter.error.log_axis_contains_zero", label);
                        } else {
                            throw e;
                        }
                    }
                } else if (domainAxisType == ValueType.NOMINAL) {
                    // TODO ensure that all values sources have the same range
                    CategoryPlot categoryPlot = (CategoryPlot) plot;
                    categoryPlot.setRangeAxis(rangeAxisIdx, rangeAxis);
                } else {
                    throw new RuntimeException(
                            "illegal value type on domain axis - this should not happen because of the checks above.");
                }
            }

            ++rangeAxisIdx;
        }

        return plot;
    }

    /**
     * 
     */
    private void checkWarnings() {
        plotInstance.getMasterOfDesaster().clearWarnings();
        if (plotInstance.hasWarnings()) {
            ConfigurationChangeResponse warningsResponse = new ConfigurationChangeResponse();
            for (PlotConfigurationError warning : plotInstance.getWarnings()) {
                warningsResponse.addWarning(warning);
            }
            plotInstance.getMasterOfDesaster().registerConfigurationChangeResponse(warningsResponse);
        }
        if (!this.getEngineWarnings().isEmpty()) {
            ConfigurationChangeResponse warningsResponse = new ConfigurationChangeResponse();
            for (PlotConfigurationError warning : this.getEngineWarnings()) {
                warningsResponse.addWarning(warning);
            }
            plotInstance.getMasterOfDesaster().registerConfigurationChangeResponse(warningsResponse);
        }
    }

    /**
     * Creates an appropriate JFreeChart {@link Dataset} for valueSource and adds it to plot.
     * 
     * @param rangeAxisIdx
     *            The index of the range axis in the {@link XYPlot}
     * 
     * @return The index of the newly added dataset in the plot.
     */
    private void addDataAndRendererToXYPlot(ValueSource valueSource, XYPlot plot, int rangeAxisIdx)
            throws ChartPlottimeException {
        VisualizationType seriesType = valueSource.getSeriesFormat().getSeriesType();
        StackingMode stackingMode = valueSource.getSeriesFormat().getStackingMode();
        IndicatorType errorIndicator = valueSource.getSeriesFormat().getUtilityUsage();

        XYItemRenderer renderer;
        XYDataset dataset;

        DefaultDimensionConfig domainConfig = valueSource.getDomainConfig();
        DimensionConfigData domainConfigData = plotInstance.getPlotData().getDimensionConfigData(domainConfig);
        if (seriesType == VisualizationType.LINES_AND_SHAPES) {
            // stacking is ignored

            // grouping is irrelevant

            if (errorIndicator == IndicatorType.DIFFERENCE) {
                XYItemRenderer[] renderers = ChartRendererFactory.createXYDifferenceRenderers(valueSource,
                        plotInstance);

                if (domainConfigData.hasDuplicateValues()) {
                    throwDuplicateValuesNotSupported(valueSource, PlotDimension.DOMAIN);
                }

                for (int seriesIdx = 0; seriesIdx < renderers.length; ++seriesIdx) {
                    dataset = ChartDatasetFactory.createDefaultXYDataset(valueSource, seriesIdx, plotInstance);
                    renderer = renderers[seriesIdx];
                    pushDataAndRendererIntoPlot(plot, rangeAxisIdx, renderer, dataset);
                }
                return;
            } else if (errorIndicator == IndicatorType.BARS) {
                dataset = ChartDatasetFactory.createDefaultIntervalXYDataset(valueSource, plotInstance, true);
                renderer = ChartRendererFactory.createXYErrorRenderer(valueSource, plotInstance, dataset);
            } else if (errorIndicator == IndicatorType.BAND) {
                dataset = ChartDatasetFactory.createDefaultIntervalXYDataset(valueSource, plotInstance, true);
                renderer = ChartRendererFactory.createDeviationRenderer(valueSource, plotInstance);
            } else if (errorIndicator == IndicatorType.NONE) {
                dataset = ChartDatasetFactory.createDefaultXYDataset(valueSource, plotInstance);
                renderer = ChartRendererFactory.createXYLineAndShapeRenderer(valueSource, plotInstance);
            } else {
                // unknown error indicator - this should not happen
                throw new IllegalArgumentException("unknown error indicator");
            }
        } else if (seriesType == VisualizationType.BARS) {
            // grouping is irrelevant

            if (errorIndicator != IndicatorType.NONE) {
                // not supported
                PlotConfigurationError error = new PlotConfigurationError("error_indicator_not_supported",
                        valueSource.toString(), errorIndicator.getName());
                SeriesFormatChangeEvent change;
                // suggest to remove error indicators
                change = new SeriesFormatChangeEvent(valueSource.getSeriesFormat(), IndicatorType.NONE);
                error.addQuickFix(new PlotConfigurationQuickFix(change));
                // suggest to switch to lines and shapes
                change = new SeriesFormatChangeEvent(valueSource.getSeriesFormat(),
                        VisualizationType.LINES_AND_SHAPES);
                error.addQuickFix(new PlotConfigurationQuickFix(change));
                throw new ChartPlottimeException(error);
            } else {
                if (stackingMode == StackingMode.NONE) {
                    if (!valueSource.isUsingDomainGrouping()
                            || !domainConfig.getGrouping().definesUpperLowerBounds()) {
                        dataset = ChartDatasetFactory.createXYSeriesCollection(valueSource, plotInstance, 0.95,
                                false, true);
                    } else {
                        dataset = ChartDatasetFactory.createDefaultIntervalXYDataset(valueSource, plotInstance,
                                false);
                    }
                    renderer = ChartRendererFactory.createClusteredXYBarRenderer(valueSource, plotInstance);
                } else if (stackingMode == StackingMode.ABSOLUTE) {
                    dataset = ChartDatasetFactory.createDefaultTableXYDataset(valueSource, plotInstance);
                    renderer = ChartRendererFactory.createStackedXYBarRenderer(valueSource, plotInstance, false);
                } else if (stackingMode == StackingMode.RELATIVE) {
                    // not supported
                    dataset = ChartDatasetFactory.createDefaultTableXYDataset(valueSource, plotInstance);
                    renderer = ChartRendererFactory.createStackedXYBarRenderer(valueSource, plotInstance, true);
                } else {
                    // error - this should not happen
                    throw new IllegalArgumentException("unknown stacking mode");
                }
            }
        } else if (seriesType == VisualizationType.AREA) {
            // grouping is irrelevant
            if (errorIndicator != IndicatorType.NONE) {
                // not supported
                PlotConfigurationError error = new PlotConfigurationError("error_indicator_not_supported",
                        valueSource.toString(), errorIndicator.getName());
                SeriesFormatChangeEvent change;
                // suggest to remove error indicators
                change = new SeriesFormatChangeEvent(valueSource.getSeriesFormat(), IndicatorType.NONE);
                error.addQuickFix(new PlotConfigurationQuickFix(change));
                // suggest to switch to lines and shapes
                change = new SeriesFormatChangeEvent(valueSource.getSeriesFormat(),
                        VisualizationType.LINES_AND_SHAPES);
                error.addQuickFix(new PlotConfigurationQuickFix(change));
                throw new ChartPlottimeException(error);
            } else {
                if (stackingMode == StackingMode.NONE) {
                    dataset = ChartDatasetFactory.createXYSeriesCollection(valueSource, plotInstance, 0, false,
                            true);
                    renderer = ChartRendererFactory.createXYAreaRenderer2(valueSource, plotInstance);
                } else if (stackingMode == StackingMode.ABSOLUTE) {
                    dataset = ChartDatasetFactory.createDefaultTableXYDataset(valueSource, plotInstance);
                    renderer = ChartRendererFactory.createStackedXYAreaRenderer2(valueSource, plotInstance, false);
                } else if (stackingMode == StackingMode.RELATIVE) {
                    // not supported
                    PlotConfigurationError error = new PlotConfigurationError("stacking_mode_not_supported",
                            valueSource.toString(), stackingMode.getName());
                    SeriesFormatChangeEvent change;
                    // suggest to change to absolute stacking
                    change = new SeriesFormatChangeEvent(valueSource.getSeriesFormat(), StackingMode.ABSOLUTE);
                    error.addQuickFix(new PlotConfigurationQuickFix(change));
                    throw new ChartPlottimeException(error);

                } else {
                    // error - this should not happen
                    throw new IllegalArgumentException("unknown stacking mode: " + stackingMode);
                }
            }
        } else {
            // error - this should not happen
            throw new IllegalArgumentException("unknown series type: " + seriesType);
        }

        pushDataAndRendererIntoPlot(plot, rangeAxisIdx, renderer, dataset);
    }

    private void pushDataAndRendererIntoPlot(XYPlot plot, int rangeAxisIdx, XYItemRenderer renderer,
            XYDataset dataset) throws ChartPlottimeException {
        if (dataset != null && renderer != null) {
            int datasetIdx = plot.getDatasetCount();
            if (datasetIdx > 0 && plot.getDataset(datasetIdx - 1) == null) {
                datasetIdx -= 1;
            }
            // push dataset and renderer into plot
            try {
                plot.setDataset(datasetIdx, dataset); // if Eclipse states that
                // dataset might not be
                // initialized, you did
                // not consider all
                // possibilities in the
                // condition block above
            } catch (RuntimeException e) {
                // probably this is because the domain axis contains values less
                // then zero and the scaling is logarithmic.
                // The shitty JFreeChart implementation does not throw a proper
                // exception stating what happened,
                // but just a RuntimeException with a string, so this is our
                // best guess:
                if (isProbablyZeroValuesOnLogScaleException(e)) {
                    throw new ChartPlottimeException("gui.plotter.error.log_axis_contains_zero", "domain axis");
                } else {
                    throw e;
                }
            }
            plot.mapDatasetToRangeAxis(datasetIdx, rangeAxisIdx);
            plot.setRenderer(datasetIdx, renderer);
        } else {
            ChartPlottimeException chartPlottimeException = new ChartPlottimeException(
                    new PlotConfigurationError("generic_plotter_error"));
            throw chartPlottimeException;
        }
    }

    /**
     * 
     * @param dataForAllGroupCells
     * @param rangeAxisIdx
     */
    private void addDataAndRendererToCategoryPlot(ValueSource valueSource, CategoryPlot plot, int rangeAxisIdx)
            throws ChartPlottimeException {
        VisualizationType seriesType = valueSource.getSeriesFormat().getSeriesType();
        StackingMode stackingMode = valueSource.getSeriesFormat().getStackingMode();
        IndicatorType errorIndicator = valueSource.getSeriesFormat().getUtilityUsage();
        DefaultDimensionConfig domainConfig = valueSource.getDomainConfig();
        DimensionConfigData domainConfigData = plotInstance.getPlotData().getDimensionConfigData(domainConfig);
        ValueSourceData valueSourceData = plotInstance.getPlotData().getValueSourceData(valueSource);

        CategoryItemRenderer renderer;
        CategoryDataset dataset;

        if (seriesType == VisualizationType.LINES_AND_SHAPES) {
            // stacking is ignored

            if (errorIndicator == IndicatorType.DIFFERENCE || errorIndicator == IndicatorType.BAND) {
                // not supported
                throwErrorIndicatorNotSupported(valueSource, errorIndicator);
                return;
            } else if (errorIndicator == IndicatorType.BARS) {
                if (domainConfigData.hasDuplicateValues()) {
                    throwDuplicateValuesNotSupported(valueSource, PlotDimension.DOMAIN);
                    return;
                }
                dataset = ChartDatasetFactory.createDefaultStatisticalCategoryDataset(valueSource, plotInstance);
                renderer = ChartRendererFactory.createStatisticalLineAndShapeRenderer(valueSource, plotInstance);
            } else if (errorIndicator == IndicatorType.NONE) {
                if (valueSource.isUsingDomainGrouping()) {
                    dataset = ChartDatasetFactory.createDefaultCategoryDataset(valueSource, plotInstance, false,
                            true);
                    renderer = ChartRendererFactory.createLineAndShapeRenderer(valueSource, plotInstance);
                } else {
                    dataset = new ValueSourceToMultiValueCategoryDatasetAdapter(valueSourceData, plotInstance);
                    renderer = ChartRendererFactory.createScatterRenderer(valueSource, plotInstance);
                }
            } else {
                // unknown error indicator - this should not happen
                throw new IllegalArgumentException("unknown error indicator: " + errorIndicator);
            }
        } else if (seriesType == VisualizationType.BARS) {
            // grouping is irrelevant

            // bars don't support duplicate values on domain dimension:
            if (domainConfigData.hasDuplicateValues()) {
                throwDuplicateValuesNotSupported(valueSource, PlotDimension.DOMAIN);
            }

            // don't support other error indicators than bars for unstacked bar
            // charts, and none at all for stacked bar charts
            if ((errorIndicator != IndicatorType.NONE && errorIndicator != IndicatorType.BARS)
                    || (errorIndicator != IndicatorType.NONE && stackingMode != StackingMode.NONE)) {
                throwErrorIndicatorNotSupported(valueSource, errorIndicator);
                return;
            } else {
                if (stackingMode == StackingMode.NONE) {
                    if (errorIndicator == IndicatorType.BARS) {
                        dataset = ChartDatasetFactory.createDefaultStatisticalCategoryDataset(valueSource,
                                plotInstance);
                        renderer = ChartRendererFactory.createStatisticalBarRenderer(valueSource, plotInstance);
                    } else { // if (errorIndicator == ErrorIndicator.NONE) { //
                             // no if needed, because we made sure above that
                             // errorIndicator is one of NONE or BARS
                        dataset = ChartDatasetFactory.createDefaultCategoryDataset(valueSource, plotInstance, false,
                                true);
                        renderer = ChartRendererFactory.createBarRenderer(valueSource, plotInstance);
                    }
                } else if (stackingMode == StackingMode.ABSOLUTE) {
                    dataset = ChartDatasetFactory.createDefaultCategoryDataset(valueSource, plotInstance, true,
                            false);
                    renderer = ChartRendererFactory.createStackedBarRenderer(valueSource, plotInstance, false);
                } else if (stackingMode == StackingMode.RELATIVE) {
                    dataset = ChartDatasetFactory.createDefaultCategoryDataset(valueSource, plotInstance, true,
                            false);
                    renderer = ChartRendererFactory.createStackedBarRenderer(valueSource, plotInstance, true);
                } else {
                    // error - this should not happen
                    throw new IllegalArgumentException("unknown stacking mode");
                }
            }
        } else if (seriesType == VisualizationType.AREA) {
            // areas don't support duplicate values on domain dimension:
            if (domainConfigData.hasDuplicateValues()) {
                throwDuplicateValuesNotSupported(valueSource, PlotDimension.DOMAIN);
            }

            // grouping is irrelevant
            if (errorIndicator != IndicatorType.NONE) {
                throwErrorIndicatorNotSupported(valueSource, errorIndicator);
                return;
            } else {
                if (stackingMode == StackingMode.NONE) {
                    dataset = ChartDatasetFactory.createDefaultCategoryDataset(valueSource, plotInstance, false,
                            true);
                    renderer = ChartRendererFactory.createAreaRenderer(valueSource, plotInstance);
                } else if (stackingMode == StackingMode.ABSOLUTE) {
                    dataset = ChartDatasetFactory.createDefaultCategoryDataset(valueSource, plotInstance, true,
                            false);
                    renderer = ChartRendererFactory.createStackedAreaRenderer(valueSource, plotInstance, false);
                } else if (stackingMode == StackingMode.RELATIVE) {
                    dataset = ChartDatasetFactory.createDefaultCategoryDataset(valueSource, plotInstance, true,
                            false);
                    renderer = ChartRendererFactory.createStackedAreaRenderer(valueSource, plotInstance, true);
                } else {
                    // error - this should not happen
                    throw new IllegalArgumentException("unknown stacking mode: " + stackingMode);
                }
            }
        } else {
            // error - this should not happen
            throw new IllegalArgumentException("unknown series type: " + seriesType);
        }

        if (dataset != null && renderer != null) {
            int datasetIdx = plot.getDatasetCount();
            if (datasetIdx > 0 && plot.getDataset(datasetIdx - 1) == null) {
                datasetIdx -= 1;
            }
            // push dataset and renderer into plot
            try {
                plot.setDataset(datasetIdx, dataset); // if Eclipse states that
                // dataset might not be
                // initialized, you did
                // not consider all
                // possibilities in the
                // condition block above
            } catch (RuntimeException e) {
                // probably this is because the domain axis contains values less
                // then zero and the scaling is logarithmic.
                // The shitty JFreeChart implementation does not throw a proper
                // exception stating what happened,
                // but just a RuntimeException with a string, so this is our
                // best guess:
                if (isProbablyZeroValuesOnLogScaleException(e)) {
                    throw new ChartPlottimeException("gui.plotter.error.log_axis_contains_zero", "domain axis");
                } else {
                    throw e;
                }
            }
            plot.mapDatasetToRangeAxis(datasetIdx, rangeAxisIdx);
            plot.setRenderer(datasetIdx, renderer);
        } else {
            ChartPlottimeException chartPlottimeException = new ChartPlottimeException(
                    new PlotConfigurationError("generic_plotter_error"));
            throw chartPlottimeException;
        }

        // int datasetIdx = plot.getDatasetCount();
        // // by default each CategoryPlot contains a null set. This one will be
        // overwritten by the following lines.
        // if (plot.getDataset(datasetIdx-1) == null) {
        // datasetIdx -= 1;
        // }
        //
        // CategoryDataset categoryDataset;
        //
        // categoryDataset =
        // ChartDatasetFactory.createDefaultCategoryDataset(valueSource,
        // plotConfiguration);
        // plot.setDataset(datasetIdx, categoryDataset);
        // plot.mapDatasetToRangeAxis(datasetIdx, rangeAxisIdx);
        // return datasetIdx;
    }

    private void throwDuplicateValuesNotSupported(ValueSource valueSource, PlotDimension dimension)
            throws ChartPlottimeException {
        throw new ChartPlottimeException("duplicate_value", valueSource.toString(), dimension.getName());
    }

    private void throwErrorIndicatorNotSupported(ValueSource valueSource, IndicatorType errorIndicator)
            throws ChartPlottimeException {
        PlotConfigurationError error = new PlotConfigurationError("error_indicator_not_supported",
                valueSource.toString(), errorIndicator.getName());
        SeriesFormatChangeEvent change;

        // suggest to remove error indicators
        change = new SeriesFormatChangeEvent(valueSource.getSeriesFormat(), IndicatorType.NONE);
        error.addQuickFix(new PlotConfigurationQuickFix(change));
        throw new ChartPlottimeException(error);
    }

    private void chartTitleChanged() {
        setChartTitle();
    }

    private void rangeAxisConfigAxisChanged(RangeAxisConfig rangeAxisConfig) {
        if (!isPlotInstanceValid()) {
            updateChartPanel(new JFreeChart(new CategoryPlot()));
            currentChartIsValid = false;
            return;
        }

        int axisIdx = plotInstance.getCurrentPlotConfigurationClone()
                .getIndexOfRangeAxisConfigById(rangeAxisConfig.getId());

        if (axisIdx == -1) {
            return;
        }

        if (currentChartIsValid) {

            try {
                ValueAxis updatedAxis = ChartAxisFactory.createRangeAxis(rangeAxisConfig, plotInstance);
                if (updatedAxis != null) {
                    JFreeChart chart = getCurrentChart();
                    if (chart != null) {
                        Plot plot = chart.getPlot();
                        if (plot instanceof XYPlot) {
                            ((XYPlot) plot).setRangeAxis(axisIdx, updatedAxis);
                        } else if (plot instanceof CategoryPlot) {
                            ((CategoryPlot) plot).setRangeAxis(axisIdx, updatedAxis);
                        }
                    }
                }

                checkWarnings();
            } catch (ChartPlottimeException e) {
                handlePlottimeException(e);
            } catch (RuntimeException e) {
                // probably this is because the domain axis contains values less
                // then zero and the scaling is logarithmic.
                // The shitty JFreeChart implementation does not throw a proper
                // exception stating what happened,
                // but just a RuntimeException with a string, so this is our best
                // guess:
                if (isProbablyZeroValuesOnLogScaleException(e)) {
                    String label = rangeAxisConfig.getLabel();
                    if (label == null) {
                        label = I18N.getGUILabel("plotter.unnamed_value_label");
                    }
                    handlePlottimeException(
                            new ChartPlottimeException("gui.plotter.error.log_axis_contains_zero", label));
                } else {
                    throw e;
                }
            }
        } else {
            updateChartPanelChart(true);
        }
    }

    /**
     * Guesses if the given exception is caused by trying to set negative values on a logarithmic
     * axis in JFreeChart. The shitty JFreeChart implementation does not throw a proper exception
     * stating what happened, but just a RuntimeException with a string, so this is our best guess:
     */
    private boolean isProbablyZeroValuesOnLogScaleException(RuntimeException e) {
        return "Values less than or equal to zero not allowed with logarithmic axis".equals(e.getMessage());
    }

    private void legendPositionChanged(LegendPosition legendPosition) {
        JFreeChart chart = getCurrentChart();
        if (chart != null) {
            LegendTitle legend = chart.getLegend();
            RectangleEdge position = legendPosition.getPosition();
            if (legend != null) {
                if (position != null) {
                    legend.setPosition(position);
                } else {
                    while (chart.getLegend() != null) {
                        chart.removeLegend();
                    }
                }
            } else {
                if (position != null) {
                    resetLegend();
                }
            }
        }
    }

    private void plotBackgroundColorChanged(Color backgroundColor) {
        setPlotBackgroundColor(backgroundColor);
    }

    private void axesFontChanged(Font axesFont) {
        setAxesFont(axesFont);
    }

    private void legendFontChanged(Font legendFont) {
        resetLegend();
    }

    private void chartBackgroundColorChanged(Color chartBackgroundColor) {
        setChartBackgroundColor(chartBackgroundColor);
    }

    /**
     * Sets the plot configuration and adapts all subscriptions of this Plotter2D to event
     * providers.
     * 
     * @param plotInstance
     *            The new PlotConfiguration. null not allowed.
     */
    public void setPlotInstance(PlotInstance plotInstance) {
        if (plotInstance == null) {
            throw new IllegalArgumentException("null PlotConfiguration not allowed");
        }

        synchronized (nextPlotInstanceLock) {
            if (updatingChart.get()) {
                if (plotInstance != this.plotInstance) {
                    StaticDebug.debug("Set NEW PLOTINSTANCE for PlotEnginge " + plotInstance);
                    this.nextPlotInstance = plotInstance;
                }
            } else {
                this.nextPlotInstance = plotInstance;
                privateSetPlotInstance();
            }
        }
    }

    private synchronized void updatingChartPanelChartDone() {
        StaticDebug.debug("Updating chart done!");
        updatingChart.getAndSet(false);
        synchronized (nextPlotInstanceLock) {
            if (nextPlotInstance != null) {
                privateSetPlotInstance();
            }
        }
    }

    private void privateSetPlotInstance() {
        StaticDebug.debug("PlotInstance has changed. Replacing..");
        unsubscribeFromPlotInstance(plotInstance);
        subscribeAtPlotInstance(nextPlotInstance);
        this.plotInstance = nextPlotInstance;
        this.nextPlotInstance = null;
        plotInstance.triggerReplot();
    }

    private void unsubscribeFromPlotInstance(PlotInstance plotInstance) {
        initializing = true;
        // unsubscribe from event sources
        PlotConfiguration masterPlotConfiguration = plotInstance.getMasterPlotConfiguration();
        masterPlotConfiguration.removePlotConfigurationListener(this);
        masterPlotConfiguration.removePlotConfigurationProcessingListener(this);
        endProcessing();
        chartPanel.removeLinkAndBrushSelectionListener(masterPlotConfiguration);
        initializing = false;
    }

    @Override
    public boolean plotConfigurationChanged(PlotConfigurationChangeEvent change) {
        PlotConfigurationChangeType type = change.getType();
        boolean processed;
        switch (type) {
        case DIMENSION_CONFIG_ADDED:
        case DIMENSION_CONFIG_REMOVED:
        case RANGE_AXIS_CONFIG_ADDED:
        case RANGE_AXIS_CONFIG_MOVED:
        case RANGE_AXIS_CONFIG_REMOVED:
        case COLOR_SCHEME:
        case DATA_TABLE_EXCHANGED:
        case TRIGGER_REPLOT:
        case META_CHANGE:
            processed = replot();
            break;
        case AXES_FONT:
            axesFontChanged(change.getAxesFont());
            processed = true;
            break;
        case FRAME_BACKGROUND_COLOR:
            chartBackgroundColorChanged(change.getFrameBackgroundColor());
            processed = true;
            break;
        case CHART_TITLE:
            chartTitleChanged();
            processed = true;
            break;
        case DIMENSION_CONFIG_CHANGED:
            processed = dimensionConfigChanged(change.getDimensionChange());
            break;
        case LEGEND_CHANGED:
            legendChanged(change.getLegendConfigurationChangeEvent());
            processed = true;
            break;
        case PLOT_BACKGROUND_COLOR:
            plotBackgroundColorChanged(change.getPlotBackgroundColor());
            processed = true;
            break;
        case RANGE_AXIS_CONFIG_CHANGED:
            processed = rangeAxisConfigChanged(change.getRangeAxisConfigChange());
            break;
        case PLOT_ORIENTATION:
            plotOrientationChanged(change.getOrientation());
            processed = true;
            break;
        case AXIS_LINE_COLOR:
            axisLineColorChanged(change.getDomainAxisLineColor());
            processed = true;
            break;
        case AXIS_LINE_WIDTH:
            axisLineWidthChanged(change.getDomainAxisLineWidth());
            processed = true;
            break;
        case LINK_AND_BRUSH_SELECTION:
            checkWarnings();
            processed = replot();
            break;
        default:
            // DONT FORGET TO RETURN TRUE OR FALSE
            throw new RuntimeException("Unknown event type " + type + ". This should not happen.");
        }
        return processed;
    }

    private void legendChanged(LegendConfigurationChangeEvent change) {
        switch (change.getType()) {
        case FONT:
            legendFontChanged(change.getLegendFont());
            break;
        case POSITON:
            legendPositionChanged(change.getLegendPosition());
            break;
        case SHOW_DIMENSION_TYPE:
            legendShowDimensionTypeChanged(change.isShowDimensionType());
            break;
        default:
            resetLegend();
        }
    }

    private void legendShowDimensionTypeChanged(boolean showDimensionType) {
        resetLegend();
    }

    private void resetLegend() {
        JFreeChart chart = getCurrentChart();
        if (chart != null) {
            updateLegendItems();
            formatLegend(chart);
            checkWarnings();
        }
    }

    private void axisLineColorChanged(Color lineColor) {
        JFreeChart chart = getCurrentChart();
        if (chart != null) {
            Plot plot = chart.getPlot();
            if (plot instanceof CategoryPlot) {
                CategoryPlot categoryPlot = (CategoryPlot) plot;
                ChartAxisFactory.formatAxis(plotInstance.getCurrentPlotConfigurationClone(),
                        categoryPlot.getDomainAxis());
                int rangeAxisCount = categoryPlot.getRangeAxisCount();
                for (int i = 0; i < rangeAxisCount; ++i) {
                    ValueAxis valueAxis = categoryPlot.getRangeAxis(i);
                    if (valueAxis != null) {
                        ChartAxisFactory.formatAxis(plotInstance.getCurrentPlotConfigurationClone(), valueAxis);
                    }
                }
            } else if (plot instanceof XYPlot) {
                XYPlot xyPlot = (XYPlot) plot;
                ChartAxisFactory.formatAxis(plotInstance.getCurrentPlotConfigurationClone(),
                        xyPlot.getDomainAxis());
                int rangeAxisCount = xyPlot.getRangeAxisCount();
                for (int i = 0; i < rangeAxisCount; ++i) {
                    ValueAxis valueAxis = xyPlot.getRangeAxis(i);
                    valueAxis.setAxisLinePaint(lineColor);
                    if (valueAxis != null) {
                        ChartAxisFactory.formatAxis(plotInstance.getCurrentPlotConfigurationClone(), valueAxis);
                    }
                }
            }
        }

    }

    private void axisLineWidthChanged(Float lineWidth) {
        JFreeChart chart = getCurrentChart();
        if (chart != null) {
            Plot plot = chart.getPlot();
            BasicStroke stroke = new BasicStroke(lineWidth);
            if (plot instanceof CategoryPlot) {
                CategoryPlot categoryPlot = (CategoryPlot) plot;
                CategoryAxis domainAxis = categoryPlot.getDomainAxis();
                if (domainAxis != null) {
                    domainAxis.setAxisLineStroke(stroke);
                }
                int rangeAxisCount = categoryPlot.getRangeAxisCount();
                for (int i = 0; i < rangeAxisCount; ++i) {
                    ValueAxis valueAxis = categoryPlot.getRangeAxis(i);
                    if (valueAxis != null) {
                        valueAxis.setAxisLineStroke(stroke);
                    }
                }
            } else if (plot instanceof XYPlot) {
                XYPlot xyPlot = (XYPlot) plot;
                ValueAxis domainAxis = xyPlot.getDomainAxis();
                if (domainAxis != null) {
                    domainAxis.setAxisLineStroke(stroke);
                }
                int rangeAxisCount = xyPlot.getRangeAxisCount();
                for (int i = 0; i < rangeAxisCount; ++i) {
                    ValueAxis valueAxis = xyPlot.getRangeAxis(i);
                    if (valueAxis != null) {
                        valueAxis.setAxisLineStroke(stroke);
                    }
                }
            }
        }

    }

    private void plotOrientationChanged(PlotOrientation orientation) {
        JFreeChart chart = getCurrentChart();
        if (chart != null) {
            Plot plot = chart.getPlot();
            if (plot instanceof CategoryPlot) {
                CategoryPlot categoryPlot = (CategoryPlot) plot;
                categoryPlot.setOrientation(orientation);
            } else if (plot instanceof XYPlot) {
                XYPlot xyPlot = (XYPlot) plot;
                xyPlot.setOrientation(orientation);
            }
        }

    }

    private boolean rangeAxisConfigChanged(RangeAxisConfigChangeEvent change) {
        RangeAxisConfig source = change.getSource();
        RangeAxisConfigChangeType type = change.getType();
        boolean processed = true;
        switch (type) {
        case VALUE_SOURCE_ADDED:
        case VALUE_SOURCE_MOVED:
        case VALUE_SOURCE_REMOVED:
        case CLEARED:
            return replot();
        case LABEL:
            String label = change.getLabel();
            if (label == null) {
                label = I18N.getGUILabel("plotter.unnamed_value_label");
            }
            setRangeAxisLabel(label, source);
            break;
        case SCALING:
            rangeAxisConfigAxisChanged(source);
            break;
        case VALUE_SOURCE_CHANGED:
            processed = valueSouceChanged(change.getValueSourceChange());
            break;
        case AUTO_NAMING:
            break;
        case RANGE_CHANGED:
            rangeAxisConfigAxisChanged(source);
            break;
        case CROSSHAIR_LINES_CHANGED:
            updateChartPanel(getCurrentChart());
            break;
        default:
            throw new RuntimeException("Unknown event type " + type + " This should not happen.");
        }
        return processed;
    }

    private boolean valueSouceChanged(ValueSourceChangeEvent change) {
        ValueSourceChangeType type = change.getType();
        boolean processed = true;
        switch (type) {
        case USES_GROUPING:
        case AGGREGATION_WINDOWING_CHANGED:
        case USE_RELATIVE_UTILITIES:
        case AGGREGATION_FUNCTION_MAP:
        case DATATABLE_COLUMN_MAP:
        case SERIES_FORMAT_CHANGED:
            return replot();
        case UPDATED:
            // this is caused by other events that cause a replot - do nothing
            break;
        case LABEL:
            resetLegend();
            break;
        case AUTO_NAMING:
            break;
        default:
            throw new RuntimeException("Unknown event type " + type + " This should not happen.");
        }
        return processed;
    }

    /**
     * Returns true iff processing is finished after this function has finished; false if a
     * processing thread is started.
     */
    private boolean dimensionConfigChanged(DimensionConfigChangeEvent change) {
        DimensionConfigChangeType type = change.getType();
        boolean processed;
        switch (type) {
        case LABEL:
            if (change.getDimension() == PlotDimension.DOMAIN) {
                String label = change.getLabel();
                if (label == null) {
                    label = I18N.getGUILabel("plotter.unnamed_value_label");
                }
                setDomainAxisLabel(label);
            } else {
                resetLegend();
            }
            processed = true;
            break;
        case DATE_FORMAT_CHANGED:
            if (change.getDimension() == PlotDimension.DOMAIN) {
                setDomainAxisDateFormat(change.getDateFormat());
            } else {
                resetLegend();
            }
            processed = true;
            break;
        case COLOR_SCHEME:
            processed = true;
            break;
        case CROSSHAIR_LINES_CHANGED:
            updateChartPanel(getCurrentChart());
            processed = true;
            break;
        default:
            return replot();
        }

        return processed;

    }

    private void handlePlottimeException(ChartPlottimeException e) {
        if (plotInstance.getMasterOfDesaster() != null) {
            plotInstance.getMasterOfDesaster().registerConfigurationChangeResponse(e.getResponse());
        }
        invalidateCache();
        currentChartIsValid = false;
        updateChartPanel(new JFreeChart(new CategoryPlot()));
    }

    /**
     * Creates the legend items for this {@link JFreeChartPlotEngine}.
     * 
     * @see org.jfree.chart.LegendItemSource#getLegendItems()
     */
    @Override
    public LegendItemCollection getLegendItems() {
        if (plotInstance.getCurrentPlotConfigurationClone().getLegendConfiguration()
                .getLegendPosition() == LegendPosition.NONE) {
            return null;
        }
        synchronized (this) {
            return cachedLegendItems;
        }
    }

    private void updateLegendItems() {
        synchronized (this) {
            cachedLegendItems = legendCreator.getLegendItems(plotInstance);
        }
    }

    /**
     * Returns the {@link ChartPanel} that is controlled by this {@link JFreeChartPlotEngine}.
     */
    public LinkAndBrushChartPanel getChartPanel() {
        return chartPanel;
    }

    public void addPlotEngineListener(JFreeChartPlotEngineListener l) {
        listeners.add(new WeakReference<JFreeChartPlotEngineListener>(l));
    }

    public void removePlotEngineListener(JFreeChartPlotEngineListener l) {
        Iterator<WeakReference<JFreeChartPlotEngineListener>> it = listeners.iterator();
        while (it.hasNext()) {
            JFreeChartPlotEngineListener listener = it.next().get();
            if (listener == null || listener == l) {
                it.remove();
            }
        }
    }

    /**
     * This method sets the coordinate transformation for this component.
     */
    public void setCoordinateTransformation(CoordinateTransformation transformation) {
        this.coordinateTransformation = transformation;
        this.chartPanel.setCoordinateTransformation(transformation);
    }

    private void fireChartChanged(JFreeChart chart) {
        Iterator<WeakReference<JFreeChartPlotEngineListener>> defaultIt = listeners.iterator();
        while (defaultIt.hasNext()) {
            WeakReference<JFreeChartPlotEngineListener> wrl = defaultIt.next();
            JFreeChartPlotEngineListener l = wrl.get();
            if (l != null) {
                l.chartChanged(this, chart);
            } else {
                defaultIt.remove();
            }
        }
    }

    @Override
    public List<PlotConfigurationError> getEngineErrors() {
        List<PlotConfigurationError> errors = new LinkedList<PlotConfigurationError>();
        return errors;
    }

    @Override
    public List<PlotConfigurationError> getEngineWarnings() {
        List<PlotConfigurationError> warnings = new LinkedList<PlotConfigurationError>();

        PlotConfiguration plotConfiguration = plotInstance.getCurrentPlotConfigurationClone();

        // check if a category plot contains value sources which request lines to be drawn
        if (plotConfiguration.getDomainConfigManager().isNominal()) {
            for (ValueSource valueSource : plotConfiguration.getAllValueSources()) {
                if (valueSource.getSeriesFormat().getSeriesType() == VisualizationType.LINES_AND_SHAPES
                        && valueSource.getSeriesFormat().getLineStyle() != LineStyle.NONE
                        && !valueSource.isUsingDomainGrouping()) {
                    warnings.add(new PlotConfigurationError("plot_does_not_support_lines",
                            "categorical scatter plot with ungrouped domain axis", valueSource.toString()));
                }
            }
        }

        // check if a value source requests a difference plot with items
        for (ValueSource valueSource : plotConfiguration.getAllValueSources()) {
            SeriesFormat format = valueSource.getSeriesFormat();
            if (format.getSeriesType() == VisualizationType.LINES_AND_SHAPES) {
                if (format.getUtilityUsage() == IndicatorType.DIFFERENCE) {
                    if (format.getItemShape() != ItemShape.NONE) {
                        warnings.add(new PlotConfigurationError("difference_plot_with_items_not_supported",
                                valueSource.toString()));
                    }
                }
            }
        }

        return warnings;
    }

    @Override
    public PlotInstance getPlotInstance() {
        return plotInstance;
    }

    @Override
    public void startProcessing() {
        plotInstance.getMasterOfDesaster().setCalculating(true);
    }

    @Override
    public void endProcessing() {
        plotInstance.getMasterOfDesaster().setCalculating(false);

    }
}