Tensor diagram notation in D3.js
Inspired by http://tensornetwork.org/diagrams/
by stared
HTML
<svg width="600" height="400"></svg>
CSS
.tensor {
stroke-width: 3px;
stroke: black;
}
.contraction {
stroke-width: 3px;
stroke: black;
}
.tensor-label {
font: italic 1.2em "Fira Sans", serif;
fill: black;
text-anchor: middle;
}
.contraction-label {
font: italic 1.2em "Fira Sans", serif;
fill: black;
text-anchor: middle;
dominant-baseline: central;
}
JavaScript
// by Piotr Migdał, https://p.migdal.pl/
// inspiration from http://tensornetwork.org/diagrams/
const tensors = [
{x: 0, y: 0, name: "x"},
{x: 1, y: 0, name: "A"},
{x: 2, y: 0, name: "B"},
];
const contractions = [
{source: tensors[0], target: tensors[1]},
{source: tensors[1], target: tensors[2]},
{source: tensors[1], target: "down", name: "i"},
{source: tensors[2], target: "down", name: "j"},
{source: tensors[2], target: "right", name: "k"},
];
const shifts = {
up: [0, -0.75],
down: [0, 0.75],
left: [-0.75, 0],
right: [0.75, 0]
};
contractions.forEach((d) => {
if (typeof d.target === 'string') {
const dv = shifts[d.target];
d.target = {
x: d.source.x + dv[0],
y: d.source.y + dv[1]
};
d.labelPosition = {
x: d.source.x + 1.4 * dv[0],
y: d.source.y + 1.4 * dv[1]
};
}
});
const xScale = d3.scaleLinear()
.domain([0, 8])
.range([100, 500]);
const yScale = d3.scaleLinear()
.domain([0, 8])
.range([100, 500]);
const lineFunction = d3.line()
.x((d) => xScale(d.x))
.y((d) => yScale(d.y));
//const colorScale = d3.scaleOrdinal(d3.schemeCategory10);
const colorScale = d3.scaleOrdinal()
.range(["#763E9B", "#00882B", "#C82505", "#EEEEEE", "#0165C0"]);
const svg = d3.select("svg");
svg.selectAll(".contraction")
.data(contractions)
.enter().append("path")
.attr("class", "contraction")
.attr("d", (d) => lineFunction([d.source, d.target]));
svg.selectAll(".tensor")
.data(tensors)
.enter().append("circle")
.attr("class", "tensor")
.attr("r", 10)
.attr("cx", (d) => xScale(d.x))
.attr("cy", (d) => yScale(d.y))
.style("fill", (d) => colorScale(d.name));
svg.selectAll(".tensor-label")
.data(tensors)
.enter().append("text")
.attr("class", "tensor-label")
.attr("x", (d) => xScale(d.x))
.attr("y", (d) => yScale(d.y - 0.4))
.text((d) => d.name);
svg.selectAll(".contraction-label")
.data(contractions.filter((d) => !!d.labelPosition))
...