Published
Edited
Oct 10, 2021
1 star
Insert cell
Insert cell
chart = {
// Variables.
let time_so_far = 300;
// The SVG object.
const svg = d3.create("svg")
.attr("viewBox", [0, 0, width, height]);

const title = svg.append('g');
title.append('g')
.call(addTitle, '민주당 대통령 후보 경선 결과 종합');

// const caption = svg.append('g');
// caption.append('g')
// .call(addCaption, '점 한 개당 득표수 1000명');

const circle = svg.append("g")
.selectAll("circle")
.data(nodes)
.join("circle")
.attr("cx", d => d.x)
.attr("cy", d => d.y)
.attr("fill", d => d.color);

// Ease in the circles.
circle
.transition()
.delay((d, i) => i)
.duration(0.1)
.attrTween("r", d => {
const i = d3.interpolate(0, d.r);
return t => d.r = i(t);
});
// Group name labels
svg.selectAll('.grp')
.data(d3.keys(groups))
.join("text")
.attr("class", "grp")
.attr("text-anchor", "middle")
.attr("x", d => groups[d].x)
// .attr("y", d => groups[d].y - 70)
.attr("y", d => groups[d].y)
.text(d => groups[d].fullname)
.attr("font-family", "Noto Sans KR")
.style("font-weight", "bold")
// .attr("font-size", width*0.02);
.attr("font-size", "1em");
// .attr("stroke", "#FFF")
// .attr("stroke-width", width*0.0005);

// Group counts
svg.selectAll('.grpcnt')
.data(d3.keys(groups))
.join("text")
.attr("class", "grpcnt")
.attr("text-anchor", "middle")
.attr("x", d => groups[d].x)
// .attr("y", d => groups[d].y - 20)
.attr("y", d => groups[d].y+15)
.text(d => d3.format(".1f")((groups[d].cnt)/10))
.style("font-weight", "bold")
.attr("font-size", "0.9em");
// Forces
const simulation = d3.forceSimulation(nodes)
.force("x", d => d3.forceX(d.x))
.force("y", d => d3.forceY(d.y))
.force("cluster", forceCluster())
.force("collide", forceCollide())
.alpha(.09)
.alphaDecay(0);

// Adjust position (and color) of circles.
simulation.on("tick", () => {
circle
.attr("cx", d => d.x)
.attr("cy", d => d.y);
// .attr("fill", d => groups[d.group].color);
});
// Make time pass. Adjust node stage as necessary.
function timer() {
nodes.forEach(function (o, i) {
o.timeleft -= 1;
if (o.timeleft == 0 && o.istage < o.stages.length - 1) {
// Decrease count for previous group.
groups[o.group].cnt -= 1;
// Update current node to new group.
o.istage += 1;
o.group = o.stages[o.istage].grp; // grp FROM CSV?
o.timeleft = o.stages[o.istage].duration; // HERE IS DURATION
// Increment count for new group.
groups[o.group].cnt += 1;
}
});
// Increment time.
time_so_far += 1;
d3.select("#timecount .cnt").text(time_so_far);
// Update counters.
svg.selectAll('.grpcnt').text(d => d3.format(".1f")(groups[d].cnt)/10);
// svg.selectAll('.grpcnt').text(d => groups[d].cnt);
// Do it again.
// d3.timeout(timer, 500);
d3.timeout(timer, 100);
} // @end timer()
// Start things off after a few seconds.
d3.timeout(timer, 1000);
return svg.node()
}

Insert cell
FileAttachment("data@2.csv").csv()
Insert cell
d3 = require("d3@5")
Insert cell
width = 600
Insert cell
height = width*1.4
Insert cell
Insert cell
Insert cell
Insert cell
radius = 3
Insert cell
padding = 3 // Space between nodes
Insert cell
Insert cell
Insert cell
Insert cell
// Load data.
stages = d3.csvParse(await FileAttachment("data@2.csv").text(), d3.autoType)
Insert cell
Insert cell
Insert cell
// Create node data.
nodes = d3.keys(people).map(function(d) {
// Initialize count for each group.
groups[people[d][0].grp].cnt += 1;
return {
id: "node"+d,
x: groups[people[d][0].grp].x + Math.random(),
y: groups[people[d][0].grp].y + Math.random(),
r: radius,
color: groups[people[d][0].grp].color,
group: people[d][0].grp,
timeleft: people[d][0].duration,
istage: 0,
stages: people[d]
}
});
Insert cell
Insert cell
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