Public
Edited
May 3
Insert cell
Insert cell
(async () => { // Wrap the entire code in an async IIFE

// 1. Data Input
const data = await FileAttachment("keeping-track-of-the-big-picture6.csv").csv({ typed: true });

// 2. Icon mapping (Using a single SVG path for diamonds)
const getIcon = (itemText) => {
return "M 0 -5 L 5 0 L 0 5 L -5 0 Z"; // SVG path for a diamond shape
};

// Function to split text into two lines (Not used in this version, but kept for potential future use)
const splitText = (text) => {
const parts = text.split(" ");
const middleIndex = Math.ceil(parts.length / 2);
return [parts.slice(0, middleIndex).join(" "), parts.slice(middleIndex).join(" ")];
};



// 3. Chart definition
const chart = () => {
// Color mapping for the outer circles based on "Trend"
const trendColor = d3.scaleOrdinal()
.domain([
"Growing self-censorship",
"Harm to independent outlets",
"Erosion of public access",
"Media curry favor with the president",
"Hostility of media environment"
])
.range([
"#730362",
"#9D0998",
"#0CA5B3",
"#1A2E7F",
"#28124A"
]);

// Color mapping for the inner icons based on "Type"
const typeColor = (type) => {
if (type === "good") return "#1CD81F";
if (type === "bad") return "#FF312E";
return "#ccc"; // Default color if type is not "good" or "bad"
};

const root = d3.hierarchy({
name: "root",
children: Array.from(d3.group(data, d => d.Trend), ([name, children]) => ({
name: name,
children: children.map(d => ({ ...d, item: d.Items, value: 1, url: d.URL })), // Store d.Items and d.URL
}))
}).sum(d => d.value);

const packed = d3.pack()
.size([width * 1.4, height * 1.4])
.padding(180)(root);
const svg = d3.select(DOM.svg(width, height))
.style("width", "100%")
.style("height", "auto")
.style("font", "10px sans-serif")
.style("overflow", "visible");

// Create a zoom behavior
const zoom = d3.zoom()
.scaleExtent([0.5, 4])
.on("zoom", zoomed);

// Create a group to hold all the content that will be zoomed
const transformGroup = svg.append("g");

// Explicitly remove any previous labels
transformGroup.selectAll(".trend-label").remove();

const node = transformGroup.append("g")
.selectAll("g")
.data(packed.descendants().slice(1))
.join("g")
.attr("transform", d => `translate(${d.x},${d.y})`);

node.append("circle")
.attr("r", d => d.r)
.style("fill", d => d.depth === 1 ? trendColor(d.data.name) : "none")
.style("stroke", "none");

node.filter(d => d.depth === 1)
.each(function (d) {
const circle = d3.select(this).select("circle");
const cx = d.x;
const cy = d.y;
const r = circle.attr("r");
let textX, textY, textAnchor;
let text = d.data.name;

// Correct the text
if (text === "Growing self-censorship") {
text = "Growing\nself-censorship";
} else if (text === "Hostility of media environment") {
text = "Hostility of\nmedia environment";
} else if (text === "Media curry favor with the president") {
text = "Media curry favor\nwith the president";
} else if (text === "Erosion of public access") {
text = "Erosion of\npublic access";
} else if (text === "Harm to independent outlets") {
text = "Harm to\nindependent outlets";
}

const lines = text.split("\n");
const numLines = lines.length;

textX = cx;
textY = cy - r - 20;
textAnchor = "middle";


transformGroup.append("text")
.attr("class", "trend-label")
.attr("x", textX)
.attr("y", textY)
.attr("dy", numLines === 1 ? "0em" : `-${(numLines - 1) * 0.75}em`)
.attr("text-anchor", textAnchor)
.style("fill", trendColor(d.data.name))
.style("font-family", "Roboto")
.style("font-size", "1em")
.style("font-weight", "bold")
.each(function () {
const textElement = d3.select(this);
lines.forEach((line, index) => {
textElement.append("tspan")
.attr("x", textX)
.attr("y", textY)
.attr("dy", index === 0 ? "0em" : "1.2em")
.text(line);
});
});
});

const leaf = node.filter(d => d.depth === 2)
.style("cursor", "pointer")
.on("click", (event, d) => {
if (d.data.url) {
window.open(d.data.url, "_blank");
}
});

leaf.append("path")
.attr("d", getIcon)
.style("fill", d => typeColor(d.data.Type))
.style("stroke", "white")
.style("stroke-width", 0.35)
.attr("transform", d => {
const scaleFactor = (d.r / 12) * 2.4; // Adjusted scale factor to make diamonds larger
return `translate(0, 0) scale(${scaleFactor})`;
});


leaf.append("title")
.text(d => d.data.item);

// Apply the zoom behavior to the SVG
svg.call(zoom);

// Zoom function
function zoomed(event) {
transformGroup.attr("transform", event.transform);
}

return svg.node();
};

const width = 700;
const height = 550;

return chart();

})()

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