/**
* Copyright (C) 2001-2017 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 com.rapidminer.datatable.DataTable;
import com.rapidminer.gui.new_plotter.ChartConfigurationException;
import com.rapidminer.gui.new_plotter.PlotConfigurationError;
import com.rapidminer.gui.new_plotter.configuration.DataTableColumn.ValueType;
import com.rapidminer.gui.new_plotter.configuration.ValueGrouping.GroupingType;
import com.rapidminer.gui.new_plotter.configuration.ValueGrouping.ValueGroupingFactory;
import com.rapidminer.gui.new_plotter.configuration.event.AxisParallelLinesConfigurationChangeEvent;
import com.rapidminer.gui.new_plotter.listener.DimensionConfigListener;
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.utility.NumericalValueRange;
import com.rapidminer.gui.new_plotter.utility.ValueRange;
import java.text.DateFormat;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;
import java.util.Vector;
/**
* A class which manages the domain groupings of all {@link ValueSource}s in a
* {@link PlotConfiguration}. This manager is necessary, because switching between grouping types is
* not possible all the time because of some constraints. The DimensionConfigManager enforces these
* constraints.
*
* @author Marius Helf, Nils Woehler
*
*/
public class DomainConfigManager extends AbstractDimensionConfig implements DimensionConfigListener, Cloneable,
AxisParallelLinesConfigurationListener {
public static enum Sorting {
NONE, ASCENDING
}
public enum GroupingState {
GROUPED, UNGROUPED, PARTIALLY_GROUPED
}
private static final Sorting DEFAULT_SORTING_MODE = Sorting.ASCENDING;
private DefaultDimensionConfig ungroupedMasterDimensionConfig, groupedMasterDimensionConfig;
private PlotConfiguration plotConfiguration;
private Sorting sortingMode = DEFAULT_SORTING_MODE;
private AxisParallelLinesConfiguration crosshairLines = new AxisParallelLinesConfiguration();
private final int id;
public DomainConfigManager(PlotConfiguration plotConfiguration, DataTableColumn domainColumn) {
super(PlotDimension.DOMAIN);
if (plotConfiguration == null) {
throw new IllegalArgumentException("null plotConfiguration not allowed.");
}
this.id = plotConfiguration.getNextId();
this.plotConfiguration = plotConfiguration;
ungroupedMasterDimensionConfig = new DefaultDimensionConfig(plotConfiguration, domainColumn, getDimension());
groupedMasterDimensionConfig = new DefaultDimensionConfig(plotConfiguration, domainColumn, getDimension());
try {
groupedMasterDimensionConfig.setGrouping(new EquidistantFixedBinCountBinning(5, Double.NaN, Double.NaN,
domainColumn, domainColumn.isNominal(), getDateFormat()));
} catch (ChartConfigurationException e) {
groupedMasterDimensionConfig.setGrouping(new DistinctValueGrouping(domainColumn, domainColumn.isNominal(),
getDateFormat()));
}
groupedMasterDimensionConfig.addDimensionConfigListener(this);
crosshairLines.addAxisParallelLinesConfigurationListener(this);
}
/**
* Private ctor, used only by the clone method.
*/
private DomainConfigManager(int id) {
super(PlotDimension.DOMAIN);
ungroupedMasterDimensionConfig = null;
groupedMasterDimensionConfig = null;
plotConfiguration = null;
crosshairLines.addAxisParallelLinesConfigurationListener(this);
this.id = id;
}
public DefaultDimensionConfig getDomainConfig(boolean grouped) {
if (grouped) {
return groupedMasterDimensionConfig;
} else {
return ungroupedMasterDimensionConfig;
}
}
public boolean isLogarithmicDomainAxis() {
return groupedMasterDimensionConfig.isLogarithmic();
}
@Override
public ValueRange getUserDefinedRangeClone(DataTable dataTable) {
return groupedMasterDimensionConfig.getUserDefinedRangeClone(dataTable);
}
public DataTableColumn getDomainColumn() {
return ungroupedMasterDimensionConfig.getDataTableColumn();
}
@Override
public PlotDimension getDimension() {
return PlotDimension.DOMAIN;
}
@Override
public DataTableColumn getDataTableColumn() {
return groupedMasterDimensionConfig.getDataTableColumn();
}
@Override
public ValueGrouping getGrouping() {
return groupedMasterDimensionConfig.getGrouping();
}
@Override
public String getLabel() {
return groupedMasterDimensionConfig.getLabel();
}
@Override
public boolean isAutoRangeRequired() {
return groupedMasterDimensionConfig.isAutoRangeRequired();
}
@Override
public boolean isLogarithmic() {
return groupedMasterDimensionConfig.isLogarithmic();
}
@Override
public boolean isAutoNaming() {
return groupedMasterDimensionConfig.isAutoNaming();
}
@Override
public void setDataTableColumn(DataTableColumn column) {
if (column == null) {
throw new IllegalArgumentException("Null Domain columns are not allowed");
}
if (column.isNominal()
&& groupedMasterDimensionConfig.getGrouping().getGroupingType() != GroupingType.DISTINCT_VALUES) {
setFireEvents(false);
try {
setGrouping(ValueGroupingFactory.getValueGrouping(GroupingType.DISTINCT_VALUES, getDataTableColumn(), true,
getDateFormat()));
} catch (ChartConfigurationException e) {
throw new RuntimeException("Could not create grouping. This should not happen");
}
setFireEvents(true);
}
ungroupedMasterDimensionConfig.setDataTableColumn(column);
groupedMasterDimensionConfig.setDataTableColumn(column);
}
public Sorting getSortingMode() {
return sortingMode;
}
public void setSortingMode(Sorting sortingMode) {
if (sortingMode != this.sortingMode) {
this.sortingMode = sortingMode;
fireDimensionConfigChanged(new DimensionConfigChangeEvent(this, PlotDimension.DOMAIN, sortingMode));
}
}
@Override
public void setUserDefinedRange(NumericalValueRange range) {
ungroupedMasterDimensionConfig.setUserDefinedRange(range);
groupedMasterDimensionConfig.setUserDefinedRange(range);
}
@Override
public void setLogarithmic(boolean logarithmic) {
ungroupedMasterDimensionConfig.setLogarithmic(logarithmic);
groupedMasterDimensionConfig.setLogarithmic(logarithmic);
}
@Override
public void setAutoNaming(boolean autoNaming) {
groupedMasterDimensionConfig.setAutoNaming(autoNaming);
}
@Override
public void setLabel(String label) {
groupedMasterDimensionConfig.setLabel(label);
}
@Override
public List<PlotConfigurationError> getErrors() {
// TODO check all preconditions
// TODO additional checks if valueType is NOMINAL (compatible categories etc.)
LinkedList<PlotConfigurationError> errors = new LinkedList<PlotConfigurationError>();
DataTableColumn dataTableColumn = getDataTableColumn();
if (dataTableColumn.getName() == null || dataTableColumn.getName().isEmpty()) {
PlotConfigurationError error = new PlotConfigurationError("undefined_dimension", PlotDimension.DOMAIN.getName());
errors.add(error);
return errors;
}
// This is now check within each RangeAxis separately
// if (getValueType() == ValueType.INVALID) {
// PlotConfigurationError error = new PlotConfigurationError("illegal_domain_type",
// getValueType(), ValueType.INVALID);
// errors.add(error);
// return errors;
// }
errors.addAll(groupedMasterDimensionConfig.getErrors());
if (errors.size() > 0) {
return errors;
}
errors.addAll(ungroupedMasterDimensionConfig.getErrors());
return errors;
}
@Override
public List<PlotConfigurationError> getWarnings() {
LinkedList<PlotConfigurationError> warnings = new LinkedList<PlotConfigurationError>();
warnings.addAll(groupedMasterDimensionConfig.getWarnings());
if (warnings.size() > 0) {
return warnings;
}
warnings.addAll(ungroupedMasterDimensionConfig.getWarnings());
return warnings;
}
@Override
public boolean isValid() {
return getErrors().isEmpty();
}
@Override
public ValueType getValueType() {
ValueType valueType = ValueType.INVALID;
List<ValueSource> allValueSources = plotConfiguration.getAllValueSources();
if (allValueSources.isEmpty()) {
if (isGrouping() && getDataTableColumn().getName().length() == 0) {
return getGrouping().getDomainType();
} else {
return ValueType.UNKNOWN;
}
}
for (ValueSource valueSource : allValueSources) {
ValueType currentValueType = valueSource.getDomainConfig().getValueType();
if (currentValueType != valueType) {
if (valueType == ValueType.INVALID) {
valueType = currentValueType;
} else {
// This implies that some valueSources have a different type on
// the domain axis than others.
// That results in an unplottable domain axis.
return ValueType.INVALID;
}
}
}
return valueType;
}
@Override
public boolean isNominal() {
return getValueType() == ValueType.NOMINAL;
}
@Override
public boolean isNumerical() {
return getValueType() == ValueType.NUMERICAL;
}
@Override
public boolean isDate() {
return getValueType() == ValueType.DATE_TIME;
}
@Override
public void setGrouping(ValueGrouping grouping) {
groupedMasterDimensionConfig.setGrouping(grouping);
}
@Override
public void setUpperBound(Double upperBound) {
ungroupedMasterDimensionConfig.setUpperBound(upperBound);
groupedMasterDimensionConfig.setUpperBound(upperBound);
}
@Override
public void setLowerBound(Double lowerBound) {
ungroupedMasterDimensionConfig.setLowerBound(lowerBound);
groupedMasterDimensionConfig.setLowerBound(lowerBound);
}
@Override
public Double getUserDefinedUpperBound() {
return groupedMasterDimensionConfig.getUserDefinedUpperBound();
}
@Override
public Double getUserDefinedLowerBound() {
return groupedMasterDimensionConfig.getUserDefinedLowerBound();
}
/**
* Returns true if at least one ValueSource in the PlotConfiguration uses the domain axis
* grouping provided by this DomainConfigManager.
*
* @see com.rapidminer.gui.new_plotter.configuration.DimensionConfig#isGrouping()
*/
@Override
public boolean isGrouping() {
for (ValueSource valueSource : plotConfiguration.getAllValueSources()) {
if (valueSource.isUsingDomainGrouping()) {
return true;
}
}
return false;
}
public GroupingState getGroupingState() {
int groupingCounter = 0;
for (ValueSource valueSource : plotConfiguration.getAllValueSources()) {
if (valueSource.isUsingDomainGrouping()) {
++groupingCounter;
}
}
if (groupingCounter == plotConfiguration.getAllValueSources().size()) {
return GroupingState.GROUPED;
}
if (groupingCounter > 0) {
return GroupingState.PARTIALLY_GROUPED;
}
return GroupingState.UNGROUPED;
}
@Override
public void dimensionConfigChanged(DimensionConfigChangeEvent change) {
DimensionConfigChangeEvent domainManagerEvent;
DimensionConfigChangeType type = change.getType();
switch (type) {
case ABOUT_TO_CHANGE_GROUPING:
domainManagerEvent = new DimensionConfigChangeEvent(this, change.getDimension(), type);
break;
case AUTO_NAMING:
domainManagerEvent = new DimensionConfigChangeEvent(this, change.getDimension(), change.getAutoNaming(),
type);
break;
case COLOR_PROVIDER:
domainManagerEvent = new DimensionConfigChangeEvent(this, change.getDimension(), change.getColorProvider());
break;
case COLOR_SCHEME:
domainManagerEvent = new DimensionConfigChangeEvent(this, change.getDimension(), type);
break;
case COLUMN:
domainManagerEvent = new DimensionConfigChangeEvent(this, change.getDimension(), change.getDataTableColumn());
break;
case CROSSHAIR_LINES_CHANGED:
domainManagerEvent = new DimensionConfigChangeEvent(this, change.getDimension(),
change.getCrosshairLinesChange());
break;
case DATE_FORMAT_CHANGED:
domainManagerEvent = new DimensionConfigChangeEvent(this, change.getDateFormat());
break;
case GROUPING_CHANGED:
domainManagerEvent = new DimensionConfigChangeEvent(this, change.getDimension(),
change.getGroupingChangeEvent());
break;
case LABEL:
domainManagerEvent = new DimensionConfigChangeEvent(this, change.getDimension(), change.getLabel());
break;
case RANGE:
domainManagerEvent = new DimensionConfigChangeEvent(this, change.getDimension(),
change.getValueRangeChangedEvent());
break;
case RESET:
domainManagerEvent = new DimensionConfigChangeEvent(this, change.getDimension(), type);
break;
case SCALING:
domainManagerEvent = new DimensionConfigChangeEvent(this, change.getDimension(), change.getLogarithmic(),
type);
break;
case SHAPE_PROVIDER:
domainManagerEvent = new DimensionConfigChangeEvent(this, change.getDimension(), change.getShapeProvider());
break;
case SIZE_PROVIDER:
domainManagerEvent = new DimensionConfigChangeEvent(this, change.getDimension(), change.getSizeProvider());
break;
case SORTING:
domainManagerEvent = new DimensionConfigChangeEvent(this, change.getDimension(), change.getSortingMode());
break;
default:
throw new IllegalArgumentException("Should not happen. Unknown event typ " + type);
}
fireDimensionConfigChanged(domainManagerEvent);
}
@Override
public Vector<GroupingType> getValidGroupingTypes() {
GroupingType[] values = ValueGrouping.GroupingType.values();
Vector<GroupingType> valueVector = new Vector<GroupingType>();
for (int i = 0; i < values.length; ++i) {
GroupingType type = values[i];
if (getDataTableColumn().isNominal()) {
if (type == GroupingType.EQUAL_DATA_FRACTION) {
continue;
}
if (type == GroupingType.EQUIDISTANT_FIXED_BIN_COUNT) {
continue;
}
}
if (type == GroupingType.NONE) {
continue;
}
valueVector.add(type);
}
return valueVector;
}
@Override
public Set<ValueType> getSupportedValueTypes() {
Set<ValueType> supportedTypes = new HashSet<DataTableColumn.ValueType>();
supportedTypes.add(ValueType.NOMINAL);
supportedTypes.add(ValueType.NUMERICAL);
supportedTypes.add(ValueType.DATE_TIME);
return supportedTypes;
}
@Override
public boolean isUsingUserDefinedLowerBound() {
return groupedMasterDimensionConfig.isUsingUserDefinedLowerBound();
}
@Override
public boolean isUsingUserDefinedUpperBound() {
return groupedMasterDimensionConfig.isUsingUserDefinedUpperBound();
}
@Override
public void setUseUserDefinedUpperBound(boolean useUpperBound) {
ungroupedMasterDimensionConfig.setUseUserDefinedUpperBound(useUpperBound);
groupedMasterDimensionConfig.setUseUserDefinedUpperBound(useUpperBound);
}
@Override
public void setUseUserDefinedLowerBound(boolean useLowerBound) {
ungroupedMasterDimensionConfig.setUseUserDefinedLowerBound(useLowerBound);
groupedMasterDimensionConfig.setUseUserDefinedLowerBound(useLowerBound);
}
/*
* Does not clone plotConfiguration and dataTable, but copies references for these fields.
*/
@Override
public DomainConfigManager clone() {
DomainConfigManager clone = new DomainConfigManager(getId());
clone.plotConfiguration = plotConfiguration;
clone.groupedMasterDimensionConfig = groupedMasterDimensionConfig.clone();
clone.groupedMasterDimensionConfig.addDimensionConfigListener(clone);
clone.ungroupedMasterDimensionConfig = ungroupedMasterDimensionConfig.clone();
clone.crosshairLines = crosshairLines.clone();
clone.sortingMode = sortingMode;
return clone;
}
@Override
public void colorSchemeChanged() {
ungroupedMasterDimensionConfig.colorSchemeChanged();
groupedMasterDimensionConfig.colorSchemeChanged();
}
public void setPlotConfiguration(PlotConfiguration plotConfiguration) {
this.plotConfiguration = plotConfiguration;
}
@Override
public void axisParallelLineConfigurationsChanged(AxisParallelLinesConfigurationChangeEvent e) {
fireDimensionConfigChanged(new DimensionConfigChangeEvent(this, getDimension(), e));
}
public AxisParallelLinesConfiguration getCrosshairLines() {
return crosshairLines;
}
@Override
public DateFormat getDateFormat() {
return groupedMasterDimensionConfig.getDateFormat();
}
@Override
public void setUserDefinedDateFormatString(String formatString) {
ungroupedMasterDimensionConfig.setUserDefinedDateFormatString(formatString);
groupedMasterDimensionConfig.setUserDefinedDateFormatString(formatString);
}
@Override
public String getUserDefinedDateFormatString() {
return groupedMasterDimensionConfig.getUserDefinedDateFormatString();
}
@Override
public void setUseUserDefinedDateFormat(boolean yes) {
ungroupedMasterDimensionConfig.setUseUserDefinedDateFormat(yes);
groupedMasterDimensionConfig.setUseUserDefinedDateFormat(yes);
}
@Override
public boolean isUsingUserDefinedDateFormat() {
return groupedMasterDimensionConfig.isUsingUserDefinedDateFormat();
}
@Override
public int getId() {
return this.id;
}
public void resetToDefaults() {
DataTableColumn domainColumn = new DataTableColumn(null, ValueType.INVALID);
setDataTableColumn(domainColumn);
setSortingMode(DEFAULT_SORTING_MODE);
while (!crosshairLines.getLines().isEmpty()) {
crosshairLines.removeLine(crosshairLines.getLines().get(0));
}
setAutoNaming(true);
try {
setGrouping(new EquidistantFixedBinCountBinning(5, Double.NaN, Double.NaN, domainColumn,
domainColumn.isNominal(), getDateFormat()));
} catch (ChartConfigurationException e) {
setGrouping(new DistinctValueGrouping(domainColumn, domainColumn.isNominal(), getDateFormat()));
}
setUserDefinedDateFormatString(DEFAULT_DATE_FORMAT_STRING);
setUseUserDefinedDateFormat(DEFAULT_USE_USER_DEFINED_DATE_FORMAT);
setUseUserDefinedLowerBound(false);
setUseUserDefinedUpperBound(false);
setLowerBound(DEFAULT_USER_DEFINED_LOWER_BOUND);
setUpperBound(DEFAULT_USER_DEFINED_UPPER_BOUND);
setLogarithmic(false);
}
}