/*
* Copyright 2004-2010 Information & Software Engineering Group (188/1)
* Institute of Software Technology and Interactive Systems
* Vienna University of Technology, Austria
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.ifs.tuwien.ac.at/dm/somtoolbox/license.html
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package at.tuwien.ifs.somtoolbox.visualization;
import java.awt.Color;
import java.awt.Graphics2D;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.image.BufferedImage;
import java.util.HashMap;
import java.util.Map;
import javax.swing.JCheckBox;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JSlider;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
import org.apache.commons.math.util.MathUtils;
import cern.colt.matrix.DoubleMatrix2D;
import at.tuwien.ifs.somtoolbox.SOMToolboxException;
import at.tuwien.ifs.somtoolbox.layers.GrowingLayer;
import at.tuwien.ifs.somtoolbox.models.GrowingSOM;
import at.tuwien.ifs.somtoolbox.util.GridBagConstraintsIFS;
/**
* Implementation of the Cluster Connections Visualisation as described in <i><b>D. Merkl and A. Rauber.</b> Proceedings
* of the Workshop on Self-Organizing Maps (WSOM97), Helsinki, Finland, June 4-6 1997.
*
* @author Robert Thurnher
* @author Michael Groh
* @author Rudolf Mayer
* @version $Id: ClusterConnectionsVisualizer.java 3874 2010-11-02 14:14:38Z mayer $
*/
public class ClusterConnectionsVisualizer extends AbstractBackgroundImageVisualizer implements
BackgroundImageVisualizer {
private double t1 = 0.8;
private double t2 = 1.1;
private double t3 = 1.6;
public ClusterConnectionsVisualizer() {
NUM_VISUALIZATIONS = 1;
VISUALIZATION_NAMES = new String[] { "Cluster Connections" };
VISUALIZATION_SHORT_NAMES = new String[] { "ClusterConn" };
VISUALIZATION_DESCRIPTIONS = new String[] { " Implementation of the Cluster Connections Visualisation as described in \"D. Merkl and A. Rauber.\"\n"
+ "Proceedings of the Workshop on Self-Organizing Maps (WSOM97),\n"
+ "Helsinki, Finland, June 4-6 1997." };
controlPanel = new ClusterConnectionsControlPanel();
}
@Override
protected String getCacheKey(GrowingSOM gsom, int currentVariant, int width, int height) {
return appendToCacheKey(gsom, currentVariant, width, height, "t1:" + t1, "t2:" + t2, "t3:" + t3);
}
@Override
public BufferedImage createVisualization(int variantIndex, GrowingSOM gsom, int width, int height)
throws SOMToolboxException {
GrowingLayer layer = gsom.getLayer();
BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
Graphics2D g = image.createGraphics();
g.setBackground(Color.WHITE);
g.clearRect(0, 0, width, height);
// need to calculate some params relative to the unit width/height, which is relative to the desired output size
// a unitWidth/Height of 10, as used previously, works by default only fine in the SOMViewer
int somWidth = layer.getXSize();
int somHeight = layer.getYSize();
double unitWidth = width / somWidth;
double unitHeight = height / somHeight;
DoubleMatrix2D unitDistanceMatrix = gsom.getLayer().getUnitDistanceMatrix();
for (int col = 0; col < somWidth; col++) {
for (int row = 0; row < somHeight; row++) {
if (col < somWidth - 1) {
// draw horizontal connection
double distanceRight = unitDistanceMatrix.get(layer.getUnitIndex(col, row), layer.getUnitIndex(
col + 1, row));
g.setPaint(getColor(distanceRight));
int xPos = (int) (col * unitHeight + unitHeight * 0.7);
int yPos = (int) (row * unitWidth + unitWidth * 0.4);
g.fillRect(xPos, yPos, (int) (unitWidth * 0.6), (int) (unitHeight * 0.2));
}
if (row < somHeight - 1) {
// draw vertical connection
double distanceLower = unitDistanceMatrix.get(layer.getUnitIndex(col, row), layer.getUnitIndex(col,
row + 1));
g.setPaint(getColor(distanceLower));
int xPos = (int) (col * unitHeight + unitHeight * 0.4);
int yPos = (int) (row * unitWidth + unitWidth * 0.7);
g.fillRect(xPos, yPos, (int) (unitWidth * 0.2), (int) (unitHeight * 0.6));
}
}
}
return image;
}
/** Gets the colour representing a certain distance value. */
private Color getColor(double distance) {
if (distance <= t1) {
return Color.BLACK;
} else if (distance > t1 && distance <= t2) {
return Color.GRAY;
} else if (distance > t2 && distance <= t3) {
return Color.LIGHT_GRAY;
} else if (distance > t3) {
return Color.WHITE;
} else {
throw new IllegalStateException("This can't happen.");
}
}
public class ClusterConnectionsControlPanel extends VisualizationControlPanel implements ChangeListener {
private static final long serialVersionUID = 1L;
private JCheckBox instantUpdateCheckBox = new JCheckBox("Instant update", true);;
private JLabel t1label;
private JLabel t2label;
private JLabel t3label;
private JSlider t1slider;
private JSlider t2slider;
private JSlider t3slider;
public ClusterConnectionsControlPanel() {
super("Cluster Connections Control Panel");
instantUpdateCheckBox.setToolTipText("Indicate whether the visualisation should be updated right away, or only at the end of the slider dragging");
t1slider = new JSlider(0, 200, (int) (t1 * 100));
t1slider.addChangeListener(this);
t2slider = new JSlider(0, 200, (int) (t2 * 100));
t2slider.addChangeListener(this);
t3slider = new JSlider(0, 200, (int) (t3 * 100));
t3slider.addChangeListener(this);
t1label = new JLabel("t1: " + t1);
t2label = new JLabel("t2: " + t2);
t3label = new JLabel("t3: " + t3);
JPanel panel = new JPanel(new GridBagLayout());
GridBagConstraintsIFS gc = new GridBagConstraintsIFS(GridBagConstraints.NORTHWEST,
GridBagConstraints.HORIZONTAL);
panel.add(instantUpdateCheckBox, gc.nextRow());
panel.add(t1label, gc.nextRow());
panel.add(t1slider, gc.nextRow());
panel.add(t2label, gc.nextRow());
panel.add(t2slider, gc.nextRow());
panel.add(t3label, gc.nextRow());
panel.add(t3slider, gc.nextRow());
add(panel, c);
}
/*** Handles state changes of sliders control. */
@Override
public void stateChanged(ChangeEvent event) {
JSlider source = (JSlider) event.getSource();
if (source.equals(t1slider)) {
if (t2slider.getValue() < t1slider.getValue()) {
t2slider.setValue(source.getValue());
}
if (instantUpdateCheckBox.isSelected() || !source.getValueIsAdjusting()) {
t1 = t1slider.getValue() / 100d;
t1label.setText("t1: " + t1);
}
} else if (source.equals(t2slider)) {
if (t3slider.getValue() < t2slider.getValue()) {
t3slider.setValue(t2slider.getValue());
}
if (t1slider.getValue() > t2slider.getValue()) {
t1slider.setValue(t2slider.getValue());
}
if (instantUpdateCheckBox.isSelected() || !source.getValueIsAdjusting()) {
t2 = t2slider.getValue() / 100d;
t2label.setText("t2: " + t2);
}
} else if (source.equals(t3slider)) {
if (t2slider.getValue() > t3slider.getValue()) {
t2slider.setValue(t3slider.getValue());
}
if (instantUpdateCheckBox.isSelected() || !source.getValueIsAdjusting()) {
t3 = t3slider.getValue() / 100d;
t3label.setText("t3: " + t3);
}
}
if (instantUpdateCheckBox.isSelected() || !source.getValueIsAdjusting()) {
visualizationUpdateListener.updateVisualization();
}
}
}
@Override
public HashMap<String, BufferedImage> getVisualizationFlavours(int index, GrowingSOM gsom, int width, int height)
throws SOMToolboxException {
HashMap<String, BufferedImage> res = new HashMap<String, BufferedImage>();
// modify t1 0.7 - 0.9
// modify t2 1.0 - 1.2
// modify t3 1.5 - 1.7
double oldT1 = t1; // save original values
double oldT2 = t2;
double oldT3 = t3;
double stepWidth = 0.05;
for (t1 = 0.7; MathUtils.round(t1, 2) <= 0.9; t1 += stepWidth) {
for (t2 = Math.max(t1, 1.0); MathUtils.round(t2, 2) <= 1.2; t2 += stepWidth) {
for (t3 = Math.max(t2, 1.5); MathUtils.round(t3, 2) <= 1.7; t3 += stepWidth) {
String key = String.format("_%.2f_%.2f_%.2f", t1, t2, t3);
res.put(key, getVisualization(index, gsom, width, height));
}
}
}
t1 = oldT1; // reset to original values
t2 = oldT2;
t3 = oldT3;
return res;
}
@Override
public HashMap<String, BufferedImage> getVisualizationFlavours(int index, GrowingSOM gsom, int width, int height,
int maxFlavours) throws SOMToolboxException {
// TODO Auto-generated method stub
return super.getVisualizationFlavours(index, gsom, width, height, maxFlavours);
}
@Override
public HashMap<String, BufferedImage> getVisualizationFlavours(int index, GrowingSOM gsom, int width, int height,
Map<String, String> flavourParameters) throws SOMToolboxException {
// FIXME: Implement this
return super.getVisualizationFlavours(index, gsom, width, height, flavourParameters);
}
}