com.rapidminer.gui.new_plotter.configuration.PlotConfiguration.java Source code

Java tutorial

Introduction

Here is the source code for com.rapidminer.gui.new_plotter.configuration.PlotConfiguration.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.configuration;

import java.awt.Color;
import java.awt.Font;
import java.awt.Paint;
import java.lang.ref.WeakReference;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.jfree.chart.plot.PlotOrientation;

import com.rapidminer.gui.MainFrame;
import com.rapidminer.gui.new_plotter.PlotConfigurationError;
import com.rapidminer.gui.new_plotter.StaticDebug;
import com.rapidminer.gui.new_plotter.configuration.DimensionConfig.PlotDimension;
import com.rapidminer.gui.new_plotter.configuration.DomainConfigManager.GroupingState;
import com.rapidminer.gui.new_plotter.configuration.LineFormat.LineStyle;
import com.rapidminer.gui.new_plotter.configuration.SeriesFormat.ItemShape;
import com.rapidminer.gui.new_plotter.configuration.SeriesFormat.VisualizationType;
import com.rapidminer.gui.new_plotter.engine.jfreechart.link_and_brush.listener.LinkAndBrushListener;
import com.rapidminer.gui.new_plotter.engine.jfreechart.link_and_brush.listener.LinkAndBrushSelection;
import com.rapidminer.gui.new_plotter.engine.jfreechart.link_and_brush.listener.LinkAndBrushSelectionListener;
import com.rapidminer.gui.new_plotter.listener.DimensionConfigListener;
import com.rapidminer.gui.new_plotter.listener.LegendConfigurationListener;
import com.rapidminer.gui.new_plotter.listener.PlotConfigurationListener;
import com.rapidminer.gui.new_plotter.listener.PlotConfigurationProcessingListener;
import com.rapidminer.gui.new_plotter.listener.RangeAxisConfigListener;
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.ValueGroupingChangeEvent;
import com.rapidminer.gui.new_plotter.listener.events.ValueGroupingChangeEvent.ValueGroupingChangeType;
import com.rapidminer.gui.new_plotter.listener.events.ValueRangeChangeEvent;
import com.rapidminer.gui.new_plotter.listener.events.ValueRangeChangeEvent.ValueRangeChangeType;
import com.rapidminer.gui.new_plotter.listener.events.ValueSourceChangeEvent;
import com.rapidminer.gui.new_plotter.templates.style.ColorRGB;
import com.rapidminer.gui.new_plotter.templates.style.ColorScheme;
import com.rapidminer.gui.new_plotter.utility.CategoricalColorProvider;
import com.rapidminer.gui.new_plotter.utility.ContinuousColorProvider;
import com.rapidminer.gui.new_plotter.utility.DataStructureUtils;
import com.rapidminer.gui.new_plotter.utility.ListUtility;
import com.rapidminer.tools.I18N;
import com.rapidminer.tools.ParameterService;

/**
 * @author Marius Helf, Nils Woehler
 */
