Drag and drop graph : Graph « Ajax Layer « JavaScript DHTML

JavaScript DHTML
1. Ajax Layer
2. Data Type
3. Date Time
4. Development
5. Document
6. Event
7. Event onMethod
8. Form Control
9. GUI Components
10. HTML
11. Javascript Collections
12. Javascript Objects
13. Language Basics
14. Node Operation
15. Object Oriented
16. Page Components
17. Security
18. Style Layout
19. Table
20. Utilities
21. Window Browser
Microsoft Office Word 2007 Tutorial
Java
Java Tutorial
Java Source Code / Java Documentation
Java Open Source
Jar File Download
Java Articles
Java Products
Java by API
C# / C Sharp
C# / CSharp Tutorial
ASP.Net
JavaScript Tutorial
JavaScript Reference
HTML / CSS
HTML CSS Reference
C / ANSI-C
C Tutorial
C++
C++ Tutorial
PHP
Python
SQL Server / T-SQL
Oracle PL / SQL
Oracle PL/SQL Tutorial
PostgreSQL
SQL / MySQL
MySQL Tutorial
VB.Net
VB.Net Tutorial
JavaScript DHTML » Ajax Layer » Graph 
Drag and drop graph




// http://js-graph-it.sf.net 

// License: GNU Library or Lesser General Public License (LGPL)









<?xml version="1.0" encoding="ISO-8859-1" ?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1" />
<title>js-graph.it homepage</title>
<script type="text/javascript">
/*********************
 * browser detection *
 *********************/

var ie=document.all;
var nn6=document.getElementById&&!document.all;

/*****************
 * drag and drop *
 *****************/
 
var isdrag=false;
var mouseStartX, mouseStartY;    // mouse position when drag starts
var elementStartX, elementStartY;  // element position when drag starts
var elementToMove;
var blockToMove;

