fmt = v => {
const r = Math.round(v * 100) / 100;
return (r < 0 ? "−" : "") + Math.abs(r).toString();
}
mul = (M, N) => [
[M[0][0] * N[0][0] + M[0][1] * N[1][0], M[0][0] * N[0][1] + M[0][1] * N[1][1]],
[M[1][0] * N[0][0] + M[1][1] * N[1][0], M[1][0] * N[0][1] + M[1][1] * N[1][1]]
]
{
const A = transforms.get(aName), B = transforms.get(bName);
const apply = (M, [x, y]) => [M[0][0] * x + M[0][1] * y, M[1][0] * x + M[1][1] * y];
const F = [[0, 0], [0, 1.2], [0.8, 1.2], [0.8, 0.95], [0.25, 0.95], [0.25, 0.7], [0.65, 0.7], [0.65, 0.45], [0.25, 0.45], [0.25, 0], [0, 0]];
const W = 680, P = 300, gap = 40, R = 2.6, yTop = 8;
const left = (W - 2 * P - gap) / 2;
const svg = d3.create("svg")
.attr("viewBox", `0 0 ${W} ${yTop + P + 96}`)
.attr("class", "viz")
.attr("role", "img")
.attr("aria-label", `The letter F under AB and under BA, with A = ${aName} and B = ${bName}`);
const panels = [
{name: "AB", order: "B First, Then A", first: B, second: A, product: mul(A, B)},
{name: "BA", order: "A First, Then B", first: A, second: B, product: mul(B, A)}
];
panels.forEach((pn, k) => {
const g = svg.append("g").attr("class", "panel").attr("transform", `translate(${left + k * (P + gap)},${yTop})`);
const sx = d3.scaleLinear([-R, R], [0, P]), sy = d3.scaleLinear([-R, R], [P, 0]);
g.append("rect").attr("width", P).attr("height", P).attr("fill", "transparent");
const grid = g.append("g").attr("class", "grid");
for (let i = -2; i <= 2; i++) {
grid.append("line").attr("x1", sx(i)).attr("x2", sx(i)).attr("y1", 0).attr("y2", P);
grid.append("line").attr("x1", 0).attr("x2", P).attr("y1", sy(i)).attr("y2", sy(i));
}
g.append("line").attr("class", "axis").attr("x1", sx(0)).attr("x2", sx(0)).attr("y1", 0).attr("y2", P);
g.append("line").attr("class", "axis").attr("x1", 0).attr("x2", P).attr("y1", sy(0)).attr("y2", sy(0));
const line = d3.line().x(d => sx(d[0])).y(d => sy(d[1]));
const clipId = `clip-${k}`;
g.append("clipPath").attr("id", clipId).append("rect").attr("width", P).attr("height", P);
const shapes = g.append("g").attr("clip-path", `url(#${clipId})`);
shapes.append("path").attr("class", "shape start").attr("d", line(F));
shapes.append("path").attr("class", "shape mid").attr("d", line(F.map(p => apply(pn.first, p))));
shapes.append("path").attr("class", "shape end").attr("d", line(F.map(p => apply(pn.second, apply(pn.first, p)))));
const yt = P + 38;
g.append("text").attr("class", "panel-title").attr("x", 0).attr("y", yt).text(pn.name);
g.append("text").attr("class", "eq").attr("x", 30).attr("y", yt).text("=");
const m = g.append("g").attr("class", "mat").attr("transform", `translate(52,${yt - 9})`);
const cw = 40, rh = 20;
m.append("path").attr("d", `M6,-7 H0 V${rh + 13} H6`);
m.append("path").attr("d", `M${2 * cw + 2},-7 H${2 * cw + 8} V${rh + 13} H${2 * cw + 2}`);
pn.product.forEach((row, i) => row.forEach((v, j) => {
m.append("text").attr("x", 4 + j * cw + cw / 2).attr("y", 4 + i * rh + 6).attr("text-anchor", "middle").text(fmt(v));
}));
g.append("text").attr("class", "panel-sub").attr("x", 0).attr("y", yt + 44).text(pn.order);
});
return svg.node();
}