/*
* Licensed Materials - Property of Rogue Wave Software, Inc.
* © Copyright Rogue Wave Software, Inc. 2014, 2017
* © 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.util.Enumeration;
import java.util.Hashtable;
import java.util.Random;
import javax.swing.JButton;
import javax.swing.JCheckBox;
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.ManagerSelectionChangedEvent;
import ilog.views.event.ManagerSelectionListener;
import ilog.views.event.TransformerChangedEvent;
import ilog.views.event.TransformerListener;
import ilog.views.graphic.IlvReliefRectangle;
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.IlvLabelingModel;
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.interactor.IlvSelectInteractor;
import ilog.views.interactor.IlvZoomViewInteractor;
import ilog.views.swing.IlvJManagerViewControlBar;
import ilog.views.swing.IlvJScrollManagerView;
import ilog.views.util.IlvProductUtil;
/**
* The main class LinkLabelLayoutApp.
*/
public class LinkLabelLayoutApp extends JRootPane implements 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 */
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 double minDistToOtherObstacles = 10.0;
/** 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<IlvGraphic, IlvLabelLinkImage> assignmentTable;
Object[] obstacles;
Object[] labels;
/**
* Initializes the application.
*/
public void init() {
showMessage("layout sample initialization...");
JPanel panel;
// 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() {
Override
public void actionPerformed(ActionEvent evt) {
randomize(manager);
}
});
layoutPanel.add(b = new JButton("Layout"));
b.setToolTipText("Perform a label layout");
b.addActionListener(new ActionListener() {
Override
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() {
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", !noLabelBackground);
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);
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() {
Override
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() {
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);
}
});
}
/**
* 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(double x, double 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<IlvGraphic, IlvLabelLinkImage>();
labelingModel.assignmentTable = assignmentTable;
for (i = 0; i < numObstacles; i++) {
if (obstacles[i] instanceof IlvLabelLinkImage) {
IlvGraphic label = ((IlvLabelLinkImage) obstacles[i]).getLabel();
assignmentTable.put(label, (IlvLabelLinkImage) 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 = 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) {
Override
public double getRotation(IlvLabelingModel model, IlvRect labelRect) {
IlvLabelLinkImage link = 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(double value) {
if (value > 100.0)
value = 100.0;
if (value < 0.0)
value = 0.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.2 * value);
}
/**
* 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 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.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 randomize(IlvManager manager) {
Random rand = new Random();
for (int i = 0; i < labels.length; i++) {
IlvZoomableLabel label = (IlvZoomableLabel) labels[i];
int x = rand.nextInt(500);
int y = rand.nextInt(500);
manager.moveObject(label, x, y, true);
}
}
/**
* Listen to selections of the manager.
*/
Override
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 = 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);
}
}
}
/**
* 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;
}
}
/**
* The labeling model that understands the IlvLabelLinkImage with its rotation.
*/
class LabelingModel extends IlvDefaultLabelingModel {
Hashtable<IlvGraphic, IlvLabelLinkImage> 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.
*/
Override
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.
*/
Override
public void moveLabel(Object label, double x, double y, boolean redraw) {
if (label instanceof IlvZoomableLabel) {
IlvZoomableLabel zlabel = (IlvZoomableLabel) label;
IlvLabelLinkImage link = 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.
*/
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;
}
}
}