layerchart 2.0.0-next.23 → 2.0.0-next.24

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.
@@ -207,7 +207,16 @@
207
207
  : undefined
208
208
  );
209
209
  const tickVals = $derived(resolveTickVals(scale, ticks, tickCount));
210
- const tickFormat = $derived(resolveTickFormat(scale, ticks, tickCount, format, tickMultiline));
210
+ const tickFormat = $derived(
211
+ resolveTickFormat({
212
+ scale,
213
+ ticks,
214
+ count: tickCount,
215
+ formatType: format,
216
+ multiline: tickMultiline,
217
+ placement,
218
+ })
219
+ );
211
220
 
212
221
  function getCoords(tick: any) {
213
222
  switch (placement) {
@@ -255,14 +264,14 @@
255
264
  return {
256
265
  textAnchor: 'middle',
257
266
  verticalAnchor: 'end',
258
- dy: -tickLength - 2, // manually adjusted until Text supports custom styles
267
+ dy: -tickLength,
259
268
  };
260
269
 
261
270
  case 'bottom':
262
271
  return {
263
272
  textAnchor: 'middle',
264
273
  verticalAnchor: 'start',
265
- dy: tickLength, // manually adjusted until Text supports custom styles
274
+ dy: tickLength,
266
275
  };
267
276
 
268
277
  case 'left':
@@ -270,7 +279,6 @@
270
279
  textAnchor: 'end',
271
280
  verticalAnchor: 'middle',
272
281
  dx: -tickLength,
273
- dy: -2, // manually adjusted until Text supports custom styles
274
282
  };
275
283
 
276
284
  case 'right':
@@ -278,7 +286,6 @@
278
286
  textAnchor: 'start',
279
287
  verticalAnchor: 'middle',
280
288
  dx: tickLength,
281
- dy: -2, // manually adjusted until Text supports custom styles
282
289
  };
283
290
 
284
291
  case 'angle':
@@ -293,7 +300,7 @@
293
300
  ? 'end'
294
301
  : 'start',
295
302
  verticalAnchor: 'middle',
296
- dx: Math.sin(xValue) * (tickLength + 2),
303
+ dx: Math.sin(xValue) * tickLength,
297
304
  dy: -Math.cos(xValue) * (tickLength + 4), // manually adjusted until Text supports custom styles
298
305
  };
299
306
 
@@ -302,7 +309,6 @@
302
309
  textAnchor: 'middle',
303
310
  verticalAnchor: 'middle',
304
311
  dx: 2,
305
- dy: -2, // manually adjusted until Text supports custom styles
306
312
  };
307
313
  }
308
314
  }
@@ -407,6 +413,9 @@
407
413
  value: tickFormat(tick, index),
408
414
  ...getDefaultTickLabelProps(tick),
409
415
  motion,
416
+ // complement 10px text (until Text supports custom styles)
417
+ capHeight: '7px',
418
+ lineHeight: '11px',
410
419
  ...tickLabelProps,
411
420
  class: cls(
412
421
  layerClass('axis-tick-label'),
@@ -86,9 +86,9 @@
86
86
  title?: string;
87
87
  label?: string;
88
88
  tick?: string;
89
- swatches?: string;
89
+ items?: string;
90
90
  swatch?: string;
91
- item?: (item: LegendItem) => string;
91
+ item?: string | ((item: LegendItem) => string);
92
92
  };
93
93
 
