From 4b9d22c693860faf58c1868c0960b0b60405b504 Mon Sep 17 00:00:00 2001 From: Kanit Wongsuphasawat Date: Fri, 14 Apr 2023 14:41:02 -0700 Subject: [PATCH] fix: correctly apply stacked to bar with quantitative x and quantitative y axes (#8838) Co-authored-by: GitHub Actions Bot --- examples/compiled/bar_qq_stack.png | Bin 0 -> 4424 bytes examples/compiled/bar_qq_stack.svg | 1 + examples/compiled/bar_qq_stack.vg.json | 123 +++++++++++++++++ examples/compiled/bar_qq_stack_horizontal.png | Bin 0 -> 4391 bytes examples/compiled/bar_qq_stack_horizontal.svg | 1 + .../compiled/bar_qq_stack_horizontal.vg.json | 124 ++++++++++++++++++ examples/specs/bar_qq_stack.vl.json | 13 ++ .../specs/bar_qq_stack_horizontal.vl.json | 14 ++ src/compile/unit.ts | 2 +- src/stack.ts | 20 ++- test/compile/mark/bar.test.ts | 8 +- 11 files changed, 298 insertions(+), 8 deletions(-) create mode 100644 examples/compiled/bar_qq_stack.png create mode 100644 examples/compiled/bar_qq_stack.svg create mode 100644 examples/compiled/bar_qq_stack.vg.json create mode 100644 examples/compiled/bar_qq_stack_horizontal.png create mode 100644 examples/compiled/bar_qq_stack_horizontal.svg create mode 100644 examples/compiled/bar_qq_stack_horizontal.vg.json create mode 100644 examples/specs/bar_qq_stack.vl.json create mode 100644 examples/specs/bar_qq_stack_horizontal.vl.json diff --git a/examples/compiled/bar_qq_stack.png b/examples/compiled/bar_qq_stack.png new file mode 100644 index 0000000000000000000000000000000000000000..e4f5fdb8a5f54f1a2b17558a8a7a077b74c18b79 GIT binary patch literal 4424 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 \ 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 0000000000000000000000000000000000000000..06752eb9dba3dacdfd78a42831f7b93ecc8f8858 GIT binary patch literal 4391 zcmcgwS6Gv4who~5CMrdcE)XfwHoX%%0wTSu5PAtcKtL4fMN~kf$X1Y0M353dst^JK zBGQ8NCQUj>31vR!>^bK=b2ZOg>?AAyO}>?^f33Hy#2FcA(NJEagg_uPI@&N}a9s!S zo17H9_q%@@1Q*i#dRj2Z+4(cCvG6$rLO-qpQ#A?BUYkJao9=OtZAz1?a&kMw%k_)s z+O?X5b#61=&9;<&_gZst; zsazLXt}02yIN{`Ere0%uz_2Nsa5%B=?iWHEvxC~`k)HBP!TBk?Fs6j@#uxSz#V$=& z)WV4W708LTXkF;w(2#5f3WeI5eoW^^p#j4LnM%UsKgn3lW9)FgI*}5e%wcJN%j3IeC^) zCXiEBQ^TlQv#N=!<;Q$ckSt|-!=px{4ck|@MkFL8R0OP=C9*4Cd#jmLzgDxD6*_wy zbff-_i?OT!!!uPvkAzT4c`dQ4tSP&U52rrgWf8lE*wISo2LtbJ%SChfTC>;N9|f$B z%eG^&GoPPOK_DeV@t;3`mKw8IC0$Nb!H6M(1sHI2qH?4Yt%}NiE8)n{&^9lOe12Z!sH)Z#VK;YotfKjmS!PW1o;WAX#4TZLj;tRaExQ7FOj~4cmNX9^-_M${n8V?j zx~@BNZ(LeOA11ylsO|6Xk4;VbKp~y6vGMf6!jsZcA%(;F=N9$; z(9dmc_o}V#SXkiK3D2HAlanCrKfC(8YY;xFhvO_ND#9GU!rqU>-gm7OQB-WS6*Y%f zj=~l$&O4>2Gkwb#g;!Qvk1S*Nc6ZyK-O$VKK0G{psTe8+vdV`FaAOKiNE7q(d8w~S zYVU&@G`fqnl^c(@yX#{(edQY#Qnfi5GyeV%{xDjKMz`cDh8mfhlU0r;9vlRN#}adV zr?6`#BI0P7-=@y@qGHIdil!zh<1M?=2h5-rr;ghO27#x@@A*0}l~8iSAM8Io05#^X zCGGMXWVg^MA3tnpP~hd`Te%hu5;e*=Va8@QHd63-i+}c%uHeOYSrv745^ip8z*rQ3 z(k$MaU14Ob854m*b)2cW`}O(mW;uBleu=oF`)5%R1#JvTZfxXc0X+f4#Qyr_(UT=jXJ_{#LX*CZM));ivF~+CULI#vP0jti zl_7kU>+|G$vE%9KuHQVQ+B~FI@qb*3#3(Stwa+q8{N0hi@JE)Zsp-K=$p<4_+aw%& zXz2v&Qryz1OD2Lz*edF7p+PGSN4B%rhl}9|NB`A_llkX{)62*m=g*rnZ51A);tfYD zC34Z+zIs;h@n&5r$il+H(o}Qv|J2E{e=FmbeMhlrNx9#m!T+|RV(#5LKK)mc6ky7= zL1W7>R__Ibggh`PW8-JZ%&;>i(2cRHx{m>((9pZ@(0i*RQ$xR9U0vnwyWBN1gZoE? z@(EW~HRvvPkN1aHhjrN2`f%M86r5RHj1ji3nXc?p8W5vS9s9n`5;WhLx-?qJ?;*ts z;pF9o;Q$x_d=CwkVIf=TTHKKqHh+IqHNY#&%4jcMyeJ?nj0G~f*~yc##2nq}=?Yz+ zYz*BxTzvhus_F{gbHIr!j#lwm+1ZKn^NtOhpQ(bXPyUjVWC5$e;bHF1z>}u^oxBvL zQxDg*C|J<2W#*^&s)m;L5?Wfj*l*=e4X%TNH-^9|nKRqCezEmC3#bjQQbW$K_GKGU zLP4vi5<$YQ$jD@I!eK~I2m*58ay8_%+$c>fD!$ zJv|}x4-Q6U*o7>opDV(>&ruXW5$;%9C*~7V=^9L;oG>f zIP!*KeO`tHJzo0%BA)6gEcEyF(Je18gQ9$O+M;RP&p39|sDPP}ko&Bdnr;p~X9Lq> zcZnU7%>Sd)41uzItxDqUZQ^Zly>2RhPdH$pm5=?g=)3;bb-oL_JS4}z$Gq`PDEXC@ z717{L2cEb7oxr##D{~-`NEy_TBw(t|77{uL#B01ZGCP}%P0pX3gp4+-xw*O3b*vbT zR)NF$df*br$Hx$ehK5FGSC^{0yC6_|Y6JW*n4#3Htc3V@%9p)x|J7ldq@<*S-2p?y zY&!)ty8^S4l9DFw0tCMnIKQxPZ=}o?{r)``xUcD%86pUPBFxT?JuEEj8;~;LIo6Qk zu%A6F3OhX#o4<_5z0CyQ#g ztDqnsV5$a3M%1BCTnFCG07;bzT4#G_kV9!Ms~UE)Hi+zDtLZ5f#v(d&F>lPt*eii{IijR%G{-w9q)VpynJw86(xX|#mnJzT#r}FYtuvjd#;Kelwc%od$E*gB}hvrkO8@#+OEK}cD_6LjZGE2azA$jH>>^ArJ zaRquAiHU>Bg^N=$=z@ZafJ`-;!%vKCY!a82To#x6YY}rsCMJ$1JyO?LSu4CIV;vwR6zr5VBBT?S%4mVjc3-*MvL+eaalm(+}vD+Lo2ZdzMRN? zxK!26?dA((0Vq_}-(M2q@C#odgWS$f-=Y!r^7IrYCMM4AKG)@dfa=INFYnQhPUlig zO3nQ6)vH&5qrgQ`>+fT(3Ys}=&0^P-{LTo$gu}x>o0xfg9sc?cYgi}U_8O-+A4##Cdq^+x~2C()kP`*B373?mgo)P@iIS3Ba z?wxZOK#WXHpSX{daY;%Jc4}Y$(%r2Nu-bIIt+!~}3;M#Q96pY$m|yCD%Pe+3DpiaK z0^n%T5R8biCoFD>h=^zg2g`uW!)aS`lK0!mgF{2*Ga4HkXMAKt!w!6$?OFnoD<-_O z0s{VMXz=`{M?Wg zXm#M470;YM8jaQlhOsD3(7d>(M?Wnyvk3gmG-KTx{QM>77#J!s7X)()IznwQHmQ8e z;Odnt=s(qCih-LiLCkd&0kiXX;KcJsmFvwv{xJ2#m#fk+i>l-(hMLK;(eAZhHbf5& znp8V>G3H zKcHbQC@8>JI@19P6m|M?1yJKshJAQzjpx{p(k%HP5x{!qq&PlqBPk`tEhg4~!(i^z z6JRj-^l)+On>rm4#2{B8?)W(De3udv6C*7oSo`@&G#$)R4Gs=+a&ZwoCZ!?v*-^9h z@)ChK00slSh)EU8v9h+Vcz~ldF)`s37iR!lm0x%-m|A?rgV>}bEc(vdszmQ#MBLJ3 zU3=pfn7{YGdk1R4^(GYIe47|3G-SORsV&qN{|PuBA=~;vC$DS#&)RR~mNbO!du1zz zG8!tMNJi7J!u{E}Gn|7F=A{;qM@Jzhrlzf9W2}Vz4W`2et~X_l2iPKDV{PjFDJ(55 zUA(+H2pi4b{=EM@bN0&i?C4~FDneSEHkCY@2~%E9-_pxkNmLb~MR}?E!GZHQxC>Fy z(Qk^2DVv&_z}ivNpBihEdk>qVfD}JIIT3UI%9gDh!S3Yb1eDT0<6!D2_Ja7agvg}{ g`F~pw^3I5l#Gm#w)qBo>e?A~O8V0a3HHSz40E|jf0{{R3 literal 0 HcmV?d00001 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 @@ +020406080b12345a \ 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'}); }); });