Published
Edited
Aug 27, 2020
1 fork
Insert cell
Insert cell
Insert cell
{ const width = 800;
const height = 800;
const marginLeft = 50;
const marginRight = 50;
const marginBottom = 50;
const marginTop = 10;
const smallFont = 12;
const medFont = 14;
const bigFont = 20;
const svg = d3
.create("svg")
.attr("viewBox", [0, 0, width*1.2, height*1.2]);

const xScale = d3
.scaleLinear()
.domain([0,100]) // % share of state population
.range([marginLeft, width - marginRight]);
const yScale = d3
.scaleLinear()
.domain([100,0]) // % share of shooting population
.range([marginTop+50, height - marginBottom]);
const xAxis = d3.axisBottom(xScale);
svg
.append("g")
.attr("class", "axis x-axis")
.attr("transform", `translate(0,${height-marginBottom})`)
.call(xAxis)
.append("text")
.attr("class", "axis-label")
.attr("x", "50%")
.attr("dy", "3em")
.text("Share of State Population")
.attr("fill", "#777");
const yAxis = d3.axisLeft(yScale);
svg
.append("g")
.attr("class", "axis y-axis")
.attr("transform", `translate(${marginLeft},${marginTop-10})`)
.call(yAxis)
.append("text")
.attr("class", "axis-label")
.attr("y", "50%")
.attr("dx", "-3em")
.attr("writing-mode", "vertical-rl")
.text("Share of Shooting Population")
.attr("fill", "#777")
const point = svg
.selectAll("circle")
.data(scatterBW)
// .data(scatterAll.flat())
.enter()
.append("circle")
.attr("class", (d) => "circle " + d.state + " " + d.race)
.attr("cy", (d) => yScale(d.shootingPct))
.attr("cx", (d) => xScale(d.demoPct))
.attr("r", 4)
.attr("fill", d => colorScale(d.race))
.attr("opacity", 1)
.on("mouseover", function(d) {
d3.select(this)
.attr("stroke", "black")
})
const groups = svg
.selectAll(“g”)
.data(d=> [d])
.join(“g”)

const lines = svg
.selectAll("line")
.data(scatterAll)
.join("line")
// .attr("CL", d => console.log(d))
.attr("x1", d => d.has("B") && xScale(d.get("B").demoPct))
.attr("x2", d => d.has("W") && xScale(d.get("W").demoPct))
.attr("y1", d => d.has("B") && yScale(d.get("B").shootingPct))
.attr("y2", d => d.has("W") && yScale(d.get("W").shootingPct))
.attr("stroke", "black")

// const lines = svg
// .selectAll((d) => "circle " + d.state)
// .filter(function (d) { return d.state, console.log("d", d.state)} )
// .append('line')
// .attr("stroke", "red")
// .attr('x1', function(d) { return xScale(d.demoPct)}) // return the point value
// //.attr('x2', function(d) { if (d.race === "B") find (d.race === "W"); return xScale(d.demoPct),
// // else if (d.race === "W") find (d.race === "B"); return xScale(d.demoPct)})
// // return the point value for the same state but the other race ^^
// .attr('y1', function(d) { return xScale(d.shootingPct)})
// //.attr('y2', function(d) { if (d.race === "B") find (d.race === "W"); return xScale(d.shootingPct),
// else if (d.race === "W") find (d.race === "B"); return xScale(d.shootingPct)})
//lines.attr(console.log("lines", lines))

//TODO:
// on click, add stroke to all dots in that state

// point color legend
/*
const legend = svg.append("g")
.attr("fill", "#777")
.attr("text-anchor", "middle")
.attr("font-family", "sans-serif")
.attr("font-size", 10)
.selectAll("g")
.data(colorScale)
.join("g")
.attr("transform", (d, i) => `translate(${width*.95 - (i + 1) * 25}, ${height*.87})`);
legend.append("circle")
.attr("fill-opacity", 1)
.attr("fill", d => (colorScale(d)))
.attr("r", 4)
legend.append("text")
.attr("dy", "2em");
//legend label
svg.append("text")
.attr("fill", "#777")
.attr("font-family", "sans-serif")
.attr("font-size", 10)
.attr("transform", `translate (${width*.78}, ${height*.92})`)
.text("shootings by race");
*/
const legendBW = svg.append("g")
.attr("fill", "#777")
.attr("text-anchor", "middle")
.attr("font-family", "sans-serif")
.attr("font-size", 10)
.selectAll("g")
.data(["#7fc97f", "#beaed4"])
.join("g")
.attr("transform", (d, i) => `translate(${width*.77}, ${height*.93 - (i + 1) * 25})`);
legendBW.append("circle")
.attr("fill-opacity", 1)
.attr("fill", d => (d))
.attr("r", 4)
//legendBW label
svg.append("text")
.attr("fill", "#777")
.attr("font-family", "sans-serif")
.attr("font-size", 10)
.attr("transform",`translate (${width*.79}, ${height*.872})`)
.text(`Black shootings per state`);
svg.append("text")
.attr("fill", "#777")
.attr("font-family", "sans-serif")
.attr("font-size", 10)
.attr("transform",`translate (${width*.79}, ${height*.903})`)
.text(`White shootings per state`);
// line y=x
svg.append("line")
.attr("x1", xScale(0))
.attr("y1", yScale(0))
.attr("x2", xScale(100))
.attr("y2", yScale(100))
//.attr("stroke", "dashed")
.style("stroke-dasharray", ("3, 3"))
.attr("stroke", "#777")
// annotations
// y=x annotation
svg.append("text")
.text("Police shooting demographics match state population demographics")
.attr("transform", `translate(${width*.6}, ${height*.38}) rotate(-45)`)
.attr("fill", "#888")
.attr("text-anchor", "middle")
.attr("font-family", "sans-serif")
.attr("font-size", smallFont);
// over rep
svg.append("text")
.text("Overrepresented as victims of police violence")
.attr("transform", `translate(${width*.3}, ${height*.25})`)
.attr("fill", "#888")
.attr("text-anchor", "middle")
.attr("font-family", "sans-serif")
.attr("font-size", smallFont);
// under rep
svg.append("text")
.text("Underrepresented as victims of police violence")
.attr("transform", `translate(${width*.8}, ${height*.65})`)
.attr("fill", "#888")
.attr("text-anchor", "middle")
.attr("font-family", "sans-serif")
.attr("font-size", smallFont);
// title
svg.append("text")
.attr("fill", "#777")
.attr("font-family", "sans-serif")
.attr("font-size", bigFont)
.attr("transform",
`translate(${width*.2}, 40)`
)
.text("Police Shooting Deaths by state and race, Jan 2015 - Aug 2020");
const tooltip2 = svg.append("g");
svg
.selectAll(".circle")
.on("mouseover", function(d) {
tooltip2.call(callout, `${d.state}
${d.absoluteNum} ${raceLookup[d.race]} people shot by police`)
tooltip2.attr("transform", `translate(${xScale(d.demoPct)}, ${yScale(d.shootingPct)})` )
.transition()
.duration(500)
.attr("fill-opacity", 0.8) //add stroke to circle and remove on mouseout
})

return svg.node();
}
Insert cell
Insert cell
Insert cell
scatterAll
Insert cell
scatterBW = [...scatterAll.flat().filter(d => d.race === "W" || d.race === "B")]
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
// opacityScale = d3.scaleLinear()
// .domain(d3.extent([...sortedRollup.values()]))
// .range([0,1])
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
md`
### Imports`
Insert cell
Insert cell
Insert cell
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