Published
Edited
Mar 9, 2020
Insert cell
md`# Assignment Two - Swanson`
Insert cell
// d3 is visualization library
d3 = require("d3@5", "d3-scale@^3.2.0")
Insert cell
// topojson is not in the d3 library so must be added seperately
topojson = require("topojson-client@3")
Insert cell
//Transform an original polygon shapefile into topojson, upload to your notebook, and successfully read the topojson file in your notebook.
// File Upload statement; "objects" part is the code describing the state's attributes
cali = FileAttachment("CA_Counties.json").json()
Insert cell
califeat = topojson.feature(cali, cali.objects.CA_Counties)
Insert cell
//Upload and read a csv file that contain some attributes of the polygon file you uploaded with the topojson in the first step.
csv_data = d3.csvParse(await FileAttachment("ACS_17_5YR_S0901_with_ann@3.csv").text(),({GEOID, Total_pop, AnyDisability}) => [GEOID, [+AnyDisability/(Total_pop/100000)]])

//Create a new normalized variable (e.g., divide population by area to calculate population density).
//Variables are GEOID (ID) and population under 18 with any disability per 100,000 children. All variables are numeric.
Insert cell
//take a generated variable column from csv
disabilityper100000 = Array.from(csv_data.values(), d => d[1][0])
Insert cell
//mapping the GEOID to values (for joining)
data= Object.assign(new Map(csv_data), {title: ["Disability per 100,000"]})
Insert cell
//Map values into a linear scale (unclassed) using the variable you created.
import {legend} from "@d3/color-legend"
Insert cell
format = d => `${d}%`
Insert cell
interpolator = t => `hello ${t}`
Insert cell
Insert cell
//Create a color scheme using ColorBrewer and assign the colors into a color array and link the array with your classification method.
//I will use a colorbrewer scheme for sequential data with five classes the hex as an array are: //['#f6eff7','#bdc9e1','#67a9cf','#1c9099','#016c59']
//link to scheme: http://colorbrewer2.org/?type=sequential&scheme=PuBuGn&n=5
Insert cell
//Quantile
quantile = d3.scaleQuantile()
.domain(disabilityper100000) // pass the whole dataset to a scaleQuantile’s domain
.range([d3.color("#f6eff7"), d3.color("#bdc9e1"), d3.color("#67a9cf"),d3.color("#1c9099"),d3.color("#016c59")])
Insert cell
chart(disabilityper100000, quantile)
Insert cell
chart(numericSort(disabilityper100000), quantile)
Insert cell
jenksthresh = simple.ckmeans(disabilityper100000, 5).map(v => v.pop())
Insert cell
//Natural Breaks
jenks = d3
.scaleThreshold()
.domain([4.153843249060665, 15.93815993943499, 32.519535989904384, 62.3568337999491, 104.06937958639092])
.range([d3.color("#f6eff7"), d3.color("#bdc9e1"), d3.color("#67a9cf"),d3.color("#1c9099"),d3.color("#016c59")])
Insert cell
chart(disabilityper100000, jenks)
Insert cell
chart(numericSort(disabilityper100000), jenks)
Insert cell
//Comparing color schemes to select the most appropriate color scheme for the data

showScaleGrouping(disabilityper100000, {
scaleQuantile: quantile,
scaleJenks: jenks,
})

// I first I thought I would use the jenks natural breaks here because it is important to distinguish the high outliers as they may require additional attention for public health and educational programming to help children with disabilities in these counties. However, it was good to understand if these are true outliers or if these are small rural counties that have unstable estimates. Since these may be small, unstable estimates, the quantile approach may be more appropriate.
Insert cell
width = 975
Insert cell
height = 610
Insert cell
projection = d3.geoTransverseMercator().rotate([94,0]).fitExtent([[80, 80], [width, height]], califeat);
Insert cell
path = d3.geoPath().projection(projection);
Insert cell
formattext = d3.format(".2f")
Insert cell
md`#Map Interpretation

This is a choropleth map of the number of children with disability (per 100,000 children) in the counties of California. First, it is noticeable that there are 12 counties that are black and do not have data. These counties are lower in population and may have been masked from census data due to low counts of children with disabilities, making them potentially identifiable. Second, there are some noticeable high counts that may be in outliers (Glenn Co. 104.07; Calaveras Co. 62.36). This is especially noticeable in the jenks natural breaks classification. We choose not to use that map due to both of those being relatively sparsely populated counties, so there was concern for unstable estimates. Third, in the quantile classification map below, there is evidence of spatial correlation of counties with high and low proportions of children with disabilities in Northwest and East central California. Proportions are lowest in Southern California.`
Insert cell
// Quantile Map

