svelteplot 0.6.0 → 0.7.0
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/Mark.svelte +7 -0
- package/dist/Plot.svelte +10 -2
- package/dist/core/FacetAxes.svelte +2 -2
- package/dist/core/Plot.svelte +7 -11
- package/dist/helpers/getBaseStyles.d.ts +2 -0
- package/dist/helpers/getBaseStyles.js +8 -0
- package/dist/helpers/removeIdenticalLines.js +3 -2
- package/dist/helpers/scales.js +2 -2
- package/dist/helpers/wordwrap.d.ts +14 -0
- package/dist/helpers/wordwrap.js +129 -0
- package/dist/marks/AxisX.svelte +2 -1
- package/dist/marks/AxisX.svelte.d.ts +1 -0
- package/dist/marks/Brush.svelte +44 -4
- package/dist/marks/Text.svelte.d.ts +1 -1
- package/dist/marks/WaffleX.svelte +115 -0
- package/dist/marks/WaffleX.svelte.d.ts +102 -0
- package/dist/marks/WaffleY.svelte +119 -0
- package/dist/marks/WaffleY.svelte.d.ts +100 -0
- package/dist/marks/helpers/BaseAxisX.svelte +31 -3
- package/dist/marks/helpers/BaseAxisX.svelte.d.ts +2 -0
- package/dist/marks/helpers/waffle.d.ts +58 -0
- package/dist/marks/helpers/waffle.js +194 -0
- package/dist/marks/index.d.ts +3 -1
- package/dist/marks/index.js +3 -1
- package/dist/transforms/group.js +11 -5
- package/dist/types/data.d.ts +1 -0
- package/dist/types/mark.d.ts +1 -1
- package/dist/types/plot.d.ts +19 -5
- package/dist/types/scale.d.ts +8 -0
- package/package.json +14 -14
package/dist/Mark.svelte
CHANGED
|
@@ -152,6 +152,12 @@
|
|
|
152
152
|
if (options?.[channel] !== undefined && out[channel] === undefined) {
|
|
153
153
|
// resolve value
|
|
154
154
|
out[channel] = resolveChannel(channel, row, options);
|
|
155
|
+
if (options[channel] === INDEX) {
|
|
156
|
+
const scale = plot.scales[CHANNEL_SCALE[channel]];
|
|
157
|
+
if (scale.type === 'band' || scale.type === 'point') {
|
|
158
|
+
out[channel] = scale.domain[out[channel] % scale.domain.length];
|
|
159
|
+
}
|
|
160
|
+
}
|
|
155
161
|
}
|
|
156
162
|
}
|
|
157
163
|
return [out];
|
|
@@ -212,6 +218,7 @@
|
|
|
212
218
|
resolvedData.flatMap((row) => {
|
|
213
219
|
const out: ScaledDataRecord<Datum> = {
|
|
214
220
|
datum: row.datum,
|
|
221
|
+
resolved: row,
|
|
215
222
|
index: row[INDEX],
|
|
216
223
|
valid: true
|
|
217
224
|
};
|
package/dist/Plot.svelte
CHANGED
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
<script lang="ts">
|
|
13
13
|
import Plot from './core/Plot.svelte';
|
|
14
14
|
|
|
15
|
-
import type { PlotOptions } from './types/index.js';
|
|
15
|
+
import type { PlotOptions, RawValue, ScaleOptions } from './types/index.js';
|
|
16
16
|
|
|
17
17
|
// implicit marks
|
|
18
18
|
import AxisX from './marks/AxisX.svelte';
|
|
@@ -62,12 +62,20 @@
|
|
|
62
62
|
const scales = $derived(
|
|
63
63
|
Object.fromEntries(
|
|
64
64
|
['x', 'y', 'r', 'color', 'opacity', 'symbol', 'length', 'fx', 'fy'].map((scale) => {
|
|
65
|
-
const scaleOpts = restOptions[scale]
|
|
65
|
+
const scaleOpts = maybeScaleOptions(restOptions[scale]);
|
|
66
66
|
const scaleFn = scaleOpts.scale || (scale === 'color' ? autoScaleColor : autoScale);
|
|
67
67
|
return [scale, { ...scaleOpts, scale: scaleFn }];
|
|
68
68
|
})
|
|
69
69
|
)
|
|
70
70
|
);
|
|
71
|
+
|
|
72
|
+
function maybeScaleOptions(
|
|
73
|
+
scaleOptions: undefined | false | RawValue[] | object
|
|
74
|
+
): Partial<ScaleOptions> | undefined {
|
|
75
|
+
if (scaleOptions === false) return { axis: false };
|
|
76
|
+
if (Array.isArray(scaleOptions)) return { domain: scaleOptions };
|
|
77
|
+
return scaleOptions || {};
|
|
78
|
+
}
|
|
71
79
|
</script>
|
|
72
80
|
|
|
73
81
|
{#snippet header()}
|
|
@@ -35,7 +35,7 @@
|
|
|
35
35
|
scaleFn={facetXScale}
|
|
36
36
|
scaleType="band"
|
|
37
37
|
ticks={fxValues}
|
|
38
|
-
tickFormat={(d) => d}
|
|
38
|
+
tickFormat={plot.options.fx.tickFormat || ((d) => d)}
|
|
39
39
|
tickFontSize={11}
|
|
40
40
|
tickSize={0}
|
|
41
41
|
tickPadding={5}
|
|
@@ -53,7 +53,7 @@
|
|
|
53
53
|
scaleFn={facetYScale}
|
|
54
54
|
scaleType="band"
|
|
55
55
|
ticks={fyValues}
|
|
56
|
-
tickFormat={(d) => d}
|
|
56
|
+
tickFormat={plot.options.fy.tickFormat || ((d) => d)}
|
|
57
57
|
tickFontSize={11}
|
|
58
58
|
tickSize={0}
|
|
59
59
|
tickPadding={5}
|
package/dist/core/Plot.svelte
CHANGED
|
@@ -114,7 +114,7 @@
|
|
|
114
114
|
class: className = '',
|
|
115
115
|
css = DEFAULTS.css,
|
|
116
116
|
width: fixedWidth,
|
|
117
|
-
...
|
|
117
|
+
...initialOptions
|
|
118
118
|
}: Partial<PlotOptions> = $props();
|
|
119
119
|
|
|
120
120
|
let width = $state(DEFAULTS.initialWidth);
|
|
@@ -164,7 +164,7 @@
|
|
|
164
164
|
);
|
|
165
165
|
|
|
166
166
|
const explicitDomains = $derived(
|
|
167
|
-
new Set(SCALES.filter((scale) => !!
|
|
167
|
+
new Set(SCALES.filter((scale) => !!initialOptions[scale]?.domain))
|
|
168
168
|
);
|
|
169
169
|
|
|
170
170
|
// one-dimensional plots have different automatic margins and heights
|
|
@@ -173,12 +173,12 @@
|
|
|
173
173
|
// construct the plot options from the user-defined options (top-level props) as well
|
|
174
174
|
// as extending them from smart context-aware defaults
|
|
175
175
|
const plotOptions = $derived(
|
|
176
|
-
extendPlotOptions(
|
|
176
|
+
extendPlotOptions(initialOptions, {
|
|
177
177
|
explicitScales,
|
|
178
178
|
explicitDomains,
|
|
179
|
-
hasProjection: !!
|
|
180
|
-
margin:
|
|
181
|
-
inset:
|
|
179
|
+
hasProjection: !!initialOptions.projection,
|
|
180
|
+
margin: initialOptions.margin,
|
|
181
|
+
inset: initialOptions.inset
|
|
182
182
|
})
|
|
183
183
|
);
|
|
184
184
|
|
|
@@ -369,7 +369,7 @@
|
|
|
369
369
|
{},
|
|
370
370
|
{ sortOrdinalDomains: DEFAULTS.sortOrdinalDomains },
|
|
371
371
|
smartDefaultPlotOptions(opts),
|
|
372
|
-
|
|
372
|
+
initialOptions
|
|
373
373
|
);
|
|
374
374
|
}
|
|
375
375
|
|
|
@@ -614,10 +614,6 @@
|
|
|
614
614
|
border: 0 !important;
|
|
615
615
|
}
|
|
616
616
|
|
|
617
|
-
.plot-header :global(h3) {
|
|
618
|
-
font-weight: 500;
|
|
619
|
-
}
|
|
620
|
-
|
|
621
617
|
.plot-footer {
|
|
622
618
|
margin-bottom: 2rem;
|
|
623
619
|
}
|
|
@@ -7,3 +7,5 @@ export default function (datum: DataRow, props: Partial<Channels>): string;
|
|
|
7
7
|
export declare function maybeToPixel(cssKey: string, value: string | number): string | number;
|
|
8
8
|
export declare function maybeFromPixel(value: string | number): string | number;
|
|
9
9
|
export declare function maybeFromRem(value: string | number, rootFontSize?: number): string | number;
|
|
10
|
+
export declare function getClipId(): string;
|
|
11
|
+
export declare function getPatternId(): string;
|
|
@@ -54,3 +54,11 @@ export function maybeFromRem(value, rootFontSize = 16) {
|
|
|
54
54
|
? +value.slice(0, -3) * rootFontSize
|
|
55
55
|
: value;
|
|
56
56
|
}
|
|
57
|
+
let nextClipId = 0;
|
|
58
|
+
let nextPatternId = 0;
|
|
59
|
+
export function getClipId() {
|
|
60
|
+
return `svp-clip-${++nextClipId}`;
|
|
61
|
+
}
|
|
62
|
+
export function getPatternId() {
|
|
63
|
+
return `svp-pattern-${++nextPatternId}`;
|
|
64
|
+
}
|
|
@@ -12,10 +12,11 @@ export default function removeIdenticalLines(input) {
|
|
|
12
12
|
text: []
|
|
13
13
|
});
|
|
14
14
|
}
|
|
15
|
-
|
|
15
|
+
const maxLines = Math.max(...input.map((t) => t.text.length));
|
|
16
|
+
for (let l = 0; l < maxLines; l++) {
|
|
16
17
|
const isIdentical = input.length > 1 && input.every((tick) => input[0].text[l] === tick.text[l]);
|
|
17
18
|
for (let c = 0; c < input.length; c++) {
|
|
18
|
-
if (!isIdentical && input[c].text[l])
|
|
19
|
+
if (!isIdentical && input[c].text[l] != null)
|
|
19
20
|
uniqueTicks[c].text.push(input[c].text[l]);
|
|
20
21
|
}
|
|
21
22
|
}
|
package/dist/helpers/scales.js
CHANGED
|
@@ -218,8 +218,8 @@ function domainFromInterval(domain, interval, name) {
|
|
|
218
218
|
return name === 'y' ? out.toReversed() : out;
|
|
219
219
|
}
|
|
220
220
|
const markTypesWithBandDefault = {
|
|
221
|
-
x: new Set(['barY', 'cell', 'tickY']),
|
|
222
|
-
y: new Set(['barX', 'cell', 'tickX'])
|
|
221
|
+
x: new Set(['barY', 'cell', 'tickY', 'waffleY']),
|
|
222
|
+
y: new Set(['barX', 'cell', 'tickX', 'waffleX'])
|
|
223
223
|
};
|
|
224
224
|
/**
|
|
225
225
|
* Infer a scale type based on the scale name, the data values mapped to it and
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Greedy word-wrapping that approximates visual width by character widths.
|
|
3
|
+
*
|
|
4
|
+
* - Splits input into words, additionally breaking on `-` to avoid very long segments.
|
|
5
|
+
* - Uses a rough character width table to approximate line widths.
|
|
6
|
+
* - Wraps once the maximum visual width is exceeded, but only after a minimum.
|
|
7
|
+
*/
|
|
8
|
+
export default function wordwrap(line: string, { maxCharactersPerLine, maxLineWidth }: {
|
|
9
|
+
maxCharactersPerLine?: number;
|
|
10
|
+
maxLineWidth?: number;
|
|
11
|
+
}, { minCharactersPerLine, minLineWidth }: {
|
|
12
|
+
minCharactersPerLine?: number;
|
|
13
|
+
minLineWidth?: number;
|
|
14
|
+
}, fontSize?: number, monospace?: boolean): string[];
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
import { sum } from 'd3-array';
|
|
2
|
+
// Per-character width table for a typical proportional font.
|
|
3
|
+
// This is not perfect for all fonts, but is better than treating
|
|
4
|
+
// all characters as equal width. Set `monospace = true` to bypass.
|
|
5
|
+
const CHAR_W = {
|
|
6
|
+
A: 7,
|
|
7
|
+
a: 7,
|
|
8
|
+
B: 8,
|
|
9
|
+
b: 7,
|
|
10
|
+
C: 8,
|
|
11
|
+
c: 6,
|
|
12
|
+
D: 9,
|
|
13
|
+
d: 7,
|
|
14
|
+
E: 7,
|
|
15
|
+
e: 7,
|
|
16
|
+
F: 7,
|
|
17
|
+
f: 4,
|
|
18
|
+
G: 9,
|
|
19
|
+
g: 7,
|
|
20
|
+
H: 9,
|
|
21
|
+
h: 7,
|
|
22
|
+
I: 3,
|
|
23
|
+
i: 3,
|
|
24
|
+
J: 5,
|
|
25
|
+
j: 3,
|
|
26
|
+
K: 8,
|
|
27
|
+
k: 6,
|
|
28
|
+
L: 7,
|
|
29
|
+
l: 3,
|
|
30
|
+
M: 11,
|
|
31
|
+
m: 11,
|
|
32
|
+
N: 9,
|
|
33
|
+
n: 7,
|
|
34
|
+
O: 9,
|
|
35
|
+
o: 7,
|
|
36
|
+
P: 8,
|
|
37
|
+
p: 7,
|
|
38
|
+
Q: 9,
|
|
39
|
+
q: 7,
|
|
40
|
+
R: 8,
|
|
41
|
+
r: 4,
|
|
42
|
+
S: 8,
|
|
43
|
+
s: 6,
|
|
44
|
+
T: 7,
|
|
45
|
+
t: 4,
|
|
46
|
+
U: 9,
|
|
47
|
+
u: 7,
|
|
48
|
+
V: 7,
|
|
49
|
+
v: 6,
|
|
50
|
+
W: 11,
|
|
51
|
+
w: 9,
|
|
52
|
+
X: 7,
|
|
53
|
+
x: 6,
|
|
54
|
+
Y: 7,
|
|
55
|
+
y: 6,
|
|
56
|
+
Z: 7,
|
|
57
|
+
z: 5,
|
|
58
|
+
'.': 2,
|
|
59
|
+
',': 2,
|
|
60
|
+
':': 2,
|
|
61
|
+
';': 2
|
|
62
|
+
};
|
|
63
|
+
/**
|
|
64
|
+
* Greedy word-wrapping that approximates visual width by character widths.
|
|
65
|
+
*
|
|
66
|
+
* - Splits input into words, additionally breaking on `-` to avoid very long segments.
|
|
67
|
+
* - Uses a rough character width table to approximate line widths.
|
|
68
|
+
* - Wraps once the maximum visual width is exceeded, but only after a minimum.
|
|
69
|
+
*/
|
|
70
|
+
export default function wordwrap(line, { maxCharactersPerLine, maxLineWidth }, { minCharactersPerLine, minLineWidth }, fontSize = 12, monospace = false) {
|
|
71
|
+
// Tokenized words (with hyphen-splitting applied) including trailing spaces/hyphens.
|
|
72
|
+
const tokens = [];
|
|
73
|
+
// First split by spaces, then further split by hyphens so we can
|
|
74
|
+
// wrap inside hyphenated words if necessary.
|
|
75
|
+
const spaceSeparated = line.split(' ');
|
|
76
|
+
spaceSeparated.forEach((word, wordIndex) => {
|
|
77
|
+
const hyphenParts = word.split('-');
|
|
78
|
+
const trailingWhitespace = wordIndex < spaceSeparated.length - 1 ? ' ' : '';
|
|
79
|
+
if (hyphenParts.length > 1) {
|
|
80
|
+
hyphenParts.forEach((part, partIndex) => {
|
|
81
|
+
const suffix = partIndex < hyphenParts.length - 1 ? '-' : trailingWhitespace;
|
|
82
|
+
tokens.push(part + suffix);
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
else {
|
|
86
|
+
tokens.push(word + trailingWhitespace);
|
|
87
|
+
}
|
|
88
|
+
});
|
|
89
|
+
const maxChars = maxCharactersPerLine || 40;
|
|
90
|
+
if (!maxLineWidth) {
|
|
91
|
+
// Fallback for max characters per line if not provided / falsy.
|
|
92
|
+
// Convert character counts into approximate visual widths.
|
|
93
|
+
maxLineWidth = maxChars * CHAR_W.a;
|
|
94
|
+
}
|
|
95
|
+
if (!minLineWidth) {
|
|
96
|
+
// Estimate a good minimum line length:
|
|
97
|
+
// - start from either a provided value or
|
|
98
|
+
// - clamp a scaled median word length between 3 and half of maxChars.
|
|
99
|
+
const sortedWordLengths = tokens.map((t) => t.length).sort((a, b) => a - b);
|
|
100
|
+
const medianIndex = Math.round(tokens.length / 2);
|
|
101
|
+
const medianWordLength = sortedWordLengths[medianIndex] ?? maxChars;
|
|
102
|
+
const minChars = minCharactersPerLine || Math.max(3, Math.min(maxChars * 0.5, 0.75 * medianWordLength));
|
|
103
|
+
minLineWidth = minChars * CHAR_W.a;
|
|
104
|
+
}
|
|
105
|
+
const lines = [];
|
|
106
|
+
const currentWords = [];
|
|
107
|
+
let currentWidth = 0;
|
|
108
|
+
// Helper to look up a character width, falling back to "a" if unknown
|
|
109
|
+
// or when monospace mode is enabled.
|
|
110
|
+
const charWidth = (char) => (fontSize / 12) * (!monospace ? CHAR_W[char] : CHAR_W.a);
|
|
111
|
+
// Greedy line construction: append tokens until the next one would exceed
|
|
112
|
+
// max visual width, but only break if the line has passed the minimum width.
|
|
113
|
+
tokens.forEach((token) => {
|
|
114
|
+
const tokenWidth = sum(token.split('').map(charWidth));
|
|
115
|
+
if (currentWidth + tokenWidth > maxLineWidth && currentWidth > minLineWidth) {
|
|
116
|
+
lines.push(currentWords.join(''));
|
|
117
|
+
currentWords.length = 0;
|
|
118
|
+
currentWidth = 0;
|
|
119
|
+
}
|
|
120
|
+
currentWidth += tokenWidth;
|
|
121
|
+
currentWords.push(token);
|
|
122
|
+
});
|
|
123
|
+
// Flush trailing tokens into the last line.
|
|
124
|
+
if (currentWords.length > 0) {
|
|
125
|
+
lines.push(currentWords.join(''));
|
|
126
|
+
}
|
|
127
|
+
// Filter out any empty lines that may have been created.
|
|
128
|
+
return lines.filter((d) => d !== '');
|
|
129
|
+
}
|
package/dist/marks/AxisX.svelte
CHANGED
|
@@ -51,6 +51,7 @@
|
|
|
51
51
|
tickSpacing?: number;
|
|
52
52
|
/** text anchor for axis labels */
|
|
53
53
|
textAnchor?: ConstantAccessor<CSS.Property.TextAnchor | 'auto', Datum>;
|
|
54
|
+
removeDuplicateTicks: boolean;
|
|
54
55
|
}
|
|
55
56
|
|
|
56
57
|
let markProps: AxisXMarkProps = $props();
|
|
@@ -223,7 +224,7 @@
|
|
|
223
224
|
{anchor}
|
|
224
225
|
{className}
|
|
225
226
|
{labelAnchor}
|
|
226
|
-
{options}
|
|
227
|
+
options={{ ...options, ...plot.options.x }}
|
|
227
228
|
{plot}
|
|
228
229
|
{text}
|
|
229
230
|
{tickClass}
|
|
@@ -87,6 +87,7 @@ declare class __sveltets_Render<Datum extends RawValue> {
|
|
|
87
87
|
tickSpacing?: number;
|
|
88
88
|
/** text anchor for axis labels */
|
|
89
89
|
textAnchor?: ConstantAccessor<"auto" | CSS.Property.TextAnchor, Datum>;
|
|
90
|
+
removeDuplicateTicks: boolean;
|
|
90
91
|
};
|
|
91
92
|
events(): {};
|
|
92
93
|
slots(): {};
|
package/dist/marks/Brush.svelte
CHANGED
|
@@ -33,14 +33,17 @@
|
|
|
33
33
|
onbrushend?: (evt: BrushEvent) => void;
|
|
34
34
|
onbrush?: (evt: BrushEvent) => void;
|
|
35
35
|
}
|
|
36
|
-
import { getContext } from 'svelte';
|
|
36
|
+
import { getContext, untrack } from 'svelte';
|
|
37
37
|
import Rect from './Rect.svelte';
|
|
38
38
|
import type { BaseMarkProps, DataRecord, PlotContext } from '../types/index.js';
|
|
39
39
|
import { clientToLayerCoordinates } from './helpers/events.js';
|
|
40
40
|
import Frame from './Frame.svelte';
|
|
41
41
|
import { getPlotDefaults } from '../hooks/plotDefaults.js';
|
|
42
42
|
|
|
43
|
-
let { brush = $bindable({ enabled: false }), ...markProps }: BrushMarkProps =
|
|
43
|
+
let { brush: brushExternal = $bindable({ enabled: false }), ...markProps }: BrushMarkProps =
|
|
44
|
+
$props();
|
|
45
|
+
|
|
46
|
+
let brush = $state<Brush>(brushExternal);
|
|
44
47
|
|
|
45
48
|
const DEFAULTS = {
|
|
46
49
|
stroke: 'currentColor',
|
|
@@ -179,6 +182,7 @@
|
|
|
179
182
|
);
|
|
180
183
|
|
|
181
184
|
$effect(() => {
|
|
185
|
+
// update brush prop when internal state changes
|
|
182
186
|
brush.x1 =
|
|
183
187
|
!brush.enabled || limitDimension === 'y'
|
|
184
188
|
? undefined
|
|
@@ -197,6 +201,38 @@
|
|
|
197
201
|
: constrain(y1 > y2 ? y1 : y2, yDomain);
|
|
198
202
|
});
|
|
199
203
|
|
|
204
|
+
// update internal state when external brush prop changes
|
|
205
|
+
$effect(() => {
|
|
206
|
+
const brushInt = untrack(() => brush);
|
|
207
|
+
if (!brushIdentical(brushInt, brushExternal)) {
|
|
208
|
+
brush = brushExternal;
|
|
209
|
+
// also keep internal x1,x2,y1,y2 in sync
|
|
210
|
+
x1 = brush.x1 as Date | number;
|
|
211
|
+
x2 = brush.x2 as Date | number;
|
|
212
|
+
y1 = brush.y1 as Date | number;
|
|
213
|
+
y2 = brush.y2 as Date | number;
|
|
214
|
+
}
|
|
215
|
+
});
|
|
216
|
+
|
|
217
|
+
// update external brush when internal state changes
|
|
218
|
+
$effect(() => {
|
|
219
|
+
const brushExt = untrack(() => brushExternal);
|
|
220
|
+
if (!brushIdentical(brush, brushExt)) {
|
|
221
|
+
// avoid cycles
|
|
222
|
+
brushExternal = brush;
|
|
223
|
+
}
|
|
224
|
+
});
|
|
225
|
+
|
|
226
|
+
function brushIdentical(b1: Brush, b2: Brush) {
|
|
227
|
+
return (
|
|
228
|
+
b1.enabled === b2.enabled &&
|
|
229
|
+
b1.x1 === b2.x1 &&
|
|
230
|
+
b1.x2 === b2.x2 &&
|
|
231
|
+
b1.y1 === b2.y1 &&
|
|
232
|
+
b1.y2 === b2.y2
|
|
233
|
+
);
|
|
234
|
+
}
|
|
235
|
+
|
|
200
236
|
function constrain<T extends number | Date>(x: T, extent: [typeof x, typeof x]) {
|
|
201
237
|
const minE = extent[0] < extent[1] ? extent[0] : extent[1];
|
|
202
238
|
const maxE = extent[0] > extent[1] ? extent[0] : extent[1];
|
|
@@ -242,8 +278,12 @@
|
|
|
242
278
|
} else {
|
|
243
279
|
// draw new brush selection
|
|
244
280
|
action = 'draw';
|
|
245
|
-
|
|
246
|
-
|
|
281
|
+
if (typeof xScaleFn.invert === 'function' && limitDimension !== 'y') {
|
|
282
|
+
x1 = x2 = xScaleFn.invert(dragStart[0]);
|
|
283
|
+
}
|
|
284
|
+
if (typeof yScaleFn.invert === 'function' && limitDimension !== 'x') {
|
|
285
|
+
y1 = y2 = yScaleFn.invert(dragStart[1]);
|
|
286
|
+
}
|
|
247
287
|
}
|
|
248
288
|
onbrushstart?.({ ...e, brush });
|
|
249
289
|
}
|
|
@@ -75,7 +75,7 @@ declare class __sveltets_Render<Datum extends DataRecord> {
|
|
|
75
75
|
* the font size of the text
|
|
76
76
|
*/
|
|
77
77
|
fontFamily?: ConstantAccessor<CSS.Property.FontFamily, Datum>;
|
|
78
|
-
fontSize?: ConstantAccessor<number | "-moz-initial" | "inherit" | "initial" | "revert" | "revert-layer" | "unset" | (string & {}) | "small" | "large" | "medium" | "x-large" | "x-small" | "xx-large" | "xx-small" | "xxx-large" | "larger" | "smaller", Datum>;
|
|
78
|
+
fontSize?: ConstantAccessor<number | "-moz-initial" | "inherit" | "initial" | "revert" | "revert-layer" | "unset" | (string & {}) | "small" | "math" | "large" | "medium" | "x-large" | "x-small" | "xx-large" | "xx-small" | "xxx-large" | "larger" | "smaller", Datum>;
|
|
79
79
|
fontWeight?: ConstantAccessor<CSS.Property.FontWeight, Datum>;
|
|
80
80
|
fontStyle?: ConstantAccessor<CSS.Property.FontStyle, Datum>;
|
|
81
81
|
fontVariant?: ConstantAccessor<CSS.Property.FontVariant, Datum>;
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
<!--
|
|
2
|
+
@component
|
|
3
|
+
The waffleX mark lets you create waffle charts by filling a rectangular area with small squares representing data values.
|
|
4
|
+
-->
|
|
5
|
+
<script lang="ts" generics="Datum extends DataRecord">
|
|
6
|
+
import type {
|
|
7
|
+
DataRecord,
|
|
8
|
+
BaseMarkProps,
|
|
9
|
+
ChannelAccessor,
|
|
10
|
+
LinkableMarkProps,
|
|
11
|
+
BorderRadius
|
|
12
|
+
} from '../types';
|
|
13
|
+
import { wafflePolygon, type WaffleOptions } from './helpers/waffle';
|
|
14
|
+
import { getPlotDefaults } from '../hooks/plotDefaults';
|
|
15
|
+
import { intervalX, recordizeX, sort, stackX } from '../transforms';
|
|
16
|
+
import type { StackOptions } from '../transforms/stack';
|
|
17
|
+
import Mark from '../Mark.svelte';
|
|
18
|
+
import { getContext } from 'svelte';
|
|
19
|
+
import { resolveProp, resolveStyles } from '../helpers/resolve';
|
|
20
|
+
import { roundedRect } from '../helpers/roundedRect';
|
|
21
|
+
|
|
22
|
+
interface WaffleXMarkProps
|
|
23
|
+
extends BaseMarkProps<Datum>,
|
|
24
|
+
LinkableMarkProps<Datum>,
|
|
25
|
+
WaffleOptions<Datum> {
|
|
26
|
+
data?: Datum[];
|
|
27
|
+
/**
|
|
28
|
+
* bound to a quantitative scale
|
|
29
|
+
*/
|
|
30
|
+
x?: ChannelAccessor<Datum>;
|
|
31
|
+
/**
|
|
32
|
+
* bound to a quantitative scale
|
|
33
|
+
*/
|
|
34
|
+
x1?: ChannelAccessor<Datum>;
|
|
35
|
+
/**
|
|
36
|
+
* bound to a quantitative scale
|
|
37
|
+
*/
|
|
38
|
+
x2?: ChannelAccessor<Datum>;
|
|
39
|
+
/**
|
|
40
|
+
* bound to a band scale
|
|
41
|
+
*/
|
|
42
|
+
y?: ChannelAccessor<Datum>;
|
|
43
|
+
stack?: StackOptions;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const DEFAULTS = {
|
|
47
|
+
fill: 'currentColor',
|
|
48
|
+
...getPlotDefaults().waffle,
|
|
49
|
+
...getPlotDefaults().waffleX
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
let markProps: WaffleXMarkProps = $props();
|
|
53
|
+
|
|
54
|
+
const {
|
|
55
|
+
data = [{} as Datum],
|
|
56
|
+
class: className = null,
|
|
57
|
+
stack,
|
|
58
|
+
symbol = null,
|
|
59
|
+
unit,
|
|
60
|
+
...options
|
|
61
|
+
}: WaffleXMarkProps = $derived({ ...DEFAULTS, ...markProps });
|
|
62
|
+
|
|
63
|
+
const { getPlotState } = getContext<PlotContext>('svelteplot');
|
|
64
|
+
const plot = $derived(getPlotState());
|
|
65
|
+
|
|
66
|
+
const args = $derived(
|
|
67
|
+
stackX(
|
|
68
|
+
intervalX(
|
|
69
|
+
// by default, sort by y channel (the ordinal labels)
|
|
70
|
+
sort(recordizeX({ data, ...options })),
|
|
71
|
+
{ plot }
|
|
72
|
+
),
|
|
73
|
+
stack
|
|
74
|
+
)
|
|
75
|
+
);
|
|
76
|
+
</script>
|
|
77
|
+
|
|
78
|
+
<Mark
|
|
79
|
+
type="waffleX"
|
|
80
|
+
requiredScales={{ y: ['band'] }}
|
|
81
|
+
channels={['x1', 'x2', 'y', 'fill', 'stroke', 'opacity', 'fillOpacity', 'strokeOpacity']}
|
|
82
|
+
{...args}>
|
|
83
|
+
{#snippet children({ mark, usedScales, scaledData })}
|
|
84
|
+
{@const wafflePoly = wafflePolygon('x', args, plot.scales)}
|
|
85
|
+
{#each scaledData as d, i (i)}
|
|
86
|
+
{@const borderRadius = resolveProp(args.borderRadius, d?.datum, 0) as BorderRadius}
|
|
87
|
+
{@const hasBorderRadius =
|
|
88
|
+
(typeof borderRadius === 'number' && borderRadius > 0) ||
|
|
89
|
+
(typeof borderRadius === 'object' &&
|
|
90
|
+
Math.max(
|
|
91
|
+
borderRadius.topRight ?? 0,
|
|
92
|
+
borderRadius.bottomRight ?? 0,
|
|
93
|
+
borderRadius.topLeft ?? 0,
|
|
94
|
+
borderRadius.bottomLeft ?? 0
|
|
95
|
+
) > 0)}
|
|
96
|
+
{@const [style, styleClass] = resolveStyles(plot, d, options, 'fill', usedScales)}
|
|
97
|
+
{@const { pattern, rect, path } = wafflePoly(d)}
|
|
98
|
+
<g class={['waffle-x', className]}>
|
|
99
|
+
<pattern {...pattern}>
|
|
100
|
+
{#if symbol}
|
|
101
|
+
{@render symbol(rect)}
|
|
102
|
+
{:else if hasBorderRadius}
|
|
103
|
+
<path
|
|
104
|
+
d={roundedRect(rect.x, rect.y, rect.width, rect.height, borderRadius)}
|
|
105
|
+
{style}
|
|
106
|
+
class={styleClass} />
|
|
107
|
+
{:else}
|
|
108
|
+
<rect {style} class={styleClass} {...rect} />
|
|
109
|
+
{/if}
|
|
110
|
+
</pattern>
|
|
111
|
+
<path {...path} />
|
|
112
|
+
</g>
|
|
113
|
+
{/each}
|
|
114
|
+
{/snippet}
|
|
115
|
+
</Mark>
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
import type { DataRecord, ChannelAccessor, LinkableMarkProps } from '../types';
|
|
2
|
+
import { type WaffleOptions } from './helpers/waffle';
|
|
3
|
+
import type { StackOptions } from '../transforms/stack';
|
|
4
|
+
declare class __sveltets_Render<Datum extends DataRecord> {
|
|
5
|
+
props(): Partial<{
|
|
6
|
+
filter: import("../types").ConstantAccessor<boolean, Datum>;
|
|
7
|
+
facet: "auto" | "include" | "exclude";
|
|
8
|
+
fx: ChannelAccessor<Datum>;
|
|
9
|
+
fy: ChannelAccessor<Datum>;
|
|
10
|
+
dx: import("../types").ConstantAccessor<number, Datum>;
|
|
11
|
+
dy: import("../types").ConstantAccessor<number, Datum>;
|
|
12
|
+
dodgeX: import("../transforms/dodge").DodgeXOptions;
|
|
13
|
+
dodgeY: import("../transforms/dodge").DodgeYOptions;
|
|
14
|
+
fill: ChannelAccessor<Datum>;
|
|
15
|
+
fillOpacity: import("../types").ConstantAccessor<number, Datum>;
|
|
16
|
+
sort: ((a: import("../types").RawValue, b: import("../types").RawValue) => number) | {
|
|
17
|
+
channel: string;
|
|
18
|
+
order?: "ascending" | "descending";
|
|
19
|
+
} | import("../types").ConstantAccessor<import("../types").RawValue, Datum>;
|
|
20
|
+
stroke: ChannelAccessor<Datum>;
|
|
21
|
+
strokeWidth: import("../types").ConstantAccessor<number, Datum>;
|
|
22
|
+
strokeOpacity: import("../types").ConstantAccessor<number, Datum>;
|
|
23
|
+
strokeLinejoin: import("../types").ConstantAccessor<import("csstype").Property.StrokeLinejoin, Datum>;
|
|
24
|
+
strokeLinecap: import("../types").ConstantAccessor<import("csstype").Property.StrokeLinecap, Datum>;
|
|
25
|
+
strokeMiterlimit: import("../types").ConstantAccessor<number, Datum>;
|
|
26
|
+
opacity: ChannelAccessor<Datum>;
|
|
27
|
+
strokeDasharray: import("../types").ConstantAccessor<string, Datum>;
|
|
28
|
+
strokeDashoffset: import("../types").ConstantAccessor<number, Datum>;
|
|
29
|
+
mixBlendMode: import("../types").ConstantAccessor<import("csstype").Property.MixBlendMode, Datum>;
|
|
30
|
+
clipPath: string;
|
|
31
|
+
imageFilter: import("../types").ConstantAccessor<string, Datum>;
|
|
32
|
+
shapeRendering: import("../types").ConstantAccessor<import("csstype").Property.ShapeRendering, Datum>;
|
|
33
|
+
paintOrder: import("../types").ConstantAccessor<string, Datum>;
|
|
34
|
+
onclick: import("svelte/elements").MouseEventHandler<SVGPathElement>;
|
|
35
|
+
ondblclick: import("svelte/elements").MouseEventHandler<SVGPathElement>;
|
|
36
|
+
onmouseup: import("svelte/elements").MouseEventHandler<SVGPathElement>;
|
|
37
|
+
onmousedown: import("svelte/elements").MouseEventHandler<SVGPathElement>;
|
|
38
|
+
onmouseenter: import("svelte/elements").MouseEventHandler<SVGPathElement>;
|
|
39
|
+
onmousemove: import("svelte/elements").MouseEventHandler<SVGPathElement>;
|
|
40
|
+
onmouseleave: import("svelte/elements").MouseEventHandler<SVGPathElement>;
|
|
41
|
+
onmouseout: import("svelte/elements").MouseEventHandler<SVGPathElement>;
|
|
42
|
+
onmouseover: import("svelte/elements").MouseEventHandler<SVGPathElement>;
|
|
43
|
+
onpointercancel: import("svelte/elements").MouseEventHandler<SVGPathElement>;
|
|
44
|
+
onpointerdown: import("svelte/elements").MouseEventHandler<SVGPathElement>;
|
|
45
|
+
onpointerup: import("svelte/elements").MouseEventHandler<SVGPathElement>;
|
|
46
|
+
onpointerenter: import("svelte/elements").MouseEventHandler<SVGPathElement>;
|
|
47
|
+
onpointerleave: import("svelte/elements").MouseEventHandler<SVGPathElement>;
|
|
48
|
+
onpointermove: import("svelte/elements").MouseEventHandler<SVGPathElement>;
|
|
49
|
+
onpointerover: import("svelte/elements").MouseEventHandler<SVGPathElement>;
|
|
50
|
+
onpointerout: import("svelte/elements").MouseEventHandler<SVGPathElement>;
|
|
51
|
+
ondrag: import("svelte/elements").MouseEventHandler<SVGPathElement>;
|
|
52
|
+
ondrop: import("svelte/elements").MouseEventHandler<SVGPathElement>;
|
|
53
|
+
ondragstart: import("svelte/elements").MouseEventHandler<SVGPathElement>;
|
|
54
|
+
ondragenter: import("svelte/elements").MouseEventHandler<SVGPathElement>;
|
|
55
|
+
ondragleave: import("svelte/elements").MouseEventHandler<SVGPathElement>;
|
|
56
|
+
ondragover: import("svelte/elements").MouseEventHandler<SVGPathElement>;
|
|
57
|
+
ondragend: import("svelte/elements").MouseEventHandler<SVGPathElement>;
|
|
58
|
+
ontouchstart: import("svelte/elements").MouseEventHandler<SVGPathElement>;
|
|
59
|
+
ontouchmove: import("svelte/elements").MouseEventHandler<SVGPathElement>;
|
|
60
|
+
ontouchend: import("svelte/elements").MouseEventHandler<SVGPathElement>;
|
|
61
|
+
ontouchcancel: import("svelte/elements").MouseEventHandler<SVGPathElement>;
|
|
62
|
+
oncontextmenu: import("svelte/elements").MouseEventHandler<SVGPathElement>;
|
|
63
|
+
onwheel: import("svelte/elements").MouseEventHandler<SVGPathElement>;
|
|
64
|
+
class: string;
|
|
65
|
+
style: string;
|
|
66
|
+
cursor: import("../types").ConstantAccessor<import("csstype").Property.Cursor, Datum>;
|
|
67
|
+
}> & LinkableMarkProps<Datum> & WaffleOptions<Datum> & {
|
|
68
|
+
data?: Datum[] | undefined;
|
|
69
|
+
/**
|
|
70
|
+
* bound to a quantitative scale
|
|
71
|
+
*/
|
|
72
|
+
x?: ChannelAccessor<Datum>;
|
|
73
|
+
/**
|
|
74
|
+
* bound to a quantitative scale
|
|
75
|
+
*/
|
|
76
|
+
x1?: ChannelAccessor<Datum>;
|
|
77
|
+
/**
|
|
78
|
+
* bound to a quantitative scale
|
|
79
|
+
*/
|
|
80
|
+
x2?: ChannelAccessor<Datum>;
|
|
81
|
+
/**
|
|
82
|
+
* bound to a band scale
|
|
83
|
+
*/
|
|
84
|
+
y?: ChannelAccessor<Datum>;
|
|
85
|
+
stack?: StackOptions;
|
|
86
|
+
};
|
|
87
|
+
events(): {};
|
|
88
|
+
slots(): {};
|
|
89
|
+
bindings(): "";
|
|
90
|
+
exports(): {};
|
|
91
|
+
}
|
|
92
|
+
interface $$IsomorphicComponent {
|
|
93
|
+
new <Datum extends DataRecord>(options: import('svelte').ComponentConstructorOptions<ReturnType<__sveltets_Render<Datum>['props']>>): import('svelte').SvelteComponent<ReturnType<__sveltets_Render<Datum>['props']>, ReturnType<__sveltets_Render<Datum>['events']>, ReturnType<__sveltets_Render<Datum>['slots']>> & {
|
|
94
|
+
$$bindings?: ReturnType<__sveltets_Render<Datum>['bindings']>;
|
|
95
|
+
} & ReturnType<__sveltets_Render<Datum>['exports']>;
|
|
96
|
+
<Datum extends DataRecord>(internal: unknown, props: ReturnType<__sveltets_Render<Datum>['props']> & {}): ReturnType<__sveltets_Render<Datum>['exports']>;
|
|
97
|
+
z_$$bindings?: ReturnType<__sveltets_Render<any>['bindings']>;
|
|
98
|
+
}
|
|
99
|
+
/** The waffleX mark lets you create waffle charts by filling a rectangular area with small squares representing data values. */
|
|
100
|
+
declare const WaffleX: $$IsomorphicComponent;
|
|
101
|
+
type WaffleX<Datum extends DataRecord> = InstanceType<typeof WaffleX<Datum>>;
|
|
102
|
+
export default WaffleX;
|