/* * 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.apps.viewer; import at.tuwien.ifs.somtoolbox.SOMToolboxException; import at.tuwien.ifs.somtoolbox.apps.PaletteEditor; import at.tuwien.ifs.somtoolbox.data.SOMLibClassInformation; import at.tuwien.ifs.somtoolbox.data.SOMLibDataInformation; import at.tuwien.ifs.somtoolbox.data.SOMVisualisationData; import at.tuwien.ifs.somtoolbox.data.SharedSOMVisualisationData; import at.tuwien.ifs.somtoolbox.input.InputCorrections; import at.tuwien.ifs.somtoolbox.input.InputCorrections.CreationType; import at.tuwien.ifs.somtoolbox.input.InputCorrections.InputCorrection; import at.tuwien.ifs.somtoolbox.input.MnemonicSOMLibFormatInputReader; import at.tuwien.ifs.somtoolbox.input.SOMLibFileFormatException; import at.tuwien.ifs.somtoolbox.input.SOMLibFormatInputReader; import at.tuwien.ifs.somtoolbox.layers.GrowingLayer; import at.tuwien.ifs.somtoolbox.layers.LayerAccessException; import at.tuwien.ifs.somtoolbox.layers.Unit; import at.tuwien.ifs.somtoolbox.models.GrowingSOM; import at.tuwien.ifs.somtoolbox.util.LabelPNodeGenerator; import at.tuwien.ifs.somtoolbox.util.ProgressListener; import at.tuwien.ifs.somtoolbox.util.StdErrProgressWriter; import at.tuwien.ifs.somtoolbox.visualization.*; import at.tuwien.ifs.somtoolbox.visualization.clustering.*; import edu.umd.cs.piccolo.PNode; import edu.umd.cs.piccolo.nodes.PImage; import edu.umd.cs.piccolo.nodes.PPath; import javax.swing.*; import java.awt.*; import java.awt.geom.GeneralPath; import java.awt.geom.Line2D; import java.awt.geom.Point2D; import java.awt.image.BufferedImage; import java.io.FileNotFoundException; import java.util.*; import java.util.logging.Logger; /** * The graphical representation of a map in the {@link SOMViewer} application. This class makes use of the <a * href="http://www.cs.umd.edu/hcil/jazz/" target="_blank">Piccolo framework</a> and is the top {@link PNode} for all * map-level visualisations. The class holds several other PNodes as children: * <ul> * <li>An array of {@link GeneralUnitPNode} - {@link MapPNode#units}</li> * <li>The currently selected visualisation image - {@link MapPNode#currentVisualizationImage}</li> * <li>The currently selected background image - {@link MapPNode#backgroundImage}</li> * </ul> * * @author Michael Dittenbach * @version $Id: MapPNode.java 3939 2010-11-17 16:06:14Z frank $ */ public class MapPNode extends PNode { private static final long serialVersionUID = 1L; public static final int DEFAULT_UNIT_HEIGHT = 130; public static final int DEFAULT_UNIT_WIDTH = 130; public static final Font DEFAULT_TOOLTIP_FONT = new Font("Sans", Font.PLAIN, 10); public static final Font DEFAULT_TOOLTIP_FONT_UNITINFO = new Font("Courier", Font.PLAIN, 7); private GrowingSOM gsom = null; private SOMLibClassInformation classInfo = null; private SOMLibDataInformation dataInfo = null; private GeneralUnitPNode[][] units = null; private int UNIT_WIDTH = DEFAULT_UNIT_WIDTH; // 130 private int UNIT_HEIGHT = DEFAULT_UNIT_HEIGHT; // 130 /** @deprecated use {@link Visualizations} instead */ @Deprecated private BackgroundImageVisualizer[] visualizations = null; private BackgroundImageVisualizer currentVisualization = null; private int currentVisualizationVariant = 0; // private PImage[][] visualizationImageCache = null; private PImage backgroundImage = null; protected PImage currentVisualizationImage = null; // private SmoothedCountHistograms overlayVis; private AbstractMatrixVisualizer overlayVis; private int overlayVis_index = 0; private PImage overlayVisualizationImage = null; // Angela private SortedMap<Integer, ClusterElementsStorage> currentClusteringElements = new TreeMap<Integer, ClusterElementsStorage>(); private ClusteringTree currentClusteringTree = null; private PNode manualLabels = new PNode(); private PNode unitsNode = new PNode(); private PNode inputCorrectionsPNode = new PNode(); private SharedSOMVisualisationData inputObjects; private JFrame parentFrame; private CommonSOMViewerStateData state; private BufferedImage originalBackgroundImage; private boolean backgroundImageVisible; private PPath inputLinkagePath; private TreeBuilder clusteringTreeBuilder; private PNode clusterLines; /** * Default constructors - reading of input files not yet done. */ public MapPNode(JFrame parentFrame, String weightVectorFileName, String unitDescriptionFileName, String mapDescriptionFileName, CommonSOMViewerStateData state) throws FileNotFoundException, SOMLibFileFormatException { this(parentFrame, new MnemonicSOMLibFormatInputReader(weightVectorFileName, unitDescriptionFileName, mapDescriptionFileName), state, true); } /** * Constructor if input files have already been read. */ public MapPNode(JFrame parentFrame, SOMLibFormatInputReader inputReader, CommonSOMViewerStateData state, boolean inizializeVis) { super(); // create GrowingSOM gsom = new GrowingSOM(inputReader); GrowingLayer growingLayer = gsom.getLayer(); init(parentFrame, inputReader, state, growingLayer, inizializeVis); } /** * Constructor for a already loaded GrowingLayer - can be used for visualize sublayers of a ghsom! * * @param parentFrame The frame containing the {@link MapPNode} * @param gsom The SOM containing the growing layer * @param growingLayer The layer for which the {@link MapPNode} is generated * @param state The state of the SOMViewer */ public MapPNode(JFrame parentFrame, GrowingSOM gsom, GrowingLayer growingLayer, CommonSOMViewerStateData state) { super(); this.gsom = gsom; init(parentFrame, null, state, growingLayer, true); } private void init(JFrame parentFrame, SOMLibFormatInputReader inputReader, CommonSOMViewerStateData state, GrowingLayer growingLayer, boolean inizializeVis) { this.state = state; state.somInputReader = inputReader; this.parentFrame = parentFrame; state.mapPNode = this; state.growingLayer = growingLayer; state.growingSOM = gsom; inputObjects = state.inputDataObjects; gsom.setSharedInputObjects(inputObjects); classInfo = inputObjects.getClassInfo(); dataInfo = inputObjects.getDataInfo(); // rudi: read input mapping shifts. note: has to be done before the units are constructed & displayed SOMVisualisationData inputCorrectionContainer = state.inputDataObjects.getObject(SOMVisualisationData.INPUT_CORRECTIONS); String fileName = inputCorrectionContainer.getFileName(); if (fileName != null && !fileName.trim().equals("")) { try { InputCorrections inputCorrections = new InputCorrections(fileName, growingLayer, state.inputDataObjects.getInputData()); inputCorrectionContainer.setData(inputCorrections); } catch (SOMToolboxException e) { Logger.getLogger("at.tuwien.ifs.somtoolbox").severe(e.getMessage()); } } else if (inputCorrectionContainer.getData() == null) { // create an empty object inputCorrectionContainer.setData(new InputCorrections()); } // create unit nodes units = new GeneralUnitPNode[growingLayer.getXSize()][growingLayer.getYSize()]; unitsNode = new PNode(); unitsNode.addAttribute("type", "unitsNode"); addChild(unitsNode); try { if (inputObjects.getDataWinnerMapping() == null) { state.exactUnitPlacementEnabled = false; } ProgressListener progress = new StdErrProgressWriter(growingLayer.getUnitCount(), "Initialising unit ", 50); for (int j = 0; j < growingLayer.getYSize(); j++) { for (int i = 0; i < growingLayer.getXSize(); i++) { if (growingLayer.getUnit(i, j) != null) { // check needed for mnemonic SOMs (might not have all // units != null) Unit unit = growingLayer.getUnit(i, j); Point[][] locations = null; if (inputObjects.getDataWinnerMapping() != null) { locations = initInputLocations(unit); } units[i][j] = new GeneralUnitPNode(unit, state, classInfo, dataInfo, locations, UNIT_WIDTH, UNIT_HEIGHT); unitsNode.addChild(units[i][j]); } progress.progress(); } } } catch (LayerAccessException e) { Logger.getLogger("at.tuwien.ifs.somtoolbox").severe(e.getMessage()); System.exit(-1); } // if we have a class info on startup, we show classes if (classInfo != null) { state.setClassPiechartMode(SOMViewer.TOGGLE_PIE_CHARTS_SHOW); setClassColors(classInfo.getClassColors()); } // create tooltip object and add event listener // final ToolTipPNode tooltipNode = new ToolTipPNode(); // this.addChild(tooltipNode); // this.addInputEventListener(new MyMapInputEventHandler(tooltipNode, this)); // initialize available visualizations if (inizializeVis) { Visualizations.initVisualizations(inputObjects, inputReader, this); } visualizations = Visualizations.getAvailableVisualizations(); // Angela: add the empty nodes for manually created labels this.addChild(manualLabels); manualLabels.moveToFront(); // add input correction arrows addChild(inputCorrectionsPNode); inputCorrectionsPNode.moveToFront(); // rudi: display linkages between data items if (state.inputDataObjects.getLinkageMap() != null) { Logger.getLogger("at.tuwien.ifs.somtoolbox").info("Drawing constellations."); GeneralPath path = new GeneralPath(); Map<String, String> linkageMap = state.inputDataObjects.getLinkageMap(); for (String beginName : linkageMap.keySet()) { String endName = linkageMap.get(beginName); Point beginPoint = getPointLocation(beginName); Point endPoint = getPointLocation(endName); if (beginPoint != null && endPoint != null) { path.append(new Line2D.Double(beginPoint, endPoint), false); } } inputLinkagePath = new PPath(path); inputLinkagePath.setStrokePaint(new Color(232, 232, 57)); inputLinkagePath.setStroke(new BasicStroke(1.5f)); inputLinkagePath.setPickable(false); if (state.displayInputLinkage) { addChild(inputLinkagePath); inputLinkagePath.moveToBack(); } Logger.getLogger("at.tuwien.ifs.somtoolbox").info("Drawing constellations done."); } // display input mapping shifts. note: has to be done after the units are constructed createInputCorrectionArrows(); } public void createInputCorrectionArrows() { for (InputCorrection correction : inputObjects.getInputCorrections().getInputCorrections()) { ArrowPNode arrow = ArrowPNode.createInputCorrectionArrow(correction, InputCorrections.CreationType.MANUAL, getUnit(correction.getSourceUnit()), getUnit(correction.getTargetUnit())); inputCorrectionsPNode.addChild(arrow); arrow.moveToBack(); } } public void updateDetailsAfterMoving() { for (InputCorrection correction : inputObjects.getInputCorrections().getInputCorrections()) { getUnit(correction.getSourceUnit()).updateDetailsAfterMoving(); getUnit(correction.getTargetUnit()).updateDetailsAfterMoving(); } } /** Calculates the absolute position of an input on the {@link MapPNode}. */ private Point getPointLocation(String name) { Unit unitBegin = gsom.getLayer().getUnitForDatum(name); if (unitBegin != null) { GeneralUnitPNode generalUnitPNode = units[unitBegin.getXPos()][unitBegin.getYPos()]; Point offset = generalUnitPNode.getPostion(); int inputIndexBegin = unitBegin.getInputIndex(name); Point point = generalUnitPNode.getLocations()[inputIndexBegin]; return new Point(point.x + offset.x + InputPNode.WIDTH_2, point.y + offset.y + InputPNode.HEIGHT_2); } else { System.out.println("did not find datum " + name); return null; } } public Point[] getStarCoords(Unit unit, int unitSize) { int currentUW = UNIT_WIDTH; int currentUH = UNIT_HEIGHT; UNIT_WIDTH = unitSize; UNIT_HEIGHT = unitSize; Point[] coords = initInputLocations(unit)[1]; UNIT_WIDTH = currentUW; UNIT_HEIGHT = currentUH; return coords; } /** * Computes the locations of each input vector within a specific unit */ private Point[][] initInputLocations(Unit unit) { int size = unit.getNumberOfMappedInputs(); // Store XY displacements in a temporary array double[][] locdxy = new double[size][2]; double[][] locdxyShifted = new double[size][2]; // Process all mapped inputs in the unit for (int index = 0; index < size; index++) { // Find vector position for this mapped input String label = unit.getMappedInputName(index); int vindex; try { vindex = inputObjects.getDataWinnerMapping().getVectPos(label); } catch (SOMToolboxException e1) { Logger.getLogger("at.tuwien.ifs.somtoolbox").warning( e1.getMessage() + " Could not draw exact point on unit!"); // e1.printStackTrace(); continue; } // Get winners information int[] xpos = inputObjects.getDataWinnerMapping().getXPos(vindex); int[] ypos = inputObjects.getDataWinnerMapping().getYPos(vindex); double[] dist = inputObjects.getDataWinnerMapping().getDists(vindex); // Computes displacement of the input vector against the next best winning unit. for (int uindex = 1; uindex <= 3; uindex++) { double vx = xpos[uindex] - xpos[0]; double vy = ypos[uindex] - ypos[0]; // Find x-axis and y-axis pull force of the winner. The pull force of 2nd unit is higher than the 3rd // and so on double force = dist[0] / (dist[uindex] * uindex); // Now calculate displacement. The key is: farther the unit, lesser must be the displacement. // Additionally the displacement is zero if units are not pulling in different directions. // Consider unit U1 being positioned at [2, 4] and unit U2's position being [3, 4]. // Now clearly both units lie on the same position on y-axis and thus there is no need to compute // displacement for y-axis locdxy[index][0] += vx == 0 ? 0 : UNIT_WIDTH / 2 * force / vx; locdxy[index][1] += vy == 0 ? 0 : UNIT_WIDTH / 2 * force / vy; } locdxyShifted[index][0] = locdxy[index][0]; locdxyShifted[index][1] = locdxy[index][1]; // Compute force of replusion if the current input is ovlapping with any of the previous inputs // store locations into separate field to allow easy switching for (int rindex = 0; rindex < index; rindex++) { double distance = Point2D.distanceSq(locdxy[rindex][0], locdxy[rindex][1], locdxy[index][0], locdxy[index][1]); if (distance < InputPNode.MIN_DISTANCE_SQ) { // Its like other input pushing the this input towards boundary if its trying to overlap locdxyShifted[index][0] *= 1.03; locdxyShifted[index][1] *= 1.03; // And on the other sie this input is forcing that input to leave a free space for him by moving // towards center. // double factor = distance/InputPNode.MIN_DISTANCE_SQ; double factor = 0.8; locdxyShifted[rindex][0] *= factor; locdxyShifted[rindex][1] *= factor; } } } // Now calculate the actual XY position for display Point[][] locations = new Point[2][size]; for (int i = 0; i < size; i++) { locations[0][i] = new Point((int) (UNIT_WIDTH / 2 + locdxy[i][0]), (int) (UNIT_WIDTH / 2 + locdxy[i][1])); locations[1][i] = new Point((int) (UNIT_WIDTH / 2 + locdxyShifted[i][0]), (int) (UNIT_WIDTH / 2 + locdxyShifted[i][1])); } return locations; } /** * @deprecated use {@link Visualizations#singleton} instead */ @Deprecated public BackgroundImageVisualizer[] getVisualizations() { return visualizations; } /** @deprecated use {@link Visualizations} instead */ @Deprecated public ThematicClassMapVisualizer getThematicClassMapVisualizer() { for (BackgroundImageVisualizer visualization : visualizations) { if (visualization.getClass().equals(ThematicClassMapVisualizer.class)) { return (ThematicClassMapVisualizer) visualization; } } return null; } public BackgroundImageVisualizer getCurrentVisualization() { return currentVisualization; } public void setNoVisualization() { currentVisualization = null; currentVisualizationVariant = 0; if (currentVisualizationImage != null && currentVisualizationImage.isDescendentOf(this)) { this.removeChild(currentVisualizationImage); currentVisualizationImage = null; // System.out.println("currentVisualizationImage removed "+currentVisualizationImage); } } public void updateVisualization() { if (currentVisualization == null) { return; } try { PImage newVisualization = new PImage(getVisualisation()); if (currentVisualizationImage != null && currentVisualizationImage.isDescendentOf(this)) { this.removeChild(currentVisualizationImage); currentVisualizationImage = null; } currentVisualizationImage = newVisualization; initCurrentVisualisation(); } catch (SOMToolboxException e) { Logger.getLogger("at.tuwien.ifs.somtoolbox").severe(e.getMessage()); } } private void initCurrentVisualisation() { currentVisualizationImage.setScale(currentVisualization.getPreferredScaleFactor()); addChild(currentVisualizationImage); currentVisualizationImage.setPickable(false); currentVisualizationImage.moveToBack(); if (backgroundImage != null) { // move the visualisation on top of the background image moveInFrontOf(backgroundImage); backgroundImage.moveToBack(); } else { } } /** * Creates a histogram based visualization, which is *temporarily* placed over the current visualization * * @param hist int[][] with histogram data, e.g. unit counts * @param vis_index visualization variant index (0 = flat, 1 = smoothed) */ public void showHistogramOverlayVisualization(int[][] hist, int vis_index) { if (overlayVisualizationImage != null) { clearHistogramOverlayVisualization(); } overlayVis_index = vis_index; // overlayVis = new SmoothedCountHistograms(); overlayVis = new SearchResultHistogramVisualizer(hist); // overlayVis.setHistogram(hist); overlayVis.setPalette(state.getSOMViewer().getCurrentlySelectedPalette()); try { overlayVisualizationImage = new PImage(overlayVis.getVisualization(overlayVis_index, gsom, getScaledBackgroundImageWidth(overlayVis), getScaledBackgroundImageHeight(overlayVis))); } catch (SOMToolboxException e) { Logger.getLogger("at.tuwien.ifs.somtoolbox").severe(e.getMessage()); return; } overlayVisualizationImage.setScale(BackgroundImageVisualizer.DEFAULT_BACKGROUND_VISUALIZATION_SCALE); overlayVisualizationImage.setPickable(false); addChild(overlayVisualizationImage); } /** * Removes temporary histogram based visualization */ public void clearHistogramOverlayVisualization() { if (overlayVisualizationImage != null && overlayVisualizationImage.isDescendentOf(this)) { removeChild(overlayVisualizationImage); overlayVisualizationImage = null; overlayVis = null; overlayVis_index = 0; } } public boolean reversePalette() throws SOMToolboxException { if (overlayVisualizationImage != null && overlayVisualizationImage.isDescendentOf(this)) { // -> overlayVis is currently active overlayVis.reversePalette(); this.removeChild(overlayVisualizationImage); overlayVisualizationImage = new PImage(overlayVis.getVisualization(overlayVis_index, gsom, getScaledBackgroundImageWidth(overlayVis), getScaledBackgroundImageHeight(overlayVis))); overlayVisualizationImage.setScale(BackgroundImageVisualizer.DEFAULT_BACKGROUND_VISUALIZATION_SCALE); overlayVisualizationImage.setPickable(false); addChild(overlayVisualizationImage); } if (currentVisualizationImage != null && currentVisualizationImage.isDescendentOf(this)) { if (currentVisualization instanceof AbstractMatrixVisualizer) { ((AbstractMatrixVisualizer) currentVisualization).reversePalette(); } BufferedImage img = getVisualisation(); this.removeChild(currentVisualizationImage); currentVisualizationImage = new PImage(img); initCurrentVisualisation(); repaint(); return true; } return false; } /** * Reloads the given palette after it has been edited with the {@link PaletteEditor}, i.e. basically invalidates the * cache for all visualisations and then calls {@link MapPNode#changePalette(Palette)} */ public boolean reloadPaletteAfterEditing(Palette palette) throws SOMToolboxException { // FIXME: move this method to the Visualizations class ! for (BackgroundImageVisualizer visualization : visualizations) { if (visualization instanceof AbstractMatrixVisualizer) { ((AbstractMatrixVisualizer) visualization).invalidateCache(palette); } } return changePalette(palette); } public boolean changePalette(Palette palette) throws SOMToolboxException { boolean doneSomething = false; if (overlayVisualizationImage != null && overlayVisualizationImage.isDescendentOf(this)) { // -> overlayVis is currently active overlayVis.setPalette(palette); this.removeChild(overlayVisualizationImage); overlayVisualizationImage = new PImage(overlayVis.getVisualization(overlayVis_index, gsom, getScaledBackgroundImageWidth(overlayVis), getScaledBackgroundImageHeight(overlayVis))); overlayVisualizationImage.setScale(BackgroundImageVisualizer.DEFAULT_BACKGROUND_VISUALIZATION_SCALE); overlayVisualizationImage.setPickable(false); addChild(overlayVisualizationImage); } if (currentVisualizationImage != null && currentVisualizationImage.isDescendentOf(this)) { if (currentVisualization instanceof AbstractMatrixVisualizer) { ((AbstractMatrixVisualizer) currentVisualization).setPalette(palette); } BufferedImage img = getVisualisation(); this.removeChild(currentVisualizationImage); currentVisualizationImage = new PImage(img); initCurrentVisualisation(); repaint(); doneSomething = true; } // Angela: Palettes for Clustering if (currentClusteringTree != null) { // no need to set palette, it is read from state when recoloring // currentClusteringTree.setPalette(palette); currentClusteringTree.recolorTree(); doneSomething = true; } return doneSomething; } // Angela /** * Display the specified number of clusters */ public void showClusters(int count) { showClusters(count, false); } // Angela /** * Display the specified number of Clusters. * * @param sticky should this level ov clustering stay visible when other levels of clustering are displayed. */ public void showClusters(int count, boolean sticky) { if (clusteringTreeBuilder instanceof NonHierarchicalTreeBuilder) { // if we have a non-hierarchical tree, we need to replace the current clustering tree try { currentClusteringTree = ((NonHierarchicalTreeBuilder) clusteringTreeBuilder).getTree(units, count); } catch (ClusteringAbortedException e) { e.printStackTrace(); } // currentClusteringTree.setState(state); currentClusteringTree.recolorTree(); } if (currentClusteringTree == null) { setClusteringElements(null); } else { setClusteringElements(currentClusteringTree.getClusteringInto(count, sticky)); currentClusteringTree.recolorTree(); } } private float scaleLineWidth(float depth, float max, float min) { float MAX_LINE_WIDTH = 40.0f; float MIN_LINE_WIDTH = 1.0f; float lineWidth = depth - min; lineWidth /= max - min; lineWidth *= MAX_LINE_WIDTH - MIN_LINE_WIDTH; lineWidth += MIN_LINE_WIDTH; return Math.abs(lineWidth); } // Angela /** * Creates new {@link TreeBuilder}. if the builder is null, the current clustering is removed. */ public void buildTree(TreeBuilder builder) throws ClusteringAbortedException { if (clusterLines != null) { this.removeChild(clusterLines); clusterLines = null; } if (builder == null) { currentClusteringTree = null; } else { currentClusteringTree = builder.createTree(units); HashMap<PNode, Integer> distanceInfo = currentClusteringTree.getDendrogramDistanceInfo(); int maxLevel = Collections.max(distanceInfo.values()); ArrayList<ClusterNode> nodes = currentClusteringTree.getNodesAtLevel(maxLevel); float maxMerge = Float.MIN_VALUE; float minMerge = Float.MAX_VALUE; for (int i = 0; i < nodes.size(); i++) { float merge1 = (float) nodes.get(i).getMergeCost(); for (int j = i + 1; j < nodes.size(); j++) { float merge2 = (float) nodes.get(j).getMergeCost(); float mergeDiff = Math.abs(merge1 - merge2); if (mergeDiff > maxMerge) { maxMerge = mergeDiff; } if (mergeDiff < minMerge) { minMerge = mergeDiff; } } } int maxDepth = Integer.MIN_VALUE; int minDepth = Integer.MAX_VALUE; Integer[] depths = new Integer[0]; depths = distanceInfo.values().toArray(depths); for (int i = 0; i < depths.length; i++) { int depth1 = depths[i]; for (int j = i + 1; j < depths.length; j++) { int depth2 = depths[j]; int depthDiff = Math.abs(depth1 - depth2); if (depthDiff > maxDepth) { maxDepth = depthDiff; } if (depthDiff < minDepth) { minDepth = depthDiff; } } } clusterLines = new PNode(); double OFFSET = 25.0; for (int col = 0; col < units.length; col++) { for (int row = 0; row < units[col].length; row++) { if (row < units[col].length - 1) { GeneralUnitPNode unit1 = units[col][row]; GeneralUnitPNode unit2 = units[col][row + 1]; float x1 = (float) (unit1.getX() + unit1.getWidth() / 2); float y1 = (float) (unit1.getY() + unit1.getHeight() - OFFSET); float x2 = (float) (unit2.getX() + unit2.getWidth() / 2); float y2 = (float) (unit2.getY() + OFFSET); PPath line = PPath.createLine(x1, y1, x2, y2); ClusterNode n1 = currentClusteringTree.findClusterOf(unit1, maxLevel); ClusterNode n2 = currentClusteringTree.findClusterOf(unit2, maxLevel); // int depth = currentClusteringTree.compareClusterDistanceOfPNodes(unit1, unit2); float lineWidth = scaleLineWidth((float) Math.abs(n1.getMergeCost() - n2.getMergeCost()), maxMerge, minMerge); line.setStroke(new BasicStroke(lineWidth, BasicStroke.CAP_SQUARE, BasicStroke.JOIN_BEVEL)); clusterLines.addChild(line); } if (col < units.length - 1) { GeneralUnitPNode unit1 = units[col][row]; GeneralUnitPNode unit2 = units[col + 1][row]; float x1 = (float) (unit1.getX() + unit1.getWidth() + OFFSET); float y1 = (float) (unit1.getY() + unit1.getHeight() / 2); float x2 = (float) (unit2.getX() - OFFSET); float y2 = (float) (unit2.getY() + unit2.getHeight() / 2); PPath line = PPath.createLine(x1, y1, x2, y2); ClusterNode n1 = currentClusteringTree.findClusterOf(unit1, maxLevel); ClusterNode n2 = currentClusteringTree.findClusterOf(unit2, maxLevel); // int depth = currentClusteringTree.compareClusterDistanceOfPNodes(unit1, unit2); float lineWidth = scaleLineWidth((float) Math.abs(n1.getMergeCost() - n2.getMergeCost()), maxMerge, minMerge); line.setStroke(new BasicStroke(lineWidth, BasicStroke.CAP_SQUARE, BasicStroke.JOIN_BEVEL)); clusterLines.addChild(line); } } } clusterLines.moveToFront(); addChild(clusterLines); // currentClusteringTree.setState(state); currentClusteringTree.recolorTree(); } this.clusteringTreeBuilder = builder; } /** * Rebuilds a clustering from the given tree. Used for deserialization. */ public void buildTree(ClusteringTree tree) { currentClusteringTree = tree; setClusteringElements(tree.getAllClusteringElements()); tree.addEditLabelEventListenerToAll(); } public ClusteringTree getCurrentClusteringTree() { return this.currentClusteringTree; } // Angela /** * Replaces currentClusteringElements with elements and makes sure the correct stuff is displayed. Also accepts null * -> just removes. */ public void setClusteringElements(SortedMap<Integer, ClusterElementsStorage> elements) { // Remove all oldborders and labels if (currentClusteringElements != null) { HashSet<PNode> tmp = new HashSet<PNode>(); // use set to avoid duplicates (labels appearing on multiple // layers) for (ClusterElementsStorage n : currentClusteringElements.values()) { if (n.clusterBorders != null) { tmp.addAll(n.clusterBorders); } if (n.clusterLabels != null) { tmp.addAll(n.clusterLabels); } if (n.clusterColors != null) { tmp.addAll(n.clusterColors); } } this.removeChildren(tmp); } currentClusteringElements = elements; LinkedList<PNode> selectedBorder = new LinkedList<PNode>(); // show new elements if (elements != null) { for (ClusterElementsStorage n : currentClusteringElements.values()) { if (n.clusterBorders != null) { // System.out.println("Added border node " + n.border.hashCode() + " (" + n.hashCode() + ")"); for (PNode borderLine : n.clusterBorders) { if( ((PPath) borderLine.getChild(0)).getStrokePaint().equals(Color.RED)) selectedBorder.add(borderLine); addChild(borderLine); borderLine.moveToFront(); if (currentVisualizationImage != null) { borderLine.moveInFrontOf(currentVisualizationImage); } } } if (n.clusterLabels != null) { // System.out.println("Added label node " + n.labels.hashCode() + " (" + n.hashCode() + ")"); for (PNode label : n.clusterLabels) { addChild(label); label.moveToFront(); } } if (n.clusterColors != null) { for (PNode colorCluster : n.clusterColors) { addChild(colorCluster); colorCluster.moveToBack(); } } } } for(PNode sb : selectedBorder) sb.moveToFront(); // System.out.println("Applied new clustering " + elements.hashCode()); } // Angela: /** * Adds a manual label to the map. Pops up a Dialog asking for the text. The created label has the size 10x number * of units in width. */ public void createLabel() { // maybe move this code somewhere else... not 100% appropriate in this class. String txt = JOptionPane.showInputDialog("Enter label text:"); PNode bigLabel; // TODO: Fontsize: not nice to have it hard coded here... maybe something // like a maxFontSize for the cluster labels and make it depending on that value bigLabel = LabelPNodeGenerator.newLabel(txt, this.units.length * 10); manualLabels.addChild(bigLabel); // bigLabel.moveToFront(); } public PNode getManualLabels() { return manualLabels; } public ArrayList<PNode> getAllClusterLabels() { ArrayList<PNode> labels = new ArrayList<PNode>(); for (ClusterElementsStorage cluster : currentClusteringElements.values()) { if (cluster.clusterLabels != null) { labels.addAll(cluster.clusterLabels); } } return labels; } // rudi for storing levels of labels separately @SuppressWarnings("unchecked") public ArrayList<PNode>[] getClusterLabelsByLevel() { ArrayList<ArrayList<PNode>> labels = new ArrayList<ArrayList<PNode>>(); for (ClusterElementsStorage cluster : currentClusteringElements.values()) { if (cluster.clusterLabels != null) { labels.add(cluster.clusterLabels); } } return labels.toArray(new ArrayList[labels.size()]); } // Doris public ClusteringTree getClusteringTree() { return currentClusteringTree; } private int getScaledBackgroundImageWidth() { return getScaledBackgroundImageWidth(currentVisualization); } private int getScaledBackgroundImageWidth(BackgroundImageVisualizer visualization) { return gsom.getLayer().getXSize() * UNIT_WIDTH / visualization.getPreferredScaleFactor(); } private int getScaledBackgroundImageHeight() { return getScaledBackgroundImageHeight(currentVisualization); } private int getScaledBackgroundImageHeight(BackgroundImageVisualizer visualization) { return gsom.getLayer().getYSize() * UNIT_HEIGHT / visualization.getPreferredScaleFactor(); } private int getBackgroundImageWidth() { return gsom.getLayer().getXSize() * UNIT_WIDTH; } private int getBackgroundImageHeight() { return gsom.getLayer().getYSize() * UNIT_HEIGHT; } public boolean setVisualization(int vis, int variant) throws SOMToolboxException { return setVisualization(visualizations[vis], variant); } /** * Method to be used for setting the initial visualisation on startup - does not actually create the image, just * sets the {@link #currentVisualization} and {@link #currentVisualizationVariant}. */ public boolean setInitialVisualizationOnStartup(BackgroundImageVisualizer vis, int variant) throws SOMToolboxException { currentVisualization = vis; currentVisualizationVariant = variant; String[] neededInputs = currentVisualization.needsAdditionalFiles(); if (neededInputs != null && neededInputs.length > 0) { for (String neededInput : neededInputs) { SOMVisualisationData inputObject = inputObjects.getObject(neededInput); // System.out.println("in need of: " + inputObject.getFileName() + ", "+ inputObject.getType() + ", " + // inputObject.getName()); inputObject.loadFromFile(state.getFileChooser(), parentFrame); if (currentVisualization.needsAdditionalFiles() == null || currentVisualization.needsAdditionalFiles().length == 0) { // if after reading this file we do not need any additional ones break; // we stop asking for more files } } } // no further files needed try { if (getVisualisation() != null) { return true; } else { currentVisualization = null; currentVisualizationVariant = 0; return false; } } catch (SOMToolboxException e) { Logger.getLogger("at.tuwien.ifs.somtoolbox").severe(e.getMessage()); return false; } } /** * Gets the visualisation variant from the current {@link #currentVisualization} obtaining the values from * {@link #currentVisualizationVariant}, {@link #getScaledBackgroundImageWidth()} and * {@link #getScaledBackgroundImageHeight()}. */ private BufferedImage getVisualisation() throws SOMToolboxException { return currentVisualization.getVisualization(currentVisualizationVariant, gsom, getScaledBackgroundImageWidth(), getScaledBackgroundImageHeight()); } public boolean setVisualization(BackgroundImageVisualizer vis, int variant) throws SOMToolboxException { boolean res = false; BackgroundImageVisualizer oldVis = currentVisualization; int oldVariant = currentVisualizationVariant; currentVisualization = vis; currentVisualizationVariant = variant; String[] neededInputs = currentVisualization.needsAdditionalFiles(); if (neededInputs != null && neededInputs.length > 0) { for (String neededInput : neededInputs) { SOMVisualisationData inputObject = inputObjects.getObject(neededInput); // System.out.println("in need of: " + inputObject.getFileName() + ", "+ inputObject.getType() + ", " + // inputObject.getName()); inputObject.loadFromFile(state.getFileChooser(), parentFrame); if (currentVisualization.needsAdditionalFiles() == null || currentVisualization.needsAdditionalFiles().length == 0) { // if after reading this file we do not need any additional ones break; // we stop asking for more files } } } // no further files needed try { BufferedImage img = getVisualisation(); if (img != null) { PImage newVisualization = new PImage(img); if (currentVisualizationImage != null && currentVisualizationImage.isDescendentOf(this)) { this.removeChild(currentVisualizationImage); currentVisualizationImage = null; } currentVisualizationImage = newVisualization; res = true; } else { currentVisualization = oldVis; currentVisualizationVariant = oldVariant; res = false; return res; } initCurrentVisualisation(); return res; } catch (SOMToolboxException e) { // JOptionPane.showMessageDialog(parentFrame, e.getMessage(), "Error!", JOptionPane.ERROR_MESSAGE); // Logging Handler will show MessageDialog when Message is severe: Logger.getLogger("at.tuwien.ifs.somtoolbox").severe(e.getMessage()); return false; } } /** Return the GeneralUnitPNode at the unit index x/y. */ public GeneralUnitPNode getUnit(int x, int y) { return units[x][y]; } /** Return the GeneralUnitPNode at the unit index x/y. */ public GeneralUnitPNode getUnit(Unit u) { return units[u.getXPos()][u.getYPos()]; } /** Return the unit width in pixles. */ public int getUnitWidth() { return UNIT_WIDTH; } /** Return the unit height in pixels. */ public int getUnitHeight() { return UNIT_HEIGHT; } /** Return the map width in pixels, i.e. the unit width times the xSize of the map. */ @Override public double getWidth() { return UNIT_WIDTH * gsom.getLayer().getXSize(); } /** Return the map height in pixels, i.e. the height width times the ySize of the map. */ @Override public double getHeight() { return UNIT_HEIGHT * gsom.getLayer().getYSize(); } @Override public boolean setBounds(double x, double y, double width, double height) { if (super.setBounds(x, y, width, height)) { // setFrame(x, y, width, height); return true; } return false; } public void setLinkageVisibilityMode(boolean visible) { if (inputLinkagePath != null) { if (visible) { if (!inputLinkagePath.isDescendentOf(this)) { addChild(inputLinkagePath); } } else { if (inputLinkagePath.isDescendentOf(this)) { removeChild(inputLinkagePath); } } } } public void reInitUnitDetails() { for (int j = 0; j < gsom.getLayer().getYSize(); j++) { for (int i = 0; i < gsom.getLayer().getXSize(); i++) { if (units[i][j] != null) { // check needed for mnemonic SOMs units[i][j].reInitUnitDetails(); } } } } public Color[] getClassLegendColors() { if (classInfo != null) { return classInfo.getClassColors(); } else { return null; } } public void setClassColor(int index, Color color) { if (classInfo != null) { for (int j = 0; j < gsom.getLayer().getYSize(); j++) { for (int i = 0; i < gsom.getLayer().getXSize(); i++) { if (units[i][j] != null && units[i][j].hasPieCharts()) { // check needed for mnemonic SOMs units[i][j].setClassColor(index, color); } } } } } public void setClassColors(Color[] colors) { if (classInfo != null) { for (int j = 0; j < gsom.getLayer().getYSize(); j++) { for (int i = 0; i < gsom.getLayer().getXSize(); i++) { if (units[i][j] != null && units[i][j].hasPieCharts()) { // check needed for mnemonic SOMs units[i][j].setClassColors(colors); } } } } } public String[] getClassLegendNames() { if (classInfo != null) { return classInfo.classNames(); } else { return null; } } public void setShowOnlySelectedClasses(boolean selectedClassesOnly) { // TODO Auto-generated method stub for (int j = 0; j < gsom.getLayer().getYSize(); j++) { for (int i = 0; i < gsom.getLayer().getXSize(); i++) { if (units[i][j] != null) { // check needed for mnemonic SOMs units[i][j].setShowOnlySelectedClasses(selectedClassesOnly); } } } this.repaint(); } public void updateClassSelection(int[] indices) { // System.out.println(indices); for (int j = 0; j < gsom.getLayer().getYSize(); j++) { for (int i = 0; i < gsom.getLayer().getXSize(); i++) { if (units[i][j] != null) { // check needed for mnemonic SOMs units[i][j].updateClassSelection(indices); } } } this.repaint(); } public void updatePointLocations() { for (int j = 0; j < gsom.getLayer().getYSize(); j++) { for (int i = 0; i < gsom.getLayer().getXSize(); i++) { if (units[i][j] != null) { // check needed for mnemonic SOMs units[i][j].reInitUnitDetails(); } } } } public SharedSOMVisualisationData getInputObjects() { return inputObjects; } public void updateClassInfo(SOMLibClassInformation classInfo) { this.classInfo = classInfo; try { for (int j = 0; j < gsom.getLayer().getYSize(); j++) { for (int i = 0; i < gsom.getLayer().getXSize(); i++) { if (gsom.getLayer().getUnit(i, j) != null) { // check needed for mnemonic SOMs units[i][j].initClassPieCharts(units[i][j].getUnit(), classInfo, UNIT_WIDTH, UNIT_HEIGHT); // now let the new class pie charts be displayed (though the chosen solution seems like a // hack..) units[i][j].reInitUnitDetails(); units[i][j].updateClassSelection(null); } } } } catch (LayerAccessException e) { Logger.getLogger("at.tuwien.ifs.somtoolbox").severe(e.getMessage()); System.exit(-1); } } // public void setClassVisibility(boolean classVisibility) { // this.showClassInfo = classVisibility; // for (int j = 0; j < gsom.getLayer().getYSize(); j++) { // for (int i = 0; i < gsom.getLayer().getXSize(); i++) { // if (units[i][j] != null) { // check needed for mnemonic SOMs // units[i][j].setClassVisibility(classVisibility); // } // } // } // } public void updateClassVisibility() { for (int j = 0; j < gsom.getLayer().getYSize(); j++) { for (int i = 0; i < gsom.getLayer().getXSize(); i++) { if (units[i][j] != null) { // check needed for mnemonic SOMs units[i][j].reInitUnitDetails(); } } } } public GrowingSOM getGsom() { return gsom; } public void setBackgroundImage(BufferedImage background) { if (backgroundImage != null) { // remove a possibly already existing background iamge removeChild(backgroundImage); } originalBackgroundImage = background; backgroundImage = new PImage(background.getScaledInstance(getBackgroundImageWidth(), getBackgroundImageHeight(), Image.SCALE_SMOOTH)); backgroundImage.setPickable(false); setBackgroundImageVisibility(true); } public void setBackgroundImageVisibility(boolean visible) { backgroundImageVisible = visible; if (visible) { addChild(backgroundImage); backgroundImage.moveToBack(); } else { removeChild(backgroundImage); } } public void reInitLabels() { // FIXME Khalid: GeneralUnitPNode constructor requires locations /* * for (int j=0;j<gsom.getLayer().getYSize();j++) { for (int i=0;i<gsom.getLayer().getXSize();i++) { //units[i][j].reInitLabels(); try { if * (gsom.getLayer().getUnit(i,j) != null) { // check needed for mnemonic SOMs (might not have all units != null) units[i][j] = new * GeneralUnitPNode(gsom.getLayer().getUnit(i,j), classInfo, dataInfo, UNIT_WIDTH, UNIT_HEIGHT); units[i][j].repaint(); } } catch * (LayerAccessException e) { // TODO Auto-generated catch block e.printStackTrace(); } } } repaint(); */ } public BufferedImage getBackgroundImage() { return originalBackgroundImage; } public boolean isBackgroundImageVisible() { return backgroundImageVisible; } public CommonSOMViewerStateData getState() { return state; } public JFrame getParentFrame() { return parentFrame; } /** * Resets the list of arrows originating from a unit for each unit on the map. */ public void resetArrows() { for (GeneralUnitPNode[] unit : units) { for (int j = 0; j < units[0].length; j++) { unit[j].resetArrows(); } } } public void reInitUnitPNodes(int detailLevel) { for (GeneralUnitPNode[] unit : units) { for (int j = 0; j < units[0].length; j++) { unit[j].reInitUnitDetails(detailLevel); } } } public PNode getInputCorrectionsPNode() { return inputCorrectionsPNode; } public void setInputCorrectionsVisible(boolean visible) { inputCorrectionsPNode.setVisible(visible); } public void clearInputCorrections() { inputCorrectionsPNode.removeAllChildren(); } public void clearInputCorrections(CreationType type) { ArrayList<PNode> toRemove = new ArrayList<PNode>(); for (Iterator<?> iterator = inputCorrectionsPNode.getChildrenIterator(); iterator.hasNext();) { ArrowPNode node = (ArrowPNode) iterator.next(); if (node.getCreationType() == type) { toRemove.add(node); } } inputCorrectionsPNode.removeChildren(toRemove); } /** * @return Returns the builder. */ public TreeBuilder getClusteringTreeBuilder() { return clusteringTreeBuilder; } public GeneralUnitPNode getGeneralUnitPNodeAtPos(Point2D p) { if (unitsNode.getFullBounds().contains(p)) { int xPos = (int) Math.floor(p.getX() / getUnitWidth()); int yPos = (int) Math.floor(p.getY() / getUnitHeight()); return getUnit(xPos, yPos); } else { return null; } } public GeneralUnitPNode getGeneralUnitPNodeAtPos(double x, double y) { return getGeneralUnitPNodeAtPos(new Point2D.Double(x, y)); } public GeneralUnitPNode getGeneralUnitPNodeAtPos(float x, float y) { return getGeneralUnitPNodeAtPos(new Point2D.Float(x, y)); } public PNode getUnitsNode() { return unitsNode; } }