diff --git a/examples/compiled/bar_grouped_discrete_bandsize.png b/examples/compiled/bar_grouped_discrete_bandsize.png new file mode 100644 index 0000000000..d46950f8ca Binary files /dev/null and b/examples/compiled/bar_grouped_discrete_bandsize.png differ diff --git a/examples/compiled/bar_grouped_discrete_bandsize.svg b/examples/compiled/bar_grouped_discrete_bandsize.svg new file mode 100644 index 0000000000..a32110aec9 --- /dev/null +++ b/examples/compiled/bar_grouped_discrete_bandsize.svg @@ -0,0 +1 @@ +ABCcategory0.00.20.40.60.81.0valuexyzgroup \ No newline at end of file diff --git a/examples/compiled/bar_grouped_discrete_bandsize.vg.json b/examples/compiled/bar_grouped_discrete_bandsize.vg.json new file mode 100644 index 0000000000..76916e641c --- /dev/null +++ b/examples/compiled/bar_grouped_discrete_bandsize.vg.json @@ -0,0 +1,142 @@ +{ + "$schema": "https://vega.github.io/schema/vega/v5.json", + "background": "white", + "padding": 5, + "height": 200, + "style": "cell", + "data": [ + { + "name": "source_0", + "values": [ + {"category": "A", "group": "x", "value": 0.1}, + {"category": "A", "group": "y", "value": 0.6}, + {"category": "A", "group": "z", "value": 0.9}, + {"category": "B", "group": "x", "value": 0.7}, + {"category": "B", "group": "y", "value": 0.2}, + {"category": "B", "group": "z", "value": 1.1}, + {"category": "C", "group": "x", "value": 0.6}, + {"category": "C", "group": "y", "value": 0.1}, + {"category": "C", "group": "z", "value": 0.2} + ] + }, + { + "name": "data_0", + "source": "source_0", + "transform": [ + { + "type": "stack", + "groupby": ["category", "group"], + "field": "value", + "sort": {"field": [], "order": []}, + "as": ["value_start", "value_end"], + "offset": "zero" + }, + { + "type": "filter", + "expr": "isValid(datum[\"value\"]) && isFinite(+datum[\"value\"])" + } + ] + } + ], + "signals": [ + { + "name": "x_step", + "update": "20 * bandspace(domain('xOffset').length, 0, 0) / (1-0.2)" + }, + { + "name": "width", + "update": "bandspace(domain('x').length, 0.2, 0.2) * x_step" + } + ], + "marks": [ + { + "name": "marks", + "type": "rect", + "style": ["bar"], + "from": {"data": "data_0"}, + "encode": { + "update": { + "fill": {"scale": "color", "field": "group"}, + "ariaRoleDescription": {"value": "bar"}, + "description": { + "signal": "\"category: \" + (isValid(datum[\"category\"]) ? datum[\"category\"] : \"\"+datum[\"category\"]) + \"; value: \" + (format(datum[\"value\"], \"\")) + \"; group: \" + (isValid(datum[\"group\"]) ? datum[\"group\"] : \"\"+datum[\"group\"])" + }, + "x": { + "scale": "x", + "field": "category", + "offset": {"scale": "xOffset", "field": "group", "band": 0.25} + }, + "width": {"signal": "max(0.25, 0.5 * bandwidth('xOffset'))"}, + "y": {"scale": "y", "field": "value_end"}, + "y2": {"scale": "y", "field": "value_start"} + } + } + } + ], + "scales": [ + { + "name": "x", + "type": "band", + "domain": {"data": "data_0", "field": "category", "sort": true}, + "range": {"step": {"signal": "x_step"}}, + "paddingInner": 0.2, + "paddingOuter": 0.2 + }, + { + "name": "y", + "type": "linear", + "domain": {"data": "data_0", "fields": ["value_start", "value_end"]}, + "range": [{"signal": "height"}, 0], + "nice": true, + "zero": true + }, + { + "name": "xOffset", + "type": "band", + "domain": {"data": "data_0", "field": "group", "sort": true}, + "range": {"step": 20} + }, + { + "name": "color", + "type": "ordinal", + "domain": {"data": "data_0", "field": "group", "sort": true}, + "range": "category" + } + ], + "axes": [ + { + "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": "category", + "labelAlign": "right", + "labelAngle": 270, + "labelBaseline": "middle", + "zindex": 0 + }, + { + "scale": "y", + "orient": "left", + "grid": false, + "title": "value", + "labelOverlap": true, + "tickCount": {"signal": "ceil(height/40)"}, + "zindex": 0 + } + ], + "legends": [{"fill": "color", "symbolType": "square", "title": "group"}] +} diff --git a/examples/specs/bar_grouped_discrete_bandsize.vl.json b/examples/specs/bar_grouped_discrete_bandsize.vl.json new file mode 100644 index 0000000000..f18a359829 --- /dev/null +++ b/examples/specs/bar_grouped_discrete_bandsize.vl.json @@ -0,0 +1,26 @@ +{ + "$schema": "https://vega.github.io/schema/vega-lite/v5.json", + "data": { + "values": [ + {"category":"A", "group": "x", "value":0.1}, + {"category":"A", "group": "y", "value":0.6}, + {"category":"A", "group": "z", "value":0.9}, + {"category":"B", "group": "x", "value":0.7}, + {"category":"B", "group": "y", "value":0.2}, + {"category":"B", "group": "z", "value":1.1}, + {"category":"C", "group": "x", "value":0.6}, + {"category":"C", "group": "y", "value":0.1}, + {"category":"C", "group": "z", "value":0.2} + ] + }, + "mark": "bar", + "encoding": { + "x": {"field": "category"}, + "y": {"field": "value", "type": "quantitative"}, + "xOffset": {"field": "group"}, + "color": {"field": "group"} + }, + "config": { + "bar": {"discreteBandSize": {"band": 0.5}} + } +} diff --git a/src/compile/mark/encode/offset.ts b/src/compile/mark/encode/offset.ts index 0ee51be1d2..b2da454a74 100644 --- a/src/compile/mark/encode/offset.ts +++ b/src/compile/mark/encode/offset.ts @@ -26,7 +26,7 @@ export function positionOffset({ markDef: MarkDef; encoding?: Encoding; model?: UnitModel; - bandPosition?: number; + bandPosition?: number | SignalRef; }): Offset { const channel = `${baseChannel}Offset` as | 'xOffset' diff --git a/src/compile/mark/encode/position-rect.ts b/src/compile/mark/encode/position-rect.ts index 634696f717..e34f54d62e 100644 --- a/src/compile/mark/encode/position-rect.ts +++ b/src/compile/mark/encode/position-rect.ts @@ -209,7 +209,16 @@ function positionAndSize( const vgChannel = vgAlignedPositionChannel(channel, markDef, config, defaultBandAlign); const center = vgChannel === 'xc' || vgChannel === 'yc'; - const {offset, offsetType} = positionOffset({channel, markDef, encoding, model, bandPosition: center ? 0.5 : 0}); + + const bandPosition = center + ? 0.5 + : isSignalRef(bandSize) + ? {signal: `(1-${bandSize})/2`} + : isRelativeBandSize(bandSize) + ? (1 - bandSize.band) / 2 + : 0; + + const {offset, offsetType} = positionOffset({channel, markDef, encoding, model, bandPosition}); const posRef = ref.midPointRefWithPositionInvalidTest({ channel, @@ -221,15 +230,7 @@ function positionAndSize( stack, offset, defaultRef: pointPositionDefaultRef({model, defaultPos: 'mid', channel, scaleName, scale}), - bandPosition: center - ? offsetType === 'encoding' - ? 0 - : 0.5 - : isSignalRef(bandSize) - ? {signal: `(1-${bandSize})/2`} - : isRelativeBandSize(bandSize) - ? (1 - bandSize.band) / 2 - : 0 + bandPosition: offsetType === 'encoding' ? 0 : bandPosition }); if (vgSizeChannel) {