@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.
@@ -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
- [xKey]: any, // X data value (key from LayerCake config)
42
- [yKey]: any, // Y data value (key from LayerCake config)
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
- [xKey]: any, // X data value (same accessor as LayerCake)
66
- [yKey]: any, // Y data value
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(dataValue) + (dx/100 × chartDimension)
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
@@ -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
- const newProps = filterObject(
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
- [$config.x]: targetDataX,
221
- [$config.y]: targetDataY,
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
- [$config.x]: targetDataX,
246
- [$config.y]: targetDataY,
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="layercake-annotation-arrowhead"
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(#layercake-annotation-arrowhead)" d={pathD}
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(#layercake-annotation-arrowhead)" d={dragPath}
151
+ <path class="arrow-visible" marker-end="url(#{markerId})" d={dragPath}
152
152
  ></path>
153
153
  {/if}
154
154
  </g>
@@ -14,7 +14,9 @@ type Arrows = {
14
14
  */
15
15
  declare const Arrows: import("svelte").Component<{
16
16
  annotations?: Annotation[];
17
+ markerId: string;
17
18
  }, {}, "">;
18
19
  type $$ComponentProps = {
19
20
  annotations?: Annotation[];
21
+ markerId: string;
20
22
  };
@@ -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
- [config.x]: xVal[0],
19
- [config.y]: yVal[0],
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
- /** X data value (key varies based on LayerCake config) */
20
- [xKey: string]: unknown;
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
- /** 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') */
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.1",
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.0.0",
46
+ "svelte": "^5.46.1",
47
47
  "svelte-check": "^4.0.0",
48
48
  "typescript": "^5.0.0",
49
49
  "vite": "^5.0.11"