Public
Edited
Feb 27, 2023
2 forks
2 stars
Insert cell
Insert cell
Insert cell
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))));

//https://datacrayon.com/posts/visualisation/visualisation-with-d3/selections-and-selecting-elements/
const svg = d3.create("svg")
.attr("viewBox", [-width*p / 2, -width*p / 2, width*p, width*p]);
//[min-x, min-y, width, height], min-x(min-y) is used to make SVG move on a horizontal (vertical) axis
// this is case, it seems to move svg left and down
const node = svg.append("g")
.attr("font-family", "sans-serif")
.attr("font-size", 6)
// .attr("fill", d3.schemeTableau10[7]) //ZW, works, not good
.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 => d.data.group == 1? colorset["group",d.data.group]["color"]:null) //byZW, this can change color initially, but will be over-written after mouse select; and can ONLY change one group
.attr("fill", d => colorScale(d.data.group)) //byZW, works
.each(function(d) { d.text = this; })
.on("mouseover", overed)
//.on("mousedown", overed) //ZW
.on("mouseout", outed)
//.on("mouseup", outed) //ZW

//hover text
.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();
}
Insert cell
colorScale.domain()
Insert cell
//ZW test
//viewof grpup = Inputs.checkbox(graph.nodes, {label: "group name", format: x => x.group})

Insert cell
graph.links
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
//ZW test; reorder (data), by id of the source node
// d3.hierarchy(data)
// .sort((a, b) => d3.ascending(a.height, b.height) || d3.ascending(a.data.id, b.data.id))

Insert cell
graph
//= FileAttachment("miserables.json").json()
//= FileAttachment("data_complete-10_2.json").json()
= FileAttachment("data_complete_3.json").json()
Insert cell
Insert cell
Insert cell
root2 = d3.hierarchy(data).sort((a, b) => d3.ascending(a.height, b.height) || d3.ascending(a.data.id, b.data.id))
Insert cell
root2.leaves()
Insert cell
map2 = new Map((d3.hierarchy(data).sort((a, b) => d3.ascending(a.height, b.height) || d3.ascending(a.data.id, b.data.id))).leaves().map(d => [d.data.id, d]))
Insert cell
//ZW, discover bilink
bilink(d3.hierarchy(data)
.sort((a, b) => d3.ascending(a.height, b.height) || d3.ascending(a.data.id, b.data.id)))
Insert cell
Insert cell
colorin = "#fc7edd"
Insert cell
colorout = "#3eede7" //#3eede7
Insert cell
colorbi = "#a742f5"
Insert cell
colornone = "#ccc"
Insert cell
p = 1.05 //ZW, this adjust the area (&position) of the figure in the whole chart
Insert cell
width = 500
Insert cell
radius = width / 2.1
Insert cell
line = d3.lineRadial()
.curve(d3.curveBundle.beta(0.85))
.radius(d => d.y)
.angle(d => d.x)
Insert cell
tree = d3.cluster()
.size([2 * Math.PI, radius - 100]) //ZW, ori "...-100", for size of circle
Insert cell
d3 = require("d3@6")
Insert cell
Insert cell
// https://observablehq.com/@d3/d3-scaleordinal
colorScale = d3.scaleOrdinal(colors) //ZW, works, need more than 10 colors
Insert cell
colorScale("Models") //ZW, test
Insert cell
colorScale.domain()
Insert cell
Swatches(d3.scaleOrdinal(colorScale.domain(), colors))
Insert cell
import {Legend, Swatches} from "@d3/color-legend"
Insert cell

One platform to build and deploy the best data apps

Experiment and prototype by building visualizations in live JavaScript notebooks. Collaborate with your team and decide which concepts to build out.
Use Observable Framework to build data apps locally. Use data loaders to build in any language or library, including Python, SQL, and R.
Seamlessly deploy to Observable. Test before you ship, use automatic deploy-on-commit, and ensure your projects are always up-to-date.
Learn more