// 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);

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[01;
    if(newElementX + elementToMove.offsetWidth > bounds[2])
      newElementX = bounds[2- elementToMove.offsetWidth - 1;
    if(newElementY < bounds[1])
      newElementY = bounds[11;
    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;
    
    if(blockToMove)
      blockToMove.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 selectmouse(e
{
  
  var eventSource = nn6 ? e.target : event.srcElement;
  
  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;
    
    // calculate start point
    //elementStartX = calculateOffsetLeft(elementToMove);
    //elementStartY = calculateOffsetTop(elementToMove);
    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(elementToMove.parentNode.style.position == 'absolute')
    {
      bounds[00;
      bounds[10;
    }
    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
    blockToMove = findBlock(eventSource.id);
    document.onmousemove = movemouse;
    
    return false;
  }
}

document.onmousedown=selectmouse;
document.onmouseup=new Function("isdrag=false");



/*************
 * Constants *
 *************/
var AUTO = 0;
var HORIZONTAL = 1;
var VERTICAL = 2;

/**************
 * 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.initCanvas = function()
  {
    // inspect canvas children to identify first level blocks
    this.findNestedBlocksAndConnectors(this.htmlElement);
    
    // init connectors
    var i;
    for(i = 0; i < this.connectors.length; i++)
    {
      this.connectors[i].initConnector();
    }
  }
  
  this.findNestedBlocksAndConnectors = function(node)
  {
    var children = node.childNodes;
    var i;
    var offsetLeft = calculateOffsetLeft(this.htmlElement);
    var offsetTop = calculateOffsetTop(this.htmlElement);
    
    for(i = 0; i < children.length; i++)
    {
      // move element in a "correct relative" position and set it size as fixed
      if(getStyle(children[i]"position"== 'absolute')
      {
        children[i].style.left = children[i].offsetLeft + offsetLeft + "px";
        children[i].style.top = children[i].offsetTop + offsetTop + "px";
        children[i].style.width = children[i].offsetWidth;
        children[i].style.height = children[i].offsetHeight;
      }
    
      if(isBlock(children[i]))
      {
        // block found initialize it
        var newBlock = new Block(children[i]this);
        newBlock.initBlock();
        this.blocks.push(newBlock);
      }
      else if(isConnector(children[i]))
      {
        // connector found, just create it, source or destination blocks may not 
        // have been initialized yet
        var newConnector = new Connector(children[i]this);
        this.connectors.push(newConnector);
      }
      else
      {
        // continue searching nested elements
        this.findNestedBlocksAndConnectors(children[i]);
      }
    }    
  }
  
  /*
   * methods
   */  
  this.print = function()
  {
    var output = '<ul><legend>canvas: ' + this.id + '</legend>';
    var i;
    for(i = 0; i < this.blocks.length; i++)
    {
      output += '<li>';
      output += this.blocks[i].print();
      output += '</li>';
    }
    output += '</ul>';
    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();  
  
  
  this.initBlock = function()
  {
    // inspect block children to identify nested blocks
    var children = this.htmlElement.childNodes;
    var i;    
    for(i = 0; i < children.length; i++)
    {
      if(isBlock(children[i]))
      {
        var innerBlock = new Block(children[i]this.canvas);
        innerBlock.initBlock();
        this.blocks.push(innerBlock);
        this.moveListeners.push(innerBlock);
      }
    }
    
    //this.htmlElement.onmousemove = new Function('if(isdrag) findBlock(\'' + this.id + '\').onMove();');
  }
  
  this.top = function()
  {
    return calculateOffsetTop(this.htmlElement);
  }
  
  this.left = function()
  {
    return calculateOffsetLeft(this.htmlElement);
  }
  
  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 += '<ul>';
      var i;
      for(i = 0; i < this.blocks.length; i++)
      {
        output += '<li>';
        output += this.blocks[i].print();
        output += '</li>';
      }
      output += '</ul>';
    }
    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;
    
    // notify listeners
    for(i = 0; i < this.moveListeners.length; i++)
    {
      this.moveListeners[i].onMove();
    }
  }
  
  this.toString = function()
  {
    return 'block: ' + this.id;
  }
}

/*
 * Connector class.
 * The init function takes two Block objects as arguments representing 
 * the source and destination of the connector
 */
function Connector(htmlElement, canvas)
{
  this.htmlElement = htmlElement;
  this.canvas = canvas;
  this.source = null;
  this.destination = null;
  this.startX = null;
  this.startY = null;
  this.destX = null;
  this.destY = null;
  this.segment1 = null;
  this.segment2 = null;
  this.segment3 = null;
  this.preferredOrientation = AUTO;
  this.orientation = HORIZONTAL;
  this.size = 1;
  this.color = 'black';
  this.moveListeners = new Array();
  
  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 <sourceBlockId>, <destBlockId>');
      return;
    }
    
    var 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.preferredOrientation = VERTICAL;
    else if(hasClass(this.htmlElement, 'horizontal'))
      this.preferredOrientation = HORIZONTAL;
    else
      this.preferredOrientation = AUTO;
    
    // build the segments
    this.segment1 = document.createElement('div');
    this.segment1.id = this.id + "_1";    
    this.canvas.htmlElement.appendChild(this.segment1);

    this.segment1.style.position = 'absolute';
    this.segment1.style.overflow = 'hidden';
    
    if(!getStyle(this.segment1, 'background-color'))
      this.segment1.style.backgroundColor = this.color;
    this.segment1.className = connectorClass;
    
    this.segment2 = document.createElement('div');
    this.segment2.id = this.id + "_2";
    this.canvas.htmlElement.appendChild(this.segment2);

    this.segment2.className = connectorClass;    
    this.segment2.style.position = 'absolute';
    this.segment2.style.overflow = 'hidden';
    
    if(!getStyle(this.segment2, 'background-color'))
      this.segment2.style.backgroundColor = this.color;
    
    this.segment3 = document.createElement('div');
    this.segment3.id = this.id + "_3";
    this.canvas.htmlElement.appendChild(this.segment3);

    this.segment3.style.position = 'absolute';
    this.segment3.style.overflow = 'hidden';
    
    if(!getStyle(this.segment3, 'background-color'))
      this.segment3.style.backgroundColor = this.color;      
    this.segment3.className = connectorClass;
    
    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);
  }
  
  /**
   * Repaints the connector
   */
  this.repaint = function()
  {
    var sourceLeft = this.source.left();
    var sourceTop = this.source.top();
    var sourceWidth = this.source.width();
    var sourceHeight = this.source.height();
    
    var destinationLeft = this.destination.left();
    var destinationTop = this.destination.top();
    var destinationWidth = this.destination.width();
    var destinationHeight = this.destination.height();
    
    if(this.preferredOrientation == HORIZONTAL)
    {
      // use horizontal orientation except if it is impossible
      if((destinationLeft - sourceLeft - sourceWidth*
        (sourceLeft - destinationLeft - destinationWidth0)
        this.orientation = VERTICAL;
      else
        this.orientation = HORIZONTAL;
    }
    else if(this.preferredOrientation == VERTICAL)
    {
      // use vertical orientation except if it is impossible
      if((destinationTop - sourceTop - sourceHeight*
        (sourceTop - destinationTop - destinationHeight0)
        this.orientation = HORIZONTAL;
      else
        this.orientation = VERTICAL;
    }
    else
    {
      // auto orientation: change current orientation if it is impossible to maintain
      if(this.orientation == HORIZONTAL &&
        (destinationLeft - sourceLeft - sourceWidth*
        (sourceLeft - destinationLeft - destinationWidth0)
      {
        this.orientation = VERTICAL;
      }
      else if(this.orientation == VERTICAL &&
        (destinationTop - sourceTop - sourceHeight*
        (sourceTop - destinationTop - destinationHeight0)
      {
        this.orientation = HORIZONTAL;
      }
    }
    
    if(this.orientation == HORIZONTAL)
    {
      // deduce which face to use on source and destination blocks
      if(sourceLeft + sourceWidth / < destinationLeft + destinationWidth / 2)
      {
        // use left side of the source block and right side of the destination block
        this.startX = sourceLeft + sourceWidth;
        this.destX = destinationLeft;
      }
      else
      {
        // use right side of the source block and left side of the destination block
        this.startX = sourceLeft;
        this.destX = destinationLeft + destinationWidth;
      }

      this.startY = sourceTop + sourceHeight / 2;
      this.destY = destinationTop + destinationHeight /2;
      
      // first horizontal segment positioning
      this.segment1.style.left = Math.min(this.startX, (this.destX + this.startX2'px';
      this.segment1.style.top = this.startY + 'px';
      this.segment1.style.width = Math.abs((this.startX - this.destX2this.size + 'px';
      this.segment1.style.height = this.size + 'px';
      
      // vertical segment positioning
      this.segment2.style.left = ((this.startX + this.destX/2'px';
      this.segment2.style.top = Math.min(this.startY, this.destY'px';
      this.segment2.style.width = this.size + 'px';
      this.segment2.style.height = Math.abs(this.destY - this.startY'px';
      
      // second horizontal segment positioning
      this.segment3.style.left = Math.min((this.startX + this.destX/2this.destX'px';
      this.segment3.style.top = this.destY + 'px';
      this.segment3.style.width = Math.abs((this.destX - this.startX2'px';
      this.segment3.style.height = this.size + 'px';
      
      // label positioning
      //this.htmlElement.style.left = this.startX + 'px';
      //this.htmlElement.style.top = this.startY + this.size + 'px';
    }
    else
    {
      // deduce which face to use on source and destination blocks
      if(sourceTop + sourceHeight / < destinationTop + destinationHeight / 2)
      {
        // use bottom side of the sheightblock and top side of thtopestination block
        this.startY = sourceTop + sourceHeight;
        this.destY = destinationTop;
      }
      else
      {
        // use top side of the source block and bottom side of the destination block
        this.startY = sourceTop;
        this.destY = destinationTop + destinationHeight;
      }
      
      this.startX = sourceLeft + sourceWidth / 2;
      this.destX = destinationLeft + destinationWidth / 2;
      
      // first vertical segment positioning
      this.segment1.style.left = this.startX + 'px';
      this.segment1.style.top = Math.min(this.startY, (this.destY + this.startY)/2'px';
      this.segment1.style.width = this.size + 'px';
      this.segment1.style.height = Math.abs((this.startY - this.destY2this.size + 'px';
      
      // horizontal segment positioning
      this.segment2.style.left = Math.min(this.startX, this.destX'px';
      this.segment2.style.top = ((this.startY + this.destY/2'px';
      this.segment2.style.width = Math.abs(this.destX - this.startX'px';
      this.segment2.style.height = this.size + 'px';
      
      // second vertical segment positioning
      this.segment3.style.left = this.destX + 'px';
      this.segment3.style.top = Math.min(this.destY, (this.destY + this.startY2'px';
      this.segment3.style.width = this.size + 'px';
      this.segment3.style.height = Math.abs((this.destY - this.startY2'px';
      
      // label positioning
      //this.htmlElement.style.left = this.startX + 'px';
      //this.htmlElement.style.top = this.startY + this.size + 'px';
    }
  }
  
  this.onMove = function()
  {
    this.repaint();
    
    // notify listeners
    var i;
    for(i = 0; i < this.moveListeners.length; i++)
      this.moveListeners[i].onMove();
  }
}

function ConnectorEnd(connector, htmlElement, segment)
{
  this.connector = connector;
  this.htmlElement = htmlElement;
  this.connector.segment1.parentNode.appendChild(htmlElement);
  // strip extension
  this.src = this.htmlElement.src.substring(0this.htmlElement.src.lastIndexOf('.'));
  this.srcExtension = this.htmlElement.src.substring(this.htmlElement.src.lastIndexOf('.'));
  
  this.orientation;
  
  this.repaint = function()
  {
    this.htmlElement.style.position = 'absolute';
    
    var orientation;
    var left;
    var top;
    
    if(connector.orientation == HORIZONTAL)
    {
      left = segment.offsetLeft;
      orientation = "l";
      if(segment.offsetLeft == connector.segment2.offsetLeft)
      {
        left += segment.offsetWidth - this.htmlElement.offsetWidth;
        var orientation = "r";
      }
  
      top = segment.offsetTop - (this.htmlElement.offsetHeight / 2);
    }
    else
    {
      top = segment.offsetTop;
      orientation = "u";
      if(segment.offsetTop == connector.segment2.offsetTop)