94
94
  /**
@@ -383,7 +383,7 @@
383
383
  layerClass('legend-swatch-group'),
384
384
  'flex gap-x-4 gap-y-1',
385
385
  orientation === 'vertical' && 'flex-col',
386
- classes.swatches
386
+ classes.items
387
387
  )}
388
388
  >
389
389
  {#each scaleConfig.tickValues ?? scaleConfig.xScale?.ticks?.(ticks) ?? [] as tick}
@@ -392,22 +392,26 @@
392
392
  <button
393
393
  class={cls(
394
394
  layerClass('legend-swatch-button'),
395
- 'flex gap-1',
395
+ 'flex items-center gap-1 truncate',
396
396
  !onclick && 'cursor-auto',
397
- classes.item?.(item)
397
+ typeof classes.item === 'function' ? classes.item(item) : classes.item
398
398
  )}
399
399
  onclick={(e) => onclick?.(e, item)}
400
400
  onpointerenter={(e) => onpointerenter?.(e, item)}
401
401
  onpointerleave={(e) => onpointerleave?.(e, item)}
402
402
  >
403
403
  <div
404
- class={cls(layerClass('legend-swatch'), 'h-4 w-4 rounded-full', classes.swatch)}
404
+ class={cls(
405
+ layerClass('legend-swatch'),
406
+ 'h-4 w-4 shrink-0 rounded-full',
407
+ classes.swatch
408
+ )}
405
409
  style:background-color={color}
406
410
  ></div>
407
411
  <div
408
412
  class={cls(
409
413
  layerClass('legend-swatch-label'),
410
- 'text-xs text-surface-content whitespace-nowrap',
414
+ 'text-xs text-surface-content truncate whitespace-nowrap',
411
415
  classes.label
412
416
  )}
413
417
  >
@@ -73,9 +73,9 @@ export type LegendPropsWithoutHTML = {
73
73
  title?: string;
74
74
  label?: string;
75
75
  tick?: string;
76
- swatches?: string;
76
+ items?: string;
77
77
  swatch?: string;
78
- item?: (item: LegendItem) => string;
78
+ item?: string | ((item: LegendItem) => string);
79
79
  };
80
80
  /**
81
81
  * A bindable reference to the wrapping `<div>` element.
@@ -337,21 +337,11 @@
337
337
 
338
338
  const startDy = $derived.by(() => {
339
339
  if (verticalAnchor === 'start') {
340
- return getPixelValue(capHeight);
340
+ return getPixelValue(lineHeight);
341
341
  } else if (verticalAnchor === 'middle') {
342
342
  return ((lineCount - 1) / 2) * -getPixelValue(lineHeight) + getPixelValue(capHeight) / 2;
343
343
  } else {
344
- return (lineCount - 1) * -getPixelValue(lineHeight);
345
- }
346
- });
347
-
348
- const pathStartDy = $derived.by(() => {
349
- if (verticalAnchor === 'start') {
350
- return getPixelValue(capHeight);
351
- } else if (verticalAnchor === 'middle') {
352
- return (0 / 2) * -getPixelValue(lineHeight) + getPixelValue(capHeight) / 2;
353
- } else {
354
- return 0 * -getPixelValue(lineHeight);
344
+ return (lineCount - 1) * -getPixelValue(lineHeight) - getPixelValue(capHeight) / 2;
355
345
  }
356
346
  });
357
347
 
@@ -537,7 +527,7 @@
537
527
  {#each wordsByLines as line, index}
538
528
  <tspan
539
529
  x={motionX.current}
540
- dy={index === 0 ? startDy : lineHeight}
530
+ dy={index === 0 ? startDy : getPixelValue(lineHeight)}
541
531
  class={layerClass('text-tspan')}
542
532
  >
543
533
  {line.words.join(' ')}
@@ -1,9 +1,20 @@
1
1
  import { type TimeInterval } from 'd3-time';
2
2
  import { Duration, type FormatType, type FormatConfig } from '@layerstack/utils';
3
3
  import { type AnyScale } from './scales.svelte.js';
4
- export declare function getDurationFormat(duration: Duration, multiline?: boolean): (date: Date, i: number) => string;
4
+ import type { AxisProps } from '../components/Axis.svelte';
5
+ export declare function getDurationFormat(duration: Duration, options?: {
6
+ multiline?: boolean;
7
+ placement?: AxisProps['placement'];
8
+ }): (date: Date, i: number) => string;
5
9
  export type TicksConfig = number | any[] | ((scale: AnyScale) => any[] | undefined) | {
6
10
  interval: TimeInterval | null;
7
11
  } | null;
8
12
  export declare function resolveTickVals(scale: AnyScale, ticks?: TicksConfig, count?: number): any[];
9
- export declare function resolveTickFormat(scale: AnyScale, ticks?: TicksConfig, count?: number, formatType?: FormatType | FormatConfig, multiline?: boolean): (date: Date, i: number) => string;
13
+ export declare function resolveTickFormat(options: {
14
+ scale: AnyScale;
15
+ ticks?: TicksConfig;
16
+ count?: number;
17
+ formatType?: FormatType | FormatConfig;
18
+ multiline?: boolean;
19
+ placement?: AxisProps['placement'];
20
+ }): (date: Date, i: number) => string;
@@ -1,43 +1,52 @@
1
1
  import { timeYear, timeMonth, timeDay, timeTicks } from 'd3-time';
2
2
  import { format, Duration, isLiteralObject, DateToken, } from '@layerstack/utils';
3
3
  import { isScaleBand, isScaleTime } from './scales.svelte.js';
4
- export function getDurationFormat(duration, multiline = false) {
4
+ export function getDurationFormat(duration, options = {
5
+ multiline: false,
6
+ }) {
7
+ const { multiline = false, placement = 'bottom' } = options;
5
8
  return function (date, i) {
9
+ let result = '';
6
10
  if (+duration >= +new Duration({ duration: { years: 1 } })) {
7
11
  // Year
8
- return format(date, 'year');
12
+ result = format(date, 'year');
9
13
  }
10
14
  else if (+duration >= +new Duration({ duration: { days: 28 } })) {
11
15
  // Month
12
16
  const isFirst = i === 0 || +timeYear.floor(date) === +date;
13
17
  if (multiline) {
14
- return (format(date, 'month', { variant: 'short' }) + (isFirst ? `\n${format(date, 'year')}` : ''));
18
+ result = [format(date, 'month', { variant: 'short' }), isFirst && format(date, 'year')];
15
19
  }
16
20
  else {
17
- return (format(date, 'month', { variant: 'short' }) +
18
- (isFirst ? ` '${format(date, 'year', { variant: 'short' })}` : ''));
21
+ result =
22
+ format(date, 'month', { variant: 'short' }) +
23
+ (isFirst ? ` '${format(date, 'year', { variant: 'short' })}` : '');
19
24
  }
20
25
  }
21
26
  else if (+duration >= +new Duration({ duration: { days: 1 } })) {
22
27
  // Day
23
28
  const isFirst = i === 0 || date.getDate() <= duration.days;
24
29
  if (multiline) {
25
- return (format(date, 'custom', { custom: DateToken.DayOfMonth_numeric }) +
26
- (isFirst ? `\n${format(date, 'month', { variant: 'short' })}` : ''));
30
+ result = [
31
+ format(date, 'custom', { custom: DateToken.DayOfMonth_numeric }),
32
+ isFirst && format(date, 'month', { variant: 'short' }),
33
+ ];
27
34
  }
28
35
  else {
29
- return format(date, 'day', { variant: 'short' });
36
+ result = format(date, 'day', { variant: 'short' });
30
37
  }
31
38
  }
32
39
  else if (+duration >= +new Duration({ duration: { hours: 1 } })) {
33
40
  // Hours
34
41
  const isFirst = i === 0 || +timeDay.floor(date) === +date;
35
42
  if (multiline) {
36
- return (format(date, 'custom', { custom: DateToken.Hour_numeric }) +
37
- (isFirst ? `\n${format(date, 'day', { variant: 'short' })}` : ''));
43
+ result = [
44
+ format(date, 'custom', { custom: DateToken.Hour_numeric }),
45
+ isFirst && format(date, 'day', { variant: 'short' }),
46
+ ];
38
47
  }
39
48
  else {
40
- return isFirst
49
+ result = isFirst
41
50
  ? format(date, 'day', { variant: 'short' })
42
51
  : format(date, 'custom', { custom: DateToken.Hour_numeric });
43
52
  }
@@ -46,34 +55,58 @@ export function getDurationFormat(duration, multiline = false) {
46
55
  // Minutes
47
56
  const isFirst = i === 0 || +timeDay.floor(date) === +date;
48
57
  if (multiline) {
49
- return (format(date, 'time', { variant: 'short' }) +
50
- (isFirst ? `\n${format(date, 'day', { variant: 'short' })}` : ''));
58
+ result = [
59
+ format(date, 'time', { variant: 'short' }),
60
+ isFirst && format(date, 'day', { variant: 'short' }),
61
+ ];
51
62
  }
52
63
  else {
53
- return format(date, 'time', { variant: 'short' });
64
+ result = format(date, 'time', { variant: 'short' });
54
65
  }
55
66
  }
56
67
  else if (+duration >= +new Duration({ duration: { seconds: 1 } })) {
57
68
  // Seconds
58
69
  const isFirst = i === 0 || +timeDay.floor(date) === +date;
59
- return (format(date, 'time') +
60
- (multiline && isFirst ? `\n${format(date, 'day', { variant: 'short' })}` : ''));
70
+ result = [
71
+ format(date, 'time'),
72
+ multiline && isFirst && format(date, 'day', { variant: 'short' }),
73
+ ];
61
74
  }
62
75
  else if (+duration >= +new Duration({ duration: { milliseconds: 1 } })) {
63
76
  // Milliseconds
64
77
  const isFirst = i === 0 || +timeDay.floor(date) === +date;
65
- return (format(date, 'custom', {
66
- custom: [
67
- DateToken.Hour_2Digit,
68
- DateToken.Minute_2Digit,
69
- DateToken.Second_2Digit,
70
- DateToken.MiliSecond_3,
71
- DateToken.Hour_woAMPM,
72
- ],
73
- }) + (multiline && isFirst ? `\n${format(date, 'day', { variant: 'short' })}` : ''));
78
+ result = [
79
+ format(date, 'custom', {
80
+ custom: [
81
+ DateToken.Hour_2Digit,
82
+ DateToken.Minute_2Digit,
83
+ DateToken.Second_2Digit,
84
+ DateToken.MiliSecond_3,
85
+ DateToken.Hour_woAMPM,
86
+ ],
87
+ }),
88
+ multiline && isFirst && format(date, 'day', { variant: 'short' }),
89
+ ];
74
90
  }
75
91
  else {
76
- return date.toString();
92
+ result = date.toString();
93
+ }
94
+ if (Array.isArray(result)) {
95
+ switch (placement) {
96
+ case 'top':
97
+ return result.filter(Boolean).reverse().join('\n');
98
+ case 'bottom':
99
+ return result.filter(Boolean).join('\n');
100
+ case 'left':
101
+ return result.filter(Boolean).reverse().join(' ');
102
+ case 'right':
103
+ return result.filter(Boolean).join(' ');
104
+ default:
105
+ return result.filter(Boolean).join('\n');
106
+ }
107
+ }
108
+ else {
109
+ return result;
77
110
  }
78
111
  };
79
112
  }
@@ -103,7 +136,8 @@ export function resolveTickVals(scale, ticks, count) {
103
136
  }
104
137
  return [];
105
138
  }
106
- export function resolveTickFormat(scale, ticks, count, formatType, multiline = false) {
139
+ export function resolveTickFormat(options) {
140
+ const { scale, ticks, count, formatType, multiline, placement } = options;
107
141
  // Explicit format
108
142
  if (formatType) {
109
143
  // @ts-expect-error - improve types
@@ -114,12 +148,12 @@ export function resolveTickFormat(scale, ticks, count, formatType, multiline = f
114
148
  if (isLiteralObject(ticks) && 'interval' in ticks && ticks.interval != null) {
115
149
  const start = ticks.interval.floor(new Date());
116
150
  const end = ticks.interval.ceil(new Date());
117
- return getDurationFormat(new Duration({ start, end }), multiline);
151
+ return getDurationFormat(new Duration({ start, end }), { multiline, placement });
118
152
  }
119
153
  else {
120
154
  // Compare first 2 ticks to determine duration between ticks for formatting
121
155
  const [start, end] = timeTicks(scale.domain()[0], scale.domain()[1], count);
122
- return getDurationFormat(new Duration({ start, end }), multiline);
156
+ return getDurationFormat(new Duration({ start, end }), { multiline, placement });
123
157
  }
124
158
  }
125
159
  // Format from scale
package/package.json CHANGED
@@ -4,7 +4,7 @@
4
4
  "author": "Sean Lynch <techniq35@gmail.com>",
5
5
  "license": "MIT",
6
6
  "repository": "techniq/layerchart",
7
- "version": "2.0.0-next.23",
7
+ "version": "2.0.0-next.24",
8
8
  "devDependencies": {
9
9
  "@changesets/cli": "^2.29.4",
10
10
  "@iconify-json/lucide": "^1.2.48",