/*
* 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.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;
}
}
}