private static void configureXYDifferenceRenderer(
      FormattedXYDifferenceRenderer renderer,
      ValueSource valueSource,
      PlotConfiguration plotConfiguration) {
    renderer.setBaseToolTipGenerator(new StandardXYToolTipGenerator());
    SeriesFormat seriesFormat = valueSource.getSeriesFormat();
    DimensionConfig domainConfig = valueSource.getDomainConfig();
    DimensionConfig colorDimensionConfig =
        plotConfiguration.getDimensionConfig(PlotDimension.COLOR);
    DimensionConfig shapeDimensionConfig =
        plotConfiguration.getDimensionConfig(PlotDimension.SHAPE);

    int seriesCount = 1; // valueSource.getSeriesDataForAllGroupCells().groupCellCount();

    // Loop all series and set series format.
    // Format based on dimension configs will be set later on in initFormatDelegate().
    for (int seriesIdx = 0; seriesIdx < seriesCount; ++seriesIdx) {
      // configure linestyle
      if (seriesFormat.getLineStyle() == LineStyle.NONE) {
      } else {
        renderer.setSeriesStroke(seriesIdx, seriesFormat.getStroke(), false);
      }

      // configure series shape if necessary
      if (!SeriesFormat.calculateIndividualFormatForEachItem(domainConfig, shapeDimensionConfig)) {
        if (seriesFormat.getItemShape() != ItemShape.NONE) {
          renderer.setSeriesShape(seriesIdx, seriesFormat.getItemShape().getShape());
        } else {
        }
      }

      // configure series color if necessary
      if (!SeriesFormat.calculateIndividualFormatForEachItem(domainConfig, colorDimensionConfig)) {
        Color itemColor = seriesFormat.getItemColor();
        Color halfTransparentPaint =
            DataStructureUtils.setColorAlpha(itemColor, itemColor.getAlpha() / 2);

        renderer.setSeriesPaint(0, halfTransparentPaint);
        renderer.setSeriesFillPaint(0, halfTransparentPaint);
        renderer.setPositivePaint(halfTransparentPaint);
        renderer.setNegativePaint(
            new Color(
                255 - itemColor.getRed(),
                255 - itemColor.getGreen(),
                255 - itemColor.getBlue(),
                itemColor.getAlpha() / 2));
      }
      renderer.setSeriesOutlinePaint(seriesIdx, PlotConfiguration.DEFAULT_SERIES_OUTLINE_PAINT);
    }
  }
  /**
   * Creates the headings for the dimension config items ( like "Color (attribute X)" ) and adds it
   * to legendItemCollection.
   */
  private void createDimensionTitleLegendItem(
      PlotInstance plotInstance,
      Set<PlotDimension> dimensionSet,
      LegendItemCollection legendItemCollection) {
    PlotConfiguration plotConfig = plotInstance.getCurrentPlotConfigurationClone();
    StringBuilder titleBuilder = new StringBuilder();
    boolean first = true;
    boolean showDimensionType = plotConfig.getLegendConfiguration().isShowDimensionType();
    if (showDimensionType) {
      for (PlotDimension dimension : dimensionSet) {
        if (!first) {
          titleBuilder.append(", ");
        }
        titleBuilder.append(dimension.getShortName());
        first = false;
      }
    }

    if (showDimensionType) {
      titleBuilder.append(" (");
    }

    // get unique dimension labels:
    Set<String> uniqueDimensionLabels = new HashSet<String>();
    first = true;
    for (PlotDimension dimension : dimensionSet) {
      DefaultDimensionConfig dimensionConfig =
          (DefaultDimensionConfig) plotConfig.getDimensionConfig(dimension);
      String label = dimensionConfig.getLabel();
      if (label == null) {
        label = I18N.getGUILabel("plotter.unnamed_value_label");
      }

      if (!uniqueDimensionLabels.contains(label)) {
        if (!first) {
          titleBuilder.append(", ");
          first = false;
        }
        titleBuilder.append(label);
        uniqueDimensionLabels.add(label);
      }
    }
    if (showDimensionType) {
      titleBuilder.append(")");
    }
    titleBuilder.append(": ");

    legendItemCollection.add(createTitleLegendItem(titleBuilder.toString(), plotConfig));
  }
  /**
   * Creates a title item (i.e. bold font etc.) with the given string. Simply gets the default font
   * from the plotConfig and sets it style to bold.
   *
   * @return The created legend item.
   */
  private LegendItem createTitleLegendItem(
      String titleString, PlotConfiguration plotConfiguration) {
    LegendItem titleItem =
        new LegendItem(
            titleString,
            "",
            "",
            "",
            false,
            new Rectangle(),
            false,
            Color.WHITE,
            false,
            Color.WHITE,
            new BasicStroke(),
            false,
            new Rectangle(),
            new BasicStroke(),
            Color.WHITE);
    Font titleFont = titleItem.getLabelFont();

    if (titleFont == null) {
      titleFont = plotConfiguration.getLegendConfiguration().getLegendFont();
    }
    titleItem.setLabelFont(titleFont.deriveFont(Font.BOLD));
    return titleItem;
  }
  private void dimensionConfigChanged(DimensionConfigChangeEvent dimensionChange) {
    PlotConfiguration currentPlotConfig =
        plotInstance.getCurrentPlotConfigurationClone(); // get current plot configuration

    // if domain config has changed
    if (dimensionChange.getDimension() == PlotDimension.DOMAIN) {
      getDomainConfigManagerData()
          .dimensionConfigChanged(dimensionChange); // inform domain config data

      // also inform both domain dimension configs
      DomainConfigManager domainConfigManager = currentPlotConfig.getDomainConfigManager();
      getDimensionConfigData(domainConfigManager.getDomainConfig(false))
          .dimensionConfigChanged(dimensionChange);
      getDimensionConfigData(domainConfigManager.getDomainConfig(true))
          .dimensionConfigChanged(dimensionChange);

    } else {
      // inform default dimension config data about change

      int id = dimensionChange.getSource().getId(); // fetch id of changed dimension
      DimensionConfig currentDimensionConfig =
          currentPlotConfig.getDefaultDimensionConfigById(id); // look up dimension config

      // if dimension config is still present, inform data about changes
      if (currentDimensionConfig != null) {
        DimensionConfigData dimData =
            getDimensionConfigData((DefaultDimensionConfig) currentDimensionConfig);
        dimData.dimensionConfigChanged(dimensionChange);
      } else {
        // if current dimension config is null it has been deleted afterwards in a meta change event
        debug(
            "#### CAUTION #### DIMENSION CHANGED:  current dimension config "
                + dimensionChange.getSource().getLabel()
                + " with id "
                + dimensionChange.getSource().getId()
                + " is null! Meta change event?");
        return; // do nothing and return
      }
    }

    // and process event here too
    if (dimensionChange.getType() == DimensionConfigChangeType.RANGE) {
      clearCache();
    }
  }
  public LegendItemCollection getLegendItems(PlotInstance plotInstance) {
    PlotConfiguration plotConfiguration = plotInstance.getCurrentPlotConfigurationClone();
    LegendItemCollection legendItemCollection = new LegendItemCollection();

    // Get list of ValueSources which get an own legend entry.
    // These are those ValueSources, for which useSeriesFormatForDimension() returns true
    // for at least one dimension (not considering X and VALUE).

    // get all values sources
    List<ValueSource> allValueSources = plotConfiguration.getAllValueSources();

    // add the plots heading (if we have any value sources)
    if (!allValueSources.isEmpty()) {
      legendItemCollection.add(
          createTitleLegendItem(
              I18N.getGUILabel("plotter.legend.plots_heading.label") + ":", plotConfiguration));
    }

    // now find those value sources for which we need a legend entry,
    // and also remember which dimensions are shown in the legend entry (color, shape, ...)
    for (ValueSource valueSource : allValueSources) {
      CustomLegendItem legendItem = createValueSourceLegendItem(plotConfiguration, valueSource);
      if (legendItem != null) {
        legendItemCollection.add(legendItem);
      }
    }

    List<Set<PlotDimension>> dimensionsWithLegend =
        findCompatibleDimensions(plotConfiguration, allValueSources);

    // create legend items for DimensionConfigs
    for (Set<PlotDimension> dimensionSet : dimensionsWithLegend) {
      PlotDimension aDimension = dimensionSet.iterator().next();
      DimensionConfig dimensionConfig = plotConfiguration.getDimensionConfig(aDimension);

      createDimensionConfigLegendItem(
          plotInstance,
          (DefaultDimensionConfig) dimensionConfig,
          dimensionSet,
          legendItemCollection);
    }
    return legendItemCollection;
  }
  private static void assertMaxValueCountNotExceededOrThrowException(
      ValueSourceData valueSourceData) throws ChartPlottimeException {
    if (valueSourceData == null) {
      return;
    }
    int maxAllowedValueCount = PlotConfiguration.getMaxAllowedValueCount();

    for (GroupCellKeyAndData groupCellKeyAndData :
        valueSourceData.getSeriesDataForAllGroupCells()) {
      int size = groupCellKeyAndData.getData().getSize();
      if (size > maxAllowedValueCount) {
        throw new ChartPlottimeException(
            "too_many_values_in_plot", valueSourceData.getValueSource().toString());
      }
    }
  }
 /**
  * Returns the data table. If sampled is true, then a sampled data table is returned, which
  * contains at most as many rows as the rapidminer property rapidminer.gui.plotter.rows.maximum
  * suggests.
  *
  * <p>This method can be very slow. It updates the filtered data table if the current data table
  * is not valid. CAUTION: DONT use this method in the event dispatcher thread.
  */
 public DataTable getDataTable(boolean sampled) {
   SortedDataTableView currentDataTable = getDataTable();
   if (!sampled) {
     return currentDataTable;
   } else {
     if (cachedSampledDataTable == null) {
       if (currentDataTable == null) {
         return null;
       }
       int maxRowCount = PlotConfiguration.getMaxAllowedValueCount();
       if (currentDataTable.getRowNumber() <= maxRowCount) {
         cachedSampledDataTable = currentDataTable;
       } else {
         cachedSampledDataTable = currentDataTable.sample(maxRowCount);
       }
     }
     return cachedSampledDataTable;
   }
 }
  public PlotData(PlotInstance plotInstance, DataTable dataTable) {
    if (plotInstance == null) {
      throw new IllegalArgumentException("null not allowed for plotInstance");
    }
    this.plotInstance = plotInstance;
    plotInstance.setPlotData(this);
    PlotConfiguration plotConfiguration = plotInstance.getMasterPlotConfiguration();
    //		if (plotConfiguration.getPrioritizedListenerCount() > 0) {
    //			plotConfiguration.clearPrioritizedListeners();
    //		}
    plotConfiguration.addPlotConfigurationListener(this, true);

    this.originalDataTable = dataTable;
    originalDataTable.addDataTableListener(this, true);

    valueMappingDataTable = new ValueMappingDataTableView(originalDataTable);
    for (int i = 0; i < valueMappingDataTable.getColumnNumber(); ++i) {
      if (valueMappingDataTable.isNominal(i)) {
        valueMappingDataTable.setMappingProvider(
            i, new NominalSortingDataTableMapping(valueMappingDataTable, i, true));
      }
    }

    // add filtered data table view to view stack
    filteredDataTableView = new FilteredDataTable(valueMappingDataTable);

    // add sorted data table view on view stack (without sort provider for now)
    sortedDataTableView = new SortedDataTableView(filteredDataTableView, null);
    sortedDataTableView.addDataTableListener(this, true);

    // init valueSourceDataMap
    for (ValueSource valueSource : plotConfiguration.getAllValueSources()) {
      ValueSourceData valueSourceData = new ValueSourceData(valueSource, plotInstance);
      valueSourceDataMap.put(valueSource.getId(), valueSourceData);
    }

    // init dimensionConfigDataMap
    for (DefaultDimensionConfig dimensionConfig :
        plotConfiguration.getDefaultDimensionConfigs().values()) {
      DimensionConfigData dimensionConfigData =
          new DimensionConfigData(plotInstance, dimensionConfig);
      dimensionConfigDataMap.put(dimensionConfig.getId(), dimensionConfigData);
    }
    DefaultDimensionConfig domainConfig;
    domainConfig = plotConfiguration.getDomainConfigManager().getDomainConfig(true);
    dimensionConfigDataMap.put(
        domainConfig.getId(), new DimensionConfigData(plotInstance, domainConfig));
    domainConfig = plotConfiguration.getDomainConfigManager().getDomainConfig(false);
    dimensionConfigDataMap.put(
        domainConfig.getId(), new DimensionConfigData(plotInstance, domainConfig));

    // init DomainConfigManagerData
    domainConfigManagerData = new DomainConfigManagerData(plotInstance);

    // init RangeAxisDataMap
    for (RangeAxisConfig rangeAxisConfig : plotConfiguration.getRangeAxisConfigs()) {
      RangeAxisData rangeAxisData = new RangeAxisData(rangeAxisConfig, plotInstance);
      rangeAxisDataMap.put(rangeAxisConfig.getId(), rangeAxisData);
    }

    clearCache();
  }
  private void rangeAxisConfigChanged(RangeAxisConfigChangeEvent rangeAxisConfigChange) {

    PlotConfiguration currentPlotConfig =
        plotInstance.getCurrentPlotConfigurationClone(); // get current plot config
    int id = rangeAxisConfigChange.getSource().getId(); // fetch id
    RangeAxisConfig currentRangeAxisConfig =
        currentPlotConfig.getRangeAxisConfigById(id); // look up range axis config

    if (currentRangeAxisConfig == null) {
      // if current range axis config is null it has been deleted afterwards in a meta change event
      debug(
          "#### CAUTION #### RANGE AXIS CONFIG CHANGE: current range axis config "
              + rangeAxisConfigChange.getSource().getLabel()
              + " with id "
              + rangeAxisConfigChange.getSource().getId()
              + " is null! Meta change event?");
      return;
    }

    // inform range axis data
    RangeAxisData rangeAxisData = getRangeAxisData(currentRangeAxisConfig);
    rangeAxisData.rangeAxisConfigChanged(rangeAxisConfigChange);

    // and also process event here
    ValueSource changeValueSource = rangeAxisConfigChange.getValueSource();
    ValueSource currentValueSource = null;
    if (changeValueSource != null) {
      id = changeValueSource.getId(); // fetch id from value source add/remove event
      currentValueSource =
          currentRangeAxisConfig.getValueSourceById(id); // look up current value source
    }
    //		else {
    //			return; // nothing to be done
    //		}

    switch (rangeAxisConfigChange.getType()) {
      case VALUE_SOURCE_ADDED:
        if (currentValueSource != null) {
          debug(
              "value source ADDED - "
                  + currentValueSource.getLabel()
                  + " ## ID: "
                  + currentValueSource.getId());
          valueSourceDataMap.put(
              currentValueSource.getId(), new ValueSourceData(currentValueSource, plotInstance));
          clearCache();
        } else {
          // if current value source is null it has been deleted afterwards in a meta change event
          debug(
              "#### CAUTION #### VALUE SOURCE ADDED: current value source"
                  + changeValueSource.getLabel()
                  + " with id "
                  + changeValueSource.getId()
                  + " is null! Meta change event?");
          return; // nothing to be done
        }
        break;
      case VALUE_SOURCE_CHANGED:
        ValueSourceChangeEvent valueSourceChange = rangeAxisConfigChange.getValueSourceChange();
        changeValueSource = valueSourceChange.getSource(); // get source
        id = changeValueSource.getId(); // fetch id from changed value source
        currentValueSource =
            currentRangeAxisConfig.getValueSourceById(id); // look up current value source

        if (currentValueSource != null) {
          debug("value source CHANGED - " + currentValueSource.getLabel() + " ## ID: " + id);
          getValueSourceData(currentValueSource)
              .valueSourceChanged(valueSourceChange, currentValueSource);
        } else {
          // if current value source is null it has been deleted afterwards in a meta change event
          debug(
              "#### CAUTION #### VALUE SOURCE CHANGED: current value source"
                  + changeValueSource.getLabel()
                  + " with id "
                  + changeValueSource.getId()
                  + " is null! Meta change event?");
          return; // nothing to be done
        }
        break;
      case VALUE_SOURCE_REMOVED:
        debug(
            "value source REMOVED - "
                + changeValueSource.getLabel()
                + " ## ID: "
                + changeValueSource.getId());
        valueSourceDataMap.remove(changeValueSource.getId());
        clearCache();
        break;
    }
  }
  @Override
  public synchronized boolean plotConfigurationChanged(PlotConfigurationChangeEvent change) {
    if (change == null || change == lastProcessedEvent) {
      return true;
    }
    lastProcessedEvent = change;

    PlotConfiguration currentPlotConfig =
        plotInstance.getCurrentPlotConfigurationClone(); // get current plot config

    // prepare temp variables
    int id = -1;
    DimensionConfig currentDimensionConfig = null;
    RangeAxisConfig currentRangeAxis = null;

    DimensionConfig changeDimensionConfig = change.getDimensionConfig();
    if (changeDimensionConfig != null) {
      // if event is a dimension config add/remove event, get current dimension config
      // (may be null if meta event is processed and it has been deleted afterwards)
      id = changeDimensionConfig.getId();
      currentDimensionConfig = currentPlotConfig.getDefaultDimensionConfigById(id);
    }

    RangeAxisConfig changeRangeAxis = change.getRangeAxisConfig();
    if (changeRangeAxis != null) {
      // if event is a range axis config add/remove event, get current range axis config
      // (may be null if meta event is processed and it has been deleted afterwards)
      id = changeRangeAxis.getId();
      currentRangeAxis = currentPlotConfig.getRangeAxisConfigById(id);
    }

    switch (change.getType()) {
      case TRIGGER_REPLOT:
        clearCache();
        break;
      case AXES_FONT:
        break;
      case AXIS_LINE_COLOR:
        break;
      case AXIS_LINE_WIDTH:
        break;
      case FRAME_BACKGROUND_COLOR:
        break;
      case CHART_TITLE:
        break;
      case COLOR_SCHEME:
        break;
      case DATA_TABLE_EXCHANGED:
        break;
      case DIMENSION_CONFIG_ADDED:
        // if current plot configuration still contains item..
        if (currentDimensionConfig != null && id != -1) {
          // add new dimension config data to map
          dimensionConfigDataMap.put(
              id,
              new DimensionConfigData(
                  plotInstance, (DefaultDimensionConfig) currentDimensionConfig));
          debug(
              "ADDED dimension "
                  + currentDimensionConfig.getDimension().getName()
                  + " ## ID: "
                  + id);
          clearCache();
        } else {
          debug(
              "#### CAUTION ###### ADD DIMENSION CONFIG: CURRENT DIMENSIONCONFIG "
                  + changeDimensionConfig.getLabel()
                  + " with id "
                  + changeDimensionConfig.getId()
                  + " IS NULL! Processing meta event?");
        }
        break;
      case DIMENSION_CONFIG_CHANGED:
        dimensionConfigChanged(change.getDimensionChange());
        break;
      case DIMENSION_CONFIG_REMOVED:
        debug(
            "REMOVED dimension "
                + changeDimensionConfig.getDimension().getName()
                + " ## ID: "
                + changeDimensionConfig.getId());
        dimensionConfigDataMap.remove(
            changeDimensionConfig.getId()); // remove dimension config data from map
        clearCache();
        break;
      case LEGEND_CHANGED:
        break;
      case PLOT_BACKGROUND_COLOR:
        break;
      case PLOT_ORIENTATION:
        break;
      case RANGE_AXIS_CONFIG_ADDED:
        // if current plot configuration still contains item..
        if (currentRangeAxis != null && id != -1) {
          // add new range axis data to map
          debug("range axis ADDED - " + currentRangeAxis.getLabel() + " ## ID: " + id);
          rangeAxisDataMap.put(id, new RangeAxisData(currentRangeAxis, plotInstance));
          for (ValueSource valueSource :
              currentRangeAxis.getValueSources()) { // also add containing value sources data to map
            debug(
                "value source ADDED - "
                    + valueSource.getLabel()
                    + " ## ID: "
                    + valueSource.getId());
            valueSourceDataMap.put(
                valueSource.getId(), new ValueSourceData(valueSource, plotInstance));
          }
        } else {
          debug(
              "#### CAUTION ###### ADD RANGE AXIS CONFIG: CURRENT RANGEAXISCONFIG "
                  + changeRangeAxis.getLabel()
                  + " with id "
                  + changeRangeAxis.getId()
                  + " IS NULL! Processing meta event?");
        }
        break;
      case RANGE_AXIS_CONFIG_CHANGED:
        RangeAxisConfigChangeEvent rangeAxisConfigChange = change.getRangeAxisConfigChange();
        debug(
            "range axis CHANGED - "
                + rangeAxisConfigChange.getSource().getLabel()
                + " ## ID: "
                + rangeAxisConfigChange.getSource().getId());
        rangeAxisConfigChanged(rangeAxisConfigChange);
        break;
      case RANGE_AXIS_CONFIG_MOVED:
        break;
      case RANGE_AXIS_CONFIG_REMOVED:
        RangeAxisConfig rangeAxis = change.getRangeAxisConfig();
        debug("range axis REMOVED - " + rangeAxis.getLabel() + " ## ID: " + rangeAxis.getId());
        rangeAxisDataMap.remove(rangeAxis.getId()); // remove range axis config from map
        for (ValueSource valueSource :
            rangeAxis.getValueSources()) { // also remove all containing value sources from data map
          valueSourceDataMap.remove(valueSource.getId());
        }
        clearCache();
        break;
      case LINK_AND_BRUSH_SELECTION:
        break;
      case META_CHANGE:
        for (PlotConfigurationChangeEvent e : change.getPlotConfigChangeEvents()) {
          plotConfigurationChanged(e);
        }
        break;
    }

    return true;
  }
  @Override
  protected void updatePlotConfiguration() {
    // don't do anything if updates are suspended due to batch updating
    if (suspendUpdates) {
      return;
    }

    PlotConfiguration plotConfiguration = plotInstance.getMasterPlotConfiguration();

    // value when "None" is selected
    String noSelection =
        I18N.getMessage(I18N.getGUIBundle(), "gui.plotter.column.empty_selection.label");

    // stop event processing
    boolean plotConfigurationProcessedEvents = plotConfiguration.isProcessingEvents();
    plotConfiguration.setProcessEvents(false);

    // restore crosshairs
    List<AxisParallelLineConfiguration> clonedListOfDomainLines =
        new LinkedList<AxisParallelLineConfiguration>(listOfDomainLines);
    for (AxisParallelLineConfiguration lineConfig : clonedListOfDomainLines) {
      plotConfiguration.getDomainConfigManager().getCrosshairLines().addLine(lineConfig);
    }

    // x axis column selection
    if (!xAxisColumn.equals(noSelection)) {
      plotConfiguration
          .getDimensionConfig(PlotDimension.DOMAIN)
          .setDataTableColumn(
              new DataTableColumn(currentDataTable, currentDataTable.getColumnIndex(xAxisColumn)));
      plotConfiguration.getDimensionConfig(PlotDimension.DOMAIN).setLogarithmic(xAxisLogarithmic);
    } else {
      // remove config
      if (currentRangeAxisConfig != null) {
        currentRangeAxisConfig.removeRangeAxisConfigListener(rangeAxisConfigListener);
        plotConfiguration.removeRangeAxisConfig(currentRangeAxisConfig);
        currentRangeAxisConfig = null;
      }
      plotConfiguration.setProcessEvents(plotConfigurationProcessedEvents);
      return;
    }

    // y axis column selection
    if (!yAxisColumn.equals(noSelection)) {
      RangeAxisConfig newRangeAxisConfig = new RangeAxisConfig(yAxisColumn, plotConfiguration);
      newRangeAxisConfig.addRangeAxisConfigListener(rangeAxisConfigListener);
      ValueSource valueSource;
      valueSource =
          new ValueSource(
              plotConfiguration,
              new DataTableColumn(currentDataTable, currentDataTable.getColumnIndex(yAxisColumn)),
              AggregationFunctionType.count,
              false);
      SeriesFormat sFormat = new SeriesFormat();
      valueSource.setSeriesFormat(sFormat);
      newRangeAxisConfig.addValueSource(valueSource, null);
      newRangeAxisConfig.setLogarithmicAxis(yAxisLogarithmic);

      // remove old config
      if (currentRangeAxisConfig != null) {
        currentRangeAxisConfig.removeRangeAxisConfigListener(rangeAxisConfigListener);
        plotConfiguration.removeRangeAxisConfig(currentRangeAxisConfig);
      }
      currentRangeAxisConfig = newRangeAxisConfig;
      // add new config and restore crosshairs
      List<AxisParallelLineConfiguration> clonedRangeAxisLineList =
          rangeAxisCrosshairLinesMap.get(newRangeAxisConfig.getLabel());
      if (clonedRangeAxisLineList != null) {
        for (AxisParallelLineConfiguration lineConfig : clonedRangeAxisLineList) {
          newRangeAxisConfig.getCrossHairLines().addLine(lineConfig);
        }
      }
      plotConfiguration.addRangeAxisConfig(newRangeAxisConfig);
      // remember the new config so we can remove it later again
    } else {
      // remove config
      if (currentRangeAxisConfig != null) {
        currentRangeAxisConfig.removeRangeAxisConfigListener(rangeAxisConfigListener);
        plotConfiguration.removeRangeAxisConfig(currentRangeAxisConfig);
        currentRangeAxisConfig = null;
      }
    }

    // color column selection
    if (!colorColumn.equals(noSelection)) {
      plotConfiguration.setDimensionConfig(PlotDimension.COLOR, null);
      DefaultDimensionConfig dimConfig =
          new DefaultDimensionConfig(
              plotConfiguration,
              new DataTableColumn(currentDataTable, currentDataTable.getColumnIndex(colorColumn)),
              PlotDimension.COLOR);
      dimConfig.setLogarithmic(colorLogarithmic);
      plotConfiguration.setDimensionConfig(PlotDimension.COLOR, dimConfig);
    } else {
      plotConfiguration.setDimensionConfig(PlotDimension.COLOR, null);
    }

    // general settings
    plotConfiguration.setAxesFont(styleProvider.getAxesFont());
    plotConfiguration.setTitleFont(styleProvider.getTitleFont());
    plotConfiguration.getLegendConfiguration().setLegendFont(styleProvider.getLegendFont());
    plotConfiguration.addColorSchemeAndSetActive(styleProvider.getColorScheme());
    if (styleProvider.isShowLegend()) {
      plotConfiguration.getLegendConfiguration().setLegendPosition(LegendPosition.BOTTOM);
    } else {
      plotConfiguration.getLegendConfiguration().setLegendPosition(LegendPosition.NONE);
    }
    plotConfiguration.setFrameBackgroundColor(
        ColorRGB.convertToColor(styleProvider.getFrameBackgroundColor()));
    plotConfiguration.setPlotBackgroundColor(
        ColorRGB.convertToColor(styleProvider.getPlotBackgroundColor()));
    plotConfiguration.setTitleText(styleProvider.getTitleText());

    // continue event processing
    plotConfiguration.setProcessEvents(plotConfigurationProcessedEvents);
  }
  private void createCategoricalLegendItems(
      PlotInstance plotInstance,
      Set<PlotDimension> dimensionSet,
      LegendItemCollection legendItemCollection,
      Iterable<Double> values) {
    createDimensionTitleLegendItem(plotInstance, dimensionSet, legendItemCollection);

    PlotConfiguration plotConfig = plotInstance.getCurrentPlotConfigurationClone();

    Shape defaultShape = new Ellipse2D.Float(-5f, -5f, 10f, 10f);
    Color defaultOutlineColor = PlotConfiguration.DEFAULT_OUTLINE_COLOR;
    ColorProvider colorProvider = null;
    ShapeProvider shapeProvider = null;
    SizeProvider sizeProvider = null;

    DefaultDimensionConfig dimensionConfig =
        (DefaultDimensionConfig) plotConfig.getDimensionConfig(dimensionSet.iterator().next());
    DimensionConfigData dimensionConfigData =
        plotInstance.getPlotData().getDimensionConfigData(dimensionConfig);
    for (PlotDimension dimension : dimensionSet) {
      if (dimension == PlotDimension.COLOR) {
        colorProvider = dimensionConfigData.getColorProvider();
      } else if (dimension == PlotDimension.SHAPE) {
        shapeProvider = dimensionConfigData.getShapeProvider();
      } else if (dimension == PlotDimension.SIZE) {
        sizeProvider = dimensionConfigData.getSizeProvider();
      }
    }

    // initialize size scale for legend
    ContinuousSizeProvider legendSizeProvider = null;
    if (sizeProvider != null) {
      double minScalingFactor = sizeProvider.getMinScalingFactor();
      double maxScalingFactor = sizeProvider.getMaxScalingFactor();
      double minLegendScalingFactor = MIN_LEGEND_ITEM_SCALING_FACTOR;
      double maxLegendScalingFactor = MAX_LEGEND_ITEM_SCALING_FACTOR;
      if (minScalingFactor > maxScalingFactor) {
        double tmp = minScalingFactor;
        minScalingFactor = maxScalingFactor;
        maxScalingFactor = tmp;
        minLegendScalingFactor = MAX_LEGEND_ITEM_SCALING_FACTOR;
        maxLegendScalingFactor = MIN_LEGEND_ITEM_SCALING_FACTOR;
      }
      legendSizeProvider =
          new ContinuousSizeProvider(
              minScalingFactor,
              maxScalingFactor,
              minLegendScalingFactor,
              maxLegendScalingFactor,
              false);
    }

    for (Double value : values) {
      // configure shape and stroke
      Shape shape = defaultShape;
      BasicStroke outlineStroke;
      Color outlineColor = new Color(0, 0, 0, 0);
      if (shapeProvider != null) {
        shape = shapeProvider.getShapeForCategory(value);
        outlineStroke = DEFAULT_OUTLINE_STROKE;
        outlineColor = defaultOutlineColor;
      } else {
        outlineStroke = new BasicStroke();
        if (colorProvider != null) {
          shape = UNDEFINED_SHAPE;
        } else {
          shape = UNDEFINED_SHAPE_AND_COLOR;
        }
      }

      // configure fill paint
      Paint paint = UNDEFINED_COLOR_PAINT;
      if (colorProvider != null) {
        paint = colorProvider.getColorForValue(value);
      }

      double scalingFactor = 1;
      if (sizeProvider != null) {
        // scale shape according to sizeProvider
        scalingFactor = sizeProvider.getScalingFactorForValue(value);
        // scale shape to fit into legend
        scalingFactor = legendSizeProvider.getScalingFactorForValue(scalingFactor);
        AffineTransform transformation = new AffineTransform();
        transformation.scale(scalingFactor, scalingFactor);
        shape = transformation.createTransformedShape(shape);
      }

      String label = dimensionConfigData.getStringForValue(value);
      if (label == null) {
        label = "";
      }

      CustomLegendItem legendItem =
          new CustomLegendItem(label, null, null, null, shape, paint, outlineStroke, outlineColor);
      legendItemCollection.add(legendItem);
    }
  }
  /**
   * Creates a continuous legend item for one item in dimensionSet, i.e. dimensionSet must be a set
   * containing exactly one value.
   *
   * @param dateFormat format used to format minValue and maxValue as dates, or null if they should
   *     be displayed numerically instead of as dates
   * @throws ChartPlottimeException
   */
  private LegendItem createContinuousLegendItem(
      PlotInstance plotInstance,
      Set<PlotDimension> dimensionSet,
      double minValue,
      double maxValue,
      DateFormat dateFormat) {
    PlotConfiguration plotConfiguration = plotInstance.getCurrentPlotConfigurationClone();
    PlotDimension dimension = dimensionSet.iterator().next();
    DefaultDimensionConfig dimensionConfig =
        (DefaultDimensionConfig) plotConfiguration.getDimensionConfig(dimension);
    DimensionConfigData dimensionConfigData =
        plotInstance.getPlotData().getDimensionConfigData(dimensionConfig);
    // String label = dimensionConfig.getLabel();
    // if(label == null) {
    // label = I18N.getGUILabel("plotter.unnamed_value_label");
    // }
    String label = "";

    if (dimension == PlotDimension.COLOR) {
      ColorProvider colorProvider = dimensionConfigData.getColorProvider();
      if (!colorProvider.supportsNumericalValues()) {
        throw new RuntimeException(
            "Color provider for continuous legend item does not support numerical values.");
      }

      // shape dimensions
      final int width = 50;
      final int height = 10;

      // create item paint

      // first disable logarithmic scale on color provider ( -> linear gradient in legend)
      // ContinuousColorProvider continuousColorProvider = null;
      // if (dimensionConfig.isLogarithmic() && colorProvider instanceof
      // ContinuousColorProvider) {
      // continuousColorProvider = (ContinuousColorProvider)colorProvider;
      // continuousColorProvider.setLogarithmic(false);
      // }

      // calculate gradient
      float fractions[] = new float[width];
      Color colors[] = new Color[width];
      for (int i = 0; i < width; ++i) {

        float fraction = i / (width - 1.0f);
        double fractionValue;
        if (colorProvider instanceof ContinuousColorProvider
            && ((ContinuousColorProvider) colorProvider)
                .isColorMinMaxValueDifferentFromOriginal(
                    ((ContinuousColorProvider) colorProvider).getMinValue(),
                    ((ContinuousColorProvider) colorProvider).getMaxValue())) {
          fractionValue =
              ((ContinuousColorProvider) colorProvider).getMinValue()
                  + fraction
                      * (((ContinuousColorProvider) colorProvider).getMaxValue()
                          - ((ContinuousColorProvider) colorProvider).getMinValue());
        } else {
          fractionValue = minValue + fraction * (maxValue - minValue);
        }
        colors[i] = colorProvider.getColorForValue(fractionValue);
        fractions[i] = fraction;
      }
      LinearGradientPaint shapeFillPaint =
          new LinearGradientPaint(
              new Point(0, 0), new Point(width, 0), fractions, colors, CycleMethod.REPEAT);

      // reset color provider to logarithmic if necessary
      // if (continuousColorProvider != null && dimensionConfig.isLogarithmic()) {
      // continuousColorProvider.setLogarithmic(true);
      // }

      // create item shape
      Rectangle itemShape = new Rectangle(width, height);

      if (colorProvider instanceof ContinuousColorProvider) {
        return createFlankedShapeLegendItem(
            label,
            ((ContinuousColorProvider) colorProvider).getMinValue(),
            ((ContinuousColorProvider) colorProvider).getMaxValue(),
            itemShape,
            shapeFillPaint,
            true,
            dateFormat);
      } else {
        return createFlankedShapeLegendItem(
            label, minValue, maxValue, itemShape, shapeFillPaint, true, dateFormat);
      }
    } else if (dimension == PlotDimension.SHAPE) {
      // shape provider probably never supports numerical values
      return null;
    } else if (dimension == PlotDimension.SIZE) {
      SizeProvider sizeProvider = dimensionConfigData.getSizeProvider();

      if (!sizeProvider.supportsNumericalValues()) {
        throw new RuntimeException(
            "Size provider for continuous legend item does not support numerical values.");
      }

      double minScalingFactor = sizeProvider.getMinScalingFactor();
      double maxScalingFactor = sizeProvider.getMaxScalingFactor();
      ContinuousSizeProvider legendSizeProvider =
          new ContinuousSizeProvider(
              minScalingFactor,
              maxScalingFactor,
              MIN_LEGEND_ITEM_SCALING_FACTOR,
              MAX_LEGEND_ITEM_SCALING_FACTOR,
              false);

      int legendItemCount = 4;
      Area composedShape = new Area();
      Shape originalShape = UNDEFINED_SHAPE;
      if (dimensionSet.contains(PlotDimension.SIZE) && dimensionSet.size() == 1) {
        originalShape = UNDEFINED_SHAPE_AND_COLOR;
      }
      double maxHeight = originalShape.getBounds().getHeight() * MAX_LEGEND_ITEM_SCALING_FACTOR;
      for (int i = 0; i < legendItemCount; ++i) {
        double fraction =
            minScalingFactor
                + ((double) i / legendItemCount * (maxScalingFactor - minScalingFactor));
        double legendScalingFactor = legendSizeProvider.getScalingFactorForValue(fraction);

        double composedWidth = composedShape.getBounds().getWidth();

        AffineTransform t = new AffineTransform();
        t.scale(legendScalingFactor, legendScalingFactor);
        Shape shape = t.createTransformedShape(originalShape);

        t = new AffineTransform();
        double shapeWidth = shape.getBounds().getWidth();
        double shapeHeight = shape.getBounds().getHeight();
        t.translate(composedWidth + shapeWidth * .1, (maxHeight - shapeHeight) / 2.0);
        t.translate(-shape.getBounds().getMinX(), -shape.getBounds().getMinY());
        shape = t.createTransformedShape(shape);
        composedShape.add(new Area(shape));
      }

      return createFlankedShapeLegendItem(
          label, minValue, maxValue, composedShape, UNDEFINED_COLOR_PAINT, false, dateFormat);

    } else {
      throw new RuntimeException(
          "Unsupported dimension. Execution path should never reach this line.");
    }
  }
  private void createDimensionConfigLegendItem(
      PlotInstance plotInstance,
      DefaultDimensionConfig dimensionConfig,
      Set<PlotDimension> dimensionSet,
      LegendItemCollection legendItemCollection) {
    PlotConfiguration plotConfiguration = plotInstance.getCurrentPlotConfigurationClone();
    DimensionConfigData dimensionConfigData =
        plotInstance.getPlotData().getDimensionConfigData(dimensionConfig);
    if (dimensionConfig.isGrouping()) {
      // create legend entry based on the grouping
      if (dimensionConfig.isNominal()) {
        // create categorical legend --> one item for each category
        createCategoricalLegendItems(
            plotInstance,
            dimensionSet,
            legendItemCollection,
            dimensionConfigData.getDistinctValues());
      } else if (dimensionConfig.isNumerical() || dimensionConfig.isDate()) {
        createDimensionTitleLegendItem(plotInstance, dimensionSet, legendItemCollection);

        // create one continuous legend item
        double minValue = dimensionConfigData.getMinValue();
        double maxValue = dimensionConfigData.getMaxValue();

        LegendItem legendItem =
            createContinuousLegendItem(
                plotInstance,
                dimensionSet,
                minValue,
                maxValue,
                dimensionConfig.isDate() ? dimensionConfig.getDateFormat() : null);
        if (legendItem != null) {
          legendItemCollection.add(legendItem);
        }
      } else {
        throw new RuntimeException(
            "unknown data type during legend creation - this should not happen");
      }
    } else {
      // dimension config not grouping --> create legend item only, if there exists
      // at least one non-aggregated value source (otherwise the dimension config is
      // not used at all in the plot and thus we also don't need a legend item for it).
      boolean createLegend = false;
      for (ValueSource valueSource : plotConfiguration.getAllValueSources()) {
        if (!valueSource.isUsingDomainGrouping()) {
          createLegend = true;
          break;
        }
      }
      if (createLegend) {
        // create legend based on the attribute values on the dimension config
        if (dimensionConfig.isNominal()) {
          // create one legend item for each nominal value
          List<Double> values = dimensionConfigData.getDistinctValues();
          createCategoricalLegendItems(plotInstance, dimensionSet, legendItemCollection, values);
        } else if (dimensionConfig.isNumerical() || dimensionConfig.isDate()) {
          createDimensionTitleLegendItem(plotInstance, dimensionSet, legendItemCollection);

          // create one continuous legend item for the value range
          double minValue = dimensionConfigData.getMinValue();
          double maxValue = dimensionConfigData.getMaxValue();

          LegendItem legendItem =
              createContinuousLegendItem(
                  plotInstance,
                  dimensionSet,
                  minValue,
                  maxValue,
                  dimensionConfig.isDate() ? dimensionConfig.getDateFormat() : null);
          if (legendItem != null) {
            legendItemCollection.add(legendItem);
          }
        } else {
          throw new RuntimeException(
              "unknown data type during legend creation - this should not happen");
        }
      }
    }
  }
  private List<Set<PlotDimension>> findCompatibleDimensions(
      PlotConfiguration plotConfiguration, List<ValueSource> allValueSources) {
    Map<PlotDimension, DefaultDimensionConfig> dimensionConfigMap =
        plotConfiguration.getDefaultDimensionConfigs();

    // find all Dimensions for which we create a legend item
    List<Set<PlotDimension>> dimensionsWithLegend = new LinkedList<Set<PlotDimension>>();
    for (Entry<PlotDimension, DefaultDimensionConfig> dimensionEntry :
        dimensionConfigMap.entrySet()) {
      PlotDimension dimension = dimensionEntry.getKey();
      DefaultDimensionConfig dimensionConfig = dimensionEntry.getValue();

      boolean createLegend = false;
      if (dimensionConfig.isGrouping()) {
        createLegend = true;
      } else {
        for (ValueSource valueSource : allValueSources) {
          if (!valueSource.isUsingDomainGrouping()) {
            createLegend = true;
            break;
          }
        }
      }

      if (createLegend) {
        if (!dimensionConfig.isNominal()) {
          Set<PlotDimension> newSet = new HashSet<DimensionConfig.PlotDimension>();
          newSet.add(dimension);
          dimensionsWithLegend.add(newSet);
        } else {
          // iterate over list and find dimensions with compatible properties
          boolean compatibleToSomething = false;
          for (Set<PlotDimension> dimensionSet : dimensionsWithLegend) {
            boolean compatible = true;
            for (PlotDimension comparedDimension : dimensionSet) {
              DefaultDimensionConfig comparedDimensionConfig =
                  (DefaultDimensionConfig) plotConfiguration.getDimensionConfig(comparedDimension);
              if (!comparedDimensionConfig.isNominal()) {
                compatible = false;
                break;
              }
              if (!dimensionConfig
                  .getDataTableColumn()
                  .equals(comparedDimensionConfig.getDataTableColumn())) {
                compatible = false;
                break;
              } else if (comparedDimensionConfig.isGrouping()
                  && comparedDimensionConfig.getGrouping() instanceof DistinctValueGrouping
                  && !dimensionConfig.isGrouping()
                  && dimensionConfig.isNominal()) {
                compatible = true;
              } else if (dimensionConfig.isGrouping()
                  && dimensionConfig.getGrouping() instanceof DistinctValueGrouping
                  && !comparedDimensionConfig.isGrouping()
                  && comparedDimensionConfig.isNominal()) {
                compatible = true;
              } else if (dimensionConfig.isGrouping() != comparedDimensionConfig.isGrouping()) {
                compatible = false;
                break;
              } else if (!dimensionConfig.isGrouping()) {
                compatible = true;
              } else if (dimensionConfig
                  .getGrouping()
                  .equals(comparedDimensionConfig.getGrouping())) {
                compatible = true;
              } else {
                compatible = false;
                break;
              }
            }
            if (compatible) {
              dimensionSet.add(dimension);
              compatibleToSomething = true;
              break;
            }
          }
          if (!compatibleToSomething) {
            Set<PlotDimension> newSet = new HashSet<DimensionConfig.PlotDimension>();
            newSet.add(dimension);
            dimensionsWithLegend.add(newSet);
          }
        }
      }
    }
    return dimensionsWithLegend;
  }