/* * Licensed Materials - Property of Perforce Software, Inc. * © Copyright Perforce Software, Inc. 2014, 2021 * © 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 java.awt.BorderLayout; import java.awt.Color; import java.awt.Dimension; import java.awt.FlowLayout; import java.awt.GridBagConstraints; import java.awt.GridBagLayout; import java.awt.Insets; import java.awt.SystemColor; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.event.ItemEvent; import java.awt.event.ItemListener; import java.io.IOException; import java.util.Enumeration; import java.util.Hashtable; import java.util.Random; import javax.swing.JButton; import javax.swing.JCheckBox; import javax.swing.JComboBox; import javax.swing.JFrame; import javax.swing.JLabel; import javax.swing.JPanel; import javax.swing.JRootPane; import javax.swing.JTextField; import javax.swing.SwingUtilities; import ilog.views.IlvApplyObject; import ilog.views.IlvDirection; import ilog.views.IlvGrapher; import ilog.views.IlvGraphic; import ilog.views.IlvGraphicEnumeration; import ilog.views.IlvHandlesSelection; import ilog.views.IlvLinkImage; import ilog.views.IlvManager; import ilog.views.IlvManagerView; import ilog.views.IlvPoint; import ilog.views.IlvRect; import ilog.views.IlvTransformer; import ilog.views.event.ManagerContentChangedEvent; import ilog.views.event.ManagerContentChangedListener; import ilog.views.event.ManagerSelectionChangedEvent; import ilog.views.event.ManagerSelectionListener; import ilog.views.event.TransformerChangedEvent; import ilog.views.event.TransformerListener; import ilog.views.graphic.IlvLine; import ilog.views.graphic.IlvZoomableLabel; import ilog.views.graphlayout.labellayout.IlvDefaultLabelingModel; import ilog.views.graphlayout.labellayout.IlvLabelLayout; import ilog.views.graphlayout.labellayout.IlvLabelLayoutReport; import ilog.views.graphlayout.labellayout.LabelLayoutEvent; import ilog.views.graphlayout.labellayout.LabelLayoutEventListener; import ilog.views.graphlayout.labellayout.annealing.IlvAnnealingLabelLayout; import ilog.views.graphlayout.labellayout.annealing.IlvAnnealingPolylineLabelDescriptor; import ilog.views.graphlayout.labellayout.random.IlvRandomLabelLayout; import ilog.views.interactor.IlvSelectInteractor; import ilog.views.interactor.IlvZoomViewInteractor; import ilog.views.io.IlvReadFileException; import ilog.views.swing.IlvJManagerViewControlBar; import ilog.views.swing.IlvJScrollManagerView; import ilog.views.util.IlvProductUtil; import ilog.views.util.swing.IlvSwingUtil; /** * The main class LinkLabelLayoutApp. */ public class LinkLabelLayoutApp extends JRootPane implements ManagerContentChangedListener, ManagerSelectionListener, TransformerListener { { // 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 */ IlvDefaultLabelingModel labelingModel; /** The view of the manager */ IlvManagerView mgrview = new LinkLabelingManagerView(manager); /** An instance of the Simulates Annealing Label Layout algorithm */ IlvAnnealingLabelLayout layout = new IlvAnnealingLabelLayout(); /** An instance of the Random Label Layout algorithm */ IlvRandomLabelLayout randomLayout = new IlvRandomLabelLayout(); /** A label layout event listener */ LinkLabelingListener layoutListener = new LinkLabelingListener(this); /** A text field to display messages */ JTextField msgLine = new JTextField(); /** The side association */ int sideAssociation = IlvAnnealingPolylineLabelDescriptor.LOCAL; JComboBox<String> sideAssocChooser = new JComboBox<String>(); boolean duringAssociationChange = false; /** The preferred side of all labels */ int preferredSide = IlvDirection.Left; JComboBox<String> preferredSideChooser = new JComboBox<String>(); /** The allowed side of all labels */ int allowedSide = 0; JComboBox<String> allowedSideChooser = new JComboBox<String>(); /** Offset distances */ static final double minDistToOtherObstacles = 10.0d; double prefDistFromOwnObstacle = 1.0; double allowedOverlapOffset = 0.0; /** Text fields for the offset parameters */ JTextField minOffset = new JTextField("" + minDistToOtherObstacles, 5); JTextField prefOffset = new JTextField("" + prefDistFromOwnObstacle, 5); JTextField ovlpOffset = new JTextField("" + allowedOverlapOffset, 5); /** * Whether we want to see the label bounding box or not. */ boolean noLabelBackground = true; /** * In this demo, the labels are assigned to links (IlvLinkImage). The labels * should be placed close to the links. Which label belongs to which link is * in this sample randomly decided, and stored in the assignment table, such * that we can show the assignment by the auxiliry links when necessary. */ Hashtable<Object, IlvLinkImage> assignmentTable; Object[] obstacles; Object[] labels; int[] labelsPerObstacle; /** The assignment lines */ IlvLine[] assignmentLines; boolean labelAssignmentVisible = false; /** * A counter to block the event handling of the auxiliary lines. Otherise, the * updating of the auxiliary lines on movements would cause cycles of events. */ int blockEventsCounter = 0; /** * Initializes the application. */ public void init() { showMessage("layout sample initialization..."); JPanel panel; // register this application as listener to the manager manager.addManagerContentChangedListener(this); // 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 // We do this AFTER adding the app as transformer listener, because // the default labeling model will register itself as transformer listener // as well. The aux lines mechanism of this app requires that the // app receives the event last. labelingModel = new IlvDefaultLabelingModel(manager); // attach the manager to the layout instances layout.attach(labelingModel); randomLayout.attach(labelingModel); layout.setObstacleOffset(minDistToOtherObstacles); layout.setAutoUpdate(false); // 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 graph selector panel.add(new JLabel("Select graph")); JComboBox<String> chooser; panel.add(chooser = new JComboBox<String>()); chooser.setToolTipText("Select a sample graph"); chooser.addItemListener(new ItemListener() { Override public void itemStateChanged(ItemEvent event) { if (event.getStateChange() == ItemEvent.DESELECTED) return; try { // disable autoupdate until we did at least one layout layout.setAutoUpdate(false); // just to be sure: store where the aux lines are visible and // hide the aux lines boolean f = labelAssignmentVisible; hideLabelAssignment(); // refill the manager from file manager.deleteAll(false); readManager((String) event.getItem()); // restore whether the aux lines were visible labelAssignmentVisible = f; // postprocessing after reading a file afterRead(); } catch (Exception e) { // e.printStackTrace(); showMessage(e.getMessage()); } } }); chooser.addItem("small1"); chooser.addItem("small2"); chooser.addItem("medium"); chooser.addItem("large"); // 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() { Override public void actionPerformed(ActionEvent evt) { preprocess(); layout(randomLayout, manager); } }); layoutPanel.add(b = new JButton("Layout")); b.setToolTipText("Perform a label layout"); b.addActionListener(new ActionListener() { Override public void actionPerformed(ActionEvent evt) { try { // validate the min offset double dvalue = Double.valueOf(minOffset.getText().trim()).doubleValue(); setMinimalOffset(dvalue); // validate the preferred offset dvalue = Double.valueOf(prefOffset.getText().trim()).doubleValue(); setPreferredOffset(dvalue); // validate the allowed overlap offset dvalue = Double.valueOf(ovlpOffset.getText().trim()).doubleValue(); setAllowedOverlapOffset(dvalue); } catch (Exception ex) { // ex.printStackTrace(); showMessage("Illegal offset: " + ex.getMessage()); return; } // now the layout preprocess(); layout(layout, manager); // from now on, the labels follow the links layout.setAutoUpdate(true); } }); // 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() { Override 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", false); auxPanel.add("Center", showLabelBox); showLabelBox.setToolTipText("Show or hide the bounding box of the labels"); showLabelBox.addItemListener(new ItemListener() { Override public void itemStateChanged(ItemEvent e) { if (e.getStateChange() == ItemEvent.SELECTED) noLabelBackground = false; else if (e.getStateChange() == ItemEvent.DESELECTED) noLabelBackground = true; setNoLabelBackground(noLabelBackground); // corner may have changed if (labelAssignmentVisible) showLabelAssignment(); manager.reDraw(); } }); // auxiliary lines JCheckBox showAuxLines = new JCheckBox("Auxiliary Lines", false); showAuxLines.setToolTipText("Show or hide the auxiliary lines of the labels"); auxPanel.add("East", showAuxLines); showAuxLines.addItemListener(new ItemListener() { Override public void itemStateChanged(ItemEvent e) { if (e.getStateChange() == ItemEvent.SELECTED) showLabelAssignment(); else if (e.getStateChange() == ItemEvent.DESELECTED) hideLabelAssignment(); } }); // 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; // side association parameter c.gridx = 0; c.gridy = 0; layoutParameters.add(new JLabel("Side Association: "), c); c.gridx = 1; c.gridy = 0; layoutParameters.add(sideAssocChooser, c); sideAssocChooser.setToolTipText("Select whether 'left', 'right', etc. has a local or a global meaning"); sideAssocChooser.addItemListener(new ItemListener() { Override public void itemStateChanged(ItemEvent event) { if (event.getStateChange() == ItemEvent.DESELECTED) return; int oldAssoc = sideAssociation; duringAssociationChange = true; if ((String) event.getItem() == "local") { sideAssociation = IlvAnnealingPolylineLabelDescriptor.LOCAL; if (oldAssoc != sideAssociation) { makePreferredSideChooserLocal(); makeAllowedSideChooserLocal(); } } if ((String) event.getItem() == "global") { sideAssociation = IlvAnnealingPolylineLabelDescriptor.GLOBAL; if (oldAssoc != sideAssociation) { makePreferredSideChooserGlobal(); makeAllowedSideChooserGlobal(); } } duringAssociationChange = false; if (oldAssoc != sideAssociation) setLayoutParameters(); } }); sideAssocChooser.addItem("local"); sideAssocChooser.addItem("global"); sideAssocChooser.setSelectedIndex(0); // preferred side parameter c.gridx = 0; c.gridy = 1; layoutParameters.add(new JLabel("Preferred Side: "), c); c.gridx = 1; c.gridy = 1; layoutParameters.add(preferredSideChooser, c); // just to determine the preferred size of the largest possible item preferredSideChooser.addItem("bottomright"); preferredSideChooser.setSize(preferredSideChooser.getPreferredSize()); // later, we need this size to make sure the chooser doesn't jitter // when changing from local to global side association preferredSideChooser.setToolTipText("Select the preferred side for all labels"); preferredSideChooser.addItemListener(new ItemListener() { Override public void itemStateChanged(ItemEvent event) { if (event.getStateChange() == ItemEvent.DESELECTED) return; int oldSide = preferredSide; if ((String) event.getItem() == "left") preferredSide = IlvDirection.Left; if ((String) event.getItem() == "right") preferredSide = IlvDirection.Right; if ((String) event.getItem() == "top") preferredSide = IlvDirection.Top; if ((String) event.getItem() == "bottom") preferredSide = IlvDirection.Bottom; if ((String) event.getItem() == "topleft") preferredSide = IlvDirection.TopLeft; if ((String) event.getItem() == "topright") preferredSide = IlvDirection.TopRight; if ((String) event.getItem() == "bottomleft") preferredSide = IlvDirection.BottomLeft; if ((String) event.getItem() == "bottomright") preferredSide = IlvDirection.BottomRight; if (oldSide != preferredSide && !duringAssociationChange) setLayoutParameters(); } }); if (sideAssociation == IlvAnnealingPolylineLabelDescriptor.LOCAL) makePreferredSideChooserLocal(); else makePreferredSideChooserGlobal(); // allowed side parameter c.gridx = 0; c.gridy = 2; layoutParameters.add(new JLabel("Allowed Side: "), c); c.gridx = 1; c.gridy = 2; layoutParameters.add(allowedSideChooser, c); // just to determine the preferred size of the largest possible item allowedSideChooser.addItem("bottomright"); allowedSideChooser.setSize(allowedSideChooser.getPreferredSize()); // later, we need this size to make sure the chooser doesn't jitter // when changing from local to global side association allowedSideChooser.setToolTipText("Select the allowed side for all labels"); allowedSideChooser.addItemListener(new ItemListener() { Override public void itemStateChanged(ItemEvent event) { if (event.getStateChange() == ItemEvent.DESELECTED) return; int oldSide = allowedSide; if ((String) event.getItem() == "all") allowedSide = 0; if ((String) event.getItem() == "left") allowedSide = IlvDirection.Left; if ((String) event.getItem() == "right") allowedSide = IlvDirection.Right; if ((String) event.getItem() == "top") allowedSide = IlvDirection.Top; if ((String) event.getItem() == "bottom") allowedSide = IlvDirection.Bottom; if ((String) event.getItem() == "topleft") allowedSide = IlvDirection.TopLeft; if ((String) event.getItem() == "topright") allowedSide = IlvDirection.TopRight; if ((String) event.getItem() == "bottomleft") allowedSide = IlvDirection.BottomLeft; if ((String) event.getItem() == "bottomright") allowedSide = IlvDirection.BottomRight; if (oldSide != allowedSide && !duringAssociationChange) setLayoutParameters(); } }); if (sideAssociation == IlvAnnealingPolylineLabelDescriptor.LOCAL) makeAllowedSideChooserLocal(); else makeAllowedSideChooserGlobal(); // minimal offset to other obstacles c.gridx = 3; c.gridy = 0; layoutParameters.add(minOffset, c); minOffset.setToolTipText("Set the minimal offset to unrelated obstacles"); minOffset.addActionListener(new ActionListener() { Override public void actionPerformed(ActionEvent e) { try { JTextField textfield = (JTextField) e.getSource(); double value = Double.valueOf(textfield.getText().trim()).doubleValue(); setMinimalOffset(value); } catch (Exception ex) { showMessage("Illegal minimal offset: " + ex.getMessage()); } } }); // preferred offset to own obstacle c.gridx = 3; c.gridy = 1; layoutParameters.add(prefOffset, c); prefOffset.setToolTipText("Set the preferred offset between label and related link"); prefOffset.addActionListener(new ActionListener() { Override public void actionPerformed(ActionEvent e) { try { JTextField textfield = (JTextField) e.getSource(); float value = Float.valueOf(textfield.getText().trim()).floatValue(); setPreferredOffset(value); } catch (Exception ex) { showMessage("Illegal preferred offset: " + ex.getMessage()); } } }); // overlap offset to own obstacle c.gridx = 3; c.gridy = 2; layoutParameters.add(ovlpOffset, c); ovlpOffset.setToolTipText("Set the allowed overlap offset between label and related link"); ovlpOffset.addActionListener(new ActionListener() { Override public void actionPerformed(ActionEvent e) { try { JTextField textfield = (JTextField) e.getSource(); float value = Float.valueOf(textfield.getText().trim()).floatValue(); setAllowedOverlapOffset(value); } catch (Exception ex) { showMessage("Illegal allowed overlap offset: " + ex.getMessage()); } } }); c.insets = new Insets(0, 25, 0, 0); c.gridx = 2; c.gridy = 0; layoutParameters.add(new JLabel("Minimal Offset: "), c); c.gridx = 2; c.gridy = 1; layoutParameters.add(new JLabel("Preferred Offset: "), c); c.gridx = 2; c.gridy = 2; 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); readManager("small1"); afterRead(); } 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() { Override public void run() { LinkLabelLayoutApp app = new LinkLabelLayoutApp(); app.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(app); frame.setVisible(true); } }); } /** * Read the grapher. */ private void readManager(String fileNameBase) throws IOException, IlvReadFileException { showMessage("Reading " + fileNameBase + ".ivl ..."); manager.read(IlvSwingUtil.getRelativeURL(this, "data/" + fileNameBase + ".ivl")); showMessage("Reading " + fileNameBase + ".ivl done."); } /** * Prepare the preferred side chooser for local side association. */ private void makePreferredSideChooserLocal() { if ((preferredSide & IlvDirection.Left) != 0) preferredSide = IlvDirection.Left; else preferredSide = IlvDirection.Right; int auxPreferredSide = preferredSide; Dimension d = preferredSideChooser.getSize(); preferredSideChooser.removeAllItems(); preferredSideChooser.addItem("left"); preferredSideChooser.addItem("right"); preferredSideChooser.setSelectedIndex(auxPreferredSide == IlvDirection.Left ? 0 : 1); preferredSideChooser.setPreferredSize(d); } /** * Prepare the preferred side chooser for global side association. */ private void makePreferredSideChooserGlobal() { int auxPreferredSide = preferredSide; preferredSideChooser.removeAllItems(); preferredSideChooser.addItem("left"); preferredSideChooser.addItem("right"); preferredSideChooser.addItem("top"); preferredSideChooser.addItem("bottom"); preferredSideChooser.addItem("topleft"); preferredSideChooser.addItem("topright"); preferredSideChooser.addItem("bottomleft"); preferredSideChooser.addItem("bottomright"); // current choice didn't change. But just to be sure. if (auxPreferredSide == IlvDirection.Left) preferredSideChooser.setSelectedIndex(0); if (auxPreferredSide == IlvDirection.Right) preferredSideChooser.setSelectedIndex(1); } /** * Prepare the allowed side chooser for local side association. */ private void makeAllowedSideChooserLocal() { if ((allowedSide & IlvDirection.Left) != 0) allowedSide = IlvDirection.Left; else if ((allowedSide & IlvDirection.Right) != 0) allowedSide = IlvDirection.Right; else allowedSide = 0; int auxAllowedSide = allowedSide; Dimension d = allowedSideChooser.getSize(); allowedSideChooser.removeAllItems(); allowedSideChooser.addItem("all"); allowedSideChooser.addItem("left"); allowedSideChooser.addItem("right"); allowedSideChooser.setSelectedIndex(auxAllowedSide == 0 ? 0 : (auxAllowedSide == IlvDirection.Left ? 1 : 2)); allowedSideChooser.setPreferredSize(d); } /** * Prepare the allowed side chooser for global side association. */ private void makeAllowedSideChooserGlobal() { int auxAllowedSide = allowedSide; allowedSideChooser.removeAllItems(); allowedSideChooser.addItem("all"); allowedSideChooser.addItem("left"); allowedSideChooser.addItem("right"); allowedSideChooser.addItem("top"); allowedSideChooser.addItem("bottom"); allowedSideChooser.addItem("topleft"); allowedSideChooser.addItem("topright"); allowedSideChooser.addItem("bottomleft"); allowedSideChooser.addItem("bottomright"); // current choice didn't change. But just to be sure. if (auxAllowedSide == 0) allowedSideChooser.setSelectedIndex(0); if (auxAllowedSide == IlvDirection.Left) allowedSideChooser.setSelectedIndex(1); if (auxAllowedSide == IlvDirection.Right) allowedSideChooser.setSelectedIndex(2); } /** * 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..."); // hide the label assignment lines because they should not be obstacles boolean labelAssignmentWasVisible = labelAssignmentVisible; hideLabelAssignment(); // 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) { if (labelAssignmentWasVisible) showLabelAssignment(); 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()); } /** * Some special handling after read. Here we determine dynamically which label * belongs to which link. Other obstacles have no labels in this sample. */ void afterRead() { int numObstacles = labelingModel.getObstaclesCount(); int numLabels = labelingModel.getLabelsCount(); obstacles = new Object[numObstacles]; labels = new Object[numLabels]; labelsPerObstacle = new int[numObstacles]; assignmentLines = new IlvLine[numLabels]; // initialize the table of obstacles Enumeration<?> e = labelingModel.getObstacles(); int numLinks = 0; int i = 0; while (e.hasMoreElements()) { obstacles[i] = e.nextElement(); if (obstacles[i] instanceof IlvLinkImage) numLinks++; labelsPerObstacle[i] = 0; i++; } int maxLabelsPerObstacle; if (numLabels <= numLinks) maxLabelsPerObstacle = 1; else if (numLabels <= 3 * numLinks) maxLabelsPerObstacle = 3; else maxLabelsPerObstacle = 10000; // initialize the table of labels e = labelingModel.getLabels(); i = 0; while (e.hasMoreElements()) { labels[i] = e.nextElement(); assignmentLines[i] = new IlvLine(0, 0, 0, 0); i++; } // assign to each label any obstacle randomly. But no obstacle should get // more than maxLabelsPerObstacle labels. Random rand = new Random(0); assignmentTable = new Hashtable<Object, IlvLinkImage>(); e = labelingModel.getLabels(); while (e.hasMoreElements()) { Object label = e.nextElement(); while (label != null) { i = rand.nextInt(); if (i < 0) i = -i; i = (i % numObstacles); if (obstacles[i] instanceof IlvLinkImage && labelsPerObstacle[i] < maxLabelsPerObstacle) { assignmentTable.put(label, (IlvLinkImage) obstacles[i]); labelsPerObstacle[i]++; label = null; } } } // if necessary, make the label background transparent if (noLabelBackground) setNoLabelBackground(true); setLayoutParameters(); if (labelAssignmentVisible) showLabelAssignment(); mgrview.fitTransformerToContent(); manager.reDraw(); } /** * Set the layout parameters for simulated annealing. */ void setLayoutParameters() { // now set the label descriptors. try { IlvZoomableLabel label; IlvLinkImage link; int anchor; for (int i = 0; i < labels.length; i++) { label = (IlvZoomableLabel) labels[i]; link = 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; layout.setLabelDescriptor(label, new IlvAnnealingPolylineLabelDescriptor(label, link, link.getFrom(), link.getTo(), anchor, prefDistFromOwnObstacle, prefDistFromOwnObstacle, preferredSide, allowedSide, sideAssociation, allowedOverlapOffset, allowedOverlapOffset, allowedOverlapOffset, allowedOverlapOffset)); } layout.setAllowedTime(240000); } catch (Exception ex) { // ex.printStackTrace(); showMessage("Illegal label descriptor: " + ex.getMessage()); } } /** * Set the minimal offset between label and obstacle. We also take 20% of this * value as the minimal offset between pairs of labels. */ void setMinimalOffset(double value) { if (value > 100) value = 100; if (value < 0) value = 0; // 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.2d * value); } /** * Set the preferred offset for all labels. */ void setPreferredOffset(double value) { if (value > 30.0) value = 30.0; if (value < 0.0) value = 0.0; // write again to be sure the right value is shown prefOffset.setText("" + value); // set the new value of the parameter double oldval = prefDistFromOwnObstacle; prefDistFromOwnObstacle = value; // need to update the layout parameters only if something changed if (oldval != value) setLayoutParameters(); } /** * Set the allowed overlap offset. */ void setAllowedOverlapOffset(double value) { if (value > 30.0) value = 30.0; if (value < 0.0) value = 0.0; // write again to be sure the right value is shown ovlpOffset.setText("" + value); // set the new value of the parameter double oldval = allowedOverlapOffset; allowedOverlapOffset = value; // need to update the layout parameters only if something changed if (oldval != value) setLayoutParameters(); } /** * Update the label assignment for all labels. This means, actually it update * the auxiliary line that shows the label assignment. */ void updateLabelAssignment() { for (int i = 0; i < labels.length; i++) { IlvLine line = assignmentLines[i]; IlvZoomableLabel label = (IlvZoomableLabel) labels[i]; Object obstacle = assignmentTable.get(label); IlvRect lr = labelingModel.boundingBox(label); updateLabelAssignment(line, lr, obstacle); } } /** * Update the auxiliary line showing label assignment for one label. */ void updateLabelAssignment(IlvLine line, IlvRect labelRect, Object obstacle) { // first test point is the middle of the polyline IlvPoint[] pts = labelingModel.getPolylinePoints(obstacle); IlvPoint p = getPolylineMiddle(pts); double ox = p.x; double oy = p.y; double bestX, bestY, tx, ty; double bestDistSqr; double distSqr; if (noLabelBackground) { // for transparent labels, show the aux line from label center bestX = labelRect.x + 0.5 * labelRect.width; bestY = labelRect.y + 0.5 * labelRect.height; bestDistSqr = (bestX - ox) * (bestX - ox) + (bestY - oy) * (bestY - oy); // check whether there is a better point if we take a vertical line tx = getPolylineX(bestY, bestX, pts); distSqr = (bestX - tx) * (bestX - tx); if (distSqr < bestDistSqr) { bestDistSqr = distSqr; ox = tx; oy = bestY; } // check whether there is a better point if we take a horizontal line ty = getPolylineY(bestX, bestY, pts); distSqr = (bestY - ty) * (bestY - ty); if (distSqr < bestDistSqr) { bestDistSqr = distSqr; ox = bestX; oy = ty; } } else { // for nonstransparent labels, show the aux line from the closest // corner of the label to (ox, oy) double x, y; // first, try to the center // top left corner bestX = labelRect.x; bestY = labelRect.y; bestDistSqr = (bestX - ox) * (bestX - ox) + (bestY - oy) * (bestY - oy); // top right corner x = labelRect.x + labelRect.width; y = labelRect.y; distSqr = (x - ox) * (x - ox) + (y - oy) * (y - oy); if (distSqr < bestDistSqr) { bestDistSqr = distSqr; bestX = x; bestY = y; } // bottom right corner x = labelRect.x + labelRect.width; y = labelRect.y + labelRect.height; distSqr = (x - ox) * (x - ox) + (y - oy) * (y - oy); if (distSqr < bestDistSqr) { bestDistSqr = distSqr; bestX = x; bestY = y; } // bottom left corner x = labelRect.x; y = labelRect.y + labelRect.height; distSqr = (x - ox) * (x - ox) + (y - oy) * (y - oy); if (distSqr < bestDistSqr) { bestDistSqr = distSqr; bestX = x; bestY = y; } // now try vertical projection // top left corner x = labelRect.x; y = labelRect.y; tx = getPolylineX(y, x, pts); distSqr = (x - tx) * (x - tx); if (distSqr < bestDistSqr) { bestDistSqr = distSqr; ox = tx; oy = y; bestX = x; bestY = y; } // top right corner x = labelRect.x + labelRect.width; y = labelRect.y; tx = getPolylineX(y, x, pts); distSqr = (x - tx) * (x - tx); if (distSqr < bestDistSqr) { bestDistSqr = distSqr; ox = tx; oy = y; bestX = x; bestY = y; } // bottom right corner x = labelRect.x + labelRect.width; y = labelRect.y + labelRect.height; tx = getPolylineX(y, x, pts); distSqr = (x - tx) * (x - tx); if (distSqr < bestDistSqr) { bestDistSqr = distSqr; ox = tx; oy = y; bestX = x; bestY = y; } // bottom left corner x = labelRect.x; y = labelRect.y + labelRect.height; tx = getPolylineX(y, x, pts); distSqr = (x - tx) * (x - tx); if (distSqr < bestDistSqr) { bestDistSqr = distSqr; ox = tx; oy = y; bestX = x; bestY = y; } // now try horizontal projection // top left corner x = labelRect.x; y = labelRect.y; ty = getPolylineY(x, y, pts); distSqr = (y - ty) * (y - ty); if (distSqr < bestDistSqr) { bestDistSqr = distSqr; ox = x; oy = ty; bestX = x; bestY = y; } // top right corner x = labelRect.x + labelRect.width; y = labelRect.y; ty = getPolylineY(x, y, pts); distSqr = (y - ty) * (y - ty); if (distSqr < bestDistSqr) { bestDistSqr = distSqr; ox = x; oy = ty; bestX = x; bestY = y; } // bottom right corner x = labelRect.x + labelRect.width; y = labelRect.y + labelRect.height; ty = getPolylineY(x, y, pts); distSqr = (y - ty) * (y - ty); if (distSqr < bestDistSqr) { bestDistSqr = distSqr; ox = x; oy = ty; bestX = x; bestY = y; } // bottom left corner x = labelRect.x; y = labelRect.y + labelRect.height; ty = getPolylineY(x, y, pts); distSqr = (y - ty) * (y - ty); if (distSqr < bestDistSqr) { bestDistSqr = distSqr; ox = x; oy = ty; bestX = x; bestY = y; } } // set coordinates and color of the line line.setFrom(new IlvPoint(ox, oy)); line.setTo(new IlvPoint(bestX, bestY)); line.setForeground(Color.red); } /** * Show the label assignment. */ void showLabelAssignment() { blockEventsCounter++; try { manager.setContentsAdjusting(true); // always update the label assignment when the lines are hidden if (labelAssignmentVisible) { hideLabelAssignment(); } updateLabelAssignment(); if (!labelAssignmentVisible) { for (int i = 0; i < labels.length; i++) { manager.addObject(assignmentLines[i], 0, false); manager.setSelectable(assignmentLines[i], false); } labelAssignmentVisible = true; } manager.setContentsAdjusting(false); manager.reDraw(); } finally { blockEventsCounter--; } } /** * Hide the label assignment. */ void hideLabelAssignment() { if (labelAssignmentVisible) { blockEventsCounter++; try { manager.setContentsAdjusting(true); for (int i = 0; i < labels.length; i++) if (assignmentLines[i].getGraphicBag() != null) manager.removeObject(assignmentLines[i], false); manager.setContentsAdjusting(false); manager.reDraw(); labelAssignmentVisible = false; } finally { blockEventsCounter--; } } } /** * Calculate the middle point of the polyline. */ IlvPoint getPolylineMiddle(IlvPoint[] pts) { // first, calculate the line length; double lineLength = 0.0; double dx, dy; int i; for (i = 1; i < pts.length; i++) { dx = pts[i].x - pts[i - 1].x; dy = pts[i].y - pts[i - 1].y; lineLength += Math.sqrt(dx * dx + dy * dy); } // now go to the middle double wishLength = lineLength / 2; double dist; for (i = 1; i < pts.length; i++) { dx = pts[i].x - pts[i - 1].x; dy = pts[i].y - pts[i - 1].y; dist = Math.sqrt(dx * dx + dy * dy); if (wishLength <= dist) { return new IlvPoint(pts[i - 1].x + wishLength / dist * dx, pts[i - 1].y + wishLength / dist * dy); } wishLength -= dist; } return pts[0]; } /** * Calculates the x coord of a point on the polyline to a given y coord. It * returns on ambiguity the point that is closest to the reference x point. It * returns POSITIVE_INFINITY if the polyline has no point at the given y * coordinate. */ double getPolylineX(double y, double refX, IlvPoint[] pts) { double bestX = Double.POSITIVE_INFINITY; double bestDist = Double.MAX_VALUE; double x, d; double dx, dy; for (int i = 1; i < pts.length; i++) { if (pts[i - 1].y == pts[i].y && pts[i].y == y) { d = Math.abs(pts[i - 1].x - refX); if (d < bestDist) { bestDist = d; bestX = pts[i - 1].x; } d = Math.abs(pts[i].x - refX); if (d < bestDist) { bestDist = d; bestX = pts[i].x; } } else if ((pts[i - 1].y <= y && y <= pts[i].y) || (pts[i].y <= y && y <= pts[i - 1].y)) { dx = pts[i].x - pts[i - 1].x; dy = pts[i].y - pts[i - 1].y; x = pts[i - 1].x + (y - pts[i - 1].y) * dx / dy; d = Math.abs(x - refX); if (d < bestDist) { bestDist = d; bestX = x; } } } return bestX; } /** * Calculates the y coord of a point on the polyline to a given x coord. It * returns on ambiguity the point that is closest to the reference y point. It * returns POSITIVE_INFINITY if the polyline has no point at the given x * coordinate. */ double getPolylineY(double x, double refY, IlvPoint[] pts) { double bestY = Double.POSITIVE_INFINITY; double bestDist = Double.MAX_VALUE; double y, d; double dx, dy; for (int i = 1; i < pts.length; i++) { if (pts[i - 1].x == pts[i].x && pts[i].x == x) { d = Math.abs(pts[i - 1].y - refY); if (d < bestDist) { bestDist = d; bestY = pts[i - 1].y; } d = Math.abs(pts[i].y - refY); if (d < bestDist) { bestDist = d; bestY = pts[i].y; } } else if ((pts[i - 1].x <= x && x <= pts[i].x) || (pts[i].x <= x && x <= pts[i - 1].x)) { dx = pts[i].x - pts[i - 1].x; dy = pts[i].y - pts[i - 1].y; y = pts[i - 1].y + (x - pts[i - 1].x) * dy / dx; d = Math.abs(y - refY); if (d < bestDist) { bestDist = d; bestY = y; } } } return bestY; } /** * Show or hide the background of all labels. */ void setNoLabelBackground(boolean flag) { IlvZoomableLabel label; IlvApplyObject apply; if (flag) apply = new IlvApplyObject() { Override public void apply(IlvGraphic obj, Object arg) { IlvZoomableLabel l = (IlvZoomableLabel) obj; l.setBorderOn(false); l.setBackgroundOn(false); } }; else apply = new IlvApplyObject() { Override 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; double w; Object ownerObstacle; // calculate the actual overlap value, and the hypothetical maximal // overlap. The hypothetical maximal 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.0); maxOverlap += 0.5 * r1.width * r1.height; maxOverlap += 0.5 * r2.width * 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.0); maxOverlap += 0.5 * r1.width * r1.height; maxOverlap += 0.5 * r2.width * r2.height; } else { r2 = labelingModel.boundingBox(obstacles[j]); overlap += labelingModel.getObstacleOverlap(labels[i], r1, obstacles[j], r2, 0.0); maxOverlap += 0.5 * r1.width * r1.height; maxOverlap += 0.5 * r2.width * r2.height; } } } } // the percentage of overlap double perc = 100 * (overlap / maxOverlap); // calculate the absolute overlap, with 2 digits after dot. overlap = ((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 = ((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 preprocess() { IlvGraphic obj; // calculate the layout region IlvGraphicEnumeration selectedObjects = manager.getObjects(); double minX = Double.MAX_VALUE; double maxX = -Double.MAX_VALUE; double minY = Double.MAX_VALUE; double maxY = -Double.MAX_VALUE; while (selectedObjects.hasMoreElements()) { obj = selectedObjects.nextElement(); if (labelingModel.isObstacle(obj)) { IlvRect r = labelingModel.boundingBox(obj); if (r.x < minX) minX = r.x; if (r.x + r.width > maxX) maxX = r.x + r.width; if (r.y < minY) minY = r.y; if (r.y + r.height > maxY) maxY = r.y + r.height; } } randomLayout.setLayoutRegion(new IlvRect(minX, minY, (maxX - minX), (maxY - minY))); } /** * Listen to changes of the manager. */ Override public void contentsChanged(ManagerContentChangedEvent evt) { // the label assignment, if visible, should be kept up to date if (!evt.isAdjusting() && blockEventsCounter == 0) { if (labelAssignmentVisible) showLabelAssignment(); } } /** * Listen to selections of the manager. */ Override public void selectionChanged(ManagerSelectionChangedEvent event) { if (labelingModel != null && layout.isAutoUpdate()) { // 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 = 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. */ Override 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); } else if (labelAssignmentVisible) { // update the label assignment just for sure. showLabelAssignment(); manager.reDraw(); } } } /** * 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 LinkLabelLayoutApp app; LinkLabelingListener(LinkLabelLayoutApp app) { this.app = app; } /** * This method is automatically called by the layout algorithm. */ Override public void layoutStepPerformed(LabelLayoutEvent event) { float percentage = event.getLayoutReport().getPercentageComplete(); if (percentage != oldPercentage) { app.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; } } /** * 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. */ Override 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; } } }