From 604b25cd9018bf53a443b135ecaacab887d0d90e Mon Sep 17 00:00:00 2001 From: Younghoon Kim Date: Wed, 19 Jul 2023 22:03:21 -0700 Subject: [PATCH] fix: Area with plain quantitative fields on both axes get stacked by default (#9018) Co-authored-by: GitHub Actions Bot --- examples/compiled/area_cumulative_freq.svg | 2 +- .../compiled/area_cumulative_freq.vg.json | 34 ++- examples/compiled/area_density.vg.json | 25 ++- examples/compiled/area_horizon.vg.json | 58 ++++- examples/compiled/area_params.vg.json | 24 +- .../compiled/stacked_area_without_agg.png | Bin 0 -> 6675 bytes .../compiled/stacked_area_without_agg.svg | 1 + .../compiled/stacked_area_without_agg.vg.json | 209 ++++++++++++++++++ examples/compiled/wheat_wages.vg.json | 69 ++++-- examples/specs/area_cumulative_freq.vl.json | 6 +- ...tacked_area_without_agg_normalized.vl.json | 31 +++ .../specs/stacked_area_without_agg.vl.json | 19 ++ src/normalize/pathoverlay.ts | 5 +- src/stack.ts | 2 +- test/normalize/pathoverlay.test.ts | 36 +++ test/stack.test.ts | 15 ++ 16 files changed, 490 insertions(+), 46 deletions(-) create mode 100644 examples/compiled/stacked_area_without_agg.png create mode 100644 examples/compiled/stacked_area_without_agg.svg create mode 100644 examples/compiled/stacked_area_without_agg.vg.json create mode 100644 examples/specs/normalized/stacked_area_without_agg_normalized.vl.json create mode 100644 examples/specs/stacked_area_without_agg.vl.json diff --git a/examples/compiled/area_cumulative_freq.svg b/examples/compiled/area_cumulative_freq.svg index 7c3c368d381..5c526a87b24 100644 --- a/examples/compiled/area_cumulative_freq.svg +++ b/examples/compiled/area_cumulative_freq.svg @@ -1 +1 @@ -246810IMDB Rating05001,0001,5002,0002,5003,0003,500Cumulative Count \ No newline at end of file +246810IMDB Rating05001,0001,5002,0002,5003,0003,500Cumulative Count \ No newline at end of file diff --git a/examples/compiled/area_cumulative_freq.vg.json b/examples/compiled/area_cumulative_freq.vg.json index db79cf7e92e..3b03a864e61 100644 --- a/examples/compiled/area_cumulative_freq.vg.json +++ b/examples/compiled/area_cumulative_freq.vg.json @@ -11,14 +11,37 @@ "url": "data/movies.json", "format": {"type": "json", "parse": {"IMDB Rating": "number"}}, "transform": [ + { + "type": "aggregate", + "groupby": ["IMDB Rating"], + "ops": ["count"], + "fields": [null], + "as": ["count"] + }, { "type": "window", "params": [null], "as": ["Cumulative Count"], - "ops": ["count"], + "ops": ["sum"], "fields": ["count"], "sort": {"field": ["IMDB Rating"], "order": ["ascending"]}, "frame": [null, 0] + }, + { + "type": "impute", + "field": "Cumulative Count", + "groupby": [], + "key": "IMDB Rating", + "method": "value", + "value": 0 + }, + { + "type": "stack", + "groupby": ["IMDB Rating"], + "field": "Cumulative Count", + "sort": {"field": [], "order": []}, + "as": ["Cumulative Count_start", "Cumulative Count_end"], + "offset": "zero" } ] } @@ -38,8 +61,8 @@ "signal": "\"IMDB Rating: \" + (format(datum[\"IMDB Rating\"], \"\")) + \"; Cumulative Count: \" + (format(datum[\"Cumulative Count\"], \"\"))" }, "x": {"scale": "x", "field": "IMDB Rating"}, - "y": {"scale": "y", "field": "Cumulative Count"}, - "y2": {"scale": "y", "value": 0}, + "y": {"scale": "y", "field": "Cumulative Count_end"}, + "y2": {"scale": "y", "field": "Cumulative Count_start"}, "defined": { "signal": "isValid(datum[\"IMDB Rating\"]) && isFinite(+datum[\"IMDB Rating\"]) && isValid(datum[\"Cumulative Count\"]) && isFinite(+datum[\"Cumulative Count\"])" } @@ -59,7 +82,10 @@ { "name": "y", "type": "linear", - "domain": {"data": "source_0", "field": "Cumulative Count"}, + "domain": { + "data": "source_0", + "fields": ["Cumulative Count_start", "Cumulative Count_end"] + }, "range": [{"signal": "height"}, 0], "nice": true, "zero": true diff --git a/examples/compiled/area_density.vg.json b/examples/compiled/area_density.vg.json index 9a2296166c9..8fc8c8ca43c 100644 --- a/examples/compiled/area_density.vg.json +++ b/examples/compiled/area_density.vg.json @@ -16,6 +16,22 @@ "field": "IMDB Rating", "bandwidth": 0.3, "as": ["value", "density"] + }, + { + "type": "impute", + "field": "density", + "groupby": [], + "key": "value", + "method": "value", + "value": 0 + }, + { + "type": "stack", + "groupby": ["value"], + "field": "density", + "sort": {"field": [], "order": []}, + "as": ["density_start", "density_end"], + "offset": "zero" } ] } @@ -35,8 +51,8 @@ "signal": "\"IMDB Rating: \" + (format(datum[\"value\"], \"\")) + \"; density: \" + (format(datum[\"density\"], \"\"))" }, "x": {"scale": "x", "field": "value"}, - "y": {"scale": "y", "field": "density"}, - "y2": {"scale": "y", "value": 0}, + "y": {"scale": "y", "field": "density_end"}, + "y2": {"scale": "y", "field": "density_start"}, "defined": { "signal": "isValid(datum[\"value\"]) && isFinite(+datum[\"value\"]) && isValid(datum[\"density\"]) && isFinite(+datum[\"density\"])" } @@ -56,7 +72,10 @@ { "name": "y", "type": "linear", - "domain": {"data": "source_0", "field": "density"}, + "domain": { + "data": "source_0", + "fields": ["density_start", "density_end"] + }, "range": [{"signal": "height"}, 0], "nice": true, "zero": true diff --git a/examples/compiled/area_horizon.vg.json b/examples/compiled/area_horizon.vg.json index f57b3a2db2c..f879dd7e2ef 100644 --- a/examples/compiled/area_horizon.vg.json +++ b/examples/compiled/area_horizon.vg.json @@ -42,7 +42,47 @@ { "name": "data_1", "source": "data_0", - "transform": [{"type": "formula", "expr": "datum.y - 50", "as": "ny"}] + "transform": [ + { + "type": "impute", + "field": "y", + "groupby": [], + "key": "x", + "method": "value", + "value": 0 + }, + { + "type": "stack", + "groupby": ["x"], + "field": "y", + "sort": {"field": [], "order": []}, + "as": ["y_start", "y_end"], + "offset": "zero" + } + ] + }, + { + "name": "data_2", + "source": "data_0", + "transform": [ + {"type": "formula", "expr": "datum.y - 50", "as": "ny"}, + { + "type": "impute", + "field": "ny", + "groupby": [], + "key": "x", + "method": "value", + "value": 0 + }, + { + "type": "stack", + "groupby": ["x"], + "field": "ny", + "sort": {"field": [], "order": []}, + "as": ["ny_start", "ny_end"], + "offset": "zero" + } + ] } ], "marks": [ @@ -52,7 +92,7 @@ "clip": true, "style": ["area"], "sort": {"field": "datum[\"x\"]"}, - "from": {"data": "data_0"}, + "from": {"data": "data_1"}, "encode": { "update": { "opacity": {"value": 0.6}, @@ -62,8 +102,8 @@ "signal": "\"x: \" + (format(datum[\"x\"], \"\")) + \"; y: \" + (format(datum[\"y\"], \"\"))" }, "x": {"scale": "x", "field": "x"}, - "y": {"scale": "y", "field": "y"}, - "y2": {"scale": "y", "value": 0}, + "y": {"scale": "y", "field": "y_end"}, + "y2": {"scale": "y", "field": "y_start"}, "defined": { "signal": "isValid(datum[\"x\"]) && isFinite(+datum[\"x\"]) && isValid(datum[\"y\"]) && isFinite(+datum[\"y\"])" } @@ -76,7 +116,7 @@ "clip": true, "style": ["area"], "sort": {"field": "datum[\"x\"]"}, - "from": {"data": "data_1"}, + "from": {"data": "data_2"}, "encode": { "update": { "orient": {"value": "vertical"}, @@ -86,8 +126,8 @@ "signal": "\"x: \" + (format(datum[\"x\"], \"\")) + \"; ny: \" + (format(datum[\"ny\"], \"\"))" }, "x": {"scale": "x", "field": "x"}, - "y": {"scale": "y", "field": "ny"}, - "y2": {"scale": "y", "value": 0}, + "y": {"scale": "y", "field": "ny_end"}, + "y2": {"scale": "y", "field": "ny_start"}, "defined": { "signal": "isValid(datum[\"x\"]) && isFinite(+datum[\"x\"]) && isValid(datum[\"ny\"]) && isFinite(+datum[\"ny\"])" } @@ -101,8 +141,8 @@ "type": "linear", "domain": { "fields": [ - {"data": "data_0", "field": "x"}, - {"data": "data_1", "field": "x"} + {"data": "data_1", "field": "x"}, + {"data": "data_2", "field": "x"} ] }, "range": [0, {"signal": "width"}], diff --git a/examples/compiled/area_params.vg.json b/examples/compiled/area_params.vg.json index a8e1e758d6a..5ce094d0a34 100644 --- a/examples/compiled/area_params.vg.json +++ b/examples/compiled/area_params.vg.json @@ -21,7 +21,23 @@ "name": "data_0", "source": "source_0", "transform": [ - {"type": "formula", "expr": "toNumber(datum[\"u\"])", "as": "u"} + {"type": "formula", "expr": "toNumber(datum[\"u\"])", "as": "u"}, + { + "type": "impute", + "field": "v", + "groupby": [], + "key": "u", + "method": "value", + "value": 0 + }, + { + "type": "stack", + "groupby": ["u"], + "field": "v", + "sort": {"field": [], "order": []}, + "as": ["v_start", "v_end"], + "offset": "zero" + } ] } ], @@ -67,8 +83,8 @@ "signal": "\"u: \" + (format(datum[\"u\"], \"\")) + \"; v: \" + (format(datum[\"v\"], \"\"))" }, "x": {"scale": "x", "field": "u"}, - "y": {"scale": "y", "field": "v"}, - "y2": {"scale": "y", "value": 0}, + "y": {"scale": "y", "field": "v_end"}, + "y2": {"scale": "y", "field": "v_start"}, "defined": { "signal": "isValid(datum[\"u\"]) && isFinite(+datum[\"u\"]) && isValid(datum[\"v\"]) && isFinite(+datum[\"v\"])" } @@ -88,7 +104,7 @@ { "name": "y", "type": "linear", - "domain": {"data": "data_0", "field": "v"}, + "domain": {"data": "data_0", "fields": ["v_start", "v_end"]}, "range": [{"signal": "height"}, 0], "nice": true, "zero": true diff --git a/examples/compiled/stacked_area_without_agg.png b/examples/compiled/stacked_area_without_agg.png new file mode 100644 index 0000000000000000000000000000000000000000..9c910e70a54e73bf14896878fd45812b522f7af8 GIT binary patch literal 6675 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 literal 0 HcmV?d00001 diff --git a/examples/compiled/stacked_area_without_agg.svg b/examples/compiled/stacked_area_without_agg.svg new file mode 100644 index 00000000000..dd60fefb225 --- /dev/null +++ b/examples/compiled/stacked_area_without_agg.svg @@ -0,0 +1 @@ +1.01.52.02.53.0a012345bc1c2c \ No newline at end of file diff --git a/examples/compiled/stacked_area_without_agg.vg.json b/examples/compiled/stacked_area_without_agg.vg.json new file mode 100644 index 00000000000..d41ae2e9b22 --- /dev/null +++ b/examples/compiled/stacked_area_without_agg.vg.json @@ -0,0 +1,209 @@ +{ + "$schema": "https://vega.github.io/schema/vega/v5.json", + "background": "white", + "padding": 5, + "width": 200, + "height": 200, + "style": "cell", + "data": [ + { + "name": "source_0", + "values": [ + {"a": 1, "b": 2, "c": "c1"}, + {"a": 2, "b": 3, "c": "c1"}, + {"a": 3, "b": 1, "c": "c1"}, + {"a": 1, "b": 2, "c": "c2"}, + {"a": 3, "b": 1, "c": "c2"}, + {"a": 2, "b": 2, "c": "c2"} + ] + }, + { + "name": "data_0", + "source": "source_0", + "transform": [ + {"type": "formula", "expr": "toNumber(datum[\"a\"])", "as": "a"}, + { + "type": "impute", + "field": "b", + "groupby": ["c"], + "key": "a", + "method": "value", + "value": 0 + }, + { + "type": "stack", + "groupby": ["a"], + "field": "b", + "sort": {"field": ["c"], "order": ["descending"]}, + "as": ["b_start", "b_end"], + "offset": "zero" + } + ] + } + ], + "marks": [ + { + "name": "layer_0_pathgroup", + "type": "group", + "from": { + "facet": { + "name": "faceted_path_layer_0_main", + "data": "data_0", + "groupby": ["c"] + } + }, + "encode": { + "update": { + "width": {"field": {"group": "width"}}, + "height": {"field": {"group": "height"}} + } + }, + "marks": [ + { + "name": "layer_0_marks", + "type": "area", + "style": ["area"], + "sort": {"field": "datum[\"a\"]"}, + "from": {"data": "faceted_path_layer_0_main"}, + "encode": { + "update": { + "opacity": {"value": 0.7}, + "orient": {"value": "vertical"}, + "fill": {"scale": "color", "field": "c"}, + "description": { + "signal": "\"a: \" + (format(datum[\"a\"], \"\")) + \"; b: \" + (format(datum[\"b\"], \"\")) + \"; c: \" + (isValid(datum[\"c\"]) ? datum[\"c\"] : \"\"+datum[\"c\"])" + }, + "x": {"scale": "x", "field": "a"}, + "y": {"scale": "y", "field": "b_end"}, + "y2": {"scale": "y", "field": "b_start"}, + "defined": { + "signal": "isValid(datum[\"a\"]) && isFinite(+datum[\"a\"]) && isValid(datum[\"b\"]) && isFinite(+datum[\"b\"])" + } + } + } + } + ] + }, + { + "name": "layer_1_pathgroup", + "type": "group", + "from": { + "facet": { + "name": "faceted_path_layer_1_main", + "data": "data_0", + "groupby": ["c"] + } + }, + "encode": { + "update": { + "width": {"field": {"group": "width"}}, + "height": {"field": {"group": "height"}} + } + }, + "marks": [ + { + "name": "layer_1_marks", + "type": "line", + "style": ["line"], + "sort": {"field": "datum[\"a\"]"}, + "from": {"data": "faceted_path_layer_1_main"}, + "encode": { + "update": { + "stroke": {"scale": "color", "field": "c"}, + "description": { + "signal": "\"a: \" + (format(datum[\"a\"], \"\")) + \"; b: \" + (format(datum[\"b\"], \"\")) + \"; c: \" + (isValid(datum[\"c\"]) ? datum[\"c\"] : \"\"+datum[\"c\"])" + }, + "x": {"scale": "x", "field": "a"}, + "y": {"scale": "y", "field": "b_end"}, + "defined": { + "signal": "isValid(datum[\"a\"]) && isFinite(+datum[\"a\"]) && isValid(datum[\"b\"]) && isFinite(+datum[\"b\"])" + } + } + } + } + ] + } + ], + "scales": [ + { + "name": "x", + "type": "linear", + "domain": {"data": "data_0", "field": "a"}, + "range": [0, {"signal": "width"}], + "nice": true, + "zero": false + }, + { + "name": "y", + "type": "linear", + "domain": {"data": "data_0", "fields": ["b_start", "b_end"]}, + "range": [{"signal": "height"}, 0], + "nice": true, + "zero": true + }, + { + "name": "color", + "type": "ordinal", + "domain": {"data": "data_0", "field": "c", "sort": true}, + "range": "category" + } + ], + "axes": [ + { + "scale": "x", + "orient": "bottom", + "gridScale": "y", + "grid": true, + "tickCount": {"signal": "ceil(width/40)"}, + "domain": false, + "labels": false, + "aria": false, + "maxExtent": 0, + "minExtent": 0, + "ticks": false, + "zindex": 0 + }, + { + "scale": "y", + "orient": "left", + "gridScale": "x", + "grid": true, + "tickCount": {"signal": "ceil(height/40)"}, + "domain": false, + "labels": false, + "aria": false, + "maxExtent": 0, + "minExtent": 0, + "ticks": false, + "zindex": 0 + }, + { + "scale": "x", + "orient": "bottom", + "grid": false, + "title": "a", + "labelFlush": true, + "labelOverlap": true, + "tickCount": {"signal": "ceil(width/40)"}, + "zindex": 0 + }, + { + "scale": "y", + "orient": "left", + "grid": false, + "title": "b", + "labelOverlap": true, + "tickCount": {"signal": "ceil(height/40)"}, + "zindex": 0 + } + ], + "legends": [ + { + "fill": "color", + "symbolType": "circle", + "title": "c", + "encode": {"symbols": {"update": {"opacity": {"value": 0.7}}}}, + "stroke": "color" + } + ] +} diff --git a/examples/compiled/wheat_wages.vg.json b/examples/compiled/wheat_wages.vg.json index d59661f0858..5a60792a744 100644 --- a/examples/compiled/wheat_wages.vg.json +++ b/examples/compiled/wheat_wages.vg.json @@ -30,7 +30,7 @@ ] }, { - "name": "data_0", + "name": "data_1", "source": "source_0", "transform": [ { @@ -48,7 +48,29 @@ ] }, { - "name": "data_1", + "name": "data_2", + "source": "source_0", + "transform": [ + { + "type": "impute", + "field": "wages", + "groupby": [], + "key": "year", + "method": "value", + "value": 0 + }, + { + "type": "stack", + "groupby": ["year"], + "field": "wages", + "sort": {"field": [], "order": []}, + "as": ["wages_start", "wages_end"], + "offset": "zero" + } + ] + }, + { + "name": "data_3", "source": "source_1", "transform": [ { @@ -58,7 +80,7 @@ ] }, { - "name": "data_2", + "name": "data_4", "source": "source_2", "transform": [ { @@ -74,7 +96,7 @@ ] }, { - "name": "data_3", + "name": "data_5", "source": "source_2", "transform": [ { @@ -99,7 +121,7 @@ "name": "layer_0_marks", "type": "rect", "style": ["bar"], - "from": {"data": "data_0"}, + "from": {"data": "data_1"}, "encode": { "update": { "fill": {"value": "#aaa"}, @@ -131,7 +153,7 @@ "name": "layer_1_marks", "type": "rule", "style": ["rule"], - "from": {"data": "data_1"}, + "from": {"data": "data_3"}, "encode": { "update": { "opacity": {"value": 0.5}, @@ -151,7 +173,7 @@ "type": "area", "style": ["area"], "sort": {"field": "datum[\"year\"]"}, - "from": {"data": "source_0"}, + "from": {"data": "data_2"}, "encode": { "update": { "opacity": {"value": 0.7}, @@ -161,8 +183,8 @@ "signal": "\"year: \" + (format(datum[\"year\"], \"d\")) + \"; wages: \" + (format(datum[\"wages\"], \"\"))" }, "x": {"scale": "x", "field": "year"}, - "y": {"scale": "y", "field": "wages"}, - "y2": {"scale": "y", "value": 0}, + "y": {"scale": "y", "field": "wages_end"}, + "y2": {"scale": "y", "field": "wages_start"}, "defined": { "signal": "isValid(datum[\"year\"]) && isFinite(+datum[\"year\"]) && isValid(datum[\"wages\"]) && isFinite(+datum[\"wages\"])" } @@ -214,7 +236,7 @@ "name": "layer_5_marks", "type": "rect", "style": ["rect"], - "from": {"data": "data_2"}, + "from": {"data": "data_4"}, "encode": { "update": { "stroke": {"value": "#000"}, @@ -233,7 +255,7 @@ "name": "layer_6_marks", "type": "text", "style": ["text"], - "from": {"data": "data_3"}, + "from": {"data": "data_5"}, "encode": { "update": { "baseline": {"value": "bottom"}, @@ -259,13 +281,14 @@ "type": "linear", "domain": { "fields": [ - {"data": "data_0", "field": "year"}, - {"data": "data_0", "field": "year_end"}, {"data": "data_1", "field": "year"}, + {"data": "data_1", "field": "year_end"}, + {"data": "data_3", "field": "year"}, + {"data": "data_2", "field": "year"}, {"data": "source_0", "field": "year"}, - {"data": "data_2", "field": "start"}, - {"data": "data_2", "field": "end"}, - {"data": "data_3", "field": "x"} + {"data": "data_4", "field": "start"}, + {"data": "data_4", "field": "end"}, + {"data": "data_5", "field": "x"} ] }, "range": [0, {"signal": "width"}], @@ -277,12 +300,14 @@ "type": "linear", "domain": { "fields": [ - {"data": "data_0", "field": "wheat_start"}, - {"data": "data_0", "field": "wheat_end"}, + {"data": "data_1", "field": "wheat_start"}, + {"data": "data_1", "field": "wheat_end"}, + {"data": "data_2", "field": "wages_start"}, + {"data": "data_2", "field": "wages_end"}, {"data": "source_0", "field": "wages"}, - {"data": "data_2", "field": "y"}, - {"data": "data_2", "field": "offset"}, - {"data": "data_3", "field": "off2"} + {"data": "data_4", "field": "y"}, + {"data": "data_4", "field": "offset"}, + {"data": "data_5", "field": "off2"} ] }, "range": [{"signal": "height"}, 0], @@ -292,7 +317,7 @@ { "name": "fill", "type": "ordinal", - "domain": {"data": "data_2", "field": "commonwealth", "sort": true}, + "domain": {"data": "data_4", "field": "commonwealth", "sort": true}, "range": ["black", "white"] } ], diff --git a/examples/specs/area_cumulative_freq.vl.json b/examples/specs/area_cumulative_freq.vl.json index d74bc5dd933..f0a54694710 100644 --- a/examples/specs/area_cumulative_freq.vl.json +++ b/examples/specs/area_cumulative_freq.vl.json @@ -2,8 +2,12 @@ "$schema": "https://vega.github.io/schema/vega-lite/v5.json", "data": {"url": "data/movies.json"}, "transform": [{ + "aggregate":[{"op": "count", "field": "*", "as": "count"}], + "groupby": ["IMDB Rating"] + }, + { "sort": [{"field": "IMDB Rating"}], - "window": [{"op": "count", "field": "count", "as": "Cumulative Count"}], + "window": [{"op": "sum", "field": "count", "as": "Cumulative Count"}], "frame": [null, 0] }], "mark": "area", diff --git a/examples/specs/normalized/stacked_area_without_agg_normalized.vl.json b/examples/specs/normalized/stacked_area_without_agg_normalized.vl.json new file mode 100644 index 00000000000..ecae7f34368 --- /dev/null +++ b/examples/specs/normalized/stacked_area_without_agg_normalized.vl.json @@ -0,0 +1,31 @@ +{ + "$schema": "https://vega.github.io/schema/vega-lite/v5.json", + "data": { + "values": [ + {"a": 1, "b": 2, "c": "c1"}, + {"a": 2, "b": 3, "c": "c1"}, + {"a": 3, "b": 1, "c": "c1"}, + {"a": 1, "b": 2, "c": "c2"}, + {"a": 3, "b": 1, "c": "c2"}, + {"a": 2, "b": 2, "c": "c2"} + ] + }, + "layer": [ + { + "mark": {"opacity": 0.7, "type": "area"}, + "encoding": { + "x": {"field": "a", "type": "quantitative"}, + "y": {"field": "b", "type": "quantitative"}, + "color": {"field": "c", "type": "nominal"} + } + }, + { + "mark": {"type": "line"}, + "encoding": { + "x": {"field": "a", "type": "quantitative"}, + "y": {"field": "b", "type": "quantitative", "stack": "zero"}, + "color": {"field": "c", "type": "nominal"} + } + } + ] +} \ No newline at end of file diff --git a/examples/specs/stacked_area_without_agg.vl.json b/examples/specs/stacked_area_without_agg.vl.json new file mode 100644 index 00000000000..a0ab50f20de --- /dev/null +++ b/examples/specs/stacked_area_without_agg.vl.json @@ -0,0 +1,19 @@ +{ + "$schema": "https://vega.github.io/schema/vega-lite/v5.json", + "data": { + "values": [ + {"a": 1, "b": 2, "c": "c1"}, + {"a": 2, "b": 3, "c": "c1"}, + {"a": 3, "b": 1, "c": "c1"}, + {"a": 1, "b": 2, "c": "c2"}, + {"a": 3, "b": 1, "c": "c2"}, + {"a": 2, "b": 2, "c": "c2"} + ] + }, + "mark": {"type": "area", "line": true}, + "encoding": { + "x": {"field": "a", "type": "quantitative"}, + "y": {"field": "b", "type": "quantitative"}, + "color": {"field": "c"} + } +} diff --git a/src/normalize/pathoverlay.ts b/src/normalize/pathoverlay.ts index e5331c9422c..92172f56841 100644 --- a/src/normalize/pathoverlay.ts +++ b/src/normalize/pathoverlay.ts @@ -9,6 +9,7 @@ import {isUnitSpec} from '../spec/unit'; import {stack} from '../stack'; import {keys, omit, pick} from '../util'; import {NonFacetUnitNormalizer, NormalizeLayerOrUnit, NormalizerParams} from './base'; +import {initMarkdef} from '../compile/mark/init'; type UnitSpecWithPathOverlay = GenericUnitSpec, Mark | MarkDef<'line' | 'area' | 'rule' | 'trail'>>; @@ -109,6 +110,7 @@ export class PathOverlayNormalizer implements NonFacetUnitNormalizer { }); }); + it('correctly normalizes default stacked area with overlay line', () => { + const spec: TopLevelSpec = { + data: {url: 'data/movies.json'}, + mark: 'area', + encoding: { + x: {field: 'IMDB Rating', type: 'quantitative'}, + y: {field: 'Rotten Tomatoes Rating', type: 'quantitative'}, + color: {field: 'MPAA RATING', type: 'nominal'} + }, + config: {area: {line: {}}} + }; + const normalizedSpec = normalize(spec); + expect(normalizedSpec).toEqual({ + data: {url: 'data/movies.json'}, + layer: [ + { + mark: {type: 'area', opacity: 0.7}, + encoding: { + x: {field: 'IMDB Rating', type: 'quantitative'}, + y: {field: 'Rotten Tomatoes Rating', type: 'quantitative'}, + color: {field: 'MPAA RATING', type: 'nominal'} + } + }, + { + mark: {type: 'line'}, + encoding: { + x: {field: 'IMDB Rating', type: 'quantitative'}, + y: {field: 'Rotten Tomatoes Rating', type: 'quantitative', stack: 'zero'}, + color: {field: 'MPAA RATING', type: 'nominal'} + } + } + ], + config: {area: {line: {}}} + }); + }); + it('correctly normalizes streamgraph with overlay line', () => { const spec: TopLevelSpec = { data: {url: 'data/stocks.csv'}, diff --git a/test/stack.test.ts b/test/stack.test.ts index f8d29dc23d4..455748d07d9 100644 --- a/test/stack.test.ts +++ b/test/stack.test.ts @@ -327,6 +327,21 @@ describe('stack', () => { } }); + it('should stack even for two plain quantitatives with the orient on the axes', () => { + for (const mark of [BAR, AREA]) { + const spec: TopLevel = { + data: {url: 'data/movies.json'}, + mark: {type: mark, orient: 'vertical'}, // orient also can be inferred by init.ts + encoding: { + x: {field: 'IMDB Rating', type: 'quantitative'}, + y: {field: 'Rotten Tomatoes Rating', type: 'quantitative'} + } + }; + const stackProps = stack(spec.mark, spec.encoding); + expect(stackProps.fieldChannel).toBe(Y); + } + }); + it('should be correct for horizontal (single)', () => { for (const stackableMark of [BAR, AREA]) { const spec: TopLevel = {