svelteplot 0.4.4-pr-205.0 → 0.4.4-pr-105.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/core/Plot.svelte +1 -0
- package/dist/marks/AreaY.svelte +2 -1
- package/dist/marks/ColorLegend.svelte +1 -1
- package/dist/transforms/recordize.js +8 -2
- package/dist/transforms/sort.js +13 -1
- package/dist/transforms/stack.d.ts +2 -2
- package/dist/transforms/stack.js +96 -41
- package/package.json +1 -1
package/dist/core/Plot.svelte
CHANGED
package/dist/marks/AreaY.svelte
CHANGED
|
@@ -10,9 +10,10 @@
|
|
|
10
10
|
import Area from './Area.svelte';
|
|
11
11
|
import { renameChannels } from '../transforms/rename.js';
|
|
12
12
|
import { stackY } from '../transforms/stack.js';
|
|
13
|
-
import { recordizeY } from '../transforms/recordize.js';
|
|
13
|
+
import { RAW_VALUE, recordizeY } from '../transforms/recordize.js';
|
|
14
14
|
import type { ChannelAccessor, DataRow, PlotDefaults } from '../types/index.js';
|
|
15
15
|
import { getContext, type Component, type ComponentProps } from 'svelte';
|
|
16
|
+
import { area } from 'd3-shape';
|
|
16
17
|
|
|
17
18
|
let markProps: AreaYMarkProps = $props();
|
|
18
19
|
|
|
@@ -26,7 +26,7 @@
|
|
|
26
26
|
? plot.options.color.tickFormat
|
|
27
27
|
: Intl.NumberFormat(
|
|
28
28
|
plot.options.locale,
|
|
29
|
-
plot.options.color.tickFormat || DEFAULTS.numberFormat
|
|
29
|
+
plot.options.color.tickFormat || { ...DEFAULTS.numberFormat, notation: 'compact' }
|
|
30
30
|
).format
|
|
31
31
|
);
|
|
32
32
|
const randId = Math.round(Math.random() * 1e6).toFixed(32);
|
|
@@ -13,12 +13,15 @@ export function indexData(data) {
|
|
|
13
13
|
export function recordizeX({ data, ...channels }, { withIndex } = { withIndex: true }) {
|
|
14
14
|
const dataIsRawValueArray = !isDataRecord(data[0]) && !Array.isArray(data[0]) && channels.x == null;
|
|
15
15
|
if (dataIsRawValueArray) {
|
|
16
|
+
// we remove x, x1 and x2 from the channels since they make no sense when
|
|
17
|
+
// the data is a raw value array
|
|
18
|
+
const { x, x1, x2, ...nonXChannels } = channels;
|
|
16
19
|
return {
|
|
17
20
|
data: data.map((value, index) => ({
|
|
18
21
|
[RAW_VALUE]: value,
|
|
19
22
|
[INDEX]: index
|
|
20
23
|
})),
|
|
21
|
-
...
|
|
24
|
+
...nonXChannels,
|
|
22
25
|
x: RAW_VALUE,
|
|
23
26
|
...(withIndex ? { y: INDEX } : {})
|
|
24
27
|
};
|
|
@@ -34,12 +37,15 @@ export function recordizeY({ data, ...channels }, { withIndex } = { withIndex: t
|
|
|
34
37
|
return { data, ...channels };
|
|
35
38
|
const dataIsRawValueArray = !isDataRecord(data[0]) && !Array.isArray(data[0]) && channels.y == null;
|
|
36
39
|
if (dataIsRawValueArray) {
|
|
40
|
+
// we remove y, y1 and y2 from the channels since they make no sense when
|
|
41
|
+
// the data is a raw value array
|
|
42
|
+
const { y, y1, y2, ...nonYChannels } = channels;
|
|
37
43
|
return {
|
|
38
44
|
data: Array.from(data).map((value, index) => ({
|
|
39
45
|
[INDEX]: index,
|
|
40
46
|
[RAW_VALUE]: value
|
|
41
47
|
})),
|
|
42
|
-
...
|
|
48
|
+
...nonYChannels,
|
|
43
49
|
...(withIndex ? { x: INDEX } : {}),
|
|
44
50
|
y: RAW_VALUE
|
|
45
51
|
};
|
package/dist/transforms/sort.js
CHANGED
|
@@ -27,7 +27,19 @@ export function sort({ data, ...channels }, options = {}) {
|
|
|
27
27
|
...d,
|
|
28
28
|
[SORT_KEY]: resolveChannel('sort', d, { ...channels, sort })
|
|
29
29
|
}))
|
|
30
|
-
.
|
|
30
|
+
.map((d) => ({
|
|
31
|
+
...d,
|
|
32
|
+
[SORT_KEY]: typeof d[SORT_KEY] === 'number' && !Number.isFinite(d[SORT_KEY])
|
|
33
|
+
? Number.POSITIVE_INFINITY
|
|
34
|
+
: d[SORT_KEY]
|
|
35
|
+
}))
|
|
36
|
+
.toSorted((a, b) => (typeof a[SORT_KEY] === 'string' && typeof b[SORT_KEY] === 'string'
|
|
37
|
+
? a[SORT_KEY].localeCompare(b[SORT_KEY])
|
|
38
|
+
: a[SORT_KEY] > b[SORT_KEY]
|
|
39
|
+
? 1
|
|
40
|
+
: a[SORT_KEY] < b[SORT_KEY]
|
|
41
|
+
? -1
|
|
42
|
+
: 0) *
|
|
31
43
|
(options.reverse ||
|
|
32
44
|
(isDataRecord(sort) && sort?.order === 'descending')
|
|
33
45
|
? -1
|
|
@@ -6,7 +6,7 @@ export type StackOptions = {
|
|
|
6
6
|
order: null | StackOrder;
|
|
7
7
|
reverse: boolean;
|
|
8
8
|
};
|
|
9
|
-
export declare function stackY<T>({ data, ...channels }: T
|
|
10
|
-
export declare function stackX({ data, ...channels }: TransformArg
|
|
9
|
+
export declare function stackY<T>({ data, ...channels }: TransformArg<T>, opts?: Partial<StackOptions>): TransformArg<T>;
|
|
10
|
+
export declare function stackX<T>({ data, ...channels }: TransformArg<T>, opts?: Partial<StackOptions>): TransformArg<T>;
|
|
11
11
|
export declare function stackMosaicX<T>(args: any, opts: any): any;
|
|
12
12
|
export declare function stackMosaicY<T>(args: any, opts: any): any;
|
package/dist/transforms/stack.js
CHANGED
|
@@ -1,12 +1,22 @@
|
|
|
1
1
|
import isDataRecord from '../helpers/isDataRecord.js';
|
|
2
2
|
import { resolveChannel, resolveProp } from '../helpers/resolve.js';
|
|
3
3
|
import { stack, stackOffsetExpand, stackOffsetSilhouette, stackOffsetWiggle, stackOrderAppearance, stackOrderAscending, stackOrderInsideOut, stackOrderNone, stackOffsetDiverging } from 'd3-shape';
|
|
4
|
-
import {
|
|
4
|
+
import { sum, groups as d3Groups, min, range } from 'd3-array';
|
|
5
5
|
import { groupFacetsAndZ } from '../helpers/group';
|
|
6
6
|
import { filter } from './filter.js';
|
|
7
7
|
import { sort } from './sort.js';
|
|
8
|
+
import { INDEX } from '../constants.js';
|
|
9
|
+
import { indexData, RAW_VALUE } from './recordize.js';
|
|
10
|
+
const S = {
|
|
11
|
+
x: Symbol('x'),
|
|
12
|
+
x1: Symbol('x1'),
|
|
13
|
+
x2: Symbol('x2'),
|
|
14
|
+
y: Symbol('y'),
|
|
15
|
+
y1: Symbol('y1'),
|
|
16
|
+
y2: Symbol('y2')
|
|
17
|
+
};
|
|
8
18
|
const GROUP = Symbol('group');
|
|
9
|
-
const FACET = Symbol('
|
|
19
|
+
const FACET = Symbol('facet');
|
|
10
20
|
const DEFAULT_STACK_OPTIONS = {
|
|
11
21
|
order: null,
|
|
12
22
|
offset: null,
|
|
@@ -41,16 +51,16 @@ function stackXY(byDim, data, channels, options) {
|
|
|
41
51
|
channels[`${byLow}`] === undefined &&
|
|
42
52
|
channels[`${byHigh}`] === undefined) {
|
|
43
53
|
// resolve all channels for easier computation below
|
|
44
|
-
const resolvedData = data.map((d) => ({
|
|
45
|
-
...(isDataRecord(d) ? d : {
|
|
46
|
-
[
|
|
54
|
+
const resolvedData = indexData(data).map((d, i) => ({
|
|
55
|
+
...(isDataRecord(d) ? d : { [RAW_VALUE]: d }),
|
|
56
|
+
[S[secondDim]]: resolveChannel(secondDim, d, channels),
|
|
47
57
|
[GROUP]: groupBy === true ? 'G' : resolveChannel(groupBy, d, channels),
|
|
48
58
|
[FACET]: groupFacetsBy.length > 0
|
|
49
59
|
? groupFacetsBy
|
|
50
60
|
.map((channel) => String(resolveChannel(channel, d, channels)))
|
|
51
61
|
.join('---')
|
|
52
62
|
: 'F',
|
|
53
|
-
[
|
|
63
|
+
[S[byDim]]: resolveChannel(byDim, d, channels)
|
|
54
64
|
}));
|
|
55
65
|
// the final data ends up here
|
|
56
66
|
const out = [];
|
|
@@ -58,9 +68,53 @@ function stackXY(byDim, data, channels, options) {
|
|
|
58
68
|
// in separate panels
|
|
59
69
|
const groups = d3Groups(resolvedData, (d) => d[FACET]);
|
|
60
70
|
for (const [, facetData] of groups) {
|
|
61
|
-
//
|
|
62
|
-
//
|
|
63
|
-
|
|
71
|
+
// create a temporary dataset for stacking
|
|
72
|
+
// If we have a grouping channel (fill/stroke/z), build objects keyed by group value
|
|
73
|
+
// so that series identities remain consistent across the secondary dimension.
|
|
74
|
+
// This is required for offsets like 'wiggle' and 'inside-out'.
|
|
75
|
+
let keys;
|
|
76
|
+
const groupedBySecondDim = d3Groups(facetData, (d) => d[S[secondDim]]);
|
|
77
|
+
let stackData;
|
|
78
|
+
const hasUniqueGroups = groupBy !== true &&
|
|
79
|
+
groupedBySecondDim.every(([, items]) => {
|
|
80
|
+
const groupSet = new Set(items.map((d) => d[GROUP]));
|
|
81
|
+
return groupSet.size === items.length;
|
|
82
|
+
});
|
|
83
|
+
if (groupBy === true || !hasUniqueGroups) {
|
|
84
|
+
// Unit stacking: map each secondary-dimension bucket to an array of values.
|
|
85
|
+
// Series are positional (0..N-1) within each bucket.
|
|
86
|
+
let maxKeys = 0;
|
|
87
|
+
stackData = groupedBySecondDim.map(([k, items]) => {
|
|
88
|
+
const values = items
|
|
89
|
+
// keep original order within bucket; no stable series identity across buckets
|
|
90
|
+
.map((d) => ({ i: d[INDEX], v: d[S[byDim]] }));
|
|
91
|
+
if (values.length > maxKeys)
|
|
92
|
+
maxKeys = values.length;
|
|
93
|
+
return values;
|
|
94
|
+
});
|
|
95
|
+
keys = range(maxKeys);
|
|
96
|
+
}
|
|
97
|
+
else {
|
|
98
|
+
// Grouped stacking: keep consistent series identities using the group key
|
|
99
|
+
const keySet = new Set(facetData.map((d) => d[GROUP]));
|
|
100
|
+
stackData = groupedBySecondDim.map(([k, items]) => {
|
|
101
|
+
const obj = {};
|
|
102
|
+
items.forEach((d) => {
|
|
103
|
+
const key = d[GROUP];
|
|
104
|
+
// If duplicates exist for the same (secondDim, group) pair, sum values
|
|
105
|
+
// and keep the latest index for back-reference.
|
|
106
|
+
if (obj[key] == null)
|
|
107
|
+
obj[key] = { i: d[INDEX], v: d[S[byDim]] };
|
|
108
|
+
else
|
|
109
|
+
obj[key] = {
|
|
110
|
+
i: d[INDEX],
|
|
111
|
+
v: obj[key].v + d[S[byDim]]
|
|
112
|
+
};
|
|
113
|
+
});
|
|
114
|
+
return obj;
|
|
115
|
+
});
|
|
116
|
+
keys = Array.from(keySet);
|
|
117
|
+
}
|
|
64
118
|
const stackOrder = (series) => {
|
|
65
119
|
const f = STACK_ORDER[options.order || 'none'];
|
|
66
120
|
return options.reverse ? f(series).reverse() : f(series);
|
|
@@ -68,35 +122,31 @@ function stackXY(byDim, data, channels, options) {
|
|
|
68
122
|
// now stack the values for each index
|
|
69
123
|
const series = stack()
|
|
70
124
|
.order(stackOrder)
|
|
71
|
-
|
|
72
|
-
.
|
|
73
|
-
|
|
125
|
+
// Wiggle requires consistent series identities; fall back to 'center' for unit stacking
|
|
126
|
+
.offset(groupBy === true && options.offset === 'wiggle'
|
|
127
|
+
? STACK_OFFSET['center']
|
|
128
|
+
: STACK_OFFSET[options.offset])
|
|
129
|
+
.keys(keys)
|
|
130
|
+
.value((d, key, i, data) => {
|
|
131
|
+
return d[key]?.v;
|
|
132
|
+
})(stackData);
|
|
74
133
|
// and combine it all back into a flat array
|
|
75
134
|
const newData = series
|
|
76
|
-
.map((
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
.map((d) => {
|
|
81
|
-
const datum = d.data[1].get(groupKey);
|
|
82
|
-
// cleanup our internal keys
|
|
83
|
-
delete datum[GROUP];
|
|
84
|
-
delete datum[FACET];
|
|
85
|
-
return { ...datum, [`__${byLow}`]: d[0], [`__${byHigh}`]: d[1] };
|
|
86
|
-
});
|
|
87
|
-
})
|
|
88
|
-
.flat(1);
|
|
135
|
+
.flatMap((s) => s.map((d) => [d[0], d[1], d.data[s.key]?.i]))
|
|
136
|
+
.filter((d) => d[2] !== undefined)
|
|
137
|
+
.map((d) => ({ [S[byLow]]: d[0], [S[byHigh]]: d[1], ...resolvedData[d[2]] }));
|
|
138
|
+
out.push(...newData);
|
|
89
139
|
// which we then add to the output data
|
|
90
|
-
out.push(newData);
|
|
140
|
+
// out.push(...newData);
|
|
91
141
|
}
|
|
92
142
|
return {
|
|
93
|
-
data: out
|
|
143
|
+
data: out,
|
|
94
144
|
...channels,
|
|
95
145
|
[byDim]: undefined,
|
|
96
146
|
...(typeof channels[byDim] === 'string' && !channels[`__${byDim}_origField`]
|
|
97
147
|
? { [`__${byDim}_origField`]: channels[byDim] }
|
|
98
148
|
: {}),
|
|
99
|
-
...{ [byLow]:
|
|
149
|
+
...{ [byLow]: S[byLow], [byHigh]: S[byHigh] }
|
|
100
150
|
};
|
|
101
151
|
}
|
|
102
152
|
return { data, ...channels };
|
|
@@ -113,12 +163,6 @@ function applyDefaults(opts) {
|
|
|
113
163
|
}
|
|
114
164
|
return { ...DEFAULT_STACK_OPTIONS, ...opts };
|
|
115
165
|
}
|
|
116
|
-
const X = Symbol('x');
|
|
117
|
-
const X1 = Symbol('x1');
|
|
118
|
-
const X2 = Symbol('x2');
|
|
119
|
-
const Y = Symbol('y');
|
|
120
|
-
const Y1 = Symbol('y1');
|
|
121
|
-
const Y2 = Symbol('y2');
|
|
122
166
|
function stackMosaic({ data, x, y, value, fx, fy, ...rest }, { outer, inner }, { x: xOpt, y: yOpt } = {}) {
|
|
123
167
|
const out = [];
|
|
124
168
|
const { data: filtered, ...restArgs } = sort(filter({ data, x, y, value, fx, fy, ...rest }));
|
|
@@ -137,10 +181,10 @@ function stackMosaic({ data, x, y, value, fx, fy, ...rest }, { outer, inner }, {
|
|
|
137
181
|
let outerPos = 0;
|
|
138
182
|
const outerChannel = outer === 'x' ? x : y;
|
|
139
183
|
const innerChannel = inner === 'x' ? x : y;
|
|
140
|
-
const outerSym1 = outer === 'x' ?
|
|
141
|
-
const outerSym2 = outer === 'x' ?
|
|
142
|
-
const innerSym1 = inner === 'x' ?
|
|
143
|
-
const innerSym2 = inner === 'x' ?
|
|
184
|
+
const outerSym1 = outer === 'x' ? S.x1 : S.y1;
|
|
185
|
+
const outerSym2 = outer === 'x' ? S.x2 : S.y2;
|
|
186
|
+
const innerSym1 = inner === 'x' ? S.x1 : S.y1;
|
|
187
|
+
const innerSym2 = inner === 'x' ? S.x2 : S.y2;
|
|
144
188
|
const outerOpt = outer === 'x' ? xOpt : yOpt;
|
|
145
189
|
const innerOpt = inner === 'x' ? xOpt : yOpt;
|
|
146
190
|
const grouped = d3Groups(data, (d) => resolveProp(d[outerChannel], d));
|
|
@@ -165,13 +209,24 @@ function stackMosaic({ data, x, y, value, fx, fy, ...rest }, { outer, inner }, {
|
|
|
165
209
|
result[outerSym2] = normO2;
|
|
166
210
|
result[innerSym1] = normI1;
|
|
167
211
|
result[innerSym2] = normI2;
|
|
168
|
-
result[
|
|
169
|
-
result[
|
|
212
|
+
result[S.x] = (result[S.x1] + result[S.x2]) / 2;
|
|
213
|
+
result[S.y] = (result[S.y1] + result[S.y2]) / 2;
|
|
170
214
|
out.push(result);
|
|
171
215
|
});
|
|
172
216
|
});
|
|
173
217
|
});
|
|
174
|
-
return {
|
|
218
|
+
return {
|
|
219
|
+
...rest,
|
|
220
|
+
fx,
|
|
221
|
+
fy,
|
|
222
|
+
data: out,
|
|
223
|
+
x: S.x,
|
|
224
|
+
x1: S.x1,
|
|
225
|
+
x2: S.x2,
|
|
226
|
+
y: S.y,
|
|
227
|
+
y1: S.y1,
|
|
228
|
+
y2: S.y2
|
|
229
|
+
};
|
|
175
230
|
}
|
|
176
231
|
export function stackMosaicX(args, opts) {
|
|
177
232
|
return stackMosaic(args, { outer: 'x', inner: 'y' }, opts);
|