diff --git a/examples/specs/normalized/airport_connections_normalized.vl.json b/examples/specs/normalized/airport_connections_normalized.vl.json index 086840ee0a..db5a7d46bd 100644 --- a/examples/specs/normalized/airport_connections_normalized.vl.json +++ b/examples/specs/normalized/airport_connections_normalized.vl.json @@ -18,6 +18,12 @@ { "mark": {"type": "rule", "color": "#000", "opacity": 0.35}, "data": {"url": "data/flights-airport.csv"}, + "encoding": { + "latitude": {"field": "latitude"}, + "longitude": {"field": "longitude"}, + "latitude2": {"field": "lat2"}, + "longitude2": {"field": "lon2"} + }, "transform": [ {"filter": {"param": "org", "empty": false}}, { @@ -38,29 +44,11 @@ "as": ["lat2", "lon2"] } ], - "encoding": { - "latitude": {"field": "latitude"}, - "longitude": {"field": "longitude"}, - "latitude2": {"field": "lat2"}, - "longitude2": {"field": "lon2"} - }, "projection": {"type": "albersUsa"} }, { "mark": {"type": "circle"}, "data": {"url": "data/flights-airport.csv"}, - "transform": [ - {"aggregate": [{"op": "count", "as": "routes"}], "groupby": ["origin"]}, - { - "lookup": "origin", - "from": { - "data": {"url": "data/airports.csv"}, - "key": "iata", - "fields": ["state", "latitude", "longitude"] - } - }, - {"filter": "datum.state !== 'PR' && datum.state !== 'VI'"} - ], "params": [ { "name": "org", @@ -83,6 +71,18 @@ }, "order": {"field": "routes", "sort": "descending"} }, + "transform": [ + {"aggregate": [{"op": "count", "as": "routes"}], "groupby": ["origin"]}, + { + "lookup": "origin", + "from": { + "data": {"url": "data/airports.csv"}, + "key": "iata", + "fields": ["state", "latitude", "longitude"] + } + }, + {"filter": "datum.state !== 'PR' && datum.state !== 'VI'"} + ], "projection": {"type": "albersUsa"} } ], diff --git a/examples/specs/normalized/area_horizon_normalized.vl.json b/examples/specs/normalized/area_horizon_normalized.vl.json index 46724e29af..43eb609403 100644 --- a/examples/specs/normalized/area_horizon_normalized.vl.json +++ b/examples/specs/normalized/area_horizon_normalized.vl.json @@ -50,7 +50,6 @@ } }, { - "transform": [{"calculate": "datum.y - 50", "as": "ny"}], "mark": {"type": "area", "clip": true, "orient": "vertical"}, "encoding": { "x": { @@ -65,7 +64,8 @@ "axis": {"title": "y"} }, "opacity": {"value": 0.3} - } + }, + "transform": [{"calculate": "datum.y - 50", "as": "ny"}] } ], "config": {"area": {"interpolate": "monotone"}} diff --git a/examples/specs/normalized/bar_grouped_horizontal_normalized.vl.json b/examples/specs/normalized/bar_grouped_horizontal_normalized.vl.json index 66cb49930d..b88e9fbd1c 100644 --- a/examples/specs/normalized/bar_grouped_horizontal_normalized.vl.json +++ b/examples/specs/normalized/bar_grouped_horizontal_normalized.vl.json @@ -1,11 +1,11 @@ { "$schema": "https://vega.github.io/schema/vega-lite/v4.json", "data": {"url": "data/population.json"}, + "config": {"view": {"stroke": "transparent"}, "axis": {"domainWidth": 1}}, "transform": [ {"filter": "datum.year == 2000"}, {"calculate": "datum.sex == 2 ? 'Female' : 'Male'", "as": "gender"} ], - "config": {"view": {"stroke": "transparent"}, "axis": {"domainWidth": 1}}, "spacing": {"row": 10}, "facet": {"row": {"field": "age"}}, "spec": { diff --git a/examples/specs/normalized/bar_grouped_normalized.vl.json b/examples/specs/normalized/bar_grouped_normalized.vl.json index c20bf2565c..45bb0b0a7a 100644 --- a/examples/specs/normalized/bar_grouped_normalized.vl.json +++ b/examples/specs/normalized/bar_grouped_normalized.vl.json @@ -1,11 +1,11 @@ { "$schema": "https://vega.github.io/schema/vega-lite/v4.json", "data": {"url": "data/population.json"}, + "config": {"view": {"stroke": "transparent"}, "axis": {"domainWidth": 1}}, "transform": [ {"filter": "datum.year == 2000"}, {"calculate": "datum.sex == 2 ? 'Female' : 'Male'", "as": "gender"} ], - "config": {"view": {"stroke": "transparent"}, "axis": {"domainWidth": 1}}, "spacing": {"column": 10}, "facet": {"column": {"field": "age", "type": "ordinal"}}, "spec": { diff --git a/examples/specs/normalized/boxplot_preaggregated_normalized.vl.json b/examples/specs/normalized/boxplot_preaggregated_normalized.vl.json index 7d86128ddc..b2b732f6ec 100644 --- a/examples/specs/normalized/boxplot_preaggregated_normalized.vl.json +++ b/examples/specs/normalized/boxplot_preaggregated_normalized.vl.json @@ -63,12 +63,12 @@ } }, { - "transform": [{"flatten": ["outliers"]}], "mark": {"type": "point", "style": "boxplot-outliers"}, "encoding": { "y": {"field": "Species", "type": "nominal", "title": null}, "x": {"field": "outliers", "type": "quantitative"} - } + }, + "transform": [{"flatten": ["outliers"]}] } ] } \ No newline at end of file diff --git a/examples/specs/normalized/concat_layer_voyager_result_normalized.vl.json b/examples/specs/normalized/concat_layer_voyager_result_normalized.vl.json index c8350ca77d..49edaf7f6c 100644 --- a/examples/specs/normalized/concat_layer_voyager_result_normalized.vl.json +++ b/examples/specs/normalized/concat_layer_voyager_result_normalized.vl.json @@ -139,7 +139,7 @@ "field": "to" }, "shape": { - "condition": {"test": "datum.to > 0", "value": "triangle-right"}, + "condition": {"value": "triangle-right", "test": "datum.to > 0"}, "value": "triangle-left" } } @@ -151,7 +151,6 @@ "style": "arrow-label", "text": ["Polestar", "More Valuable"] }, - "transform": [{"filter": "datum.label === 'PoleStar'"}], "encoding": { "x": { "type": "quantitative", @@ -159,7 +158,8 @@ "axis": null, "field": "from" } - } + }, + "transform": [{"filter": "datum.label === 'PoleStar'"}] }, { "mark": { @@ -168,7 +168,6 @@ "style": "arrow-label", "text": ["Voyager / Voyager 2", "More Valuable"] }, - "transform": [{"filter": "datum.label !== 'PoleStar'"}], "encoding": { "x": { "type": "quantitative", @@ -176,7 +175,8 @@ "axis": null, "field": "from" } - } + }, + "transform": [{"filter": "datum.label !== 'PoleStar'"}] } ] } diff --git a/examples/specs/normalized/geo_repeat_normalized.vl.json b/examples/specs/normalized/geo_repeat_normalized.vl.json index b8c68661d4..f465ec90f8 100644 --- a/examples/specs/normalized/geo_repeat_normalized.vl.json +++ b/examples/specs/normalized/geo_repeat_normalized.vl.json @@ -9,6 +9,12 @@ { "width": 500, "height": 300, + "projection": {"type": "albersUsa"}, + "mark": "geoshape", + "encoding": { + "shape": {"field": "geo", "type": "geojson"}, + "color": {"field": "population", "type": "quantitative"} + }, "transform": [ { "lookup": "id", @@ -22,17 +28,17 @@ "as": "geo" } ], - "projection": {"type": "albersUsa"}, - "mark": "geoshape", - "encoding": { - "shape": {"field": "geo", "type": "geojson"}, - "color": {"field": "population", "type": "quantitative"} - }, "name": "child__row_population" }, { "width": 500, "height": 300, + "projection": {"type": "albersUsa"}, + "mark": "geoshape", + "encoding": { + "shape": {"field": "geo", "type": "geojson"}, + "color": {"field": "engineers", "type": "quantitative"} + }, "transform": [ { "lookup": "id", @@ -46,17 +52,17 @@ "as": "geo" } ], - "projection": {"type": "albersUsa"}, - "mark": "geoshape", - "encoding": { - "shape": {"field": "geo", "type": "geojson"}, - "color": {"field": "engineers", "type": "quantitative"} - }, "name": "child__row_engineers" }, { "width": 500, "height": 300, + "projection": {"type": "albersUsa"}, + "mark": "geoshape", + "encoding": { + "shape": {"field": "geo", "type": "geojson"}, + "color": {"field": "hurricanes", "type": "quantitative"} + }, "transform": [ { "lookup": "id", @@ -70,12 +76,6 @@ "as": "geo" } ], - "projection": {"type": "albersUsa"}, - "mark": "geoshape", - "encoding": { - "shape": {"field": "geo", "type": "geojson"}, - "color": {"field": "hurricanes", "type": "quantitative"} - }, "name": "child__row_hurricanes" } ] diff --git a/examples/specs/normalized/interactive_area_brush_normalized.vl.json b/examples/specs/normalized/interactive_area_brush_normalized.vl.json index 22c3b175c8..3ed81cbdfe 100644 --- a/examples/specs/normalized/interactive_area_brush_normalized.vl.json +++ b/examples/specs/normalized/interactive_area_brush_normalized.vl.json @@ -13,8 +13,8 @@ } }, { - "transform": [{"filter": {"param": "brush"}}], "mark": {"type": "area", "color": "goldenrod"}, + "transform": [{"filter": {"param": "brush"}}], "encoding": { "x": {"timeUnit": "yearmonth", "field": "date"}, "y": {"aggregate": "sum", "field": "count"} diff --git a/examples/specs/normalized/interactive_global_development_normalized.vl.json b/examples/specs/normalized/interactive_global_development_normalized.vl.json index a18b683510..b2483831b9 100644 --- a/examples/specs/normalized/interactive_global_development_normalized.vl.json +++ b/examples/specs/normalized/interactive_global_development_normalized.vl.json @@ -6,10 +6,6 @@ "height": 500, "layer": [ { - "transform": [ - {"filter": {"field": "country", "equal": "Afghanistan"}}, - {"filter": {"param": "year"}} - ], "mark": { "type": "text", "fontSize": 100, @@ -17,28 +13,13 @@ "y": 250, "opacity": 0.06 }, - "encoding": {"text": {"field": "year"}} + "encoding": {"text": {"field": "year"}}, + "transform": [ + {"filter": {"field": "country", "equal": "Afghanistan"}}, + {"filter": {"param": "year"}} + ] }, { - "transform": [ - { - "lookup": "cluster", - "from": { - "key": "id", - "fields": ["name"], - "data": { - "values": [ - {"id": 0, "name": "South Asia"}, - {"id": 1, "name": "Europe & Central Asia"}, - {"id": 2, "name": "Sub-Saharan Africa"}, - {"id": 3, "name": "America"}, - {"id": 4, "name": "East Asia & Pacific"}, - {"id": 5, "name": "Middle East & North Africa"} - ] - } - } - } - ], "layer": [ { "mark": { @@ -64,13 +45,13 @@ "order": {"field": "year"}, "opacity": { "condition": { + "value": 0.8, "test": { "or": [ {"param": "hovered", "empty": false}, {"param": "clicked", "empty": false} ] - }, - "value": 0.8 + } }, "value": 0 } @@ -104,7 +85,6 @@ "select": {"type": "point", "fields": ["country"]} } ], - "transform": [{"filter": {"param": "year"}}], "mark": {"type": "circle", "size": 100, "opacity": 0.9}, "encoding": { "x": { @@ -120,24 +100,10 @@ "axis": {"tickCount": 5, "title": "Life Expectancy"} }, "color": {"field": "name", "title": "Region"} - } + }, + "transform": [{"filter": {"param": "year"}}] }, { - "transform": [ - { - "filter": { - "and": [ - {"param": "year"}, - { - "or": [ - {"param": "clicked", "empty": false}, - {"param": "hovered", "empty": false} - ] - } - ] - } - } - ], "mark": { "type": "text", "yOffset": -12, @@ -159,13 +125,24 @@ }, "text": {"field": "country"}, "color": {"field": "name", "title": "Region"} - } + }, + "transform": [ + { + "filter": { + "and": [ + {"param": "year"}, + { + "or": [ + {"param": "clicked", "empty": false}, + {"param": "hovered", "empty": false} + ] + } + ] + } + } + ] }, { - "transform": [ - {"filter": {"param": "hovered", "empty": false}}, - {"filter": {"not": {"param": "year"}}} - ], "layer": [ { "mark": { @@ -207,8 +184,31 @@ } } } + ], + "transform": [ + {"filter": {"param": "hovered", "empty": false}}, + {"filter": {"not": {"param": "year"}}} ] } + ], + "transform": [ + { + "lookup": "cluster", + "from": { + "key": "id", + "fields": ["name"], + "data": { + "values": [ + {"id": 0, "name": "South Asia"}, + {"id": 1, "name": "Europe & Central Asia"}, + {"id": 2, "name": "Sub-Saharan Africa"}, + {"id": 3, "name": "America"}, + {"id": 4, "name": "East Asia & Pacific"}, + {"id": 5, "name": "Middle East & North Africa"} + ] + } + } + } ] } ] diff --git a/examples/specs/normalized/interactive_index_chart_normalized.vl.json b/examples/specs/normalized/interactive_index_chart_normalized.vl.json index b71dcf82ba..db2d1876c2 100644 --- a/examples/specs/normalized/interactive_index_chart_normalized.vl.json +++ b/examples/specs/normalized/interactive_index_chart_normalized.vl.json @@ -24,13 +24,6 @@ } }, { - "transform": [ - {"lookup": "symbol", "from": {"param": "index", "key": "symbol"}}, - { - "calculate": "datum.index && datum.index.price > 0 ? (datum.price - datum.index.price)/datum.index.price : 0", - "as": "indexed_price" - } - ], "mark": "line", "encoding": { "x": {"field": "date", "type": "temporal", "axis": null}, @@ -40,10 +33,16 @@ "axis": {"format": "%"} }, "color": {"field": "symbol", "type": "nominal"} - } + }, + "transform": [ + {"lookup": "symbol", "from": {"param": "index", "key": "symbol"}}, + { + "calculate": "datum.index && datum.index.price > 0 ? (datum.price - datum.index.price)/datum.index.price : 0", + "as": "indexed_price" + } + ] }, { - "transform": [{"filter": {"param": "index"}}], "layer": [ { "mark": {"type": "rule", "strokeWidth": 0.5}, @@ -61,7 +60,8 @@ "y": {"value": 310} } } - ] + ], + "transform": [{"filter": {"param": "index"}}] } ] } \ No newline at end of file diff --git a/examples/specs/normalized/interactive_layered_crossfilter_discrete_normalized.vl.json b/examples/specs/normalized/interactive_layered_crossfilter_discrete_normalized.vl.json index 715327d038..a9ad32ae40 100644 --- a/examples/specs/normalized/interactive_layered_crossfilter_discrete_normalized.vl.json +++ b/examples/specs/normalized/interactive_layered_crossfilter_discrete_normalized.vl.json @@ -22,12 +22,12 @@ } }, { - "transform": [{"filter": {"param": "brush"}}], "mark": "bar", "encoding": { "x": {"field": "distance", "bin": {"maxbins": 20}}, "y": {"aggregate": "count"} - } + }, + "transform": [{"filter": {"param": "brush"}}] } ], "name": "child__column_distance" @@ -46,12 +46,12 @@ } }, { - "transform": [{"filter": {"param": "brush"}}], "mark": "bar", "encoding": { "x": {"field": "delay", "bin": {"maxbins": 20}}, "y": {"aggregate": "count"} - } + }, + "transform": [{"filter": {"param": "brush"}}] } ], "name": "child__column_delay" @@ -70,12 +70,12 @@ } }, { - "transform": [{"filter": {"param": "brush"}}], "mark": "bar", "encoding": { "x": {"field": "time", "bin": {"maxbins": 20}}, "y": {"aggregate": "count"} - } + }, + "transform": [{"filter": {"param": "brush"}}] } ], "name": "child__column_time" diff --git a/examples/specs/normalized/interactive_layered_crossfilter_normalized.vl.json b/examples/specs/normalized/interactive_layered_crossfilter_normalized.vl.json index ca1bfe16bf..a92a7644d0 100644 --- a/examples/specs/normalized/interactive_layered_crossfilter_normalized.vl.json +++ b/examples/specs/normalized/interactive_layered_crossfilter_normalized.vl.json @@ -25,12 +25,12 @@ } }, { - "transform": [{"filter": {"param": "brush"}}], "mark": "bar", "encoding": { "x": {"field": "distance", "bin": {"maxbins": 20}}, "y": {"aggregate": "count"} - } + }, + "transform": [{"filter": {"param": "brush"}}] } ], "name": "child__column_distance" @@ -52,12 +52,12 @@ } }, { - "transform": [{"filter": {"param": "brush"}}], "mark": "bar", "encoding": { "x": {"field": "delay", "bin": {"maxbins": 20}}, "y": {"aggregate": "count"} - } + }, + "transform": [{"filter": {"param": "brush"}}] } ], "name": "child__column_delay" @@ -79,12 +79,12 @@ } }, { - "transform": [{"filter": {"param": "brush"}}], "mark": "bar", "encoding": { "x": {"field": "time", "bin": {"maxbins": 20}}, "y": {"aggregate": "count"} - } + }, + "transform": [{"filter": {"param": "brush"}}] } ], "name": "child__column_time" diff --git a/examples/specs/normalized/interactive_line_hover_normalized.vl.json b/examples/specs/normalized/interactive_line_hover_normalized.vl.json index 9eb8036118..3c1eb153ef 100644 --- a/examples/specs/normalized/interactive_line_hover_normalized.vl.json +++ b/examples/specs/normalized/interactive_line_hover_normalized.vl.json @@ -2,7 +2,6 @@ "$schema": "https://vega.github.io/schema/vega-lite/v4.json", "description": "Multi-series line chart with labels and interactive highlight on hover. We also set the selection's initial value to provide a better screenshot", "data": {"url": "data/stocks.csv"}, - "transform": [{"filter": "datum.symbol!=='IBM'"}], "layer": [ { "description": "transparent layer to make it easier to trigger selection", @@ -112,5 +111,6 @@ ] } ], - "config": {"view": {"stroke": null}} + "config": {"view": {"stroke": null}}, + "transform": [{"filter": "datum.symbol!=='IBM'"}] } \ No newline at end of file diff --git a/examples/specs/normalized/interactive_multi_line_label_normalized.vl.json b/examples/specs/normalized/interactive_multi_line_label_normalized.vl.json index fd72deaf0e..5403c6250a 100644 --- a/examples/specs/normalized/interactive_multi_line_label_normalized.vl.json +++ b/examples/specs/normalized/interactive_multi_line_label_normalized.vl.json @@ -40,7 +40,6 @@ ] }, { - "transform": [{"filter": {"param": "label", "empty": false}}], "layer": [ { "mark": {"type": "rule", "color": "gray"}, @@ -76,7 +75,8 @@ } ] } - ] + ], + "transform": [{"filter": {"param": "label", "empty": false}}] } ] } \ No newline at end of file diff --git a/examples/specs/normalized/interactive_multi_line_pivot_tooltip_normalized.vl.json b/examples/specs/normalized/interactive_multi_line_pivot_tooltip_normalized.vl.json index 79c1c9b2e3..59f8b3e186 100644 --- a/examples/specs/normalized/interactive_multi_line_pivot_tooltip_normalized.vl.json +++ b/examples/specs/normalized/interactive_multi_line_pivot_tooltip_normalized.vl.json @@ -15,8 +15,8 @@ } }, { - "transform": [{"filter": {"param": "hover", "empty": false}}], "mark": "point", + "transform": [{"filter": {"param": "hover", "empty": false}}], "encoding": { "x": {"field": "date", "type": "temporal"}, "color": {"field": "symbol", "type": "nominal"}, @@ -26,7 +26,6 @@ ] }, { - "transform": [{"pivot": "symbol", "value": "price", "groupby": ["date"]}], "mark": "rule", "encoding": { "x": {"field": "date", "type": "temporal"}, @@ -53,7 +52,8 @@ "clear": "mouseout" } } - ] + ], + "transform": [{"pivot": "symbol", "value": "price", "groupby": ["date"]}] } ] } \ No newline at end of file diff --git a/examples/specs/normalized/interactive_stocks_nearest_index_normalized.vl.json b/examples/specs/normalized/interactive_stocks_nearest_index_normalized.vl.json index 86e4f2610a..1d9002b034 100644 --- a/examples/specs/normalized/interactive_stocks_nearest_index_normalized.vl.json +++ b/examples/specs/normalized/interactive_stocks_nearest_index_normalized.vl.json @@ -32,18 +32,18 @@ } }, { - "transform": [{"filter": {"and": ["index.date", {"param": "index"}]}}], "mark": "rule", + "transform": [{"filter": {"and": ["index.date", {"param": "index"}]}}], "encoding": {"x": {"field": "date", "type": "temporal"}} }, { - "transform": [{"filter": {"and": ["index.date", {"param": "index"}]}}], "mark": "text", "encoding": { "x": {"field": "date", "type": "temporal"}, "y": {"value": 10}, "text": {"field": "date", "type": "temporal"} - } + }, + "transform": [{"filter": {"and": ["index.date", {"param": "index"}]}}] } ], "config": {"text": {"align": "right", "dx": -5, "dy": 5}} diff --git a/examples/specs/normalized/layer_bar_annotations_normalized.vl.json b/examples/specs/normalized/layer_bar_annotations_normalized.vl.json index 6d4f260f49..71db2ba731 100644 --- a/examples/specs/normalized/layer_bar_annotations_normalized.vl.json +++ b/examples/specs/normalized/layer_bar_annotations_normalized.vl.json @@ -32,10 +32,6 @@ }, { "mark": "bar", - "transform": [ - {"filter": "datum.Value >= 300"}, - {"calculate": "300", "as": "baseline"} - ], "encoding": { "x": {"field": "Day", "type": "ordinal"}, "y": { @@ -45,7 +41,11 @@ }, "y2": {"field": "Value"}, "color": {"value": "#e45755"} - } + }, + "transform": [ + {"filter": "datum.Value >= 300"}, + {"calculate": "300", "as": "baseline"} + ] } ] }, diff --git a/examples/specs/normalized/layer_bar_fruit_normalized.vl.json b/examples/specs/normalized/layer_bar_fruit_normalized.vl.json index db2abcddc8..d3cef9c089 100644 --- a/examples/specs/normalized/layer_bar_fruit_normalized.vl.json +++ b/examples/specs/normalized/layer_bar_fruit_normalized.vl.json @@ -34,7 +34,7 @@ "x": {"field": "count", "type": "quantitative", "title": null}, "text": {"field": "count", "type": "quantitative"}, "color": { - "condition": {"test": {"field": "count", "gt": 10}, "value": "white"}, + "condition": {"value": "white", "test": {"field": "count", "gt": 10}}, "value": "black" } } diff --git a/examples/specs/normalized/layer_candlestick_normalized.vl.json b/examples/specs/normalized/layer_candlestick_normalized.vl.json index df6a44b745..4cb58f39c0 100644 --- a/examples/specs/normalized/layer_candlestick_normalized.vl.json +++ b/examples/specs/normalized/layer_candlestick_normalized.vl.json @@ -24,7 +24,7 @@ "field": "low" }, "color": { - "condition": {"test": "datum.open < datum.close", "value": "#06982d"}, + "condition": {"value": "#06982d", "test": "datum.open < datum.close"}, "value": "#ae1325" }, "y2": {"field": "high"} @@ -50,7 +50,7 @@ "field": "open" }, "color": { - "condition": {"test": "datum.open < datum.close", "value": "#06982d"}, + "condition": {"value": "#06982d", "test": "datum.open < datum.close"}, "value": "#ae1325" }, "y2": {"field": "close"} diff --git a/examples/specs/normalized/layer_cumulative_histogram_normalized.vl.json b/examples/specs/normalized/layer_cumulative_histogram_normalized.vl.json index 31927cf4bc..0ca2963af6 100644 --- a/examples/specs/normalized/layer_cumulative_histogram_normalized.vl.json +++ b/examples/specs/normalized/layer_cumulative_histogram_normalized.vl.json @@ -1,19 +1,6 @@ { "$schema": "https://vega.github.io/schema/vega-lite/v4.json", "data": {"url": "data/movies.json"}, - "transform": [ - {"bin": true, "field": "IMDB Rating", "as": "bin_IMDB_Rating"}, - { - "aggregate": [{"op": "count", "as": "count"}], - "groupby": ["bin_IMDB_Rating", "bin_IMDB_Rating_end"] - }, - {"filter": "datum.bin_IMDB_Rating !== null"}, - { - "sort": [{"field": "bin_IMDB_Rating"}], - "window": [{"op": "sum", "field": "count", "as": "Cumulative Count"}], - "frame": [null, 0] - } - ], "layer": [ { "mark": "bar", @@ -41,5 +28,18 @@ "y": {"field": "count", "type": "quantitative"} } } + ], + "transform": [ + {"bin": true, "field": "IMDB Rating", "as": "bin_IMDB_Rating"}, + { + "aggregate": [{"op": "count", "as": "count"}], + "groupby": ["bin_IMDB_Rating", "bin_IMDB_Rating_end"] + }, + {"filter": "datum.bin_IMDB_Rating !== null"}, + { + "sort": [{"field": "bin_IMDB_Rating"}], + "window": [{"op": "sum", "field": "count", "as": "Cumulative Count"}], + "frame": [null, 0] + } ] } \ No newline at end of file diff --git a/examples/specs/normalized/layer_dual_axis_normalized.vl.json b/examples/specs/normalized/layer_dual_axis_normalized.vl.json index c45d97ea96..ae445be701 100644 --- a/examples/specs/normalized/layer_dual_axis_normalized.vl.json +++ b/examples/specs/normalized/layer_dual_axis_normalized.vl.json @@ -4,7 +4,6 @@ "width": 400, "height": 300, "data": {"url": "data/weather.csv"}, - "transform": [{"filter": "datum.location == \"Seattle\""}], "layer": [ { "mark": {"opacity": 0.3, "type": "area", "color": "#85C5A6"}, @@ -41,5 +40,6 @@ } } ], - "resolve": {"scale": {"y": "independent"}} + "resolve": {"scale": {"y": "independent"}}, + "transform": [{"filter": "datum.location == \"Seattle\""}] } \ No newline at end of file diff --git a/examples/specs/normalized/layer_likert_normalized.vl.json b/examples/specs/normalized/layer_likert_normalized.vl.json index a5b57c0ba5..68018b6f05 100644 --- a/examples/specs/normalized/layer_likert_normalized.vl.json +++ b/examples/specs/normalized/layer_likert_normalized.vl.json @@ -223,11 +223,6 @@ { "mark": "circle", "data": {"name": "values"}, - "transform": [ - {"filter": "datum.name != 'Toolbar_First'"}, - {"filter": "datum.name != 'Tablet_First'"}, - {"filter": "datum.name != 'Participant ID'"} - ], "encoding": { "y": { "field": "name", @@ -255,7 +250,12 @@ "legend": {"offset": 75} }, "color": {"value": "#6EB4FD"} - } + }, + "transform": [ + {"filter": "datum.name != 'Toolbar_First'"}, + {"filter": "datum.name != 'Tablet_First'"}, + {"filter": "datum.name != 'Participant ID'"} + ] }, { "mark": "tick", diff --git a/examples/specs/normalized/layer_line_co2_concentration_normalized.vl.json b/examples/specs/normalized/layer_line_co2_concentration_normalized.vl.json index ffd9ba0a4d..ec5551394a 100644 --- a/examples/specs/normalized/layer_line_co2_concentration_normalized.vl.json +++ b/examples/specs/normalized/layer_line_co2_concentration_normalized.vl.json @@ -6,18 +6,6 @@ }, "width": 800, "height": 500, - "transform": [ - {"calculate": "year(datum.Date)", "as": "year"}, - {"calculate": "floor(datum.year / 10)", "as": "decade"}, - { - "calculate": "(datum.year % 10) + (month(datum.Date)/12)", - "as": "scaled_date" - }, - { - "calculate": "datum.first_date === datum.scaled_date ? 'first' : datum.last_date === datum.scaled_date ? 'last' : null", - "as": "end" - } - ], "layer": [ { "mark": "line", @@ -95,5 +83,17 @@ } } ], - "config": {"text": {"align": "left", "dx": 3, "dy": 1}} + "config": {"text": {"align": "left", "dx": 3, "dy": 1}}, + "transform": [ + {"calculate": "year(datum.Date)", "as": "year"}, + {"calculate": "floor(datum.year / 10)", "as": "decade"}, + { + "calculate": "(datum.year % 10) + (month(datum.Date)/12)", + "as": "scaled_date" + }, + { + "calculate": "datum.first_date === datum.scaled_date ? 'first' : datum.last_date === datum.scaled_date ? 'last' : null", + "as": "end" + } + ] } \ No newline at end of file diff --git a/examples/specs/normalized/layer_line_rolling_mean_point_raw_normalized.vl.json b/examples/specs/normalized/layer_line_rolling_mean_point_raw_normalized.vl.json index 1568a2db16..5322efc1d6 100644 --- a/examples/specs/normalized/layer_line_rolling_mean_point_raw_normalized.vl.json +++ b/examples/specs/normalized/layer_line_rolling_mean_point_raw_normalized.vl.json @@ -4,12 +4,6 @@ "width": 400, "height": 300, "data": {"url": "data/seattle-weather.csv"}, - "transform": [ - { - "window": [{"field": "temp_max", "op": "mean", "as": "rolling_mean"}], - "frame": [-15, 15] - } - ], "layer": [ { "mark": {"type": "point", "opacity": 0.3}, @@ -35,5 +29,11 @@ } } } + ], + "transform": [ + { + "window": [{"field": "temp_max", "op": "mean", "as": "rolling_mean"}], + "frame": [-15, 15] + } ] } \ No newline at end of file diff --git a/examples/specs/normalized/layer_line_window_normalized.vl.json b/examples/specs/normalized/layer_line_window_normalized.vl.json index c444ae8b60..31494fa243 100644 --- a/examples/specs/normalized/layer_line_window_normalized.vl.json +++ b/examples/specs/normalized/layer_line_window_normalized.vl.json @@ -5,12 +5,12 @@ "layer": [ { "data": {"name": "falcon"}, + "mark": "line", "transform": [ {"window": [{"op": "row_number", "as": "row"}]}, {"calculate": "1000/datum.data", "as": "fps"}, {"calculate": "'Falcon'", "as": "system"} ], - "mark": "line", "encoding": { "x": { "field": "row", @@ -36,12 +36,12 @@ }, { "data": {"name": "square"}, + "mark": "line", "transform": [ {"window": [{"op": "row_number", "as": "row"}]}, {"calculate": "1000/datum.data", "as": "fps"}, {"calculate": "'Square Crossfilter (3M)'", "as": "system"} ], - "mark": "line", "encoding": { "x": { "field": "row", diff --git a/examples/specs/normalized/layer_ranged_dot_normalized.vl.json b/examples/specs/normalized/layer_ranged_dot_normalized.vl.json index 291fc74ed7..f31fc07a92 100644 --- a/examples/specs/normalized/layer_ranged_dot_normalized.vl.json +++ b/examples/specs/normalized/layer_ranged_dot_normalized.vl.json @@ -2,15 +2,6 @@ "$schema": "https://vega.github.io/schema/vega-lite/v4.json", "description": "A ranged dot plot that uses 'layer' to convey changing life expectancy for the five most populous countries (between 1955 and 2000).", "data": {"url": "data/countries.json"}, - "transform": [ - { - "filter": { - "field": "country", - "oneOf": ["China", "India", "United States", "Indonesia", "Brazil"] - } - }, - {"filter": {"field": "year", "oneOf": [1955, 2000]}} - ], "layer": [ { "mark": "line", @@ -64,5 +55,14 @@ "opacity": {"value": 1} } } + ], + "transform": [ + { + "filter": { + "field": "country", + "oneOf": ["China", "India", "United States", "Indonesia", "Brazil"] + } + }, + {"filter": {"field": "year", "oneOf": [1955, 2000]}} ] } \ No newline at end of file diff --git a/examples/specs/normalized/layer_text_heatmap_joinaggregate_normalized.vl.json b/examples/specs/normalized/layer_text_heatmap_joinaggregate_normalized.vl.json index b7e759e55a..3f48d96cae 100644 --- a/examples/specs/normalized/layer_text_heatmap_joinaggregate_normalized.vl.json +++ b/examples/specs/normalized/layer_text_heatmap_joinaggregate_normalized.vl.json @@ -1,21 +1,6 @@ { "$schema": "https://vega.github.io/schema/vega-lite/v4.json", "data": {"url": "data/cars.json"}, - "transform": [ - { - "aggregate": [{"op": "count", "as": "num_cars"}], - "groupby": ["Origin", "Cylinders"] - }, - { - "joinaggregate": [ - {"op": "max", "field": "num_cars", "as": "max_num_cars"} - ] - }, - { - "calculate": "datum.num_cars > 0.5 * datum.max_num_cars", - "as": "more_than_half" - } - ], "layer": [ { "mark": "rect", @@ -45,5 +30,20 @@ } } ], - "config": {"axis": {"grid": true, "tickBand": "extent"}} + "config": {"axis": {"grid": true, "tickBand": "extent"}}, + "transform": [ + { + "aggregate": [{"op": "count", "as": "num_cars"}], + "groupby": ["Origin", "Cylinders"] + }, + { + "joinaggregate": [ + {"op": "max", "field": "num_cars", "as": "max_num_cars"} + ] + }, + { + "calculate": "datum.num_cars > 0.5 * datum.max_num_cars", + "as": "more_than_half" + } + ] } \ No newline at end of file diff --git a/examples/specs/normalized/layer_text_heatmap_normalized.vl.json b/examples/specs/normalized/layer_text_heatmap_normalized.vl.json index 16a6fbbd1e..41c63c3dc2 100644 --- a/examples/specs/normalized/layer_text_heatmap_normalized.vl.json +++ b/examples/specs/normalized/layer_text_heatmap_normalized.vl.json @@ -1,12 +1,6 @@ { "$schema": "https://vega.github.io/schema/vega-lite/v4.json", "data": {"url": "data/cars.json"}, - "transform": [ - { - "aggregate": [{"op": "count", "as": "num_cars"}], - "groupby": ["Origin", "Cylinders"] - } - ], "layer": [ { "mark": "rect", @@ -28,11 +22,17 @@ "x": {"field": "Cylinders", "type": "ordinal"}, "text": {"field": "num_cars", "type": "quantitative"}, "color": { - "condition": {"test": "datum['num_cars'] < 40", "value": "black"}, + "condition": {"value": "black", "test": "datum['num_cars'] < 40"}, "value": "white" } } } ], - "config": {"axis": {"grid": true, "tickBand": "extent"}} + "config": {"axis": {"grid": true, "tickBand": "extent"}}, + "transform": [ + { + "aggregate": [{"op": "count", "as": "num_cars"}], + "groupby": ["Origin", "Cylinders"] + } + ] } \ No newline at end of file diff --git a/examples/specs/normalized/line_color_label_normalized.vl.json b/examples/specs/normalized/line_color_label_normalized.vl.json index 7ae54f5fde..1ed731b72a 100644 --- a/examples/specs/normalized/line_color_label_normalized.vl.json +++ b/examples/specs/normalized/line_color_label_normalized.vl.json @@ -2,7 +2,6 @@ "$schema": "https://vega.github.io/schema/vega-lite/v4.json", "description": "Multi-series line chart with labels.", "data": {"url": "data/stocks.csv"}, - "transform": [{"filter": "datum.symbol!=='IBM'"}], "layer": [ { "mark": "line", @@ -42,5 +41,6 @@ ] } ], - "config": {"view": {"stroke": null}} + "config": {"view": {"stroke": null}}, + "transform": [{"filter": "datum.symbol!=='IBM'"}] } \ No newline at end of file diff --git a/examples/specs/normalized/parallel_coordinate_normalized.vl.json b/examples/specs/normalized/parallel_coordinate_normalized.vl.json index ca54b7bbee..f4651fe0d2 100644 --- a/examples/specs/normalized/parallel_coordinate_normalized.vl.json +++ b/examples/specs/normalized/parallel_coordinate_normalized.vl.json @@ -4,30 +4,6 @@ "data": {"url": "data/penguins.json"}, "width": 600, "height": 300, - "transform": [ - {"filter": "datum['Beak Length (mm)']"}, - {"window": [{"op": "count", "as": "index"}]}, - { - "fold": [ - "Beak Length (mm)", - "Beak Depth (mm)", - "Flipper Length (mm)", - "Body Mass (g)" - ] - }, - { - "joinaggregate": [ - {"op": "min", "field": "value", "as": "min"}, - {"op": "max", "field": "value", "as": "max"} - ], - "groupby": ["key"] - }, - { - "calculate": "(datum.value - datum.min) / (datum.max-datum.min)", - "as": "norm_val" - }, - {"calculate": "(datum.min + datum.max) / 2", "as": "mid"} - ], "layer": [ { "mark": {"type": "rule", "color": "#ccc"}, @@ -119,5 +95,29 @@ "label": {"baseline": "middle", "align": "right", "dx": -5}, "tick": {"orient": "horizontal"} } - } + }, + "transform": [ + {"filter": "datum['Beak Length (mm)']"}, + {"window": [{"op": "count", "as": "index"}]}, + { + "fold": [ + "Beak Length (mm)", + "Beak Depth (mm)", + "Flipper Length (mm)", + "Body Mass (g)" + ] + }, + { + "joinaggregate": [ + {"op": "min", "field": "value", "as": "min"}, + {"op": "max", "field": "value", "as": "max"} + ], + "groupby": ["key"] + }, + { + "calculate": "(datum.value - datum.min) / (datum.max-datum.min)", + "as": "norm_val" + }, + {"calculate": "(datum.min + datum.max) / 2", "as": "mid"} + ] } \ No newline at end of file diff --git a/examples/specs/normalized/trellis_area_normalized.vl.json b/examples/specs/normalized/trellis_area_normalized.vl.json index 94055d4929..11894e4f30 100644 --- a/examples/specs/normalized/trellis_area_normalized.vl.json +++ b/examples/specs/normalized/trellis_area_normalized.vl.json @@ -1,8 +1,8 @@ { "$schema": "https://vega.github.io/schema/vega-lite/v4.json", "description": "Stock prices of four large companies as a small multiples of area charts.", - "transform": [{"filter": "datum.symbol !== 'GOOG'"}], "data": {"url": "data/stocks.csv"}, + "transform": [{"filter": "datum.symbol !== 'GOOG'"}], "facet": {"row": {"field": "symbol", "type": "nominal", "title": "Symbol"}}, "spec": { "width": 300, diff --git a/examples/specs/normalized/trellis_area_sort_array_normalized.vl.json b/examples/specs/normalized/trellis_area_sort_array_normalized.vl.json index 6eb53ea7dc..33590fa37a 100644 --- a/examples/specs/normalized/trellis_area_sort_array_normalized.vl.json +++ b/examples/specs/normalized/trellis_area_sort_array_normalized.vl.json @@ -1,8 +1,8 @@ { "$schema": "https://vega.github.io/schema/vega-lite/v4.json", "description": "Stock prices of four large companies as a small multiples of area charts.", - "transform": [{"filter": "datum.symbol !== 'GOOG'"}], "data": {"url": "data/stocks.csv"}, + "transform": [{"filter": "datum.symbol !== 'GOOG'"}], "facet": { "row": { "field": "symbol", diff --git a/examples/specs/normalized/waterfall_chart_normalized.vl.json b/examples/specs/normalized/waterfall_chart_normalized.vl.json index 0f226c1ea4..cd7b05087c 100644 --- a/examples/specs/normalized/waterfall_chart_normalized.vl.json +++ b/examples/specs/normalized/waterfall_chart_normalized.vl.json @@ -20,35 +20,6 @@ }, "width": 800, "height": 450, - "transform": [ - {"window": [{"op": "sum", "field": "amount", "as": "sum"}]}, - {"window": [{"op": "lead", "field": "label", "as": "lead"}]}, - { - "calculate": "datum.lead === null ? datum.label : datum.lead", - "as": "lead" - }, - { - "calculate": "datum.label === 'End' ? 0 : datum.sum - datum.amount", - "as": "previous_sum" - }, - { - "calculate": "datum.label === 'End' ? datum.sum : datum.amount", - "as": "amount" - }, - { - "calculate": "(datum.label !== 'Begin' && datum.label !== 'End' && datum.amount > 0 ? '+' : '') + datum.amount", - "as": "text_amount" - }, - {"calculate": "(datum.sum + datum.previous_sum) / 2", "as": "center"}, - { - "calculate": "datum.sum < datum.previous_sum ? datum.sum : ''", - "as": "sum_dec" - }, - { - "calculate": "datum.sum > datum.previous_sum ? datum.sum : ''", - "as": "sum_inc" - } - ], "layer": [ { "mark": {"type": "bar", "size": 45}, @@ -68,10 +39,10 @@ "color": { "condition": [ { - "test": "datum.label === 'Begin' || datum.label === 'End'", - "value": "#f7e0b6" + "value": "#f7e0b6", + "test": "datum.label === 'Begin' || datum.label === 'End'" }, - {"test": "datum.sum < datum.previous_sum", "value": "#f78a64"} + {"value": "#f78a64", "test": "datum.sum < datum.previous_sum"} ], "value": "#93c4aa" } @@ -137,8 +108,8 @@ "color": { "condition": [ { - "test": "datum.label === 'Begin' || datum.label === 'End'", - "value": "#725a30" + "value": "#725a30", + "test": "datum.label === 'Begin' || datum.label === 'End'" } ], "value": "white" @@ -146,5 +117,34 @@ } } ], - "config": {"text": {"fontWeight": "bold", "color": "#404040"}} + "config": {"text": {"fontWeight": "bold", "color": "#404040"}}, + "transform": [ + {"window": [{"op": "sum", "field": "amount", "as": "sum"}]}, + {"window": [{"op": "lead", "field": "label", "as": "lead"}]}, + { + "calculate": "datum.lead === null ? datum.label : datum.lead", + "as": "lead" + }, + { + "calculate": "datum.label === 'End' ? 0 : datum.sum - datum.amount", + "as": "previous_sum" + }, + { + "calculate": "datum.label === 'End' ? datum.sum : datum.amount", + "as": "amount" + }, + { + "calculate": "(datum.label !== 'Begin' && datum.label !== 'End' && datum.amount > 0 ? '+' : '') + datum.amount", + "as": "text_amount" + }, + {"calculate": "(datum.sum + datum.previous_sum) / 2", "as": "center"}, + { + "calculate": "datum.sum < datum.previous_sum ? datum.sum : ''", + "as": "sum_dec" + }, + { + "calculate": "datum.sum > datum.previous_sum ? datum.sum : ''", + "as": "sum_inc" + } + ] } \ No newline at end of file diff --git a/examples/specs/normalized/wheat_wages_normalized.vl.json b/examples/specs/normalized/wheat_wages_normalized.vl.json index 72a44bf81d..583829dcd1 100644 --- a/examples/specs/normalized/wheat_wages_normalized.vl.json +++ b/examples/specs/normalized/wheat_wages_normalized.vl.json @@ -3,7 +3,6 @@ "width": 900, "height": 400, "data": {"url": "data/wheat.json"}, - "transform": [{"calculate": "+datum.year + 5", "as": "year_end"}], "layer": [ { "mark": {"type": "bar", "fill": "#aaa", "stroke": "#999"}, @@ -76,13 +75,6 @@ }, { "data": {"url": "data/monarchs.json"}, - "transform": [ - { - "calculate": "((!datum.commonwealth && datum.index % 2) ? -1: 1) * 2 + 95", - "as": "offset" - }, - {"calculate": "95", "as": "y"} - ], "mark": {"type": "bar", "stroke": "#000"}, "encoding": { "y": {"type": "quantitative", "axis": {"zindex": 1}, "field": "y"}, @@ -98,17 +90,17 @@ "scale": {"range": ["black", "white"]}, "legend": null } - } - }, - { - "data": {"url": "data/monarchs.json"}, + }, "transform": [ { - "calculate": "((!datum.commonwealth && datum.index % 2) ? -1: 1) + 95", - "as": "off2" + "calculate": "((!datum.commonwealth && datum.index % 2) ? -1: 1) * 2 + 95", + "as": "offset" }, - {"calculate": "+datum.start + (+datum.end - +datum.start)/2", "as": "x"} - ], + {"calculate": "95", "as": "y"} + ] + }, + { + "data": {"url": "data/monarchs.json"}, "mark": { "type": "text", "yOffset": 16, @@ -124,7 +116,14 @@ "field": "x" }, "text": {"field": "name"} - } + }, + "transform": [ + { + "calculate": "((!datum.commonwealth && datum.index % 2) ? -1: 1) + 95", + "as": "off2" + }, + {"calculate": "+datum.start + (+datum.end - +datum.start)/2", "as": "x"} + ] } ], "config": { @@ -135,5 +134,6 @@ "domain": false }, "view": {"stroke": "transparent"} - } + }, + "transform": [{"calculate": "+datum.year + 5", "as": "year_end"}] } \ No newline at end of file diff --git a/src/normalize/base.ts b/src/normalize/base.ts index d6f16c8c1b..89c3603ec3 100644 --- a/src/normalize/base.ts +++ b/src/normalize/base.ts @@ -2,11 +2,13 @@ import {SignalRef} from 'vega'; import {FieldName} from '../channeldef'; import {Config} from '../config'; import {Encoding} from '../encoding'; +import {ParameterPredicate} from '../predicate'; import {Projection} from '../projection'; import {TopLevelSelectionParameter} from '../selection'; import {GenericSpec, NormalizedSpec} from '../spec'; import {GenericLayerSpec, NormalizedLayerSpec} from '../spec/layer'; import {GenericUnitSpec, NormalizedUnitSpec} from '../spec/unit'; +import {Dict} from '../util'; import {RepeaterValue} from './repeater'; export type Normalize, NS extends NormalizedSpec> = ( @@ -43,5 +45,7 @@ export interface NormalizerParams { repeater?: RepeaterValue; repeaterPrefix?: string; selections?: TopLevelSelectionParameter[]; + emptySelections?: Dict; + selectionPredicates?: Dict; path?: string[]; } diff --git a/src/normalize/selectioncompat.ts b/src/normalize/selectioncompat.ts index ff9516f91b..e3a41586b4 100644 --- a/src/normalize/selectioncompat.ts +++ b/src/normalize/selectioncompat.ts @@ -1,10 +1,11 @@ +import {isArray} from 'vega'; import {BinParams, isBinParams} from '../bin'; -import {Field} from '../channeldef'; +import {ChannelDef, Field, isConditionalDef, isFieldDef, isScaleFieldDef} from '../channeldef'; import {LogicalComposition, normalizeLogicalComposition} from '../logical'; -import {SelectionParameter} from '../selection'; import {FacetedUnitSpec, GenericSpec, LayerSpec, RepeatSpec, UnitSpec} from '../spec'; import {SpecMapper} from '../spec/map'; import {isBin, isFilter, isLookup} from '../transform'; +import {duplicate, entries, vals} from '../util'; import {NormalizerParams} from './base'; export class SelectionCompatibilityNormalizer extends SpecMapper< @@ -13,105 +14,141 @@ export class SelectionCompatibilityNormalizer extends SpecMapper< LayerSpec, UnitSpec > { - public map(spec: GenericSpec, LayerSpec, RepeatSpec, Field>, params: NormalizerParams) { - if (spec.transform) { - spec.transform = spec.transform.map(t => { - if (isFilter(t)) { - return {filter: normalizePredicate(t)}; - } else if (isBin(t) && isBinParams(t.bin)) { - return { - ...t, - bin: normalizeBinExtent(t.bin) - }; - } else if (isLookup(t)) { - const {selection, ...rest} = t.from as any; - return selection - ? { - ...t, - from: {param: selection, ...rest} - } - : t; - } - return t; - }); + public map( + spec: GenericSpec, LayerSpec, RepeatSpec, Field>, + normParams: NormalizerParams + ) { + normParams.emptySelections = normParams.emptySelections ?? {}; + normParams.selectionPredicates = normParams.selectionPredicates ?? {}; + spec = normalizeTransforms(spec, normParams); + return super.map(spec, normParams); + } + + public mapLayerOrUnit(spec: FacetedUnitSpec | LayerSpec, normParams: NormalizerParams) { + spec = normalizeTransforms(spec, normParams); + + if (spec.encoding) { + const encoding = {}; + for (const [channel, enc] of entries(spec.encoding)) { + encoding[channel] = normalizeChannelDef(enc, normParams); + } + + spec = {...spec, encoding}; } - return super.map(spec, params); + return super.mapLayerOrUnit(spec, normParams); } - public mapUnit(spec: UnitSpec) { + public mapUnit(spec: UnitSpec, normParams: NormalizerParams) { const {selection, ...rest} = spec as any; - const encoding = {}; - const params: SelectionParameter[] = []; - - if (!selection) return spec; - - for (const [name, selDef] of Object.entries(selection)) { - const {init, bind, ...select} = selDef as any; - if (select.type === 'single') { - select.type = 'point'; - select.toggle = false; - } else if (select.type === 'multi') { - select.type = 'point'; - } + if (selection) { + return { + ...rest, + params: entries(selection).map(([name, selDef]) => { + const {init: value, bind, empty, ...select} = selDef as any; + if (select.type === 'single') { + select.type = 'point'; + select.toggle = false; + } else if (select.type === 'multi') { + select.type = 'point'; + } - params.push({ - name, - value: init, - select, - bind - }); + // Propagate emptiness forwards and backwards + normParams.emptySelections[name] = empty !== 'none'; + for (const pred of vals(normParams.selectionPredicates[name] ?? {})) { + pred.empty = empty !== 'none'; + } + + return {name, value, select, bind}; + }) + }; } - for (const [channel, enc] of Object.entries(spec.encoding)) { - encoding[channel] = { - ...normalizeChannelDef(enc), - ...(enc.condition + return spec; + } +} + +function normalizeTransforms(spec: any, normParams: NormalizerParams) { + const {transform: tx, ...rest} = spec; + if (tx) { + const transform = tx.map((t: any) => { + if (isFilter(t)) { + return {filter: normalizePredicate(t, normParams)}; + } else if (isBin(t) && isBinParams(t.bin)) { + return { + ...t, + bin: normalizeBinExtent(t.bin) + }; + } else if (isLookup(t)) { + const {selection: param, ...from} = t.from as any; + return param ? { - condition: { - ...normalizeChannelDef(enc.condition), - test: normalizePredicate(enc.condition) - } + ...t, + from: {param, ...from} } - : {}) - }; - } + : t; + } + return t; + }); - return {...rest, params, encoding}; + return {...rest, transform}; } + + return spec; } -function normalizeChannelDef(obj: any) { - const {bin, scale, selection, ...rest} = obj; - const {selection: param, ...domain} = scale?.domain || {}; - return { - ...rest, - ...(bin ? {bin: isBinParams(bin) ? normalizeBinExtent(bin) : bin} : {}), - ...(scale - ? { - scale: { - ...scale, - domain: param ? {...domain, param} : domain - } - } - : {}) - }; +function normalizeChannelDef(obj: any, normParams: NormalizerParams): ChannelDef { + const enc = duplicate(obj); + + if (isFieldDef(enc) && isBinParams(enc.bin)) { + enc.bin = normalizeBinExtent(enc.bin); + } + + if (isScaleFieldDef(enc) && (enc.scale?.domain as any)?.selection) { + const {selection: param, ...domain} = enc.scale.domain as any; + enc.scale.domain = {...domain, ...(param ? {param} : {})}; + } + + if (isConditionalDef(enc)) { + if (isArray(enc.condition)) { + enc.condition = enc.condition.map((c: any) => { + const {selection, param, test, ...cond} = c; + return param ? c : {...cond, test: normalizePredicate(c, normParams)}; + }); + } else { + const {selection, param, test, ...cond} = normalizeChannelDef(enc.condition, normParams) as any; + enc.condition = param + ? enc.condition + : { + ...cond, + test: normalizePredicate(enc.condition, normParams) + }; + } + } + + return enc; } function normalizeBinExtent(bin: BinParams): BinParams { const ext = bin.extent as any; if (ext?.selection) { - const {selection, ...rest} = ext; - return {...bin, extent: {...rest, param: selection}}; + const {selection: param, ...rest} = ext; + return {...bin, extent: {...rest, param}}; } return bin; } -function normalizePredicate(op: any) { +function normalizePredicate(op: any, normParams: NormalizerParams) { // Normalize old compositions of selection names (e.g., selection: {and: ["one", "two"]}) const normalizeSelectionComposition = (o: LogicalComposition) => { - return normalizeLogicalComposition(o, param => ({param} as any)); + return normalizeLogicalComposition(o, param => { + const empty = normParams.emptySelections[param] ?? true; + const pred = {param, empty}; + normParams.selectionPredicates[param] = normParams.selectionPredicates[param] ?? []; + normParams.selectionPredicates[param].push(pred); + return pred as any; + }); }; return op.selection diff --git a/test/normalize/selectioncompat.test.ts b/test/normalize/selectioncompat.test.ts index 8425b93526..e35bea5216 100644 --- a/test/normalize/selectioncompat.test.ts +++ b/test/normalize/selectioncompat.test.ts @@ -1,7 +1,9 @@ import {normalize} from '../../src'; +import {NormalizerParams} from '../../src/normalize'; import {SelectionCompatibilityNormalizer} from '../../src/normalize/selectioncompat'; import {NormalizedUnitSpec} from '../../src/spec'; +const normParams: NormalizerParams = {config: {}, emptySelections: {}, selectionPredicates: {}}; const selectionCompatNormalizer = new SelectionCompatibilityNormalizer(); const unit: NormalizedUnitSpec = { data: {url: 'data/cars.json'}, @@ -27,7 +29,7 @@ describe('SelectionCompatibilityNormalizer', () => { } }; - const normedUnit = selectionCompatNormalizer.mapUnit(spec); + const normedUnit = selectionCompatNormalizer.mapUnit(spec, normParams); expect(normedUnit.selection).toBeUndefined(); expect(normedUnit.params).toHaveLength(1); expect(normedUnit.params[0]).toHaveProperty('name', 'CylYr'); @@ -54,7 +56,7 @@ describe('SelectionCompatibilityNormalizer', () => { } }; - const normedUnit = selectionCompatNormalizer.mapUnit(spec); + const normedUnit = selectionCompatNormalizer.mapUnit(spec, normParams); expect(normedUnit.selection).toBeUndefined(); expect(normedUnit.params).toHaveLength(1); expect(normedUnit.params[0]).toHaveProperty('name', 'Org'); @@ -81,7 +83,7 @@ describe('SelectionCompatibilityNormalizer', () => { } }; - const normedUnit = selectionCompatNormalizer.mapUnit(spec); + const normedUnit = selectionCompatNormalizer.mapUnit(spec, normParams); expect(normedUnit.selection).toBeUndefined(); expect(normedUnit.params).toHaveLength(2); expect(normedUnit.params[0]).toHaveProperty('name', 'brush'); @@ -99,8 +101,8 @@ describe('SelectionCompatibilityNormalizer', () => { {filter: {or: [{selection: {not: 'foo'}}, 'false']}} ], selection: { - foo: {type: 'single'}, - bar: {type: 'multi'}, + foo: {type: 'single', empty: 'all'}, + bar: {type: 'multi', empty: 'none'}, brush: {type: 'interval'} }, mark: 'line', @@ -128,6 +130,18 @@ describe('SelectionCompatibilityNormalizer', () => { type: 'ordinal' }, value: 'grey' + }, + strokeWidth: { + condition: [ + { + test: { + and: [{selection: 'foo'}, 'length(data("foo_store"))'] + }, + value: 2 + }, + {selection: 'bar', value: 1} + ], + value: 0 } } }; @@ -135,18 +149,38 @@ describe('SelectionCompatibilityNormalizer', () => { const normalized = normalize(spec) as any; expect(normalized.transform).toEqual( expect.arrayContaining([ - {filter: {param: 'foo'}}, - {filter: {and: [{param: 'foo'}, {param: 'bar'}]}}, - {filter: {or: [{not: {param: 'foo'}}, 'false']}} + {filter: {param: 'foo', empty: true}}, + { + filter: { + and: [ + {param: 'foo', empty: true}, + {param: 'bar', empty: false} + ] + } + }, + {filter: {or: [{not: {param: 'foo', empty: true}}, 'false']}} ]) ); - expect(normalized.encoding.x.condition.test).toEqual({param: 'bar'}); + expect(normalized.encoding.x.condition.test).toEqual({param: 'bar', empty: false}); expect(normalized.encoding.y.condition.test).toEqual({ - or: [{param: 'foo'}, {param: 'brush'}] + or: [ + {param: 'foo', empty: true}, + {param: 'brush', empty: true} + ] }); expect(normalized.encoding.color.condition.test).toEqual({ - and: [{not: {param: 'foo'}}, 'true'] + and: [{not: {param: 'foo', empty: true}}, 'true'] }); + expect(normalized.encoding.strokeWidth.condition).toEqual([ + { + value: 2, + test: {and: [{param: 'foo', empty: true}, 'length(data("foo_store"))']} + }, + { + value: 1, + test: {param: 'bar', empty: false} + } + ]); // And make sure we didn't delete any properties by mistake expect(normalized.encoding.x).toHaveProperty('field', 'Horsepower'); @@ -356,4 +390,123 @@ describe('SelectionCompatibilityNormalizer', () => { expect(normalized.spec.layer[0]).toHaveProperty('params'); expect(normalized.spec.layer[0].params[0].name).toEqual('brush'); }); + + it('should normalize multi-views', () => { + const spec: any = { + data: {url: 'data/stocks.csv'}, + encoding: { + color: { + condition: { + selection: 'hover', + field: 'symbol', + type: 'nominal' + }, + value: 'grey' + } + }, + layer: [ + { + encoding: { + x: {field: 'date', type: 'temporal', title: 'date'}, + y: {field: 'price', type: 'quantitative', title: 'price'} + }, + layer: [ + { + selection: { + hover: { + type: 'single', + on: 'mouseover', + empty: 'all', + fields: ['symbol'], + init: {symbol: 'AAPL'} + } + }, + mark: {type: 'line', strokeWidth: 8, stroke: 'transparent'} + }, + { + mark: 'line' + } + ] + }, + { + mark: {type: 'circle'}, + encoding: { + x: {aggregate: 'max', field: 'date', type: 'temporal'}, + y: {aggregate: {argmax: 'date'}, field: 'price', type: 'quantitative'} + } + } + ] + }; + + expect(normalize(spec)).toEqual({ + data: {url: 'data/stocks.csv'}, + layer: [ + { + layer: [ + { + mark: {type: 'line', strokeWidth: 8, stroke: 'transparent'}, + params: [ + { + name: 'hover', + value: {symbol: 'AAPL'}, + select: { + type: 'point', + on: 'mouseover', + fields: ['symbol'], + toggle: false + } + } + ], + encoding: { + color: { + condition: { + field: 'symbol', + type: 'nominal', + test: {param: 'hover', empty: true} + }, + value: 'grey' + }, + x: {field: 'date', type: 'temporal', title: 'date'}, + y: {field: 'price', type: 'quantitative', title: 'price'} + } + }, + { + mark: 'line', + encoding: { + color: { + condition: { + field: 'symbol', + type: 'nominal', + test: {param: 'hover', empty: true} + }, + value: 'grey' + }, + x: {field: 'date', type: 'temporal', title: 'date'}, + y: {field: 'price', type: 'quantitative', title: 'price'} + } + } + ] + }, + { + mark: {type: 'circle'}, + encoding: { + color: { + condition: { + field: 'symbol', + type: 'nominal', + test: {param: 'hover', empty: true} + }, + value: 'grey' + }, + x: {aggregate: 'max', field: 'date', type: 'temporal'}, + y: { + aggregate: {argmax: 'date'}, + field: 'price', + type: 'quantitative' + } + } + } + ] + }); + }); });