/* * RapidMiner * * Copyright (C) 2001-2011 by Rapid-I and the contributors * * Complete list of developers available at our web site: * * http://rapid-i.com * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero 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 Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see http://www.gnu.org/licenses/. */ package com.rapidminer.gui.graphs; import java.awt.Dimension; import java.awt.Paint; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.Set; import java.util.SortedSet; import java.util.TreeSet; import javax.swing.JComboBox; import javax.swing.JComponent; import javax.swing.JLabel; import javax.swing.JSlider; import javax.swing.JSpinner; import javax.swing.SpinnerNumberModel; import javax.swing.SwingConstants; import javax.swing.event.ChangeEvent; import javax.swing.event.ChangeListener; import org.apache.commons.collections15.Factory; import org.apache.commons.collections15.Transformer; import com.rapidminer.ObjectVisualizer; import com.rapidminer.example.Attribute; import com.rapidminer.example.Example; import com.rapidminer.example.ExampleSet; import com.rapidminer.gui.tools.ExtendedJComboBox; import com.rapidminer.gui.tools.SwingTools; import com.rapidminer.operator.visualization.dependencies.TransitionGraph; import com.rapidminer.tools.ObjectVisualizerService; import com.rapidminer.tools.Tools; import edu.uci.ics.jung.graph.DirectedSparseGraph; import edu.uci.ics.jung.graph.Graph; import edu.uci.ics.jung.graph.util.EdgeType; import edu.uci.ics.jung.visualization.VisualizationViewer; /** * The graph model creator for transition graphs. * * @author Ingo Mierswa */ public class TransitionGraphCreator extends GraphCreatorAdaptor { private static class SourceId implements Comparable<SourceId> { private final String id; private final String label; public SourceId(String id, String label) { this.id = id; this.label = label; } public String getId() { return id; } @Override public String toString() { return label; } public int compareTo(SourceId o) { return this.label.compareTo(o.label); } @Override public boolean equals(Object obj) { if (this == obj) return true; if (obj == null) return false; if (getClass() != obj.getClass()) return false; SourceId other = (SourceId) obj; if (label == null) { if (other.label != null) return false; } else if (!label.equals(other.label)) return false; return true; } } private final Factory<String> edgeFactory = new Factory<String>() { int i = 0; public String create() { return "E" + i++; } }; private final JSlider edgeSlider = new JSlider(SwingConstants.HORIZONTAL, 0, 1000, 100) { private static final long serialVersionUID = -6931545310805789589L; @Override public Dimension getMinimumSize() { return new Dimension(40, (int)super.getMinimumSize().getHeight()); } @Override public Dimension getPreferredSize() { return new Dimension(40, (int)super.getPreferredSize().getHeight()); } @Override public Dimension getMaximumSize() { return new Dimension(40, (int)super.getMaximumSize().getHeight()); } }; private final JComboBox sourceFilter; private final JSpinner numberOfHops = new JSpinner(new SpinnerNumberModel(1, 1, Integer.MAX_VALUE, 1)); private Graph<String,String> graph; private final Attribute sourceAttribute; private final Attribute targetAttribute; private Attribute strengthAttribute; private Attribute typeAttribute; private final String nodeDescription; private final ExampleSet exampleSet; private final Map<String, String> edgeLabelMap = new HashMap<String, String>(); private final Map<String, Double> edgeStrengthMap = new HashMap<String, Double>(); private final Map<String, String> vertexLabelMap = new HashMap<String, String>(); private final DefaultObjectViewer objectViewer; public TransitionGraphCreator(TransitionGraph transitionGraph, ExampleSet exampleSet) { this.sourceAttribute = exampleSet.getAttributes().get(transitionGraph.getSourceAttribute()); this.targetAttribute = exampleSet.getAttributes().get(transitionGraph.getTargetAttribute()); if (transitionGraph.getStrengthAttribute() != null) this.strengthAttribute = exampleSet.getAttributes().get(transitionGraph.getStrengthAttribute()); if (transitionGraph.getTypeAttribute() != null) this.typeAttribute = exampleSet.getAttributes().get(transitionGraph.getTypeAttribute()); this.exampleSet = exampleSet; this.nodeDescription = transitionGraph.getNodeDescription(); SortedSet<SourceId> sourceNames = new TreeSet<SourceId>(); Attribute idAttribute = exampleSet.getAttributes().getId(); for (Example example : exampleSet) { Object id = example.getValue(idAttribute); if (idAttribute.isNominal()) id = example.getValueAsString(idAttribute); String description = getNodeDescription(id); if (description == null) { sourceNames.add(new SourceId(id.toString(), id.toString())); } else { sourceNames.add(new SourceId(id.toString(), description)); } } sourceFilter = new ExtendedJComboBox(200); sourceFilter.addItem(new SourceId("None", "None")); for (SourceId sourceId : sourceNames) { sourceFilter.addItem(sourceId); } objectViewer = new DefaultObjectViewer(exampleSet); } public Graph<String,String> createGraph() { graph = new DirectedSparseGraph<String, String>(); updateGraph(); return graph; } @Override public String getEdgeName(String id) { return edgeLabelMap.get(id); } @Override public String getVertexName(String id) { String storedName = vertexLabelMap.get(id); if (storedName == null) { return id; } else { return storedName; } } @Override public String getVertexToolTip(String id) { return id; } /** Returns the label offset. In most case, using -1 is just fine (default offset). * Some tree like graphs might prefer to use 0 since they manage the offset themself. */ @Override public int getLabelOffset() { return -1; } @Override public int getNumberOfOptionComponents() { return 6; } @Override public JComponent getOptionComponent(final GraphViewer viewer, int index) { if (index == 0) { return new JLabel("Source Filter:"); } else if (index == 1) { sourceFilter.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { updateGraph(); viewer.updateLayout(); } }); return sourceFilter; } else if (index == 2) { return new JLabel("Number of Hops:"); } else if (index == 3) { this.numberOfHops.addChangeListener(new ChangeListener() { public void stateChanged(ChangeEvent e) { updateGraph(); viewer.updateLayout(); } }); return numberOfHops; } else if (index == 4) { return new JLabel("Number of Edges:"); } else if (index == 5) { this.edgeSlider.addChangeListener(new ChangeListener() { public void stateChanged(ChangeEvent e) { if (!edgeSlider.getValueIsAdjusting()) { updateGraph(); viewer.updateLayout(); } } }); return edgeSlider; } else { return null; } } private void updateGraph() { // remove old edges if available Iterator<String> e = edgeLabelMap.keySet().iterator(); while (e.hasNext()) { graph.removeEdge(e.next()); } edgeLabelMap.clear(); edgeStrengthMap.clear(); // remove old vertices if available Iterator<String> v = vertexLabelMap.keySet().iterator(); while (v.hasNext()) { graph.removeVertex(v.next()); } vertexLabelMap.clear(); String sourceFilterName = null; if (sourceFilter.getSelectedIndex() > 0) { sourceFilterName = ((SourceId) sourceFilter.getSelectedItem()).getId(); } List<SortableEdge> sortableEdges = new LinkedList<SortableEdge>(); if (sourceFilterName == null) { for (Example example : exampleSet) { String source = example.getValueAsString(sourceAttribute); String target = example.getValueAsString(targetAttribute); double strength = 1.0d; if (strengthAttribute != null) strength = example.getValue(strengthAttribute); String type = null; if (typeAttribute != null) type = example.getValueAsString(typeAttribute); String edgeName = null; if (type != null) { edgeName = type; } else { edgeName = strength + ""; } sortableEdges.add(new SortableEdge(source, target, edgeName, strength, SortableEdge.DIRECTION_INCREASE)); } } else { List<String> sources = new LinkedList<String>(); sources.add(sourceFilterName); int hop = 1; int maxHops = (Integer)numberOfHops.getValue(); do { List<String> newSources = new LinkedList<String>(); for (String currentSourceFilterName : sources) { for (Example example : exampleSet) { String source = example.getValueAsString(sourceAttribute); if (currentSourceFilterName != null) { if (!currentSourceFilterName.equals(source)) { continue; } } String target = example.getValueAsString(targetAttribute); double strength = 1.0d; if (strengthAttribute != null) strength = example.getValue(strengthAttribute); String type = null; if (typeAttribute != null) type = example.getValueAsString(typeAttribute); String edgeName = null; if (type != null) { edgeName = type; } else { edgeName = strength + ""; } sortableEdges.add(new SortableEdge(source, target, edgeName, strength, SortableEdge.DIRECTION_INCREASE)); newSources.add(target); } } sources.clear(); hop++; if (hop > maxHops) { sources = null; } else { sources = newSources; } } while (sources != null); } Collections.sort(sortableEdges); // determine used vertices Set<String> allVertices = new HashSet<String>(); int numberOfEdges = edgeSlider.getValue(); int counter = 0; for (SortableEdge sortableEdge : sortableEdges) { if (counter > numberOfEdges) break; allVertices.add(sortableEdge.getFirstVertex()); allVertices.add(sortableEdge.getSecondVertex()); counter++; } // add all used vertices to graph for (String vertex : allVertices) { graph.addVertex(vertex); String description = getNodeDescription(vertex); if (description == null) { vertexLabelMap.put(vertex, vertex); } else { vertexLabelMap.put(vertex, description); } } counter = 0; double minStrength = Double.POSITIVE_INFINITY; double maxStrength = Double.NEGATIVE_INFINITY; Map<String,Double> strengthMap = new HashMap<String, Double>(); for (SortableEdge sortableEdge : sortableEdges) { if (counter > numberOfEdges) break; String idString = edgeFactory.create(); graph.addEdge(idString, sortableEdge.getFirstVertex(), sortableEdge.getSecondVertex(), EdgeType.DIRECTED); edgeLabelMap.put(idString, Tools.formatIntegerIfPossible(sortableEdge.getEdgeValue())); double strength = sortableEdge.getEdgeValue(); minStrength = Math.min(minStrength, strength); maxStrength = Math.max(maxStrength, strength); strengthMap.put(idString, strength); counter++; } for (Entry<String, Double> entry : strengthMap.entrySet()) { edgeStrengthMap.put(entry.getKey(), (strengthMap.get(entry.getKey()) - minStrength) / (maxStrength - minStrength)); } } private String getNodeDescription(Object vertexId) { if (nodeDescription != null) { ObjectVisualizer visualizer = ObjectVisualizerService.getVisualizerForObject(exampleSet); if (visualizer != null) { if (visualizer.isCapableToVisualize(vertexId)) { StringBuffer resultString = new StringBuffer(); int currentIndex = 0; int startIndex = nodeDescription.indexOf("%{", currentIndex); while (startIndex >= currentIndex) { int endIndex = nodeDescription.indexOf("}", startIndex); if (endIndex >= startIndex) { String fieldName = nodeDescription.substring(startIndex + 2, endIndex); String fieldValue = visualizer.getDetailData(vertexId, fieldName); resultString.append(nodeDescription.substring(currentIndex, startIndex)); if (fieldValue != null) resultString.append(fieldValue); else resultString.append("?"); currentIndex = endIndex + 1; } else { resultString.append(nodeDescription.substring(startIndex)); currentIndex = nodeDescription.length(); } startIndex = nodeDescription.indexOf("%{", currentIndex); } if (currentIndex < nodeDescription.length()) { resultString.append(nodeDescription.substring(currentIndex)); } return resultString.toString(); } } } return null; } @Override public Transformer<String, Paint> getVertexPaintTransformer(VisualizationViewer<String, String> viewer) { return new Transformer<String, Paint>() { public Paint transform(String name) { if ((sourceFilter.getSelectedIndex() > 0) && (((SourceId)sourceFilter.getSelectedItem()).getId().equals(name))) { return SwingTools.LIGHT_YELLOW; } else { return SwingTools.LIGHT_BLUE; } } }; } /** Returns false. */ @Override public boolean showEdgeLabelsDefault() { return false; } /** Returns false. */ @Override public boolean showVertexLabelsDefault() { return true; } @Override public double getEdgeStrength(String id) { Double value = edgeStrengthMap.get(id); if (value == null) { return 1.0d; } else { if (Double.isNaN(value)) return 1.0d; else return value; } } /** Returns the shape of the edges. */ @Override public int getEdgeShape() { return EDGE_SHAPE_QUAD_CURVE; } @Override public Object getObject(String id) { return id; } @Override public GraphObjectViewer getObjectViewer() { return objectViewer; } }