Published
Edited
Nov 25, 2020
8 stars
Insert cell
Insert cell
Insert cell
chart = {
// based on this example https://observablehq.com/@mbostock/wheres-that-2-trillion-going
const element = this || html` <div> <svg viewBox = "0 0 ${width} ${height}"
style="overflow:hidden; text-anchor:middle; font:10px sans-serif"/svg> </div>`

const svg = d3.select(element).select('svg')
const tooltip = d3.select("body").append("div")
.attr("class", "tooltip")
.style("text-align", "center")
.style("display", "block")
.style("position", "absolute")
.style("visibility", "hidden")
.style("font-size", "1rem")
.style("background", "rgba(255,255,255,0.9)")
.style("padding", "0.1rem 0.5rem")
.style("color", "grey")
.style("border-radius", "5px")
.style("border", "1px solid grey")
.style("transform", "translateX(-50%)")
.style("pointer-events", "none");
const root = pack(data);
const t = svg.transition().duration(600)
const node = svg
.selectAll("g")
.data(root.descendants().slice(1), d => d.data[0])
.join(
enter => {
const nodeEnter = enter.append("g").attr("transform", d => `translate(${d.x},${d.y})`)
const circle = nodeEnter.append("circle")
.attr("r", d => d.r)
.attr("fill", d => d.children ? "none" : d.parent.data[0] === "Proportional representation"| d.parent.data[0] === "Plurality/Majority" | d.parent.data[0] === "Mixed" ? colorScale(d.parent.data[0]) : grey)
.attr("stroke", d => d.children ? "#bbb" : "none")
.attr("pointer-events", "all")

nodeEnter.append("path")
.attr("id", d => (d.circleUid = DOM.uid("circle")).id)
.attr("d", d => circleC(d.r + 3))
.attr("display", "none");
nodeEnter.filter(d => d.children).append("text")
.attr("fill", "#555")
.append("textPath")
.attr("xlink:href", d => d.circleUid.href)
.attr("startOffset", d => d.data[0] !== "Modified BC" ? "50%" : "27%")
.text(d => d.data[0]);
return nodeEnter
},
update => update,
exit => {
exit.transition(t)
.attr('opacity', 0)
.remove()
}
)
// transitions
node.transition(t).attr("transform", d => `translate(${d.x},${d.y})`);

node
.selectAll("circle")
.data(root.descendants().slice(1), d => d.data[0])
.transition(t)
.attr("r", d => d.r);
node
.selectAll("path")
.data(root.descendants().slice(1), d => d.data[0]).filter(d => d.children)
.attr("id", d => (d.circleUid = DOM.uid("circle")).id)
.attr("d", d => circleC(d.r + 3))
.attr("display", "none");
node
.filter(d => d.children)
.append("text")
.attr("fill", "#555")
.append("textPath")
.attr("xlink:href", d => d.circleUid.href)
.attr("startOffset", d => d.data[0] !== "Modified BC" ? "50%" : "27%")
.text(d => d.data[0]);
// on hover
node
.on("mouseout", () => {
if (choice == "system") {
node
.selectAll("circle")
.attr("fill", d => d.children ? "none" : d.parent.data[0] === "Proportional representation"| d.parent.data[0] === "Plurality/Majority" | d.parent.data[0] === "Mixed" ? colorScale(d.parent.data[0]) : grey);
tooltip.style("visibility", "hidden");
} else {
const packNew = (data) => d3.pack()
.size([width, height])
.padding(1)(
d3.hierarchy(d3.rollup(data, v => d3.sum(v, d => d.count), d => d.system, d => d.country))
.sum(([, value]) => value)
.sort((a, b) => b.value - a.value)
)
const rootNew = packNew(data)
node
.selectAll("circle")
.data(rootNew.descendants().slice(1), d => d.data[0])
.attr("fill", d => d.children ? "none" : d.parent.data[0] === "Proportional representation"| d.parent.data[0] === "Plurality/Majority" | d.parent.data[0] === "Mixed" ? colorScale(d.parent.data[0]) : grey);
tooltip.style("visibility", "hidden");
}
})
.on("mouseover", (event, d) => {
if(d.height) return;
const ancestors = new Set(d.ancestors());
const array = new Set(d);
const current = event.currentTarget;
if (choice == "system") {
node.filter(d => array.has(d))
.selectAll("circle")
.attr("fill", d => d.children ? "none" : d.parent.data[0] === "Proportional representation"| d.parent.data[0] === "Plurality/Majority" | d.parent.data[0] === "Mixed" ? colorScaleDark(d.parent.data[0]) : darkGrey)
} else {

const packNew = (data) => d3.pack()
.size([width, height])
.padding(1)(
d3.hierarchy(d3.rollup(data, v => d3.sum(v, d => d.count), d => d.system, d => d.country))
.sum(([, value]) => value)
.sort((a, b) => b.value - a.value)
)
const rootNew = packNew(data)
node.filter(d => array.has(d))
.selectAll("circle")
.data(rootNew.descendants().slice(1), d => d.data[0])
.attr("fill", d => d.children ? "none" : d.parent.data[0] === "Proportional representation"| d.parent.data[0] === "Plurality/Majority" | d.parent.data[0] === "Mixed" ? colorScaleDark(d.parent.data[0]) : darkGrey)
}
tooltip
.style("visibility", "visible")
.style("top", (d.y +125) + "px")
.style("left", (d.x + d.r) + "px")
.html(
`<div>${d.data[0]}</div>`
);
})

return element;
}
Insert cell
pack = (data) => d3.pack()
.size([width, height])
.padding(1)(
d3.hierarchy(d3.rollup(data, v => d3.sum(v, d => d.count), d => d[choice], d => d.country))
.sum(([, value]) => value)
.sort((a, b) => b.value - a.value)
)
Insert cell
Insert cell
Insert cell
darkGrey = d3.color(grey).darker().formatHex()
Insert cell
colorScale = d3.scaleOrdinal(systems,colors)
Insert cell
systems = ["Proportional representation", "Plurality/Majority", "Mixed"]
Insert cell
colors = ["#bca3cd","#a6c3bc", "#f6cba6"]
Insert cell
darkerColors = colors.map(color => d3.color(color).darker().formatHex())
Insert cell
colorScaleDark = d3.scaleOrdinal(systems,darkerColors)
Insert cell
Insert cell
height = width - 150
Insert cell
Insert cell
data = FileAttachment("polRegDemJoined.csv").csv({typed: true})
Insert cell
d3 = require("d3@6")
Insert cell
_ = require("lodash")
Insert cell
import {radio} from "@jashkenas/inputs"
Insert cell
import {form} from "@mbostock/form-input"
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