public class PlotConfiguration implements DimensionConfigListener, RangeAxisConfigListener, Cloneable,
        LinkAndBrushSelectionListener, LegendConfigurationListener, LinkAndBrushListener {

    public static final Paint DEFAULT_SERIES_OUTLINE_PAINT = Color.BLACK;

    public static final Font DEFAULT_AXES_FONT = new Font("Dialog", Font.PLAIN, 10);

    public static final Color DEFAULT_OUTLINE_COLOR = Color.BLACK;

    private boolean initializing = false;

    private static final double MIN_SHAPE_SCALING_FACTOR = 0.4;
    private static final double MAX_SHAPE_SCALING_FACTOR = 5.0;

    public static final int GUI_PLOTTER_ROWS_MAXIMUM_IF_RAPIDMINER_PROPERTY_NOT_READABLE = 5000;

    private static final String DEFAULT_TITLE_TEXT = null;
    private static final Font DEFAULT_TITLE_FONT = new Font("Arial", Font.PLAIN, 20);

    private static final Color DEFAULT_PLOT_BACKGROUND_COLOR = Color.white;
    private static final Color DEFAULT_FRAME_BACKGROUND_COLOR = Color.white;
    private static final PlotOrientation DEFAULT_PLOT_ORIENTATION = PlotOrientation.VERTICAL;

    private static final float DEFAULT_AXIS_LINE_WIDTH = 1.0f;

    private static final Color DEFAULT_AXIS_COLOR = Color.black;

    public static final Color DEFAULT_TITLE_COLOR = Color.black;

    private final List<RangeAxisConfig> rangeAxisConfigs = new LinkedList<RangeAxisConfig>();
    private final DomainConfigManager domainConfigManager;

    /**
     * Stores which DimensionConfig is used for a Dimension. All {@link PlotValueConfig} use the
     * same DimensionConfig. To be precisely, all {@link PlotValueConfig}s reference the same object
     * and take the reference from this map.
     *
     * Exception: the domain Dimension is stored in the DomainConfigManager, to control proper
     * setting of groupings for all {@link ValueSource}s.
     *
     * For obvious reason also the VALUE-dimension is NOT stored in this map.
     */
    private Map<PlotDimension, DefaultDimensionConfig> dimensionConfigMap = new HashMap<DimensionConfig.PlotDimension, DefaultDimensionConfig>();

    private String titleText = DEFAULT_TITLE_TEXT;
    private Font titleFont = DEFAULT_TITLE_FONT;

    private Color plotBackgroundColor = DEFAULT_PLOT_BACKGROUND_COLOR;
    private Color frameBackgroundColor = DEFAULT_FRAME_BACKGROUND_COLOR;
    private Font axesFont = DEFAULT_AXES_FONT;
    private PlotOrientation orientation = DEFAULT_PLOT_ORIENTATION;

    private static int CLASS_ID = 0;
    private int unique_debug_id = -1;
    private boolean cloned_debug = false;

    /**
     * If this variable is true, events that happen inside this PlotConfiguration, e.g. changes of
     * RangeAxis, ValueSource etc., are process by the event queue. If this variable is false, no
     * events are processed.
     *
     * Best Practice to use it: boolean processing = isProcessingEvents(); setProcessEvents(false);
     * OTHER CODE setProcessEvents(processing);
     */
    private Boolean processEvents = new Boolean(true);

    private List<PlotConfigurationChangeEvent> eventList = new LinkedList<PlotConfigurationChangeEvent>();
    private Integer listenersInformedCounter = 0;

    private transient List<WeakReference<PlotConfigurationListener>> defaultListeners = new LinkedList<WeakReference<PlotConfigurationListener>>();
    private transient List<WeakReference<PlotConfigurationListener>> prioritizedListeners = new LinkedList<WeakReference<PlotConfigurationListener>>();
    private transient List<WeakReference<PlotConfigurationProcessingListener>> processingListeners = new LinkedList<WeakReference<PlotConfigurationProcessingListener>>();

    private boolean changingRange;
    private boolean changingGrouping;

    private float axisLineWidth = DEFAULT_AXIS_LINE_WIDTH;
    private Color axisLineColor = DEFAULT_AXIS_COLOR;

    private Map<String, ColorScheme> colorSchemes = new HashMap<String, ColorScheme>();
    private String activeSchemeName;

    /**
     * The event which is currently fired.
     */
    private PlotConfigurationChangeEvent currentEvent = null;

    private LinkAndBrushMaster linkAndBrushMaster;

    private LegendConfiguration legendConfiguration = new LegendConfiguration();

    private int idCounter = 0;

    private Color titleColor = DEFAULT_TITLE_COLOR;

    /**
     * Creates a plot configuration with one empty {@link RangeAxisConfig}.
     *
     * @param domainColumn
     */
    public PlotConfiguration(DataTableColumn domainColumn) {
        ++CLASS_ID;
        unique_debug_id = CLASS_ID;
        this.domainConfigManager = new DomainConfigManager(this, domainColumn);
        this.linkAndBrushMaster = new LinkAndBrushMaster(this);
        this.linkAndBrushMaster.addLinkAndBrushListener(this);
        domainConfigManager.addDimensionConfigListener(this);
        legendConfiguration.addListener(this);

        createDefaultColorSchemes();

    }

    /**
     *
     */
    private void createDefaultColorSchemes() {
        ColorScheme colorScheme = getDefaultColorScheme();
        this.colorSchemes.put(colorScheme.getName(), colorScheme);

        /*
         * default color schemes are defined here
         */
        List<ColorRGB> listOfColors = new LinkedList<ColorRGB>();
        listOfColors.add(new ColorRGB(124, 181, 236));
        listOfColors.add(new ColorRGB(67, 67, 72));
        listOfColors.add(new ColorRGB(144, 237, 125));
        listOfColors.add(new ColorRGB(247, 163, 92));
        listOfColors.add(new ColorRGB(128, 133, 233));
        listOfColors.add(new ColorRGB(241, 92, 128));
        listOfColors.add(new ColorRGB(228, 211, 84));
        listOfColors.add(new ColorRGB(128, 133, 232));
        listOfColors.add(new ColorRGB(141, 70, 83));
        listOfColors.add(new ColorRGB(145, 232, 225));
        ColorScheme cs = new ColorScheme("Pastel", listOfColors);
        this.colorSchemes.put(cs.getName(), cs);

        listOfColors = new LinkedList<ColorRGB>();
        ColorRGB colorful1 = new ColorRGB(222, 217, 26);
        ColorRGB colorful2 = new ColorRGB(219, 138, 47);
        ColorRGB colorful3 = new ColorRGB(217, 26, 21);
        ColorRGB colorful4 = new ColorRGB(156, 217, 84);
        ColorRGB colorful5 = new ColorRGB(83, 70, 255);

        listOfColors.add(colorful1);
        listOfColors.add(colorful2);
        listOfColors.add(colorful3);
        listOfColors.add(colorful4);
        listOfColors.add(colorful5);
        cs = new ColorScheme("Mud", listOfColors, colorful2, colorful5);
        this.colorSchemes.put(cs.getName(), cs);

        listOfColors = new LinkedList<ColorRGB>();
        ColorRGB forest1 = new ColorRGB(94, 173, 0);
        ColorRGB forest2 = new ColorRGB(255, 188, 10);
        ColorRGB forest3 = new ColorRGB(189, 39, 53);
        ColorRGB forest4 = new ColorRGB(255, 119, 0);
        ColorRGB forest5 = new ColorRGB(81, 17, 84);

        listOfColors.add(forest1);
        listOfColors.add(forest2);
        listOfColors.add(forest3);
        listOfColors.add(forest4);
        listOfColors.add(forest5);
        cs = new ColorScheme("Forest", listOfColors, forest4, forest5);
        this.colorSchemes.put(cs.getName(), cs);

        listOfColors = new LinkedList<ColorRGB>();
        ColorRGB baw1 = new ColorRGB(0, 0, 0);
        ColorRGB baw2 = new ColorRGB(204, 204, 204);
        ColorRGB baw3 = new ColorRGB(255, 255, 255);
        ColorRGB baw4 = new ColorRGB(102, 102, 102);
        ColorRGB baw5 = new ColorRGB(51, 51, 51);

        listOfColors.add(baw1);
        listOfColors.add(baw2);
        listOfColors.add(baw3);
        listOfColors.add(baw4);
        listOfColors.add(baw5);
        cs = new ColorScheme("Grayscale", listOfColors, baw2, baw1);
        this.colorSchemes.put(cs.getName(), cs);

        this.activeSchemeName = colorScheme.getName();
    }

    public PlotConfiguration(DataTableColumn domainColumn, ColorScheme activeColorScheme,
            Map<String, ColorScheme> colorSchemes) {
        this(domainColumn);

        this.colorSchemes = colorSchemes;

        if (activeColorScheme != null) {
            setActiveColorScheme(activeColorScheme.getName());
        }

    }

    /**
     * Private ctor, used only by {@link #clone()} method
     */
    private PlotConfiguration(DomainConfigManager domainConfigManager, LegendConfiguration legendConfiguration) {
        ++CLASS_ID;
        unique_debug_id = CLASS_ID;
        this.linkAndBrushMaster = new LinkAndBrushMaster(this);
        this.linkAndBrushMaster.addLinkAndBrushListener(this);
        this.legendConfiguration = legendConfiguration;
        this.domainConfigManager = domainConfigManager;
        domainConfigManager.setPlotConfiguration(this);
        domainConfigManager.addDimensionConfigListener(this);
        legendConfiguration.addListener(this);
    }

    /**
     * Returns the list of all {@link RangeAxisConfig}. All plot value configurations of a plot
     * range axis *must* refer to the same column of the underlying DataTable.
     *
     * All x-Dimensions must use either the same categories, or all x-Dimensions must be numerical.
     */
    public List<RangeAxisConfig> getRangeAxisConfigs() {
        return rangeAxisConfigs;
    }

    public void addRangeAxisConfig(int index, RangeAxisConfig rangeAxis) {
        debug("ADDING RANGEAXIS " + rangeAxis.getId());

        rangeAxisConfigs.add(index, rangeAxis);
        fireRangeAxisAdded(index, rangeAxis);
        rangeAxis.addRangeAxisConfigListener(this);
    }

    /**
     * Adds a PlotRangeAxis to this PlotConfiguration.
     *
     * See also the comment of getPlotRangeAxis() for the contract for the x-Dimension.
     */
    public void addRangeAxisConfig(RangeAxisConfig rangeAxis) {
        addRangeAxisConfig(rangeAxisConfigs.size(), rangeAxis);
    }

    public void removeRangeAxisConfig(RangeAxisConfig rangeAxis) {
        final int index = rangeAxisConfigs.indexOf(rangeAxis);
        removeRangeAxisConfig(index);
    }

    public int getNextId() {
        ++idCounter;
        debug("Generated ID " + idCounter);
        return idCounter;
    }

    public void changeIndex(int index, RangeAxisConfig rangeAxis) {
        if (ListUtility.changeIndex(rangeAxisConfigs, rangeAxis, index)) {
            linkAndBrushMaster.clearZooming(false);
            fireRangeAxisMoved(index, rangeAxis);
        }
    }

    public int getRangeAxisCount() {
        return rangeAxisConfigs.size();
    }

    public void removeRangeAxisConfig(int index) {
        RangeAxisConfig rangeAxis = rangeAxisConfigs.get(index);
        rangeAxis.removeRangeAxisConfigListener(this);
        debug(" REMOVING RANGEAXIS with index " + index + " and ID " + rangeAxis.getId());
        rangeAxisConfigs.remove(index);
        fireRangeAxisRemoved(index, rangeAxis);
    }

    public void addPlotConfigurationListener(PlotConfigurationListener l) {
        addPlotConfigurationListener(l, false);
    }

    /**
     * This functions registers a {@link PlotConfigurationListener} as prioritized. This means that
     * is is being informed at first when new events are being processed. At the moment only one
     * prioritized listener may be registered at the same time. Check if you may register via
     * getPrioritizedListenerCount().
     */
    public void addPlotConfigurationListener(PlotConfigurationListener l, boolean prioritized) {
        if (prioritized) {
            synchronized (prioritizedListeners) {
                prioritizedListeners.add(new WeakReference<PlotConfigurationListener>(l));
            }
        } else {
            synchronized (defaultListeners) {
                defaultListeners.add(new WeakReference<PlotConfigurationListener>(l));
            }
        }
    }

    public int getPrioritizedListenerCount() {
        synchronized (prioritizedListeners) {
            return prioritizedListeners.size();
        }
    }

    /**
     * Removes prioritized and default listeners if contained in one of these lists.
     */
    public void removePlotConfigurationListener(PlotConfigurationListener l) {
        synchronized (prioritizedListeners) {
            Iterator<WeakReference<PlotConfigurationListener>> it = prioritizedListeners.iterator();
            while (it.hasNext()) {
                PlotConfigurationListener listener = it.next().get();
                if (listener == null || listener == l) {
                    it.remove();
                }
            }
        }

        synchronized (defaultListeners) {
            Iterator<WeakReference<PlotConfigurationListener>> it_default = defaultListeners.iterator();
            while (it_default.hasNext()) {
                PlotConfigurationListener listener = it_default.next().get();
                if (listener == null || listener == l) {
                    it_default.remove();
                }
            }
        }
    }

    /**
     * @return the title
     */
    public String getTitleText() {
        return titleText;
    }

    /**
     * @param title
     *            the title to set
     */
    public void setTitleText(String title) {
        if (title != this.titleText) {
            this.titleText = title;
            fireTitleChanged();
        }
    }

    /**
     * @return the titleFont
     */
    public Font getTitleFont() {
        return titleFont;
    }

    /**
     * @param titleFont
     *            the titleFont to set
     */
    public void setTitleFont(Font titleFont) {
        if (!titleFont.equals(this.titleFont)) {
            this.titleFont = titleFont;
            fireTitleChanged();
        }
    }

    /**
     * @return the backGroundColor
     */
    public Color getPlotBackgroundColor() {
        return plotBackgroundColor;
    }

    /**
     * @param backgroundColor
     *            the backGroundColor to set
     */
    public void setPlotBackgroundColor(Color backgroundColor) {
        if (!backgroundColor.equals(this.plotBackgroundColor)) {
            this.plotBackgroundColor = backgroundColor;
            firePlotBackgroundColorChanged();
        }
    }

    /**
     * @return the axesFont
     */
    public Font getAxesFont() {
        return axesFont;
    }

    /**
     * @param axesFont
     *            the axesFont to set
     */
    public void setAxesFont(Font axesFont) {
        if (axesFont != this.axesFont) {
            this.axesFont = axesFont;
            fireAxesFontChanged();
        }
    }

    /**
     * Returns the next free color for a value source based on the active color schema.
     */
    public Color getNextColor() {
        int idx = 0;
        Set<Color> usedColors = new HashSet<Color>();
        for (ValueSource valueSource : getAllValueSources()) {
            usedColors.add(DataStructureUtils.setColorAlpha(valueSource.getSeriesFormat().getItemColor(), 255));
        }
        ColorScheme colorScheme = getActiveColorScheme();
        while (usedColors.contains(CategoricalColorProvider.getColorForCategoryIdx(idx, colorScheme))) {
            ++idx;
        }
        return CategoricalColorProvider.getColorForCategoryIdx(idx, colorScheme);
    }

    /**
     * @return the orientation
     */
    public PlotOrientation getOrientation() {
        return orientation;
    }

    /**
     * @param orientation
     *            the orientation to set
     */
    public void setOrientation(PlotOrientation orientation) {
        if (!this.orientation.equals(orientation)) {
            this.orientation = orientation;
            firePlotConfigurationChanged(new PlotConfigurationChangeEvent(this, orientation));
        }
    }

    /**
     * @return the domainAxisLineStroke
     */
    public float getAxisLineWidth() {
        return axisLineWidth;
    }

    /**
     * @return the domainAxisLineColor
     */
    public Color getAxisLineColor() {
        return axisLineColor;
    }

    /**
     * @param domainAxisLineStroke
     *            the domainAxisLineStroke to set
     */
    public void setAxisLineWidth(float axisLineWidth) {
        if (axisLineWidth != this.axisLineWidth) {
            this.axisLineWidth = axisLineWidth;
            firePlotConfigurationChanged(new PlotConfigurationChangeEvent(this, axisLineWidth));
        }
    }

    public void setAxisLineColor(Color axisLineColor) {
        if (!axisLineColor.equals(this.axisLineColor)) {
            this.axisLineColor = axisLineColor;
            firePlotConfigurationChanged(new PlotConfigurationChangeEvent(this,
                    PlotConfigurationChangeType.AXIS_LINE_COLOR, axisLineColor));
        }
    }

    public Color getChartBackgroundColor() {
        return frameBackgroundColor;
    }

    /**
     * @param frameBackgroundColor
     *            the chartBackgroundColor to set
     */
    public void setFrameBackgroundColor(Color frameBackgroundColor) {
        if (!frameBackgroundColor.equals(this.frameBackgroundColor)) {
            this.frameBackgroundColor = frameBackgroundColor;
            fireChartBackgroundChanged();
        }
    }

    private void fireRangeAxisMoved(int index, RangeAxisConfig rangeAxis) {
        if (!initializing) {
            firePlotConfigurationChanged(new PlotConfigurationChangeEvent(this,
                    PlotConfigurationChangeType.RANGE_AXIS_CONFIG_MOVED, rangeAxis, index));
        }
    }

    private void fireRangeAxisAdded(int index, RangeAxisConfig rangeAxis) {
        if (!initializing) {
            firePlotConfigurationChanged(new PlotConfigurationChangeEvent(this,
                    PlotConfigurationChangeType.RANGE_AXIS_CONFIG_ADDED, rangeAxis, index));
        }
    }

    private void fireRangeAxisRemoved(int index, RangeAxisConfig rangeAxis) {
        if (!initializing) {
            firePlotConfigurationChanged(new PlotConfigurationChangeEvent(this,
                    PlotConfigurationChangeType.RANGE_AXIS_CONFIG_REMOVED, rangeAxis, index));
        }
    }

    private void firePlotBackgroundColorChanged() {
        if (!initializing) {
            firePlotConfigurationChanged(new PlotConfigurationChangeEvent(this,
                    PlotConfigurationChangeType.PLOT_BACKGROUND_COLOR, plotBackgroundColor));
        }

    }

    private void fireLegendChanged(LegendConfigurationChangeEvent change) {
        if (!initializing) {
            firePlotConfigurationChanged(new PlotConfigurationChangeEvent(this, change));
        }
    }

    private void fireChartBackgroundChanged() {
        if (!initializing) {
            firePlotConfigurationChanged(new PlotConfigurationChangeEvent(this,
                    PlotConfigurationChangeType.FRAME_BACKGROUND_COLOR, frameBackgroundColor));
        }
    }

    /**
     * Sets the dimension config for a specific dimension.
     *
     * @param dimension
     *            The dimension for which the config is specified. Must not be VALUE or DOMAIN.
     * @param dimensionConfig
     *            The new dimension config for the dimension. null means to remove the dimension
     *            config.
     */
    public void setDimensionConfig(PlotDimension dimension, DefaultDimensionConfig dimensionConfig) {
        DimensionConfig currentDimensionConfig = dimensionConfigMap.get(dimension);
        if (dimensionConfig != currentDimensionConfig) {
            if (dimensionConfig == null) {
                if (currentDimensionConfig != null) {
                    currentDimensionConfig.removeDimensionConfigListener(this);
                }
                dimensionConfigMap.remove(dimension);
                fireDimensionConfigRemoved(dimension, currentDimensionConfig);
            } else {
                if (currentDimensionConfig != dimensionConfig) {
                    if (currentDimensionConfig != null) {
                        currentDimensionConfig.removeDimensionConfigListener(this);
                    }

                    dimensionConfigMap.put(dimension, dimensionConfig);
                    dimensionConfig.addDimensionConfigListener(this);

                    fireDimensionConfigAdded(dimension, dimensionConfig);
                }
            }
        }
    }

    private void informValueSourcesAboutDimensionChange(DimensionConfigChangeEvent e) {
        for (RangeAxisConfig rangeAxisConfig : rangeAxisConfigs) {
            for (ValueSource valueSource : rangeAxisConfig.getValueSources()) {
                valueSource.dimensionConfigChanged(e);
            }
        }
    }

    private void fireAxesFontChanged() {
        if (!initializing) {
            firePlotConfigurationChanged(new PlotConfigurationChangeEvent(this, axesFont));
        }
    }

    /**
     * Returns a list of all {@link PlotValueConfig}s, no matter in which {@link RangeAxisConfig}
     * they are located.
     */
    public List<ValueSource> getAllValueSources() {
        List<ValueSource> resultList = new LinkedList<ValueSource>();
        for (RangeAxisConfig rangeAxisConfig : getRangeAxisConfigs()) {
            resultList.addAll(rangeAxisConfig.getValueSources());
        }
        return resultList;
    }

    /**
     * Returns the dimension config for the specified dimension. May return <code>null</code> if not
     * existing.
     */
    public DimensionConfig getDimensionConfig(PlotDimension dimension) {
        if (dimension == PlotDimension.DOMAIN) {
            return domainConfigManager;
        }
        return dimensionConfigMap.get(dimension);
    }

    /**
     * Returns all existing dimension configurations except the DOMAIN dimension config. This has to
     * be fetched be getDomainConfigManager().
     *
     * @return
     */
    public Map<PlotDimension, DefaultDimensionConfig> getDefaultDimensionConfigs() {
        return dimensionConfigMap;
    }

    public SeriesFormat getAutomaticSeriesFormatForNextValueSource(RangeAxisConfig rangeAxisConfig) {
        SeriesFormat seriesFormat = new SeriesFormat();

        List<ValueSource> valueSourceList = rangeAxisConfig.getValueSources();
        int size = valueSourceList.size();
        if (size > 0) {
            ValueSource lastValueSource = valueSourceList.get(valueSourceList.size() - 1);
            SeriesFormat lastFormat = lastValueSource.getSeriesFormat();
            // seriesFormat.setSeriesType(lastFormat.getSeriesType());
            if (lastFormat.getSeriesType() == VisualizationType.LINES_AND_SHAPES) {

                if (lastFormat.getItemShape() == ItemShape.NONE) {
                    seriesFormat.setItemShape(ItemShape.NONE);
                    LineStyle[] values = LineStyle.values();
                    LineStyle nextItem = values[size % (values.length - 1) + 1];
                    seriesFormat.setLineStyle(nextItem);
                } else {
                    ItemShape[] values = SeriesFormat.ItemShape.values();
                    ItemShape nextShape = values[size % (values.length - 1) + 1];
                    seriesFormat.setItemShape(nextShape);
                    seriesFormat.setLineStyle(lastFormat.getLineStyle());
                }

                seriesFormat.setLineWidth(lastFormat.getLineWidth());
                seriesFormat.setItemSize(lastFormat.getItemSize());
            }
            seriesFormat.setStackingMode(lastFormat.getStackingMode());
            seriesFormat.setOpacity(lastFormat.getOpacity());
            seriesFormat.setAreaFillStyle(lastFormat.getAreaFillStyle());
        }
        seriesFormat.setItemColor(getNextColor());

        return seriesFormat;
    }

    private void fireDimensionConfigAdded(PlotDimension dimension, DimensionConfig dimensionConfig) {
        if (!initializing) {
            // save old processing status
            boolean processEvents = isProcessingEvents();

            // set processing to false
            setProcessEvents(false);
            informValueSourcesAboutDimensionChange(
                    new DimensionConfigChangeEvent(dimensionConfig, dimension, DimensionConfigChangeType.RESET));
            firePlotConfigurationChanged(new PlotConfigurationChangeEvent(this,
                    PlotConfigurationChangeType.DIMENSION_CONFIG_ADDED, dimension, dimensionConfig));
            setProcessEvents(processEvents); // Restore old state
        }
    }

    private void fireDimensionConfigRemoved(PlotDimension dimension, DimensionConfig dimensionConfig) {
        if (!initializing) {
            // save old processing status
            boolean processEvents = isProcessingEvents();

            // set processing to false
            setProcessEvents(false);
            informValueSourcesAboutDimensionChange(
                    new DimensionConfigChangeEvent(dimensionConfig, dimension, DimensionConfigChangeType.RESET));
            firePlotConfigurationChanged(new PlotConfigurationChangeEvent(this,
                    PlotConfigurationChangeType.DIMENSION_CONFIG_REMOVED, dimension, dimensionConfig));
            setProcessEvents(processEvents); // Restore old state
        }
    }

    private void fireTitleChanged() {
        if (!initializing) {
            firePlotConfigurationChanged(new PlotConfigurationChangeEvent(this, titleText));
        }
    }

    private Color getColorFromProperty(String propertyName, Color errorColor) {
        String propertyString = ParameterService.getParameterValue(propertyName);
        if (propertyString != null) {
            String[] colors = propertyString.split(",");
            if (colors.length != 3) {
                throw new IllegalArgumentException("Color '" + propertyString + "' defined as value for property '"
                        + propertyName + "' is not a vaild color. Colors must be of the form 'r,g,b'.");
            } else {
                Color color = new Color(Integer.parseInt(colors[0].trim()), Integer.parseInt(colors[1].trim()),
                        Integer.parseInt(colors[2].trim()));
                return color;
            }
        } else {
            return errorColor;
        }
    }

    /**
     * Tells the {@link PlotConfiguration} that the current event has been processed. If all
     * Listeners have processed the last current event this triggers the next event to be processed
     * if another is available.
     */
    public synchronized void plotConfigurationChangeEventProcessed() {
        eventProcessed(false);
    }

    private synchronized void eventProcessed(boolean processedAtOnce) {
        listenersInformedCounter--;

        if (!processedAtOnce && listenersInformedCounter <= 0) {
            listenersInformedCounter = 0;
            resetCurrentEvent();
        }
    }

    private synchronized void resetCurrentEvent() {
        // no changes to the event list may be done while adding removing change
        // events
        informOfProcessingStatus(false);
        currentEvent = null;
        listenersInformedCounter = 0;
        debug("PlotConfiguration: Reset current event..");
        processQueueEvent();
    }

    /**
     * If this parameter is true, events that happen inside this PlotConfiguration, e.g. changes of
     * RangeAxis, ValueSource etc. are process by the event queue. If this parameter is false, no
     * events are processed.
     *
     * Best Practice to use it: boolean processing = isProcessingEvents(); setProcessEvents(false);
     * OTHER CODE setProcessEvents(processing);
     */
    public synchronized void setProcessEvents(Boolean process) {
        this.processEvents = process;
        debug(" SET PROCESS EVENTS TO: " + process);

        if (processEvents) {
            processQueueEvent();
        }
    }

    public synchronized boolean isProcessingEvents() {
        return processEvents.booleanValue();
    }

    private void informOfProcessingStatus(boolean started) {
        // create a copy of listeners
        List<WeakReference<PlotConfigurationProcessingListener>> processingListenerCopy = new LinkedList<WeakReference<PlotConfigurationProcessingListener>>();
        processingListenerCopy.addAll(processingListeners);

        // iterate over all listeners
        Iterator<WeakReference<PlotConfigurationProcessingListener>> defaultIt = processingListenerCopy.iterator();
        while (defaultIt.hasNext()) {
            WeakReference<PlotConfigurationProcessingListener> wrl = defaultIt.next();
            PlotConfigurationProcessingListener l = wrl.get();
            if (l != null) {
                if (started) {
                    l.startProcessing();
                } else {
                    l.endProcessing();
                }
            } else {
                defaultIt.remove();
            }
        }
    }

    private synchronized void processQueueEvent() {
        boolean booleanValue = false;
        debug("PROCESS EVENT QUEUE");
        // eventInformCounter has to be synchronize to prevent reaching 0 value
        // while informing listeners
        booleanValue = processEvents.booleanValue();
        debug("PROCESS EVENTS: " + booleanValue);
        debug("CURRENT EVENT: " + currentEvent);
        if (booleanValue && currentEvent == null) {
            // if no current event is being processed

            // no changes to the event list may be done while processing a new
            // event

            // if there is an events that needs to be handled do so
            if (eventList.size() > 0) {
                // get new event
                currentEvent = eventList.get(0);
                currentEvent.setSource(this.clone());

                debug("GOT NEW CURRENT EVENT: " + currentEvent);

                StaticDebug.debugEvent(0, currentEvent);

                eventList.remove(0);

                informOfProcessingStatus(true);
            } else {

                debug("NO CURRENT EVENTS TO HANDLE");
                StaticDebug.emptyDebugLine();
                StaticDebug.emptyDebugLine();

                // there are no recent events that have to be handled
                return;
            }

            // iterate over all listeners

            // first prioritizedListeners
            List<WeakReference<PlotConfigurationListener>> clonedPrioListeners = new LinkedList<WeakReference<PlotConfigurationListener>>();
            synchronized (prioritizedListeners) {
                clonedPrioListeners.addAll(prioritizedListeners);
            }

            Iterator<WeakReference<PlotConfigurationListener>> it = clonedPrioListeners.iterator();

            // // set counter to > 0, so that it will never drop to zero while
            // we are in the loop.
            // // This prevents the currentEvent becoming null while we are
            // still informing listeners.
            // // Will be decreased by 1 after we have informed all listeners.
            // listenersInformedCounter++;

            while (it.hasNext()) {
                WeakReference<PlotConfigurationListener> wrl = it.next();
                PlotConfigurationListener l = wrl.get();
                if (l != null) {
                    // inform listener and increase informed counter
                    listenersInformedCounter++;

                    // if the event has been processed immediately decrease
                    // informed counter
                    if (l.plotConfigurationChanged(currentEvent)) {
                        eventProcessed(true);
                    }
                } else {
                    // TODO this removes the element from the CLONED list, but should be removed
                    // from original list
                    it.remove();
                }
            }

            // then default listeners
            List<WeakReference<PlotConfigurationListener>> clonedDefaultListeners = new LinkedList<WeakReference<PlotConfigurationListener>>();
            synchronized (defaultListeners) {
                clonedDefaultListeners.addAll(defaultListeners);
            }
            Iterator<WeakReference<PlotConfigurationListener>> defaultIt = clonedDefaultListeners.iterator();
            while (defaultIt.hasNext()) {
                WeakReference<PlotConfigurationListener> wrl = defaultIt.next();
                PlotConfigurationListener l = wrl.get();
                if (l != null) {
                    // inform listener and increase informed counter
                    listenersInformedCounter++;

                    // if the event has been processed immediately decrease
                    // informed counter
                    if (l.plotConfigurationChanged(currentEvent)) {
                        eventProcessed(true);
                    }
                } else {
                    // TODO this removes the element from the CLONED list, but should be removed
                    // from original list
                    defaultIt.remove();
                }
            }

            // Decrease listener count, cause we increased it before (see
            // comment above)
            // listenersInformedCounter--;

            // if all event listeners have processed the event immediately
            if (listenersInformedCounter <= 0) {
                // remove event from list
                resetCurrentEvent();
            }

        }

        return;
    }

    /**
     * Adds an {@link PlotConfigurationChangeEvent} to the event queue. Tries to process new event
     * afterwards, if no other event is being processed.
     *
     * @param e
     */
    private synchronized void addEventToQueue(PlotConfigurationChangeEvent e) {

        debug("ADD EVENT TO QUEUE");

        // no changes to the event list may be done while adding new change
        // events

        if (eventList.size() > 0) {
            PlotConfigurationChangeEvent plotConfigurationChangeEvent = eventList.get(0);
            if (plotConfigurationChangeEvent.getType() == PlotConfigurationChangeType.META_CHANGE) {
                plotConfigurationChangeEvent.addPlotConfigChangeEvent(this, e);
                debug("ADD EVENT TO META EVENT");
            } else {
                List<PlotConfigurationChangeEvent> events = new LinkedList<PlotConfigurationChangeEvent>();
                events.add(plotConfigurationChangeEvent);
                events.add(e);
                PlotConfigurationChangeEvent metaEvent = new PlotConfigurationChangeEvent(this, events);
                eventList.set(0, metaEvent);
                debug("CREATE NEW META EVENT");
            }
        } else {
            debug("ADD EVENT TO LIST");
            eventList.add(e);
        }

        processQueueEvent();
    }

    private void firePlotConfigurationChanged(PlotConfigurationChangeEvent e) {
        if (!initializing) {
            debug("Firing a PlotConfigChangeEvent");
            addEventToQueue(e);
        }
    }

    public DomainConfigManager getDomainConfigManager() {
        return domainConfigManager;
    }

    private void debug(String msg) {
        if (cloned_debug) {
            StaticDebug.debug("CLONED PlotConfig(" + unique_debug_id + "): " + msg);
        } else {
            StaticDebug.debug("PlotConfig(" + unique_debug_id + "): " + msg);
        }
    }

    @Override
    public void dimensionConfigChanged(DimensionConfigChangeEvent change) {
        DimensionConfigChangeType type = change.getType();

        // save old processing status
        boolean processEvents = isProcessingEvents();

        // set processing to false
        setProcessEvents(false);

        switch (type) {
        case ABOUT_TO_CHANGE_GROUPING:
            changingGrouping = true;
            break;
        case GROUPING_CHANGED:
            ValueGroupingChangeEvent groupingChange = change.getGroupingChangeEvent();
            if (groupingChange.getType() == ValueGroupingChangeType.RESET) {
                if (changingGrouping) {
                    changingGrouping = false;
                }
            }

            informValueSourcesAboutDimensionChange(change);
            linkAndBrushMaster.clearZooming(false);
            firePlotConfigurationChanged(new PlotConfigurationChangeEvent(this, change));
            break;
        case RANGE:
            ValueRangeChangeEvent rangeChange = change.getValueRangeChangedEvent();
            if (rangeChange.getType() == ValueRangeChangeType.RESET) {
            }
            informValueSourcesAboutDimensionChange(change);
            linkAndBrushMaster.clearZooming(false);
            firePlotConfigurationChanged(new PlotConfigurationChangeEvent(this, change));
            break;
        default:
            if (!changingGrouping) {
                informValueSourcesAboutDimensionChange(change);
                linkAndBrushMaster.clearZooming(false);
                firePlotConfigurationChanged(new PlotConfigurationChangeEvent(this, change));
            }
            break;
        }
        setProcessEvents(processEvents);

    }

    @Override
    public void rangeAxisConfigChanged(RangeAxisConfigChangeEvent e) {
        switch (e.getType()) {
        case AUTO_NAMING:
        case LABEL:
            break;
        case VALUE_SOURCE_CHANGED:
            ValueSourceChangeEvent valueSourceChange = e.getValueSourceChange();
            boolean shouldBreak = false;
            switch (valueSourceChange.getType()) {
            case SERIES_FORMAT_CHANGED:
                shouldBreak = true;
                break;
            default:
                linkAndBrushMaster.clearZooming(false);
            }
            if (shouldBreak) {
                break;
            }
        default:
            linkAndBrushMaster.clearRangeAxisZooming(false);

        }
        firePlotConfigurationChanged(new PlotConfigurationChangeEvent(this, e));
    }

    public List<PlotConfigurationError> getErrors() {
        List<PlotConfigurationError> errors = new LinkedList<PlotConfigurationError>();

        errors.addAll(domainConfigManager.getErrors());
        errors.addAll(linkAndBrushMaster.getErrors());

        for (DimensionConfig dimensionConfig : dimensionConfigMap.values()) {
            errors.addAll(dimensionConfig.getErrors());
        }

        for (RangeAxisConfig rangeAxisConfig : rangeAxisConfigs) {
            errors.addAll(rangeAxisConfig.getErrors());
        }

        return errors;
    }

    public List<PlotConfigurationError> getWarnings() {
        List<PlotConfigurationError> warnings = new LinkedList<PlotConfigurationError>();

        warnings.addAll(domainConfigManager.getWarnings());
        warnings.addAll(linkAndBrushMaster.getWarnings());

        for (DimensionConfig dimensionConfig : dimensionConfigMap.values()) {
            warnings.addAll(dimensionConfig.getWarnings());

            if (!isDimensionUsed(dimensionConfig.getDimension())) {
                warnings.add(new PlotConfigurationError("dimension_config_not_used",
                        dimensionConfig.getDimension().getName()));
            }

        }

        boolean emptyAxis = false;
        for (RangeAxisConfig rangeAxisConfig : rangeAxisConfigs) {
            warnings.addAll(rangeAxisConfig.getWarnings());
            if (rangeAxisConfig.getSize() == 0) {
                emptyAxis = true;
            }
        }

        if (emptyAxis) {
            warnings.add(new PlotConfigurationError("no_data_configuration_assigned"));
        }

        return warnings;
    }

    public boolean isDimensionUsed(PlotDimension dimension) {
        if (dimension != PlotDimension.DOMAIN) {
            DimensionConfig dimensionConfig = getDimensionConfig(dimension);
            if (dimensionConfig == null) {
                return false;
            }
            if (!dimensionConfig.isGrouping()) {
                if (getDomainConfigManager().getGroupingState() == GroupingState.GROUPED) {
                    return false;
                }
            }
            return true;
        } else {
            return true;
        }
    }

    public boolean isValid() {
        return getErrors().isEmpty();
    }

    public double getMinShapeSize() {
        return MIN_SHAPE_SCALING_FACTOR;
    }

    public double getMaxShapeSize() {
        return MAX_SHAPE_SCALING_FACTOR;
    }

    @Override
    public PlotConfiguration clone() {
        PlotConfiguration clone = new PlotConfiguration(domainConfigManager.clone(), legendConfiguration.clone());
        clone.cloned_debug = true;

        debug(" Started CLONING");

        clone.initializing = true;

        clone.domainConfigManager.setPlotConfiguration(clone);

        clone.axesFont = axesFont;
        clone.changingGrouping = changingGrouping;
        clone.changingRange = changingRange;
        clone.frameBackgroundColor = frameBackgroundColor;

        // copy color schemes
        clone.colorSchemes = new HashMap<String, ColorScheme>();
        for (String key : colorSchemes.keySet()) {
            clone.colorSchemes.put(key, colorSchemes.get(key).clone());
        }
        clone.activeSchemeName = activeSchemeName;

        clone.axisLineColor = axisLineColor;
        clone.axisLineWidth = axisLineWidth;
        clone.orientation = orientation;
        clone.plotBackgroundColor = plotBackgroundColor;
        clone.titleFont = titleFont;
        clone.titleText = titleText;
        clone.titleColor = titleColor;

        clone.idCounter = idCounter;

        // clone dimension configs
        for (Map.Entry<PlotDimension, DefaultDimensionConfig> entry : dimensionConfigMap.entrySet()) {
            clone.setDimensionConfig(entry.getKey(), entry.getValue().clone());
        }

        // clone range axis configs
        for (RangeAxisConfig rangeAxisConfig : getRangeAxisConfigs()) {
            clone.addRangeAxisConfig(rangeAxisConfig.clone());
        }

        // update domain config manager on cloned value sources
        for (ValueSource clonedValueSource : clone.getAllValueSources()) {
            clonedValueSource.setDomainConfigManager(clone.getDomainConfigManager());
        }

        clone.linkAndBrushMaster = linkAndBrushMaster.clone(clone);

        clone.initializing = false;

        debug(" CLONING done");

        return clone;
    }

    public ColorScheme getDefaultColorScheme() {
        List<ColorRGB> listOfColors = new LinkedList<ColorRGB>();
        Color minColor = getColorFromProperty(MainFrame.PROPERTY_RAPIDMINER_GUI_PLOTTER_LEGEND_MINCOLOR,
                Color.BLUE);
        ColorRGB minColorRGB = ColorRGB.convertColorToColorRGB(minColor);
        listOfColors.add(minColorRGB);
        Color maxColor = getColorFromProperty(MainFrame.PROPERTY_RAPIDMINER_GUI_PLOTTER_LEGEND_MAXCOLOR, Color.RED);
        ColorRGB maxColorRGB = ColorRGB.convertColorToColorRGB(maxColor);
        listOfColors.add(maxColorRGB);

        Color third = ContinuousColorProvider.getColorForValue(3, 255, false, 0, 4, minColor, maxColor);
        listOfColors.add(ColorRGB.convertColorToColorRGB(third));

        Color second = ContinuousColorProvider.getColorForValue(2, 255, false, 0, 5, minColor, maxColor);
        listOfColors.add(ColorRGB.convertColorToColorRGB(second));

        Color first = ContinuousColorProvider.getColorForValue(1, 255, false, 0, 5, minColor, maxColor);
        listOfColors.add(ColorRGB.convertColorToColorRGB(first));

        return new ColorScheme(I18N.getGUILabel("plotter.default_color_scheme_name.label"), listOfColors,
                minColorRGB, maxColorRGB);
    }

    /**
     * @param colorScheme
     *            the colorScheme to set
     */
    public void setColorSchemes(Map<String, ColorScheme> colorSchemes, String activeColorSchemeName) {
        boolean changed = false;

        ColorScheme oldActiveScheme = getActiveColorScheme();
        if (!colorSchemes.equals(this.colorSchemes)) {
            this.colorSchemes = colorSchemes;
            changed = true;
        }

        if (!oldActiveScheme.equals(colorSchemes.get(activeColorSchemeName))) {
            setActiveScheme(activeColorSchemeName);
            changed = true;
        }
        if (changed) {
            firePlotConfigurationChanged(new PlotConfigurationChangeEvent(this, getActiveColorScheme()));
        }
    }

    public Map<String, ColorScheme> getColorSchemes() {
        return colorSchemes;
    }

    private void setActiveScheme(String name) {
        this.activeSchemeName = name;

        // fetch new active color scheme
        ColorScheme colorScheme = colorSchemes.get(name);
        if (colorScheme == null) {
            colorSchemes.put(name, getDefaultColorScheme());
        }

        // save old processing status
        boolean processEvents = isProcessingEvents();

        // set processing to false
        setProcessEvents(false);

        for (PlotDimension dimension : PlotDimension.values()) {
            DimensionConfig dimConf = getDimensionConfig(dimension);
            if (dimConf != null) {
                dimConf.colorSchemeChanged();
            }
        }

        setProcessEvents(processEvents);
    }

    public void setActiveColorScheme(String name) {

        // check if colorscheme has changed
        if (!this.activeSchemeName.equals(name)) {
            setActiveScheme(name);

            firePlotConfigurationChanged(new PlotConfigurationChangeEvent(this, getActiveColorScheme()));
        }
    }

    /**
     * @return the colorScheme
     */
    public ColorScheme getActiveColorScheme() {
        ColorScheme activeColorScheme = colorSchemes.get(activeSchemeName);
        if (activeColorScheme == null) {
            return getDefaultColorScheme();
        }
        return activeColorScheme;
    }

    public PlotConfigurationChangeEvent getCurrentEvent() {
        return currentEvent;
    }

    /**
     * Adds a {@link ColorScheme} to the existing ones. If another {@link ColorScheme} with same
     * name already exists it will be overwritten.
     */
    public void addColorScheme(ColorScheme colorScheme) {
        colorSchemes.put(colorScheme.getName(), colorScheme);
    }

    /**
     * Adds a {@link ColorScheme} to the existing ones. If another {@link ColorScheme} with same
     * name already exists it will be overwritten. Afterwards it is set to be the active
     * {@link ColorScheme}.
     */
    public void addColorSchemeAndSetActive(ColorScheme colorScheme) {
        addColorScheme(colorScheme);
        setActiveScheme(colorScheme.getName());
        firePlotConfigurationChanged(new PlotConfigurationChangeEvent(this, getActiveColorScheme()));
    }

    /**
     * Removes {@link ColorScheme} with parameter name if present.
     */
    public void removeColorScheme(String name) {
        colorSchemes.remove(name);
    }

    /**
     * @return the link and brush master
     */
    public LinkAndBrushMaster getLinkAndBrushMaster() {
        return linkAndBrushMaster;
    }

    /**
     * Returns whether grouping is required or not. Grouping of new {@link ValueSource}s is required
     * if a containing {@link ValueSource} is grouped and the selected grouping is categorical.
     *
     * Lives here and not in {@link RangeAxisConfig} because needs the domain config manager of this
     * {@link PlotConfiguration}.
     */
    public boolean isGroupingRequiredForNewValueSource(RangeAxisConfig rangeAxis) {
        List<ValueSource> valueSources = rangeAxis.getValueSources();
        for (ValueSource source : valueSources) {
            if (source.isUsingDomainGrouping() && getDomainConfigManager().isNominal()) {
                return true;
            }
        }
        return false;
    }

    @Override
    public void selectedLinkAndBrushRectangle(LinkAndBrushSelection e) {
        linkAndBrushMaster.selectedLinkAndBrushRectangle(e);
    }

    public static int getMaxAllowedValueCount() {
        String parameterValue = ParameterService
                .getParameterValue(MainFrame.PROPERTY_RAPIDMINER_GUI_PLOTTER_ROWS_MAXIMUM);
        if (parameterValue != null) {
            return Integer.parseInt(parameterValue);
        } else {
            return PlotConfiguration.GUI_PLOTTER_ROWS_MAXIMUM_IF_RAPIDMINER_PROPERTY_NOT_READABLE;
        }
    }

    @Override
    public void legendConfigurationChanged(LegendConfigurationChangeEvent change) {
        fireLegendChanged(change);
    }

    public LegendConfiguration getLegendConfiguration() {
        return legendConfiguration;
    }

    /**
     * Removes all value sources, dimension configs and range axes, and resets all options to their
     * default values.
     */
    public void resetToDefaults() {
        boolean processing = isProcessingEvents();

        // do it this strange way to counter concurrent modification exception
        while (!getRangeAxisConfigs().isEmpty()) {
            removeRangeAxisConfig(0);
        }

        // do it this strange way to counter concurrent modification exception
        while (!getDefaultDimensionConfigs().isEmpty()) {
            setDimensionConfig(getDefaultDimensionConfigs().keySet().iterator().next(), null);
        }

        domainConfigManager.resetToDefaults();

        setAxesFont(DEFAULT_AXES_FONT);
        setFrameBackgroundColor(DEFAULT_FRAME_BACKGROUND_COLOR);
        setPlotBackgroundColor(DEFAULT_PLOT_BACKGROUND_COLOR);
        setOrientation(DEFAULT_PLOT_ORIENTATION);
        setTitleText(DEFAULT_TITLE_TEXT);
        setAxisLineWidth(DEFAULT_AXIS_LINE_WIDTH);
        setAxisLineColor(DEFAULT_AXIS_COLOR);

        legendConfiguration.resetToDefaults();

        addRangeAxisConfig(new RangeAxisConfig(null, this));

        setProcessEvents(processing);
    }

    public void addPlotConfigurationProcessingListener(PlotConfigurationProcessingListener l) {
        processingListeners.add(new WeakReference<PlotConfigurationProcessingListener>(l));
    }

    public void removePlotConfigurationProcessingListener(PlotConfigurationProcessingListener l) {
        processingListeners.remove(l);
    }

    public RangeAxisConfig getRangeAxisConfigById(int id) {
        for (RangeAxisConfig rangeAxisConfig : getRangeAxisConfigs()) {
            if (rangeAxisConfig.getId() == id) {
                return rangeAxisConfig;
            }
        }

        return null;
    }

    /**
     * Returns the index of the {@link RangeAxisConfig} with a given id.
     *
     * @param id
     * @return The index of the range axis, or -1 if no such range axis exists.
     */
    public int getIndexOfRangeAxisConfigById(int id) {
        int idx = 0;
        for (RangeAxisConfig rangeAxisConfig : getRangeAxisConfigs()) {
            if (rangeAxisConfig.getId() == id) {
                return idx;
            }
            ++idx;
        }

        return -1;
    }

    public DefaultDimensionConfig getDefaultDimensionConfigById(int dimensionConfigId) {
        if (domainConfigManager.getDomainConfig(true).getId() == dimensionConfigId) {
            return domainConfigManager.getDomainConfig(true);
        }
        if (domainConfigManager.getDomainConfig(false).getId() == dimensionConfigId) {
            return domainConfigManager.getDomainConfig(false);
        }

        for (DefaultDimensionConfig dimensionConfig : getDefaultDimensionConfigs().values()) {
            if (dimensionConfig.getId() == dimensionConfigId) {
                return dimensionConfig;
            }
        }
        return null;
    }

    /**
     * @return
     */
    public Color getTitleColor() {
        return titleColor;
    }

    public void setTitleColor(Color titleColor) {
        this.titleColor = titleColor;
        fireTitleChanged();
    }

    /**
     * This function can be used to fire a TRIGGER_REPLOT event.
     */
    public void triggerReplot() {
        firePlotConfigurationChanged(new PlotConfigurationChangeEvent(this));
    }

    @Override
    public void linkAndBrushUpdate(LinkAndBrushSelection e) {
        firePlotConfigurationChanged(new PlotConfigurationChangeEvent(this, e));
    }
}