Public
Edited
May 11
1 fork
Insert cell
Insert cell
proportionalSymbols = {
const svg = d3.create("svg")
.attr("viewBox", [0, 0, width, height]);

// Base map
svg.append("path")
.datum(topojson.feature(basepolygons, basepolygons.objects.ca_counties_wgs84))
.attr("fill", "#f0f0f0")
.attr("stroke", "#999")
.attr("d", path_basemap);

svg.append("path")
.datum(topojson.mesh(basepolygons, basepolygons.objects.ca_counties_wgs84, (a, b) => a !== b))
.attr("fill", "none")
.attr("stroke", "white")
.attr("stroke-linejoin", "round")
.attr("d", path_basemap);

// Centroids + join
const counties = topojson.feature(basepolygons, basepolygons.objects.ca_counties_wgs84).features;
const centroids = counties.map(d => ({
GEOID: +d.properties.GEOID,
name: d.properties.NAME,
coordinates: path_basemap.centroid(d)
}));

const joined = centroids.map(d => {
const match = failingData.find(e => +e.GEOID === d.GEOID);
return {
...d,
Failing_System_Count: match ? +match.Failing_System_Count : 0
};
});

// Make the circles much bigger
const radius = d3.scaleSqrt()
.domain([0, d3.max(joined, d => d.Failing_System_Count)])
.range([0, 40]); // Increased max radius

// Add circles
svg.append("g")
.attr("fill", "#e34a33") // orange-red, bold
.attr("fill-opacity", 0.6)
.attr("stroke", "#b30000")
.attr("stroke-width", 0.6)
.selectAll("circle")
.data(joined.sort((a, b) => b.Failing_System_Count - a.Failing_System_Count))
.join("circle")
.attr("cx", d => d.coordinates[0])
.attr("cy", d => d.coordinates[1])
.attr("r", d => radius(d.Failing_System_Count))
.append("title")
.text(d => `${d.name}\n${d.Failing_System_Count} failing systems`);
const legendValues = [d3.max(joined, d => d.Failing_System_Count), 200, 50];
const format = d3.format(",");

const legend = svg.append("g")
.attr("fill", "#555")
.attr("transform", "translate(120, 470)")
.attr("text-anchor", "start")
.style("font", "11px sans-serif");

legend.append("text")
.attr("x", 0)
.attr("y", -radius(legendValues[0]) - 25)
.attr("font-weight", "bold")
.text("Failing Systems");

const legendCircles = legend.selectAll("g")
.data(legendValues)
.join("g")
.attr("transform", d => `translate(0, ${-radius(d)})`);

legendCircles.append("circle")
.attr("r", radius)
.attr("fill", "none")
.attr("stroke", "#999");

legendCircles.append("text")
.attr("x", radius(legendValues[0]) + 6) // horizontal offset to right of biggest circle
.attr("dy", "0.35em")
.text(d => `${format(d)}`);


return svg.node();
}

Insert cell
height = 700
Insert cell
width = 975
Insert cell
path_points = d3.geoPath().projection(projection)
Insert cell
path_basemap = d3.geoPath().projection(projection)
Insert cell
failingData = FileAttachment("failingwatersystems.json")
.json()
.then(d => d.features.map(f => f.properties))
Insert cell
projection = d3.geoAlbers()
.rotate([120, 0])
.center([0, 37])
.fitSize([width, height], topojson.feature(basepolygons, basepolygons.objects.ca_counties_wgs84))
Insert cell
format = d3.format(",.0f")
Insert cell
radius(5000000)
Insert cell
d3.max([...data.values()])
Insert cell
radius(39)
Insert cell
radius = d3.scaleSqrt([0, d3.max([...data.values()])], [0, 20])
Insert cell
data.get("6027")
Insert cell
data = Object.assign(new Map(
systems_csv.map(([GEOID, FailingSystemCount]) => [String(GEOID), FailingSystemCount])
))

Insert cell
systems_csv = d3.csvParse(await FileAttachment("systemsfailure.csv").text(), ({GEOID, FailingSystemCount}) => [GEOID, +FailingSystemCount])
Insert cell
systems_points = FileAttachment("failingwatersystems.json").json()
Insert cell
basepolygons = FileAttachment("ca_counties_wgs84.json").json()
Insert cell
topojson = require("topojson-client@3")
Insert cell
d3 = require("d3@5")
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