chart = {
const dimensions = {
width: 700,
height: 500
};
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.3rem"
: "1.5rem"
)
.style("font-weight", (d) =>
d.chart_category === "Community media" ? "700" : "500"
)
.style("fill", (d) =>
d.chart_category === "Community media" ? "#333333" : "#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")
.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" ? "#eeeeee" : "#333333") // "#4F4557"
);
// Text: $ amts
const amtLabels = catLabels
.append("tspan")
.attr("x", 0)
.attr("dy", "2em")
.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"
"0.9rem"
: "0.9rem"
)
.style("font-weight", "600")
.style("fill", (d) =>
d.chart_category === "Community media"
? "#a46104"
: d.chart_category === "Traditional media"
? "#191D88"
: newShade(colorScale(d.chart_category), -75)
);
///////////////
// ANNOTATIONS
///////////////
// Text: Traditional + Hispanic
const tradHispX = 150;
const tradHispY = 145;
svg
.append("text")
.style("fill", "#459bec")
.style("font-size", ".9rem")
.style("font-weight", "400")
.attr("text-anchor", "end")
//
.text("8.4% of spending")
.attr("x", `${tradHispX}`)
.attr("y", `${tradHispY}`)
//
.append("tspan")
.text("in traditional")
.attr("x", `${tradHispX}`)
.attr("dy", "1em")
//
.append("tspan")
.text("media went to")
.attr("x", `${tradHispX}`)
.attr("dy", "1em")
//
.append("tspan")
.text("Hispanic media")
.attr("x", `${tradHispX}`)
.attr("dy", "1em")
.style("font-weight", "600")
//
.append("tspan")
.text("including Univision")
.attr("x", `${tradHispX}`)
.attr("dy", "1em")
.style("font-weight", "400")
//
.append("tspan")
.text("and Telemundo")
.attr("x", `${tradHispX}`)
.attr("dy", "1em");
// Text: Unspecified + Hispanic
const unspHispX = 475;
const unspHispY = 325;
svg
.append("text")
.style("fill", "#0fb2e8")
.style("font-weight", "400")
.style("font-size", "0.9em")
//
.text("13.2% of spending in")
.attr("x", `${unspHispX}`)
.attr("y", `${unspHispY}`)
//
.append("tspan")
.text("unspecified traditional")
.attr("x", `${unspHispX}`)
.attr("dy", "1em")
//
.append("tspan")
.text("media went to")
.attr("x", `${unspHispX}`)
.attr("dy", "1em")
//
.append("tspan")
.text("Hispanic media")
.attr("x", `${unspHispX}`)
.attr("dy", "1em")
.style("font-weight", "600");
// Annotation: Community media
const commMediaX = 240;
const commMediaY = 375;
svg
.append("text")
.style("fill", "#eeb304")
.style("font-weight", "400")
.style("font-size", "1.05rem")
.style("text-anchor", "end")
//
.text("Community media")
.attr("x", `${commMediaX}`)
.attr("y", `${commMediaY}`)
.attr("transform", `translate(53, 18)`)
//
.append("tspan")
.text("$5.2m")
.style("font-size", "0.9rem")
.style("font-weight", "600")
.style("fill", "#eeb304")
.attr("x", `${commMediaX}`)
.attr("dy", "1.25em");
// const points = [
// { x: 335, y: 500 },
// { x: 310, y: 525 }
// ];
// const lineGen = d3
// .line()
// .x((d) => d.x)
// .y((d) => d.y)
// .curve(d3.curveNatural);
// // arrowhead
// svg
// .append("svg:defs")
// .append("svg:marker")
// .attr("id", "triangle")
// .attr("refX", 6)
// .attr("refY", 6)
// .attr("markerWidth", 15)
// .attr("markerHeight", 15)
// .attr("orient", "auto")
// .append("path")
// .attr("d", "M 0 0 8 6 0 12 4 6")
// .style("fill", "black");
// // line for unspecified arrow
// svg
// .append("path")
// .attr("d", lineGen(points))
// .attr("marker-end", "url(#arrow)")
// .attr("fill", "none")
// .attr("stroke", "#777777")
// .attr("marker-end", "url(#triangle)")
// .attr("transform", `translate(35, 20)`);
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;
}