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