{"id":"4fe4c9e01737d433","slug":"try-to-impeach-this-challenge-accepted","trashed":false,"description":"","likes":139,"publish_level":"live","forks":17,"fork_of":{"id":"794f4dcf7e6dc8d2","slug":"how-well-does-population-density-predict-u-s-voting-outcome","title":"How well does population density predict U.S. voting outcomes?","owner":{"id":"cbb2b32473d3e648","avatar_url":"https://avatars.observableusercontent.com/avatar/fcff7503af57e71a7006d2543ad38643918ba16eb09e01f116b42d94a497c0a0","login":"jake-low","name":"Jake Low","bio":"","home_url":"","type":"team","tier":"starter_2024"},"version":1174},"has_importers":true,"update_time":"2025-11-27T14:44:20.372Z","first_public_version":1379,"paused_version":null,"publish_time":"2024-04-12T12:44:37.179Z","publish_version":1381,"latest_version":1381,"thumbnail":"59654ca0854baf639ca87823dd0a6325612d2698fc7cf08b9c53860ba16e16b1","default_thumbnail":"59654ca0854baf639ca87823dd0a6325612d2698fc7cf08b9c53860ba16e16b1","roles":[],"sharing":null,"owner":{"id":"d7884d59069d1d32","avatar_url":"https://avatars.observableusercontent.com/avatar/dbe7b109627d1141cc6fd36f9a1db388ebed9bfd565c7b606155838d99ff336c","login":"karimdouieb","name":"KDO","bio":"Co-founder of Jetpack.ai","home_url":"http://twitter.com/karim_douieb","type":"team","tier":"starter_2024"},"creator":{"id":"9002a3c6466edd6e","avatar_url":"https://avatars.observableusercontent.com/avatar/dbe7b109627d1141cc6fd36f9a1db388ebed9bfd565c7b606155838d99ff336c","login":"karimdouieb","name":"KDO","bio":"Co-founder of Jetpack.ai","home_url":"http://twitter.com/karim_douieb","tier":"pro"},"authors":[{"id":"9002a3c6466edd6e","avatar_url":"https://avatars.observableusercontent.com/avatar/dbe7b109627d1141cc6fd36f9a1db388ebed9bfd565c7b606155838d99ff336c","name":"KDO","login":"karimdouieb","bio":"Co-founder of Jetpack.ai","home_url":"http://twitter.com/karim_douieb","tier":"pro","approved":true,"description":""}],"collections":[{"id":"6ad523ac4dcb4b45","type":"public","slug":"instagram-shares","title":"Observable's Instagram Shares","description":"A collection of Observable Canvases that we share on Instagram.   https://www.instagram.com/observablehq/","update_time":"2021-11-18T19:06:11.216Z","pinned":false,"ordered":false,"custom_thumbnail":null,"default_thumbnail":"74d2227190ace84e393c990c47430aac47c760dd024fd7a0f8ba81f8d3d6cdd5","thumbnail":"74d2227190ace84e393c990c47430aac47c760dd024fd7a0f8ba81f8d3d6cdd5","listing_count":125,"parent_collection_count":0,"owner":{"id":"f35c755083683fe5","avatar_url":"https://avatars.observableusercontent.com/avatar/5a51c3b908225a581d20577e488e2aba8cbc9541c52982c638638c370c3e5e8e","login":"observablehq","name":"Observable","bio":"The end-to-end solution for building and hosting better data apps, dashboards, and reports.","home_url":"https://observablehq.com","type":"team","tier":"enterprise_2024"}},{"id":"aa8e262f42c95c83","type":"public","slug":"product-page","title":"Product page","description":"Notebooks to display on the product page","update_time":"2021-10-15T15:03:26.270Z","pinned":false,"ordered":false,"custom_thumbnail":null,"default_thumbnail":"2000d58a53de52a26198face6a9361b9304027249ee374d6e0275cf57c412b06","thumbnail":"2000d58a53de52a26198face6a9361b9304027249ee374d6e0275cf57c412b06","listing_count":15,"parent_collection_count":0,"owner":{"id":"f35c755083683fe5","avatar_url":"https://avatars.observableusercontent.com/avatar/5a51c3b908225a581d20577e488e2aba8cbc9541c52982c638638c370c3e5e8e","login":"observablehq","name":"Observable","bio":"The end-to-end solution for building and hosting better data apps, dashboards, and reports.","home_url":"https://observablehq.com","type":"team","tier":"enterprise_2024"}},{"id":"b336128061579b00","type":"public","slug":"2020-viral-notebooks","title":"Top 10 Viral Notebooks of 2020","description":"A collection of notebooks that went viral in 2020 ","update_time":"2020-12-18T17:33:10.480Z","pinned":false,"ordered":false,"custom_thumbnail":"e347434917323ff5086d787c5e127d304dbbd4f958a7dca2f9333301bfc3ea28","default_thumbnail":"59654ca0854baf639ca87823dd0a6325612d2698fc7cf08b9c53860ba16e16b1","thumbnail":"e347434917323ff5086d787c5e127d304dbbd4f958a7dca2f9333301bfc3ea28","listing_count":9,"parent_collection_count":1,"owner":{"id":"f35c755083683fe5","avatar_url":"https://avatars.observableusercontent.com/avatar/5a51c3b908225a581d20577e488e2aba8cbc9541c52982c638638c370c3e5e8e","login":"observablehq","name":"Observable","bio":"The end-to-end solution for building and hosting better data apps, dashboards, and reports.","home_url":"https://observablehq.com","type":"team","tier":"enterprise_2024"}},{"id":"0f6027212426cec8","type":"public","slug":"elections","title":"Elections","description":"A selection of election-related data visualizations created by our community, the Observable Journalism Network, and our team. Explore maps, voter wait times, paths to victory, and more.","update_time":"2020-11-12T23:36:29.856Z","pinned":false,"ordered":false,"custom_thumbnail":"c7a6fe68e71a7b984fa84fe2cdca2e1a9273ca92441e2e6433bcb3a280bb451c","default_thumbnail":"ef2259175e0af66d02529bcd96c792e77ab0bb3f5b98b3db3fa72e114ee53d9f","thumbnail":"c7a6fe68e71a7b984fa84fe2cdca2e1a9273ca92441e2e6433bcb3a280bb451c","listing_count":60,"parent_collection_count":0,"owner":{"id":"f35c755083683fe5","avatar_url":"https://avatars.observableusercontent.com/avatar/5a51c3b908225a581d20577e488e2aba8cbc9541c52982c638638c370c3e5e8e","login":"observablehq","name":"Observable","bio":"The end-to-end solution for building and hosting better data apps, dashboards, and reports.","home_url":"https://observablehq.com","type":"team","tier":"enterprise_2024"}},{"id":"98a964a057c43289","type":"public","slug":"county-maps","title":"County Maps","description":"A collection of examples of US County based maps for reference and inpsiration","update_time":"2020-10-06T19:45:00.816Z","pinned":false,"ordered":false,"custom_thumbnail":null,"default_thumbnail":"deca7aa1610e893b52b1f96bf4e2377c50985fd74cb81732f4ba729697ed36c8","thumbnail":"deca7aa1610e893b52b1f96bf4e2377c50985fd74cb81732f4ba729697ed36c8","listing_count":26,"parent_collection_count":1,"owner":{"id":"f35c755083683fe5","avatar_url":"https://avatars.observableusercontent.com/avatar/5a51c3b908225a581d20577e488e2aba8cbc9541c52982c638638c370c3e5e8e","login":"observablehq","name":"Observable","bio":"The end-to-end solution for building and hosting better data apps, dashboards, and reports.","home_url":"https://observablehq.com","type":"team","tier":"enterprise_2024"}}],"files":[],"comments":[],"commenting_lock":null,"suggestion_from":null,"suggestions_to":[],"version":1381,"title":"Try to impeach this? Challenge accepted!","license":null,"copyright":"","nodes":[{"id":0,"value":"md`# Try to impeach this? Challenge accepted!`","pinned":false,"mode":"js","data":null,"name":null},{"id":1208,"value":"tweet(\"1178030815671980032\")","pinned":false,"mode":"js","data":null,"name":null},{"id":1239,"value":"vote_map = {\n  const height = width * 5/8;\n  \n  const svg = d3.select(DOM.svg(width, height))\n      .attr(\"viewBox\", \"0 0 960 600\")\n      .style(\"width\", \"100%\")\n      .style(\"height\", \"auto\");\n  \n  const color = d3.scaleSequential(d3.interpolateRdBu);\n  \n  // render map\n  \n  const path = d3.geoPath(projection);\n\n  svg.append(\"g\")\n    .selectAll(\"path\")\n    .data(counties)\n    .enter().append(\"path\")\n      //.attr(\"fill\", county => county.properties.votes.two_party_ratio > 0.5 ? \"#0e0eb9\" : \"#ea0004\")\n      .attr(\"fill\", county => county.properties.votes.percent.dem > county.properties.votes.percent.gop ? \"#0e0eb9\" : \"#ea0004\")  \n      .attr(\"d\", path)\n    .append(\"title\")\n      .text(d => [\n        d.properties.name,\n        `${format.percent(d.properties.votes.percent.dem)} Clinton`,\n        `${format.percent(d.properties.votes.percent.gop)} Trump`,\n        ].join(\" – \")\n      )\n\n  svg.append(\"path\")\n      //.datum(topojson.mesh(us, us.objects.states, (a, b) => a !== b))\n      .datum(topojson.mesh(us, us.objects.counties, (a, b) => a !== b))\n      .attr(\"fill\", \"none\")\n      .attr(\"stroke\", \"white\")\n      .attr(\"stroke-linejoin\", \"round\")\n      .attr(\"stroke-width\", 0.5)\n      .attr(\"d\", path);\n\n  return svg.node();\n}","pinned":false,"mode":"js","data":null,"name":null},{"id":11,"value":"vote_map_population_bubble = {\n  const height = width * 5/8;\n  \n  const svg = d3.select(DOM.svg(width, height))\n      .attr(\"viewBox\", \"0 0 960 600\")\n      .style(\"width\", \"100%\")\n      .style(\"height\", \"auto\");\n  \n  const color = d3.scaleSequential(d3.interpolateRdBu);\n  \n  // render map\n  \n  const path = d3.geoPath(projection);\n  \n  svg.append(\"g\")\n    .selectAll(\"circle\")\n    .data(counties)\n    .enter().append(\"circle\")\n      .attr(\"fill\", d => d.properties.votes.percent.dem > d.properties.votes.percent.gop ? \"#0e0eb9\" : \"#ea0004\")  \n      .attr(\"cx\", d => d.properties.centroid ? d.properties.centroid[0] : 0)\n      .attr(\"cy\", d => d.properties.centroid ? d.properties.centroid[1] : 0)\n      .attr(\"r\", d => d.properties.radius)\n      .style(\"stroke\", \"white\")\n\n  // render legend\n  \n  const x = d3.scaleLinear()\n      .domain(color.domain())\n      .rangeRound([0, 260]);\n\n  return svg.node();\n}","pinned":false,"mode":"js","data":null,"name":null},{"id":1285,"value":"spreadCounties = applySimulation(counties)","pinned":false,"mode":"js","data":null,"name":null},{"id":1262,"value":"vote_map_population_spread_bubble = {\n  const height = width * 5/8;\n  \n  const svg = d3.select(DOM.svg(width, height))\n      .attr(\"viewBox\", \"0 0 960 600\")\n      .style(\"width\", \"100%\")\n      .style(\"height\", \"auto\");\n  \n  const color = d3.scaleSequential(d3.interpolateRdBu);\n  \n  // render map\n  \n  const path = d3.geoPath(projection);\n  svg.append(\"g\")\n    .selectAll(\"circle\")\n    .data(spreadCounties)\n    .enter().append(\"circle\")\n      .attr(\"fill\", d => d.properties.votes.percent.dem > d.properties.votes.percent.gop ? \"#0e0eb9\" : \"#ea0004\")  \n      .attr(\"cx\", d => d.x)\n      .attr(\"cy\", d => d.y)\n      .attr(\"r\", d => d.properties.radius)\n      .style(\"stroke\", \"white\")\n\n  // render legend\n  \n  const x = d3.scaleLinear()\n      .domain(color.domain())\n      .rangeRound([0, 260]);\n\n  return svg.node();\n}","pinned":false,"mode":"js","data":null,"name":null},{"id":1295,"value":" vote_map_animation = {\n  const height = width * 5/8;\n  \n  const svg = d3.select(DOM.svg(width, height))\n      .attr(\"viewBox\", \"0 0 960 600\")\n      .style(\"width\", \"100%\")\n      .style(\"height\", \"auto\");\n  \n  const color = d3.scaleSequential(d3.interpolateRdBu);\n  \n  // render map\n  \n  const path = d3.geoPath(projection);\n\n   svg.append(\"g\")\n     .selectAll(\"path\")\n     .data(counties)\n     .enter().append(\"path\")\n     .attr(\"class\", \"countyShape\")\n     .attr(\"fill\", county => county.properties.votes.percent.dem > county.properties.votes.percent.gop ? \"#0e0eb9\" : \"#ea0004\")  \n     .attr(\"d\", path)\n     .attr(\"stroke\", \"white\")\n     .attr(\"stroke-width\", 0.5)\n     .append(\"title\")\n     .text(d => [\n        d.properties.name,\n        `${format.percent(d.properties.votes.percent.dem)} Clinton`,\n        `${format.percent(d.properties.votes.percent.gop)} Trump`,\n        ].join(\" – \")\n      ) \n   \n  setInterval(() => {\n    svg.selectAll(\".countyShape\")\n      .transition()\n      .delay(d => d.rank*2)\n      .duration(5000)\n      .attrTween('d', function(d, i) {\n        return flubber.toCircle(path(d), d.x, d.y, d.properties.radius, {maxSegmentLength: 2});\n      })\n\n    svg.selectAll(\".countyShape\")\n      .transition()\n      .delay(d => 10000 + d.rank*2)\n      .duration(5000)\n      .attrTween('d', function(d, i) {\n        return flubber.fromCircle(d.x, d.y, d.properties.radius, path(d), {maxSegmentLength: 2});\n      })\n  }, 25000)\n\n  return svg.node();\n}","pinned":false,"mode":"js","data":null,"name":null},{"id":1227,"value":"maxRadius = 25","pinned":false,"mode":"js","data":null,"name":null},{"id":1254,"value":"nodePadding = 0.1","pinned":false,"mode":"js","data":null,"name":null},{"id":356,"value":"counties = topojson.feature(us, us.objects.counties).features.map(county => {\n  const { count, percent, two_party_ratio } = votes.find(v => v.id === county.id)\n  const { population } = populations.find(p => p.id === county.id)\n  \n  const state = us.objects.states.geometries.find(state => state.id === county.properties.STATEFP);\n  \n  const name = `${county.properties.NAME} County, ${state.properties.name}`; \n  \n  return {\n    ...county,\n    properties: {\n      name,\n      state: state.properties.name,\n      votes: { count, percent, two_party_ratio },\n      population,\n      density: population / county.properties.ALAND * 1e6,\n      centroid: projection(turf.centroid(county.geometry).geometry.coordinates),\n      radius: radiusScale(population)\n    }\n  }\n})\n.filter(c => c.properties.centroid)\n//.filter(c => c.geometry.type === \"MultiPolygon\")\n.sort((a,b) => a.properties.centroid[0] < b.properties.centroid[0] ? -1 : 1)\n.map((d, i) => {\n  let geometry;\n  if (d.geometry.type !== \"MultiPolygon\") { \n    geometry = d.geometry\n  } else {\n    geometry = {\n      type: d.geometry.type,\n      coordinates: d.geometry.coordinates.sort((a,b) => \n        turf.area(turf.polygon(a)) > turf.area(turf.polygon(b)) ? -1 : 1\n      ).slice(0, 1)\n    }\n  }\n  return {\n    ...d, \n    rank: i, \n    geometry\n  }  \n })\n","pinned":false,"mode":"js","data":null,"name":null},{"id":1230,"value":"radiusScale = {\n  const populationMax = d3.max(populations, c => c.population)\n  return d3.scaleSqrt()\n    .domain([0, populationMax])\n    .range([1, maxRadius]) \n}","pinned":false,"mode":"js","data":null,"name":null},{"id":1252,"value":"applySimulation = (nodes) => {\n  const simulation = d3.forceSimulation(nodes)\n    .force(\"cx\", d3.forceX().x(d => width / 2).strength(0.02))\n    .force(\"cy\", d3.forceY().y(d => width * (5/8) / 2).strength(0.02))\n    .force(\"x\", d3.forceX().x(d => d.properties.centroid ? d.properties.centroid[0] : 0).strength(0.3))\n    .force(\"y\", d3.forceY().y(d => d.properties.centroid ? d.properties.centroid[1] : 0).strength(0.3))\n    .force(\"charge\", d3.forceManyBody().strength(-1))\n    .force(\"collide\", d3.forceCollide().radius(d => d.properties.radius + nodePadding).strength(1))\n    .stop()\n\n  let i = 0; \n  while (simulation.alpha() > 0.01 && i < 200) {\n    simulation.tick(); \n    i++;\n    console.log(`${Math.round(100*i/200)}%`)\n  }\n\n  return simulation.nodes();\n}","pinned":false,"mode":"js","data":null,"name":null},{"id":567,"value":"format = ({\n  density: (x) => x > 1000 ? d3.format(\".2s\")(x) : d3.format(\".3r\")(x),\n  percent: d3.format(\".1%\")\n})","pinned":false,"mode":"js","data":null,"name":null},{"id":190,"value":"projection = d3.geoAlbersUsa()\n  .fitSize([960, 600], topojson.feature(us, us.objects.counties))","pinned":false,"mode":"js","data":null,"name":null},{"id":9,"value":"votes = {\n  const url = \"https://raw.githubusercontent.com/tonmcg/County_Level_Election_Results_12-16/master/2016_US_County_Level_Presidential_Results.csv\";\n  \n  const csv = await d3.csv(url);\n  \n  const votes = csv\n    .map(row => ({\n     id: row.combined_fips.padStart(5, \"0\"),\n     count: { total: +row.votes_total, dem: +row.votes_dem, gop: +row.votes_gop },\n     percent: { dem: +row.per_dem, gop: +row.per_gop }, \n     two_party_ratio: (+row.votes_dem) / ((+row.votes_dem) + (+row.votes_gop))\n    }))\n    .map(row => {\n      switch (row.id) {\n        case \"02270\": // Wade Hampton Census Area was renamed to Kusilvak Census Area (Alaska)\n          return { ...row, id: \"02158\" };\n        case \"46113\": // Shannon County Census Area was renamed to Oglala Lakota County Census Area (South Dakota)\n          return { ...row, id: \"46102\" };\n        default:\n          return row;\n      }\n    })\n  \n  return votes;\n}\n    ","pinned":false,"mode":"js","data":null,"name":null},{"id":103,"value":"populations = {\n  const data = await d3.csv(\"https://gist.githubusercontent.com/jake-low/907af4cc717e4c289346c6b262d68a50/raw/4e9f4012d346ecff75aaeee751e7f1af3cd9c1d7/co-est2017-alldata.csv\");\n  \n  let population = data\n    .filter(row => row.COUNTY !== \"000\")\n    .map(row => ({\n      id: row.STATE + row.COUNTY,\n      population: +row.POPESTIMATE2016\n    }));\n  \n  // Kalawao County (FIPS 15005) was incorporated into Maui County (FIPS 15009)\n  const kalawao = population.find(county => county.id === \"15005\");\n  const maui = population.find(county => county.id === \"15009\");\n  \n  maui.population += kalawao.population; // add kalawao population to maui county\n  population = population.filter(county => county.id !== \"15005\"); // remove kalawao county\n  \n  return population;\n}","pinned":false,"mode":"js","data":null,"name":null},{"id":5,"value":"us = { \n  const url = \"https://gist.githubusercontent.com/jake-low/bd39a072eb4c0822d2c32473816e1c11/raw/5a3296a2049d6719d38b66d0b77c9359b81b8c4c/us-10m-unprojected.json\";\n  const us = await d3.json(url);\n  \n  // Kalawao County (FIPS 15005) was incorporated into Maui County (FIPS 15009)\n  const counties = us.objects.counties;\n  \n  const kalawao = counties.geometries.find(county => county.id === \"15005\");\n  const maui = counties.geometries.find(county => county.id === \"15009\");\n  \n  maui.arcs.concat(kalawao.arcs); // join the kalawao county geometries into maui county\n  counties.geometries = counties.geometries.filter(county => county.id !== \"15005\"); // remove kalawao county\n  \n  // Exclude territories and minor outlying areas (Puerto Rico, American Samoa, U.S. Virgin Islands, etc)\n  // FIPS prefixes 01xxx (Alabama) through 56xxx (Wyoming) are states; larger values are territories.\n  counties.geometries = counties.geometries.filter(county => +county.id < 57000);\n  \n  const state_fips_codes = await d3.tsv(\"https://gist.githubusercontent.com/jake-low/f9857e7b5c9a30000dc87cfaf9330ab5/raw/4471d6bbbfb098f27fae5dfc8d9b4ada10dc58e3/state_fips_table.tsv\");\n  \n  const states = us.objects.states;\n  \n  states.geometries = states.geometries.map(state => ({\n    ...state,\n    properties: {\n      ...state.properties,\n      name: state_fips_codes.find(row => row.STATE === state.id).STATE_NAME\n    }\n  }));\n   \n  return us;\n}","pinned":false,"mode":"js","data":null,"name":null},{"id":337,"value":"regression = require(\"https://bundle.run/regression@2.0.1\")","pinned":false,"mode":"js","data":null,"name":null},{"id":3,"value":"topojson = require(\"topojson-client@3\")","pinned":false,"mode":"js","data":null,"name":null},{"id":1220,"value":"md`## Dependencies`","pinned":false,"mode":"js","data":null,"name":null},{"id":2,"value":"d3 = require(\"d3@5\")","pinned":false,"mode":"js","data":null,"name":null},{"id":1201,"value":"twttr = require(\"https://platform.twitter.com/widgets.js\").catch(() => window.twttr)","pinned":false,"mode":"js","data":null,"name":null},{"id":1206,"value":"async function tweet(id, options) {\n  const div = document.createElement(\"DIV\");\n  await twttr.widgets.createTweet(id, div, options);\n  return div;\n}","pinned":false,"mode":"js","data":null,"name":null},{"id":1223,"value":"turf = require(\"@turf/turf@5\")","pinned":false,"mode":"js","data":null,"name":null},{"id":1289,"value":"flubber = require('flubber')","pinned":false,"mode":"js","data":null,"name":null}],"resolutions":[],"schedule":null,"last_view_time":null}