layerchart 2.0.0-next.30 → 2.0.0-next.31
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 +22 -17
- package/dist/components/Axis.svelte.d.ts +2 -2
- package/dist/components/Bar.svelte +12 -8
- package/dist/components/Chart.svelte +37 -1
- package/dist/components/Chart.svelte.d.ts +11 -0
- package/dist/components/ForceSimulation.svelte +40 -24
- package/dist/components/ForceSimulation.svelte.d.ts +19 -11
- package/dist/components/Highlight.svelte +7 -4
- package/dist/components/charts/BarChart.svelte +18 -10
- package/dist/components/tooltip/TooltipContext.svelte +58 -9
- package/dist/utils/rect.svelte.d.ts +2 -2
- package/dist/utils/rect.svelte.js +69 -1
- package/dist/utils/ticks.d.ts +1 -1
- package/dist/utils/ticks.js +7 -2
- package/dist/utils/ticks.test.js +5 -0
- package/package.json +1 -1
|
@@ -44,7 +44,7 @@
|
|
|
44
44
|
ticks?: TicksConfig;
|
|
45
45
|
|
|
46
46
|
/**
|
|
47
|
-
* Width or height of each tick in
|
|
47
|
+
* Width or height of each tick in pixels (enabling responsive count)
|
|
48
48
|
*/
|
|
49
49
|
tickSpacing?: number;
|
|
50
50
|
|
|
@@ -96,7 +96,7 @@
|
|
|
96
96
|
transitionInParams?: TransitionParams<In>;
|
|
97
97
|
|
|
98
98
|
/**
|
|
99
|
-
*
|
|
99
|
+
* Override scale for the axis
|
|
100
100
|
*/
|
|
101
101
|
scale?: any;
|
|
102
102
|
|
|
@@ -183,6 +183,9 @@
|
|
|
183
183
|
const scale = $derived(
|
|
184
184
|
scaleProp ?? (['horizontal', 'angle'].includes(orientation) ? ctx.xScale : ctx.yScale)
|
|
185
185
|
);
|
|
186
|
+
const interval = $derived(
|
|
187
|
+
['horizontal', 'angle'].includes(orientation) ? ctx.xInterval : ctx.yInterval
|
|
188
|
+
);
|
|
186
189
|
|
|
187
190
|
const xRangeMinMax = $derived(extent<number>(ctx.xRange)) as [number, number];
|
|
188
191
|
const yRangeMinMax = $derived(extent<number>(ctx.yRange)) as [number, number];
|
|
@@ -206,7 +209,7 @@
|
|
|
206
209
|
? Math.round(ctxSize / tickSpacing)
|
|
207
210
|
: undefined
|
|
208
211
|
);
|
|
209
|
-
const tickVals = $derived(resolveTickVals(scale, ticks, tickCount));
|
|
212
|
+
const tickVals = $derived(resolveTickVals(scale, ticks, tickCount, interval));
|
|
210
213
|
const tickFormat = $derived(
|
|
211
214
|
resolveTickFormat({
|
|
212
215
|
scale,
|
|
@@ -221,27 +224,29 @@
|
|
|
221
224
|
function getCoords(tick: any) {
|
|
222
225
|
switch (placement) {
|
|
223
226
|
case 'top':
|
|
224
|
-
return {
|
|
225
|
-
x: scale(tick) + (isScaleBand(scale) ? scale.bandwidth() / 2 : 0),
|
|
226
|
-
y: yRangeMinMax[0],
|
|
227
|
-
};
|
|
228
|
-
|
|
229
227
|
case 'bottom':
|
|
230
228
|
return {
|
|
231
|
-
x:
|
|
232
|
-
|
|
229
|
+
x:
|
|
230
|
+
scale(tick) +
|
|
231
|
+
(isScaleBand(scale)
|
|
232
|
+
? scale.bandwidth() / 2
|
|
233
|
+
: ctx.xInterval
|
|
234
|
+
? (scale(ctx.xInterval.offset(tick)) - scale(tick)) / 2 // offset 1/2 width of time interval
|
|
235
|
+
: 0),
|
|
236
|
+
y: placement === 'top' ? yRangeMinMax[0] : yRangeMinMax[1],
|
|
233
237
|
};
|
|
234
238
|
|
|
235
239
|
case 'left':
|
|
236
|
-
return {
|
|
237
|
-
x: xRangeMinMax[0],
|
|
238
|
-
y: scale(tick) + (isScaleBand(scale) ? scale.bandwidth() / 2 : 0),
|
|
239
|
-
};
|
|
240
|
-
|
|
241
240
|
case 'right':
|
|
242
241
|
return {
|
|
243
|
-
x: xRangeMinMax[1],
|
|
244
|
-
y:
|
|
242
|
+
x: placement === 'left' ? xRangeMinMax[0] : xRangeMinMax[1],
|
|
243
|
+
y:
|
|
244
|
+
scale(tick) +
|
|
245
|
+
(isScaleBand(scale)
|
|
246
|
+
? scale.bandwidth() / 2
|
|
247
|
+
: ctx.yInterval
|
|
248
|
+
? (scale(ctx.yInterval.offset(tick)) - scale(tick)) / 2 // offset 1/2 height of time interval
|
|
249
|
+
: 0),
|
|
245
250
|
};
|
|
246
251
|
|
|
247
252
|
case 'angle':
|
|
@@ -37,7 +37,7 @@ export type AxisPropsWithoutHTML<In extends Transition = Transition> = {
|
|
|
37
37
|
*/
|
|
38
38
|
ticks?: TicksConfig;
|
|
39
39
|
/**
|
|
40
|
-
* Width or height of each tick in
|
|
40
|
+
* Width or height of each tick in pixels (enabling responsive count)
|
|
41
41
|
*/
|
|
42
42
|
tickSpacing?: number;
|
|
43
43
|
/**
|
|
@@ -83,7 +83,7 @@ export type AxisPropsWithoutHTML<In extends Transition = Transition> = {
|
|
|
83
83
|
*/
|
|
84
84
|
transitionInParams?: TransitionParams<In>;
|
|
85
85
|
/**
|
|
86
|
-
*
|
|
86
|
+
* Override scale for the axis
|
|
87
87
|
*/
|
|
88
88
|
scale?: any;
|
|
89
89
|
/**
|
|
@@ -81,7 +81,7 @@
|
|
|
81
81
|
import Rect from './Rect.svelte';
|
|
82
82
|
import Spline from './Spline.svelte';
|
|
83
83
|
|
|
84
|
-
import { isScaleBand } from '../utils/scales.svelte.js';
|
|
84
|
+
import { isScaleBand, isScaleTime } from '../utils/scales.svelte.js';
|
|
85
85
|
import { accessor, type Accessor } from '../utils/common.js';
|
|
86
86
|
import { getChartContext } from './Chart.svelte';
|
|
87
87
|
import type { CommonStyleProps, Without } from '../utils/types.js';
|
|
@@ -127,7 +127,7 @@
|
|
|
127
127
|
|
|
128
128
|
const dimensions = $derived(getDimensions(data) ?? { x: 0, y: 0, width: 0, height: 0 });
|
|
129
129
|
|
|
130
|
-
const isVertical = $derived(isScaleBand(ctx.xScale));
|
|
130
|
+
const isVertical = $derived(isScaleBand(ctx.xScale) || isScaleTime(ctx.xScale));
|
|
131
131
|
const valueAccessor = $derived(accessor(isVertical ? y : x));
|
|
132
132
|
const value = $derived(valueAccessor(data));
|
|
133
133
|
const resolvedValue = $derived(Array.isArray(value) ? greatestAbs(value) : value);
|
|
@@ -151,16 +151,20 @@
|
|
|
151
151
|
const bottomRight = $derived(['all', 'bottom', 'right', 'bottom-right'].includes(rounded));
|
|
152
152
|
const width = $derived(dimensions.width);
|
|
153
153
|
const height = $derived(dimensions.height);
|
|
154
|
-
|
|
154
|
+
|
|
155
|
+
// Clamp radius to prevent extending beyond bounding box
|
|
156
|
+
const r = $derived(Math.min(radius, width / 2, height / 2));
|
|
157
|
+
const diameter = $derived(2 * r);
|
|
158
|
+
|
|
155
159
|
const pathData = $derived(
|
|
156
|
-
`M${dimensions.x +
|
|
157
|
-
${topRight ? `a${
|
|
160
|
+
`M${dimensions.x + r},${dimensions.y} h${width - diameter}
|
|
161
|
+
${topRight ? `a${r},${r} 0 0 1 ${r},${r}` : `h${r}v${r}`}
|
|
158
162
|
v${height - diameter}
|
|
159
|
-
${bottomRight ? `a${
|
|
163
|
+
${bottomRight ? `a${r},${r} 0 0 1 ${-r},${r}` : `v${r}h${-r}`}
|
|
160
164
|
h${diameter - width}
|
|
161
|
-
${bottomLeft ? `a${
|
|
165
|
+
${bottomLeft ? `a${r},${r} 0 0 1 ${-r},${-r}` : `h${-r}v${-r}`}
|
|
162
166
|
v${diameter - height}
|
|
163
|
-
${topLeft ? `a${
|
|
167
|
+
${topLeft ? `a${r},${r} 0 0 1 ${r},${-r}` : `v${-r}h${r}`}
|
|
164
168
|
z`
|
|
165
169
|
.split('\n')
|
|
166
170
|
.join('')
|
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
createScale,
|
|
8
8
|
getRange,
|
|
9
9
|
isScaleBand,
|
|
10
|
+
isScaleTime,
|
|
10
11
|
makeAccessor,
|
|
11
12
|
type AnyScale,
|
|
12
13
|
type DomainType,
|
|
@@ -40,6 +41,7 @@
|
|
|
40
41
|
import TransformContext, { type TransformContextValue } from './TransformContext.svelte';
|
|
41
42
|
import BrushContext, { type BrushContextValue } from './BrushContext.svelte';
|
|
42
43
|
import { layerClass } from '../utils/attributes.js';
|
|
44
|
+
import type { TimeInterval } from 'd3-time';
|
|
43
45
|
|
|
44
46
|
const defaultPadding = { top: 0, right: 0, bottom: 0, left: 0 };
|
|
45
47
|
|
|
@@ -149,6 +151,8 @@
|
|
|
149
151
|
cGet: (d: T) => any;
|
|
150
152
|
x1Get: (d: T) => any;
|
|
151
153
|
y1Get: (d: T) => any;
|
|
154
|
+
xInterval: TimeInterval | null;
|
|
155
|
+
yInterval: TimeInterval | null;
|
|
152
156
|
radial: boolean;
|
|
153
157
|
tooltip: TooltipContextValue<T>;
|
|
154
158
|
geo: GeoContextValue;
|
|
@@ -640,6 +644,16 @@
|
|
|
640
644
|
*/
|
|
641
645
|
yBaseline?: number | null;
|
|
642
646
|
|
|
647
|
+
/**
|
|
648
|
+
* Time interval to use for the x-axis when using a time scale.
|
|
649
|
+
*/
|
|
650
|
+
xInterval?: TimeInterval | null;
|
|
651
|
+
|
|
652
|
+
/**
|
|
653
|
+
* Time interval to use for the y-axis when using a time scale.
|
|
654
|
+
*/
|
|
655
|
+
yInterval?: TimeInterval | null;
|
|
656
|
+
|
|
643
657
|
/* Props passed to ChartContext */
|
|
644
658
|
|
|
645
659
|
/**
|
|
@@ -738,6 +752,8 @@
|
|
|
738
752
|
rRange: rRangeProp,
|
|
739
753
|
xBaseline = null,
|
|
740
754
|
yBaseline = null,
|
|
755
|
+
xInterval = null,
|
|
756
|
+
yInterval = null,
|
|
741
757
|
meta = {},
|
|
742
758
|
children: _children,
|
|
743
759
|
radial = false,
|
|
@@ -780,6 +796,12 @@
|
|
|
780
796
|
|
|
781
797
|
const _xDomain: DomainType | undefined = $derived.by(() => {
|
|
782
798
|
if (xDomainProp !== undefined) return xDomainProp;
|
|
799
|
+
|
|
800
|
+
if (xInterval != null && Array.isArray(data) && data.length > 0) {
|
|
801
|
+
const lastXValue = accessor(xProp)(data[data.length - 1]);
|
|
802
|
+
return [null, xInterval.offset(lastXValue)];
|
|
803
|
+
}
|
|
804
|
+
|
|
783
805
|
if (xBaseline != null && Array.isArray(data)) {
|
|
784
806
|
const xValues = data.flatMap(accessor(xProp));
|
|
785
807
|
return [min([xBaseline, ...xValues]), max([xBaseline, ...xValues])];
|
|
@@ -788,6 +810,12 @@
|
|
|
788
810
|
|
|
789
811
|
const _yDomain: DomainType | undefined = $derived.by(() => {
|
|
790
812
|
if (yDomainProp !== undefined) return yDomainProp;
|
|
813
|
+
|
|
814
|
+
if (yInterval != null && Array.isArray(data) && data.length > 0) {
|
|
815
|
+
const lastYValue = accessor(yProp)(data[data.length - 1]);
|
|
816
|
+
return [null, yInterval.offset(lastYValue)];
|
|
817
|
+
}
|
|
818
|
+
|
|
791
819
|
if (yBaseline != null && Array.isArray(data)) {
|
|
792
820
|
const yValues = data.flatMap(accessor(yProp));
|
|
793
821
|
return [min([yBaseline, ...yValues]), max([yBaseline, ...yValues])];
|
|
@@ -798,7 +826,9 @@
|
|
|
798
826
|
_yRangeProp ?? (radial ? ({ height }: { height: number }) => [0, height / 2] : undefined)
|
|
799
827
|
);
|
|
800
828
|
|
|
801
|
-
const yReverse = $derived(
|
|
829
|
+
const yReverse = $derived(
|
|
830
|
+
yScaleProp ? !isScaleBand(yScaleProp) && !isScaleTime(yScaleProp) : true
|
|
831
|
+
);
|
|
802
832
|
|
|
803
833
|
const x = $derived(makeAccessor(xProp));
|
|
804
834
|
const y = $derived(makeAccessor(yProp));
|
|
@@ -1247,6 +1277,12 @@
|
|
|
1247
1277
|
get y1Scale() {
|
|
1248
1278
|
return y1Scale;
|
|
1249
1279
|
},
|
|
1280
|
+
get xInterval() {
|
|
1281
|
+
return xInterval;
|
|
1282
|
+
},
|
|
1283
|
+
get yInterval() {
|
|
1284
|
+
return yInterval;
|
|
1285
|
+
},
|
|
1250
1286
|
get radial() {
|
|
1251
1287
|
return radial;
|
|
1252
1288
|
},
|
|
@@ -8,6 +8,7 @@ import type { HierarchyNode } from 'd3-hierarchy';
|
|
|
8
8
|
import type { SankeyGraph } from 'd3-sankey';
|
|
9
9
|
import TransformContext, { type TransformContextValue } from './TransformContext.svelte';
|
|
10
10
|
import BrushContext, { type BrushContextValue } from './BrushContext.svelte';
|
|
11
|
+
import type { TimeInterval } from 'd3-time';
|
|
11
12
|
export type ChartResizeDetail = {
|
|
12
13
|
width: number;
|
|
13
14
|
height: number;
|
|
@@ -81,6 +82,8 @@ export type ChartContextValue<T = any, XScale extends AnyScale = AnyScale, YScal
|
|
|
81
82
|
cGet: (d: T) => any;
|
|
82
83
|
x1Get: (d: T) => any;
|
|
83
84
|
y1Get: (d: T) => any;
|
|
85
|
+
xInterval: TimeInterval | null;
|
|
86
|
+
yInterval: TimeInterval | null;
|
|
84
87
|
radial: boolean;
|
|
85
88
|
tooltip: TooltipContextValue<T>;
|
|
86
89
|
geo: GeoContextValue;
|
|
@@ -484,6 +487,14 @@ export type ChartPropsWithoutHTML<T, XScale extends AnyScale = AnyScale, YScale
|
|
|
484
487
|
* @default null
|
|
485
488
|
*/
|
|
486
489
|
yBaseline?: number | null;
|
|
490
|
+
/**
|
|
491
|
+
* Time interval to use for the x-axis when using a time scale.
|
|
492
|
+
*/
|
|
493
|
+
xInterval?: TimeInterval | null;
|
|
494
|
+
/**
|
|
495
|
+
* Time interval to use for the y-axis when using a time scale.
|
|
496
|
+
*/
|
|
497
|
+
yInterval?: TimeInterval | null;
|
|
487
498
|
/**
|
|
488
499
|
* Use radial instead of cartesian coordinates, mapping `x` to `angle` and `y`` to radial.
|
|
489
500
|
* Radial lines are positioned relative to the origin, use transform (ex. `<Group center>`)
|
|
@@ -31,7 +31,7 @@
|
|
|
31
31
|
> = {
|
|
32
32
|
alpha: number;
|
|
33
33
|
alphaTarget: number;
|
|
34
|
-
simulation:
|
|
34
|
+
simulation: Simulation<NodeDatum, LinkDatum>;
|
|
35
35
|
};
|
|
36
36
|
|
|
37
37
|
export type OnTickEvent<
|
|
@@ -40,9 +40,9 @@
|
|
|
40
40
|
> = {
|
|
41
41
|
alpha: number;
|
|
42
42
|
alphaTarget: number;
|
|
43
|
-
nodes:
|
|
44
|
-
links:
|
|
45
|
-
simulation:
|
|
43
|
+
nodes: NodeDatum[];
|
|
44
|
+
links: LinkDatum[];
|
|
45
|
+
simulation: Simulation<NodeDatum, LinkDatum>;
|
|
46
46
|
};
|
|
47
47
|
|
|
48
48
|
export type OnEndEvent<
|
|
@@ -51,7 +51,18 @@
|
|
|
51
51
|
> = {
|
|
52
52
|
alpha: number;
|
|
53
53
|
alphaTarget: number;
|
|
54
|
-
simulation:
|
|
54
|
+
simulation: Simulation<NodeDatum, LinkDatum>;
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
export type OnNodesChangeEvent<
|
|
58
|
+
NodeDatum extends SimulationNodeDatum,
|
|
59
|
+
LinkDatum extends SimulationLinkDatum<NodeDatum> | undefined,
|
|
60
|
+
> = {
|
|
61
|
+
alpha: number;
|
|
62
|
+
alphaTarget: number;
|
|
63
|
+
nodes: NodeDatum[];
|
|
64
|
+
links: LinkDatum[];
|
|
65
|
+
simulation: Simulation<NodeDatum, LinkDatum>;
|
|
55
66
|
};
|
|
56
67
|
|
|
57
68
|
/**
|
|
@@ -81,16 +92,6 @@
|
|
|
81
92
|
*/
|
|
82
93
|
export const DEFAULT_VELOCITY_DECAY: number = 0.4;
|
|
83
94
|
|
|
84
|
-
type NodeDatumFor<NodeDatum> = NodeDatum & SimulationNodeDatum;
|
|
85
|
-
|
|
86
|
-
type LinkDatumFor<NodeDatum, LinkDatum> = LinkDatum &
|
|
87
|
-
SimulationLinkDatum<NodeDatumFor<NodeDatum>>;
|
|
88
|
-
|
|
89
|
-
type SimulationFor<NodeDatum, LinkDatum> = Simulation<
|
|
90
|
-
NodeDatumFor<NodeDatum>,
|
|
91
|
-
LinkDatumFor<NodeDatum, LinkDatum>
|
|
92
|
-
>;
|
|
93
|
-
|
|
94
95
|
export type ForceSimulationProps<
|
|
95
96
|
NodeDatum extends SimulationNodeDatum,
|
|
96
97
|
LinkDatum extends SimulationLinkDatum<NodeDatum> | undefined,
|
|
@@ -159,6 +160,11 @@
|
|
|
159
160
|
*/
|
|
160
161
|
onStart?: (e: OnStartEvent<NodeDatum, LinkDatum | undefined>) => void;
|
|
161
162
|
|
|
163
|
+
/**
|
|
164
|
+
* Callback function triggered right before nodes get passed to the simulation
|
|
165
|
+
*/
|
|
166
|
+
onNodesChange?: (e: OnNodesChangeEvent<NodeDatum, LinkDatum | undefined>) => void;
|
|
167
|
+
|
|
162
168
|
/**
|
|
163
169
|
* Callback function triggered on each simulation tick
|
|
164
170
|
*/
|
|
@@ -172,10 +178,10 @@
|
|
|
172
178
|
children?: Snippet<
|
|
173
179
|
[
|
|
174
180
|
{
|
|
175
|
-
nodes:
|
|
176
|
-
links:
|
|
181
|
+
nodes: NodeDatum[];
|
|
182
|
+
links: LinkDatum[];
|
|
177
183
|
linkPositions: LinkPosition[];
|
|
178
|
-
simulation:
|
|
184
|
+
simulation: Simulation<NodeDatum, LinkDatum>;
|
|
179
185
|
},
|
|
180
186
|
]
|
|
181
187
|
>;
|
|
@@ -200,6 +206,7 @@
|
|
|
200
206
|
stopped = false,
|
|
201
207
|
static: staticProp,
|
|
202
208
|
onStart: onStartProp,
|
|
209
|
+
onNodesChange: onNodesChangeProp,
|
|
203
210
|
onTick: onTickProp,
|
|
204
211
|
onEnd: onEndProp,
|
|
205
212
|
children,
|
|
@@ -211,15 +218,13 @@
|
|
|
211
218
|
// MARK: Private Props
|
|
212
219
|
|
|
213
220
|
let linkPositions: LinkPosition[] = $state([]);
|
|
214
|
-
let simulatedNodes:
|
|
215
|
-
let simulatedLinks:
|
|
216
|
-
(data.links ?? []) as LinkDatumFor<NodeDatum, LinkDatum>[]
|
|
217
|
-
);
|
|
221
|
+
let simulatedNodes: NodeDatum[] = $state([]);
|
|
222
|
+
let simulatedLinks: LinkDatum[] = $derived(data.links ?? []);
|
|
218
223
|
|
|
219
224
|
// This casting is unfortunately necessary, due to unfortunate
|
|
220
225
|
// overloading choices made, over at `@typed/d3-force`:
|
|
221
|
-
const simulation:
|
|
222
|
-
forceSimulation() as
|
|
226
|
+
const simulation: Simulation<NodeDatum, LinkDatum> = (
|
|
227
|
+
forceSimulation<NodeDatum>() as Simulation<NodeDatum, LinkDatum>
|
|
223
228
|
).stop();
|
|
224
229
|
|
|
225
230
|
// d3.Simulation does not provide a `.forces()` getter, so we need to
|
|
@@ -263,6 +268,7 @@
|
|
|
263
268
|
() => {
|
|
264
269
|
// Any time the `nodes` prop, or the `data` store gets changed
|
|
265
270
|
// we pass them to the internal d3 simulation object:
|
|
271
|
+
onNodesChange();
|
|
266
272
|
pushNodesToSimulation(data.nodes);
|
|
267
273
|
runOrResumeSimulation();
|
|
268
274
|
}
|
|
@@ -498,6 +504,16 @@
|
|
|
498
504
|
});
|
|
499
505
|
}
|
|
500
506
|
|
|
507
|
+
function onNodesChange() {
|
|
508
|
+
onNodesChangeProp?.({
|
|
509
|
+
alpha,
|
|
510
|
+
alphaTarget,
|
|
511
|
+
nodes: data.nodes,
|
|
512
|
+
links: data.links ?? [],
|
|
513
|
+
simulation,
|
|
514
|
+
});
|
|
515
|
+
}
|
|
516
|
+
|
|
501
517
|
$effect(() => {
|
|
502
518
|
return () => {
|
|
503
519
|
simulation.stop();
|
|
@@ -14,19 +14,26 @@ export type LinkPosition = {
|
|
|
14
14
|
export type OnStartEvent<NodeDatum extends SimulationNodeDatum, LinkDatum extends SimulationLinkDatum<NodeDatum> | undefined> = {
|
|
15
15
|
alpha: number;
|
|
16
16
|
alphaTarget: number;
|
|
17
|
-
simulation:
|
|
17
|
+
simulation: Simulation<NodeDatum, LinkDatum>;
|
|
18
18
|
};
|
|
19
19
|
export type OnTickEvent<NodeDatum extends SimulationNodeDatum, LinkDatum extends SimulationLinkDatum<NodeDatum> | undefined> = {
|
|
20
20
|
alpha: number;
|
|
21
21
|
alphaTarget: number;
|
|
22
|
-
nodes:
|
|
23
|
-
links:
|
|
24
|
-
simulation:
|
|
22
|
+
nodes: NodeDatum[];
|
|
23
|
+
links: LinkDatum[];
|
|
24
|
+
simulation: Simulation<NodeDatum, LinkDatum>;
|
|
25
25
|
};
|
|
26
26
|
export type OnEndEvent<NodeDatum extends SimulationNodeDatum, LinkDatum extends SimulationLinkDatum<NodeDatum> | undefined> = {
|
|
27
27
|
alpha: number;
|
|
28
28
|
alphaTarget: number;
|
|
29
|
-
simulation:
|
|
29
|
+
simulation: Simulation<NodeDatum, LinkDatum>;
|
|
30
|
+
};
|
|
31
|
+
export type OnNodesChangeEvent<NodeDatum extends SimulationNodeDatum, LinkDatum extends SimulationLinkDatum<NodeDatum> | undefined> = {
|
|
32
|
+
alpha: number;
|
|
33
|
+
alphaTarget: number;
|
|
34
|
+
nodes: NodeDatum[];
|
|
35
|
+
links: LinkDatum[];
|
|
36
|
+
simulation: Simulation<NodeDatum, LinkDatum>;
|
|
30
37
|
};
|
|
31
38
|
/**
|
|
32
39
|
* Default initial alpha value of the simulation.
|
|
@@ -50,9 +57,6 @@ export declare const DEFAULT_ALPHA_MIN: number;
|
|
|
50
57
|
* Default velocity decay factor applied to nodes each tick.
|
|
51
58
|
*/
|
|
52
59
|
export declare const DEFAULT_VELOCITY_DECAY: number;
|
|
53
|
-
type NodeDatumFor<NodeDatum> = NodeDatum & SimulationNodeDatum;
|
|
54
|
-
type LinkDatumFor<NodeDatum, LinkDatum> = LinkDatum & SimulationLinkDatum<NodeDatumFor<NodeDatum>>;
|
|
55
|
-
type SimulationFor<NodeDatum, LinkDatum> = Simulation<NodeDatumFor<NodeDatum>, LinkDatumFor<NodeDatum, LinkDatum>>;
|
|
56
60
|
export type ForceSimulationProps<NodeDatum extends SimulationNodeDatum, LinkDatum extends SimulationLinkDatum<NodeDatum> | undefined> = {
|
|
57
61
|
/**
|
|
58
62
|
* Force simulation parameters
|
|
@@ -107,6 +111,10 @@ export type ForceSimulationProps<NodeDatum extends SimulationNodeDatum, LinkDatu
|
|
|
107
111
|
* Callback function triggered when simulation starts
|
|
108
112
|
*/
|
|
109
113
|
onStart?: (e: OnStartEvent<NodeDatum, LinkDatum | undefined>) => void;
|
|
114
|
+
/**
|
|
115
|
+
* Callback function triggered right before nodes get passed to the simulation
|
|
116
|
+
*/
|
|
117
|
+
onNodesChange?: (e: OnNodesChangeEvent<NodeDatum, LinkDatum | undefined>) => void;
|
|
110
118
|
/**
|
|
111
119
|
* Callback function triggered on each simulation tick
|
|
112
120
|
*/
|
|
@@ -117,10 +125,10 @@ export type ForceSimulationProps<NodeDatum extends SimulationNodeDatum, LinkDatu
|
|
|
117
125
|
onEnd?: (e: OnEndEvent<NodeDatum, LinkDatum | undefined>) => void;
|
|
118
126
|
children?: Snippet<[
|
|
119
127
|
{
|
|
120
|
-
nodes:
|
|
121
|
-
links:
|
|
128
|
+
nodes: NodeDatum[];
|
|
129
|
+
links: LinkDatum[];
|
|
122
130
|
linkPositions: LinkPosition[];
|
|
123
|
-
simulation:
|
|
131
|
+
simulation: Simulation<NodeDatum, LinkDatum>;
|
|
124
132
|
}
|
|
125
133
|
]>;
|
|
126
134
|
};
|
|
@@ -116,7 +116,7 @@
|
|
|
116
116
|
import { notNull } from '@layerstack/utils';
|
|
117
117
|
import { cls } from '@layerstack/tailwind';
|
|
118
118
|
|
|
119
|
-
import { isScaleBand } from '../utils/scales.svelte.js';
|
|
119
|
+
import { isScaleBand, isScaleTime } from '../utils/scales.svelte.js';
|
|
120
120
|
import { asAny } from '../utils/types.js';
|
|
121
121
|
import { getChartContext } from './Chart.svelte';
|
|
122
122
|
import { getTooltipContext } from './tooltip/TooltipContext.svelte';
|
|
@@ -158,7 +158,9 @@
|
|
|
158
158
|
Array.isArray(yValue) ? yValue.map((v) => ctx.yScale(v)) : ctx.yScale(yValue)
|
|
159
159
|
);
|
|
160
160
|
const yOffset = $derived(isScaleBand(ctx.yScale) && !ctx.radial ? ctx.yScale.bandwidth() / 2 : 0);
|
|
161
|
-
const axis = $derived(
|
|
161
|
+
const axis = $derived(
|
|
162
|
+
axisProp == null ? (isScaleBand(ctx.yScale) || isScaleTime(ctx.yScale) ? 'y' : 'x') : axisProp
|
|
163
|
+
);
|
|
162
164
|
|
|
163
165
|
const _lines: { x1: number; y1: number; x2: number; y2: number }[] = $derived.by(() => {
|
|
164
166
|
let tmpLines: { x1: number; y1: number; x2: number; y2: number }[] = [];
|
|
@@ -249,6 +251,7 @@
|
|
|
249
251
|
height: 0,
|
|
250
252
|
};
|
|
251
253
|
if (!highlightData) return tmpArea;
|
|
254
|
+
|
|
252
255
|
if (axis === 'x' || axis === 'both') {
|
|
253
256
|
// x area
|
|
254
257
|
if (Array.isArray(xCoord)) {
|
|
@@ -284,9 +287,9 @@
|
|
|
284
287
|
tmpArea.height = ctx.yScale.step();
|
|
285
288
|
} else {
|
|
286
289
|
// Find width to next data point
|
|
287
|
-
const index = ctx.flatData.findIndex((d) => Number(
|
|
290
|
+
const index = ctx.flatData.findIndex((d) => Number(y(d)) === Number(y(highlightData)));
|
|
288
291
|
const isLastPoint = index + 1 === ctx.flatData.length;
|
|
289
|
-
const nextDataPoint = isLastPoint ? max(ctx.yDomain) :
|
|
292
|
+
const nextDataPoint = isLastPoint ? max(ctx.yDomain) : y(ctx.flatData[index + 1]);
|
|
290
293
|
tmpArea.height = (ctx.yScale(nextDataPoint) ?? 0) - (yCoord ?? 0);
|
|
291
294
|
}
|
|
292
295
|
|
|
@@ -141,6 +141,8 @@
|
|
|
141
141
|
bandPadding = radial ? 0 : 0.4,
|
|
142
142
|
groupPadding = 0,
|
|
143
143
|
stackPadding = 0,
|
|
144
|
+
xInterval,
|
|
145
|
+
yInterval,
|
|
144
146
|
tooltip = true,
|
|
145
147
|
children: childrenProp,
|
|
146
148
|
aboveContext,
|
|
@@ -211,21 +213,25 @@
|
|
|
211
213
|
|
|
212
214
|
const xScale = $derived(
|
|
213
215
|
xScaleProp ??
|
|
214
|
-
(
|
|
215
|
-
?
|
|
216
|
-
:
|
|
217
|
-
?
|
|
218
|
-
:
|
|
216
|
+
(xInterval
|
|
217
|
+
? scaleTime()
|
|
218
|
+
: isVertical
|
|
219
|
+
? scaleBand().padding(bandPadding)
|
|
220
|
+
: accessor(xProp)(chartData[0]) instanceof Date // TODO: also check for Array<Date> instances (ex. x={['start', 'end']})
|
|
221
|
+
? scaleTime()
|
|
222
|
+
: scaleLinear())
|
|
219
223
|
);
|
|
220
224
|
const xBaseline = $derived(isVertical || isScaleTime(xScale) ? undefined : 0);
|
|
221
225
|
|
|
222
226
|
const yScale = $derived(
|
|
223
227
|
yScaleProp ??
|
|
224
|
-
(
|
|
225
|
-
?
|
|
226
|
-
|
|
227
|
-
:
|
|
228
|
-
|
|
228
|
+
(yInterval
|
|
229
|
+
? scaleTime()
|
|
230
|
+
: isVertical
|
|
231
|
+
? accessor(yProp)(chartData[0]) instanceof Date // TODO: also check for Array<Date> instances (ex. y={['start', 'end']})
|
|
232
|
+
? scaleTime()
|
|
233
|
+
: scaleLinear()
|
|
234
|
+
: scaleBand().padding(bandPadding))
|
|
229
235
|
);
|
|
230
236
|
const yBaseline = $derived(isVertical || isScaleTime(yScale) ? 0 : undefined);
|
|
231
237
|
|
|
@@ -436,6 +442,7 @@
|
|
|
436
442
|
{x1Scale}
|
|
437
443
|
{x1Domain}
|
|
438
444
|
{x1Range}
|
|
445
|
+
{xInterval}
|
|
439
446
|
y={resolveAccessor(yProp)}
|
|
440
447
|
{yScale}
|
|
441
448
|
{yBaseline}
|
|
@@ -443,6 +450,7 @@
|
|
|
443
450
|
{y1Scale}
|
|
444
451
|
{y1Domain}
|
|
445
452
|
{y1Range}
|
|
453
|
+
{yInterval}
|
|
446
454
|
c={isVertical ? yProp : xProp}
|
|
447
455
|
cRange={['var(--color-primary)']}
|
|
448
456
|
{radial}
|
|
@@ -135,7 +135,7 @@
|
|
|
135
135
|
import ChartClipPath from './../ChartClipPath.svelte';
|
|
136
136
|
import Voronoi from './../Voronoi.svelte';
|
|
137
137
|
|
|
138
|
-
import { isScaleBand, scaleInvert } from '../../utils/scales.svelte.js';
|
|
138
|
+
import { isScaleBand, isScaleTime, scaleInvert } from '../../utils/scales.svelte.js';
|
|
139
139
|
import { cartesianToPolar } from '../../utils/math.js';
|
|
140
140
|
import { quadtreeRects } from '../../utils/quadtree.js';
|
|
141
141
|
import { raise } from '../../utils/chart.js';
|
|
@@ -493,14 +493,63 @@
|
|
|
493
493
|
const fullHeight = max(ctx.yRange) - min(ctx.yRange);
|
|
494
494
|
|
|
495
495
|
if (mode === 'band') {
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
496
|
+
if (isScaleBand(ctx.xScale)) {
|
|
497
|
+
// full band width/height regardless of value
|
|
498
|
+
return {
|
|
499
|
+
x: x - xOffset,
|
|
500
|
+
y: min(ctx.yRange),
|
|
501
|
+
width: ctx.xScale.step(),
|
|
502
|
+
height: fullHeight,
|
|
503
|
+
data: d,
|
|
504
|
+
};
|
|
505
|
+
} else if (isScaleBand(ctx.yScale)) {
|
|
506
|
+
return {
|
|
507
|
+
x: min(ctx.xRange),
|
|
508
|
+
y: y - yOffset,
|
|
509
|
+
width: fullWidth,
|
|
510
|
+
height: ctx.yScale.step(),
|
|
511
|
+
data: d,
|
|
512
|
+
};
|
|
513
|
+
} else if (isScaleTime(ctx.xScale)) {
|
|
514
|
+
// Find width to next data point
|
|
515
|
+
const index = ctx.flatData.findIndex(
|
|
516
|
+
(d2) => Number(ctx.x(d2)) === Number(ctx.x(d))
|
|
517
|
+
);
|
|
518
|
+
const isLastPoint = index + 1 === ctx.flatData.length;
|
|
519
|
+
const nextDataPoint = isLastPoint
|
|
520
|
+
? max(ctx.xDomain)
|
|
521
|
+
: ctx.x(ctx.flatData[index + 1]);
|
|
522
|
+
|
|
523
|
+
return {
|
|
524
|
+
x: x - xOffset,
|
|
525
|
+
y: min(ctx.yRange),
|
|
526
|
+
width: (ctx.xScale(nextDataPoint) ?? 0) - (xValue ?? 0),
|
|
527
|
+
height: fullHeight,
|
|
528
|
+
data: d,
|
|
529
|
+
};
|
|
530
|
+
} else if (isScaleTime(ctx.yScale)) {
|
|
531
|
+
// Find height to next data point
|
|
532
|
+
const index = ctx.flatData.findIndex(
|
|
533
|
+
(d2) => Number(ctx.y(d2)) === Number(ctx.y(d))
|
|
534
|
+
);
|
|
535
|
+
const isLastPoint = index + 1 === ctx.flatData.length;
|
|
536
|
+
const nextDataPoint = isLastPoint
|
|
537
|
+
? max(ctx.yDomain)
|
|
538
|
+
: ctx.y(ctx.flatData[index + 1]);
|
|
539
|
+
|
|
540
|
+
return {
|
|
541
|
+
x: min(ctx.xRange),
|
|
542
|
+
y: y - yOffset,
|
|
543
|
+
width: fullWidth,
|
|
544
|
+
height: (ctx.yScale(nextDataPoint) ?? 0) - (yValue ?? 0),
|
|
545
|
+
data: d,
|
|
546
|
+
};
|
|
547
|
+
} else {
|
|
548
|
+
console.warn(
|
|
549
|
+
'[layerchart] TooltipContext band mode requires at least one scale to be band or time.'
|
|
550
|
+
);
|
|
551
|
+
return undefined;
|
|
552
|
+
}
|
|
504
553
|
} else if (mode === 'bounds') {
|
|
505
554
|
return {
|
|
506
555
|
x: isScaleBand(ctx.xScale) || Array.isArray(xValue) ? x - xOffset : min(ctx.xRange),
|
|
@@ -36,10 +36,10 @@ export declare function createDimensionGetter<TData>(ctx: ChartContextValue<TDat
|
|
|
36
36
|
y: any;
|
|
37
37
|
width: number;
|
|
38
38
|
height: number;
|
|
39
|
-
};
|
|
39
|
+
} | undefined;
|
|
40
40
|
/**
|
|
41
41
|
* If value is an array, returns first item, else returns original value
|
|
42
42
|
* Useful when x/y getters for band scale are an array (such as for histograms)
|
|
43
43
|
*/
|
|
44
|
-
export declare function firstValue(value: number | number[]): number;
|
|
44
|
+
export declare function firstValue(value: number | number[] | undefined): number | undefined;
|
|
45
45
|
export {};
|
|
@@ -59,7 +59,7 @@ export function createDimensionGetter(ctx, getOptions) {
|
|
|
59
59
|
const width = Math.max(0, ctx.xScale(right) - ctx.xScale(left) - insets.left - insets.right);
|
|
60
60
|
return { x, y, width, height };
|
|
61
61
|
}
|
|
62
|
-
else {
|
|
62
|
+
else if (isScaleBand(ctx.xScale)) {
|
|
63
63
|
// Vertical band or linear
|
|
64
64
|
const x = firstValue(ctx.xScale(_x(item))) + (ctx.x1Scale ? ctx.x1Scale(_x1(item)) : 0) + insets.left;
|
|
65
65
|
const width = Math.max(0, ctx.xScale.bandwidth
|
|
@@ -94,6 +94,74 @@ export function createDimensionGetter(ctx, getOptions) {
|
|
|
94
94
|
const height = ctx.yScale(bottom) - ctx.yScale(top) - insets.bottom - insets.top;
|
|
95
95
|
return { x, y, width, height };
|
|
96
96
|
}
|
|
97
|
+
else if (ctx.xInterval) {
|
|
98
|
+
// x-axis time scale with interval
|
|
99
|
+
const xValue = _x(item);
|
|
100
|
+
const start = ctx.xInterval.floor(xValue);
|
|
101
|
+
const end = ctx.xInterval.offset(start);
|
|
102
|
+
const x = ctx.xScale(start) + insets.left;
|
|
103
|
+
const width = ctx.xScale(end) - x - insets.right;
|
|
104
|
+
const yValue = _y(item);
|
|
105
|
+
let top = 0;
|
|
106
|
+
let bottom = 0;
|
|
107
|
+
if (Array.isArray(yValue)) {
|
|
108
|
+
// Array contains both top and bottom values (stack, etc);
|
|
109
|
+
top = max(yValue);
|
|
110
|
+
bottom = min(yValue);
|
|
111
|
+
}
|
|
112
|
+
else if (yValue == null) {
|
|
113
|
+
// null/undefined value
|
|
114
|
+
top = 0;
|
|
115
|
+
bottom = 0;
|
|
116
|
+
}
|
|
117
|
+
else if (yValue > 0) {
|
|
118
|
+
// Positive value
|
|
119
|
+
top = yValue;
|
|
120
|
+
bottom = max([0, yDomainMinMax[0]]);
|
|
121
|
+
}
|
|
122
|
+
else {
|
|
123
|
+
// Negative value
|
|
124
|
+
top = min([0, yDomainMinMax[1]]);
|
|
125
|
+
bottom = yValue;
|
|
126
|
+
}
|
|
127
|
+
const y = ctx.yScale(top) + insets.top;
|
|
128
|
+
const height = ctx.yScale(bottom) - ctx.yScale(top) - insets.bottom - insets.top;
|
|
129
|
+
return { x, y, width, height };
|
|
130
|
+
}
|
|
131
|
+
else if (ctx.yInterval) {
|
|
132
|
+
// y-axis time scale with interval
|
|
133
|
+
const yValue = _y(item);
|
|
134
|
+
const start = ctx.yInterval.floor(yValue);
|
|
135
|
+
const end = ctx.yInterval.offset(start);
|
|
136
|
+
const y = ctx.yScale(start) + insets.top;
|
|
137
|
+
const height = ctx.yScale(end) - y - insets.bottom;
|
|
138
|
+
const xValue = _x(item);
|
|
139
|
+
let left = 0;
|
|
140
|
+
let right = 0;
|
|
141
|
+
if (Array.isArray(xValue)) {
|
|
142
|
+
// Array contains both top and bottom values (stack, etc);
|
|
143
|
+
left = min(xValue);
|
|
144
|
+
right = max(xValue);
|
|
145
|
+
}
|
|
146
|
+
else if (xValue == null) {
|
|
147
|
+
// null/undefined value
|
|
148
|
+
left = 0;
|
|
149
|
+
right = 0;
|
|
150
|
+
}
|
|
151
|
+
else if (xValue > 0) {
|
|
152
|
+
// Positive value
|
|
153
|
+
left = max([0, xDomainMinMax[0]]);
|
|
154
|
+
right = xValue;
|
|
155
|
+
}
|
|
156
|
+
else {
|
|
157
|
+
// Negative value
|
|
158
|
+
left = xValue;
|
|
159
|
+
right = min([0, xDomainMinMax[1]]);
|
|
160
|
+
}
|
|
161
|
+
const x = ctx.xScale(left) + insets.left;
|
|
162
|
+
const width = ctx.xScale(right) - x - insets.right;
|
|
163
|
+
return { x, y, width, height };
|
|
164
|
+
}
|
|
97
165
|
};
|
|
98
166
|
}
|
|
99
167
|
/**
|
package/dist/utils/ticks.d.ts
CHANGED
|
@@ -9,7 +9,7 @@ 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 resolveTickVals(scale: AnyScale, ticks?: TicksConfig, count?: number): any[];
|
|
12
|
+
export declare function resolveTickVals(scale: AnyScale, ticks?: TicksConfig, count?: number, interval?: TimeInterval | null): any[];
|
|
13
13
|
export declare function resolveTickFormat(options: {
|
|
14
14
|
scale: AnyScale;
|
|
15
15
|
ticks?: TicksConfig;
|
package/dist/utils/ticks.js
CHANGED
|
@@ -110,7 +110,7 @@ export function getDurationFormat(duration, options = {
|
|
|
110
110
|
}
|
|
111
111
|
};
|
|
112
112
|
}
|
|
113
|
-
export function resolveTickVals(scale, ticks, count) {
|
|
113
|
+
export function resolveTickVals(scale, ticks, count, interval) {
|
|
114
114
|
// Explicit ticks
|
|
115
115
|
if (Array.isArray(ticks))
|
|
116
116
|
return ticks;
|
|
@@ -132,7 +132,12 @@ export function resolveTickVals(scale, ticks, count) {
|
|
|
132
132
|
}
|
|
133
133
|
// Ticks from scale
|
|
134
134
|
if (scale.ticks && typeof scale.ticks === 'function') {
|
|
135
|
-
|
|
135
|
+
const tickVals = scale.ticks(count ?? (typeof ticks === 'number' ? ticks : undefined));
|
|
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;
|
|
136
141
|
}
|
|
137
142
|
return [];
|
|
138
143
|
}
|
package/dist/utils/ticks.test.js
CHANGED
|
@@ -54,4 +54,9 @@ describe('resolveTickVals', () => {
|
|
|
54
54
|
expect(resolveTickVals(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
|
+
});
|
|
57
62
|
});
|
package/package.json
CHANGED