Public
Edited
Dec 14, 2023
1 fork
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
shootings.csv
Type Table, then Shift-Enter. Ctrl-space for more options.

Insert cell
Insert cell
Insert cell
Insert cell
import {slider,button,select,text,radio,checkbox,number} from "@jashkenas/inputs"
Insert cell
d3 = require("d3@6")
Insert cell
data = FileAttachment("shootings.csv").csv()
Insert cell
shots = data
Insert cell
Insert cell
shots
select distinct armed, count(*) as number
from shots
group by armed
order by number desc
Insert cell
yearStr = year.toString() + "%"
Insert cell
shots
select distinct gender, count(*) as number
from shots where date like ${yearStr}
group by gender
order by number desc
Insert cell
shots
select distinct race as name, count(*) as value
from shots where date like ${yearStr}
group by race
order by value desc
Insert cell
shots
SELECT ' <18' AS age_range, COUNT(id) AS number
FROM shots
WHERE age < 18 AND date LIKE ${yearStr}
UNION
SELECT '18-24' AS age_range, COUNT(id) AS number
FROM shots
WHERE age >= 18 AND age <= 24 AND date LIKE ${yearStr}
UNION
SELECT '25-34' AS age_range, COUNT(id) AS number
FROM shots
WHERE age >= 25 AND age <= 34 AND date LIKE ${yearStr}
UNION
SELECT '35-44' AS age_range, COUNT(id) AS number
FROM shots
WHERE age >= 35 AND age <= 44 AND date LIKE ${yearStr}
UNION
SELECT '45-54' AS age_range, COUNT(id) AS number
FROM shots
WHERE age >= 45 AND age <= 54 AND date LIKE ${yearStr}
UNION
SELECT '55-64' AS age_range, COUNT(id) AS number
FROM shots
WHERE age >= 55 AND age <= 64 AND date LIKE ${yearStr}

UNION
SELECT '>64' AS age_range, COUNT(id) AS number
FROM shots
WHERE age >= 65 AND date LIKE ${yearStr}

ORDER BY age_range
Insert cell
AgeYear
Insert cell
shots
select distinct signs_of_mental_illness, count(*) as number
from shots where date like ${yearStr}
group by signs_of_mental_illness
order by number desc
Insert cell
shots
select distinct body_camera, count(*) as number
from shots where date like ${yearStr}
group by body_camera
order by number desc
Insert cell
shots
select distinct threat_level as name, count(*) as value
from shots where date like ${yearStr}
group by threat_level
order by value desc
Insert cell
shots
select distinct flee as name, count(*) as value
from shots where date like ${yearStr}
group by flee
order by value desc
Insert cell
shots
select distinct arms_category as name, count(*) as value
from shots where date like ${yearStr}
group by arms_category
order by value desc
Insert cell
arms = ArmsYear.map(d => d.name)
Insert cell
// turn countryScale into a scalePoint
ArmsScale = d3.scalePoint()
.domain(arms)
// changed from [0, visHeight] to [0, visWidth]
.range([0, 300])
.padding(0.2)
Insert cell
arms_numberScale = d3.scaleLinear()
.domain([0, d3.max(ArmsYear, d => d.value)])
.nice() // make the domain start and end on round values
// changed from [0, visWidth] to [visHeight, 0]
.range([200, 0])
Insert cell
{
// create and select an svg element that is the size of the bars plus margins
const margin = {top: 10, bottom: 100, left: 75, right: 10}
const visWidth = 300
const visHeight = 200
const svg = d3.create('svg')
.attr('width', visWidth + margin.left + margin.right)
.attr('height', visHeight + margin.top + margin.bottom);
// append a group element and move it left and down to create space
// for the left and top margins
const g = svg.append("g")
.attr('transform', `translate(${margin.left}, ${margin.top})`);
// bind our data to lines
g.selectAll('line')
.data(ArmsYear)
.join('line')
// countryScale sets the x position instead of y
.attr('x1', d => ArmsScale(d.name))
.attr('x2', d => ArmsScale(d.name))
// populationScale gives y position
.attr('y1', d => arms_numberScale(d.value))
.attr('y2', d => visHeight)
.attr('stroke', 'lightgray');
// bind our data to circles
g.selectAll('circle')
.data(ArmsYear)
.join('circle')
// countryScale sets the x position instead of y
.attr('cx', d => ArmsScale(d.name))
// populationScale gives y position
.attr('cy', d => arms_numberScale(d.value))
.attr('r', 5)
.attr('fill', 'steelblue');
// calculate the mean score
const meanArmsNumber = d3.mean(ArmsYear, d => d.value);
// add a line for the mean
g.append('line')
.attr('x1', 0)
.attr('x2', visWidth)
.attr('y1', arms_numberScale(meanArmsNumber))
.attr('y2', arms_numberScale(meanArmsNumber))
.attr('stroke', 'red');

// changed countryScale to populationScale
const yAxis = d3.axisLeft(arms_numberScale).tickFormat(d3.format('~s'))
// add a group for the y-axis
g.append('g')
.call(yAxis);

const xAxis = d3.axisBottom(ArmsScale)
// add a group for the x-axis
g.append('g')
// we have to move this group down to the bottom of the vis
.attr('transform', `translate(0, ${visHeight})`)
.call(xAxis)
.selectAll("text")
.style("text-anchor", "end")
.attr("dx", "-.8em")
.attr("dy", ".15em")
.attr("transform", "rotate(-55)")
// add a label for the x-axis
.append('text')
.attr('fill', 'black')
.attr('font-family', 'sans-serif')
.attr('x', visWidth / 2)
.attr('y', 40)
.text("arms");
return svg.node();
}
Insert cell
Insert cell
{
const year_shots = new Map()
years.map(year => year_shots[year] = [])
shots.map(row => {
const cur_year = row.date.substring(0, 4)
year_shots[cur_year].push(row)
})
return year_shots
}
Insert cell
pieGenerator = d3.pie().value(d => d.number)
Insert cell
exampleAngles = pieGenerator(RaceYear)
Insert cell
exampleArcGenerator = d3.arc()
.innerRadius(0)
.outerRadius(200)
Insert cell
exampleColor = d3.scaleOrdinal()
.domain(RaceYear.map(d => d.race))
.range(d3.schemeCategory10);
Insert cell
{
const margin = {top:50, left:10, right:30, bottom:0}
const visWidth = 1000 - margin.left - margin.right
const visHeight = 140 - margin.top - margin.bottom
const svg = d3.create('svg')
.attr('width', visWidth + margin.left + margin.right)
.attr('height', visHeight + margin.top + margin.bottom)
const g = svg.append("g")
.attr('transform', `translate(${margin.left}, ${margin.top})`)
const data = [{name:"Gender", male:GenderYear[0].number, female:GenderYear[1].number}]
const y_scale = d3.scaleBand()
.domain(["Gender"])
.range([margin.top ,visHeight])
.padding(0.3)
const male_scale = d3.scaleLinear()
.domain(d3.extent([0, 1000]))
.range([visWidth/2 + 65, visWidth])
const female_scale = d3.scaleLinear()
.domain(d3.extent([0, 1000]))
.range([visWidth/2 - 65, 0])
const donation_Axis = d3.axisTop(male_scale)
.ticks(5)
const received_Axis = d3.axisTop(female_scale)
.ticks(5)
// g.append('text')
// .text("Amounts Donated and Received Per Country from 1973 to 2013")
// .attr('x', visWidth/2)
// .attr('y', 0)
// .attr('font-size', '15px')
// .attr("text-anchor", "middle")
// .attr("dominant-baseline", "top")
// .attr("font-family", "sans-serif")
g.append('text')
.text("Female")
.attr('x', visWidth/4)
.attr('y', 20)
.attr('font-size', '12px')
.attr("text-anchor", "middle")
.attr("font-family", "sans-serif")
g.append('text')
.text("Male")
.attr('x', visWidth/2 + visWidth/4)
.attr('y', 20)
.attr('font-size', '12px')
.attr("text-anchor", "middle")
.attr("font-family", "sans-serif")
g.append('g')
.attr('transform', `translate(0,${margin.top})`)
.call(donation_Axis)
g.append('g')
.attr('transform', `translate(0,${margin.top})`)
.call(received_Axis)
g.selectAll('.col')
.data(data)
.join('g')
.append('text')
.text(d => d.name)
.attr('x', visWidth/2)
.attr('y', d => y_scale(d.name) + 3)
.attr('font-size', '12px')
.attr("text-anchor", "middle")
.attr("dominant-baseline", "hanging")
.attr("font-family", "sans-serif")
g.append("g")
.selectAll("rect")
.data(data)
.join("rect")
.attr("fill", 'orange')
.attr("x", visWidth/2 + 65)
.attr("y", d => y_scale(d.name))
.attr("width", d => male_scale(d.male) - (visWidth/2 + 65))
.attr("height", y_scale.bandwidth());
g.append("g")
.selectAll("rect")
.data(data)
.join("rect")
.attr("fill", 'steelblue')
.attr("x", d => female_scale(d.female))
.attr("y", d => y_scale(d.name))
.attr("width", d => (visWidth/2 -65) - female_scale(d.female))
.attr("height", y_scale.bandwidth());

g.append('line')
.attr('x1', visWidth/2 + 65)
.attr('y1', 50)
.attr('x2', visWidth/2 + 65)
.attr('y2', visHeight)
.attr('stroke', 'green')
.attr('stroke-opacity', 0.3)
g.append('line')
.attr('x1', visWidth/2 - 65)
.attr('y1', 50)
.attr('x2', visWidth/2 - 65)
.attr('y2', visHeight)
.attr('stroke', 'red')
.attr('stroke-opacity', 0.3)
return svg.node()
}
Insert cell
age_ranges = AgeYear.map(d => d["age_range"])
Insert cell
// turn countryScale into a scalePoint
ageScaleBand = d3.scaleBand()
.domain(age_ranges)
// changed from [0, visHeight] to [0, visWidth]
.range([0, 200])
.padding(0.2)
// turn countryScale into a scalePoint

