/* * Licensed Materials - Property of Rogue Wave Software, Inc. * © Copyright Rogue Wave Software, Inc. 2014, 2015 * © Copyright IBM Corp. 2009, 2014 * © Copyright ILOG 1996, 2009 * All Rights Reserved. * * Note to U.S. Government Users Restricted Rights: * The Software and Documentation were developed at private expense and * are "Commercial Items" as that term is defined at 48 CFR 2.101, * consisting of "Commercial Computer Software" and * "Commercial Computer Software Documentation", as such terms are * used in 48 CFR 12.212 or 48 CFR 227.7202-1 through 227.7202-4, * as applicable. */ import ilog.views.*; import ilog.views.io.IlvReadFileException; import ilog.views.interactor.*; import ilog.views.event.*; import ilog.views.graphic.*; import ilog.views.swing.*; import ilog.views.graphlayout.labellayout.*; import ilog.views.graphlayout.labellayout.annealing.*; import ilog.views.util.IlvProductUtil; import ilog.views.util.IlvResourceUtil; import ilog.views.util.swing.IlvSwingUtil; import java.awt.*; import java.awt.event.*; import java.util.*; import java.io.IOException; import javax.swing.*; /** * The main class LinkLabelLayoutApplet. */ public class LinkLabelLayoutApplet extends JApplet implements ManagerSelectionListener, TransformerListener { static { // This applet is designed to run only with default resource bundle // and various selected other resource bundles. // Setting the available resource suffixes avoids that the applet // tries to load resource bundles for other locales over the net, // even if the current locale of the browser is different. if (IlvResourceUtil.isInApplet()) IlvResourceUtil.setAvailableResourceSuffixes("", "_ja"); } { // This sample uses JViews Diagrammer features. When deploying an // application that includes this code, you need to be in possession // of a JViews Diagrammer Deployment license. IlvProductUtil.DeploymentLicenseRequired( IlvProductUtil.JViews_Diagrammer_Deployment); } /** * The manager: because we deal with links in this sample, we use a grapher * instead of a general purpose manager. However, it doesn't matter for the * labeling algorithm, only for the redraw refresh on interactions. */ IlvManager manager = new IlvGrapher(); /** The labeling model for the manager */ LabelingModel labelingModel; /** The view of the manager */ IlvManagerView mgrview = new LinkLabelingManagerView(manager); /** An instance of the Simulates Annealing Label Layout algorithm */ IlvAnnealingLabelLayout layout = new IlvAnnealingLabelLayout(); /** A label layout event listener */ LinkLabelingListener layoutListener = new LinkLabelingListener(this); /** A text field to display messages */ JTextField msgLine = new JTextField(); /** Offset distances */ static final float minDistToOtherObstacles = 10.0f; /** Text fields for the offset parameters */ JTextField minOffset = new JTextField("" + minDistToOtherObstacles, 5); /** * Whether we want to see the label bounding box or not. */ boolean noLabelBackground = false; /** * In this demo, the labels are assigned to links (IlvLabelLinkImage). * The labels should be placed close to the links. * The link automatically rotates the labels according to the link * segment where the label attaches. */ Hashtable assignmentTable; Object[] obstacles; Object[] labels; /** * Initializes the applet/application. */ public void init() { showMessage("layout sample initialization..."); JPanel panel; super.init(); // register this application as selection listener to the manager manager.addManagerSelectionListener(this); // create the scroll manager view IlvJScrollManagerView scrollManView = new IlvJScrollManagerView(mgrview); // Some settings on the manager view and on the scroll manager view mgrview.setAntialiasing(true); mgrview.setKeepingAspectRatio(true); mgrview.setBackground(Color.white); mgrview.setForeground(SystemColor.windowText); Color xc = SystemColor.windowText; xc = new Color(255-xc.getRed(), 255-xc.getGreen(), 255-xc.getBlue()); mgrview.setDefaultXORColor(xc); mgrview.setDefaultGhostColor(SystemColor.windowText); mgrview.setSize(new Dimension(488,329)); mgrview.addTransformerListener(this); // Settings parameters for selection handles IlvHandlesSelection.defaultHandleColor = Color.black; IlvHandlesSelection.defaultHandleBackgroundColor = Color.white; IlvHandlesSelection.defaultHandleShape = IlvHandlesSelection.SQUARE_SHAPE; // create a new labeling model labelingModel = new LabelingModel(manager); // attach the manager to the layout instances layout.attach(labelingModel); layout.setObstacleOffset(minDistToOtherObstacles); // links of class IlvLabelLinkImage update the label positions themselves. // Therefore the autoupdate feature of the label layout is not needed // for those links. layout.setAutoUpdate(false); // the rotation formulas of this example are only good for manager mode layout.setCoordinatesMode(IlvLabelLayout.MANAGER_COORDINATES); labelingModel.setCoordinatesMode(IlvLabelLayout.MANAGER_COORDINATES); // install the layout iteration listener on the layout instance layout.addLabelLayoutEventListener(layoutListener); // set the layout manager getContentPane().setLayout(new BorderLayout(0,0)); // fit so far all together getContentPane().add("Center", scrollManView); getContentPane().add("North", panel = new JPanel()); panel.setLayout(new FlowLayout(FlowLayout.LEFT)); // create the standard control bar IlvJManagerViewControlBar controlBar = new IlvJManagerViewControlBar(); controlBar.setView(mgrview); panel.add(controlBar); // modify the interactors such that the demo looks better ((IlvSelectInteractor)controlBar.getSelectInteractor()).setOpaqueMove(true); ((IlvZoomViewInteractor)controlBar.getZoomViewInteractor()).setPermanent(true); // set the initial interactor mgrview.setInteractor(controlBar.getSelectInteractor()); // create the panel on bottom JPanel bottomPanel = new JPanel(); bottomPanel.setLayout(new BorderLayout()); getContentPane().add("South", bottomPanel); // create the layout buttons JButton b; JPanel layoutPanel = new JPanel(); bottomPanel.add("North", layoutPanel); layoutPanel.add(b = new JButton("Random")); b.setToolTipText("Randomize the label positions"); b.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent evt) { randomize(manager); }}); layoutPanel.add(b = new JButton("Layout")); b.setToolTipText("Perform a label layout"); b.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent evt) { try { float value; // validate the min offset value = Float.valueOf(minOffset.getText().trim()).floatValue(); setMinimumOffset(value); } catch (Exception ex) { // ex.printStackTrace(); showMessage("Illegal offset: " + ex.getMessage()); return; } // now the layout layout(layout, manager); }}); // quadtree JPanel auxPanel = new JPanel(); auxPanel.setLayout(new BorderLayout(0,0)); layoutPanel.add(auxPanel); JCheckBox useQuadtree = new JCheckBox("Quadtree", true); useQuadtree.setToolTipText( "Enable or disable the quadtree as speedup mechanism"); auxPanel.add("West", useQuadtree); useQuadtree.addItemListener(new ItemListener() { public void itemStateChanged(ItemEvent e) { if (e.getStateChange() == ItemEvent.SELECTED) layout.setUseQuadtree(true); else if (e.getStateChange() == ItemEvent.DESELECTED) layout.setUseQuadtree(false); } }); // label box JCheckBox showLabelBox = new JCheckBox("Label Box", !noLabelBackground); auxPanel.add("Center", showLabelBox); showLabelBox.setToolTipText( "Show or hide the bounding box of the labels"); showLabelBox.addItemListener(new ItemListener() { public void itemStateChanged(ItemEvent e) { if (e.getStateChange() == ItemEvent.SELECTED) noLabelBackground = false; else if (e.getStateChange() == ItemEvent.DESELECTED) noLabelBackground = true; setNoLabelBackground(noLabelBackground); manager.reDraw(); } }); // create the layout parameters panel JPanel layoutParameters = new JPanel(); layoutParameters.setLayout(new GridBagLayout()); bottomPanel.add("Center", layoutParameters); GridBagConstraints c = new GridBagConstraints(); c.fill = GridBagConstraints.HORIZONTAL; c.anchor = GridBagConstraints.EAST; // minimum offset to other obstacles c.gridx = 1; c.gridy = 0; layoutParameters.add(minOffset, c); minOffset.setToolTipText("Set the minimum offset to unrelated obstacles"); minOffset.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { try { JTextField textfield = (JTextField)e.getSource(); float value = Float.valueOf(textfield.getText().trim()).floatValue(); setMinimumOffset(value); } catch (Exception ex) { showMessage("Illegal minimum offset: " + ex.getMessage()); } } }); c.insets = new Insets(0, 25, 0, 0); c.gridx = 0; c.gridy = 0; layoutParameters.add(new JLabel("Overlap Offset: "), c); // add the message line to the bottom panel bottomPanel.add("South", msgLine); msgLine.setEditable(false); // load a sample manager try { manager.deleteAll(false); fillGrapher(); } catch (Exception e) { // e.printStackTrace(); showMessage(e.getMessage()); } } /** * Allows you to run the demo as a standalone application. */ public static void main(String[] arg) { // Sun recommends that to put the entire GUI initialization into the // AWT thread SwingUtilities.invokeLater( new Runnable() { public void run() { LinkLabelLayoutApplet applet = new LinkLabelLayoutApplet(); applet.init(); JFrame frame = new JFrame("Label Layout Demo"); // EXIT_ON_CLOSE is not defined in jdk1.2, hence this way: frame.setDefaultCloseOperation(3 /* EXIT_ON_CLOSE, >= jdk1.2.2 */); frame.setSize(600, 600); frame.getContentPane().add(applet); frame.setVisible(true); } }); } /** * Performs the layout of the graph. */ private void layout(IlvLabelLayout layout, IlvManager manager) { // the layout report instance; this is an object in which // the layout algorithm stores information about its behavior IlvLabelLayoutReport layoutReport = null; showMessage("layout started..."); // initialize the iteration listener layoutListener.initialize(); try { // perform the layout and get the layout report layoutReport = layout.performLayout(false, false); // print the code from the layout report showMessage(layoutReport.codeToString(layoutReport.getCode()) + "." + " Time: " + (0.001f * layoutReport.getLayoutTime()) + " sec." + " Overlap: " + getOverlapAsString()); } catch (Exception e) { // e.printStackTrace(); showMessage(e.getMessage()); } finally { if (layoutReport != null && layoutReport.getCode() != IlvLabelLayoutReport.NOT_NEEDED) { mgrview.repaint(); } } } /** * Displays a message. */ void showMessage(String message) { // the message is displayed in the message line msgLine.setText(message); msgLine.paintImmediately(0, 0, msgLine.getWidth(), msgLine.getHeight()); } /** * Fills the grapher with the example graph. */ void fillGrapher() { // first create a couple of nodes IlvGraphic node1 = createNode(100, 10); IlvGraphic node2 = createNode(200, 10); IlvGraphic node3 = createNode(300, 205); IlvGraphic node4 = createNode(100, 280); IlvGraphic node5 = createNode(10, 390); IlvGraphic node6 = createNode(200, 390); // now create a couple of links with labels IlvPoint[] points; createLink(node1, node2, null, "Label"); points = new IlvPoint[2]; points[0] = new IlvPoint(10,100); points[1] = new IlvPoint(10,200); createLink(node1, node4, points, "Start"); points = new IlvPoint[2]; points[0] = new IlvPoint(220,120); points[1] = new IlvPoint(220,200); createLink(node1, node4, points, "Start"); createLink(node2, node3, null, "Label"); createLink(node3, node6, null, "Label"); createLink(node4, node5, null, "End"); createLink(node4, node6, null, "End"); points = new IlvPoint[1]; points[0] = new IlvPoint(120,480); createLink(node5, node6, points, "Label"); // register the labels for layout registerLabelsAndObstacles(); } /** * Create a node. */ IlvGraphic createNode(float x, float y) { IlvGrapher grapher = (IlvGrapher)manager; IlvRect rect = new IlvRect(x, y, 30, 30); IlvReliefRectangle node = new IlvReliefRectangle(rect, 4); node.setBackground(Color.gray); grapher.addNode(node, false); return node; } /** * Create a link. */ void createLink(IlvGraphic node1, IlvGraphic node2, IlvPoint[] bends, String labelString) { IlvGrapher grapher = (IlvGrapher)manager; IlvZoomableLabel label = new IlvZoomableLabel(new IlvPoint(), labelString, false); label.setLeftMargin(4); label.setRightMargin(4); label.setTopMargin(2); label.setBottomMargin(2); label.setAntialiasing(true); grapher.addObject(label, 2, false); IlvLabelLinkImage link = new IlvLabelLinkImage(node1, node2, label, true, bends); grapher.addLink(link, false); } /** * Registers the labels and obstacles in this demo. * This is needed to show the assignment lines bw. label and related obstacle. */ void registerLabelsAndObstacles() { int numObstacles = labelingModel.getObstaclesCount(); int numLabels = labelingModel.getLabelsCount(); obstacles = new Object[numObstacles]; labels = new Object[numLabels]; // initialize the table of obstacles Enumeration e = labelingModel.getObstacles(); int i = 0; while (e.hasMoreElements()) { obstacles[i++] = e.nextElement(); } // initialize the table of labels e = labelingModel.getLabels(); i = 0; while (e.hasMoreElements()) { labels[i++] = e.nextElement(); } // build the assignment table that tells which label belongs to which // obstacle assignmentTable = new Hashtable(); labelingModel.assignmentTable = assignmentTable; for (i = 0; i < numObstacles; i++) { if (obstacles[i] instanceof IlvLabelLinkImage) { IlvGraphic label = ((IlvLabelLinkImage)obstacles[i]).getLabel(); assignmentTable.put(label, obstacles[i]); } } // if necessary, make the label background transparent setNoLabelBackground(noLabelBackground); setLayoutParameters(); mgrview.fitTransformerToContent(); manager.reDraw(); } /** * Set the layout parameters for simulated annealing. */ void setLayoutParameters() { // now set the label descriptors. try { IlvZoomableLabel label; IlvLabelLinkImage link; int anchor; for (int i = 0; i < labels.length; i++) { label = (IlvZoomableLabel)labels[i]; link = (IlvLabelLinkImage)assignmentTable.get(label); // in this sample, it is a good idea to look at the label text to // decide what should be the anchor. This just for demonstration // purposes. if (label.getLabel().indexOf("Center") >= 0) anchor = IlvAnnealingPolylineLabelDescriptor.CENTER; else if (label.getLabel().indexOf("Start") >= 0) anchor = IlvAnnealingPolylineLabelDescriptor.START; else if (label.getLabel().indexOf("End") >= 0) anchor = IlvAnnealingPolylineLabelDescriptor.END; else anchor = IlvAnnealingPolylineLabelDescriptor.FREE; // IlvLabelLinkImage only supports labels at one side, so the // side association must be LOCAL and the allowed and preferred // side must match the setting in IlvLabelLinkImage. int side = (link.isRightSide() ? IlvDirection.Right : IlvDirection.Left); layout.setLabelDescriptor(label, new IlvAnnealingPolylineLabelDescriptor(label, link, link.getFrom(), link.getTo(), anchor, 0, 0, side, side, IlvAnnealingPolylineLabelDescriptor.LOCAL, 0, 0, 0, 0) { public double getRotation(IlvLabelingModel model, IlvRect labelRect) { IlvLabelLinkImage link = (IlvLabelLinkImage)assignmentTable.get(getLabel()); return link.getRotation(labelRect, null); } }); } layout.setAllowedTime(240000); } catch (Exception ex) { // ex.printStackTrace(); showMessage("Illegal label descriptor: " + ex.getMessage()); } } /** * Set the minimum offset between label and obstacle. * We also take 20% of this value as the minimum offset between pairs * of labels. */ void setMinimumOffset(float value) { if (value > 100.0f) value = 100.0f; if (value < 0.0f) value = 0.0f; // write again to be sure the right value is shown minOffset.setText("" + value); // set the new value of the parameter layout.setObstacleOffset(value); // also, increase the offset between the labels a little bit layout.setLabelOffset(0.2f * value); } /** * Show or hide the background of all labels. */ void setNoLabelBackground(boolean flag) { IlvZoomableLabel label; IlvApplyObject apply; if (flag) apply = new IlvApplyObject() { public void apply(IlvGraphic obj, Object arg) { IlvZoomableLabel l = (IlvZoomableLabel)obj; l.setBorderOn(false); l.setBackgroundOn(false); } }; else apply = new IlvApplyObject() { public void apply(IlvGraphic obj, Object arg) { IlvZoomableLabel l = (IlvZoomableLabel)obj; l.setBorderOn(true); l.setBackgroundOn(true); } }; for (int i = 0; i < labels.length; i++) { label = (IlvZoomableLabel)labels[i]; manager.applyToObject(label, apply, null, true); } } /** * Calculate the overlap amount. */ String getOverlapAsString() { double overlap = 0.0; double maxOverlap = 0.0; int i, j; IlvRect r1, r2; IlvPoint[] pts; float w; Object ownerObstacle; // calculate the actual overlap value, and the hypothetical maximum // overlap. The hypothetical maximum overlap is used to print some // percentage value. for (i = 0; i < labels.length; i++) { r1 = labelingModel.boundingBox(labels[i]); for (j = i+1; j < labels.length; j++) { r2 = labelingModel.boundingBox(labels[j]); overlap += labelingModel.getLabelOverlap( labels[i], r1, labels[j], r2, 0.0f); maxOverlap += 0.5 * (double)r1.width * (double)r1.height; maxOverlap += 0.5 * (double)r2.width * (double)r2.height; } } for (i = 0; i < labels.length; i++) { r1 = labelingModel.boundingBox(labels[i]); ownerObstacle = assignmentTable.get(labels[i]); for (j = 0; j < obstacles.length; j++) { if (obstacles[j] != ownerObstacle) { if (labelingModel.isPolylineObstacle(obstacles[j])) { r2 = labelingModel.boundingBox(obstacles[j]); pts = labelingModel.getPolylinePoints(obstacles[j]); w = labelingModel.getPolylineWidth(obstacles[j]); overlap += labelingModel.getPolylineObstacleOverlap( labels[i], r1, obstacles[j], pts, w, 0.0f); maxOverlap += 0.5 * (double)r1.width * (double)r1.height; maxOverlap += 0.5 * (double)r2.width * (double)r2.height; } else { r2 = labelingModel.boundingBox(obstacles[j]); overlap += labelingModel.getObstacleOverlap( labels[i], r1, obstacles[j], r2, 0.0f); maxOverlap += 0.5 * (double)r1.width * (double)r1.height; maxOverlap += 0.5 * (double)r2.width * (double)r2.height; } } } } // the percentage of overlap double perc = 100 * (overlap / maxOverlap); // calculate the absolute overlap, with 2 digits after dot. overlap = (double)((int)(overlap * 100)) / 100.0; // calculate the logarithmic overlap percentage (with 2 digits after dot) // The logarithmic percentage is here more useful because it represents // better how much the overlaps disturb the user. perc = 100 / Math.log(101) * Math.log(perc + 1); float logPerc = (float)((int)(100 * perc))/100.0f; return ("" + overlap + " (Log. Perc.: " + logPerc + "%)"); } /** * Preprocess graph: Add some local parameters for demonstration purpose. * Here, it tells the random layout about the layout region. */ void randomize(IlvManager manager) { Random rand = new Random(); for (int i = 0; i < labels.length; i++) { IlvZoomableLabel label = (IlvZoomableLabel)labels[i]; float x = rand.nextInt(500); float y = rand.nextInt(500); manager.moveObject(label, x, y, true); } } /** * Listen to selections of the manager. */ public void selectionChanged(ManagerSelectionChangedEvent event) { // to make sure a deselected object is always moveable if (event.getGraphic() != null && !(event.getGraphic() instanceof IlvLinkImage)) manager.setMovable(event.getGraphic(), true); // we need to make sure that, if a label and its related link (or // one of its end nodes) are both selected, that the label is // registered nonmovable. Otherwise we get weird effects because // the label tries to follow the link by itself. IlvGraphicEnumeration e = manager.getSelectedObjects(false); while (e.hasMoreElements()) { IlvGraphic g = e.nextElement(); if (labelingModel.isLabel(g)) { IlvLinkImage link = (IlvLinkImage)assignmentTable.get(g); if (link != null && (manager.isSelected(link) || manager.isSelected(link.getFrom()) || manager.isSelected(link.getTo()))) manager.setMovable(g, false); else manager.setMovable(g, true); } } } /** * Listen to changes of the transformer. */ public void transformerChanged(TransformerChangedEvent event) { IlvTransformer t = event.getNewValue(); // limit the zoom factor. Otherwise, we may run into serious rounding // problems in the arithmetic formulas of the auxiliary lines. if (t.zoomFactor() < 0.01) { t.setValues(0.01, t.getx12(), t.getx21(), 0.01); mgrview.setTransformer(t); } else if (t.zoomFactor() > 20.0) { t.setValues(20.0, t.getx12(), t.getx21(), 20.0); mgrview.setTransformer(t); } } } /** * A label layout iteration listener. Implementing the interface * LabelLayoutEventListener gives you the possibility to receive * during the layout information about the behavior of the layout * algorithm. This information is contained in the label layout event. * * In our case, we will simply print the percentage completed by the * layout each time the method layoutStepPerformed is called, that is * after each iteration of the Annealing Label Layout algorithm. */ class LinkLabelingListener implements LabelLayoutEventListener { private float oldPercentage; private LinkLabelLayoutApplet applet; LinkLabelingListener(LinkLabelLayoutApplet applet) { this.applet = applet; } /** * This method is automatically called by the layout algorithm. */ public void layoutStepPerformed(LabelLayoutEvent event) { float percentage = event.getLayoutReport().getPercentageComplete(); if (percentage != oldPercentage) { applet.showMessage("" + percentage + "%"); oldPercentage = percentage; } } /** * Initialize the listener by reseting the old percentage variable. * This method must be called before the layout is started. */ void initialize() { oldPercentage = -1.0f; } } /** * The labeling model that understands the IlvLabelLinkImage with its * rotation. */ class LabelingModel extends IlvDefaultLabelingModel { Hashtable assignmentTable; public LabelingModel(IlvManager manager) { super(manager); } /** * Returns the bounding rectangle of a label or obstacle in manager's * coordinates. We need to make sure that we get the rectangle of the * unrotated label, not its current rotated bounding box. */ public IlvRect boundingBox(Object labelOrObstacle) { if (labelOrObstacle instanceof IlvZoomableLabel) { return IlvLabelLinkImage.getUnrotatedBoundingBox( (IlvZoomableLabel)labelOrObstacle, null); } return super.boundingBox(labelOrObstacle); } /** * Moves the label. * In our case, we have to translate the position into a percentage from * the start of the link, because this is the preferred way of moving the * label via the link. */ public void moveLabel(Object label, float x, float y, boolean redraw) { if (label instanceof IlvZoomableLabel) { IlvZoomableLabel zlabel = (IlvZoomableLabel)label; IlvLabelLinkImage link = (IlvLabelLinkImage)assignmentTable.get(zlabel); IlvRect labelBox = boundingBox(label); labelBox.x = x; labelBox.y = y; float percentage = link.getPercentageFromStart(labelBox, null); link.setPercentage(percentage); link.updateLabelPosition(); } else super.moveLabel(label, x, y, redraw); } } /** * A manager view with limited range for zooming. * It doesn't allow to set the zoom level below 0.001 or above 30.0, * so that the demo runs smoothly even if the user stress-tests the * zoomin and zoomout button. */ class LinkLabelingManagerView extends IlvManagerView { boolean insideVerifyTransformer = false; public LinkLabelingManagerView(IlvManager manager) { super(manager); } /** * Checks a transformer. This method is called * every time the transformer of the view is changed by * calling <code>setTransformer</code> or <code>addTransformer</code>. * It makes sure that the zoomFactor is between 0.001 and 30.0. */ public void verifyTransformer() { super.verifyTransformer(); if (insideVerifyTransformer) return; IlvTransformer t = getTransformer(); double zoomFactor = t.zoomFactor(); if (zoomFactor < 0.001) { double x0 = t.getx0() * 0.999 / (1 - zoomFactor); double y0 = t.gety0() * 0.999 / (1 - zoomFactor); t.setValues(0.001, t.getx12(), t.getx21(), 0.001, x0, y0); insideVerifyTransformer = true; setTransformer(t); insideVerifyTransformer = false; } else if (zoomFactor > 30.0) { double x0 = t.getx0() * 29.0 / (zoomFactor - 1); double y0 = t.gety0() * 29.0 / (zoomFactor - 1); t.setValues(30.0, t.getx12(), t.getx21(), 30.0, x0, y0); insideVerifyTransformer = true; setTransformer(t); insideVerifyTransformer = false; } } }