chart = {
const root = tree(bilink(d3.hierarchy(data)
.sort((a, b) => d3.ascending(a.height, b.height) || d3.ascending(a.data.id, b.data.id))));
const svg = d3.create("svg")
.attr("viewBox", [-width*p / 2, -width*p / 2, width*p, width*p]);
const node = svg.append("g")
.attr("font-family", "sans-serif")
.attr("font-size", 6)
.selectAll("g")
.data(root.leaves())
.join("g")
.attr("transform", d => `rotate(${d.x * 180 / Math.PI - 90}) translate(${d.y},0)`)
.append("text")
.attr("dy", "0.31em")
.attr("x", d => d.x < Math.PI ? 6 : -6)
.attr("text-anchor", d => d.x < Math.PI ? "start" : "end")
.attr("transform", d => d.x >= Math.PI ? "rotate(180)" : null)
.text(d => d.data.name)
.attr("fill", d => colorScale(d.data.group))
.each(function(d) { d.text = this; })
.on("mouseover", overed)
.on("mouseout", outed)
.call(text => text.append("title").text(d => `${d.data.name}
${d.outgoing.length} outgoing
${d.incoming.length} incoming`));
const link = svg.append("g")
.attr("stroke", colornone) //ori, colornone
//.attr("stroke", "lightyellow") //ZW, works, but no use
.attr("fill", "none")
.selectAll("path")
.data(root.leaves().flatMap(leaf => leaf.outgoing))
.join("path")
.style("mix-blend-mode", "multiply")
.attr("d", ([i, o]) => line(i.path(o)))
.each(function(d) { d.path = this; });
function overed(event, d) {
link.style("mix-blend-mode", null); //ZW, was null, "multiply" performance not good
//node.style("mix-blend-mode", "multiply"); //ZW,performance not good
node.attr("fill", "lightgrey"); //ZW
link.attr("stroke", "#F3F3F3") //ZW, "#F3F3F3" for very light grey
.attr("stroke-opacity", 1.0) //ZW, no use so far
//.lower() //ZW, ori none
;
d3.select(this).attr("font-weight", "bold")
.attr("font-size", 6.5).attr("fill", d => colorScale(d.data.group)); //ZW
//one-directional links, below all work
d3.selectAll(d.incoming.map(d => d.path)).attr("stroke", colorin)
//.attr("stroke-opacity", 1.0) //ZW, 1.0 no difference
.attr("stroke-width", 1.8) //ZW
.raise() //ori, raise()
;
d3.selectAll(d.incoming.map(([d]) => d.text)).attr("fill", colorin).attr("font-weight", "bold")
.attr("font-size", 6.1); //ZW
d3.selectAll(d.outgoing.map(d => d.path)).attr("stroke", colorout)
//.attr("stroke-opacity", 1.0) //ZW,failed, bad performance
.attr("stroke-width", 0.9) //ZW
.raise() //ori, raise()
;
d3.selectAll(d.outgoing.map(([, d]) => d.text)).attr("fill", colorout) //ori, fill
.attr("font-weight", "bold")
//.attr("fill-opacity", 0.5) //ZW, failed, bad performance
.attr("font-size", 6.1)
//.raise() //ori, nothing
;
//ZW, try bidirectional links, under construction, node works, path not
d3.selectAll(
d.incoming.map(d => d.path)
.filter(x => d.outgoing.map(d => d.path).indexOf(x) !== -1)
)
.attr("stroke", colorbi)
.attr("stroke-opacity", 1.0) //ZW
.attr("stroke-width", 1.0) //ZW
.raise();
d3.selectAll(
d.incoming.map(([d]) => d.text)
.filter(x => d.outgoing.map(([, d]) => d.text).indexOf(x) !== -1)
)
.attr("fill", colorbi) //ZW, works
//ZW, text gradient color, fails
//https://observablehq.com/d/b507eb40266749a1
//https://cssgradient.io/blog/css-gradient-text/
//https://stackoverflow.com/questions/40622594/how-do-i-make-a-colour-changing-text-gradient-with-javascript
//.style("background","linear-gradient(colorin, colorout)")
//.style("webkitBackgroundClip", "text")
//.style("webkitTextFillColor", "transparent")
.attr("font-weight", "bold")
.attr("font-size", 6.1)
.raise();
//ZW, try have group legend react to mouse event, only group of selected variable is colorful
//https://stackoverflow.com/questions/23205865/filter-based-on-the-text-content-of-elements
legend_g.selectAll("circle")
.filter(function(x) { return x != d.data.group; })
.attr("fill", "lightgrey");
legend_g.selectAll("text")
//.filter(function(x) { return d3.select(this).text() === "Models"; }) //ZW, works but not what I want
//.filter(function(x) { return x === "Models"; }) //ZW, similar to above
// .filter(function(x,d) { return d3.select(this).text() === d.data.group; }) //ZW, failed
.filter(function(x) { return x != d.data.group; })
.attr("fill", "lightgrey")
//.style("font-size", 7) //ZW, works, later to see if can change group name that matches selected element
;
legend_g4.selectAll("circle").attr("fill", color3);
legend_g4.selectAll("text").attr("fill", color3);
}
function outed(event, d) {
link.style("mix-blend-mode", "multiply");
node.attr("fill", d => colorScale(d.data.group)) //ZW
link.attr("stroke", colornone); //ZW,ori colornone
d3.select(this).attr("font-weight", null)
.attr("font-size", null); //ZW
d3.selectAll(d.incoming.map(d => d.path)).attr("stroke", null);
d3.selectAll(d.incoming.map(([d]) => d.text)).attr("fill", d => colorScale(d.data.group)).attr("font-weight", null) //ZW, change fill color,
.attr("font-size", null);
d3.selectAll(d.outgoing.map(d => d.path)).attr("stroke", null);
d3.selectAll(d.outgoing.map(([, d]) => d.text)).attr("fill", d => colorScale(d.data.group))
.attr("stroke", null)
.attr("font-weight", null)
.attr("font-size", null);
legend_g.selectAll("circle").attr("fill", colorScale);
legend_g.selectAll("text").attr("fill", colorScale);
//legend_g2.selectAll("mydots").attr("fill", "skyblue"); //ZW, not working
legend_g4.selectAll("circle").attr("fill", "lightgrey");
legend_g4.selectAll("text").attr("fill", "lightgrey");
}
//ZW, title
const title = svg.append("text")
.attr("x", -width*p / 2)
.attr("y", -width*p / 2.19)
.style("text-anchor", "left")
.style("font-size", 22)
.attr("font-family", "sans-serif")
.text("Model mapping");
//ZW, figure courtesy
const courtesy = svg.append("text")
.attr("x", -width*p / 2)
.attr("y", width*p / 2.30)
.style("text-anchor", "left")
.style("font-size", 6.5)
.attr("font-style", "italic")
.attr("font-family", "sans-serif")
.text("Figure courtesy of Zongfei Wang and Adriana Marcucci Bustos");
//ZW, draw the legend for groups
const legend_g = svg.selectAll(".legend")
.data(
colorScale.domain() //option 1, works
//[...new Set(graph.nodes.map(item => item.group))] //opion 2, works
)
.enter().append("g")
.attr("transform", (d, i) => `translate(${width},${i * 9})`); //ZW, i*X decides line spacing of legend
legend_g.append("circle")
.attr("cx", -width*p / 1.64)
.attr("cy", -width*p / 2.085)
.attr("r", 3)
.attr("fill", colorScale);
legend_g.append("text")
.attr("x", -width*p / 1.68)
.attr("y", -width*p / 2.1)
.attr("fill", colorScale)
.style("font-size", 6)
.style("text-anchor", "left")
.text(d => d);
//ZW, test works, can change a single legend color
// legend_g.selectAll("text")
// .filter(function(d) { return d3.select(this).text() === "Models"; })
// .attr("fill", "black");
//ZW,draw the legend for input/output
const keys = ["incoming", "outgoing", "bidirectional"];
const color3 = d3.scaleOrdinal().domain(keys).range([colorin, colorout, colorbi]);
const legend_g4 = svg.selectAll(".legend")
.data(color3.domain())
.enter().append("g")
.attr("transform", (d, i) => `translate(${width},${i * 9})`); //ZW, i*X decides line spacing of legend
legend_g4.append("circle")
.attr("cx", -width*p / 1.4)
.attr("cy", -width*p / 2.085)
.attr("r", 3)
.attr("fill", "lightgrey");
legend_g4.append("text")
.attr("x", -width*p / 1.43)
.attr("y", -width*p / 2.1)
.attr("fill", "lightgrey")
.style("font-size", 6)
.style("text-anchor", "left")
.text(d => d);
return svg.node();
}