block by emeeks f36f3da8fa6f45bed6e9a15f5532856d

W E B Du Bois Spiral Viz

Full Screen

An attempt to procedurally draw the amazing data visualization of African-American demographics in 1890 by W. E. B. Du Bois based on census data. Rural/Small Town/Suburban/Urban is just a threshold for pop density of less than 10, 10 to 100, 100 to 1000 and 1000 plus people per square mile from census land area of counties.

Celebrate Black History

index.html


<!DOCTYPE html>

<head>
  <title>W E B Du Bois Data Visualization Remade</title>
</head>
<meta charset="utf-8">
<style>

svg {
  height: 300px;
  width: 300px;
  display: inline-block;
}

body {
  background: #e6d7c8;
}

.rural {
  fill: #d9384a;
  stroke: #a7212f;
  background: #d9384a;
  border: 1px solid #a7212f;
}

.urban {
  fill: #3e6454;
  stroke: #4f5e51;
  background: #3e6454;
  border: 1px solid #4f5e51;
}
.suburban {
  fill: #5a74b0;
  stroke: #47527a;
  background: #5a74b0;
  border: 1px solid #47527a;
}
.smalltown {
  fill: #f4b32a;
  stroke: #c9a15a;
  background: #f4b32a;
  border: 1px solid #c9a15a;
}

.final {
  stroke: none;
}

.legend {
  height: 200px;
  width: 200px;
  padding: 40px;
  display: inline-block;
  vertical-align: top;
}

.legend > div {
  line-height: 40px;
}

.legend-item {
  display: inline-block;
  width: 30px;
  height: 30px;
  margin-right: 15px;
}

path {
  stroke-linejoin: round;
  stroke: none;
}

#viz > div {
  display: inline-block;
  width: 400px;
  height: 100px;
}

</style>
<body>
<div id="viz">
  <div class="legend">
    <div><div class="legend-item urban"></div>Urban</div>
    <div><div class="legend-item suburban"></div>Suburban</div>
    <div><div class="legend-item smalltown"></div>Small Town</div>
    <div><div class="legend-item rural"></div>Rural</div>
  </div>
  <div><a href="//www.loc.gov/pictures/resource/ppmsca.33873/?co=anedub" target="_blank">Based on a data visualization of African-American demographics in 1890 by W. E. B. Du Bois</a>
</div>
</body>
  <script src="https://cdnjs.cloudflare.com/ajax/libs/d3/4.5.0/d3.min.js" type="text/JavaScript"></script>
  <script src="states.js" type="text/JavaScript"></script>
  <script src="fipsToState.js" type="text/JavaScript"></script>

<script>