Insert cell
// turn countryScale into a scalePoint
age_range_scale = d3.scalePoint()
.domain(age_ranges)
// changed from [0, visHeight] to [0, visWidth]
.range([0, 100])
.padding(0.2)
Insert cell
ageScale = d3.scaleLinear()
.domain([0, d3.max(AgeYear, d=> d.number)])
.nice() // make the domain start and end on round values
// changed from [0, visWidth] to [visHeight, 0]
.range([100, 0])
Insert cell
// changed populationScale to countryScale
xAxis = d3.axisBottom(age_range_scale)
Insert cell
// changed countryScale to populationScale
yAxis = d3.axisLeft(ageScale).tickFormat(d3.format('~s'))
Insert cell
{
// create and select an svg element that is the size of the bars plus margins
const svg = d3.create('svg')
.attr('width', 400)
.attr('height', 400);
// append a group element and move it left and down to create space
// for the left and top margins
const g = svg.append("g")
.attr('transform', `translate(100,100)`);
// bind our data to lines
g.selectAll('rect')
.data(AgeYear)
.join('rect')
// countryScale sets the x position instead of y
.attr('x', d => ageScaleBand(d.age_range))
.attr('width', d => ageScaleBand.bandwidth())
// populationScale gives y position
.attr('y', d => ageScale(d.number))
.attr('height', d => 100 - ageScale(d.number))
.attr('fill', 'steelblue');
// add a group for the y-axis
g.append('g')
.call(yAxis);
// add a group for the x-axis
g.append('g')
// we have to move this group down to the bottom of the vis
.attr('transform', `translate(0, 100)`)
.call(d3.axisBottom(ageScaleBand))
.selectAll("text")
.style("text-anchor", "end")
.attr("dx", "-.8em")
.attr("dy", ".15em")
.attr("transform", "rotate(-55)")
// add a label for the x-axis
.append('text')
.attr('fill', 'black')
.attr('font-family', 'sans-serif')
.attr('x', 100 / 2)
.attr('y', 40)
.text("age range");
return svg.node();
}
Insert cell
Insert cell
viewof year = slider({max:years[years.length-1],min:years[0], step:1})
Insert cell
{
const svg = d3.create('svg')
.attr('width', 1200)
.attr('height', 900);
// race pie
const race_graph = svg.append('g').attr('transform', 'translate(-350,0)');
let height = 250;
let radius = Math.min(width, height) / 2;
let margin = {top: 10, right: 10, bottom: 10, left: 10}
let colorScale = d3.scaleOrdinal(d3.schemeCategory10);
let totals = RaceYear.map((element) => element.value);
let grandTotal = totals.reduce((a, b) => a + b);
let arc = d3.arc()
.outerRadius(radius - 25)
.innerRadius(1.5);
let labelArc = d3.arc()
.outerRadius(radius - 60)
.innerRadius(radius - 60);
let pie = d3.pie()
.sort((a, b) => d3.ascending(a, b))
.value((d) => d.value)
.padAngle(0.01);
let arcGroup = race_graph.append("g")
.attr("class", "arc-group")
.attr("transform", `translate(${width/2 - margin.left - margin.right}, ${height/2 + margin.top + margin.bottom})`);
let arcs = arcGroup.selectAll(".arc")
.data(pie(RaceYear)).enter()
.append("g")
.attr("class", "arc");
arcs.append("path")
.attr("d", arc)
.style("fill", (d) => colorScale(d.data.name));
let legendPadding = 5;
let legendBox = race_graph.append("g")
.attr("class", "legend-group")
.attr("transform", `translate(${width/2+radius}, ${(3/4) * radius})`);
let legend = legendBox.selectAll(".legend")
.data(RaceYear).enter()
.append("g")
.attr("class", "legend")
.attr("transform", (d, i) => `translate(${0}, ${i * 20 + 20})`);
let displacement = "0.4em"
legend.append("circle")
.attr("class", "legend-color")
.attr("r", displacement)
.style("fill", (d) => colorScale(d.name));
legend.append("text")
.attr("class", "legend-text")
.attr("x", "1em")
.attr("y", displacement)
.style("font", "14px sans-serif")
.style("text-anchor", "start")
.text((d) => d.name);
arcs.append("text")
.attr("transform", (d) => `translate(${labelArc.centroid(d)})`)
.attr("dy", ".35em")
.style("font", "14px sans-serif")
.style("text-anchor", "middle")
.style("stroke", "white")
.style("stroke-width", "3px")
.style("opacity", 0.8)
.text((d, i) => `${((totals[i] / grandTotal) * 100).toFixed(2)}%`);
arcs.append("text")
.attr("transform", (d) => `translate(${labelArc.centroid(d)})`)
.attr("dy", ".35em")
.style("font", "14px sans-serif")
.style("text-anchor", "middle")
.text((d, i) => `${((totals[i] / grandTotal) * 100).toFixed(2)}%`);
race_graph.append("text")
.attr("x", width/2-25)
.attr("y", 30)
.style("text-anchor", "middle")
.style("font", "14px sans-serif")
.text("Race Distribution");

// flee pie
const flee_graph = svg.append('g').attr('transform', 'translate(0, 0)');
let colorScale2 = d3.scaleOrdinal(d3.schemeCategory10);
let totals2 = FleeYear.map((element) => element.value);
let grandTotal2 = totals2.reduce((a, b) => a + b);
let arc2 = d3.arc()
.outerRadius(radius - 25)
.innerRadius(1.5);
let labelArc2 = d3.arc()
.outerRadius(radius - 60)
.innerRadius(radius - 60);
let pie2 = d3.pie()
.sort((a, b) => d3.ascending(a, b))
.value((d) => d.value)
.padAngle(0.01);
let arcGroup2 = flee_graph.append("g")
.attr("class", "arc-group")
.attr("transform", `translate(${width/2 - margin.left - margin.right}, ${height/2 + margin.top + margin.bottom})`);
let arcs2 = arcGroup2.selectAll(".arc")
.data(pie(FleeYear)).enter()
.append("g")
.attr("class", "arc");
arcs2.append("path")
.attr("d", arc2)
.style("fill", (d) => colorScale2(d.data.name));
let legendPadding2 = 5;
let legendBox2 = flee_graph.append("g")
.attr("class", "legend-group")
.attr("transform", `translate(${width/2+radius}, ${(3/4) * radius})`);
let legend2 = legendBox2.selectAll(".legend")
.data(FleeYear).enter()
.append("g")
.attr("class", "legend")
.attr("transform", (d, i) => `translate(${0}, ${i * 20 + 20})`);
let displacement2 = "0.4em"
legend2.append("circle")
.attr("class", "legend-color")
.attr("r", displacement2)
.style("fill", (d) => colorScale2(d.name));
legend2.append("text")
.attr("class", "legend-text")
.attr("x", "1em")
.attr("y", displacement2)
.style("font", "14px sans-serif")
.style("text-anchor", "start")
.text((d) => d.name);
arcs2.append("text")
.attr("transform", (d) => `translate(${labelArc2.centroid(d)})`)
.attr("dy", ".35em")
.style("font", "14px sans-serif")
.style("text-anchor", "middle")
.style("stroke", "white")
.style("stroke-width", "3px")
.style("opacity", 0.8)
.text((d, i) => `${((totals2[i] / grandTotal2) * 100).toFixed(2)}%`);
arcs2.append("text")
.attr("transform", (d) => `translate(${labelArc2.centroid(d)})`)
.attr("dy", ".35em")
.style("font", "14px sans-serif")
.style("text-anchor", "middle")
.text((d, i) => `${((totals2[i] / grandTotal2) * 100).toFixed(2)}%`);
flee_graph.append("text")
.attr("x", width/2-25)
.attr("y", 30)
.style("text-anchor", "middle")
.style("font", "14px sans-serif")
.text("Flee Distribution");
// theat pie
const threat_graph = svg.append('g').attr('transform', 'translate(350,0)');
let colorScale3 = d3.scaleOrdinal(d3.schemeCategory10);
let totals3 = ThreatYear.map((element) => element.value);
let grandTotal3 = totals3.reduce((a, b) => a + b);
let arc3 = d3.arc()
.outerRadius(radius - 25)
.innerRadius(1.5);
let labelArc3 = d3.arc()
.outerRadius(radius - 60)
.innerRadius(radius - 60);
let pie3 = d3.pie()
.sort((a, b) => d3.ascending(a, b))
.value((d) => d.value)
.padAngle(0.01);
let arcGroup3 = threat_graph.append("g")
.attr("class", "arc-group")
.attr("transform", `translate(${width/2 - margin.left - margin.right}, ${height/2 + margin.top + margin.bottom})`);
let arcs3 = arcGroup3.selectAll(".arc")
.data(pie(ThreatYear)).enter()
.append("g")
.attr("class", "arc");
arcs3.append("path")
.attr("d", arc3)
.style("fill", (d) => colorScale3(d.data.name));
let legendPadding3 = 5;
let legendBox3 = threat_graph.append("g")
.attr("class", "legend-group")
.attr("transform", `translate(${width/2+radius}, ${(3/4) * radius})`);
let legend3 = legendBox3.selectAll(".legend")
.data(ThreatYear).enter()
.append("g")
.attr("class", "legend")
.attr("transform", (d, i) => `translate(${0}, ${i * 20 + 20})`);
let displacement3 = "0.4em"
legend3.append("circle")
.attr("class", "legend-color")
.attr("r", displacement3)
.style("fill", (d) => colorScale3(d.name));
legend3.append("text")
.attr("class", "legend-text")
.attr("x", "1em")
.attr("y", displacement3)
.style("font", "14px sans-serif")
.style("text-anchor", "start")
.text((d) => d.name);
arcs3.append("text")
.attr("transform", (d) => `translate(${labelArc3.centroid(d)})`)
.attr("dy", ".35em")
.style("font", "14px sans-serif")
.style("text-anchor", "middle")
.style("stroke", "white")
.style("stroke-width", "3px")
.style("opacity", 0.8)
.text((d, i) => `${((totals3[i] / grandTotal3) * 100).toFixed(2)}%`);
arcs3.append("text")
.attr("transform", (d) => `translate(${labelArc3.centroid(d)})`)
.attr("dy", ".35em")
.style("font", "14px sans-serif")
.style("text-anchor", "middle")
.text((d, i) => `${((totals3[i] / grandTotal3) * 100).toFixed(2)}%`);
threat_graph.append("text")
.attr("x", width/2 - 25)
.attr("y", 30)
.style("text-anchor", "middle")
.style("font", "14px sans-serif")
.text("Threat Level Distribution");

// gender bar
const gender_bar = svg.append("g")
.attr('transform', `translate(50, 250)`)
const gender_data = [{name:"Gender", male:GenderYear[0].number, female:GenderYear[1].number}]
const margin2 = {top:50, left:10, right:30, bottom:0}
const visWidth = 1000 - margin2.left - margin2.right
const visHeight = 130 - margin2.top - margin2.bottom
const y_scale = d3.scaleBand()
.domain(["Gender"])
.range([margin2.top ,visHeight])
.padding(0.3)
const male_scale = d3.scaleLinear()
.domain(d3.extent([0, 1000]))
.range([visWidth/2 + 65, visWidth])
const female_scale = d3.scaleLinear()
.domain(d3.extent([0, 1000]))
.range([visWidth/2 - 65, 0])
const male_Axis = d3.axisTop(male_scale)
.ticks(5)
const female_Axis = d3.axisTop(female_scale)
.ticks(5)
gender_bar.append('text')
.text("Female")
.attr('x', visWidth/4)
.attr('y', 20)
.attr('font-size', '12px')
.attr("text-anchor", "middle")
.attr("font-family", "sans-serif")
gender_bar.append('text')
.text("Male")
.attr('x', visWidth/2 + visWidth/4)
.attr('y', 20)
.attr('font-size', '12px')
.attr("text-anchor", "middle")
.attr("font-family", "sans-serif")
gender_bar.append('g')
.attr('transform', `translate(0,${margin2.top})`)
.call(male_Axis)
gender_bar.append('g')
.attr('transform', `translate(0,${margin2.top})`)
.call(female_Axis)
gender_bar.selectAll('.col')
.data(gender_data)
.join('g')
.append('text')
.text(d => d.name)
.attr('x', visWidth/2)
.attr('y', d => y_scale(d.name) + 3)
.attr('font-size', '12px')
.attr("text-anchor", "middle")
.attr("dominant-baseline", "hanging")
.attr("font-family", "sans-serif")
gender_bar.append("g")
.selectAll("rect")
.data(gender_data)
.join("rect")
.attr("fill", 'orange')
.attr("x", visWidth/2 + 65)
.attr("y", d => y_scale(d.name))
.attr("width", d => male_scale(d.male) - (visWidth/2 + 65))
.attr("height", y_scale.bandwidth());
gender_bar.append("g")
.selectAll("rect")
.data(gender_data)
.join("rect")
.attr("fill", 'steelblue')
.attr("x", d => female_scale(d.female))
.attr("y", d => y_scale(d.name))
.attr("width", d => (visWidth/2 -65) - female_scale(d.female))
.attr("height", y_scale.bandwidth());
gender_bar.append('line')
.attr('x1', visWidth/2 + 65)
.attr('y1', 50)
.attr('x2', visWidth/2 + 65)
.attr('y2', visHeight)
.attr('stroke', 'red')
.attr('stroke-opacity', 0.3)
gender_bar.append('line')
.attr('x1', visWidth/2 - 65)
.attr('y1', 50)
.attr('x2', visWidth/2 - 65)
.attr('y2', visHeight)
.attr('stroke', 'green')
.attr('stroke-opacity', 0.3)

// signs of mental illness
const mental_bar = svg.append("g")
.attr('transform', `translate(50, 350)`)
const mental_data = [{name:"Signs of Mental Illness", False:MentalYear[0].number, True:MentalYear[1].number}]
const yy_scale = d3.scaleBand()
.domain(["Signs of Mental Illness"])
.range([margin2.top ,visHeight])
.padding(0.3)
const true_scale = d3.scaleLinear()
.domain(d3.extent([0, 1000]))
.range([visWidth/2 + 65, visWidth])
const false_scale = d3.scaleLinear()
.domain(d3.extent([0, 1000]))
.range([visWidth/2 - 65, 0])
const false_Axis = d3.axisTop(false_scale)
.ticks(5)
const true_Axis = d3.axisTop(true_scale)
.ticks(5)
mental_bar.append('text')
.text("False")
.attr('x', visWidth/4)
.attr('y', 20)
.attr('font-size', '12px')
.attr("text-anchor", "middle")
.attr("font-family", "sans-serif")
mental_bar.append('text')
.text("True")
.attr('x', visWidth/2 + visWidth/4)
.attr('y', 20)
.attr('font-size', '12px')
.attr("text-anchor", "middle")
.attr("font-family", "sans-serif")
mental_bar.append('g')
.attr('transform', `translate(0,${margin2.top})`)
.call(false_Axis)
mental_bar.append('g')
.attr('transform', `translate(0,${margin2.top})`)
.call(true_Axis)
mental_bar.selectAll('.col')
.data(mental_data)
.join('g')
.append('text')
.text(d => d.name)
.attr('x', visWidth/2)
.attr('y', d => yy_scale(d.name) + 3)
.attr('font-size', '12px')
.attr("text-anchor", "middle")
.attr("dominant-baseline", "hanging")
.attr("font-family", "sans-serif")
mental_bar.append("g")
.selectAll("rect")
.data(mental_data)
.join("rect")
.attr("fill", 'orange')
.attr("x", visWidth/2 + 65)
.attr("y", d => yy_scale(d.name))
.attr("width", d => true_scale(d.True) - (visWidth/2 + 65))
.attr("height", yy_scale.bandwidth());
mental_bar.append("g")
.selectAll("rect")
.data(mental_data)
.join("rect")
.attr("fill", 'steelblue')
.attr("x", d => false_scale(d.False))
.attr("y", d => yy_scale(d.name))
.attr("width", d => (visWidth/2 -65) - false_scale(d.False))
.attr("height", yy_scale.bandwidth());
mental_bar.append('line')
.attr('x1', visWidth/2 + 65)
.attr('y1', 50)
.attr('x2', visWidth/2 + 65)
.attr('y2', visHeight)
.attr('stroke', 'red')
.attr('stroke-opacity', 0.3)
mental_bar.append('line')
.attr('x1', visWidth/2 - 65)
.attr('y1', 50)
.attr('x2', visWidth/2 - 65)
.attr('y2', visHeight)
.attr('stroke', 'green')
.attr('stroke-opacity', 0.3)
// body camera
const camera_bar = svg.append("g")
.attr('transform', `translate(50, 450)`)
const camera_data = [{name:"Body Camera", False: CameraYear[0].number, True:CameraYear[1].number}]
const yyy_scale = d3.scaleBand()
.domain(["Body Camera"])
.range([margin2.top ,visHeight])
.padding(0.3)
const true_scale2 = d3.scaleLinear()
.domain(d3.extent([0, 1000]))
.range([visWidth/2 + 65, visWidth])
const false_scale2 = d3.scaleLinear()
.domain(d3.extent([0, 1000]))
.range([visWidth/2 - 65, 0])
const false_Axis2 = d3.axisTop(false_scale2)
.ticks(5)
const true_Axis2 = d3.axisTop(true_scale2)
.ticks(5)
camera_bar.append('text')
.text("False")
.attr('x', visWidth/4)
.attr('y', 20)
.attr('font-size', '12px')
.attr("text-anchor", "middle")
.attr("font-family", "sans-serif")
camera_bar.append('text')
.text("True")
.attr('x', visWidth/2 + visWidth/4)
.attr('y', 20)
.attr('font-size', '12px')
.attr("text-anchor", "middle")
.attr("font-family", "sans-serif")
camera_bar.append('g')
.attr('transform', `translate(0,${margin2.top})`)
.call(false_Axis2)
camera_bar.append('g')
.attr('transform', `translate(0,${margin2.top})`)
.call(true_Axis2)
camera_bar.selectAll('.col')
.data(camera_data)
.join('g')
.append('text')
.text(d => d.name)
.attr('x', visWidth/2)
.attr('y', d => yyy_scale(d.name) + 3)
.attr('font-size', '12px')
.attr("text-anchor", "middle")
.attr("dominant-baseline", "hanging")
.attr("font-family", "sans-serif")
camera_bar.append("g")
.selectAll("rect")
.data(camera_data)
.join("rect")
.attr("fill", 'orange')
.attr("x", visWidth/2 + 65)
.attr("y", d => yyy_scale(d.name))
.attr("width", d => true_scale2(d.True) - (visWidth/2 + 65))
.attr("height", yyy_scale.bandwidth());
camera_bar.append("g")
.selectAll("rect")
.data(camera_data)
.join("rect")
.attr("fill", 'steelblue')
.attr("x", d => false_scale2(d.False))
.attr("y", d => yyy_scale(d.name))
.attr("width", d => (visWidth/2 -65) - false_scale2(d.False))
.attr("height", yyy_scale.bandwidth());
camera_bar.append('line')
.attr('x1', visWidth/2 + 65)
.attr('y1', 50)
.attr('x2', visWidth/2 + 65)
.attr('y2', visHeight)
.attr('stroke', 'red')
.attr('stroke-opacity', 0.3)
camera_bar.append('line')
.attr('x1', visWidth/2 - 65)
.attr('y1', 50)
.attr('x2', visWidth/2 - 65)
.attr('y2', visHeight)
.attr('stroke', 'green')
.attr('stroke-opacity', 0.3)
//age bar
const age_bar = svg.append("g")
.attr('transform', `translate(650,600)`);

// turn countryScale into a scalePoint
const age_ranges = AgeYear.map(d => d["age_range"])
const ageScaleBand = d3.scaleBand()
.domain(age_ranges)
// changed from [0, visHeight] to [0, visWidth]
.range([0, 300])
.padding(0.2)
// turn countryScale into a scalePoint
const age_range_scale = d3.scalePoint()
.domain(age_ranges)
// changed from [0, visHeight] to [0, visWidth]
.range([0, 300])
.padding(0.2)
const ageScale = d3.scaleLinear()
.domain([0, d3.max(AgeYear, d=> d.number)])
.nice() // make the domain start and end on round values
// changed from [0, visWidth] to [visHeight, 0]
.range([200, 0])
const agexAxis = d3.axisBottom(age_range_scale)
const ageyAxis = d3.axisLeft(ageScale).tickFormat(d3.format('~s'))

age_bar.append('text')
.attr('fill', 'black')
.attr('font-family', 'sans-serif')
.attr('x', 0)
.attr('y', -20)
.text("Age Distribution");
age_bar.selectAll('rect')
.data(AgeYear)
.join('rect')
.attr('x', d => ageScaleBand(d.age_range))
.attr('width', d => ageScaleBand.bandwidth())
.attr('y', d => ageScale(d.number))
.attr('height', d => 200 - ageScale(d.number))
.attr('fill', 'steelblue');
age_bar.append('g')
.call(ageyAxis);
age_bar.append('g')
.attr('transform', `translate(0, 200)`)
.call(d3.axisBottom(ageScaleBand))
.selectAll("text")
.style("text-anchor", "end")
.attr("dx", "-.8em")
.attr("dy", ".15em")
.attr("transform", "rotate(-55)")
.append('text')
.attr('fill', 'black')
.attr('font-family', 'sans-serif')
.attr('x', 100 / 2)
.attr('y', 40)
.text("age range");


// arm bar
const margin3 = {top: 10, bottom: 100, left: 75, right: 10}
const visWidth3 = 300
const visHeight3 = 200
const arms_graph = svg.append("g")
.attr('transform', `translate(100, 600)`);
arms_graph.append('text')
.attr('fill', 'black')
.attr('font-family', 'sans-serif')
.attr('x', 0)
.attr('y', -20)
.text("Arms Category");
arms_graph.selectAll('line')
.data(ArmsYear)
.join('line')
.attr('x1', d => ArmsScale(d.name))
.attr('x2', d => ArmsScale(d.name))
.attr('y1', d => arms_numberScale(d.value))
.attr('y2', d => visHeight3)
.attr('stroke', 'lightgray');
// bind our data to circles
arms_graph.selectAll('circle')
.data(ArmsYear)
.join('circle')
// countryScale sets the x position instead of y
.attr('cx', d => ArmsScale(d.name))
// populationScale gives y position
.attr('cy', d => arms_numberScale(d.value))
.attr('r', 5)
.attr('fill', 'steelblue');
const meanArmsNumber = d3.mean(ArmsYear, d => d.value);
arms_graph.append('line')
.attr('x1', 0)
.attr('x2', visWidth3)
.attr('y1', arms_numberScale(meanArmsNumber))
.attr('y2', arms_numberScale(meanArmsNumber))
.attr('stroke', 'red');
const yAxis3 = d3.axisLeft(arms_numberScale).tickFormat(d3.format('~s'))
arms_graph.append('g')
.call(yAxis3);
const xAxis3 = d3.axisBottom(ArmsScale)
arms_graph.append('g')
.attr('transform', `translate(0, ${visHeight3})`)
.call(xAxis3)
.selectAll("text")
.style("text-anchor", "end")
.attr("dx", "-.8em")
.attr("dy", ".15em")
.attr("transform", "rotate(-55)")
.append('text')
.attr('fill', 'black')
.attr('font-family', 'sans-serif')
.attr('x', visWidth3 / 2)
.attr('y', 40)
.text("arms");
return svg.node();
}
Insert cell
Insert cell
Insert cell
shootings = data
Insert cell
top10Cities = {
const cityCounts = d3.rollup(shootings, v => v.length, d => d.city);
const sortedCities = Array.from(cityCounts).sort((a, b) => d3.descending(a[1], b[1]));
const top10Cities = sortedCities.slice(0, 10);
return top10Cities;
}
Insert cell
{
const width = 800;
const height = 400;
const margin = { top: 20, right: 30, bottom: 40, left: 90 };
const x = d3.scaleLinear()
.domain([0, d3.max(top10Cities, d => d[1])])
.range([margin.left, width - margin.right]);
const y = d3.scaleBand()
.domain(top10Cities.map(d => d[0]))
.range([margin.top, height - margin.bottom])
.padding(0.1);
const svg = d3.create("svg")
.attr("viewBox", [0, 0, width, height]);
svg.append("g")
.selectAll("rect")
.data(top10Cities)
.join("rect")
.attr("x", x(0))
.attr("y", d => y(d[0]))
.attr("width", d => x(d[1]) - x(0))
.attr("height", y.bandwidth())
.attr("fill", "steelblue");
svg.append("g")
.attr("fill", "#000")
.selectAll("text")
.data(top10Cities)
.join("text")
.attr("x", d => x(d[1]) + 4)
.attr("y", d => y(d[0]) + y.bandwidth() / 2)
.attr("dy", "0.35em")
.text(d => d[1])
.attr("font-size", "15px");
svg.append("g")
.attr("transform", `translate(0,${height - margin.bottom})`)
.call(d3.axisBottom(x).ticks(null, "s"));
svg.append("g")
.attr("transform", `translate(${margin.left},0)`)
.call(d3.axisLeft(y));
return svg.node();
}
Insert cell
Insert cell
{
const topCities = [
{ name: "Los Angeles", coordinates: [-118.2437, 34.0522], shootings: 78 },
{ name: "Phoenix", coordinates: [-112.0740, 33.4484], shootings: 66 },
{ name: "Houston", coordinates: [-95.3698, 29.7604], shootings: 51 },
{ name: "Las Vegas", coordinates: [-115.1398, 36.1699], shootings: 41 },
{ name: "San Antonio", coordinates: [-98.4936, 29.4241], shootings: 40 },
{ name: "Chicago", coordinates: [-87.6298, 41.8781], shootings: 38 },
{ name: "Columbus", coordinates: [-82.9988, 39.9612], shootings: 36 },
{ name: "Albuquerque", coordinates: [-106.6504, 35.0844], shootings: 32 },
{ name: "Jacksonville", coordinates: [-81.6557, 30.3322], shootings: 31 },
{ name: "St. Louis", coordinates: [-90.1994, 38.6270], shootings: 30 }
];
const geoData = await d3.json("https://raw.githubusercontent.com/johan/world.geo.json/master/countries.geo.json");
const usFeature = geoData.features.find(d => d.properties.name === "United States of America");

const width = 960;
const height = 600;
const projection = d3.geoAlbersUsa().scale(1300).translate([width / 2, height / 2]);
const pathGenerator = d3.geoPath().projection(projection);
const svg = d3.create("svg")
.attr("viewBox", [0, 0, width, height]);
svg.append("path")
.datum(usFeature)
.attr("d", pathGenerator)
.attr("fill", "#ddd");
const colorScale = d3.scaleSequential(d3.interpolateBlues)
.domain([0, d3.max(topCities, d => d.shootings)]);
const tooltip = d3.select("body").append("div")
.attr("class", "tooltip")
.style("position", "absolute")
.style("visibility", "hidden")
.style("background", "#fff")
.style("border", "1px solid #ddd")
.style("padding", "5px")
.style("border-radius", "5px")
.style("text-align", "left");
svg.selectAll("circle")
.data(topCities)
.enter().append("circle")
.attr("cx", d => projection(d.coordinates)[0])
.attr("cy", d => projection(d.coordinates)[1])
.attr("r", 5)
.attr("fill", d => colorScale(d.shootings))
.on("mouseover", function(event, d) {
tooltip.style("visibility", "visible")
.text(`${d.name}: ${d.shootings} shootings`);
})
.on("mousemove", function(event) {
tooltip.style("top", (event.pageY - 10) + "px")
.style("left",(event.pageX + 10) + "px");
})
.on("mouseout", function() {
tooltip.style("visibility", "hidden");
});
return svg.node();

}
Insert cell
Insert cell
sortedStates = {
const stateCounts = d3.rollup(shootings, v => v.length, d => d.state);
const sortedStates = Array.from(stateCounts).sort((a, b) => d3.descending(a[1], b[1]));
return sortedStates;
}
Insert cell
minMaxRate = d3.extent(sortedStates, d => d[1]);
Insert cell
color = d3.scaleSequential()
.domain(minMaxRate)
.interpolator(d3.interpolateBlues);
Insert cell
legend({
color: color,
title: '# of shootings'
});
Insert cell
{
const stateGrid = [[0,0,"AK"],[10,0,"ME"],[5,1,"WI"],[9,1,"VT"],[10,1,"NH"],[0,2,"WA"],[1,2,"ID"],[2,2,"MT"],[3,2,"ND"],[4,2,"MN"],[5,2,"IL"],[6,2,"MI"],[7,2,"NY"],[9,2,"MA"],[0,3,"OR"],[1,3,"NV"],[2,3,"WY"],[3,3,"SD"],[4,3,"IA"],[5,3,"IN"],[6,3,"OH"],[7,3,"PA"],[8,3,"NJ"],[9,3,"CT"],[10,3,"RI"],[0,4,"CA"],[1,4,"UT"],[2,4,"CO"],[3,4,"NE"],[4,4,"MO"],[5,4,"KY"],[6,4,"WV"],[7,4,"VA"],[8,4,"MD"],[9,4,"DE"],[1,5,"AZ"],[2,5,"NM"],[3,5,"KS"],[4,5,"AR"],[5,5,"TN"],[6,5,"NC"],[7,5,"SC"],[8,5,"DC"],[3,6,"OK"],[4,6,"LA"],[5,6,"MS"],[6,6,"AL"],[7,6,"GA"],[0,7,"HI"],[3,7,"TX"],[8,7,"FL"]];
const cityStateRates = sortedStates.map(stateInfo => {
const [state, rate] = stateInfo;
const gridInfo = stateGrid.find(item => item[2] === state);
if (gridInfo) {
const [col, row] = gridInfo;
return { state, rate, row, col };
} else {
return null;
}
}).filter(item => item !== null);
const numberOfRows = d3.max(cityStateRates, d => d.row) + 1;
const numberOfCols = d3.max(cityStateRates, d => d.col) + 1;
const cellSize = 50;
const mapWidth = numberOfCols * cellSize;
const mapHeight = numberOfRows * cellSize;
const row = d3.scaleBand()
.domain(d3.range(numberOfRows))
.range([0, mapHeight])
.padding(0.05);
const col = d3.scaleBand()
.domain(d3.range(numberOfCols))
.range([0, mapWidth])
.padding(0.05);
const svg = d3.create('svg')
.attr('width', mapWidth)
.attr('height', mapHeight);
const cells = svg.selectAll('g')
.data(cityStateRates)
.join('g')
.attr('transform', d => `translate(${col(d.col)}, ${row(d.row)})`);
cells.append('rect')
.attr('width', col.bandwidth())
.attr('height', row.bandwidth())
.attr('fill', d => color(d.rate));
cells.append('text')
.attr('font-size', 12)
.attr('font-family', 'sans-serif')
.attr('fill', d => d3.hcl(color(d.rate)).l > 50 ? 'black' : 'white')
.attr('dominant-baseline', 'middle')
.attr('text-anchor', 'middle')
.attr('x', col.bandwidth() / 2)
.attr('y', row.bandwidth() / 2)
.text(d => d.state);
return svg.node();

}
Insert cell
The visualization presents a grid map of the United States, quantifying police shootings across different states. Each state is represented by a cell in the grid, with the color intensity of each cell corresponding to the number of shootings in that state. States with higher numbers of shootings are darker, allowing for immediate visual comparison across the grid.

