Published
Edited
Jul 29, 2020
1 fork
Importers
Insert cell
Insert cell
taserUseDisabilityChart = md`
#### Proportion of Police Taser use in England and Wales against those identified by the officer to have a disability, 2018-19

<figcaption>Disability status is officer-defined as the police do not capture self-identity for disability status. No data is available for Staffordshire, Warwickshire, West Mercia, Norfolk and Suffolk.</figcaption>

<div style="margin-left: ${graph.margin.left - 1}px">${legend}</div>
${chart}
`
Insert cell
barColours = ["#000000", "#434343", "#C1272D", "#C4C4C4"]
Insert cell
chart = {
const svg = d3.create("svg").attr("viewBox", [0, 0, width, height]);

// The bars themselves
svg
.append("g")
.selectAll("g")
.data(series)
.join("g")
// Each indivdual bar
.attr("fill", (d, i) => barColours[i])
.selectAll("rect")
.data(d => d)
.join("rect")
.attr("x", d => x(d[0]))
.attr("y", (d, i) => y(d.data.force))
.attr("width", d => x(d[1]) - x(d[0]))
.attr("height", y.bandwidth())
// Text - total
svg
.append("g")
.attr("fill", "white")
.attr("text-anchor", "end")
.attr("font-family", graph.font)
.attr("font-size", graph.fontSize)
.selectAll("text")
.data(disabilityLeaderboard.sort((a,b) => b.disabilityRatio - a.disabilityRatio))
.join("text")
.attr("x", d => x(d.disabilityRatio))
.attr("y", (d, i) => y(d.force) + y.bandwidth() / 2)
.attr("dy", "0.35em")
.attr("dx", -5)
.text(d => d3.format(".0%")(d.disabilityRatio))
// Draw axes
svg.append("g").call(xAxis);
svg.append("g").call(yAxis);

return svg.node()
}
Insert cell
x = d3
.scaleLinear()
.domain([0, d3.max(series, d => d3.max(d, d => d[1]))])
.range([graph.margin.left, width - graph.margin.right])
Insert cell
y = d3
.scaleBand()
.domain(disabilityLeaderboard.map(d => d.force))
.rangeRound([graph.margin.top, height - graph.margin.bottom])
.padding(0.1)
Insert cell
xAxis = g =>
g
.attr("transform", `translate(0,${graph.margin.top})`)
.call(d3.axisTop(x).ticks(width / 80, series.format).tickFormat(d3.format(".0%")))
.call(g => g.select(".domain").remove())
Insert cell
yAxis = g =>
g
.attr("transform", `translate(${graph.margin.left},0)`)
.call(d3.axisLeft(y).tickSizeOuter(0))
.attr("font-size", graph.fontSize)
Insert cell
getDisabilityStats = (forceName) => {
// Get matching rows from array
let forceStats = taserByDisability.filter((f) => f["Police Force"] === forceName)
// Assign each one to a variable
let total = forceStats.find((f) => f.variable === "Total").value
let mental = forceStats.find((f) => f.variable === "Mental").value
let physical = forceStats.find((f) => f.variable === "Physical").value
let both = forceStats.find((f) => f.variable === "Both").value
let none = forceStats.find((f) => f.variable === "None").value
let notReported = forceStats.find((f) => f.variable === "Not reported").value
// We need these as ratios for output
let hasDisability = total - none
let disabilityRatio = hasDisability / total
// Breakdown ratios
let mentalRatio = mental / total
let physicalRatio = physical / total
let bothRatio = both / total
let notReportedRatio = notReported / total
// Apparently this isn't needed now? hmm
// [disabilityRatio, mentalRatio, physicalRatio, bothRatio, notReportedRatio].forEach((r) => {
// console.log(r)
// if(isNaN(r)) { r = 0 }
// })
return {
force: forceName,
total: total,
totalDisability: hasDisability,
disabilityRatio: disabilityRatio,
mentalRatio: mentalRatio,
physicalRatio: physicalRatio,
notReportedRatio: notReportedRatio,
bothRatio: bothRatio
}
}
Insert cell
getDisabilityStats("Greater Manchester")
Insert cell
disabilityLeaderboard = forces.map((f) => {
return getDisabilityStats(f)
}).filter((f) => !isNaN(f.disabilityRatio))
.sort((a, b) => b.disabilityRatio - a.disabilityRatio)
Insert cell
// This object provides the stacked bars
series = d3
.stack()
.keys(['bothRatio', 'physicalRatio', 'mentalRatio', 'notReportedRatio'])(disabilityLeaderboard)
.map(d => (d.forEach(v => (v.key = d.key)), d))
Insert cell
legend = swatches({
color: d3.scaleOrdinal(["Mental and physical disability", "Physical disability", "Mental disability", "Not recorded"], barColours)
})
Insert cell
height = Math.ceil((disabilityLeaderboard.length + 0.1) * graph.barHeight) + graph.margin.top + graph.margin.bottom
Insert cell
disabilityPerFTE2019 = (force = false) => {
let total = getDisabilityStats(force).totalDisability
let officers = officerFTETotalByYear(2019, force)
let formatted = d3.format(".2")(total * 100/ officers)
return formatted
}
Insert cell
Insert cell
Insert cell
taserByDisability = d3.csv(
"https://data.resistancelab.network/analysis/0006-taser-use-by-disability/taser-use-by-disability.csv",
d3.autoType
)
Insert cell
import { swatches } from "@d3/color-legend"
Insert cell
import {forces, officerCounts} from "09b0c9da5e0018e9"
Insert cell
import {colours, d3, graph, officerFTETotalByYear} from '@resistancelab/resistance-lab-defaults'
Insert cell

One platform to build and deploy the best data apps

Experiment and prototype by building visualizations in live JavaScript notebooks. Collaborate with your team and decide which concepts to build out.
Use Observable Framework to build data apps locally. Use data loaders to build in any language or library, including Python, SQL, and R.
Seamlessly deploy to Observable. Test before you ship, use automatic deploy-on-commit, and ensure your projects are always up-to-date.
Learn more