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