Skip to content

Commit 73a5782

Browse files
Filmbostock
andauthored
center the silhouette and wiggle offsets (#444)
* center the silhouette and wiggle offsets closes #424 * center the silhouette and wiggle stacks across facets, while keeping them based on the smaller x1 overall at 0 * embed the offsetCenterFacet logic in the offset function see discussion at #444 (review) * consolidate map * rename for clarity * tweak footballCoverage example Co-authored-by: Mike Bostock <[email protected]>
1 parent ac6bff0 commit 73a5782

File tree

5 files changed

+644
-37
lines changed

5 files changed

+644
-37
lines changed

src/transforms/stack.js

Lines changed: 62 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import {InternMap, cumsum, group, groupSort, greatest, rollup, sum, min} from "d3";
1+
import {InternMap, cumsum, group, groupSort, greatest, max, min, rollup, sum} from "d3";
22
import {ascendingDefined} from "../defined.js";
33
import {field, lazyChannel, maybeLazyChannel, maybeZ, mid, range, valueof, maybeZero, isOptions, maybeValue} from "../mark.js";
44
import {basic} from "./basic.js";
@@ -93,6 +93,7 @@ function stack(x, y = () => 1, ky, {offset, order, reverse}, options) {
9393
const n = data.length;
9494
const Y1 = setY1(new Float64Array(n));
9595
const Y2 = setY2(new Float64Array(n));
96+
const facetstacks = [];
9697
for (const facet of facets) {
9798
const stacks = X ? Array.from(group(facet, i => X[i]).values()) : [facet];
9899
if (O) applyOrder(stacks, O);
@@ -106,8 +107,9 @@ function stack(x, y = () => 1, ky, {offset, order, reverse}, options) {
106107
else Y2[i] = Y1[i] = yp; // NaN or zero
107108
}
108109
}
109-
if (offset) offset(stacks, Y1, Y2, Z);
110+
facetstacks.push(stacks);
110111
}
112+
if (offset) offset(facetstacks, Y1, Y2, Z);
111113
return {data, facets};
112114
}),
113115
X,
@@ -139,51 +141,59 @@ function extent(stack, Y2) {
139141
return [min, max];
140142
}
141143