chart2 = {
const svg = d3.create("svg")
.attr("viewBox", [0, 0, width, height]);

// svg.append("g")
// .attr("transform", "translate(360,20)")
// .append(() => legend({color, title: data.title, width: 260, tickFormat: ".1f"}));
svg.append("g")
.attr("transform", "translate(360,20)")
.append(() =>
legend({
color: quantile,
title: "Children with Disability (per 100,000 children)",
tickFormat: ".2f"
})
);

svg.append("g")
.selectAll("path")
.data(califeat.features)
.join("path")
.attr("stroke", "grey")
.attr("stroke-linejoin", "round")
// .attr("fill",function(d){
// console.log(d.properties.GEOID)
// })
.attr("fill", d => quantile(data.get(d.properties.GEOID)))
.attr("d", path)
.append("title")
.text(d => " Children with Disability per 100,000 children for " + d.properties.NAME + " County:" +formattext(data.get(d.properties.GEOID)));

return svg.node();
}
Insert cell
//legend

//there are undefined variables (counties with (presumably) very low populations that are not included in the census data, so black should be added to the legend as "No data"
Insert cell
//APPENDIX
Insert cell
// Adapted from "@mbostock/color-ramp"
function ramp(color, numscale, n = 512) {
const canvas = DOM.canvas(n, 1);
const context = canvas.getContext("2d"),
w = width + 28;
canvas.style.margin = "0 -14px";
canvas.style.width = `${w}px`;
canvas.style.height = "40px";
canvas.style.imageRendering = "pixelated";

// companion numerical scale, to define the axis.
if (numscale === undefined) numscale = d3.scaleLinear();
if (color.domain) numscale.domain(color.domain());
numscale.range([0, n]);
const t = color.ticks ? color.ticks(n) : d3.range(n).map(i => i / (n - 1));

for (let i = 0; i < t.length; ++i) {
context.fillStyle = color(t[i]);
context.fillRect((i * n) / t.length, 0, 100, 1);
}

d3.select(canvas).on("mousemove click", function() {
const t = numscale.invert((d3.mouse(this)[0] / w) * n);
canvas.value = t;
canvas.dispatchEvent(new CustomEvent("input"));
});
canvas.value = 0;

return canvas;
}
Insert cell
function chart(data, scale) {
const w = 30,
cols = Math.floor(Math.min(600, width) / w),
lines = Math.ceil(100 / cols);
const chart = d3
.create("svg")
.attr("width", cols * w)
.attr("height", lines * w);

chart
.append("g")
.attr("transform", "translate(2,2)")
.attr("style", "stroke:black; fill:white;")
.selectAll("rect")
.data(data)
.join("rect")
.attr("width", w - 3)
.attr("height", w - 3)
.attr("x", (_, i) => w * (i % cols))
.attr("y", (_, i) => w * ((i / cols) | 0))
.style("fill", d => (scale ? scale(d) : "#ddd"));
return chart.node();
}
Insert cell
function showScaleGrouping(data, scales) {
const margins = { left: 130 };
const x = d3
.scaleLinear()
.domain(d3.extent(data))
.range([margins.left, width - 5]);
const r = 3;
const rectHeight = 10;

const chart = svg`
<svg width=${width} height="${20 * (1.5 + Object.entries(scales).length)}"
style="font-family: sans-serif; alignment-baseline: middle; font-size:12px">
<g transform="translate(2,2)">
<text x="5" y=${5 + r}>Data</text>
${data.map(
d => `<circle r=${r} cx=${x(d)} cy=5 fill=black opacity="0.3" />`
)}
</g>
<g transform="translate(2,30)">
${Object.entries(scales).map(([name, s], i) => {
const scaleCuts = s.thresholds
? s.thresholds()
: s.quantiles
? s.quantiles()
: s.domain();
const limits = [0, ...scaleCuts, d3.max(data)];
const boxLimits = limits
.slice(0, limits.length - 1)
.map((d, j) => [limits[j], limits[j + 1]]);
return (
`<text x="5" y=${(2 * i + 1) * rectHeight}>${name}</text>` +
boxLimits.map(
(l, k) => `
<rect x=${x(l[0])} y=${i * 2 * rectHeight} height=10
width=${x(l[1]) - x(l[0])}
style="stroke:black;fill:${s.range()[k]};"
/>`
)
);
})}
</g>
</svg>`;
return chart;
}
Insert cell
simple = require("simple-statistics@7.0.7/dist/simple-statistics.min.js")
Insert cell
function numericSort(x) {
return (
x
// ensure the array is not changed in-place
.slice()
// comparator function that treats input as numeric
.sort((a, b) => a - b)
);
}
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