svelteplot 0.5.3-pr-255.0 → 0.5.3-pr-255.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.
Files changed (40) hide show
  1. package/dist/Mark.svelte +6 -0
  2. package/dist/constants.d.ts +1 -1
  3. package/dist/core/FacetAxes.svelte +2 -2
  4. package/dist/core/Plot.svelte +0 -4
  5. package/dist/helpers/colors.d.ts +8 -11
  6. package/dist/helpers/facets.d.ts +1 -1
  7. package/dist/helpers/getBaseStyles.d.ts +2 -4
  8. package/dist/helpers/index.d.ts +1 -1
  9. package/dist/helpers/reduce.d.ts +1 -1
  10. package/dist/helpers/removeIdenticalLines.js +3 -2
  11. package/dist/helpers/scales.d.ts +5 -5
  12. package/dist/helpers/symbols.d.ts +2 -2
  13. package/dist/helpers/time.d.ts +3 -3
  14. package/dist/helpers/typeChecks.d.ts +4 -4
  15. package/dist/helpers/wordwrap.d.ts +14 -0
  16. package/dist/helpers/wordwrap.js +129 -0
  17. package/dist/marks/AxisX.svelte +2 -1
  18. package/dist/marks/AxisX.svelte.d.ts +1 -0
  19. package/dist/marks/Brush.svelte +44 -4
  20. package/dist/marks/CustomMark.svelte +9 -3
  21. package/dist/marks/Image.svelte +76 -0
  22. package/dist/marks/Image.svelte.d.ts +19 -0
  23. package/dist/marks/Text.svelte.d.ts +1 -1
  24. package/dist/marks/WaffleX.svelte +28 -4
  25. package/dist/marks/WaffleY.svelte +35 -8
  26. package/dist/marks/helpers/Anchor.svelte +16 -2
  27. package/dist/marks/helpers/Anchor.svelte.d.ts +28 -14
  28. package/dist/marks/helpers/BaseAxisX.svelte +20 -2
  29. package/dist/marks/helpers/BaseAxisX.svelte.d.ts +2 -0
  30. package/dist/marks/helpers/waffle.d.ts +17 -2
  31. package/dist/marks/helpers/waffle.js +0 -1
  32. package/dist/marks/index.d.ts +2 -1
  33. package/dist/marks/index.js +2 -1
  34. package/dist/transforms/centroid.d.ts +1 -4
  35. package/dist/transforms/group.js +11 -5
  36. package/dist/transforms/recordize.d.ts +3 -3
  37. package/dist/transforms/sort.d.ts +2 -2
  38. package/dist/types/plot.d.ts +7 -3
  39. package/dist/types/scale.d.ts +8 -0
  40. 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];
@@ -13,4 +13,4 @@ export declare const CSS_COLOR_MIX: RegExp;
13
13
  export declare const CSS_COLOR_CONTRAST: RegExp;
14
14
  export declare const CSS_RGBA: RegExp;
15
15
  export declare const CSS_URL: RegExp;
16
- export declare const INDEX: unique symbol;
16
+ export declare const INDEX: any;
@@ -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}
@@ -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
  }
@@ -1,13 +1,10 @@
1
- import { interpolateBrBG } from 'd3-scale-chromatic';
2
1
  import type { ColorScheme } from '../types/index.js';
3
- export declare const categoricalSchemes: Map<string, readonly string[]>;
4
- export declare function isCategoricalScheme(scheme: string): boolean;
5
- type SchemeGetter = (n: number) => readonly string[];
6
- export declare function isOrdinalScheme(scheme: ColorScheme): boolean;
7
- export declare function ordinalScheme(scheme: string): SchemeGetter | undefined;
8
- export declare function ordinalRange(scheme: string, length: number): readonly string[] | undefined;
2
+ export declare const categoricalSchemes: any;
3
+ export declare function isCategoricalScheme(scheme: string): any;
4
+ export declare function isOrdinalScheme(scheme: ColorScheme): any;
5
+ export declare function ordinalScheme(scheme: string): any;
6
+ export declare function ordinalRange(scheme: string, length: number): any;
9
7
  export declare function maybeBooleanRange(domain: boolean[], scheme?: string): any[] | undefined;
10
- export declare function isQuantitativeScheme(scheme: string): boolean;
11
- export declare function quantitativeScheme(scheme: string): typeof interpolateBrBG | undefined;
12
- export declare function isDivergingScheme(scheme: string): boolean;
13
- export {};
8
+ export declare function isQuantitativeScheme(scheme: string): any;
9
+ export declare function quantitativeScheme(scheme: string): any;
10
+ export declare function isDivergingScheme(scheme: string): any;
@@ -9,4 +9,4 @@ import type { GenericMarkOptions, Mark, RawValue } from '../types/index.js';
9
9
  * @param fyValues y facet domain
10
10
  * @returns
11
11
  */
12
- export declare function getEmptyFacets(marks: Mark<GenericMarkOptions>[], fxValues: RawValue[], fyValues: RawValue[]): Map<RawValue, Map<RawValue, boolean>>;
12
+ export declare function getEmptyFacets(marks: Mark<GenericMarkOptions>[], fxValues: RawValue[], fyValues: RawValue[]): any;
@@ -1,9 +1,7 @@
1
1
  import type { Channels } from '../types/index.js';
2
2
  import type { DataRow } from '../types/index.js';
3
- export declare function getBaseStylesObject(datum: DataRow, props: Partial<Channels>): {
4
- [k: string]: string | number;
5
- };
6
- export default function (datum: DataRow, props: Partial<Channels>): string;
3
+ export declare function getBaseStylesObject(datum: DataRow, props: Partial<Channels>): any;
4
+ export default function (datum: DataRow, props: Partial<Channels>): any;
7
5
  export declare function maybeToPixel(cssKey: string, value: string | number): string | number;
8
6
  export declare function maybeFromPixel(value: string | number): string | number;
