chart = {
const svg = d3.select(DOM.svg(width, height))
.attr("font-family", "BlinkMacSystemFont")
.attr("font-size", 12)
.attr("font-weight", "normal")
.attr("shape-rendering", "geometricPrecision")
.style("user-select", "none")
svg.append("defs")
.call(defs => defs.append("linearGradient")
.attr("id", "bg")
.call(lg => lg.append("stop").attr("offset", "0%").attr("stop-color", "#dadada"))
.call(lg => lg.append("stop").attr("offset", "20%").attr("stop-color", "#fbfbfb"))
.call(lg => lg.append("stop").attr("offset", "80%").attr("stop-color", "#fbfbfb"))
.call(lg => lg.append("stop").attr("offset", "100%").attr("stop-color", "#dadada"))
)
.call(defs => defs.append("marker")
.attr("id", "arrowhead")
.attr("viewBox", "0 -5 10 11")
.attr("refX", 9)
.attr("refY", 0)
.attr("markerWidth", "1em")
.attr("markerHeight", "1em")
.attr("markerUnits", "userSpaceOnUse")
.attr("orient", "auto-start-reverse")
.append("path")
.attr("d", "M0,-5L11,0,L0,5")
)
;
svg.append("rect")
.attr("x", xAxisPadding)
.attr("width", width - 2*xAxisPadding)
.attr("y", height * 0.08)
.attr("height", height * 0.84)
.attr("fill", "url(#bg)")
svg.append("g")
.attr("class", "yaxis")
svg.append("g")
.attr("class", "xaxis")
svg.append("g")
.attr("class", "meridians")
const linkGroup = svg.append("g")
.attr("stroke", "#000")
.attr("stroke-width", 1.5)
.attr("stroke-opacity", 0.3);
const nodeGroup = svg.append("g")
.style("text-shadow", "rgba(255, 255, 255, 1) 0 0 5px")
.attr("stroke", "#fff")
.attr("stroke-width", 2);
var voronoiEdges = svg.append("g")
.style("stroke", "red")
.style("fill", "none")
.attr("class", "voronoi-edges");
var voronoiCentroids = svg.append("g")
.attr("stroke", "orange")
.attr("class", "voronoi-centroids");
svg.append("g")
.append("text")
.attr("class", "xaxis-title");
svg.append("g")
.append("text")
.attr("class", "yaxis-title");
svg.on("click", function() {
console.log("click")
});
let setSelection = (values) => {
svg.node().value = values
svg.node().dispatchEvent(new CustomEvent("input"))
}
function updateData(data, xScale) {
linkGroup
.selectAll("line")
.data(data.links)
.join("line")
.attr("stroke-width", d => Math.sqrt(d.value))
.attr("x1", d => d.source.x)
.attr("y1", d => d.source.y)
.attr("x2", d => d.target.x)
.attr("y2", d => d.target.y)
const node = nodeGroup
.selectAll("g")
.data(data.nodes)
.join(
enter => enter.append("g")
.call(drag(simulation, xScale))
.call(enter => enter.append('circle'))
.call(enter => enter.append('text')),
update => update,
exit => exit.remove(),
)
;
node
.attr("transform", d => translate(d.x, d.y))
.style("cursor", d => d.props.phase[0] === undefined ? "arrow" : "ew-resize")
.on("click", function() {
data.nodes.forEach(d => (d.selected = false))
var selected = d3.select(this).data()
selected.forEach(d => (d.selected = true))
setSelection(selected)
});
;
node.select("circle")
.attr("r", 7)
.attr("fill", "white")
.attr("stroke", color)
.attr("stroke-width", d => d.selected ? 4 : 2)
.append("title")
.text(d => d.id);
node.select("text")
.attr("dy", 15)
.attr("y", 0)
.attr("font-weight", 500)
.attr("stroke", "none")
.text(d => d.id)
.each(function(d) {
if (!d.polygon) {
d3.select(this).call(orient.botton)
return
}
const {x, y} = d, [cx, cy] = d3.polygonCentroid(d.polygon);
const angle = Math.round(Math.atan2(cy - y, cx - x) / Math.PI * 2);
// console.log("angle", angle, d)
d3.select(this).call(
angle === 0 ? orient.right
: angle === -1 ? orient.top
: angle === 1 ? orient.bottom
: orient.left);
})
;
const meridians = xScale.stops()
let shownMeridians =
(meridians.length === 1 && meridians[0] === undefined) ? []
: meridians.map(d => ({
x: xScale(d),
value: d,
stroke: "#aaa",
strokeDasharray: "1 2",
}))
data.nodes.forEach(d => {
if (!d.dragging) {
return
}
let phase = d.dragging
console.log("phase", phase)
d3.range(0, 1, 0.1).forEach(
i => {
let x = xScale([phase, i])
shownMeridians.push(
{
x,
stroke: x == d.fx ? "#000" : "#ccc",
strokeDasharray: x == d.fx ? "1" : "1 2",
}
)
}
)
})
// console.log("shownMeridians", shownMeridians, meridians)
svg.select("g.meridians")
.selectAll("line")
.data(shownMeridians)
.join("line")
.attr("stroke", d => d.stroke)
.attr("stroke-dasharray", d => d.strokeDasharray)
.attr("x1", d => d.x)
.attr("x2", d => d.x)
.attr("y1", 2*yAxisPadding)
.attr("y2", height - yAxisPadding)
;
if (debugVoronoi) {
voronoiEdges.selectAll("path")
.data(data.nodes)
.join("path")
.attr("d", d => `M${d.polygon.join("L")}Z`)
;
voronoiCentroids.selectAll("path")
.data(data.nodes)
.join("path")
.attr("d", d => `M${d3.polygonCentroid(d.polygon)}L${d.x},${d.y}`);
}
}
function updateAll(data, xScale, yScale) {
svg.select("text.yaxis-title")
.attr("transform", translate(xAxisPadding, height/2 - yAxisPadding) + " rotate(-90)")
.attr("text-anchor", "middle")
.attr("font-size", 16)
.attr("font-weight", "bold")
.attr("alignment-baseline", "baseline")
.attr("dy", "-0.5em")
.text(yScale.title())
;
svg.select("text.xaxis-title")
.attr("transform", translate(width - xAxisPadding, height - 2*yAxisPadding))
.attr("text-anchor", "end")
.attr("font-size", 16)
.attr("font-weight", "bold")
.attr("alignment-baseline", "hanging")
.attr("dy", "1.5em")
.text(xScale.title())
;
let tickValues = xScale.stops()
if (tickValues.length === 1 && tickValues[0][0] === undefined) {
tickValues = []
}
svg.select("g.xaxis")
.attr("transform", translate(0, yScale(d3.max(yScale.domain()))))
.call(
d3.axisBottom(xScale)
.tickValues(tickValues)
.tickSize(0)
.tickFormat(d => {
switch (d[0]) {
case undefined:
return "unknown"
default:
return d[0]
}
})
)
.call(g => g.select("path")
.style("dominant-baseline", "central")
.attr("stroke-width", d => xScale.title() ? 2 : 0)
.attr("marker-end", d => xScale.title() ? "url(#arrowhead)" : undefined)
)
.call(g => g.selectAll("text")
.attr("font-size", 12)
.attr("font-style", "italic")
.attr("dx", "0.5em")
.attr("y", "0.5em")
.style("text-transform", "capitalize")
.style("text-anchor", "start")
.style("user-select", "none")
)
;
svg.select("g.yaxis")
.call(
d3.axisLeft(yScale)
.tickValues(d3.extent(yScale.domain()))
.tickFormat(d => d === 0 ? "Visible" : "Invisible")
.tickSize(0)
)
.call(g => g.selectAll("text")
.attr("transform", "rotate(-90)")
.attr("dy", "-0.75em")
.attr("dx", d => d === 0 ? "-0.5em" : "0.5em")
.attr("font-size", 12)
// .attr("font-style", "italic")
.attr("text-anchor", d => {
// console.log("text-anchor", d)
return d === 0 ? "end" : "start"
})
)
.attr("transform", translate(xScale(xScale.stops()[0]), 0))
.call(g => g.select("path")
.style("dominant-baseline", "central")
.attr("stroke-width", 2)
.attr("marker-start", "url(#arrowhead)")
)
;
updateData(data, xScale)
}
return {svg, updateAll, updateData}
}