California (CA) stands out with the highest number of reported shootings, followed by Texas (TX) and Florida (FL), as indicated by the darker cells. This could suggest a correlation between state population sizes and the number of police shooting incidents. States such as Arizona (AZ), Colorado (CO), and Georgia (GA) also show significant numbers, highlighting regions where police shootings are more prevalent.
Insert cell
Insert cell
import {Legend,legend, swatches} from "@d3/color-legend"
Insert cell
data_days = d3.rollups(
data,
group => group.length,
d => new Date(d.date)
)
Insert cell
daily_changes = {let result = [];

for (let i = 0; i < data_days.length; i++) {
if (i === 0) {
// For the first day, the change is 0
result.push([data_days[i][0], 0]);
} else {
// For subsequent days, calculate the change from the previous day
let change = data_days[i][1] - data_days[i - 1][1];
result.push([data_days[i][0], change]);
}
}

return result
}
Insert cell
data_month = d3.rollups(
data,
group => group.length,
d => d.date.substring(0,7)
)
Insert cell
data_year = d3.rollups(
data,
group => group.length,
d => d.date.substring(0,4)
)
Insert cell
years = d3.map(data_year, d=>d[0])
Insert cell
year_gender = {
data.forEach(d => d.year = d.date.split('-')[0]);

const countByYearRace = {};
data.forEach(d => {
const key = d.year + "_" + d.gender;
if (!countByYearRace[key]) {
countByYearRace[key] = { year: d.year, gender: d.gender, count: 0 };
}
countByYearRace[key].count += 1;
});


let result = Object.values(countByYearRace);

result = result.sort((a, b) => {
if (a.year === b.year) {
return a.gender.localeCompare(b.gender);
}
return a.year.localeCompare(b.year);
});

return result
}
Insert cell
year_race = {
data.forEach(d => d.year = d.date.split('-')[0]);

const countByYearRace = {};
data.forEach(d => {
const key = d.year + "_" + d.race;
if (!countByYearRace[key]) {
countByYearRace[key] = { year: d.year, race: d.race, count: 0 };
}
countByYearRace[key].count += 1;
});


let result = Object.values(countByYearRace);

result = result.sort((a, b) => {
if (a.year === b.year) {
return a.race.localeCompare(b.race);
}
return a.year.localeCompare(b.year);
});

return result
}
Insert cell
year_state = d3.rollup(data, v => v.length, d => d.state);
Insert cell
year_age = {
// Define age groups order
const ageGroupOrder = ['Children', 'Teenagers', 'Young Adults', 'Adults', 'Middle-aged Adults', 'Seniors', 'Elderly'];

// Helper function to categorize age
function categorizeAge(age) {
if (age <= 12) return 'Children';
if (age <= 19) return 'Teenagers';
if (age <= 29) return 'Young Adults';
if (age <= 39) return 'Adults';
if (age <= 54) return 'Middle-aged Adults';
if (age <= 64) return 'Seniors';
return 'Elderly';
}

// Extract year and categorize age
data.forEach(d => {
d.year = d.date.split('-')[0];
d.ageGroup = categorizeAge(d.age);
});

// Group and count
const countByYearAgeGroup = {};
data.forEach(d => {
const key = d.year + "_" + d.ageGroup;
if (!countByYearAgeGroup[key]) {
countByYearAgeGroup[key] = { year: d.year, ageGroup: d.ageGroup, count: 0 };
}
countByYearAgeGroup[key].count += 1;
});

// Convert to array and sort
let result = Object.values(countByYearAgeGroup);
result = result.sort((a, b) => {
if (a.year === b.year) {
return ageGroupOrder.indexOf(a.ageGroup) - ageGroupOrder.indexOf(b.ageGroup);
}
return a.year.localeCompare(b.year);
});

return result;
}
Insert cell
year_age_missing = year_age.map(item => {
// Check if the year is 2020
if (item.year === "2020") {
return { ...item, count: item.count * 2 };
}
return item;
});
Insert cell
year_race_missing = year_race.map(item => {
// Check if the year is 2020
if (item.year === "2020") {
return { ...item, count: item.count * 2 };
}
return item;
});
Insert cell
series_age ={//
const ageGroups = Array.from(new Set(year_age.map(d => d.ageGroup)));


const dataByYear = Array.from(d3.group(year_age, d => d.year), ([year, values]) => {
const yearData = { year };
ageGroups.forEach(ageGroup => {
yearData[ageGroup] = values.filter(v => v.ageGroup === ageGroup).reduce((sum, v) => sum + v.count, 0);
});
return yearData;
});

const series_age = d3.stack()
.keys(ageGroups)
.value((d, key) => d[key])
(dataByYear);

series_age.forEach(layer => {
layer.forEach(segment => {
const year = segment.data.year;
const ageData = year_age.find(d => d.year === year && d.ageGroup === layer.key);
segment.data = [year, new Map([[layer.key, ageData]])];
});
});


return series_age
}
Insert cell
series_age_missing ={//
const ageGroups = Array.from(new Set(year_age_missing.map(d => d.ageGroup)));


const dataByYear = Array.from(d3.group(year_age_missing, d => d.year), ([year, values]) => {
const yearData = { year };
ageGroups.forEach(ageGroup => {
yearData[ageGroup] = values.filter(v => v.ageGroup === ageGroup).reduce((sum, v) => sum + v.count, 0);
});
return yearData;
});

const series_age = d3.stack()
.keys(ageGroups)
.value((d, key) => d[key])
(dataByYear);

series_age.forEach(layer => {
layer.forEach(segment => {
const year = segment.data.year;
const ageData = year_age_missing.find(d => d.year === year && d.ageGroup === layer.key);
segment.data = [year, new Map([[layer.key, ageData]])];
});
});


return series_age
}
Insert cell
series_race = d3.stack()
.keys(d3.union(year_race.map(d => d.race))) // distinct series keys, in input order
.value(([, D], key) => D.get(key).count) // get value for each series key and stack
(d3.index(year_race, d => d.year, d => d.race)); // group by stack then series key
Insert cell
series_race_missing = d3.stack()
.keys(d3.union(year_race_missing.map(d => d.race))) // distinct series keys, in input order
.value(([, D], key) => D.get(key).count) // get value for each series key and stack
(d3.index(year_race_missing, d => d.year, d => d.race)); // group by stack then series key
Insert cell
columns = ["yes", "no"]
Insert cell
viewof missing = Inputs.radio(columns, {value: 'view_full', label: 'Deal with Missing Data'})
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
typeof(years[0])
Insert cell
months =
{let months = [];
for (let i = 0; i < 12; i++) {
let date = new Date(2020, i, 1);
let month = date.toLocaleString('en-US', { month: 'long' });
months.push(month);
}
return months}
Insert cell
monthData = {let aggregatedData = [];


data.forEach(item => {
let date = new Date(item.date);
let year = date.getFullYear().toString();
let month = date.toLocaleString('en-US', { month: 'long' });

let yearData = aggregatedData.find(y => y.index === year);
if (!yearData) {
yearData = { index: year };
months.forEach(m => yearData[m] = 0);
aggregatedData.push(yearData);
}

yearData[month]++;
});


return aggregatedData
}
Insert cell
month_Changes = {let monthDataChanges = [];

for (let i = 0; i < monthData.length; i++) {
let yearData = { index: monthData[i].index };
for (let j = 0; j < months.length; j++) {
const month = months[j];
const previousMonth = j === 0 ? "December" : months[j - 1];
const previousYear = j === 0 ? i - 1 : i;

if (i === 0 && j === 0) {
yearData[month] = 0;
} else {
const previousValue = previousYear >= 0 ? monthData[previousYear][previousMonth] : 0;
const change = monthData[i][month] - previousValue;
yearData[month] = change;
}
}
monthDataChanges.push(yearData);
}

return monthDataChanges;
}
Insert cell
monthData[5].June = 38
Insert cell
max_value = 106
Insert cell
fun1 = ()=> {
const width = 700;
const height = width+130;
const margin = ({top: width/10, right: width/10, bottom: width/5, left: width/5});

const squareSize = 50;

const xRange = [margin.left, margin.left + squareSize * years.length+16];
const yRange = [height - margin.bottom+20, height - margin.bottom - squareSize * months.length];


const x = d3.scaleBand()
.domain(years)
.range(xRange)
.padding(0.03);

const y = d3.scaleBand()
.domain(months)
.range(yRange)
.padding(0.03);




const color = d3.scaleSequential()
.domain([0,max_value])
.interpolator(d3.interpolateYlOrBr)

const xAxis = g => g
.attr("transform", `translate(0, ${height - margin.bottom+25})`)
.call(d3.axisBottom(x).tickSizeOuter(0))
.call(g => g.selectAll(".domain").remove())
.selectAll("text")
.style("text-anchor", "start")
.style("font-size", "16px")
.attr("dx", "-.8em")
.attr("dy", ".8em")
const yAxis = g => g
.attr("transform", `translate(${margin.left}, 0)`)
.call(d3.axisLeft(y).ticks(null, "s"))
.call(g => g.selectAll(".domain").remove())
.selectAll("text")
.style("font-size", "14px")
.text(d => d)


const svg = d3.select(DOM.svg(width, height))
.style("width", "100%")
.style("height", "auto")
.style("font", "1rem verdana");
const make_class = (item) => item.toLowerCase().split(' ').join('_').split('-').join('')
const make_id = d => `coords_${Math.floor(x(d.xval))}_${Math.floor(y(d.yval))}`
const tooltip = d3.select("body").append("div")
.attr("class", "tooltip")
.style("opacity", 0)
.style("position", "absolute")
.style("text-align", "center")
.style("padding", "8px")
.style("font", "12px sans-serif")
.style("background", "lightsteelblue")
.style("border", "0px")
.style("border-radius", "8px")
.style("pointer-events", "none");


const rects = svg.append("g")
.selectAll("g")
.data(monthData)
.enter().append("g")
.attr("class", (d, i) => `${i} bar`)
.selectAll("g")
.data(d => {
const innerData = [];
for (const month in d) {
if (month !== "index") {
innerData.push({
xval: d.index,
yval: month,
count: d[month]
});
}
}
console.log(innerData)
return innerData;
})
.enter().append("g");




rects.append("rect")
.attr("x", d => x(d.xval))
.attr("y", d => y(d.yval))
.attr("width", squareSize)
.attr("height", squareSize)
.style("fill", d => d.count === 0 ? "lightgray" : color(d.count))
.on("mouseover", function(event, d) {
tooltip.transition()
.duration(200)
.style("opacity", 0.9);
tooltip.html(`${d.yval}<br/>${d.xval}<br/>Shootings: ${d.count}`)
.style("left", (event.pageX) + "px")
.style("top", (event.pageY - 28) + "px");

d3.select(this)
.style("stroke", "white")
.style("stroke-width", 4);
})
.on("mouseout", function(d) {
tooltip.transition()
.duration(500)
.style("opacity", 0);
d3.select(this).style("stroke", "none");
})
;



svg.append("g")
.call(xAxis);
svg.append("g")
.call(yAxis);

return svg.node();



}
Insert cell
max = 61
Insert cell
fun2 = ()=>{
const width = 700;
const height = width+ 130;
const margin = ({top: width/10, right: width/10, bottom: width/5, left: width/5-50});
const max_value = 61


const squareSize = 50;

const xRange = [margin.left, margin.left + squareSize * years.length+16];
const yRange = [height - margin.bottom+20, height - margin.bottom - squareSize * months.length];


const x = d3.scaleBand()
.domain(years)
.range(xRange)
.padding(0.03);

const y = d3.scaleBand()
.domain(months)
.range(yRange)
.padding(0.03);






const color = d3.scaleSequential()
.domain([max_value, -max_value])
.interpolator(d3.interpolatePiYG)


const xAxis = g => g
.attr("transform", `translate(0, ${height - margin.bottom+25})`)
.call(d3.axisBottom(x).tickSizeOuter(0))
.call(g => g.selectAll(".domain").remove())
.selectAll("text")
.style("text-anchor", "start")
.style("font-size", "16px")
.attr("dx", "-.8em")
.attr("dy", ".8em")
const yAxis = g => g
.attr("transform", `translate(${margin.left}, 0)`)
.call(d3.axisLeft(y).ticks(null, "s"))
.call(g => g.selectAll(".domain").remove())
.selectAll("text")
.style("font-size", "14px")
.text(d => d)


const svg = d3.select(DOM.svg(width, height))
.style("width", "100%")
.style("height", "auto")
.style("font", "1rem verdana");
const make_class = (item) => item.toLowerCase().split(' ').join('_').split('-').join('')
const make_id = d => `coords_${Math.floor(x(d.xval))}_${Math.floor(y(d.yval))}`
const tooltip = d3.select("body").append("div")
.attr("class", "tooltip")
.style("opacity", 0)
.style("position", "absolute")
.style("text-align", "center")
.style("padding", "8px")
.style("font", "12px sans-serif")
.style("background", "lightsteelblue")
.style("border", "0px")
.style("border-radius", "8px")
.style("pointer-events", "none");


const rects = svg.append("g")
.selectAll("g")
.data(month_Changes)
.enter().append("g")
.attr("class", (d, i) => `${i} bar`)
.selectAll("g")
.data(d => {
const innerData = [];
for (const month in d) {
if (month !== "index") {
innerData.push({
xval: d.index,
yval: month,
count: d[month]
});
}
}
console.log(innerData)
return innerData;
})
.enter().append("g");




rects.append("rect")
.attr("x", d => x(d.xval))
.attr("y", d => y(d.yval))
.attr("width", squareSize)
.attr("height", squareSize)
.style("fill", d => d.count === 0 ? "lightgray" : color(d.count))
.on("mouseover", function(event, d) {
tooltip.transition()
.duration(200)
.style("opacity", 0.9);
tooltip.html(`${d.yval}<br/>${d.xval}<br/>Changes: ${d.count}`)
.style("left", (event.pageX) + "px")
.style("top", (event.pageY - 28) + "px");

d3.select(this)
.style("stroke", "white")
.style("stroke-width", 4);
})
.on("mouseout", function(d) {
tooltip.transition()
.duration(500)
.style("opacity", 0);
d3.select(this).style("stroke", "none");
});
svg.append("g")
.call(xAxis);
svg.append("g")
.call(yAxis);

return svg.node();



}
Insert cell
legend1 = ()=>{
return Legend(d3.scaleSequential([0, max_value], d3.interpolateYlOrBr), {
title: "Shooting number",
})
}
Insert cell
legend2 = ()=>{
return Legend(d3.scaleSequential([max,-max], d3.interpolatePiYG), {
title: "Shooting number changes",
})
}
Insert cell
Insert cell
{
const ages = fun1();
const barchart_gender = fun2()
const legend_2 = legend2()
const legend_1 = legend1();
return html`

<div style="display: flex; width: 120%; max-width: 1500px; margin: auto; height: 100%; ">

<div style="width: 50%; padding: 0px; margin-left:3% ">
${legend_1}
</div>
<div style="width: 50%; padding: 0px;">
${legend_2}
</div>
</div>
<div style="display: flex; width: 120%; max-width: 1500px; margin: auto; height: 100%; ">

<div style="width: 50%; padding: 0px;">
${ages}
</div>
<div style="width: 50%; padding: 0px;">
${barchart_gender}
</div>
</div>
`;



}
Insert cell
// Copyright 2021 Observable, Inc.
// Released under the ISC license.
// https://observablehq.com/@d3/calendar-view
function Calendar(data, {
x = ([x]) => x,
y = ([, y]) => y,
title,
width = 928,
cellSize = 17,
weekday = "monday",
formatDay = i => "SMTWTFS"[i],
formatMonth = "%b",
yFormat,
colors = d3.interpolateYlOrBr
} = {}) {
const X = d3.map(data, x);
const Y = d3.map(data, y);
const I = d3.range(X.length);

const countDay = weekday === "sunday" ? i => i : i => (i + 6) % 7;
const timeWeek = weekday === "sunday" ? d3.utcSunday : d3.utcMonday;
const weekDays = weekday === "weekday" ? 5 : 7;
const height = cellSize * (weekDays + 2);

const max = d3.quantile(Y, 0.9975, Math.abs);
const color = d3.scaleSequential([0,max], colors).unknown("none");

formatMonth = d3.utcFormat(formatMonth);

if (title === undefined) {
const formatDate = d3.utcFormat("%B %-d, %Y");
const formatValue = color.tickFormat(100, yFormat);
title = i => `${formatDate(X[i])}\n${formatValue(Y[i])}`;
} else if (title !== null) {
const T = d3.map(data, title);
title = i => T[i];
}

const years = d3.groups(I, i => X[i].getUTCFullYear()).reverse();

function pathMonth(t) {
const d = Math.max(0, Math.min(weekDays, countDay(t.getUTCDay())));
const w = timeWeek.count(d3.utcYear(t), t);
return `${d === 0 ? `M${w * cellSize},0`
: d === weekDays ? `M${(w + 1) * cellSize},0`
: `M${(w + 1) * cellSize},0V${d * cellSize}H${w * cellSize}`}V${weekDays * cellSize}`;
}

const svg = d3.create("svg")
.attr("width", width)
.attr("height", height * years.length)
.attr("viewBox", [0, 0, width, height * years.length])
.attr("style", "max-width: 100%; height: auto; height: intrinsic;")
.attr("font-family", "sans-serif")
.attr("font-size", 10);

const year = svg.selectAll("g")
.data(years)
.join("g")
.attr("transform", (d, i) => `translate(40.5,${height * i + cellSize * 1.5})`);

year.append("text")
.attr("x", -5)
.attr("y", -5)
.attr("font-weight", "bold")
.attr("text-anchor", "end")
.text(([key]) => key);

year.append("g")
.attr("text-anchor", "end")
.selectAll("text")
.data(weekday === "weekday" ? d3.range(1, 6) : d3.range(7))
.join("text")
.attr("x", -5)
.attr("y", i => (countDay(i) + 0.5) * cellSize)
.attr("dy", "0.31em")
.text(formatDay);

const cell = year.append("g")
.selectAll("rect")
.data(weekday === "weekday"
? ([, I]) => I.filter(i => ![0, 6].includes(X[i].getUTCDay()))
: ([, I]) => I)
.join("rect")
.attr("width", cellSize - 1)
.attr("height", cellSize - 1)
.attr("x", i => timeWeek.count(d3.utcYear(X[i]), X[i]) * cellSize + 0.5)
.attr("y", i => countDay(X[i].getUTCDay()) * cellSize + 0.5)
.attr("fill", i => color(Y[i]));

if (title) cell.append("title")
.text(title);

const month = year.append("g")
.selectAll("g")
.data(([, I]) => d3.utcMonths(d3.utcMonth(X[I[0]]), X[I[I.length - 1]]))
.join("g");

month.filter((d, i) => i).append("path")
.attr("fill", "none")
.attr("stroke", "#fff")
.attr("stroke-width", 3)
.attr("d", pathMonth);

month.append("text")
.attr("x", d => timeWeek.count(d3.utcYear(d), timeWeek.ceil(d)) * cellSize + 2)
.attr("y", -5)
.text(formatMonth);

return Object.assign(svg.node(), {scales: {color}});
}

