/* * Copyright (C) 2014 Alec Dhuse * * 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 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 General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. */ package co.foldingmap.GUISupport; import co.foldingmap.GUISupport.components.ColorGradientComboBox; import co.foldingmap.GUISupport.components.HeatMapValueTableModel; import co.foldingmap.GUISupport.panels.ActionPanel; import co.foldingmap.Logger; import co.foldingmap.actions.Actions; import co.foldingmap.map.DigitalMap; import co.foldingmap.map.themes.ColorRamp; import co.foldingmap.map.vector.VectorObject; import co.foldingmap.map.vector.VectorObjectList; import co.foldingmap.map.visualization.HeatMap; import co.foldingmap.map.visualization.HeatMapKey; import java.awt.*; import java.awt.event.ActionEvent; import java.awt.event.ItemEvent; import java.awt.event.ItemListener; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import javax.swing.*; import javax.swing.event.ListSelectionEvent; import javax.swing.event.ListSelectionListener; /** * * @author Alec */ public class HeatMapProperties extends ActionPanel implements ItemListener, ListSelectionListener { private Actions actions; private ArrayList<String> customDataFields; private boolean areNumbers; private ButtonGroup objectsToUseButtonGroup, keyOrientationButtonGroup; private ColorGradientComboBox comboGradientStyle; private DigitalMap mapData; private HeatMap heatMap; private HeatMapValueTableModel heatMapValueTableModel; private JComboBox comboKeyPosition; private JLabel labelDisplayInterval, labelGradientStyle; private JLabel labelKeyPosition, labelKeyOrientation; private JLabel labelMaxValue, labelMinValue, labelName; private JLabel labelObjectsToUse, labelTransparency, labelVariable; private JList listVariable; private JPanel panelGradientStyle, panelKeyOrientation; private JPanel panelObjectsToUseButtons, panelOptions; private JRadioButton radioUseAllObject, radioUseSelectedObjects; private JRadioButton radioHorizontal, radioVertical; private JSlider sliderTransparency; private JTable tableObjectValues; private JTextField textDisplayInterval, textMaxValue, textMinValue, textName; private JScrollPane spaneListVariables, spaneStringValues; private String[] keyPositions = {"No Key", "Top Left", "Top Right", "Bottom Left", "Bottom Right"}; private VectorObjectList<VectorObject> selectedVectorObjects; /** * Used for editing the properties of an existing HeatMap. * * @param heatMap */ public HeatMapProperties(DigitalMap mapData, HeatMap heatMap) { this.heatMap = heatMap; this.mapData = mapData; this.selectedVectorObjects = heatMap.getMapObjects(); init(); //set layer name textName.setText(heatMap.getName()); //Set the used variables as selected int[] indices = new int[heatMap.getVariables().length]; for (int i = 0; i < heatMap.getVariables().length; i++) indices[i] = customDataFields.indexOf(heatMap.getVariables()[i]); listVariable.setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION); listVariable.setSelectedIndices(indices); updateMinMax(); //update the display interval textDisplayInterval.setText(Integer.toString(heatMap.getDisplayInterval())); //update values ColorRamp colorRamp = mapData.getTheme().getColorRamp(heatMap.getColorRampID()); ArrayList<String> keys = new ArrayList<String>(colorRamp.getKeySet()); ArrayList<Color> colors = new ArrayList<Color>(colorRamp.getColors()); //set transparency sliderTransparency.setValue(colors.get(1).getAlpha()); if (areNumbers) { //update min and max values Collections.sort(keys); this.textMaxValue.setText(keys.get(keys.size() - 1)); if (!keys.get(0).equals("")) { this.textMinValue.setText(keys.get(0)); } else { this.textMinValue.setText(keys.get(1)); } } else { //set each color and value in the list for (int i = 0; i < colors.size(); i++) { Color c = colors.get(i); heatMapValueTableModel.setColorValue(i, c); } } if (heatMap.getHeatMapKey().getPositionReference() == HeatMapKey.NONE) { comboKeyPosition.setSelectedIndex(0); } else if (heatMap.getHeatMapKey().getPositionReference() == HeatMapKey.TOP_LEFT) { comboKeyPosition.setSelectedIndex(1); } else if (heatMap.getHeatMapKey().getPositionReference() == HeatMapKey.TOP_RIGHT) { comboKeyPosition.setSelectedIndex(2); } else if (heatMap.getHeatMapKey().getPositionReference() == HeatMapKey.BOTTOM_LEFT) { comboKeyPosition.setSelectedIndex(3); } else if (heatMap.getHeatMapKey().getPositionReference() == HeatMapKey.BOTTOM_RIGHT) { comboKeyPosition.setSelectedIndex(4); } if (heatMap.getHeatMapKey().hasHorizontalOrientation()) { radioHorizontal.setEnabled(true); radioVertical.setEnabled(true); radioHorizontal.setSelected(true); } else { radioHorizontal.setEnabled(true); radioVertical.setEnabled(true); radioVertical.setSelected(true); } } /** * Used for creating a new HeatMap. * * @param mapData * @param actions */ public HeatMapProperties(DigitalMap mapData, Actions actions) { this.actions = actions; this.mapData = mapData; this.selectedVectorObjects = new VectorObjectList<VectorObject>(mapData.getSelectedObjects()); init(); updateMinMax(); } @Override public void actionPerformed(ActionEvent ae) { HeatMapKey heatMapKey; JButton clickedButton; String actionEvent; VectorObjectList<VectorObject> mapObjects; actionEvent = ae.getActionCommand(); mapObjects = new VectorObjectList<VectorObject>(mapData.getAllMapObjects()); if (ae.getSource() instanceof JButton) { clickedButton = (JButton) ae.getSource(); } else { clickedButton = null; } if (ae.getSource() == radioUseAllObject) { updateMinMax(); } else if (ae.getSource() == radioUseSelectedObjects) { updateMinMax(); } else if (actionEvent.equals("Change Color")) { Color newColor = JColorChooser.showDialog(null, "Change Color", clickedButton.getBackground()); clickedButton.setBackground(newColor); clickedButton.setForeground(newColor); heatMapValueTableModel.setColorValue(tableObjectValues.getSelectedRow(), newColor); } else if (actionEvent.equalsIgnoreCase("Ok")) { heatMapKey = new HeatMapKey(getKeyLabels(), getColors(), getKeyPosition(), radioHorizontal.isSelected()); //Action called when the ok button on the windows property dialog is clicked if (heatMap == null) { //create a new heatmap Object[] objects = this.listVariable.getSelectedValues(); String[] fields = new String[objects.length]; for (int i = 0; i < objects.length; i++) fields[i] = (String) objects[i]; ColorRamp colorRamp = this.getColorRamp(); int displayInterval = Integer.parseInt(textDisplayInterval.getText()); if (radioUseSelectedObjects.isSelected()) { actions.createHeatMap(mapData, textName.getText(), selectedVectorObjects, fields, colorRamp, displayInterval, heatMapKey); } else { actions.createHeatMap(mapData, textName.getText(), mapObjects, fields, colorRamp, displayInterval, heatMapKey); } } else { //update values in an existing heatmap heatMap.setName(textName.getText()); //update selected fields Object[] objects = this.listVariable.getSelectedValues(); String[] fields = new String[objects.length]; for (int i = 0; i < objects.length; i++) fields[i] = (String) objects[i]; //Add Color Ramp to Theme mapData.getTheme().addColorRamp(getColorRamp()); //Set the ColorRamp Info on the HeatMap Layer heatMap.setColorRampID(getColorRamp().getID()); heatMap.setVariables(fields); //update display interval heatMap.setDisplayInterval(Integer.parseInt(textDisplayInterval.getText())); //update objects if (radioUseAllObject.isSelected()) { //User updates the objects to be used heatMap.setMapObjects(mapObjects); } heatMap.getHeatMapKey().setHorizontal(radioHorizontal.isSelected()); heatMap.getHeatMapKey().setPositionReference(getKeyPosition()); } } else if (actionEvent.equals("Remove")) { heatMapValueTableModel.removeRow(tableObjectValues.getSelectedRow()); } } /** * Adds objects to the main window and sets up the layout */ private void addObjectsToFrame(boolean numericalValues) { this.removeAll(); panelOptions.removeAll(); //center panel this.add(panelOptions); //objects to use panelObjectsToUseButtons.add(radioUseAllObject); panelObjectsToUseButtons.add(radioUseSelectedObjects); objectsToUseButtonGroup.add(radioUseAllObject); objectsToUseButtonGroup.add(radioUseSelectedObjects); //options panel panelOptions.add(labelName); panelOptions.add(textName); panelOptions.add(labelObjectsToUse); panelOptions.add(panelObjectsToUseButtons); panelOptions.add(labelKeyPosition); panelOptions.add(comboKeyPosition); panelOptions.add(labelKeyOrientation); panelOptions.add(panelKeyOrientation); panelOptions.add(labelVariable); panelOptions.add(spaneListVariables); panelOptions.add(labelDisplayInterval); panelOptions.add(textDisplayInterval); if (numericalValues) { panelGradientStyle.add(comboGradientStyle); panelOptions.add(labelMaxValue); panelOptions.add(textMaxValue); panelOptions.add(labelMinValue); panelOptions.add(textMinValue); panelOptions.add(labelGradientStyle); panelOptions.add(panelGradientStyle); } panelOptions.add(labelTransparency); panelOptions.add(sliderTransparency); //Heatmap Key Orientation keyOrientationButtonGroup.add(radioHorizontal); keyOrientationButtonGroup.add(radioVertical); panelKeyOrientation.add(radioHorizontal); panelKeyOrientation.add(radioVertical); if (numericalValues) { SpringUtilities.makeCompactGrid(panelOptions, 10, 2, 3, 3, 10, 10); SpringUtilities.makeCompactGrid(this, 1, 1, 3, 3, 10, 10); } else { this.add(spaneStringValues); SpringUtilities.makeCompactGrid(panelOptions, 7, 2, 3, 3, 10, 10); SpringUtilities.makeCompactGrid(this, 2, 1, 3, 3, 10, 10); } this.validate(); this.repaint(); } /** * Initializes all displayable objects */ private void init() { try { customDataFields = mapData.getAllCustomDataFields(); customDataFields.add("Altitude"); Collections.sort(customDataFields); comboGradientStyle = new ColorGradientComboBox(); comboKeyPosition = new JComboBox(keyPositions); labelGradientStyle = new JLabel("Style"); heatMapValueTableModel = new HeatMapValueTableModel(this, customDataFields); keyOrientationButtonGroup = new ButtonGroup(); labelDisplayInterval = new JLabel("Display Interval (ms)"); labelKeyPosition = new JLabel("Key Position"); labelKeyOrientation = new JLabel("Key Orientation"); labelMaxValue = new JLabel("Max Value"); labelMinValue = new JLabel("Min Value"); labelName = new JLabel("Name"); labelObjectsToUse = new JLabel("Objects To Use"); labelTransparency = new JLabel("Transparency"); labelVariable = new JLabel("Variable"); listVariable = new JList(customDataFields.toArray()); tableObjectValues = new JTable(heatMapValueTableModel); panelGradientStyle = new JPanel(new FlowLayout(FlowLayout.CENTER)); panelObjectsToUseButtons = new JPanel(new GridLayout(1, 2)); panelOptions = new JPanel(new SpringLayout()); panelKeyOrientation = new JPanel(new GridLayout(1, 2)); objectsToUseButtonGroup = new ButtonGroup(); radioHorizontal = new JRadioButton("Horizontal", true); radioVertical = new JRadioButton("Vertical"); radioUseAllObject = new JRadioButton("All Objects"); radioUseSelectedObjects = new JRadioButton("Selected objects"); sliderTransparency = new JSlider(0, 255, 180); spaneStringValues = new JScrollPane(tableObjectValues); spaneListVariables = new JScrollPane(listVariable); textDisplayInterval = new JTextField("500"); textMaxValue = new JTextField(Double.toString(mapData.getMaximumFieldValue(customDataFields.get(0)))); textMinValue = new JTextField(Double.toString(mapData.getMinimumFieldValue(customDataFields.get(0)))); textName = new JTextField("HeatMap"); this.setLayout(new SpringLayout()); radioUseAllObject.addActionListener(this); radioUseSelectedObjects.addActionListener(this); listVariable.addListSelectionListener(this); listVariable.setSelectedIndex(0); labelDisplayInterval.setEnabled(false); textDisplayInterval.setEnabled(false); tableObjectValues.setDefaultRenderer(Component.class, new CellEditorRenderer()); tableObjectValues.setDefaultEditor(Component.class, new CellEditorRenderer()); tableObjectValues.setAutoResizeMode(JTable.AUTO_RESIZE_OFF); tableObjectValues.getColumnModel().getColumn(0).setPreferredWidth(220); tableObjectValues.getColumnModel().getColumn(1).setPreferredWidth(80); tableObjectValues.getColumnModel().getColumn(2).setPreferredWidth(70); spaneStringValues.setPreferredSize( new Dimension(245, 180)); spaneListVariables.setMinimumSize( new Dimension(245, 90)); spaneListVariables.setMaximumSize( new Dimension(245, 90)); spaneListVariables.setPreferredSize(new Dimension(245, 90)); textDisplayInterval.setMaximumSize( new Dimension(250, 25)); textMaxValue.setMaximumSize(new Dimension(250, 25)); textMinValue.setMaximumSize(new Dimension(250, 25)); textName.setMaximumSize(new Dimension(250, 25)); panelObjectsToUseButtons.setMaximumSize(new Dimension(250, 30)); sliderTransparency.setMaximumSize(new Dimension(250, 30)); panelGradientStyle.setMaximumSize(new Dimension(250, 30)); comboKeyPosition.addItemListener(this); labelKeyOrientation.setEnabled(false); radioHorizontal.setEnabled(false); radioVertical.setEnabled(false); //Set the option buttons of what objects to use, baised on selected objects. if (selectedVectorObjects.size() > 1) { radioUseAllObject.setSelected(false); radioUseSelectedObjects.setSelected(true); } else { radioUseAllObject.setSelected(true); radioUseSelectedObjects.setSelected(false); } } catch (Exception e) { Logger.log(Logger.ERR, "Error in HeatMapProperties.init() - " + e); } } @Override public void itemStateChanged(ItemEvent ie) { if (ie.getSource() == comboKeyPosition) { if (comboKeyPosition.getSelectedIndex() == 0) { labelKeyOrientation.setEnabled(false); radioHorizontal.setEnabled(false); radioVertical.setEnabled(false); } else { labelKeyOrientation.setEnabled(true); radioHorizontal.setEnabled(true); radioVertical.setEnabled(true); } } else { updateMinMax(); } } /** * Returns the color from the Gradient associated with the given value. * The min an max from the textFields will be used to determine this color. * * @param value * @return */ public Color getColorFromGradient(float value) { Color color; float adjustedValue, max, min, valueRatio; int intColorInteger, colorPosistion; int red, blue, green, alpha; int[] pixelData; color = Color.BLACK; try { max = Float.parseFloat(textMaxValue.getText()); min = Float.parseFloat(textMinValue.getText()); adjustedValue = value - min; valueRatio = adjustedValue / max; colorPosistion = (int) (valueRatio * 255); pixelData = comboGradientStyle.getSelectedGradientPixelData(); //adjust for custom min and max if (colorPosistion > 255) { colorPosistion = 255; } else if (colorPosistion < 0) { colorPosistion = 0; } if (pixelData != null) { intColorInteger = pixelData[colorPosistion]; red = (intColorInteger & 0x00ff0000) >> 16; green = (intColorInteger & 0x0000ff00) >> 8; blue = (intColorInteger & 0x000000ff); alpha = sliderTransparency.getValue(); color = new Color(red, green, blue, alpha); } } catch (Exception e) { Logger.log(Logger.ERR, "Error in HeatMapProperties.getColorFromGradient(float) - " + e); } return color; } public ArrayList<Color> getColors() { ArrayList<Color> colors; boolean areNumericalValues; Color color; int red, blue, green; colors = new ArrayList<Color>(); //test to see if field value are numbers or not if (getValuesAsNumbers(getFieldValues()) != null) { areNumericalValues = true; } else { areNumericalValues = false; } if (areNumericalValues) { for (int i: comboGradientStyle.getSelectedGradientPixelData()) { red = (i & 0x00ff0000) >> 16; green = (i & 0x0000ff00) >> 8; blue = (i & 0x000000ff);; color = new Color(red, green, blue); colors.add(color); } } else { for (int i = 0; i < getFieldValues().size(); i++) { colors.add(heatMapValueTableModel.getColorValues().get(i)); } } return colors; } /** * Gets the field values to be used when making the HeatMap. * * @return */ public ArrayList<String> getFieldValues() { ArrayList<String> currentValues, fieldValues; Object[] objects; fieldValues = new ArrayList<String>(); objects = listVariable.getSelectedValues(); if (radioUseSelectedObjects.isSelected()) { for (Object o: objects) { currentValues = selectedVectorObjects.getCustomDataFieldValue((String) o); for (String s: currentValues) { if (!fieldValues.contains(s)) fieldValues.add(s); } } } else { for (Object o: objects) { currentValues = mapData.getCustomDataFieldValue((String) o); for (String s: currentValues) { if (!fieldValues.contains(s)) fieldValues.add(s); } } } return fieldValues; } /** * Returns the Labels to be used in the HeatMapKey. * * @return */ public ArrayList<String> getKeyLabels() { ArrayList<String> labels; ArrayList<Float> values; values = getValuesAsNumbers(getFieldValues()); if (values != null) { labels = new ArrayList<String>(); Collections.sort(values); labels.add(values.get(0).toString()); labels.add(values.get(values.size() / 2).toString()); labels.add(values.get(values.size() - 1).toString()); } else { labels = getFieldValues(); } return labels; } /** * Returns the value from the comboKeyPosition as an int representing * the chosen position. * * @return */ public int getKeyPosition() { if (comboKeyPosition.getSelectedItem().equals("No Key")) { return HeatMapKey.NONE; } else if (comboKeyPosition.getSelectedItem().equals("Top Left")) { return HeatMapKey.TOP_LEFT; } else if (comboKeyPosition.getSelectedItem().equals("Top Right")) { return HeatMapKey.TOP_RIGHT; } else if (comboKeyPosition.getSelectedItem().equals("Bottom Left")) { return HeatMapKey.BOTTOM_LEFT; } else if (comboKeyPosition.getSelectedItem().equals("Bottom Right")) { return HeatMapKey.BOTTOM_RIGHT; } else { return -1; } } /** * Returns the String values as numbers, if they can be converted. * Returns null if they cannot, meaning they are not all numbers. * * @param values * @return */ public ArrayList<Float> getValuesAsNumbers(ArrayList<String> values) { ArrayList<Float> numbers; float number; numbers = new ArrayList<Float>(); try { for (String s: values) { if (!s.equals("")) { number = Float.parseFloat(s); numbers.add(number); } else { //ignore blanks } } return numbers; } catch (Exception e) { //Formatting Error, not a number return null; } } public ColorRamp getColorRamp() { ArrayList<String> fieldValues; boolean addPair, areNumericalValues; Color color; ColorRamp colorRamp; int numberOfValues; fieldValues = getFieldValues(); //test to see if field value are numbers or not if (getValuesAsNumbers(fieldValues) != null) { areNumericalValues = true; } else { areNumericalValues = false; } //Create the HashMap with the initial capacity equal to the number of field values numberOfValues = fieldValues.size(); colorRamp = new ColorRamp(textName.getText() + "-ramp", numberOfValues); //Load the HashMap for (String value: fieldValues) { if (areNumericalValues) { if (!value.equals("")) { color = getColorFromGradient(Float.parseFloat(value)); addPair = true; } else { //make blanks transparent color = new Color(0,0,0,0); addPair = false; } } else { color = heatMapValueTableModel.getColorForValue(value); addPair = true; } if (addPair == true) colorRamp.addEntry(value, color); } return colorRamp; } /** * Generates and returns a HashMap representing values and the color * that should be assigned to that value. This is for use in the * CreateHeatMap action. * * @return */ public HashMap<String, Color> getValueColorHashMap() { ArrayList<String> fieldValues; boolean areNumericalValues; Color color; HashMap<String, Color> valueColorSet; int numberOfValues; fieldValues = getFieldValues(); //test to see if field value are numbers or not if (getValuesAsNumbers(fieldValues) != null) { areNumericalValues = true; } else { areNumericalValues = false; } //Create the HashMap with the initial capacity equal to the number of field values numberOfValues = fieldValues.size(); valueColorSet = new HashMap<String, Color>(numberOfValues); //Load the HashMap for (String value: fieldValues) { if (areNumericalValues) { if (!value.equals("")) { color = getColorFromGradient(Float.parseFloat(value)); } else { //make blanks transparent color = new Color(0,0,0,0); } } else { color = heatMapValueTableModel.getColorForValue(value); } valueColorSet.put(value, color); } return valueColorSet; } /** * Updates the min and max text fields. */ private void updateMinMax() { ArrayList<Float> numbers; ArrayList<String> currentValues, fieldValues; Object[] objects; String selectedField; try { objects = listVariable.getSelectedValues(); if (objects.length == 1) { selectedField = (String) listVariable.getSelectedValue(); fieldValues = getFieldValues(); numbers = getValuesAsNumbers(fieldValues); //turn off display interval labelDisplayInterval.setEnabled(false); textDisplayInterval.setEnabled(false); } else { fieldValues = new ArrayList<String>(); for (Object o: objects) { selectedField = (String) o; currentValues = getFieldValues(); for (String s: currentValues) { if (!fieldValues.contains(s)) fieldValues.add(s); } } numbers = getValuesAsNumbers(fieldValues); //turn on display interval labelDisplayInterval.setEnabled(true); textDisplayInterval.setEnabled(true); } if (numbers != null && numbers.size() > 0) { areNumbers = true; Collections.sort(numbers); textMinValue.setText(numbers.get(0).toString()); if (numbers.size() > 1) { textMaxValue.setText(numbers.get(numbers.size() - 1).toString()); } else { textMinValue.setText(numbers.get(0).toString()); } addObjectsToFrame(true); } else { areNumbers = false; Collections.sort(fieldValues); heatMapValueTableModel.setTableData(fieldValues); addObjectsToFrame(false); } } catch (Exception e) { Logger.log(Logger.ERR, "Error in HeatMapProperties.updateMinMax() - " + e); } } @Override public void valueChanged(ListSelectionEvent lse) { updateMinMax(); } }