Published
Edited
Oct 21, 2020
2 stars
Insert cell
Insert cell
width = 1080
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
colors = new Object({
red: '#FF51AE',
blue: '#5675D9',
gray: '#AEA2B1',
black: '#47535C',
white: '#fff',
highlight: '#FFED6B',
background: '#fff',
})
Insert cell
Insert cell
totalRemoved = _.sumBy(removedMarginDiff, 'removed')
Insert cell
_.sumBy(masterData, d => d.A9e[2016] || 0)
Insert cell
{
const data = _.filter(diffByState, d => d.difference > 0)
// calculate scales
const numSlides = 2
const margin = {left: width * 0.5, top: 80, right: 80, bottom: 80}
// each state has two bars
const xScale = d3.scaleBand().domain(_.map(data, 'state'))
.range([margin.left, numSlides * width - margin.right])
.padding(0.2)
const maxRemoved = d3.max(data, d => d.removed)
const yScale = d3.scaleLinear().domain([0, maxRemoved])
.range([height - margin.bottom, margin.top])
const heightScale = d3.scaleLinear().domain([0, maxRemoved])
.range([0, height - margin.top - margin.bottom])
// render
const bandwidth = xScale.bandwidth()
let render = _.map(data, d => {
return {
x: xScale(d.state), y: height - margin.bottom,
width: bandwidth,
bars: [
{
x: bandwidth * 0.35, width: bandwidth * 0.65,
y: yScale(d.removed), height: heightScale(d.removed),
pattern: true, color: 'gray', text: d3.format(',')(d.removed), fill: true,
},
{
x: 0, width: bandwidth * 0.65,
y: yScale(d.margin), height: heightScale(d.margin),
color: d.party === 'D' ? 'blue' : 'red', fill: true, text: d3.format(',')(d.margin),
},
],
text: d.state,
}
})
const svg = renderBars(render)
return scrollSVG(svg.node())
}
Insert cell
{
const data = _.filter(diffByState, d => d.difference > 0)
// calculate scales
const numSlides = 1
const margin = {left: 60, top: 80, right: 60, bottom: 80}
// each state has two bars
const xScale = d3.scaleBand().domain(_.map(data, 'state'))
.range([margin.left, numSlides * width - margin.right])
.padding(0.2)
const maxRemoved = d3.max(data, d => d.removed)
const yScale = d3.scaleLinear().domain([0, maxRemoved])
.range([height - margin.bottom, margin.top])
const heightScale = d3.scaleLinear().domain([0, maxRemoved])
.range([0, height - margin.top - margin.bottom])
// render
const bandwidth = xScale.bandwidth()
let render = _.map(data, d => {
const bars = [{
x: 0, width: bandwidth,
y: yScale(d.removed), height: heightScale(d.removed),
pattern: true, fill: true, color: 'gray', text: d3.format(',')(d.removed),
}, {
x: 0, width: bandwidth,
y: yScale(d.margin), height: heightScale(d.margin),
color: d.party === 'D' ? 'blue' : 'red', fill: true,
}]
return {
x: xScale(d.state), y: height - margin.bottom,
width: bandwidth,
bars,
text: d.stateAbbr,
}
})
const svg = renderBars(render)
return scrollSVG(svg.node())
}
Insert cell
{
const data = _.filter(diffByState, d => d.difference > 0)
// calculate scales
const numSlides = 1
const margin = {left: 60, top: 80, right: 60, bottom: 80}
// each state has two bars
const xScale = d3.scaleBand().domain(_.map(data, 'state'))
.range([margin.left, numSlides * width - margin.right])
.padding(0.2)
const maxRemoved = d3.max(data, d => d.removed)
const yScale = d3.scaleLinear().domain([0, maxRemoved])
.range([height - margin.bottom, margin.top])
const heightScale = d3.scaleLinear().domain([0, maxRemoved])
.range([0, height - margin.top - margin.bottom])
// render
const bandwidth = xScale.bandwidth()
let render = _.map(data, d => {
const bars = [{
x: 0, width: bandwidth,
y: yScale(d.removed), height: heightScale(d.removed),
pattern: true, fill: true, color: 'gray', text: `${_.round(d.ratio, 1)}x`,
}]
_.times(Math.ceil(d.ratio), i => {
const height = heightScale(d.margin)
bars.push({
x: 0, width: bandwidth,
y: yScale(d.margin) - i * height, height,
color: d.party === 'D' ? 'blue' : 'red', fill: i === 0,
})
})
return {
x: xScale(d.state), y: height - margin.bottom,
width: bandwidth,
bars,
text: d.stateAbbr,
}
})
const svg = renderBars(render)
return scrollSVG(svg.node())
}
Insert cell
renderBars = (data) => {
const svg = d3.select(DOM.svg(width, height))
.style('background', colors.background)
const patterns = _.reduce(_.keys(colors), (obj, color) => {
obj[color] = textures
.lines()
.heavier()
// .thicker()
.stroke(colors[color])
svg.call(obj[color])
return obj
}, {})
// draw bars
const bars = svg.append('g')
.attr('id', 'bars')
.selectAll('g')
.data(data).join('g')
.attr('transform', d => `translate(${d.x}, 0)`)
bars.selectAll('rect')
.data(d => d.bars).join('rect')
.attr('x', d => d.x)
.attr('y', d => d.y)
.attr('width', d => d.width)
.attr('height', d => d.height)
.attr('fill', d => d.pattern ? patterns[d.color].url() : colors[d.color])
.attr('fill-opacity', d => d.fill ? 1 : 0.2)
.attr('stroke', '#fff')
.attr('stroke-width', 3)
bars.append('line')
.attr('x2', d => d.width)
.attr('y1', d => d.y)
.attr('y2', d => d.y)
.attr('stroke', colors.black)
// texts
const fontSize = 22
const texts = svg.append('g')
.attr('id', 'texts')
.selectAll('g').data(data).join('g')
.attr('transform', d => `translate(${d.x}, 0)`)
.attr('fill', colors.black)
.style('font-family', 'sans-serif')
.style('font-size', fontSize)
// state name
texts.append('text')
.attr('class', 'state')
.attr('x', d => d.width / 2)
.attr('y', d => d.y + fontSize)
// .attr('text-anchor', 'middle')
.attr('dy', '.35em')
.text(d => d.text)
texts.selectAll('.number')
.data(d => d.bars).join('text')
.attr('number', true)
.attr('x', d => d.x + d.width / 2)
.attr('y', d => d.y - fontSize / 2)
// .attr('text-anchor', 'middle')
.attr('dy', '.35em')
.text(d => d.text)
return svg
}
Insert cell
year = 2016
Insert cell
Insert cell
Insert cell
{
// const data = removedMarginDiff
const states = _.chain(removedMarginDiff)
.groupBy('state')
.map(counties => _.countBy(counties, d => d.difference > 0))
.value()
const data = _.chain(removedMarginDiff)
.groupBy('state')
.map((counties, state) => {
// try separating outlier & non-outlier
const median = d3.median(counties, d => d.removed)
const q1 = d3.quantile(counties, 0.25, d => d.removed)
const q3 = d3.quantile(counties, 0.75, d => d.removed)
const minOutlier = q1 - 1.5 * (q3 - q1)
const maxOutlier = q3 + 1.5 * (q3 - q1)
return _.map(counties, d => Object.assign(d, {
outlier: d.removed > maxOutlier,
type: `${state}: ${d.removed > maxOutlier}`
}))
}).filter(counties => _.some(counties, 'outlier'))
// .filter(counties => _.filter(counties, d => d.difference > 0).length > 1)
.flatten().value()
console.log(data)
return vl.data(data)
// .width(width)
.height(300)
.encode(vl.x().fieldN('type'))
.layer(
vl.markPoint().encode(vl.y().fieldQ('blackPop')),
vl.markPoint({color: 'red'}).encode(vl.y().mean('blackPop'))
).render()
}
Insert cell
Insert cell
Insert cell
import {checkbox} from "@jashkenas/inputs"
Insert cell
import {scrollSVG} from '@sxywu/utility-functions'
Insert cell
d3 = require('d3')
Insert cell
_ = require('lodash')
Insert cell
textures = require('textures')
Insert cell
import {vl} from '@vega/vega-lite-api'
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