Insert cell
// Copyright 2021 Observable, Inc.
// Released under the ISC license.
// https://observablehq.com/@d3/calendar-view
function Calendar1(data, {
x = ([x]) => x,
y = ([, y]) => y,
title,
width = 928,
cellSize = 17,
weekday = "monday",
formatDay = i => "SMTWTFS"[i],
formatMonth = "%b",
yFormat,
colors = d3.interpolatePiYG
} = {}) {
const X = d3.map(data, x);
const Y = d3.map(data, y);
const I = d3.range(X.length);

const countDay = weekday === "sunday" ? i => i : i => (i + 6) % 7;
const timeWeek = weekday === "sunday" ? d3.utcSunday : d3.utcMonday;
const weekDays = weekday === "weekday" ? 5 : 7;
const height = cellSize * (weekDays + 2);

const max = d3.quantile(Y, 0.9975, Math.abs);
const color = d3.scaleSequential([max,-max], colors).unknown("none");

formatMonth = d3.utcFormat(formatMonth);

if (title === undefined) {
const formatDate = d3.utcFormat("%B %-d, %Y");
const formatValue = color.tickFormat(100, yFormat);
title = i => `${formatDate(X[i])}\n${formatValue(Y[i])}`;
} else if (title !== null) {
const T = d3.map(data, title);
title = i => T[i];
}

const years = d3.groups(I, i => X[i].getUTCFullYear()).reverse();

function pathMonth(t) {
const d = Math.max(0, Math.min(weekDays, countDay(t.getUTCDay())));
const w = timeWeek.count(d3.utcYear(t), t);
return `${d === 0 ? `M${w * cellSize},0`
: d === weekDays ? `M${(w + 1) * cellSize},0`
: `M${(w + 1) * cellSize},0V${d * cellSize}H${w * cellSize}`}V${weekDays * cellSize}`;
}

const svg = d3.create("svg")
.attr("width", width)
.attr("height", height * years.length)
.attr("viewBox", [0, 0, width, height * years.length])
.attr("style", "max-width: 100%; height: auto; height: intrinsic;")
.attr("font-family", "sans-serif")
.attr("font-size", 10);

const year = svg.selectAll("g")
.data(years)
.join("g")
.attr("transform", (d, i) => `translate(40.5,${height * i + cellSize * 1.5})`);

year.append("text")
.attr("x", -5)
.attr("y", -5)
.attr("font-weight", "bold")
.attr("text-anchor", "end")
.text(([key]) => key);

year.append("g")
.attr("text-anchor", "end")
.selectAll("text")
.data(weekday === "weekday" ? d3.range(1, 6) : d3.range(7))
.join("text")
.attr("x", -5)
.attr("y", i => (countDay(i) + 0.5) * cellSize)
.attr("dy", "0.31em")
.text(formatDay);

const cell = year.append("g")
.selectAll("rect")
.data(weekday === "weekday"
? ([, I]) => I.filter(i => ![0, 6].includes(X[i].getUTCDay()))
: ([, I]) => I)
.join("rect")
.attr("width", cellSize - 1)
.attr("height", cellSize - 1)
.attr("x", i => timeWeek.count(d3.utcYear(X[i]), X[i]) * cellSize + 0.5)
.attr("y", i => countDay(X[i].getUTCDay()) * cellSize + 0.5)
.attr("fill", i => color(Y[i]));

if (title) cell.append("title")
.text(title);

const month = year.append("g")
.selectAll("g")
.data(([, I]) => d3.utcMonths(d3.utcMonth(X[I[0]]), X[I[I.length - 1]]))
.join("g");

month.filter((d, i) => i).append("path")
.attr("fill", "none")
.attr("stroke", "#fff")
.attr("stroke-width", 3)
.attr("d", pathMonth);

month.append("text")
.attr("x", d => timeWeek.count(d3.utcYear(d), timeWeek.ceil(d)) * cellSize + 2)
.attr("y", -5)
.text(formatMonth);

return Object.assign(svg.node(), {scales: {color}});
}

