Skip to content

Commit fad970f

Browse files
authored
fix: align stack order with color domain (#9641)
## PR Description This PR addresses long-standing issues (dating back to 2016) related to misalignment between the order of stacked marks and the domain in the color encoding. It potentially closes #1734, #6203, and #9496. ### Proposed Solution A new method has been added to the unit parser to check if a color domain is defined in the unit. If a domain is present and no custom order is set, additional encoding is injected to ensure that the color order of the mark matches the order in the legend. ### Note This is my first contribution to the project. If my code doesn't meet the project's standards or if the process I followed is incorrect, I apologise. I'm happy to refactor, feedback is very welcome! ### Examples Below are examples of specs rendered with the current code (left) and the same specs rendered with the proposed solution (right). All specs share this color encoding: `"scale": {"domain": ["B", "A", "C"], "range": ["red", "blue", "green"]}` <img width="830" height="1440" alt="examples" src="https://github.com/user-attachments/assets/785c734f-9eee-4e08-9f2a-74b2a9351ce9" />
1 parent d2866df commit fad970f

11 files changed

+458
-2
lines changed
10.1 KB
Loading
Lines changed: 1 addition & 0 deletions
Loading
Lines changed: 157 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,157 @@
1+
{
2+
"$schema": "https://vega.github.io/schema/vega/v6.json",
3+
"background": "white",
4+
"padding": 5,
5+
"height": 300,
6+
"style": "cell",
7+
"data": [
8+
{
9+
"name": "source_0",
10+
"values": [
11+
{"category": "A", "group": "x", "value": 0.1},
12+
{"category": "A", "group": "y", "value": 0.6},
13+
{"category": "A", "group": "z", "value": 0.9},
14+
{"category": "B", "group": "x", "value": 0.7},
15+
{"category": "B", "group": "y", "value": 0.2},
16+
{"category": "B", "group": "z", "value": 1.1},
17+
{"category": "C", "group": "x", "value": 0.6},
18+
{"category": "C", "group": "y", "value": 0.1},
19+
{"category": "C", "group": "z", "value": 0.2}
20+
]
21+
},
22+
{
23+
"name": "data_0",
24+
"source": "source_0",
25+
"transform": [
26+
{
27+
"type": "formula",
28+
"expr": "datum[\"group\"]===\"y\" ? 0 : datum[\"group\"]===\"x\" ? 1 : datum[\"group\"]===\"z\" ? 2 : 3",
29+
"as": "xOffset_group_sort_index"
30+
}
31+
]
32+
},
33+
{
34+
"name": "data_1",
35+
"source": "data_0",
36+
"transform": [
37+
{
38+
"type": "stack",
39+
"groupby": ["category", "group"],
40+
"field": "value",
41+
"sort": {"field": [], "order": []},
42+
"as": ["value_start", "value_end"],
43+
"offset": "zero"
44+
},
45+
{
46+
"type": "filter",
47+
"expr": "isValid(datum[\"value\"]) && isFinite(+datum[\"value\"])"
48+
}
49+
]
50+
}
51+
],
52+
"signals": [
53+
{
54+
"name": "x_step",
55+
"update": "20 * bandspace(domain('xOffset').length, 0, 0) / (1-0.2)"
56+
},
57+
{
58+
"name": "width",
59+
"update": "bandspace(domain('x').length, 0.2, 0.2) * x_step"
60+
}
61+
],
62+
"marks": [
63+
{
64+
"name": "marks",
65+
"type": "rect",
66+
"style": ["bar"],
67+
"from": {"data": "data_1"},
68+
"encode": {
69+
"update": {
70+
"fill": {"scale": "fill", "field": "group"},
71+
"ariaRoleDescription": {"value": "bar"},
72+
"description": {
73+
"signal": "\"category: \" + (isValid(datum[\"category\"]) ? datum[\"category\"] : \"\"+datum[\"category\"]) + \"; value: \" + (format(datum[\"value\"], \"\")) + \"; group: \" + (isValid(datum[\"group\"]) ? datum[\"group\"] : \"\"+datum[\"group\"])"
74+
},
75+
"x": {
76+
"scale": "x",
77+
"field": "category",
78+
"offset": {"scale": "xOffset", "field": "group"}
79+
},
80+
"width": {"signal": "max(0.25, bandwidth('xOffset'))"},
81+
"y": {"scale": "y", "field": "value_end"},
82+
"y2": {"scale": "y", "field": "value_start"}
83+
}
84+
}
85+
}
86+
],
87+
"scales": [
88+
{
89+
"name": "x",
90+
"type": "band",
91+
"domain": {"data": "data_1", "field": "category", "sort": true},
92+
"range": {"step": {"signal": "x_step"}},
93+
"paddingInner": 0.2,
94+
"paddingOuter": 0.2
95+
},
96+
{
97+
"name": "y",
98+
"type": "linear",
99+
"domain": {"data": "data_1", "fields": ["value_start", "value_end"]},
100+
"range": [{"signal": "height"}, 0],
101+
"nice": true,
102+
"zero": true
103+
},
104+
{
105+
"name": "xOffset",
106+
"type": "band",
107+
"domain": {
108+
"data": "data_0",
109+
"field": "group",
110+
"sort": {"op": "min", "field": "xOffset_group_sort_index"}
111+
},
112+
"range": {"step": 20}
113+
},
114+
{
115+
"name": "fill",
116+
"type": "ordinal",
117+
"domain": ["y", "x", "z"],
118+
"range": "category"
119+
}
120+
],
121+
"axes": [
122+
{
123+
"scale": "y",
124+
"orient": "left",
125+
"gridScale": "x",
126+
"grid": true,
127+
"tickCount": {"signal": "ceil(height/40)"},
128+
"domain": false,
129+
"labels": false,
130+
"aria": false,
131+
"maxExtent": 0,
132+
"minExtent": 0,
133+
"ticks": false,
134+
"zindex": 0
135+
},
136+
{
137+
"scale": "x",
138+
"orient": "bottom",
139+
"grid": false,
140+
"title": "category",
141+
"labelAlign": "right",
142+
"labelAngle": 270,
143+
"labelBaseline": "middle",
144+
"zindex": 0
145+
},
146+
{
147+
"scale": "y",
148+
"orient": "left",
149+
"grid": false,
150+
"title": "value",
151+
"labelOverlap": true,
152+
"tickCount": {"signal": "ceil(height/40)"},
153+
"zindex": 0
154+
}
155+
],
156+
"legends": [{"fill": "fill", "symbolType": "square", "title": "group"}]
157+
}
21.1 KB
Loading

examples/compiled/stacked_bar_h_custom_color_domain.svg

Lines changed: 1 addition & 0 deletions
Loading
Lines changed: 136 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,136 @@
1+
{
2+
"$schema": "https://vega.github.io/schema/vega/v6.json",
3+
"background": "white",
4+
"padding": 5,
5+
"width": 300,
6+
"style": "cell",
7+
"data": [
8+
{
9+
"name": "source_0",
10+
"url": "data/barley.json",
11+
"format": {"type": "json"},
12+
"transform": [
13+
{
14+
"type": "formula",
15+
"expr": "indexof([\"Morris\",\"Crookston\",\"Grand Rapids\",\"University Farm\",\"Waseca\",\"Duluth\"], datum.site)",
16+
"as": "_site_sort_index"
17+
},
18+
{
19+
"type": "aggregate",
20+
"groupby": ["variety", "site", "_site_sort_index"],
21+
"ops": ["sum"],
22+
"fields": ["yield"],
23+
"as": ["sum_yield"]
24+
},
25+
{
26+
"type": "stack",
27+
"groupby": ["variety"],
28+
"field": "sum_yield",
29+
"sort": {"field": ["_site_sort_index"], "order": ["ascending"]},
30+
"as": ["sum_yield_start", "sum_yield_end"],
31+
"offset": "zero"
32+
},
33+
{
34+
"type": "filter",
35+
"expr": "isValid(datum[\"sum_yield\"]) && isFinite(+datum[\"sum_yield\"])"
36+
}
37+
]
38+
}
39+
],
40+
"signals": [
41+
{"name": "y_step", "value": 20},
42+
{
43+
"name": "height",
44+
"update": "bandspace(domain('y').length, 0.1, 0.05) * y_step"
45+
}
46+
],
47+
"marks": [
48+
{
49+
"name": "marks",
50+
"type": "rect",
51+
"style": ["bar"],
52+
"from": {"data": "source_0"},
53+
"encode": {
54+
"update": {
55+
"fill": {"scale": "color", "field": "site"},
56+
"ariaRoleDescription": {"value": "bar"},
57+
"description": {
58+
"signal": "\"Sum of yield: \" + (format(datum[\"sum_yield\"], \"\")) + \"; variety: \" + (isValid(datum[\"variety\"]) ? datum[\"variety\"] : \"\"+datum[\"variety\"]) + \"; site: \" + (isValid(datum[\"site\"]) ? datum[\"site\"] : \"\"+datum[\"site\"]) + \"; _site_sort_index: \" + (format(datum[\"_site_sort_index\"], \"\"))"
59+
},
60+
"x": {"scale": "x", "field": "sum_yield_end"},
61+
"x2": {"scale": "x", "field": "sum_yield_start"},
62+
"y": {"scale": "y", "field": "variety"},
63+
"height": {"signal": "max(0.25, bandwidth('y'))"}
64+
}
65+
}
66+
}
67+
],
68+
"scales": [
69+
{
70+
"name": "x",
71+
"type": "linear",
72+
"domain": {
73+
"data": "source_0",
74+
"fields": ["sum_yield_start", "sum_yield_end"]
75+
},
76+
"range": [0, {"signal": "width"}],
77+
"nice": true,
78+
"zero": true
79+
},
80+
{
81+
"name": "y",
82+
"type": "band",
83+
"domain": {"data": "source_0", "field": "variety", "sort": true},
84+
"range": {"step": {"signal": "y_step"}},
85+
"paddingInner": 0.1,
86+
"paddingOuter": 0.05
87+
},
88+
{
89+
"name": "color",
90+
"type": "ordinal",
91+
"domain": [
92+
"Morris",
93+
"Crookston",
94+
"Grand Rapids",
95+
"University Farm",
96+
"Waseca",
97+
"Duluth"
98+
],
99+
"range": "category"
100+
}
101+
],
102+
"axes": [
103+
{
104+
"scale": "x",
105+
"orient": "bottom",
106+
"gridScale": "y",
107+
"grid": true,
108+
"tickCount": {"signal": "ceil(width/40)"},
109+
"domain": false,
110+
"labels": false,
111+
"aria": false,
112+
"maxExtent": 0,
113+
"minExtent": 0,
114+
"ticks": false,
115+
"zindex": 0
116+
},
117+
{
118+
"scale": "x",
119+
"orient": "bottom",
120+
"grid": false,
121+
"title": "Sum of yield",
122+
"labelFlush": true,
123+
"labelOverlap": true,
124+
"tickCount": {"signal": "ceil(width/40)"},
125+
"zindex": 0
126+
},
127+
{
128+
"scale": "y",
129+
"orient": "left",
130+
"grid": false,
131+
"title": "variety",
132+
"zindex": 0
133+
}
134+
],
135+
"legends": [{"fill": "color", "symbolType": "square", "title": "site"}]
136+
}

0 commit comments

Comments
 (0)