diff --git a/README.md b/README.md index c6b3c66233..7adbf4ac88 100644 --- a/README.md +++ b/README.md @@ -1032,6 +1032,7 @@ To control how the quantitative dimensions *x* and *y* are divided into bins, th * **thresholds** - the threshold values; see below * **domain** - values outside the domain will be omitted * **cumulative** - if positive, each bin will contain all lesser bins +* **empty** - whether to include empty bins; false by default If the **domain** option is not specified, it defaults to the minimum and maximum of the corresponding dimension (*x* or *y*), possibly niced to match the threshold interval to ensure that the first and last bin have the same width as other bins. If **cumulative** is negative (-1 by convention), each bin will contain all *greater* bins rather than all *lesser* bins, representing the [complementary cumulative distribution](https://en.wikipedia.org/wiki/Cumulative_distribution_function#Complementary_cumulative_distribution_function_.28tail_distribution.29). diff --git a/src/transforms/bin.js b/src/transforms/bin.js index b3e068cc3f..ca7f281223 100644 --- a/src/transforms/bin.js +++ b/src/transforms/bin.js @@ -96,10 +96,10 @@ function binn( for (const [k, g] of maybeGroup(I, K)) { for (const [x1, x2, fx] of BX) { const bb = fx(g); - if (bb.length === 0) continue; + if (bx && bb.length === 0 && !bx.empty) continue; for (const [y1, y2, fy] of BY) { const b = fy(bb); - if (b.length === 0) continue; + if (by && b.length === 0 && !by.empty) continue; groupFacet.push(i++); groupData.push(reduceData.reduce(b, data)); if (K) GK.push(k); @@ -124,12 +124,13 @@ function binn( }; } -function maybeBinValue(value, {cumulative, domain, thresholds} = {}, defaultValue) { +function maybeBinValue(value, {cumulative, domain, thresholds, empty} = {}, defaultValue) { value = {...maybeValue(value)}; if (value.domain === undefined) value.domain = domain; if (value.cumulative === undefined) value.cumulative = cumulative; if (value.thresholds === undefined) value.thresholds = thresholds; if (value.value === undefined) value.value = defaultValue; + value.empty = !!empty; // Note: cannot be set per-dimension value.thresholds = maybeThresholds(value.thresholds); return value; } @@ -144,7 +145,7 @@ function maybeBinValueTuple(options = {}) { function maybeBin(options) { if (options == null) return; - const {value, cumulative, domain = extent, thresholds} = options; + const {value, cumulative, domain = extent, thresholds, empty} = options; const bin = data => { const V = valueof(data, value); const bin = binner().value(i => V[i]); @@ -165,8 +166,10 @@ function maybeBin(options) { } let bins = bin(range(data)).map(binset); if (cumulative) bins = (cumulative < 0 ? bins.reverse() : bins).map(bincumset); - return bins.filter(nonempty2).map(binfilter); + if (!empty) bins = bins.filter(nonempty2); + return bins.map(binfilter); }; + bin.empty = empty; bin.label = labelof(value); return bin; } diff --git a/test/data/availability.csv b/test/data/availability.csv new file mode 100644 index 0000000000..e612043a82 --- /dev/null +++ b/test/data/availability.csv @@ -0,0 +1,470 @@ +date,value +2021-02-03,2 +2021-02-04,2 +2021-02-05,2 +2021-02-06,2 +2021-02-07,2 +2021-02-08,2 +2021-02-09,2 +2021-02-10,2 +2021-02-11,2 +2021-02-12,2 +2021-02-13,2 +2021-02-14,2 +2021-02-15,2 +2021-02-16,2 +2021-02-17,2 +2021-02-18,2 +2021-02-19,2 +2021-02-20,2 +2021-02-21,2 +2021-02-22,2 +2021-02-23,2 +2021-02-24,2 +2021-03-10,2 +2021-03-11,1 +2021-03-12,1 +2021-03-13,2 +2021-03-14,2 +2021-03-15,2 +2021-03-16,2 +2021-03-17,2 +2021-03-18,1 +2021-03-19,2 +2021-03-20,2 +2021-03-21,2 +2021-03-22,2 +2021-03-23,1 +2021-03-24,1 +2021-03-25,1 +2021-03-26,1 +2021-03-27,1 +2021-03-28,1 +2021-03-29,1 +2021-03-30,1 +2021-03-31,1 +2021-04-01,1 +2021-04-02,1 +2021-04-03,1 +2021-04-04,2 +2021-04-05,2 +2021-04-06,2 +2021-04-07,2 +2021-04-08,2 +2021-04-09,2 +2021-04-10,2 +2021-04-11,2 +2021-04-12,2 +2021-04-13,2 +2021-04-14,2 +2021-04-15,2 +2021-04-16,2 +2021-04-17,2 +2021-04-18,2 +2021-04-19,2 +2021-04-20,2 +2021-04-21,2 +2021-04-22,2 +2021-04-23,2 +2021-04-24,2 +2021-04-25,2 +2021-04-26,2 +2021-04-27,2 +2021-04-28,2 +2021-04-29,2 +2021-04-30,2 +2021-05-01,2 +2021-05-02,2 +2021-05-03,2 +2021-05-04,2 +2021-05-06,2 +2021-05-05,2 +2021-05-07,2 +2021-05-08,2 +2021-05-09,2 +2021-05-10,2 +2021-05-11,2 +2021-05-12,2 +2021-05-13,2 +2021-05-14,2 +2021-05-15,2 +2021-05-16,2 +2021-05-17,2 +2021-05-18,2 +2020-09-12,1 +2020-09-13,1 +2020-09-14,1 +2020-09-15,1 +2020-09-16,1 +2020-09-17,1 +2020-09-18,1 +2020-09-19,1 +2020-09-20,1 +2020-09-21,1 +2020-09-22,1 +2020-09-23,1 +2020-09-27,1 +2020-09-24,1 +2020-09-25,1 +2020-09-26,1 +2020-09-28,1 +2020-09-29,1 +2020-09-30,1 +2020-10-01,1 +2020-10-02,1 +2020-10-03,1 +2020-10-04,1 +2020-10-05,1 +2020-10-06,1 +2020-10-07,1 +2020-10-08,1 +2020-10-09,1 +2020-10-10,1 +2020-10-11,1 +2020-10-16,1 +2020-10-17,1 +2020-10-21,1 +2020-10-18,1 +2020-10-22,1 +2020-10-19,1 +2020-10-20,1 +2020-10-26,1 +2020-10-23,1 +2020-10-24,1 +2020-10-25,1 +2020-10-27,1 +2020-10-31,1 +2020-10-28,1 +2020-10-29,1 +2020-10-30,1 +2020-11-01,1 +2020-11-04,1 +2020-11-05,1 +2020-11-02,1 +2020-11-03,1 +2020-11-06,1 +2020-10-12,1 +2020-10-13,1 +2020-10-14,1 +2020-10-15,1 +2020-01-22,5 +2020-01-23,5 +2019-12-31,5 +2020-01-01,5 +2020-01-02,5 +2020-01-03,5 +2020-01-04,5 +2020-01-05,5 +2020-01-06,5 +2020-01-07,5 +2020-01-08,5 +2020-01-09,5 +2020-01-10,5 +2020-01-11,5 +2020-01-12,5 +2020-01-13,5 +2020-01-14,5 +2020-01-15,5 +2020-01-16,5 +2020-01-17,5 +2020-01-18,5 +2020-01-19,5 +2020-01-20,5 +2020-01-21,5 +2020-01-24,5 +2020-01-25,5 +2020-01-26,5 +2020-01-27,5 +2020-01-28,5 +2020-01-29,5 +2020-01-30,5 +2020-01-31,5 +2020-02-01,5 +2020-02-02,4 +2020-02-03,4 +2020-02-04,4 +2020-02-05,5 +2020-02-06,5 +2020-02-07,5 +2020-02-08,5 +2020-02-09,5 +2020-02-10,5 +2020-02-11,5 +2020-02-12,5 +2020-02-13,5 +2020-02-14,5 +2020-02-15,4 +2020-02-16,4 +2020-02-17,4 +2020-02-18,4 +2020-02-19,4 +2020-02-20,4 +2020-02-21,4 +2020-02-22,4 +2020-02-23,4 +2020-02-24,4 +2020-02-25,4 +2020-02-26,5 +2020-02-27,5 +2020-02-28,5 +2020-02-29,5 +2020-03-01,5 +2020-03-02,5 +2020-03-03,5 +2020-03-04,5 +2020-03-05,5 +2020-03-06,5 +2020-03-07,5 +2020-03-08,5 +2020-03-09,5 +2020-03-10,5 +2020-03-11,5 +2020-03-12,5 +2020-03-13,5 +2020-03-14,5 +2020-03-15,5 +2020-03-16,5 +2020-03-17,5 +2020-03-18,5 +2020-03-19,5 +2020-03-20,5 +2020-03-21,5 +2020-03-22,5 +2020-03-23,4 +2020-03-24,4 +2020-03-25,5 +2020-03-26,5 +2020-03-27,5 +2020-03-28,5 +2020-03-29,5 +2020-03-30,5 +2020-03-31,5 +2020-04-01,5 +2020-04-02,5 +2020-04-03,5 +2020-04-04,5 +2020-04-05,5 +2020-04-06,5 +2020-04-07,5 +2020-04-08,5 +2020-04-09,5 +2020-04-10,5 +2020-04-11,5 +2020-04-12,5 +2020-04-13,5 +2020-04-14,5 +2020-04-15,5 +2020-04-16,5 +2020-04-17,5 +2020-04-18,5 +2020-04-19,5 +2020-04-20,5 +2020-04-21,5 +2020-04-22,5 +2020-04-23,5 +2020-04-24,5 +2020-04-25,5 +2020-04-26,5 +2020-04-27,5 +2020-04-28,5 +2020-04-29,5 +2020-04-30,5 +2020-05-01,5 +2020-05-02,5 +2020-05-03,5 +2020-05-04,5 +2020-05-05,5 +2020-05-06,5 +2020-05-07,5 +2020-05-08,4 +2020-05-09,4 +2020-05-10,4 +2020-05-11,4 +2020-05-12,4 +2020-05-13,4 +2020-05-14,4 +2020-05-15,4 +2020-05-16,4 +2020-05-17,3 +2020-05-18,3 +2020-05-19,3 +2020-05-20,3 +2020-05-21,3 +2020-05-22,3 +2020-05-23,3 +2020-05-24,3 +2020-05-25,3 +2020-05-26,3 +2020-05-27,3 +2020-05-28,3 +2020-05-29,3 +2020-05-30,3 +2020-05-31,3 +2020-06-01,3 +2020-06-02,3 +2020-06-03,3 +2020-06-04,3 +2020-06-05,3 +2020-06-06,3 +2020-06-07,3 +2020-06-08,3 +2020-06-09,3 +2020-06-10,3 +2020-06-11,3 +2020-06-12,3 +2020-06-13,3 +2020-06-14,3 +2020-06-15,3 +2020-06-16,3 +2020-06-17,3 +2020-06-18,3 +2020-06-19,3 +2020-06-20,3 +2020-06-21,3 +2020-06-22,3 +2020-06-23,3 +2020-06-24,3 +2020-06-25,3 +2020-06-26,2 +2020-06-27,2 +2020-06-28,2 +2020-06-29,2 +2020-06-30,2 +2020-07-24,2 +2020-07-25,2 +2020-07-26,2 +2020-07-27,2 +2020-07-28,2 +2020-07-29,2 +2020-07-30,1 +2020-07-31,1 +2020-08-01,1 +2020-08-02,1 +2020-08-03,1 +2020-08-04,1 +2020-08-05,1 +2020-08-06,1 +2020-08-07,1 +2020-08-08,1 +2020-08-09,1 +2020-08-10,1 +2020-08-11,1 +2020-08-12,1 +2020-08-13,1 +2020-08-14,1 +2020-08-15,1 +2020-08-16,1 +2020-08-17,1 +2020-08-18,1 +2020-08-19,1 +2020-08-20,1 +2020-08-21,1 +2020-08-22,1 +2020-08-23,1 +2020-08-24,1 +2020-08-25,1 +2020-08-26,1 +2020-08-27,1 +2020-08-28,1 +2020-08-29,1 +2020-08-30,1 +2020-08-31,1 +2020-09-01,1 +2020-09-02,1 +2020-09-03,1 +2020-09-04,1 +2020-09-05,1 +2020-09-06,1 +2020-09-07,1 +2020-09-08,1 +2020-09-09,1 +2020-09-10,1 +2020-09-11,1 +2020-11-07,1 +2020-11-08,1 +2020-11-09,1 +2020-11-10,1 +2020-11-11,1 +2020-11-12,1 +2020-11-13,2 +2020-11-14,2 +2020-11-15,2 +2020-11-16,2 +2020-11-17,2 +2020-11-18,2 +2020-11-19,2 +2020-11-20,2 +2020-11-21,2 +2020-11-22,2 +2020-11-23,2 +2020-11-24,2 +2020-11-25,2 +2020-11-26,2 +2020-11-27,2 +2020-11-28,2 +2020-11-29,2 +2020-11-30,2 +2020-12-01,2 +2020-12-02,2 +2020-12-03,2 +2020-12-04,2 +2020-12-05,2 +2020-12-06,2 +2020-12-07,2 +2020-12-08,2 +2020-12-09,2 +2020-12-10,2 +2020-12-11,2 +2020-12-12,1 +2020-12-13,1 +2020-12-14,1 +2020-12-15,2 +2020-12-16,2 +2020-12-17,2 +2020-12-18,2 +2020-12-19,2 +2020-12-20,2 +2020-12-21,2 +2020-12-22,2 +2020-12-23,2 +2020-12-24,2 +2020-12-25,2 +2020-12-26,2 +2020-12-27,2 +2020-12-28,2 +2020-12-29,2 +2020-12-30,2 +2020-12-31,2 +2021-01-01,2 +2021-01-02,2 +2021-01-03,2 +2021-01-04,2 +2021-01-05,2 +2021-01-06,2 +2021-01-07,2 +2021-01-08,1 +2021-01-09,1 +2021-01-10,1 +2021-01-11,1 +2021-01-12,1 +2021-01-13,1 +2021-01-14,1 +2021-01-15,1 +2021-01-16,1 +2021-01-17,1 +2021-01-18,1 +2021-01-19,1 +2021-01-20,1 +2021-01-21,1 +2021-01-22,1 +2021-01-23,1 +2021-01-24,1 +2021-01-25,1 +2021-01-26,2 +2021-01-27,2 +2021-01-28,2 +2021-01-29,2 +2021-01-30,2 +2021-01-31,2 +2021-02-01,2 +2021-02-02,2 \ No newline at end of file diff --git a/test/output/availability.svg b/test/output/availability.svg new file mode 100644 index 0000000000..d200bcaa0f --- /dev/null +++ b/test/output/availability.svg @@ -0,0 +1,51 @@ + + + + 0 + + + 1 + + + 2 + + + 3 + + + 4 + + + 5 + ↑ value + + + + 2020 + + + April + + + July + + + October + + + 2021 + + + April + + + + + + + + + + + + \ No newline at end of file diff --git a/test/plots/availability.js b/test/plots/availability.js new file mode 100644 index 0000000000..46bddf6dd4 --- /dev/null +++ b/test/plots/availability.js @@ -0,0 +1,41 @@ +import * as Plot from "@observablehq/plot"; +import * as d3 from "d3"; + +export default async function() { + const data = await d3.csv("data/availability.csv", d3.autoType); + return Plot.plot({ + marks: [ + Plot.areaY( + data, + Plot.binX( + { y: "sum" }, + { + x: "date", + y: "value", + sort: "date", + curve: "step", + empty: true, + thresholds: d3.utcDay, + fill: "#f2f2fe" + } + ) + ), + Plot.line( + data, + Plot.binX( + { y: "sum" }, + { + x: "date", + y: "value", + sort: "date", + curve: "step", + empty: true, + thresholds: d3.utcDay + } + ) + ), + Plot.ruleY([0]) + ], + height: 180 + }); +} diff --git a/test/plots/index.js b/test/plots/index.js index 694adcd6ec..be11c78faa 100644 --- a/test/plots/index.js +++ b/test/plots/index.js @@ -14,6 +14,7 @@ export {default as athletesSportSex} from "./athletes-sport-sex.js"; export {default as athletesSportWeight} from "./athletes-sport-weight.js"; export {default as athletesWeight} from "./athletes-weight.js"; export {default as athletesWeightCumulative} from "./athletes-weight-cumulative.js"; +export {default as availability} from "./availability.js"; export {default as ballotStatusRace} from "./ballot-status-race.js"; export {default as beckerBarley} from "./becker-barley.js"; export {default as caltrain} from "./caltrain.js";