/*
* The Unified Mapping Platform (JUMP) is an extensible, interactive GUI
* for visualizing and manipulating spatial features with geometry and attributes.
*
* Copyright (C) 2003 Vivid Solutions
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
*
* For more information, contact:
*
* Vivid Solutions
* Suite #1A
* 2328 Government Street
* Victoria BC V8T 5G5
* Canada
*
* (250)385-6040
* www.vividsolutions.com
*/
package com.vividsolutions.jump.workbench.ui.renderer.style.attributeclassifications;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.Insets;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.SortedMap;
import java.util.SortedSet;
import java.util.TreeMap;
import java.util.TreeSet;
import javax.swing.DefaultComboBoxModel;
import javax.swing.JButton;
import javax.swing.JComboBox;
import javax.swing.JComponent;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.event.TableModelEvent;
import javax.swing.event.TableModelListener;
import org.openjump.core.attributeoperations.Classifier1D;
//import org.openjump.core.ui.style.classification.CalculRange;
import com.vividsolutions.jts.util.Assert;
import com.vividsolutions.jump.I18N;
import com.vividsolutions.jump.util.CollectionUtil;
import com.vividsolutions.jump.util.Range;
import com.vividsolutions.jump.workbench.ui.renderer.style.ColorScheme;
import com.vividsolutions.jump.workbench.ui.renderer.style.ColorThemingStylePanel;
import com.vividsolutions.jump.workbench.ui.renderer.style.ColorThemingTableModel;
import com.vividsolutions.jump.workbench.ui.renderer.style.ColorThemingStylePanel.State;
import com.vividsolutions.jump.workbench.ui.renderer.style.ColorThemingTableModel.AttributeValueTableModelEvent;
public class QuantileColorThemingState implements ColorThemingStylePanel.State {
private ColorThemingStylePanel stylePanel;
private static final String RANGE_COUNT_KEY =
QuantileColorThemingState.class.getName() + " - QUANTILE COUNT";
public String getAllOtherValuesDescription() {
return I18N.get("ui.renderer.style.RangeColorThemingState.values-below-these-values");
}
public String getAttributeValueColumnTitle() {
return I18N.get("ui.renderer.style.RangeColorThemingState.minimum-attribute-values");
}
private int getRangeCount() {
return ((Integer) comboBox.getSelectedItem()).intValue();
}
private int getMaxAttributeClassCount() {
return stylePanel.getAttributeValuesCount().entrySet().size()+1;
}
// return a collection of lower bound values for a quantile classification.
// avgClassSize is the average size of a quantile class
// valuesCount is the map (attributeValue -> number of corresponding entities)
private Collection getQuantileBreaks(double avgClassSize, SortedMap valuesCount){
Set filteredValues = new TreeSet();
Iterator v = valuesCount.keySet().iterator();
if (!v.hasNext())
return filteredValues;
Object value = v.next();
int count= ((Integer) valuesCount.get(value)).intValue();
filteredValues.add(value);
// update of classification variables
int classified = count; // total classified at this point
double avgClassifiedAtNextLoop = avgClassSize; // expected to be classified at next loop
while(v.hasNext()) {
value = v.next();
count = ((Integer) valuesCount.get(value)).intValue();
if (((double)classified)>avgClassifiedAtNextLoop) {
// System.out.println("break value : " + value.toString());
filteredValues.add(value);
avgClassifiedAtNextLoop = avgClassifiedAtNextLoop + avgClassSize; // expected to be classified at next loop
}
classified = classified + count; // total classified at this point
}
return filteredValues;
}
/**
* Returns a Collection with double values that are corresponding to the class breaks.
* Note: the smallest number is equal to the minValue of all attributes.
*/
public Collection filterAttributeValues(SortedSet attributeValues) {
//-1 because one row in the table is reserved for "all other values". [Jon Aquino]
//int classCount = getRangeCount() - 1;
Collection filteredValues = new ArrayList();
if (attributeValues.size() == 0) {
return filteredValues;
}
// -1 deleted because class range is false
int classCount = Math.min(getRangeCount(), attributeValues.size());
//-- [sstein 15.Feb. 2009]
// replaced O-Bedels code by own code to be consistent
// in case somebody uses Classifier1D methods for classification
// and attaches the result as attribute values (i.e. to ensure
// that same values are obtained)
/*
int featuresCount = stylePanel.getLayer().getFeatureCollectionWrapper().size();
double avgClassSize = ((double) featuresCount) / ((double) classCount); // type double to avoid round off errors
SortedMap valuesCount = stylePanel.getAttributeValuesCount();
Collection filteredValues = getQuantileBreaks(avgClassSize, valuesCount);
*/
//-- sstein: new code
double[] data = new double[attributeValues.size()];
int i=0; boolean isInteger = false;
for (Iterator iterator = attributeValues.iterator(); iterator.hasNext();) {
Object val = (Object) iterator.next();
if (val instanceof Integer){
data[i] = (Integer)val;
isInteger = true;
}
else if (val instanceof Double){
data[i] = (Double)val;
}
else{
data[i] = Double.NaN;
}
i++;
}
double[] breaks = Classifier1D.classifyEqualNumber(data, classCount);
double minVal = org.math.array.DoubleArray.min(data);
//add minVal as smallest value
if(isInteger){
filteredValues.add(new Integer((int)minVal));
}
else{
filteredValues.add(new Double(minVal));
}
for (int j = 0; j < breaks.length; j++) {
if(isInteger){
filteredValues.add(new Integer((int)breaks[j]));
}
else{
filteredValues.add(new Double(breaks[j]));
}
}
//-- sstein: end
return filteredValues;
}
private JPanel panel = new JPanel(new GridBagLayout()) {
public void setEnabled(boolean enabled) {
comboBox.setEnabled(enabled);
label.setEnabled(enabled);
reverseButton.setEnabled(enabled);
super.setEnabled(enabled);
}
};
public QuantileColorThemingState(final ColorThemingStylePanel stylePanel) {
this.stylePanel = stylePanel;
addComboBoxItems();
comboBox.setSelectedItem(
stylePanel.getLayer().getLayerManager().getBlackboard().get(
RANGE_COUNT_KEY,
new Integer(5)));
//Don't add action listeners until items have been added to the
//combo box. [Jon Aquino]
comboBox.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
stylePanel.populateTable();
stylePanel.getLayer().getLayerManager().getBlackboard().put(
RANGE_COUNT_KEY,
comboBox.getSelectedItem());
}
});
panel.add(
label,
new GridBagConstraints(
1,
0,
1,
1,
0,
0,
GridBagConstraints.WEST,
GridBagConstraints.NONE,
new Insets(2, 2, 2, 2),
0,
0));
panel.add(
comboBox,
new GridBagConstraints(
2,
0,
1,
1,
0,
0,
GridBagConstraints.WEST,
GridBagConstraints.NONE,
new Insets(2, 2, 2, 2),
0,
0));
panel.add(
reverseButton,
new GridBagConstraints(
3,
0,
1,
1,
0,
0,
GridBagConstraints.WEST,
GridBagConstraints.NONE,
new Insets(2, 2, 2, 2),
0,
0));
reverseButton.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
reversingColorScheme = !reversingColorScheme;
stylePanel.applyColorScheme();
}
});
}
private JButton reverseButton = new JButton(I18N.get("ui.renderer.style.RangeColorThemingState.reverse-colors"));
private void addComboBoxItems() {
int maxColorSchemeSize = -1;
for (Iterator i = ColorScheme.rangeColorSchemeNames().iterator();
i.hasNext();
) {
String rangeColorSchemeName = (String) i.next();
maxColorSchemeSize =
Math.max(
maxColorSchemeSize,
ColorScheme
.create(rangeColorSchemeName)
.getColors()
.size());
}
for (int i = 3; i <= maxColorSchemeSize; i++) {
comboBoxModel.addElement(new Integer(i));
}
}
private DefaultComboBoxModel comboBoxModel = new DefaultComboBoxModel();
private JComboBox comboBox = new JComboBox(comboBoxModel);
private JLabel label = new JLabel(I18N.get("ui.renderer.style.RangeColorThemingState.range-count"));
public JComponent getPanel() {
return panel;
}
public Map fromExternalFormat(Map attributeValueToObjectMap) {
//Table takes values, not ranges. [Jon Aquino]
TreeMap newMap = new TreeMap();
for (Iterator i = attributeValueToObjectMap.keySet().iterator();
i.hasNext();
) {
Range range = (Range) i.next();
newMap.put(
range.getMin(),
attributeValueToObjectMap.get(range));
}
return newMap;
}
public Map toExternalFormat(Map attributeValueToObjectMap) {
if (attributeValueToObjectMap.isEmpty()) {
return attributeValueToObjectMap;
}
//Turn the values into ranges. Validations have already ensured that
//the values are unique and contain no nulls. [Jon Aquino]
Assert.isTrue(attributeValueToObjectMap instanceof SortedMap);
TreeMap newMap = new Range.RangeTreeMap();
Object previousValue = null;
for (Iterator i = attributeValueToObjectMap.keySet().iterator();
i.hasNext();
) {
Object value = i.next();
try {
if (previousValue == null) {
//Let the default style handle values from negative infinity to
//the first value. [Jon Aquino]
continue;
}
//Make one side inclusive and the other exclusive to ensure no
//overlaps. [Jon Aquino]
newMap.put(
new Range(previousValue, true, value, false),
attributeValueToObjectMap.get(previousValue));
} finally {
previousValue = value;
}
}
newMap.put(
new Range(previousValue, true, new Range.PositiveInfinity(), false),
attributeValueToObjectMap.get(previousValue));
return newMap;
}
public void applyColorScheme(ColorScheme colorScheme) {
stylePanel.tableModel().apply(
new ColorScheme(
null,
CollectionUtil.stretch(
colorScheme.getColors(),
new ArrayList(),
stylePanel.tableModel().getRowCount())),
false);
}
public Collection getColorSchemeNames() {
return ColorScheme.rangeColorSchemeNames();
}
private TableModelListener tableModelListener = new TableModelListener() {
public void tableChanged(TableModelEvent e) {
if (e
instanceof ColorThemingTableModel.AttributeValueTableModelEvent) {
stylePanel.tableModel().sort(stylePanel.tableModel().wasLastSortAscending());
//I'd like to scroll to the row at this point, but the user probably
//finished the edit by clicking on another cell, so even if I scroll
//to the row, it scrolls back to where the user clicked. [Jon Aquino]
}
}
};
private int row(Object attributeValue) {
for (int i = 0; i < stylePanel.tableModel().getRowCount(); i++) {
Object otherAttributeValue =
stylePanel.tableModel().getValueAt(
i,
ColorThemingTableModel.ATTRIBUTE_COLUMN);
if (attributeValue == null && otherAttributeValue == null) {
return i;
}
if (attributeValue != null
&& attributeValue.equals(otherAttributeValue)) {
return i;
}
}
Assert.shouldNeverReachHere();
return -1;
}
public void activate() {
stylePanel.tableModel().addTableModelListener(tableModelListener);
}
public void deactivate() {
stylePanel.tableModel().removeTableModelListener(tableModelListener);
}
private boolean reversingColorScheme = false;
public ColorScheme filterColorScheme(ColorScheme colorScheme) {
if (!reversingColorScheme) {
return colorScheme;
}
List colors = new ArrayList(colorScheme.getColors());
Collections.reverse(colors);
return new ColorScheme(colorScheme.getName(), colors);
}
}