in packages/network-navigator/src/NetworkNavigator.ts [322:585]
public renderGraph() {
// TODO: Break this apart, this is ginormous
if (this.graph) {
const graph = this.graph;
const me = this;
this.zoom = d3.behavior.zoom()
.scaleExtent([this._configuration.minZoom, this._configuration.maxZoom])
.on("zoom", () => {
const event = d3.event as d3.ZoomEvent;
this.scale = event.scale;
this.translate = event.translate;
this.zoomToViewport();
this.events.raiseEvent("zoomed", {
scale: this.scale,
translate: this.translate,
});
});
const drag = d3.behavior.drag()
.origin((d: any) => d)
// The use of "function" is important to preserve "this"
.on("dragstart", function(d: any) { // tslint:disable-line only-arrow-functions
// Stop the force graph animation while we are dragging, otherwise it causes the graph to
// jitter while you drag it
(<any>d3.event).sourceEvent.stopPropagation();
d3.select(this).classed("dragging", true);
me.force.stop();
})
.on("drag", function(d: any) { // tslint:disable-line only-arrow-functions
// While we drag, adjust the dragged node, and tell our node renderer to draw a frame
const evt = <any>d3.event;
d.px = d.x = evt.x;
d.py = d.y = evt.y;
/* tslint:disable */
tick();
/* tslint:enable */
})
.on("dragend", function(d: any) { // tslint:disable-line only-arrow-functions
d3.select(this).classed("dragging", false);
// If we have animation on, then start that beast
if (me.configuration.animate) {
me.force.resume();
}
});
this.svg.remove();
this.svg = d3.select(this.svgContainer[0]).append("svg")
.attr("width", this.dimensions.width)
.attr("height", this.dimensions.height)
.attr("preserveAspectRatio", "xMidYMid meet")
.attr("pointer-events", "all")
.call(this.zoom);
this.vis = this.svg.append("svg:g");
const nodes = graph.nodes.slice();
const links: Array<{ source: any; target: any; }> = [];
const bilinks: any[] = [];
graph.links.forEach((graphLink) => {
const s = nodes[graphLink.source];
const t = nodes[graphLink.target];
const w = graphLink.value;
const cw = graphLink.colorValue;
const i = {}; // intermediate node
nodes.push(<any>i);
links.push({ source: s, target: i }, { source: i, target: t });
bilinks.push([s, i, t, w, cw]);
});
this.force.nodes(nodes).links(links);
// If we have animation on, then start that beast
if (this.configuration.animate) {
this.force.start();
}
const determineDomain: (
configMin: number,
configMax: number,
fn: (i: any) => number,
) => [number, number] = (
configMin: number,
configMax: number,
fn: (i: any) => number,
) => {
if (configMin !== undefined && configMax !== undefined) {
return [configMin, configMax];
} else {
let min: number;
let max: number;
const data = bilinks.map(fn);
data.forEach((d: number) => {
if (min === undefined || d < min) {
min = d;
}
if (max === undefined || d > max) {
max = d;
}
});
return [min, max];
}
};
const edgeColorWeightDomain = determineDomain(
this.configuration.minEdgeColorWeight,
this.configuration.maxEdgeColorWeight,
(b) => b[4],
);
const edgeWidthDomain = determineDomain(
this.configuration.minEdgeWeight,
this.configuration.maxEdgeWeight,
(b) => b[3],
);
const edgeColorScale = d3.scale.linear()
.domain(edgeColorWeightDomain)
.interpolate(d3.interpolateRgb as any)
.range([
this.configuration.edgeStartColor,
this.configuration.edgeEndColor,
] as any);
const edgeWidthScale = d3.scale.linear()
.domain(edgeWidthDomain)
.interpolate(d3.interpolateNumber as any)
.range([
this.configuration.edgeMinWidth,
this.configuration.edgeMaxWidth,
]);
const domainBound = (v: number, domain: [number, number]) => (
Math.min(domain[1], Math.max(domain[0], v))
);
const xform = (v: number, scale: Function, domain: [number, number], defaultValue: any) => {
const isValuePresent = v !== undefined;
const boundedValue = isValuePresent ? domainBound(v, domain) : v;
const result = isValuePresent ?
scale(boundedValue) :
defaultValue;
return result;
};
this.vis.append("svg:defs").selectAll("marker")
.data(["end"])
.enter()
.append("svg:marker")
.attr("id", String)
.attr("viewBox", "0 -5 10 10")
.attr("refX", 15)
.attr("refY", 0)
.attr("markerWidth", 7)
.attr("markerHeight", 7)
.attr("orient", "auto")
.append("svg:path")
.attr("d", "M0,-5L10,0L0,5");
const link = this.vis.selectAll(".link")
.data(bilinks)
.enter().append("line")
.attr("class", "link")
.style("stroke", function(d: any) { // tslint:disable-line only-arrow-functions
return xform(
d[4],
edgeColorScale,
edgeColorWeightDomain,
"gray",
);
})
.style("stroke-width", function(d: any) { // tslint:disable-line only-arrow-functions
return xform(
d[3],
edgeWidthScale,
edgeWidthDomain,
DEFAULT_EDGE_SIZE,
);
})
.attr("id", function(d: any) { // tslint:disable-line only-arrow-functions
return d[0].name.replace(/\./g, "_").replace(/@/g, "_") + "_" +
d[2].name.replace(/\./g, "_").replace(/@/g, "_");
});
const node = this.vis.selectAll(".node")
.data(graph.nodes)
.enter().append("g")
.call(drag)
.attr("class", "node");
node.append("svg:circle")
.attr("r", (d: any) => {
let width = d.value;
/* tslint:disable */
if (typeof width === "undefined" || width === null) {
/* tslint:enable */
width = DEFAULT_NODE_SIZE;
}
const maxSize = this._configuration.maxNodeSize;
const minSize = this._configuration.minNodeSize;
width = (maxSize && width > maxSize) ? maxSize : width;
width = (minSize && width < minSize) ? minSize : width;
// Make sure > 0
return width > 0 ? width : 0;
})
.style("fill", (d: any) => d.color)
.style("stroke", "red")
.style("stroke-width", (d: any) => d.selected ? 1 : 0);
node.on("click", (n: INetworkNavigatorNode) => this.onNodeClicked(n));
node.on("mouseover", () => {
/* tslint:disable */
d3.select(this.svgContainer.find("svg text")[0]).style("display", null);
/* tslint:enable */
});
node.on("mouseout", () => {
if (!this._configuration.labels) {
d3.select(this.svgContainer.find("svg text")[0]).style("display", "none");
}
});
link.append("svg:text")
.text((d: any) => "yes")
.attr("fill", "black")
.attr("stroke", "black")
.attr("font-size", () => `${this.configuration.fontSizePT}pt`)
.attr("stroke-width", "0.5px")
.attr("class", "linklabel")
.attr("text-anchor", "middle");
node.append("svg:text")
.attr("class", "node-label")
.text((d: any) => d.name)
.attr("fill", (d: any) => d.labelColor || this.configuration.defaultLabelColor)
.attr("stroke", (d: any) => d.labelColor || this.configuration.defaultLabelColor)
.attr("font-size", () => `${this.configuration.fontSizePT}pt`)
.attr("stroke-width", "0.5px")
/* tslint:disable */
.style("display", this._configuration.labels ? null : "none");
/* tslint:enable */
// If we are not animating, then play the force quickly
if (!this.configuration.animate) {
this.reflow(link, node);
}
// Our tick function, which actually moves the nodes on the svg based on their x/y positions
const tick = () => {
if (this.configuration.animate) {
link.attr("x1", (d) => d[0].x)
.attr("y1", (d) => d[0].y)
.attr("x2", (d) => d[2].x)
.attr("y2", (d) => d[2].y);
node.attr("transform", (d: any) => `translate(${d.x},${d.y})`);
}
};
this.force.on("tick", tick);
}
}