layerchart 2.0.0-next.25 → 2.0.0-next.26
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/utils/array.d.ts +10 -0
- package/dist/utils/array.js +17 -0
- package/dist/utils/array.test.d.ts +1 -0
- package/dist/utils/array.test.js +162 -0
- package/package.json +2 -2
package/dist/utils/array.d.ts
CHANGED
|
@@ -11,3 +11,13 @@ export declare function extent<T extends Numeric>(iterable: Parameters<typeof d3
|
|
|
11
11
|
* of making a set
|
|
12
12
|
*/
|
|
13
13
|
export declare function arraysEqual(arr1: unknown[], arr2: unknown[]): boolean;
|
|
14
|
+
/**
|
|
15
|
+
* Add `lanes` property to each element in the data array support densely packing.
|
|
16
|
+
* This is useful for visualizing overlapping events in a timeline / Gantt chart.
|
|
17
|
+
*/
|
|
18
|
+
export declare function applyLanes<T extends Record<string, any>>(data: T[], options?: {
|
|
19
|
+
start: keyof T;
|
|
20
|
+
end: keyof T;
|
|
21
|
+
}): (T & {
|
|
22
|
+
lane: number;
|
|
23
|
+
})[];
|
package/dist/utils/array.js
CHANGED
|
@@ -18,3 +18,20 @@ export function arraysEqual(arr1, arr2) {
|
|
|
18
18
|
return arr2.includes(k);
|
|
19
19
|
});
|
|
20
20
|
}
|
|
21
|
+
/**
|
|
22
|
+
* Add `lanes` property to each element in the data array support densely packing.
|
|
23
|
+
* This is useful for visualizing overlapping events in a timeline / Gantt chart.
|
|
24
|
+
*/
|
|
25
|
+
export function applyLanes(data, options = { start: 'start', end: 'end' }) {
|
|
26
|
+
const result = [];
|
|
27
|
+
let stack = [];
|
|
28
|
+
for (const d of data) {
|
|
29
|
+
let lane = stack.findIndex((s) => s[options.end] <= d[options.start] && s[options.start] < d[options.start]);
|
|
30
|
+
if (lane === -1) {
|
|
31
|
+
lane = stack.length;
|
|
32
|
+
}
|
|
33
|
+
result.push({ ...d, lane });
|
|
34
|
+
stack[lane] = d;
|
|
35
|
+
}
|
|
36
|
+
return result;
|
|
37
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest';
|
|
2
|
+
import { applyLanes } from './array.js';
|
|
3
|
+
describe('applyLanes', () => {
|
|
4
|
+
it('should assign same lane to non-overlapping events', () => {
|
|
5
|
+
const data = [
|
|
6
|
+
{ id: 1, start: new Date('2023-01-01'), end: new Date('2023-01-02') },
|
|
7
|
+
{ id: 2, start: new Date('2023-01-03'), end: new Date('2023-01-05') },
|
|
8
|
+
{ id: 3, start: new Date('2023-01-06'), end: new Date('2023-01-08') },
|
|
9
|
+
];
|
|
10
|
+
const result = applyLanes(data);
|
|
11
|
+
expect(result).toEqual([
|
|
12
|
+
{ id: 1, start: new Date('2023-01-01'), end: new Date('2023-01-02'), lane: 0 },
|
|
13
|
+
{ id: 2, start: new Date('2023-01-03'), end: new Date('2023-01-05'), lane: 0 },
|
|
14
|
+
{ id: 3, start: new Date('2023-01-06'), end: new Date('2023-01-08'), lane: 0 },
|
|
15
|
+
]);
|
|
16
|
+
});
|
|
17
|
+
it('should assign different lanes to overlapping events', () => {
|
|
18
|
+
const data = [
|
|
19
|
+
{ id: 1, start: new Date('2023-01-01'), end: new Date('2023-01-03') },
|
|
20
|
+
{ id: 2, start: new Date('2023-01-02'), end: new Date('2023-01-04') },
|
|
21
|
+
{ id: 3, start: new Date('2023-01-02T12:00:00'), end: new Date('2023-01-05') },
|
|
22
|
+
];
|
|
23
|
+
const result = applyLanes(data);
|
|
24
|
+
expect(result).toEqual([
|
|
25
|
+
{ id: 1, start: new Date('2023-01-01'), end: new Date('2023-01-03'), lane: 0 },
|
|
26
|
+
{ id: 2, start: new Date('2023-01-02'), end: new Date('2023-01-04'), lane: 1 },
|
|
27
|
+
{ id: 3, start: new Date('2023-01-02T12:00:00'), end: new Date('2023-01-05'), lane: 2 },
|
|
28
|
+
]);
|
|
29
|
+
});
|
|
30
|
+
it('should reuse lanes when events no longer overlap', () => {
|
|
31
|
+
const data = [
|
|
32
|
+
{ id: 1, start: new Date('2023-01-01'), end: new Date('2023-01-02') },
|
|
33
|
+
{ id: 2, start: new Date('2023-01-01T12:00:00'), end: new Date('2023-01-03') },
|
|
34
|
+
{ id: 3, start: new Date('2023-01-04'), end: new Date('2023-01-06') }, // starts after id: 1 ends
|
|
35
|
+
{ id: 4, start: new Date('2023-01-05'), end: new Date('2023-01-07') }, // starts after id: 2 ends
|
|
36
|
+
];
|
|
37
|
+
const result = applyLanes(data);
|
|
38
|
+
expect(result).toEqual([
|
|
39
|
+
{ id: 1, start: new Date('2023-01-01'), end: new Date('2023-01-02'), lane: 0 },
|
|
40
|
+
{ id: 2, start: new Date('2023-01-01T12:00:00'), end: new Date('2023-01-03'), lane: 1 },
|
|
41
|
+
{ id: 3, start: new Date('2023-01-04'), end: new Date('2023-01-06'), lane: 0 }, // reuses lane 0
|
|
42
|
+
{ id: 4, start: new Date('2023-01-05'), end: new Date('2023-01-07'), lane: 1 }, // reuses lane 1
|
|
43
|
+
]);
|
|
44
|
+
});
|
|
45
|
+
it('should handle events that start exactly when another ends', () => {
|
|
46
|
+
const data = [
|
|
47
|
+
{ id: 1, start: new Date('2023-01-01'), end: new Date('2023-01-02') },
|
|
48
|
+
{ id: 2, start: new Date('2023-01-02'), end: new Date('2023-01-04') }, // starts exactly when id: 1 ends
|
|
49
|
+
{ id: 3, start: new Date('2023-01-01T12:00:00'), end: new Date('2023-01-03') }, // overlaps with both
|
|
50
|
+
];
|
|
51
|
+
const result = applyLanes(data);
|
|
52
|
+
expect(result).toEqual([
|
|
53
|
+
{ id: 1, start: new Date('2023-01-01'), end: new Date('2023-01-02'), lane: 0 },
|
|
54
|
+
{ id: 2, start: new Date('2023-01-02'), end: new Date('2023-01-04'), lane: 0 }, // can reuse lane 0
|
|
55
|
+
{ id: 3, start: new Date('2023-01-01T12:00:00'), end: new Date('2023-01-03'), lane: 1 }, // overlaps, needs new lane
|
|
56
|
+
]);
|
|
57
|
+
});
|
|
58
|
+
it('should work with string keys for start and end', () => {
|
|
59
|
+
const data = [
|
|
60
|
+
{ name: 'Task 1', startTime: new Date('2023-01-01'), endTime: new Date('2023-01-03') },
|
|
61
|
+
{ name: 'Task 2', startTime: new Date('2023-01-02'), endTime: new Date('2023-01-04') },
|
|
62
|
+
];
|
|
63
|
+
const result = applyLanes(data, { start: 'startTime', end: 'endTime' });
|
|
64
|
+
expect(result).toEqual([
|
|
65
|
+
{
|
|
66
|
+
name: 'Task 1',
|
|
67
|
+
startTime: new Date('2023-01-01'),
|
|
68
|
+
endTime: new Date('2023-01-03'),
|
|
69
|
+
lane: 0,
|
|
70
|
+
},
|
|
71
|
+
{
|
|
72
|
+
name: 'Task 2',
|
|
73
|
+
startTime: new Date('2023-01-02'),
|
|
74
|
+
endTime: new Date('2023-01-04'),
|
|
75
|
+
lane: 1,
|
|
76
|
+
},
|
|
77
|
+
]);
|
|
78
|
+
});
|
|
79
|
+
it('should handle empty array', () => {
|
|
80
|
+
const data = [];
|
|
81
|
+
const result = applyLanes(data);
|
|
82
|
+
expect(result).toEqual([]);
|
|
83
|
+
});
|
|
84
|
+
it('should handle single event', () => {
|
|
85
|
+
const data = [{ id: 1, start: new Date('2023-01-01'), end: new Date('2023-01-02') }];
|
|
86
|
+
const result = applyLanes(data);
|
|
87
|
+
expect(result).toEqual([
|
|
88
|
+
{ id: 1, start: new Date('2023-01-01'), end: new Date('2023-01-02'), lane: 0 },
|
|
89
|
+
]);
|
|
90
|
+
});
|
|
91
|
+
it('should handle complex overlapping scenario', () => {
|
|
92
|
+
const data = [
|
|
93
|
+
{ id: 1, start: new Date('2023-01-01'), end: new Date('2023-01-05') }, // long event
|
|
94
|
+
{ id: 2, start: new Date('2023-01-02'), end: new Date('2023-01-03') }, // short event inside
|
|
95
|
+
{ id: 3, start: new Date('2023-01-02T12:00:00'), end: new Date('2023-01-04') }, // overlaps with both
|
|
96
|
+
{ id: 4, start: new Date('2023-01-03'), end: new Date('2023-01-04T12:00:00') }, // overlaps with 1 and 3
|
|
97
|
+
{ id: 5, start: new Date('2023-01-06'), end: new Date('2023-01-08') }, // separate event
|
|
98
|
+
];
|
|
99
|
+
const result = applyLanes(data);
|
|
100
|
+
expect(result).toEqual([
|
|
101
|
+
{ id: 1, start: new Date('2023-01-01'), end: new Date('2023-01-05'), lane: 0 },
|
|
102
|
+
{ id: 2, start: new Date('2023-01-02'), end: new Date('2023-01-03'), lane: 1 },
|
|
103
|
+
{ id: 3, start: new Date('2023-01-02T12:00:00'), end: new Date('2023-01-04'), lane: 2 },
|
|
104
|
+
{ id: 4, start: new Date('2023-01-03'), end: new Date('2023-01-04T12:00:00'), lane: 1 }, // can reuse lane 1 since id: 2 ended
|
|
105
|
+
{ id: 5, start: new Date('2023-01-06'), end: new Date('2023-01-08'), lane: 0 }, // can reuse lane 0 since id: 1 ended
|
|
106
|
+
]);
|
|
107
|
+
});
|
|
108
|
+
it('should preserve all original properties', () => {
|
|
109
|
+
const data = [
|
|
110
|
+
{
|
|
111
|
+
id: 1,
|
|
112
|
+
start: new Date('2023-01-01'),
|
|
113
|
+
end: new Date('2023-01-02'),
|
|
114
|
+
name: 'First',
|
|
115
|
+
priority: 'high',
|
|
116
|
+
metadata: { foo: 'bar' },
|
|
117
|
+
},
|
|
118
|
+
{
|
|
119
|
+
id: 2,
|
|
120
|
+
start: new Date('2023-01-01T12:00:00'),
|
|
121
|
+
end: new Date('2023-01-03'),
|
|
122
|
+
name: 'Second',
|
|
123
|
+
priority: 'low',
|
|
124
|
+
metadata: { baz: 'qux' },
|
|
125
|
+
},
|
|
126
|
+
];
|
|
127
|
+
const result = applyLanes(data);
|
|
128
|
+
expect(result).toEqual([
|
|
129
|
+
{
|
|
130
|
+
id: 1,
|
|
131
|
+
start: new Date('2023-01-01'),
|
|
132
|
+
end: new Date('2023-01-02'),
|
|
133
|
+
name: 'First',
|
|
134
|
+
priority: 'high',
|
|
135
|
+
metadata: { foo: 'bar' },
|
|
136
|
+
lane: 0,
|
|
137
|
+
},
|
|
138
|
+
{
|
|
139
|
+
id: 2,
|
|
140
|
+
start: new Date('2023-01-01T12:00:00'),
|
|
141
|
+
end: new Date('2023-01-03'),
|
|
142
|
+
name: 'Second',
|
|
143
|
+
priority: 'low',
|
|
144
|
+
metadata: { baz: 'qux' },
|
|
145
|
+
lane: 1,
|
|
146
|
+
},
|
|
147
|
+
]);
|
|
148
|
+
});
|
|
149
|
+
it('should work with numeric values', () => {
|
|
150
|
+
const data = [
|
|
151
|
+
{ id: 1, start: 0, end: 3 },
|
|
152
|
+
{ id: 2, start: 1, end: 4 },
|
|
153
|
+
{ id: 3, start: 5, end: 7 },
|
|
154
|
+
];
|
|
155
|
+
const result = applyLanes(data);
|
|
156
|
+
expect(result).toEqual([
|
|
157
|
+
{ id: 1, start: 0, end: 3, lane: 0 },
|
|
158
|
+
{ id: 2, start: 1, end: 4, lane: 1 },
|
|
159
|
+
{ id: 3, start: 5, end: 7, lane: 0 }, // can reuse lane 0 since id: 1 ended
|
|
160
|
+
]);
|
|
161
|
+
});
|
|
162
|
+
});
|
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.26",
|
|
8
8
|
"devDependencies": {
|
|
9
9
|
"@changesets/cli": "^2.29.4",
|
|
10
10
|
"@iconify-json/lucide": "^1.2.48",
|
|
@@ -56,7 +56,7 @@
|
|
|
56
56
|
"svelte": "5.34.1",
|
|
57
57
|
"svelte-check": "^4.2.1",
|
|
58
58
|
"svelte-json-tree": "^2.2.0",
|
|
59
|
-
"svelte-ux": "2.0.0-next.
|
|
59
|
+
"svelte-ux": "2.0.0-next.13",
|
|
60
60
|
"svelte2tsx": "^0.7.39",
|
|
61
61
|
"tailwindcss": "^4.1.10",
|
|
62
62
|
"topojson-client": "^3.1.0",
|