9
7
  export declare function maybeFromRem(value: string | number, rootFontSize?: number): string | number;
@@ -15,4 +15,4 @@ export declare const POSITION_CHANNELS: Set<ChannelName>;
15
15
  export declare function parseInset(inset: number | string, width: number): number;
16
16
  export declare function omit<T extends {}, K extends keyof T>(obj: T, ...keys: K[]): Omit<T, K>;
17
17
  export declare function identity<T>(x: T): T;
18
- export declare const GEOJSON_PREFER_STROKE: Set<string>;
18
+ export declare const GEOJSON_PREFER_STROKE: any;
@@ -4,7 +4,7 @@ type ReducerOption = ReducerName | ReducerFunc;
4
4
  type Digit = 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9;
5
5
  export type ReducerPercentile = (`p${Digit}${Digit}` & Record<never, never>) | 'p25' | 'p50' | 'p75';
6
6
  export type ReducerName = 'count' | 'deviation' | 'difference' | 'first' | 'last' | 'max' | 'mean' | 'median' | 'min' | 'mode' | 'ratio' | 'sum' | 'variance' | ReducerPercentile;
7
- export declare const Reducer: Record<ReducerName, ReducerFunc>;
7
+ export declare const Reducer: any;
8
8
  export declare function mayberReducer(r: ReducerOption): ReducerFunc;
9
9
  export declare function reduceOutputs(newDatum: DataRecord, data: DataRecord[], options: Record<ChannelName, ReducerOption>, outputs: Iterable<ChannelName>, channels: Channels, newChannels: Channels): void;
10
10
  export {};
@@ -12,10 +12,11 @@ export default function removeIdenticalLines(input) {
12
12
  text: []
13
13
  });
14
14
  }
15
- for (let l = 0; l < input[0].text.length; l++) {
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
  }
@@ -1,4 +1,4 @@
1
- import type { ChannelAccessor, GenericMarkOptions, Mark, MarkType, PlotDefaults, PlotOptions, PlotScales, PlotState, RawValue, ScaleName, ScaleOptions, ScaleType, ScaledChannelName, UsedScales } from '../types/index.js';
1
+ import type { GenericMarkOptions, Mark, MarkType, PlotDefaults, PlotOptions, PlotScales, PlotState, RawValue, ScaleName, ScaleOptions, ScaleType, UsedScales } from '../types/index.js';
2
2
  /**
3
3
  * compute the plot scales
4
4
  */
@@ -8,7 +8,7 @@ export declare function createScale<T extends ScaleOptions>(name: ScaleName, sca
8
8
  domain: number[];
9
9
  range: number[];
10
10
  fn: (() => string) | (() => number);
11
- skip: Map<any, any>;
11
+ skip: any;
12
12
  isDummy: boolean;
13
13
  manualActiveMarks?: undefined;
14
14
  uniqueScaleProps?: undefined;
@@ -18,9 +18,9 @@ export declare function createScale<T extends ScaleOptions>(name: ScaleName, sca
18
18
  domain: any;
19
19
  range: any;
20
20
  fn: any;
21
- skip: Map<ScaledChannelName, Set<symbol>>;
21
+ skip: any;
22
22
  manualActiveMarks: number;
23
- uniqueScaleProps: Set<ChannelAccessor>;
23
+ uniqueScaleProps: any;
24
24
  autoTitle: string | null;
25
25
  isDummy?: undefined;
26
26
  };
@@ -36,7 +36,7 @@ export declare function inferScaleType(name: ScaleName, dataValues: RawValue[],
36
36
  * not. That's what this function is used for.
37
37
  */
38
38
  export declare function getUsedScales(plot: PlotState, options: GenericMarkOptions, mark: Mark<GenericMarkOptions>): UsedScales;
39
- export declare function looksLikeANumber(input: string | number): boolean;
39
+ export declare function looksLikeANumber(input: string | number): any;
40
40
  export declare function projectXY(scales: PlotScales, x: RawValue, y: RawValue, useXScale?: boolean, useYScale?: boolean): [number, number];
41
41
  export declare function projectX(channel: 'x' | 'x1' | 'x2', scales: PlotScales, value: RawValue): number;
42
42
  export declare function projectY(channel: 'y' | 'y1' | 'y2', scales: PlotScales, value: RawValue): number;
@@ -1,5 +1,5 @@
1
1
  import { type SymbolType } from 'd3-shape';
2
2
  export declare const sqrt3: number;
3
3
  export declare const sqrt4_3: number;
4
- export declare function isSymbol(value: string | SymbolType): boolean;
5
- export declare function maybeSymbol(symbol: SymbolType | string): SymbolType;
4
+ export declare function isSymbol(value: string | SymbolType): any;
5
+ export declare function maybeSymbol(symbol: SymbolType | string): any;
@@ -1,6 +1,6 @@
1
- export declare const durations: Map<string, number>;
2
- export declare const intervalDuration: unique symbol;
3
- export declare const intervalType: unique symbol;
1
+ export declare const durations: any;
2
+ export declare const intervalDuration: any;
3
+ export declare const intervalType: any;
4
4
  export declare function parseTimeInterval(input: string): [string, number];
5
5
  export declare function maybeTimeInterval(input: string): any;
6
6
  export declare function maybeUtcInterval(input: string): any;
@@ -2,9 +2,9 @@ import type { RawValue } from '../types/index.js';
2
2
  export declare function isBooleanOrNull(v: RawValue): boolean;
3
3
  export declare function isDate(v: RawValue): v is Date;
4
4
  export declare function isDateOrNull(v: RawValue | null | undefined): boolean;
5
- export declare function isNumberOrNull(v: RawValue | null | undefined): boolean;
6
- export declare function isNumberOrNullOrNaN(v: RawValue | null | undefined): boolean;
5
+ export declare function isNumberOrNull(v: RawValue | null | undefined): any;
6
+ export declare function isNumberOrNullOrNaN(v: RawValue | null | undefined): any;
7
7
  export declare function isStringOrNull(v: RawValue | null | undefined): boolean;
8
- export declare function isSymbolOrNull(v: RawValue | null | undefined): boolean;
8
+ export declare function isSymbolOrNull(v: RawValue | null | undefined): any;
9
9
  export declare function isColorOrNull(v: RawValue | null | undefined): any;
10
- export declare function isOpacityOrNull(v: RawValue): boolean;
10
+ export declare function isOpacityOrNull(v: RawValue): any;
@@ -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
+ }
@@ -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}
@@ -88,6 +88,7 @@ declare class __sveltets_Render<Datum extends RawValue> {
88
88
  tickSpacing?: number;
89
89
  /** text anchor for axis labels */
90
90
  textAnchor?: ConstantAccessor<"auto" | CSS.Property.TextAnchor, Datum>;
91
+ removeDuplicateTicks: boolean;
91
92
  };
