Skip to content

Instantly share code, notes, and snippets.

@cmgiven
Last active December 22, 2018 14:24
Show Gist options
  • Save cmgiven/abca90f6ba5f0a14c54d1eb952f8949c to your computer and use it in GitHub Desktop.
Save cmgiven/abca90f6ba5f0a14c54d1eb952f8949c to your computer and use it in GitHub Desktop.
Brushable Scatterplot/Choropleth
license: mit
height: 350

Block-a-Day #2. A brush interaction on a scatterplot filters a choropleth showing the Y axis variable.

Data Sources: State of Obesity and 2014 ACS.

What I Learned: This is kinda a quickie as I ran into an issue with the planned block, but I had never used D3-brush before.

What I'd Do With More Time: I'd probably add a mouseover to the map so the association between the charts is two-way.

Block-a-Day

Just what it sounds like. For fifteen days, I will make a D3.js v4 block every single day. Rules:

  1. Ideas over implementation. Do something novel, don't sweat the details.
  2. No more than two hours can be spent on coding (give or take).
  3. Every. Single. Day.

Previously

state obesity income
Alabama 0.335 42830
Alaska 0.297 71583
Arizona 0.289 50068
Arkansas 0.359 41262
California 0.247 61933
Colorado 0.213 61303
Connecticut 0.263 70048
Delaware 0.307 59716
District of Columbia 0.217 71648
Florida 0.262 47463
Georgia 0.305 49321
Hawaii 0.221 69592
Idaho 0.289 47861
Illinois 0.293 57444
Indiana 0.327 49446
Iowa 0.309 53712
Kansas 0.313 52504
Kentucky 0.316 42958
Louisiana 0.349 44555
Maine 0.282 49462
Maryland 0.296 73971
Massachusetts 0.233 69160
Michigan 0.307 49847
Minnesota 0.276 61481
Mississippi 0.355 39680
Missouri 0.302 48363
Montana 0.264 46328
Nebraska 0.302 52686
Nevada 0.277 51450
New Hampshire 0.274 66532
New Jersey 0.269 71919
New Mexico 0.284 44803
New York 0.27 58878
North Carolina 0.297 46556
North Dakota 0.322 59029
Ohio 0.326 49308
Oklahoma 0.33 47529
Oregon 0.279 51075
Pennsylvania 0.302 53234
Rhode Island 0.27 54891
South Carolina 0.321 45238
South Dakota 0.298 50979
Tennessee 0.312 44361
Texas 0.319 53035
Utah 0.257 60922
Vermont 0.248 54166
Virginia 0.285 64902
Washington 0.273 61366
West Virginia 0.357 41059
Wisconsin 0.312 52622
Wyoming 0.295 57055
<!DOCTYPE html>
<meta charset="utf-8">
<style>
div { float: left; }
</style>
<body>
<div id="scatterplot"></div>
<div id="choropleth"></div>
<script src="//d3js.org/d3.v4.min.js"></script>
<script>
d3.queue()
.defer(d3.csv, 'data.csv', function (d) {
return {
name: d.state,
obesity: +d.obesity,
income: +d.income
}
})
.defer(d3.json, 'us-states.json')
.awaitAll(initialize)
var color = d3.scaleThreshold()
.domain([0.24, 0.28, 0.32])
.range(['#fbb4b9', '#f768a1', '#c51b8a', '#7a0177'])
function initialize(error, results) {
if (error) { throw error }
var data = results[0]
var features = results[1].features
var components = [
choropleth(features),
scatterplot(onBrush)
]
function update() {
components.forEach(function (component) { component(data) })
}
function onBrush(x0, x1, y0, y1) {
var clear = x0 === x1 || y0 === y1
data.forEach(function (d) {
d.filtered = clear ? false
: d.income < x0 || d.income > x1 || d.obesity < y0 || d.obesity > y1
})
update()
}
update()
}
function scatterplot(onBrush) {
var margin = { top: 10, right: 15, bottom: 40, left: 75 }
var width = 480 - margin.left - margin.right
var height = 350 - margin.top - margin.bottom
var x = d3.scaleLinear()
.range([0, width])
var y = d3.scaleLinear()
.range([height, 0])
var xAxis = d3.axisBottom()
.scale(x)
.tickFormat(d3.format('$.2s'))
var yAxis = d3.axisLeft()
.scale(y)
.tickFormat(d3.format('.0%'))
var brush = d3.brush()
.extent([[0, 0], [width, height]])
.on('start brush', function () {
var selection = d3.event.selection
var x0 = x.invert(selection[0][0])
var x1 = x.invert(selection[1][0])
var y0 = y.invert(selection[1][1])
var y1 = y.invert(selection[0][1])
onBrush(x0, x1, y0, y1)
})
var svg = d3.select('#scatterplot')
.append('svg')
.attr('width', width + margin.left + margin.right)
.attr('height', height + margin.top + margin.bottom)
.append('g')
.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')')
var bg = svg.append('g')
var gx = svg.append('g')
.attr('class', 'x axis')
.attr('transform', 'translate(0,' + height + ')')
var gy = svg.append('g')
.attr('class', 'y axis')
gx.append('text')
.attr('x', width)
.attr('y', 35)
.style('text-anchor', 'end')
.style('fill', '#000')
.style('font-weight', 'bold')
.text('Median household income')
gy.append('text')
.attr('transform', 'rotate(-90)')
.attr('x', 0)
.attr('y', -40)
.style('text-anchor', 'end')
.style('fill', '#000')
.style('font-weight', 'bold')
.text('Obesity rate')
svg.append('g')
.attr('class', 'brush')
.call(brush)
return function update(data) {
x.domain(d3.extent(data, function (d) { return d.income })).nice()
y.domain(d3.extent(data, function (d) { return d.obesity })).nice()
gx.call(xAxis)
gy.call(yAxis)
var bgRect = bg.selectAll('rect')
.data(d3.pairs(d3.merge([[y.domain()[0]], color.domain(), [y.domain()[1]]])))
bgRect.exit().remove()
bgRect.enter().append('rect')
.attr('x', 0)
.attr('width', width)
.merge(bgRect)
.attr('y', function (d) { return y(d[1]) })
.attr('height', function (d) { return y(d[0]) - y(d[1]) })
.style('fill', function (d) { return color(d[0]) })
var circle = svg.selectAll('circle')
.data(data, function (d) { return d.name })
circle.exit().remove()
circle.enter().append('circle')
.attr('r', 4)
.style('stroke', '#fff')
.merge(circle)
.attr('cx', function (d) { return x(d.income) })
.attr('cy', function (d) { return y(d.obesity) })
.style('fill', function (d) { return color(d.obesity) })
.style('opacity', function (d) { return d.filtered ? 0.5 : 1 })
.style('stroke-width', function (d) { return d.filtered ? 1 : 2 })
}
}
function choropleth(features) {
var width = 480
var height = 350
var projection = d3.geoAlbersUsa()
.scale([width * 1.25])
.translate([width / 2, height / 2])
var path = d3.geoPath().projection(projection)
var svg = d3.select('#choropleth')
.append('svg')
.attr('width', width)
.attr('height', height)
svg.selectAll('path')
.data(features)
.enter()
.append('path')
.attr('d', path)
.style('stroke', '#fff')
.style('stroke-width', 1)
return function update(data) {
svg.selectAll('path')
.data(data, function (d) { return d.name || d.properties.name })
.style('fill', function (d) { return d.filtered ? '#ddd' : color(d.obesity) })
}
}
</script>
</body>
Display the source blob
Display the rendered blob
Raw
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment