layerchart 2.0.0-next.32 → 2.0.0-next.34
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 +53 -7
- package/dist/components/Bar.svelte +2 -2
- package/dist/components/Bars.svelte +1 -1
- package/dist/components/Grid.svelte +3 -3
- package/dist/components/Points.svelte +1 -64
- package/dist/components/Points.svelte.d.ts +1 -8
- package/dist/components/Rule.svelte +156 -73
- package/dist/components/Rule.svelte.d.ts +7 -2
- package/dist/utils/genData.d.ts +8 -8
- package/dist/utils/genData.js +10 -8
- package/dist/utils/rect.svelte.js +4 -0
- package/dist/utils/scales.svelte.d.ts +1 -0
- package/dist/utils/scales.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,
|
|
@@ -393,8 +439,8 @@
|
|
|
393
439
|
{#if rule !== false}
|
|
394
440
|
{@const ruleProps = extractLayerProps(rule, 'axis-rule')}
|
|
395
441
|
<Rule
|
|
396
|
-
x={placement === 'left'
|
|
397
|
-
y={placement === 'top'
|
|
442
|
+
x={placement === 'left' ? '$left' : placement === 'right' ? '$right' : placement === 'angle'}
|
|
443
|
+
y={placement === 'top' ? '$top' : placement === 'bottom' ? '$bottom' : placement === 'radius'}
|
|
398
444
|
{motion}
|
|
399
445
|
{...ruleProps}
|
|
400
446
|
class={cls('stroke-surface-content/50', classes.rule, ruleProps?.class)}
|
|
@@ -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
|
|
@@ -32,8 +32,8 @@
|
|
|
32
32
|
import Bar, { type BarProps, type BarPropsWithoutHTML } from './Bar.svelte';
|
|
33
33
|
import Group from './Group.svelte';
|
|
34
34
|
|
|
35
|
-
import { chartDataArray } from '../utils/common.js';
|
|
36
35
|
import { getChartContext } from './Chart.svelte';
|
|
36
|
+
import { chartDataArray } from '../utils/common.js';
|
|
37
37
|
import { extractLayerProps, layerClass } from '../utils/attributes.js';
|
|
38
38
|
|
|
39
39
|
let {
|
|
@@ -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)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
<script lang="ts" module>
|
|
2
2
|
import type { CommonStyleProps, Without } from '../utils/types.js';
|
|
3
|
-
import type {
|
|
3
|
+
import type { Snippet } from 'svelte';
|
|
4
4
|
|
|
5
5
|
export type Point = { x: number; y: number; r: number; xValue: any; yValue: any; data: any };
|
|
6
6
|
type Offset = number | ((value: number, context: any) => number) | undefined;
|
|
@@ -37,13 +37,6 @@
|
|
|
37
37
|
*/
|
|
38
38
|
offsetY?: Offset;
|
|
39
39
|
|
|
40
|
-
/**
|
|
41
|
-
* Enable showing links between related points (array x/y accessors)
|
|
42
|
-
*
|
|
43
|
-
* @default false
|
|
44
|
-
*/
|
|
45
|
-
links?: boolean | Partial<ComponentProps<typeof Link>>;
|
|
46
|
-
|
|
47
40
|
children?: Snippet<[{ points: Point[] }]>;
|
|
48
41
|
} & CommonStyleProps;
|
|
49
42
|
|
|
@@ -71,7 +64,6 @@
|
|
|
71
64
|
r = 5,
|
|
72
65
|
offsetX,
|
|
73
66
|
offsetY,
|
|
74
|
-
links = false,
|
|
75
67
|
fill,
|
|
76
68
|
fillOpacity,
|
|
77
69
|
stroke,
|
|
@@ -136,66 +128,11 @@
|
|
|
136
128
|
return [];
|
|
137
129
|
}) as Point[]
|
|
138
130
|
);
|
|
139
|
-
|
|
140
|
-
const _links = $derived(
|
|
141
|
-
pointsData.flatMap((d: any) => {
|
|
142
|
-
const xValue = xAccessor(d);
|
|
143
|
-
const yValue = yAccessor(d);
|
|
144
|
-
|
|
145
|
-
if (Array.isArray(xValue)) {
|
|
146
|
-
/*
|
|
147
|
-
x={["prop1" ,"prop2"]}
|
|
148
|
-
y="prop3"
|
|
149
|
-
*/
|
|
150
|
-
const [xMin, xMax] = extent(ctx.xGet(d)) as unknown as [number, number];
|
|
151
|
-
const y = ctx.yGet(d) + getOffset(ctx.yGet(d), offsetY, ctx.yScale);
|
|
152
|
-
return {
|
|
153
|
-
source: {
|
|
154
|
-
x: xMin + getOffset(xMin, offsetX, ctx.xScale) + (ctx.config.r ? ctx.rGet(d) : r),
|
|
155
|
-
y,
|
|
156
|
-
},
|
|
157
|
-
target: {
|
|
158
|
-
x: xMax + getOffset(xMax, offsetX, ctx.xScale) - (ctx.config.r ? ctx.rGet(d) : r),
|
|
159
|
-
y: y,
|
|
160
|
-
},
|
|
161
|
-
data: d,
|
|
162
|
-
};
|
|
163
|
-
} else if (Array.isArray(yValue)) {
|
|
164
|
-
/*
|
|
165
|
-
x="prop1"
|
|
166
|
-
y={["prop2" ,"prop3"]}
|
|
167
|
-
*/
|
|
168
|
-
const x = ctx.xGet(d) + getOffset(ctx.xGet(d), offsetX, ctx.xScale);
|
|
169
|
-
const [yMin, yMax] = extent(ctx.yGet(d)) as unknown as [number, number];
|
|
170
|
-
return {
|
|
171
|
-
source: {
|
|
172
|
-
x: x,
|
|
173
|
-
y: yMin + getOffset(yMin, offsetY, ctx.yScale),
|
|
174
|
-
},
|
|
175
|
-
target: {
|
|
176
|
-
x: x,
|
|
177
|
-
y: yMax + getOffset(yMax, offsetY, ctx.yScale),
|
|
178
|
-
},
|
|
179
|
-
data: d,
|
|
180
|
-
};
|
|
181
|
-
}
|
|
182
|
-
})
|
|
183
|
-
);
|
|
184
131
|
</script>
|
|
185
132
|
|
|
186
133
|
{#if children}
|
|
187
134
|
{@render children({ points })}
|
|
188
135
|
{:else}
|
|
189
|
-
{#if links}
|
|
190
|
-
{#each _links as link}
|
|
191
|
-
<Link
|
|
192
|
-
data={link}
|
|
193
|
-
stroke={fill ?? (ctx.config.c ? ctx.cGet(link.data) : null)}
|
|
194
|
-
{...extractLayerProps(links, 'points-link')}
|
|
195
|
-
/>
|
|
196
|
-
{/each}
|
|
197
|
-
{/if}
|
|
198
|
-
|
|
199
136
|
{#each points as point}
|
|
200
137
|
<Circle
|
|
201
138
|
cx={point.x}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { CommonStyleProps, Without } from '../utils/types.js';
|
|
2
|
-
import type {
|
|
2
|
+
import type { Snippet } from 'svelte';
|
|
3
3
|
export type Point = {
|
|
4
4
|
x: number;
|
|
5
5
|
y: number;
|
|
@@ -36,19 +36,12 @@ export type PointsPropsWithoutHTML = {
|
|
|
36
36
|
* The offset of the point in the y direction
|
|
37
37
|
*/
|
|
38
38
|
offsetY?: Offset;
|
|
39
|
-
/**
|
|
40
|
-
* Enable showing links between related points (array x/y accessors)
|
|
41
|
-
*
|
|
42
|
-
* @default false
|
|
43
|
-
*/
|
|
44
|
-
links?: boolean | Partial<ComponentProps<typeof Link>>;
|
|
45
39
|
children?: Snippet<[{
|
|
46
40
|
points: Point[];
|
|
47
41
|
}]>;
|
|
48
42
|
} & CommonStyleProps;
|
|
49
43
|
export type PointsProps = PointsPropsWithoutHTML & Omit<Without<CircleProps, PointsPropsWithoutHTML>, 'ref'>;
|
|
50
44
|
import { type CircleProps } from './Circle.svelte';
|
|
51
|
-
import Link from './Link.svelte';
|
|
52
45
|
import { type Accessor } from '../utils/common.js';
|
|
53
46
|
declare const Points: import("svelte").Component<PointsProps, {}, "">;
|
|
54
47
|
type Points = ReturnType<typeof Points>;
|
|
@@ -3,6 +3,11 @@
|
|
|
3
3
|
import type { SVGAttributes } from 'svelte/elements';
|
|
4
4
|
|
|
5
5
|
export type BaseRulePropsWithoutHTML = {
|
|
6
|
+
/**
|
|
7
|
+
* Override the data from the context.
|
|
8
|
+
*/
|
|
9
|
+
data?: any;
|
|
10
|
+
|
|
6
11
|
/**
|
|
7
12
|
* Create a vertical `x` line
|
|
8
13
|
* - If true or 'left', will draw at chart left (xRange[0])
|
|
@@ -12,7 +17,7 @@
|
|
|
12
17
|
*
|
|
13
18
|
* @default false
|
|
14
19
|
*/
|
|
15
|
-
x?: number | Date | boolean | 'left' | 'right';
|
|
20
|
+
x?: number | Date | boolean | '$left' | '$right' | Accessor;
|
|
16
21
|
|
|
17
22
|
/**
|
|
18
23
|
* Pixel offset to apply to `x` coordinate
|
|
@@ -30,7 +35,7 @@
|
|
|
30
35
|
*
|
|
31
36
|
* @default false
|
|
32
37
|
*/
|
|
33
|
-
y?: number | Date | boolean | 'top' | 'bottom';
|
|
38
|
+
y?: number | Date | boolean | '$top' | '$bottom' | Accessor;
|
|
34
39
|
|
|
35
40
|
/**
|
|
36
41
|
* Pixel offset to apply to `y` coordinate
|
|
@@ -55,13 +60,17 @@
|
|
|
55
60
|
import Group from './Group.svelte';
|
|
56
61
|
import Line, { type LinePropsWithoutHTML } from './Line.svelte';
|
|
57
62
|
import { getChartContext } from './Chart.svelte';
|
|
63
|
+
import { accessor, chartDataArray, type Accessor } from '../utils/common.js';
|
|
58
64
|
import { layerClass } from '../utils/attributes.js';
|
|
65
|
+
import { isScaleBand, isScaleNumeric } from '../utils/scales.svelte.js';
|
|
59
66
|
|
|
60
67
|
let {
|
|
68
|
+
data: dataProp,
|
|
61
69
|
x = false,
|
|
62
70
|
xOffset = 0,
|
|
63
71
|
y = false,
|
|
64
72
|
yOffset = 0,
|
|
73
|
+
stroke: strokeProp,
|
|
65
74
|
class: className,
|
|
66
75
|
children,
|
|
67
76
|
...restProps
|
|
@@ -69,89 +78,163 @@
|
|
|
69
78
|
|
|
70
79
|
const ctx = getChartContext();
|
|
71
80
|
|
|
72
|
-
const
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
81
|
+
const data = $derived(chartDataArray(dataProp ?? ctx.data));
|
|
82
|
+
|
|
83
|
+
const singleX = $derived(
|
|
84
|
+
typeof x === 'number' ||
|
|
85
|
+
x instanceof Date ||
|
|
86
|
+
x === true ||
|
|
87
|
+
x === '$left' ||
|
|
88
|
+
x === '$right' ||
|
|
89
|
+
(isScaleBand(ctx.xScale) && ctx.xDomain.includes(x as any))
|
|
90
|
+
);
|
|
91
|
+
const singleY = $derived(
|
|
92
|
+
typeof y === 'number' ||
|
|
93
|
+
y instanceof Date ||
|
|
94
|
+
y === true ||
|
|
95
|
+
y === '$bottom' ||
|
|
96
|
+
y === '$top' ||
|
|
97
|
+
(isScaleBand(ctx.yScale) && ctx.yDomain.includes(y as any))
|
|
98
|
+
);
|
|
99
|
+
|
|
100
|
+
const xRangeMinMax = $derived(extent<number>(ctx.xRange));
|
|
101
|
+
const yRangeMinMax = $derived(extent<number>(ctx.yRange));
|
|
102
|
+
|
|
103
|
+
const lines = $derived.by(() => {
|
|
104
|
+
const result: {
|
|
105
|
+
x1: number;
|
|
106
|
+
y1: number;
|
|
107
|
+
x2: number;
|
|
108
|
+
y2: number;
|
|
109
|
+
axis: 'x' | 'y';
|
|
110
|
+
stroke?: string;
|
|
111
|
+
}[] = [];
|
|
112
|
+
|
|
113
|
+
// Single x line
|
|
114
|
+
if (singleX) {
|
|
115
|
+
const _x =
|
|
116
|
+
x === true || x === '$left'
|
|
117
|
+
? xRangeMinMax[0]!
|
|
118
|
+
: x === '$right'
|
|
119
|
+
? xRangeMinMax[1]!
|
|
120
|
+
: ctx.xScale(x) + xOffset;
|
|
121
|
+
|
|
122
|
+
result.push({
|
|
123
|
+
x1: _x,
|
|
124
|
+
y1: ctx.yRange[0] || 0,
|
|
125
|
+
x2: _x,
|
|
126
|
+
y2: ctx.yRange[1] || 0,
|
|
127
|
+
axis: 'x',
|
|
128
|
+
});
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
// Single y line
|
|
132
|
+
if (singleY) {
|
|
133
|
+
const _y =
|
|
134
|
+
y === true || y === '$bottom'
|
|
135
|
+
? yRangeMinMax[1]!
|
|
136
|
+
: y === '$top'
|
|
137
|
+
? yRangeMinMax[0]!
|
|
138
|
+
: ctx.yScale(y) + yOffset;
|
|
139
|
+
|
|
140
|
+
result.push({
|
|
141
|
+
x1: ctx.xRange[0] || 0,
|
|
142
|
+
y1: _y,
|
|
143
|
+
x2: ctx.xRange[1] || 0,
|
|
144
|
+
y2: _y,
|
|
145
|
+
axis: 'y',
|
|
146
|
+
});
|
|
87
147
|
}
|
|
88
|
-
|
|
148
|
+
|
|
149
|
+
// Data driven lines
|
|
150
|
+
if (!singleX && !singleY) {
|
|
151
|
+
const xAccessor = x !== false ? accessor(x as Accessor) : ctx.x;
|
|
152
|
+
const yAccessor = y !== false ? accessor(y as Accessor) : ctx.y;
|
|
153
|
+
|
|
154
|
+
const xBandOffset = isScaleBand(ctx.xScale) ? ctx.xScale.bandwidth() / 2 : 0;
|
|
155
|
+
const yBandOffset = isScaleBand(ctx.yScale) ? ctx.yScale.bandwidth() / 2 : 0;
|
|
156
|
+
|
|
157
|
+
for (const d of data) {
|
|
158
|
+
const xValue = xAccessor(d);
|
|
159
|
+
const yValue = yAccessor(d);
|
|
160
|
+
|
|
161
|
+
const x1Value = Array.isArray(xValue) ? xValue[0] : isScaleNumeric(ctx.xScale) ? 0 : xValue;
|
|
162
|
+
const x2Value = Array.isArray(xValue) ? xValue[1] : xValue;
|
|
163
|
+
const y1Value = Array.isArray(yValue) ? yValue[0] : isScaleNumeric(ctx.yScale) ? 0 : yValue;
|
|
164
|
+
const y2Value = Array.isArray(yValue) ? yValue[1] : yValue;
|
|
165
|
+
|
|
166
|
+
result.push({
|
|
167
|
+
x1: ctx.xScale(x1Value) + xBandOffset + xOffset,
|
|
168
|
+
y1: ctx.yScale(y1Value) + yBandOffset + yOffset,
|
|
169
|
+
x2: ctx.xScale(x2Value) + xBandOffset + xOffset,
|
|
170
|
+
y2: ctx.yScale(y2Value) + yBandOffset + yOffset,
|
|
171
|
+
axis: Array.isArray(yValue) || isScaleBand(ctx.xScale) ? 'x' : 'y', // TODO: what about single prop like lollipop?
|
|
172
|
+
stroke: (strokeProp ?? ctx.config.c) ? ctx.cGet(d) : null, // use color scale, if available
|
|
173
|
+
});
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
// Remove lines if out of range of chart (non-0 baseline, brushing, etc)
|
|
178
|
+
return result.filter((line) => {
|
|
179
|
+
return (
|
|
180
|
+
line.x1 >= xRangeMinMax[0]! &&
|
|
181
|
+
line.x2 <= xRangeMinMax[1]! &&
|
|
182
|
+
line.y1 >= yRangeMinMax[0]! &&
|
|
183
|
+
line.y2 <= yRangeMinMax[1]!
|
|
184
|
+
);
|
|
185
|
+
});
|
|
186
|
+
});
|
|
187
|
+
|
|
188
|
+
// $inspect({ lines });
|
|
89
189
|
</script>
|
|
90
190
|
|
|
91
191
|
<Group class={layerClass('rule-g')}>
|
|
92
|
-
{#
|
|
93
|
-
{@const
|
|
94
|
-
x === true || x === 'left'
|
|
95
|
-
? xRangeMinMax[0]
|
|
96
|
-
: x === 'right'
|
|
97
|
-
? xRangeMinMax[1]
|
|
98
|
-
: ctx.xScale(x) + xOffset}
|
|
192
|
+
{#each lines as line}
|
|
193
|
+
{@const stroke = line.stroke}
|
|
99
194
|
|
|
100
195
|
{#if ctx.radial}
|
|
101
|
-
{
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
196
|
+
{#if line.axis === 'x'}
|
|
197
|
+
{@const [x1, y1] = pointRadial(line.x1, line.y1)}
|
|
198
|
+
{@const [x2, y2] = pointRadial(line.x2, line.y2)}
|
|
199
|
+
<Line
|
|
200
|
+
{...restProps}
|
|
201
|
+
{x1}
|
|
202
|
+
{y1}
|
|
203
|
+
{x2}
|
|
204
|
+
{y2}
|
|
205
|
+
{stroke}
|
|
206
|
+
class={cls(
|
|
207
|
+
layerClass('rule-x-radial-line'),
|
|
208
|
+
!stroke && 'stroke-surface-content/10',
|
|
209
|
+
className
|
|
210
|
+
)}
|
|
211
|
+
/>
|
|
212
|
+
{:else if line.axis === 'y'}
|
|
213
|
+
<Circle
|
|
214
|
+
r={line.y1}
|
|
215
|
+
{stroke}
|
|
216
|
+
class={cls(
|
|
217
|
+
layerClass('rule-y-radial-circle'),
|
|
218
|
+
!stroke && 'stroke-surface-content/50',
|
|
219
|
+
'fill-none',
|
|
220
|
+
className
|
|
221
|
+
)}
|
|
222
|
+
/>
|
|
223
|
+
{/if}
|
|
112
224
|
{:else}
|
|
113
225
|
<Line
|
|
114
226
|
{...restProps}
|
|
115
|
-
x1={
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
y2={
|
|
119
|
-
|
|
120
|
-
/>
|
|
121
|
-
{/if}
|
|
122
|
-
{/if}
|
|
123
|
-
|
|
124
|
-
{#if showRule(y, 'y')}
|
|
125
|
-
{#if ctx.radial}
|
|
126
|
-
<Circle
|
|
127
|
-
r={y === true || y === 'bottom'
|
|
128
|
-
? yRangeMinMax[1]
|
|
129
|
-
: y === 'top'
|
|
130
|
-
? yRangeMinMax[0]
|
|
131
|
-
: ctx.yScale(y) + yOffset}
|
|
227
|
+
x1={line.x1}
|
|
228
|
+
y1={line.y1}
|
|
229
|
+
x2={line.x2}
|
|
230
|
+
y2={line.y2}
|
|
231
|
+
{stroke}
|
|
132
232
|
class={cls(
|
|
133
|
-
layerClass('rule-y-
|
|
134
|
-
'
|
|
233
|
+
layerClass(line.axis === 'x' ? 'rule-x-line' : 'rule-y-line'),
|
|
234
|
+
!stroke && 'stroke-surface-content/50',
|
|
135
235
|
className
|
|
136
236
|
)}
|
|
137
237
|
/>
|
|
138
|
-
{:else}
|
|
139
|
-
<Line
|
|
140
|
-
{...restProps}
|
|
141
|
-
x1={ctx.xRange[0] || 0}
|
|
142
|
-
x2={ctx.xRange[1] || 0}
|
|
143
|
-
y1={y === true || y === 'bottom'
|
|
144
|
-
? yRangeMinMax[1]
|
|
145
|
-
: y === 'top'
|
|
146
|
-
? yRangeMinMax[0]
|
|
147
|
-
: ctx.yScale(y) + yOffset}
|
|
148
|
-
y2={y === true || y === 'bottom'
|
|
149
|
-
? yRangeMinMax[1]
|
|
150
|
-
: y === 'top'
|
|
151
|
-
? yRangeMinMax[0]
|
|
152
|
-
: ctx.yScale(y) + yOffset}
|
|
153
|
-
class={cls(layerClass('rule-y-line'), 'stroke-surface-content/50', className)}
|
|
154
|
-
/>
|
|
155
238
|
{/if}
|
|
156
|
-
{/
|
|
239
|
+
{/each}
|
|
157
240
|
</Group>
|
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
import type { Without } from '../utils/types.js';
|
|
2
2
|
import type { SVGAttributes } from 'svelte/elements';
|
|
3
3
|
export type BaseRulePropsWithoutHTML = {
|
|
4
|
+
/**
|
|
5
|
+
* Override the data from the context.
|
|
6
|
+
*/
|
|
7
|
+
data?: any;
|
|
4
8
|
/**
|
|
5
9
|
* Create a vertical `x` line
|
|
6
10
|
* - If true or 'left', will draw at chart left (xRange[0])
|
|
@@ -10,7 +14,7 @@ export type BaseRulePropsWithoutHTML = {
|
|
|
10
14
|
*
|
|
11
15
|
* @default false
|
|
12
16
|
*/
|
|
13
|
-
x?: number | Date | boolean | 'left' | 'right';
|
|
17
|
+
x?: number | Date | boolean | '$left' | '$right' | Accessor;
|
|
14
18
|
/**
|
|
15
19
|
* Pixel offset to apply to `x` coordinate
|
|
16
20
|
*
|
|
@@ -26,7 +30,7 @@ export type BaseRulePropsWithoutHTML = {
|
|
|
26
30
|
*
|
|
27
31
|
* @default false
|
|
28
32
|
*/
|
|
29
|
-
y?: number | Date | boolean | 'top' | 'bottom';
|
|
33
|
+
y?: number | Date | boolean | '$top' | '$bottom' | Accessor;
|
|
30
34
|
/**
|
|
31
35
|
* Pixel offset to apply to `y` coordinate
|
|
32
36
|
* @default 0
|
|
@@ -36,6 +40,7 @@ export type BaseRulePropsWithoutHTML = {
|
|
|
36
40
|
export type RulePropsWithoutHTML = BaseRulePropsWithoutHTML & Without<Partial<LinePropsWithoutHTML>, BaseRulePropsWithoutHTML>;
|
|
37
41
|
export type RuleProps = RulePropsWithoutHTML & Without<SVGAttributes<SVGElement>, RulePropsWithoutHTML>;
|
|
38
42
|
import { type LinePropsWithoutHTML } from './Line.svelte';
|
|
43
|
+
import { type Accessor } from '../utils/common.js';
|
|
39
44
|
declare const Rule: import("svelte").Component<RuleProps, {}, "">;
|
|
40
45
|
type Rule = ReturnType<typeof Rule>;
|
|
41
46
|
export default Rule;
|
package/dist/utils/genData.d.ts
CHANGED
|
@@ -23,21 +23,21 @@ export declare function createSeries<TKey extends string>(options: {
|
|
|
23
23
|
}): ({
|
|
24
24
|
x: number;
|
|
25
25
|
} & { [K in TKey]: number; })[];
|
|
26
|
-
export declare function createDateSeries<TKey extends string>(options
|
|
26
|
+
export declare function createDateSeries<TKey extends string>(options?: {
|
|
27
27
|
count?: number;
|
|
28
|
-
min
|
|
29
|
-
max
|
|
28
|
+
min?: number;
|
|
29
|
+
max?: number;
|
|
30
30
|
keys?: TKey[];
|
|
31
31
|
value?: 'number' | 'integer';
|
|
32
32
|
}): ({
|
|
33
33
|
date: Date;
|
|
34
34
|
} & { [K in TKey]: number; })[];
|
|
35
|
-
export declare function createTimeSeries<TKey extends string>(options
|
|
35
|
+
export declare function createTimeSeries<TKey extends string>(options?: {
|
|
36
36
|
count?: number;
|
|
37
|
-
min
|
|
38
|
-
max
|
|
39
|
-
keys
|
|
40
|
-
value
|
|
37
|
+
min?: number;
|
|
38
|
+
max?: number;
|
|
39
|
+
keys?: TKey[];
|
|
40
|
+
value?: 'number' | 'integer';
|
|
41
41
|
}): ({
|
|
42
42
|
name: string;
|
|
43
43
|
startDate: Date;
|
package/dist/utils/genData.js
CHANGED
|
@@ -43,29 +43,31 @@ export function createSeries(options) {
|
|
|
43
43
|
};
|
|
44
44
|
});
|
|
45
45
|
}
|
|
46
|
-
export function createDateSeries(options) {
|
|
46
|
+
export function createDateSeries(options = {}) {
|
|
47
47
|
const now = timeDay.floor(new Date());
|
|
48
48
|
const count = options.count ?? 10;
|
|
49
|
-
const min = options.min;
|
|
50
|
-
const max = options.max;
|
|
49
|
+
const min = options.min ?? 0;
|
|
50
|
+
const max = options.max ?? 100;
|
|
51
51
|
const keys = options.keys ?? ['value'];
|
|
52
|
+
const valueType = options.value ?? 'number';
|
|
52
53
|
return Array.from({ length: count }).map((_, i) => {
|
|
53
54
|
return {
|
|
54
55
|
date: timeDay.offset(now, -count + i),
|
|
55
56
|
...Object.fromEntries(keys.map((key) => {
|
|
56
57
|
return [
|
|
57
58
|
key,
|
|
58
|
-
|
|
59
|
+
valueType === 'integer' ? getRandomInteger(min, max) : getRandomNumber(min, max),
|
|
59
60
|
];
|
|
60
61
|
})),
|
|
61
62
|
};
|
|
62
63
|
});
|
|
63
64
|
}
|
|
64
|
-
export function createTimeSeries(options) {
|
|
65
|
+
export function createTimeSeries(options = {}) {
|
|
65
66
|
const count = options.count ?? 10;
|
|
66
|
-
const min = options.min;
|
|
67
|
-
const max = options.max;
|
|
67
|
+
const min = options.min ?? 0;
|
|
68
|
+
const max = options.max ?? 100;
|
|
68
69
|
const keys = options.keys ?? ['value'];
|
|
70
|
+
const valueType = options.value ?? 'number';
|
|
69
71
|
let lastStartDate = timeDay.floor(new Date());
|
|
70
72
|
const timeSeries = Array.from({ length: count }).map((_, i) => {
|
|
71
73
|
const startDate = timeMinute.offset(lastStartDate, getRandomInteger(0, 60));
|
|
@@ -78,7 +80,7 @@ export function createTimeSeries(options) {
|
|
|
78
80
|
...Object.fromEntries(keys.map((key) => {
|
|
79
81
|
return [
|
|
80
82
|
key,
|
|
81
|
-
|
|
83
|
+
valueType === 'integer' ? getRandomInteger(min, max) : getRandomNumber(min, max),
|
|
82
84
|
];
|
|
83
85
|
})),
|
|
84
86
|
};
|
|
@@ -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 };
|
|
@@ -25,6 +25,7 @@ export type AnyScale<TInput extends SingleDomainType = any, TOutput extends Sing
|
|
|
25
25
|
};
|
|
26
26
|
export declare function isScaleBand(scale: AnyScale<any, any>): scale is ScaleBand<any>;
|
|
27
27
|
export declare function isScaleTime(scale: AnyScale<any, any>): scale is ScaleTime<any, any>;
|
|
28
|
+
export declare function isScaleNumeric(scale: AnyScale<any, any>): scale is ScaleTime<any, any>;
|
|
28
29
|
export declare function getRange(scale: any): any[];
|
|
29
30
|
export type SingleDomainType = number | string | Date | null | undefined;
|
|
30
31
|
export type DomainType = (number | string | Date | null | undefined)[] | null | undefined;
|
|
@@ -12,6 +12,10 @@ export function isScaleTime(scale) {
|
|
|
12
12
|
const domain = scale.domain();
|
|
13
13
|
return domain[0] instanceof Date || domain[1] instanceof Date;
|
|
14
14
|
}
|
|
15
|
+
export function isScaleNumeric(scale) {
|
|
16
|
+
const domain = scale.domain();
|
|
17
|
+
return typeof domain[0] === 'number' || typeof domain[1] === 'number';
|
|
18
|
+
}
|
|
15
19
|
export function getRange(scale) {
|
|
16
20
|
if (isAnyScale(scale)) {
|
|
17
21
|
return scale.range();
|
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.34",
|
|
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",
|