@fundar/data-chart-telling 0.0.11 → 0.0.13

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.
@@ -219,7 +219,7 @@
219
219
  </Plot>
220
220
 
221
221
  {#if tooltip && ctrl.showTooltip && ctrl.tooltipPosition}
222
- <Tooltip x={ctrl.tooltipPosition?.x ?? 0} y={ctrl.tooltipPosition?.y ?? 0}>
222
+ <Tooltip x={ctrl.tooltipPosition?.x ?? 0} y={ctrl.tooltipPosition?.y ?? 0} bounds={{ width, height }}>
223
223
  {#if tooltip.content}
224
224
  {@render tooltip.content({ rows: ctrl.matchedRows.map((p) => p.row) })}
225
225
  {:else}
@@ -6,6 +6,11 @@
6
6
  * parent. It is styled entirely from the `--dct-tooltip-*` variables that
7
7
  * {@link ChartContainer} stamps from the config, so it themes for free. Plots
8
8
  * that implement hovering can render this with their own content snippet.
9
+ *
10
+ * Placement is boundary-aware: it prefers sitting centered above the point,
11
+ * but flips below when it would overflow the top and shifts horizontally
12
+ * when it would overflow the left/right, so it always stays clear of the
13
+ * `bounds` box (typically the plot's own width/height).
9
14
  */
10
15
  let {
11
16
  x,
@@ -13,6 +18,7 @@
13
18
  visible = true,
14
19
  background,
15
20
  color,
21
+ bounds,
16
22
  children,
17
23
  }: {
18
24
  x: number;
@@ -20,15 +26,39 @@
20
26
  visible?: boolean;
21
27
  background?: string;
22
28
  color?: string;
29
+ /** Box the tooltip must stay within — usually the hosting plot's width/height. */
30
+ bounds?: { width: number; height: number };
23
31
  children: Snippet;
24
32
  } = $props();
33
+
34
+ const GAP = 10;
35
+ const EDGE_MARGIN = 6;
36
+
37
+ let boxWidth = $state(0);
38
+ let boxHeight = $state(0);
39
+
40
+ const placement = $derived.by(() => {
41
+ const boundsWidth = bounds?.width ?? Infinity;
42
+ const boundsHeight = bounds?.height ?? Infinity;
43
+
44
+ let top = y - GAP - boxHeight;
45
+ if (top < EDGE_MARGIN) top = y + GAP;
46
+ top = Math.min(Math.max(top, EDGE_MARGIN), Math.max(EDGE_MARGIN, boundsHeight - boxHeight - EDGE_MARGIN));
47
+
48
+ let left = x - boxWidth / 2;
49
+ left = Math.min(Math.max(left, EDGE_MARGIN), Math.max(EDGE_MARGIN, boundsWidth - boxWidth - EDGE_MARGIN));
50
+
51
+ return { left, top };
52
+ });
25
53
  </script>
26
54
 
27
55
  {#if visible}
28
56
  <div
29
57
  class="dct-tooltip"
30
- style:left="{x}px"
31
- style:top="{y}px"
58
+ bind:clientWidth={boxWidth}
59
+ bind:clientHeight={boxHeight}
60
+ style:left="{placement.left}px"
61
+ style:top="{placement.top}px"
32
62
  style:background={background}
33
63
  style:color={color}
34
64
  >
@@ -40,7 +70,6 @@
40
70
  .dct-tooltip {
41
71
  position: absolute;
42
72
  z-index: 10;
43
- transform: translate(-50%, -110%);
44
73
  pointer-events: none;
45
74
  background: var(--dct-tooltip-bg, #111827);
46
75
  color: var(--dct-tooltip-color, #fff);
@@ -5,6 +5,11 @@ type $$ComponentProps = {
5
5
  visible?: boolean;
6
6
  background?: string;
7
7
  color?: string;
8
+ /** Box the tooltip must stay within — usually the hosting plot's width/height. */
9
+ bounds?: {
10
+ width: number;
11
+ height: number;
12
+ };
8
13
  children: Snippet;
9
14
  };
10
15
  declare const Tooltip: import("svelte").Component<$$ComponentProps, {}, "">;
@@ -47,7 +47,7 @@ export function createHoverController(args) {
47
47
  return cursor != null;
48
48
  return h.sync;
49
49
  });
50
- const tooltipText = $derived(matchedRows.length ? matchedRows.map((p) => args.format()(p.row)).join('\n\n') : '');
50
+ const tooltipText = $derived(matchedRows.length ? matchedRows.map((p) => args.format()(p.row)).join('\n') : '');
51
51
  function onPointer(data) {
52
52
  const h = args.hover();
53
53
  if (data.length) {
@@ -156,10 +156,12 @@
156
156
  ruleStyle={marker.ruleStyle}
157
157
  dotStyle={marker.dotStyle}
158
158
  fontStyle={marker.fontStyle}
159
+ seriesColor={seg.style?.fill ?? defaultColor}
159
160
  />
160
161
  {/if}
161
162
  {/each}
162
163
  {:else}
164
+ {@const segs = resolveSegmentsForSeries(segments, group.series.name)}
163
165
  <HoverMarker
164
166
  data={seriesPoints}
165
167
  ruleX={marker.ruleX !== false && (impliesRuleX || marker.ruleX === true) ? ruleX : null}
@@ -168,6 +170,7 @@
168
170
  ruleStyle={marker.ruleStyle}
169
171
  dotStyle={marker.dotStyle}
170
172
  fontStyle={marker.fontStyle}
173
+ seriesColor={segs[0]?.style?.fill ?? defaultColor}
171
174
  />
172
175
  {/if}
173
176
  {/if}
@@ -178,10 +178,12 @@
178
178
  ruleStyle={marker.ruleStyle}
179
179
  dotStyle={marker.dotStyle}
180
180
  fontStyle={marker.fontStyle}
181
+ seriesColor={seg.style?.stroke?.stroke ?? defaultColor}
181
182
  />
182
183
  {/if}
183
184
  {/each}
184
185
  {:else}
186
+ {@const segs = resolveSegmentsForSeries(segments, group.series.name)}
185
187
  <HoverMarker
186
188
  data={seriesPoints}
187
189
  ruleX={marker.ruleX !== false && (impliesRuleX || marker.ruleX === true) ? ruleX : null}
@@ -190,6 +192,7 @@
190
192
  ruleStyle={marker.ruleStyle}
191
193
  dotStyle={marker.dotStyle}
192
194
  fontStyle={marker.fontStyle}
195
+ seriesColor={segs[0]?.style?.stroke?.stroke ?? defaultColor}
193
196
  />
194
197
  {/if}
195
198
  {/if}
@@ -34,9 +34,14 @@
34
34
  ruleStyle,
35
35
  dotStyle,
36
36
  fontStyle,
37
+ seriesColor,
37
38
  }: Props = $props();
38
39
 
39
40
  const cfg = $derived(getConfiguration());
41
+
42
+ /** Explicit style wins; otherwise fall back to the series color, then the point's own resolved color. */
43
+ const resolveColor = (explicit: string | undefined, point: HoverDisplayPoint) =>
44
+ explicit ?? seriesColor ?? point.color;
40
45
  </script>
41
46
 
42
47
  {#if ruleX != null}
@@ -70,8 +75,8 @@
70
75
  style={{
71
76
  ...cfg.hover.dot,
72
77
  ...dotStyle,
73
- ...(dotStyle?.dotFill === 'currentColor' && point.color ? { dotFill: point.color } : {}),
74
- ...(dotStyle?.dotStroke === 'currentColor' && point.color ? { dotStroke: point.color } : {}),
78
+ dotFill: resolveColor(dotStyle?.dotFill, point),
79
+ dotStroke: resolveColor(dotStyle?.dotStroke, point),
75
80
  }}
76
81
  />
77
82
  {/each}
@@ -85,7 +90,7 @@
85
90
  style={{
86
91
  ...cfg.hover.font,
87
92
  ...fontStyle,
88
- ...(fontStyle?.fill === 'currentColor' && point.color ? { fill: point.color } : {}),
93
+ fill: resolveColor(fontStyle?.fill, point),
89
94
  }}
90
95
  />
91
96
  {/each}
@@ -262,10 +262,12 @@
262
262
  ruleStyle={marker.ruleStyle}
263
263
  dotStyle={marker.dotStyle}
264
264
  fontStyle={marker.fontStyle}
265
+ seriesColor={seg.style?.fill ?? defaultColor}
265
266
  />
266
267
  {/if}
267
268
  {/each}
268
269
  {:else}
270
+ {@const segs = resolveSegmentsForSeries(segments, group.series.name)}
269
271
  <HoverMarker
270
272
  data={seriesPoints}
271
273
  ruleX={marker.ruleX !== false && (impliesRuleX || marker.ruleX === true) ? ruleX : null}
@@ -274,6 +276,7 @@
274
276
  ruleStyle={marker.ruleStyle}
275
277
  dotStyle={marker.dotStyle}
276
278
  fontStyle={marker.fontStyle}
279
+ seriesColor={segs[0]?.style?.fill ?? defaultColor}
277
280
  />
278
281
  {/if}
279
282
  {/if}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fundar/data-chart-telling",
3
- "version": "0.0.11",
3
+ "version": "0.0.13",
4
4
  "type": "module",
5
5
  "files": [
6
6
  "dist"