diff java/src/uk/ac/qmul/eecs/ccmi/simpletemplate/SimpleShapeNode.java @ 0:78b7fc5391a2

first import, outcome of NIME 2014 hackaton
author Fiore Martin <f.martin@qmul.ac.uk>
date Tue, 08 Jul 2014 16:28:59 +0100
parents
children
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/java/src/uk/ac/qmul/eecs/ccmi/simpletemplate/SimpleShapeNode.java	Tue Jul 08 16:28:59 2014 +0100
@@ -0,0 +1,600 @@
+/*  
+ CCmI Editor - A Collaborative Cross-Modal Diagram Editing Tool
+  
+ Copyright (C) 2011  Queen Mary University of London (http://ccmi.eecs.qmul.ac.uk/)
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program.  If not, see <http://www.gnu.org/licenses/>.
+*/  
+
+package uk.ac.qmul.eecs.ccmi.simpletemplate;
+
+import java.awt.Color;
+import java.awt.Graphics2D;
+import java.awt.Shape;
+import java.awt.geom.Ellipse2D;
+import java.awt.geom.Line2D;
+import java.awt.geom.Path2D;
+import java.awt.geom.Point2D;
+import java.awt.geom.Rectangle2D;
+import java.awt.geom.RectangularShape;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.LinkedHashMap;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+import org.w3c.dom.NodeList;
+
+import uk.ac.qmul.eecs.ccmi.diagrammodel.ElementChangedEvent;
+import uk.ac.qmul.eecs.ccmi.diagrammodel.NodeProperties;
+import uk.ac.qmul.eecs.ccmi.gui.Direction;
+import uk.ac.qmul.eecs.ccmi.gui.Node;
+import uk.ac.qmul.eecs.ccmi.gui.persistence.PersistenceManager;
+import uk.ac.qmul.eecs.ccmi.utils.Pair;
+
+/**
+ * 
+ * A diagram node that can be represented visually as a simple shape such as 
+ * a rectangle, square, circle, ellipse or triangle.
+ *
+ */
+@SuppressWarnings("serial")
+public abstract class SimpleShapeNode extends Node {
+	
+	public static SimpleShapeNode getInstance(ShapeType shapeType, String typeName, NodeProperties properties){
+		switch(shapeType){
+		case Rectangle :
+			return new RectangularNode(typeName, properties);
+		case Square : 
+			return new SquareNode(typeName, properties);
+		case Circle :
+			return new CircleNode(typeName, properties);
+		case Ellipse :
+			return new EllipticalNode(typeName, properties);
+		case Triangle :
+			return new TriangularNode(typeName, properties);
+		}
+		return null;
+	}
+	
+	protected SimpleShapeNode(String typeName, NodeProperties properties){
+		super(typeName, properties);
+		dataDisplayBounds = (Rectangle2D.Double)getMinBounds();
+		/* Initialise the data structures for displaying the properties inside and outside */
+		propertyNodesMap = new LinkedHashMap<String,List<PropertyNode>>(); 
+		int numInsideProperties = 0;
+		for(String type : getProperties().getTypes()){
+			if(((PropertyView)getProperties().getView(type)).getPosition() == Position.Inside)
+				numInsideProperties++;
+			else
+				propertyNodesMap.put(type, new LinkedList<PropertyNode>());
+		}
+		propertyLabels = new MultiLineString[numInsideProperties];
+		nameLabel = new MultiLineString();
+	}
+	
+	@Override
+	protected void notifyChange(ElementChangedEvent evt){
+		if(!evt.getChangeType().equals("translate")&&!evt.getChangeType().equals("stop_move")){ //don't reshape for just moving 
+			Rectangle2D boundsBeforeReshape = getBounds();
+			reshape();
+			Rectangle2D boundsAfterReshape = getBounds();
+			/* after renaming or setting properties the boundaries can change resulting in a slight shift of the *   
+			 * node centre from its original position. the next line is to place it back to the right position   */
+			Point2D start = new Point2D.Double(boundsAfterReshape.getCenterX(),boundsAfterReshape.getCenterY());
+			translateImplementation(start,
+				boundsBeforeReshape.getCenterX() - boundsAfterReshape.getCenterX(),
+				boundsBeforeReshape.getCenterY() - boundsAfterReshape.getCenterY());
+		}
+		super.notifyChange(evt);
+	}
+	
+	@Override
+	public void setId(long id){
+		super.setId(id);
+		/* when they are given an id nodes change name into "new <type> node <id>" *
+		 * where <type> is the actual type of the node and <id> is the given id    *
+		 * therefore a reshape is necessary to display the new name                */
+		Rectangle2D boundsBeforeReshape = getBounds();
+		reshape();
+		/* the reshape might change the bounds, so the shape is translated so that the top-left  *
+		 * point is at the same position as before just to keep it more consistent               */
+		Rectangle2D boundsAfterReshape = getBounds();
+		translateImplementation(
+			new Point2D.Double(),
+			boundsBeforeReshape.getX() - boundsAfterReshape.getX(),
+			boundsBeforeReshape.getY() - boundsAfterReshape.getY()
+		);
+	}
+	
+	@Override
+	public boolean contains(Point2D p) {
+		if (getShape().contains(p))
+			return true;
+		for(List<PropertyNode> pnList : propertyNodesMap.values())
+			for(PropertyNode pn : pnList) 
+				if(pn.contains(p))
+					return true;
+		return false;
+	}
+	
+	protected void reshape(){
+		Pair<List<String>, List<String>> splitPropertyTypes = splitPropertyTypes();
+		/* properties displayed internally */
+		reshapeInnerProperties(splitPropertyTypes.first);
+		/* properties displayed externally */ 
+		reshapeOuterProperties(splitPropertyTypes.second);	
+	}
+	
+	protected Pair<List<String>, List<String>> splitPropertyTypes(){
+		List<String> types = getProperties().getTypes();
+		ArrayList<String> insidePropertyTypes = new ArrayList<String>(types.size());
+		ArrayList<String> outsidePropertyTypes = new ArrayList<String>(types.size());
+		for(String type : types){
+			if(((PropertyView)getProperties().getView(type)).getPosition() == Position.Inside)
+				insidePropertyTypes.add(type);
+			else
+				outsidePropertyTypes.add(type);
+		}
+		
+		return new Pair<List<String>, List<String>> (insidePropertyTypes,outsidePropertyTypes);
+	}
+	
+	protected void reshapeOuterProperties(List<String> outsidePropertyTypes){
+		for(String type : outsidePropertyTypes){
+			List<PropertyNode> propertyNodes = propertyNodesMap.get(type);
+			List<String> propertyValues = getProperties().getValues(type);
+			int diff = propertyNodes.size()-propertyValues.size();
+			if(diff > 0) // properties have been removed
+				for(int i=0; i < diff; i++)
+					propertyNodes.remove(propertyNodes.size() - 1);
+			else if(diff < 0){ // properties have been added. We need more properties node.
+				for(int i=0; i < -diff; i++){
+					PropertyNode propertyNode = new PropertyNode(((PropertyView)getProperties().getView(type)).getShapeType());
+					Rectangle2D bounds = getBounds();
+					double x = bounds.getCenterX() - bounds.getWidth()/2 - PROP_NODE_DIST;
+					double y = bounds.getCenterX() - PROP_NODE_DIST * i;
+					propertyNode.translate(x, y);
+					propertyNodes.add(propertyNode);
+				}
+			}
+			/* set the text on all the property nodes */
+			int i = 0;
+			for(String text : propertyValues){
+				NodeProperties.Modifiers modifiers = getProperties().getModifiers(type);
+				Set<Integer> viewIndexes = modifiers.getIndexes(i);
+				ModifierView[] views = new ModifierView[viewIndexes.size()]; 
+				int j =0;
+				for(Integer I : viewIndexes){
+					views[j] = (ModifierView) getProperties().getModifiers(type).getView(modifiers.getTypes().get(I));
+					j++;
+				}
+				propertyNodes.get(i).setText(text,views);
+				i++;
+			}
+		}
+	}
+	
+	protected void reshapeInnerProperties(List<String> insidePropertyTypes){
+		/* set the bounds for each multiline string and the resulting bound of the node */
+		nameLabel = new MultiLineString();
+		nameLabel.setText(getName().isEmpty() ? " " : getName());
+		nameLabel.setBold(true);		
+		Rectangle2D r = nameLabel.getBounds();
+		
+		for(int i=0; i<insidePropertyTypes.size();i++){
+			propertyLabels[i] = new MultiLineString();
+			String propertyType = insidePropertyTypes.get(i);
+	    	if(getProperties().getValues(propertyType).size() == 0){
+	    		propertyLabels[i].setText(" ");
+	    	}else{
+	    		propertyLabels[i].setJustification(MultiLineString.LEFT);
+	    		String[] array = new String[getProperties().getValues(propertyType).size()];
+	    		propertyLabels[i].setText(getProperties().getValues(propertyType).toArray(array), getProperties().getModifiers(propertyType));
+	    	}
+	    	r.add(new Rectangle2D.Double(r.getX(),r.getMaxY(),propertyLabels[i].getBounds().getWidth(),propertyLabels[i].getBounds().getHeight()));
+    	}
+		/* set a gap to uniformly distribute the extra space among property rectangles to reach the minimum bound's height */
+		boundsGap = 0;
+		Rectangle2D.Double minBounds = (Rectangle2D.Double)getMinBounds();
+		if(r.getHeight() < minBounds.height){
+			boundsGap = minBounds.height - r.getHeight();
+			boundsGap /= insidePropertyTypes.size();
+		}
+		r.add(minBounds); //make sure it's at least as big as the minimum bounds 
+		dataDisplayBounds.setFrame(new Rectangle2D.Double(dataDisplayBounds.x,dataDisplayBounds.y,r.getWidth(),r.getHeight()));
+	}
+	
+	/**
+	  Draw the node from the Shape with shadow
+	  @param g2 the graphics context
+	*/
+	@Override
+	public void draw(Graphics2D g2){
+		/* draw the external shape */
+		Shape shape = getShape();
+			if (shape == null) return;
+		Color oldColor = g2.getColor();
+		g2.translate(SHADOW_GAP, SHADOW_GAP);      
+		g2.setColor(SHADOW_COLOR);
+		g2.fill(shape);
+		g2.translate(-SHADOW_GAP, -SHADOW_GAP);
+		g2.setColor(g2.getBackground());
+		g2.fill(shape);
+		g2.setColor(Color.BLACK);
+		g2.draw(shape);      
+		g2.setColor(oldColor);
+	
+		/* if there ain't any property to display inside, then display the name in the middle of the data Display bounds */
+		if(!anyInsideProperties()){
+	    	nameLabel.draw(g2, dataDisplayBounds);
+		}else{
+	    	/* draw name */
+	    	Rectangle2D currentBounds = new Rectangle2D.Double(
+	    			dataDisplayBounds.x,
+	    			dataDisplayBounds.y,
+	    			dataDisplayBounds.getWidth(),
+	    			nameLabel.getBounds().getHeight());
+	    	if(drawPropertySeparators){
+	    		Shape oldClip = g2.getClip();
+	    		g2.setClip(getShape());
+	    		g2.draw(new Rectangle2D.Double(
+	    				getBounds().getX(),
+		    			dataDisplayBounds.y,
+		    			getBounds().getWidth(),
+		    			nameLabel.getBounds().getHeight())
+	    				);
+	    		g2.setClip(oldClip);
+	    	}
+	    	nameLabel.draw(g2, currentBounds);
+	    	
+	    	/* draw internal properties */
+	    	Rectangle2D previousBounds;
+	    	for(int i=0;i<propertyLabels.length;i++){
+	    		previousBounds = currentBounds;
+	    		currentBounds = new Rectangle2D.Double(
+	    				previousBounds.getX(),
+	    				previousBounds.getMaxY(),
+	    				dataDisplayBounds.getWidth(),
+	    				propertyLabels[i].getBounds().getHeight()+boundsGap);
+	    		if(drawPropertySeparators){
+		    		Shape oldClip = g2.getClip();
+		    		g2.setClip(getShape());
+	    			g2.draw(new Rectangle2D.Double(
+		    				getBounds().getX(),
+			    			currentBounds.getY(),
+			    			getBounds().getWidth(),
+			    			currentBounds.getHeight())
+		    				);
+	    			g2.setClip(oldClip);
+	    		}
+	    		propertyLabels[i].draw(g2, currentBounds);
+	    	}
+	    }
+	    	
+		/* draw external properties */
+		for(List<PropertyNode> pnList : propertyNodesMap.values())
+			for(PropertyNode pn : pnList){
+				pn.draw(g2);
+				Direction d = new Direction( getBounds().getCenterX() - pn.getCenter().getX(), getBounds().getCenterY() - pn.getCenter().getY());
+				g2.draw(new Line2D.Double(pn.getConnectionPoint(d), getConnectionPoint(d.turn(180))));
+			}
+		/* draw visual cue for bookmarks and notes */
+		super.draw(g2);
+	}
+	
+	protected Rectangle2D getMinBounds(){
+		return (Rectangle2D)minBounds.clone();
+	}
+	
+	public abstract  ShapeType getShapeType();
+
+	@Override
+	public void encode(Document doc, Element parent){
+		super.encode(doc, parent);
+		if(getProperties().isEmpty())
+			 return;
+		NodeList propTagList = doc.getElementsByTagName(PersistenceManager.PROPERTY);
+		
+		/* scan all the PROPERTY tags to add the position tag */
+		for(int i = 0 ; i< propTagList.getLength(); i++){
+			Element propertyTag = (Element)propTagList.item(i);
+			Element typeTag = (Element)propertyTag.getElementsByTagName(PersistenceManager.TYPE).item(0);
+			String type = typeTag.getTextContent();
+			
+			/* a property of another node, continue */
+			if(!getProperties().getTypes().contains(type))
+				continue;
+			
+			if(((PropertyView)getProperties().getView(type)).getPosition() == SimpleShapeNode.Position.Inside)
+				continue;
+			
+			List<String> values = getProperties().getValues(type);
+			if(values.isEmpty())
+				continue;
+			
+			NodeList elementTagList = propertyTag.getElementsByTagName(PersistenceManager.ELEMENT);
+			List<PropertyNode> pnList = propertyNodesMap.get(type);
+			for(int j=0; j<elementTagList.getLength();j++){
+				Element elementTag = (Element)elementTagList.item(j);
+				Element positionTag = doc.createElement(SimpleShapePrototypePersistenceDelegate.POSITION);	
+				positionTag.setAttribute(PersistenceManager.X, String.valueOf(pnList.get(j).getX()));
+				positionTag.setAttribute(PersistenceManager.Y, String.valueOf(pnList.get(j).getY()));
+				elementTag.appendChild(positionTag);
+			}
+		}
+	}
+	
+	@Override
+	public void decode(Document doc, Element nodeTag) throws IOException{
+		super.decode(doc, nodeTag);
+		
+		NodeList propTagList = nodeTag.getElementsByTagName(PersistenceManager.PROPERTY);
+
+		/* split the property types into internal and external, properties have been set by super.decodeXMLInstance  */
+		ArrayList<String> insidePropertyTypes = new ArrayList<String>(getProperties().getTypes().size());
+		ArrayList<String> outsidePropertyTypes = new ArrayList<String>(getProperties().getTypes().size());
+		for(String type : getProperties().getTypes()){
+			if(((PropertyView)getProperties().getView(type)).getPosition() == SimpleShapeNode.Position.Inside)
+				insidePropertyTypes.add(type);
+			else
+				outsidePropertyTypes.add(type);
+		}
+		
+		/* set the multi-line string bounds for the properties which are displayed internally */		
+		reshapeInnerProperties(insidePropertyTypes);
+		
+		/* scan all the PROPERTY tags to decode the position tag of the properties which are displayed externally */
+		for(int i = 0 ; i< propTagList.getLength(); i++){
+			Element propertyTag = (Element)propTagList.item(i);
+			if(propertyTag.getElementsByTagName(PersistenceManager.TYPE).item(0) == null)
+				throw new IOException();
+			Element typeTag = (Element)propertyTag.getElementsByTagName(PersistenceManager.TYPE).item(0);
+
+			String type = typeTag.getTextContent();
+			/* (check on whether type exists in the node type definition is done in super.decode */
+			
+			if(((PropertyView)getProperties().getView(type)).getPosition() == SimpleShapeNode.Position.Inside)
+				continue;
+			/* this will create external nodes and assign them their position */
+			NodeList elementTagList = propertyTag.getElementsByTagName(PersistenceManager.ELEMENT);
+			List<PropertyNode> pnList = new LinkedList<PropertyNode>();
+			for(int j=0; j<elementTagList.getLength();j++){
+				Element elementTag  = (Element)elementTagList.item(j);
+				if(elementTag.getElementsByTagName(SimpleShapePrototypePersistenceDelegate.POSITION).item(0) == null ||
+						elementTag.getElementsByTagName(PersistenceManager.VALUE).item(0) == null)
+					throw new IOException();
+				Element positionTag = (Element)elementTag.getElementsByTagName(SimpleShapePrototypePersistenceDelegate.POSITION).item(0);				
+				Element valueTag = (Element)elementTag.getElementsByTagName(PersistenceManager.VALUE).item(0);
+				double dx,dy;
+				try{
+					dx = Double.parseDouble(positionTag.getAttribute(PersistenceManager.X));
+					dy = Double.parseDouble(positionTag.getAttribute(PersistenceManager.Y));
+				}catch(NumberFormatException nfe){
+					throw new IOException();
+				}
+				PropertyNode pn = new PropertyNode(((PropertyView)getProperties().getView(type)).getShapeType());
+				pn.translate(dx, dy);
+				pn.setText(valueTag.getTextContent(),null);
+				pnList.add(pn);
+			}
+			propertyNodesMap.put(type, pnList);
+			/* this will apply the modifier format to the properties */
+			reshapeOuterProperties(outsidePropertyTypes);
+		}
+	}
+	
+	@Override
+	protected void translateImplementation(Point2D p, double dx, double dy){
+		dataDisplayBounds.setFrame(dataDisplayBounds.getX() + dx,
+				dataDisplayBounds.getY() + dy, 
+				dataDisplayBounds.getWidth(), 
+				dataDisplayBounds.getHeight());
+		/* translate all the external property nodes  */
+		for(List<PropertyNode> pnList : propertyNodesMap.values())
+			for(PropertyNode pn : pnList)
+				pn.translate(dx, dy);
+	}
+	
+	@Override
+	public Object clone(){
+		SimpleShapeNode n = (SimpleShapeNode)super.clone();
+		n.propertyLabels = new MultiLineString[propertyLabels.length];
+		n.nameLabel = new MultiLineString();
+		n.propertyNodesMap = new LinkedHashMap<String, List<PropertyNode>>();
+		for(String s : propertyNodesMap.keySet())
+			n.propertyNodesMap.put(s, new LinkedList<PropertyNode>());
+		n.dataDisplayBounds = (Rectangle2D.Double)dataDisplayBounds.clone();
+		return n;
+	}
+	
+	protected boolean anyInsideProperties(){
+		boolean propInside = false;
+		for(String type : getProperties().getTypes()){
+	    	if(((PropertyView)getProperties().getView(type)).getPosition() == Position.Inside){
+	    		if(!getProperties().getValues(type).isEmpty()){
+	    			propInside = true;
+	    			break;
+	    		}
+	    	}
+	    }
+		return propInside;
+	}
+	
+	protected Rectangle2D.Double dataDisplayBounds;
+	protected double boundsGap; 
+	protected boolean drawPropertySeparators = true;
+	protected MultiLineString[] propertyLabels;
+	protected MultiLineString nameLabel;
+	protected Map<String,List<PropertyNode>> propertyNodesMap;
+	
+	public static enum ShapeType  {Circle, Ellipse, Rectangle, Square, Triangle, Transparent};
+	public static enum Position  {Inside, Outside};
+	
+	private static final int DEFAULT_WIDTH = 100;
+    private static final int DEFAULT_HEIGHT = 60;
+	private static final Rectangle2D.Double minBounds = new Rectangle2D.Double(0,0,DEFAULT_WIDTH,DEFAULT_HEIGHT);
+	private static final int PROP_NODE_DIST = 50;
+	
+	/**
+	 * When properties are configure to appear outside, the values are represented as small nodes 
+	 * connected (with a straight line) to the node they belong to. This class represents such 
+	 * small nodes. The possible shapes are: triangle, rectangle, square, circle, ellipse and no shape in 
+	 * which case only the string with the property value and the line connecting it to the node is shown. 
+	 *
+	 */
+	protected static class PropertyNode{
+		public PropertyNode(ShapeType aShape){
+			/* add a little padding in the shape holding the label */
+			label = new MultiLineString(){
+				 public Rectangle2D getBounds(){
+					 Rectangle2D bounds = super.getBounds();
+					 if(bounds.getWidth() != 0 || bounds.getHeight() != 0){
+						 bounds.setFrame(
+								 bounds.getX(),
+								 bounds.getY(), 
+								 bounds.getWidth() + PADDING, 
+								 bounds.getHeight() + PADDING);
+					 }
+					 return bounds;
+				 }
+			};
+			label.setJustification(MultiLineString.CENTER);
+			shapeType = aShape;
+			shape  = label.getBounds();
+		}
+		
+		public void setText(String text, ModifierView[] views){
+			label.setText(text,views);
+			
+			switch(shapeType){
+			case Circle :
+				Rectangle2D circleBounds = EllipticalNode.getOutBounds(label.getBounds());	
+				shape = new Ellipse2D.Double(
+						circleBounds.getX(),
+						circleBounds.getY(),
+						Math.max(circleBounds.getWidth(),circleBounds.getHeight()),
+						Math.max(circleBounds.getWidth(),circleBounds.getHeight())
+					);
+				break;
+			case Ellipse :
+				Rectangle2D ellipseBounds = EllipticalNode.getOutBounds(label.getBounds());
+				shape = new Ellipse2D.Double(
+							ellipseBounds.getX(),
+							ellipseBounds.getY(),
+							ellipseBounds.getWidth(),
+							ellipseBounds.getHeight()
+						);
+				break;
+			case Triangle : 
+				shape = TriangularNode.getOutShape(label.getBounds());
+				break;
+			default : // Rectangle, Square and Transparent 
+				shape = label.getBounds();;
+				break;
+			}
+			
+			/* a new shape, placed at (0,0) has been created as a result of set text, therefore  *
+			 * we must put it back where the old shape was, since the translation is performed   * 
+			 * by adding the translate args to x and y, x and y must first be set to 0 			 */
+			double currentX = x;
+			double currentY = y;
+			x = 0;
+			y = 0;
+			translate(currentX,currentY);
+		}
+		
+		public void draw(Graphics2D g){
+			Color oldColor = g.getColor();
+			if(shapeType != ShapeType.Transparent){
+				g.translate(SHADOW_GAP, SHADOW_GAP);      
+				g.setColor(SHADOW_COLOR);
+				g.fill(shape);
+				g.translate(-SHADOW_GAP, -SHADOW_GAP);
+				
+				g.setColor(g.getBackground());
+				g.fill(shape);
+				g.setColor(Color.BLACK);
+				g.draw(shape);
+			}
+			
+			label.draw(g, shape.getBounds2D());		
+			g.setColor(oldColor);
+		}
+		
+		public void translate(double dx, double dy){
+			x += dx;
+			y += dy;
+			
+			if(shape instanceof Path2D){ //it's a triangle
+				Rectangle2D labelBounds = label.getBounds();
+				labelBounds.setFrame(
+						x,
+						y,
+						labelBounds.getWidth(),
+						labelBounds.getHeight()
+						);
+				shape = TriangularNode.getOutShape(labelBounds);
+			}else{
+				Rectangle2D bounds = shape.getBounds2D();
+				((RectangularShape)shape).setFrame(
+						x,
+						y,
+						bounds.getWidth(),
+						bounds.getHeight()
+				);
+			}
+		}
+		
+		public Point2D getConnectionPoint(Direction d) {
+			switch(shapeType){
+			case Circle :
+			case Ellipse :
+				return EllipticalNode.calculateConnectionPoint(d, shape.getBounds2D());
+			case Triangle :
+				return TriangularNode.calculateConnectionPoint(d, shape.getBounds2D());
+			default :
+				return RectangularNode.calculateConnectionPoint(d, shape.getBounds2D());
+			}
+		}
+		
+		public boolean contains(Point2D p){
+			return shape.contains(p);
+		}
+		
+		public Point2D getCenter(){
+			Rectangle2D bounds =  shape.getBounds2D() ;
+			return new Point2D.Double(bounds.getCenterX(), bounds.getCenterY());
+		}
+		
+		double getX(){
+			return x;
+		}
+		
+		double getY(){
+			return y;
+		}
+		
+		private static final int PADDING = 5;
+		private MultiLineString label;
+		private ShapeType shapeType;
+		private Shape shape;
+		private double x;
+		private double y;
+	}
+}