/** * 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.StaticDebug; import com.rapidminer.gui.new_plotter.configuration.DataTableColumn.ValueType; import com.rapidminer.gui.new_plotter.listener.ValueGroupingListener; import com.rapidminer.gui.new_plotter.listener.events.ValueGroupingChangeEvent; import com.rapidminer.gui.new_plotter.utility.DataStructureUtils; import com.rapidminer.gui.new_plotter.utility.NumericalValueRange; import com.rapidminer.gui.new_plotter.utility.ValueRange; import java.text.DateFormat; import java.util.LinkedList; import java.util.List; /** * Base class for most implementations of ValueGrouping. * * All groupings inheriting this class have in common that they depend on exactly one column of a * datatable. * * @author Marius Helf, Nils Woehler * */ public abstract class AbstractValueGrouping implements ValueGrouping { private boolean categorical; private DataTableColumn dataTableColumn; private List<ValueGroupingListener> listeners = new LinkedList<ValueGroupingListener>(); private DateFormat dateFormat; public AbstractValueGrouping(DataTableColumn dataTableColumn, boolean isCategorical, DateFormat dateFormat) { this.categorical = isCategorical; this.dataTableColumn = dataTableColumn; this.dateFormat = dateFormat; } @Override public boolean isCategorical() { return categorical || getDomainType() == ValueType.NOMINAL; } /** * @param categorical * the categorical to set */ public void setCategorical(boolean categorical) { if (categorical != this.categorical) { this.categorical = categorical; fireGroupingChanged(new ValueGroupingChangeEvent(this, categorical)); } } @Override public void addListener(ValueGroupingListener l) { listeners.add(l); } @Override public void removeListener(ValueGroupingListener l) { listeners.remove(l); } protected void fireGroupingChanged(ValueGroupingChangeEvent e) { for (ValueGroupingListener l : listeners) { l.valueGroupingChanged(e); } } @Override public abstract ValueGrouping clone(); public DataTableColumn getDataTableColumn() { return dataTableColumn; } public void setDataTableColumn(DataTableColumn dataTableColumn) { if (dataTableColumn != this.dataTableColumn) { this.dataTableColumn = dataTableColumn; } } /** * Is called each time the underlying data (or the data column) changes. The default * implementation invalidates the cache. */ // protected void invalidateCache() { // cachedGroupingModel = null; // } protected void forceDataTableColumn(DataTableColumn dataTableColumn) { this.dataTableColumn = dataTableColumn; } @Override public ValueType getDomainType() { if (categorical) { return ValueType.NOMINAL; } else { return dataTableColumn.getValueType(); } } @Override public final List<ValueRange> getGroupingModel(DataTable data, double upperBound, double lowerBound) { StaticDebug.debug("Create grouping with upper bound: " + upperBound); StaticDebug.debug("and lower bound: " + lowerBound); return createGroupingModel(data, upperBound, lowerBound); } /** * Returns an up-to-date grouping model without cumulation applied. Does not need to implement * caching, since this is handled in AbstractValueGrouping. */ protected abstract List<ValueRange> createGroupingModel(DataTable data, double upperBound, double lowerBound); @Override public abstract boolean equals(Object obj); /** * Configures the value ranges in valueGroups such that their toString() method display the * optimal number of digits to discriminate both the intervals itself (upper/lower bound) and * also discriminate intervals from their neighbours. For each data point a different precision * might be calculated. * * For dates this functions searches for the biggest displayed time unit by looking at the * distance between the two farthest data points, and for the smallest displayed value by * looking at the closest distance between two neighbouring data points. For all date values the * same format string is used. * * Expects the list to contain only {@link NumericalValueRange}s. */ /** * @param valueGroups * @param valuesAreDates * * TODO use param valuesAreDates */ protected void applyAdaptiveVisualRounding(List<ValueRange> valueGroups, boolean valuesAreDates) { if (valuesAreDates) { DateFormat dateFormat = getDateFormat(); for (ValueRange range : valueGroups) { NumericalValueRange numericalValueRange = (NumericalValueRange) range; numericalValueRange.setDateFormat(dateFormat); } } else { // values are not dates // first pass NumericalValueRange previous = null; NumericalValueRange current = null; NumericalValueRange next = null; for (ValueRange valueGroup : valueGroups) { next = (NumericalValueRange) valueGroup; if (previous != null) { int precisionLower = Math.min( DataStructureUtils.getOptimalPrecision(current.getLowerBound(), current.getUpperBound()), DataStructureUtils.getOptimalPrecision(previous.getLowerBound(), current.getLowerBound())); int precisionUpper = Math.min( DataStructureUtils.getOptimalPrecision(current.getLowerBound(), current.getUpperBound()), DataStructureUtils.getOptimalPrecision(current.getUpperBound(), next.getUpperBound())); if (precisionUpper >= Integer.MAX_VALUE) { precisionUpper = precisionLower; } current.setVisualPrecision(precisionLower, precisionUpper); } else if (current != null) { int precisionLower = DataStructureUtils.getOptimalPrecision(current.getLowerBound(), current.getUpperBound()); int precisionUpper = Math.min( DataStructureUtils.getOptimalPrecision(current.getLowerBound(), current.getUpperBound()), DataStructureUtils.getOptimalPrecision(current.getUpperBound(), next.getUpperBound())); if (precisionUpper >= Integer.MAX_VALUE) { precisionUpper = precisionLower; } current.setVisualPrecision(precisionLower, precisionUpper); } previous = current; current = next; } if (previous != null) { // even if eclipse states that this code is dead, it is not! (eclipse bug) int precisionLower = Math.min( DataStructureUtils.getOptimalPrecision(current.getLowerBound(), current.getUpperBound()), DataStructureUtils.getOptimalPrecision(previous.getLowerBound(), current.getLowerBound())); int precisionUpper = DataStructureUtils .getOptimalPrecision(current.getLowerBound(), current.getUpperBound()); if (precisionUpper >= Integer.MAX_VALUE) { precisionUpper = precisionLower; } current.setVisualPrecision(precisionLower, precisionUpper); } else if (current != null) { int precision = DataStructureUtils.getOptimalPrecision(current.getLowerBound(), current.getUpperBound()); current.setVisualPrecision(precision, precision); } // // second pass // current = null; // next = null; // for (ValueRange valueGroup : valueGroups ) { // next = (NumericalValueRange)valueGroup; // if (current != null) { // int currentPrecision = current.getUpperPrecision(); // int nextPrecision = next.getLowerPrecision(); // int precision = Math.min(nextPrecision, currentPrecision); // current.setUpperPrecision(precision); // next.setLowerPrecision(precision); // } // current = next; // } } } @Override public DateFormat getDateFormat() { return dateFormat; } @Override public void setDateFormat(DateFormat dateFormat) { if (dateFormat != this.dateFormat) { this.dateFormat = dateFormat; fireGroupingChanged(new ValueGroupingChangeEvent(this, dateFormat)); } } }