1 // ==========================================================================
  2 // NodeGraph.NodeGraphView
  3 //
  4 // License:  PaperCube is open source software released under 
  5 //           the MIT License (see license.js)
  6 // ==========================================================================
  7 
  8 require('core');
  9 require('mixins/dragpan');
 10 /** @class NodeGraph.NodeGraphView
 11 
 12   This is the node graph view. This will be extended by various views to 
 13   show a node-edge graph It will show all nodes for an centered object
 14   and also link between them. Strong relationships will be shown with thicker and 
 15   darker lines. This view uses SVG.
 16 
 17   Constants defined in core.js:
 18 
 19   var NODE = 0;
 20   var EDGE = 1;
 21   var TEXT = 2;
 22   var EDGETEXT = 4;
 23   var REL = 3;
 24   var NODEGRAPH_DEFAULT_SCALE = 10;
 25   var NODEGRAPH_USE_CHILDREN_OFFSET = YES;
 26   var NODEGRAPH_OFFSET_PERCENT = .40;
 27 
 28   @extends SC.View
 29   @extends NodeGraph.DragPanMixin
 30   @object Peter Bergstrom
 31   @version 1.0
 32   @copyright 2008-2009 Peter Bergström.
 33 */
 34 
 35 NodeGraph.NodeGraphView = SC.View.extend(NodeGraph.DragPanMixin, 
 36 /** @scope NodeGraph.NodeGraphView.prototype */ {
 37 
 38 
 39   /**
 40     @property
 41     @type {NodeGraph.NodeGraphDelegate}
 42     
 43     The controller delegate for the node graph.
 44   */
 45   delegate: null,
 46   
 47   /************************************************************************************
 48     Bindings that are customized when extending this object.
 49   *************************************************************************************/
 50   
 51   /**
 52     The value for the depth slider.
 53   
 54     @property {Integer}
 55   */
 56   depth: 2,
 57 
 58   /**
 59     Depth cached value.
 60   
 61     @property {Integer}
 62     @default 2
 63   */
 64   _cached_depth: 2,
 65   
 66   /**
 67     Link stregth threshold. Override this.
 68 
 69     @property {Integer}
 70     @default 0
 71   */
 72   linkThreshold: 0,
 73 
 74   /**
 75     linkThreshold cached value.
 76   
 77     @property {Integer}
 78     @default 0
 79   */
 80   _cached_linkThreshold: 0,
 81 
 82   /**
 83     Display references or citations.
 84 
 85     @property {Boolean}
 86     @default YES
 87   */
 88   _displayRefs: YES,
 89 
 90   /************************************************************************************
 91     Methods and variables that you should extend to use this view.
 92   *************************************************************************************/
 93 
 94   /**
 95     Node background color.
 96     
 97     @property {String}
 98     @config
 99     @default '#FFCB2F'
100   */
101   nodeColor: '#FFCB2F',
102 
103   /**
104     Selected node background color.
105     
106     @property {String}
107     @config
108     @default 'yellow'
109   */
110   nodeColorSel: 'yellow',
111 
112   /**
113     Node border color.
114     
115     @property {String}
116     @config
117     @default '#EAA400'
118   */
119   nodeBorderColor: '#EAA400',
120 
121   /**
122     Selected node border color.
123     
124     @property {String}
125     @config
126     @default '#FFB60B'
127   */
128   nodeBorderColorSel: '#FFB60B',
129 
130   /**
131     Node text color.
132     
133     @property {String}
134     @config
135     @default '#666'
136   */
137   nodeTextColor: '#666',
138 
139   /**
140     Default node size ratio from the size of the screen.
141     
142     Default is 25 times smaller than height.
143     
144     @property {Integer}
145     @config
146     @default 25
147   */
148   nodeDefaultRadius: 25,
149 
150   /**
151     The default width of the node border.
152     
153     @property {Integer}
154     @config
155     @default 1px
156   */
157   nodeBorderWidth: 1,
158   
159   /**
160     Node x-y ratio The default is x radius is 1.5 times y.
161     
162     @property {Float}
163     @config
164     @default 1.5
165   */
166   nodeXYRatio: 1.5,
167 
168   /**
169     Edge color.
170     
171     @property {String}
172     @config
173     @default '#333'
174   */
175   edgeColor: '#333',
176 
177   /**
178     Selected edge color.
179     
180     @property {String}
181     @config
182     @default 'red'
183   */
184   edgeColorSel: 'red',
185 
186   /**
187     Edge text color.
188     
189     @property {String}
190     @config
191     @default '#666'
192   */
193   edgeTextColor: '#666',
194 
195   /**
196     The minimum width of an edge.
197     
198     @property {Integer}
199     @config
200     @default 1px
201   */
202   edgeMinWidth: 1,
203   
204   /**
205     The nodeTextRatio allows the font size for the node to the calculated. 
206     
207     The font size is calculated as radius/nodeTextRatio.
208     
209     @property {Integer}
210     @config
211     @default 2
212   */
213   nodeTextRatio: 2,
214 
215   /**
216     A value of 0.1 would puts the edge label close to start node, 0.5 would put it in the middle of the edge,  
217     0.9 would put it close to the end node.
218 
219     @property {Float}
220     @config
221     @default 0.3
222   */
223   edgeTextPosOffset: 0.3, 
224 
225   /**
226     Node opacity.
227     
228     @property {Float}
229     @config
230     @default 1
231   */
232   nodeOpacity: 1,
233 
234   /**
235     Edge opacity.
236     
237     @property {Float}
238     @config
239     @default 0.2
240   */
241   edgeOpacity: 0.2,
242 
243   /**
244     Selected edge opacity.
245     
246     @property {Float}
247     @config
248     @default 0.5
249   */
250   edgeOpacitySel: 0.5,
251   
252   /**
253     If set to YES, show the edge label, if NO, hide the label.
254     
255     @property {Boolean}
256     @config
257     @default YES
258     @default YES
259   */
260   showEdgeLabel: YES,
261 
262   /**
263     If set to YES, calculate the edge width by looking at the weight of the item, otherwise, skip this operation.
264     
265     @property {Boolean}
266     @config
267     @default YES
268     @default YES
269   */
270   useEdgeWeightWidth: YES,
271 
272   /**
273     If NO, don't show edges.
274     
275     @property {Boolean}
276     @config
277     @default YES
278     @default YES
279   */
280   showEdges: YES,
281 
282   /** 
283     The key for the default title display.
284 
285     @property {String}
286     @config
287     @default 'title'
288   */
289   defaultTitleKey: 'title',
290 
291   /**
292     The type of content being displayed.
293     
294     @property {String}
295     @config
296     @default 'Paper'
297   */
298   contentTypeViewing: 'Paper',
299   
300   /**
301     The name of the view. 
302     
303     @property {String}
304     @config
305     @default 'none'
306   */
307   viewName: 'none',
308 
309   /**
310     Meta data box small height. 
311     
312     @property {Integer}
313     @config
314     @default 100px
315   */  
316   metaDataBoxHeightSmall: 100,
317 
318   /**
319     Meta data box small width. 
320     
321     @property {Integer}
322     @config
323     @default 200px
324   */  
325   metaDataBoxWidthSmall: 200,
326 
327   /** 
328     Class name for meta data DIV.
329     
330     @property {String}
331     @config
332     @default ''
333   */
334   metaDataClassName: '',
335 
336   /**
337     Set the default title for the view.
338 
339     @param content {Record} Content object used to get the title.
340   */
341   setDefaultTitle: function(content)
342   {
343     var str = '';
344     if(content)
345     {
346       str = content.get(this.defaultTitleKey);
347     }
348     this.graphTitle.innerHTML = str;
349   },
350   
351   /** 
352     Generate custom metadata for item.
353     
354     @param guid {string} The guid for content object that is found in the SC Store.
355 
356     @returns {array|SC.Record} Returns the found content object.
357   */
358   findCustomObject: function(guid)
359   {
360     // Add customized code here.
361     return null;
362   },
363 
364   /** 
365     Find Custom Object Relation Attribute.
366 
367     @param object {Record} The content object.
368 
369     @returns {Array} Returns the found relation attribute array.
370   */
371   findCustomObjectAttr: function(object)
372   {
373     // Add customized code here.
374     return [];
375   },
376 
377   /**
378     Given relation object, return guid for it.
379     
380     @param rel {Object} The relation object or array.
381 
382     @returns {String} Returns the guid for the relation object.
383   */
384   getGuidForRelation: function(rel)
385   {
386     // Add customized code here.
387     return '';
388   },
389   
390   /**
391     Revert bindings to default.
392   */
393   setBindingDefaults: function()
394   {
395     // Add customized code here.
396   },
397   
398   /**
399     Given an object, return its label.
400 
401     @param object {Object} The content object.
402 
403     @returns {String} Returns a title string for display.
404   */
405   findCustomObjectLabel: function()
406   {
407     // Add customized code here.
408   },
409   
410   /**
411     Given relation object, return weight for it.
412 
413     @param rel {Object} The relation object or array.
414 
415     @returns {Integer} Returns the calculated weight for the relation object.
416   */
417   calcRelationWeight: function()
418   {
419     // Add customized code here.
420     return 1;
421   },
422   
423   /** 
424     Get the details of a given item.
425     
426     @param guids {Array} The array of guids.
427     @param callBack {Function} The callBack function is called when the request is successful.
428   */
429   performCustomRequest: function(guids, callBack)
430   {
431     // Add customized code here.
432   },
433   
434   /**
435     Custom threshold calculation for complex link threshold calculations.
436 
437     @param rel {Object} The relation's guid.
438 
439     @returns {String} Returns if the item is accepted by the threshold(s). Returns NO otherwise.
440   */
441   relationMeetsCustomThreshold: function(rel)
442   {
443     // Add customized code here.
444     return (this.calcRelationWeight(rel)>=this._cached_linkThreshold);
445   },
446 
447 
448   /************************************************************************************
449   *  Default binding.
450   *************************************************************************************/
451 
452   /**
453     This is generally a binding from the delegate.
454     
455     To set up to the binding, do the following:
456     
457     displayPropertiesBinding: 'Path.to.delegate.displayProperties'
458     
459     Values 
460     {
461       height: 0,
462       width: 0,
463       left: 0,
464       top: 0,
465       zoomValue: 0,
466       portalWidth: 0,
467       portalHeight: 0
468     }
469     
470     @property {Object}
471   */
472   displayProperties: {
473     height: NodeGraph.windowHeight(),
474     width: NodeGraph.windowHeight(),
475     left: 0,
476     top: 0,
477     zoomValue: 1,
478     portalWidth: NodeGraph.windowWidth(),
479     portalHeight: NodeGraph.windowHeight(),
480   },
481 
482   /************************************************************************************
483   *  Only one outlet, for the meta data view.
484   *************************************************************************************/
485 
486   /**
487     Outlets for author stat view.
488 
489     ["metaDataView", 'graphTitle']
490   */
491   outlets: ["metaDataView", 'graphTitle'],
492 
493   /**
494     The DOM element that contains the meta data for an item. Bound to the '.meta-data?' element.
495 
496     @outlet {DOM Element} '.meta-data?'
497   */
498   metaDataView: ".meta-data?",
499 
500   /**
501     The title for what is being viewed.
502 
503     @outlet {DOM Element} '.graph-data?'
504   */
505   graphTitle: ".graph-title?",
506 
507 
508 
509   /************************************************************************************
510     Internal properties that are used for rendering and book keeping.
511   *************************************************************************************/
512 
513   /**
514     Cached width of the view's canvas, set by the displayProperties.
515 
516     @property {Integer}
517     @default 800px
518   */
519   _canvasWidth: 800,
520 
521   /**
522     Cached height of the view's canvas, set by the displayProperties.
523 
524     @property {Integer}
525     @default 600px
526   */
527   _canvasHeight: 600,
528 
529   /**
530     Cached height property from displayProperties.
531 
532     @property {Integer}
533   */
534   _h: 0,
535 
536   /**
537     Cached width property from displayProperties.
538 
539     @property {Integer}
540   */
541   _w: 0,
542 
543   /**
544     Cached x-axis offset property from displayProperties.
545 
546     @property {Integer}
547   */
548   _x: 0,
549 
550   /**
551     Cached y-axis offset property from displayProperties.
552 
553     @property {Integer}
554   */
555   _y: 0,
556 
557   /** 
558     The found GUID.
559   
560     @property {String}
561   */
562   _foundGuid: null,
563 
564   /** 
565     "canvas" for the svg tag. 
566   
567     @property {DOM Element}
568   */
569   svgCanvas: null,
570 
571   /** 
572     The deepest explored area.
573   
574     @private 
575     @property {Integer}
576     @default 0
577   */
578   _deepest: 0,
579   
580   /** 
581     Option to keep lines pinned if desired.
582   
583     @private 
584     @property {Boolean}
585     @default NO
586   */
587   _linesPinned: NO,
588   
589   /** 
590     Visited nodes.
591   
592     @private 
593     @property {Object}
594   */
595   _visited: {},
596   
597   /** 
598     Positioned nodes.
599   
600     @private 
601     @property {Object}
602   */
603   _positioned: {},
604 
605   /** 
606     Nodes.
607   
608     @private 
609     @property {Object}
610   */
611   _nodes: {},
612 
613   /** 
614     SVG Node cache. To prevent needless waste.
615   
616     @private 
617     @property {Object}
618   */
619   _svgNodesCache: {},
620 
621   /** 
622     SVG Edges Text cache. To prevent needless waste.
623   
624     @private 
625     @property {Object}
626   */
627   _svgEdgesTextCache: {},
628 
629   /** 
630     SVG Edges cache. To prevent needless waste.
631   
632     @private 
633     @property {Object}
634   */
635   _svgEdgesCache: {},
636 
637   /** 
638     SVG Text cache. To prevent needless waste.
639   
640     @private 
641     @property {Object}
642   */
643   _svgTextCache: {},
644 
645   /** 
646     The left bound of the visualization, used for specifying a bounding box to zoom to.
647   
648     @private 
649     @property {Integer}
650     @default 0
651   */
652   _lBound: 0,
653 
654   /** 
655     The right bound of the visualization, used for specifying a bounding box to zoom to.
656   
657     @private 
658     @property {Integer}
659     @default 0
660   */
661   _rBound: 0,
662 
663   /** 
664     The top bound of the visualization, used for specifying a bounding box to zoom to.
665   
666     @private 
667     @property {Integer}
668     @default 0
669   */
670   _tBound: 0,
671 
672   /** 
673     The bottom bound of the visualization, used for specifying a bounding box to zoom to.
674   
675     @private 
676     @property {Integer}
677     @default 0
678   */
679   _bBound: 0,
680   
681   /** 
682     The length of the edges.
683   
684     @private 
685     @property {Integer}
686     @default 0
687   */
688   _offset: 0,
689   
690   /** 
691     The radius of the circle.
692   
693     @private 
694     @property {Integer}
695     @default 0
696   */
697   _radius: 0,
698 
699   /** 
700     The last deepest zoom.
701   
702     @private 
703     @property {Integer}
704     @default 0
705   */
706   _lastDeepest: 0,
707 
708   /** 
709     Node border width style caches.
710   
711     @private 
712     @property {Integer}
713     @default 1px
714   */
715   _last_nodeBorderWidth: 1,
716   
717   /**
718     Hash used to prevent infinite loops during retrieval.
719 
720     @private
721     @type {Object}
722   */
723   _retrieved: {},
724 
725   /************************************************************************************
726     Observers.
727   *************************************************************************************/
728 
729   /**
730     When the displayProperities binding changes, update the view appropriately.
731 
732     @observes displayProperties
733     @observes depth
734     @observes linkThreshold
735   
736     @param force {boolean} If YES, force a redraw.
737     
738     @returns {Boolean} Returns NO if there is no guid of if the view is not visible.
739   */
740   displayPropertiesDidChange: function(force)
741   {
742 
743     if(!this.get("isVisible") || !this.displayProperties) return NO;
744 
745     // Save display properties locally.
746     var h = this.displayProperties.height;
747     var w = this.displayProperties.width;
748     var x = this.displayProperties.left;
749     var y = this.displayProperties.top;
750     var z = this.displayProperties.zoomValue;
751     var pW = this.displayProperties.portalWidth;
752     var pH = this.displayProperties.portalHeight;
753     var depth = this.depth;
754     var linkThreshold = this.linkThreshold;
755     
756     this._linesPinned = NO;
757     var left = top = right = bottom = 0;
758 
759     var delegate = this.get('delegate');
760     if(delegate) {
761       this._left = delegate.get('leftOffset');
762       this._top = delegate.get('topOffset');
763       this._right = delegate.get('rightOffset');
764       this._bottom = delegate.get('bottomOffset');
765     }
766 
767     // Set the style and frame of the view. This replaces set('frame', {...}) due to performance reasons.
768     this.setStyle({top: top+'px', right: right+'px', bottom: bottom+'px', left: left+'px'});
769     this._frame = {top: top, right: right, bottom: bottom, left: left};
770 
771     // Set the canvas dimensions.
772     this.svgCanvas.setAttributeNS(null, 'height', pH);
773     this.svgCanvas.setAttributeNS(null, 'width', pW);
774 
775     // Invert the display variables here to set the view box.
776     // this.svgCanvas.setAttributeNS(null, 'viewBox', (pW+' '+pH+' '+Math.abs(x)+' '+Math.abs(y)));
777 
778     // Save the canvas height and width.
779     this._canvasHeight = h;
780     this._canvasWidth = w;
781 
782     // Force redraw when window is resized.
783     var forceRedraw = (((this._h != h || this._w != w)) && this._z == z || depth != this._cached_depth || linkThreshold != this._cached_linkThreshold);
784     
785     if(this._cachedLinkThreshold != linkThreshold)
786     {
787       this._hideExistingLines();
788     }
789 
790     // Save the properties as needed.
791     this._h = h;
792     this._w = w;
793     this._x = x;
794     this._y = y;
795     this._z = z;
796     this._zI = z/NODEGRAPH_DEFAULT_SCALE;
797     this._pH = pH;
798     this._pW = pW;
799     this._cached_depth = depth;
800     this._cached_linkThreshold = linkThreshold;
801     
802     // Hide meta data as the coordinates of things may have changed.
803     this._hideMetaData();
804 
805 
806     if(this.svgGroup)
807       this.svgGroup.setAttributeNS(null, 'transform', 'scale('+this._zI+') translate('+ (this._x/this._zI) +',' + (this._y/this._zI)+')');
808 
809     // If there is a resize of the window, then you have to redraw to match the new view port.
810     if(forceRedraw && typeof force != "boolean" && this._cachedContent)  
811     {
812       this._foundGuid =  null;
813       this._render();
814     }
815   }.observes('displayProperties', 'depth', 'linkThreshold'),
816 
817   /**
818     Redraw based on 'content', 'isVisible' binding changes.
819 
820     @observes content
821     @observes isVisible
822   
823     @returns {Boolean} Returns NO if there is no guid of if the view is not visible.
824   */
825   redrawParamsDidChange: function()
826   {
827     var content = this.get('content');
828 
829     // If there is no content or if you're not visible, bail.
830     if(!this.get("isVisible") || !content) return NO;
831 
832     if(this.get('delegate')) {
833       // Set the zoom values to the view defaults.
834       this.get('delegate').set("zoomValueMax", NODEGRAPH_DEFAULT_SCALE);
835       this.get('delegate').set("zoomStep", 0.5);
836     }
837 
838     if(this.metaDataClassName != '')
839     {
840       this.metaDataView.addClassName(this.metaDataClassName);
841     }
842     
843     if(!this._cachedContent || this._cachedContent.get('guid') !== content.get('guid')) 
844     {
845       // Hide meta data view.
846       this._hideMetaData();
847       this._svgNodesCache = {};
848       this._svgEdgesTextCache = {};
849       this._svgEdgesCache = {};
850       this._svgTextCache = {};
851       this._svgRels = {};
852       this._clearSVG();
853       if(this.get('delegate')) this.get('delegate').zoomOutFull();
854       this._lastDeepest = null;
855       
856       this.setDefaultTitle(content);
857       
858     }
859     
860     if(!this._cachedContent)
861     {
862       this.setBindingDefaults();
863     }
864     
865     this._foundGuid =  null;
866     this._cachedContent = content;
867     this.displayPropertiesDidChange(YES);
868     this._deepest = 0;
869     this._render();
870 
871   }.observes('content', 'isVisible'),
872   
873   
874   
875   /************************************************************************************
876     Mouse handling.
877   *************************************************************************************/
878 
879   /** 
880     If the mouse is moved on top of the view, see if there is any meta data to show.
881   
882     @param {DOM Event} evt The mouseMoved event.
883   */
884   mouseMoved: function(evt)
885   {
886     var guid = evt.target.getAttribute('guid');
887     if(guid)
888     {
889       if(this._foundGuid != guid && !this._linesPinned)
890       {
891 
892         this._hideExistingLines();
893 
894         this._foundGuid = guid;
895 
896         var object = this.findCustomObject(guid);
897         var elm = (this._svgNodesCache[guid]) ? this._svgNodesCache[guid].elm : null;
898         if(elm)
899         {
900           elm.setAttributeNS(null, 'fill', this.nodeColorSel);    
901           elm.setAttributeNS(null, 'stroke', this.nodeBorderColorSel);    
902         }
903         if(object)
904         {
905           var relations = this.findCustomObjectAttr(object);
906         }
907         var relLen = relations.length;
908         for(var i=0; i<relLen; i++)
909         {
910           var g = this.getGuidForRelation(relations[i]);
911           var edge = (this._svgEdgesCache[this._foundGuid+g] || this._svgEdgesCache[g+this._foundGuid]);
912           if(edge)
913           {
914             var elmm = edge.elm;
915             if(elmm)
916             {
917               elmm.setAttributeNS(null, 'stroke', this.edgeColorSel);
918               elmm.setAttributeNS(null, 'stroke-opacity', this.edgeOpacitySel);
919             }
920           }
921         }
922       }
923 
924       this._showMetaData({x: Event.pointerX(evt), y: Event.pointerY(evt)}, guid);
925     }
926     else
927     {
928       this._hideMetaData();
929     }
930   },
931 
932   /**
933     Hide the lines when redrawing happens.
934   */
935   _hideExistingLines: function()
936   {
937 
938     if(this._foundGuid && this._svgNodesCache[this._foundGuid])
939     {
940       var object = this.findCustomObject(this._foundGuid);
941 
942       if(object)
943       {
944         var relations = this.findCustomObjectAttr(object);
945       }
946       
947       var elm = this._svgNodesCache[this._foundGuid].elm;
948       if(elm)
949       {
950         elm.removeAttributeNS(null, 'fill');    
951         elm.removeAttributeNS(null, 'stroke');    
952       }
953       
954       var relLen = relations.length;
955       for(var i=0; i<relLen; i++)
956       {
957         var g = this.getGuidForRelation(relations[i]);
958         var edge = (this._svgEdgesCache[this._foundGuid+g] || this._svgEdgesCache[g+this._foundGuid]);
959         if(edge)
960         {
961           var elmm = edge.elm;
962           if(elmm)
963           {
964             elmm.removeAttributeNS(null, 'stroke');    
965             elmm.removeAttributeNS(null, 'stroke-opacity');    
966           }
967         }
968       }
969     }    
970     
971     this._foundGuid = null;
972   },
973 
974   /** 
975     If the mouse leaves the area, hide the meta-data view.
976   
977     @param {DOM Event} evt The mouseExited event.
978   */
979   mouseExited: function(evt)
980   {
981     this._hideMetaData();
982   },
983 
984   /** 
985     Save the GUID of the element and invoke delegate if possible.
986     
987     @param {DOM Event} evt The mouseDown event.
988   */
989   mouseDown: function(evt)
990   {
991     var guid = evt.target.getAttribute('guid');
992     if(guid)
993     {
994       this._mouseDownGUID = guid;
995       if(this.get('delegate')) {
996         this.get('delegate').nodeGraphDidMouseDown(evt, this, guid);
997       }
998       return YES;
999     }
1000     else
1001     {
1002       return this.handleMouseDownDrag(evt);
1003     }
1004     return NO;
1005 
1006   },
1007 
1008   /**
1009     Pin the lines for object.
1010   */
1011   pinLinesForObj: function()
1012   {
1013     this._linesPinned = !this._linesPinned;
1014   },
1015 
1016 
1017   /************************************************************************************
1018     Internal methods.
1019   *************************************************************************************/
1020 
1021   /**
1022     Hide meta data box.
1023   */
1024   _hideMetaData: function()
1025   {
1026     // Hide meta data view.
1027     this.metaDataView.removeClassName("show-meta-data-small");
1028   },
1029 
1030   /**
1031     Show meta data for item!
1032     
1033 
1034     @param coordinates {Object} The x,y coordinates of the mouse pointer.
1035     @param guid {string} The guid of the item.
1036   
1037     @returns {Boolean} Returns NO if there are no coordinates or content.
1038   */
1039   _showMetaData: function(coordinates, guid)
1040   {
1041     if(!coordinates) return NO;
1042 
1043     var x = coordinates.x+10;
1044     var y = coordinates.y-10;
1045   
1046     var topOffset = this._topOffset ? this._topOffset : 0;
1047     var bottomOffset = this._bottomOffset ? this._bottomOffset : 0;
1048     var rightOffset = this._rightOffset ? this._rightOffset : 0;
1049     var leftOffset = this._leftOffset ? this._leftOffset : 0;
1050 
1051     var wHeight = NodeGraph.windowHeight()-topOffset-bottomOffset;
1052     var wWidth = NodeGraph.windowWidth()-rightOffset-leftOffset;
1053     
1054     if(y < topOffset) y = topOffset;
1055     if(x < leftOffset) x = leftOffset;
1056     if(y > (wHeight-this.metaDataBoxHeightSmall)) y = (wHeight-this.metaDataBoxHeightSmall)-topOffset-bottomOffset;
1057     if(x > (wWidth-this.metaDataBoxWidthSmall)) x = (wWidth-this.metaDataBoxWidthSmall)+1;
1058 
1059     this.metaDataView.style.top = y + "px";
1060     this.metaDataView.style.left = x + "px";
1061 
1062     // Show the meta data view.
1063     this.metaDataView.addClassName("show-meta-data-small");
1064 
1065     this.generateCustomMetaData(guid);
1066   },
1067 
1068 
1069   /**
1070     Collect what needs to be rendered.
1071     
1072     Then render the view.
1073 
1074   */
1075   _render: function()
1076   {
1077     // Null out variables.
1078     this._guidsNeeded = [];
1079     this._nodes = {};
1080     this._visited = {};
1081     this._positioned = {};
1082 
1083     // Collect any guids that we need to retrieve from the server.
1084     this._checkForData(this._cachedContent.get('guid'), 0);
1085     
1086     // Get the actually deepest level.
1087     var deepest = 1;
1088     for(var g in this._visited)
1089     {
1090       if(this._visited[g].level > deepest) deepest = this._visited[g].level;
1091     }
1092     
1093     // Set it for use in other functions.
1094     this._deepest = deepest;
1095     
1096     // Calculate the radius and the edge length offset.
1097     this._radius = this._h/this.nodeDefaultRadius/deepest*NODEGRAPH_DEFAULT_SCALE/this._z;
1098     this._offset = (this._h/2-(this._h/2*.2))/this._deepest*NODEGRAPH_DEFAULT_SCALE/this._z;
1099 
1100     var fixedGuid = this._cachedContent.get('guid');
1101     
1102     this._lBound = 1000000;
1103     this._rBound = 0;
1104     this._tBound = 1000000;
1105     this._bBound = 0;
1106     
1107     // Calculate the local x and y for the center node.
1108     var lX = this._w/2*NODEGRAPH_DEFAULT_SCALE/this._z;
1109     var lY = this._h/2*NODEGRAPH_DEFAULT_SCALE/this._z;
1110 
1111     // Set the main node's info.
1112     this._positioned[fixedGuid] = 1;
1113     this._nodes[fixedGuid] = {guid: fixedGuid, level: 0, radius: this._radius, sx: lX, sy: lY, ex: lX, ey: lY, isMain: YES, content: this._cachedContent};
1114     
1115     // Recursively coordinates for everything else. Start with theta and delta at 360.
1116     this._generateDataCoords(this._cachedContent.get('guid'), 0, lX, lY, 360, 360);
1117 
1118     // Now retrieve the next level as needed..
1119     if(this._guidsNeeded.length > 0)
1120     {
1121       // console.log("retrieving " +this._guidsNeeded.length + " objects…");
1122       // Now retrieve the next level as needed..
1123       var callBack = function() { this.redrawParamsDidChange(); }.bind(this, this.get('content'));
1124       this.performCustomRequest(this._guidsNeeded, callBack);
1125     }
1126     
1127     // Zoom in to reveal more of the graph it is needed.
1128     
1129     var lBound = this._radius*2;
1130     var rBound = this._w*NODEGRAPH_DEFAULT_SCALE/this._z-this._radius*4;
1131     var tBound = this._radius;
1132     var bBound = this._h*NODEGRAPH_DEFAULT_SCALE/this._z-this._radius;
1133     var height = (bBound-tBound);
1134     var width = (rBound-lBound);
1135     
1136     var iLBound = this._lBound-this._radius;
1137     var iRBound = this._rBound+this._radius;
1138     var iTBound = this._tBound-this._radius;
1139     var iBBound = this._bBound+this._radius;
1140     var iHeight = (iBBound-iTBound);
1141     var iWidth = (iRBound-iLBound);
1142     
1143     
1144     if(this._lastDeepest != deepest)
1145     {
1146       var zoomValue = Math.min(width/iWidth, height/iHeight);
1147 
1148       zoomValue = zoomValue-zoomValue%.5;
1149       var pctX =  (1- (width-iWidth/2-iLBound)/width);
1150       var pctY =  (1- (height-iHeight/2-iTBound)/height);
1151 
1152       if(this.get('delegate')) this.get('delegate').zoomToLocation(pctX, pctY, zoomValue);
1153     }
1154 
1155       // Render the SVG.
1156     this._renderGraph();
1157     
1158     this._lastDeepest = deepest;
1159   },
1160   
1161   /**
1162     Render the SVG.
1163 
1164   */
1165   _renderGraph: function()
1166   {
1167     // Get the objects.
1168     var nodes = this._nodes;
1169     var subjectArr = []; 
1170     
1171     // Calculate the zoom value used.
1172     var zoomVal = NODEGRAPH_DEFAULT_SCALE/this._deepest;
1173 
1174     // Load custom values.
1175     var showEdgeLabel = this.showEdgeLabel;
1176     var edgeMinWidth = this.edgeMinWidth;
1177     var useEdgeWeightWidth = this.useEdgeWeightWidth;
1178     var edgeTextPosOffset = this.edgeTextPosOffset;
1179     var edgeTextPosOffsetI = 1-this.edgeTextPosOffset;
1180     var nodeTextRatio = this.nodeTextRatio;
1181     var nodeXYRatio = this.nodeXYRatio;
1182     var newNodeStartRadius = 4*zoomVal;
1183 
1184     var nodeBorderWidth = this.nodeBorderWidth*zoomVal;
1185     if(nodeBorderWidth != this._last_nodeBorderWidth)
1186     {
1187       
1188       subjectArr.push({
1189         elm: this.svgNodes, 
1190         props: {'stroke-width': {start: this._last_nodeBorderWidth, end: nodeBorderWidth}},
1191         duration: 250,
1192         isSVG: YES
1193        });
1194       this._last_nodeBorderWidth = nodeBorderWidth;
1195     }
1196     
1197     // Set the temp arrays. Used later to discard unused nodes.
1198     var t_svgNodes = [];
1199     var t_svgEdges = [];
1200     var t_svgEdgesText = [];
1201     var t_svgText = [];
1202 
1203     // Pull the current nodes from the cache.
1204     var svgNodes = this._svgNodesCache;
1205     var svgEdges = this._svgEdgesCache;
1206     var svgText = this._svgTextCache;
1207     var svgEdgesText = this._svgEdgesTextCache;
1208 
1209     // For each explored node, render it and the associated edges.
1210     for(var key in nodes)
1211     {
1212       var node1 = nodes[key];
1213       var object = node1.content;
1214       if(!object) continue;
1215       
1216       // Get the relations for the edges.
1217       var relations = this.findCustomObjectAttr(object);
1218 
1219       // Calculate things now that will be static.
1220       var node1sx = node1.sx;
1221       var node1sy = node1.sy;
1222       var node1ex = node1.ex;
1223       var node1ey = node1.ey;
1224       var ry = node1.radius;
1225       var rx = ry*nodeXYRatio;
1226       
1227       var fontSize = ry/nodeTextRatio;
1228       var oldFontSize = ry/nodeTextRatio;
1229 
1230       var newNode = (!svgNodes[key]);
1231       var x = !newNode ? node1ex : node1sx;
1232       var y = (!newNode ? node1ey : node1sy);
1233       
1234       // Set up the defaults for the node and text.
1235       var params = {cx: x, cy: y, ry: ry, rx: rx, type: 'unfocused', guid: key};
1236       var txtParams = {x: x, y: y, 'font-size': fontSize, type: 'unfocused', guid: key};
1237       
1238       // If the node exists, see if it needs to transition or not.
1239       if(!newNode)
1240       {
1241         var oldParams = svgNodes[key].params;
1242         var nodeElm = svgNodes[key].elm;
1243         var textElm = svgText[key].elm;
1244         
1245         // animation properties.
1246         var anim = {};
1247         var tAnim = {};
1248         var doAnim = NO;
1249         // var tLen = svgText[key].tLen;
1250         
1251         if(oldParams.cx != x) { anim.cx = {start: oldParams.cx, end: x}; tAnim.x = {start: oldParams.cx, end: x}; doAnim = YES; }
1252         if(oldParams.cy != y) { oldFontSize = oldParams.ry/2; anim.cy = {start: oldParams.cy, end: y}; tAnim.y = {start: oldParams.cy, end: y}; doAnim = YES; }
1253         if(oldParams.ry != ry) { 
1254           anim.rx = {start: oldParams.rx, end: rx}; 
1255           anim.ry = {start: oldParams.ry, end: ry}; 
1256           tAnim['font-size'] = {start: oldFontSize, end: fontSize}; 
1257           doAnim = YES; 
1258         }
1259       
1260         if(doAnim)
1261         {
1262           subjectArr.push({
1263             elm: nodeElm, 
1264             props: anim,
1265             duration: 250,
1266             isSVG: YES
1267            });
1268           subjectArr.push({
1269            elm: textElm, 
1270            props: tAnim,
1271            duration: 250,
1272            isSVG: YES
1273           });
1274         }
1275       }
1276       
1277       // If the node doesn't exist, we need to create it.
1278       else
1279       {
1280         // Create the node.
1281         svgNodes[key] = {elm: this._createSVGElement('ellipse', params,{}, NODE), params: params};
1282 
1283         // Create the text.
1284         svgText[key] = {elm: this._createSVGElement('text', txtParams, {}, TEXT), params: txtParams};
1285   
1286         svgText[key].elm.appendChild(document.createTextNode(this.findCustomObjectLabel(object)));
1287         
1288         // Animate it in.
1289         subjectArr.push({
1290           elm: svgNodes[key].elm, 
1291           props:
1292           {
1293             cx: {start: x, end: node1ex},
1294             cy: {start: y, end: node1ey},
1295             ry: {start: newNodeStartRadius, end: ry},
1296             rx: {start: newNodeStartRadius, end: rx}
1297           },
1298           duration: 250,
1299           isSVG: YES
1300          });
1301         subjectArr.push({
1302          elm: svgText[key].elm, 
1303          props:
1304          {
1305            x: {start: x, end: node1ex},
1306            y: {start: y, end: node1ey}
1307          },
1308          duration: 250,
1309          isSVG: YES
1310         });
1311       }
1312       
1313       // Save back the end params.
1314       svgNodes[key].params.rx = rx;
1315       svgNodes[key].params.ry = ry;
1316       svgText[key].params.x = node1ex-2*zoomVal;
1317       svgText[key].params.y = node1ey-2*zoomVal;
1318 
1319       // Push it on the temp array so that we keep it.
1320       t_svgNodes.push(key);
1321       t_svgText.push(key);
1322 
1323       
1324       if(this.showEdges)
1325       {
1326         
1327         // For each of the relations, draw the edges if we need to.
1328         var relLen = relations.length;
1329         for(var i=0; i<relLen; i++)
1330         {
1331           // Get the relation.
1332           var k = this.getGuidForRelation(relations[i]);
1333           var node2 = nodes[k];
1334 
1335           var key1 = k+key;
1336           var key2 = key+k;
1337         
1338           // If the relation exists.
1339           if(node2)
1340           {
1341             // Save variables.
1342             var node2sx = node2.sx;
1343             var node2sy = node2.sy;
1344             var node2ex = node2.ex;
1345             var node2ey = node2.ey;
1346 
1347             if(useEdgeWeightWidth)
1348             {
1349               // Calculate edge weight.
1350               var weight = this.calcRelationWeight(relations[i]); 
1351 
1352               // Set the width of the edge.
1353               var width = Math.max(edgeMinWidth, Math.min(Math.floor(weight/5+2), 6))*zoomVal;
1354             }
1355             else
1356             {
1357               var width = edgeMinWidth*zoomVal;
1358             }
1359 
1360             // Pick the start and end.
1361             var x1 = (newNode) ? node1sx : node1ex;
1362             var y1 = (newNode) ? node1sy : node1ey;
1363             var x2 = (!svgNodes[k]) ? node2sx : node2ex;
1364             var y2 = (!svgNodes[k]) ? node2sy : node2ey;
1365           
1366             if(showEdgeLabel)
1367             {
1368 
1369               // Mid point label end point.
1370               var emx = (node1ex*edgeTextPosOffsetI+node2ex*(edgeTextPosOffset));
1371               var emy = (node1ey*edgeTextPosOffsetI+node2ey*(edgeTextPosOffset))+zoomVal*3;
1372             }
1373 
1374             var params = {x1: x1, y1: y1, x2: x2, y2: y2, 'stroke-width': width};
1375 
1376             // If there is an edge already that hasn't been processed yet, process it.
1377             if((svgEdges[key1] || svgEdges[key2]) && t_svgEdges.indexOf(key1) == -1 && t_svgEdges.indexOf(key2) == -1)
1378             {
1379             
1380               // Get the edge info.
1381               var oldParams = svgEdges[key1].params;
1382               var edgeElm = svgEdges[key1].elm;
1383 
1384               // animation properties.
1385               var anim = {};
1386               var doAnim = NO;
1387               if(oldParams.x1 != x1) { anim.x1 = {start: oldParams.x1, end: x1}; doAnim = YES; }
1388               if(oldParams.x2 != x2) { anim.x2 = {start: oldParams.x2, end: x2}; doAnim = YES; }
1389               if(oldParams.y1 != y1) { anim.y1 = {start: oldParams.y1, end: y1}; doAnim = YES; }
1390               if(oldParams.y2 != y2) { anim.y2 = {start: oldParams.y2, end: y2}; doAnim = YES; }
1391 
1392               // direct set properities.
1393               edgeElm.setAttributeNS(null, "stroke-width", width);
1394 
1395               if(doAnim)
1396               {
1397                 subjectArr.push({
1398                   elm: edgeElm, 
1399                   props: anim,
1400                   duration: 250,
1401                   isSVG: YES
1402                  });
1403 
1404                  if(showEdgeLabel)
1405                  {
1406                    subjectArr.push({
1407                     elm: svgEdgesText[key1].elm, 
1408                     props: {
1409                       x: {start: (oldParams.x1*edgeTextPosOffsetI+oldParams.x2*(edgeTextPosOffset)), end: emx},
1410                       y: {start: (oldParams.y1*edgeTextPosOffsetI+oldParams.y2*(edgeTextPosOffset))+3*zoomVal, end: emy},
1411                       'font-size': {start: oldFontSize, end: fontSize}
1412                     },
1413                     duration: 250,
1414                     isSVG: YES
1415                    });
1416                  }
1417               }
1418               svgEdges[key1].params.x1 = svgEdges[key2].params.x1 = x1;
1419               svgEdges[key1].params.y1 = svgEdges[key2].params.y1 = y1;
1420               svgEdges[key1].params.x2 = svgEdges[key2].params.x2 = x2;
1421               svgEdges[key1].params.y2 = svgEdges[key2].params.y2 = y2;
1422             }
1423           
1424             // If you need to create a new one, do the following.
1425             else if(t_svgEdges.indexOf(key1) == -1 && t_svgEdges.indexOf(key2) == -1)
1426             {
1427             
1428               x1 = (newNode) ? node1sx : svgNodes[key].params.cx;
1429               y1 = (newNode) ? node1sy : svgNodes[key].params.cy;
1430             
1431               if(showEdgeLabel)
1432               {
1433                 var smx = (x1*edgeTextPosOffsetI+x2*(edgeTextPosOffset));
1434                 var smy = (y1*edgeTextPosOffsetI+y2*(edgeTextPosOffset))+3*zoomVal;
1435                 var textParams = {x: smx, y: smy, 'font-size': fontSize};
1436                 svgEdgesText[key1] = svgEdgesText[key2] = {elm: this._createSVGElement('text', textParams, {}, EDGETEXT), params: params};
1437                 svgEdgesText[key1].elm.appendChild(document.createTextNode(weight));
1438               }
1439 
1440               svgEdges[key1] = svgEdges[key2] = {elm: this._createSVGElement('line', params,{}, EDGE), params: params};
1441 
1442               var anim = {};
1443               var doAnim = NO;
1444 
1445               if(node1ex != x1) { anim.x1 = {start: x1, end: node1ex}; doAnim = YES; }
1446               if(node1ey != y1) { anim.y1 = {start: y1, end: node1ey}; doAnim = YES;  }
1447               if(node2ex != x2) { anim.x2 = {start: x2, end: node2ex}; doAnim = YES;  }
1448               if(node2ey != y2) { anim.y2 = {start: y2, end: node2ey}; doAnim = YES;  }
1449 
1450               if(doAnim)
1451               {
1452               
1453                 subjectArr.push({
1454                  elm: svgEdges[key1].elm, 
1455                  props: anim,
1456                  duration: 250,
1457                  isSVG: YES
1458                 });
1459               
1460                 if(showEdgeLabel)
1461                 {
1462                   subjectArr.push({
1463                    elm: svgEdgesText[key1].elm, 
1464                    props: {
1465                      x: {start: smx, end: emx},
1466                      y: {start: smy, end: emy},
1467                      'font-size': {start: oldFontSize, end: fontSize}
1468                    },
1469                    duration: 250,
1470                    isSVG: YES
1471                   });
1472                 }
1473               }
1474               svgEdges[key1].params.x1 = svgEdges[key2].params.x1 = node1ex;
1475               svgEdges[key1].params.y1 = svgEdges[key2].params.y1 = node1ey;
1476               svgEdges[key1].params.x2 = svgEdges[key2].params.x2 = node2ex;
1477               svgEdges[key1].params.y2 = svgEdges[key2].params.y2 = node2ey;
1478             }
1479             if(showEdgeLabel)
1480             {
1481               svgEdgesText[key1].params.x = svgEdgesText[key2].params.x = emx;
1482               svgEdgesText[key1].params.y = svgEdgesText[key2].params.y = emy;
1483             }
1484             t_svgEdges.push(key2);
1485             t_svgEdges.push(key1);
1486             if(showEdgeLabel)
1487             {
1488               t_svgEdgesText.push(key2);
1489               t_svgEdgesText.push(key1);
1490             }
1491           }
1492         }
1493       }
1494       svgNodes[key].params.cx = node1ex;
1495       svgNodes[key].params.cy = node1ey;
1496     }
1497     NodeGraph.animator.addSubjects(subjectArr, this.svgCanvas, this._pH);
1498     
1499     // Clear elements as needed.
1500     this._clearSVGElms(svgNodes, t_svgNodes, this.svgNodes);
1501     this._clearSVGElms(svgEdges, t_svgEdges, this.svgEdges);
1502     this._clearSVGElms(svgText, t_svgText, this.svgText);
1503     this._clearSVGElms(svgEdgesText, t_svgEdgesText, this.svgEdgesText);
1504 
1505     // Save back the cache.
1506     this._svgTextCache = svgText;
1507     this._svgEdgesTextCache = svgEdgesText;
1508     this._svgEdgesCache = svgEdges;
1509     this._svgNodesCache = svgNodes;
1510 
1511   },
1512 
1513   /**
1514     Check what data needs to be retrieved.
1515   
1516   
1517     @param guid {string} The guid of the item.
1518     @param level {Integer} The current level.
1519   */
1520   _checkForData: function(guid, level)
1521   {
1522     // Get the object.
1523     var object = this.findCustomObject(guid);
1524     
1525     // If the object exists, then get its relations.  Also enter if you are at a lower level.
1526     if(object && (!this._visited[guid] || this._visited[guid].level > level))
1527     {
1528       var rels = this.findCustomObjectAttr(object);
1529       var childCount = rels.length;
1530       var nextLvl = level+1;
1531       if(!this._visited[guid])
1532       {
1533         this._visited[guid] = {level: level,  count: 1, childCount: childCount};
1534       }
1535       else
1536       {
1537         var lLevel = this._visited[guid].level;
1538         this._visited[guid].level = (level < lLevel) ? level : lLevel;
1539         this._visited[guid].count++;
1540       }
1541       
1542       // If you need to explore deeper, do so.
1543       if(nextLvl <= this._cached_depth)
1544       {
1545         for(var i=0; i<childCount; i++)
1546         {
1547           if(this.relationMeetsCustomThreshold(rels[i]))
1548           {
1549             this._checkForData(this.getGuidForRelation(rels[i]), nextLvl);   
1550           }
1551         }
1552       }
1553     }
1554     else if(this._visited[guid])
1555     {
1556       this._visited[guid].count++;
1557     }
1558     else
1559     {
1560       if(!this._retrieved[guid])
1561       {
1562         this._guidsNeeded.push(guid);
1563         this._retrieved[guid] = 1;
1564       }
1565     }
1566   },
1567   
1568   /**
1569     Generate the coordinates needed.
1570   
1571   
1572     @param guid {string} The guid of the item.
1573     @param level {Integer} The current level.
1574     @param x {Integer} The x coordinate in pixels.
1575     @param y {Integer} The y coordinate in pixels.
1576     @param delta {Integer} The delta in degrees.
1577     @param theta {Integer} The delta in theta.
1578   */
1579   _generateDataCoords: function(guid, level, x, y, delta, theta)
1580   {
1581     // Get the object.
1582     var object = this.findCustomObject(guid);
1583     var n = this._nodes[guid];
1584     if(object && n)
1585     {
1586       n.content = object;
1587       var nextLvl = level+1;
1588       var r = this.findCustomObjectAttr(object);
1589       var rels = [];
1590       var pos = [];
1591       var max = 0;
1592       var maxChildren = 0;
1593       var rLen = r.length;
1594       
1595       // Look through the relation array and find the items have been visited but not positioned. 
1596       // Save the max values for the number of children and weight.
1597       for(var i=0; i<rLen; i++)
1598       {
1599         var rel = r[i];
1600         var g = this.getGuidForRelation(rel);
1601         if(this._visited[g] &&  !this._positioned[g]) 
1602         {
1603           if(this._visited[g].childCount > maxChildren) 
1604           {
1605             maxChildren = this._visited[g].childCount;
1606           }
1607           var w = this.calcRelationWeight(rel);
1608           if(w > max) 
1609           {
1610             max = w;
1611           }
1612           rels.push([g, w]);
1613         }
1614       }
1615 
1616       // If there are children, render them.
1617       var childCount = rels.length;
1618       if(childCount > 0)
1619       {
1620         var dl = (level == 0) ? delta/childCount : delta/(childCount-1);
1621         var th = (level == 0 || childCount == 1) ? theta : theta-delta/2;
1622         var sTh = th;
1623         var offset = this._offset;
1624         var radius = Math.min(this._radius,(2*Math.PI*offset)*(delta/360)/childCount/4);
1625         var PI180 = Math.PI/180;
1626         var deepest = this._deepest;
1627         var lOffset = (offset*NODEGRAPH_OFFSET_PERCENT);
1628 
1629         // For each child, calculate the position information.
1630         for(var i=0; i<childCount; i++)
1631         {
1632           pos[i] = [];
1633           var g = rels[i][0];
1634           var visitLvl = this._visited[g].level;
1635           if(visitLvl == nextLvl && !this._positioned[g])
1636           {
1637             var o = (visitLvl != deepest) ? lOffset*(1-this._visited[g].childCount/maxChildren) : 0;
1638             var locOffset = (NODEGRAPH_USE_CHILDREN_OFFSET) ? (offset-o) : offset;
1639 
1640             var angle = th*PI180;
1641             var lx = x + locOffset*Math.cos(angle);
1642             var ly = y + locOffset*Math.sin(angle);
1643             if(lx < this._lBound) this._lBound = lx;
1644             if(lx > this._rBound) this._rBound = lx;
1645             if(ly < this._tBound) this._tBound = ly;
1646             if(ly > this._bBound) this._bBound = ly;
1647             
1648             this._nodes[g] = {guid: g, level: visitLvl, ex: lx, ey: ly, sx: x, sy: y, radius: radius};
1649             this._positioned[g] = YES;
1650             pos[i] = [lx, ly];
1651             th+=dl;
1652           }
1653         }
1654         
1655         // If you should render the next, level iterate over the children again and go deeper.
1656         if(nextLvl <= this._cached_depth)
1657         {
1658           for(var i=0; i<childCount; i++)
1659           {
1660             this._generateDataCoords(rels[i][0], nextLvl, pos[i][0], pos[i][1], Math.min(dl%360*1.5, 90), sTh%360);  
1661             sTh+=dl;
1662           }
1663         }
1664       }
1665     }
1666   },
1667   
1668   /**
1669     Clear all SVG elements.
1670   
1671   */
1672   _clearSVG: function()
1673   {
1674     var svg = this.svgNodes;
1675     while (svg.firstChild) 
1676     {
1677       svg.removeChild(svg.firstChild);
1678     }
1679     var svg = this.svgEdges;
1680     while (svg.firstChild) 
1681     {
1682       svg.removeChild(svg.firstChild);
1683     }
1684     var svg = this.svgText;
1685     while (svg.firstChild) 
1686     {
1687       svg.removeChild(svg.firstChild);
1688     }
1689     var svg = this.svgEdgesText;
1690     while (svg.firstChild) 
1691     {
1692       svg.removeChild(svg.firstChild);
1693     }
1694   },
1695 
1696   /**
1697     Create a new SVG element.
1698   
1699     @param type {string} The node type.
1700     @param attributes {Object} The attributes hash.
1701     @param events {Object} The events hash.
1702     @param kind {Integer} Flag that specifies what SVG element to attach the element to.
1703     
1704     @returns {Array} {DOM Element} The SVG element that was created.
1705   */
1706   _createSVGElement: function(type, attributes, events, kind)
1707   {
1708     var elm = document.createElementNS("http://www.w3.org/2000/svg", type);
1709     if(attributes)
1710     {
1711       for(var key in attributes)
1712       {
1713         if(isNaN(attributes[key]) || key.indexOf('opacity') != -1)
1714         {
1715            elm.setAttributeNS(null, key, attributes[key]);
1716         }
1717         else
1718         {
1719            elm.setAttributeNS(null, key, Math.floor(attributes[key]));
1720         }
1721       }
1722     }
1723     
1724     switch(kind)
1725     {
1726       case NODE:
1727         this.svgNodes.appendChild(elm);
1728         break;
1729       case EDGE:
1730         this.svgEdges.appendChild(elm);
1731         break;
1732       case TEXT:
1733         this.svgText.appendChild(elm);
1734         break;
1735       case EDGETEXT:
1736         this.svgEdgesText.appendChild(elm);
1737         break;
1738       default:
1739         break;
1740     }
1741      return elm;
1742   },
1743   
1744   /**
1745     Clear SVG elements selectively.
1746   
1747     @param has {Object} The old DOM node cache.
1748     @param array {Array} The array of currently rendered DOM nodes.
1749     @param {DOM Element} svg The DOM element to attach to.
1750   */
1751   _clearSVGElms: function(hash, array, svg)
1752   {
1753     for(var key in hash)
1754     {
1755       if(array.indexOf(key) == -1)
1756       {
1757         try
1758         {
1759           svg.removeChild(hash[key].elm);
1760           delete hash[key].elm;
1761           delete hash[key].params;
1762           delete hash[key];
1763         }
1764         catch(e)
1765         {
1766           try
1767           {
1768             delete hash[key];
1769           }
1770           catch(e){}
1771         }
1772       }
1773     }
1774   },
1775   
1776   /**
1777      Initalization function.
1778 
1779      Set up the SVG DOM Nodes.
1780 
1781   */
1782   init: function()
1783   {
1784     sc_super();
1785     
1786     if(!this.get('delegate')) console.log("NodeGraph: Delegate not specified. Certain functionality like complex zooming is NOT supported without using a delegate.");
1787 
1788     // Set up svg canvas.
1789     this.svgCanvas = document.createElementNS("http://www.w3.org/2000/svg", 'svg');
1790     this.rootElement.appendChild(this.svgCanvas);
1791 
1792     this.svgFitGroup = document.createElementNS("http://www.w3.org/2000/svg", 'g');
1793     this.svgCanvas.appendChild(this.svgFitGroup);
1794 
1795     this.svgGroup = document.createElementNS("http://www.w3.org/2000/svg", 'g');
1796     this.svgFitGroup.appendChild(this.svgGroup);
1797 
1798     this.svgEdges = document.createElementNS("http://www.w3.org/2000/svg", 'g');
1799     this.svgGroup.appendChild(this.svgEdges);
1800 
1801     // Set up the edge default styling.
1802     this.svgEdges.setAttributeNS(null, "stroke", this.edgeColor);
1803    
1804     if(this.edgeOpacity != 1)
1805     {
1806       this.svgEdges.setAttributeNS(null, 'stroke-opacity', this.edgeOpacity);
1807     }
1808 
1809     this.svgEdgesText = document.createElementNS("http://www.w3.org/2000/svg", 'g');
1810     this.svgGroup.appendChild(this.svgEdgesText);
1811 
1812     // Set up edge text default styles.
1813 
1814     this.svgEdgesText.setAttributeNS(null, 'fill', this.edgeTextColor);
1815     this.svgEdgesText.setAttributeNS(null, 'text-anchor', 'middle');
1816     this.svgEdgesText.setAttributeNS(null, 'style', "cursor: default");
1817 
1818     this.svgNodes = document.createElementNS("http://www.w3.org/2000/svg", 'g');
1819     this.svgGroup.appendChild(this.svgNodes);
1820     
1821     // Set up node default styling.
1822     this.svgNodes.setAttributeNS(null, 'fill', this.nodeColor);
1823     this.svgNodes.setAttributeNS(null, 'stroke', this.nodeBorderColor);
1824     this._last_nodeBorderWidth = this.nodeBorderWidth*NODEGRAPH_DEFAULT_SCALE;
1825     this.svgNodes.setAttributeNS(null, 'stroke-width', this._last_nodeBorderWidth);
1826 
1827 
1828     if(this.nodeOpacity != 1)
1829     {
1830       this.svgNodes.setAttributeNS(null, 'opacity', this.nodeOpacity);
1831     }
1832 
1833     this.svgText = document.createElementNS("http://www.w3.org/2000/svg", 'g');
1834     this.svgGroup.appendChild(this.svgText);
1835     
1836     // Set up the text default styling.
1837     this.svgText.setAttributeNS(null, 'fill', this.nodeTextColor);
1838     this.svgText.setAttributeNS(null, 'text-anchor', 'middle');
1839     this.svgText.setAttributeNS(null, 'style', "cursor: default");
1840 
1841     // If there is a delegate, then notify it that the init is completed.
1842     if(this.get('delegate')) this.get('delegate').finishInitForGraph(this);
1843   }
1844   
1845 }) ;
1846 
1847