/**
   * Updates the filters of the filteredDataTableView so that the data is filtered by the user
   * defined ranges (if applicable) of the {@link DimensionConfig}s.
   */
  private void updateFilteredDataTable() {
    cachedSampledDataTable = null;
    List<ValueRange> dimensionRanges = new LinkedList<ValueRange>();
    for (PlotDimension dimension : PlotDimension.values()) {
      DimensionConfig dimConf =
          plotInstance.getCurrentPlotConfigurationClone().getDimensionConfig(dimension);
      if (dimConf != null) {
        if (dimConf.isUsingUserDefinedLowerBound() || dimConf.isUsingUserDefinedUpperBound()) {
          ValueRange dimensionRange =
              dimConf.getUserDefinedRangeClone(filteredDataTableView.getParentTable());
          if (dimensionRange instanceof NumericalValueRange) {
            NumericalValueRange numericalDimensionRange = (NumericalValueRange) dimensionRange;
            if (!dimConf.isUsingUserDefinedLowerBound()) {
              numericalDimensionRange.setLowerBound(Double.NEGATIVE_INFINITY);
            }
            if (!dimConf.isUsingUserDefinedUpperBound()) {
              numericalDimensionRange.setUpperBound(Double.POSITIVE_INFINITY);
            }
          }
          if (dimensionRange != null) {
            dimensionRanges.add(dimensionRange);
          }
        }
      }
    }

    filteredDataTableView.replaceConditions(dimensionRanges);

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

    for (ValueSourceData valueSourceData : getValueSourcesData()) {
      errors.addAll(valueSourceData.getErrors());
    }

    for (DimensionConfigData data : dimensionConfigDataMap.values()) {
      if (data.getDimensionConfig().getDimension() == PlotDimension.DOMAIN) {
        continue;
      } else {
        List<PlotConfigurationError> error = data.getErrors();
        errors.addAll(error);
      }
    }

    // check domain dimension for errors depending on grouping state
    DomainConfigManager domainConfigManager =
        plotInstance.getCurrentPlotConfigurationClone().getDomainConfigManager();
    GroupingState groupingState = domainConfigManager.getGroupingState();
    switch (groupingState) {
      case GROUPED:
        errors.addAll(
            getDimensionConfigData(domainConfigManager.getDomainConfig(true)).getErrors());
        break;
      case PARTIALLY_GROUPED:
        errors.addAll(
            getDimensionConfigData(domainConfigManager.getDomainConfig(true)).getErrors());
        errors.addAll(
            getDimensionConfigData(domainConfigManager.getDomainConfig(false)).getErrors());
        break;
      case UNGROUPED:
        errors.addAll(
            getDimensionConfigData(domainConfigManager.getDomainConfig(false)).getErrors());
        break;
    }

    for (RangeAxisData data : rangeAxisDataMap.values()) {
      errors.addAll(data.getErrors());
    }

    errors.addAll(domainConfigManagerData.getErrors());

    return errors;
  }
  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 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;
  }