/* Alloy Analyzer 4 -- Copyright (c) 2007-2008, Derek Rayside * * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files * (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, * merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF * OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ package edu.mit.csail.sdg.alloy4viz; import java.util.LinkedHashSet; import java.util.List; import java.util.Set; import edu.mit.csail.sdg.alloy4.ConstList; import edu.mit.csail.sdg.alloy4.Util; import edu.mit.csail.sdg.alloy4.ConstList.TempList; import edu.mit.csail.sdg.alloy4graph.DotColor; import edu.mit.csail.sdg.alloy4graph.DotPalette; import edu.mit.csail.sdg.alloy4graph.DotShape; import static edu.mit.csail.sdg.alloy4graph.DotShape.BOX; import static edu.mit.csail.sdg.alloy4graph.DotShape.DIAMOND; import static edu.mit.csail.sdg.alloy4graph.DotShape.TRAPEZOID; import static edu.mit.csail.sdg.alloy4graph.DotShape.HOUSE; import static edu.mit.csail.sdg.alloy4graph.DotShape.ELLIPSE; import static edu.mit.csail.sdg.alloy4graph.DotShape.EGG; import static edu.mit.csail.sdg.alloy4graph.DotShape.HEXAGON; import static edu.mit.csail.sdg.alloy4graph.DotShape.OCTAGON; import static edu.mit.csail.sdg.alloy4graph.DotShape.INV_HOUSE; import static edu.mit.csail.sdg.alloy4graph.DotShape.INV_TRAPEZOID; import static edu.mit.csail.sdg.alloy4graph.DotShape.INV_TRIANGLE; import static edu.mit.csail.sdg.alloy4graph.DotShape.DOUBLE_OCTAGON; import static edu.mit.csail.sdg.alloy4graph.DotShape.TRIPLE_OCTAGON; import static edu.mit.csail.sdg.alloy4graph.DotShape.M_CIRCLE; import static edu.mit.csail.sdg.alloy4graph.DotShape.M_DIAMOND; import static edu.mit.csail.sdg.alloy4graph.DotShape.M_SQUARE; import static edu.mit.csail.sdg.alloy4graph.DotShape.PARALLELOGRAM; import edu.mit.csail.sdg.alloy4graph.DotStyle; /** This class implements the automatic visualization inference. * * <p><b>Thread Safety:</b> Can be called only by the AWT event thread. */ final class MagicColor { /** The VizState object that we're going to configure. */ private final VizState vizState; /** Constructor. */ private MagicColor(final VizState vizState) { this.vizState = vizState; } /** Main method to infer settings. */ public static void magic(final VizState vizState) { vizState.setNodePalette(DotPalette.MARTHA); final MagicColor st = new MagicColor(vizState); st.nodeNames(); st.nodeShape(); st.nodeColour(); st.skolemColour(); } /** SYNTACTIC/VISUAL: Determine colours for nodes. * * when do we color things and what is the meaning of color * <ul> * <li> symmetry breaking: colors only matter up to recoloring (diff from * shape!) * <li> color substitutes for name/label * </ul> */ private void nodeColour() { final Set<AlloyType> visibleUserTypes = MagicUtil.visibleUserTypes(vizState); final Set<AlloyType> uniqueColourTypes; if (visibleUserTypes.size() <= 5) { // can give every visible user type its own shape uniqueColourTypes = visibleUserTypes; } else { // give every top-level visible user type its own shape uniqueColourTypes = MagicUtil.partiallyVisibleUserTopLevelTypes(vizState); } int index = 0; for (final AlloyType t : uniqueColourTypes) { vizState.nodeColor.put(t, (DotColor) DotColor.valuesWithout(DotColor.MAGIC)[index]); index = (index + 1) % DotColor.valuesWithout(DotColor.MAGIC).length; } } /** SYNTACTIC/VISUAL: Determine colour highlighting for skolem constants. */ private void skolemColour() { final Set<AlloySet> sets = vizState.getCurrentModel().getSets(); for (final AlloySet s : sets) { // change the style vizState.nodeStyle.put(s, DotStyle.BOLD); // change the label String label = vizState.label.get(s); final int lastUnderscore = label.lastIndexOf('_'); if (lastUnderscore >= 0) { label = label.substring(lastUnderscore+1); } vizState.label.put(s, label); } } /** The list of shape families. */ private static final List<ConstList<DotShape>> families; static { TempList<ConstList<DotShape>> list = new TempList<ConstList<DotShape>>(); list.add(Util.asList(BOX, TRAPEZOID, HOUSE)); list.add(Util.asList(ELLIPSE, EGG)); list.add(Util.asList(HEXAGON, OCTAGON, DOUBLE_OCTAGON, TRIPLE_OCTAGON)); list.add(Util.asList(INV_TRIANGLE, INV_HOUSE, INV_TRAPEZOID)); list.add(Util.asList(M_DIAMOND, M_SQUARE, M_CIRCLE)); list.add(Util.asList(PARALLELOGRAM, DIAMOND)); families = list.makeConst(); } /** SYNTACTIC/VISUAL: Determine shapes for nodes. * <ul> * <li> trapezoid, hexagon, rectangle, ellipse, circle, square -- no others * <li> actual shape matters -- do not break symmetry as with color * <li> ellipse by default * <li> circle if special extension of ellipse * <li> rectangle if lots of attributes * <li> square if special extension of rectangle * <li> when to use other shapes? * * </ul> */ private void nodeShape() { final Set<List<DotShape>> usedShapeFamilies = new LinkedHashSet<List<DotShape>>(); final Set<AlloyType> topLevelTypes = MagicUtil.partiallyVisibleUserTopLevelTypes(vizState); for (final AlloyType t : topLevelTypes) { // get the type family final Set<AlloyType> subTypes = MagicUtil.visibleSubTypes(vizState, t); final boolean isTvisible = MagicUtil.isActuallyVisible(vizState, t); final int size = subTypes.size() + (isTvisible ? 1 : 0); //log("TopLevelType: " + t + " -- " + subTypes + " " + size); // match it to a shape family // 1. look for exact match boolean foundExactMatch = false; for (final List<DotShape> shapeFamily: families) { if (size == shapeFamily.size() && !usedShapeFamilies.contains(shapeFamily)) { // found a match! usedShapeFamilies.add(shapeFamily); assignNodeShape(t, subTypes, isTvisible, shapeFamily); foundExactMatch = true; break; } } if (foundExactMatch) continue; // 2. look for approximate match List<DotShape> approxShapeFamily = null; int approxShapeFamilyDistance = Integer.MAX_VALUE; for (final List<DotShape> shapeFamily: families) { if (size <= shapeFamily.size() && !usedShapeFamilies.contains(shapeFamily)) { // found a potential match final int distance = shapeFamily.size() - size; if (distance < approxShapeFamilyDistance) { // it's a closer fit than the last match, keep it for now approxShapeFamily = shapeFamily; approxShapeFamilyDistance = distance; } } } if (approxShapeFamily != null) { // use the best approximate match that we just found usedShapeFamilies.add(approxShapeFamily); assignNodeShape(t, subTypes, isTvisible, approxShapeFamily); } // 3. re-use a shape family matched to something else -- just give up for now } } /** Helper for nodeShape(). */ private void assignNodeShape(final AlloyType t, final Set<AlloyType> subTypes, final boolean isTvisible, final List<DotShape> shapeFamily) { int index = 0; // shape for t, if visible if (isTvisible) { final DotShape shape = shapeFamily.get(index++); //log("AssignNodeShape " + t + " " + shape); vizState.shape.put(t, shape); } // shapes for visible subtypes for (final AlloyType subt : subTypes) { final DotShape shape = shapeFamily.get(index++); //log("AssignNodeShape " + subt + " " + shape); vizState.shape.put(subt, shape); } } /** SYNTACTIC/VISUAL: Should the names of nodes be displayed on them? * * when should names be used? * <ul> * <li> not when only a single sig (e.g. state machine with only one 'node' sig) * <li> not when only a single relation * <li> check for single things _after_ hiding things by default * </ul> */ private void nodeNames() { final Set<AlloyType> visibleUserTypes = MagicUtil.visibleUserTypes(vizState); // trim names for (final AlloyType t : visibleUserTypes) { // trim label before last slash MagicUtil.trimLabelBeforeLastSlash(vizState, t); } // hide names if there's only one node type visible if (1 == visibleUserTypes.size()) { vizState.label.put(visibleUserTypes.iterator().next(), ""); } } }