Insert cell
Calendar(data_days, {
x: d => d[0],
y: d => d[1],
weekday:"sunday",
width
})
Insert cell
Calendar1(daily_changes, {
x: d => d[0],
y: d => d[1],
weekday:"sunday",
width
})
Insert cell
Insert cell
Insert cell
{
const cameraUsageCounts = {
'With Camera': shootings.filter(d => d.body_camera).length,
'Without Camera': shootings.filter(d => !d.body_camera).length
};
const pieData = d3.pie().value(d => d[1])(Object.entries(cameraUsageCounts));
const total = d3.sum(pieData, d => d.value);

const pieWidth = 30;
const pieHeight = 30;
const pieRadius = Math.min(pieWidth, pieHeight) / 2;
const arcGenerator = d3.arc().innerRadius(0).outerRadius(pieRadius - 10);
const color = d3.scaleOrdinal(d3.schemeCategory10);
const svg = d3.create("svg")
.attr("viewBox", [0, 0, pieWidth, pieHeight]);

const pieSvg = svg.append("g")
.attr("transform", `translate(${pieWidth / 2},${pieHeight / 2})`);
pieSvg.selectAll("path")
.data(pieData)
.enter()
.append("path")
.attr("d", arcGenerator)
.attr("fill", d => color(d.data[0]));
pieSvg.selectAll("text")
.data(pieData)
.enter()
.append("text")
.attr("transform", d => {
const centroid = arcGenerator.centroid(d);
return `translate(${centroid[0]}, ${centroid[1]})`;
})
.attr("dy", "0.35em")
.attr("text-anchor", "middle")
.style("font-size", "0.5px")
.text(d => {
const percentage = (d.value / total * 100).toFixed(1);
return `${d.data[0]}(${percentage}%)`;
});
return svg.node();
}
Insert cell
Insert cell
{
const outcomeCounts = d3.rollups(shootings, v => v.length, d => d.body_camera, d => d.manner_of_death);
const cameraStatus = (camera) => camera ? "With Camera" : "Without Camera";
const barWidth = 800;
const barHeight = 400;
const barMargin = { top: 20, right: 180, bottom: 40, left: 180 };
const x = d3.scaleLinear().domain([0, d3.max(outcomeCounts, d => d3.max(d[1], g => g[1]))]).range([barMargin.left, barWidth - barMargin.right]);
const y = d3.scaleBand()
.domain(outcomeCounts.flatMap(d => d[1].map(g => `${cameraStatus(d[0])}: ${g[0]}`)))
.range([barMargin.top, barHeight - barMargin.bottom])
.padding(0.1);
const barSvg = d3.create("svg")
.attr("viewBox", [0, 0, barWidth, barHeight]);
barSvg.append("g")
.selectAll("rect")
.data(outcomeCounts.flatMap(d => d[1].map(g => ({ key: `${cameraStatus(d[0])}: ${g[0]}`, value: g[1] }))))
.join("rect")
.attr("x", x(0))
.attr("y", d => y(d.key))
.attr("width", d => x(d.value) - x(0))
.attr("height", y.bandwidth())
.attr("fill", "steelblue");
barSvg.append("g")
.attr("fill", "#000")
.selectAll("text")
.data(outcomeCounts.flatMap(d => d[1].map(g => ({ key: `${cameraStatus(d[0])}: ${g[0]}`, value: g[1] }))))
.join("text")
.attr("x", d => x(d.value) + 3)
.attr("y", d => y(d.key) + y.bandwidth() / 2)
.attr("dy", "0.35em")
.text(d => d.value)
.attr("font-size", "15px");
barSvg.append("g")
.attr("transform", `translate(0,${barHeight - barMargin.bottom})`)
.call(d3.axisBottom(x).ticks(null, "s"));
barSvg.append("g")
.attr("transform", `translate(${barMargin.left},0)`)
.call(d3.axisLeft(y));
return barSvg.node();

}
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