From 261594c55ff861b1020b553ff170097a4c418f0b Mon Sep 17 00:00:00 2001 From: Toph Tucker Date: Tue, 21 Mar 2023 18:59:10 -0400 Subject: [PATCH 01/13] Auto mark: never render rect; move zero-ness to autoSpec --- src/marks/auto.js | 41 +- test/output/aaautoJeff.svg | 124 +++ test/output/aaautoTanner.svg | 1778 ++++++++++++++++++++++++++++++++++ test/plots/autoplot.ts | 12 + 4 files changed, 1943 insertions(+), 12 deletions(-) create mode 100644 test/output/aaautoJeff.svg create mode 100644 test/output/aaautoTanner.svg diff --git a/src/marks/auto.js b/src/marks/auto.js index 062bd43f46..cd6aebb447 100644 --- a/src/marks/auto.js +++ b/src/marks/auto.js @@ -5,7 +5,7 @@ import {dot} from "./dot.js"; import {line, lineX, lineY} from "./line.js"; import {ruleX, ruleY} from "./rule.js"; import {barX, barY} from "./bar.js"; -import {rect, rectX, rectY} from "./rect.js"; +import {rectX, rectY} from "./rect.js"; import {cell} from "./cell.js"; import {frame} from "./frame.js"; import {bin, binX, binY} from "../transforms/bin.js"; @@ -62,7 +62,7 @@ export function autoSpec(data, options) { mark = sizeValue != null || sizeReduce != null ? "dot" - : xZero || yZero || colorReduce != null // histogram or heatmap + : isZeroReducer(xReduce) || isZeroReducer(yReduce) || colorReduce != null // histogram or heatmap ? "bar" : xValue != null && yValue != null ? isOrdinal((X ??= materializeValue(data, x))) || @@ -75,19 +75,43 @@ export function autoSpec(data, options) { : null; } + // TODO: should we just always materialize in here? + X ??= materializeValue(data, x); + Y ??= materializeValue(data, y); + + // If zero-ness is not specified, default based on whether the resolved mark + // type will include a zero baseline. + // TODO: This still fails on the heatmaps, which define separate x1 and x2 on + // barX, or separate y1 and y2 on barY, such that the bar is not actually + // standing on the baseline, and there shouldn't be a zero. + // TODO: Exception for binning? Idk what that code was doing before + if (mark === "area" || mark === "bar" || mark === "rule") { + if (!(isOrdinalReduced(xReduce, X) && isOrdinalReduced(yReduce, Y))) { + if (isOrdinalReduced(xReduce, X)) { + yZero ??= true; + } else if (isOrdinalReduced(yReduce, Y)) { + xZero ??= true; + } else if (Y && isMonotonic(Y)) { + xZero ??= true; + } else { + yZero ??= true; + } + } + } + return { fx: fx ?? null, fy: fy ?? null, x: { value: xValue ?? null, reduce: xReduce ?? null, - ...(xZero !== undefined && {zero: xZero}), // TODO realize default + zero: xZero ?? null, ...xOptions }, y: { value: yValue ?? null, reduce: yReduce ?? null, - ...(yZero !== undefined && {zero: yZero}), // TODO realize default + zero: yZero ?? null, ...yOptions }, color: { @@ -171,7 +195,7 @@ export function auto(data, options) { ? barY : isOrdinalReduced(yReduce, Y) ? barX - : rect; + : rectY; colorMode = "fill"; break; default: @@ -214,13 +238,6 @@ export function auto(data, options) { markOptions = transform(transformOptions, markOptions); } - // If zero-ness is not specified, default based on whether the resolved mark - // type will include a zero baseline. TODO Move this to autoSpec. - if (xZero === undefined) - xZero = X && transform !== binX && (mark === barX || mark === areaX || mark === rectX || mark === ruleY); - if (yZero === undefined) - yZero = Y && transform !== binY && (mark === barY || mark === areaY || mark === rectY || mark === ruleX); - // In the case of filled marks (particularly bars and areas) the frame and // rules should come after the mark; in the case of stroked marks // (particularly dots and lines) they should come before the mark. diff --git a/test/output/aaautoJeff.svg b/test/output/aaautoJeff.svg new file mode 100644 index 0000000000..c8915a1b2c --- /dev/null +++ b/test/output/aaautoJeff.svg @@ -0,0 +1,124 @@ + + + + + + + + + + + + + + + + 0.0 + 0.2 + 0.4 + 0.6 + 0.8 + 1.0 + 1.2 + 1.4 + 1.6 + 1.8 + + + ↑ height + + + + + + + + + + + + + + + 1955 + 1960 + 1965 + 1970 + 1975 + 1980 + 1985 + 1990 + 1995 + 2000 + + + date_of_birth → + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/test/output/aaautoTanner.svg b/test/output/aaautoTanner.svg new file mode 100644 index 0000000000..78e8dc1fdb --- /dev/null +++ b/test/output/aaautoTanner.svg @@ -0,0 +1,1778 @@ + + + + + + + + + + + + + + 0 + 2,000 + 4,000 + 6,000 + 8,000 + 10,000 + 12,000 + 14,000 + + + ↑ unemployed + + + + + + + + + + + + + + + + 2000 + 2001 + 2002 + 2003 + 2004 + 2005 + 2006 + 2007 + 2008 + 2009 + 2010 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/test/plots/autoplot.ts b/test/plots/autoplot.ts index 85d8195ae6..32f2a57d66 100644 --- a/test/plots/autoplot.ts +++ b/test/plots/autoplot.ts @@ -1,6 +1,18 @@ import * as Plot from "@observablehq/plot"; import * as d3 from "d3"; +// Tanner's bug +export async function autoLineZero() { + const industries = await d3.csv("data/bls-industry-unemployment.csv", d3.autoType); + return Plot.auto(industries, {x: "date", y: {value: "unemployed", zero: true}, color: "industry"}).plot() +} + +// Jeff's bug +export async function autoBarNonZeroReducer() { + const athletes = await d3.csv("data/athletes.csv", d3.autoType); + return Plot.auto(athletes, {x: "date_of_birth", y: { value: "height", reduce: "mean" }, mark: "bar"}).plot() +} + export async function autoHistogram() { const athletes = await d3.csv("data/athletes.csv", d3.autoType); return Plot.auto(athletes, {x: "weight"}).plot(); From f7d100f3ff9f9cc4c2bd7f96ddcd41807be76430 Mon Sep 17 00:00:00 2001 From: Toph Tucker Date: Tue, 21 Mar 2023 19:07:33 -0400 Subject: [PATCH 02/13] update test artifacts --- test/output/aaautoTanner.svg | 1778 ----------------- ...autoJeff.svg => autoBarNonZeroReducer.svg} | 0 test/output/autoLineZero.svg | 94 + 3 files changed, 94 insertions(+), 1778 deletions(-) delete mode 100644 test/output/aaautoTanner.svg rename test/output/{aaautoJeff.svg => autoBarNonZeroReducer.svg} (100%) create mode 100644 test/output/autoLineZero.svg diff --git a/test/output/aaautoTanner.svg b/test/output/aaautoTanner.svg deleted file mode 100644 index 78e8dc1fdb..0000000000 --- a/test/output/aaautoTanner.svg +++ /dev/null @@ -1,1778 +0,0 @@ - - - - - - - - - - - - - - 0 - 2,000 - 4,000 - 6,000 - 8,000 - 10,000 - 12,000 - 14,000 - - - ↑ unemployed - - - - - - - - - - - - - - - - 2000 - 2001 - 2002 - 2003 - 2004 - 2005 - 2006 - 2007 - 2008 - 2009 - 2010 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/test/output/aaautoJeff.svg b/test/output/autoBarNonZeroReducer.svg similarity index 100% rename from test/output/aaautoJeff.svg rename to test/output/autoBarNonZeroReducer.svg diff --git a/test/output/autoLineZero.svg b/test/output/autoLineZero.svg new file mode 100644 index 0000000000..911d370189 --- /dev/null +++ b/test/output/autoLineZero.svg @@ -0,0 +1,94 @@ + + + + + + + + + + + + + + + + + + + 0 + 200 + 400 + 600 + 800 + 1,000 + 1,200 + 1,400 + 1,600 + 1,800 + 2,000 + 2,200 + 2,400 + + + ↑ unemployed + + + + + + + + + + + + + + + + 2000 + 2001 + 2002 + 2003 + 2004 + 2005 + 2006 + 2007 + 2008 + 2009 + 2010 + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file From 6ac8928acb73e4f21036039218643e92f01f37c2 Mon Sep 17 00:00:00 2001 From: Toph Tucker Date: Tue, 21 Mar 2023 19:23:06 -0400 Subject: [PATCH 03/13] fix some tests; only set zero on a dimension if that dimension is defined --- src/marks/auto.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/marks/auto.js b/src/marks/auto.js index cd6aebb447..2e1af2b440 100644 --- a/src/marks/auto.js +++ b/src/marks/auto.js @@ -88,13 +88,13 @@ export function autoSpec(data, options) { if (mark === "area" || mark === "bar" || mark === "rule") { if (!(isOrdinalReduced(xReduce, X) && isOrdinalReduced(yReduce, Y))) { if (isOrdinalReduced(xReduce, X)) { - yZero ??= true; + if (Y) yZero ??= true; } else if (isOrdinalReduced(yReduce, Y)) { - xZero ??= true; + if (X) xZero ??= true; } else if (Y && isMonotonic(Y)) { - xZero ??= true; + if (X) xZero ??= true; } else { - yZero ??= true; + if (Y) yZero ??= true; } } } From 3a375baf5f1816ff3187ad94afa3e806f8120c77 Mon Sep 17 00:00:00 2001 From: Toph Tucker Date: Tue, 21 Mar 2023 21:06:29 -0400 Subject: [PATCH 04/13] Update src/marks/auto.js Co-authored-by: Mike Bostock --- src/marks/auto.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/marks/auto.js b/src/marks/auto.js index 2e1af2b440..b3d3704d4a 100644 --- a/src/marks/auto.js +++ b/src/marks/auto.js @@ -105,7 +105,7 @@ export function autoSpec(data, options) { x: { value: xValue ?? null, reduce: xReduce ?? null, - zero: xZero ?? null, + zero: xZero ?? false, ...xOptions }, y: { From 9f5b78db13ffcf555123a6e0cf811b1416ff0ecf Mon Sep 17 00:00:00 2001 From: Toph Tucker Date: Tue, 21 Mar 2023 22:32:44 -0400 Subject: [PATCH 05/13] dont set zero-ness if colorReduce --- src/marks/auto.js | 20 ++++++++------------ 1 file changed, 8 insertions(+), 12 deletions(-) diff --git a/src/marks/auto.js b/src/marks/auto.js index 2e1af2b440..7c8e1ca601 100644 --- a/src/marks/auto.js +++ b/src/marks/auto.js @@ -75,21 +75,17 @@ export function autoSpec(data, options) { : null; } - // TODO: should we just always materialize in here? - X ??= materializeValue(data, x); - Y ??= materializeValue(data, y); - - // If zero-ness is not specified, default based on whether the resolved mark - // type will include a zero baseline. - // TODO: This still fails on the heatmaps, which define separate x1 and x2 on - // barX, or separate y1 and y2 on barY, such that the bar is not actually - // standing on the baseline, and there shouldn't be a zero. + // Update the default zero-ness depending on the inferred mark. // TODO: Exception for binning? Idk what that code was doing before if (mark === "area" || mark === "bar" || mark === "rule") { - if (!(isOrdinalReduced(xReduce, X) && isOrdinalReduced(yReduce, Y))) { - if (isOrdinalReduced(xReduce, X)) { + // Don't set zeros if both dimensions are ordinal, or if there's a + // colorReduce, implying a 2D group/bin + const xOrdinal = isOrdinalReduced(xReduce, (X ??= materializeValue(data, x))); + const yOrdinal = isOrdinalReduced(yReduce, (Y ??= materializeValue(data, y))); + if (!(xOrdinal && yOrdinal) && !colorReduce) { + if (xOrdinal) { if (Y) yZero ??= true; - } else if (isOrdinalReduced(yReduce, Y)) { + } else if (yOrdinal) { if (X) xZero ??= true; } else if (Y && isMonotonic(Y)) { if (X) xZero ??= true; From 2907f2ffccfe2cccff3e44c931339c31d33cfc55 Mon Sep 17 00:00:00 2001 From: Toph Tucker Date: Tue, 21 Mar 2023 22:34:35 -0400 Subject: [PATCH 06/13] fix some tests --- src/marks/auto.js | 2 +- test/marks/auto-test.js | 16 ++++++++-------- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/src/marks/auto.js b/src/marks/auto.js index 14c84fa46b..f07d77c9bf 100644 --- a/src/marks/auto.js +++ b/src/marks/auto.js @@ -107,7 +107,7 @@ export function autoSpec(data, options) { y: { value: yValue ?? null, reduce: yReduce ?? null, - zero: yZero ?? null, + zero: yZero ?? false, ...yOptions }, color: { diff --git a/test/marks/auto-test.js b/test/marks/auto-test.js index 88ccb00036..87266e1ef7 100644 --- a/test/marks/auto-test.js +++ b/test/marks/auto-test.js @@ -6,7 +6,7 @@ it("Plot.autoSpec makes a histogram from a quantitative dimension", () => { assert.deepStrictEqual(Plot.autoSpec(data, {x: "value"}), { fx: null, fy: null, - x: {value: "value", reduce: null}, + x: {value: "value", reduce: null, zero: false}, y: {value: null, reduce: "count", zero: true}, color: {value: null, reduce: null}, size: {value: null, reduce: null}, @@ -19,7 +19,7 @@ it("Plot.autoSpec makes a bar chart from an ordinal dimension", () => { assert.deepStrictEqual(Plot.autoSpec(data, {x: "value", color: "blue"}), { fx: null, fy: null, - x: {value: "value", reduce: null}, + x: {value: "value", reduce: null, zero: false}, y: {value: null, reduce: "count", zero: true}, color: {value: null, reduce: null, color: "blue"}, size: {value: null, reduce: null}, @@ -36,8 +36,8 @@ it("Plot.autoSpec makes a line from a monotonic dimension", () => { assert.deepStrictEqual(Plot.autoSpec(data, {x: "date", y: "value"}), { fx: null, fy: null, - x: {value: "date", reduce: null}, - y: {value: "value", reduce: null}, + x: {value: "date", reduce: null, zero: false}, + y: {value: "value", reduce: null, zero: false}, color: {value: null, reduce: null}, size: {value: null, reduce: null}, mark: "line" @@ -53,8 +53,8 @@ it("Plot.autoSpec makes a dot plot from two quantitative dimensions", () => { assert.deepStrictEqual(Plot.autoSpec(data, {x: "x", y: "y"}), { fx: null, fy: null, - x: {value: "x", reduce: null}, - y: {value: "y", reduce: null}, + x: {value: "x", reduce: null, zero: false}, + y: {value: "y", reduce: null, zero: false}, color: {value: null, reduce: null}, size: {value: null, reduce: null}, mark: "dot" @@ -73,8 +73,8 @@ it("Plot.autoSpec makes a faceted heatmap", () => { assert.deepStrictEqual(Plot.autoSpec(data, {x: "x", y: "y", fy: "f", color: "count"}), { fx: null, fy: "f", - x: {value: "x", reduce: null}, - y: {value: "y", reduce: null}, + x: {value: "x", reduce: null, zero: false}, + y: {value: "y", reduce: null, zero: false}, color: {value: null, reduce: "count"}, size: {value: null, reduce: null}, mark: "bar" From fcce38bb47696a5bbf7e4e75ae874eb2ded9a39a Mon Sep 17 00:00:00 2001 From: Toph Tucker Date: Tue, 21 Mar 2023 23:06:36 -0400 Subject: [PATCH 07/13] prettier --- test/plots/autoplot.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/plots/autoplot.ts b/test/plots/autoplot.ts index 32f2a57d66..b62cf8e5b7 100644 --- a/test/plots/autoplot.ts +++ b/test/plots/autoplot.ts @@ -4,13 +4,13 @@ import * as d3 from "d3"; // Tanner's bug export async function autoLineZero() { const industries = await d3.csv("data/bls-industry-unemployment.csv", d3.autoType); - return Plot.auto(industries, {x: "date", y: {value: "unemployed", zero: true}, color: "industry"}).plot() + return Plot.auto(industries, {x: "date", y: {value: "unemployed", zero: true}, color: "industry"}).plot(); } // Jeff's bug export async function autoBarNonZeroReducer() { const athletes = await d3.csv("data/athletes.csv", d3.autoType); - return Plot.auto(athletes, {x: "date_of_birth", y: { value: "height", reduce: "mean" }, mark: "bar"}).plot() + return Plot.auto(athletes, {x: "date_of_birth", y: {value: "height", reduce: "mean"}, mark: "bar"}).plot(); } export async function autoHistogram() { From 0b3efd3491e27b63d8b0d799a893e2aa88ff78af Mon Sep 17 00:00:00 2001 From: Toph Tucker Date: Wed, 22 Mar 2023 19:42:58 -0400 Subject: [PATCH 08/13] just committing the state after pairing so i have it --- src/marks/auto.js | 109 +++++++++++++++++++++++++++++++++-------- test/plots/autoplot.ts | 17 +++++++ 2 files changed, 106 insertions(+), 20 deletions(-) diff --git a/src/marks/auto.js b/src/marks/auto.js index f07d77c9bf..e01b812d4e 100644 --- a/src/marks/auto.js +++ b/src/marks/auto.js @@ -12,6 +12,8 @@ import {bin, binX, binY} from "../transforms/bin.js"; import {group, groupX, groupY} from "../transforms/group.js"; import {marks} from "../mark.js"; +// TODO: normalize mark to lowercase sooner??? + export function autoSpec(data, options) { options = normalizeOptions(options); @@ -48,10 +50,6 @@ export function autoSpec(data, options) { sizeReduce = "count"; } - // Determine the default zero-ness. - if (xZero === undefined) xZero = isZeroReducer(xReduce) ? true : undefined; - if (yZero === undefined) yZero = isZeroReducer(yReduce) ? true : undefined; - // TODO Shorthand: array of primitives should result in a histogram if (xValue == null && yValue == null) throw new Error("must specify x or y"); if (xReduce != null && yValue == null) throw new Error("reducing x requires y"); @@ -75,24 +73,71 @@ export function autoSpec(data, options) { : null; } - // Update the default zero-ness depending on the inferred mark. - // TODO: Exception for binning? Idk what that code was doing before - if (mark === "area" || mark === "bar" || mark === "rule") { - // Don't set zeros if both dimensions are ordinal, or if there's a - // colorReduce, implying a 2D group/bin - const xOrdinal = isOrdinalReduced(xReduce, (X ??= materializeValue(data, x))); - const yOrdinal = isOrdinalReduced(yReduce, (Y ??= materializeValue(data, y))); - if (!(xOrdinal && yOrdinal) && !colorReduce) { - if (xOrdinal) { - if (Y) yZero ??= true; - } else if (yOrdinal) { - if (X) xZero ??= true; - } else if (Y && isMonotonic(Y)) { - if (X) xZero ??= true; + // Determine the default zero-ness. + if (xZero === undefined) xZero = isZeroReducer(xReduce) ? true : undefined; + if (yZero === undefined) yZero = isZeroReducer(yReduce) ? true : undefined; + + // // TODO: Exception for binning? Idk what that code was doing before + // if ((xZero === undefined || yZero === undefined) && (mark === "area" || mark === "bar" || mark === "rule")) { + // // Don't set zeros if both dimensions are ordinal, or if there's a + // // colorReduce, implying a 2D group/bin + // const xOrdinal = isOrdinalReduced(xReduce, (X ??= materializeValue(data, x))); + // const yOrdinal = isOrdinalReduced(yReduce, (Y ??= materializeValue(data, y))); + // if (!(xOrdinal && yOrdinal) && !colorReduce && !sizeReduce) { + // if (xOrdinal) { + // if (yZero === undefined) yZero = !!Y; + // if (xZero === undefined) xZero = false; + // } else if (yOrdinal) { + // if (X) xZero ??= true; + // } else if (Y && isMonotonic(Y)) { + // if (X) xZero ??= true; + // } else { + // if (Y) yZero ??= true; + // } + // } + // } + + X ??= materializeValue(data, x); + Y ??= materializeValue(data, y); + + switch (`${mark}`.toLowerCase()) { + case "area": + if (yZero) { + // areaY, do nothing + } else if (xZero || (Y && isMonotonic(Y))) { + // areaX + if (xZero === undefined) xZero = true; } else { - if (Y) yZero ??= true; + // areaY + if (yZero === undefined) yZero = true; } - } + break; + case "rule": + if (X) { + // ruleX + if (yZero === undefined) yZero = yValue != null || yReduce != null; + } else { + // ruleY + if (xZero === undefined) xZero = xValue != null || xReduce != null; + } + break; + case "bar": + mark = yZero + ? isOrdinalReduced(xReduce, X) + ? barY + : rectY + : xZero + ? isOrdinalReduced(yReduce, Y) + ? barX + : rectX + : isOrdinalReduced(xReduce, X) && isOrdinalReduced(yReduce, Y) + ? cell + : isOrdinalReduced(xReduce, X) + ? barY + : isOrdinalReduced(yReduce, Y) + ? barX + : rectY; + break; } return { @@ -341,3 +386,27 @@ function isReducer(reduce) { function isHighCardinality(value) { return value ? new InternSet(value).size > value.length >> 1 : false; } + +/* SKETCH + +export function autoSpec(data, options) { + // TODO: allowlist instead of denylist + const {x, y, fx, fy, color, size, mark} = autoImpl() + // don't instantiate! redact and expose information about what would be done + return {x, y, fx, fy, color, size, mark}; +} + +function autoImpl() { + // decide mark impl + // decide transform impl + // everything but instantiating!! + return {...options, markImpl, markOptions, transformImpl, transformOptions} +} + +export function auto(data, options) { + const {markImpl, markOptions, transformImpl, transformOptions, xZero, yZero, fx, fy, colorMode} = autoImpl(options) + // now actually instantiate + return marks(...); +} + +*/ \ No newline at end of file diff --git a/test/plots/autoplot.ts b/test/plots/autoplot.ts index b62cf8e5b7..3671dcc90a 100644 --- a/test/plots/autoplot.ts +++ b/test/plots/autoplot.ts @@ -1,5 +1,22 @@ import * as Plot from "@observablehq/plot"; import * as d3 from "d3"; +import * as htl from "htl"; + +export async function autoMatrix() { + return htl.html`
+ + ${await autoHistogram()} + ${await autoBarZero()} + ${await autoLineZero()} + ${await autoBarNonZeroReducer()} + ${await autoNullReduceOrdinal()} + ${await autoAreaColor()} + ${await autoHeatmap()} + ${await autoRuleZero()} + ${await autoBarMeanZero()} +
` +} + // Tanner's bug export async function autoLineZero() { From 31ba14629684ecfd8cd92d9f3758db65480435e5 Mon Sep 17 00:00:00 2001 From: Toph Tucker Date: Wed, 22 Mar 2023 19:45:44 -0400 Subject: [PATCH 09/13] revert auto file --- src/marks/auto.js | 114 +++++++--------------------------------------- 1 file changed, 16 insertions(+), 98 deletions(-) diff --git a/src/marks/auto.js b/src/marks/auto.js index e01b812d4e..062bd43f46 100644 --- a/src/marks/auto.js +++ b/src/marks/auto.js @@ -5,15 +5,13 @@ import {dot} from "./dot.js"; import {line, lineX, lineY} from "./line.js"; import {ruleX, ruleY} from "./rule.js"; import {barX, barY} from "./bar.js"; -import {rectX, rectY} from "./rect.js"; +import {rect, rectX, rectY} from "./rect.js"; import {cell} from "./cell.js"; import {frame} from "./frame.js"; import {bin, binX, binY} from "../transforms/bin.js"; import {group, groupX, groupY} from "../transforms/group.js"; import {marks} from "../mark.js"; -// TODO: normalize mark to lowercase sooner??? - export function autoSpec(data, options) { options = normalizeOptions(options); @@ -50,6 +48,10 @@ export function autoSpec(data, options) { sizeReduce = "count"; } + // Determine the default zero-ness. + if (xZero === undefined) xZero = isZeroReducer(xReduce) ? true : undefined; + if (yZero === undefined) yZero = isZeroReducer(yReduce) ? true : undefined; + // TODO Shorthand: array of primitives should result in a histogram if (xValue == null && yValue == null) throw new Error("must specify x or y"); if (xReduce != null && yValue == null) throw new Error("reducing x requires y"); @@ -60,7 +62,7 @@ export function autoSpec(data, options) { mark = sizeValue != null || sizeReduce != null ? "dot" - : isZeroReducer(xReduce) || isZeroReducer(yReduce) || colorReduce != null // histogram or heatmap + : xZero || yZero || colorReduce != null // histogram or heatmap ? "bar" : xValue != null && yValue != null ? isOrdinal((X ??= materializeValue(data, x))) || @@ -73,86 +75,19 @@ export function autoSpec(data, options) { : null; } - // Determine the default zero-ness. - if (xZero === undefined) xZero = isZeroReducer(xReduce) ? true : undefined; - if (yZero === undefined) yZero = isZeroReducer(yReduce) ? true : undefined; - - // // TODO: Exception for binning? Idk what that code was doing before - // if ((xZero === undefined || yZero === undefined) && (mark === "area" || mark === "bar" || mark === "rule")) { - // // Don't set zeros if both dimensions are ordinal, or if there's a - // // colorReduce, implying a 2D group/bin - // const xOrdinal = isOrdinalReduced(xReduce, (X ??= materializeValue(data, x))); - // const yOrdinal = isOrdinalReduced(yReduce, (Y ??= materializeValue(data, y))); - // if (!(xOrdinal && yOrdinal) && !colorReduce && !sizeReduce) { - // if (xOrdinal) { - // if (yZero === undefined) yZero = !!Y; - // if (xZero === undefined) xZero = false; - // } else if (yOrdinal) { - // if (X) xZero ??= true; - // } else if (Y && isMonotonic(Y)) { - // if (X) xZero ??= true; - // } else { - // if (Y) yZero ??= true; - // } - // } - // } - - X ??= materializeValue(data, x); - Y ??= materializeValue(data, y); - - switch (`${mark}`.toLowerCase()) { - case "area": - if (yZero) { - // areaY, do nothing - } else if (xZero || (Y && isMonotonic(Y))) { - // areaX - if (xZero === undefined) xZero = true; - } else { - // areaY - if (yZero === undefined) yZero = true; - } - break; - case "rule": - if (X) { - // ruleX - if (yZero === undefined) yZero = yValue != null || yReduce != null; - } else { - // ruleY - if (xZero === undefined) xZero = xValue != null || xReduce != null; - } - break; - case "bar": - mark = yZero - ? isOrdinalReduced(xReduce, X) - ? barY - : rectY - : xZero - ? isOrdinalReduced(yReduce, Y) - ? barX - : rectX - : isOrdinalReduced(xReduce, X) && isOrdinalReduced(yReduce, Y) - ? cell - : isOrdinalReduced(xReduce, X) - ? barY - : isOrdinalReduced(yReduce, Y) - ? barX - : rectY; - break; - } - return { fx: fx ?? null, fy: fy ?? null, x: { value: xValue ?? null, reduce: xReduce ?? null, - zero: xZero ?? false, + ...(xZero !== undefined && {zero: xZero}), // TODO realize default ...xOptions }, y: { value: yValue ?? null, reduce: yReduce ?? null, - zero: yZero ?? false, + ...(yZero !== undefined && {zero: yZero}), // TODO realize default ...yOptions }, color: { @@ -236,7 +171,7 @@ export function auto(data, options) { ? barY : isOrdinalReduced(yReduce, Y) ? barX - : rectY; + : rect; colorMode = "fill"; break; default: @@ -279,6 +214,13 @@ export function auto(data, options) { markOptions = transform(transformOptions, markOptions); } + // If zero-ness is not specified, default based on whether the resolved mark + // type will include a zero baseline. TODO Move this to autoSpec. + if (xZero === undefined) + xZero = X && transform !== binX && (mark === barX || mark === areaX || mark === rectX || mark === ruleY); + if (yZero === undefined) + yZero = Y && transform !== binY && (mark === barY || mark === areaY || mark === rectY || mark === ruleX); + // In the case of filled marks (particularly bars and areas) the frame and // rules should come after the mark; in the case of stroked marks // (particularly dots and lines) they should come before the mark. @@ -386,27 +328,3 @@ function isReducer(reduce) { function isHighCardinality(value) { return value ? new InternSet(value).size > value.length >> 1 : false; } - -/* SKETCH - -export function autoSpec(data, options) { - // TODO: allowlist instead of denylist - const {x, y, fx, fy, color, size, mark} = autoImpl() - // don't instantiate! redact and expose information about what would be done - return {x, y, fx, fy, color, size, mark}; -} - -function autoImpl() { - // decide mark impl - // decide transform impl - // everything but instantiating!! - return {...options, markImpl, markOptions, transformImpl, transformOptions} -} - -export function auto(data, options) { - const {markImpl, markOptions, transformImpl, transformOptions, xZero, yZero, fx, fy, colorMode} = autoImpl(options) - // now actually instantiate - return marks(...); -} - -*/ \ No newline at end of file From 455aeeb6e88dccc48f51f8ef459a41eebdf905ac Mon Sep 17 00:00:00 2001 From: Toph Tucker Date: Wed, 22 Mar 2023 19:53:21 -0400 Subject: [PATCH 10/13] re-fix the original motivating bugs, i think --- src/marks/auto.js | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/marks/auto.js b/src/marks/auto.js index 062bd43f46..90098e18e8 100644 --- a/src/marks/auto.js +++ b/src/marks/auto.js @@ -62,7 +62,7 @@ export function autoSpec(data, options) { mark = sizeValue != null || sizeReduce != null ? "dot" - : xZero || yZero || colorReduce != null // histogram or heatmap + : isZeroReducer(xReduce) || isZeroReducer(yReduce) || colorReduce != null // histogram or heatmap ? "bar" : xValue != null && yValue != null ? isOrdinal((X ??= materializeValue(data, x))) || @@ -171,7 +171,7 @@ export function auto(data, options) { ? barY : isOrdinalReduced(yReduce, Y) ? barX - : rect; + : rectY; colorMode = "fill"; break; default: @@ -217,9 +217,9 @@ export function auto(data, options) { // If zero-ness is not specified, default based on whether the resolved mark // type will include a zero baseline. TODO Move this to autoSpec. if (xZero === undefined) - xZero = X && transform !== binX && (mark === barX || mark === areaX || mark === rectX || mark === ruleY); + xZero = X && transform !== binX && transform !== bin && (mark === barX || mark === areaX || mark === rectX || mark === ruleY); if (yZero === undefined) - yZero = Y && transform !== binY && (mark === barY || mark === areaY || mark === rectY || mark === ruleX); + yZero = Y && transform !== binY && transform !== bin && (mark === barY || mark === areaY || mark === rectY || mark === ruleX); // In the case of filled marks (particularly bars and areas) the frame and // rules should come after the mark; in the case of stroked marks @@ -253,6 +253,7 @@ function isMonotonic(values) { // (but note that they can also be specified as a {transform} object such as // Plot.identity). We don’t support reducers for the faceting, but for symmetry // with x and y we allow facets to be specified as {value} objects. +// TODO Normalize mark name to lowercase? function normalizeOptions({x, y, color, size, fx, fy, mark} = {}) { if (!isOptions(x)) x = makeOptions(x); if (!isOptions(y)) y = makeOptions(y); From 4c970d0ac8926af21ac7fd89536e66929c244b73 Mon Sep 17 00:00:00 2001 From: Toph Tucker Date: Wed, 22 Mar 2023 23:14:51 -0400 Subject: [PATCH 11/13] autoImpl --- src/marks/auto.js | 164 ++++++++++++++++++++++++---------------------- 1 file changed, 86 insertions(+), 78 deletions(-) diff --git a/src/marks/auto.js b/src/marks/auto.js index 90098e18e8..843e8d3a8a 100644 --- a/src/marks/auto.js +++ b/src/marks/auto.js @@ -5,7 +5,7 @@ import {dot} from "./dot.js"; import {line, lineX, lineY} from "./line.js"; import {ruleX, ruleY} from "./rule.js"; import {barX, barY} from "./bar.js"; -import {rect, rectX, rectY} from "./rect.js"; +import {rectX, rectY} from "./rect.js"; import {cell} from "./cell.js"; import {frame} from "./frame.js"; import {bin, binX, binY} from "../transforms/bin.js"; @@ -13,8 +13,23 @@ import {group, groupX, groupY} from "../transforms/group.js"; import {marks} from "../mark.js"; export function autoSpec(data, options) { + const {x, y, fx, fy, color, size, mark} = autoImpl(data, options); + return {x, y, fx, fy, color, size, mark}; +} + +function autoImpl(data, options) { options = normalizeOptions(options); + // Greedily materialize columns for type inference; we’ll need them anyway to + // plot! Note that we don’t apply any type inference to the fx and fy + // channels, if present; these are always ordinal (at least for now). + const {x, y, color, size} = options; + const X = materializeValue(data, x); + const Y = materializeValue(data, y); + const C = materializeValue(data, color); + const S = materializeValue(data, size); + + // Compute the default options. let { fx, fy, @@ -25,10 +40,6 @@ export function autoSpec(data, options) { mark } = options; - // Lazily materialize x and y columns for type inference, if needed. - const {x, y} = options; - let X, Y; - // Determine the default reducer, if any. if (xReduce === undefined) xReduce = yReduce == null && xValue == null && sizeValue == null && yValue != null ? "count" : null; @@ -42,8 +53,8 @@ export function autoSpec(data, options) { colorReduce == null && xReduce == null && yReduce == null && - (xValue == null || isOrdinal((X ??= materializeValue(data, x)))) && - (yValue == null || isOrdinal((Y ??= materializeValue(data, y)))) + (xValue == null || isOrdinal(X)) && + (yValue == null || isOrdinal(Y)) ) { sizeReduce = "count"; } @@ -65,9 +76,7 @@ export function autoSpec(data, options) { : isZeroReducer(xReduce) || isZeroReducer(yReduce) || colorReduce != null // histogram or heatmap ? "bar" : xValue != null && yValue != null - ? isOrdinal((X ??= materializeValue(data, x))) || - isOrdinal((Y ??= materializeValue(data, y))) || - (xReduce == null && yReduce == null && !isMonotonic(X) && !isMonotonic(Y)) + ? isOrdinal(X) || isOrdinal(Y) || (xReduce == null && yReduce == null && !isMonotonic(X) && !isMonotonic(Y)) ? "dot" : "line" : xValue != null || yValue != null @@ -75,89 +84,33 @@ export function autoSpec(data, options) { : null; } - return { - fx: fx ?? null, - fy: fy ?? null, - x: { - value: xValue ?? null, - reduce: xReduce ?? null, - ...(xZero !== undefined && {zero: xZero}), // TODO realize default - ...xOptions - }, - y: { - value: yValue ?? null, - reduce: yReduce ?? null, - ...(yZero !== undefined && {zero: yZero}), // TODO realize default - ...yOptions - }, - color: { - value: colorValue ?? null, - reduce: colorReduce ?? null, - ...(colorColor !== undefined && {color: colorColor}) - }, - size: { - value: sizeValue ?? null, - reduce: sizeReduce ?? null - }, - mark - }; -} - -export function auto(data, options) { - options = normalizeOptions(options); - - // Greedily materialize columns for type inference; we’ll need them anyway to - // plot! Note that we don’t apply any type inference to the fx and fy - // channels, if present; these are always ordinal (at least for now). - const {x, y, color, size} = options; - const X = materializeValue(data, x); - const Y = materializeValue(data, y); - const C = materializeValue(data, color); - const S = materializeValue(data, size); - - // Compute the default options via autoSpec. - let { - fx, - fy, - x: {reduce: xReduce, zero: xZero, ...xOptions}, - y: {reduce: yReduce, zero: yZero, ...yOptions}, - color: {color: colorColor, reduce: colorReduce}, - size: {reduce: sizeReduce}, - mark - } = autoSpec(data, { - ...options, - x: {...x, value: X}, - y: {...y, value: Y}, - color: {...color, value: C}, - size: {...size, value: S} - }); - let Z; // may be set to null to disable series-by-color for line and area let colorMode; // "fill" or "stroke" // Determine the mark implementation. + let markImpl; if (mark != null) { switch (`${mark}`.toLowerCase()) { case "dot": - mark = dot; + markImpl = dot; colorMode = "stroke"; break; case "line": - mark = X && Y ? line : X ? lineX : lineY; // 1d line by index + markImpl = X && Y ? line : X ? lineX : lineY; // 1d line by index colorMode = "stroke"; if (isHighCardinality(C)) Z = null; // TODO only if z not set by user break; case "area": - mark = yZero ? areaY : xZero || (Y && isMonotonic(Y)) ? areaX : areaY; // favor areaY if unsure + markImpl = yZero ? areaY : xZero || (Y && isMonotonic(Y)) ? areaX : areaY; // favor areaY if unsure colorMode = "fill"; if (isHighCardinality(C)) Z = null; // TODO only if z not set by user break; case "rule": - mark = X ? ruleX : ruleY; + markImpl = X ? ruleX : ruleY; colorMode = "stroke"; break; case "bar": - mark = yZero + markImpl = yZero ? isOrdinalReduced(xReduce, X) ? barY : rectY @@ -211,23 +164,78 @@ export function auto(data, options) { if (transform) { if (transform === bin || transform === binX) markOptions.x = {value: X, ...xOptions}; if (transform === bin || transform === binY) markOptions.y = {value: Y, ...yOptions}; - markOptions = transform(transformOptions, markOptions); } // If zero-ness is not specified, default based on whether the resolved mark - // type will include a zero baseline. TODO Move this to autoSpec. + // type will include a zero baseline. if (xZero === undefined) - xZero = X && transform !== binX && transform !== bin && (mark === barX || mark === areaX || mark === rectX || mark === ruleY); + xZero = + X && + transform !== bin && + transform !== binX && + (markImpl === barX || markImpl === areaX || markImpl === rectX || markImpl === ruleY); if (yZero === undefined) - yZero = Y && transform !== binY && transform !== bin && (mark === barY || mark === areaY || mark === rectY || mark === ruleX); + yZero = + Y && + transform !== bin && + transform !== binY && + (markImpl === barY || markImpl === areaY || markImpl === rectY || markImpl === ruleX); + + return { + fx: fx ?? null, + fy: fy ?? null, + x: { + value: xValue ?? null, + reduce: xReduce ?? null, + zero: xZero ?? false, + ...xOptions + }, + y: { + value: yValue ?? null, + reduce: yReduce ?? null, + zero: yZero ?? false, + ...yOptions + }, + color: { + value: colorValue ?? null, + reduce: colorReduce ?? null, + ...(colorColor !== undefined && {color: colorColor}) + }, + size: { + value: sizeValue ?? null, + reduce: sizeReduce ?? null + }, + mark, + markImpl, + markOptions, + transform, + transformOptions, + colorMode + }; +} + +export function auto(data, options) { + let { + fx, + fy, + x: {zero: xZero}, + y: {zero: yZero}, + markImpl, + markOptions, + transform, + transformOptions, + colorMode + } = autoImpl(data, options); + + if (transform) markOptions = transform(transformOptions, markOptions); // In the case of filled marks (particularly bars and areas) the frame and // rules should come after the mark; in the case of stroked marks // (particularly dots and lines) they should come before the mark. const frames = fx != null || fy != null ? frame({strokeOpacity: 0.1}) : null; const rules = [xZero ? ruleX([0]) : null, yZero ? ruleY([0]) : null]; - mark = mark(data, markOptions); - return colorMode === "stroke" ? marks(frames, rules, mark) : marks(frames, mark, rules); + markImpl = markImpl(data, markOptions); + return colorMode === "stroke" ? marks(frames, rules, markImpl) : marks(frames, markImpl, rules); } // TODO What about sorted within series? From 1ab57eac8c279f6fd4dccb6b8d2a5b4c539e3971 Mon Sep 17 00:00:00 2001 From: Toph Tucker Date: Wed, 22 Mar 2023 23:48:34 -0400 Subject: [PATCH 12/13] rm autoplot matrix test bc it didnt work with test runner --- test/output/autoBarZero.svg | 127 ------------------ test/output/autoDotZero.svg | 127 ++++++++++++++++++ ...toBarMeanZero.svg => autoLineMeanZero.svg} | 37 ++--- test/plots/autoplot.ts | 39 ++---- 4 files changed, 147 insertions(+), 183 deletions(-) delete mode 100644 test/output/autoBarZero.svg create mode 100644 test/output/autoDotZero.svg rename test/output/{autoBarMeanZero.svg => autoLineMeanZero.svg} (55%) diff --git a/test/output/autoBarZero.svg b/test/output/autoBarZero.svg deleted file mode 100644 index 5b63abcdf7..0000000000 --- a/test/output/autoBarZero.svg +++ /dev/null @@ -1,127 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - A - B - C - D - E - F - G - H - I - J - K - L - M - N - O - P - Q - R - S - T - U - V - W - X - Y - Z - - - letter - - - - - - - - - - - - 0.00 - 0.02 - 0.04 - 0.06 - 0.08 - 0.10 - 0.12 - - - frequency → - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/test/output/autoDotZero.svg b/test/output/autoDotZero.svg new file mode 100644 index 0000000000..a1837e91d1 --- /dev/null +++ b/test/output/autoDotZero.svg @@ -0,0 +1,127 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + A + B + C + D + E + F + G + H + I + J + K + L + M + N + O + P + Q + R + S + T + U + V + W + X + Y + Z + + + letter + + + + + + + + + + + + 0.00 + 0.02 + 0.04 + 0.06 + 0.08 + 0.10 + 0.12 + + + frequency → + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/test/output/autoBarMeanZero.svg b/test/output/autoLineMeanZero.svg similarity index 55% rename from test/output/autoBarMeanZero.svg rename to test/output/autoLineMeanZero.svg index 5e4b404754..09fed388cc 100644 --- a/test/output/autoBarMeanZero.svg +++ b/test/output/autoLineMeanZero.svg @@ -47,38 +47,19 @@ ↑ temp_max - - - - - + + + - 2012 - 2013 - 2014 - 2015 - 2016 - - - - - - - - - - - - - - - - - - + 2013 + 2014 + 2015 + + + \ No newline at end of file diff --git a/test/plots/autoplot.ts b/test/plots/autoplot.ts index 3671dcc90a..a5a63c169c 100644 --- a/test/plots/autoplot.ts +++ b/test/plots/autoplot.ts @@ -1,30 +1,13 @@ import * as Plot from "@observablehq/plot"; import * as d3 from "d3"; -import * as htl from "htl"; -export async function autoMatrix() { - return htl.html`
- - ${await autoHistogram()} - ${await autoBarZero()} - ${await autoLineZero()} - ${await autoBarNonZeroReducer()} - ${await autoNullReduceOrdinal()} - ${await autoAreaColor()} - ${await autoHeatmap()} - ${await autoRuleZero()} - ${await autoBarMeanZero()} -
` -} - - -// Tanner's bug +// Tanner's bug https://github.com/observablehq/plot/issues/1365 export async function autoLineZero() { const industries = await d3.csv("data/bls-industry-unemployment.csv", d3.autoType); return Plot.auto(industries, {x: "date", y: {value: "unemployed", zero: true}, color: "industry"}).plot(); } -// Jeff's bug +// Jeff's bug https://github.com/observablehq/plot/issues/1340 export async function autoBarNonZeroReducer() { const athletes = await d3.csv("data/athletes.csv", d3.autoType); return Plot.auto(athletes, {x: "date_of_birth", y: {value: "height", reduce: "mean"}, mark: "bar"}).plot(); @@ -110,14 +93,14 @@ export async function autoDotOrdCont() { return Plot.auto(penguins, {x: "culmen_length_mm", y: "species"}).plot(); } -export async function autoBar() { +export async function autoDotZero() { const alphabet = await d3.csv("data/alphabet.csv", d3.autoType); - return Plot.auto(alphabet, {x: "frequency", y: "letter", mark: "bar"}).plot(); + return Plot.auto(alphabet, {x: {value: "frequency", zero: true}, y: "letter"}).plot(); } -export async function autoBarZero() { +export async function autoBar() { const alphabet = await d3.csv("data/alphabet.csv", d3.autoType); - return Plot.auto(alphabet, {x: {value: "frequency", zero: true}, y: "letter"}).plot(); + return Plot.auto(alphabet, {x: "frequency", y: "letter", mark: "bar"}).plot(); } export async function autoConnectedScatterplot() { @@ -226,11 +209,6 @@ export async function autoBarStackColorConstant() { return Plot.auto(athletes, {x: "height", color: "gold"}).plot(); } -export async function autoBarMeanZero() { - const weather = await d3.csv("data/seattle-weather.csv", d3.autoType); - return Plot.auto(weather, {x: "date", y: {value: "temp_max", reduce: "mean", zero: true}}).plot(); -} - export async function autoBarMode() { const penguins = await d3.csv("data/penguins.csv", d3.autoType); return Plot.auto(penguins, {x: "island", y: {value: "species", reduce: "mode"}, mark: "bar"}).plot(); @@ -241,6 +219,11 @@ export async function autoLineMean() { return Plot.auto(weather, {x: "date", y: {value: "temp_max", reduce: "mean"}}).plot(); } +export async function autoLineMeanZero() { + const weather = await d3.csv("data/seattle-weather.csv", d3.autoType); + return Plot.auto(weather, {x: "date", y: {value: "temp_max", reduce: "mean", zero: true}}).plot(); +} + export async function autoLineMeanColor() { const athletes = await d3.csv("data/athletes.csv", d3.autoType); return Plot.auto(athletes, {x: "date_of_birth", y: {value: "height", reduce: "mean"}, color: "sex"}).plot(); From 6ca436301e21f8df4a07fa51a9e94ea49c1f7856 Mon Sep 17 00:00:00 2001 From: Toph Tucker Date: Thu, 23 Mar 2023 00:14:43 -0400 Subject: [PATCH 13/13] WIP: compile auto mark to explicit marks --- src/index.js | 2 +- src/marks/auto.js | 28 ++++++++++++++++++++++++++++ test/marks/auto-test.js | 13 +++++++++++++ 3 files changed, 42 insertions(+), 1 deletion(-) diff --git a/src/index.js b/src/index.js index 330fc618c2..953d7eaeef 100644 --- a/src/index.js +++ b/src/index.js @@ -2,7 +2,7 @@ export {plot} from "./plot.js"; export {Mark, marks} from "./mark.js"; export {Area, area, areaX, areaY} from "./marks/area.js"; export {Arrow, arrow} from "./marks/arrow.js"; -export {auto, autoSpec} from "./marks/auto.js"; +export {auto, autoSpec, autoCompile} from "./marks/auto.js"; export {axisX, axisY, axisFx, axisFy, gridX, gridY, gridFx, gridFy} from "./marks/axis.js"; export {BarX, BarY, barX, barY} from "./marks/bar.js"; export {boxX, boxY} from "./marks/box.js"; diff --git a/src/marks/auto.js b/src/marks/auto.js index 843e8d3a8a..940cc560da 100644 --- a/src/marks/auto.js +++ b/src/marks/auto.js @@ -12,6 +12,34 @@ import {bin, binX, binY} from "../transforms/bin.js"; import {group, groupX, groupY} from "../transforms/group.js"; import {marks} from "../mark.js"; +export function autoCompile(data, options) { + let { + fx, + fy, + x: {zero: xZero}, + y: {zero: yZero}, + markImpl, + markOptions, + transform, + transformOptions, + colorMode + } = autoImpl(data, options); + + const markOptionsString = transform + ? `Plot.${transform.name}(${JSON.stringify(transformOptions)}, ${JSON.stringify(markOptions)})` + : JSON.stringify(markOptions); + const frame = fx != null || fy != null ? `Plot.frame({strokeOpacity: 0.1})` : null; + const rules = [xZero ? `Plot.ruleX([0])` : null, yZero ? `Plot.ruleY([0])` : null]; + const mark = `Plot.${markImpl.name}(data, ${markOptionsString})`; + return `Plot.plot({ + marks: [ + ${(colorMode === "stroke" ? [frame, ...rules, mark] : [frame, mark, ...rules]) + .filter((mark) => mark) + .join(",\n ")} + ] +})`; +} + export function autoSpec(data, options) { const {x, y, fx, fy, color, size, mark} = autoImpl(data, options); return {x, y, fx, fy, color, size, mark}; diff --git a/test/marks/auto-test.js b/test/marks/auto-test.js index 87266e1ef7..c9a0a4b187 100644 --- a/test/marks/auto-test.js +++ b/test/marks/auto-test.js @@ -1,6 +1,19 @@ import * as Plot from "@observablehq/plot"; import assert from "assert"; +it("Plot.autoCompile compiles to explicit marks", () => { + const data = [{value: 1}, {value: 1}, {value: 38}]; + assert.strictEqual( + Plot.autoCompile(data, {x: "value"}), + `Plot.plot({ + marks: [ + Plot.rectY(data, Plot.binX({"y":"count"}, {"x":{"value":[1,1,38]}})), + Plot.ruleY([0]) + ] +})` + ); +}); + it("Plot.autoSpec makes a histogram from a quantitative dimension", () => { const data = [{value: 1}, {value: 1}, {value: 38}]; assert.deepStrictEqual(Plot.autoSpec(data, {x: "value"}), {