Published
Edited
Oct 27, 2020
5 stars
Insert cell
Insert cell
confidenceOrder
Insert cell
Insert cell
currentData = new Object({senateDem: 12, senateRep: 19, houseDem: 67, houseRep: 83, senateOpen: 16, senateClosed: 15})
Insert cell
senateData = _.filter(data, ({legislature}) => legislature === 'SD')
Insert cell
houseData = _.filter(data, ({legislature}) => legislature === 'HD')
Insert cell
colors = new Object({
red: '#FF51AE',
blue: '#5675D9',
gray: '#AEA2B1',
black: '#2D1832',
white: '#fff',
highlight: '#FFED6B',
background: '#fff',
})
Insert cell
width = 1080
Insert cell
height = 1350
Insert cell
margin = new Object({left: 120, top: 60, right: 120, bottom: 120})
Insert cell
Insert cell
Insert cell
Insert cell
{
return renderDots(calcPoints(data))
}
Insert cell
calcPoints = (data) => {
// scales
const perRow = 3
const numRows = Math.ceil(currentData.houseRep / perRow)
const perWidth = 100
const xScale = d3.scaleBand().domain([0, 1, 2]).range([0, perWidth])
const yScale = d3.scalePoint().domain(_.range(numRows))
.range([height - margin.bottom, height - margin.bottom - xScale.bandwidth() * numRows])
return _.chain(data)
.groupBy(d => `${d.legislature}, ${d.favored}`)
.sortBy(([{legislature, favored}]) => 2 * (legislature === 'HD') + (favored === 'R'))
.map((seats, offset) => {
const points = _.chain(seats)
.sortBy(({confidence}) => confidenceOrder.indexOf(confidence))
.map(({confidence, favored}, i) => {
confidence = confidenceMap[confidence]
const color = favored === 'D' ? 'blue' : 'red'
let fill, stroke, pattern
if (confidence === 'Closed') {
fill = colors[color]
pattern = false
} else if (confidence === 'Safe') {
fill = chroma(colors[color]).alpha(0.25).css(),
stroke = colors.gray
pattern = false
} else if (confidence === 'Likely') {
fill = color
stroke = colors.gray
pattern = 'thicker'
} else if (confidence === 'Lean') {
fill = color
stroke = colors.gray
pattern = 'thinner'
} else if (confidence === 'Toss-Up') {
fill = 'gray'
stroke = colors.gray
pattern = 'thinner'
}

return {
fill, stroke, pattern,
// x: xScale(i % perRow) + xScale.bandwidth() / 2,
x: xScale(i % perRow),
y: yScale(Math.floor(i / perRow)),
radius: Math.floor(xScale.bandwidth() * 0.7),
}
}).value()
return {
x: margin.left + offset * perWidth,
points,
}
}).value()
}
Insert cell
renderDots = (render) => {
// render
const svg = d3.select(DOM.svg(width, height))
// patterns
const thicker = _.reduce(_.keys(colors), (obj, color) => {
obj[color] = textures
.lines()
.heavier()
.thicker()
.stroke(colors[color])
svg.call(obj[color])
return obj
}, {})
const thinner = _.reduce(_.keys(colors), (obj, color) => {
obj[color] = textures
.lines()
.lighter()
.thicker(1.1)
.stroke(colors[color])
svg.call(obj[color])
return obj
}, {})
const patterns = {thicker, thinner}
console.log(patterns.thicker.red.url())
// draw dots
const group = svg.selectAll('g')
.data(render).join('g')
.attr('transform', d => `translate(${d.x}, 0)`)
group.selectAll('rect')
.data(d => d.points).join('rect')
.attr('transform', d => `translate(${d.x}, ${d.y})`)
.attr('width', d => d.radius)
.attr('height', d => d.radius)
.attr('fill', d => d.pattern ? patterns[d.pattern][d.fill].url() : d.fill)
.attr('stroke', d => d.stroke)
.attr('stroke-width', 3)
.attr('rx', 5).attr('ry', 5)
// partisan
return svg.node()
}
Insert cell
Insert cell
import {texas2020 as data, confidenceOrder} from '@sxywu/gerrymandering-data'
Insert cell
d3 = require('d3')
Insert cell
_ = require('lodash')
Insert cell
chroma = require('chroma-js')
Insert cell
textures = require('textures')
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