Public
Edited
Oct 20, 2023
Insert cell
Insert cell
precinct_json = FileAttachment("Police Precincts.geojson").json()
Insert cell
uri = 'https://data.cityofnewyork.us/resource/8h9b-rp9u.json?$where=ARREST_DATE>"2022-01-01"&$limit=50000';
Insert cell
// there are too many records for observable, so we are limiting to just 2022
historicalData = fetch(uri).then(res => res.json())
Insert cell
// both the current dataset and the historical dataset use the legacy soda API, which limits queries to 50k. So in the case we can upload it, it's just easier than making a bunch of calls
currentData = ( await FileAttachment("NYPD_Arrest_Data__Year_to_Date__20231017.csv").csv()).map( arrest => {
// parse out data for easy filtering
const splitDate = arrest.ARREST_DATE.split("/");
arrest.ARREST_MONTH = splitDate[0]
arrest.ARREST_DAY = splitDate[1]
arrest.ARREST_YEAR = splitDate[2];

return arrest;
})
Insert cell
data = {
const allData = currentData.concat(historicalData);
return allData
}
Insert cell
listOfMonths = {
const formattedMonths = [];
const uniqueDates = new Set( data.map( d => d.ARREST_DATE ));

uniqueDates.forEach(dd => {
const date = new Date('2023', dd);
const monthName = date.toLocaleString('en-US', { month: 'long' });
formattedMonths.push(
{
name: `${monthName} 2023`,
value: dd
}
)
})
return formattedMonths;
}
Insert cell
crimeTypes = {
const crimeList = [];
for (const i in data) {
if ( crimeList.indexOf(data[i].KEY_CD) < 0) {
crimeList.push(
{
value: data[i].KY_CD,
desc: data[i].OFNS_DESC
}
)
}
}
return crimeList;
}
Insert cell
//filteredData = data.filter(d => d.ARREST_MONTH === selectedMonth.value )
filteredData = data.filter(d => d.PD_CD === '339' )
Insert cell
listOfOffenseLevels = {
const formattedLevels = [];
for( const i in data ) {
formattedLevels.push(
{
name: data[i].PD_DESC,
value: data[i].PD_CD
}
)
}
return formattedLevels;
}
Insert cell
aggregatedDataByPrecinct = d3.rollup(
filteredData,
(arrests) => arrests.length,
(d) => d.ARREST_PRECINCT
);
Insert cell
aggregatedDataByPD = d3.rollup(
aggregatedDataByPrecinct,
)
Insert cell
precinct_arrests = Array.from(aggregatedDataByPrecinct, ([precinct, count]) => ({
precinct,
count,
}));
Insert cell
maxArrests = d3.max( precinct_arrests, (d) => d.count )
Insert cell
color = d3.scaleSequential()
.domain([0, maxArrests])
.interpolator(d3.interpolateReds)
Insert cell
margin = ({ top: 10, right: 0, bottom: 0, left: 10 });
Insert cell
width = 640 - margin.left - margin.right;
Insert cell
height = 640 - margin.top - margin.bottom;
Insert cell
projection = d3.geoAlbers()
.fitSize( [width, height], precinct_json)
Insert cell
path = d3.geoPath().projection(projection)
Insert cell
viewof selectedMonth = Select(listOfMonths, { label: 'Month', format: d => d.name })
Insert cell
viewof selectedOffenseLevel = Select(listOfOffenseLevels, { label: 'Offense', format: d => d.name })
Insert cell
{
const svg = d3.create('svg')
.attr('width', width + margin.left + margin.right)
.attr('height', height + margin.top + margin.bottom);

const g = svg.append('g')
.attr('transform', `translate(${margin.left}, ${margin.top})`);

// add legend
g.append('g')
.attr('transform', `translate(0,${-margin.top})`)
.append(() => legend({color, title: 'title', tickFormat: '.2~s'}))

g.selectAll('path')
.data(precinct_json.features)
.join('path')
.attr('d', path)
.attr('fill', d => color(aggregatedDataByPrecinct.get(d.properties.precinct)))
.attr('stroke', 'white')



return svg.node();
}
Insert cell
aggregatedDataByPrecinct('107')
Insert cell
import {legend} from "@d3/color-legend"
Insert cell
import {Radio, Select} from '@observablehq/inputs'
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