diff --git a/docs/components/apiHistogram.js b/docs/components/apiHistogram.js deleted file mode 100644 index d7ca07f..0000000 --- a/docs/components/apiHistogram.js +++ /dev/null @@ -1,125 +0,0 @@ -import * as Plot from "npm:@observablehq/plot"; -import * as d3 from "npm:d3"; -// import {dy} from "./apiHeatmap.js"; - -const dy = 400; // number of rows -const marginTop = 0; -const marginRight = 20; -const marginBottom = 30; -const marginLeft = 20; -const barWidth = 4; -const canvasCache = new WeakSet(); - -export function ApiHistogram( - value, - count, - category, - {canvas = document.createElement("canvas"), color, width, height = 360, label, y1, y2} -) { - const ky = 6; // number of requests per pixel - - const plot = Plot.plot({ - figure: true, - width, - height, - marginTop, - marginRight, - marginBottom, - marginLeft, - style: "overflow: visible;", - x: {type: "log", domain: [y1, y2 - 1], label}, - y: {axis: null, domain: [0, (height - marginTop - marginBottom) * ky], label: "requests"}, - color: {label: color.label, legend: false}, - marks: [ - Plot.ruleY([0]), - Plot.tip({length: 1}, {fill: [""], x: [y1], y: [0], format: {x: null, y: null}, render: renderTip}) - ] - }); - - const svg = plot.querySelector("svg"); - const div = document.createElement("div"); - div.style = "position: relative;"; - - if (!canvasCache.has(canvas)) { - canvasCache.add(canvas); - canvas.width = dy; - canvas.height = height - marginTop - marginBottom; - canvas.style = ` - image-rendering: pixelated; - position: absolute; - left: ${marginLeft}px; - top: ${marginTop}px; - width: calc(100% - ${marginLeft + marginRight}px); - height: calc(100% - ${marginTop + marginBottom}px); - `; - - const tick = (i, j1, j2) => { - for (let j = j1; j < j2; ++j) { - let sum = 0; - for (; i < value.length; ++i) { - const currentValue = value.get(i); - if (currentValue < j) continue; - if (currentValue > j) break; - const currentCount = count.get(i); - const currentCategory = category.get(i); - const y0 = plot.scale("y").apply(sum); - const y1 = plot.scale("y").apply((sum += currentCount)); - context.fillStyle = color.apply(currentCategory); - // console.log(context.fillStyle, currentCategory); - context.fillRect(j, y1, barWidth, y0 - y1); - } - } - if (j2 < dy) requestAnimationFrame(() => tick(i, j2, j2 + (j2 - j1))); - }; - - - const context = canvas.getContext("2d"); - requestAnimationFrame(() => tick(0, 0, 20)); - } - - svg.style.position = "relative"; - svg.replaceWith(div); - div.appendChild(canvas); - div.appendChild(svg); - - function renderTip(index, scales, values, dimensions, context, next) { - let g = next([], scales, values, dimensions, context); - const svg = context.ownerSVGElement; - svg.addEventListener("pointerenter", pointermove); - svg.addEventListener("pointermove", pointermove); - svg.addEventListener("pointerleave", pointerleave); - function pointermove(event) { - const [px, py] = d3.pointer(event); - const found = find(scales.x.invert(px), scales.y.invert(py)); - if (found == null) return pointerleave(); - const [k, y] = found; - values.x[0] = px; - values.y[0] = scales.y(y); - values.fill[0] = color.apply(category.get(k)); - values.channels.fill.value[0] = category.get(k); - const r = next([0], scales, values, dimensions, context); - g.replaceWith(r); - g = r; - } - function pointerleave() { - const r = next([], scales, values, dimensions, context); - g.replaceWith(r); - g = r; - } - return g; - } - - function find(y, currentCount) { - if (!(y1 <= y && y <= y2)) return; - const currentValue = Math.floor(((Math.log(y) - Math.log(y1)) / (Math.log(y2) - Math.log(y1))) * dy); - let i = 0, j, sum = 0; - for (; i < value.length; ++i) { - if (value.get(i) < currentValue) continue; - if (value.get(i) > currentValue) break; - if ((sum += count.get((j = i))) >= currentCount) break; - } - if (sum) return [j, sum - count.get(j) / 2]; - } - - return plot; -} \ No newline at end of file diff --git a/docs/components/timeline.js b/docs/components/timeline.js deleted file mode 100644 index 23d692c..0000000 --- a/docs/components/timeline.js +++ /dev/null @@ -1,16 +0,0 @@ -import * as Plot from "npm:@observablehq/plot"; - -export function timeline(events, {width, height} = {}) { - return Plot.plot({ - width, - height, - marginTop: 30, - x: {nice: true, label: null, tickFormat: ""}, - y: {axis: null}, - marks: [ - Plot.ruleX(events, {x: "year", y: "y", markerEnd: "dot", strokeWidth: 2.5}), - Plot.ruleY([0]), - Plot.text(events, {x: "year", y: "y", text: "name", lineAnchor: "bottom", dy: -10, lineWidth: 10, fontSize: 12}) - ] - }); -} diff --git a/docs/index.md b/docs/index.md index 3d67d67..d817977 100644 --- a/docs/index.md +++ b/docs/index.md @@ -2,125 +2,98 @@ toc: false --- - - -
here
or see how these visualizations were generated on GitHub
. Ping me on Twitter or email if you have ideas for what other variables in the census to look at or how else to display these millions of datapoints!here
or see how these visualizations were generated on GitHub
. Ping me on Twitter or email if you have ideas for what other variables in the census to look at or how else to display these millions of datapoints!
+80% of visualization is data processing; learn how this data was processed here!↗︎
```js
-const incomeHistogram = FileAttachment("data/income-histogram.parquet").parquet();
-const incomeCanvas = document.createElement("canvas");
+const income = FileAttachment("data/income-histogram.parquet").parquet();
+const rent = FileAttachment("data/rent-histogram.parquet").parquet();
```
```js
-// Assuming modification for categorization based on sector
-const sectorColorMapping = d3.sort(d3.rollups(incomeHistogram.getChild("sector"), (D) => D.length, (d) => d).filter(([d]) => d), ([, d]) => -d).map(([sector, count]) => ({sector, count}));
-const sectorColor = Object.assign(Plot.scale({color: {domain: sectorColorMapping.map((d) => d.sector)}}), {label: "sector"});
-const sectorSwatch = (sector) => html` ${sector}`;
-```
-
-```js
-// Import the rent histogram data from the parquet file
-const rentHistogram = FileAttachment("data/rent-histogram.parquet").parquet();
-const rentCanvas = document.createElement("canvas");
+function incomeChart(income, width) {
+ // Order the sectors by mean income
+ const orderSectors = d3.groupSort(
+ income,
+ (v) => -d3.sum(v, (d) => d.income * d.count) / d3.sum(v, (d) => d.count),
+ (d) => d.sector
+ );
+
+ // Create a histogram with a logarithmic base.
+ return Plot.plot({
+ width,
+ marginLeft: 60,
+ x: { type: "log" },
+ color: { legend: "swatches", columns: 6, domain: orderSectors },
+ marks: [
+ Plot.rectY(
+ income,
+ Plot.binX(
+ { y: "sum" },
+ {
+ x: "income",
+ y: "count",
+ fill: "sector",
+ order: orderSectors,
+ thresholds: d3
+ .ticks(Math.log10(2_000), Math.log10(1_000_000), 40)
+ .map((d) => +(10 ** d).toPrecision(3)),
+ tip: true,
+ }
+ )
+ ),
+ ],
+ });
+}
```
```js
-// Create the color mapping for regions
-const regionColorMapping = d3.sort(d3.rollups(rentHistogram.getChild("region"), (D) => D.length, (d) => d).filter(([d]) => d), ([, d]) => -d).map(([region, count]) => ({ region, count }));
-const regionColor = Object.assign(Plot.scale({ color: { domain: regionColorMapping.map((d) => d.region) } }), {label: "region" });
-const regionSwatch = (region) => html`${region}`;
+function rentChart(rent, width) {
+ // Order the regions by mean rent
+ const orderRegions = d3.groupSort(
+ rent,
+ (v) => d3.sum(v, (d) => d.rent * d.count) / d3.sum(v, (d) => +d.count),
+ (d) => d.region
+ );
+
+ // Create a histogram with a logarithmic base.
+ return Plot.plot({
+ width,
+ marginLeft: 60,
+ x: { type: "log" },
+ color: { legend: "swatches", columns: 6, domain: orderRegions },
+ marks: [
+ Plot.areaY(
+ rent,
+ Plot.binX(
+ { y: "sum" },
+ {
+ x: "rent",
+ y: "count",
+ fill: "region",
+ order: orderRegions,
+ thresholds: d3
+ .ticks(Math.log10(100), Math.log10(10000), 50)
+ .map((d) => +(10 ** d).toPrecision(3)),
+ tip: true,
+ curve: "monotone-x",
+ }
+ )
+ ),
+ ],
+ });
+}
```
-