blob: 0ddf913c8b71f8e9d5491663b602726da03428a2 [file] [log] [blame]
/*******************************************************************************
* Copyright 2005, CHISEL Group, University of Victoria, Victoria, BC, Canada.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* The Chisel Group, University of Victoria
*******************************************************************************/
package org.eclipse.mylar.zest.layouts.exampleUses;
import java.awt.BasicStroke;
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Cursor;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Point;
import java.awt.Polygon;
import java.awt.RenderingHints;
import java.awt.Shape;
import java.awt.Stroke;
import java.awt.Toolkit;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.event.MouseMotionListener;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.awt.geom.AffineTransform;
import java.awt.geom.Point2D;
import java.awt.geom.Rectangle2D;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.Iterator;
import java.util.List;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JToggleButton;
import javax.swing.JToolBar;
import org.eclipse.mylar.zest.layouts.InvalidLayoutConfiguration;
import org.eclipse.mylar.zest.layouts.LayoutAlgorithm;
import org.eclipse.mylar.zest.layouts.LayoutBendPoint;
import org.eclipse.mylar.zest.layouts.LayoutEntity;
import org.eclipse.mylar.zest.layouts.LayoutRelationship;
import org.eclipse.mylar.zest.layouts.LayoutStyles;
import org.eclipse.mylar.zest.layouts.algorithms.FadeLayoutAlgorithm;
import org.eclipse.mylar.zest.layouts.algorithms.GridLayoutAlgorithm;
import org.eclipse.mylar.zest.layouts.algorithms.HorizontalLayoutAlgorithm;
import org.eclipse.mylar.zest.layouts.algorithms.HorizontalTreeLayoutAlgorithm;
import org.eclipse.mylar.zest.layouts.algorithms.RadialLayoutAlgorithm;
import org.eclipse.mylar.zest.layouts.algorithms.SpringLayoutAlgorithm;
import org.eclipse.mylar.zest.layouts.algorithms.TreeLayoutAlgorithm;
import org.eclipse.mylar.zest.layouts.algorithms.VerticalLayoutAlgorithm;
import org.eclipse.mylar.zest.layouts.exampleStructures.SimpleNode;
import org.eclipse.mylar.zest.layouts.exampleStructures.SimpleRelationship;
import org.eclipse.mylar.zest.layouts.progress.ProgressEvent;
import org.eclipse.mylar.zest.layouts.progress.ProgressListener;
/**
* @author Rob Lintern
* @author Chris Bennett
*
* A simple example of using layout algorithms with a Swing application.
*/
public class SimpleSwingExample {
private static final Color NODE_NORMAL_COLOR = new Color (225, 225, 255);
private static final Color NODE_SELECTED_COLOR = new Color(255, 125, 125);
//private static final Color NODE_ADJACENT_COLOR = new Color (255, 200, 125);
private static final Color BORDER_NORMAL_COLOR = new Color(0, 0, 0);
private static final Color BORDER_SELECTED_COLOR = new Color (255, 0, 0);
//private static final Color BORDER_ADJACENT_COLOR = new Color (255, 128, 0);
private static final Stroke BORDER_NORMAL_STROKE = new BasicStroke (1.0f);
private static final Stroke BORDER_SELECTED_STROKE = new BasicStroke (2.0f);
private static final Color RELATIONSHIP_NORMAL_COLOR = Color.BLUE;
//private static final Color RELATIONSHIP_HIGHLIGHT_COLOR = new Color (255, 200, 125);
public static final FadeLayoutAlgorithm FADE = new FadeLayoutAlgorithm ( LayoutStyles.NONE );
public static final SpringLayoutAlgorithm SPRING = new SpringLayoutAlgorithm ( LayoutStyles.NONE );
public static final TreeLayoutAlgorithm TREE_VERT = new TreeLayoutAlgorithm ( LayoutStyles.NONE );
public static final HorizontalTreeLayoutAlgorithm TREE_HORIZ = new HorizontalTreeLayoutAlgorithm ( LayoutStyles.NONE );
public static final RadialLayoutAlgorithm RADIAL = new RadialLayoutAlgorithm ( LayoutStyles.NONE );
public static final GridLayoutAlgorithm GRID = new GridLayoutAlgorithm ( LayoutStyles.NONE );
public static final HorizontalLayoutAlgorithm HORIZ = new HorizontalLayoutAlgorithm ( LayoutStyles.NONE );
public static final VerticalLayoutAlgorithm VERT = new VerticalLayoutAlgorithm ( LayoutStyles.NONE );
private List algorithms = new ArrayList();
private List algorithmNames = new ArrayList();
private static final int INITIAL_PANEL_WIDTH = 700;
private static final int INITIAL_PANEL_HEIGHT = 500;
private static final boolean RENDER_HIGH_QUALITY = true;
private static final double INITIAL_NODE_WIDTH = 20;
private static final double INITIAL_NODE_HEIGHT = 20;
private static final int ARROW_HALF_WIDTH = 4;
private static final int ARROW_HALF_HEIGHT = 6;
private static final Shape ARROW_SHAPE = new Polygon (new int [] {-ARROW_HALF_HEIGHT, ARROW_HALF_HEIGHT, -ARROW_HALF_HEIGHT}, new int []{-ARROW_HALF_WIDTH, 0, ARROW_HALF_WIDTH}, 3);
private static final Stroke ARROW_BORDER_STROKE = new BasicStroke (0.5f);
private static final Color ARROW_HEAD_FILL_COLOR = new Color (125, 255, 125);
private static final Color ARROW_HEAD_BORDER_COLOR = Color.BLACK;
public static final String DEFAULT_NODE_SHAPE = "oval";
private long updateGUICount = 0;
private JFrame mainFrame;
private JPanel mainPanel;
private List entities;
private List relationships;
private JToolBar toolBar;
private JLabel lblProgress;
private JToggleButton btnContinuous;
private JToggleButton btnAsynchronous;
private JButton btnStop;
private LayoutAlgorithm currentLayoutAlgorithm;
protected String currentLayoutAlgorithmName;
protected SimpleNode selectedEntity;
protected Point mouseDownPoint;
protected Point selectedEntityPositionAtMouseDown;
private long idCount;
protected String currentNodeShape = DEFAULT_NODE_SHAPE; // e.g., oval, rectangle
public SimpleSwingExample () {
SPRING.setIterations(1000);
// initialize layouts
TREE_VERT.setComparator(new Comparator () {
public int compare(Object o1, Object o2) {
if (o1 instanceof Comparable && o2 instanceof Comparable) {
return ((Comparable)o1).compareTo(o2);
}
return 0;
}
});
GRID.setRowPadding(20);
addAlgorithm (FADE, "FADE", false);
addAlgorithm (SPRING, "Spring", false);
addAlgorithm (TREE_VERT, "Tree-V", false);
addAlgorithm (TREE_HORIZ, "Tree-H", false);
addAlgorithm (RADIAL, "Radial", false);
addAlgorithm (GRID, "Grid", false);
addAlgorithm (HORIZ, "Horiz", false);
addAlgorithm (VERT, "Vert", false);
}
protected void addAlgorithm (LayoutAlgorithm algorithm, String name, boolean animate) {
algorithms.add(algorithm);
algorithmNames.add(name);
}
public void start() {
mainFrame = new JFrame ("Simple Swing Layout Example");
toolBar = new JToolBar ();
mainFrame.getContentPane().setLayout(new BorderLayout());
mainFrame.getContentPane().add(toolBar, BorderLayout.NORTH);
lblProgress = new JLabel ("Progress: ");
mainFrame.getContentPane().add(lblProgress, BorderLayout.SOUTH);
for (int i = 0; i < algorithms.size(); i++) {
final LayoutAlgorithm algorithm = (LayoutAlgorithm) algorithms.get(i);
final String algorithmName = (String) algorithmNames.get(i);
//final boolean algorithmAnimate = ((Boolean)algorithmAnimates.get(i)).booleanValue();
JButton algorithmButton = new JButton (algorithmName);
algorithmButton.addActionListener(new ActionListener () {
public void actionPerformed(ActionEvent e) {
currentLayoutAlgorithm = algorithm;
currentLayoutAlgorithmName = algorithmName;
algorithm.setEntityAspectRatio((double)mainPanel.getWidth()/(double)mainPanel.getHeight());
//animate = algorithmAnimate;
performLayout();
}
});
toolBar.add(algorithmButton);
}
createMainPanel();
mainFrame.addWindowListener(new WindowAdapter() {
public void windowClosing(WindowEvent e) {
stop();
mainFrame.dispose();
}
});
btnContinuous = new JToggleButton ("continuous", false);
btnAsynchronous = new JToggleButton ("asynchronous", false);
toolBar.add(btnContinuous);
toolBar.add(btnAsynchronous);
btnStop = new JButton("Stop");
btnStop.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
stop();
}
});
toolBar.add(btnStop);
JButton btnCreateGraph = new JButton ("New graph");
btnCreateGraph.addActionListener(new ActionListener () {
public void actionPerformed(ActionEvent e) {
stop();
createGraph(true);
}
});
toolBar.add(btnCreateGraph);
JButton btnCreateTree = new JButton ("New tree");
btnCreateTree.addActionListener(new ActionListener () {
public void actionPerformed(ActionEvent e) {
stop();
createGraph(false);
}
});
toolBar.add(btnCreateTree);
createGraph(false);
Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize();
mainFrame.setLocation((int) (screenSize.getWidth() - INITIAL_PANEL_WIDTH)/2, (int) (screenSize.getHeight() - INITIAL_PANEL_HEIGHT)/2);
mainFrame.pack();
mainFrame.setVisible(true);
mainFrame.repaint();
}
private void stop() {
if (currentLayoutAlgorithm != null && currentLayoutAlgorithm.isRunning()) {
currentLayoutAlgorithm.stop();
}
}
protected void performLayout () {
stop();
final Cursor cursor = mainFrame.getCursor();
updateGUICount = 0;
placeRandomly();
final boolean continuous = btnContinuous.isSelected();
final boolean asynchronous = btnAsynchronous.isSelected();
ProgressListener progressListener = new ProgressListener () {
public void progressUpdated(final ProgressEvent e) {
//if (asynchronous) {
updateGUI();
//}
lblProgress.setText("Progress: " + e.getStepsCompleted() + " of " + e.getTotalNumberOfSteps() + " completed ...");
lblProgress.paintImmediately(0,0,lblProgress.getWidth(), lblProgress.getHeight());
}
public void progressStarted(ProgressEvent e) {
if (!asynchronous) {
mainFrame.setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR));
}
lblProgress.setText("Layout started ...");
lblProgress.paintImmediately(0,0,lblProgress.getWidth(), lblProgress.getHeight());
}
public void progressEnded(ProgressEvent e) {
lblProgress.setText("Layout completed ...");
lblProgress.paintImmediately(0,0,lblProgress.getWidth(), lblProgress.getHeight());
currentLayoutAlgorithm.removeProgressListener(this);
if (!asynchronous) {
mainFrame.setCursor(cursor);
}
}
};
currentLayoutAlgorithm.addProgressListener(progressListener);
try {
LayoutEntity [] layoutEntities = new LayoutEntity [entities.size()];
entities.toArray(layoutEntities);
LayoutRelationship [] layoutRelationships = new LayoutRelationship [relationships.size()];
relationships.toArray(layoutRelationships);
currentLayoutAlgorithm.applyLayout(layoutEntities, layoutRelationships, 0, 0, mainPanel.getWidth(), mainPanel.getHeight(), asynchronous, continuous);
//if (!animate) {
updateGUI();
//}
// reset
currentNodeShape = DEFAULT_NODE_SHAPE;
} catch (InvalidLayoutConfiguration e) {
JOptionPane.showMessageDialog(mainFrame, "Not a valid layout configuration:\nlayout='" + currentLayoutAlgorithmName + "', continuous='" + continuous + "', asynchronous='" + asynchronous +"'");
} catch (StackOverflowError e) {
e.printStackTrace();
} finally {
}
}
private void createMainPanel () {
mainPanel = new MainPanel(); // see below for class definition
mainPanel.setPreferredSize(new Dimension(INITIAL_PANEL_WIDTH, INITIAL_PANEL_HEIGHT));
mainPanel.setBackground(Color.WHITE);
mainPanel.setLayout(null);
mainFrame.getContentPane().add(new JScrollPane(mainPanel), BorderLayout.CENTER);
mainPanel.addMouseListener(new MouseAdapter () {
public void mousePressed(MouseEvent e) {
selectedEntity = null;
for (Iterator iter = entities.iterator(); iter.hasNext() && selectedEntity == null;) {
SimpleNode entity = (SimpleNode) iter.next();
double x = entity.getX();
double y = entity.getY();
double w = entity.getWidth();
double h = entity.getHeight();
Rectangle2D.Double rect = new Rectangle2D.Double (x, y, w, h);
if (rect.contains(e.getX(), e.getY())) {
selectedEntity = entity;
}
}
if (selectedEntity != null) {
mouseDownPoint = e.getPoint();
selectedEntityPositionAtMouseDown = new Point ((int)selectedEntity.getX(), (int)selectedEntity.getY());
} else {
mouseDownPoint = null;
selectedEntityPositionAtMouseDown = null;
}
updateGUI();
}
public void mouseReleased(MouseEvent e) {
selectedEntity = null;
mouseDownPoint = null;
selectedEntityPositionAtMouseDown = null;
updateGUI();
}
});
mainPanel.addMouseMotionListener(new MouseMotionListener () {
public void mouseDragged(MouseEvent e) {
// if (selectedEntity != null) {
// //TODO: Add mouse moving
// //selectedEntity.setLocationInLayout(selectedEntityPositionAtMouseDown.x + dx, selectedEntityPositionAtMouseDown.y + dy);
// updateGUI();
// }
}
public void mouseMoved(MouseEvent e) {
}
});
}
private void createGraph(boolean addNonTreeRels) {
entities = new ArrayList();
relationships = new ArrayList();
selectedEntity = null;
createTreeGraph(2, 4, 2, 5, true, true, addNonTreeRels);
// createCustomGraph();
placeRandomly();
mainPanel.repaint();
}
/**
*
* @param maxLevels Max number of levels wanted in tree
* @param maxChildren Max number of children for each node in the tree
* @param randomNumChildren Whether or not to pick random number of levels (from 1 to maxLevels) and
* random number of children (from 1 to maxChildren)
*/
private void createTreeGraph (int minChildren, int maxChildren, int minLevels, int maxLevels, boolean randomNumChildren, boolean randomLevels, boolean addNonTreeRels) {
LayoutEntity currentParent = createSimpleNode(getNextID());
entities.add(currentParent);
createTreeGraphRecursive (currentParent, minChildren, maxChildren, minLevels, maxLevels, 1, randomNumChildren, randomLevels, addNonTreeRels);
}
private void createTreeGraphRecursive (LayoutEntity currentParentNode, int minChildren, int maxChildren, int minLevel, int maxLevel, int level, boolean randomNumChildren, boolean randomLevels, boolean addNonTreeRels) {
if (level > maxLevel) return;
if (randomLevels) {
if (level > minLevel) {
double zeroToOne = Math.random();
if (zeroToOne < 0.75) {
return;
}
}
}
int numChildren = randomNumChildren ? Math.max (minChildren, (int) (Math.random() * maxChildren + 1)) : maxChildren;
for (int i = 0; i < numChildren; i++) {
LayoutEntity newNode = createSimpleNode(getNextID());
entities.add(newNode);
if (addNonTreeRels && entities.size() % 5 == 0) {
int index = (int) (Math.random() * entities.size());
LayoutRelationship rel = new SimpleRelationship ((LayoutEntity) entities.get(index), newNode, false);
relationships.add(rel);
}
LayoutRelationship rel = new SimpleRelationship (currentParentNode, newNode, false);
relationships.add(rel);
createTreeGraphRecursive(newNode, minChildren, maxChildren, minLevel, maxLevel, level + 1, randomNumChildren, randomLevels, addNonTreeRels);
}
}
/**
* Call this from createGraph in place of createTreeGraph
* this for debugging and testing.
*/
/* private void createCustomGraph() {
LayoutEntity A = createSimpleNode("1");
LayoutEntity B = createSimpleNode("10");
LayoutEntity _1 = createSimpleNode("100");
entities.add(A);
entities.add(B);
entities.add(_1);
relationships.add(new SimpleRelationship (A, B, false));
relationships.add(new SimpleRelationship (A, _1, false));
relationships.add(new SimpleRelationship (_1, A, false));
}
*/
private String getNextID () {
String id = "" + idCount;
idCount++;
return id;
}
/** Places nodes randomly on the screen **/
private void placeRandomly () {
for (Iterator iter = entities.iterator(); iter.hasNext();) {
SimpleNode simpleNode = (SimpleNode) iter.next();
double x = Math.random() * INITIAL_PANEL_WIDTH - INITIAL_NODE_WIDTH;
double y = Math.random() * INITIAL_PANEL_HEIGHT - INITIAL_NODE_HEIGHT;
simpleNode.setLocationInLayout( x, y );
simpleNode.setSizeInLayout( INITIAL_NODE_WIDTH, INITIAL_NODE_HEIGHT );
}
}
/**
* Creates a SimpleNode
* @param name
* @return
*/
private SimpleNode createSimpleNode (String name) {
SimpleNode simpleNode = new SimpleNode (name);
return simpleNode;
}
private void updateGUI () {
updateGUICount++;
if (updateGUICount > 0) {
mainPanel.paintImmediately(0, 0, mainPanel.getWidth(), mainPanel.getHeight());
}
}
private static Point2D.Double getEllipseIntersectionPoint (double theta, double ellipseWidth, double ellipseHeight) {
double nhalfw = ellipseWidth/2.0; // half elllipse width
double nhalfh = ellipseHeight/2.0; // half ellipse height
double tanTheta = Math.tan(theta);
double a = nhalfw;
double b = nhalfh;
double x = (a*b)/Math.sqrt( Math.pow(b, 2) + Math.pow(a, 2) * Math.pow(tanTheta, 2));
if ((theta > Math.PI/2.0 && theta < 1.5*Math.PI) || (theta < -Math.PI/2.0 && theta > -1.5*Math.PI)) {
x = -x;
}
double y = tanTheta*x;
Point2D.Double p = new Point2D.Double(x, y);
return p;
}
public static void main(String[] args) {
(new SimpleSwingExample ()).start();
}
/**
* A JPanel that provides entity and relationship rendering
* Instead of letting Swing paint all the JPanels for us, we will just do our own painting here
*/
private class MainPanel extends JPanel {
private static final long serialVersionUID = 1;
protected void paintChildren(Graphics g) {
if (g instanceof Graphics2D && RENDER_HIGH_QUALITY) {
((Graphics2D)g).setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
((Graphics2D)g).setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
((Graphics2D)g).setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY);
((Graphics2D)g).setRenderingHint(RenderingHints.KEY_FRACTIONALMETRICS, RenderingHints.VALUE_FRACTIONALMETRICS_ON);
}
// paint the nodes
for (Iterator iter = entities.iterator(); iter.hasNext();) {
paintEntity((SimpleNode) iter.next(), g);
}
// paint the relationships
for (Iterator iter = relationships.iterator(); iter.hasNext();) {
paintRelationship((LayoutRelationship) iter.next(), g);
}
}
private void paintEntity(SimpleNode entity, Graphics g) {
boolean isSelected = selectedEntity != null && selectedEntity.equals(entity);
g.setColor(isSelected ? NODE_SELECTED_COLOR : NODE_NORMAL_COLOR);
if (currentNodeShape.equals("rectangle")) {
g.fillRect((int)entity.getX(), (int)entity.getY(), (int)entity.getWidth(), (int)entity.getHeight());
}
else { // default
g.fillOval((int)entity.getX(), (int)entity.getY(), (int)entity.getWidth(), (int)entity.getHeight());
}
g.setColor( isSelected ? BORDER_SELECTED_COLOR : BORDER_NORMAL_COLOR);
String name = entity.toString();
Rectangle2D nameBounds = g.getFontMetrics().getStringBounds(name, g);
g.drawString(name, (int) (entity.getX() + entity.getWidth()/2.0 - nameBounds.getWidth()/2.0),
(int)(entity.getY() + entity.getHeight()/2.0 + nameBounds.getHeight()/2.0));//- nameBounds.getHeight() - nameBounds.getY()));
if (g instanceof Graphics2D) {
((Graphics2D)g).setStroke(isSelected ? BORDER_SELECTED_STROKE : BORDER_NORMAL_STROKE);
}
if (currentNodeShape.equals("rectangle")) {
g.drawRect((int)entity.getX(), (int)entity.getY(), (int)entity.getWidth(), (int)entity.getHeight());
}
else { // default
g.drawOval((int)entity.getX(), (int)entity.getY(), (int)entity.getWidth(), (int)entity.getHeight());
}
}
private void paintRelationship(LayoutRelationship rel, Graphics g) {
SimpleNode src = (SimpleNode) rel.getSourceInLayout();
SimpleNode dest = (SimpleNode) rel.getDestinationInLayout();
// Add bend points if required
if (((SimpleRelationship) rel).getBendPoints() != null
&& ((SimpleRelationship) rel).getBendPoints().length > 0) {
drawBendPoints(rel, g);
} else {
double srcX = src.getX() + src.getWidth() / 2.0;
double srcY = src.getY() + src.getHeight() / 2.0;
double destX = dest.getX() + dest.getWidth() / 2.0;
double destY = dest.getY() + dest.getHeight() / 2.0;
double dx = getLength(srcX, destX);
double dy = getLength(srcY, destY);
double theta = Math.atan2(dy, dx);
drawRelationship(src, dest, theta, srcX, srcY, destX, destY, g);
// draw an arrow in the middle of the line
drawArrow(theta, srcX, srcY, dx, dy, g);
}
}
/**
* Draw a line from the edge of the src node to the edge of the destination node
*/
private void drawRelationship(SimpleNode src, SimpleNode dest, double theta,
double srcX, double srcY, double destX, double destY, Graphics g) {
double reverseTheta = theta > 0.0d ? theta - Math.PI : theta + Math.PI;
Point2D.Double srcIntersectionP =
getEllipseIntersectionPoint(theta, src.getWidth(),
src.getHeight());
Point2D.Double destIntersectionP =
getEllipseIntersectionPoint(reverseTheta, dest.getWidth(),
dest.getHeight());
drawRelationship(srcX + srcIntersectionP.getX(), srcY + srcIntersectionP.getY(),
destX + destIntersectionP.getX(), destY + destIntersectionP.getY(), g);
}
/**
* Draw a line from specified source to specified destination
*/
private void drawRelationship(double srcX, double srcY, double destX, double destY, Graphics g) {
g.setColor(RELATIONSHIP_NORMAL_COLOR);
g.drawLine((int)srcX, (int)srcY, (int)destX, (int)destY);
}
private void drawArrow(double theta, double srcX, double srcY, double dx, double dy, Graphics g) {
AffineTransform tx = new AffineTransform();
double arrX = srcX + (dx) / 2.0;
double arrY = srcY + (dy) / 2.0;
tx.translate(arrX, arrY);
tx.rotate(theta);
Shape arrowTx = tx.createTransformedShape(ARROW_SHAPE);
if (g instanceof Graphics2D) {
g.setColor(ARROW_HEAD_FILL_COLOR);
((Graphics2D) g).fill(arrowTx);
((Graphics2D) g).setStroke(ARROW_BORDER_STROKE);
g.setColor(ARROW_HEAD_BORDER_COLOR);
((Graphics2D) g).draw(arrowTx);
}
}
/**
* Get the length of a line ensuring it is not too small to render
* @param start
* @param end
* @return
*/
private double getLength(double start, double end) {
double length = end - start;
// make sure dx is not zero or too small
if (length < 0.01 && length > -0.01) {
if (length > 0) {
length = 0.01;
} else if (length < 0) {
length = -0.01;
}
}
return length;
}
/**
* Draws a set of lines between bendpoints, returning the last bendpoint
* drawn. Note that this assumes the first and last bendpoints are actually
* the source node and destination node centre points.
* @param relationship
* @param bendNodes
* @param bendEdges
* @return the last bendpoint entity or null if there are no bendpoints
*/
private void drawBendPoints(LayoutRelationship rel, Graphics g) {
final String DUMMY_TITLE = "dummy";
LayoutBendPoint bp;
SimpleNode startEntity = (SimpleNode) rel.getSourceInLayout();
SimpleNode destEntity = (SimpleNode) rel.getDestinationInLayout();
double srcX = startEntity.getX();
double srcY = startEntity.getY();
// Transform the bendpoints to this coordinate system
LayoutBendPoint[] bendPoints = ((SimpleRelationship)rel).getBendPoints();
srcX = bendPoints[1].getX();
srcY = bendPoints[1].getY();
for (int i = 2; i < bendPoints.length-1; i++) { // ignore first and last bendpoints (src and dest)
bp = bendPoints[i];
destEntity = new SimpleNode(DUMMY_TITLE, bp.getX(), bp.getY(), 0.01, 0.01);
drawRelationship(srcX, srcY, bp.getX(), bp.getY(), g);
startEntity = (SimpleNode) destEntity;
if (i == bendPoints.length - 2) { // last point
// draw an arrow in the middle of the line
double dx = getLength(srcX, destEntity.getX());
double dy = getLength(srcY, destEntity.getY());
double theta = Math.atan2(dy, dx);
drawArrow(theta, srcX, srcY, dx, dy, g);
}
else {
srcX = startEntity.getX();
srcY = startEntity.getY();
}
}
}
}
}