Sources:
<!doctype html>
<meta charset="utf-8">
<style type="text/css" media="screen">
body {
margin: 0;
}
.district {
stroke: #fff;
stroke-width: 1;
fill-opacity: 0.6;
}
.district:hover {
fill-opacity: 0.8;
}
.county {
fill: none;
stroke: #333;
stroke-width: 0.5;
stroke-opacity: 0.6;
}
.label {
font-family: Helvetica;
font-size: 11px;
font-weight: bold;
fill-opacity: 1;
text-anchor: middle;
stroke: none;
pointer-events: none;
}
</style>
<body>
<script src="//d3js.org/d3.v3.min.js"></script>
<script src="//d3js.org/topojson.v1.min.js"></script>
<script type="text/javascript" charset="utf-8">
var width = 960,
height = 600
var path = d3.geo.path()
var color = d3.scale.category20()
var svg = d3.select("body").append('svg')
.attr('width', width)
.attr('height', height)
d3.json('us.json', function(error, us) {
if(error) return console.error("Boom goes the dynamite: " + error)
var dem = 0, rep = 0
us.objects.districts.geometries.forEach(function(d) {
d.properties.judges.forEach(function(j) {
if(j.ppa === 'Democratic') dem++
if(j.ppa === 'Republican') rep++
})
})
console.log(dem + " Democrat appointments", rep + " Republican appointments");
svg.append('path')
.datum(topojson.mesh(us, us.objects.counties))
.attr('class', 'county')
.attr('d', path)
districts = svg.selectAll('.district')
.data(topojson.feature(us, us.objects.districts).features)
.enter().append('path')
.attr('class', 'district')
.attr('d', path)
.style('fill', function(d, i) { return color(i) })
.on('click', function(district) {
var d = 0, r = 0
var names = district.properties.judges.map(function(j) {
if(j.ppa === 'Democratic') d++
if(j.ppa === 'Republican') r++
return j.ppa + ": " + j.name
}).join("\n")
console.log("D: " + d, "R: " + r, "\n\n" + names);
})
var labels = svg.append('g')
districts.each(function(d) {
var center = path.centroid(d)
labels.append('text')
.attr('class', 'label')
.attr('dx', function(d) { return center[0] })
.attr('dy', function(d) { return center[1] })
.text(d.properties.jdcode)
})
})
</script>
</body>
all: us.json
clean:
rm -rf -- us.json \
build/*-ungrouped.* \
build/*.json \
build/judicial-districts.shp \
build/judicial-districts.sbn \
build/judicial-districts.sbx \
build/judicial-districts.shx \
build/judicial-districts.prj \
build/judicial-districts.dbf
.PHONY: all clean
build/judicial-districts.shp:
rm -rf $(basename $@)
mkdir -p $(basename $@)
tar -xzm -C build -f ./build/judicial-districts.tar.gz
for file in $(basename $@)/*; do chmod 644 $$file; mv $$file $(basename $@).$${file##*.}; done
rmdir $(basename $@)
build/counties.json: build/judicial-districts.shp
node_modules/.bin/topojson \
-o $@ \
--no-pre-quantization \
--post-quantization=1e6 \
--simplify=7e-7 \
-p jdcode=+JDCODE,state=State,name=JD_NAME \
--id-property=+FIPS \
-- counties=build/judicial-districts.shp
build/districts.json: build/counties.json
rm $(basename $@)-unreconciled.json
node_modules/.bin/topojson-merge \
-o $(basename $@)-unreconciled.json \
--in-object=counties \
--out-object=districts \
--key='d.properties.jdcode' \
-- $<
./reconcile-judges < $(basename $@)-unreconciled.json > $@
us.json: build/districts.json
node_modules/.bin/topojson-merge \
-o $@ \
--in-object=districts \
--out-object=nation \
-- $<
{
"name": "anonymous",
"version": "0.0.1",
"private": true,
"dependencies": {
"fast-csv": "0.2.4",
"topojson": ">=1.6.2 <2"
}
}
#!/usr/bin/env node
// Because no one bothered to standardize on court names or give them an ID and
// assign judges to one, we're left with the cesspool you see below
//
var fs = require('fs'),
csv = require('fast-csv')
var topo = JSON.parse(fs.readFileSync('build/districts-unreconciled.json'))
var judges = [],
states = [],
names = [],
locations = ['Northern', 'Southern', 'Middle', 'Western', 'Eastern']
// Only keep active US District Court judges
function terminated(record) {
var termination = record['Date of Termination'],
death = record['Death year'],
courtType = record['Court Type'],
retired = record['Retirement from Active Service']
if(termination === '' && courtType === 'USDC' && retired === '') {
judges.push(record)
}
}
function reconcile() {
topo.objects.districts.geometries.forEach(function(geo) {
geo.properties.judges = []
if(geo.properties.name === null) return;
var parts = geo.properties.name.split(' '),
geostate = null,
geolocation = null
if(parts.length > 1) {
geolocation = parts.pop()
geostate = parts.join(' ')
} else {
geostate = parts[0]
}
judges.forEach(function(judge) {
var cname = judge['Court Name'],
info = {
name: judge['Judge First Name'] + " " + judge['Judge Last Name'],
ppa: judge['Party Affiliation of President']
// race: judge['Race or Ethnicity'],
// gender: judge['Gender'],
// date: judge['Commission Date']
}
if(cname.indexOf(geostate) !== -1 && (geolocation && cname.indexOf(geolocation) !== -1)) {
geo.properties.judges.push(info)
} else if(!geolocation && cname.indexOf(geostate) != -1) {
geo.properties.judges.push(info)
}
})
})
console.log(JSON.stringify(topo));
}
csv.fromPath('build/states.csv', { headers: true })
.on('record', function(state) { states.push(state) })
.on('end', function() {
csv.fromPath('build/judges.csv', { headers: true })
.on('record', terminated)
.on('end', reconcile)
})