/** * Copyright (C) 2001-2017 by RapidMiner and the contributors * * Complete list of developers available at our web site: * * http://rapidminer.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.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.Iterator; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Map.Entry; import javax.swing.JComponent; import javax.swing.JLabel; import javax.swing.JSlider; import javax.swing.SwingConstants; import javax.swing.event.ChangeEvent; import javax.swing.event.ChangeListener; import org.apache.commons.collections15.Factory; import com.rapidminer.example.Attribute; import com.rapidminer.example.Example; import com.rapidminer.example.ExampleSet; import com.rapidminer.gui.tools.SwingTools; import com.rapidminer.tools.Tools; import com.rapidminer.tools.container.Pair; import com.rapidminer.tools.math.similarity.DistanceMeasure; import edu.uci.ics.jung.graph.Graph; import edu.uci.ics.jung.graph.UndirectedSparseGraph; import edu.uci.ics.jung.graph.util.EdgeType; import edu.uci.ics.jung.visualization.renderers.Renderer.EdgeLabel; /** * The graph model creator for similarity measurements. * * @author Ingo Mierswa */ public class SimilarityGraphCreator extends GraphCreatorAdaptor { private Factory<String> edgeFactory = new Factory<String>() { int i = 0; @Override public String create() { return "E" + i++; } }; private JSlider distanceSlider = 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 Graph<String, String> graph; private DistanceMeasure measure; private ExampleSet exampleSet; private Map<String, String> edgeLabelMap = new HashMap<>(); private Map<String, Double> edgeStrengthMap = new HashMap<>(); private Map<String, List<Pair<String, Double>>> adjacencyMap = new HashMap<>(); private DefaultObjectViewer objectViewer; public SimilarityGraphCreator(DistanceMeasure measure, ExampleSet exampleSet) { this.measure = measure; this.exampleSet = exampleSet; objectViewer = new DefaultObjectViewer(exampleSet); } @Override public Graph<String, String> createGraph() { graph = new UndirectedSparseGraph<String, String>(); Attribute id = exampleSet.getAttributes().getId(); if (id != null) { for (Example example : exampleSet) { graph.addVertex(example.getValueAsString(id)); } addEdges(); } return graph; } @Override public String getEdgeName(String id) { return edgeLabelMap.get(id); } @Override public String getVertexName(String id) { return id; } @Override public String getVertexToolTip(String id) { return createTooltip(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 2; } @Override public JComponent getOptionComponent(final GraphViewer<?, ?> viewer, int index) { if (index == 0) { return new JLabel("Number of Edges:"); } else if (index == 1) { this.distanceSlider.addChangeListener(new ChangeListener() { @Override public void stateChanged(ChangeEvent e) { if (!distanceSlider.getValueIsAdjusting()) { addEdges(); viewer.updateLayout(); } } }); return distanceSlider; } else { return null; } } private void addEdges() { // remove old edges if available Iterator<String> e = edgeLabelMap.keySet().iterator(); while (e.hasNext()) { graph.removeEdge(e.next()); } edgeLabelMap.clear(); adjacencyMap.clear(); boolean isDistance = measure.isDistance(); Attribute id = exampleSet.getAttributes().getId(); List<SortableEdge> sortableEdges = new LinkedList<SortableEdge>(); for (int i = 0; i < exampleSet.size(); i++) { Example example = exampleSet.getExample(i); for (int j = i + 1; j < exampleSet.size(); j++) { Example comExample = exampleSet.getExample(j); if (isDistance) { sortableEdges.add(new SortableEdge(example.getValueAsString(id), comExample.getValueAsString(id), null, measure.calculateDistance(example, comExample), SortableEdge.DIRECTION_INCREASE)); } else { sortableEdges.add(new SortableEdge(example.getValueAsString(id), comExample.getValueAsString(id), null, measure.calculateSimilarity(example, comExample), SortableEdge.DIRECTION_DECREASE)); } } } Collections.sort(sortableEdges); int numberOfEdges = distanceSlider.getValue(); int 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.UNDIRECTED); edgeLabelMap.put(idString, Tools.formatIntegerIfPossible(sortableEdge.getEdgeValue())); List<Pair<String, Double>> edgesFirstList = adjacencyMap.get(sortableEdge.getFirstVertex()); if (edgesFirstList == null) { edgesFirstList = new ArrayList<>(); adjacencyMap.put(sortableEdge.getFirstVertex(), edgesFirstList); } edgesFirstList.add(new Pair<>(sortableEdge.getSecondVertex(), sortableEdge.getEdgeValue())); List<Pair<String, Double>> edgesSecondList = adjacencyMap.get(sortableEdge.getSecondVertex()); if (edgesSecondList == null) { edgesSecondList = new ArrayList<>(); adjacencyMap.put(sortableEdge.getSecondVertex(), edgesSecondList); } edgesSecondList.add(new Pair<>(sortableEdge.getFirstVertex(), 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(), (entry.getValue() - minStrength) / (maxStrength - minStrength)); } } /** Returns true. */ @Override public boolean showEdgeLabelsDefault() { return true; } /** Returns false. */ @Override public boolean showVertexLabelsDefault() { return false; } @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; } @Override public EdgeLabel<String, String> getEdgeLabelRenderer() { return new TreeModelEdgeLabelRenderer<String, String>(); } /** * Create the tooltip for a node. * * @param id * the id of the node for which to create the tooltip * @return the HTML-formatted tooltip string */ private String createTooltip(String id) { StringBuilder sb = new StringBuilder(); sb.append("<html><div style=\"font-size: 10px; font-family: 'Open Sans'\">"); sb.append("<p style=\"font-size: 110%; text-align: center; font-family: 'Open Sans Semibold'\"><b>" + id + "</b><hr NOSHADE style=\"color: '#000000'; width: 95%; \"/></p><br/>"); sb.append("Top adjacent items: " + SwingTools.transformToolTipText(formatAdjacencyMap(adjacencyMap.get(id)), false, 200, false, false)); sb.append("</div></html>"); return sb.toString(); } /** * Displays the rule premise/conclusion list contents nicely formatted for humans. * * @param dependencyList * the list of premises/conclusions of the rule to format * @return the formatted string */ private String formatAdjacencyMap(List<Pair<String, Double>> list) { StringBuilder sb = new StringBuilder(); if (list == null) { sb.append('-'); return sb.toString(); } int counter = 0; for (Pair<String, Double> edge : list) { sb.append("<br/>"); sb.append(edge.getFirst()); sb.append(' '); sb.append('-'); sb.append(' '); sb.append("<i>"); sb.append(Tools.formatNumber(edge.getSecond(), 2)); sb.append("</i>"); if (counter++ >= 2) { break; } } return sb.toString(); } }