diff --git a/examples/compiled/bar_qq_stack.png b/examples/compiled/bar_qq_stack.png
new file mode 100644
index 0000000000..e4f5fdb8a5
Binary files /dev/null and b/examples/compiled/bar_qq_stack.png differ
diff --git a/examples/compiled/bar_qq_stack.svg b/examples/compiled/bar_qq_stack.svg
new file mode 100644
index 0000000000..6d13253cd9
--- /dev/null
+++ b/examples/compiled/bar_qq_stack.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/examples/compiled/bar_qq_stack.vg.json b/examples/compiled/bar_qq_stack.vg.json
new file mode 100644
index 0000000000..c7c5a653e6
--- /dev/null
+++ b/examples/compiled/bar_qq_stack.vg.json
@@ -0,0 +1,123 @@
+{
+ "$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": 28}, {"a": 1, "b": 55}, {"a": 5, "b": 43}]
+ },
+ {
+ "name": "data_0",
+ "source": "source_0",
+ "transform": [
+ {
+ "type": "stack",
+ "groupby": ["a"],
+ "field": "b",
+ "sort": {"field": [], "order": []},
+ "as": ["b_start", "b_end"],
+ "offset": "zero"
+ },
+ {
+ "type": "filter",
+ "expr": "isValid(datum[\"a\"]) && isFinite(+datum[\"a\"]) && isValid(datum[\"b\"]) && isFinite(+datum[\"b\"])"
+ }
+ ]
+ }
+ ],
+ "marks": [
+ {
+ "name": "marks",
+ "type": "rect",
+ "style": ["bar"],
+ "from": {"data": "data_0"},
+ "encode": {
+ "update": {
+ "fill": {"value": "#4c78a8"},
+ "ariaRoleDescription": {"value": "bar"},
+ "description": {
+ "signal": "\"a: \" + (format(datum[\"a\"], \"\")) + \"; b: \" + (format(datum[\"b\"], \"\"))"
+ },
+ "xc": {"scale": "x", "field": "a"},
+ "width": {"value": 5},
+ "y": {"scale": "y", "field": "b_end"},
+ "y2": {"scale": "y", "field": "b_start"}
+ }
+ }
+ }
+ ],
+ "scales": [
+ {
+ "name": "x",
+ "type": "linear",
+ "domain": {"data": "data_0", "field": "a"},
+ "range": [0, {"signal": "width"}],
+ "nice": true,
+ "zero": false,
+ "padding": 5
+ },
+ {
+ "name": "y",
+ "type": "linear",
+ "domain": {"data": "data_0", "fields": ["b_start", "b_end"]},
+ "range": [{"signal": "height"}, 0],
+ "nice": true,
+ "zero": true
+ }
+ ],
+ "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",
+ "labelAngle": 0,
+ "labelBaseline": "top",
+ "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
+ }
+ ]
+}
diff --git a/examples/compiled/bar_qq_stack_horizontal.png b/examples/compiled/bar_qq_stack_horizontal.png
new file mode 100644
index 0000000000..06752eb9db
Binary files /dev/null and b/examples/compiled/bar_qq_stack_horizontal.png differ
diff --git a/examples/compiled/bar_qq_stack_horizontal.svg b/examples/compiled/bar_qq_stack_horizontal.svg
new file mode 100644
index 0000000000..049a2a761e
--- /dev/null
+++ b/examples/compiled/bar_qq_stack_horizontal.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/examples/compiled/bar_qq_stack_horizontal.vg.json b/examples/compiled/bar_qq_stack_horizontal.vg.json
new file mode 100644
index 0000000000..734c1bd839
--- /dev/null
+++ b/examples/compiled/bar_qq_stack_horizontal.vg.json
@@ -0,0 +1,124 @@
+{
+ "$schema": "https://vega.github.io/schema/vega/v5.json",
+ "description": "A simple bar chart with embedded data.",
+ "background": "white",
+ "padding": 5,
+ "width": 200,
+ "height": 200,
+ "style": "cell",
+ "data": [
+ {
+ "name": "source_0",
+ "values": [{"a": 1, "b": 28}, {"a": 1, "b": 55}, {"a": 5, "b": 43}]
+ },
+ {
+ "name": "data_0",
+ "source": "source_0",
+ "transform": [
+ {
+ "type": "stack",
+ "groupby": ["a"],
+ "field": "b",
+ "sort": {"field": [], "order": []},
+ "as": ["b_start", "b_end"],
+ "offset": "zero"
+ },
+ {
+ "type": "filter",
+ "expr": "isValid(datum[\"b\"]) && isFinite(+datum[\"b\"]) && isValid(datum[\"a\"]) && isFinite(+datum[\"a\"])"
+ }
+ ]
+ }
+ ],
+ "marks": [
+ {
+ "name": "marks",
+ "type": "rect",
+ "style": ["bar"],
+ "from": {"data": "data_0"},
+ "encode": {
+ "update": {
+ "fill": {"value": "#4c78a8"},
+ "ariaRoleDescription": {"value": "bar"},
+ "description": {
+ "signal": "\"b: \" + (format(datum[\"b\"], \"\")) + \"; a: \" + (format(datum[\"a\"], \"\"))"
+ },
+ "x": {"scale": "x", "field": "b_end"},
+ "x2": {"scale": "x", "field": "b_start"},
+ "yc": {"scale": "y", "field": "a"},
+ "height": {"value": 5}
+ }
+ }
+ }
+ ],
+ "scales": [
+ {
+ "name": "x",
+ "type": "linear",
+ "domain": {"data": "data_0", "fields": ["b_start", "b_end"]},
+ "range": [0, {"signal": "width"}],
+ "nice": true,
+ "zero": true
+ },
+ {
+ "name": "y",
+ "type": "linear",
+ "domain": {"data": "data_0", "field": "a"},
+ "range": [{"signal": "height"}, 0],
+ "nice": true,
+ "zero": false,
+ "padding": 5
+ }
+ ],
+ "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": "b",
+ "labelFlush": true,
+ "labelOverlap": true,
+ "tickCount": {"signal": "ceil(width/40)"},
+ "zindex": 0
+ },
+ {
+ "scale": "y",
+ "orient": "left",
+ "grid": false,
+ "title": "a",
+ "labelAngle": 0,
+ "labelAlign": "right",
+ "labelOverlap": true,
+ "tickCount": {"signal": "ceil(height/40)"},
+ "zindex": 0
+ }
+ ]
+}
diff --git a/examples/specs/bar_qq_stack.vl.json b/examples/specs/bar_qq_stack.vl.json
new file mode 100644
index 0000000000..64bc4db147
--- /dev/null
+++ b/examples/specs/bar_qq_stack.vl.json
@@ -0,0 +1,13 @@
+{
+ "$schema": "https://vega.github.io/schema/vega-lite/v5.json",
+ "data": {
+ "values": [
+ {"a": 1, "b": 28}, {"a": 1, "b": 55}, {"a": 5, "b": 43}
+ ]
+ },
+ "mark": "bar",
+ "encoding": {
+ "x": {"field": "a", "type": "quantitative", "axis": {"labelAngle": 0}},
+ "y": {"field": "b", "type": "quantitative"}
+ }
+}
diff --git a/examples/specs/bar_qq_stack_horizontal.vl.json b/examples/specs/bar_qq_stack_horizontal.vl.json
new file mode 100644
index 0000000000..04ba5cd170
--- /dev/null
+++ b/examples/specs/bar_qq_stack_horizontal.vl.json
@@ -0,0 +1,14 @@
+{
+ "$schema": "https://vega.github.io/schema/vega-lite/v5.json",
+ "description": "A simple bar chart with embedded data.",
+ "data": {
+ "values": [
+ {"a": 1, "b": 28}, {"a": 1, "b": 55}, {"a": 5, "b": 43}
+ ]
+ },
+ "mark": {"type": "bar", "orient": "horizontal"},
+ "encoding": {
+ "y": {"field": "a", "type": "quantitative", "axis": {"labelAngle": 0}},
+ "x": {"field": "b", "type": "quantitative"}
+ }
+}
diff --git a/src/compile/unit.ts b/src/compile/unit.ts
index c13ebde5b3..c56a8d4501 100644
--- a/src/compile/unit.ts
+++ b/src/compile/unit.ts
@@ -113,7 +113,7 @@ export class UnitModel extends ModelWithField {
});
// calculate stack properties
- this.stack = stack(mark, encoding);
+ this.stack = stack(this.markDef, encoding);
this.specifiedScales = this.initScales(mark, encoding);
this.specifiedAxes = this.initAxes(encoding);
diff --git a/src/stack.ts b/src/stack.ts
index 89035980b2..fb4663b7c0 100644
--- a/src/stack.ts
+++ b/src/stack.ts
@@ -82,10 +82,13 @@ function isUnbinnedQuantitative(channelDef: PositionDef) {
function potentialStackedChannel(
encoding: Encoding,
- x: 'x' | 'theta'
+ x: 'x' | 'theta',
+ {orient, type: mark}: MarkDef
): 'x' | 'y' | 'theta' | 'radius' | undefined {
const y = x === 'x' ? 'y' : 'radius';
+ const isCartesian = x === 'x';
+
const xDef = encoding[x];
const yDef = encoding[y];
@@ -111,6 +114,14 @@ function potentialStackedChannel(
return x;
}
}
+
+ if (isCartesian && mark === 'bar') {
+ if (orient === 'vertical') {
+ return y;
+ } else if (orient === 'horizontal') {
+ return x;
+ }
+ }
} else if (isUnbinnedQuantitative(xDef)) {
return x;
} else if (isUnbinnedQuantitative(yDef)) {
@@ -138,7 +149,9 @@ function getDimensionChannel(channel: 'x' | 'y' | 'theta' | 'radius') {
}
export function stack(m: Mark | MarkDef, encoding: Encoding): StackProperties {
- const mark = isMarkDef(m) ? m.type : m;
+ const markDef = isMarkDef(m) ? m : {type: m};
+ const mark = markDef.type;
+
// Should have stackable mark
if (!STACKABLE_MARKS.has(mark)) {
return null;
@@ -149,7 +162,8 @@ export function stack(m: Mark | MarkDef, encoding: Encoding): StackPrope
// Note: The logic here is not perfectly correct. If we want to support stacked dot plots where each dot is a pie chart with label, we have to change the stack logic here to separate Cartesian stacking for polar stacking.
// However, since we probably never want to do that, let's just note the limitation here.
- const fieldChannel = potentialStackedChannel(encoding, 'x') || potentialStackedChannel(encoding, 'theta');
+ const fieldChannel =
+ potentialStackedChannel(encoding, 'x', markDef) || potentialStackedChannel(encoding, 'theta', markDef);
if (!fieldChannel) {
return null;
diff --git a/test/compile/mark/bar.test.ts b/test/compile/mark/bar.test.ts
index 29fabebecd..1aa15b011e 100644
--- a/test/compile/mark/bar.test.ts
+++ b/test/compile/mark/bar.test.ts
@@ -903,8 +903,8 @@ describe('Mark: Bar', () => {
const props = bar.encodeEntry(model);
it('should produce horizontal bar using x, x2', () => {
- expect(props.x).toEqual({scale: 'x', field: 'Acceleration'});
- expect(props.x2).toEqual({scale: 'x', value: 0});
+ expect(props.x).toEqual({scale: 'x', field: 'Acceleration_end'});
+ expect(props.x2).toEqual({scale: 'x', field: 'Acceleration_start'});
expect(props.yc).toEqual({scale: 'y', field: 'Horsepower'});
expect(props.height).toEqual({value: defaultBarConfig.continuousBandSize});
});
@@ -930,8 +930,8 @@ describe('Mark: Bar', () => {
it('should produce horizontal bar using x, x2', () => {
expect(props.xc).toEqual({scale: 'x', field: 'Acceleration'});
expect(props.width).toEqual({value: defaultBarConfig.continuousBandSize});
- expect(props.y).toEqual({scale: 'y', field: 'Horsepower'});
- expect(props.y2).toEqual({scale: 'y', value: 0});
+ expect(props.y).toEqual({scale: 'y', field: 'Horsepower_end'});
+ expect(props.y2).toEqual({scale: 'y', field: 'Horsepower_start'});
});
});