function drawWindowInteractive({ state, chars, showState = false }) {
const height = width * (1 / 7);
const svgWidth = width * (4 / 5);
const svg = d3
.create("svg")
.attr("viewBox", [0, 0, svgWidth, height])
.attr("width", svgWidth);
const { start, end, label } = state;
const marginX = 5;
const xScale = d3
.scaleLinear()
.domain([-0.5, chars.length + 0.5])
.range([marginX, svgWidth - 2 * marginX]);
const windowHeight = height / (7 / 3);
const windowUnit = xScale(1) - xScale(0);
const y = height / 3;
const letters = svg
.selectAll(".letters")
.data(chars)
.join("text")
.attr("class", "letters")
.attr("font-size", 36)
.attr("text-anchor", "middle")
.attr("dominant-baseline", "middle")
.attr("x", (d, i) => xScale(i))
.attr("y", y)
.text((d) => d);
const rect = svg
.selectAll("rect.window")
.data([[start, end]])
.join("rect")
.attr("class", "window")
.attr("x", (d) => xScale(d[0] - 0.5))
.attr("y", y - windowHeight / 2)
.attr("width", ([start, end]) =>
Math.max(windowUnit * Math.max(0.125, end - start))
)
.attr("height", windowHeight)
.style("fill", "#189a18")
.attr("opacity", 0.5)
.attr("rx", 3);
if (showState) {
const variables = svg
.selectAll(".variables")
.data([[start], [end]])
.join("text")
.attr("class", "letters")
.attr("font-size", 14)
.attr("text-anchor", "middle")
.attr("dominant-baseline", "middle")
.attr("x", (d, i) => xScale(d))
.attr(
"y",
(d, i) => y + windowHeight / 2 + 10 + (start === end ? 15 * i : 0)
)
.text((d, i) => (i === 0 ? "left" : "right"));
if (label && label.length > 0) {
const bracketHeight = 10;
const strokeWidth = 5;
const bracketGroup = svg
.selectAll(".lengthLabel")
.data([label])
.join("g")
.attr(
"transform",
([start, end]) =>
`translate(${xScale(start)}, ${y / 4 - bracketHeight})`
)
.attr("stroke", "#65a765")
.attr("opacity", 0.75)
.attr("stroke-width", strokeWidth);
bracketGroup
.append("line")
.attr("x1", 0)
.attr("x2", ([start, end]) => windowUnit * (end - start))
.attr("y1", 0)
.attr("y2", 0);
bracketGroup
.append("line")
.attr("x1", strokeWidth / 2)
.attr("x2", strokeWidth / 2)
.attr("y1", 0)
.attr("y2", bracketHeight);
bracketGroup
.append("line")
.attr(
"x1",
([start, end]) => windowUnit * (end - start) - strokeWidth / 2
)
.attr(
"x2",
([start, end]) => windowUnit * (end - start) - strokeWidth / 2
)
.attr("y1", 0)
.attr("y2", bracketHeight);
}
const stateData = extractVariables(state, ["start", "end", "label"]);
const stateSpaceMargin = 75;
const stateScale = d3
.scaleLinear()
.domain([0, stateData.length])
.range([stateSpaceMargin, width - 2 * stateSpaceMargin]);
svg
.selectAll(".state")
.data(stateData)
.join("text")
.attr("class", "state")
.attr("font-size", 16)
.attr("text-anchor", "start")
.attr("dominant-baseline", "middle")
.attr("x", (d, i) => stateScale(i))
.attr("y", height * (19 / 20))
.text((d) => `${d.name}: ${toString(d.value)}`);
}
const expandable =
index < allStates.length - 1 &&
allStates[index].end === allStates[index + 1].end - 1;
const expand = svg
.selectAll("rect.expand")
.data([[start, end]])
.join("rect")
.attr("class", expandable ? "active expand" : "expand")
.attr("width", expanderWidth)
.attr("y", y - windowHeight / 2 + windowHeight / 4)
.attr(
"x",
([_, end]) =>
xScale(start - 0.5) +
Math.max(windowUnit * (end - start), expanderWidth)
)
.attr("height", windowHeight * 0.5)
.style("fill", "grey")
.attr("opacity", 0.25)
.attr("rx", 1)
.style("cursor", expandable ? "pointer" : "default");
if (expandable)
expand.call(
expanderClosure({ expanderWidth, xScale, windowUnit, selection: svg })
);
if (start !== end) {
const shrinkable =
index < allStates.length - 1 &&
allStates[index].start === allStates[index + 1].start - 1;
const shrink = svg
.selectAll("rect.shrink")
.data([[start, end]])
.join("rect")
.attr("class", shrinkable ? "active shrink" : "shrink")
.attr("width", expanderWidth)
.attr("y", y - windowHeight / 2 + windowHeight / 4)
.attr("x", ([start]) => xScale(start - 0.5) - expanderWidth)
.attr("height", windowHeight * 0.5)
.style("fill", "grey")
.attr("opacity", 0.25)
.attr("rx", 1)
.style("cursor", shrinkable ? "pointer" : "default");
if (shrinkable) {
shrink.call(
shrinkerClosure({ expanderWidth, xScale, windowUnit, selection: svg })
);
}
}
return svg.node();
}