@mhkeller/layercake-annotations 0.3.1 → 0.4.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/dist/Editor.svelte +4 -2
- package/dist/README.md +12 -8
- package/dist/Static.svelte +4 -2
- package/dist/components/AnnotationEditor.svelte +15 -4
- package/dist/components/AnnotationsData.svelte +2 -2
- package/dist/components/ArrowZone.svelte +8 -4
- package/dist/components/ArrowheadMarker.svelte +3 -3
- package/dist/components/ArrowheadMarker.svelte.d.ts +2 -0
- package/dist/components/Arrows.svelte +4 -4
- package/dist/components/Arrows.svelte.d.ts +2 -0
- package/dist/modules/coordinates.js +6 -6
- package/dist/modules/newAnnotation.js +4 -2
- package/dist/types.d.ts +4 -6
- package/package.json +2 -2
package/dist/Editor.svelte
CHANGED
|
@@ -20,6 +20,8 @@
|
|
|
20
20
|
import createRef from './modules/createRef.svelte.js';
|
|
21
21
|
import newAnnotation from './modules/newAnnotation.js';
|
|
22
22
|
|
|
23
|
+
const markerId = $props.id();
|
|
24
|
+
|
|
23
25
|
/** @type {{ annotations?: Annotation[], containerClass?: string }} */
|
|
24
26
|
let { annotations: annos = $bindable([]), containerClass } = $props();
|
|
25
27
|
|
|
@@ -178,11 +180,11 @@
|
|
|
178
180
|
</script>
|
|
179
181
|
|
|
180
182
|
{#snippet defs()}
|
|
181
|
-
<ArrowheadMarker />
|
|
183
|
+
<ArrowheadMarker {markerId} />
|
|
182
184
|
{/snippet}
|
|
183
185
|
|
|
184
186
|
<Svg {defs}>
|
|
185
|
-
<Arrows annotations={annos} />
|
|
187
|
+
<Arrows annotations={annos} {markerId} />
|
|
186
188
|
</Svg>
|
|
187
189
|
|
|
188
190
|
<Html>
|
package/dist/README.md
CHANGED
|
@@ -38,8 +38,10 @@ src/lib/
|
|
|
38
38
|
```typescript
|
|
39
39
|
{
|
|
40
40
|
id: number, // Unique identifier
|
|
41
|
-
|
|
42
|
-
|
|
41
|
+
data: { // User data values (nested to avoid key collisions)
|
|
42
|
+
[xKey]: any, // X data value (key from LayerCake config)
|
|
43
|
+
[yKey]: any // Y data value (key from LayerCake config)
|
|
44
|
+
},
|
|
43
45
|
dx: number, // X offset: -100 to 100 (percentage of chart width)
|
|
44
46
|
dy: number, // Y offset: -100 to 100 (percentage of chart height)
|
|
45
47
|
text: string, // Annotation text (supports line breaks)
|
|
@@ -62,8 +64,10 @@ src/lib/
|
|
|
62
64
|
dy: number // Pixels from annotation vertical center
|
|
63
65
|
},
|
|
64
66
|
target: {
|
|
65
|
-
|
|
66
|
-
|
|
67
|
+
data: { // User data values (nested to avoid key collisions)
|
|
68
|
+
[xKey]: any, // X data value (same accessor as LayerCake)
|
|
69
|
+
[yKey]: any // Y data value
|
|
70
|
+
},
|
|
67
71
|
dx?: number, // 0-100: % offset within ordinal band (X)
|
|
68
72
|
dy?: number // 0-100: % offset within ordinal band (Y)
|
|
69
73
|
}
|
|
@@ -77,13 +81,13 @@ The library uses multiple coordinate systems:
|
|
|
77
81
|
### Annotation Position
|
|
78
82
|
|
|
79
83
|
```
|
|
80
|
-
Final position = scale(
|
|
84
|
+
Final position = scale(data[key]) + (dx/100 × chartDimension)
|
|
81
85
|
```
|
|
82
86
|
|
|
83
87
|
| Property | Unit | Range | Example |
|
|
84
88
|
|----------|------|-------|---------|
|
|
85
|
-
| `[xKey]` | Data value | Depends on data | `new Date('2024-01-15')` |
|
|
86
|
-
| `[yKey]` | Data value | Depends on data | `42` |
|
|
89
|
+
| `data.[xKey]` | Data value | Depends on data | `new Date('2024-01-15')` |
|
|
90
|
+
| `data.[yKey]` | Data value | Depends on data | `42` |
|
|
87
91
|
| `dx` | % of chart width | -100 to 100 | `5` = 5% right |
|
|
88
92
|
| `dy` | % of chart height | -100 to 100 | `-10` = 10% up |
|
|
89
93
|
|
|
@@ -100,7 +104,7 @@ Pixels relative to annotation box edge:
|
|
|
100
104
|
|
|
101
105
|
| Property | Unit | When used |
|
|
102
106
|
|----------|------|-----------|
|
|
103
|
-
| `[xKey]`, `[yKey]` | Data values | Always (same keys as LayerCake config) |
|
|
107
|
+
| `data.[xKey]`, `data.[yKey]` | Data values | Always (same keys as LayerCake config) |
|
|
104
108
|
| `dx`, `dy` | % within band (0-100) | Only for ordinal scales |
|
|
105
109
|
|
|
106
110
|
## Keyboard Shortcuts
|
package/dist/Static.svelte
CHANGED
|
@@ -9,14 +9,16 @@
|
|
|
9
9
|
|
|
10
10
|
/** @type {{ annotations?: Annotation[] }} */
|
|
11
11
|
let { annotations = [] } = $props();
|
|
12
|
+
|
|
13
|
+
const markerId = $props.id();
|
|
12
14
|
</script>
|
|
13
15
|
|
|
14
16
|
{#snippet defs()}
|
|
15
|
-
<ArrowheadMarker />
|
|
17
|
+
<ArrowheadMarker {markerId} />
|
|
16
18
|
{/snippet}
|
|
17
19
|
|
|
18
20
|
<Svg {defs} pointerEvents={false}>
|
|
19
|
-
<Arrows {annotations} />
|
|
21
|
+
<Arrows {annotations} {markerId} />
|
|
20
22
|
</Svg>
|
|
21
23
|
|
|
22
24
|
<Html pointerEvents={false}>
|
|
@@ -41,8 +41,8 @@
|
|
|
41
41
|
/**
|
|
42
42
|
* Coordinates
|
|
43
43
|
*/
|
|
44
|
-
let left = $derived(`calc(${$xGet(d)}${units} + ${d.dx}%)`);
|
|
45
|
-
let top = $derived(`calc(${$yGet(d)}${units} + ${d.dy}%)`);
|
|
44
|
+
let left = $derived(`calc(${$xGet(d.data)}${units} + ${d.dx}%)`);
|
|
45
|
+
let top = $derived(`calc(${$yGet(d.data)}${units} + ${d.dy}%)`);
|
|
46
46
|
|
|
47
47
|
/**
|
|
48
48
|
* @param {Array} [position] - The x and y pixel coordinates of the draggable element.
|
|
@@ -52,10 +52,21 @@
|
|
|
52
52
|
const xVal = x ? invertScale($xScale, x) : [];
|
|
53
53
|
const yVal = y ? invertScale($yScale, y) : [];
|
|
54
54
|
|
|
55
|
-
|
|
55
|
+
// Build data object, preserving existing values and overlaying new ones
|
|
56
|
+
const newData = filterObject(
|
|
56
57
|
{
|
|
58
|
+
...d.data,
|
|
57
59
|
[$config.x]: xVal[0],
|
|
58
|
-
[$config.y]: yVal[0]
|
|
60
|
+
[$config.y]: yVal[0]
|
|
61
|
+
},
|
|
62
|
+
(d) => d !== undefined
|
|
63
|
+
);
|
|
64
|
+
|
|
65
|
+
/** @type {Record<string, unknown>} */
|
|
66
|
+
const newProps = filterObject(
|
|
67
|
+
{
|
|
68
|
+
// Only include data if it has values (avoid overwriting with empty object)
|
|
69
|
+
data: Object.keys(newData).length > 0 ? newData : undefined,
|
|
59
70
|
dx: xVal[1],
|
|
60
71
|
dy: yVal[1]
|
|
61
72
|
},
|
|
@@ -21,8 +21,8 @@
|
|
|
21
21
|
<div
|
|
22
22
|
class="static-wrapper"
|
|
23
23
|
data-id={i}
|
|
24
|
-
style:left={`calc(${$xGet(d)}${units} + ${d.dx || 0}%)`}
|
|
25
|
-
style:top={`calc(${$yGet(d)}${units} + ${d.dy || 0}%)`}
|
|
24
|
+
style:left={`calc(${$xGet(d.data)}${units} + ${d.dx || 0}%)`}
|
|
25
|
+
style:top={`calc(${$yGet(d.data)}${units} + ${d.dy || 0}%)`}
|
|
26
26
|
style:width={d.width}
|
|
27
27
|
>
|
|
28
28
|
<div class="layercake-annotation {d.class || ''}" style={d.style} style:text-align={d.align || 'left'}>
|
|
@@ -217,8 +217,10 @@
|
|
|
217
217
|
clockwise,
|
|
218
218
|
source: { dx: newSourceDx, dy: newSourceDy },
|
|
219
219
|
target: {
|
|
220
|
-
|
|
221
|
-
|
|
220
|
+
data: {
|
|
221
|
+
[$config.x]: targetDataX,
|
|
222
|
+
[$config.y]: targetDataY
|
|
223
|
+
},
|
|
222
224
|
dx: targetOffsetX,
|
|
223
225
|
dy: targetOffsetY
|
|
224
226
|
}
|
|
@@ -242,8 +244,10 @@
|
|
|
242
244
|
dy: arrow?.source?.dy ?? defaultSourceDy
|
|
243
245
|
},
|
|
244
246
|
target: {
|
|
245
|
-
|
|
246
|
-
|
|
247
|
+
data: {
|
|
248
|
+
[$config.x]: targetDataX,
|
|
249
|
+
[$config.y]: targetDataY
|
|
250
|
+
},
|
|
247
251
|
dx: targetOffsetX,
|
|
248
252
|
dy: targetOffsetY
|
|
249
253
|
}
|
|
@@ -3,12 +3,12 @@
|
|
|
3
3
|
Generates an SVG marker containing a marker for a triangle makes a nice arrowhead. Add it to the named slot called "defs" on the SVG layout component.
|
|
4
4
|
-->
|
|
5
5
|
<script>
|
|
6
|
-
/** @type {{ fill?: string, stroke?: string }} */
|
|
7
|
-
let { fill = '#000', stroke = '#000' } = $props();
|
|
6
|
+
/** @type {{ markerId: string, fill?: string, stroke?: string }} */
|
|
7
|
+
let { markerId, fill = '#000', stroke = '#000' } = $props();
|
|
8
8
|
</script>
|
|
9
9
|
|
|
10
10
|
<marker
|
|
11
|
-
id=
|
|
11
|
+
id={markerId}
|
|
12
12
|
viewBox="-10 -10 20 20"
|
|
13
13
|
markerWidth="17"
|
|
14
14
|
markerHeight="17"
|
|
@@ -5,10 +5,12 @@ type ArrowheadMarker = {
|
|
|
5
5
|
};
|
|
6
6
|
/** Generates an SVG marker containing a marker for a triangle makes a nice arrowhead. Add it to the named slot called "defs" on the SVG layout component. */
|
|
7
7
|
declare const ArrowheadMarker: import("svelte").Component<{
|
|
8
|
+
markerId: string;
|
|
8
9
|
fill?: string;
|
|
9
10
|
stroke?: string;
|
|
10
11
|
}, {}, "">;
|
|
11
12
|
type $$ComponentProps = {
|
|
13
|
+
markerId: string;
|
|
12
14
|
fill?: string;
|
|
13
15
|
stroke?: string;
|
|
14
16
|
};
|
|
@@ -18,8 +18,8 @@
|
|
|
18
18
|
import { createArrowPath } from '../modules/arrowUtils.js';
|
|
19
19
|
import { getArrowSource, getArrowTarget } from '../modules/coordinates.js';
|
|
20
20
|
|
|
21
|
-
/** @type {{ annotations?: Annotation[] }} */
|
|
22
|
-
let { annotations = [] } = $props();
|
|
21
|
+
/** @type {{ annotations?: Annotation[], markerId: string }} */
|
|
22
|
+
let { annotations = [], markerId } = $props();
|
|
23
23
|
|
|
24
24
|
const { xScale, yScale, x, y, width, height } = getContext('LayerCake');
|
|
25
25
|
|
|
@@ -127,7 +127,7 @@
|
|
|
127
127
|
{#if !isBeingDragged}
|
|
128
128
|
{@const pathD = getStaticPath(anno, arrow)}
|
|
129
129
|
<!-- Visible arrow -->
|
|
130
|
-
<path class="arrow-visible" marker-end="url(#
|
|
130
|
+
<path class="arrow-visible" marker-end="url(#{markerId})" d={pathD}
|
|
131
131
|
></path>
|
|
132
132
|
<!-- Invisible hit area for clicking (edit mode only) -->
|
|
133
133
|
{#if modifyArrow}
|
|
@@ -148,7 +148,7 @@
|
|
|
148
148
|
|
|
149
149
|
<!-- Arrow being dragged (new or existing) - rendered with live coordinates -->
|
|
150
150
|
{#if dragPath}
|
|
151
|
-
<path class="arrow-visible" marker-end="url(#
|
|
151
|
+
<path class="arrow-visible" marker-end="url(#{markerId})" d={dragPath}
|
|
152
152
|
></path>
|
|
153
153
|
{/if}
|
|
154
154
|
</g>
|
|
@@ -37,9 +37,9 @@ export function getAnnotationBox(anno, scales) {
|
|
|
37
37
|
const offsetX = ((anno.dx ?? 0) / 100) * width;
|
|
38
38
|
const offsetY = ((anno.dy ?? 0) / 100) * height;
|
|
39
39
|
|
|
40
|
-
// Calculate top-left position
|
|
41
|
-
const left = xScale(x(anno)) + offsetX;
|
|
42
|
-
const top = yScale(y(anno)) + offsetY;
|
|
40
|
+
// Calculate top-left position (data values are nested in anno.data)
|
|
41
|
+
const left = xScale(x(anno.data)) + offsetX;
|
|
42
|
+
const top = yScale(y(anno.data)) + offsetY;
|
|
43
43
|
|
|
44
44
|
// Get width (stored as "150px" string or use default)
|
|
45
45
|
const annoWidth = anno.width ? parseInt(anno.width) : DEFAULT_ANNOTATION_WIDTH;
|
|
@@ -90,9 +90,9 @@ export function getArrowSource(anno, arrow, scales, annoHeight = 0) {
|
|
|
90
90
|
export function getArrowTarget(arrow, scales) {
|
|
91
91
|
const { xScale, yScale, x, y, width, height } = scales;
|
|
92
92
|
|
|
93
|
-
// Target is in data space
|
|
94
|
-
const baseX = xScale(x(arrow.target));
|
|
95
|
-
const baseY = yScale(y(arrow.target));
|
|
93
|
+
// Target is in data space (data values are nested in arrow.target.data)
|
|
94
|
+
const baseX = xScale(x(arrow.target.data));
|
|
95
|
+
const baseY = yScale(y(arrow.target.data));
|
|
96
96
|
|
|
97
97
|
// Add percentage offsets (used for ordinal scales)
|
|
98
98
|
const offsetX = ((arrow.target?.dx ?? 0) / 100) * width;
|
|
@@ -15,8 +15,10 @@ export default function newAnnotation(e, id, { xScale, yScale, config }) {
|
|
|
15
15
|
|
|
16
16
|
return {
|
|
17
17
|
id,
|
|
18
|
-
|
|
19
|
-
|
|
18
|
+
data: {
|
|
19
|
+
[config.x]: xVal[0],
|
|
20
|
+
[config.y]: yVal[0]
|
|
21
|
+
},
|
|
20
22
|
dx: xVal[1],
|
|
21
23
|
dy: yVal[1],
|
|
22
24
|
text: 'New note...',
|
package/dist/types.d.ts
CHANGED
|
@@ -16,9 +16,8 @@ export interface ArrowSource {
|
|
|
16
16
|
* Arrow target position (data space coordinates)
|
|
17
17
|
*/
|
|
18
18
|
export interface ArrowTarget {
|
|
19
|
-
/**
|
|
20
|
-
|
|
21
|
-
/** Y data value (key varies based on LayerCake config) */
|
|
19
|
+
/** User data values (x/y keys match LayerCake config) */
|
|
20
|
+
data: Record<string, unknown>;
|
|
22
21
|
/** Percentage offset for ordinal X scales (0-100) */
|
|
23
22
|
dx?: number;
|
|
24
23
|
/** Percentage offset for ordinal Y scales (0-100) */
|
|
@@ -45,9 +44,8 @@ export interface Arrow {
|
|
|
45
44
|
export interface Annotation {
|
|
46
45
|
/** Unique identifier */
|
|
47
46
|
id: number;
|
|
48
|
-
/**
|
|
49
|
-
|
|
50
|
-
/** Y data value (key varies based on LayerCake config, e.g., 'value' or 'y') */
|
|
47
|
+
/** User data values (x/y keys match LayerCake config) */
|
|
48
|
+
data: Record<string, unknown>;
|
|
51
49
|
/** Percentage offset from data point in X direction */
|
|
52
50
|
dx: number;
|
|
53
51
|
/** Percentage offset from data point in Y direction */
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mhkeller/layercake-annotations",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.0",
|
|
4
4
|
"scripts": {
|
|
5
5
|
"dev": "vite dev",
|
|
6
6
|
"build": "vite build && pnpm package",
|
|
@@ -43,7 +43,7 @@
|
|
|
43
43
|
"prettier": "^3.1.1",
|
|
44
44
|
"prettier-plugin-svelte": "^3.1.2",
|
|
45
45
|
"publint": "^0.2.0",
|
|
46
|
-
"svelte": "^5.
|
|
46
|
+
"svelte": "^5.46.1",
|
|
47
47
|
"svelte-check": "^4.0.0",
|
|
48
48
|
"typescript": "^5.0.0",
|
|
49
49
|
"vite": "^5.0.11"
|