Public
Edited
May 6
Insert cell
md`# CAFOs vs Health Outcomes 3D histograms`
Insert cell
d3 = require("d3@5")
Insert cell
topojson = require("topojson-client@3")
Insert cell
IA_counties = FileAttachment("iacountydiseaseWGS84.json").json()
Insert cell
US_counties_features = topojson.feature(IA_counties, IA_counties.objects.iacountydiseaseWGS84)
Insert cell
csv_data = d3.csvParse(await FileAttachment("IA_countycafos.csv").text(),({NAMELSAD, Join_Count, crude_resp_rate}) => [NAMELSAD, +Join_Count, +crude_resp_rate])
Insert cell
csv_data_objects = Object.assign((d3.csvParse(await FileAttachment("IA_countycafos.csv").text(), d3.autoType)).map(({NAMELSAD, Join_Count, crude_resp_rate}) => ({County: NAMELSAD, Number_CAFOS: +Join_Count, Crude_Resp_Rate: +crude_resp_rate})))
Insert cell
viewof bins = Inputs.range([0, 20], {step: 1, label: "Bins"})
Insert cell
Plot.plot({
marks: [
Plot.rectY(csv_data_objects, Plot.binX({y: "count"}, {x: "Number_CAFOS", thresholds: bins})),
Plot.ruleY([0])
]
})
Insert cell
Plot.plot({
marks: [
Plot.rectY(csv_data_objects, Plot.binX({y: "count"}, {x: "Crude_Resp_Rate", thresholds: bins})),
Plot.ruleY([0])
]
})
Insert cell
cafoBins = d3.range(0, 60, 5)
Insert cell
Insert cell
Insert cell
csv_data_objects.forEach(d => {
const x = d.Number_CAFOS;
const y = d.Crude_Resp_Rate;

const xBin = d3.bisectLeft(cafoBins, x) - 1;
const yBin = d3.bisectLeft(respBins, y) - 1;

if (xBin >= 0 && yBin >= 0 && xBin < z[0].length && yBin < z.length) {
z[yBin][xBin]++;
}
})
Insert cell
Plotly = require("https://cdn.plot.ly/plotly-latest.min.js")
Insert cell
Plotly.newPlot(DOM.element("div"), [{
type: 'surface',
z: z,
x: cafoBins.slice(0, -1),
y: respBins.slice(0, -1),
colorscale: 'Viridis'
}], {
title: '3D Histogram: Number of CAFOs vs Respiratory Disease Rate',
scene: {
xaxis: { title: 'Number of CAFOs' },
yaxis: { title: 'Crude Respiratory Rate' },
zaxis: { title: 'Frequency (Counties)' }
}
})
Insert cell
import { createThreeJSHistogram } from '@observablehq/stdlib'
Insert cell
THREE = require("three@0.150.1")
Insert cell
binnedData = {
const xBins = 10; // adjust as needed
const yBins = 10;

const xExtent = d3.extent(csv_data_objects, d => d.Number_CAFOS);
const yExtent = d3.extent(csv_data_objects, d => d.Crude_Resp_Rate);

const xStep = (xExtent[1] - xExtent[0]) / xBins;
const yStep = (yExtent[1] - yExtent[0]) / yBins;

const bins = [];

for (let i = 0; i < xBins; i++) {
for (let j = 0; j < yBins; j++) {
const x0 = xExtent[0] + i * xStep;
const x1 = x0 + xStep;
const y0 = yExtent[0] + j * yStep;
const y1 = y0 + yStep;

const count = csv_data_objects.filter(d =>
d.Number_CAFOS >= x0 && d.Number_CAFOS < x1 &&
d.Crude_Resp_Rate >= y0 && d.Crude_Resp_Rate < y1
).length;

const xCenter = (x0 + x1) / 2;
const yCenter = (y0 + y1) / 2;

bins.push({ x: xCenter, y: yCenter, count });
}
}

return bins;
}
Insert cell
viewof renderer = {
const width = 800;
const height = 600;

const scene = new THREE.Scene();
const camera = new THREE.PerspectiveCamera(45, width / height, 0.1, 1000);
camera.position.set(0, -100, 100);
camera.lookAt(0, 0, 0);

const renderer = new THREE.WebGLRenderer();
renderer.setSize(width, height);

const light = new THREE.DirectionalLight(0xffffff, 1);
light.position.set(10, 10, 10);
scene.add(light);

const binSize = 1;

// Normalize axes for visual clarity
const xValues = binnedData.map(d => d.x);
const yValues = binnedData.map(d => d.y);
const maxCount = Math.max(...binnedData.map(d => d.count));

const xScale = d3.scaleLinear()
.domain(d3.extent(xValues))
.range([-10, 10]);

const yScale = d3.scaleLinear()
.domain(d3.extent(yValues))
.range([-10, 10]);

const zScale = d3.scaleLinear()
.domain([0, maxCount])
.range([0, 10]);

binnedData.forEach(({ x, y, count }) => {
if (count === 0) return;

const scaledX = xScale(x);
const scaledY = yScale(y);
const scaledZ = zScale(count);

const geometry = new THREE.BoxGeometry(1, 1, scaledZ);
const material = new THREE.MeshLambertMaterial({ color: 0x44aa88 });
const cube = new THREE.Mesh(geometry, material);
cube.position.set(scaledX, scaledY, scaledZ / 2);
scene.add(cube);
});

function animate() {
requestAnimationFrame(animate);
scene.rotation.z += 0.005;
scene.rotation.x += 0.005;
renderer.render(scene, camera);
}

animate();

return renderer.domElement;
}
Insert cell
Number_Of_Cafos = Array.from(csv_data.values(), d => d[1])
Insert cell
data = Object.assign(new Map(csv_data), {title: ["Number of CAFOS in Iowa Counties"]})
Insert cell
md`# Linear Scale (Unclassed)`
Insert cell
linear = d3.scaleLinear()
.domain(d3.extent(Number_Of_Cafos))
.range(["white", "red"])
Insert cell
chart(numericSort(Number_Of_Cafos), linear)
Insert cell
md`# Quantile Classification`
Insert cell
quantile = d3.scaleQuantile()
.domain(Number_Of_Cafos)
.range(["#f2f0f7", "#cbc9e2", "#9e9ac8", "#756bb1", "#54278f"])
Insert cell
chart(numericSort(Number_Of_Cafos), quantile)
Insert cell
md`# Jenks Natural Breaks Classification`
Insert cell
naturalbreaks = simple.ckmeans(Number_Of_Cafos, 5).map(v => v.pop())
Insert cell
jenks = d3
.scaleThreshold()
.domain(naturalbreaks)
.range(["#feedde", "#fdbe85", "#fd8d3c", "#e6550d", "#a63603"])
Insert cell
chart(numericSort(Number_Of_Cafos), jenks)
Insert cell
md`# Equal Interval Classification (Quantize)`
Insert cell
quantize = d3.scaleQuantize()
.domain([d3.min(Number_Of_Cafos),d3.max(Number_Of_Cafos)])
.range(["#f2f0f7", "#cbc9e2", "#9e9ac8", "#756bb1", "#54278f"])
Insert cell
chart(numericSort(Number_Of_Cafos), quantize)
Insert cell
md`# Threshold`
Insert cell
threshold = d3.scaleThreshold()
.domain([50, 125, 200, 250, 300])
.range(["#feedde", "#fdbe85", "#fd8d3c", "#e6550d", "#a63603"])
Insert cell
chart(numericSort(Number_Of_Cafos), threshold)
Insert cell
showScaleGrouping(Number_Of_Cafos, {
scaleQuantile: quantile,
scaleThreshold: threshold,
scaleJenks: jenks,
scaleQuantize: quantize,
scaleQuantizeNice: quantize.copy().nice()
})
Insert cell
md`# Annex`
Insert cell
Insert cell
function chart(data, scale) {
const w = 20,
cols = Math.floor(Math.min(200, 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
simple = require("simple-statistics@7.0.7/dist/simple-statistics.min.js")
Insert cell
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