@ -14,7 +14,10 @@ function Graph (container, data, options) {
this . containerElement = container ;
this . containerElement = container ;
this . width = '100%' ;
this . width = '100%' ;
this . height = '100%' ;
this . height = '100%' ;
this . refreshRate = 50 ; // milliseconds
// to give everything a nice fluidity, we seperate the rendering and calculating of the forces
this . calculationRefreshRate = 40 ; // milliseconds
this . calculationStartTime = 0 ;
this . renderRefreshRate = 10 ; // milliseconds
this . stabilize = true ; // stabilize before displaying the graph
this . stabilize = true ; // stabilize before displaying the graph
this . selectable = true ;
this . selectable = true ;
@ -82,43 +85,63 @@ function Graph (container, data, options) {
activeAreaBoxSize : 100 , // (px) | box area around the curser where clusters are popped open.
activeAreaBoxSize : 100 , // (px) | box area around the curser where clusters are popped open.
massTransferCoefficient : 1 // (multiplier) | parent.mass += massTransferCoefficient * child.mass
massTransferCoefficient : 1 // (multiplier) | parent.mass += massTransferCoefficient * child.mass
} ,
} ,
UI : {
enabled : true ,
xMovementSpeed : 10 ,
yMovementSpeed : 10 ,
zoomMovementSpeed : 0.02
} ,
minForce : 0.05 ,
minForce : 0.05 ,
minVelocity : 0.02 , // px/s
minVelocity : 0.02 , // px/s
maxIterations : 1000 // maximum number of iteration to stabilize
maxIterations : 1000 // maximum number of iteration to stabilize
} ;
} ;
// Node variables
this . groups = new Groups ( ) ; // object with groups
this . groups = new Groups ( ) ; // object with groups
this . images = new Images ( ) ; // object with images
this . images = new Images ( ) ; // object with images
this . images . setOnloadCallback ( function ( ) {
this . images . setOnloadCallback ( function ( ) {
graph . _redraw ( ) ;
graph . _redraw ( ) ;
} ) ;
} ) ;
// UI variables
this . UIvisible = true ;
this . xIncrement = 0 ;
this . yIncrement = 0 ;
this . zoomIncrement = 0 ;
// create a frame and canvas
// create a frame and canvas
this . _create ( ) ;
this . _create ( ) ;
// call the constructor of the cluster object
// apply options
this . setOptions ( options ) ;
// load the cluster system. (mandatory)
this . _loadClusterSystem ( ) ;
this . _loadClusterSystem ( ) ;
// call the sector constructor
// load the sector system. (mandatory)
this . _loadSectorSystem ( ) ;
this . _loadSectorSystem ( ) ;
// load the UI system.
// load the selection system. (mandatory)
this . _loadSelectionSystem ( ) ;
// load the UI system. (mandatory)
this . _loadUISystem ( ) ;
this . _loadUISystem ( ) ;
// other vars
var graph = this ;
var graph = this ;
this . freezeSimulation = false ; // freeze the simulation
this . freezeSimulation = false ; // freeze the simulation
this . tapTimer = 0 ; // timer to detect doubleclick or double tap
this . nodeIndices = [ ] ; // array with all the indices of the nodes. Used to speed up forces calculation
this . nodeIndices = [ ] ; // array with all the indices of the nodes. Used to speed up forces calculation
this . nodes = { } ; // object with Node objects
this . nodes = { } ; // object with Node objects
this . edges = { } ; // object with Edge objects
this . edges = { } ; // object with Edge objects
this . canvasTopLeft = { "x" : 0 , "y" : 0 } ; // coordinates of the top left of the canvas. they will be set during calcForces .
this . canvasBottomRight = { "x" : 0 , "y" : 0 } ; // coordinates of the bottom right of the canvas. they will be set during calcForces
this . canvasTopLeft = { "x" : 0 , "y" : 0 } ; // coordinates of the top left of the canvas. they will be set during _redraw .
this . canvasBottomRight = { "x" : 0 , "y" : 0 } ; // coordinates of the bottom right of the canvas. they will be set during _redraw
this . zoom Center = { } ; // object with x and y elements used for determining the center of the zoom action
this . scale = 1 ; // defining the global scale variable in the constructor
this . area Center = { } ; // object with x and y elements used for determining the center of the zoom action
this . scale = 1 ; // defining the global scale variable in the constructor
this . previousScale = this . scale ; // this is used to check if the zoom operation is zooming in or out
this . previousScale = this . scale ; // this is used to check if the zoom operation is zooming in or out
// TODO: create a counter to keep track on the number of nodes having values
// TODO: create a counter to keep track on the number of nodes having values
// TODO: create a counter to keep track on the number of nodes currently moving
// TODO: create a counter to keep track on the number of nodes currently moving
@ -160,13 +183,8 @@ function Graph (container, data, options) {
// properties of the data
// properties of the data
this . moving = false ; // True if any of the nodes have an undefined position
this . moving = false ; // True if any of the nodes have an undefined position
this . selection = [ ] ;
this . timer = undefined ;
this . timer = undefined ;
// apply options
this . setOptions ( options ) ;
// load data (the disable start variable will be the same as the enabled clustering)
// load data (the disable start variable will be the same as the enabled clustering)
this . setData ( data , this . constants . clustering . enabled ) ;
this . setData ( data , this . constants . clustering . enabled ) ;
@ -279,6 +297,14 @@ Graph.prototype.setOptions = function (options) {
}
}
}
}
if ( options . UI ) {
for ( var prop in options . UI ) {
if ( options . UI . hasOwnProperty ( prop ) ) {
this . constants . UI [ prop ] = options . UI [ prop ] ;
}
}
}
// TODO: work out these options and document them
// TODO: work out these options and document them
if ( options . edges ) {
if ( options . edges ) {
for ( prop in options . edges ) {
for ( prop in options . edges ) {
@ -393,52 +419,55 @@ Graph.prototype._create = function () {
prevent_default : true
prevent_default : true
} ) ;
} ) ;
this . hammer . on ( 'tap' , me . _onTap . bind ( me ) ) ;
this . hammer . on ( 'tap' , me . _onTap . bind ( me ) ) ;
this . hammer . on ( 'doubletap' , me . _onDoubleTap . bind ( me ) ) ;
this . hammer . on ( 'hold' , me . _onHold . bind ( me ) ) ;
this . hammer . on ( 'hold' , me . _onHold . bind ( me ) ) ;
this . hammer . on ( 'pinch' , me . _onPinch . bind ( me ) ) ;
this . hammer . on ( 'pinch' , me . _onPinch . bind ( me ) ) ;
this . hammer . on ( 'touch' , me . _onTouch . bind ( me ) ) ;
this . hammer . on ( 'touch' , me . _onTouch . bind ( me ) ) ;
this . hammer . on ( 'dragstart' , me . _onDragStart . bind ( me ) ) ;
this . hammer . on ( 'dragstart' , me . _onDragStart . bind ( me ) ) ;
this . hammer . on ( 'drag' , me . _onDrag . bind ( me ) ) ;
this . hammer . on ( 'drag' , me . _onDrag . bind ( me ) ) ;
this . hammer . on ( 'dragend' , me . _onDragEnd . bind ( me ) ) ;
this . hammer . on ( 'dragend' , me . _onDragEnd . bind ( me ) ) ;
this . hammer . on ( 'release' , me . _onRelease . bind ( me ) ) ;
this . hammer . on ( 'mousewheel' , me . _onMouseWheel . bind ( me ) ) ;
this . hammer . on ( 'mousewheel' , me . _onMouseWheel . bind ( me ) ) ;
this . hammer . on ( 'DOMMouseScroll' , me . _onMouseWheel . bind ( me ) ) ; // for FF
this . hammer . on ( 'DOMMouseScroll' , me . _onMouseWheel . bind ( me ) ) ; // for FF
this . hammer . on ( 'mousemove' , me . _onMouseMoveTitle . bind ( me ) ) ;
this . hammer . on ( 'mousemove' , me . _onMouseMoveTitle . bind ( me ) ) ;
this . mouseTrap = mouseTrap ;
/ *
this . mouseTrap . bind ( "=" , this . decreaseClusterLevel . bind ( me ) ) ;
this . mouseTrap . bind ( "-" , this . increaseClusterLevel . bind ( me ) ) ;
this . mouseTrap . bind ( "s" , this . singleStep . bind ( me ) ) ;
this . mouseTrap . bind ( "h" , this . updateClustersDefault . bind ( me ) ) ;
this . mouseTrap . bind ( "c" , this . _collapseSector . bind ( me ) ) ;
this . mouseTrap . bind ( "f" , this . toggleFreeze . bind ( me ) ) ;
* /
// add the frame to the container element
// add the frame to the container element
this . containerElement . appendChild ( this . frame ) ;
this . containerElement . appendChild ( this . frame ) ;
} ;
} ;
/ * *
*
* @ param { { x : Number , y : Number } } pointer
* @ return { Number | null } node
* @ private
* /
Graph . prototype . _getNodeAt = function ( pointer ) {
var x = this . _canvasToX ( pointer . x ) ;
var y = this . _canvasToY ( pointer . y ) ;
var obj = {
left : x ,
top : y ,
right : x ,
bottom : y
} ;
// if there are overlapping nodes, select the last one, this is the
// one which is drawn on top of the others
var overlappingNodes = this . _getNodesOverlappingWith ( obj ) ;
return ( overlappingNodes . length > 0 ) ?
overlappingNodes [ overlappingNodes . length - 1 ] : null ;
} ;
Graph . prototype . _createKeyBinds = function ( ) {
var me = this ;
this . mouseTrap = mouseTrap ;
this . mouseTrap . bind ( "up" , this . _moveUp . bind ( me ) , "keydown" ) ;
this . mouseTrap . bind ( "up" , this . _yStopMoving . bind ( me ) , "keyup" ) ;
this . mouseTrap . bind ( "down" , this . _moveDown . bind ( me ) , "keydown" ) ;
this . mouseTrap . bind ( "down" , this . _yStopMoving . bind ( me ) , "keyup" ) ;
this . mouseTrap . bind ( "left" , this . _moveLeft . bind ( me ) , "keydown" ) ;
this . mouseTrap . bind ( "left" , this . _xStopMoving . bind ( me ) , "keyup" ) ;
this . mouseTrap . bind ( "right" , this . _moveRight . bind ( me ) , "keydown" ) ;
this . mouseTrap . bind ( "right" , this . _xStopMoving . bind ( me ) , "keyup" ) ;
this . mouseTrap . bind ( "=" , this . _zoomIn . bind ( me ) , "keydown" ) ;
this . mouseTrap . bind ( "=" , this . _stopZoom . bind ( me ) , "keyup" ) ;
this . mouseTrap . bind ( "-" , this . _zoomOut . bind ( me ) , "keydown" ) ;
this . mouseTrap . bind ( "-" , this . _stopZoom . bind ( me ) , "keyup" ) ;
this . mouseTrap . bind ( "[" , this . _zoomIn . bind ( me ) , "keydown" ) ;
this . mouseTrap . bind ( "[" , this . _stopZoom . bind ( me ) , "keyup" ) ;
this . mouseTrap . bind ( "]" , this . _zoomOut . bind ( me ) , "keydown" ) ;
this . mouseTrap . bind ( "]" , this . _stopZoom . bind ( me ) , "keyup" ) ;
this . mouseTrap . bind ( "pageup" , this . _zoomIn . bind ( me ) , "keydown" ) ;
this . mouseTrap . bind ( "pageup" , this . _stopZoom . bind ( me ) , "keyup" ) ;
this . mouseTrap . bind ( "pagedown" , this . _zoomOut . bind ( me ) , "keydown" ) ;
this . mouseTrap . bind ( "pagedown" , this . _stopZoom . bind ( me ) , "keyup" ) ;
this . mouseTrap . bind ( "u" , this . _toggleUI . bind ( me ) , "keydown" ) ;
/ * =
this . mouseTrap . bind ( "=" , this . decreaseClusterLevel . bind ( me ) ) ;
this . mouseTrap . bind ( "-" , this . increaseClusterLevel . bind ( me ) ) ;
this . mouseTrap . bind ( "s" , this . singleStep . bind ( me ) ) ;
this . mouseTrap . bind ( "h" , this . updateClustersDefault . bind ( me ) ) ;
this . mouseTrap . bind ( "c" , this . _collapseSector . bind ( me ) ) ;
this . mouseTrap . bind ( "f" , this . toggleFreeze . bind ( me ) ) ;
* /
}
/ * *
/ * *
* Get the pointer location from a touch location
* Get the pointer location from a touch location
@ -462,6 +491,8 @@ Graph.prototype._onTouch = function (event) {
this . drag . pointer = this . _getPointer ( event . gesture . touches [ 0 ] ) ;
this . drag . pointer = this . _getPointer ( event . gesture . touches [ 0 ] ) ;
this . drag . pinched = false ;
this . drag . pinched = false ;
this . pinch . scale = this . _getScale ( ) ;
this . pinch . scale = this . _getScale ( ) ;
this . _handleTouch ( this . drag . pointer ) ;
} ;
} ;
/ * *
/ * *
@ -470,17 +501,18 @@ Graph.prototype._onTouch = function (event) {
* /
* /
Graph . prototype . _onDragStart = function ( ) {
Graph . prototype . _onDragStart = function ( ) {
var drag = this . drag ;
var drag = this . drag ;
var node = this . _getNodeAt ( drag . pointer ) ;
// note: drag.pointer is set in _onTouch to get the initial touch location
drag . selection = [ ] ;
drag . selection = [ ] ;
drag . translation = this . _getTranslation ( ) ;
drag . translation = this . _getTranslation ( ) ;
drag . nodeId = this . _getNodeAt ( drag . pointer ) ;
// note: drag.pointer is set in _onTouch to get the initial touch location
drag . nodeId = null ;
var node = this . nodes [ drag . nodeId ] ;
if ( node ) {
if ( node != null ) {
drag . nodeId = node . id ;
// select the clicked node if not yet selected
// select the clicked node if not yet selected
if ( ! node . isSelected ( ) ) {
if ( ! node . isSelected ( ) ) {
this . _selectNodes ( [ drag . nodeId ] ) ;
this . _selectNode ( node , false ) ;
}
}
// create an array with the selected nodes and their original location and status
// create an array with the selected nodes and their original location and status
@ -505,7 +537,6 @@ Graph.prototype._onDragStart = function () {
drag . selection . push ( s ) ;
drag . selection . push ( s ) ;
}
}
} ) ;
} ) ;
}
}
} ;
} ;
@ -582,58 +613,38 @@ Graph.prototype._onDragEnd = function () {
* /
* /
Graph . prototype . _onTap = function ( event ) {
Graph . prototype . _onTap = function ( event ) {
var pointer = this . _getPointer ( event . gesture . touches [ 0 ] ) ;
var pointer = this . _getPointer ( event . gesture . touches [ 0 ] ) ;
this . _handleTap ( pointer ) ;
} ;
var nodeId = this . _getNodeAt ( pointer ) ;
var node = this . nodes [ nodeId ] ;
var elapsedTime = new Date ( ) . getTime ( ) - this . tapTimer ;
this . tapTimer = new Date ( ) . getTime ( ) ;
if ( node ) {
if ( node . isSelected ( ) && elapsedTime < 300 ) {
this . zoomCenter = { "x" : this . _canvasToX ( pointer . x ) ,
"y" : this . _canvasToY ( pointer . y ) } ;
this . openCluster ( node ) ;
}
// select this node
this . _selectNodes ( [ nodeId ] ) ;
/ * *
* handle doubletap event
* @ private
* /
Graph . prototype . _onDoubleTap = function ( event ) {
var pointer = this . _getPointer ( event . gesture . touches [ 0 ] ) ;
this . _handleDoubleTap ( pointer ) ;
if ( ! this . moving ) {
this . _redraw ( ) ;
}
}
else {
// remove selection
this . _unselectNodes ( ) ;
this . _redraw ( ) ;
}
} ;
} ;
/ * *
/ * *
* handle long tap event : multi select nodes
* handle long tap event : multi select nodes
* @ private
* @ private
* /
* /
Graph . prototype . _onHold = function ( event ) {
Graph . prototype . _onHold = function ( event ) {
var pointer = this . _getPointer ( event . gesture . touches [ 0 ] ) ;
var pointer = this . _getPointer ( event . gesture . touches [ 0 ] ) ;
var nodeId = this . _getNodeAt ( pointer ) ;
var node = this . nodes [ nodeId ] ;
if ( node ) {
if ( ! node . isSelected ( ) ) {
// select this node, keep previous selection
var append = true ;
this . _selectNodes ( [ nodeId ] , append ) ;
}
else {
this . _unselectNodes ( [ nodeId ] ) ;
}
this . _handleOnHold ( pointer ) ;
} ;
if ( ! this . moving ) {
this . _redraw ( ) ;
}
}
else {
// Do nothing
}
/ * *
* handle the release of the screen
*
* @ param event
* @ private
* /
Graph . prototype . _onRelease = function ( event ) {
this . _handleOnRelease ( ) ;
} ;
} ;
/ * *
/ * *
@ -676,10 +687,11 @@ Graph.prototype._zoom = function(scale, pointer) {
var tx = ( 1 - scaleFrac ) * pointer . x + translation . x * scaleFrac ;
var tx = ( 1 - scaleFrac ) * pointer . x + translation . x * scaleFrac ;
var ty = ( 1 - scaleFrac ) * pointer . y + translation . y * scaleFrac ;
var ty = ( 1 - scaleFrac ) * pointer . y + translation . y * scaleFrac ;
this . zoom Center = { "x" : this . _canvasToX ( pointer . x ) ,
this . area Center = { "x" : this . _canvasToX ( pointer . x ) ,
"y" : this . _canvasToY ( pointer . y ) } ;
"y" : this . _canvasToY ( pointer . y ) } ;
// this.zoomCenter = {"x" : pointer.x,"y" : pointer.y };
// this.areaCenter = {"x" : pointer.x,"y" : pointer.y };
this . pinch . mousewheelScale = scale ;
this . _setScale ( scale ) ;
this . _setScale ( scale ) ;
this . _setTranslation ( tx , ty ) ;
this . _setTranslation ( tx , ty ) ;
this . updateClustersDefault ( ) ;
this . updateClustersDefault ( ) ;
@ -731,8 +743,8 @@ Graph.prototype._onMouseWheel = function(event) {
// apply the new scale
// apply the new scale
scale = this . _zoom ( scale , pointer ) ;
scale = this . _zoom ( scale , pointer ) ;
// store the new, applied scale
this . pinch . mousewheelScale = scale ;
// store the new, applied scale -- this is now done in _zoom
// this.pinch.mousewheelScale = scale;
}
}
// Prevent default actions caused by mouse wheel.
// Prevent default actions caused by mouse wheel.
@ -749,6 +761,8 @@ Graph.prototype._onMouseMoveTitle = function (event) {
var gesture = util . fakeGesture ( this , event ) ;
var gesture = util . fakeGesture ( this , event ) ;
var pointer = this . _getPointer ( gesture . center ) ;
var pointer = this . _getPointer ( gesture . center ) ;
this . lastPointerPosition = pointer ;
// check if the previously selected node is still selected
// check if the previously selected node is still selected
if ( this . popupNode ) {
if ( this . popupNode ) {
this . _checkHidePopup ( pointer ) ;
this . _checkHidePopup ( pointer ) ;
@ -761,7 +775,7 @@ Graph.prototype._onMouseMoveTitle = function (event) {
me . _checkShowPopup ( pointer ) ;
me . _checkShowPopup ( pointer ) ;
} ;
} ;
if ( this . popupTimer ) {
if ( this . popupTimer ) {
clearInterval ( this . popupTimer ) ; // stop any running timer
clearInterval ( this . popupTimer ) ; // stop any running calcula tionT imer
}
}
if ( ! this . leftButtonDown ) {
if ( ! this . leftButtonDown ) {
this . popupTimer = setTimeout ( checkShow , 300 ) ;
this . popupTimer = setTimeout ( checkShow , 300 ) ;
@ -854,214 +868,6 @@ Graph.prototype._checkHidePopup = function (pointer) {
}
}
} ;
} ;
/ * *
* Unselect selected nodes . If no selection array is provided , all nodes
* are unselected
* @ param { Object [ ] } selection Array with selection objects , each selection
* object has a parameter row . Optional
* @ param { Boolean } triggerSelect If true ( default ) , the select event
* is triggered when nodes are unselected
* @ return { Boolean } changed True if the selection is changed
* @ private
* /
Graph . prototype . _unselectNodes = function ( selection , triggerSelect ) {
var changed = false ;
var i , iMax , id ;
if ( selection ) {
// remove provided selections
for ( i = 0 , iMax = selection . length ; i < iMax ; i ++ ) {
id = selection [ i ] ;
if ( this . nodes . hasOwnProperty ( id ) ) {
this . nodes [ id ] . unselect ( ) ;
}
var j = 0 ;
while ( j < this . selection . length ) {
if ( this . selection [ j ] == id ) {
this . selection . splice ( j , 1 ) ;
changed = true ;
}
else {
j ++ ;
}
}
}
}
else if ( this . selection && this . selection . length ) {
// remove all selections
for ( i = 0 , iMax = this . selection . length ; i < iMax ; i ++ ) {
id = this . selection [ i ] ;
if ( this . nodes . hasOwnProperty ( id ) ) {
this . nodes [ id ] . unselect ( ) ;
}
changed = true ;
}
this . selection = [ ] ;
}
if ( changed && ( triggerSelect == true || triggerSelect == undefined ) ) {
// fire the select event
this . _trigger ( 'select' ) ;
}
return changed ;
} ;
/ * *
* select all nodes on given location x , y
* @ param { Array } selection an array with node ids
* @ param { boolean } append If true , the new selection will be appended to the
* current selection ( except for duplicate entries )
* @ return { Boolean } changed True if the selection is changed
* @ private
* /
Graph . prototype . _selectNodes = function ( selection , append ) {
var changed = false ;
var i , iMax ;
// TODO: the selectNodes method is a little messy, rework this
// check if the current selection equals the desired selection
var selectionAlreadyThere = true ;
if ( selection . length != this . selection . length ) {
selectionAlreadyThere = false ;
}
else {
for ( i = 0 , iMax = Math . min ( selection . length , this . selection . length ) ; i < iMax ; i ++ ) {
if ( selection [ i ] != this . selection [ i ] ) {
selectionAlreadyThere = false ;
break ;
}
}
}
if ( selectionAlreadyThere ) {
return changed ;
}
if ( append == undefined || append == false ) {
// first deselect any selected node
var triggerSelect = false ;
changed = this . _unselectNodes ( undefined , triggerSelect ) ;
}
for ( i = 0 , iMax = selection . length ; i < iMax ; i ++ ) {
// add each of the new selections, but only when they are not duplicate
var id = selection [ i ] ;
var isDuplicate = ( this . selection . indexOf ( id ) != - 1 ) ;
if ( ! isDuplicate ) {
this . nodes [ id ] . select ( ) ;
this . selection . push ( id ) ;
changed = true ;
}
}
if ( changed ) {
// fire the select event
this . _trigger ( 'select' ) ;
}
return changed ;
} ;
/ * *
* retrieve all nodes overlapping with given object
* @ param { Object } obj An object with parameters left , top , right , bottom
* @ return { Number [ ] } An array with id ' s of the overlapping nodes
* @ private
* /
Graph . prototype . _getNodesOverlappingWith = function ( obj ) {
var overlappingNodes = [ ] ;
var nodes , sector ;
// search in all sectors for nodes
for ( sector in this . sectors [ "active" ] ) {
if ( this . sectors [ "active" ] . hasOwnProperty ( sector ) ) {
nodes = this . sectors [ "active" ] [ sector ] [ "nodes" ] ;
for ( var id in nodes ) {
if ( nodes . hasOwnProperty ( id ) ) {
if ( nodes [ id ] . isOverlappingWith ( obj ) ) {
overlappingNodes . push ( id ) ;
}
}
}
}
}
for ( sector in this . sectors [ "frozen" ] ) {
if ( this . sectors [ "frozen" ] . hasOwnProperty ( sector ) ) {
nodes = this . sectors [ "frozen" ] [ sector ] [ "nodes" ] ;
for ( var id in nodes ) {
if ( nodes . hasOwnProperty ( id ) ) {
if ( nodes [ id ] . isOverlappingWith ( obj ) ) {
overlappingNodes . push ( id ) ;
}
}
}
}
}
this . nodes = this . sectors [ "active" ] [ this . activeSector [ this . activeSector . length - 1 ] ] [ "nodes" ] ;
return overlappingNodes ;
} ;
/ * *
* retrieve the currently selected nodes
* @ return { Number [ ] | String [ ] } selection An array with the ids of the
* selected nodes .
* /
Graph . prototype . getSelection = function ( ) {
return this . selection . concat ( [ ] ) ;
} ;
/ * *
* select zero or more nodes
* @ param { Number [ ] | String [ ] } selection An array with the ids of the
* selected nodes .
* /
Graph . prototype . setSelection = function ( selection ) {
var i , iMax , id ;
if ( ! selection || ( selection . length == undefined ) )
throw 'Selection must be an array with ids' ;
// first unselect any selected node
for ( i = 0 , iMax = this . selection . length ; i < iMax ; i ++ ) {
id = this . selection [ i ] ;
this . nodes [ id ] . unselect ( ) ;
}
this . selection = [ ] ;
for ( i = 0 , iMax = selection . length ; i < iMax ; i ++ ) {
id = selection [ i ] ;
var node = this . nodes [ id ] ;
if ( ! node ) {
throw new RangeError ( 'Node with id "' + id + '" not found' ) ;
}
node . select ( ) ;
this . selection . push ( id ) ;
}
this . redraw ( ) ;
} ;
/ * *
* Validate the selection : remove ids of nodes which no longer exist
* @ private
* /
Graph . prototype . _updateSelection = function ( ) {
var i = 0 ;
while ( i < this . selection . length ) {
var id = this . selection [ i ] ;
if ( ! this . nodes [ id ] ) {
this . selection . splice ( i , 1 ) ;
}
else {
i ++ ;
}
}
} ;
/ * *
/ * *
* Temporary method to test calculating a hub value for the nodes
* Temporary method to test calculating a hub value for the nodes
@ -1159,7 +965,9 @@ Graph.prototype.setSize = function(width, height) {
this . frame . canvas . width = this . frame . canvas . clientWidth ;
this . frame . canvas . width = this . frame . canvas . clientWidth ;
this . frame . canvas . height = this . frame . canvas . clientHeight ;
this . frame . canvas . height = this . frame . canvas . clientHeight ;
this . _relocateUI ( ) ;
if ( this . constants . UI . enabled == true ) {
this . _relocateUI ( ) ;
}
} ;
} ;
/ * *
/ * *
@ -1495,6 +1303,11 @@ Graph.prototype._redraw = function() {
ctx . translate ( this . translation . x , this . translation . y ) ;
ctx . translate ( this . translation . x , this . translation . y ) ;
ctx . scale ( this . scale , this . scale ) ;
ctx . scale ( this . scale , this . scale ) ;
this . canvasTopLeft = { "x" : ( 0 - this . translation . x ) / this . scale ,
"y" : ( 0 - this . translation . y ) / this . scale } ;
this . canvasBottomRight = { "x" : ( this . frame . canvas . clientWidth - this . translation . x ) / this . scale ,
"y" : ( this . frame . canvas . clientHeight - this . translation . y ) / this . scale } ;
this . _doInAllSectors ( "_drawAllSectorNodes" , ctx ) ;
this . _doInAllSectors ( "_drawAllSectorNodes" , ctx ) ;
this . _doInAllSectors ( "_drawEdges" , ctx ) ;
this . _doInAllSectors ( "_drawEdges" , ctx ) ;
this . _doInAllSectors ( "_drawNodes" , ctx ) ;
this . _doInAllSectors ( "_drawNodes" , ctx ) ;
@ -1502,7 +1315,9 @@ Graph.prototype._redraw = function() {
// restore original scaling and translation
// restore original scaling and translation
ctx . restore ( ) ;
ctx . restore ( ) ;
this . _doInUISector ( "_drawNodes" , ctx , true ) ;
if ( this . UIvisible == true ) {
this . _doInUISector ( "_drawNodes" , ctx , true ) ;
}
} ;
} ;
/ * *
/ * *
@ -1707,10 +1522,6 @@ Graph.prototype._initializeForceCalculation = function() {
* @ private
* @ private
* /
* /
Graph . prototype . _calculateForces = function ( ) {
Graph . prototype . _calculateForces = function ( ) {
this . canvasTopLeft = { "x" : ( 0 - this . translation . x ) / this . scale ,
"y" : ( 0 - this . translation . y ) / this . scale } ;
this . canvasBottomRight = { "x" : ( this . frame . canvas . clientWidth - this . translation . x ) / this . scale ,
"y" : ( this . frame . canvas . clientHeight - this . translation . y ) / this . scale } ;
var centerPos = { "x" : 0.5 * ( this . canvasTopLeft . x + this . canvasBottomRight . x ) ,
var centerPos = { "x" : 0.5 * ( this . canvasTopLeft . x + this . canvasBottomRight . x ) ,
"y" : 0.5 * ( this . canvasTopLeft . y + this . canvasBottomRight . y ) }
"y" : 0.5 * ( this . canvasTopLeft . y + this . canvasBottomRight . y ) }
@ -1924,7 +1735,7 @@ Graph.prototype._isMoving = function(vmin) {
* @ private
* @ private
* /
* /
Graph . prototype . _discreteStepNodes = function ( ) {
Graph . prototype . _discreteStepNodes = function ( ) {
var interval = this . r efreshRate / 1000.0 ; // in seconds
var interval = this . calculationR efreshRate / 1000.0 ; // in seconds
var nodes = this . nodes ;
var nodes = this . nodes ;
for ( var id in nodes ) {
for ( var id in nodes ) {
if ( nodes . hasOwnProperty ( id ) ) {
if ( nodes . hasOwnProperty ( id ) ) {
@ -1936,33 +1747,62 @@ Graph.prototype._discreteStepNodes = function() {
this . moving = this . _isMoving ( vmin ) ;
this . moving = this . _isMoving ( vmin ) ;
} ;
} ;
/ * *
/ * *
* Start animating nodes and edges
* Start animating nodes and edges
*
* @ poram { Boolean } runCalculationStep
* /
* /
Graph . prototype . start = function ( ) {
Graph . prototype . start = function ( runCalculationStep ) {
if ( runCalculationStep === undefined ) {
runCalculationStep = true ;
}
if ( ! this . freezeSimulation ) {
if ( ! this . freezeSimulation ) {
if ( this . moving ) {
if ( this . moving && runCalculationStep ) {
this . _doInAllActiveSectors ( "_initializeForceCalculation" ) ;
this . _doInAllActiveSectors ( "_initializeForceCalculation" ) ;
this . _doInAllActiveSectors ( "_discreteStepNodes" ) ;
this . _doInAllActiveSectors ( "_discreteStepNodes" ) ;
}
}
if ( this . moving ) {
// start animation. only start timer if it is not already running
if ( this . moving || this . xIncrement != 0 || this . yIncrement != 0 || this . zoomIncrement != 0 ) {
// start animation. only start calcula tionT imer if it is not already running
if ( ! this . timer ) {
if ( ! this . timer ) {
var graph = this ;
var graph = this ;
this . timer = window . setTimeout ( function ( ) {
this . timer = window . setTimeout ( function ( ) {
graph . timer = undefined ;
graph . timer = undefined ;
graph . start ( ) ;
graph . start ( ) ;
// keyboad movement
if ( graph . xIncrement != 0 || graph . yIncrement != 0 ) {
var translation = graph . _getTranslation ( ) ;
graph . _setTranslation ( translation . x + graph . xIncrement , translation . y + graph . yIncrement ) ;
}
if ( graph . zoomIncrement != 0 ) {
graph . _zoom ( graph . scale * ( 1 + graph . zoomIncrement ) , graph . lastPointerPosition ) ;
}
var calculateNextStep = false ;
var time = window . performance . now ( ) ;
if ( time - graph . calculationStartTime > graph . calculationRefreshRate && graph . moving ) {
calculateNextStep = true ;
graph . calculationStartTime = window . performance . now ( ) ;
}
graph . start ( calculateNextStep ) ;
//var startTime = window.performance.now();
graph . _redraw ( ) ;
graph . _redraw ( ) ;
// var start = window.performance.now();
// graph._redraw();
// var end = window.performance.now();
// var time = end - start;
// console.log('Drawing time: ' + time);
} , this . refreshRate ) ;
//var end = window.performance.now();
//time = end - startTime;
//console.log('Drawing time: ' + time);
//this.end = window.performance.now();
//this.time = this.end - this.startTime;
//console.log('refresh time: ' + this.time);
//this.startTime = window.performance.now();
} , this . renderRefreshRate ) ;
}
}
}
}
else {
else {
@ -1972,6 +1812,8 @@ Graph.prototype.start = function() {
} ;
} ;
Graph . prototype . singleStep = function ( ) {
Graph . prototype . singleStep = function ( ) {
if ( this . moving ) {
if ( this . moving ) {
this . _initializeForceCalculation ( ) ;
this . _initializeForceCalculation ( ) ;
@ -2035,7 +1877,7 @@ Graph.prototype._loadSectorSystem = function() {
"formationScale" : 1.0 ,
"formationScale" : 1.0 ,
"drawingNode" : undefined } ;
"drawingNode" : undefined } ;
this . nodeIndices = this . sectors [ "active" ] [ this . activeSector [ this . activeSector . length - 1 ] ] [ "nodeIndices" ] ; // the node indices list is used to speed up the computation of the repulsion fields
this . nodeIndices = this . sectors [ "active" ] [ "default" ] [ "nodeIndices" ] ; // the node indices list is used to speed up the computation of the repulsion fields
for ( var mixinFunction in SectorMixin ) {
for ( var mixinFunction in SectorMixin ) {
if ( SectorMixin . hasOwnProperty ( mixinFunction ) ) {
if ( SectorMixin . hasOwnProperty ( mixinFunction ) ) {
Graph . prototype [ mixinFunction ] = SectorMixin [ mixinFunction ] ;
Graph . prototype [ mixinFunction ] = SectorMixin [ mixinFunction ] ;
@ -2044,59 +1886,43 @@ Graph.prototype._loadSectorSystem = function() {
} ;
} ;
/ * *
* Mixin the selection system and initialize the parameters required
*
* @ private
* /
Graph . prototype . _loadSelectionSystem = function ( ) {
this . selection = [ ] ;
this . selectionObj = { } ;
Graph . prototype . _loadUISystem = function ( ) {
this . _loadUIElements ( ) ;
}
Graph . prototype . _loadUIElements = function ( ) {
var DIR = 'img/UI/' ;
this . UIclientWidth = this . frame . canvas . clientWidth ;
this . UIclientHeight = this . frame . canvas . clientHeight ;
var UINodes = [
{ id : 'UI_up' , shape : 'image' , image : DIR + 'uparrow.png' ,
verticalAlignTop : false , x : 50 , y : this . UIclientHeight - 50 } ,
{ id : 'UI_down' , shape : 'image' , image : DIR + 'downarrow.png' ,
verticalAlignTop : false , x : 50 , y : this . UIclientHeight - 20 } ,
{ id : 'UI_left' , shape : 'image' , image : DIR + 'leftarrow.png' ,
verticalAlignTop : false , x : 20 , y : this . UIclientHeight - 20 } ,
{ id : 'UI_right' , shape : 'image' , image : DIR + 'rightarrow.png' ,
verticalAlignTop : false , x : 80 , y : this . UIclientHeight - 20 } ,
{ id : 'UI_plus' , shape : 'image' , image : DIR + 'plus.png' ,
verticalAlignTop : false , x : 130 , y : this . UIclientHeight - 20 } ,
{ id : 'UI_minus' , shape : 'image' , image : DIR + 'minus.png' ,
verticalAlignTop : false , x : 160 , y : this . UIclientHeight - 20 }
] ;
for ( var i = 0 ; i < UINodes . length ; i ++ ) {
this . sectors [ "UI" ] [ 'nodes' ] [ UINodes [ i ] [ 'id' ] ] = new Node ( UINodes [ i ] , this . images , this . groups , this . constants ) ;
for ( var mixinFunction in SelectionMixin ) {
if ( SelectionMixin . hasOwnProperty ( mixinFunction ) ) {
Graph . prototype [ mixinFunction ] = SelectionMixin [ mixinFunction ] ;
}
}
}
} ;
}
Graph . prototype . _relocateUI = function ( ) {
Graph . prototype . _relocateUI = function ( ) {
var xOffset = this . UIclientWidth - this . frame . canvas . clientWidth ;
var yOffset = this . UIclientHeight - this . frame . canvas . clientHeight ;
this . UIclientWidth = this . frame . canvas . clientWidth ;
this . UIclientHeight = this . frame . canvas . clientHeight ;
var node = null ;
for ( var nodeId in this . sectors [ "UI" ] [ "nodes" ] ) {
if ( this . sectors [ "UI" ] [ "nodes" ] . hasOwnProperty ( nodeId ) ) {
node = this . sectors [ "UI" ] [ "nodes" ] [ nodeId ] ;
if ( ! node . horizontalAlignLeft ) {
node . x -= xOffset ;
}
if ( ! node . verticalAlignTop ) {
node . y -= yOffset ;
}
// empty, will be overloaded when loading the UI system
}
/ * *
* Mixin the UI ( User Interface ) system and initialize the parameters required
*
* @ private
* /
Graph . prototype . _loadUISystem = function ( ) {
for ( var mixinFunction in UIMixin ) {
if ( UIMixin . hasOwnProperty ( mixinFunction ) ) {
Graph . prototype [ mixinFunction ] = UIMixin [ mixinFunction ] ;
}
}
}
}
} ;
this . _loadUIElements ( ) ;
if ( this . constants . UI . enabled == true ) {
this . _createKeyBinds ( ) ;
}
}