@mhkeller/layercake-annotations 0.1.0
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/README.md +184 -0
- package/dist/Annotations.svelte +15 -0
- package/dist/Annotations.svelte.d.ts +14 -0
- package/dist/Editor.svelte +213 -0
- package/dist/Editor.svelte.d.ts +19 -0
- package/dist/README.md +200 -0
- package/dist/Static.svelte +24 -0
- package/dist/Static.svelte.d.ts +12 -0
- package/dist/components/AnnotationEditor.svelte +127 -0
- package/dist/components/AnnotationEditor.svelte.d.ts +14 -0
- package/dist/components/AnnotationsData.svelte +56 -0
- package/dist/components/AnnotationsData.svelte.d.ts +15 -0
- package/dist/components/ArrowZone.svelte +366 -0
- package/dist/components/ArrowZone.svelte.d.ts +27 -0
- package/dist/components/ArrowheadMarker.svelte +18 -0
- package/dist/components/ArrowheadMarker.svelte.d.ts +14 -0
- package/dist/components/Arrows.svelte +175 -0
- package/dist/components/Arrows.svelte.d.ts +20 -0
- package/dist/components/Draggable.svelte +121 -0
- package/dist/components/Draggable.svelte.d.ts +33 -0
- package/dist/components/EditableText.svelte +131 -0
- package/dist/components/EditableText.svelte.d.ts +16 -0
- package/dist/components/ResizeHandles.svelte +146 -0
- package/dist/components/ResizeHandles.svelte.d.ts +21 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.js +11 -0
- package/dist/modules/arrowUtils.d.ts +17 -0
- package/dist/modules/arrowUtils.js +32 -0
- package/dist/modules/coordinates.d.ts +85 -0
- package/dist/modules/coordinates.js +139 -0
- package/dist/modules/createRef.svelte.d.ts +7 -0
- package/dist/modules/createRef.svelte.js +10 -0
- package/dist/modules/filterObject.d.ts +7 -0
- package/dist/modules/filterObject.js +14 -0
- package/dist/modules/invertScale.d.ts +1 -0
- package/dist/modules/invertScale.js +5 -0
- package/dist/modules/newAnnotation.d.ts +9 -0
- package/dist/modules/newAnnotation.js +26 -0
- package/dist/modules/ordinalInvert.d.ts +9 -0
- package/dist/modules/ordinalInvert.js +22 -0
- package/dist/types.d.ts +162 -0
- package/package.json +61 -0
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Create an SVG arc path between two points.
|
|
3
|
+
* Adapted from bizweekgraphics/swoopyarrows
|
|
4
|
+
*
|
|
5
|
+
* @param {{ x: number, y: number }} source - Start point
|
|
6
|
+
* @param {{ x: number, y: number }} target - End point
|
|
7
|
+
* @param {boolean|null} clockwise - Arc direction (null for straight line)
|
|
8
|
+
* @param {number} [angle=Math.PI/2] - Arc angle in radians
|
|
9
|
+
* @returns {string} SVG path string
|
|
10
|
+
*/
|
|
11
|
+
export function createArrowPath(source, target, clockwise, angle = Math.PI / 2) {
|
|
12
|
+
// Straight line if clockwise is null
|
|
13
|
+
if (clockwise === null) {
|
|
14
|
+
return `M ${source.x},${source.y} L ${target.x},${target.y}`;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
// Clamp angle to valid range
|
|
18
|
+
angle = Math.min(Math.max(angle, 1e-6), Math.PI - 1e-6);
|
|
19
|
+
|
|
20
|
+
// Calculate chord length between points
|
|
21
|
+
const dx = target.x - source.x;
|
|
22
|
+
const dy = target.y - source.y;
|
|
23
|
+
const chordLength = Math.sqrt(dx * dx + dy * dy);
|
|
24
|
+
|
|
25
|
+
// Calculate arc radius from chord length and angle
|
|
26
|
+
const distance = chordLength / (2 * Math.tan(angle / 2));
|
|
27
|
+
const radius = Math.sqrt(distance * distance + (chordLength / 2) * (chordLength / 2));
|
|
28
|
+
|
|
29
|
+
// SVG arc command: M x,y a rx,ry rotation large-arc-flag,sweep-flag dx,dy
|
|
30
|
+
return `M ${source.x},${source.y} a ${radius},${radius} 0 0,${clockwise ? '1' : '0'} ${dx},${dy}`;
|
|
31
|
+
}
|
|
32
|
+
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Calculate annotation box position in pixels.
|
|
3
|
+
*
|
|
4
|
+
* @param {Object} anno - Annotation object with x/y data values and dx/dy percentage offsets
|
|
5
|
+
* @param {Object} scales - Object containing LayerCake scales and accessors
|
|
6
|
+
* @param {Function} scales.xScale - X scale function
|
|
7
|
+
* @param {Function} scales.yScale - Y scale function
|
|
8
|
+
* @param {Function} scales.x - X accessor function
|
|
9
|
+
* @param {Function} scales.y - Y accessor function
|
|
10
|
+
* @param {number} scales.width - Chart width in pixels
|
|
11
|
+
* @param {number} scales.height - Chart height in pixels
|
|
12
|
+
* @returns {{ left: number, top: number, width: number }}
|
|
13
|
+
*/
|
|
14
|
+
export function getAnnotationBox(anno: any, scales: {
|
|
15
|
+
xScale: Function;
|
|
16
|
+
yScale: Function;
|
|
17
|
+
x: Function;
|
|
18
|
+
y: Function;
|
|
19
|
+
width: number;
|
|
20
|
+
height: number;
|
|
21
|
+
}): {
|
|
22
|
+
left: number;
|
|
23
|
+
top: number;
|
|
24
|
+
width: number;
|
|
25
|
+
};
|
|
26
|
+
/**
|
|
27
|
+
* Calculate arrow source position in pixels.
|
|
28
|
+
*
|
|
29
|
+
* @param {Object} anno - Annotation object
|
|
30
|
+
* @param {Object} arrow - Arrow object with side and source offset
|
|
31
|
+
* @param {Object} scales - LayerCake scales
|
|
32
|
+
* @param {number} [annoHeight] - Annotation height for vertical centering (optional)
|
|
33
|
+
* @returns {{ x: number, y: number }}
|
|
34
|
+
*/
|
|
35
|
+
export function getArrowSource(anno: any, arrow: any, scales: any, annoHeight?: number): {
|
|
36
|
+
x: number;
|
|
37
|
+
y: number;
|
|
38
|
+
};
|
|
39
|
+
/**
|
|
40
|
+
* Calculate arrow target position in pixels.
|
|
41
|
+
*
|
|
42
|
+
* @param {Object} arrow - Arrow object with target data values
|
|
43
|
+
* @param {Object} scales - LayerCake scales
|
|
44
|
+
* @returns {{ x: number, y: number }}
|
|
45
|
+
*/
|
|
46
|
+
export function getArrowTarget(arrow: any, scales: any): {
|
|
47
|
+
x: number;
|
|
48
|
+
y: number;
|
|
49
|
+
};
|
|
50
|
+
/**
|
|
51
|
+
* Calculate the source dx value to store when saving an arrow.
|
|
52
|
+
* Converts pixel position back to offset from annotation edge.
|
|
53
|
+
*
|
|
54
|
+
* @param {number} pixelX - Current X position in pixels
|
|
55
|
+
* @param {Object} anno - Annotation object
|
|
56
|
+
* @param {string} side - 'east' or 'west'
|
|
57
|
+
* @param {Object} scales - LayerCake scales
|
|
58
|
+
* @returns {number} - The dx offset to store
|
|
59
|
+
*/
|
|
60
|
+
export function calculateSourceDx(pixelX: number, anno: any, side: string, scales: any): number;
|
|
61
|
+
/**
|
|
62
|
+
* Calculate the source dy value to store when saving an arrow.
|
|
63
|
+
*
|
|
64
|
+
* @param {number} pixelY - Current Y position in pixels
|
|
65
|
+
* @param {Object} anno - Annotation object
|
|
66
|
+
* @param {Object} scales - LayerCake scales
|
|
67
|
+
* @returns {number} - The dy offset to store
|
|
68
|
+
*/
|
|
69
|
+
export function calculateSourceDy(pixelY: number, anno: any, scales: any): number;
|
|
70
|
+
/**
|
|
71
|
+
* Shared coordinate calculation utilities for annotations and arrows.
|
|
72
|
+
*
|
|
73
|
+
* Coordinate System:
|
|
74
|
+
* - Annotations are positioned in DATA SPACE (using xScale/yScale)
|
|
75
|
+
* - dx/dy offsets are PERCENTAGES of chart width/height
|
|
76
|
+
* - Arrow source dx/dy are PIXEL offsets from annotation edge
|
|
77
|
+
* - Arrow target is in DATA SPACE with optional percentage offsets
|
|
78
|
+
*
|
|
79
|
+
* For east arrows, source dx is relative to RIGHT edge of annotation.
|
|
80
|
+
* For west arrows, source dx is relative to LEFT edge of annotation.
|
|
81
|
+
*/
|
|
82
|
+
/** Default annotation width in pixels when not explicitly set */
|
|
83
|
+
export const DEFAULT_ANNOTATION_WIDTH: 155;
|
|
84
|
+
/** Default handle offset from annotation edge in pixels */
|
|
85
|
+
export const HANDLE_OFFSET_PX: 12;
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared coordinate calculation utilities for annotations and arrows.
|
|
3
|
+
*
|
|
4
|
+
* Coordinate System:
|
|
5
|
+
* - Annotations are positioned in DATA SPACE (using xScale/yScale)
|
|
6
|
+
* - dx/dy offsets are PERCENTAGES of chart width/height
|
|
7
|
+
* - Arrow source dx/dy are PIXEL offsets from annotation edge
|
|
8
|
+
* - Arrow target is in DATA SPACE with optional percentage offsets
|
|
9
|
+
*
|
|
10
|
+
* For east arrows, source dx is relative to RIGHT edge of annotation.
|
|
11
|
+
* For west arrows, source dx is relative to LEFT edge of annotation.
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
/** Default annotation width in pixels when not explicitly set */
|
|
15
|
+
export const DEFAULT_ANNOTATION_WIDTH = 155;
|
|
16
|
+
|
|
17
|
+
/** Default handle offset from annotation edge in pixels */
|
|
18
|
+
export const HANDLE_OFFSET_PX = 12;
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Calculate annotation box position in pixels.
|
|
22
|
+
*
|
|
23
|
+
* @param {Object} anno - Annotation object with x/y data values and dx/dy percentage offsets
|
|
24
|
+
* @param {Object} scales - Object containing LayerCake scales and accessors
|
|
25
|
+
* @param {Function} scales.xScale - X scale function
|
|
26
|
+
* @param {Function} scales.yScale - Y scale function
|
|
27
|
+
* @param {Function} scales.x - X accessor function
|
|
28
|
+
* @param {Function} scales.y - Y accessor function
|
|
29
|
+
* @param {number} scales.width - Chart width in pixels
|
|
30
|
+
* @param {number} scales.height - Chart height in pixels
|
|
31
|
+
* @returns {{ left: number, top: number, width: number }}
|
|
32
|
+
*/
|
|
33
|
+
export function getAnnotationBox(anno, scales) {
|
|
34
|
+
const { xScale, yScale, x, y, width, height } = scales;
|
|
35
|
+
|
|
36
|
+
// Convert percentage offsets to pixels
|
|
37
|
+
const offsetX = ((anno.dx ?? 0) / 100) * width;
|
|
38
|
+
const offsetY = ((anno.dy ?? 0) / 100) * height;
|
|
39
|
+
|
|
40
|
+
// Calculate top-left position
|
|
41
|
+
const left = xScale(x(anno)) + offsetX;
|
|
42
|
+
const top = yScale(y(anno)) + offsetY;
|
|
43
|
+
|
|
44
|
+
// Get width (stored as "150px" string or use default)
|
|
45
|
+
const annoWidth = anno.width ? parseInt(anno.width) : DEFAULT_ANNOTATION_WIDTH;
|
|
46
|
+
|
|
47
|
+
return { left, top, width: annoWidth };
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Calculate arrow source position in pixels.
|
|
52
|
+
*
|
|
53
|
+
* @param {Object} anno - Annotation object
|
|
54
|
+
* @param {Object} arrow - Arrow object with side and source offset
|
|
55
|
+
* @param {Object} scales - LayerCake scales
|
|
56
|
+
* @param {number} [annoHeight] - Annotation height for vertical centering (optional)
|
|
57
|
+
* @returns {{ x: number, y: number }}
|
|
58
|
+
*/
|
|
59
|
+
export function getArrowSource(anno, arrow, scales, annoHeight = 0) {
|
|
60
|
+
const box = getAnnotationBox(anno, scales);
|
|
61
|
+
|
|
62
|
+
// Default offsets when no arrow source is specified
|
|
63
|
+
const defaultDx = arrow.side === 'west' ? -HANDLE_OFFSET_PX : HANDLE_OFFSET_PX;
|
|
64
|
+
const defaultDy = annoHeight / 2;
|
|
65
|
+
|
|
66
|
+
const dx = arrow.source?.dx ?? defaultDx;
|
|
67
|
+
const dy = arrow.source?.dy ?? defaultDy;
|
|
68
|
+
|
|
69
|
+
let x;
|
|
70
|
+
if (arrow.side === 'east') {
|
|
71
|
+
// East arrow: offset from right edge
|
|
72
|
+
x = box.left + box.width + dx;
|
|
73
|
+
} else {
|
|
74
|
+
// West arrow: offset from left edge
|
|
75
|
+
x = box.left + dx;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
const y = box.top + dy;
|
|
79
|
+
|
|
80
|
+
return { x, y };
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Calculate arrow target position in pixels.
|
|
85
|
+
*
|
|
86
|
+
* @param {Object} arrow - Arrow object with target data values
|
|
87
|
+
* @param {Object} scales - LayerCake scales
|
|
88
|
+
* @returns {{ x: number, y: number }}
|
|
89
|
+
*/
|
|
90
|
+
export function getArrowTarget(arrow, scales) {
|
|
91
|
+
const { xScale, yScale, x, y, width, height } = scales;
|
|
92
|
+
|
|
93
|
+
// Target is in data space
|
|
94
|
+
const baseX = xScale(x(arrow.target));
|
|
95
|
+
const baseY = yScale(y(arrow.target));
|
|
96
|
+
|
|
97
|
+
// Add percentage offsets (used for ordinal scales)
|
|
98
|
+
const offsetX = ((arrow.target?.dx ?? 0) / 100) * width;
|
|
99
|
+
const offsetY = ((arrow.target?.dy ?? 0) / 100) * height;
|
|
100
|
+
|
|
101
|
+
return {
|
|
102
|
+
x: baseX + offsetX,
|
|
103
|
+
y: baseY + offsetY
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* Calculate the source dx value to store when saving an arrow.
|
|
109
|
+
* Converts pixel position back to offset from annotation edge.
|
|
110
|
+
*
|
|
111
|
+
* @param {number} pixelX - Current X position in pixels
|
|
112
|
+
* @param {Object} anno - Annotation object
|
|
113
|
+
* @param {string} side - 'east' or 'west'
|
|
114
|
+
* @param {Object} scales - LayerCake scales
|
|
115
|
+
* @returns {number} - The dx offset to store
|
|
116
|
+
*/
|
|
117
|
+
export function calculateSourceDx(pixelX, anno, side, scales) {
|
|
118
|
+
const box = getAnnotationBox(anno, scales);
|
|
119
|
+
|
|
120
|
+
if (side === 'east') {
|
|
121
|
+
// dx is offset from right edge
|
|
122
|
+
return pixelX - (box.left + box.width);
|
|
123
|
+
}
|
|
124
|
+
// dx is offset from left edge
|
|
125
|
+
return pixelX - box.left;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* Calculate the source dy value to store when saving an arrow.
|
|
130
|
+
*
|
|
131
|
+
* @param {number} pixelY - Current Y position in pixels
|
|
132
|
+
* @param {Object} anno - Annotation object
|
|
133
|
+
* @param {Object} scales - LayerCake scales
|
|
134
|
+
* @returns {number} - The dy offset to store
|
|
135
|
+
*/
|
|
136
|
+
export function calculateSourceDy(pixelY, anno, scales) {
|
|
137
|
+
const box = getAnnotationBox(anno, scales);
|
|
138
|
+
return pixelY - box.top;
|
|
139
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Create a state reference that can be passed between components.
|
|
3
|
+
* @template T
|
|
4
|
+
* @param {T} [data]
|
|
5
|
+
* @returns {import('../types.js').Ref<T>}
|
|
6
|
+
*/
|
|
7
|
+
export default function createRef(data) {
|
|
8
|
+
let state = $state({ value: data });
|
|
9
|
+
return state;
|
|
10
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Filters an object by a predicate function.
|
|
3
|
+
* @param {Object} obj - The object to filter.
|
|
4
|
+
* @param {Function} predicate - The predicate function.
|
|
5
|
+
* @returns {Object} - The filtered object.
|
|
6
|
+
*/
|
|
7
|
+
export default function filterObject(obj: any, predicate: Function): any;
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Filters an object by a predicate function.
|
|
3
|
+
* @param {Object} obj - The object to filter.
|
|
4
|
+
* @param {Function} predicate - The predicate function.
|
|
5
|
+
* @returns {Object} - The filtered object.
|
|
6
|
+
*/
|
|
7
|
+
export default function filterObject(obj, predicate) {
|
|
8
|
+
return Object.keys(obj).reduce((acc, key) => {
|
|
9
|
+
if (predicate(obj[key])) {
|
|
10
|
+
acc[key] = obj[key];
|
|
11
|
+
}
|
|
12
|
+
return acc;
|
|
13
|
+
}, {});
|
|
14
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export default function invertScale(scale: any, pos: any): any[];
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Create a new annotation at the click position
|
|
3
|
+
* @param {MouseEvent} e - Click event
|
|
4
|
+
* @param {number} id - Unique identifier
|
|
5
|
+
* @param {Object} options - LayerCake scales and config
|
|
6
|
+
* @returns {Annotation}
|
|
7
|
+
*/
|
|
8
|
+
export default function newAnnotation(e: MouseEvent, id: number, { xScale, yScale, config }: any): Annotation;
|
|
9
|
+
export type Annotation = import("../types.js").Annotation;
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/** @typedef {import('../types.js').Annotation} Annotation */
|
|
2
|
+
|
|
3
|
+
import invertScale from './invertScale.js';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Create a new annotation at the click position
|
|
7
|
+
* @param {MouseEvent} e - Click event
|
|
8
|
+
* @param {number} id - Unique identifier
|
|
9
|
+
* @param {Object} options - LayerCake scales and config
|
|
10
|
+
* @returns {Annotation}
|
|
11
|
+
*/
|
|
12
|
+
export default function newAnnotation(e, id, { xScale, yScale, config }) {
|
|
13
|
+
const xVal = invertScale(xScale, e.offsetX);
|
|
14
|
+
const yVal = invertScale(yScale, e.offsetY);
|
|
15
|
+
|
|
16
|
+
return {
|
|
17
|
+
id,
|
|
18
|
+
[config.x]: xVal[0],
|
|
19
|
+
[config.y]: yVal[0],
|
|
20
|
+
dx: xVal[1],
|
|
21
|
+
dy: yVal[1],
|
|
22
|
+
text: 'Enter your note here...',
|
|
23
|
+
width: '168px',
|
|
24
|
+
arrows: []
|
|
25
|
+
};
|
|
26
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Find the domain value at a given position.
|
|
3
|
+
* https://stackoverflow.com/questions/20758373/inversion-with-ordinal-scale/30743306#30743306
|
|
4
|
+
*
|
|
5
|
+
* @param {d3.scale} scale - The d3 scale to invert.
|
|
6
|
+
* @param {number} pos - The position to invert.
|
|
7
|
+
* @returns {Array} - The domain value and offset.
|
|
8
|
+
*/
|
|
9
|
+
export default function ordinalInvert(scale: d3.scale, pos: number): any[];
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Find the domain value at a given position.
|
|
3
|
+
* https://stackoverflow.com/questions/20758373/inversion-with-ordinal-scale/30743306#30743306
|
|
4
|
+
*
|
|
5
|
+
* @param {d3.scale} scale - The d3 scale to invert.
|
|
6
|
+
* @param {number} pos - The position to invert.
|
|
7
|
+
* @returns {Array} - The domain value and offset.
|
|
8
|
+
*/
|
|
9
|
+
export default function ordinalInvert(scale, pos) {
|
|
10
|
+
let previous = null;
|
|
11
|
+
let domain = scale.domain();
|
|
12
|
+
let offset = 0;
|
|
13
|
+
|
|
14
|
+
for (let dm of domain) {
|
|
15
|
+
if (scale(dm) > pos) {
|
|
16
|
+
return [previous, offset];
|
|
17
|
+
}
|
|
18
|
+
previous = dm;
|
|
19
|
+
offset = ((pos - scale(dm)) / Math.max(...scale.range())) * 100;
|
|
20
|
+
}
|
|
21
|
+
return [previous, offset];
|
|
22
|
+
}
|
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Type definitions for layercake-annotations
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Arrow source position (pixel offsets from annotation edge)
|
|
7
|
+
*/
|
|
8
|
+
export interface ArrowSource {
|
|
9
|
+
/** X offset in pixels from annotation edge (west=left, east=right) */
|
|
10
|
+
dx: number;
|
|
11
|
+
/** Y offset in pixels from annotation top */
|
|
12
|
+
dy: number;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Arrow target position (data space coordinates)
|
|
17
|
+
*/
|
|
18
|
+
export interface ArrowTarget {
|
|
19
|
+
/** X data value (key varies based on LayerCake config) */
|
|
20
|
+
[xKey: string]: unknown;
|
|
21
|
+
/** Y data value (key varies based on LayerCake config) */
|
|
22
|
+
/** Percentage offset for ordinal X scales (0-100) */
|
|
23
|
+
dx?: number;
|
|
24
|
+
/** Percentage offset for ordinal Y scales (0-100) */
|
|
25
|
+
dy?: number;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Arrow definition
|
|
30
|
+
*/
|
|
31
|
+
export interface Arrow {
|
|
32
|
+
/** Which side of annotation: 'west' or 'east' */
|
|
33
|
+
side: 'west' | 'east';
|
|
34
|
+
/** Arc direction: true=clockwise, false=counter-clockwise, null=straight line */
|
|
35
|
+
clockwise: boolean | null;
|
|
36
|
+
/** Source position (pixel offsets from annotation) */
|
|
37
|
+
source: ArrowSource;
|
|
38
|
+
/** Target position (data coordinates) */
|
|
39
|
+
target: ArrowTarget;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Annotation definition
|
|
44
|
+
*/
|
|
45
|
+
export interface Annotation {
|
|
46
|
+
/** Unique identifier */
|
|
47
|
+
id: number;
|
|
48
|
+
/** X data value (key varies based on LayerCake config, e.g., 'date' or 'x') */
|
|
49
|
+
[xKey: string]: unknown;
|
|
50
|
+
/** Y data value (key varies based on LayerCake config, e.g., 'value' or 'y') */
|
|
51
|
+
/** Percentage offset from data point in X direction */
|
|
52
|
+
dx: number;
|
|
53
|
+
/** Percentage offset from data point in Y direction */
|
|
54
|
+
dy: number;
|
|
55
|
+
/** Annotation text content */
|
|
56
|
+
text: string;
|
|
57
|
+
/** Width of annotation box (e.g., "150px") */
|
|
58
|
+
width?: string;
|
|
59
|
+
/** Text alignment: 'left', 'center', or 'right' */
|
|
60
|
+
align?: 'left' | 'center' | 'right';
|
|
61
|
+
/** Arrows attached to this annotation */
|
|
62
|
+
arrows: Arrow[];
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Hovering state for interactions
|
|
67
|
+
*/
|
|
68
|
+
export interface HoverState {
|
|
69
|
+
/** ID of the annotation being hovered */
|
|
70
|
+
annotationId: number;
|
|
71
|
+
/** What is being hovered: 'body' for annotation text, 'arrow' for arrow handles */
|
|
72
|
+
type: 'body' | 'arrow';
|
|
73
|
+
/** For arrow hovers: which side ('west' or 'east') */
|
|
74
|
+
side?: 'west' | 'east';
|
|
75
|
+
/** For arrow hovers: which handle ('source', 'target', or 'create') */
|
|
76
|
+
handle?: 'source' | 'target' | 'create';
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Drag state for live arrow preview during dragging
|
|
81
|
+
*/
|
|
82
|
+
export interface DragState {
|
|
83
|
+
/** ID of the annotation being edited */
|
|
84
|
+
annotationId: number;
|
|
85
|
+
/** Which arrow side is being dragged */
|
|
86
|
+
side: 'west' | 'east';
|
|
87
|
+
/** Current source X position in pixels */
|
|
88
|
+
sourceX: number;
|
|
89
|
+
/** Current source Y position in pixels */
|
|
90
|
+
sourceY: number;
|
|
91
|
+
/** Current target X position in pixels */
|
|
92
|
+
targetX: number;
|
|
93
|
+
/** Current target Y position in pixels */
|
|
94
|
+
targetY: number;
|
|
95
|
+
/** Arc direction */
|
|
96
|
+
clockwise: boolean | null;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* LayerCake scales object passed to coordinate utilities
|
|
101
|
+
*/
|
|
102
|
+
export interface LayerCakeScales {
|
|
103
|
+
/** X scale function (data -> pixels) */
|
|
104
|
+
xScale: (value: unknown) => number;
|
|
105
|
+
/** Y scale function (data -> pixels) */
|
|
106
|
+
yScale: (value: unknown) => number;
|
|
107
|
+
/** X accessor function */
|
|
108
|
+
x: (d: unknown) => unknown;
|
|
109
|
+
/** Y accessor function */
|
|
110
|
+
y: (d: unknown) => unknown;
|
|
111
|
+
/** Chart width in pixels */
|
|
112
|
+
width: number;
|
|
113
|
+
/** Chart height in pixels */
|
|
114
|
+
height: number;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* Annotation box position and dimensions
|
|
119
|
+
*/
|
|
120
|
+
export interface AnnotationBox {
|
|
121
|
+
/** Left edge position in pixels */
|
|
122
|
+
left: number;
|
|
123
|
+
/** Top edge position in pixels */
|
|
124
|
+
top: number;
|
|
125
|
+
/** Width in pixels */
|
|
126
|
+
width: number;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* Point coordinates
|
|
131
|
+
*/
|
|
132
|
+
export interface Point {
|
|
133
|
+
x: number;
|
|
134
|
+
y: number;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* Reactive reference wrapper (from createRef)
|
|
139
|
+
*/
|
|
140
|
+
export interface Ref<T> {
|
|
141
|
+
value: T;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/**
|
|
145
|
+
* Function to modify annotation properties
|
|
146
|
+
*/
|
|
147
|
+
export type ModifyAnnotationFn = (id: number, newProps: Partial<Annotation>) => void;
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* Function to create or update an arrow
|
|
151
|
+
*/
|
|
152
|
+
export type SetArrowFn = (id: number, arrow: Arrow) => void;
|
|
153
|
+
|
|
154
|
+
/**
|
|
155
|
+
* Function to modify arrow properties
|
|
156
|
+
*/
|
|
157
|
+
export type ModifyArrowFn = (id: number, side: 'west' | 'east', attrs: Partial<Arrow>) => void;
|
|
158
|
+
|
|
159
|
+
/**
|
|
160
|
+
* Function to save annotation config (provided by parent)
|
|
161
|
+
*/
|
|
162
|
+
export type SaveAnnotationConfigFn = (annotations: Annotation[]) => void;
|
package/package.json
ADDED
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@mhkeller/layercake-annotations",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"scripts": {
|
|
5
|
+
"dev": "vite dev",
|
|
6
|
+
"build": "vite build && pnpm package",
|
|
7
|
+
"preview": "vite preview",
|
|
8
|
+
"package": "svelte-kit sync && svelte-package -o dist && publint",
|
|
9
|
+
"prepublishOnly": "pnpm package",
|
|
10
|
+
"check": "svelte-kit sync && svelte-check --tsconfig ./jsconfig.json",
|
|
11
|
+
"check:watch": "svelte-kit sync && svelte-check --tsconfig ./jsconfig.json --watch",
|
|
12
|
+
"lint": "prettier --check .",
|
|
13
|
+
"format": "prettier --write .",
|
|
14
|
+
"test": "playwright test",
|
|
15
|
+
"test:ui": "playwright test --ui",
|
|
16
|
+
"test:update": "playwright test --update-snapshots"
|
|
17
|
+
},
|
|
18
|
+
"exports": {
|
|
19
|
+
".": {
|
|
20
|
+
"types": "./dist/index.d.ts",
|
|
21
|
+
"svelte": "./dist/index.js"
|
|
22
|
+
},
|
|
23
|
+
"./types": {
|
|
24
|
+
"types": "./dist/types.d.ts"
|
|
25
|
+
}
|
|
26
|
+
},
|
|
27
|
+
"files": [
|
|
28
|
+
"dist",
|
|
29
|
+
"!dist/**/*.test.*",
|
|
30
|
+
"!dist/**/*.spec.*"
|
|
31
|
+
],
|
|
32
|
+
"peerDependencies": {
|
|
33
|
+
"svelte": "^5.0.0"
|
|
34
|
+
},
|
|
35
|
+
"devDependencies": {
|
|
36
|
+
"@playwright/test": "^1.57.0",
|
|
37
|
+
"@rollup/plugin-dsv": "^3.0.4",
|
|
38
|
+
"@sveltejs/adapter-auto": "^3.0.0",
|
|
39
|
+
"@sveltejs/kit": "^2.0.0",
|
|
40
|
+
"@sveltejs/package": "^2.0.0",
|
|
41
|
+
"@sveltejs/vite-plugin-svelte": "^4.0.0-next.6",
|
|
42
|
+
"d3-scale": "^4.0.2",
|
|
43
|
+
"prettier": "^3.1.1",
|
|
44
|
+
"prettier-plugin-svelte": "^3.1.2",
|
|
45
|
+
"publint": "^0.2.0",
|
|
46
|
+
"svelte": "^5.0.0",
|
|
47
|
+
"svelte-check": "^4.0.0",
|
|
48
|
+
"typescript": "^5.0.0",
|
|
49
|
+
"vite": "^5.0.11"
|
|
50
|
+
},
|
|
51
|
+
"svelte": "./dist/index.js",
|
|
52
|
+
"types": "./dist/index.d.ts",
|
|
53
|
+
"type": "module",
|
|
54
|
+
"publishConfig": {
|
|
55
|
+
"access": "public"
|
|
56
|
+
},
|
|
57
|
+
"dependencies": {
|
|
58
|
+
"layercake": "^10.0.2",
|
|
59
|
+
"underscore": "^1.13.7"
|
|
60
|
+
}
|
|
61
|
+
}
|