142-
function offsetExpand(stacks, Y1, Y2) {
143-
for (const stack of stacks) {
144-
const [yn, yp] = extent(stack, Y2);
145-
for (const i of stack) {
146-
const m = 1 / (yp - yn || 1);
147-
Y1[i] = m * (Y1[i] - yn);
148-
Y2[i] = m * (Y2[i] - yn);
144+
function offsetExpand(facetstacks, Y1, Y2) {
145+
for (const stacks of facetstacks) {
146+
for (const stack of stacks) {
147+
const [yn, yp] = extent(stack, Y2);
148+
for (const i of stack) {
149+
const m = 1 / (yp - yn || 1);
150+
Y1[i] = m * (Y1[i] - yn);
151+
Y2[i] = m * (Y2[i] - yn);
152+
}
149153
}
150154
}
151155
}
152156

153-
function offsetCenter(stacks, Y1, Y2) {
154-
for (const stack of stacks) {
155-
const [yn, yp] = extent(stack, Y2);
156-
for (const i of stack) {
157-
const m = (yp + yn) / 2;
158-
Y1[i] -= m;
159-
Y2[i] -= m;
157+
function offsetCenter(facetstacks, Y1, Y2) {
158+
for (const stacks of facetstacks) {
159+
for (const stack of stacks) {
160+
const [yn, yp] = extent(stack, Y2);
161+
for (const i of stack) {
162+
const m = (yp + yn) / 2;
163+
Y1[i] -= m;
164+
Y2[i] -= m;
165+
}
160166
}
167+
offsetZero(stacks, Y1, Y2);
161168
}
162-
offsetZero(stacks, Y1, Y2);
169+
offsetCenterFacets(facetstacks, Y1, Y2);
163170
}
164171

165-
function offsetWiggle(stacks, Y1, Y2, Z) {
166-
const prev = new InternMap();
167-
let y = 0;
168-
for (const stack of stacks) {
169-
let j = -1;
170-
const Fi = stack.map(i => Math.abs(Y2[i] - Y1[i]));
171-
const Df = stack.map(i => {
172-
j = Z ? Z[i] : ++j;
173-
const value = Y2[i] - Y1[i];
174-
const diff = prev.has(j) ? value - prev.get(j) : 0;
175-
prev.set(j, value);
176-
return diff;
177-
});
178-
const Cf1 = [0, ...cumsum(Df)];
179-
for (const i of stack) {
180-
Y1[i] += y;
181-
Y2[i] += y;
172+
function offsetWiggle(facetstacks, Y1, Y2, Z) {
173+
for (const stacks of facetstacks) {
174+
const prev = new InternMap();
175+
let y = 0;
176+
for (const stack of stacks) {
177+
let j = -1;
178+
const Fi = stack.map(i => Math.abs(Y2[i] - Y1[i]));
179+
const Df = stack.map(i => {
180+
j = Z ? Z[i] : ++j;
181+
const value = Y2[i] - Y1[i];
182+
const diff = prev.has(j) ? value - prev.get(j) : 0;
183+
prev.set(j, value);
184+
return diff;
185+
});
186+
const Cf1 = [0, ...cumsum(Df)];
187+
for (const i of stack) {
188+
Y1[i] += y;
189+
Y2[i] += y;
190+
}
191+
const s1 = sum(Fi);
192+
if (s1) y -= sum(Fi, (d, i) => (Df[i] / 2 + Cf1[i]) * d) / s1;
182193
}
183-
const s1 = sum(Fi);
184-
if (s1) y -= sum(Fi, (d, i) => (Df[i] / 2 + Cf1[i]) * d) / s1;
194+
offsetZero(stacks, Y1, Y2);
185195
}
186-
offsetZero(stacks, Y1, Y2);
196+
offsetCenterFacets(facetstacks, Y1, Y2);
187197
}
188198

189199
function offsetZero(stacks, Y1, Y2) {
@@ -196,6 +206,21 @@ function offsetZero(stacks, Y1, Y2) {
196206
}
197207
}
198208

209+
function offsetCenterFacets(facetstacks, Y1, Y2) {
210+
const n = facetstacks.length;
211+
if (n === 1) return;
212+
const facets = facetstacks.map(stacks => stacks.flat());
213+
const m = facets.map(I => (min(I, i => Y1[i]) + max(I, i => Y2[i])) / 2);
214+
const m0 = min(m);
215+
for (let j = 0; j < n; j++) {
216+
const p = m0 - m[j];
217+
for (const i of facets[j]) {
218+
Y1[i] += p;
219+
Y2[i] += p;
220+
}
221+
}
222+
}
223+
199224
function maybeOrder(order, offset, ky) {
200225
if (order === undefined && offset === offsetWiggle) return orderInsideOut;
201226
if (order == null) return;

test/data/football-coverage.csv

Lines changed: 225 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,225 @@
1+
coverage,value
2+
C2,0.124
3+
C2,0.1012
4+
C2,0.0902
5+
C2,0.1421
6+
C2,0.0905
7+
C2,0.1248
8+
C2,0.0766
9+
C2,0.0898
10+
C2,0.1121
11+
C2,0.1385
12+
C2,0.1282
13+
C2,0.1016
14+
C2,0.1352
15+
C2,0.1082
16+
C2,0.1115
17+
C2,0.1335
18+
C2,0.087
19+
C2,0.0827
20+
C2,0.119
21+
C2,0.1023
22+
C2,0.0942
23+
C2,0.1445
24+
C2,0.1056
25+
C2,0.1146
26+
C2,0.1244
27+
C2,0.1107
28+
C2,0.14
29+
C2,0.0907
30+
C2,0.1226
31+
C2,0.1349
32+
C2,0.1003
33+
C2,0.1284
34+
C3,0.339
35+
C3,0.3513
36+
C3,0.3826
37+
C3,0.3429
38+
C3,0.3157
39+
C3,0.3537
40+
C3,0.3884
41+
C3,0.3404
42+
C3,0.3156
43+
C3,0.3538
44+
C3,0.3359
45+
C3,0.3288
46+
C3,0.3461
47+
C3,0.3465
48+
C3,0.3425
49+
C3,0.3187
50+
C3,0.3646
51+
C3,0.3492
52+
C3,0.3649
53+
C3,0.3482
54+
C3,0.3292
55+
C3,0.3806
56+
C3,0.3389
57+
C3,0.406
58+
C3,0.356
59+
C3,0.3298
60+
C3,0.3437
61+
C3,0.3335
62+
C3,0.3539
63+
C3,0.3421
64+
C3,0.3623
65+
C3,0.353
66+
C4,0.2056
67+
C4,0.1592
68+
C4,0.2042
69+
C4,0.196
70+
C4,0.234
71+
C4,0.2445
72+
C4,0.2376
73+
C4,0.2099
74+
C4,0.1559
75+
C4,0.2277
76+
C4,0.2016
77+
C4,0.2124
78+
C4,0.1622
79+
C4,0.1893
80+
C4,0.216
81+
C4,0.174
82+
C4,0.1869
83+
C4,0.179
84+
C4,0.2296
85+
C4,0.1784
86+
C4,0.1934
87+
C4,0.1913
88+
C4,0.182
89+
C4,0.21
90+
C4,0.1694
91+
C4,0.1545
92+
C4,0.2174
93+
C4,0.2177
94+
C4,0.1869
95+
C4,0.1873
96+
C4,0.1645
97+
C4,0.2029
98+
M0,0.2023
99+
M0,0.2262
100+
M0,0.1968
101+
M0,0.2103
102+
M0,0.2423
103+
M0,0.1992
104+
M0,0.193
105+
M0,0.1994
106+
M0,0.1532
107+
M0,0.2495
108+
M0,0.1746
109+
M0,0.2285
110+
M0,0.1937
111+
M0,0.2176
112+
M0,0.2182
113+
M0,0.2042
114+
M0,0.1953
115+
M0,0.2388
116+
M0,0.2363
117+
M0,0.2658
118+
M0,0.2132
119+
M0,0.2283
120+
M0,0.2363
121+
M0,0.1727
122+
M0,0.2334
123+
M0,0.2274
124+
M0,0.2406
125+
M0,0.2407
126+
M0,0.2212
127+
M0,0.2392
128+
M0,0.1921
129+
M0,0.2553
130+
M1,0.3343
131+
M1,0.3426
132+
M1,0.3487
133+
M1,0.3454
134+
M1,0.2872
135+
M1,0.32
136+
M1,0.3529
137+
M1,0.3403
138+
M1,0.3245
139+
M1,0.3158
140+
M1,0.2974
141+
M1,0.3061
142+
M1,0.315
143+
M1,0.3068
144+
M1,0.3262
145+
M1,0.2838
146+
M1,0.3513
147+
M1,0.3544
148+
M1,0.2983
149+
M1,0.3652
150+
M1,0.3224
151+
M1,0.3025
152+
M1,0.3416
153+
M1,0.3204
154+
M1,0.35
155+
M1,0.3126
156+
M1,0.3496
157+
M1,0.3153
158+
M1,0.3303
159+
M1,0.3364
160+
M1,0.3251
161+
M1,0.3418
162+
M2,0.1089
163+
M2,0.1769
164+
M2,0.1504
165+
M2,0.1378
166+
M2,0.1904
167+
M2,0.1491
168+
M2,0.1825
169+
M2,0.153
170+
M2,0.1617
171+
M2,0.2008
172+
M2,0.1366
173+
M2,0.1549
174+
M2,0.19
175+
M2,0.1114
176+
M2,0.154
177+
M2,0.1609
178+
M2,0.1634
179+
M2,0.1661
180+
M2,0.1698
181+
M2,0.1242
182+
M2,0.1542
183+
M2,0.1641
184+
M2,0.1422
185+
M2,0.1561
186+
M2,0.1857
187+
M2,0.1489
188+
M2,0.1626
189+
M2,0.1386
190+
M2,0.1443
191+
M2,0.1596
192+
M2,0.1603
193+
M2,0.2085
194+
T2,0.3073
195+
T2,0.312
196+
T2,0.2928
197+
T2,0.3228
198+
T2,0.3124
199+
T2,0.2958
200+
T2,0.3288
201+
T2,0.3444
202+
T2,0.2848
203+
T2,0.3199
204+
T2,0.3006
205+
T2,0.3044
206+
T2,0.3358
207+
T2,0.3026
208+
T2,0.2613
209+
T2,0.2752
210+
T2,0.3272
211+
T2,0.3275
212+
T2,0.3371
213+
T2,0.2767
214+
T2,0.3074
215+
T2,0.316
216+
T2,0.2976
217+
T2,0.3328
218+
T2,0.2836
219+
T2,0.3019
220+
T2,0.3103
221+
T2,0.3155
222+
T2,0.3264
223+
T2,0.3178
224+
T2,0.2654
225+
T2,0.2922

0 commit comments

Comments
 (0)