App-Netdisco
view release on metacpan or search on metacpan
share/public/javascripts/d3-force-network-chart.js view on Meta::CPAN
}; // --> 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 + "_customizing").remove();
// d3.selectAll("#" + v.dom.containerId + "_tooltip").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
share/public/javascripts/d3-force-network-chart.js view on Meta::CPAN
// 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("#d3-force-customize-link") === null) {
v.dom.svg.append("svg:text")
.attr("id", "d3-force-customize-link")
.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("#d3-force-customize-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) {
share/public/javascripts/d3-force-network-chart.js view on Meta::CPAN
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;
}
share/public/javascripts/d3-force-network-chart.js view on Meta::CPAN
.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;
};
/**
* The `resume` method restarts only the force on your graph without a `render` cycle. This saves CPU time and can be useful if you change only things in your graph which do not need rendering to taking into effect:
*
* example.releaseFixedNodes().resume();
* @see {@link module:API.start}
* @see {@link module:API.render}
* @returns {Object} The graph object for method chaining.
*/
graph.resume = function() {
v.main.force.resume();
v.tools.createCustomizeWizardIfNotRendering();
return graph;
};
/**
* If true, a class named border is added to the SVG element, if false the class will be removed. The border itself is defined in the delivered CSS - you can overwrite it if the current style does not match your needs. No `render` or `resume` cal...
*
* example.showBorder(false);
* @param {boolean} [value=true] - The new config value.
* @returns {(boolean|Object)} The current config value if no parameter is given or the graph object for method chaining.
*/
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;
};
/**
* If true, a legend for all COLORVALUEs in the node data is rendered in the bottom left corner of the graph. No `render` or `resume` call needed to take into effect:
*
* example.showLegend(false);
* @param {boolean} [value=true] - The new config value.
* @returns {(boolean|Object)} The current config value if no parameter is given or the graph object for method chaining.
*/
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;
};
/**
* If true, then links with the same source and target are rendered along a path around the node bottom. Needs a `render` call to take into effect:
*
* example.showSelfLinks(false).render();
* @param {boolean} [value=true] - The new config value.
* @returns {(boolean|Object)} The current config value if no parameter is given or the graph object for method chaining.
*/
graph.showSelfLinks = function(value) {
if (!arguments.length) {
return v.conf.showSelfLinks;
}
v.conf.showSelfLinks = value;
if (v.status.graphStarted) {
v.tools.createCustomizeWizardIfNotRendering();
}
return graph;
};
/**
* If true, you get an marker at the end of a link. Needs a `render` call to take into effect:
*
* example.showLinkDirection(false).render();
* @param {boolean} [value=true] - The new config value.
* @returns {(boolean|Object)} The current config value if no parameter is given or the graph object for method chaining.
*/
graph.showLinkDirection = function(value) {
if (!arguments.length) {
return v.conf.showLinkDirection;
}
v.conf.showLinkDirection = value;
if (v.status.graphStarted) {
v.tools.createCustomizeWizardIfNotRendering();
}
return graph;
};
/**
* If true and you provided in your source data an attribute INFOSTRING, then a tooltip is shown by hovering a node. No `render` or `resume` call needed to take into effect:
( run in 1.182 second using v1.01-cache-2.11-cpan-39bf76dae61 )