From c4f926438dcb8abe805e910399940f79ff643c4b Mon Sep 17 00:00:00 2001
From: ziejd2
Date: Thu, 14 Sep 2017 15:39:41 -0500
Subject: Add files via upload
---
sourcecodes/js-graph-it.js | 1802 ++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 1802 insertions(+)
create mode 100644 sourcecodes/js-graph-it.js
(limited to 'sourcecodes/js-graph-it.js')
diff --git a/sourcecodes/js-graph-it.js b/sourcecodes/js-graph-it.js
new file mode 100644
index 00000000..8d9387b4
--- /dev/null
+++ b/sourcecodes/js-graph-it.js
@@ -0,0 +1,1802 @@
+/*********************
+ * browser detection *
+ *********************/
+
+var ie=document.all;
+var nn6=document.getElementById&&!document.all;
+
+/****************************************************
+ * This class is a scanner for the visitor pattern. *
+ ****************************************************/
+
+/**
+ * Constructor, parameters are:
+ * visitor: the visitor implementation, it must be a class with a visit(element) method.
+ * scanElementsOnly: a flag telling whether to scan html elements only or all html nodes.
+ */
+function DocumentScanner(visitor, scanElementsOnly)
+{
+ this.visitor = visitor;
+ this.scanElementsOnly = scanElementsOnly;
+
+ /**
+ * Scans the element
+ */
+ this.scan = function(element)
+ {
+ var i;
+ if(this.visitor.visit(element))
+ {
+ // visit child elements
+ var children = element.childNodes;
+ for(i = 0; i < children.length; i++)
+ {
+ if(!this.scanElementsOnly || children[i].nodeType == 1)
+ {
+ this.scan(children[i]);
+ }
+ }
+ }
+ }
+}
+
+/*****************
+ * drag and drop *
+ *****************/
+
+var isdrag=false; // this flag indicates that the mouse movement is actually a drag.
+var mouseStartX, mouseStartY; // mouse position when drag starts
+var elementStartX, elementStartY; // element position when drag starts
+
+/**
+ * the html element being dragged.
+ */
+var elementToMove;
+
+/**
+ * an array containing the blocks being dragged. This is used to notify them of move.
+ */
+var blocksToMove;
+
+/**
+ * this variable stores the orginal z-index of the object being dragged in order
+ * to restore it upon drop.
+ */
+var originalZIndex;
+
+/**
+ * an array containing bounds to be respected while dragging elements,
+ * these bounds are left, top, left + width, top + height of the parent element.
+ */
+var bounds = new Array(4);
+
+/**
+ * this visitor is used to find blocks nested in the element being moved.
+ */
+function BlocksToMoveVisitor()
+{
+ this.visit = function(element)
+ {
+ if(isBlock(element))
+ {
+ blocksToMove.push(findBlock(element.id));
+ return false;
+ }
+ else
+ return true;
+ }
+}
+
+var blocksToMoveScanner = new DocumentScanner(new BlocksToMoveVisitor(), true);
+
+function movemouse(e)
+{
+ if (isdrag)
+ {
+ var currentMouseX = nn6 ? e.clientX : event.clientX;
+ var currentMouseY = nn6 ? e.clientY : event.clientY;
+ var newElementX = elementStartX + currentMouseX - mouseStartX;
+ var newElementY = elementStartY + currentMouseY - mouseStartY;
+
+ // check bounds
+ // note: the "-1" and "+1" is to avoid borders overlap
+ if(newElementX < bounds[0])
+ newElementX = bounds[0] + 1;
+ if(newElementX + elementToMove.offsetWidth > bounds[2])
+ newElementX = bounds[2] - elementToMove.offsetWidth - 1;
+ if(newElementY < bounds[1])
+ newElementY = bounds[1] + 1;
+ if(newElementY + elementToMove.offsetHeight > bounds[3])
+ newElementY = bounds[3] - elementToMove.offsetHeight - 1;
+
+ // move element
+ elementToMove.style.left = newElementX + 'px';
+ elementToMove.style.top = newElementY + 'px';
+
+// elementToMove.style.left = newElementX / elementToMove.parentNode.offsetWidth * 100 + '%';
+// elementToMove.style.top = newElementY / elementToMove.parentNode.offsetHeight * 100 + '%';
+
+ elementToMove.style.right = null;
+ elementToMove.style.bottom = null;
+
+ var i;
+ for(i = 0; i < blocksToMove.length; i++)
+ {
+ blocksToMove[i].onMove();
+ }
+ return false;
+ }
+}
+
+/**
+ * finds the innermost draggable element starting from the one that generated the event "e"
+ * (i.e.: the html element under mouse pointer), then setup the document's onmousemove function to
+ * move the element around.
+ */
+function startDrag(e)
+{
+ var eventSource = nn6 ? e.target : event.srcElement;
+ if(eventSource.tagName == 'HTML')
+ return;
+
+ while (eventSource != document.body && !hasClass(eventSource, "draggable"))
+ {
+ eventSource = nn6 ? eventSource.parentNode : eventSource.parentElement;
+ }
+
+ // if a draggable element was found, calculate its actual position
+ if (hasClass(eventSource, "draggable"))
+ {
+ isdrag = true;
+ elementToMove = eventSource;
+
+ // set absolute positioning on the element
+ elementToMove.style.position = "absolute";
+
+ // calculate start point
+ elementStartX = elementToMove.offsetLeft;
+ elementStartY = elementToMove.offsetTop;
+
+ // calculate mouse start point
+ mouseStartX = nn6 ? e.clientX : event.clientX;
+ mouseStartY = nn6 ? e.clientY : event.clientY;
+
+ // calculate bounds as left, top, width, height of the parent element
+ if(getStyle(elementToMove.parentNode, "position") == 'absolute')
+ {
+ bounds[0] = 0;
+ bounds[1] = 0;
+ }
+ else
+ {
+ bounds[0] = calculateOffsetLeft(elementToMove.parentNode);
+ bounds[1] = calculateOffsetTop(elementToMove.parentNode);
+ }
+ bounds[2] = bounds[0] + elementToMove.parentNode.offsetWidth;
+ bounds[3] = bounds[1] + elementToMove.parentNode.offsetHeight;
+
+ // either find the block related to the dragging element to call its onMove method
+ blocksToMove = new Array();
+
+ blocksToMoveScanner.scan(eventSource);
+ document.onmousemove = movemouse;
+
+ originalZIndex = getStyle(elementToMove, "z-index");
+ elementToMove.style.zIndex = "3";
+
+ return false;
+ }
+}
+
+function stopDrag(e)
+{
+ isdrag=false;
+ if(elementToMove)
+ elementToMove.style.zIndex=originalZIndex;
+ elementToMove = null;
+ document.onmousemove = null;
+
+}
+
+document.onmousedown = startDrag;
+document.onmouseup = stopDrag;
+
+
+
+/*************
+ * Constants *
+ *************/
+var LEFT = 1;
+var RIGHT = 2;
+var UP = 4;
+var DOWN = 8;
+var HORIZONTAL = LEFT + RIGHT;
+var VERTICAL = UP + DOWN;
+var AUTO = HORIZONTAL + VERTICAL;
+
+var START = 0;
+var END = 1;
+var SCROLLBARS_WIDTH = 18;
+
+/**************
+ * Inspectors *
+ **************/
+
+var inspectors = new Array();
+
+/**
+ * The canvas class.
+ * This class is built on a div html element.
+ */
+function Canvas(htmlElement)
+{
+ /*
+ * initialization
+ */
+ this.id = htmlElement.id;
+ this.htmlElement = htmlElement;
+ this.blocks = new Array();
+ this.connectors = new Array();
+ this.offsetLeft = calculateOffsetLeft(this.htmlElement);
+ this.offsetTop = calculateOffsetTop(this.htmlElement);
+
+ this.width;
+ this.height;
+
+ // create the inner div element
+ this.innerDiv = document.createElement("div");
+
+ this.initCanvas = function()
+ {
+ // setup the inner div
+ var children = this.htmlElement.childNodes;
+ var i;
+ var el;
+ var n = children.length;
+ for(i = 0; i < n; i++)
+ {
+ el = children[0];
+ this.htmlElement.removeChild(el);
+ this.innerDiv.appendChild(el);
+ if(el.style)
+ el.style.zIndex = "2";
+ }
+ this.htmlElement.appendChild(this.innerDiv);
+
+ this.htmlElement.style.overflow = "auto";
+ this.htmlElement.style.position = "relative";
+ this.innerDiv.id = this.id + "_innerDiv";
+ this.innerDiv.style.border = "none";
+ this.innerDiv.style.padding = "0px";
+ this.innerDiv.style.margin = "0px";
+ this.innerDiv.style.position = "absolute";
+ this.innerDiv.style.top = "0px";
+ this.innerDiv.style.left = "0px";
+ this.width = 0;
+ this.height = 0;
+ this.offsetLeft = calculateOffsetLeft(this.innerDiv);
+ this.offsetTop = calculateOffsetTop(this.innerDiv);
+
+ // inspect canvas children to identify first level blocks
+ new DocumentScanner(this, true).scan(this.htmlElement);
+
+ // now this.width and this.height are populated with minimum values needed for the inner
+ // blocks to fit, add 2 to avoid border overlap;
+ this.height += 2;
+ this.width += 2;
+
+ var visibleWidth = this.htmlElement.offsetWidth - 2; // - 2 is to avoid border overlap
+ var visibleHeight = this.htmlElement.offsetHeight - 2; // - 2 is to avoid border overlap
+
+ // consider the scrollbars width calculating the inner div size
+ if(this.height > visibleHeight)
+ visibleWidth -= SCROLLBARS_WIDTH;
+ if(this.width > visibleWidth)
+ visibleHeight -= SCROLLBARS_WIDTH;
+
+ this.height = Math.max(this.height, visibleHeight);
+ this.width = Math.max(this.width, visibleWidth);
+
+ this.innerDiv.style.width = this.width + "px";
+ this.innerDiv.style.height = this.height + "px";
+
+ // init connectors
+ for(i = 0; i < this.connectors.length; i++)
+ {
+ this.connectors[i].initConnector();
+ }
+ }
+
+ this.visit = function(element)
+ {
+ if(element == this.htmlElement)
+ return true;
+
+ // check the element dimensions against the acutal size of the canvas
+ this.width = Math.max(this.width, calculateOffsetLeft(element) - this.offsetLeft + element.offsetWidth);
+ this.height = Math.max(this.height, calculateOffsetTop(element) - this.offsetTop + element.offsetHeight);
+
+ if(isBlock(element))
+ {
+ // block found initialize it
+ var newBlock = new Block(element, this);
+ newBlock.initBlock();
+ this.blocks.push(newBlock);
+ return false;
+ }
+ else if(isConnector(element))
+ {
+ // connector found, just create it, source or destination blocks may not
+ // have been initialized yet
+ var newConnector = new Connector(element, this);
+ this.connectors.push(newConnector);
+ return false;
+ }
+ else
+ {
+ // continue searching nested elements
+ return true;
+ }
+ }
+
+ /*
+ * methods
+ */
+ this.print = function()
+ {
+ var output = '
';
+ var i;
+ for(i = 0; i < this.blocks.length; i++)
+ {
+ output += '- ';
+ output += this.blocks[i].print();
+ output += '
';
+ }
+ output += '
';
+ return output;
+ }
+
+ /*
+ * This function searches for a nested block with a given id
+ */
+ this.findBlock = function(blockId)
+ {
+ var result;
+ var i;
+ for(i = 0; i < this.blocks.length && !result; i++)
+ {
+ result = this.blocks[i].findBlock(blockId);
+ }
+
+ return result;
+ }
+
+ this.toString = function()
+ {
+ return 'canvas: ' + this.id;
+ }
+}
+
+/*
+ * Block class
+ */
+function Block(htmlElement, canvas)
+{
+ /*
+ * initialization
+ */
+
+ this.canvas = canvas;
+ this.htmlElement = htmlElement;
+ this.id = htmlElement.id;
+ this.blocks = new Array();
+ this.moveListeners = new Array();
+
+ if(this.id == 'description2_out1')
+ var merda = 0;
+ this.currentTop = calculateOffsetTop(this.htmlElement) - this.canvas.offsetTop;
+ this.currentLeft = calculateOffsetLeft(this.htmlElement) - this.canvas.offsetLeft;
+
+ this.visit = function(element)
+ {
+ if(element == this.htmlElement)
+ {
+ // exclude itself
+ return true;
+ }
+
+ if(isBlock(element))
+ {
+ var innerBlock = new Block(element, this.canvas);
+ innerBlock.initBlock();
+ this.blocks.push(innerBlock);
+ this.moveListeners.push(innerBlock);
+ return false;
+ }
+ else
+ return true;
+ }
+
+ this.initBlock = function()
+ {
+ // inspect block children to identify nested blocks
+
+ new DocumentScanner(this, true).scan(this.htmlElement);
+ }
+
+ this.top = function()
+ {
+ return this.currentTop;
+ }
+
+ this.left = function()
+ {
+ return this.currentLeft;
+ }
+
+ this.width = function()
+ {
+ return this.htmlElement.offsetWidth;
+ }
+
+ this.height = function()
+ {
+ return this.htmlElement.offsetHeight;
+ }
+
+ /*
+ * methods
+ */
+ this.print = function()
+ {
+ var output = 'block: ' + this.id;
+ if(this.blocks.length > 0)
+ {
+ output += '';
+ var i;
+ for(i = 0; i < this.blocks.length; i++)
+ {
+ output += '- ';
+ output += this.blocks[i].print();
+ output += '
';
+ }
+ output += '
';
+ }
+ return output;
+ }
+
+ /*
+ * This function searches for a nested block (or the block itself) with a given id
+ */
+ this.findBlock = function(blockId)
+ {
+ if(this.id == blockId)
+ return this;
+
+ var result;
+ var i;
+ for(i = 0; i < this.blocks.length && !result; i++)
+ {
+ result = this.blocks[i].findBlock(blockId);
+ }
+
+ return result;
+ }
+
+ this.move = function(left, top)
+ {
+ this.htmlElement.style.left = left;
+ this.htmlElement.style.top = top;
+
+ this.onMove();
+ }
+
+ this.onMove = function()
+ {
+ var i;
+ this.currentLeft = calculateOffsetLeft(this.htmlElement) - this.canvas.offsetLeft;
+ this.currentTop = calculateOffsetTop(this.htmlElement) - this.canvas.offsetTop;
+ // notify listeners
+ for(i = 0; i < this.moveListeners.length; i++)
+ {
+ this.moveListeners[i].onMove();
+ }
+ }
+
+ this.toString = function()
+ {
+ return 'block: ' + this.id;
+ }
+}
+
+/**
+ * This class represents a connector segment, it is drawn via a div element.
+ * A segment has a starting point defined by the properties startX and startY, a length,
+ * a thickness and an orientation.
+ * Allowed values for the orientation property are defined by the constants UP, LEFT, DOWN and RIGHT.
+ */
+function Segment(id, parentElement)
+{
+ this.id = id;
+ this.htmlElement = document.createElement('div');
+ this.htmlElement.id = id;
+ this.htmlElement.style.position = 'absolute';
+ this.htmlElement.style.overflow = 'hidden';
+ parentElement.appendChild(this.htmlElement);
+
+ this.startX;
+ this.startY;
+ this.length;
+ this.thickness;
+ this.orientation;
+ this.nextSegment;
+ this.visible = true;
+
+ /**
+ * draw the segment. This operation is cascaded to next segment if any.
+ */
+ this.draw = function()
+ {
+ // set properties to next segment
+ if(this.nextSegment)
+ {
+ this.nextSegment.startX = this.getEndX();
+ this.nextSegment.startY = this.getEndY();
+ }
+
+ if(this.visible)
+ this.htmlElement.style.display = 'block';
+ else
+ this.htmlElement.style.display = 'none';
+
+ switch(this.orientation)
+ {
+ case LEFT:
+ this.htmlElement.style.left = (this.startX - this.length) + "px";
+ this.htmlElement.style.top = this.startY + "px";
+ this.htmlElement.style.width = this.length + "px";
+ this.htmlElement.style.height = this.thickness + "px";
+ break;
+ case RIGHT:
+ this.htmlElement.style.left = this.startX + "px";
+ this.htmlElement.style.top = this.startY + "px";
+ if(this.nextSegment)
+ this.htmlElement.style.width = this.length + this.thickness + "px";
+ else
+ this.htmlElement.style.width = this.length + "px";
+ this.htmlElement.style.height = this.thickness + "px";
+ break;
+ case UP:
+ this.htmlElement.style.left = this.startX + "px";
+ this.htmlElement.style.top = (this.startY - this.length) + "px";
+ this.htmlElement.style.width = this.thickness + "px";
+ this.htmlElement.style.height = this.length + "px";
+ break;
+ case DOWN:
+ this.htmlElement.style.left = this.startX + "px";
+ this.htmlElement.style.top = this.startY + "px";
+ this.htmlElement.style.width = this.thickness + "px";
+ if(this.nextSegment)
+ this.htmlElement.style.height = this.length + this.thickness + "px";
+ else
+ this.htmlElement.style.height = this.length + "px";
+ break;
+ }
+
+ if(this.nextSegment)
+ this.nextSegment.draw();
+ }
+
+ /**
+ * Returns the "left" coordinate of the end point of this segment
+ */
+ this.getEndX = function()
+ {
+ switch(this.orientation)
+ {
+ case LEFT: return this.startX - this.length;
+ case RIGHT: return this.startX + this.length;
+ case DOWN: return this.startX;
+ case UP: return this.startX;
+ }
+ }
+
+ /**
+ * Returns the "top" coordinate of the end point of this segment
+ */
+ this.getEndY = function()
+ {
+ switch(this.orientation)
+ {
+ case LEFT: return this.startY;
+ case RIGHT: return this.startY;
+ case DOWN: return this.startY + this.length;
+ case UP: return this.startY - this.length;
+ }
+ }
+
+ /**
+ * Append another segment to the end point of this.
+ * If another segment is already appended to this, cascades the operation so
+ * the given next segment will be appended to the tail of the segments chain.
+ */
+ this.append = function(nextSegment)
+ {
+ if(!nextSegment)
+ return;
+ if(!this.nextSegment)
+ {
+ this.nextSegment = nextSegment;
+ this.nextSegment.startX = this.getEndX();
+ this.nextSegment.startY = this.getEndY();
+ }
+ else
+ this.nextSegment.append(nextSegment);
+ }
+
+ this.detach = function()
+ {
+ var s = this.nextSegment;
+ this.nextSegment = null;
+ return s;
+ }
+
+ /**
+ * hides this segment and all the following
+ */
+ this.cascadeHide = function()
+ {
+ this.visible = false;
+ if(this.nextSegment)
+ this.nextSegment.cascadeHide();
+ }
+}
+/**
+ * Connector class.
+ * The init function takes two Block objects as arguments representing
+ * the source and destination of the connector
+ */
+function Connector(htmlElement, canvas)
+{
+ /**
+ * declaring html element
+ */
+ this.htmlElement = htmlElement;
+
+ /**
+ * the canvas this connector is in
+ */
+ this.canvas = canvas;
+
+ /**
+ * the source block
+ */
+ this.source = null;
+
+ /**
+ * the destination block
+ */
+ this.destination = null;
+
+ /**
+ * preferred orientation
+ */
+ this.preferredSourceOrientation = AUTO;
+ this.preferredDestinationOrientation = AUTO;
+
+ /**
+ * css class to be applied to the connector's segments
+ */
+ this.connectorClass;
+
+ /**
+ * minimum length for a connector segment.
+ */
+ this.minSegmentLength = 10;
+
+ /**
+ * size of the connector, i.e.: thickness of the segments.
+ */
+ this.size = 2;
+
+ /**
+ * connector's color
+ */
+ this.color = 'black';
+
+ /**
+ * move listeners, they are notify when connector moves
+ */
+ this.moveListeners = new Array();
+
+ this.firstSegment;
+
+ this.segmentsPool;
+
+ this.segmentsNumber = 0;
+
+ this.strategy;
+
+ this.initConnector = function()
+ {
+ // detect the connector id
+ if(this.htmlElement.id)
+ this.id = this.htmlElement.id;
+ else
+ this.id = this.htmlElement.className;
+
+ // split the class name to get the ids of the source and destination blocks
+ var splitted = htmlElement.className.split(' ');
+ if(splitted.length < 3)
+ {
+ alert('Unable to create connector \'' + id + '\', class is not in the correct format: connector , ');
+ return;
+ }
+
+ this.connectorClass = splitted[0] + ' ' + splitted[1] + ' ' + splitted[2];
+
+ this.source = this.canvas.findBlock(splitted[1]);
+ if(!this.source)
+ {
+ alert('cannot find source block with id \'' + splitted[1] + '\'');
+ return;
+ }
+
+ this.destination = this.canvas.findBlock(splitted[2]);
+ if(!this.destination)
+ {
+ alert('cannot find destination block with id \'' + splitted[2] + '\'');
+ return;
+ }
+
+ // check preferred orientation
+ if(hasClass(this.htmlElement, 'vertical'))
+ {
+ this.preferredSourceOrientation = VERTICAL;
+ this.preferredDestinationOrientation = VERTICAL;
+ }
+ else if(hasClass(this.htmlElement, 'horizontal'))
+ {
+ this.preferredSourceOrientation = HORIZONTAL;
+ this.preferredDestinationOrientation = HORIZONTAL;
+ }
+ else
+ {
+ // check preferred orientation on source side
+ if(hasClass(this.htmlElement, 'vertical_start'))
+ this.preferredSourceOrientation = VERTICAL;
+ else if(hasClass(this.htmlElement, 'horizontal_start'))
+ this.preferredSourceOrientation = HORIZONTAL;
+ else if(hasClass(this.htmlElement, 'left_start'))
+ this.preferredSourceOrientation = LEFT;
+ else if(hasClass(this.htmlElement, 'right_start'))
+ this.preferredSourceOrientation = RIGHT;
+ else if(hasClass(this.htmlElement, 'up_start'))
+ this.preferredSourceOrientation = UP;
+ else if(hasClass(this.htmlElement, 'down_start'))
+ this.preferredSourceOrientation = DOWN;
+
+ // check preferred orientation on destination side
+ if(hasClass(this.htmlElement, 'vertical_end'))
+ this.preferredDestinationOrientation = VERTICAL;
+ else if(hasClass(this.htmlElement, 'horizontal_end'))
+ this.preferredDestinationOrientation = HORIZONTAL;
+ else if(hasClass(this.htmlElement, 'left_end'))
+ this.preferredDestinationOrientation = LEFT;
+ else if(hasClass(this.htmlElement, 'right_end'))
+ this.preferredDestinationOrientation = RIGHT;
+ else if(hasClass(this.htmlElement, 'up_end'))
+ this.preferredDestinationOrientation = UP;
+ else if(hasClass(this.htmlElement, 'down_end'))
+ this.preferredDestinationOrientation = DOWN;
+ }
+
+ // get the first strategy as default
+ this.strategy = strategies[0](this);
+ this.repaint();
+
+ this.source.moveListeners.push(this);
+ this.destination.moveListeners.push(this);
+
+ // call inspectors for this connector
+ var i;
+ for(i = 0; i < inspectors.length; i++)
+ {
+ inspectors[i].inspect(this);
+ }
+
+ // remove old html element
+ this.htmlElement.parentNode.removeChild(this.htmlElement);
+ }
+
+ this.getStartSegment = function()
+ {
+ return this.firstSegment;
+ }
+
+ this.getEndSegment = function()
+ {
+ var s = this.firstSegment;
+ while(s.nextSegment)
+ s = s.nextSegment;
+ return s;
+ }
+
+ this.getMiddleSegment = function()
+ {
+ if(!this.strategy)
+ return null;
+ else
+ return this.strategy.getMiddleSegment();
+ }
+
+ this.createSegment = function()
+ {
+ var segment;
+
+ // if the pool contains more objects, borrow the segment, create it otherwise
+ if(this.segmentsPool)
+ {
+ segment = this.segmentsPool;
+ this.segmentsPool = this.segmentsPool.detach();
+ }
+ else
+ {
+ segment = new Segment(this.id + "_" + (this.segmentsNumber + 1), this.canvas.htmlElement);
+ segment.htmlElement.className = this.connectorClass;
+ if(!getStyle(segment.htmlElement, 'background-color'))
+ segment.htmlElement.style.backgroundColor = this.color;
+ segment.thickness = this.size;
+ }
+ this.segmentsNumber++;
+
+ if(this.firstSegment)
+ this.firstSegment.append(segment);
+ else
+ this.firstSegment = segment;
+ segment.visible = true;
+ return segment;
+ }
+
+ /**
+ * Repaints the connector
+ */
+ this.repaint = function()
+ {
+ // check strategies fitness and choose the best fitting one
+ var i;
+ var maxFitness = 0;
+ var fitness;
+ var s;
+
+ // check if any strategy is possible with preferredOrientation
+ for(i = 0; i < strategies.length; i++)
+ {
+ this.clearSegments();
+
+ fitness = 0;
+ s = strategies[i](this);
+ if(s.isApplicable())
+ {
+ fitness++;
+ s.paint();
+ // check resulting orientation against the preferred orientations
+ if((this.firstSegment.orientation & this.preferredSourceOrientation) != 0)
+ fitness++;
+ if((this.getEndSegment().orientation & this.preferredDestinationOrientation) != 0)
+ fitness++;
+ }
+
+ if(fitness > maxFitness)
+ {
+ this.strategy = s;
+ maxFitness = fitness;
+ }
+ }
+
+ this.clearSegments();
+
+ this.strategy.paint();
+ this.firstSegment.draw();
+
+ // this is needed to actually hide unused html elements
+ if(this.segmentsPool)
+ this.segmentsPool.draw();
+ }
+
+ /**
+ * Hide all the segments and return them to pool
+ */
+ this.clearSegments = function()
+ {
+ if(this.firstSegment)
+ {
+ this.firstSegment.cascadeHide();
+ this.firstSegment.append(this.segmentsPool);
+ this.segmentsPool = this.firstSegment;
+ this.firstSegment = null;
+ }
+ }
+
+ this.onMove = function()
+ {
+ this.repaint();
+
+ // notify listeners
+ var i;
+ for(i = 0; i < this.moveListeners.length; i++)
+ this.moveListeners[i].onMove();
+ }
+}
+
+var strategies = new Array();
+
+function ConnectorEnd(htmlElement, connector, side)
+{
+ this.side = side;
+ this.htmlElement = htmlElement;
+ this.connector = connector;
+ connector.canvas.htmlElement.appendChild(htmlElement);
+ // strip extension
+ if(this.htmlElement.tagName.toLowerCase() == "img")
+ {
+ this.src = this.htmlElement.src.substring(0, this.htmlElement.src.lastIndexOf('.'));
+ this.srcExtension = this.htmlElement.src.substring(this.htmlElement.src.lastIndexOf('.'));
+ this.htmlElement.style.zIndex = getStyle(this.connector.htmlElement, "z-index");
+ }
+
+ this.orientation;
+
+ this.repaint = function()
+ {
+ this.htmlElement.style.position = 'absolute';
+
+ var left;
+ var top;
+ var segment;
+ var orientation;
+
+ if(this.side == START)
+ {
+ segment = connector.getStartSegment();
+ left = segment.startX;
+ top = segment.startY;
+ orientation = segment.orientation;
+ // swap orientation
+ if((orientation & VERTICAL) != 0)
+ orientation = (~orientation) & VERTICAL;
+ else
+ orientation = (~orientation) & HORIZONTAL;
+ }
+ else
+ {
+ segment = connector.getEndSegment();
+ left = segment.getEndX();
+ top = segment.getEndY();
+ orientation = segment.orientation;
+ }
+
+ switch(orientation)
+ {
+ case LEFT:
+ top -= (this.htmlElement.offsetHeight - segment.thickness) / 2;
+ break;
+ case RIGHT:
+ left -= this.htmlElement.offsetWidth;
+ top -= (this.htmlElement.offsetHeight - segment.thickness) / 2;
+ break;
+ case DOWN:
+ top -= this.htmlElement.offsetHeight;
+ left -= (this.htmlElement.offsetWidth - segment.thickness) / 2;
+ break;
+ case UP:
+ left -= (this.htmlElement.offsetWidth - segment.thickness) / 2;
+ break;
+ }
+
+ this.htmlElement.style.left = Math.ceil(left) + "px";
+ this.htmlElement.style.top = Math.ceil(top) + "px";
+
+ if(this.htmlElement.tagName.toLowerCase() == "img" && this.orientation != orientation)
+ {
+ var orientationSuffix;
+ switch(orientation)
+ {
+ case UP: orientationSuffix = "u"; break;
+ case DOWN: orientationSuffix = "d"; break;
+ case LEFT: orientationSuffix = "l"; break;
+ case RIGHT: orientationSuffix = "r"; break;
+ }
+ this.htmlElement.src = this.src + "_" + orientationSuffix + this.srcExtension;
+ }
+ this.orientation = orientation;
+ }
+
+ this.onMove = function()
+ {
+ this.repaint();
+ }
+}
+
+function SideConnectorLabel(connector, htmlElement, side)
+{
+ this.connector = connector;
+ this.htmlElement = htmlElement;
+ this.side = side;
+ this.connector.htmlElement.parentNode.appendChild(htmlElement);
+
+ this.repaint = function()
+ {
+ this.htmlElement.style.position = 'absolute';
+ var left;
+ var top;
+ var segment;
+
+ if(this.side == START)
+ {
+ segment = this.connector.getStartSegment();
+ left = segment.startX;
+ top = segment.startY;
+ if(segment.orientation == LEFT)
+ left -= this.htmlElement.offsetWidth;
+ if(segment.orientation == UP)
+ top -= this.htmlElement.offsetHeight;
+
+ if((segment.orientation & HORIZONTAL) != 0 && top < this.connector.getEndSegment().getEndY())
+ top -= this.htmlElement.offsetHeight;
+ if((segment.orientation & VERTICAL) != 0 && left < this.connector.getEndSegment().getEndX())
+ left -= this.htmlElement.offsetWidth;
+ }
+ else
+ {
+ segment = this.connector.getEndSegment();
+ left = segment.getEndX();
+ top = segment.getEndY();
+ if(segment.orientation == RIGHT)
+ left -= this.htmlElement.offsetWidth;
+ if(segment.orientation == DOWN)
+ top -= this.htmlElement.offsetHeight;
+ if((segment.orientation & HORIZONTAL) != 0 && top < this.connector.getStartSegment().startY)
+ top -= this.htmlElement.offsetHeight;
+ if((segment.orientation & VERTICAL) != 0 && left < this.connector.getStartSegment().startX)
+ left -= this.htmlElement.offsetWidth;
+ }
+
+ this.htmlElement.style.left = Math.ceil(left) + "px";
+ this.htmlElement.style.top = Math.ceil(top) + "px";
+ }
+
+ this.onMove = function()
+ {
+ this.repaint();
+ }
+}
+
+function MiddleConnectorLabel(connector, htmlElement)
+{
+ this.connector = connector;
+ this.htmlElement = htmlElement;
+ this.connector.canvas.htmlElement.appendChild(htmlElement);
+
+ this.repaint = function()
+ {
+ this.htmlElement.style.position = 'absolute';
+
+ var left;
+ var top;
+ var segment = connector.getMiddleSegment();
+
+ if((segment.orientation & VERTICAL) != 0)
+ {
+ // put label at middle height on right side of the connector
+ top = segment.htmlElement.offsetTop + (segment.htmlElement.offsetHeight - this.htmlElement.offsetHeight) / 2;
+ left = segment.htmlElement.offsetLeft;
+ }
+ else
+ {
+ // put connector below the connector at middle widths
+ top = segment.htmlElement.offsetTop;
+ left = segment.htmlElement.offsetLeft + (segment.htmlElement.offsetWidth - this.htmlElement.offsetWidth) / 2;;
+ }
+
+ this.htmlElement.style.left = Math.ceil(left) + "px";
+ this.htmlElement.style.top = Math.ceil(top) + "px";
+ }
+
+ this.onMove = function()
+ {
+ this.repaint();
+ }
+}
+
+/*
+ * Inspector classes
+ */
+
+function ConnectorEndsInspector()
+{
+ this.inspect = function(connector)
+ {
+ var children = connector.htmlElement.childNodes;
+ var i;
+ for(i = 0; i < children.length; i++)
+ {
+ if(hasClass(children[i], "connector-end"))
+ {
+ var newElement = new ConnectorEnd(children[i], connector, END);
+ newElement.repaint();
+ connector.moveListeners.push(newElement);
+ }
+ else if(hasClass(children[i], "connector-start"))
+ {
+ var newElement = new ConnectorEnd(children[i], connector, START);
+ newElement.repaint();
+ connector.moveListeners.push(newElement);
+ }
+ }
+ }
+}
+
+function ConnectorLabelsInspector()
+{
+ this.inspect = function(connector)
+ {
+ var children = connector.htmlElement.childNodes;
+ var i;
+ for(i = 0; i < children.length; i++)
+ {
+ if(hasClass(children[i], "source-label"))
+ {
+ var newElement = new SideConnectorLabel(connector, children[i], START);
+ newElement.repaint();
+ connector.moveListeners.push(newElement);
+ }
+ else if(hasClass(children[i], "middle-label"))
+ {
+ var newElement = new MiddleConnectorLabel(connector, children[i]);
+ newElement.repaint();
+ connector.moveListeners.push(newElement);
+ }
+ else if(hasClass(children[i], "destination-label"))
+ {
+ var newElement = new SideConnectorLabel(connector, children[i], END);
+ newElement.repaint();
+ connector.moveListeners.push(newElement);
+ }
+ }
+ }
+}
+
+/*
+ * Inspector registration
+ */
+
+inspectors.push(new ConnectorEndsInspector());
+inspectors.push(new ConnectorLabelsInspector());
+
+/*
+ * an array containing all the canvases in document
+ */
+var canvases = new Array();
+
+/*
+ * This function initializes the js_graph objects inspecting the html document
+ */
+function initPageObjects()
+{
+ if(isCanvas(document.body))
+ {
+ var newCanvas = new Canvas(document.body);
+ newCanvas.initCanvas();
+ canvases.push(newCanvas);
+ }
+ else
+ {
+ var divs = document.getElementsByTagName('div');
+ var i;
+ for(i = 0; i < divs.length; i++)
+ {
+ if(isCanvas(divs[i]) && !findCanvas(divs[i].id))
+ {
+ var newCanvas = new Canvas(divs[i]);
+ newCanvas.initCanvas();
+ canvases.push(newCanvas);
+ }
+ }
+ }
+}
+
+
+/*
+ * Utility functions
+ */
+
+
+function findCanvas(canvasId)
+{
+ var i;
+ for(i = 0; i < canvases.length; i++)
+ if(canvases[i].id == canvasId)
+ return canvases[i];
+ return null;
+}
+
+function findBlock(blockId)
+{
+ var i;
+ for(i = 0; i < canvases.length; i++)
+ {
+ var block = canvases[i].findBlock(blockId);
+ if(block)
+ return block;
+ }
+ return null;
+}
+
+/*
+ * This function determines whether a html element is to be considered a canvas
+ */
+function isBlock(htmlElement)
+{
+ return hasClass(htmlElement, 'block');
+}
+
+/*
+ * This function determines whether a html element is to be considered a block
+ */
+function isCanvas(htmlElement)
+{
+ return hasClass(htmlElement, 'canvas');
+}
+
+/*
+ * This function determines whether a html element is to be considered a connector
+ */
+function isConnector(htmlElement)
+{
+ return htmlElement.className && htmlElement.className.match(new RegExp('connector .*'));
+}
+
+/*
+ * This function calculates the absolute 'top' value for a html node
+ */
+function calculateOffsetTop(obj)
+{
+ var curtop = 0;
+ if (obj.offsetParent)
+ {
+ curtop = obj.offsetTop
+ while (obj = obj.offsetParent)
+ curtop += obj.offsetTop
+ }
+ else if (obj.y)
+ curtop += obj.y;
+ return curtop;
+}
+
+/*
+ * This function calculates the absolute 'left' value for a html node
+ */
+function calculateOffsetLeft(obj)
+{
+ var curleft = 0;
+ if (obj.offsetParent)
+ {
+ curleft = obj.offsetLeft
+ while (obj = obj.offsetParent)
+ {
+ curleft += obj.offsetLeft;
+ }
+ }
+ else if (obj.x)
+ curleft += obj.x;
+ return curleft;
+}
+
+function parseBorder(obj, side)
+{
+ var sizeString = getStyle(obj, "border-" + side + "-width");
+ if(sizeString && sizeString != "")
+ {
+ if(sizeString.substring(sizeString.length - 2) == "px")
+ return parseInt(sizeString.substring(0, sizeString.length - 2));
+ }
+ return 0;
+}
+
+function hasClass(element, className)
+{
+ if(!element || !element.className)
+ return false;
+
+ var classes = element.className.split(' ');
+ var i;
+ for(i = 0; i < classes.length; i++)
+ if(classes[i] == className)
+ return true;
+ return false;
+}
+
+/**
+ * This function retrieves the actual value of a style property even if it is set via css.
+ */
+function getStyle(node, styleProp)
+{
+ // if not an element
+ if( node.nodeType != 1)
+ return;
+
+ var value;
+ if (node.currentStyle)
+ {
+ // ie case
+ styleProp = replaceDashWithCamelNotation(styleProp);
+ value = node.currentStyle[styleProp];
+ }
+ else if (window.getComputedStyle)
+ {
+ // mozilla case
+ value = document.defaultView.getComputedStyle(node, null).getPropertyValue(styleProp);
+ }
+
+ return value;
+}
+
+function replaceDashWithCamelNotation(value)
+{
+ var pos = value.indexOf('-');
+ while(pos > 0 && value.length > pos + 1)
+ {
+ value = value.substring(0, pos) + value.substring(pos + 1, pos + 2).toUpperCase() + value.substring(pos + 2);
+ pos = value.indexOf('-');
+ }
+ return value;
+}
+
+
+/*******************************
+ * Connector paint strategies. *
+ *******************************/
+
+/**
+ * Horizontal "S" routing strategy.
+ */
+function HorizontalSStrategy(connector)
+{
+ this.connector = connector;
+
+ this.startSegment;
+ this.middleSegment;
+ this.endSegment;
+
+ this.strategyName = "horizontal_s";
+
+ this.getMiddleSegment = function()
+ {
+ return this.middleSegment;
+ }
+
+ this.isApplicable = function()
+ {
+ var sourceLeft = this.connector.source.left();
+ var sourceWidth = this.connector.source.width();
+ var destinationLeft = this.connector.destination.left();
+ var destinationWidth = this.connector.destination.width();
+
+ return Math.abs(2 * destinationLeft + destinationWidth - (2 * sourceLeft + sourceWidth)) - (sourceWidth + destinationWidth) > 4 * this.connector.minSegmentLength;
+ }
+
+ this.paint = function()
+ {
+ this.startSegment = connector.createSegment();
+ this.middleSegment = connector.createSegment();
+ this.endSegment = connector.createSegment();
+
+ var sourceLeft = this.connector.source.left();
+ var sourceTop = this.connector.source.top();
+ var sourceWidth = this.connector.source.width();
+ var sourceHeight = this.connector.source.height();
+
+ var destinationLeft = this.connector.destination.left();
+ var destinationTop = this.connector.destination.top();
+ var destinationWidth = this.connector.destination.width();
+ var destinationHeight = this.connector.destination.height();
+
+ var hLength;
+
+ this.startSegment.startY = Math.floor(sourceTop + sourceHeight / 2);
+
+ // deduce which face to use on source and destination blocks
+ if(sourceLeft + sourceWidth / 2 < destinationLeft + destinationWidth / 2)
+ {
+ // use left side of the source block and right side of the destination block
+ this.startSegment.startX = sourceLeft + sourceWidth;
+ hLength = destinationLeft - (sourceLeft + sourceWidth);
+ }
+ else
+ {
+ // use right side of the source block and left side of the destination block
+ this.startSegment.startX = sourceLeft;
+ hLength = destinationLeft + destinationWidth - sourceLeft;
+ }
+
+ // first horizontal segment positioning
+ this.startSegment.length = Math.floor(Math.abs(hLength) / 2);
+ this.startSegment.orientation = hLength > 0 ? RIGHT : LEFT;
+
+ // vertical segment positioning
+ var vLength = Math.floor(destinationTop + destinationHeight / 2 - (sourceTop + sourceHeight / 2));
+ this.middleSegment.length = Math.abs(vLength);
+ if(vLength == 0)
+ this.middleSegment.visible = false;
+ this.middleSegment.orientation = vLength > 0 ? DOWN : UP;
+
+ // second horizontal segment positioning
+ this.endSegment.length = Math.floor(Math.abs(hLength) / 2);
+ this.endSegment.orientation = hLength > 0 ? RIGHT : LEFT;
+ }
+}
+
+/**
+ * Vertical "S" routing strategy.
+ */
+function VerticalSStrategy(connector)
+{
+ this.connector = connector;
+
+ this.startSegment;
+ this.middleSegment;
+ this.endSegment;
+
+ this.strategyName = "vertical_s";
+
+ this.getMiddleSegment = function()
+ {
+ return this.middleSegment;
+ }
+
+ this.isApplicable = function()
+ {
+ var sourceTop = this.connector.source.top();
+ var sourceHeight = this.connector.source.height();
+ var destinationTop = this.connector.destination.top();
+ var destinationHeight = this.connector.destination.height();
+ return Math.abs(2 * destinationTop + destinationHeight - (2 * sourceTop + sourceHeight)) - (sourceHeight + destinationHeight) > 4 * this.connector.minSegmentLength;
+ }
+
+ this.paint = function()
+ {
+ this.startSegment = connector.createSegment();
+ this.middleSegment = connector.createSegment();
+ this.endSegment = connector.createSegment();
+
+ var sourceLeft = this.connector.source.left();
+ var sourceTop = this.connector.source.top();
+ var sourceWidth = this.connector.source.width();
+ var sourceHeight = this.connector.source.height();
+
+ var destinationLeft = this.connector.destination.left();
+ var destinationTop = this.connector.destination.top();
+ var destinationWidth = this.connector.destination.width();
+ var destinationHeight = this.connector.destination.height();
+
+ var vLength;
+
+ this.startSegment.startX = Math.floor(sourceLeft + sourceWidth / 2);
+
+ // deduce which face to use on source and destination blocks
+ if(sourceTop + sourceHeight / 2 < destinationTop + destinationHeight / 2)
+ {
+ // use bottom side of the source block and top side of destination block
+ this.startSegment.startY = sourceTop + sourceHeight;
+ vLength = destinationTop - (sourceTop + sourceHeight);
+ }
+ else
+ {
+ // use top side of the source block and bottom side of the destination block
+ this.startSegment.startY = sourceTop;
+ vLength = destinationTop + destinationHeight - sourceTop;
+ }
+
+ // first vertical segment positioning
+ this.startSegment.length = Math.floor(Math.abs(vLength) / 2);
+ this.startSegment.orientation = vLength > 0 ? DOWN : UP;
+
+ // horizontal segment positioning
+ var hLength = Math.floor(destinationLeft + destinationWidth / 2 - (sourceLeft + sourceWidth / 2));
+ this.middleSegment.length = Math.abs(hLength);
+ this.middleSegment.orientation = hLength > 0 ? RIGHT : LEFT;
+
+ // second vertical segment positioning
+ this.endSegment.length = Math.floor(Math.abs(vLength) / 2);
+ this.endSegment.orientation = vLength > 0 ? DOWN : UP;
+ }
+}
+
+/**
+ * A horizontal "L" connector routing strategy
+ */
+function HorizontalLStrategy(connector)
+{
+ this.connector = connector;
+
+ this.destination;
+
+ this.startSegment;
+ this.endSegment;
+
+ this.strategyName = "horizontal_L";
+
+ this.isApplicable = function()
+ {
+ var destMiddle = Math.floor(this.connector.destination.left() + this.connector.destination.width() / 2);
+ var sl = this.connector.source.left();
+ var sw = this.connector.source.width();
+ var dt = this.connector.destination.top();
+ var dh = this.connector.destination.height();
+ var sourceMiddle = Math.floor(this.connector.source.top() + this.connector.source.height() / 2);
+
+ if(destMiddle > sl && destMiddle < sl + sw)
+ return false;
+ if(sourceMiddle > dt && sourceMiddle < dt + dh)
+ return false;
+ return true;
+ }
+
+ /**
+ * Chooses the longest segment as the "middle" segment.
+ */
+ this.getMiddleSegment = function()
+ {
+ if(this.startSegment.length > this.endSegment.length)
+ return this.startSegment;
+ else
+ return this.endSegment;
+ }
+
+ this.paint = function()
+ {
+ this.startSegment = this.connector.createSegment();
+ this.endSegment = this.connector.createSegment();
+
+ var destMiddleX = Math.floor(this.connector.destination.left() + this.connector.destination.width() / 2);
+ var sl = this.connector.source.left();
+ var sw = this.connector.source.width();
+ var dt = this.connector.destination.top();
+ var dh = this.connector.destination.height();
+
+ this.startSegment.startY = Math.floor(this.connector.source.top() + this.connector.source.height() / 2);
+
+ // decide which side of the source block to connect to
+ if(Math.abs(destMiddleX - sl) < Math.abs(destMiddleX - (sl + sw)))
+ {
+ // use the left face
+ this.startSegment.orientation = (destMiddleX < sl) ? LEFT : RIGHT;
+ this.startSegment.startX = sl;
+ }
+ else
+ {
+ // use the right face
+ this.startSegment.orientation = (destMiddleX > (sl + sw)) ? RIGHT : LEFT;
+ this.startSegment.startX = sl + sw;
+ }
+
+ this.startSegment.length = Math.abs(destMiddleX - this.startSegment.startX);
+
+ // decide which side of the destination block to connect to
+ if(Math.abs(this.startSegment.startY - dt) < Math.abs(this.startSegment.startY - (dt + dh)))
+ {
+ // use the upper face
+ this.endSegment.orientation = (this.startSegment.startY < dt) ? DOWN : UP;
+ this.endSegment.length = Math.abs(this.startSegment.startY - dt);
+ }
+ else
+ {
+ // use the lower face
+ this.endSegment.orientation = (this.startSegment.startY > (dt + dh)) ? UP : DOWN;
+ this.endSegment.length = Math.abs(this.startSegment.startY - (dt + dh));
+ }
+ }
+}
+
+/**
+ * Vertical "L" connector routing strategy
+ */
+function VerticalLStrategy(connector)
+{
+ this.connector = connector;
+
+ this.startSegment;
+ this.endSegment;
+
+ this.strategyName = "vertical_L";
+
+ this.isApplicable = function()
+ {
+ var sourceMiddle = Math.floor(this.connector.source.left() + this.connector.source.width() / 2);
+ var dl = this.connector.destination.left();
+ var dw = this.connector.destination.width();
+ var st = this.connector.source.top();
+ var sh = this.connector.source.height();
+ var destMiddle = Math.floor(this.connector.destination.top() + this.connector.destination.height() / 2);
+
+ if(sourceMiddle > dl && sourceMiddle < dl + dw)
+ return false;
+ if(destMiddle > st && destMiddle < st + sh)
+ return false;
+ return true;
+ }
+
+ /**
+ * Chooses the longest segment as the "middle" segment.
+ */
+ this.getMiddleSegment = function()
+ {
+ if(this.startSegment.length > this.endSegment.length)
+ return this.startSegment;
+ else
+ return this.endSegment;
+ }
+
+ this.paint = function()
+ {
+ this.startSegment = this.connector.createSegment();
+ this.endSegment = this.connector.createSegment();
+
+ var destMiddleY = Math.floor(this.connector.destination.top() + this.connector.destination.height() / 2);
+ var dl = this.connector.destination.left();
+ var dw = this.connector.destination.width();
+ var st = this.connector.source.top();
+ var sh = this.connector.source.height();
+
+ this.startSegment.startX = Math.floor(this.connector.source.left() + this.connector.source.width() / 2);
+
+ // decide which side of the source block to connect to
+ if(Math.abs(destMiddleY - st) < Math.abs(destMiddleY - (st + sh)))
+ {
+ // use the upper face
+ this.startSegment.orientation = (destMiddleY < st) ? UP : DOWN;
+ this.startSegment.startY = st;
+ }
+ else
+ {
+ // use the lower face
+ this.startSegment.orientation = (destMiddleY > (st + sh)) ? DOWN : UP;
+ this.startSegment.startY = st + sh;
+ }
+
+ this.startSegment.length = Math.abs(destMiddleY - this.startSegment.startY);
+
+ // decide which side of the destination block to connect to
+ if(Math.abs(this.startSegment.startX - dl) < Math.abs(this.startSegment.startX - (dl + dw)))
+ {
+ // use the left face
+ this.endSegment.orientation = (this.startSegment.startX < dl) ? RIGHT : LEFT;
+ this.endSegment.length = Math.abs(this.startSegment.startX - dl);
+ }
+ else
+ {
+ // use the right face
+ this.endSegment.orientation = (this.startSegment.startX > dl + dw) ? LEFT : RIGHT;
+ this.endSegment.length = Math.abs(this.startSegment.startX - (dl + dw));
+ }
+ }
+}
+
+function HorizontalCStrategy(connector, startOrientation)
+{
+ this.connector = connector;
+
+ this.startSegment;
+ this.middleSegment;
+ this.endSegment;
+
+ this.strategyName = "horizontal_c";
+
+ this.getMiddleSegment = function()
+ {
+ return this.middleSegment;
+ }
+
+ this.isApplicable = function()
+ {
+ return true;
+ }
+
+ this.paint = function()
+ {
+ this.startSegment = connector.createSegment();
+ this.middleSegment = connector.createSegment();
+ this.endSegment = connector.createSegment();
+
+ var sign = 1;
+ if(startOrientation == RIGHT)
+ sign = -1;
+
+ var startX = this.connector.source.left();
+ if(startOrientation == RIGHT)
+ startX += this.connector.source.width();
+ var startY = Math.floor(this.connector.source.top() + this.connector.source.height() / 2);
+
+ var endX = this.connector.destination.left();
+ if(startOrientation == RIGHT)
+ endX += this.connector.destination.width();
+ var endY = Math.floor(this.connector.destination.top() + this.connector.destination.height() / 2);
+
+ this.startSegment.startX = startX;
+ this.startSegment.startY = startY;
+ this.startSegment.orientation = startOrientation;
+ this.startSegment.length = this.connector.minSegmentLength + Math.max(0, sign * (startX - endX));
+
+ var vLength = endY - startY;
+ this.middleSegment.orientation = vLength > 0 ? DOWN : UP;
+ this.middleSegment.length = Math.abs(vLength);
+
+ this.endSegment.orientation = startOrientation == LEFT ? RIGHT : LEFT;
+ this.endSegment.length = Math.max(0, sign * (endX - startX)) + this.connector.minSegmentLength;
+ }
+}
+
+function VerticalCStrategy(connector, startOrientation)
+{
+ this.connector = connector;
+
+ this.startSegment;
+ this.middleSegment;
+ this.endSegment;
+
+ this.strategyName = "vertical_c";
+
+ this.getMiddleSegment = function()
+ {
+ return this.middleSegment;
+ }
+
+ this.isApplicable = function()
+ {
+ return true;
+ }
+
+ this.paint = function()
+ {
+ this.startSegment = connector.createSegment();
+ this.middleSegment = connector.createSegment();
+ this.endSegment = connector.createSegment();
+
+ var sign = 1;
+ if(startOrientation == DOWN)
+ sign = -1;
+
+ var startY = this.connector.source.top();
+ if(startOrientation == DOWN)
+ startY += this.connector.source.height();
+ var startX = Math.floor(this.connector.source.left() + this.connector.source.width() / 2);
+
+ var endY = this.connector.destination.top();
+ if(startOrientation == DOWN)
+ endY += this.connector.destination.height();
+ var endX = Math.floor(this.connector.destination.left() + this.connector.destination.width() / 2);
+
+ this.startSegment.startX = startX;
+ this.startSegment.startY = startY;
+ this.startSegment.orientation = startOrientation;
+ this.startSegment.length = this.connector.minSegmentLength + Math.max(0, sign * (startY - endY));
+
+ var hLength = endX - startX;
+ this.middleSegment.orientation = hLength > 0 ? RIGHT : LEFT;
+ this.middleSegment.length = Math.abs(hLength);
+
+ this.endSegment.orientation = startOrientation == UP ? DOWN : UP;
+ this.endSegment.length = Math.max(0, sign * (endY - startY)) + this.connector.minSegmentLength;
+ }
+}
+
+
+strategies[0] = function(connector) {return new HorizontalSStrategy(connector)};
+strategies[1] = function(connector) {return new VerticalSStrategy(connector)};
+strategies[2] = function(connector) {return new HorizontalLStrategy(connector)};
+strategies[3] = function(connector) {return new VerticalLStrategy(connector)};
+strategies[4] = function(connector) {return new HorizontalCStrategy(connector, LEFT)};
+strategies[5] = function(connector) {return new HorizontalCStrategy(connector, RIGHT)};
+strategies[6] = function(connector) {return new VerticalCStrategy(connector, UP)};
+strategies[7] = function(connector) {return new VerticalCStrategy(connector, DOWN)};
\ No newline at end of file
--
cgit 1.4.1