/******************************************************************************* * 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.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.GeneralPath; import java.awt.geom.Point2D; import java.awt.geom.Rectangle2D; import java.lang.reflect.InvocationTargetException; 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.JPanel; import javax.swing.JScrollPane; import javax.swing.JToggleButton; import javax.swing.JToolBar; import javax.swing.SwingUtilities; import org.eclipse.zest.layouts.InvalidLayoutConfiguration; import org.eclipse.zest.layouts.LayoutAlgorithm; import org.eclipse.zest.layouts.LayoutBendPoint; import org.eclipse.zest.layouts.LayoutEntity; import org.eclipse.zest.layouts.LayoutRelationship; import org.eclipse.zest.layouts.LayoutStyles; import org.eclipse.zest.layouts.algorithms.GridLayoutAlgorithm; import org.eclipse.zest.layouts.algorithms.HorizontalLayoutAlgorithm; import org.eclipse.zest.layouts.algorithms.HorizontalTreeLayoutAlgorithm; import org.eclipse.zest.layouts.algorithms.RadialLayoutAlgorithm; import org.eclipse.zest.layouts.algorithms.SpringLayoutAlgorithm; import org.eclipse.zest.layouts.algorithms.TreeLayoutAlgorithm; import org.eclipse.zest.layouts.algorithms.VerticalLayoutAlgorithm; import org.eclipse.zest.layouts.exampleStructures.SimpleNode; import org.eclipse.zest.layouts.exampleStructures.SimpleRelationship; import org.eclipse.zest.layouts.progress.ProgressEvent; import org.eclipse.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 SpringLayoutAlgorithm SPRING = new SpringLayoutAlgorithm(LayoutStyles.NONE); public static TreeLayoutAlgorithm TREE_VERT = new TreeLayoutAlgorithm(LayoutStyles.NONE); public static HorizontalTreeLayoutAlgorithm TREE_HORIZ = new HorizontalTreeLayoutAlgorithm(LayoutStyles.NONE); public static RadialLayoutAlgorithm RADIAL = new RadialLayoutAlgorithm(LayoutStyles.NONE); public static GridLayoutAlgorithm GRID = new GridLayoutAlgorithm(LayoutStyles.NONE); public static HorizontalLayoutAlgorithm HORIZ = new HorizontalLayoutAlgorithm(LayoutStyles.NONE); public static 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() { } 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); 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(); try { SwingUtilities.invokeAndWait(new Runnable() { public void run() { SPRING = new SpringLayoutAlgorithm(LayoutStyles.NONE); TREE_VERT = new TreeLayoutAlgorithm(LayoutStyles.NONE); TREE_HORIZ = new HorizontalTreeLayoutAlgorithm(LayoutStyles.NONE); RADIAL = new RadialLayoutAlgorithm(LayoutStyles.NONE); GRID = new GridLayoutAlgorithm(LayoutStyles.NONE); HORIZ = new HorizontalLayoutAlgorithm(LayoutStyles.NONE); VERT = new VerticalLayoutAlgorithm(LayoutStyles.NONE); 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(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); 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); } } }); } catch (InterruptedException e1) { // TODO Auto-generated catch block e1.printStackTrace(); } catch (InvocationTargetException e1) { // TODO Auto-generated catch block e1.printStackTrace(); } } 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 { final LayoutEntity[] layoutEntities = new LayoutEntity[entities.size()]; entities.toArray(layoutEntities); final LayoutRelationship[] layoutRelationships = new LayoutRelationship[relationships.size()]; relationships.toArray(layoutRelationships); SwingUtilities.invokeLater(new Runnable() { public void run() { try { currentLayoutAlgorithm.applyLayout(layoutEntities, layoutRelationships, 0, 0, mainPanel.getWidth(), mainPanel.getHeight(), asynchronous, continuous); } catch (InvalidLayoutConfiguration e) { // TODO Auto-generated catch block e.printStackTrace(); } } }); //if (!animate) { updateGUI(); //} // reset currentNodeShape = DEFAULT_NODE_SHAPE; } 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; } /** * Draw a line from specified source to specified destination */ private void drawCurvedRelationship(double srcX, double srcY, double control1X, double control1Y, double control2X, double control2Y, double destX, double destY, Graphics g) { GeneralPath shape = new GeneralPath(); shape.moveTo((float) srcX, (float) srcY); shape.curveTo((float) control1X, (float) control1Y, (float) control2X, (float) control2Y, (float) destX, (float) destY); g.setColor(RELATIONSHIP_NORMAL_COLOR); ((Graphics2D) g).draw(shape); } /** * 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(); int bpNum = 2; while (bpNum < bendPoints.length - 1) { // ignore first and last bendpoints (src and dest) int currentBpNum = bpNum; bp = bendPoints[bpNum]; if (bp.getIsControlPoint()) { if (bendPoints[bpNum + 1].getIsControlPoint()) { destEntity = new SimpleNode(DUMMY_TITLE, bendPoints[bpNum + 2].getX(), bendPoints[bpNum + 2].getY(), 0.01, 0.01); drawCurvedRelationship(srcX, srcY, bp.getX(), bp.getY(), bendPoints[bpNum + 1].getX(), bendPoints[bpNum + 1].getY(), bendPoints[bpNum + 2].getX(), bendPoints[bpNum + 2].getY(), g); bpNum += 4; } else { destEntity = new SimpleNode(DUMMY_TITLE, bp.getX(), bp.getY(), 0.01, 0.01); } } else { drawRelationship(srcX, srcY, bp.getX(), bp.getY(), g); bpNum++; destEntity = new SimpleNode(DUMMY_TITLE, bp.getX(), bp.getY(), 0.01, 0.01); } startEntity = destEntity; if (currentBpNum == 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(); } } } } }