["white", "hispanic", "black", "asian", "nativeamerican", "hawaiipacificisland", "multiracial"].forEach(race => {
  const div = d3.select("body").append("div")
  div.append("h1")
    .html(race)
    .style("text-align", "center")

  Object.keys(fipsToState).forEach(fip => {
  const svg = div
    .append("svg")
    .attr("class", "f" + fip)
    .attr("width", 300)
    .attr("height", 300)

  drawState(svg, fip)
})

function drawState(svg, selectedStateID) {
  const selectedState = states.filter(d => d.id === selectedStateID)[0]
  if (!selectedState) {
    return
  }
const ruralPopulation = selectedState.rural ? selectedState.rural[race] : 0
const urbanPopulation = selectedState.urban ? selectedState.urban[race] : 0
const suburbanPopulation = selectedState.suburban ? selectedState.suburban[race] : 0
const smallTownPopulation = selectedState.smalltown ? selectedState.smalltown[race] : 0

const piScale = d3.scaleLinear().domain([0,20]).range([0,Math.PI * 2])

let newData = []

const duboisData = [
  { type: "rural", value: ruralPopulation },
  { type: "smalltown", value: smallTownPopulation },
  { type: "suburban", value: suburbanPopulation },
  { type: "urban", value: urbanPopulation }
]
.sort((a,b) => a.value - b.value)

const max = duboisData[3].value
const popScale = d3.scaleLinear().domain([0, max]).range([0,100])

const ruralData = d3.range(popScale(ruralPopulation))
const urbanData = d3.range(popScale(urbanPopulation))
const suburbanData = d3.range(popScale(suburbanPopulation))
const smallTownData = d3.range(popScale(smallTownPopulation))

svg
.append("g")
.attr("transform", `translate(${100 - popScale(duboisData[2].value)},50)`)
.attr("class", "dubois")

svg
  .append("text")
  .text(fipsToState[selectedStateID])
  .style("text-anchor", "middle")
  .attr("x", 100)
  .attr("y", 30)

const topArea = d3.area()
  .x(d => d)
  .y0(0)
  .y1(10)

svg.select("g.dubois")
  .append("path")
  .attr("class", duboisData[2].type)
  .attr("transform", "translate(0,0)")
  .attr("d", topArea(d3.range(popScale(duboisData[2].value))))

const secondArea = d3.area()
  .x0(d => popScale(duboisData[0].value) - d)
  .x1(d => popScale(duboisData[0].value) - d + 10)
  .y0(d => d)
  .y1(d => d)

const secondOffset = popScale(duboisData[2].value) - popScale(duboisData[0].value) - 10

svg.select("g.dubois")
  .append("path")
  .attr("class", duboisData[0].type)
  .attr("transform", `translate(${secondOffset},10)`)
  .attr("d", secondArea(d3.range(popScale(duboisData[0].value))))

const thirdOffset = secondOffset

const thirdOffsetY = 10 + popScale(duboisData[0].value)

const thirdArea = d3.area()
  .x0(d => d)
  .x1(d => d + 10)
  .y0(d => d)
  .y1(d => d)

svg.select("g.dubois")
  .append("path")
  .attr("class", duboisData[1].type)
  .attr("transform", `translate(${thirdOffset},${thirdOffsetY})`)
  .attr("d", thirdArea(d3.range(popScale(duboisData[1].value))))

let fourthOffsetY = thirdOffsetY + popScale(duboisData[1].value)
const connectorAmount = Math.min(50, popScale(duboisData[3].value))
let fourthOffset = thirdOffset + popScale(duboisData[1].value) - connectorAmount

const radialConnectorArea = d3.area()
  .x0(d => connectorAmount - d)
  .x1(d => connectorAmount - d + 10)
  .y0(d => d)
  .y1(d => d)

svg.select("g.dubois")
  .append("path")
  .attr("class", "final " + duboisData[3].type)
  .attr("transform", `translate(${fourthOffset},${fourthOffsetY})`)
  .attr("d", radialConnectorArea(d3.range(connectorAmount)))

const spiralData = Math.max(popScale(duboisData[3].value) - 50, 0) + 6
const fifthOffsetY = fourthOffsetY + spiralData / 2 + connectorAmount + 3
const fifthOffset = fourthOffset + connectorAmount - 5

const radialArea = d3.radialArea()
  .angle(d => -piScale(d%20) - 0.57)
  .innerRadius(d => d >= spiralData - 6 ? spiralData - d : spiralData - d - 8)
  .outerRadius(d => spiralData - d)
  .curve(d3.curveBasis)

svg.select("g.dubois")
  .append("path")
  .attr("class", "final " + duboisData[3].type)
  .attr("transform", `translate(${fifthOffset},${fifthOffsetY})`)
  .attr("d", radialArea(d3.range(spiralData)))
}

})

</script>

fipsToState.js

const fipsToState = {
"2": "ALASKA",
"28": "MISSISSIPPI",
"1": "ALABAMA",
"30": "MONTANA",
"5": "ARKANSAS",
"37": "NORTH CAROLINA",
"60": "AMERICAN SAMOA",
"38": "NORTH DAKOTA",
"4": "ARIZONA",
"31": "NEBRASKA",
"6": "CALIFORNIA",
"33": "NEW HAMPSHIRE",
"8": "COLORADO",
"34": "NEW JERSEY",
"9": "CONNECTICUT",
"35": "NEW MEXICO",
"11": "DISTRICT OF COLUMBIA",
"32": "NEVADA",
"10": "DELAWARE",
"36": "NEW YORK",
"12": "FLORIDA",
"39": "OHIO",
"13": "GEORGIA",
"40": "OKLAHOMA",
"66": "GUAM",
"41": "OREGON",
"15": "HAWAII",
"42": "PENNSYLVANIA",
"19": "IOWA",
"72": "PUERTO RICO",
"16": "IDAHO",
"44": "RHODE ISLAND",
"17": "ILLINOIS",
"45": "SOUTH CAROLINA",
"18": "INDIANA",
"46": "SOUTH DAKOTA",
"20": "KANSAS",
"47": "TENNESSEE",
"21": "KENTUCKY",
"48": "TEXAS",
"22": "LOUISIANA",
"49": "UTAH",
"25": "MASSACHUSETTS",
"51": "VIRGINIA",
"24": "MARYLAND",
"78": "VIRGIN ISLANDS",
"23": "MAINE",
"50": "VERMONT",
"26": "MICHIGAN",
"53": "WASHINGTON",
"27": "MINNESOTA",
"55": "WISCONSIN",
"29": "MISSOURI",
"54": "WEST VIRGINIA",
"56": "WYOMING"
}