Public
Edited
Dec 22, 2023
Insert cell
Insert cell
chart = {
document.head.innerHTML += style;
const div = d3
.create("div")
.style("display", "flex")
.style("flex-wrap", "wrap")
.style("justify-content", "center")
.style("font-family", "Inter");

const facet = div
.selectAll("div")
.data(array) // sets num of divs based on keys
.join("div");
//.style("border", "1px solid black");

facet.call(drawChart);
//console.log();

return div.node();
}
// Based off of https://observablehq.com/@info474/d3-small-multiples
// Embedding font: https://talk.observablehq.com/t/controlling-size-of-embeds-excess-whitespace/7534/2
Insert cell
drawChart = (container) => {
const width = 400;
const height = 400;
// draw svg container
const svg = container
.append("svg")
.attr("width", width)
.attr("height", height)
.append("g")
.attr("transform", `translate(${width / 2 - 50},${height / 2 - 30})`);

// radius determined by $ amt
const radiusScale = d3
.scaleSqrt()
.domain([0, d3.max(data, (d) => d.sum)]) // using raw data to standardize circle sizes
.range([10, 100]);

// draw circles
const circles = svg
.selectAll("circle")
.data((d) => d[1])
//.data(d => {console.log(d); return d[1]})
.join("circle")
.attr("r", (d) => radiusScale(d.sum))
.attr("fill", (d) => colorScale(d.chart_category))
.attr("opacity", 0.9)
.attr("stroke", (d) => newShade(colorScale(d.chart_category), -50))
.attr("stroke-width", 1.25);

// create div for tooltip
const tooltip = container.append("div").attr("class", "tooltip");

// binding nodes to set x and y positions
array.forEach((d) => {
// for each campaign
const simulation = d3
.forceSimulation(d[1])
.force("xPosition", d3.forceX((d) => radiusScale(d.sum)).strength(0.2))
.force("yPosition", d3.forceY((d) => radiusScale(d.sum)).strength(0.2))
.force(
"collision",
d3.forceCollide().radius((d) => radiusScale(d.sum) + 2)
);

simulation
// .nodes(
// //Object.values(array.map((d) => d[1])
// array.map((d) => d[1]).map((d) => d[1])
// ) // not needed because nodes bound above in forceSimulation()
.on("tick", () => {
// position circles
circles.attr("cx", (d) => d.x).attr("cy", (d) => d.y);

// tooltip
circles
.on("mouseover", (e, d) => {
const tooltipContent = `
<div style="text-transform: uppercase; font-size: 0.65rem; font-weight: 700;">${
d.chart_category === "Ad serving/targeting companies"
? "Ad serving/targeting"
: d.chart_category
}</div>
<div style="font-size: 1.1rem; color: #fff;">${d.vendor}</div>
<div style="color: #fff;">Received <strong>${d3.format("$,.2s")(
d.sum
)}</strong></div>
`;

tooltip.html(tooltipContent);
tooltip
.style(
"transform",
//`translate(${e.pageX + 5}px,${e.pageY - 200}px)`
`translate(${e.pageX + 5}px,${e.pageY - 100}px)`
)
.style("color", newShade(colorScale(d.chart_category), 50))
.transition()
.duration(500)
.style("opacity", 0.9);
})
.on("mouseout", () => tooltip.transition().style("opacity", 0));

// company names
svg
.selectAll("text")
.data((d) => d[1])
.join("text")
.attr("class", "company-labels")
.text((d) =>
d.vendor
.replace("Bustle Digital Group", "Bustle")
.replace("New Tang Dynasty", "NTD")
.replace("Programmatic Mechanics", "PM")
.replace("MightyHive", "MH")
.replace("OM Trade Desk", "OM")
.replace("MobileFuse", "MF")
.replace(
"Snapchat",
d.campaign_flight == "Dec '19-Jan '20" ? "SC" : "Snapchat"
)
.replace(
"Facebook",
d.campaign_flight == "Dec '19-Jan '20" ? "FB" : "Facebook"
)
)
.attr("x", (d) => d.x)
.attr("y", (d) => d.y)
.attr("text-anchor", "middle")
.style("font-size", "0.8rem");

// campaign labels
const campaignLabels = svg
.append("text")
.html((d) => d[0])
.attr("text-anchor", "middle")
.attr("x", 50)
.attr("y", -140)
.style("font-size", "1rem")
.style("font-weight", 500);

campaignLabels
.append("tspan")
.attr("x", 50)
.attr("dy", "1.25em")
.html(function (d) {
const accumulator = d[1]
.map((d) => d.sum)
.reduce((accumulator, currentValue) => {
return currentValue + accumulator;
}, 0);

return `Campaign total: ${
accumulator > 1000000
? d3.format("$,.1s")(accumulator).toLowerCase()
: d3.format("$,.3s")(accumulator).toLowerCase()
}`;
})
.style("font-size", "0.9rem")
.style("font-weight", 400)
.style("fill", "#989898");
});
});
}

// TY: Madeleine F.
Insert cell
Insert cell
Insert cell
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