in src/playground.ts [538:655]
function drawNetwork(network: nn.Node[][]): void {
let svg = d3.select("#svg");
// Remove all svg elements.
svg.select("g.core").remove();
// Remove all div elements.
d3.select("#network").selectAll("div.canvas").remove();
d3.select("#network").selectAll("div.plus-minus-neurons").remove();
// Get the width of the svg container.
let padding = 3;
let co = d3.select(".column.output").node() as HTMLDivElement;
let cf = d3.select(".column.features").node() as HTMLDivElement;
let width = co.offsetLeft - cf.offsetLeft;
svg.attr("width", width);
// Map of all node coordinates.
let node2coord: {[id: string]: {cx: number, cy: number}} = {};
let container = svg.append("g")
.classed("core", true)
.attr("transform", `translate(${padding},${padding})`);
// Draw the network layer by layer.
let numLayers = network.length;
let featureWidth = 118;
let layerScale = d3.scale.ordinal<number, number>()
.domain(d3.range(1, numLayers - 1))
.rangePoints([featureWidth, width - RECT_SIZE], 0.7);
let nodeIndexScale = (nodeIndex: number) => nodeIndex * (RECT_SIZE + 25);
let calloutThumb = d3.select(".callout.thumbnail").style("display", "none");
let calloutWeights = d3.select(".callout.weights").style("display", "none");
let idWithCallout = null;
let targetIdWithCallout = null;
// Draw the input layer separately.
let cx = RECT_SIZE / 2 + 50;
let nodeIds = Object.keys(INPUTS);
let maxY = nodeIndexScale(nodeIds.length);
nodeIds.forEach((nodeId, i) => {
let cy = nodeIndexScale(i) + RECT_SIZE / 2;
node2coord[nodeId] = {cx, cy};
drawNode(cx, cy, nodeId, true, container);
});
// Draw the intermediate layers.
for (let layerIdx = 1; layerIdx < numLayers - 1; layerIdx++) {
let numNodes = network[layerIdx].length;
let cx = layerScale(layerIdx) + RECT_SIZE / 2;
maxY = Math.max(maxY, nodeIndexScale(numNodes));
addPlusMinusControl(layerScale(layerIdx), layerIdx);
for (let i = 0; i < numNodes; i++) {
let node = network[layerIdx][i];
let cy = nodeIndexScale(i) + RECT_SIZE / 2;
node2coord[node.id] = {cx, cy};
drawNode(cx, cy, node.id, false, container, node);
// Show callout to thumbnails.
let numNodes = network[layerIdx].length;
let nextNumNodes = network[layerIdx + 1].length;
if (idWithCallout == null &&
i === numNodes - 1 &&
nextNumNodes <= numNodes) {
calloutThumb.style({
display: null,
top: `${20 + 3 + cy}px`,
left: `${cx}px`
});
idWithCallout = node.id;
}
// Draw links.
for (let j = 0; j < node.inputLinks.length; j++) {
let link = node.inputLinks[j];
let path: SVGPathElement = drawLink(link, node2coord, network,
container, j === 0, j, node.inputLinks.length).node() as any;
// Show callout to weights.
let prevLayer = network[layerIdx - 1];
let lastNodePrevLayer = prevLayer[prevLayer.length - 1];
if (targetIdWithCallout == null &&
i === numNodes - 1 &&
link.source.id === lastNodePrevLayer.id &&
(link.source.id !== idWithCallout || numLayers <= 5) &&
link.dest.id !== idWithCallout &&
prevLayer.length >= numNodes) {
let midPoint = path.getPointAtLength(path.getTotalLength() * 0.7);
calloutWeights.style({
display: null,
top: `${midPoint.y + 5}px`,
left: `${midPoint.x + 3}px`
});
targetIdWithCallout = link.dest.id;
}
}
}
}
// Draw the output node separately.
cx = width + RECT_SIZE / 2;
let node = network[numLayers - 1][0];
let cy = nodeIndexScale(0) + RECT_SIZE / 2;
node2coord[node.id] = {cx, cy};
// Draw links.
for (let i = 0; i < node.inputLinks.length; i++) {
let link = node.inputLinks[i];
drawLink(link, node2coord, network, container, i === 0, i,
node.inputLinks.length);
}
// Adjust the height of the svg.
svg.attr("height", maxY);
// Adjust the height of the features column.
let height = Math.max(
getRelativeHeight(calloutThumb),
getRelativeHeight(calloutWeights),
getRelativeHeight(d3.select("#network"))
);
d3.select(".column.features").style("height", height + "px");
}