Published
Edited
Mar 12, 2020
Insert cell
md`# Bubble matrix`
Insert cell
chart = {
var c1w = 130,
c2w = 80,
c3w = 40,
r1h = 150,
w = 600,
h = w,
pad = 20,
left_pad = 50;
var svg = d3.select(DOM.svg(c1w + c2w + c3w + w + pad + 30, r1h + h))

var tab_data = d3.csvParse(raw_data);
var traits = tab_data.columns.filter(x => !(x in {"Company":0, "Candidate":0}));
var candidates = tab_data.map(r => r.Candidate);

var row_data = [], row, prop;
for (row of tab_data) {
for (prop of traits) {
row_data.push({
name: row["Candidate"],
prop: prop,
value: row[prop]
});
}
}

var x_step = (w-pad-left_pad) / (traits.length + 1),
y_step = ((h-pad*2) - pad) / (candidates.length + 1),
x_range = traits.map((v,i) => c1w + c2w + c3w + left_pad + ((1+x_step) * i)),
y_range = candidates.map((v,i) => r1h + pad + ((1+y_step) * i)),
x = d3.scaleOrdinal().domain(traits).range(x_range),
y = d3.scaleOrdinal().domain(candidates).range(y_range);

var max_r = 5,
r = d3.scaleLinear()
.domain([0, max_r])
.range([0, 16]);
var classinate = t => t.replace(/\s+/gi, "_");
var opacity = m => (m in {"Managed Money":0,"Location":0})?"0.5":(m in {"Municipal":0})?"0.3":"1.0";
var onMouseOver = function(d) {
var name = classinate(d["name"]);
var prop = classinate(d["prop"]);
svg.selectAll(`text.label.${prop}`)
.attr("display", "block");
svg.selectAll(`text.label.${name}`)
.attr("display", "block");
};

var onMouseOut = function(d) {
var name = classinate(d["name"]);
var prop = classinate(d["prop"]);
svg.selectAll(`text.label.${prop}`)
.attr("display", "none");
svg.selectAll(`text.label.${name}`)
.attr("display", "none");
};
var circle_groups = svg.selectAll("g.cgroup")
.data(row_data, (d,i) => `cgroup${i}`)
.enter()
.append("g")
.attr("class", "cgroup")
.attr("opacity", d => opacity(d["prop"]))
.on("mouseleave", onMouseOut )
.on("mouseenter", onMouseOver);
var circles = circle_groups.append("circle")
.attr("class", d => "circle " + classinate(d["prop"]) + " " + classinate(d["name"]))
.attr("cx", function (d) { return x(d["prop"]); })
.attr("cy", function (d) { return y(d["name"]); });

circle_groups.append("text")
.attr("class", d => "label " + classinate(d["prop"]) + " " + classinate(d["name"]))
.attr("display", "none")
.attr("text-anchor", "middle")
.attr("font-size", "14px")
.attr("stroke", "black")
.attr("fill", "black")
.attr("stroke-width", "1px")
.attr("x", function (d) { return x(d["prop"]); })
.attr("y", function (d) { return y(d["name"]); })
.text(function (d) { return d["value"]});
var z_circle_groups = svg.selectAll("g.zcgroup")
.data(tab_data, (d,i) => `zcgroup${i}`)
.enter()
.append("g")
.attr("class","zcgroup")
.on("mouseleave", d => {d["name"] = d["Candidate"]; d["prop"] = "Z"; onMouseOut(d)})
.on("mouseenter", d => {d["name"] = d["Candidate"]; d["prop"] = "Z"; onMouseOver(d)});

var zx = c1w + (c2w/2);
var z_circles = z_circle_groups.append("circle")
.attr("class", d => "zcircle " + "Z" + " " + classinate(d["Candidate"]))
.attr("cx", function (d) { return zx; })
.attr("cy", function (d) { return y(d["Candidate"]); });

z_circle_groups.append("text")
.attr("class", d => "label " + "Z" + " " + classinate(d["Candidate"]))
.attr("display", "none")
.attr("text-anchor", "middle")
.attr("font-size", "14px")
.attr("stroke", "black")
.attr("fill", "black")
.attr("stroke-width", "1px")
.attr("x", function (d) { return zx; })
.attr("y", function (d) { return y(d["Candidate"]); })
.text(function (d) { return d["Z"]});

var k_circle_groups = svg.selectAll("g.kcgroup")
.data(tab_data, (d,i) => `kcgroup${i}`)
.enter()
.append("g")
.attr("class","kcgroup")
.on("mouseleave", d => {d["name"] = d["Candidate"]; d["prop"] = "Key Skill"; onMouseOut(d)})
.on("mouseenter", d => {d["name"] = d["Candidate"]; d["prop"] = "Key Skill"; onMouseOver(d)});

var kx = c1w + c2w + (c3w/2);
var k_circles = k_circle_groups.append("circle")
.attr("class", d => "kcircle " + classinate("Key Skill") + " " + classinate(d["Candidate"]))
.attr("cx", function (d) { return kx; })
.attr("cy", function (d) { return y(d["Candidate"]); });

k_circle_groups.append("text")
.attr("class", d => "label " + classinate("Key Skill") + " " + classinate(d["Candidate"]))
.attr("display", "none")
.attr("text-anchor", "middle")
.attr("font-size", "14px")
.attr("stroke", "black")
.attr("fill", "black")
.attr("stroke-width", "1px")
.attr("x", function (d) { return kx; })
.attr("y", function (d) { return y(d["Candidate"]); })
.text(function (d) { return d["Key Skill"]});
var trait_labels = svg.selectAll("text.trait")
.data(traits)
.enter()
.append("text")
.attr("class", "trait")
.attr("x", d => x(d))
.attr("y", r1h - 10)
.attr("font-size", "14px")
.attr("stroke", "black")
.attr("fill", "black")
.attr("stroke-width", "1px")
.attr("opacity", d => opacity(d))
.attr("transform", d => `rotate(-40 ${x(d)},${r1h-10})`)
.text(d => d)
var candidate_labels = svg.selectAll("text.candidate")
.data(candidates)
.enter()
.append("text")
.attr("class", "candidate")
.attr("x", 10)
.attr("y", d => y(d))
.attr("font-size", "14px")
.attr("stroke", "black")
.attr("fill", "black")
.attr("stroke-width", "1px")
.text(d => d)

circles.transition()
.duration(800)
.attr("r", function (d) { return r(d["value"]); })
.attr("fill", d => d3.interpolateRdYlGn(d["value"] / 7));

k_circles.transition()
.duration(1600)
.attr("r", function (d) { return r(d["Key Skill"]); })
.attr("fill", d => d3.interpolateRdYlGn(d["Key Skill"] / 7));
z_circles.transition()
.duration(2400)
.attr("r", function (d) { return r(d["Z"]) / 2; })
.attr("fill", d => d3.interpolateRdBu(d["Z"] / 12));


return svg.node()
}
Insert cell
d3 = require("https://d3js.org/d3.v5.min.js")
Insert cell
raw_data = `Candidate,Company,Bailey,Boris,Corbyn,Nandy,Stramer,Thornberry
labour,,,13,,12,28,27,27
need,,8,,8,28,15,15
must,,,,9,13,17,17
people,,10,10,9,20,,
party,,9,,,,18,18
support,,10,,,,15,15
country,,,16,,23,,
government,,9,,9,15,,
today,,,,7,,12,12
leader,,,,,,13,13
#anotherfutureispossible,,,,,,13,13
thank,,,,,,11,11
members,,,,,,11,11
want,,,,,17,,
going,,,17,,,,
power,,15,,,,,
path,,15,,,,,
vote,,,,14,,,
leadership,,,,,13,,
make,,,,,12,,
fight,,,,,11,,
#peoplespmqs,,,11,,,,
unite,,,10,,,,
questions,,,10,,,,
🌹,,10,,,,,
save,,,,9,,,
johnson,,,,9,,,
green,,9,,,,,
eu,,,9,,,,
brexit,,,9,,,,
time,,,,8,,,
nhs,,,8,,,,
get,,,8,,,,
`
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