Published
Edited
May 3, 2021
Fork of Arc diagram
Insert cell
Insert cell
md `
## what data you chose and why

I randomly sampled 1 million posts from Stack Overflow, and then extract the tags information for the year 2014 for each of the post. I want to see the relationship between tags, to have a better understanding of how different programming languages and packages interact with each other.

## what innovations you did with the code (at least three substantial changes beyond what was provided in class)
1. I want to know every tag and their popularity, so I use the size of the circle for each node to represent it.
2. I only want to see a arc if the connection for those two nodes are strong, so I made the arc to be only visualized if the connections of those two nodes are beyond a threshold.
3. I also want to see more clearly of the arcs for different tags, so I color-encoded the arcs.


## what code changes you made or code you added (a brief summary)

1. I visulized the arc on condition of the value of the link. One arc will be implemented only if the connections are more than a threshold.
2. The size of each circle for each node here is adapted with the popularity of that specific tag.
3. I made the color of the corresponds with the node.


## what you wanted to do but were not able to do.
I want to make the arc diagram a circular one
`
Insert cell
Insert cell
chart = {
const svg = d3.select(DOM.svg(width, height));

svg.append("style").text(`

.hover path {
stroke: #ccc;
}

.hover text {
fill: #ccc;
}

.hover g.primary text {
fill: black;
font-weight: bold;
}

.hover g.secondary text {
fill: #333;
}

.hover path.primary {
stroke: #333;
stroke-opacity: 1;
}

`);

const label = svg.append("g")
.attr("font-family", "sans-serif")
.attr("font-size", 10)
.attr("text-anchor", "end")
.selectAll("g")
.data(graph.nodes)
// .attr("font-size", d => {Math.round(d.popularity/1600)})
.join("g")
.attr("transform", d => `translate(${margin.left},${d.y = y(d.id)})`)
.call(g => g.append("text")
// .attr("font-size", d => {d.popularity/100})
.attr("x", -6)
.attr("dy", "0.35em")
.attr("fill", d => d3.lab(color(d.group)).darker(2))
.text(d => d.id))
.call(g => g.append("circle")
.attr("r", d => 3 + (d.popularity / 1600) * 1)
// .attr("r", d => (d.popularity / 1600 * 3) + 3)
.attr("fill", d => color(d.group)))
.attr("id", d => d.id);

const path = svg.insert("g", "*")
.attr("fill", "none")
.attr("stroke-opacity", 0.4)
.attr("stroke-width", 1.5)
.selectAll("path")
.data(graph.links)
.join("path")
// .attr("stroke", d => d.source.group === d.target.group ? color(d.source.group) : "#aaa")
// .attr("stroke", d => d.source.group === d.target.group ? color(d.source.group) : d.source.color)
.attr("stroke", d => d.source.group === d.target.group ? color(d.source.group) : color(d.target.group))
.attr("d", arc);

const overlay = svg.append("g")
.attr("fill", "none")
.attr("pointer-events", "all")
.selectAll("rect")
.data(graph.nodes)
.join("rect")
.attr("width", margin.left + 40)
.attr("height", step)
.attr("y", d => y(d.id) - step / 2)
.on("mouseover", d => {
svg.classed("hover", true);
label.classed("primary", n => n === d);
label.classed("secondary", n => n.sourceLinks.some(l => l.target === d) || n.targetLinks.some(l => l.source === d));
path.classed("primary", l => l.source === d || l.target === d).filter(".primary").raise();
})
.on("mouseout", d => {
svg.classed("hover", false);
label.classed("primary", false);
label.classed("secondary", false);
path.classed("primary", false).order();
});

function update() {
y.domain(graph.nodes.sort(viewof order.value).map(d => d.id));

const t = svg.transition()
.duration(750);

label.transition(t)
.delay((d, i) => i * 20)
.attrTween("transform", d => {
const i = d3.interpolateNumber(d.y, y(d.id));
return t => `translate(${margin.left},${d.y = i(t)})`;
});

path.transition(t)
.duration(750 + graph.nodes.length * 20)
.attrTween("d", d => () => arc(d));

overlay.transition(t)
.delay((d, i) => i * 20)
.attr("y", d => y(d.id) - step / 2);
}

viewof order.addEventListener("input", update);
invalidation.then(() => viewof order.removeEventListener("input", update));

return svg.node();
}
Insert cell
d3.scalePoint
Insert cell
function arc(d) {
const y1 = d.source.y;
const y2 = d.target.y;
const r = Math.abs(y2 - y1) / 2;
return `M${margin.left},${y1}A${r},${r} 0,0,${y1 < y2 ? 1 : 0} ${margin.left},${y2}`;
}
Insert cell
y = d3.scalePoint(graph.nodes.map(d => d.id).sort(d3.ascending), [margin.top, height - margin.bottom])
Insert cell
margin = ({top: 20, right: 20, bottom: 20, left: 100})
Insert cell
height = (data.nodes.length - 1) * step + margin.top + margin.bottom
Insert cell
step = 14
Insert cell
color = d3.scaleOrdinal(graph.nodes.map(d => d.group).sort(d3.ascending), d3.schemeCategory10)
Insert cell
graph = {
const nodes = data.nodes.map(({id, group, popularity}) => ({
id,
sourceLinks: [],
targetLinks: [],
group,
popularity
}));

const nodeById = new Map(nodes.map(d => [d.id, d]));

// data.links = data.links.map(({source, target, value}) =>
// ( value > 30?
// {source, target, value})
// );
const links = data.links.map(({source, target, value}) =>
(value > 30 ?
// ({
{
source: nodeById.get(source),
target: nodeById.get(target),
value
}:
{
// value, value, value
source: nodeById.get(source),
target: nodeById.get(source),
value
}
)
// })
);

for (const link of links) {
const {source, target, value} = link;
// if (value > 0) {
if (source != target) {
source.sourceLinks.push(link);
target.targetLinks.push(link);
} else{}
}

return {nodes, links};
}
Insert cell
// data = FileAttachment("miserables.json").json()
data = FileAttachment("d3_sample_2014@2.json").json()
Insert cell
d3 = require("d3@5")
Insert cell

Purpose-built for displays of data

Observable is your go-to platform for exploring data and creating expressive data visualizations. Use reactive JavaScript notebooks for prototyping and a collaborative canvas for visual data exploration and dashboard creation.
Learn more