4176 lines
		
	
	
		
			163 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
			
		
		
	
	
			4176 lines
		
	
	
		
			163 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
| /***********************************************************************************************************************
 | |
|  * D3 Force Implementation, Playground and Oracle APEX Plugin: https://github.com/ogobrecht/d3-force-apex-plugin
 | |
|  *
 | |
|  * @param pDomContainerId         The DOM container, where the graph should be rendered
 | |
|  * @param pOptions                The configuration object to configure the graph, see also the API reference
 | |
|  * @param pApexPluginId           If used as APEX plugin, this ID is the plugin identifier for the AJAX calls
 | |
|  * @param pApexPageItemsToSubmit  If used as APEX plugin, these page items are submitted before the AJAX call
 | |
|  * @returns {graph}               The graph function is returned to allow method chaining
 | |
|  */
 | |
| function netGobrechtsD3Force(pDomContainerId, pOptions, pApexPluginId, pApexPageItemsToSubmit) { // jshint ignore:line
 | |
|     /* exported netGobrechtsD3Force */
 | |
|     /* globals apex, $v, navigator, d3, document, console, window, clearInterval, ActiveXObject, DOMParser */
 | |
| 
 | |
|     "use strict";
 | |
| 
 | |
|     // setup graph variable
 | |
|     var v = {
 | |
|         "conf": {},
 | |
|         "confDefaults": {},
 | |
|         "data": {},
 | |
|         "dom": {},
 | |
|         "events": {},
 | |
|         "lib": {},
 | |
|         "main": {},
 | |
|         "status": {},
 | |
|         "tools": {},
 | |
|         "version": "2.0.3"
 | |
|     };
 | |
| 
 | |
| 
 | |
|     /*******************************************************************************************************************
 | |
|      * MAIN: INIT
 | |
|      */
 | |
|     v.main.init = function() {
 | |
| 
 | |
|         // save parameter for later use
 | |
|         v.dom.containerId = pDomContainerId || "D3Force" + Math.floor(Math.random() * 1000000);
 | |
|         v.confUser = pOptions || {};
 | |
|         v.status.apexPluginId = pApexPluginId;
 | |
|         //v.status.apexPageItemsToSubmit = (pApexPageItemsToSubmit === "" ? false :
 | |
|         //    pApexPageItemsToSubmit.replace(/\s/g,"").split(","));
 | |
| 
 | |
|         // initialize the graph function
 | |
|         v.main.setupConfiguration();
 | |
|         v.main.setupDom();
 | |
|         v.main.setupFunctionReferences();
 | |
|     };
 | |
| 
 | |
| 
 | |
|     /*******************************************************************************************************************
 | |
|      * MAIN: SETUP CONFIGURATION
 | |
|      */
 | |
|     v.main.setupConfiguration = function() {
 | |
|         /* jshint -W074, -W071 */
 | |
|         // configure debug mode for APEX, can be overwritten by users configuration object
 | |
|         // or later on with the API debug method
 | |
|         v.conf.debug = (v.status.apexPluginId && apex.jQuery("#pdebug").length === 1);
 | |
|         v.status.debugPrefix = "D3 Force in DOM container #" + v.dom.containerId + ": ";
 | |
| 
 | |
|         // status variables
 | |
|         v.status.customize = false;
 | |
|         v.status.customizeCurrentMenu = "nodes";
 | |
|         v.status.customizeCurrentTabPosition = null;
 | |
|         v.status.forceTickCounter = 0;
 | |
|         v.status.forceStartTime = 0;
 | |
|         v.status.forceRunning = false;
 | |
|         v.status.graphStarted = false;
 | |
|         v.status.graphRendering = false;
 | |
|         v.status.graphReady = false;
 | |
|         v.status.graphOldPositions = null;
 | |
|         v.status.sampleData = false;
 | |
| 
 | |
|         // default configuration
 | |
|         v.confDefaults.minNodeRadius = {
 | |
|             "display": true,
 | |
|             "relation": "node",
 | |
|             "type": "number",
 | |
|             "val": 6,
 | |
|             "options": [12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1]
 | |
|         };
 | |
|         v.confDefaults.maxNodeRadius = {
 | |
|             "display": true,
 | |
|             "relation": "node",
 | |
|             "type": "number",
 | |
|             "val": 18,
 | |
|             "options": [36, 34, 32, 30, 28, 26, 24, 22, 20, 18, 16, 14, 12]
 | |
|         };
 | |
|         v.confDefaults.colorScheme = {
 | |
|             "display": true,
 | |
|             "relation": "node",
 | |
|             "type": "text",
 | |
|             "val": "color20",
 | |
|             "options": ["color20", "color20b", "color20c", "color10", "direct"]
 | |
|         };
 | |
|         v.confDefaults.dragMode = {
 | |
|             "display": true,
 | |
|             "relation": "node",
 | |
|             "type": "bool",
 | |
|             "val": true,
 | |
|             "options": [true, false]
 | |
|         };
 | |
|         v.confDefaults.pinMode = {
 | |
|             "display": true,
 | |
|             "relation": "node",
 | |
|             "type": "bool",
 | |
|             "val": false,
 | |
|             "options": [true, false]
 | |
|         };
 | |
|         v.confDefaults.nodeEventToStopPinMode = {
 | |
|             "display": true,
 | |
|             "relation": "node",
 | |
|             "type": "text",
 | |
|             "val": "contextmenu",
 | |
|             "options": ["none", "dblclick", "contextmenu"]
 | |
|         };
 | |
|         v.confDefaults.onNodeContextmenuPreventDefault = {
 | |
|             "display": true,
 | |
|             "relation": "node",
 | |
|             "type": "bool",
 | |
|             "val": false,
 | |
|             "options": [true, false]
 | |
|         };
 | |
|         v.confDefaults.nodeEventToOpenLink = {
 | |
|             "display": true,
 | |
|             "relation": "node",
 | |
|             "type": "text",
 | |
|             "val": "dblclick",
 | |
|             "options": ["none", "click", "dblclick", "contextmenu"]
 | |
|         };
 | |
|         v.confDefaults.nodeLinkTarget = {
 | |
|             "display": true,
 | |
|             "relation": "node",
 | |
|             "type": "text",
 | |
|             "val": "_blank",
 | |
|             "options": ["none", "_blank", "nodeID", "domContainerID"]
 | |
|         };
 | |
|         v.confDefaults.showLabels = {
 | |
|             "display": true,
 | |
|             "relation": "node",
 | |
|             "type": "bool",
 | |
|             "val": true,
 | |
|             "options": [true, false]
 | |
|         };
 | |
|         v.confDefaults.labelsCircular = {
 | |
|             "display": true,
 | |
|             "relation": "node",
 | |
|             "type": "bool",
 | |
|             "val": false,
 | |
|             "options": [true, false]
 | |
|         };
 | |
|         v.confDefaults.labelDistance = {
 | |
|             "display": true,
 | |
|             "relation": "node",
 | |
|             "type": "number",
 | |
|             "val": 12,
 | |
|             "options": [30, 28, 26, 24, 22, 20, 18, 16, 14, 12, 10, 8, 6, 4, 2]
 | |
|         };
 | |
|         v.confDefaults.preventLabelOverlappingOnForceEnd = {
 | |
|             "display": true,
 | |
|             "relation": "node",
 | |
|             "type": "bool",
 | |
|             "val": false,
 | |
|             "options": [true, false]
 | |
|         };
 | |
|         v.confDefaults.labelPlacementIterations = {
 | |
|             "display": true,
 | |
|             "relation": "node",
 | |
|             "type": "number",
 | |
|             "val": 250,
 | |
|             "options": [2000, 1000, 500, 250, 125]
 | |
|         };
 | |
|         v.confDefaults.showTooltips = {
 | |
|             "display": true,
 | |
|             "relation": "node",
 | |
|             "type": "bool",
 | |
|             "val": true,
 | |
|             "options": [true, false]
 | |
|         };
 | |
|         v.confDefaults.tooltipPosition = {
 | |
|             "display": true,
 | |
|             "relation": "node",
 | |
|             "type": "text",
 | |
|             "val": "svgTopRight",
 | |
|             "options": ["node", "svgTopLeft", "svgTopRight"]
 | |
|         };
 | |
|         v.confDefaults.alignFixedNodesToGrid = {
 | |
|             "display": true,
 | |
|             "relation": "node",
 | |
|             "type": "bool",
 | |
|             "val": false,
 | |
|             "options": [true, false]
 | |
|         };
 | |
|         v.confDefaults.gridSize = {
 | |
|             "display": true,
 | |
|             "relation": "node",
 | |
|             "type": "number",
 | |
|             "val": 50,
 | |
|             "options": [150, 140, 130, 120, 110, 100, 90, 80, 70, 60, 50, 40, 30, 20, 10]
 | |
|         };
 | |
| 
 | |
|         v.confDefaults.linkDistance = {
 | |
|             "display": true,
 | |
|             "relation": "link",
 | |
|             "type": "number",
 | |
|             "val": 80,
 | |
|             "options": [120, 110, 100, 90, 80, 70, 60, 50, 40, 30, 20]
 | |
|         };
 | |
|         v.confDefaults.showLinkDirection = {
 | |
|             "display": true,
 | |
|             "relation": "link",
 | |
|             "type": "bool",
 | |
|             "val": true,
 | |
|             "options": [true, false]
 | |
|         };
 | |
|         v.confDefaults.showSelfLinks = {
 | |
|             "display": true,
 | |
|             "relation": "link",
 | |
|             "type": "bool",
 | |
|             "val": true,
 | |
|             "options": [true, false]
 | |
|         };
 | |
|         v.confDefaults.selfLinkDistance = {
 | |
|             "display": true,
 | |
|             "relation": "link",
 | |
|             "type": "number",
 | |
|             "val": 20,
 | |
|             "options": [30, 28, 26, 24, 22, 20, 18, 16, 14, 12, 10, 8]
 | |
|         };
 | |
| 
 | |
|         v.confDefaults.useDomParentWidth = {
 | |
|             "display": true,
 | |
|             "relation": "graph",
 | |
|             "type": "bool",
 | |
|             "val": false,
 | |
|             "options": [true, false]
 | |
|         };
 | |
|         v.confDefaults.width = {
 | |
|             "display": true,
 | |
|             "relation": "graph",
 | |
|             "type": "number",
 | |
|             "val": 1200,
 | |
|             "options": [1200, 1150, 1100, 1050, 1000, 950, 900, 850, 800, 750, 700, 650, 600, 550, 500, 450, 400, 350,
 | |
|                 300
 | |
|             ]
 | |
|         };
 | |
|         v.confDefaults.height = {
 | |
|             "display": true,
 | |
|             "relation": "graph",
 | |
|             "type": "number",
 | |
|             "val": 650,
 | |
|             "options": [1200, 1150, 1100, 1050, 1000, 950, 900, 850, 800, 750, 700, 650, 600, 550, 500, 450, 400, 350,
 | |
|                 300
 | |
|             ]
 | |
|         };
 | |
|         v.confDefaults.setDomParentPaddingToZero = {
 | |
|             "display": true,
 | |
|             "relation": "graph",
 | |
|             "type": "bool",
 | |
|             "val": false,
 | |
|             "options": [true, false]
 | |
|         };
 | |
|         v.confDefaults.showBorder = {
 | |
|             "display": true,
 | |
|             "relation": "graph",
 | |
|             "type": "bool",
 | |
|             "val": true,
 | |
|             "options": [true, false]
 | |
|         };
 | |
|         v.confDefaults.showLegend = {
 | |
|             "display": true,
 | |
|             "relation": "graph",
 | |
|             "type": "bool",
 | |
|             "val": true,
 | |
|             "options": [true, false]
 | |
|         };
 | |
|         v.confDefaults.showLoadingIndicatorOnAjaxCall = {
 | |
|             "display": true,
 | |
|             "relation": "graph",
 | |
|             "type": "bool",
 | |
|             "val": true,
 | |
|             "options": [true, false]
 | |
|         };
 | |
|         v.confDefaults.lassoMode = {
 | |
|             "display": true,
 | |
|             "relation": "graph",
 | |
|             "type": "bool",
 | |
|             "val": false,
 | |
|             "options": [true, false]
 | |
|         };
 | |
|         v.confDefaults.zoomMode = {
 | |
|             "display": true,
 | |
|             "relation": "graph",
 | |
|             "type": "bool",
 | |
|             "val": false,
 | |
|             "options": [true, false]
 | |
|         };
 | |
|         v.confDefaults.minZoomFactor = {
 | |
|             "display": true,
 | |
|             "relation": "graph",
 | |
|             "type": "number",
 | |
|             "val": 0.2,
 | |
|             "options": [1.0, 0.9, 0.8, 0.7, 0.6, 0.5, 0.4, 0.3, 0.2, 0.1]
 | |
|         };
 | |
|         v.confDefaults.maxZoomFactor = {
 | |
|             "display": true,
 | |
|             "relation": "graph",
 | |
|             "type": "number",
 | |
|             "val": 5,
 | |
|             "options": [10, 9, 8, 7, 6, 5, 4, 3, 2, 1]
 | |
|         };
 | |
|         v.confDefaults.transform = {
 | |
|             "display": false,
 | |
|             "relation": "graph",
 | |
|             "type": "object",
 | |
|             "val": {
 | |
|                 "translate": [0, 0],
 | |
|                 "scale": 1
 | |
|             }
 | |
|         };
 | |
|         v.confDefaults.autoRefresh = {
 | |
|             "display": true,
 | |
|             "relation": "graph",
 | |
|             "type": "bool",
 | |
|             "val": false,
 | |
|             "options": [true, false]
 | |
|         };
 | |
|         v.confDefaults.refreshInterval = {
 | |
|             "display": true,
 | |
|             "relation": "graph",
 | |
|             "type": "number",
 | |
|             "val": 5000,
 | |
|             "options": [60000, 30000, 15000, 10000, 5000, 2500]
 | |
|         };
 | |
|         v.confDefaults.chargeDistance = {
 | |
|             "display": false,
 | |
|             "relation": "graph",
 | |
|             "type": "number",
 | |
|             "val": Infinity,
 | |
|             "options": [Infinity, 25600, 12800, 6400, 3200, 1600, 800, 400, 200, 100],
 | |
|             "internal": true
 | |
|         };
 | |
|         v.confDefaults.charge = {
 | |
|             "display": true,
 | |
|             "relation": "graph",
 | |
|             "type": "number",
 | |
|             "val": -350,
 | |
|             "options": [-1000, -950, -900, -850, -800, -750, -700, -650, -600, -550, -500, -450, -400, -350, -300, -250,
 | |
|                 -200, -150, -100, -50, 0],
 | |
|             "internal": true
 | |
|         };
 | |
|         v.confDefaults.gravity = {
 | |
|             "display": true,
 | |
|             "relation": "graph",
 | |
|             "type": "number",
 | |
|             "val": 0.1,
 | |
|             "options": [1.00, 0.95, 0.90, 0.85, 0.80, 0.75, 0.70, 0.65, 0.60, 0.55, 0.50, 0.45, 0.40, 0.35, 0.30, 0.25,
 | |
|                 0.20, 0.15, 0.1, 0.05, 0.00
 | |
|             ],
 | |
|             "internal": true
 | |
|         };
 | |
|         v.confDefaults.linkStrength = {
 | |
|             "display": true,
 | |
|             "relation": "graph",
 | |
|             "type": "number",
 | |
|             "val": 1,
 | |
|             "options": [1.00, 0.95, 0.90, 0.85, 0.80, 0.75, 0.70, 0.65, 0.60, 0.55, 0.50, 0.45, 0.40, 0.35, 0.30, 0.25,
 | |
|                 0.20, 0.15, 0.10, 0.05, 0.00
 | |
|             ],
 | |
|             "internal": true
 | |
|         };
 | |
|         v.confDefaults.friction = {
 | |
|             "display": true,
 | |
|             "relation": "graph",
 | |
|             "type": "number",
 | |
|             "val": 0.9,
 | |
|             "options": [1.00, 0.95, 0.90, 0.85, 0.80, 0.75, 0.70, 0.65, 0.60, 0.55, 0.50, 0.45, 0.40, 0.35, 0.30, 0.25,
 | |
|                 0.20, 0.15, 0.10, 0.05, 0.00
 | |
|             ],
 | |
|             "internal": true
 | |
|         };
 | |
|         v.confDefaults.theta = {
 | |
|             "display": true,
 | |
|             "relation": "graph",
 | |
|             "type": "number",
 | |
|             "val": 0.8,
 | |
|             "options": [1, 0.95, 0.9, 0.85, 0.8, 0.75, 0.7, 0.65, 0.6, 0.55, 0.5, 0.45, 0.4, 0.35, 0.3, 0.25, 0.2, 0.15,
 | |
|                 0.1, 0.05, 0
 | |
|             ],
 | |
|             "internal": true
 | |
|         };
 | |
| 
 | |
|         // create intial configuration
 | |
|         v.conf.debug = (typeof v.confUser.debug !== "undefined" ? v.tools.parseBool(v.confUser.debug) : false);
 | |
| 
 | |
|         v.conf.minNodeRadius = v.confUser.minNodeRadius || v.confDefaults.minNodeRadius.val;
 | |
|         v.conf.maxNodeRadius = v.confUser.maxNodeRadius || v.confDefaults.maxNodeRadius.val;
 | |
|         v.conf.colorScheme = v.confUser.colorScheme || v.confDefaults.colorScheme.val;
 | |
|         v.conf.dragMode = (typeof v.confUser.dragMode !== "undefined" ? v.tools.parseBool(v.confUser.dragMode) :
 | |
|             v.confDefaults.dragMode.val);
 | |
|         v.conf.pinMode = (typeof v.confUser.pinMode !== "undefined" ? v.tools.parseBool(v.confUser.pinMode) :
 | |
|             v.confDefaults.pinMode.val);
 | |
|         v.conf.nodeEventToStopPinMode = v.confUser.nodeEventToStopPinMode || v.confDefaults.nodeEventToStopPinMode.val;
 | |
|         v.conf.onNodeContextmenuPreventDefault = (typeof v.confUser.onNodeContextmenuPreventDefault !== "undefined" ?
 | |
|             v.tools.parseBool(v.confUser.onNodeContextmenuPreventDefault) :
 | |
|             v.confDefaults.onNodeContextmenuPreventDefault.val);
 | |
|         v.conf.nodeEventToOpenLink = v.confUser.nodeEventToOpenLink || v.confDefaults.nodeEventToOpenLink.val;
 | |
|         v.conf.nodeLinkTarget = v.confUser.nodeLinkTarget || v.confDefaults.nodeLinkTarget.val;
 | |
|         v.conf.showLabels = (typeof v.confUser.showLabels !== "undefined" ? v.tools.parseBool(v.confUser.showLabels) :
 | |
|             v.confDefaults.showLabels.val);
 | |
|         v.conf.labelsCircular = (typeof v.confUser.labelsCircular !== "undefined" ?
 | |
|             v.tools.parseBool(v.confUser.labelsCircular) : v.confDefaults.labelsCircular.val);
 | |
|         v.conf.labelDistance = v.confUser.labelDistance || v.confDefaults.labelDistance.val;
 | |
|         v.conf.preventLabelOverlappingOnForceEnd =
 | |
|             (typeof v.confUser.preventLabelOverlappingOnForceEnd !== "undefined" ?
 | |
|                 v.tools.parseBool(v.confUser.preventLabelOverlappingOnForceEnd) :
 | |
|                 v.confDefaults.preventLabelOverlappingOnForceEnd.val);
 | |
|         v.conf.labelPlacementIterations = v.confUser.labelPlacementIterations ||
 | |
|             v.confDefaults.labelPlacementIterations.val;
 | |
|         v.conf.showTooltips = (typeof v.confUser.showTooltips !== "undefined" ?
 | |
|             v.tools.parseBool(v.confUser.showTooltips) : v.confDefaults.showTooltips.val);
 | |
|         v.conf.tooltipPosition = v.confUser.tooltipPosition || v.confDefaults.tooltipPosition.val;
 | |
|         v.conf.alignFixedNodesToGrid = (typeof v.confUser.alignFixedNodesToGrid !== "undefined" ?
 | |
|             v.tools.parseBool(v.confUser.alignFixedNodesToGrid) : v.confDefaults.alignFixedNodesToGrid.val);
 | |
|         v.conf.gridSize = (v.confUser.gridSize && v.confUser.gridSize > 0 ?
 | |
|             v.confUser.gridSize : v.confDefaults.gridSize.val);
 | |
| 
 | |
|         v.conf.linkDistance = v.confUser.linkDistance || v.confDefaults.linkDistance.val;
 | |
|         v.conf.showLinkDirection = (typeof v.confUser.showLinkDirection !== "undefined" ?
 | |
|             v.tools.parseBool(v.confUser.showLinkDirection) : v.confDefaults.showLinkDirection.val);
 | |
|         v.conf.showSelfLinks = (typeof v.confUser.showSelfLinks !== "undefined" ?
 | |
|             v.tools.parseBool(v.confUser.showSelfLinks) : v.confDefaults.showSelfLinks.val);
 | |
|         v.conf.selfLinkDistance = v.confUser.selfLinkDistance || v.confDefaults.selfLinkDistance.val;
 | |
| 
 | |
|         v.conf.useDomParentWidth = (typeof v.confUser.useDomParentWidth !== "undefined" ?
 | |
|             v.tools.parseBool(v.confUser.useDomParentWidth) : v.confDefaults.useDomParentWidth.val);
 | |
|         v.conf.width = v.confUser.width || v.confDefaults.width.val;
 | |
|         v.conf.height = v.confUser.height || v.confDefaults.height.val;
 | |
|         v.conf.setDomParentPaddingToZero = (typeof v.confUser.setDomParentPaddingToZero !== "undefined" ?
 | |
|             v.tools.parseBool(v.confUser.setDomParentPaddingToZero) : v.confDefaults.setDomParentPaddingToZero.val);
 | |
|         v.conf.showBorder = (typeof v.confUser.showBorder !== "undefined" ? v.tools.parseBool(v.confUser.showBorder) :
 | |
|             v.confDefaults.showBorder.val);
 | |
|         v.conf.showLegend = (typeof v.confUser.showLegend !== "undefined" ? v.tools.parseBool(v.confUser.showLegend) :
 | |
|             v.confDefaults.showLegend.val);
 | |
|         v.conf.showLoadingIndicatorOnAjaxCall = (typeof v.confUser.showLoadingIndicatorOnAjaxCall !== "undefined" ?
 | |
|             v.tools.parseBool(v.confUser.showLoadingIndicatorOnAjaxCall) :
 | |
|             v.confDefaults.showLoadingIndicatorOnAjaxCall.val);
 | |
|         v.conf.lassoMode = (typeof v.confUser.lassoMode !== "undefined" ? v.tools.parseBool(v.confUser.lassoMode) :
 | |
|             v.confDefaults.lassoMode.val);
 | |
|         v.conf.zoomMode = (typeof v.confUser.zoomMode !== "undefined" ? v.tools.parseBool(v.confUser.zoomMode) :
 | |
|             v.confDefaults.zoomMode.val);
 | |
|         v.conf.minZoomFactor = v.confUser.minZoomFactor || v.confDefaults.minZoomFactor.val;
 | |
|         v.conf.maxZoomFactor = v.confUser.maxZoomFactor || v.confDefaults.maxZoomFactor.val;
 | |
|         v.conf.transform = v.confUser.transform || v.confDefaults.transform.val;
 | |
|         v.conf.autoRefresh = (typeof v.confUser.autoRefresh !== "undefined" ?
 | |
|             v.tools.parseBool(v.confUser.autoRefresh) : v.confDefaults.autoRefresh.val);
 | |
|         v.conf.refreshInterval = v.confUser.refreshInterval || v.confDefaults.refreshInterval.val;
 | |
|         v.conf.chargeDistance = v.confUser.chargeDistance || Infinity;
 | |
|         v.conf.charge = v.confUser.charge || v.confDefaults.charge.val;
 | |
|         v.conf.gravity = v.confUser.gravity || v.confDefaults.gravity.val;
 | |
|         v.conf.linkStrength = v.confUser.linkStrength || v.confDefaults.linkStrength.val;
 | |
|         v.conf.friction = v.confUser.friction || v.confDefaults.friction.val;
 | |
|         v.conf.theta = v.confUser.theta || v.confDefaults.theta.val;
 | |
| 
 | |
|         v.conf.onNodeMouseenterFunction = v.confUser.onNodeMouseenterFunction || null;
 | |
|         v.conf.onNodeMouseleaveFunction = v.confUser.onNodeMouseleaveFunction || null;
 | |
|         v.conf.onNodeClickFunction = v.confUser.onNodeClickFunction || null;
 | |
|         v.conf.onNodeDblclickFunction = v.confUser.onNodeDblclickFunction || null;
 | |
|         v.conf.onNodeContextmenuFunction = v.confUser.onNodeContextmenuFunction || null;
 | |
|         v.conf.onLinkClickFunction = v.confUser.onLinkClickFunction || null;
 | |
|         v.conf.onLassoStartFunction = v.confUser.onLassoStartFunction || null;
 | |
|         v.conf.onLassoEndFunction = v.confUser.onLassoEndFunction || null;
 | |
| 
 | |
|         // initialize sample data
 | |
|         /* jshint -W110 */
 | |
|         v.data.sampleData = '<data>' +
 | |
|             '<nodes ID="7839" LABEL="KING is THE KING, you know?" LABELCIRCULAR="true" COLORVALUE="10" ' +
 | |
|             'COLORLABEL="Accounting" SIZEVALUE="5000" LINK="http://apex.oracle.com/" ' +
 | |
|             'INFOSTRING="This visualization is based on the well known emp table." />' +
 | |
|             '<nodes ID="7698" LABEL="BLAKE" COLORVALUE="30" COLORLABEL="Sales" SIZEVALUE="2850" />' +
 | |
|             '<nodes ID="7782" LABEL="CLARK" COLORVALUE="10" COLORLABEL="Accounting" SIZEVALUE="2450" />' +
 | |
|             '<nodes ID="7566" LABEL="JONES" COLORVALUE="20" COLORLABEL="Research" SIZEVALUE="2975" />' +
 | |
|             '<nodes ID="7788" LABEL="SCOTT" COLORVALUE="20" COLORLABEL="Research" SIZEVALUE="3000" />' +
 | |
|             '<nodes ID="7902" LABEL="FORD" COLORVALUE="20" COLORLABEL="Research" SIZEVALUE="3000" />' +
 | |
|             '<nodes ID="7369" LABEL="SMITH" COLORVALUE="20" COLORLABEL="Research" SIZEVALUE="800" />' +
 | |
|             '<nodes ID="7499" LABEL="ALLEN" COLORVALUE="30" COLORLABEL="Sales" SIZEVALUE="1600" />' +
 | |
|             '<nodes ID="7521" LABEL="WARD" COLORVALUE="30" COLORLABEL="Sales" SIZEVALUE="1250" />' +
 | |
|             '<nodes ID="7654" LABEL="MARTIN" COLORVALUE="30" COLORLABEL="Sales" SIZEVALUE="1250" />' +
 | |
|             '<nodes ID="7844" LABEL="TURNER" COLORVALUE="30" COLORLABEL="Sales" SIZEVALUE="1500" />' +
 | |
|             '<nodes ID="7876" LABEL="ADAMS" COLORVALUE="20" COLORLABEL="Research" SIZEVALUE="1100" />' +
 | |
|             '<nodes ID="7900" LABEL="JAMES" COLORVALUE="30" COLORLABEL="Sales" SIZEVALUE="950" />' +
 | |
|             '<nodes ID="7934" LABEL="MILLER" COLORVALUE="10" COLORLABEL="Accounting" SIZEVALUE="1300" />' +
 | |
|             '<nodes ID="8888" LABEL="Who am I?" COLORVALUE="green" COLORLABEL="unspecified" SIZEVALUE="2000" ' +
 | |
|             'LINK="https://github.com/ogobrecht/d3-force-apex-plugin/wiki/API-Reference#nodelinktarget" ' +
 | |
|             'INFOSTRING="This is a good question. Think about it." />' +
 | |
|             '<nodes ID="9999" LABEL="Where I am?" COLORVALUE="#f00" COLORLABEL="unspecified" SIZEVALUE="1000" ' +
 | |
|             'LINK="https://github.com/ogobrecht/d3-force-apex-plugin/wiki/API-Reference#nodelinktarget" ' +
 | |
|             'INFOSTRING="This is a good question. What do you think?" />' +
 | |
|             '<links FROMID="7839" TOID="7839" STYLE="dotted" COLOR="blue" ' +
 | |
|             'INFOSTRING="This is a self link (same source and target node) rendered along a path with the STYLE ' +
 | |
|             'attribute set to dotted and COLOR attribute set to blue." />' +
 | |
|             '<links FROMID="7698" TOID="7839" STYLE="dashed" />' +
 | |
|             '<links FROMID="7782" TOID="7839" STYLE="dashed" COLOR="red" INFOSTRING="This is a link with the STYLE ' +
 | |
|             'attribute set to dashed and COLOR attribute set to red." />' +
 | |
|             '<links FROMID="7566" TOID="7839" STYLE="dashed" />' +
 | |
|             '<links FROMID="7788" TOID="7566" STYLE="solid" />' +
 | |
|             '<links FROMID="7902" TOID="7566" STYLE="solid" />' +
 | |
|             '<links FROMID="7369" TOID="7902" STYLE="solid" />' +
 | |
|             '<links FROMID="7499" TOID="7698" STYLE="solid" />' +
 | |
|             '<links FROMID="7521" TOID="7698" STYLE="solid" />' +
 | |
|             '<links FROMID="7654" TOID="7698" STYLE="solid" />' +
 | |
|             '<links FROMID="7844" TOID="7698" STYLE="solid" />' +
 | |
|             '<links FROMID="7876" TOID="7788" STYLE="solid" />' +
 | |
|             '<links FROMID="7900" TOID="7698" STYLE="solid" />' +
 | |
|             '<links FROMID="7934" TOID="7782" STYLE="solid" />' +
 | |
|             '</data>';
 | |
|         /* jshint +W110 */
 | |
| 
 | |
|         // check user agent: http://stackoverflow.com/questions/16135814/check-for-ie-10
 | |
|         v.status.userAgent = navigator.userAgent;
 | |
|         v.status.userAgentIe9To11 = false;
 | |
|         // Hello IE 9 - 11
 | |
|         if (navigator.appVersion.indexOf("MSIE 9") !== -1 ||
 | |
|             navigator.appVersion.indexOf("MSIE 10") !== -1 ||
 | |
|             v.status.userAgent.indexOf("Trident") !== -1 && v.status.userAgent.indexOf("rv:11") !== -1) {
 | |
|             v.status.userAgentIe9To11 = true;
 | |
|             v.tools.logError("Houston, we have a problem - user agent is IE 9, 10 or 11 - we have to provide a fix " +
 | |
|                 "for markers: " +
 | |
|                 "http://stackoverflow.com/questions/15588478/internet-explorer-10-not-showing-svg-path-d3-js-graph");
 | |
|         }
 | |
| 
 | |
|     }; // --> END v.main.setupConfiguration
 | |
| 
 | |
|     /*******************************************************************************************************************
 | |
|      * MAIN: SETUP DOM
 | |
|      */
 | |
|     v.main.setupDom = function() {
 | |
| 
 | |
|         // create reference to body
 | |
|         v.dom.body = d3.select("body");
 | |
| 
 | |
|         // create DOM container element, if not existing (if we have an APEX context, it is already created from the
 | |
|         // APEX engine )
 | |
|         if (document.querySelector("#" + v.dom.containerId) === null) {
 | |
|             v.dom.container = v.dom.body.append("div")
 | |
|                 .attr("id", v.dom.containerId);
 | |
|         } else {
 | |
|             v.dom.container = d3.select("#" + v.dom.containerId);
 | |
|             d3.selectAll("#" + v.dom.containerId + "_tooltip, #" + v.dom.containerId + "_customizing").remove();
 | |
|         }
 | |
| 
 | |
|         // create SVG element, if not existing (if we have an APEX context, it is already created from the APEX plugin )
 | |
|         if (document.querySelector("#" + v.dom.containerId + " svg") === null) {
 | |
|             v.dom.svg = v.dom.container.append("svg");
 | |
|         } else {
 | |
|             v.dom.svg = d3.select("#" + v.dom.containerId + " svg");
 | |
|             d3.selectAll("#" + v.dom.containerId + " svg *").remove();
 | |
|         }
 | |
| 
 | |
|         v.dom.svgParent = d3.select(v.dom.svg.node().parentNode);
 | |
|         if (v.conf.setDomParentPaddingToZero) {
 | |
|             v.dom.svgParent.style("padding", "0");
 | |
|         }
 | |
| 
 | |
|         // configure SVG element
 | |
|         v.dom.svg
 | |
|             .attr("class", "net_gobrechts_d3_force")
 | |
|             .classed("border", v.conf.showBorder)
 | |
|             .attr("width", v.conf.width)
 | |
|             .attr("height", v.conf.height);
 | |
| 
 | |
|         // calculate width of SVG parent
 | |
|         v.dom.containerWidth = v.tools.getSvgParentInnerWidth();
 | |
|         if (v.conf.useDomParentWidth) {
 | |
|             v.dom.svg.attr("width", v.dom.containerWidth);
 | |
|         }
 | |
| 
 | |
|         // create definitions element inside the SVG element
 | |
|         v.dom.defs = v.dom.svg.append("defs");
 | |
| 
 | |
|         // create overlay element to fetch events for lasso & zoom
 | |
|         v.dom.graphOverlay = v.dom.svg.append("g").attr("class", "graphOverlay");
 | |
| 
 | |
|         // create element for resizing the overlay g element
 | |
|         v.dom.graphOverlaySizeHelper = v.dom.graphOverlay.append("rect").attr("class", "graphOverlaySizeHelper");
 | |
| 
 | |
|         // create graph group element for zoom and pan
 | |
|         v.dom.graph = v.dom.graphOverlay.append("g").attr("class", "graph");
 | |
| 
 | |
|         // create legend group element
 | |
|         v.dom.legend = v.dom.svg.append("g").attr("class", "legend");
 | |
| 
 | |
|         // create loading indicator
 | |
|         v.dom.loading = v.dom.svg.append("svg:g")
 | |
|             .attr("class", "loading")
 | |
|             .style("display", "none");
 | |
|         v.dom.loadingRect = v.dom.loading
 | |
|             .append("svg:rect")
 | |
|             .attr("width", v.tools.getGraphWidth())
 | |
|             .attr("height", v.conf.height);
 | |
|         v.dom.loadingText = v.dom.loading
 | |
|             .append("svg:text")
 | |
|             .attr("x", v.tools.getGraphWidth() / 2)
 | |
|             .attr("y", v.conf.height / 2)
 | |
|             .text("Loading...");
 | |
| 
 | |
|         // create marker definitions
 | |
|         v.dom.defs
 | |
|             .append("svg:marker")
 | |
|             .attr("id", v.dom.containerId + "_highlighted")
 | |
|             .attr("class", "highlighted")
 | |
|             .attr("viewBox", "0 0 10 10")
 | |
|             .attr("refX", 10)
 | |
|             .attr("refY", 5)
 | |
|             .attr("markerWidth", 5)
 | |
|             .attr("markerHeight", 5)
 | |
|             .attr("orient", "auto")
 | |
|             .attr("markerUnits", "strokeWidth")
 | |
|             .append("svg:path")
 | |
|             .attr("d", "M0,0 L10,5 L0,10");
 | |
| 
 | |
|         v.dom.defs
 | |
|             .append("svg:marker")
 | |
|             .attr("id", v.dom.containerId + "_normal")
 | |
|             .attr("class", "normal")
 | |
|             .attr("viewBox", "0 0 10 10")
 | |
|             .attr("refX", 10)
 | |
|             .attr("refY", 5)
 | |
|             .attr("markerWidth", 5)
 | |
|             .attr("markerHeight", 5)
 | |
|             .attr("orient", "auto")
 | |
|             .attr("markerUnits", "strokeWidth")
 | |
|             .append("svg:path")
 | |
|             .attr("d", "M0,0 L10,5 L0,10");
 | |
| 
 | |
|         // create tooltip container
 | |
|         if (document.querySelector("#" + v.dom.containerId + "_tooltip") === null) {
 | |
|             v.dom.tooltip = v.dom.body.append("div")
 | |
|                 .attr("id", v.dom.containerId + "_tooltip")
 | |
|                 .attr("class", "net_gobrechts_d3_force_tooltip")
 | |
|                 .style("top", "0px")
 | |
|                 .style("left", "0px");
 | |
|         } else {
 | |
|             v.dom.tooltip = d3.select("#" + v.dom.containerId + "_tooltip");
 | |
|         }
 | |
| 
 | |
|     }; // --> END v.main.setupDom
 | |
| 
 | |
| 
 | |
|     /*******************************************************************************************************************
 | |
|      * MAIN: SETUP FUNCTION REFERENCES
 | |
|      */
 | |
|     v.main.setupFunctionReferences = function() {
 | |
| 
 | |
|         // create force reference
 | |
|         v.main.force = d3.layout.force()
 | |
|             .on("start", function() {
 | |
|                 v.tools.log("Force started.");
 | |
|                 if (v.status.customize && v.dom.customizePositions) {
 | |
|                     v.dom.customizePositions.text("Force started - wait for end event to show positions...");
 | |
|                 }
 | |
|                 v.status.forceTickCounter = 0;
 | |
|                 v.status.forceStartTime = new Date().getTime();
 | |
|                 v.status.forceRunning = true;
 | |
|             })
 | |
|             .on("tick", function() {
 | |
|                 // hello IE 9 - 11:
 | |
|                 // http://stackoverflow.com/questions/15588478/internet-explorer-10-not-showing-svg-path-d3-js-graph
 | |
|                 if (v.status.userAgentIe9To11 && v.conf.showLinkDirection) {
 | |
|                     v.main.links.each(function() {
 | |
|                         this.parentNode.insertBefore(this, this);
 | |
|                     });
 | |
|                     v.main.selfLinks.each(function() {
 | |
|                         this.parentNode.insertBefore(this, this);
 | |
|                     });
 | |
|                 }
 | |
|                 v.main.selfLinks
 | |
|                     .attr("transform", function(l) {
 | |
|                         return "translate(" + l.source.x + "," + l.source.y + ")";
 | |
|                     });
 | |
|                 v.main.links
 | |
|                     .attr("x1", function(l) {
 | |
|                         return v.tools.adjustSourceX(l);
 | |
|                     })
 | |
|                     .attr("y1", function(l) {
 | |
|                         return v.tools.adjustSourceY(l);
 | |
|                     })
 | |
|                     .attr("x2", function(l) {
 | |
|                         return v.tools.adjustTargetX(l);
 | |
|                     })
 | |
|                     .attr("y2", function(l) {
 | |
|                         return v.tools.adjustTargetY(l);
 | |
|                     });
 | |
|                 if (v.conf.showLabels) {
 | |
|                     v.main.labels
 | |
|                         .attr("x", function(l) {
 | |
|                             return l.x;
 | |
|                         })
 | |
|                         .attr("y", function(l) {
 | |
|                             return l.y - l.radius - v.conf.labelDistance;
 | |
|                         });
 | |
|                     v.main.labelPaths
 | |
|                         .attr("transform", function(n) {
 | |
|                             return "translate(" + n.x + "," + n.y + ")";
 | |
|                         });
 | |
|                 }
 | |
|                 v.main.nodes
 | |
|                     .attr("cx", function(n) {
 | |
|                         return n.x;
 | |
|                     })
 | |
|                     .attr("cy", function(n) {
 | |
|                         return n.y;
 | |
|                     });
 | |
|                 v.status.forceTickCounter += 1;
 | |
|             })
 | |
|             .on("end", function() {
 | |
|                 if (v.conf.showLabels && v.conf.preventLabelOverlappingOnForceEnd) {
 | |
|                     v.data.simulatedAnnealingLabels = [];
 | |
|                     v.data.simulatedAnnealingAnchors = [];
 | |
|                     v.main.labels.each(function(l, i) {
 | |
|                         v.data.simulatedAnnealingLabels[i] = {
 | |
|                             width: this.getBBox().width,
 | |
|                             height: this.getBBox().height,
 | |
|                             x: l.x,
 | |
|                             y: l.y - l.radius - v.conf.labelDistance
 | |
|                         };
 | |
|                     });
 | |
|                     v.main.nodes.filter(function(n) {
 | |
|                         return !n.LABELCIRCULAR && !v.conf.labelsCircular;
 | |
|                     }).each(function(n, i) {
 | |
|                         v.data.simulatedAnnealingAnchors[i] = {
 | |
|                             x: n.x,
 | |
|                             y: n.y - n.radius - v.conf.labelDistance, // set anchors to the same positions as the label
 | |
|                             r: 0.5 //fake radius for labeler plugin, because our labels are already outside of the nodes
 | |
|                         };
 | |
|                     });
 | |
|                     v.lib.labelerPlugin()
 | |
|                         .label(v.data.simulatedAnnealingLabels)
 | |
|                         .anchor(v.data.simulatedAnnealingAnchors)
 | |
|                         .width(v.tools.getGraphWidth())
 | |
|                         .height(v.conf.height)
 | |
|                         .start(v.conf.labelPlacementIterations);
 | |
|                     v.main.labels.each(function(l, i) {
 | |
|                         d3.select(this)
 | |
|                             .transition()
 | |
|                             .duration(800)
 | |
|                             .attr("x", v.data.simulatedAnnealingLabels[i].x)
 | |
|                             .attr("y", v.data.simulatedAnnealingLabels[i].y);
 | |
|                     });
 | |
|                 }
 | |
|                 v.status.forceRunning = false;
 | |
|                 var milliseconds = new Date().getTime() - v.status.forceStartTime;
 | |
|                 var seconds = (milliseconds / 1000).toFixed(1);
 | |
|                 var ticksPerSecond = Math.round(v.status.forceTickCounter / (milliseconds / 1000));
 | |
|                 var millisecondsPerTick = Math.round(milliseconds / v.status.forceTickCounter);
 | |
|                 if (v.status.customize && v.dom.customizePositions) {
 | |
|                     v.dom.customizePositions.text(JSON.stringify(graph.positions()));
 | |
|                 }
 | |
|                 v.tools.log("Force ended.");
 | |
|                 v.tools.log(seconds + " seconds, " + v.status.forceTickCounter + " ticks to cool down (" +
 | |
|                     ticksPerSecond + " ticks/s, " + millisecondsPerTick + " ms/tick).");
 | |
|             });
 | |
| 
 | |
|         // create drag reference
 | |
|         v.main.drag = v.main.force.drag();
 | |
| 
 | |
|         // create lasso reference
 | |
|         v.main.lasso = v.lib.lassoPlugin()
 | |
|             .closePathDistance(100) // max distance for the lasso loop to be closed
 | |
|             .closePathSelect(true) // can items be selected by closing the path?
 | |
|             .hoverSelect(true) // can items by selected by hovering over them?
 | |
|             .area(v.dom.graphOverlay) // area where the lasso can be started
 | |
|             .pathContainer(v.dom.svg); // Container for the path
 | |
| 
 | |
|         // create zoom reference
 | |
|         v.main.zoom = d3.behavior.zoom();
 | |
| 
 | |
|     }; // --> END v.main.setupFunctionReferences
 | |
| 
 | |
| 
 | |
|     /*******************************************************************************************************************
 | |
|      * HELPER FUNCTIONS
 | |
|      */
 | |
| 
 | |
|     // helper to check boolean values
 | |
|     v.tools.parseBool = function(value) {
 | |
|         switch (String(value).trim().toLowerCase()) {
 | |
|             case "true":
 | |
|             case "yes":
 | |
|             case "1":
 | |
|                 return true;
 | |
|             case "false":
 | |
|             case "no":
 | |
|             case "0":
 | |
|             case "":
 | |
|                 return false;
 | |
|             default:
 | |
|                 return false;
 | |
|         }
 | |
|     };
 | |
| 
 | |
|     // parse XML string to XML
 | |
|     v.tools.parseXml = function(xml) {
 | |
|         var dom = null;
 | |
|         if (xml) {
 | |
|             if (window.DOMParser) {
 | |
|                 try {
 | |
|                     dom = (new DOMParser()).parseFromString(xml, "text/xml");
 | |
|                 } catch (e) {
 | |
|                     dom = null;
 | |
|                     v.tools.logError("DOMParser - unable to parse XML: " + e.message);
 | |
|                 }
 | |
|             } else if (window.ActiveXObject) {
 | |
|                 try {
 | |
|                     dom = new ActiveXObject("Microsoft.XMLDOM");
 | |
|                     dom.async = false;
 | |
|                     // parse error ...
 | |
|                     if (!dom.loadXML(xml)) {
 | |
|                         v.tools.logError("Microsoft.XMLDOM - unable to parse XML: " + dom.parseError.reason +
 | |
|                             dom.parseError.srcText);
 | |
|                     }
 | |
|                 } catch (e) {
 | |
|                     dom = null;
 | |
|                     v.tools.logError("Microsoft.XMLDOM - unable to parse XML: " + e.message);
 | |
|                 }
 | |
|             }
 | |
|         }
 | |
|         return dom;
 | |
|     };
 | |
| 
 | |
|     // convert XML to JSON: modified version of http://davidwalsh.name/convert-xml-json
 | |
|     v.tools.xmlToJson = function(xml) {
 | |
|         var obj = null, subobj, item, subItem, nodeName, attribute;
 | |
|     	var convertItemToJson = function(item) {
 | |
|     		subobj = {};
 | |
|     		if (item.attributes.length > 0) {
 | |
|     			for (var i = 0; i < item.attributes.length; i++) {
 | |
|     				attribute = item.attributes.item(i);
 | |
|     				subobj[attribute.nodeName] = attribute.nodeValue;
 | |
|     			}
 | |
|     		}
 | |
|     		if (item.hasChildNodes()) {
 | |
|     			for(var j = 0; j < item.childNodes.length; j++) {
 | |
|     				subItem = item.childNodes.item(j);
 | |
|     				// check, if subItem has minimum one child (hopefully a textnode) inside
 | |
|     				if (subItem.hasChildNodes()) { subobj[subItem.nodeName] = subItem.childNodes.item(0).nodeValue; }
 | |
|     				else { subobj[subItem.nodeName] = ""; }
 | |
|     			}
 | |
|     		}
 | |
|     		return subobj;
 | |
|     	};
 | |
|     	if (xml) {
 | |
|     		obj = {};
 | |
|     		obj.data = {};
 | |
|     		obj.data.nodes = [];
 | |
|     		obj.data.links = [];
 | |
|     		if (xml.childNodes.item(0).hasChildNodes()) {
 | |
|     			for(var i = 0; i < xml.childNodes.item(0).childNodes.length; i++) {
 | |
|     				subobj = null;
 | |
|     				item = xml.childNodes.item(0).childNodes.item(i);
 | |
|     				nodeName = item.nodeName;
 | |
|     				if (nodeName === "nodes" || nodeName === "node") {
 | |
|     					obj.data.nodes.push(convertItemToJson(item));
 | |
|     				}
 | |
|     				else if (nodeName === "links" || nodeName === "link") {
 | |
|     					obj.data.links.push(convertItemToJson(item));
 | |
|     				}
 | |
|     			}
 | |
|     		}
 | |
|     	}
 | |
|     	return obj;
 | |
|     };
 | |
| 
 | |
|     // get inner width for the SVG parents element
 | |
|     v.tools.getSvgParentInnerWidth = function() {
 | |
|         return parseInt(v.dom.svgParent.style("width")) -
 | |
|             parseInt(v.dom.svgParent.style("padding-left")) -
 | |
|             parseInt(v.dom.svgParent.style("padding-right")) -
 | |
|             (v.dom.svg.style("border-width") ? parseInt(v.dom.svg.style("border-width")) : 1) * 2;
 | |
|     };
 | |
| 
 | |
|     // helper function to get effective graph width
 | |
|     v.tools.getGraphWidth = function() {
 | |
|         return (v.conf.useDomParentWidth ? v.dom.containerWidth : v.conf.width);
 | |
|     };
 | |
| 
 | |
|     // log function for debug mode
 | |
|     v.tools.log = function(message, omitDebugPrefix) {
 | |
|         if (v.conf.debug) {
 | |
|             if (omitDebugPrefix) {
 | |
|                 console.log(message);
 | |
|             } else {
 | |
|                 console.log(v.status.debugPrefix + message);
 | |
|             }
 | |
|         }
 | |
|         if (v.status.customize && v.dom.customizeLog) {
 | |
|             v.dom.customizeLog.text(message + "\n" + v.dom.customizeLog.text());
 | |
|         }
 | |
|     };
 | |
| 
 | |
|     // log error function
 | |
|     v.tools.logError = function(message) {
 | |
|         console.log(v.status.debugPrefix + "ERROR: " + message);
 | |
|         if (v.status.customize && v.dom.customizeLog) {
 | |
|             v.dom.customizeLog.text("ERROR: " + message + "\n" + v.dom.customizeLog.text());
 | |
|         }
 | |
|     };
 | |
| 
 | |
|     // trigger APEX events, if we have an APEX context
 | |
|     v.tools.triggerApexEvent = function(domNode, event, data) {
 | |
|         if (v.status.apexPluginId) {
 | |
|             apex.event.trigger(domNode, event, data);
 | |
|         }
 | |
|     };
 | |
| 
 | |
|     // helper function to calculate node radius from "SIZEVALUE" attribute
 | |
|     v.tools.setRadiusFunction = function() {
 | |
|         v.tools.radius = d3.scale.sqrt()
 | |
|             .range([v.conf.minNodeRadius, v.conf.maxNodeRadius])
 | |
|             .domain(d3.extent(v.data.nodes, function(n) {
 | |
|                 return parseFloat(n.SIZEVALUE);
 | |
|             }));
 | |
|     };
 | |
| 
 | |
|     // helper function to calculate node fill color from COLORVALUE attribute
 | |
|     v.tools.setColorFunction = function() {
 | |
|         if (v.conf.colorScheme === "color20") {
 | |
|             v.tools.color = d3.scale.category20();
 | |
|         } else if (v.conf.colorScheme === "color20b") {
 | |
|             v.tools.color = d3.scale.category20b();
 | |
|         } else if (v.conf.colorScheme === "color20c") {
 | |
|             v.tools.color = d3.scale.category20c();
 | |
|         } else if (v.conf.colorScheme === "color10") {
 | |
|             v.tools.color = d3.scale.category10();
 | |
|         } else if (v.conf.colorScheme === "direct") {
 | |
|             v.tools.color = function(d) {
 | |
|                 return d;
 | |
|             };
 | |
|         } else {
 | |
|             v.conf.colorScheme = "color20";
 | |
|             v.tools.color = d3.scale.category20();
 | |
|         }
 | |
|     };
 | |
| 
 | |
|     // check, if two nodes are neighbors
 | |
|     v.tools.neighboring = function(a, b) {
 | |
|         return (v.data.neighbors.indexOf(a.ID + ":" + b.ID) > -1 ||
 | |
|             v.data.neighbors.indexOf(b.ID + ":" + a.ID) > -1);
 | |
|     };
 | |
| 
 | |
|     // get nearest grid position
 | |
|     v.tools.getNearestGridPosition = function(currentPos, maxPos) {
 | |
|         var offset, position;
 | |
|         // no size limit for calculated positions, if zoomMode is set to true
 | |
|         if (v.conf.zoomMode) {
 | |
|             offset = currentPos % v.conf.gridSize;
 | |
|             position = (offset > v.conf.gridSize / 2 ? currentPos - offset + v.conf.gridSize : currentPos - offset);
 | |
|         }
 | |
|         // size limit for calculated positions is SVG size, if zoomMode is set to false
 | |
|         else {
 | |
|             if (currentPos >= maxPos) {
 | |
|                 offset = maxPos % v.conf.gridSize;
 | |
|                 position = maxPos - offset;
 | |
|                 if (position === maxPos) {
 | |
|                     position = position - v.conf.gridSize;
 | |
|                 }
 | |
|             } else if (currentPos <= v.conf.gridSize / 2) {
 | |
|                 position = v.conf.gridSize;
 | |
|             } else {
 | |
|                 offset = currentPos % v.conf.gridSize;
 | |
|                 position = (offset > v.conf.gridSize / 2 ? currentPos - offset + v.conf.gridSize : currentPos - offset);
 | |
|                 if (position >= maxPos) {
 | |
|                     position = position - v.conf.gridSize;
 | |
|                 }
 | |
|             }
 | |
|         }
 | |
|         return position;
 | |
|     };
 | |
| 
 | |
|     // adjust link x/y
 | |
|     v.tools.adjustSourceX = function(l) {
 | |
|         return l.source.x + Math.cos(v.tools.calcAngle(l)) * (l.source.radius);
 | |
|     };
 | |
|     v.tools.adjustSourceY = function(l) {
 | |
|         return l.source.y + Math.sin(v.tools.calcAngle(l)) * (l.source.radius);
 | |
|     };
 | |
|     v.tools.adjustTargetX = function(l) {
 | |
|         return l.target.x - Math.cos(v.tools.calcAngle(l)) * (l.target.radius);
 | |
|     };
 | |
|     v.tools.adjustTargetY = function(l) {
 | |
|         return l.target.y - Math.sin(v.tools.calcAngle(l)) * (l.target.radius);
 | |
|     };
 | |
|     v.tools.calcAngle = function(l) {
 | |
|         return Math.atan2(l.target.y - l.source.y, l.target.x - l.source.x);
 | |
|     };
 | |
| 
 | |
|     // create a path for self links
 | |
|     v.tools.getSelfLinkPath = function(l) {
 | |
|         var ri = l.source.radius;
 | |
|         var ro = l.source.radius + v.conf.selfLinkDistance;
 | |
|         var x = 0; // we position the path later with transform/translate
 | |
|         var y = 0;
 | |
|         var pathStart = {
 | |
|             "source": {
 | |
|                 "x": 0,
 | |
|                 "y": 0,
 | |
|                 "radius": ri
 | |
|             },
 | |
|             "target": {
 | |
|                 "x": (x + ro / 2),
 | |
|                 "y": (y + ro),
 | |
|                 "radius": ri
 | |
|             }
 | |
|         };
 | |
|         var pathEnd = {
 | |
|             "source": {
 | |
|                 "x": (x - ro / 2),
 | |
|                 "y": (y + ro),
 | |
|                 "radius": ri
 | |
|             },
 | |
|             "target": {
 | |
|                 "x": x,
 | |
|                 "y": y,
 | |
|                 "radius": ri
 | |
|             }
 | |
|         };
 | |
|         var path = "M" + v.tools.adjustSourceX(pathStart) + "," + v.tools.adjustSourceY(pathStart);
 | |
|         path += " L" + (x + ro / 2) + "," + (y + ro);
 | |
|         path += " A" + ro + "," + ro + " 0 0,1 " + (x - ro / 2) + "," + (y + ro);
 | |
|         path += " L" + v.tools.adjustTargetX(pathEnd) + "," + v.tools.adjustTargetY(pathEnd);
 | |
|         return path;
 | |
|     };
 | |
| 
 | |
|     // create a path for labels - example: d="M100,100 a20,20 0 0,1 40,0"
 | |
|     v.tools.getLabelPath = function(n) {
 | |
|         var r = n.radius + v.conf.labelDistance;
 | |
|         var x = 0; // we position the path later with transform/translate
 | |
|         var y = 0;
 | |
|         var path = "M" + (x - r) + "," + y;
 | |
|         //path += " a" + r + "," + r + " 0 0,1 " + (r * 2) + ",0";
 | |
|         path += " a" + r + "," + r + " 0 0,1 " + (r * 2) + ",0";
 | |
|         path += " a" + r + "," + r + " 0 0,1 -" + (r * 2) + ",0";
 | |
|         return path;
 | |
|     };
 | |
| 
 | |
|     // open link function
 | |
|     v.tools.openLink = function(node) {
 | |
|         var win;
 | |
|         if (v.conf.nodeLinkTarget === "none") {
 | |
|             window.location.assign(node.LINK);
 | |
|         } else if (v.conf.nodeLinkTarget === "nodeID") {
 | |
|             win = window.open(node.LINK, node.ID);
 | |
|             win.focus();
 | |
|         } else if (v.conf.nodeLinkTarget === "domContainerID") {
 | |
|             win = window.open(node.LINK, v.dom.containerId);
 | |
|             win.focus();
 | |
|         } else {
 | |
|             win = window.open(node.LINK, v.conf.nodeLinkTarget);
 | |
|             win.focus();
 | |
|         }
 | |
|     };
 | |
| 
 | |
|     v.tools.applyConfigurationObject = function(confObject) {
 | |
|         var key;
 | |
|         for (key in confObject) {
 | |
|             if (confObject.hasOwnProperty(key) &&
 | |
|                 v.conf.hasOwnProperty(key) &&
 | |
|                 confObject[key] !== v.conf[key]) {
 | |
|                 graph[key](confObject[key]);
 | |
|             }
 | |
|         }
 | |
|     };
 | |
| 
 | |
|     // http://stackoverflow.com/questions/13713528/how-to-disable-pan-for-d3-behavior-zoom
 | |
|     // http://stackoverflow.com/questions/11786023/how-to-disable-double-click-zoom-for-d3-behavior-zoom
 | |
|     // zoom event proxy
 | |
|     v.tools.zoomEventProxy = function(fn) {
 | |
|         return function() {
 | |
|             if (
 | |
|                 (!v.conf.dragMode || v.conf.dragMode && d3.event.target.tagName !== "circle") &&
 | |
|                 v.conf.zoomMode &&
 | |
|                 (!d3.event.altKey && !d3.event.shiftKey)
 | |
|             ) {
 | |
|                 fn.apply(this, arguments);
 | |
|             }
 | |
|         };
 | |
|     };
 | |
|     // lasso event proxy
 | |
|     v.tools.lassoEventProxy = function(fn) {
 | |
|         return function() {
 | |
|             if (
 | |
|                 (!v.conf.dragMode || d3.event.target.tagName !== "circle") &&
 | |
|                 v.conf.lassoMode &&
 | |
|                 (!v.conf.zoomMode || d3.event.altKey || d3.event.shiftKey)
 | |
|             ) {
 | |
|                 fn.apply(this, arguments);
 | |
|             }
 | |
|         };
 | |
|     };
 | |
| 
 | |
|     // show tooltip
 | |
|     v.tools.showTooltip = function(text) {
 | |
|         var position;
 | |
|         v.dom.tooltip.html(text).style("display", "block");
 | |
|         if (v.conf.tooltipPosition === "svgTopLeft") {
 | |
|             position = v.tools.getOffsetRect(v.dom.svg.node());
 | |
|             v.dom.tooltip
 | |
|                 .style("top", position.top +
 | |
|                     (v.dom.svg.style("border-width") ? parseInt(v.dom.svg.style("border-width")) : 1) +
 | |
|                     "px")
 | |
|                 .style("left", position.left +
 | |
|                     (v.dom.svg.style("border-width") ? parseInt(v.dom.svg.style("border-width")) : 1) +
 | |
|                     "px");
 | |
|         } else if (v.conf.tooltipPosition === "svgTopRight") {
 | |
|             position = v.tools.getOffsetRect(v.dom.svg.node());
 | |
|             v.dom.tooltip
 | |
|                 .style("top", position.top +
 | |
|                     parseInt((v.dom.svg.style("border-width") ? parseInt(v.dom.svg.style("border-width")) : 1)) +
 | |
|                     "px")
 | |
|                 .style("left", position.left +
 | |
|                     parseInt(v.dom.svg.style("width")) +
 | |
|                     parseInt((v.dom.svg.style("border-width") ? parseInt(v.dom.svg.style("border-width")) : 1)) -
 | |
|                     parseInt(v.dom.tooltip.style("width")) -
 | |
|                     2 * parseInt(
 | |
|                         (v.dom.tooltip.style("border-width") ? parseInt(v.dom.tooltip.style("border-width")) : 0)
 | |
|                     ) -
 | |
|                     parseInt(v.dom.tooltip.style("padding-left")) -
 | |
|                     parseInt(v.dom.tooltip.style("padding-right")) +
 | |
|                     "px");
 | |
|         } else {
 | |
|             v.dom.tooltip
 | |
|                 .style("left", d3.event.pageX + 10 + "px")
 | |
|                 .style("top", d3.event.pageY + "px");
 | |
|         }
 | |
|     };
 | |
| 
 | |
|     // hide tooltip
 | |
|     v.tools.hideTooltip = function() {
 | |
|         v.dom.tooltip.style("display", "none");
 | |
|     };
 | |
| 
 | |
|     // on link click function
 | |
|     v.tools.onLinkClick = function(link) {
 | |
|         if (d3.event.defaultPrevented) { // ignore drag
 | |
|             return null;
 | |
|         } else {
 | |
|             v.tools.log("Event link_click triggered.");
 | |
|             v.tools.triggerApexEvent(this, "net_gobrechts_d3_force_linkclick", link);
 | |
|             if (typeof(v.conf.onLinkClickFunction) === "function") {
 | |
|                 v.conf.onLinkClickFunction.call(this, d3.event, link);
 | |
|             }
 | |
|         }
 | |
|     };
 | |
|     // get marker URL
 | |
|     v.tools.getMarkerUrl = function(l) {
 | |
|         if (v.conf.showLinkDirection) {
 | |
|             return "url(#" + v.dom.containerId + "_" + (l.COLOR ? l.COLOR : "normal") + ")";
 | |
|         } else {
 | |
|             return null;
 | |
|         }
 | |
|     };
 | |
|     v.tools.getMarkerUrlHighlighted = function() {
 | |
|         if (v.conf.showLinkDirection) {
 | |
|             return "url(#" + v.dom.containerId + "_highlighted)";
 | |
|         } else {
 | |
|             return null;
 | |
|         }
 | |
|     };
 | |
| 
 | |
|     // on link mouseenter function
 | |
|     v.tools.onLinkMouseenter = function(link) {
 | |
|         if (v.conf.showTooltips && link.INFOSTRING) {
 | |
|             v.tools.showTooltip(link.INFOSTRING);
 | |
|         }
 | |
|     };
 | |
| 
 | |
|     // on link mouseleave function
 | |
|     v.tools.onLinkMouseleave = function() {
 | |
|         if (v.conf.showTooltips) {
 | |
|             v.tools.hideTooltip();
 | |
|         }
 | |
|     };
 | |
| 
 | |
|     // on node mouse enter function
 | |
|     v.tools.onNodeMouseenter = function(node) {
 | |
|         v.main.nodes.classed("highlighted", function(n) {
 | |
|             return v.tools.neighboring(n, node);
 | |
|         });
 | |
|         v.main.links
 | |
|             .classed("highlighted", function(l) {
 | |
|                 return l.source.ID === node.ID || l.target.ID === node.ID;
 | |
|             })
 | |
|             .style("marker-end", function(l) {
 | |
|                 if (l.source.ID === node.ID || l.target.ID === node.ID) {
 | |
|                     return v.tools.getMarkerUrlHighlighted(l);
 | |
|                 } else {
 | |
|                     return v.tools.getMarkerUrl(l);
 | |
|                 }
 | |
|             });
 | |
|         v.main.selfLinks
 | |
|             .classed("highlighted", function(l) {
 | |
|                 return l.FROMID === node.ID;
 | |
|             })
 | |
|             .style("marker-end", function(l) {
 | |
|                 if (l.source.ID === node.ID || l.target.ID === node.ID) {
 | |
|                     return v.tools.getMarkerUrlHighlighted(l);
 | |
|                 } else {
 | |
|                     return v.tools.getMarkerUrl(l);
 | |
|                 }
 | |
|             });
 | |
|         if (v.conf.showLabels) {
 | |
|             v.main.labels.classed("highlighted", function (l) {
 | |
|                 return l.ID === node.ID;
 | |
|             });
 | |
|             v.main.labelsCircular.classed("highlighted", function (l) {
 | |
|                 return l.ID === node.ID;
 | |
|             });
 | |
|         }
 | |
|         d3.select(this).classed("highlighted", true);
 | |
|         v.tools.log("Event node_mouseenter triggered.");
 | |
|         v.tools.triggerApexEvent(this, "net_gobrechts_d3_force_mouseenter", node);
 | |
|         if (typeof(v.conf.onNodeMouseenterFunction) === "function") {
 | |
|             v.conf.onNodeMouseenterFunction.call(this, d3.event, node);
 | |
|         }
 | |
|         if (v.conf.showTooltips && node.INFOSTRING) {
 | |
|             v.tools.showTooltip(node.INFOSTRING);
 | |
|         }
 | |
|     };
 | |
| 
 | |
|     // on node mouse leave function
 | |
|     v.tools.onNodeMouseleave = function(node) {
 | |
|         v.main.nodes.classed("highlighted", false);
 | |
|         v.main.links
 | |
|             .classed("highlighted", false)
 | |
|             .style("marker-end", v.tools.getMarkerUrl);
 | |
|         v.main.selfLinks
 | |
|             .classed("highlighted", false)
 | |
|             .style("marker-end", v.tools.getMarkerUrl);
 | |
|         if (v.conf.showLabels) {
 | |
|             v.main.labels.classed("highlighted", false);
 | |
|             v.main.labelsCircular.classed("highlighted", false);
 | |
|         }
 | |
|         v.tools.log("Event node_mouseleave triggered.");
 | |
|         v.tools.triggerApexEvent(this, "net_gobrechts_d3_force_mouseleave", node);
 | |
|         if (typeof(v.conf.onNodeMouseleaveFunction) === "function") {
 | |
|             v.conf.onNodeMouseleaveFunction.call(this, d3.event, node);
 | |
|         }
 | |
|         if (v.conf.showTooltips) {
 | |
|             v.tools.hideTooltip();
 | |
|         }
 | |
|     };
 | |
| 
 | |
|     // on node click function
 | |
|     v.tools.onNodeClick = function(node) {
 | |
|         if (d3.event.defaultPrevented) { // ignore drag
 | |
|             return null;
 | |
|         } else {
 | |
|             if (node.LINK && v.conf.nodeEventToOpenLink === "click") {
 | |
|                 v.tools.openLink(node);
 | |
|             }
 | |
|             if (v.conf.nodeEventToStopPinMode === "click") {
 | |
|                 d3.select(this).classed("fixed", node.fixed = 0);
 | |
|             }
 | |
|             v.tools.log("Event node_click triggered.");
 | |
|             v.tools.triggerApexEvent(this, "net_gobrechts_d3_force_click", node);
 | |
|             if (typeof(v.conf.onNodeClickFunction) === "function") {
 | |
|                 v.conf.onNodeClickFunction.call(this, d3.event, node);
 | |
|             }
 | |
|         }
 | |
|     };
 | |
| 
 | |
|     // on node double click function
 | |
|     v.tools.onNodeDblclick = function(node) {
 | |
|         if (node.LINK && v.conf.nodeEventToOpenLink === "dblclick") {
 | |
|             v.tools.openLink(node);
 | |
|         }
 | |
|         if (v.conf.nodeEventToStopPinMode === "dblclick") {
 | |
|             d3.select(this).classed("fixed", node.fixed = 0);
 | |
|         }
 | |
|         v.tools.log("Event node_dblclick triggered.");
 | |
|         v.tools.triggerApexEvent(this, "net_gobrechts_d3_force_dblclick", node);
 | |
|         if (typeof(v.conf.onNodeDblclickFunction) === "function") {
 | |
|             v.conf.onNodeDblclickFunction.call(this, d3.event, node);
 | |
|         }
 | |
|     };
 | |
| 
 | |
|     // on node contextmenu function
 | |
|     v.tools.onNodeContextmenu = function(node) {
 | |
|         if (v.conf.onNodeContextmenuPreventDefault) {
 | |
|             d3.event.preventDefault();
 | |
|         }
 | |
|         if (node.LINK && v.conf.nodeEventToOpenLink === "contextmenu") {
 | |
|             v.tools.openLink(node);
 | |
|         }
 | |
|         if (v.conf.nodeEventToStopPinMode === "contextmenu") {
 | |
|             d3.select(this).classed("fixed", node.fixed = 0);
 | |
|         }
 | |
|         v.tools.log("Event node_contextmenu triggered.");
 | |
|         v.tools.triggerApexEvent(this, "net_gobrechts_d3_force_contextmenu", node);
 | |
|         if (typeof(v.conf.onNodeContextmenuFunction) === "function") {
 | |
|             v.conf.onNodeContextmenuFunction.call(this, d3.event, node);
 | |
|         }
 | |
|     };
 | |
| 
 | |
|     // on lasso start function
 | |
|     v.tools.onLassoStart = function(nodes) {
 | |
|         var data = {};
 | |
|         data.numberOfSelectedNodes = 0;
 | |
|         data.idsOfSelectedNodes = null;
 | |
|         data.numberOfNodes = nodes.size();
 | |
|         data.nodes = nodes;
 | |
|         v.tools.log("Event lasso_start triggered.");
 | |
|         v.tools.triggerApexEvent(document.querySelector("#" + v.dom.containerId),
 | |
|             "net_gobrechts_d3_force_lassostart",
 | |
|             data
 | |
|         );
 | |
|         if (typeof(v.conf.onLassoStartFunction) === "function") {
 | |
|             v.conf.onLassoStartFunction.call(v.dom.svg, d3.event, data);
 | |
|         }
 | |
|     };
 | |
| 
 | |
|     // on lasso end function
 | |
|     v.tools.onLassoEnd = function(nodes) {
 | |
|         var data = {};
 | |
|         data.numberOfSelectedNodes = 0;
 | |
|         data.idsOfSelectedNodes = "";
 | |
|         data.numberOfNodes = nodes.size();
 | |
|         data.nodes = nodes;
 | |
|         nodes.each(function(n) {
 | |
|             if (n.selected) {
 | |
|                 data.idsOfSelectedNodes += (n.ID + ":");
 | |
|                 data.numberOfSelectedNodes++;
 | |
|             }
 | |
|         });
 | |
|         data.idsOfSelectedNodes =
 | |
|             (data.idsOfSelectedNodes.length > 0 ?
 | |
|                 data.idsOfSelectedNodes.substr(0, data.idsOfSelectedNodes.length - 1) :
 | |
|                 null);
 | |
|         v.tools.log("Event lasso_end triggered.");
 | |
|         v.tools.triggerApexEvent(document.querySelector("#" + v.dom.containerId),
 | |
|             "net_gobrechts_d3_force_lassoend", data);
 | |
|         if (typeof(v.conf.onLassoEndFunction) === "function") {
 | |
|             v.conf.onLassoEndFunction.call(v.dom.svg, d3.event, data);
 | |
|         }
 | |
|     };
 | |
| 
 | |
|     // get offset for an element relative to the document: http://javascript.info/tutorial/coordinates
 | |
|     v.tools.getOffsetRect = function(elem) {
 | |
|         var box = elem.getBoundingClientRect();
 | |
|         var body = document.body;
 | |
|         var docElem = document.documentElement;
 | |
|         var scrollTop = window.pageYOffset || docElem.scrollTop || body.scrollTop;
 | |
|         var scrollLeft = window.pageXOffset || docElem.scrollLeft || body.scrollLeft;
 | |
|         var clientTop = docElem.clientTop || body.clientTop || 0;
 | |
|         var clientLeft = docElem.clientLeft || body.clientLeft || 0;
 | |
|         var top = box.top + scrollTop - clientTop;
 | |
|         var left = box.left + scrollLeft - clientLeft;
 | |
|         return {
 | |
|             top: Math.round(top),
 | |
|             left: Math.round(left)
 | |
|         };
 | |
|     };
 | |
| 
 | |
|     // create legend
 | |
|     v.tools.createLegend = function() {
 | |
|         v.data.distinctNodeColorValues.forEach(function(colorString, i) {
 | |
|             var color = colorString.split(";");
 | |
|             v.dom.legend
 | |
|                 .append("circle")
 | |
|                 .attr("cx", 11)
 | |
|                 .attr("cy", v.conf.height - ((i + 1) * 14 - 3))
 | |
|                 .attr("r", 6)
 | |
|                 .attr("fill", v.tools.color(color[1]));
 | |
|             v.dom.legend
 | |
|                 .append("text")
 | |
|                 .attr("x", 21)
 | |
|                 .attr("y", v.conf.height - ((i + 1) * 14 - 6))
 | |
|                 .text((color[0] ? color[0] : color[1]));
 | |
|         });
 | |
|     };
 | |
| 
 | |
|     // remove legend
 | |
|     v.tools.removeLegend = function() {
 | |
|         v.dom.legend.selectAll("*").remove();
 | |
|     };
 | |
| 
 | |
|     // write conf object into customization wizard
 | |
|     v.tools.writeConfObjectIntoWizard = function() {
 | |
|         if (v.status.customize) {
 | |
|             v.dom.customizeConfObject.text(JSON.stringify(graph.optionsCustomizationWizard(), null, "  "));
 | |
|         }
 | |
|     };
 | |
| 
 | |
|     // create customize link
 | |
|     v.tools.createCustomizeLink = function() {
 | |
|         if (!v.status.customize &&
 | |
|             (v.conf.debug || document.querySelector("#apex-dev-toolbar") || document.querySelector("#apexDevToolbar"))
 | |
|         ) {
 | |
|             if (document.querySelector("#" + v.dom.containerId + " svg text.link") === null) {
 | |
|                 v.dom.svg.append("svg:text")
 | |
|                     .attr("class", "link")
 | |
|                     .attr("x", 5)
 | |
|                     .attr("y", 15)
 | |
|                     .attr("text-anchor", "start")
 | |
|                     .text("Customize Me")
 | |
|                     .on("click", function() {
 | |
|                         graph.customize(true);
 | |
|                     });
 | |
|             }
 | |
|         }
 | |
|     };
 | |
| 
 | |
|     // remove customize link
 | |
|     v.tools.removeCustomizeLink = function() {
 | |
|         v.dom.svg.select("#" + v.dom.containerId + " svg text.link").remove();
 | |
|     };
 | |
| 
 | |
|     // dragability for customizing container
 | |
|     v.tools.customizeDrag = d3.behavior.drag()
 | |
|         .on("dragstart", function() {
 | |
|             var mouseToBody = d3.mouse(document.body);
 | |
|             v.dom.customizePosition = v.tools.getOffsetRect(document.querySelector("#" + v.dom.containerId +
 | |
|                 "_customizing"));
 | |
|             v.dom.customizePosition.mouseLeft = mouseToBody[0] - v.dom.customizePosition.left;
 | |
|             v.dom.customizePosition.mouseTop = mouseToBody[1] - v.dom.customizePosition.top;
 | |
|         })
 | |
|         .on("drag", function() {
 | |
|             var mouseToBody = d3.mouse(document.body);
 | |
|             v.dom.customize
 | |
|                 .style("left", Math.max(0,
 | |
|                     mouseToBody[0] - v.dom.customizePosition.mouseLeft) + "px")
 | |
|                 .style("top", Math.max(0,
 | |
|                     mouseToBody[1] - v.dom.customizePosition.mouseTop) + "px");
 | |
|         })
 | |
|         .on("dragend", function() {
 | |
|             //v.dom.customizePosition = v.tools.getOffsetRect(document.querySelector("#" + v.dom.containerId +
 | |
|             //"_customizing"));
 | |
|             v.dom.customizePosition = v.tools.getOffsetRect(v.dom.customize.node());
 | |
|         });
 | |
| 
 | |
|     // create customize wizard, if graph not rendering
 | |
|     v.tools.createCustomizeWizardIfNotRendering = function() {
 | |
|         if (v.status.customize && !v.status.graphRendering) {
 | |
|             v.tools.createCustomizeWizard();
 | |
|         }
 | |
|     };
 | |
| 
 | |
|     // customize wizard
 | |
|     v.tools.createCustomizeWizard = function() {
 | |
|         /* jshint -W074, -W071 */
 | |
|         var grid, gridRow, gridCell, row, td, form, i = 4,
 | |
|             currentOption, valueInOptions, key;
 | |
|         var releaseFixedNodesAndResume = function() {
 | |
|             graph.releaseFixedNodes().resume();
 | |
|         };
 | |
|         var onSelectChange = function() {
 | |
|             v.status.customizeCurrentTabPosition = this.id;
 | |
|             if (v.confDefaults[this.name].type === "text") {
 | |
|                 graph[this.name](this.options[this.selectedIndex].value).render();
 | |
|             } else if (v.confDefaults[this.name].type === "number") {
 | |
|                 graph[this.name](parseFloat(this.options[this.selectedIndex].value)).render();
 | |
|             } else if (v.confDefaults[this.name].type === "bool") {
 | |
|                 graph[this.name]((this.options[this.selectedIndex].value === "true")).render();
 | |
|             }
 | |
|         };
 | |
|         var appendOptionsToSelect = function(key) {
 | |
|             v.confDefaults[key].options.forEach(function(option) {
 | |
|                 currentOption = option;
 | |
|                 form.append("option")
 | |
|                     .attr("value", option)
 | |
|                     .attr("selected", function() {
 | |
|                         if (v.confDefaults[key].type === "text" || v.confDefaults[key].type === "bool") {
 | |
|                             if (currentOption === v.conf[key]) {
 | |
|                                 valueInOptions = true;
 | |
|                                 return "selected";
 | |
|                             } else {
 | |
|                                 return null;
 | |
|                             }
 | |
|                         } else if (v.confDefaults[key].type === "number") {
 | |
|                             if (parseFloat(currentOption) === v.conf[key]) {
 | |
|                                 valueInOptions = true;
 | |
|                                 return "selected";
 | |
|                             } else {
 | |
|                                 return null;
 | |
|                             }
 | |
|                         }
 | |
|                     })
 | |
|                     .text(option);
 | |
|             });
 | |
|         };
 | |
|         // render customization wizard only if we have the right status, otherwise remove the wizard
 | |
|         if (!v.status.customize) {
 | |
|             v.tools.removeCustomizeWizard();
 | |
|             v.tools.createCustomizeLink();
 | |
|         } else {
 | |
|             v.tools.removeCustomizeLink();
 | |
|             // set initial position
 | |
|             if (!v.dom.customizePosition) {
 | |
|                 v.dom.customizePosition = v.tools.getOffsetRect(v.dom.svg.node());
 | |
|                 v.dom.customizePosition.left = v.dom.customizePosition.left + v.conf.width + 8;
 | |
|             }
 | |
|             if (document.querySelector("#" + v.dom.containerId + "_customizing") !== null) {
 | |
|                 v.dom.customize.remove();
 | |
|             }
 | |
|             v.dom.customize = v.dom.body.insert("div")
 | |
|                 .attr("id", v.dom.containerId + "_customizing")
 | |
|                 .attr("class", "net_gobrechts_d3_force_customize")
 | |
|                 .style("left", v.dom.customizePosition.left + "px")
 | |
|                 .style("top", v.dom.customizePosition.top + "px");
 | |
|             v.dom.customize.append("span")
 | |
|                 .attr("class", "drag")
 | |
|                 .call(v.tools.customizeDrag)
 | |
|                 .append("span")
 | |
|                 .attr("class", "title")
 | |
|                 .text("Customize \"" + v.dom.containerId + "\"");
 | |
|             v.dom.customize.append("a")
 | |
|                 .attr("class", "close focus")
 | |
|                 .attr("tabindex", 1)
 | |
|                 .text("Close")
 | |
|                 .on("click", function() {
 | |
|                     v.status.customize = false;
 | |
|                     v.tools.removeCustomizeWizard();
 | |
|                     v.tools.createCustomizeLink();
 | |
|                 })
 | |
|                 .on("keydown", function() {
 | |
|                     if (d3.event.keyCode === 13) {
 | |
|                         v.status.customize = false;
 | |
|                         v.tools.removeCustomizeWizard();
 | |
|                         v.tools.createCustomizeLink();
 | |
|                     }
 | |
|                 });
 | |
|             grid = v.dom.customize.append("table");
 | |
|             gridRow = grid.append("tr");
 | |
|             gridCell = gridRow.append("td").style("vertical-align", "top");
 | |
|             v.dom.customizeMenu = gridCell.append("span");
 | |
|             v.dom.customizeOptionsTable = gridCell.append("table");
 | |
|             for (key in v.confDefaults) {
 | |
|                 if (v.confDefaults.hasOwnProperty(key) && v.confDefaults[key].display) {
 | |
|                     i += 1;
 | |
|                     row = v.dom.customizeOptionsTable.append("tr")
 | |
|                         .attr("class", v.confDefaults[key].relation + "-related");
 | |
|                     row.append("td")
 | |
|                         .attr("class", "label")
 | |
|                         .html("<a href=\"https://gobrechts.net/wiki/projects/d3-force-apex-plugin#" +
 | |
|                             key.toLowerCase() + "\" target=\"github_d3_force\" tabindex=\"" + i + 100 + "\">" +
 | |
|                             key + "</a>");
 | |
|                     td = row.append("td");
 | |
|                     form = td.append("select")
 | |
|                         .attr("id", v.dom.containerId + "_" + key)
 | |
|                         .attr("name", key)
 | |
|                         .attr("value", v.conf[key])
 | |
|                         .attr("tabindex", i + 1)
 | |
|                         .classed("warning", v.confDefaults[key].internal)
 | |
|                         .on("change", onSelectChange);
 | |
|                     valueInOptions = false;
 | |
|                     appendOptionsToSelect(key);
 | |
|                     // append current value if not existing in default options
 | |
|                     if (!valueInOptions) {
 | |
|                         form.append("option")
 | |
|                             .attr("value", v.conf[key])
 | |
|                             .attr("selected", "selected")
 | |
|                             .text(v.conf[key]);
 | |
|                         v.confDefaults[key].options.push(v.conf[key]);
 | |
|                     }
 | |
|                     // add short link to release all fixed (pinned) nodes
 | |
|                     if (key === "pinMode") {
 | |
|                         td.append("a")
 | |
|                             .text("release all")
 | |
|                             .attr("href", null)
 | |
|                             .on("click", releaseFixedNodesAndResume);
 | |
|                     }
 | |
|                 }
 | |
|             }
 | |
|             v.dom.customizeOptionsTable.style("width", d3.select(v.dom.customizeOptionsTable).node()[0][0].clientWidth +
 | |
|                 "px");
 | |
|             gridCell.append("span").html("<br>");
 | |
|             gridCell = gridRow.append("td")
 | |
|                 .style("vertical-align", "top")
 | |
|                 .style("padding-left", "5px");
 | |
|             gridCell.append("span")
 | |
|                 .html("Your Configuration Object<p style=\"font-size:10px;margin:0;\">" +
 | |
|                     (v.status.apexPluginId ?
 | |
|                         "To save your options please copy<br>this to your plugin region attributes.<br>" +
 | |
|                         "Only non-default options are shown.</p>" :
 | |
|                         "Use this to initialize your graph.<br>Only non-default options are shown.</p>")
 | |
|                 );
 | |
|             v.dom.customizeConfObject = gridCell.append("textarea")
 | |
|                 .attr("tabindex", i + 5)
 | |
|                 .attr("readonly", "readonly");
 | |
|             gridCell.append("span").html("<br><br>Current Positions<br>");
 | |
|             v.dom.customizePositions = gridCell.append("textarea")
 | |
|                 .attr("tabindex", i + 6)
 | |
|                 .attr("readonly", "readonly")
 | |
|                 .text((v.status.forceRunning ? "Force started - wait for end event to show positions..." :
 | |
|                     JSON.stringify(graph.positions())));
 | |
|             gridCell.append("span").html("<br><br>Debug Log (descending)<br>");
 | |
|             v.dom.customizeLog = gridCell.append("textarea")
 | |
|                 .attr("tabindex", i + 7)
 | |
|                 .attr("readonly", "readonly");
 | |
|             gridRow = grid.append("tr");
 | |
|             gridCell = gridRow.append("td")
 | |
|                 .attr("colspan", 2)
 | |
|                 .html("Copyrights:");
 | |
|             gridRow = grid.append("tr");
 | |
|             gridCell = gridRow.append("td")
 | |
|                 .attr("colspan", 2)
 | |
|                 .html("<table><tr><td style=\"padding-right:20px;\">" +
 | |
|                     "<a href=\"https://github.com/ogobrecht/d3-force-apex-plugin\" target=\"_blank\" " +
 | |
|                     "tabindex=\"" + (i + 8) + "\">D3 Force APEX Plugin</a> (" + v.version +
 | |
|                     ")<br>Ottmar Gobrecht</td><td style=\"padding-right:20px;\">" +
 | |
|                     "<a href=\"https://github.com/mbostock/d3\" target=\"d3js_org\" tabindex=\"" + (i + 9) +
 | |
|                     "\">D3.js</a> (" + d3.version + ") and " +
 | |
|                     "<a href=\"https://github.com/d3/d3-plugins/tree/master/lasso\" target=\"_blank\" tabindex=\"" +
 | |
|                     (i + 10) + "\">D3 Lasso Plugin</a> (modified)<br>Mike Bostock" +
 | |
|                     "</td></tr><tr><td colspan=\"3\">" +
 | |
|                     "<a href=\"https://github.com/tinker10/D3-Labeler\" target=\"github_d3_labeler\" " +
 | |
|                     "tabindex=\"" + (i + 11) +
 | |
|                     "\">D3 Labeler Plugin</a> (automatic label placement using simulated annealing)" +
 | |
|                     "<br>Evan Wang</td></tr></table>"); // https://github.com/tinker10/D3-Labeler
 | |
|             v.tools.createCustomizeMenu(v.status.customizeCurrentMenu);
 | |
|             v.tools.writeConfObjectIntoWizard();
 | |
|             if (v.status.customizeCurrentTabPosition) {
 | |
|                 document.getElementById(v.status.customizeCurrentTabPosition).focus();
 | |
|             }
 | |
|         }
 | |
|     };
 | |
| 
 | |
|     v.tools.removeCustomizeWizard = function() {
 | |
|         d3.select("#" + v.dom.containerId + "_customizing").remove();
 | |
|     };
 | |
| 
 | |
|     v.tools.createCustomizeMenu = function(relation) {
 | |
|         v.status.customizeCurrentMenu = relation;
 | |
|         v.dom.customizeMenu.selectAll("*").remove();
 | |
|         v.dom.customizeMenu.append("span").text("Show options for:");
 | |
|         if (v.status.customizeCurrentMenu === "nodes") {
 | |
|             v.dom.customizeMenu.append("span").style("font-weight", "bold").style("margin-left", "10px").text("NODES");
 | |
|             v.dom.customizeOptionsTable.selectAll("tr.node-related").classed("hidden", false);
 | |
|             v.dom.customizeOptionsTable.selectAll("tr.link-related,tr.graph-related").classed("hidden", true);
 | |
|         } else {
 | |
|             v.dom.customizeMenu.append("a")
 | |
|                 .style("font-weight", "bold")
 | |
|                 .style("margin-left", "10px")
 | |
|                 .text("NODES")
 | |
|                 .attr("tabindex", 2)
 | |
|                 .on("click", function() {
 | |
|                     v.tools.createCustomizeMenu("nodes");
 | |
|                     v.dom.customizeOptionsTable.selectAll("tr.node-related").classed("hidden", false);
 | |
|                     v.dom.customizeOptionsTable.selectAll("tr.link-related,tr.graph-related").classed("hidden", true);
 | |
|                 })
 | |
|                 .on("keydown", function() {
 | |
|                     if (d3.event.keyCode === 13) {
 | |
|                         v.tools.createCustomizeMenu("nodes");
 | |
|                         v.dom.customizeOptionsTable.selectAll("tr.node-related").classed("hidden", false);
 | |
|                         v.dom.customizeOptionsTable.selectAll("tr.link-related,tr.graph-related")
 | |
|                             .classed("hidden", true);
 | |
|                     }
 | |
|                 });
 | |
|         }
 | |
|         if (v.status.customizeCurrentMenu === "links") {
 | |
|             v.dom.customizeMenu.append("span").style("font-weight", "bold").style("margin-left", "10px").text("LINKS");
 | |
|             v.dom.customizeOptionsTable.selectAll("tr.link-related").classed("hidden", false);
 | |
|             v.dom.customizeOptionsTable.selectAll("tr.node-related,tr.graph-related").classed("hidden", true);
 | |
|         } else {
 | |
|             v.dom.customizeMenu.append("a")
 | |
|                 .style("font-weight", "bold")
 | |
|                 .style("margin-left", "10px")
 | |
|                 .text("LINKS")
 | |
|                 .attr("tabindex", 3)
 | |
|                 .on("click", function() {
 | |
|                     v.tools.createCustomizeMenu("links");
 | |
|                     v.dom.customizeOptionsTable.selectAll("tr.link-related").classed("hidden", false);
 | |
|                     v.dom.customizeOptionsTable.selectAll("tr.node-related,tr.graph-related").classed("hidden", true);
 | |
|                 })
 | |
|                 .on("keydown", function() {
 | |
|                     if (d3.event.keyCode === 13) {
 | |
|                         v.tools.createCustomizeMenu("links");
 | |
|                         v.dom.customizeOptionsTable.selectAll("tr.link-related").classed("hidden", false);
 | |
|                         v.dom.customizeOptionsTable.selectAll("tr.node-related,tr.graph-related")
 | |
|                             .classed("hidden", true);
 | |
|                     }
 | |
|                 });
 | |
|         }
 | |
|         if (v.status.customizeCurrentMenu === "graph") {
 | |
|             v.dom.customizeMenu.append("span").style("font-weight", "bold").style("margin-left", "10px").text("GRAPH");
 | |
|             v.dom.customizeOptionsTable.selectAll("tr.graph-related").classed("hidden", false);
 | |
|             v.dom.customizeOptionsTable.selectAll("tr.node-related,tr.link-related").classed("hidden", true);
 | |
|         } else {
 | |
|             v.dom.customizeMenu.append("a")
 | |
|                 .style("font-weight", "bold")
 | |
|                 .style("margin-left", "10px")
 | |
|                 .text("GRAPH")
 | |
|                 .attr("tabindex", 4)
 | |
|                 .on("click", function() {
 | |
|                     v.tools.createCustomizeMenu("graph");
 | |
|                     v.dom.customizeOptionsTable.selectAll("tr.graph-related").classed("hidden", false);
 | |
|                     v.dom.customizeOptionsTable.selectAll("tr.node-related,tr.link-related").classed("hidden", true);
 | |
|                 })
 | |
|                 .on("keydown", function() {
 | |
|                     if (d3.event.keyCode === 13) {
 | |
|                         v.tools.createCustomizeMenu("graph");
 | |
|                         v.dom.customizeOptionsTable.selectAll("tr.graph-related").classed("hidden", false);
 | |
|                         v.dom.customizeOptionsTable.selectAll("tr.node-related,tr.link-related")
 | |
|                             .classed("hidden", true);
 | |
|                     }
 | |
|                 });
 | |
|         }
 | |
|         v.dom.customizeMenu.append("span").html("<br><br>");
 | |
|     };
 | |
| 
 | |
|     /*******************************************************************************************************************
 | |
|      * LIBRARIES
 | |
|      */
 | |
| 
 | |
|     // D3 labeler plugin
 | |
|     /* Source Code: https://github.com/tinker10/D3-Labeler
 | |
|     The MIT License (MIT)
 | |
| 
 | |
|     Copyright (c) 2013 Evan Wang
 | |
| 
 | |
|     Permission is hereby granted, free of charge, to any person obtaining a copy
 | |
|     of this software and associated documentation files (the "Software"), to deal
 | |
|     in the Software without restriction, including without limitation the rights
 | |
|     to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 | |
|     copies of the Software, and to permit persons to whom the Software is
 | |
|     furnished to do so, subject to the following conditions:
 | |
| 
 | |
|     The above copyright notice and this permission notice shall be included in all
 | |
|     copies or substantial portions of the Software.
 | |
| 
 | |
|     THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 | |
|     IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 | |
|     FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 | |
|     AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 | |
|     LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 | |
|     OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 | |
|     SOFTWARE.
 | |
|     */
 | |
|     v.lib.labelerPlugin = function() {
 | |
|         /* jshint -W106 */
 | |
|         var lab = [],
 | |
|             anc = [],
 | |
|             w = 1, // box width
 | |
|             h = 1, // box width
 | |
|             labeler = {};
 | |
| 
 | |
|         var max_move = 5, //5.0,
 | |
|             max_angle = 0.5, //0.5,
 | |
|             acc = 0,
 | |
|             rej = 0;
 | |
| 
 | |
|         // weights
 | |
|         var w_len = 0.2, // leader line length
 | |
|             w_inter = 1.0, // leader line intersection
 | |
|             w_lab2 = 30.0, // label-label overlap
 | |
|             w_lab_anc = 30.0, // label-anchor overlap
 | |
|             w_orient = 1.0; //3.0; // orientation bias
 | |
| 
 | |
|         // booleans for user defined functions
 | |
|         var user_energy = false,
 | |
|             user_schedule = false;
 | |
| 
 | |
|         var user_defined_energy,
 | |
|             user_defined_schedule;
 | |
| 
 | |
|         var energy = function(index) {
 | |
|             /* jshint -W071 */
 | |
|             // energy function, tailored for label placement
 | |
| 
 | |
|             var m = lab.length,
 | |
|                 ener = 0,
 | |
|                 dx = lab[index].x - anc[index].x,
 | |
|                 dy = anc[index].y - lab[index].y,
 | |
|                 dist = Math.sqrt(dx * dx + dy * dy),
 | |
|                 overlap = true;
 | |
| 
 | |
|             // penalty for length of leader line
 | |
|             if (dist > 0) {
 | |
|                 ener += dist * w_len;
 | |
|             }
 | |
| 
 | |
|             // label orientation bias
 | |
|             dx /= dist;
 | |
|             dy /= dist;
 | |
|             if (dx > 0 && dy > 0) {
 | |
|                 ener += 0;
 | |
|             } else if (dx < 0 && dy > 0) {
 | |
|                 ener += w_orient;
 | |
|             } else if (dx < 0 && dy < 0) {
 | |
|                 ener += 2 * w_orient;
 | |
|             } else {
 | |
|                 ener += 3 * w_orient;
 | |
|             }
 | |
| 
 | |
|             var x21 = lab[index].x,
 | |
|                 y21 = lab[index].y - lab[index].height + 2.0,
 | |
|                 x22 = lab[index].x + lab[index].width,
 | |
|                 y22 = lab[index].y + 2.0;
 | |
|             var x11, x12, y11, y12, x_overlap, y_overlap, overlap_area;
 | |
| 
 | |
|             for (var i = 0; i < m; i++) {
 | |
|                 if (i !== index) {
 | |
| 
 | |
|                     // penalty for intersection of leader lines
 | |
|                     overlap = intersect(anc[index].x, lab[index].x, anc[i].x, lab[i].x,
 | |
|                         anc[index].y, lab[index].y, anc[i].y, lab[i].y);
 | |
|                     if (overlap) {
 | |
|                         ener += w_inter;
 | |
|                     }
 | |
| 
 | |
|                     // penalty for label-label overlap
 | |
|                     x11 = lab[i].x;
 | |
|                     y11 = lab[i].y - lab[i].height + 2.0;
 | |
|                     x12 = lab[i].x + lab[i].width;
 | |
|                     y12 = lab[i].y + 2.0;
 | |
|                     x_overlap = Math.max(0, Math.min(x12, x22) - Math.max(x11, x21));
 | |
|                     y_overlap = Math.max(0, Math.min(y12, y22) - Math.max(y11, y21));
 | |
|                     overlap_area = x_overlap * y_overlap;
 | |
|                     ener += (overlap_area * w_lab2);
 | |
|                 }
 | |
| 
 | |
|                 // penalty for label-anchor overlap
 | |
|                 x11 = anc[i].x - anc[i].r;
 | |
|                 y11 = anc[i].y - anc[i].r;
 | |
|                 x12 = anc[i].x + anc[i].r;
 | |
|                 y12 = anc[i].y + anc[i].r;
 | |
|                 x_overlap = Math.max(0, Math.min(x12, x22) - Math.max(x11, x21));
 | |
|                 y_overlap = Math.max(0, Math.min(y12, y22) - Math.max(y11, y21));
 | |
|                 overlap_area = x_overlap * y_overlap;
 | |
|                 ener += (overlap_area * w_lab_anc);
 | |
| 
 | |
|             }
 | |
|             return ener;
 | |
|         };
 | |
| 
 | |
|         var mcmove = function(currT) {
 | |
|             // Monte Carlo translation move
 | |
| 
 | |
|             // select a random label
 | |
|             var i = Math.floor(Math.random() * lab.length);
 | |
| 
 | |
|             // save old coordinates
 | |
|             var x_old = lab[i].x;
 | |
|             var y_old = lab[i].y;
 | |
| 
 | |
|             // old energy
 | |
|             var old_energy;
 | |
|             if (user_energy) {
 | |
|                 old_energy = user_defined_energy(i, lab, anc);
 | |
|             } else {
 | |
|                 old_energy = energy(i);
 | |
|             }
 | |
| 
 | |
|             // random translation
 | |
|             lab[i].x += (Math.random() - 0.5) * max_move;
 | |
|             lab[i].y += (Math.random() - 0.5) * max_move;
 | |
| 
 | |
|             // hard wall boundaries
 | |
|             if (lab[i].x > w) {
 | |
|                 lab[i].x = x_old;
 | |
|             }
 | |
|             if (lab[i].x < 0) {
 | |
|                 lab[i].x = x_old;
 | |
|             }
 | |
|             if (lab[i].y > h) {
 | |
|                 lab[i].y = y_old;
 | |
|             }
 | |
|             if (lab[i].y < 0) {
 | |
|                 lab[i].y = y_old;
 | |
|             }
 | |
| 
 | |
|             // new energy
 | |
|             var new_energy;
 | |
|             if (user_energy) {
 | |
|                 new_energy = user_defined_energy(i, lab, anc);
 | |
|             } else {
 | |
|                 new_energy = energy(i);
 | |
|             }
 | |
| 
 | |
|             // delta E
 | |
|             var delta_energy = new_energy - old_energy;
 | |
| 
 | |
|             if (Math.random() < Math.exp(-delta_energy / currT)) {
 | |
|                 acc += 1;
 | |
|             } else {
 | |
|                 // move back to old coordinates
 | |
|                 lab[i].x = x_old;
 | |
|                 lab[i].y = y_old;
 | |
|                 rej += 1;
 | |
|             }
 | |
| 
 | |
|         };
 | |
| 
 | |
|         var mcrotate = function(currT) {
 | |
|             /* jshint -W071 */
 | |
|             // Monte Carlo rotation move
 | |
| 
 | |
|             // select a random label
 | |
|             var i = Math.floor(Math.random() * lab.length);
 | |
| 
 | |
|             // save old coordinates
 | |
|             var x_old = lab[i].x;
 | |
|             var y_old = lab[i].y;
 | |
| 
 | |
|             // old energy
 | |
|             var old_energy;
 | |
|             if (user_energy) {
 | |
|                 old_energy = user_defined_energy(i, lab, anc);
 | |
|             } else {
 | |
|                 old_energy = energy(i);
 | |
|             }
 | |
| 
 | |
|             // random angle
 | |
|             var angle = (Math.random() - 0.5) * max_angle;
 | |
| 
 | |
|             var s = Math.sin(angle);
 | |
|             var c = Math.cos(angle);
 | |
| 
 | |
|             // translate label (relative to anchor at origin):
 | |
|             lab[i].x -= anc[i].x;
 | |
|             lab[i].y -= anc[i].y;
 | |
| 
 | |
|             // rotate label
 | |
|             var x_new = lab[i].x * c - lab[i].y * s,
 | |
|                 y_new = lab[i].x * s + lab[i].y * c;
 | |
| 
 | |
|             // translate label back
 | |
|             lab[i].x = x_new + anc[i].x;
 | |
|             lab[i].y = y_new + anc[i].y;
 | |
| 
 | |
|             // hard wall boundaries
 | |
|             if (lab[i].x > w) {
 | |
|                 lab[i].x = x_old;
 | |
|             }
 | |
|             if (lab[i].x < 0) {
 | |
|                 lab[i].x = x_old;
 | |
|             }
 | |
|             if (lab[i].y > h) {
 | |
|                 lab[i].y = y_old;
 | |
|             }
 | |
|             if (lab[i].y < 0) {
 | |
|                 lab[i].y = y_old;
 | |
|             }
 | |
| 
 | |
|             // new energy
 | |
|             var new_energy;
 | |
|             if (user_energy) {
 | |
|                 new_energy = user_defined_energy(i, lab, anc);
 | |
|             } else {
 | |
|                 new_energy = energy(i);
 | |
|             }
 | |
| 
 | |
|             // delta E
 | |
|             var delta_energy = new_energy - old_energy;
 | |
| 
 | |
|             if (Math.random() < Math.exp(-delta_energy / currT)) {
 | |
|                 acc += 1;
 | |
|             } else {
 | |
|                 // move back to old coordinates
 | |
|                 lab[i].x = x_old;
 | |
|                 lab[i].y = y_old;
 | |
|                 rej += 1;
 | |
|             }
 | |
| 
 | |
|         };
 | |
| 
 | |
|         var intersect = function(x1, x2, x3, x4, y1, y2, y3, y4) {
 | |
|             // returns true if two lines intersect, else false
 | |
|             // from http://paulbourke.net/geometry/lineline2d/
 | |
| 
 | |
|             var mua, mub;
 | |
|             var denom, numera, numerb;
 | |
| 
 | |
|             denom = (y4 - y3) * (x2 - x1) - (x4 - x3) * (y2 - y1);
 | |
|             numera = (x4 - x3) * (y1 - y3) - (y4 - y3) * (x1 - x3);
 | |
|             numerb = (x2 - x1) * (y1 - y3) - (y2 - y1) * (x1 - x3);
 | |
| 
 | |
|             /* Is the intersection along the the segments */
 | |
|             mua = numera / denom;
 | |
|             mub = numerb / denom;
 | |
|             return !(mua < 0 || mua > 1 || mub < 0 || mub > 1);
 | |
| 
 | |
|         };
 | |
| 
 | |
|         var cooling_schedule = function(currT, initialT, nsweeps) {
 | |
|             // linear cooling
 | |
|             return (currT - (initialT / nsweeps));
 | |
|         };
 | |
| 
 | |
|         labeler.start = function(nsweeps) {
 | |
|             // main simulated annealing function
 | |
|             var m = lab.length,
 | |
|                 currT = 1.0,
 | |
|                 initialT = 1.0;
 | |
| 
 | |
|             for (var i = 0; i < nsweeps; i++) {
 | |
|                 for (var j = 0; j < m; j++) {
 | |
|                     if (Math.random() < 0.5) {
 | |
|                         mcmove(currT);
 | |
|                     } else {
 | |
|                         mcrotate(currT);
 | |
|                     }
 | |
|                 }
 | |
|                 currT = cooling_schedule(currT, initialT, nsweeps);
 | |
|             }
 | |
|         };
 | |
| 
 | |
|         labeler.width = function(x) {
 | |
|             // users insert graph width
 | |
|             if (!arguments.length) {
 | |
|                 return w;
 | |
|             }
 | |
|             w = x;
 | |
|             return labeler;
 | |
|         };
 | |
| 
 | |
|         labeler.height = function(x) {
 | |
|             // users insert graph height
 | |
|             if (!arguments.length) {
 | |
|                 return h;
 | |
|             }
 | |
|             h = x;
 | |
|             return labeler;
 | |
|         };
 | |
| 
 | |
|         labeler.label = function(x) {
 | |
|             // users insert label positions
 | |
|             if (!arguments.length) {
 | |
|                 return lab;
 | |
|             }
 | |
|             lab = x;
 | |
|             return labeler;
 | |
|         };
 | |
| 
 | |
|         labeler.anchor = function(x) {
 | |
|             // users insert anchor positions
 | |
|             if (!arguments.length) {
 | |
|                 return anc;
 | |
|             }
 | |
|             anc = x;
 | |
|             return labeler;
 | |
|         };
 | |
| 
 | |
|         labeler.alt_energy = function(x) {
 | |
|             // user defined energy
 | |
|             if (!arguments.length) {
 | |
|                 return energy;
 | |
|             }
 | |
|             user_defined_energy = x;
 | |
|             user_energy = true;
 | |
|             return labeler;
 | |
|         };
 | |
| 
 | |
|         labeler.alt_schedule = function(x) {
 | |
|             // user defined cooling_schedule
 | |
|             if (!arguments.length) {
 | |
|                 return cooling_schedule;
 | |
|             }
 | |
|             user_defined_schedule = x;
 | |
|             user_schedule = true;
 | |
|             return labeler;
 | |
|         };
 | |
| 
 | |
|         return labeler;
 | |
|     };
 | |
| 
 | |
|     // D3 lasso plugin
 | |
|     /* Source Code: https://github.com/d3/d3-plugins/blob/master/lasso/lasso.js
 | |
|     Copyright (c) 2012-2014, Michael Bostock
 | |
|     All rights reserved.
 | |
| 
 | |
|     Redistribution and use in source and binary forms, with or without
 | |
|     modification, are permitted provided that the following conditions are met:
 | |
| 
 | |
|     * Redistributions of source code must retain the above copyright notice, this
 | |
|       list of conditions and the following disclaimer.
 | |
| 
 | |
|     * Redistributions in binary form must reproduce the above copyright notice,
 | |
|       this list of conditions and the following disclaimer in the documentation
 | |
|       and/or other materials provided with the distribution.
 | |
| 
 | |
|     * The name Michael Bostock may not be used to endorse or promote products
 | |
|       derived from this software without specific prior written permission.
 | |
| 
 | |
|     THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 | |
|     AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 | |
|     IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 | |
|     DISCLAIMED. IN NO EVENT SHALL MICHAEL BOSTOCK BE LIABLE FOR ANY DIRECT,
 | |
|     INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
 | |
|     BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 | |
|     DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
 | |
|     OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
 | |
|     NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
 | |
|     EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 | |
|     */
 | |
|     v.lib.lassoPlugin = function() {
 | |
|         /* jshint -W040, -W106 */
 | |
|         var items = null,
 | |
|             closePathDistance = 75,
 | |
|             closePathSelect = true,
 | |
|             isPathClosed = false,
 | |
|             hoverSelect = true,
 | |
|             area = null,
 | |
|             pathContainer = null,
 | |
|             on = {
 | |
|                 start: function() {},
 | |
|                 draw: function() {},
 | |
|                 end: function() {}
 | |
|             };
 | |
| 
 | |
|         function lasso() {
 | |
|             var _this = d3.select(this[0][0]);
 | |
|             /* START MODIFICATION ------------------------------------------------------>
 | |
|              * Reuse lasso path group element, if possible. In my D3 force implementation
 | |
|              * I provide the possibility to enable or disable the lasso. After enabling
 | |
|              * the lasso I get always a new lasso element. I prefer to reuse the existing
 | |
|              * one.
 | |
|              * */
 | |
|             //
 | |
|             var g, dyn_path, close_path, complete_path, path, origin, last_known_point, path_length_start, drag;
 | |
|             pathContainer = pathContainer || _this; // if not set then defaults to _this
 | |
|             if (pathContainer.selectAll("g.lasso").size() === 0) {
 | |
|                 g = pathContainer.append("g").attr("class", "lasso");
 | |
|                 dyn_path = g.append("path").attr("class", "drawn");
 | |
|                 close_path = g.append("path").attr("class", "loop_close");
 | |
|                 complete_path = g.append("path").attr("class", "complete_path").attr("display", "none");
 | |
|             } else {
 | |
|                 g = pathContainer.select("g.lasso");
 | |
|                 dyn_path = g.select("path.drawn");
 | |
|                 close_path = g.select("path.loop_close");
 | |
|                 complete_path = g.select("path.complete_path");
 | |
|             }
 | |
|             /* <-------------------------------------------------------- END MODIFICATION */
 | |
| 
 | |
|             function dragstart() {
 | |
|                 // Reset blank lasso path
 | |
|                 path = "";
 | |
|                 dyn_path.attr("d", null);
 | |
|                 close_path.attr("d", null);
 | |
|                 // Set path length start
 | |
|                 path_length_start = 0;
 | |
|                 // Set every item to have a false selection and reset their center point and counters
 | |
|                 items[0].forEach(function(d) {
 | |
|                     d.hoverSelected = false;
 | |
|                     d.loopSelected = false;
 | |
|                     var cur_box = d.getBBox();
 | |
|                     /* START MODIFICATION ------------------------------------------------------>
 | |
|                      * Implement correct values after zoom and pan based on the following article:
 | |
|                      * http://stackoverflow.com/questions/18554224/getting-screen-positions-of-d3-nodes-after-transform
 | |
|                      * */
 | |
|                     var ctm = d.getCTM();
 | |
|                     d.lassoPoint = {
 | |
|                         cx: Math.round((cur_box.x + cur_box.width / 2) * ctm.a + ctm.e),
 | |
|                         cy: Math.round((cur_box.y + cur_box.height / 2) * ctm.d + ctm.f),
 | |
|                         /* <-------------------------------------------------------- END MODIFICATION */
 | |
|                         edges: {
 | |
|                             top: 0,
 | |
|                             right: 0,
 | |
|                             bottom: 0,
 | |
|                             left: 0
 | |
|                         },
 | |
|                         close_edges: {
 | |
|                             left: 0,
 | |
|                             right: 0
 | |
|                         }
 | |
|                     };
 | |
|                 });
 | |
| 
 | |
|                 // if hover is on, add hover function
 | |
|                 if (hoverSelect === true) {
 | |
|                     items.on("mouseover.lasso", function() {
 | |
|                         // if hovered, change lasso selection attribute to true
 | |
|                         d3.select(this)[0][0].hoverSelected = true;
 | |
|                     });
 | |
|                 }
 | |
| 
 | |
|                 // Run user defined start function
 | |
|                 on.start();
 | |
|             }
 | |
| 
 | |
|             function dragmove() {
 | |
|                 /* jshint -W071 */
 | |
|                 var x = d3.mouse(this)[0],
 | |
|                     y = d3.mouse(this)[1],
 | |
|                     distance,
 | |
|                     close_draw_path,
 | |
|                     complete_path_d,
 | |
|                     close_path_node,
 | |
|                     close_path_length,
 | |
|                     close_path_edges,
 | |
|                     path_node,
 | |
|                     path_length_end,
 | |
|                     i,
 | |
|                     last_pos,
 | |
|                     prior_pos,
 | |
|                     prior_pos_obj,
 | |
|                     cur_pos,
 | |
|                     cur_pos_obj,
 | |
|                     calcLassoPointEdges = function(d) {
 | |
|                         if (cur_pos_obj.x > d.lassoPoint.cx) {
 | |
|                             d.lassoPoint.edges.right = d.lassoPoint.edges.right + 1;
 | |
|                         }
 | |
|                         if (cur_pos_obj.x < d.lassoPoint.cx) {
 | |
|                             d.lassoPoint.edges.left = d.lassoPoint.edges.left + 1;
 | |
|                         }
 | |
|                     },
 | |
|                     calcLassoPointCloseEdges = function(d) {
 | |
|                         if (Math.round(cur_pos.y) !== Math.round(prior_pos.y) &&
 | |
|                             Math.round(cur_pos.x) > d.lassoPoint.cx) {
 | |
|                             d.lassoPoint.close_edges.right = 1;
 | |
|                         }
 | |
|                         if (Math.round(cur_pos.y) !== Math.round(prior_pos.y) &&
 | |
|                             Math.round(cur_pos.x) < d.lassoPoint.cx) {
 | |
|                             d.lassoPoint.close_edges.left = 1;
 | |
|                         }
 | |
|                     },
 | |
|                     ckeckIfNodeYequalsCurrentPosY = function(d) {
 | |
|                         return d.lassoPoint.cy === Math.round(cur_pos.y);
 | |
|                     },
 | |
|                     ckeckIfNodeYequalsCurrentPriorPosY = function(d) {
 | |
|                         var a;
 | |
|                         if (d.lassoPoint.cy === cur_pos_obj.y && d.lassoPoint.cy !== prior_pos_obj.y) {
 | |
|                             last_known_point = {
 | |
|                                 x: prior_pos_obj.x,
 | |
|                                 y: prior_pos_obj.y
 | |
|                             };
 | |
|                             a = false;
 | |
|                         } else if (d.lassoPoint.cy === cur_pos_obj.y && d.lassoPoint.cy === prior_pos_obj.y) {
 | |
|                             a = false;
 | |
|                         } else if (d.lassoPoint.cy === prior_pos_obj.y && d.lassoPoint.cy !== cur_pos_obj.y) {
 | |
|                             a = sign(d.lassoPoint.cy - cur_pos_obj.y) !== sign(d.lassoPoint.cy - last_known_point.y);
 | |
|                         } else {
 | |
|                             last_known_point = {
 | |
|                                 x: prior_pos_obj.x,
 | |
|                                 y: prior_pos_obj.y
 | |
|                             };
 | |
|                             a = sign(d.lassoPoint.cy - cur_pos_obj.y) !== sign(d.lassoPoint.cy - prior_pos_obj.y);
 | |
|                         }
 | |
|                         return a;
 | |
|                     };
 | |
| 
 | |
|                 // Initialize the path or add the latest point to it
 | |
|                 if (path === "") {
 | |
|                     path = path + "M " + x + " " + y;
 | |
|                     origin = [x, y];
 | |
|                 } else {
 | |
|                     path = path + " L " + x + " " + y;
 | |
|                 }
 | |
| 
 | |
|                 // Reset closed edges counter
 | |
|                 items[0].forEach(function(d) {
 | |
|                     d.lassoPoint.close_edges = {
 | |
|                         left: 0,
 | |
|                         right: 0
 | |
|                     };
 | |
|                 });
 | |
| 
 | |
|                 // Calculate the current distance from the lasso origin
 | |
|                 distance = Math.sqrt(Math.pow(x - origin[0], 2) + Math.pow(y - origin[1], 2));
 | |
| 
 | |
|                 // Set the closed path line
 | |
|                 close_draw_path = "M " + x + " " + y + " L " + origin[0] + " " + origin[1];
 | |
| 
 | |
|                 // Draw the lines
 | |
|                 dyn_path.attr("d", path);
 | |
| 
 | |
|                 // If within the closed path distance parameter, show the closed path. otherwise, hide it
 | |
|                 if (distance <= closePathDistance) {
 | |
|                     close_path.attr("display", null);
 | |
|                 } else {
 | |
|                     close_path.attr("display", "none");
 | |
|                 }
 | |
| 
 | |
|                 isPathClosed = distance <= closePathDistance;
 | |
| 
 | |
|                 // create complete path
 | |
|                 complete_path_d = d3.select("path")[0][0].attributes.d.value + "Z";
 | |
|                 complete_path.attr("d", complete_path_d);
 | |
| 
 | |
|                 // get path length
 | |
|                 path_node = dyn_path.node();
 | |
|                 path_length_end = path_node.getTotalLength();
 | |
|                 last_pos = path_node.getPointAtLength(path_length_start - 1);
 | |
| 
 | |
|                 for (i = path_length_start; i <= path_length_end; i++) {
 | |
|                     cur_pos = path_node.getPointAtLength(i);
 | |
|                     cur_pos_obj = {
 | |
|                         x: Math.round(cur_pos.x * 100) / 100,
 | |
|                         y: Math.round(cur_pos.y * 100) / 100
 | |
|                     };
 | |
|                     prior_pos = path_node.getPointAtLength(i - 1);
 | |
|                     prior_pos_obj = {
 | |
|                         x: Math.round(prior_pos.x * 100) / 100,
 | |
|                         y: Math.round(prior_pos.y * 100) / 100
 | |
|                     };
 | |
| 
 | |
|                     items[0].filter(ckeckIfNodeYequalsCurrentPriorPosY).forEach(calcLassoPointEdges);
 | |
|                 }
 | |
| 
 | |
|                 if (isPathClosed === true && closePathSelect === true) {
 | |
|                     close_path.attr("d", close_draw_path);
 | |
|                     close_path_node = close_path.node();
 | |
|                     close_path_length = close_path_node.getTotalLength();
 | |
|                     close_path_edges = {
 | |
|                         left: 0,
 | |
|                         right: 0
 | |
|                     };
 | |
|                     for (i = 0; i <= close_path_length; i++) {
 | |
|                         cur_pos = close_path_node.getPointAtLength(i);
 | |
|                         prior_pos = close_path_node.getPointAtLength(i - 1);
 | |
|                         items[0].filter(ckeckIfNodeYequalsCurrentPosY).forEach(calcLassoPointCloseEdges);
 | |
|                     }
 | |
|                     items[0].forEach(function(a) {
 | |
|                         if ((a.lassoPoint.edges.left + a.lassoPoint.close_edges.left) > 0 &&
 | |
|                             (a.lassoPoint.edges.right + a.lassoPoint.close_edges.right) % 2 === 1) {
 | |
|                             a.loopSelected = true;
 | |
|                         } else {
 | |
|                             a.loopSelected = false;
 | |
|                         }
 | |
|                     });
 | |
|                 } else {
 | |
|                     items[0].forEach(function(d) {
 | |
|                         d.loopSelected = false;
 | |
|                     });
 | |
|                 }
 | |
| 
 | |
|                 // Tag possible items
 | |
|                 d3.selectAll(items[0].filter(function(d) {
 | |
|                         return (d.loopSelected && isPathClosed) || d.hoverSelected;
 | |
|                     }))
 | |
|                     .attr("d", function(d) {
 | |
|                         d.possible = true;
 | |
|                         return d.possible;
 | |
|                     });
 | |
| 
 | |
|                 d3.selectAll(items[0].filter(function(d) {
 | |
|                         return !((d.loopSelected && isPathClosed) || d.hoverSelected);
 | |
|                     }))
 | |
|                     .attr("d", function(d) {
 | |
|                         d.possible = false;
 | |
|                         return d.possible;
 | |
|                     });
 | |
| 
 | |
|                 on.draw();
 | |
| 
 | |
|                 // Continue drawing path from where it left off
 | |
|                 path_length_start = path_length_end + 1;
 | |
|             }
 | |
| 
 | |
|             function dragend() {
 | |
|                 // Remove mouseover tagging function
 | |
|                 items.on("mouseover.lasso", null);
 | |
| 
 | |
|                 // Tag selected items
 | |
|                 items.filter(function(d) {
 | |
|                         return d.possible === true;
 | |
|                     })
 | |
|                     .attr("d", function(d) {
 | |
|                         d.selected = true;
 | |
|                         return d.selected;
 | |
|                     });
 | |
| 
 | |
|                 items.filter(function(d) {
 | |
|                         return d.possible === false;
 | |
|                     })
 | |
|                     .attr("d", function(d) {
 | |
|                         d.selected = false;
 | |
|                         return d.selected;
 | |
|                     });
 | |
| 
 | |
|                 // Reset possible items
 | |
|                 items.attr("d", function(d) {
 | |
|                     d.possible = false;
 | |
|                     return d.possible;
 | |
|                 });
 | |
| 
 | |
|                 // Clear lasso
 | |
|                 dyn_path.attr("d", null);
 | |
|                 close_path.attr("d", null);
 | |
| 
 | |
|                 // Run user defined end function
 | |
|                 on.end();
 | |
| 
 | |
|             }
 | |
|             drag = d3.behavior.drag()
 | |
|                 .on("dragstart", dragstart)
 | |
|                 .on("drag", dragmove)
 | |
|                 .on("dragend", dragend);
 | |
|             area.call(drag);
 | |
|         }
 | |
| 
 | |
|         lasso.items = function(_) {
 | |
| 
 | |
|             if (!arguments.length) {
 | |
|                 return items;
 | |
|             }
 | |
|             items = _;
 | |
|             items[0].forEach(function(d) {
 | |
|                 var item = d3.select(d);
 | |
|                 if (typeof item.datum() === "undefined") {
 | |
|                     item.datum({
 | |
|                         possible: false,
 | |
|                         selected: false
 | |
|                     });
 | |
|                 } else {
 | |
|                     item.attr("d", function(e) {
 | |
|                         e.possible = false;
 | |
|                         e.selected = false;
 | |
|                         return e;
 | |
|                     });
 | |
|                 }
 | |
|             });
 | |
|             return lasso;
 | |
|         };
 | |
| 
 | |
|         lasso.closePathDistance = function(_) {
 | |
|             if (!arguments.length) {
 | |
|                 return closePathDistance;
 | |
|             }
 | |
|             closePathDistance = _;
 | |
|             return lasso;
 | |
|         };
 | |
| 
 | |
|         lasso.closePathSelect = function(_) {
 | |
|             if (!arguments.length) {
 | |
|                 return closePathSelect;
 | |
|             }
 | |
|             closePathSelect = _ === true;
 | |
|             return lasso;
 | |
|         };
 | |
| 
 | |
|         lasso.isPathClosed = function(_) {
 | |
|             if (!arguments.length) {
 | |
|                 return isPathClosed;
 | |
|             }
 | |
|             isPathClosed = _ === true;
 | |
|             return lasso;
 | |
|         };
 | |
| 
 | |
|         lasso.hoverSelect = function(_) {
 | |
|             if (!arguments.length) {
 | |
|                 return hoverSelect;
 | |
|             }
 | |
|             hoverSelect = _ === true;
 | |
|             return lasso;
 | |
|         };
 | |
| 
 | |
|         lasso.on = function(type, _) {
 | |
|             if (!arguments.length) {
 | |
|                 return on;
 | |
|             }
 | |
|             if (arguments.length === 1) {
 | |
|                 return on[type];
 | |
|             }
 | |
|             var types = ["start", "draw", "end"];
 | |
|             if (types.indexOf(type) > -1) {
 | |
|                 on[type] = _;
 | |
|             }
 | |
|             return lasso;
 | |
|         };
 | |
| 
 | |
|         lasso.area = function(_) {
 | |
|             if (!arguments.length) {
 | |
|                 return area;
 | |
|             }
 | |
|             area = _;
 | |
|             return lasso;
 | |
|         };
 | |
| 
 | |
|         /* START MODIFICATION ------------------------------------------------------>
 | |
|          * Allow different container for lasso path than area, where lasso can be started
 | |
|          * */
 | |
|         lasso.pathContainer = function(_) {
 | |
|             if (!arguments.length) {
 | |
|                 return pathContainer;
 | |
|             }
 | |
|             pathContainer = d3.select(_[0][0]);
 | |
|             return lasso;
 | |
|         };
 | |
|         /* <-------------------------------------------------------- END MODIFICATION */
 | |
| 
 | |
|         function sign(x) {
 | |
|             return x ? x < 0 ? -1 : 1 : 0;
 | |
|         }
 | |
| 
 | |
|         return lasso;
 | |
|     };
 | |
| 
 | |
| 
 | |
|     /*******************************************************************************************************************
 | |
|      * MAIN
 | |
|      */
 | |
| 
 | |
|     v.main.init();
 | |
| 
 | |
| 
 | |
|     /*******************************************************************************************************************
 | |
|      * PUBLIC GRAPH FUNCTION AND API METHODS
 | |
|      */
 | |
| 
 | |
|     function graph() {}
 | |
| 
 | |
|     // public start function: get data and start visualization
 | |
|     graph.start = function(pData) {
 | |
|         var firstChar;
 | |
|         // try to use the input data - this means also, we can overwrite the data from APEX with raw data (textarea or
 | |
|         // whatever you like...)
 | |
|         if (pData) {
 | |
|             graph.render(pData);
 | |
|         }
 | |
|         // if we have no data, then we try to use the APEX context (if APEX plugin ID is set)
 | |
|         else if (v.status.apexPluginId) {
 | |
|             if (v.conf.showLoadingIndicatorOnAjaxCall) {
 | |
|                 graph.showLoadingIndicator(true);
 | |
|             }
 | |
|             apex.server.plugin(
 | |
|                 v.status.apexPluginId, {
 | |
|                     p_debug: $v("pdebug"), //jshint ignore:line
 | |
|                     pageItems: v.status.apexPageItemsToSubmit
 | |
|                 }, {
 | |
|                     success: function(dataString) {
 | |
|                         // dataString starts NOT with "<" or "{", when there are no queries defined in APEX or
 | |
|                         // when the queries returns empty data or when a error occurs on the APEX backend side
 | |
|                         if (v.conf.showLoadingIndicatorOnAjaxCall) {
 | |
|                             graph.showLoadingIndicator(false);
 | |
|                         }
 | |
|                         firstChar = dataString.trim().substr(0, 1);
 | |
|                         if (firstChar === "<" || firstChar === "{") {
 | |
|                             graph.render(dataString);
 | |
|                         } else if (dataString.trim().substr(0, 16) === "no_query_defined") {
 | |
|                             // this will keep the old data or using the sample data, if no old data existing
 | |
|                             graph.render();
 | |
|                             v.tools.logError("No query defined.");
 | |
|                         } else if (dataString.trim().substr(0, 22) === "query_returned_no_data") {
 | |
|                             graph.render({
 | |
|                                 "data": {
 | |
|                                     "nodes": [{
 | |
|                                         "ID": "1",
 | |
|                                         "LABEL": "ERROR: No data.",
 | |
|                                         "COLORVALUE": "1",
 | |
|                                         "SIZEVALUE": "1"
 | |
|                                     }],
 | |
|                                     "links": []
 | |
|                                 }
 | |
|                             });
 | |
|                             v.tools.logError("Query returned no data.");
 | |
|                         } else {
 | |
|                             graph.render({
 | |
|                                 "data": {
 | |
|                                     "nodes": [{
 | |
|                                         "ID": "1",
 | |
|                                         "LABEL": "ERROR: " + dataString + ".",
 | |
|                                         "COLORVALUE": "1",
 | |
|                                         "SIZEVALUE": "1"
 | |
|                                     }],
 | |
|                                     "links": []
 | |
|                                 }
 | |
|                             });
 | |
|                             v.tools.logError(dataString);
 | |
|                         }
 | |
|                     },
 | |
|                     error: function(xhr, status, errorThrown) {
 | |
|                         graph.render({
 | |
|                             "data": {
 | |
|                                 "nodes": [{
 | |
|                                     "ID": "1",
 | |
|                                     "LABEL": "AJAX call terminated with errors.",
 | |
|                                     "COLORVALUE": "1",
 | |
|                                     "SIZEVALUE": "1"
 | |
|                                 }],
 | |
|                                 "links": []
 | |
|                             }
 | |
|                         });
 | |
|                         v.tools.logError("AJAX call terminated with errors: " + errorThrown + ".");
 | |
|                     },
 | |
|                     dataType: "text"
 | |
|                 }
 | |
|             );
 | |
|         }
 | |
|         // if we have no raw data and no APEX context, then we start to render without data (the render function will
 | |
|         // then provide sample data)
 | |
|         else {
 | |
|             graph.render();
 | |
|         }
 | |
|         return graph;
 | |
|     };
 | |
| 
 | |
|     graph.render = function(pData) {
 | |
|         /* jshint -W074, -W071 */
 | |
|         var message;
 | |
|         v.status.graphStarted = true;
 | |
|         v.status.graphRendering = true;
 | |
| 
 | |
|         v.tools.triggerApexEvent(document.querySelector("#" + v.dom.containerId), "apexbeforerefresh");
 | |
| 
 | |
|         // if we start the rendering the first time and there is no input data, then provide sample data
 | |
|         if (!pData && !v.status.graphReady) {
 | |
|             v.tools.logError("Houston, we have a problem - we have to provide sample data.");
 | |
|             v.status.sampleData = true;
 | |
|             pData = v.data.sampleData;
 | |
|         } else if (pData) {
 | |
|             v.status.sampleData = false;
 | |
|         }
 | |
| 
 | |
|         // if we have incoming data, than we do our transformations here, otherwise we use the existing data
 | |
|         if (pData) {
 | |
| 
 | |
|             if (v.status.graphReady) {
 | |
|                 v.status.graphOldPositions = graph.positions();
 | |
|             }
 | |
| 
 | |
|             // data is an object
 | |
|             if (pData.constructor === Object) {
 | |
|                 v.data.dataConverted = pData;
 | |
|                 if (v.conf.debug) {
 | |
|                     v.tools.log("Data object:");
 | |
|                     v.tools.log(v.data.dataConverted, true);
 | |
|                 }
 | |
|             }
 | |
|             // data is a string
 | |
|             else if (pData.constructor === String) {
 | |
|                 // convert incoming data depending on type
 | |
|                 if (pData.trim().substr(0, 1) === "<") {
 | |
|                     try {
 | |
|                         v.data.dataConverted = v.tools.xmlToJson(v.tools.parseXml(pData));
 | |
|                         if (v.data.dataConverted === null) {
 | |
|                             message = "Unable to convert XML string.";
 | |
|                             v.tools.logError(message);
 | |
|                             v.data.dataConverted = {
 | |
|                                 "data": {
 | |
|                                     "nodes": [{
 | |
|                                         "ID": "1",
 | |
|                                         "LABEL": "ERROR: " + message,
 | |
|                                         "COLORVALUE": "1",
 | |
|                                         "SIZEVALUE": "1"
 | |
|                                     }],
 | |
|                                     "links": []
 | |
|                                 }
 | |
|                             };
 | |
|                         }
 | |
|                     } catch (e) {
 | |
|                         message = "Unable to convert XML string: " + e.message + ".";
 | |
|                         v.tools.logError(message);
 | |
|                         v.data.dataConverted = {
 | |
|                             "data": {
 | |
|                                 "nodes": [{
 | |
|                                     "ID": "1",
 | |
|                                     "LABEL": "ERROR: " + message,
 | |
|                                     "COLORVALUE": "1",
 | |
|                                     "SIZEVALUE": "1"
 | |
|                                 }],
 | |
|                                 "links": []
 | |
|                             }
 | |
|                         };
 | |
|                     }
 | |
|                 } else if (pData.trim().substr(0, 1) === "{") {
 | |
|                     try {
 | |
|                         v.data.dataConverted = JSON.parse(pData);
 | |
|                     } catch (e) {
 | |
|                         message = "Unable to parse JSON string: " + e.message + ".";
 | |
|                         v.tools.logError(message);
 | |
|                         v.data.dataConverted = {
 | |
|                             "data": {
 | |
|                                 "nodes": [{
 | |
|                                     "ID": "1",
 | |
|                                     "LABEL": "ERROR: " + message,
 | |
|                                     "COLORVALUE": "1",
 | |
|                                     "SIZEVALUE": "1"
 | |
|                                 }],
 | |
|                                 "links": []
 | |
|                             }
 | |
|                         };
 | |
|                     }
 | |
|                 } else {
 | |
|                     message = "Your data string is not starting with \"<\" or \"{\" - parsing not possible.";
 | |
|                     v.tools.logError(message);
 | |
|                     v.data.dataConverted = {
 | |
|                         "data": {
 | |
|                             "nodes": [{
 | |
|                                 "ID": "1",
 | |
|                                 "LABEL": "ERROR: " + message,
 | |
|                                 "COLORVALUE": "1",
 | |
|                                 "SIZEVALUE": "1"
 | |
|                             }],
 | |
|                             "links": []
 | |
|                         }
 | |
|                     };
 | |
|                 }
 | |
|                 if (v.conf.debug) {
 | |
|                     v.tools.log("Data string:");
 | |
|                     v.tools.log(pData, true);
 | |
|                     v.tools.log("Converted data object:");
 | |
|                     v.tools.log(v.data.dataConverted, true);
 | |
|                 }
 | |
|             }
 | |
|             // data has unknown format
 | |
|             else {
 | |
|                 message = "Unable to parse your data - input data can be a XML string, " +
 | |
|                     "JSON string or JavaScript object.";
 | |
|                 v.tools.logError(message);
 | |
|                 v.data.dataConverted = {
 | |
|                     "data": {
 | |
|                         "nodes": [{
 | |
|                             "ID": "1",
 | |
|                             "LABEL": "ERROR: " + message,
 | |
|                             "COLORVALUE": "1",
 | |
|                             "SIZEVALUE": "1"
 | |
|                         }],
 | |
|                         "links": []
 | |
|                     }
 | |
|                 };
 | |
|             }
 | |
| 
 | |
|             // create references to our new data
 | |
|             if (v.data.dataConverted !== null) {
 | |
|                 if (v.data.dataConverted.hasOwnProperty("data") && v.data.dataConverted.data !== null) {
 | |
|                     if (v.data.dataConverted.data.hasOwnProperty("nodes") && v.data.dataConverted.data.nodes !== null) {
 | |
|                         v.data.nodes = v.data.dataConverted.data.nodes;
 | |
|                         if (v.data.nodes.length === 0) {
 | |
|                             message = "Your data contains an empty nodes array.";
 | |
|                             v.tools.logError(message);
 | |
|                             v.data.nodes = [{
 | |
|                                 "ID": "1",
 | |
|                                 "LABEL": "ERROR: " + message,
 | |
|                                 "COLORVALUE": "1",
 | |
|                                 "SIZEVALUE": "1"
 | |
|                             }];
 | |
|                         }
 | |
|                     } else {
 | |
|                         message = "Your data contains no nodes.";
 | |
|                         v.tools.logError(message);
 | |
|                         v.data.nodes = [{
 | |
|                             "ID": "1",
 | |
|                             "LABEL": "ERROR: " + message,
 | |
|                             "COLORVALUE": "1",
 | |
|                             "SIZEVALUE": "1"
 | |
|                         }];
 | |
|                     }
 | |
|                     if (v.data.dataConverted.data.hasOwnProperty("links") && v.data.dataConverted.data.links !== null) {
 | |
|                         v.data.links = v.data.dataConverted.data.links;
 | |
|                     } else {
 | |
|                         v.data.links = [];
 | |
|                     }
 | |
|                 } else {
 | |
|                     message = "Missing root element named data.";
 | |
|                     v.tools.logError(message);
 | |
|                     v.data = {
 | |
|                         "nodes": [{
 | |
|                             "ID": "1",
 | |
|                             "LABEL": "ERROR: " + message,
 | |
|                             "COLORVALUE": "1",
 | |
|                             "SIZEVALUE": "1"
 | |
|                         }],
 | |
|                         "links": []
 | |
|                     };
 | |
|                 }
 | |
|             } else {
 | |
|                 message = "Unable to parse your data - please consult the API reference for possible data formats.";
 | |
|                 v.tools.logError(message);
 | |
|                 v.data = {
 | |
|                     "nodes": [{
 | |
|                         "ID": "1",
 | |
|                         "LABEL": "ERROR: " + message,
 | |
|                         "COLORVALUE": "1",
 | |
|                         "SIZEVALUE": "1"
 | |
|                     }],
 | |
|                     "links": []
 | |
|                 };
 | |
|             }
 | |
| 
 | |
|             // switch links to point to node objects instead of id's (needed for force layout) and calculate attributes
 | |
|             v.data.idLookup = []; // helper array to lookup node objects by id's
 | |
|             v.data.nodes.forEach(function(n) {
 | |
|                 n.SIZEVALUE = parseFloat(n.SIZEVALUE); // convert size to float value
 | |
|                 n.LABELCIRCULAR = v.tools.parseBool(n.LABELCIRCULAR); // convert labelCircular to boolean
 | |
|                 if (n.fixed) {
 | |
|                     n.fixed = v.tools.parseBool(n.fixed);
 | |
|                 } // convert fixed to boolean
 | |
|                 if (n.x) {
 | |
|                     n.x = parseFloat(n.x);
 | |
|                 } // convert X position to float value
 | |
|                 if (n.y) {
 | |
|                     n.y = parseFloat(n.y);
 | |
|                 } // convert Y position to float value
 | |
|                 v.data.idLookup[n.ID] = n; // add object reference to lookup array
 | |
|             });
 | |
|             v.data.links.forEach(function(l) {
 | |
|                 l.source = v.data.idLookup[l.FROMID]; // add attribute source as a node reference to the link
 | |
|                 l.target = v.data.idLookup[l.TOID]; // add attribute target as a node reference to the link
 | |
|             });
 | |
| 
 | |
|             // sort out links with invalid node references
 | |
|             v.data.links = v.data.links.filter(function(l) {
 | |
|                 return typeof l.source !== "undefined" && typeof l.target !== "undefined";
 | |
|             });
 | |
| 
 | |
|             // create helper array to lookup if nodes are neighbors
 | |
|             v.data.neighbors = v.data.links.map(function(l) {
 | |
|                 return l.FROMID + ":" + l.TOID;
 | |
|             });
 | |
| 
 | |
|             // calculate distinct node colors for the legend
 | |
|             v.data.distinctNodeColorValues = v.data.nodes
 | |
|                 .map(function(n) {
 | |
|                     return (n.COLORLABEL ? n.COLORLABEL : "") + ";" + n.COLORVALUE;
 | |
|                 })
 | |
|                 // http://stackoverflow.com/questions/1960473/unique-values-in-an-array
 | |
|                 .filter(function(value, index, self) {
 | |
|                     return self.indexOf(value) === index;
 | |
|                 })
 | |
|                 .sort(function(a, b) { // http://www.sitepoint.com/sophisticated-sorting-in-javascript/
 | |
|                     var x = a.toLowerCase(),
 | |
|                         y = b.toLowerCase();
 | |
|                     return x < y ? 1 : x > y ? -1 : 0;
 | |
|                 });
 | |
| 
 | |
|             // calculate distinct link colors for the markers
 | |
|             v.data.distinctLinkColorValues = v.data.links
 | |
|                 .map(function(l) {
 | |
|                     return l.COLOR;
 | |
|                 })
 | |
|                 // http://stackoverflow.com/questions/28607451/removing-undefined-values-from-array
 | |
|                 // http://stackoverflow.com/questions/1960473/unique-values-in-an-array
 | |
|                 .filter(Boolean)
 | |
|                 .filter(function(value, index, self) {
 | |
|                     return self.indexOf(value) === index;
 | |
|                 })
 | |
|                 .sort(function(a, b) { // http://www.sitepoint.com/sophisticated-sorting-in-javascript/
 | |
|                     var x = a.toLowerCase(),
 | |
|                         y = b.toLowerCase();
 | |
|                     return x < y ? 1 : x > y ? -1 : 0;
 | |
|                 });
 | |
| 
 | |
|             // apply user provided positions once (new data has priority)
 | |
|             if (v.conf.positions) {
 | |
|                 if (v.conf.positions.constructor === Array) {
 | |
|                     v.conf.positions.forEach(function(n) {
 | |
|                         if (v.data.idLookup[n.ID] !== undefined) {
 | |
|                             if (!v.data.idLookup[n.ID].fixed) {
 | |
|                                 v.data.idLookup[n.ID].fixed = n.fixed;
 | |
|                             }
 | |
|                             if (!v.data.idLookup[n.ID].x) {
 | |
|                                 v.data.idLookup[n.ID].x = v.data.idLookup[n.ID].px = n.x;
 | |
|                             }
 | |
|                             if (!v.data.idLookup[n.ID].y) {
 | |
|                                 v.data.idLookup[n.ID].y = v.data.idLookup[n.ID].py = n.y;
 | |
|                             }
 | |
|                         }
 | |
|                     });
 | |
|                 } else {
 | |
|                     v.tools.logError("Unable to set node positions: positions method parameter must be an array of " +
 | |
|                         "node positions");
 | |
|                 }
 | |
|             }
 | |
|             // apply old positions (new data has priority - if graph was ready, than user provided positions are
 | |
|             // already present in old positions) - see also graph.positions method
 | |
|             else if (v.status.graphOldPositions) {
 | |
|                 v.status.graphOldPositions.forEach(function(n) {
 | |
|                     if (v.data.idLookup[n.ID] !== undefined) {
 | |
|                         if (!v.data.idLookup[n.ID].fixed) {
 | |
|                             v.data.idLookup[n.ID].fixed = n.fixed;
 | |
|                         }
 | |
|                         if (!v.data.idLookup[n.ID].x) {
 | |
|                             v.data.idLookup[n.ID].x = v.data.idLookup[n.ID].px = n.x;
 | |
|                         }
 | |
|                         if (!v.data.idLookup[n.ID].y) {
 | |
|                             v.data.idLookup[n.ID].y = v.data.idLookup[n.ID].py = n.y;
 | |
|                         }
 | |
|                     }
 | |
|                 });
 | |
|             }
 | |
|             // clear positions
 | |
|             v.conf.positions = null;
 | |
|             v.status.graphOldPositions = null;
 | |
| 
 | |
|         } //END: if (pData)
 | |
| 
 | |
|         // set color and radius function and calculate nodes radius
 | |
|         v.tools.setColorFunction();
 | |
|         v.tools.setRadiusFunction();
 | |
|         v.data.nodes.forEach(function(n) {
 | |
|             n.radius = v.tools.radius(n.SIZEVALUE);
 | |
|         });
 | |
| 
 | |
|         // MARKERS
 | |
|         v.main.markers = v.dom.defs.selectAll("marker.custom")
 | |
|             .data(v.data.distinctLinkColorValues,
 | |
|                 function(m) {
 | |
|                     return m;
 | |
|                 }); // distinctLinkColorValues is a simple array, we return the "whole" color value string
 | |
|         v.main.markers.enter().append("svg:marker")
 | |
|             .attr("id", function(m) {
 | |
|                 return v.dom.containerId + "_" + m;
 | |
|             })
 | |
|             .attr("class", "custom")
 | |
|             .attr("stroke", "none")
 | |
|             .attr("fill", function(m) {
 | |
|                 return m;
 | |
|             })
 | |
|             .attr("viewBox", "0 0 10 10")
 | |
|             .attr("refX", 10)
 | |
|             .attr("refY", 5)
 | |
|             .attr("markerWidth", 5)
 | |
|             .attr("markerHeight", 5)
 | |
|             .attr("orient", "auto")
 | |
|             .attr("markerUnits", "strokeWidth")
 | |
|             .append("svg:path")
 | |
|             .attr("d", "M0,0 L10,5 L0,10");
 | |
|         v.main.markers.exit().remove();
 | |
| 
 | |
|         // LINKS
 | |
|         v.main.links = v.dom.graph.selectAll("line.link")
 | |
|             .data(v.data.links.filter(function(l) {
 | |
|                     return l.FROMID !== l.TOID;
 | |
|                 }),
 | |
|                 function(l) {
 | |
|                     return l.FROMID + "_" + l.TOID;
 | |
|                 });
 | |
|         v.main.links.enter().append("svg:line")
 | |
|             .attr("class", "link")
 | |
|             .on("mouseenter", v.tools.onLinkMouseenter)
 | |
|             .on("mouseleave", v.tools.onLinkMouseleave)
 | |
|             .on("click", v.tools.onLinkClick);
 | |
|         v.main.links.exit().remove();
 | |
|         // update all
 | |
|         v.main.links
 | |
|             .style("marker-end", v.tools.getMarkerUrl)
 | |
|             .classed("dotted", function(l) {
 | |
|                 return (l.STYLE === "dotted");
 | |
|             })
 | |
|             .classed("dashed", function(l) {
 | |
|                 return (l.STYLE === "dashed");
 | |
|             })
 | |
|             .style("stroke", function(l) {
 | |
|                 return (l.COLOR ? l.COLOR : null);
 | |
|             });
 | |
| 
 | |
|         // SELFLINKS
 | |
|         v.main.selfLinks = v.dom.graph.selectAll("path.link")
 | |
|             .data(v.data.links.filter(function(l) {
 | |
|                     return l.FROMID === l.TOID && v.conf.showSelfLinks;
 | |
|                 }),
 | |
|                 function(l) {
 | |
|                     return l.FROMID + "_" + l.TOID;
 | |
|                 });
 | |
|         v.main.selfLinks.enter().append("svg:path")
 | |
|             .attr("id", function(l) {
 | |
|                 return v.dom.containerId + "_link_" + l.FROMID + "_" + l.TOID;
 | |
|             })
 | |
|             .attr("class", "link")
 | |
|             .on("mouseenter", v.tools.onLinkMouseenter)
 | |
|             .on("mouseleave", v.tools.onLinkMouseleave)
 | |
|             .on("click", v.tools.onLinkClick);
 | |
|         v.main.selfLinks.exit().remove();
 | |
|         // update all
 | |
|         v.main.selfLinks
 | |
|             .attr("d", function(l) {
 | |
|                 return v.tools.getSelfLinkPath(l);
 | |
|             })
 | |
|             .style("marker-end", v.tools.getMarkerUrl)
 | |
|             .classed("dotted", function(l) {
 | |
|                 return (l.STYLE === "dotted");
 | |
|             })
 | |
|             .classed("dashed", function(l) {
 | |
|                 return (l.STYLE === "dashed");
 | |
|             })
 | |
|             .style("stroke", function(l) {
 | |
|                 return (l.COLOR ? l.COLOR : null);
 | |
|             });
 | |
| 
 | |
|         // PATTERN for nodes with image attribute set
 | |
|         v.main.patterns = v.dom.defs.selectAll("pattern")
 | |
|             .data(v.data.nodes.filter(function(n) {
 | |
|                     return (n.IMAGE ? true : false);
 | |
|                 }),
 | |
|                 function(n) {
 | |
|                     return n.ID;
 | |
|                 });
 | |
|         v.main.patterns.enter().append("svg:pattern")
 | |
|             .attr("id", function(n) {
 | |
|                 return v.dom.containerId + "_pattern_" + n.ID;
 | |
|             })
 | |
|             .append("svg:image");
 | |
|         v.main.patterns.exit().remove();
 | |
|         // update all
 | |
|         v.main.patterns.each(function() {
 | |
|             d3.select(this)
 | |
|                 .attr("x", 0)
 | |
|                 .attr("y", 0)
 | |
|                 .attr("height", function(n) {
 | |
|                     return n.radius * 2;
 | |
|                 })
 | |
|                 .attr("width", function(n) {
 | |
|                     return n.radius * 2;
 | |
|                 });
 | |
|             d3.select(this.firstChild)
 | |
|                 .attr("x", 0)
 | |
|                 .attr("y", 0)
 | |
|                 .attr("height", function(n) {
 | |
|                     return n.radius * 2;
 | |
|                 })
 | |
|                 .attr("width", function(n) {
 | |
|                     return n.radius * 2;
 | |
|                 })
 | |
|                 .attr("xlink:href", function(n) {
 | |
|                     return n.IMAGE;
 | |
|                 });
 | |
|         });
 | |
| 
 | |
|         // NODES
 | |
|         v.main.nodes = v.dom.graph.selectAll("circle.node")
 | |
|             .data(v.data.nodes,
 | |
|                 function(n) {
 | |
|                     return n.ID;
 | |
|                 });
 | |
|         v.main.nodes.enter().append("svg:circle")
 | |
|             .attr("class", "node")
 | |
|             .attr("cx", function(n) {
 | |
|                 if (!n.fixed && !n.x) {
 | |
|                     n.x = Math.floor((Math.random() * v.tools.getGraphWidth()) + 1);
 | |
|                     return n.x;
 | |
|                 }
 | |
|             })
 | |
|             .attr("cy", function(n) {
 | |
|                 if (!n.fixed && !n.y) {
 | |
|                     n.y = Math.floor((Math.random() * v.conf.height) + 1);
 | |
|                     return n.y;
 | |
|                 }
 | |
|             })
 | |
|             .on("mouseenter", v.tools.onNodeMouseenter)
 | |
|             .on("mouseleave", v.tools.onNodeMouseleave)
 | |
|             .on("click", v.tools.onNodeClick)
 | |
|             .on("dblclick", v.tools.onNodeDblclick)
 | |
|             .on("contextmenu", v.tools.onNodeContextmenu);
 | |
|         v.main.nodes.exit().remove();
 | |
|         // update all
 | |
|         v.main.nodes
 | |
|             .attr("r", function(n) {
 | |
|                 return n.radius;
 | |
|             })
 | |
|             .attr("fill", function(n) {
 | |
|                 return (n.IMAGE ? "url(#" + v.dom.containerId + "_pattern_" + n.ID + ")" : v.tools.color(n.COLORVALUE));
 | |
|             });
 | |
| 
 | |
| 
 | |
|         // LABELS
 | |
| 
 | |
|         if (v.conf.showLabels) {
 | |
| 
 | |
|             // normal text labels
 | |
|             v.main.labels = v.dom.graph.selectAll("text.label")
 | |
|                 .data(v.data.nodes.filter(function(n) {
 | |
|                         return !n.LABELCIRCULAR && !v.conf.labelsCircular;
 | |
|                     }),
 | |
|                     function(n) {
 | |
|                         return n.ID;
 | |
|                     });
 | |
|             v.main.labels.enter().append("svg:text")
 | |
|                 .attr("class", "label");
 | |
|             v.main.labels.exit().remove();
 | |
|             // update all
 | |
|             v.main.labels.text(function(n) {
 | |
|                 return n.LABEL;
 | |
|             });
 | |
| 
 | |
|             // paths for circular labels
 | |
|             v.main.labelPaths = v.dom.defs.selectAll("path.label")
 | |
|                 .data(v.data.nodes.filter(function(n) {
 | |
|                         return n.LABELCIRCULAR || v.conf.labelsCircular;
 | |
|                     }),
 | |
|                     function(n) {
 | |
|                         return n.ID;
 | |
|                     });
 | |
|             v.main.labelPaths.enter().append("svg:path")
 | |
|                 .attr("id", function(n) {
 | |
|                     return v.dom.containerId + "_textPath_" + n.ID;
 | |
|                 })
 | |
|                 .attr("class", "label");
 | |
|             v.main.labelPaths.exit().remove();
 | |
|             // update all
 | |
|             v.main.labelPaths.attr("d", function(n) {
 | |
|                 return v.tools.getLabelPath(n);
 | |
|             });
 | |
| 
 | |
|             // circular labels
 | |
|             v.main.labelsCircular = v.dom.graph.selectAll("text.labelCircular")
 | |
|                 .data(v.data.nodes.filter(function(n) {
 | |
|                         return n.LABELCIRCULAR || v.conf.labelsCircular;
 | |
|                     }),
 | |
|                     function(n) {
 | |
|                         return n.ID;
 | |
|                     });
 | |
|             v.main.labelsCircular.enter().append("svg:text")
 | |
|                 .attr("class", "labelCircular")
 | |
|                 .append("svg:textPath")
 | |
|                 .attr("xlink:href", function(n) {
 | |
|                     return "#" + v.dom.containerId + "_textPath_" + n.ID;
 | |
|                 });
 | |
|             v.main.labelsCircular.exit().remove();
 | |
|             // update all
 | |
|             v.main.labelsCircular.each(function(n) {
 | |
|                 d3.select(this.firstChild).text(n.LABEL);
 | |
|             });
 | |
|         } else {
 | |
|             v.dom.defs.selectAll("path.label").remove();
 | |
|             v.dom.graph.selectAll("text.label,text.labelCircular").remove();
 | |
|         }
 | |
| 
 | |
|         // initialize the graph (some options implicit initializes v.main.force, e.g. linkDistance, charge, ...)
 | |
|         graph
 | |
|             .debug(v.conf.debug)
 | |
|             .showBorder(v.conf.showBorder)
 | |
|             .setDomParentPaddingToZero(v.conf.setDomParentPaddingToZero)
 | |
|             .useDomParentWidth(v.conf.useDomParentWidth)
 | |
|             .width(v.conf.width)
 | |
|             .height(v.conf.height)
 | |
|             .alignFixedNodesToGrid(v.conf.alignFixedNodesToGrid)
 | |
|             .dragMode(v.conf.dragMode)
 | |
|             .pinMode(v.conf.pinMode)
 | |
|             .lassoMode(v.conf.lassoMode)
 | |
|             .zoomMode(v.conf.zoomMode)
 | |
|             .transform(v.conf.transform)
 | |
|             .autoRefresh(v.conf.autoRefresh)
 | |
|             .linkDistance(v.conf.linkDistance)
 | |
|             .charge(v.conf.charge)
 | |
|             .chargeDistance(v.conf.chargeDistance)
 | |
|             .gravity(v.conf.gravity)
 | |
|             .linkStrength(v.conf.linkStrength)
 | |
|             .friction(v.conf.friction)
 | |
|             .theta(v.conf.theta);
 | |
| 
 | |
|         // start visualization
 | |
|         v.main.force
 | |
|             .nodes(v.data.nodes)
 | |
|             .links(v.data.links)
 | |
|             .start();
 | |
| 
 | |
|         if (v.status.customize) {
 | |
|             v.tools.createCustomizeWizard();
 | |
|         } else {
 | |
|             v.tools.createCustomizeLink();
 | |
|         }
 | |
| 
 | |
|         v.status.graphReady = true;
 | |
|         v.status.graphRendering = false;
 | |
| 
 | |
|         v.tools.triggerApexEvent(document.querySelector("#" + v.dom.containerId), "apexafterrefresh");
 | |
| 
 | |
|         return graph;
 | |
|     };
 | |
| 
 | |
|     graph.resume = function() {
 | |
|         v.main.force.resume();
 | |
|         v.tools.createCustomizeWizardIfNotRendering();
 | |
|         return graph;
 | |
|     };
 | |
| 
 | |
|     graph.showBorder = function(value) {
 | |
|         if (!arguments.length) {
 | |
|             return v.conf.showBorder;
 | |
|         }
 | |
|         v.conf.showBorder = value;
 | |
|         if (v.status.graphStarted) {
 | |
|             v.dom.svg.classed("border", v.conf.showBorder);
 | |
|             v.tools.createCustomizeWizardIfNotRendering();
 | |
|         }
 | |
|         return graph;
 | |
|     };
 | |
| 
 | |
|     graph.showLegend = function(value) {
 | |
|         if (!arguments.length) {
 | |
|             return v.conf.showLegend;
 | |
|         }
 | |
|         v.conf.showLegend = value;
 | |
|         if (v.status.graphStarted) {
 | |
|             if (v.conf.showLegend) {
 | |
|                 v.tools.removeLegend();
 | |
|                 v.tools.createLegend();
 | |
|             } else {
 | |
|                 v.tools.removeLegend();
 | |
|             }
 | |
|             v.tools.createCustomizeWizardIfNotRendering();
 | |
|         }
 | |
|         return graph;
 | |
|     };
 | |
| 
 | |
|     graph.showSelfLinks = function(value) {
 | |
|         if (!arguments.length) {
 | |
|             return v.conf.showSelfLinks;
 | |
|         }
 | |
|         v.conf.showSelfLinks = value;
 | |
|         if (v.status.graphStarted) {
 | |
|             v.tools.createCustomizeWizardIfNotRendering();
 | |
|         }
 | |
|         return graph;
 | |
|     };
 | |
| 
 | |
|     graph.showLinkDirection = function(value) {
 | |
|         if (!arguments.length) {
 | |
|             return v.conf.showLinkDirection;
 | |
|         }
 | |
|         v.conf.showLinkDirection = value;
 | |
|         if (v.status.graphStarted) {
 | |
|             v.tools.createCustomizeWizardIfNotRendering();
 | |
|         }
 | |
|         return graph;
 | |
|     };
 | |
| 
 | |
|     graph.showTooltips = function(value) {
 | |
|         if (!arguments.length) {
 | |
|             return v.conf.showTooltips;
 | |
|         }
 | |
|         v.conf.showTooltips = value;
 | |
|         if (v.status.graphStarted) {
 | |
|             v.tools.createCustomizeWizardIfNotRendering();
 | |
|         }
 | |
|         return graph;
 | |
|     };
 | |
| 
 | |
|     graph.tooltipPosition = function(value) {
 | |
|         if (!arguments.length) {
 | |
|             return v.conf.tooltipPosition;
 | |
|         }
 | |
|         v.conf.tooltipPosition = value;
 | |
|         if (v.status.graphStarted) {
 | |
|             v.tools.createCustomizeWizardIfNotRendering();
 | |
|         }
 | |
|         return graph;
 | |
|     };
 | |
| 
 | |
|     graph.colorScheme = function(value) {
 | |
|         if (!arguments.length) {
 | |
|             return v.conf.colorScheme;
 | |
|         }
 | |
|         v.conf.colorScheme = value;
 | |
|         v.tools.setColorFunction();
 | |
|         if (v.status.graphStarted) {
 | |
|             v.main.nodes
 | |
|                 .attr("fill", function(n) {
 | |
|                     return (n.IMAGE ? "url(#" + v.dom.containerId + "_pattern_" + n.ID + ")" :
 | |
|                         v.tools.color(n.COLORVALUE));
 | |
|                 });
 | |
|             if (v.conf.showLegend) {
 | |
|                 v.tools.removeLegend();
 | |
|                 v.tools.createLegend();
 | |
|             }
 | |
|             v.tools.createCustomizeWizardIfNotRendering();
 | |
|         }
 | |
|         return graph;
 | |
|     };
 | |
| 
 | |
|     graph.showLabels = function(value) {
 | |
|         if (!arguments.length) {
 | |
|             return v.conf.showLabels;
 | |
|         }
 | |
|         v.conf.showLabels = value;
 | |
|         if (v.status.graphStarted) {
 | |
|             v.tools.createCustomizeWizardIfNotRendering();
 | |
|         }
 | |
|         return graph;
 | |
|     };
 | |
| 
 | |
|     graph.labelsCircular = function(value) {
 | |
|         if (!arguments.length) {
 | |
|             return v.conf.labelsCircular;
 | |
|         }
 | |
|         v.conf.labelsCircular = value;
 | |
|         if (v.status.graphStarted) {
 | |
|             v.tools.createCustomizeWizardIfNotRendering();
 | |
|         }
 | |
|         return graph;
 | |
|     };
 | |
| 
 | |
|     graph.preventLabelOverlappingOnForceEnd = function(value) {
 | |
|         if (!arguments.length) {
 | |
|             return v.conf.preventLabelOverlappingOnForceEnd;
 | |
|         }
 | |
|         v.conf.preventLabelOverlappingOnForceEnd = value;
 | |
|         if (v.status.graphStarted) {
 | |
|             v.tools.createCustomizeWizardIfNotRendering();
 | |
|         }
 | |
|         return graph;
 | |
|     };
 | |
| 
 | |
|     graph.labelPlacementIterations = function(value) {
 | |
|         if (!arguments.length) {
 | |
|             return v.conf.labelPlacementIterations;
 | |
|         }
 | |
|         v.conf.labelPlacementIterations = value;
 | |
|         if (v.status.graphStarted) {
 | |
|             v.tools.createCustomizeWizardIfNotRendering();
 | |
|         }
 | |
|         return graph;
 | |
|     };
 | |
| 
 | |
|     graph.dragMode = function(value) {
 | |
|         if (!arguments.length) {
 | |
|             return v.conf.dragMode;
 | |
|         }
 | |
|         v.conf.dragMode = value;
 | |
|         if (v.status.graphStarted) {
 | |
|             if (v.conf.dragMode) {
 | |
|                 v.main.nodes.call(v.main.drag);
 | |
|             } else {
 | |
|                 // http://stackoverflow.com/questions/13136355/d3-js-remove-force-drag-from-a-selection
 | |
|                 v.main.nodes.on("mousedown.drag", null);
 | |
|                 v.main.nodes.on("touchstart.drag", null);
 | |
|             }
 | |
|             v.tools.createCustomizeWizardIfNotRendering();
 | |
|         }
 | |
|         return graph;
 | |
|     };
 | |
| 
 | |
|     graph.pinMode = function(value) {
 | |
|         if (!arguments.length) {
 | |
|             return v.conf.pinMode;
 | |
|         }
 | |
|         v.conf.pinMode = value;
 | |
|         if (v.status.graphStarted) {
 | |
|             if (v.conf.pinMode) {
 | |
|                 v.main.drag.on("dragstart", function(n) {
 | |
|                     d3.select(this).classed("fixed", n.fixed = 1);
 | |
|                 });
 | |
|             } else {
 | |
|                 v.main.drag.on("dragstart", null);
 | |
|             }
 | |
|             v.tools.createCustomizeWizardIfNotRendering();
 | |
|         }
 | |
|         return graph;
 | |
|     };
 | |
| 
 | |
|     graph.lassoMode = function(value) {
 | |
|         if (!arguments.length) {
 | |
|             return v.conf.lassoMode;
 | |
|         }
 | |
|         v.conf.lassoMode = value;
 | |
|         if (v.status.graphStarted) {
 | |
|             if (v.conf.lassoMode) {
 | |
|                 v.dom.graphOverlay.call(v.main.lasso);
 | |
|                 v.main.lasso.items(v.main.nodes);
 | |
|                 v.main.lasso.on("start", function() {
 | |
|                     v.main.lasso.items().classed("selected", false);
 | |
|                     v.tools.onLassoStart(v.main.lasso.items());
 | |
|                 });
 | |
|                 v.main.lasso.on("draw", function() {
 | |
|                     v.main.lasso.items().filter(function(d) {
 | |
|                             return d.possible === true;
 | |
|                         })
 | |
|                         .classed("selected", true);
 | |
|                     v.main.lasso.items().filter(function(d) {
 | |
|                             return d.possible === false;
 | |
|                         })
 | |
|                         .classed("selected", false);
 | |
|                 });
 | |
|                 v.main.lasso.on("end", function() {
 | |
|                     v.main.lasso.items().filter(function(d) {
 | |
|                             return d.selected === true;
 | |
|                         })
 | |
|                         .classed("selected", true);
 | |
|                     v.main.lasso.items().filter(function(d) {
 | |
|                             return d.selected === false;
 | |
|                         })
 | |
|                         .classed("selected", false);
 | |
|                     v.tools.onLassoEnd(v.main.lasso.items());
 | |
|                 });
 | |
|                 // save lasso event for use in event proxy
 | |
|                 v.events.mousedownLasso = v.dom.graphOverlay.on("mousedown.drag");
 | |
|                 v.events.touchstartLasso = v.dom.graphOverlay.on("touchstart.drag");
 | |
|                 //v.events.touchmoveDrag = v.dom.graphOverlay.on("touchmove.drag");
 | |
|                 //v.events.touchendDrag = v.dom.graphOverlay.on("touchend.drag");
 | |
| 
 | |
|                 // register event proxy for relevant lasso events who conflict with force functions -> see also
 | |
|                 // v.tools.lassoEventProxy
 | |
|                 v.dom.graphOverlay.on("mousedown.drag", v.tools.lassoEventProxy(v.events.mousedownLasso));
 | |
|                 v.dom.graphOverlay.on("touchstart.drag", v.tools.lassoEventProxy(v.events.touchstartLasso));
 | |
|                 //v.dom.graphOverlay.on("touchmove.drag", v.tools.lassoEventProxy(v.events.touchmoveDrag));
 | |
|                 //v.dom.graphOverlay.on("touchend.drag", v.tools.lassoEventProxy(v.events.touchendDrag));
 | |
|             } else {
 | |
|                 v.dom.graphOverlay.on(".drag", null);
 | |
|                 v.main.nodes.classed("selected", false);
 | |
|             }
 | |
|             v.tools.createCustomizeWizardIfNotRendering();
 | |
|         }
 | |
|         return graph;
 | |
|     };
 | |
| 
 | |
|     graph.transform = function(value) {
 | |
|         if (!arguments.length) {
 | |
|             return {
 | |
|                 "translate": v.main.zoom.translate(),
 | |
|                 "scale": v.main.zoom.scale()
 | |
|             };
 | |
|         } else {
 | |
|             v.main.zoom.translate(value.translate);
 | |
|             v.main.zoom.scale(value.scale);
 | |
|             v.conf.transform = {
 | |
|                 "translate": v.main.zoom.translate(),
 | |
|                 "scale": v.main.zoom.scale()
 | |
|             };
 | |
|             if (v.conf.zoomMode && v.status.graphStarted) {
 | |
|                 v.dom.graph.attr("transform", "translate(" + v.main.zoom.translate() + ")scale(" +
 | |
|                     v.main.zoom.scale() + ")");
 | |
|                 v.tools.writeConfObjectIntoWizard();
 | |
|             }
 | |
|         }
 | |
|         return graph;
 | |
|     };
 | |
| 
 | |
|     graph.zoomMode = function(value) {
 | |
|         if (!arguments.length) {
 | |
|             return v.conf.zoomMode;
 | |
|         }
 | |
|         v.conf.zoomMode = value;
 | |
|         if (v.status.graphStarted) {
 | |
|             if (v.conf.zoomMode) {
 | |
|                 v.dom.graphOverlay.call(v.main.zoom);
 | |
|                 v.main.zoom.scaleExtent([v.conf.minZoomFactor, v.conf.maxZoomFactor])
 | |
|                     .size([v.tools.getGraphWidth(), v.conf.height])
 | |
|                     .on("zoom", function() {
 | |
|                         v.main.zoom.translate(d3.event.translate);
 | |
|                         v.main.zoom.scale(d3.event.scale);
 | |
|                         v.conf.transform = {
 | |
|                             "translate": v.main.zoom.translate(),
 | |
|                             "scale": v.main.zoom.scale()
 | |
|                         };
 | |
|                         v.dom.graph.attr("transform", "translate(" + v.main.zoom.translate() + ")scale(" +
 | |
|                             v.main.zoom.scale() + ")");
 | |
|                         v.tools.writeConfObjectIntoWizard();
 | |
|                     });
 | |
|                 // save zoom events for use in event proxy
 | |
|                 v.events.dblclickZoom = v.dom.graphOverlay.on("dblclick.zoom");
 | |
|                 v.events.mousedownZoom = v.dom.graphOverlay.on("mousedown.zoom");
 | |
|                 v.events.touchstartZoom = v.dom.graphOverlay.on("touchstart.zoom");
 | |
|                 //v.events.touchmoveZoom = v.dom.graphOverlay.on("touchmove.zoom");
 | |
|                 //v.events.touchendZoom = v.dom.graphOverlay.on("touchend.zoom");
 | |
| 
 | |
|                 // register event proxy for relevant zoom events who conflicts with force functions -> see also
 | |
|                 // v.tools.zoomEventProxy
 | |
|                 v.dom.graphOverlay.on("dblclick.zoom", v.tools.zoomEventProxy(v.events.dblclickZoom));
 | |
|                 v.dom.graphOverlay.on("mousedown.zoom", v.tools.zoomEventProxy(v.events.mousedownZoom));
 | |
|                 v.dom.graphOverlay.on("touchstart.zoom", v.tools.zoomEventProxy(v.events.touchstartZoom));
 | |
|                 //v.dom.graphOverlay.on("touchmove.zoom", v.tools.zoomEventProxy(v.events.touchmoveZoom));
 | |
|                 //v.dom.graphOverlay.on("touchend.zoom", v.tools.zoomEventProxy(v.events.touchendZoom));
 | |
| 
 | |
|                 // transform graph, if conf is not default
 | |
|                 if (JSON.stringify(v.conf.transform) !== JSON.stringify(v.confDefaults.transform)) {
 | |
|                     v.dom.graph.attr("transform", "translate(" + v.main.zoom.translate() + ")scale(" +
 | |
|                         v.main.zoom.scale() + ")");
 | |
|                     v.tools.writeConfObjectIntoWizard();
 | |
|                 }
 | |
|             } else {
 | |
|                 // http://stackoverflow.com/questions/22302919/
 | |
|                 // unregister-zoom-listener-and-restore-scroll-ability-in-d3-js/22303160?noredirect=1#22303160
 | |
|                 v.dom.graphOverlay.on(".zoom", null);
 | |
|                 v.main.zoom.translate([0, 0]);
 | |
|                 v.main.zoom.scale(1);
 | |
|                 v.conf.transform = {
 | |
|                     "translate": [0, 0],
 | |
|                     "scale": 1
 | |
|                 };
 | |
|                 v.dom.graph.attr("transform", "translate(0,0)scale(1)");
 | |
|             }
 | |
|             v.tools.createCustomizeWizardIfNotRendering();
 | |
|         }
 | |
|         return graph;
 | |
|     };
 | |
| 
 | |
|     graph.minZoomFactor = function(value) {
 | |
|         if (!arguments.length) {
 | |
|             return v.conf.minZoomFactor;
 | |
|         }
 | |
|         v.conf.minZoomFactor = value;
 | |
|         if (v.status.graphReady) {
 | |
|             graph.zoomMode(v.conf.zoomMode);
 | |
|         }
 | |
|         return graph;
 | |
|     };
 | |
| 
 | |
|     graph.maxZoomFactor = function(value) {
 | |
|         if (!arguments.length) {
 | |
|             return v.conf.maxZoomFactor;
 | |
|         }
 | |
|         v.conf.maxZoomFactor = value;
 | |
|         if (v.status.graphReady) {
 | |
|             graph.zoomMode(v.conf.zoomMode);
 | |
|         }
 | |
|         return graph;
 | |
|     };
 | |
| 
 | |
|     graph.zoom = function(centerX, centerY, viewportWidth) {
 | |
|         if (v.conf.zoomMode) {
 | |
|             var width = v.tools.getGraphWidth();
 | |
|             if (!centerX) {
 | |
|                 centerX = width / 2;
 | |
|             }
 | |
|             if (!centerY) {
 | |
|                 centerY = v.conf.height / 2;
 | |
|             }
 | |
|             if (!viewportWidth) {
 | |
|                 viewportWidth = width;
 | |
|             }
 | |
|             v.main.zoom.scale(width / viewportWidth);
 | |
|             if (v.main.zoom.scale() < v.conf.minZoomFactor) {
 | |
|                 v.main.zoom.scale(v.conf.minZoomFactor);
 | |
|             }
 | |
|             if (v.main.zoom.scale() > v.conf.maxZoomFactor) {
 | |
|                 v.main.zoom.scale(v.conf.maxZoomFactor);
 | |
|             }
 | |
|             v.main.zoom.translate([
 | |
|                 (width / 2 - centerX * v.main.zoom.scale()), (v.conf.height / 2 - centerY * v.main.zoom.scale())
 | |
|             ]);
 | |
|             v.conf.transform = {
 | |
|                 "translate": v.main.zoom.translate(),
 | |
|                 "scale": v.main.zoom.scale()
 | |
|             };
 | |
|             v.main.zoom.event(v.dom.graphOverlay);
 | |
|             v.tools.writeConfObjectIntoWizard();
 | |
|         }
 | |
|         return graph;
 | |
|     };
 | |
| 
 | |
|     graph.zoomSmooth = function(centerX, centerY, viewportWidth, duration) {
 | |
|         if (v.conf.zoomMode) {
 | |
|             var width = v.tools.getGraphWidth();
 | |
|             if (!centerX) {
 | |
|                 centerX = width / 2;
 | |
|             }
 | |
|             if (!centerY) {
 | |
|                 centerY = v.conf.height / 2;
 | |
|             }
 | |
|             if (!viewportWidth) {
 | |
|                 viewportWidth = width;
 | |
|             }
 | |
|             if (!duration) {
 | |
|                 duration = 1500;
 | |
|             }
 | |
|             v.main.zoom.scale(width / viewportWidth);
 | |
|             if (v.main.zoom.scale() < v.conf.minZoomFactor) {
 | |
|                 v.main.zoom.scale(v.conf.minZoomFactor);
 | |
|             }
 | |
|             if (v.main.zoom.scale() > v.conf.maxZoomFactor) {
 | |
|                 v.main.zoom.scale(v.conf.maxZoomFactor);
 | |
|             }
 | |
|             v.main.zoom.translate([
 | |
|                 (width / 2 - centerX * v.main.zoom.scale()), (v.conf.height / 2 - centerY * v.main.zoom.scale())
 | |
|             ]);
 | |
|             v.conf.translate = {
 | |
|                 "translate": v.main.zoom.translate(),
 | |
|                 "scale": v.main.zoom.scale()
 | |
|             };
 | |
|             v.dom.graphOverlay.transition()
 | |
|                 .duration(duration)
 | |
|                 .call(v.main.zoom.event);
 | |
|             v.tools.writeConfObjectIntoWizard();
 | |
|         }
 | |
|         return graph;
 | |
|     };
 | |
| 
 | |
|     graph.showLoadingIndicatorOnAjaxCall = function(value) {
 | |
|         if (!arguments.length) {
 | |
|             return v.conf.showLoadingIndicatorOnAjaxCall;
 | |
|         }
 | |
|         v.conf.showLoadingIndicatorOnAjaxCall = value;
 | |
|         return graph;
 | |
|     };
 | |
| 
 | |
|     graph.showLoadingIndicator = function(value) {
 | |
|         if (v.tools.parseBool(value)) {
 | |
|             v.dom.loading.style("display", "block");
 | |
|         } else {
 | |
|             v.dom.loading.style("display", "none");
 | |
|         }
 | |
|         return graph;
 | |
|     };
 | |
| 
 | |
|     graph.alignFixedNodesToGrid = function(value) {
 | |
|         if (!arguments.length) {
 | |
|             return v.conf.alignFixedNodesToGrid;
 | |
|         }
 | |
|         v.conf.alignFixedNodesToGrid = value;
 | |
|         if (v.status.graphStarted) {
 | |
|             // align fixed nodes to grid
 | |
|             if (v.conf.alignFixedNodesToGrid) {
 | |
|                 // NO aligning on the very first start: this would overwrite user defined positions
 | |
|                 if (v.status.graphReady) {
 | |
|                     v.main.nodes.each(function(n) {
 | |
|                         if (n.fixed) {
 | |
|                             n.x = n.px = v.tools.getNearestGridPosition(n.x, v.conf.width);
 | |
|                             n.y = n.py = v.tools.getNearestGridPosition(n.y, v.conf.height);
 | |
|                         }
 | |
|                     });
 | |
|                 }
 | |
|                 v.main.drag.on("dragend", function(n) {
 | |
|                     n.x = n.px = v.tools.getNearestGridPosition(n.x, v.conf.width);
 | |
|                     n.y = n.py = v.tools.getNearestGridPosition(n.y, v.conf.height);
 | |
|                 });
 | |
|             } else {
 | |
|                 v.main.drag.on("dragend", null);
 | |
|             }
 | |
|             v.tools.createCustomizeWizardIfNotRendering();
 | |
|         }
 | |
|         return graph;
 | |
|     };
 | |
| 
 | |
|     graph.moveFixedNodes = function(x, y) {
 | |
|         if (v.status.graphStarted) {
 | |
|             if (!x) {
 | |
|                 x = 0;
 | |
|             }
 | |
|             if (!y) {
 | |
|                 y = 0;
 | |
|             }
 | |
|             if (x !== 0 || y !== 0) {
 | |
|                 v.main.nodes.each(function(n) {
 | |
|                     if (n.fixed) {
 | |
|                         n.x = n.px = (v.conf.alignFixedNodesToGrid ?
 | |
|                             v.tools.getNearestGridPosition(n.x + x, v.conf.width) : n.x + x);
 | |
|                         n.y = n.py = (v.conf.alignFixedNodesToGrid ?
 | |
|                             v.tools.getNearestGridPosition(n.y + y, v.conf.width) : n.y + y);
 | |
|                     }
 | |
|                 });
 | |
|             }
 | |
|         }
 | |
|         return graph;
 | |
|     };
 | |
| 
 | |
|     graph.gridSize = function(value) {
 | |
|         if (!arguments.length) {
 | |
|             return v.conf.gridSize;
 | |
|         }
 | |
|         v.conf.gridSize = value;
 | |
|         if (v.status.graphStarted) {
 | |
|             v.tools.createCustomizeWizardIfNotRendering();
 | |
|         }
 | |
|         return graph;
 | |
|     };
 | |
| 
 | |
|     graph.nodeEventToStopPinMode = function(value) {
 | |
|         if (!arguments.length) {
 | |
|             return v.conf.nodeEventToStopPinMode;
 | |
|         }
 | |
|         v.conf.nodeEventToStopPinMode = value;
 | |
|         if (v.status.graphStarted) {
 | |
|             v.tools.createCustomizeWizardIfNotRendering();
 | |
|         }
 | |
|         return graph;
 | |
|     };
 | |
| 
 | |
|     graph.onNodeContextmenuPreventDefault = function(value) {
 | |
|         if (!arguments.length) {
 | |
|             return v.conf.onNodeContextmenuPreventDefault;
 | |
|         }
 | |
|         v.conf.onNodeContextmenuPreventDefault = value;
 | |
|         if (v.status.graphStarted) {
 | |
|             v.tools.createCustomizeWizardIfNotRendering();
 | |
|         }
 | |
|         return graph;
 | |
|     };
 | |
| 
 | |
|     graph.nodeEventToOpenLink = function(value) {
 | |
|         if (!arguments.length) {
 | |
|             return v.conf.nodeEventToOpenLink;
 | |
|         }
 | |
|         v.conf.nodeEventToOpenLink = value;
 | |
|         if (v.status.graphStarted) {
 | |
|             v.tools.createCustomizeWizardIfNotRendering();
 | |
|         }
 | |
|         return graph;
 | |
|     };
 | |
| 
 | |
|     graph.nodeLinkTarget = function(value) {
 | |
|         if (!arguments.length) {
 | |
|             return v.conf.nodeLinkTarget;
 | |
|         }
 | |
|         v.conf.nodeLinkTarget = value;
 | |
|         if (v.status.graphStarted) {
 | |
|             v.tools.createCustomizeWizardIfNotRendering();
 | |
|         }
 | |
|         return graph;
 | |
|     };
 | |
| 
 | |
|     graph.autoRefresh = function(value) {
 | |
|         if (!arguments.length) {
 | |
|             return v.conf.autoRefresh;
 | |
|         }
 | |
|         v.conf.autoRefresh = value;
 | |
|         if (v.status.graphStarted) {
 | |
|             if (v.conf.autoRefresh && v.conf.refreshInterval && !v.conf.interval) {
 | |
|                 v.conf.interval = window.setInterval(function() {
 | |
|                     graph.start();
 | |
|                 }, v.conf.refreshInterval);
 | |
|                 v.tools.log("Auto refresh started with an interval of " + v.conf.refreshInterval + " milliseconds.");
 | |
|             } else if (!v.conf.autoRefresh && v.conf.interval) {
 | |
|                 clearInterval(v.conf.interval);
 | |
|                 v.conf.interval = null;
 | |
|                 v.tools.log("Auto refresh stopped.");
 | |
|             }
 | |
|             v.tools.createCustomizeWizardIfNotRendering();
 | |
|         }
 | |
|         return graph;
 | |
|     };
 | |
| 
 | |
|     graph.refreshInterval = function(value) {
 | |
|         if (!arguments.length) {
 | |
|             return v.conf.refreshInterval;
 | |
|         }
 | |
|         v.conf.refreshInterval = value;
 | |
|         if (v.status.graphStarted) {
 | |
|             v.tools.createCustomizeWizardIfNotRendering();
 | |
|         }
 | |
|         return graph;
 | |
|     };
 | |
| 
 | |
|     graph.useDomParentWidth = function(value) {
 | |
|         if (!arguments.length) {
 | |
|             return v.conf.useDomParentWidth;
 | |
|         }
 | |
|         v.conf.useDomParentWidth = value;
 | |
|         if (v.status.graphStarted) {
 | |
|             if (v.conf.useDomParentWidth) {
 | |
|                 v.dom.containerWidth = v.tools.getSvgParentInnerWidth();
 | |
|                 d3.select(window).on("resize", function() {
 | |
|                     var oldWidth = v.dom.containerWidth;
 | |
|                     var newWidth = v.tools.getSvgParentInnerWidth();
 | |
|                     if (oldWidth !== newWidth) {
 | |
|                         v.dom.containerWidth = newWidth;
 | |
|                         graph.width(v.conf.width).resume();
 | |
|                     }
 | |
|                 });
 | |
|             } else {
 | |
|                 d3.select(window).on("resize", null);
 | |
|             }
 | |
|             v.tools.createCustomizeWizardIfNotRendering();
 | |
|         }
 | |
|         return graph;
 | |
|     };
 | |
| 
 | |
|     graph.setDomParentPaddingToZero = function(value) {
 | |
|         if (!arguments.length) {
 | |
|             return v.conf.setDomParentPaddingToZero;
 | |
|         }
 | |
|         v.conf.setDomParentPaddingToZero = value;
 | |
|         if (v.status.graphStarted) {
 | |
|             if (v.conf.setDomParentPaddingToZero) {
 | |
|                 v.dom.svgParent.style("padding", "0");
 | |
|             } else {
 | |
|                 v.dom.svgParent.style("padding", null);
 | |
|             }
 | |
|             v.tools.createCustomizeWizardIfNotRendering();
 | |
|         }
 | |
|         return graph;
 | |
|     };
 | |
| 
 | |
|     graph.domParentWidth = function() {
 | |
|         return v.dom.containerWidth || v.tools.getSvgParentInnerWidth();
 | |
|     };
 | |
| 
 | |
|     graph.width = function(value) {
 | |
|         if (!arguments.length) {
 | |
|             return v.conf.width;
 | |
|         }
 | |
|         v.conf.width = value;
 | |
|         if (v.status.graphStarted) {
 | |
|             v.dom.svg.attr("width", v.tools.getGraphWidth());
 | |
|             v.dom.graphOverlaySizeHelper.attr("width", v.tools.getGraphWidth());
 | |
|             v.dom.loadingRect.attr("width", v.tools.getGraphWidth());
 | |
|             v.dom.loadingText.attr("x", v.tools.getGraphWidth() / 2);
 | |
|             v.main.force.size([v.tools.getGraphWidth(), v.conf.height]);
 | |
|             if (v.conf.zoomMode) {
 | |
|                 v.main.zoom.size([v.tools.getGraphWidth(), v.conf.height]);
 | |
|             }
 | |
|             v.tools.createCustomizeWizardIfNotRendering();
 | |
|         }
 | |
|         return graph;
 | |
|     };
 | |
| 
 | |
|     graph.height = function(value) {
 | |
|         if (!arguments.length) {
 | |
|             return v.conf.height;
 | |
|         }
 | |
|         v.conf.height = value;
 | |
|         if (v.status.graphStarted) {
 | |
|             v.dom.svg.attr("height", v.conf.height);
 | |
|             v.dom.graphOverlaySizeHelper.attr("height", v.conf.height);
 | |
|             v.dom.loadingRect.attr("height", v.conf.height);
 | |
|             v.dom.loadingText.attr("y", v.conf.height / 2);
 | |
|             v.main.force.size([v.tools.getGraphWidth(), v.conf.height]);
 | |
|             if (v.conf.showLegend) {
 | |
|                 v.tools.removeLegend();
 | |
|                 v.tools.createLegend();
 | |
|             }
 | |
|             if (v.conf.zoomMode) {
 | |
|                 v.main.zoom.size([v.tools.getGraphWidth(), v.conf.height]);
 | |
|             }
 | |
|             v.tools.createCustomizeWizardIfNotRendering();
 | |
|         }
 | |
|         return graph;
 | |
|     };
 | |
| 
 | |
|     graph.minNodeRadius = function(value) {
 | |
|         if (!arguments.length) {
 | |
|             return v.conf.minNodeRadius;
 | |
|         }
 | |
|         v.conf.minNodeRadius = value;
 | |
|         if (v.status.graphReady) {
 | |
|             v.tools.setRadiusFunction();
 | |
|             v.main.nodes.each(function(n) {
 | |
|                 n.radius = v.tools.radius(n.SIZEVALUE);
 | |
|             });
 | |
|             v.main.nodes.attr("r", function(n) {
 | |
|                 return n.radius;
 | |
|             });
 | |
|             v.tools.createCustomizeWizardIfNotRendering();
 | |
|         }
 | |
|         return graph;
 | |
|     };
 | |
| 
 | |
|     graph.maxNodeRadius = function(value) {
 | |
|         if (!arguments.length) {
 | |
|             return v.conf.maxNodeRadius;
 | |
|         }
 | |
|         v.conf.maxNodeRadius = value;
 | |
|         if (v.status.graphReady) {
 | |
|             v.tools.setRadiusFunction();
 | |
|             v.main.nodes.each(function(n) {
 | |
|                 n.radius = v.tools.radius(n.SIZEVALUE);
 | |
|             });
 | |
|             v.main.nodes.attr("r", function(n) {
 | |
|                 return n.radius;
 | |
|             });
 | |
|             v.tools.createCustomizeWizardIfNotRendering();
 | |
|         }
 | |
|         return graph;
 | |
|     };
 | |
| 
 | |
|     graph.labelDistance = function(value) {
 | |
|         if (!arguments.length) {
 | |
|             return v.conf.labelDistance;
 | |
|         }
 | |
|         v.conf.labelDistance = value;
 | |
|         if (v.status.graphStarted) {
 | |
|             v.tools.createCustomizeWizardIfNotRendering();
 | |
|         }
 | |
|         return graph;
 | |
|     };
 | |
| 
 | |
|     graph.selfLinkDistance = function(value) {
 | |
|         if (!arguments.length) {
 | |
|             return v.conf.selfLinkDistance;
 | |
|         }
 | |
|         v.conf.selfLinkDistance = value;
 | |
|         if (v.status.graphStarted) {
 | |
|             v.tools.createCustomizeWizardIfNotRendering();
 | |
|         }
 | |
|         return graph;
 | |
|     };
 | |
| 
 | |
|     graph.linkDistance = function(value) {
 | |
|         if (!arguments.length) {
 | |
|             return v.conf.linkDistance;
 | |
|         }
 | |
|         v.conf.linkDistance = value;
 | |
|         if (v.status.graphStarted) {
 | |
|             v.main.force.linkDistance(v.conf.linkDistance);
 | |
|             v.tools.createCustomizeWizardIfNotRendering();
 | |
|         }
 | |
|         return graph;
 | |
|     };
 | |
| 
 | |
|     graph.charge = function(value) {
 | |
|         if (!arguments.length) {
 | |
|             return v.conf.charge;
 | |
|         }
 | |
|         v.conf.charge = value;
 | |
|         if (v.status.graphStarted) {
 | |
|             v.main.force.charge(v.conf.charge);
 | |
|             v.tools.createCustomizeWizardIfNotRendering();
 | |
|         }
 | |
|         return graph;
 | |
|     };
 | |
| 
 | |
|     graph.chargeDistance = function(value) {
 | |
|         if (!arguments.length) {
 | |
|             return v.conf.chargeDistance;
 | |
|         }
 | |
|         v.conf.chargeDistance = value;
 | |
|         if (v.status.graphStarted) {
 | |
|             v.main.force.chargeDistance(v.conf.chargeDistance);
 | |
|             v.tools.createCustomizeWizardIfNotRendering();
 | |
|         }
 | |
|         return graph;
 | |
|     };
 | |
| 
 | |
|     graph.gravity = function(value) {
 | |
|         if (!arguments.length) {
 | |
|             return v.conf.gravity;
 | |
|         }
 | |
|         v.conf.gravity = value;
 | |
|         if (v.status.graphStarted) {
 | |
|             v.main.force.gravity(v.conf.gravity);
 | |
|             v.tools.createCustomizeWizardIfNotRendering();
 | |
|         }
 | |
|         return graph;
 | |
|     };
 | |
| 
 | |
|     graph.linkStrength = function(value) {
 | |
|         if (!arguments.length) {
 | |
|             return v.conf.linkStrength;
 | |
|         }
 | |
|         v.conf.linkStrength = value;
 | |
|         if (v.status.graphStarted) {
 | |
|             v.main.force.linkStrength(v.conf.linkStrength);
 | |
|             v.tools.createCustomizeWizardIfNotRendering();
 | |
|         }
 | |
|         return graph;
 | |
|     };
 | |
| 
 | |
|     graph.friction = function(value) {
 | |
|         if (!arguments.length) {
 | |
|             return v.conf.friction;
 | |
|         }
 | |
|         v.conf.friction = value;
 | |
|         if (v.status.graphStarted) {
 | |
|             v.main.force.friction(v.conf.friction);
 | |
|             v.tools.createCustomizeWizardIfNotRendering();
 | |
|         }
 | |
|         return graph;
 | |
|     };
 | |
| 
 | |
|     graph.theta = function(value) {
 | |
|         if (!arguments.length) {
 | |
|             return v.conf.theta;
 | |
|         }
 | |
|         v.conf.theta = value;
 | |
|         if (v.status.graphStarted) {
 | |
|             v.main.force.theta(v.conf.theta);
 | |
|             v.tools.createCustomizeWizardIfNotRendering();
 | |
|         }
 | |
|         return graph;
 | |
|     };
 | |
| 
 | |
|     graph.positions = function(value) {
 | |
|         if (!arguments.length) {
 | |
|             var positions = [];
 | |
|             v.data.nodes.forEach(function(n) {
 | |
|                 positions.push({
 | |
|                     "ID": n.ID,
 | |
|                     "x": Math.round(n.x),
 | |
|                     "y": Math.round(n.y),
 | |
|                     "fixed": (n.fixed ? 1 : 0)
 | |
|                 });
 | |
|             });
 | |
|             return positions;
 | |
|         } else {
 | |
|             if (v.status.graphReady) {
 | |
|                 if (value.constructor === Array) {
 | |
|                     value.forEach(function(n) {
 | |
|                         if (v.data.idLookup[n.ID] !== undefined) {
 | |
|                             v.data.idLookup[n.ID].fixed = v.tools.parseBool(n.fixed);
 | |
|                             v.data.idLookup[n.ID].x = v.data.idLookup[n.ID].px = n.x;
 | |
|                             v.data.idLookup[n.ID].y = v.data.idLookup[n.ID].py = n.y;
 | |
|                         }
 | |
|                     });
 | |
|                 } else {
 | |
|                     v.tools.logError("Unable to set node positions: positions method parameter must be an array of " +
 | |
|                         "node positions");
 | |
|                 }
 | |
|             } else {
 | |
|                 v.conf.positions = value; // we do positioning later after start() is called
 | |
|             }
 | |
|             return graph;
 | |
|         }
 | |
|     };
 | |
| 
 | |
|     graph.onLinkClickFunction = function(value) {
 | |
|         if (!arguments.length) {
 | |
|             return v.conf.onLinkClickFunction;
 | |
|         }
 | |
|         v.conf.onLinkClickFunction = value;
 | |
|         return graph;
 | |
|     };
 | |
| 
 | |
|     graph.onNodeMouseenterFunction = function(value) {
 | |
|         if (!arguments.length) {
 | |
|             return v.conf.onNodeMouseenterFunction;
 | |
|         }
 | |
|         v.conf.onNodeMouseenterFunction = value;
 | |
|         return graph;
 | |
|     };
 | |
| 
 | |
|     graph.onNodeMouseleaveFunction = function(value) {
 | |
|         if (!arguments.length) {
 | |
|             return v.conf.onNodeMouseleaveFunction;
 | |
|         }
 | |
|         v.conf.onNodeMouseleaveFunction = value;
 | |
|         return graph;
 | |
|     };
 | |
| 
 | |
|     graph.onNodeClickFunction = function(value) {
 | |
|         if (!arguments.length) {
 | |
|             return v.conf.onNodeClickFunction;
 | |
|         }
 | |
|         v.conf.onNodeClickFunction = value;
 | |
|         return graph;
 | |
|     };
 | |
| 
 | |
|     graph.onNodeDblclickFunction = function(value) {
 | |
|         if (!arguments.length) {
 | |
|             return v.conf.onNodeDblclickFunction;
 | |
|         }
 | |
|         v.conf.onNodeDblclickFunction = value;
 | |
|         return graph;
 | |
|     };
 | |
| 
 | |
|     graph.onNodeContextmenuFunction = function(value) {
 | |
|         if (!arguments.length) {
 | |
|             return v.conf.onNodeContextmenuFunction;
 | |
|         }
 | |
|         v.conf.onNodeContextmenuFunction = value;
 | |
|         return graph;
 | |
|     };
 | |
| 
 | |
|     graph.onLassoStartFunction = function(value) {
 | |
|         if (!arguments.length) {
 | |
|             return v.conf.onLassoStartFunction;
 | |
|         }
 | |
|         v.conf.onLassoStartFunction = value;
 | |
|         return graph;
 | |
|     };
 | |
| 
 | |
|     graph.onLassoEndFunction = function(value) {
 | |
|         if (!arguments.length) {
 | |
|             return v.conf.onLassoEndFunction;
 | |
|         }
 | |
|         v.conf.onLassoEndFunction = value;
 | |
|         return graph;
 | |
|     };
 | |
| 
 | |
|     graph.sampleData = function(value) {
 | |
|         if (!arguments.length) {
 | |
|             return v.data.sampleData;
 | |
|         }
 | |
|         v.data.sampleData = value;
 | |
|         return graph;
 | |
|     };
 | |
| 
 | |
|     graph.data = function() {
 | |
|         return v.data.dataConverted;
 | |
|     };
 | |
| 
 | |
|     graph.nodeDataById = function(ID) {
 | |
|         return v.data.idLookup[ID];
 | |
|     };
 | |
| 
 | |
|     graph.options = function(value) {
 | |
|         var key;
 | |
|         if (!arguments.length) {
 | |
|             var conf = {};
 | |
|             for (key in v.conf) {
 | |
|                 if (v.conf.hasOwnProperty(key)) {
 | |
|                     if (v.confDefaults.hasOwnProperty(key)) {
 | |
|                         if ((v.confDefaults[key].type === "bool" ||
 | |
|                                 v.confDefaults[key].type === "number" ||
 | |
|                                 v.confDefaults[key].type === "text") &&
 | |
|                             v.confDefaults[key].val !== v.conf[key]) {
 | |
|                             conf[key] = v.conf[key];
 | |
|                         } else if (v.confDefaults[key].type === "object" &&
 | |
|                             JSON.stringify(v.confDefaults[key].val) !== JSON.stringify(v.conf[key])) {
 | |
|                             conf[key] = v.conf[key];
 | |
|                         }
 | |
|                     } else if (!v.confDefaults.hasOwnProperty(key) &&
 | |
|                         v.conf[key] !== undefined &&
 | |
|                         v.conf[key] !== null) {
 | |
|                         conf[key] = v.conf[key];
 | |
|                     }
 | |
|                 }
 | |
|             }
 | |
|             return conf;
 | |
|         } else {
 | |
|             v.tools.applyConfigurationObject(value);
 | |
|             return graph;
 | |
|         }
 | |
|     };
 | |
| 
 | |
|     graph.optionsCustomizationWizard = function(value) {
 | |
|         var key;
 | |
|         if (!arguments.length) {
 | |
|             var conf = {};
 | |
|             for (key in v.confDefaults) {
 | |
|                 if (v.confDefaults.hasOwnProperty(key)) {
 | |
|                     if ((v.confDefaults[key].type === "bool" ||
 | |
|                             v.confDefaults[key].type === "number" ||
 | |
|                             v.confDefaults[key].type === "text") &&
 | |
|                         v.confDefaults[key].val !== v.conf[key]) {
 | |
|                         conf[key] = v.conf[key];
 | |
|                     } else if (v.confDefaults[key].type === "object" &&
 | |
|                         JSON.stringify(v.confDefaults[key].val) !== JSON.stringify(v.conf[key])) {
 | |
|                         conf[key] = v.conf[key];
 | |
|                     }
 | |
|                 }
 | |
|             }
 | |
|             return conf;
 | |
|         } else {
 | |
|             v.tools.applyConfigurationObject(value);
 | |
|             return graph;
 | |
|         }
 | |
|     };
 | |
| 
 | |
|     graph.customize = function(value) {
 | |
|         if (!arguments.length) {
 | |
|             return v.status.customize;
 | |
|         }
 | |
|         v.status.customize = value;
 | |
|         if (v.status.graphStarted) {
 | |
|             if (v.status.customize) {
 | |
|                 v.tools.createCustomizeWizard();
 | |
|             } else {
 | |
|                 v.tools.removeCustomizeWizard();
 | |
|             }
 | |
|         }
 | |
|         return graph;
 | |
|     };
 | |
| 
 | |
|     graph.debug = function(value) {
 | |
|         if (!arguments.length) {
 | |
|             return v.conf.debug;
 | |
|         }
 | |
|         v.conf.debug = value;
 | |
|         if (v.status.graphStarted) {
 | |
|             if (v.conf.debug) {
 | |
|                 v.tools.createCustomizeLink();
 | |
|             } else {
 | |
|                 v.tools.removeCustomizeLink();
 | |
|             }
 | |
|         }
 | |
|         return graph;
 | |
|     };
 | |
| 
 | |
|     graph.releaseFixedNodes = function() {
 | |
|         if (v.status.graphStarted) {
 | |
|             v.main.nodes.each(function(n) {
 | |
|                 n.fixed = 0;
 | |
|             });
 | |
|         }
 | |
|         return graph;
 | |
|     };
 | |
| 
 | |
|     graph.userAgent = function() {
 | |
|         return v.status.userAgent;
 | |
|     };
 | |
| 
 | |
|     // public inspect function: to inspect the global object, which holds all data, functions and references
 | |
|     graph.inspect = function() {
 | |
|         return v;
 | |
|     };
 | |
| 
 | |
|     graph.version = function() {
 | |
|         return v.version;
 | |
|     };
 | |
| 
 | |
|     /*******************************************************************************************************************
 | |
|      * Startup code - runs one time after the initialization of a new chart - example:
 | |
|      * var myChart = net_gobrechts_d3_force( pDomContainerId, pConf, pApexPluginId ).start();
 | |
|      */
 | |
| 
 | |
|     // bind to the apexrefresh event, so that this region can be refreshed by a dynamic action
 | |
|     if (v.status.apexPluginId) {
 | |
|         apex.jQuery("#" + v.dom.containerId).bind("apexrefresh", function() {
 | |
|             graph.start();
 | |
|         });
 | |
|     }
 | |
| 
 | |
|     // final return
 | |
|     return graph;
 | |
| 
 | |
| }
 |