Public
Edited
May 3
Insert cell
Insert cell
chart = {
// set dimensions
const dimensions = {
width: 500,
height: 400
};

// canvas
const svg = d3
.create("svg")
.attr("width", dimensions.width)
.attr("height", dimensions.height);

const canvas = html`
<style>
@import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800&family=Poppins:wght@700&display=swap');

.chart-container {
padding-top: 0.25em;
font-family: "Inter", sans-serif;
}

@media screen and (max-width: 800px) {
/* scrollable on mobile */
.chart-mobile-container {
display: flex;
flex-wrap: nowrap;
overflow-x: scroll;
padding: 0;
}
/* scroll horizontally */
.chart-mobile-container::-webkit-scrollbar {
display: none;
}
}
</style>
<div class="chart-mobile-container">
<div class="chart-container">
<div class="chart">${svg.node()}</div>
</div>
</div>
`;

const bubblesSVG = svg
.append("g")
.attr(
"transform",
`translate(${dimensions.width / 2},${dimensions.height / 2})`
);

// color palette
const colorScale = d3
.scaleOrdinal()
.domain([
"Community media",
"Traditional media",
"Unspecified traditional media",
"Unspecified digital media",
"Unspecified media placements/buys",
"Social media and tech",
"Ad serving/targeting companies",
"Sports and live events marketing"
])
.range([
"#F2BE22",
"#4489E3",
"#57C9FA",
"#b298dc",
"#bdbdbd",
"#FF7D5E",
"#FFB26B",
"#A8DF8E"
]);

// establish radius of bubbles
const radiusScale = d3
.scaleSqrt()
.domain([0, d3.max(data, (d) => d.sum)])
.range([1, 125]);

// bubble force
const simulation = d3
.forceSimulation()
.force("x", d3.forceX().strength(0.1))
.force("y", d3.forceY().strength(0.1))
.force(
"collide",
d3.forceCollide((d) => radiusScale(d.sum) + 2) // space btwn circles
);

// Draw bubbles
const bubbles = bubblesSVG
.selectAll("circle")
.data(data)
.join("circle")
.attr("r", (d) => radiusScale(d.sum))
.attr("fill", (d) => colorScale(d.chart_category))
.attr("stroke", (d) => newShade(colorScale(d.chart_category), -50))
.attr("stroke-width", "1.1");

// Text: Pct labels
const pctLabels = bubblesSVG
.selectAll("text")
.data(data)
.join("text")
.attr("class", "labels")
.attr("text-anchor", "middle")
.text((d) =>
d.chart_category === "Community media"
? `${d3.format(",.1~f")(d.pct)}`
: `${d3.format(",.1~f")(d.pct)}%`
)
.attr("dy", (d) =>
d.chart_category === "Community media" ? "0.3em" : "-0.25em"
)
.style("font-size", (d) =>
d.chart_category === "Community media" ||
d.chart_category === "Sports and live events marketing"
? "1.5rem"
: "1.5rem"
)
.style("font-weight", "500")
.style("fill", (d) =>
d.chart_category === "Community media" ? "#ffffff" : "#333333"
);

// Text: Category labels
const catLabels = pctLabels
.append("tspan")
.attr("x", 0)
.attr("dy", "0.75em")
.text((d) =>
d.chart_category.match(
/Community media|Traditional media|Unspecified traditional media|Social media and tech|Ad serving\/targeting companies|Sports and live events marketing/
)
? d.chart_category
.replace("Community media", "")
//.replace("Traditional media", "Traditional")
.replace("Unspecified traditional media", "Unspecified traditional")
.replace("Social media and tech", "Social media + tech")
.replace("Ad serving/targeting companies", "Ad serving")
.replace("Sports and live events marketing", "Sports + events")
: d.chart_category
)
.style("font-size", (d) =>
d.chart_category === "Community media" ||
d.chart_category === "Sports and live events marketing"
? "1.05rem"
: "1.05rem"
)
.style("font-weight", "500")
.style(
"fill",
(d) => (d.chart_category === "Community media" ? "#333333" : "#333333") // "#4F4557"
);

// Text: $ amts
const amtLabels = catLabels
.append("tspan")
.attr("x", 0)
.attr("dy", "2.1em")
.html((d) =>
d.chart_category === "Community media"
? null
: `${d3.format("$,.3s")(d.sum).toLowerCase()}`
)
.style("font-size", (d) =>
d.chart_category === "Community media" //||
? // d.chart_category === "Social media and tech" ||
// d.chart_category === "Sports and live events marketing"
"1rem"
: "1rem"
)
.style("font-weight", "600")
.style("fill", (d) =>
d.chart_category === "Traditional media"
? "#191D88"
: newShade(colorScale(d.chart_category), -75)
);

simulation.nodes(data).on("tick", ticked);

function ticked() {
bubbles.attr("cx", (d) => d.x).attr("cy", (d) => d.y);
pctLabels.attr("x", (d) => d.x).attr("y", (d) => d.y);
catLabels.attr("x", (d) => d.x).attr("y", (d) => d.y);
amtLabels.attr("x", (d) => d.x).attr("y", (d) => d.y);
}

return canvas;
}
Insert cell
Insert cell
Insert cell
data = data_raw.objects()
Insert cell
Inputs.table(data)
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