92
93
  events(): {};
93
94
  slots(): {};
@@ -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 = $props();
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
- x1 = x2 = xScaleFn.invert(dragStart[0]);
246
- y1 = y2 = yScaleFn.invert(dragStart[1]);
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
  }
@@ -5,6 +5,7 @@
5
5
  <script lang="ts" generics="Datum extends DataRecord">
6
6
  interface CustomMarkProps extends BaseMarkProps<Datum> {
7
7
  data?: Datum[];
8
+ type?: string;
8
9
  x?: ChannelAccessor<Datum>;
9
10
  x1?: ChannelAccessor<Datum>;
10
11
  x2?: ChannelAccessor<Datum>;
@@ -18,7 +19,6 @@
18
19
  marks?: Snippet<[{ records: ScaledDataRecord<Datum>[]; usedScales: UsedScales }]>;
19
20
  }
20
21
 
21
- import { getContext } from 'svelte';
22
22
  import type {
23
23
  PlotContext,
24
24
  DataRecord,
@@ -33,7 +33,13 @@
33
33
 
34
34
  import Mark from '../Mark.svelte';
35
35
 
36
- let { data = [{} as Datum], mark, marks, ...options }: CustomMarkProps = $props();
36
+ let {
37
+ data = [{} as Datum],
38
+ mark,
39
+ type = 'custom',
40
+ marks,
41
+ ...options
42
+ }: CustomMarkProps = $props();
37
43
 
38
44
  const args = $derived(sort({ data, ...options })) as CustomMarkProps;
39
45
 
@@ -53,7 +59,7 @@
53
59
  ];
54
60
  </script>
55
61
 
