layerchart 2.0.0-next.31 → 2.0.0-next.33
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/components/Axis.svelte +51 -5
- package/dist/components/Bar.svelte +2 -2
- package/dist/components/Grid.svelte +3 -3
- package/dist/components/Points.svelte +9 -5
- package/dist/utils/rect.svelte.js +4 -0
- package/dist/utils/ticks.d.ts +2 -2
- package/dist/utils/ticks.js +3 -8
- package/dist/utils/ticks.test.js +12 -17
- package/package.json +5 -5
|
@@ -125,8 +125,17 @@
|
|
|
125
125
|
|
|
126
126
|
import { extent } from 'd3-array';
|
|
127
127
|
import { pointRadial } from 'd3-shape';
|
|
128
|
-
|
|
129
|
-
|
|
128
|
+
import {
|
|
129
|
+
timeDay,
|
|
130
|
+
timeHour,
|
|
131
|
+
timeMillisecond,
|
|
132
|
+
timeMinute,
|
|
133
|
+
timeMonth,
|
|
134
|
+
timeSecond,
|
|
135
|
+
timeYear,
|
|
136
|
+
} from 'd3-time';
|
|
137
|
+
|
|
138
|
+
import { type FormatType, type FormatConfig, unique, PeriodType } from '@layerstack/utils';
|
|
130
139
|
import { cls } from '@layerstack/tailwind';
|
|
131
140
|
|
|
132
141
|
import Group, { type GroupProps } from './Group.svelte';
|
|
@@ -138,7 +147,7 @@
|
|
|
138
147
|
import { getChartContext } from './Chart.svelte';
|
|
139
148
|
import { extractLayerProps, layerClass } from '../utils/attributes.js';
|
|
140
149
|
import { type MotionProp } from '../utils/motion.svelte.js';
|
|
141
|
-
import {
|
|
150
|
+
import { autoTickVals, autoTickFormat, type TicksConfig } from '../utils/ticks.js';
|
|
142
151
|
|
|
143
152
|
let {
|
|
144
153
|
placement,
|
|
@@ -209,9 +218,46 @@
|
|
|
209
218
|
? Math.round(ctxSize / tickSpacing)
|
|
210
219
|
: undefined
|
|
211
220
|
);
|
|
212
|
-
const tickVals = $derived(
|
|
221
|
+
const tickVals = $derived.by(() => {
|
|
222
|
+
let tickVals = autoTickVals(scale, ticks, tickCount);
|
|
223
|
+
|
|
224
|
+
if (interval != null) {
|
|
225
|
+
// Remove last tick when interval is provided (such as for bar charts with center aligned (offset) ticks)
|
|
226
|
+
tickVals.pop();
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
// Use format to filter ticks (helpful to keep ticks above a threshold for wide charts or short durations)
|
|
230
|
+
const formatType = typeof format === 'object' ? format?.type : format;
|
|
231
|
+
|
|
232
|
+
if (formatType === 'integer') {
|
|
233
|
+
tickVals = tickVals.filter(Number.isInteger);
|
|
234
|
+
} else if (formatType === 'year' || formatType === PeriodType.CalendarYear) {
|
|
235
|
+
tickVals = tickVals.filter((val) => +timeYear.floor(val) === +val);
|
|
236
|
+
} else if (
|
|
237
|
+
formatType === 'month' ||
|
|
238
|
+
formatType === PeriodType.Month ||
|
|
239
|
+
formatType === PeriodType.MonthYear
|
|
240
|
+
) {
|
|
241
|
+
// tickVals = tickVals.filter((val) => +timeMonth.floor(val) === +val);
|
|
242
|
+
tickVals = tickVals.filter((val) => val.getDate() < 7); // first week of the month
|
|
243
|
+
} else if (formatType === 'day' || formatType === PeriodType.Day) {
|
|
244
|
+
tickVals = tickVals.filter((val) => +timeDay.floor(val) === +val);
|
|
245
|
+
} else if (formatType === 'hour' || formatType === PeriodType.Hour) {
|
|
246
|
+
tickVals = tickVals.filter((val) => +timeHour.floor(val) === +val);
|
|
247
|
+
} else if (formatType === 'minute' || formatType === PeriodType.Minute) {
|
|
248
|
+
tickVals = tickVals.filter((val) => +timeMinute.floor(val) === +val);
|
|
249
|
+
} else if (formatType === 'second' || formatType === PeriodType.Second) {
|
|
250
|
+
tickVals = tickVals.filter((val) => +timeSecond.floor(val) === +val);
|
|
251
|
+
} else if (formatType === 'millisecond' || formatType === PeriodType.Millisecond) {
|
|
252
|
+
tickVals = tickVals.filter((val) => +timeMillisecond.floor(val) === +val);
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
// Remove any duplicates (manually added)
|
|
256
|
+
return unique(tickVals);
|
|
257
|
+
});
|
|
258
|
+
|
|
213
259
|
const tickFormat = $derived(
|
|
214
|
-
|
|
260
|
+
autoTickFormat({
|
|
215
261
|
scale,
|
|
216
262
|
ticks,
|
|
217
263
|
count: tickCount,
|
|
@@ -136,10 +136,10 @@
|
|
|
136
136
|
const rounded = $derived(
|
|
137
137
|
roundedProp === 'edge'
|
|
138
138
|
? isVertical
|
|
139
|
-
? resolvedValue >= 0
|
|
139
|
+
? resolvedValue >= 0 && ctx.yRange[0] > ctx.yRange[1] // not inverted (bottom to top)
|
|
140
140
|
? 'top'
|
|
141
141
|
: 'bottom'
|
|
142
|
-
: resolvedValue >= 0
|
|
142
|
+
: resolvedValue >= 0 && ctx.xRange[0] < ctx.xRange[1] // not inverted (left to right)
|
|
143
143
|
? 'right'
|
|
144
144
|
: 'left'
|
|
145
145
|
: roundedProp
|
|
@@ -99,7 +99,7 @@
|
|
|
99
99
|
import Spline from './Spline.svelte';
|
|
100
100
|
import { getChartContext } from './Chart.svelte';
|
|
101
101
|
import { extractLayerProps, layerClass } from '../utils/attributes.js';
|
|
102
|
-
import {
|
|
102
|
+
import { autoTickVals, type TicksConfig } from '../utils/ticks.js';
|
|
103
103
|
|
|
104
104
|
const ctx = getChartContext();
|
|
105
105
|
|
|
@@ -131,8 +131,8 @@
|
|
|
131
131
|
|
|
132
132
|
const transitionIn = $derived((transitionInProp ?? tweenConfig?.options) ? fade : () => ({}));
|
|
133
133
|
|
|
134
|
-
const xTickVals = $derived(
|
|
135
|
-
const yTickVals = $derived(
|
|
134
|
+
const xTickVals = $derived(autoTickVals(ctx.xScale, xTicks));
|
|
135
|
+
const yTickVals = $derived(autoTickVals(ctx.yScale, yTicks));
|
|
136
136
|
|
|
137
137
|
const xBandOffset = $derived(
|
|
138
138
|
isScaleBand(ctx.xScale)
|
|
@@ -103,9 +103,14 @@
|
|
|
103
103
|
const scaledX: number = ctx.xScale(xVal);
|
|
104
104
|
const scaledY: number = ctx.yScale(yVal);
|
|
105
105
|
|
|
106
|
+
const x = scaledX + getOffset(scaledX, offsetX, ctx.xScale);
|
|
107
|
+
const y = scaledY + getOffset(scaledY, offsetY, ctx.yScale);
|
|
108
|
+
|
|
109
|
+
const radialPoint = pointRadial(x, y);
|
|
110
|
+
|
|
106
111
|
return {
|
|
107
|
-
x:
|
|
108
|
-
y:
|
|
112
|
+
x: ctx.radial ? radialPoint[0] : x,
|
|
113
|
+
y: ctx.radial ? radialPoint[1] : y,
|
|
109
114
|
r: ctx.config.r ? ctx.rGet(d) : r,
|
|
110
115
|
xValue: xVal,
|
|
111
116
|
yValue: yVal,
|
|
@@ -192,10 +197,9 @@
|
|
|
192
197
|
{/if}
|
|
193
198
|
|
|
194
199
|
{#each points as point}
|
|
195
|
-
{@const radialPoint = pointRadial(point.x, point.y)}
|
|
196
200
|
<Circle
|
|
197
|
-
cx={
|
|
198
|
-
cy={
|
|
201
|
+
cx={point.x}
|
|
202
|
+
cy={point.y}
|
|
199
203
|
r={point.r}
|
|
200
204
|
fill={fill ?? (ctx.config.c ? ctx.cGet(point.data) : null)}
|
|
201
205
|
{fillOpacity}
|
|
@@ -90,6 +90,10 @@ export function createDimensionGetter(ctx, getOptions) {
|
|
|
90
90
|
top = min([0, yDomainMinMax[1]]);
|
|
91
91
|
bottom = yValue;
|
|
92
92
|
}
|
|
93
|
+
// If yRange is inverted (drawing from top), swap top and bottom
|
|
94
|
+
if (ctx.yRange[0] < ctx.yRange[1]) {
|
|
95
|
+
[top, bottom] = [bottom, top];
|
|
96
|
+
}
|
|
93
97
|
const y = ctx.yScale(top) + insets.top;
|
|
94
98
|
const height = ctx.yScale(bottom) - ctx.yScale(top) - insets.bottom - insets.top;
|
|
95
99
|
return { x, y, width, height };
|
package/dist/utils/ticks.d.ts
CHANGED
|
@@ -9,8 +9,8 @@ export declare function getDurationFormat(duration: Duration, options?: {
|
|
|
9
9
|
export type TicksConfig = number | any[] | ((scale: AnyScale) => any[] | undefined) | {
|
|
10
10
|
interval: TimeInterval | null;
|
|
11
11
|
} | null;
|
|
12
|
-
export declare function
|
|
13
|
-
export declare function
|
|
12
|
+
export declare function autoTickVals(scale: AnyScale, ticks?: TicksConfig, count?: number): any[];
|
|
13
|
+
export declare function autoTickFormat(options: {
|
|
14
14
|
scale: AnyScale;
|
|
15
15
|
ticks?: TicksConfig;
|
|
16
16
|
count?: number;
|
package/dist/utils/ticks.js
CHANGED
|
@@ -110,7 +110,7 @@ export function getDurationFormat(duration, options = {
|
|
|
110
110
|
}
|
|
111
111
|
};
|
|
112
112
|
}
|
|
113
|
-
export function
|
|
113
|
+
export function autoTickVals(scale, ticks, count) {
|
|
114
114
|
// Explicit ticks
|
|
115
115
|
if (Array.isArray(ticks))
|
|
116
116
|
return ticks;
|
|
@@ -132,16 +132,11 @@ export function resolveTickVals(scale, ticks, count, interval) {
|
|
|
132
132
|
}
|
|
133
133
|
// Ticks from scale
|
|
134
134
|
if (scale.ticks && typeof scale.ticks === 'function') {
|
|
135
|
-
|
|
136
|
-
if (interval) {
|
|
137
|
-
// Remove last tick when interval is provided (such as for bar charts with center aligned (offset) ticks)
|
|
138
|
-
tickVals.pop();
|
|
139
|
-
}
|
|
140
|
-
return tickVals;
|
|
135
|
+
return scale.ticks(count ?? (typeof ticks === 'number' ? ticks : undefined));
|
|
141
136
|
}
|
|
142
137
|
return [];
|
|
143
138
|
}
|
|
144
|
-
export function
|
|
139
|
+
export function autoTickFormat(options) {
|
|
145
140
|
const { scale, ticks, count, formatType, multiline, placement } = options;
|
|
146
141
|
// Explicit format
|
|
147
142
|
if (formatType) {
|
package/dist/utils/ticks.test.js
CHANGED
|
@@ -1,62 +1,57 @@
|
|
|
1
1
|
import { describe, it, expect, vi } from 'vitest';
|
|
2
|
-
import {
|
|
2
|
+
import { autoTickVals } from './ticks.js';
|
|
3
3
|
// Mock helpers
|
|
4
4
|
const mockTicksFn = vi.fn();
|
|
5
5
|
const mockDomain = vi.fn(() => ['a', 'b', 'c', 'd', 'e']);
|
|
6
|
-
describe('
|
|
6
|
+
describe('autoTickVals', () => {
|
|
7
7
|
it('returns array ticks directly', () => {
|
|
8
8
|
const ticks = [1, 2, 3];
|
|
9
9
|
const scale = { ticks: mockTicksFn };
|
|
10
|
-
expect(
|
|
10
|
+
expect(autoTickVals(scale, ticks)).toEqual([1, 2, 3]);
|
|
11
11
|
});
|
|
12
12
|
it('calls function ticks with scale', () => {
|
|
13
13
|
const fnTicks = vi.fn(() => [4, 5, 6]);
|
|
14
14
|
const scale = { ticks: mockTicksFn };
|
|
15
|
-
expect(
|
|
15
|
+
expect(autoTickVals(scale, fnTicks)).toEqual([4, 5, 6]);
|
|
16
16
|
expect(fnTicks).toHaveBeenCalledWith(scale);
|
|
17
17
|
});
|
|
18
18
|
it('uses interval when provided', () => {
|
|
19
19
|
const interval = { every: vi.fn() };
|
|
20
20
|
const ticksConfig = { interval };
|
|
21
21
|
const scale = { ticks: vi.fn(() => [7, 8, 9]) };
|
|
22
|
-
expect(
|
|
22
|
+
expect(autoTickVals(scale, ticksConfig)).toEqual([7, 8, 9]);
|
|
23
23
|
expect(scale.ticks).toHaveBeenCalledWith(interval);
|
|
24
24
|
});
|
|
25
25
|
it('returns empty array if interval is null', () => {
|
|
26
26
|
const ticksConfig = { interval: null };
|
|
27
27
|
const scale = { ticks: mockTicksFn };
|
|
28
|
-
expect(
|
|
28
|
+
expect(autoTickVals(scale, ticksConfig)).toEqual([]);
|
|
29
29
|
});
|
|
30
30
|
it('filters band scale domain with number ticks', () => {
|
|
31
31
|
const scale = { domain: mockDomain, bandwidth: vi.fn() };
|
|
32
|
-
expect(
|
|
32
|
+
expect(autoTickVals(scale, 2)).toEqual(['a', 'c', 'e']);
|
|
33
33
|
});
|
|
34
34
|
it('returns full domain for band scale without ticks', () => {
|
|
35
35
|
const scale = { domain: mockDomain, bandwidth: vi.fn() };
|
|
36
|
-
expect(
|
|
36
|
+
expect(autoTickVals(scale)).toEqual(['a', 'b', 'c', 'd', 'e']);
|
|
37
37
|
});
|
|
38
38
|
it('uses undefined for non-left/right placement', () => {
|
|
39
39
|
const scale = { domain: mockDomain, ticks: vi.fn(() => [1, 2]) };
|
|
40
|
-
expect(
|
|
40
|
+
expect(autoTickVals(scale, undefined, undefined)).toEqual([1, 2]);
|
|
41
41
|
expect(scale.ticks).toHaveBeenCalledWith(undefined);
|
|
42
42
|
});
|
|
43
43
|
it('passes number ticks to scale.ticks', () => {
|
|
44
44
|
const scale = { domain: mockDomain, ticks: vi.fn(() => [10, 20]) };
|
|
45
|
-
expect(
|
|
45
|
+
expect(autoTickVals(scale, 5)).toEqual([10, 20]);
|
|
46
46
|
expect(scale.ticks).toHaveBeenCalledWith(5);
|
|
47
47
|
});
|
|
48
48
|
it('returns empty array for scale without ticks', () => {
|
|
49
49
|
const scale = { domain: mockDomain };
|
|
50
|
-
expect(
|
|
50
|
+
expect(autoTickVals(scale, 5)).toEqual([]);
|
|
51
51
|
});
|
|
52
52
|
it('handles null ticks with placement', () => {
|
|
53
53
|
const scale = { domain: mockDomain, ticks: vi.fn(() => [1, 2, 3]) };
|
|
54
|
-
expect(
|
|
54
|
+
expect(autoTickVals(scale, null, undefined)).toEqual([1, 2, 3]);
|
|
55
55
|
expect(scale.ticks).toHaveBeenCalledWith(undefined);
|
|
56
56
|
});
|
|
57
|
-
it('removes last tick when interval is provided', () => {
|
|
58
|
-
const interval = { every: vi.fn() };
|
|
59
|
-
const scale = { ticks: vi.fn(() => [1, 2, 3, 4]) };
|
|
60
|
-
expect(resolveTickVals(scale, undefined, undefined, interval)).toEqual([1, 2, 3]);
|
|
61
|
-
});
|
|
62
57
|
});
|
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.
|
|
7
|
+
"version": "2.0.0-next.33",
|
|
8
8
|
"devDependencies": {
|
|
9
9
|
"@changesets/cli": "^2.29.4",
|
|
10
10
|
"@iconify-json/lucide": "^1.2.48",
|
|
@@ -72,10 +72,10 @@
|
|
|
72
72
|
"type": "module",
|
|
73
73
|
"dependencies": {
|
|
74
74
|
"@dagrejs/dagre": "^1.1.4",
|
|
75
|
-
"@layerstack/svelte-actions": "1.0.1-next.
|
|
76
|
-
"@layerstack/svelte-state": "0.1.0-next.
|
|
77
|
-
"@layerstack/tailwind": "2.0.0-next.
|
|
78
|
-
"@layerstack/utils": "2.0.0-next.
|
|
75
|
+
"@layerstack/svelte-actions": "1.0.1-next.14",
|
|
76
|
+
"@layerstack/svelte-state": "0.1.0-next.19",
|
|
77
|
+
"@layerstack/tailwind": "2.0.0-next.17",
|
|
78
|
+
"@layerstack/utils": "2.0.0-next.14",
|
|
79
79
|
"d3-array": "^3.2.4",
|
|
80
80
|
"d3-color": "^3.1.0",
|
|
81
81
|
"d3-delaunay": "^6.0.4",
|