Unlisted
Edited
Oct 17, 2022
Insert cell
# Districts with Spatial Diagrams
Insert cell
candidates = FileAttachment("candidates_2014.json").json()
Insert cell
d3.extent(candidates, c => c['recipient.cfscore'])
Insert cell
can_by_district = d3.group(candidates, d => d.district)
Insert cell
districts = FileAttachment("nv_districts.geojson").json()
Insert cell
click_and_zoom = {
//set up container
const svgHeight = 0.3 * width;
const top = htl.html`
<div class="wrapper">
<div class="info">
<div class="info-label">
</div>
<div class="info-diagram">
</div>
</div>
<svg viewBox="0 0 ${width} ${svgHeight}" class="map">
</svg>
</div>
`;
//initialize projection and path functions
const projection = d3.geoAlbersUsa();
const path = d3.geoPath().projection(projection);
//set scale and center
projection.scale(1).translate([0, 0]);
const b = path.bounds(districts);
const s =
0.95 /
Math.max((b[1][0] - b[0][0]) / width, (b[1][1] - b[0][1]) / svgHeight);
const t = [
(width - s * (b[1][0] + b[0][0])) / 2,
(svgHeight - s * (b[1][1] + b[0][1])) / 2
];
projection.scale(s).translate(t);

let svgMap = d3
.select(top)
.select("svg.map")
.on("click", function () {
d3.select(top)
.select("div.info-label")
.text("Click or touch a district for info");
svgMap.selectAll("path").attr("class", "district");
d3.select(top)
.select("div.info-diagram")
.html("");
});

let map = svgMap.append("g");
//draw the districts
map
.selectAll("path")
.data(districts.features)
.enter()
.append("path")
.attr("d", path)
.attr("id", (d) => d.properties.district)
.attr("class", "district")
.on("click", function (evt, d) {
d3.selectAll("path").attr("class", "district");
d3.select(this).attr("class", "selected");
d3.select(top).select("div.info-label").text(d.properties.district);
d3.select(top).select('div.info-diagram').html('');
d3.select(top).select("div.info-diagram").append(() => spatialDetails(can_by_district.get(d.properties.district)));
evt.stopPropagation();
});
//initialize the info box
d3.select(top).select("div.info-label").text("Click or touch a district for info");

//zoom
svgMap.call(
d3
.zoom()
.extent([
[0, 0],
[width, svgHeight]
])
.scaleExtent([1, 8])
.on("zoom", zoomed)
);
function zoomed({ transform }) {
map.attr("transform", transform);
}
return top;
}
Insert cell
function spatialDetails(candidates){
const margin = {top: 5, right: 10, bottom: 5, left: 10};
//const maxCandidates = d3.max(Array.from(can_by_district.keys()).map(k => can_by_district.get(k).length))
const height = margin.top + 55 + 3 + 10 + 15*candidates.length + 5 + margin.bottom
const pScale = d3.scaleLinear()
.domain([-7,7])
.range([margin.right,width - margin.left]);
const yPos = margin.top+55;
return htl.html`
<svg viewBox="0 0 ${width} ${height}" font-size="10px">
<path d="M${pScale(-7)} ${yPos} L${pScale(7)} ${yPos}" stroke="black" fill="none" stroke-width="6px" stroke-linecap="round" />
<path d="M${pScale(0)} ${yPos-3} l-5 -10 l10 0 l-5 10" stroke="black" fill="none" stroke-width="0.5px" />
<path d="M${pScale(0)} ${yPos-3-10} l0 -10" stroke="black" fill="none" stroke-width="0.5px" />
<text x=${pScale(0)} y=${yPos-3-20} text-anchor="middle" >0</text>
${candidates.sort((a,b) => d3.ascending(a['recipient.cfscore'],b['recipient.cfscore'])).map((c,i) => htl.svg`
<g transform=translate(${pScale(c['recipient.cfscore'])},${yPos+3})>
<path d="M0 0 l-5 10 l10 0 l-5 -10" stroke="black" fill="white" stroke-width="0.5px" />
<path d="M0 10 l0 ${15*candidates.length - i*15}" stroke="black" fill="none" stroke-width="0.5px" />
<text x="0" y=${15 + 15*candidates.length - i*15} text-anchor="start"} ">${c.description}</text>
${c.winner === "W" ? htl.svg`
<path d="M0 -6 l-5 -10 l10 0 l-5 10" stroke="black" fill="white" stroke-width="0.5px" />
<path d="M0 -16 l0 -20" stroke="black" fill="none" stroke-width="0.5px" />
<text x=0 y=${-6-30} text-anchor="middle" >W</text>
` : null}
${Math.abs(c['recipient.cfscore']) <= d3.min(candidates, d => Math.abs(d['recipient.cfscore'])) ?
c.winner === "W" ?
htl.svg`
<text x=0 y=${-6-40} text-anchor="middle" >M</text>
` :
htl.svg`
<path d="M0 -6 l-5 -10 l10 0 l-5 10" stroke="black" fill="white" stroke-width="0.5px" />
<path d="M0 -16 l0 -30" stroke="black" fill="none" stroke-width="0.5px" />
<text x=0 y=${-6-40} text-anchor="middle" >M</text>
`
: null}
</g>
`)}
</svg>
`
}
Insert cell
style = htl.html`
<style>
.info {
display: flex;
flex-direction: column;
align-items: center;
border: 1px solid black;
}
.info-label {
font-size: 20px;
font-weight: bold;
}
.info-diagram {
width: 100%;
}
.district {
stroke: gray;
stroke-width: 1px;
fill: white;
}
.selected {
stroke: black;
stroke-width: 1px;
fill: blue;
}
.zoom-overlay {
fill: none;
pointer-events: all;
}
</style>
`
Insert cell
Array.from(can_by_district.keys()).filter(d => d.substring(0,2) === "TX").map(d => can_by_district.get(d))
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