56
- <Mark type="custom" required={[]} channels={channels.filter((d) => !!options[d])} {...args}>
62
+ <Mark {type} required={[]} channels={channels.filter((d) => !!options[d])} {...args}>
57
63
  {#snippet children({ scaledData, usedScales })}
58
64
  {#if marks}
59
65
  {@render marks({ records: scaledData.filter((d) => d.valid), usedScales })}
@@ -0,0 +1,76 @@
1
+ <!--
2
+ @component
3
+ For showing images positioned at x/y coordinates
4
+ -->
5
+ <script lang="ts" generics="Datum extends DataRecord">
6
+ interface ImageMarkProps extends BaseMarkProps<Datum>, LinkableMarkProps<Datum> {
7
+ data: Datum[];
8
+ x: ChannelAccessor<Datum>;
9
+ y: ChannelAccessor<Datum>;
10
+ r?: ChannelAccessor<Datum>;
11
+ width?: ConstantAccessor<number, Datum>;
12
+ height?: ConstantAccessor<number, Datum>;
13
+ src?: ConstantAccessor<string, Datum>;
14
+ title?: ConstantAccessor<string, Datum>;
15
+ preserveAspectRatio?: string;
16
+ // canvas?: boolean;
17
+ imageClass?: ConstantAccessor<string, Datum>;
18
+ }
19
+
20
+ import type {
21
+ BaseMarkProps,
22
+ ChannelAccessor,
23
+ ConstantAccessor,
24
+ DataRecord,
25
+ LinkableMarkProps
26
+ } from '../types';
27
+ import { resolveProp } from '../helpers/resolve';
28
+ import CustomMark from './CustomMark.svelte';
29
+ import { getPlotDefaults } from '../hooks/plotDefaults';
30
+ import { sort } from '../transforms';
31
+ import Anchor from './helpers/Anchor.svelte';
32
+
33
+ let markProps: ImageMarkProps = $props();
34
+
35
+ const DEFAULTS: Partial<ImageMarkProps> = {
36
+ width: 20,
37
+ preserveAspectRatio: 'xMidYMin slice',
38
+ ...getPlotDefaults().image
39
+ };
40
+
41
+ const {
42
+ data = [{} as Datum],
43
+ width,
44
+ height,
45
+ src,
46
+ title,
47
+ imageClass,
48
+ preserveAspectRatio,
49
+ ...options
50
+ }: ImageMarkProps = $derived({
51
+ ...DEFAULTS,
52
+ ...markProps
53
+ });
54
+
55
+ const args = $derived(sort({ data, ...options }));
56
+ </script>
57
+
58
+ <CustomMark type="image" {...args}>
59
+ {#snippet mark({ record, index, usedScales })}
60
+ {@const w = record.r !== undefined ? record.r * 2 : resolveProp(width, record.datum, 20)}
61
+ {@const h =
62
+ record.r !== undefined ? record.r * 2 : resolveProp(height || width, record.datum, 20)}
63
+ <Anchor {options} datum={record.datum}>
64
+ <image
65
+ class={resolveProp(imageClass, record.datum, null)}
66
+ href={resolveProp(src, record.datum, '')}
67
+ x={record.x - w * 0.5}
68
+ y={record.y - h * 0.5}
69
+ {preserveAspectRatio}
70
+ clip-path={record.r !== undefined ? `circle(${record.r}px)` : null}
71
+ width={w}
72
+ height={h}
73
+ >{#if title}<title>{resolveProp(title, record.datum, '')}</title>{/if}</image>
74
+ </Anchor>
75
+ {/snippet}
76
+ </CustomMark>
@@ -0,0 +1,19 @@
1
+ import type { DataRecord } from '../types';
2
+ declare class __sveltets_Render<Datum extends DataRecord> {
3
+ props(): any;
4
+ events(): {};
5
+ slots(): {};
6
+ bindings(): "";
7
+ exports(): {};
8
+ }
9
+ interface $$IsomorphicComponent {
10
+ 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']>> & {
11
+ $$bindings?: ReturnType<__sveltets_Render<Datum>['bindings']>;
12
+ } & ReturnType<__sveltets_Render<Datum>['exports']>;
13
+ <Datum extends DataRecord>(internal: unknown, props: ReturnType<__sveltets_Render<Datum>['props']> & {}): ReturnType<__sveltets_Render<Datum>['exports']>;
14
+ z_$$bindings?: ReturnType<__sveltets_Render<any>['bindings']>;
15
+ }
16
+ /** For showing images positioned at x/y coordinates */
17
+ declare const Image: $$IsomorphicComponent;
18
+ type Image<Datum extends DataRecord> = InstanceType<typeof Image<Datum>>;
19
+ export default Image;
@@ -76,7 +76,7 @@ declare class __sveltets_Render<Datum extends DataRecord> {
76
76
  * the font size of the text
77
77
  */
78
78
  fontFamily?: ConstantAccessor<CSS.Property.FontFamily, Datum>;
79
- fontSize?: ConstantAccessor<number | "-moz-initial" | "inherit" | "initial" | "revert" | "revert-layer" | "unset" | (string & {}) | "small" | "medium" | "large" | "x-large" | "x-small" | "xx-large" | "xx-small" | "xxx-large" | "larger" | "smaller", Datum>;
79
+ 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>;
80
80
  fontWeight?: ConstantAccessor<CSS.Property.FontWeight, Datum>;
81
81
  fontStyle?: ConstantAccessor<CSS.Property.FontStyle, Datum>;
82
82
  fontVariant?: ConstantAccessor<CSS.Property.FontVariant, Datum>;
@@ -7,7 +7,8 @@
7
7
  DataRecord,
8
8
  BaseMarkProps,
9
9
  ChannelAccessor,
10
- LinkableMarkProps
10
+ LinkableMarkProps,
11
+ BorderRadius
11
12
  } from '../types';
12
13
  import { wafflePolygon, type WaffleOptions } from './helpers/waffle';
13
14
  import { getPlotDefaults } from '../hooks/plotDefaults';
@@ -15,11 +16,13 @@
15
16
  import type { StackOptions } from '../transforms/stack';
16
17
  import Mark from '../Mark.svelte';
17
18
  import { getContext } from 'svelte';
19
+ import { resolveProp, resolveStyles } from '../helpers/resolve';
20
+ import { roundedRect } from '../helpers/roundedRect';
18
21
 
19
22
  interface WaffleXMarkProps
20
23
  extends BaseMarkProps<Datum>,
21
24
  LinkableMarkProps<Datum>,
22
- WaffleOptions {
25
+ WaffleOptions<Datum> {
23
26
  data?: Datum[];
24
27
  /**
25
28
  * bound to a quantitative scale
@@ -52,6 +55,7 @@
52
55
  data = [{} as Datum],
53
56
  class: className = null,
54
57
  stack,
58
+ symbol = null,
55
59
  unit,
56
60
  ...options
57
61
  }: WaffleXMarkProps = $derived({ ...DEFAULTS, ...markProps });
@@ -79,10 +83,30 @@
79
83
  {#snippet children({ mark, usedScales, scaledData })}
80
84
  {@const wafflePoly = wafflePolygon('x', args, plot.scales)}
81
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)}
82
97
  {@const { pattern, rect, path } = wafflePoly(d)}
83
- <g>
98
+ <g class={['waffle-x', className]}>
84
99
  <pattern {...pattern}>
85
- <rect {...rect} fill="currentColor" />
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}
86
110
  </pattern>
87
111
  <path {...path} />
88
112
  </g>
@@ -8,18 +8,22 @@
8
8
  ChannelAccessor,
9
9
  BaseMarkProps,
10
10
  LinkableMarkProps,
11
- PlotContext
11
+ PlotContext,
12
+ BorderRadius
12
13
  } from '../types';
13
14
  import { wafflePolygon, type WaffleOptions } from './helpers/waffle';
14
15
  import { getPlotDefaults } from '../hooks/plotDefaults';
15
16
  import { getContext } from 'svelte';
16
17
  import { intervalY, recordizeY, sort, stackY } from '../transforms';
17
18
  import Mark from '../Mark.svelte';
19
+ import { resolveProp, resolveStyles } from '../helpers/resolve';
20
+ import { roundedRect } from '../helpers/roundedRect';
21
+ import GroupMultiple from './helpers/GroupMultiple.svelte';
18
22
 
19
23
  interface WaffleYMarkProps
20
24
  extends BaseMarkProps<Datum>,
21
25
  LinkableMarkProps<Datum>,
22
- WaffleOptions {
26
+ WaffleOptions<Datum> {
23
27
  data?: Datum[];
24
28
  /**
25
29
  * bound to a babd scale
@@ -37,11 +41,9 @@
37
41
  * bound to a quantitative scale
38
42
  */
39
43
  y2?: ChannelAccessor<Datum>;
40
- stack?: StackOptions;
41
44
  }
42
45
 
43
46
  const DEFAULTS = {
44
- fill: 'currentColor',
45
47
  ...getPlotDefaults().waffle,
46
48
  ...getPlotDefaults().waffleY
47
49
  };
@@ -52,6 +54,7 @@
52
54
  data = [{} as Datum],
53
55
  class: className = null,
54
56
  stack,
57
+ symbol = null,
55
58
  ...options
56
59
  }: WaffleYMarkProps = $derived({ ...DEFAULTS, ...markProps });
57
60
 
@@ -68,8 +71,6 @@
68
71
  stack
69
72
  )
70
73
  );
71
-
72
- $inspect({ args, options });
73
74
  </script>
74
75
 
75
76
  <Mark
@@ -80,10 +81,36 @@
80
81
  {#snippet children({ mark, usedScales, scaledData })}
81
82
  {@const wafflePoly = wafflePolygon('y', args, plot.scales)}
82
83
  {#each scaledData as d, i (i)}
84
+ {@const [style, styleClass] = resolveStyles(
85
+ plot,
86
+ d,
87
+ args,
88
+ args.stroke && !args.fill ? 'stroke' : 'fill',
89
+ usedScales
90
+ )}
91
+ {@const borderRadius = resolveProp(args.borderRadius, d?.datum, 0) as BorderRadius}
92
+ {@const hasBorderRadius =
93
+ (typeof borderRadius === 'number' && borderRadius > 0) ||
94
+ (typeof borderRadius === 'object' &&
95
+ Math.max(
96
+ borderRadius.topRight ?? 0,
97
+ borderRadius.bottomRight ?? 0,
98
+ borderRadius.topLeft ?? 0,
99
+ borderRadius.bottomLeft ?? 0
100
+ ) > 0)}
83
101
  {@const { pattern, rect, path } = wafflePoly(d)}
84
- <g>
102
+ <g class={['waffle-y', className]}>
85
103
  <pattern {...pattern}>
86
- <rect {...rect} fill="currentColor" />
104
+ {#if symbol}
105
+ {@render symbol({ ...rect, style, styleClass, datum: d.datum })}
106
+ {:else if hasBorderRadius}
107
+ <path
108
+ d={roundedRect(rect.x, rect.y, rect.width, rect.height, borderRadius)}
109
+ {style}
110
+ class={styleClass} />
111
+ {:else}
112
+ <rect {style} class={styleClass} {...rect} />
113
+ {/if}
87
114
  </pattern>
88
115
  <path {...path} />
89
116
  </g>
@@ -1,7 +1,21 @@
1
- <script>
1
+ <script lang="ts" generics="Datum extends Record<string, any>">
2
2
  import { resolveProp } from '../../helpers/resolve.js';
3
+ import type { ConstantAccessor } from '../../types';
3
4
 
4
- let { datum = {}, options = {}, children } = $props();
5
+ interface AnchorProps {
6
+ datum?: Datum;
7
+ options?: {
8
+ href?: ConstantAccessor<string, Datum>;
9
+ target?: ConstantAccessor<string, Datum>;
10
+ rel?: ConstantAccessor<string, Datum>;
11
+ type?: ConstantAccessor<string, Datum>;
12
+ download?: ConstantAccessor<string, Datum>;
13
+ [key: string]: any;
14
+ };
15
+ children?: () => any;
16
+ }
17
+
18
+ let { datum = {}, options = {}, children }: AnchorProps = $props();
5
19
 
6
20
  const href = $derived(resolveProp(options.href, datum, null));
7
21
  const target = $derived(resolveProp(options.target, datum, null));
@@ -1,15 +1,29 @@
1
+ import type { ConstantAccessor } from '../../types';
2
+ declare class __sveltets_Render<Datum extends Record<string, any>> {
3
+ props(): {
4
+ datum?: Datum | undefined;
5
+ options?: {
6
+ [key: string]: any;
7
+ href?: ConstantAccessor<string, Datum_1>;
8
+ target?: ConstantAccessor<string, Datum_1>;
9
+ rel?: ConstantAccessor<string, Datum_1>;
10
+ type?: ConstantAccessor<string, Datum_1>;
11
+ download?: ConstantAccessor<string, Datum_1>;
12
+ } | undefined;
13
+ children?: (() => any) | undefined;
14
+ };
15
+ events(): {};
16
+ slots(): {};
17
+ bindings(): "";
18
+ exports(): {};
19
+ }
20
+ interface $$IsomorphicComponent {
21
+ new <Datum extends Record<string, any>>(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']>> & {
22
+ $$bindings?: ReturnType<__sveltets_Render<Datum>['bindings']>;
23
+ } & ReturnType<__sveltets_Render<Datum>['exports']>;
24
+ <Datum extends Record<string, any>>(internal: unknown, props: ReturnType<__sveltets_Render<Datum>['props']> & {}): ReturnType<__sveltets_Render<Datum>['exports']>;
25
+ z_$$bindings?: ReturnType<__sveltets_Render<any>['bindings']>;
26
+ }
27
+ declare const Anchor: $$IsomorphicComponent;
28
+ type Anchor<Datum extends Record<string, any>> = InstanceType<typeof Anchor<Datum>>;
1
29
  export default Anchor;
2
- type Anchor = {
3
- $on?(type: string, callback: (e: any) => void): () => void;
4
- $set?(props: Partial<$$ComponentProps>): void;
5
- };
6
- declare const Anchor: import("svelte").Component<{
7
- datum?: Record<string, any>;
8
- options?: Record<string, any>;
9
- children: any;
10
- }, {}, "">;
11
- type $$ComponentProps = {
12
- datum?: Record<string, any>;
13
- options?: Record<string, any>;
14
- children: any;
15
- };
@@ -17,6 +17,7 @@
17
17
  import { randomId, testFilter } from '../../helpers/index.js';
18
18
  import { INDEX } from '../../constants';
19
19
  import { RAW_VALUE } from '../../transforms/recordize';
20
+ import wordwrap from '../../helpers/wordwrap';
20
21
 
21
22
  type BaseAxisXProps = {
22
23
  scaleFn: (d: RawValue) => number;
@@ -35,7 +36,9 @@
35
36
  dx: ConstantAccessor<number>;
36
37
  dy: ConstantAccessor<number>;
37
38
  filter: ChannelAccessor;
39
+ wordwrap: boolean;
38
40
  textAnchor: ConstantAccessor<'start' | 'middle' | 'end'> | 'auto';
41
+ removeDuplicateTicks: boolean;
39
42
  };
40
43
  text: boolean;
41
44
  plot: PlotState;
@@ -59,8 +62,21 @@
59
62
  text = true
60
63
  }: BaseAxisXProps = $props();
61
64
 
65
+ const isBandScale = $derived(scaleType === 'band');
66
+ const bandWidth = $derived(isBandScale ? scaleFn.bandwidth() : 0);
67
+
62
68
  function splitTick(tick: string | string[]) {
63
- return Array.isArray(tick) ? tick : [tick];
69
+ return Array.isArray(tick)
70
+ ? tick
71
+ : typeof tick === 'string' && isBandScale && options.wordwrap !== false
72
+ ? wordwrap(
73
+ tick,
74
+ { maxLineWidth: bandWidth * 0.9 },
75
+ { minCharactersPerLine: 4 },
76
+ +resolveProp(tickFontSize, {}, 11),
77
+ false
78
+ )
79
+ : [tick];
64
80
  }
65
81
 
66
82
  let tickRotate = $derived(plot.options.x.tickRotate || 0);
@@ -222,7 +238,9 @@
222
238
  {:else}
223
239
  {#each textLines as line, i (i)}
224
240
  <tspan x="0" dy={i ? 12 : 0}
225
- >{!prevTextLines || prevTextLines[i] !== line
241
+ >{!prevTextLines ||
242
+ prevTextLines[i] !== line ||
243
+ options.removeDuplicateTicks === false
226
244
  ? line
227
245
  : ''}</tspan>
228
246
  {/each}
@@ -16,7 +16,9 @@ type BaseAxisXProps = {
16
16
  dx: ConstantAccessor<number>;
17
17
  dy: ConstantAccessor<number>;
18
18
  filter: ChannelAccessor;
19
+ wordwrap: boolean;
19
20
  textAnchor: ConstantAccessor<'start' | 'middle' | 'end'> | 'auto';
21
+ removeDuplicateTicks: boolean;
20
22
  };
21
23
  text: boolean;
22
24
  plot: PlotState;
@@ -1,6 +1,8 @@
1
- import type { PlotScales, ScaledDataRecord } from '../../types';
1
+ import type { Snippet } from 'svelte';
2
+ import type { StackOptions } from '../../transforms/stack';
3
+ import type { BorderRadius, ConstantAccessor, PlotScales, ScaledDataRecord } from '../../types';
2
4
  type Point = [number, number];
3
- export type WaffleOptions = {
5
+ export type WaffleOptions<T> = {
4
6
  /**
5
7
  * the quantity represented by each square in the waffle chart, defaults to 1
6
8
  */
@@ -17,6 +19,19 @@ export type WaffleOptions = {
17
19
  * whether to round values to avoid partial cells; defaults to false
18
20
  */
19
21
  round?: boolean;
22
+ stack?: StackOptions;
23
+ borderRadius?: ConstantAccessor<BorderRadius, T>;
24
+ symbol?: Snippet<[
25
+ {
26
+ x: number;
27
+ y: number;
28
+ width: number;
29
+ height: number;
30
+ style: string | null;
31
+ styleClass: string | null;
32
+ datum: T;
33
+ }
34
+ ]>;
20
35
  };
21
36
  type WaffleProps = {
22
37
  pattern: {
@@ -66,7 +66,6 @@ export function wafflePolygon(y, options, scales) {
66
66
  const y2val = d.resolved[y2];
67
67
  const P = wafflePoints(round(y1val / unit), round(y2val / unit), multiple).map(transform);
68
68
  const c = P.pop();
69
- console.log({ c, P, xv, y1val, y2val });
70
69
  const id = getPatternId();
71
70
  const pos = [d[x] + tx + mx, y0];
72
71
  return {
@@ -27,12 +27,12 @@ export { default as Geo } from './Geo.svelte';
27
27
  export { default as Graticule } from './Graticule.svelte';
28
28
  export { default as GridX } from './GridX.svelte';
29
29
  export { default as GridY } from './GridY.svelte';
30
+ export { default as Image } from './Image.svelte';
30
31
  export { default as Line } from './Line.svelte';
31
32
  export { default as LineX } from './LineX.svelte';
32
33
  export { default as LineY } from './LineY.svelte';
33
34
  export { default as Link } from './Link.svelte';
34
35
  export { default as Pointer } from './Pointer.svelte';
35
- export { default as Vector } from './Vector.svelte';
36
36
  export { default as Rect } from './Rect.svelte';
37
37
  export { default as RectX } from './RectX.svelte';
38
38
  export { default as RectY } from './RectY.svelte';
@@ -45,6 +45,7 @@ export { default as Spike } from './Spike.svelte';
45
45
  export { default as Text } from './Text.svelte';
46
46
  export { default as TickX } from './TickX.svelte';
47
47
  export { default as TickY } from './TickY.svelte';
48
+ export { default as Vector } from './Vector.svelte';
48
49
  export { default as WaffleX } from './WaffleX.svelte';
49
50
  export { default as WaffleY } from './WaffleY.svelte';
50
51
  export { default as ColorLegend } from './ColorLegend.svelte';
@@ -27,12 +27,12 @@ export { default as Geo } from './Geo.svelte';
27
27
  export { default as Graticule } from './Graticule.svelte';
28
28
  export { default as GridX } from './GridX.svelte';
29
29
  export { default as GridY } from './GridY.svelte';
30
+ export { default as Image } from './Image.svelte';
30
31
  export { default as Line } from './Line.svelte';
31
32
  export { default as LineX } from './LineX.svelte';
32
33
  export { default as LineY } from './LineY.svelte';
33
34
  export { default as Link } from './Link.svelte';
34
35
  export { default as Pointer } from './Pointer.svelte';
35
- export { default as Vector } from './Vector.svelte';
36
36
  export { default as Rect } from './Rect.svelte';
37
37
  export { default as RectX } from './RectX.svelte';
38
38
  export { default as RectY } from './RectY.svelte';
@@ -45,6 +45,7 @@ export { default as Spike } from './Spike.svelte';
45
45
  export { default as Text } from './Text.svelte';
46
46
  export { default as TickX } from './TickX.svelte';
47
47
  export { default as TickY } from './TickY.svelte';
48
+ export { default as Vector } from './Vector.svelte';
48
49
  export { default as WaffleX } from './WaffleX.svelte';
49
50
  export { default as WaffleY } from './WaffleY.svelte';
50
51
  // HTML marks
@@ -1,8 +1,5 @@
1
1
  import type { DataRecord, TransformArg } from '../types/index.js';
2
- declare const CENTROID: unique symbol;
3
- type WithCentroid<T> = T & {
4
- [CENTROID]: [number, number];
5
- };
2
+ type WithCentroid<T> = T & {};
6
3
  export declare function geoCentroid<Datum extends DataRecord>({ data, ...options }: {
7
4
  data: Datum[];
8
5
  } & TransformArg<Datum>): TransformArg<WithCentroid<Datum>>;
@@ -1,5 +1,5 @@
1
1
  import { groupFacetsAndZ } from '../helpers/group.js';
2
- import { testFilter } from '../helpers/index.js';
2
+ import { isValid, testFilter } from '../helpers/index.js';
3
3
  import { reduceOutputs } from '../helpers/reduce.js';
4
4
  import { resolveChannel } from '../helpers/resolve.js';
5
5
  import { groups as d3Groups } from 'd3-array';
@@ -58,8 +58,12 @@ export function groupY(input, options = {}) {
58
58
  export function groupZ(input, options = {}) {
59
59
  return groupXYZ('z', input, options);
60
60
  }
61
+ const groupDimRaw = Symbol('groupDimRaw');
61
62
  function groupXYZ(dim, { data, ...channels }, options = {}) {
62
- if ((dim === 'z' ? channels.z || channels.fill || channels.stroke : channels[dim]) == null)
63
+ // console.log({ dim, data, channels, options });
64
+ if ((dim === 'z'
65
+ ? channels.z || channels.fill || channels.stroke || channels.fx || channels.fy
66
+ : channels[dim]) == null)
63
67
  throw new Error('you must provide a channel to group on ' + dim);
64
68
  const propName = options[`${dim}PropName`] != null
65
69
  ? options[`${dim}PropName`]
@@ -70,9 +74,11 @@ function groupXYZ(dim, { data, ...channels }, options = {}) {
70
74
  // group by x or y
71
75
  const groups = dim === 'z'
72
76
  ? [[null, data]]
73
- : d3Groups(data.filter((d) => testFilter(d, channels)), (d) => {
74
- const v = resolveChannel(dim, d, channels);
75
- return interval ? interval.round(v) : v;
77
+ : d3Groups(data
78
+ .filter((d) => testFilter(d, channels))
79
+ .map((d) => ({ ...d, [groupDimRaw]: resolveChannel(dim, d, channels) }))
80
+ .filter((d) => isValid(d[groupDimRaw])), (d) => {
81
+ return interval ? interval.floor(d[groupDimRaw]) : d[groupDimRaw];
76
82
  });
77
83
  const newData = [];
78
84
  let newChannels = omit({ ...channels }, 'filter');
@@ -1,7 +1,7 @@
1
1
  import type { TransformArgsRow, TransformArgsRecord } from '../types/index.js';
2
- export declare const X: unique symbol;
3
- export declare const Y: unique symbol;
4
- export declare const RAW_VALUE: unique symbol;
2
+ export declare const X: any;
3
+ export declare const Y: any;
4
+ export declare const RAW_VALUE: any;
5
5
  export declare function indexData<T extends object>(data: T[]): (T & {})[];
6
6
  export declare function recordizeX<T>({ data, ...channels }: TransformArgsRow<T>, { withIndex }?: {
7
7
  withIndex: boolean;
@@ -1,6 +1,6 @@
1
1
  import type { DataRow, TransformArg } from '../types/index.js';
2
- export declare const SORT_KEY: unique symbol;
3
- export declare const IS_SORTED: unique symbol;
2
+ export declare const SORT_KEY: any;
3
+ export declare const IS_SORTED: any;
4
4
  export declare function sort<T>({ data, ...channels }: TransformArg<T>, options?: {
5
5
  reverse?: boolean;
6
6
  }): any;
@@ -3,7 +3,7 @@ import type { ColorScheme } from './colorScheme.js';
3
3
  import type { GeoProjection } from 'd3-geo';
4
4
  import type { ChannelAccessor, ChannelName, ColorScaleOptions, DataRecord, LegendScaleOptions, PlotScales, ScaleOptions, XScaleOptions, YScaleOptions } from './index.js';
5
5
  import type { Snippet } from 'svelte';
6
- import type { Area, AreaX, AreaY, Arrow, AxisX, AxisY, BarX, BarY, BoxX, BoxY, Brush, BrushX, BrushY, Cell, DifferenceY, Dot, Frame, Geo, Graticule, GridX, GridY, Line, Link, Pointer, Rect, RectX, RectY, RuleX, RuleY, Sphere, Spike, Text, TickX, TickY, Vector } from '../marks/index.js';
6
+ import type { Area, AreaX, AreaY, Arrow, AxisX, AxisY, BarX, BarY, BoxX, BoxY, Brush, BrushX, BrushY, Cell, DifferenceY, Dot, Frame, Geo, Graticule, GridX, GridY, Image, Line, Link, Pointer, Rect, RectX, RectY, RuleX, RuleY, Sphere, Spike, Text, TickX, TickY, Vector } from '../marks/index.js';
7
7
  import type WaffleX from '../marks/WaffleX.svelte';
8
8
  import type WaffleY from '../marks/WaffleY.svelte';
9
9
  export type PlotState = {
@@ -232,6 +232,10 @@ export type PlotDefaults = {
232
232
  gridY: Partial<Omit<ComponentProps<typeof GridY>, IgnoreDefaults> & {
233
233
  implicit: boolean;
234
234
  }>;
235
+ /**
236
+ * default props for image marks
237
+ */
238
+ image: Partial<Omit<ComponentProps<typeof Image>, IgnoreDefaults>>;
235
239
  /**
236
240
  * default props for line marks
237
241
  */
@@ -432,8 +436,8 @@ export type PlotOptions = {
432
436
  opacity: Partial<ScaleOptions>;
433
437
  symbol: Partial<LegendScaleOptions>;
434
438
  length: Partial<ScaleOptions>;
435
- fx: Partial<ScaleOptions>;
436
- fy: Partial<ScaleOptions>;
439
+ fx: Partial<XScaleOptions>;
440
+ fy: Partial<YScaleOptions>;
437
441
  children: Snippet<[
438
442
  {
439
443
  width: number;
@@ -106,6 +106,14 @@ export type XScaleOptions = ScaleOptions & {
106
106
  tickRotate: number;
107
107
  labelAnchor: 'auto' | 'left' | 'center' | 'right';
108
108
  tickFormat: false | Intl.NumberFormatOptions | ((d: RawValue) => string);
109
+ /**
110
+ * Enable word wrapping for axis tick labels, default true
111
+ */
112
+ wordWrap: boolean;
113
+ /**
114
+ * Remove duplicate ticks from axis, default true
115
+ */
116
+ removeDuplicateTicks: boolean;
109
117
  };
110
118
  export type YScaleOptions = ScaleOptions & {
111
119
  /**
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "svelteplot",
3
- "version": "0.5.3-pr-255.0",
3
+ "version": "0.5.3-pr-255.1",
4
4
  "license": "ISC",
5
5
  "author": {
6
6
  "name": "Gregor Aisch",
@@ -57,13 +57,13 @@
57
57
  "@sveltejs/adapter-auto": "^6.1.1",
58
58
  "@sveltejs/adapter-static": "^3.0.10",
59
59
  "@sveltejs/eslint-config": "^8.3.4",
60
- "@sveltejs/kit": "^2.47.3",
60
+ "@sveltejs/kit": "^2.48.5",
61
61
  "@sveltejs/package": "^2.5.4",
62
62
  "@sveltejs/vite-plugin-svelte": "5.1.1",
63
63
  "@sveltepress/theme-default": "^6.0.4",
64
64
  "@sveltepress/twoslash": "^1.2.2",
65
65
  "@sveltepress/vite": "^1.2.2",
66
- "@testing-library/svelte": "^5.2.8",
66
+ "@testing-library/svelte": "^5.2.9",
67
67
  "@testing-library/user-event": "^14.6.1",
68
68
  "@types/d3-array": "^3.2.2",
69
69
  "@types/d3-color": "^3.1.3",
@@ -79,24 +79,24 @@
79
79
  "@types/geojson": "^7946.0.16",
80
80
  "@types/topojson": "^3.2.6",
81
81
  "@types/topojson-client": "^3.1.5",
82
- "@typescript-eslint/eslint-plugin": "^8.46.2",
83
- "@typescript-eslint/parser": "^8.46.2",
84
- "csstype": "^3.1.3",
82
+ "@typescript-eslint/eslint-plugin": "^8.46.4",
83
+ "@typescript-eslint/parser": "^8.46.4",
84
+ "csstype": "^3.2.0",
85
85
  "d3-dsv": "^3.0.1",
86
86
  "d3-fetch": "^3.0.1",
87
87
  "d3-force": "^3.0.0",
88
- "eslint": "^9.38.0",
88
+ "eslint": "^9.39.1",
89
89
  "eslint-config-prettier": "^10.1.8",
90
- "eslint-plugin-svelte": "3.12.5",
90
+ "eslint-plugin-svelte": "3.13.0",
91
91
  "jsdom": "^26.1.0",
92
92
  "prettier": "^3.6.2",
93
93
  "prettier-plugin-svelte": "^3.4.0",
94
- "puppeteer": "^24.26.1",
94
+ "puppeteer": "^24.30.0",
95
95
  "remark-code-extra": "^1.0.1",
96
96
  "remark-code-frontmatter": "^1.0.0",
97
97
  "resize-observer-polyfill": "^1.5.1",
98
- "sass": "^1.93.2",
99
- "svelte-check": "^4.3.3",
98
+ "sass": "^1.94.0",
99
+ "svelte-check": "^4.3.4",
100
100
  "svelte-eslint-parser": "1.4.0",
101
101
  "svelte-highlight": "^7.9.0",
102
102
  "svg-path-parser": "^1.1.0",
@@ -107,7 +107,7 @@
107
107
  "typedoc-plugin-markdown": "^4.9.0",
108
108
  "typescript": "^5.9.3",
109
109
  "vite": "^6.4.1",
110
- "vitest": "^3.2.4",
110
+ "vitest": "^4.0.9",
111
111
  "vitest-matchmedia-mock": "^2.0.3",
112
112
  "yoctocolors": "^2.1.2"
113
113
  },
@@ -127,9 +127,9 @@
127
127
  "d3-shape": "^3.2.0",
128
128
  "d3-time": "^3.1.0",
129
129
  "es-toolkit": "^1.41.0",
130
- "fast-equals": "^5.3.2",
130
+ "fast-equals": "^5.3.3",
131
131
  "interval-tree-1d": "^1.0.4",
132
132
  "merge-deep": "^3.0.3",
133
- "svelte": "5.43.0"
133
+ "svelte": "5"
134
134
  }
135
135
  }