/* * 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.GraphicsEnvironment; import java.awt.Insets; import java.awt.Transparency; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.image.BufferedImage; import java.util.List; import javax.swing.JCheckBox; import javax.swing.JComboBox; import javax.swing.JLabel; import javax.swing.JPanel; import javax.swing.JSpinner; import javax.swing.SpinnerNumberModel; import javax.swing.event.ChangeEvent; import javax.swing.event.ChangeListener; import at.tuwien.ifs.somtoolbox.SOMToolboxException; import at.tuwien.ifs.somtoolbox.data.SOMVisualisationData; import at.tuwien.ifs.somtoolbox.layers.GrowingLayer; import at.tuwien.ifs.somtoolbox.models.GrowingSOM; import at.tuwien.ifs.somtoolbox.util.VisualisationUtils; import at.tuwien.ifs.somtoolbox.visualization.minimumSpanningTree.Edge; import at.tuwien.ifs.somtoolbox.visualization.minimumSpanningTree.Graph; import at.tuwien.ifs.somtoolbox.visualization.minimumSpanningTree.InputdataGraph; import at.tuwien.ifs.somtoolbox.visualization.minimumSpanningTree.SomGraph; import at.tuwien.ifs.somtoolbox.visualization.minimumSpanningTree.SomGraph.NeighbourhoodMode; /** * @author Thomas Kern * @author Magdalena Widl * @author Rudolf Mayer * @version $Id: MinimumSpanningTreeVisualizer.java 3862 2010-10-15 09:42:45Z frank $ */ public class MinimumSpanningTreeVisualizer extends AbstractBackgroundImageVisualizer implements BackgroundImageVisualizer { // // Visualisation parameters // private boolean weightLines = false; private boolean skipInterpolationUnits = false; private NeighbourhoodMode neighbourhoodMode = NeighbourhoodMode.All; private int disconnectUnfavoured = 0; // END Visualisation parameters public MinimumSpanningTreeVisualizer() { NUM_VISUALIZATIONS = 3; VISUALIZATION_NAMES = new String[] { "Minimum Spanning Tree SOM", "Minimum Spanning Tree Input Data", "Minimum Spanning Tree Both" }; VISUALIZATION_SHORT_NAMES = new String[] { "MSTsom", "MSTdata", "MSTboth" }; VISUALIZATION_DESCRIPTIONS = new String[] { "Implementation of a minimum spanning tree on a SOM", "Implementation of a minimum spanning tree on the input data", "Implementation of a minimum spanning tree on the SOM and input data, ideal for comparing them" }; neededInputObjects = new String[] { SOMVisualisationData.INPUT_VECTOR }; if (!GraphicsEnvironment.isHeadless()) { controlPanel = new MinimumSpanningTreeControlPanel(); } preferredScaleFactor = getPreferredScaleFactor(); } @Override public BufferedImage createVisualization(int variantIndex, GrowingSOM gsom, int width, int height) throws SOMToolboxException { checkVariantIndex(variantIndex, getClass()); // set maximum value for disconnect spinner, based on the size of the SOM to visualise if (controlPanel != null) { ((MinimumSpanningTreeControlPanel) controlPanel).disconnectUnfavouredModel.setMaximum(gsom.getLayer().getUnitCount()); } if (drawInputTree(variantIndex)) { if (gsom.getSharedInputObjects().getInputData() == null) { throw new SOMToolboxException("Input data is needed for this Minimum Spanning Tree!"); } } BufferedImage image = new BufferedImage(width, height, Transparency.TRANSLUCENT); if (drawSOMTree(variantIndex)) { // draw the SOM tree drawMinimumSpanningTree(image, new SomGraph(gsom, skipInterpolationUnits, neighbourhoodMode), disconnectUnfavoured, gsom.getLayer(), Color.BLACK); } if (drawInputTree(variantIndex)) { // draw the input data tree drawMinimumSpanningTree(image, new InputdataGraph(gsom), disconnectUnfavoured, gsom.getLayer(), Color.BLUE); } return image; } private boolean drawInputTree(int index) { return index == 1 || index == 2; } private boolean drawSOMTree(int index) { return index == 0 || index == 2; } @Override protected String getCacheKey(GrowingSOM gsom, int index, int width, int height) { if (drawSOMTree(index)) {// draw the SOM tree return appendToCacheKey(gsom, index, width, height, "weighting:" + weightLines, "disconnect:" + disconnectUnfavoured, "skipInterpolation:" + skipInterpolationUnits, "neighbourhood:" + neighbourhoodMode.toString()); } else { return appendToCacheKey(gsom, index, width, height, "weighting:" + weightLines, "disconnect:" + disconnectUnfavoured); } } private void drawMinimumSpanningTree(BufferedImage res, Graph graph, int disconnectUnfavoured, GrowingLayer layer, Color color) { Graphics2D g = (Graphics2D) res.getGraphics(); int unitWidth = res.getWidth() / layer.getXSize(); int unitHeight = res.getHeight() / layer.getYSize(); // draw the line & circle approx. 1/20 of the unitWidth int lineWidth = Math.round(unitWidth / 5); int lineHeight = Math.round(unitHeight / 5); // draw the edges g.setPaint(color); List<Edge> mst = graph.getMinimumSpanningTree(); for (int i = 0; i < mst.size() && i + disconnectUnfavoured < mst.size(); i++) { Edge e = mst.get(i); graph.drawLine(g, unitWidth, unitHeight, e, weightLines); } // draw the nodes g.setPaint(Color.RED); for (Edge e : mst) { VisualisationUtils.drawUnitCentreMarker(g, e.getStart().getUnit(), unitWidth, unitHeight, lineWidth, lineHeight); // starting vertex VisualisationUtils.drawUnitCentreMarker(g, e.getEnd().getUnit(), unitWidth, unitHeight, lineWidth, lineHeight); // end vertex } } private class MinimumSpanningTreeControlPanel extends VisualizationControlPanel { private static final long serialVersionUID = 1L; private JCheckBox weightLinesCheckbox = new JCheckBox("Weight lines", weightLines); private JCheckBox skipInterpolationUnitsCheckbox = new JCheckBox("Skip interpol. Units", skipInterpolationUnits); private JComboBox neighbourhoodModeBox = new JComboBox(NeighbourhoodMode.values()); private SpinnerNumberModel disconnectUnfavouredModel = new SpinnerNumberModel(disconnectUnfavoured, 0, 100, 1); private JSpinner disconnectUnfavouredSpinner = new JSpinner(disconnectUnfavouredModel); public MinimumSpanningTreeControlPanel() { super("MinimumSpanningTree Control"); c.insets = new Insets(1, 4, 1, 4); weightLinesCheckbox.setToolTipText("Weight the lines of the MST by the relative distance between the nodes."); weightLinesCheckbox.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { weightLines = weightLinesCheckbox.isSelected(); if (visualizationUpdateListener != null) { visualizationUpdateListener.updateVisualization(); } } }); add(weightLinesCheckbox, c); skipInterpolationUnitsCheckbox.setToolTipText("Skip nodes that are just interpolation nodes, i.e. nodes that have no input samples mapped"); skipInterpolationUnitsCheckbox.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { skipInterpolationUnits = skipInterpolationUnitsCheckbox.isSelected(); if (visualizationUpdateListener != null) { visualizationUpdateListener.updateVisualization(); } } }); c.gridy++; add(skipInterpolationUnitsCheckbox, c); neighbourhoodModeBox.setSelectedItem(neighbourhoodMode); neighbourhoodModeBox.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { neighbourhoodMode = (NeighbourhoodMode) neighbourhoodModeBox.getSelectedItem(); if (visualizationUpdateListener != null) { visualizationUpdateListener.updateVisualization(); } } }); c.gridy++; add(neighbourhoodModeBox, c); disconnectUnfavouredSpinner.addChangeListener(new ChangeListener() { @Override public void stateChanged(ChangeEvent e) { disconnectUnfavoured = disconnectUnfavouredModel.getNumber().intValue(); if (visualizationUpdateListener != null) { visualizationUpdateListener.updateVisualization(); } } }); JPanel disconnectLastXPanel = new JPanel(); JLabel label = new JLabel("Disconnect edges"); label.setToolTipText("Do not connect the X least favourable edges"); disconnectLastXPanel.add(label); disconnectLastXPanel.add(disconnectUnfavouredSpinner); c.gridy++; add(disconnectLastXPanel, c); } } @Override public int getPreferredScaleFactor() { // the visualisation is mostly lines => less zooming return 1; } }