@vielzeug/prism 26.10.5 → 26.10.7
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 +1 -1
- package/dist/axes/axis.cjs +1 -1
- package/dist/axes/axis.cjs.map +1 -1
- package/dist/axes/axis.d.ts +10 -3
- package/dist/axes/axis.d.ts.map +1 -1
- package/dist/axes/axis.js +1 -1
- package/dist/axes/axis.js.map +1 -1
- package/dist/axes/grid.cjs +1 -1
- package/dist/axes/grid.cjs.map +1 -1
- package/dist/axes/grid.d.ts +1 -1
- package/dist/axes/grid.d.ts.map +1 -1
- package/dist/axes/grid.js +1 -1
- package/dist/axes/grid.js.map +1 -1
- package/dist/charts/area/area-chart.cjs +1 -1
- package/dist/charts/area/area-chart.cjs.map +1 -1
- package/dist/charts/area/area-chart.d.ts.map +1 -1
- package/dist/charts/area/area-chart.js +1 -1
- package/dist/charts/area/area-chart.js.map +1 -1
- package/dist/charts/bar/bar-chart.cjs +1 -1
- package/dist/charts/bar/bar-chart.cjs.map +1 -1
- package/dist/charts/bar/bar-chart.d.ts.map +1 -1
- package/dist/charts/bar/bar-chart.js +1 -1
- package/dist/charts/bar/bar-chart.js.map +1 -1
- package/dist/charts/line/line-chart.cjs +1 -1
- package/dist/charts/line/line-chart.cjs.map +1 -1
- package/dist/charts/line/line-chart.d.ts.map +1 -1
- package/dist/charts/line/line-chart.js +1 -1
- package/dist/charts/line/line-chart.js.map +1 -1
- package/dist/charts/scatter/index.d.ts +2 -0
- package/dist/charts/scatter/index.d.ts.map +1 -0
- package/dist/charts/scatter/scatter-chart.cjs +2 -0
- package/dist/charts/scatter/scatter-chart.cjs.map +1 -0
- package/dist/charts/scatter/scatter-chart.d.ts +3 -0
- package/dist/charts/scatter/scatter-chart.d.ts.map +1 -0
- package/dist/charts/scatter/scatter-chart.js +2 -0
- package/dist/charts/scatter/scatter-chart.js.map +1 -0
- package/dist/charts/scatter/scatter-renderer.cjs +2 -0
- package/dist/charts/scatter/scatter-renderer.cjs.map +1 -0
- package/dist/charts/scatter/scatter-renderer.d.ts +24 -0
- package/dist/charts/scatter/scatter-renderer.d.ts.map +1 -0
- package/dist/charts/scatter/scatter-renderer.js +2 -0
- package/dist/charts/scatter/scatter-renderer.js.map +1 -0
- package/dist/index.cjs +1 -1
- package/dist/index.d.ts +2 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/interaction/announcer.cjs +1 -1
- package/dist/interaction/announcer.cjs.map +1 -1
- package/dist/interaction/announcer.d.ts +1 -1
- package/dist/interaction/announcer.d.ts.map +1 -1
- package/dist/interaction/announcer.js +1 -1
- package/dist/interaction/announcer.js.map +1 -1
- package/dist/interaction/scatter-interaction.cjs +2 -0
- package/dist/interaction/scatter-interaction.cjs.map +1 -0
- package/dist/interaction/scatter-interaction.d.ts +36 -0
- package/dist/interaction/scatter-interaction.d.ts.map +1 -0
- package/dist/interaction/scatter-interaction.js +2 -0
- package/dist/interaction/scatter-interaction.js.map +1 -0
- package/dist/interaction/series-interaction.cjs +1 -1
- package/dist/interaction/series-interaction.cjs.map +1 -1
- package/dist/interaction/series-interaction.d.ts +3 -1
- package/dist/interaction/series-interaction.d.ts.map +1 -1
- package/dist/interaction/series-interaction.js +1 -1
- package/dist/interaction/series-interaction.js.map +1 -1
- package/dist/interaction/tooltip.cjs +1 -1
- package/dist/interaction/tooltip.cjs.map +1 -1
- package/dist/interaction/tooltip.d.ts.map +1 -1
- package/dist/interaction/tooltip.js +1 -1
- package/dist/interaction/tooltip.js.map +1 -1
- package/dist/prism.cjs +1 -1
- package/dist/prism.cjs.map +1 -1
- package/dist/prism.iife.js +1 -1
- package/dist/prism.iife.js.map +1 -1
- package/dist/prism.js +1 -1
- package/dist/prism.js.map +1 -1
- package/dist/theme/prism.css +6 -0
- package/dist/types.d.ts +38 -5
- package/dist/types.d.ts.map +1 -1
- package/package.json +4 -3
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"scatter-interaction.cjs","names":[],"sources":["../../src/interaction/scatter-interaction.ts"],"sourcesContent":["import type { ChartEventHandlers } from '../core/chart-scaffold';\nimport { chartArea } from '../core/layout';\nimport { createSvgElement } from '../svg/element';\nimport type { Point } from '../svg/path';\nimport type { ChartDimensions, ChartEvent, ChartMargin, ContinuousDatum, Series, TooltipConfig } from '../types';\nimport { type Announcer, describeValues } from './announcer';\nimport type { CrosshairState } from './crosshair';\nimport { getMousePosition } from './events';\nimport { keyLabel } from './series-interaction';\nimport { comparisonContent, type TooltipState } from './tooltip';\n\nexport interface ScatterInteractionOptions {\n announcer: Announcer;\n colors: () => string[];\n crosshair?: CrosshairState | null;\n dims: () => ChartDimensions;\n /** Per-series data, parallel to `getPoints`; each datum is one independent point. */\n getData: () => ContinuousDatum[][];\n getPoints: () => Point[][];\n getSeriesList: () => Series[];\n /** Effective chart margin: hit bounds and tooltip offsets use this, never the shared dimensions. */\n margin: ChartMargin;\n /** Receives the single active-point marker. */\n markers: SVGGElement;\n onClick?: ((event: ChartEvent) => void) | undefined;\n onHover?: ((event: ChartEvent | null) => void) | undefined;\n /** Parent of the per-series point groups; the active series is emphasised and the rest dim. */\n seriesGroup: SVGGElement;\n svg: SVGSVGElement;\n tooltip?: TooltipState | null;\n /** Value/title formatters from the tooltip config, applied to the row and spoken text alike. */\n tooltipFormat?: Pick<TooltipConfig, 'titleFormat' | 'valueFormat'>;\n}\n\ninterface Hit {\n datum: ContinuousDatum;\n point: Point;\n seriesIndex: number;\n}\n\n/**\n * Pointer and keyboard interaction for a scatter plot. A scatter point is an\n * independent `(x, y)` pair rather than one sample at a shared key, so the active\n * point is the one nearest the pointer in two dimensions, events carry no\n * cross-series `values`, and keyboard navigation walks every point in x order.\n */\nexport function createScatterInteraction(opts: ScatterInteractionOptions): ChartEventHandlers {\n let keyboardIndex = -1;\n let active = false;\n\n const hitAt = (seriesIndex: number, pointIndex: number): Hit | null => {\n const datum = opts.getData()[seriesIndex]?.[pointIndex];\n const point = opts.getPoints()[seriesIndex]?.[pointIndex];\n\n return datum && point ? { datum, point, seriesIndex } : null;\n };\n\n const renderedHit = (event: Event): Hit | null => {\n const target = event.target;\n\n if (\n !(target instanceof SVGElement) ||\n !target.classList.contains('prism-scatter-point') ||\n !target.parentElement ||\n target.parentElement.parentNode !== opts.seriesGroup\n )\n return null;\n\n return hitAt(\n [...opts.seriesGroup.children].indexOf(target.parentElement),\n [...target.parentElement.children].indexOf(target),\n );\n };\n\n const nearestHit = (pos: Point): Hit | null => {\n let best: Hit | null = null;\n let minDistance = Infinity;\n\n for (const [seriesIndex, points] of opts.getPoints().entries()) {\n for (const pointIndex of points.keys()) {\n const hit = hitAt(seriesIndex, pointIndex);\n\n if (!hit) continue;\n\n const distance = Math.hypot(hit.point.x - pos.x, hit.point.y - pos.y);\n\n if (distance < minDistance) {\n minDistance = distance;\n best = hit;\n }\n }\n }\n\n return best;\n };\n\n // Every point across every series in x order (then y, then series order), for keyboard traversal.\n const orderedHits = (): Hit[] => {\n const hits: Hit[] = [];\n\n for (const [seriesIndex, data] of opts.getData().entries()) {\n for (const pointIndex of data.keys()) {\n const hit = hitAt(seriesIndex, pointIndex);\n\n if (hit) hits.push(hit);\n }\n }\n\n return hits.sort((a, b) => a.point.x - b.point.x || a.point.y - b.point.y || a.seriesIndex - b.seriesIndex);\n };\n\n const eventFor = (hit: Hit, originalEvent: Event): ChartEvent => ({\n datum: hit.datum,\n originalEvent,\n series: opts.getSeriesList()[hit.seriesIndex],\n });\n\n const highlight = (hit: Hit | null): void => {\n const groups = [...opts.seriesGroup.children];\n\n opts.markers.replaceChildren();\n\n if (hit) {\n opts.markers.appendChild(\n createSvgElement('circle', {\n class: 'prism-active-point',\n cx: hit.point.x,\n cy: hit.point.y,\n fill: opts.colors()[hit.seriesIndex],\n r: 4,\n }),\n );\n }\n\n opts.seriesGroup.classList.toggle('prism-series-focused', hit !== null && groups.length > 1);\n for (const [index, group] of groups.entries())\n group.classList.toggle('prism-series-active', index === hit?.seriesIndex);\n };\n\n const clear = (): void => {\n opts.crosshair?.hide();\n opts.tooltip?.hide();\n opts.announcer.clear();\n highlight(null);\n active = false;\n opts.onHover?.(null);\n };\n\n const focus = (hit: Hit, raw: Point | null, originalEvent: Event): void => {\n const dims = opts.dims();\n const area = chartArea(dims.width, dims.height, opts.margin);\n const follow = opts.crosshair?.snap === false && raw;\n const event = eventFor(hit, originalEvent);\n const { datum, series } = event;\n const label = opts.tooltipFormat?.titleFormat ? opts.tooltipFormat.titleFormat(datum.key) : keyLabel(datum.key);\n const valueFormat = opts.tooltipFormat?.valueFormat;\n const spoken = describeValues(label, [{ datum, series }], valueFormat ?? String);\n\n opts.crosshair?.show(follow ? raw.x : hit.point.x, follow ? raw.y : hit.point.y, area.width, area.height);\n highlight(hit);\n\n if (opts.tooltip) {\n const row = {\n color: opts.colors()[hit.seriesIndex],\n name: series.name,\n value: valueFormat ? valueFormat(datum.value, datum, series) : String(datum.value),\n };\n\n opts.tooltip.show(\n hit.point.x + opts.margin.left,\n hit.point.y + opts.margin.top,\n datum,\n series,\n comparisonContent(opts.svg.ownerDocument, label, [row], spoken),\n );\n } else {\n opts.announcer.announce(spoken);\n }\n\n active = true;\n opts.onHover?.(event);\n };\n\n const pointerHit = (event: MouseEvent): { hit: Hit; raw: Point } | null => {\n const rendered = renderedHit(event);\n\n if (rendered) return { hit: rendered, raw: rendered.point };\n\n const dims = opts.dims();\n const raw = getMousePosition(opts.svg, event, opts.margin.left, opts.margin.top);\n const area = chartArea(dims.width, dims.height, opts.margin);\n\n if (raw.x < 0 || raw.x > area.width || raw.y < 0 || raw.y > area.height) return null;\n\n const hit = nearestHit(raw);\n\n return hit ? { hit, raw } : null;\n };\n\n const onMouseMove = (event: MouseEvent): void => {\n const found = pointerHit(event);\n\n if (found) focus(found.hit, found.raw, event);\n else if (active) clear();\n };\n\n const onClick = (event: MouseEvent): void => {\n if (!opts.onClick) return;\n\n const found = pointerHit(event);\n\n if (found) opts.onClick(eventFor(found.hit, event));\n };\n\n const onKeyDown = (event: KeyboardEvent): void => {\n const hits = orderedHits();\n\n if (hits.length === 0) return;\n\n const last = hits.length - 1;\n const target: Record<string, number> = {\n ArrowLeft: keyboardIndex < 0 ? last : Math.max(0, keyboardIndex - 1),\n ArrowRight: keyboardIndex < 0 ? 0 : Math.min(last, keyboardIndex + 1),\n End: last,\n Home: 0,\n };\n\n if (event.key in target) {\n event.preventDefault();\n keyboardIndex = target[event.key];\n focus(hits[keyboardIndex], null, event);\n } else if ((event.key === 'Enter' || event.key === ' ') && keyboardIndex >= 0 && opts.onClick) {\n event.preventDefault();\n opts.onClick(eventFor(hits[keyboardIndex], event));\n } else if (event.key === 'Escape') {\n keyboardIndex = -1;\n clear();\n }\n };\n\n return { onClick, onKeyDown, onMouseLeave: clear, onMouseMove };\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import type { ChartEventHandlers } from '../core/chart-scaffold';
|
|
2
|
+
import type { Point } from '../svg/path';
|
|
3
|
+
import type { ChartDimensions, ChartEvent, ChartMargin, ContinuousDatum, Series, TooltipConfig } from '../types';
|
|
4
|
+
import { type Announcer } from './announcer';
|
|
5
|
+
import type { CrosshairState } from './crosshair';
|
|
6
|
+
import { type TooltipState } from './tooltip';
|
|
7
|
+
export interface ScatterInteractionOptions {
|
|
8
|
+
announcer: Announcer;
|
|
9
|
+
colors: () => string[];
|
|
10
|
+
crosshair?: CrosshairState | null;
|
|
11
|
+
dims: () => ChartDimensions;
|
|
12
|
+
/** Per-series data, parallel to `getPoints`; each datum is one independent point. */
|
|
13
|
+
getData: () => ContinuousDatum[][];
|
|
14
|
+
getPoints: () => Point[][];
|
|
15
|
+
getSeriesList: () => Series[];
|
|
16
|
+
/** Effective chart margin: hit bounds and tooltip offsets use this, never the shared dimensions. */
|
|
17
|
+
margin: ChartMargin;
|
|
18
|
+
/** Receives the single active-point marker. */
|
|
19
|
+
markers: SVGGElement;
|
|
20
|
+
onClick?: ((event: ChartEvent) => void) | undefined;
|
|
21
|
+
onHover?: ((event: ChartEvent | null) => void) | undefined;
|
|
22
|
+
/** Parent of the per-series point groups; the active series is emphasised and the rest dim. */
|
|
23
|
+
seriesGroup: SVGGElement;
|
|
24
|
+
svg: SVGSVGElement;
|
|
25
|
+
tooltip?: TooltipState | null;
|
|
26
|
+
/** Value/title formatters from the tooltip config, applied to the row and spoken text alike. */
|
|
27
|
+
tooltipFormat?: Pick<TooltipConfig, 'titleFormat' | 'valueFormat'>;
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Pointer and keyboard interaction for a scatter plot. A scatter point is an
|
|
31
|
+
* independent `(x, y)` pair rather than one sample at a shared key, so the active
|
|
32
|
+
* point is the one nearest the pointer in two dimensions, events carry no
|
|
33
|
+
* cross-series `values`, and keyboard navigation walks every point in x order.
|
|
34
|
+
*/
|
|
35
|
+
export declare function createScatterInteraction(opts: ScatterInteractionOptions): ChartEventHandlers;
|
|
36
|
+
//# sourceMappingURL=scatter-interaction.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"scatter-interaction.d.ts","sourceRoot":"","sources":["../../src/interaction/scatter-interaction.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,wBAAwB,CAAC;AAGjE,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,aAAa,CAAC;AACzC,OAAO,KAAK,EAAE,eAAe,EAAE,UAAU,EAAE,WAAW,EAAE,eAAe,EAAE,MAAM,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AACjH,OAAO,EAAE,KAAK,SAAS,EAAkB,MAAM,aAAa,CAAC;AAC7D,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAGlD,OAAO,EAAqB,KAAK,YAAY,EAAE,MAAM,WAAW,CAAC;AAEjE,MAAM,WAAW,yBAAyB;IACxC,SAAS,EAAE,SAAS,CAAC;IACrB,MAAM,EAAE,MAAM,MAAM,EAAE,CAAC;IACvB,SAAS,CAAC,EAAE,cAAc,GAAG,IAAI,CAAC;IAClC,IAAI,EAAE,MAAM,eAAe,CAAC;IAC5B,qFAAqF;IACrF,OAAO,EAAE,MAAM,eAAe,EAAE,EAAE,CAAC;IACnC,SAAS,EAAE,MAAM,KAAK,EAAE,EAAE,CAAC;IAC3B,aAAa,EAAE,MAAM,MAAM,EAAE,CAAC;IAC9B,oGAAoG;IACpG,MAAM,EAAE,WAAW,CAAC;IACpB,+CAA+C;IAC/C,OAAO,EAAE,WAAW,CAAC;IACrB,OAAO,CAAC,EAAE,CAAC,CAAC,KAAK,EAAE,UAAU,KAAK,IAAI,CAAC,GAAG,SAAS,CAAC;IACpD,OAAO,CAAC,EAAE,CAAC,CAAC,KAAK,EAAE,UAAU,GAAG,IAAI,KAAK,IAAI,CAAC,GAAG,SAAS,CAAC;IAC3D,+FAA+F;IAC/F,WAAW,EAAE,WAAW,CAAC;IACzB,GAAG,EAAE,aAAa,CAAC;IACnB,OAAO,CAAC,EAAE,YAAY,GAAG,IAAI,CAAC;IAC9B,gGAAgG;IAChG,aAAa,CAAC,EAAE,IAAI,CAAC,aAAa,EAAE,aAAa,GAAG,aAAa,CAAC,CAAC;CACpE;AAQD;;;;;GAKG;AACH,wBAAgB,wBAAwB,CAAC,IAAI,EAAE,yBAAyB,GAAG,kBAAkB,CAmM5F"}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import{createSvgElement as e}from"../svg/element.js";import{describeValues as t}from"./announcer.js";import{comparisonContent as n}from"./tooltip.js";import{chartArea as r}from"../core/layout.js";import{getMousePosition as i}from"./events.js";import{keyLabel as a}from"./series-interaction.js";function createScatterInteraction(o){let s=-1,c=!1,hitAt=(e,t)=>{let n=o.getData()[e]?.[t],r=o.getPoints()[e]?.[t];return n&&r?{datum:n,point:r,seriesIndex:e}:null},renderedHit=e=>{let t=e.target;return!(t instanceof SVGElement)||!t.classList.contains(`prism-scatter-point`)||!t.parentElement||t.parentElement.parentNode!==o.seriesGroup?null:hitAt([...o.seriesGroup.children].indexOf(t.parentElement),[...t.parentElement.children].indexOf(t))},nearestHit=e=>{let t=null,n=1/0;for(let[r,i]of o.getPoints().entries())for(let a of i.keys()){let i=hitAt(r,a);if(!i)continue;let o=Math.hypot(i.point.x-e.x,i.point.y-e.y);o<n&&(n=o,t=i)}return t},orderedHits=()=>{let e=[];for(let[t,n]of o.getData().entries())for(let r of n.keys()){let n=hitAt(t,r);n&&e.push(n)}return e.sort((e,t)=>e.point.x-t.point.x||e.point.y-t.point.y||e.seriesIndex-t.seriesIndex)},eventFor=(e,t)=>({datum:e.datum,originalEvent:t,series:o.getSeriesList()[e.seriesIndex]}),highlight=t=>{let n=[...o.seriesGroup.children];o.markers.replaceChildren(),t&&o.markers.appendChild(e(`circle`,{class:`prism-active-point`,cx:t.point.x,cy:t.point.y,fill:o.colors()[t.seriesIndex],r:4})),o.seriesGroup.classList.toggle(`prism-series-focused`,t!==null&&n.length>1);for(let[e,r]of n.entries())r.classList.toggle(`prism-series-active`,e===t?.seriesIndex)},clear=()=>{o.crosshair?.hide(),o.tooltip?.hide(),o.announcer.clear(),highlight(null),c=!1,o.onHover?.(null)},focus=(e,i,s)=>{let l=o.dims(),u=r(l.width,l.height,o.margin),d=o.crosshair?.snap===!1&&i,f=eventFor(e,s),{datum:p,series:m}=f,h=o.tooltipFormat?.titleFormat?o.tooltipFormat.titleFormat(p.key):a(p.key),g=o.tooltipFormat?.valueFormat,_=t(h,[{datum:p,series:m}],g??String);if(o.crosshair?.show(d?i.x:e.point.x,d?i.y:e.point.y,u.width,u.height),highlight(e),o.tooltip){let t={color:o.colors()[e.seriesIndex],name:m.name,value:g?g(p.value,p,m):String(p.value)};o.tooltip.show(e.point.x+o.margin.left,e.point.y+o.margin.top,p,m,n(o.svg.ownerDocument,h,[t],_))}else o.announcer.announce(_);c=!0,o.onHover?.(f)},pointerHit=e=>{let t=renderedHit(e);if(t)return{hit:t,raw:t.point};let n=o.dims(),a=i(o.svg,e,o.margin.left,o.margin.top),s=r(n.width,n.height,o.margin);if(a.x<0||a.x>s.width||a.y<0||a.y>s.height)return null;let c=nearestHit(a);return c?{hit:c,raw:a}:null},onMouseMove=e=>{let t=pointerHit(e);t?focus(t.hit,t.raw,e):c&&clear()},onClick=e=>{if(!o.onClick)return;let t=pointerHit(e);t&&o.onClick(eventFor(t.hit,e))},onKeyDown=e=>{let t=orderedHits();if(t.length===0)return;let n=t.length-1,r={ArrowLeft:s<0?n:Math.max(0,s-1),ArrowRight:s<0?0:Math.min(n,s+1),End:n,Home:0};e.key in r?(e.preventDefault(),s=r[e.key],focus(t[s],null,e)):(e.key===`Enter`||e.key===` `)&&s>=0&&o.onClick?(e.preventDefault(),o.onClick(eventFor(t[s],e))):e.key===`Escape`&&(s=-1,clear())};return{onClick,onKeyDown,onMouseLeave:clear,onMouseMove}}export{createScatterInteraction};
|
|
2
|
+
//# sourceMappingURL=scatter-interaction.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"scatter-interaction.js","names":[],"sources":["../../src/interaction/scatter-interaction.ts"],"sourcesContent":["import type { ChartEventHandlers } from '../core/chart-scaffold';\nimport { chartArea } from '../core/layout';\nimport { createSvgElement } from '../svg/element';\nimport type { Point } from '../svg/path';\nimport type { ChartDimensions, ChartEvent, ChartMargin, ContinuousDatum, Series, TooltipConfig } from '../types';\nimport { type Announcer, describeValues } from './announcer';\nimport type { CrosshairState } from './crosshair';\nimport { getMousePosition } from './events';\nimport { keyLabel } from './series-interaction';\nimport { comparisonContent, type TooltipState } from './tooltip';\n\nexport interface ScatterInteractionOptions {\n announcer: Announcer;\n colors: () => string[];\n crosshair?: CrosshairState | null;\n dims: () => ChartDimensions;\n /** Per-series data, parallel to `getPoints`; each datum is one independent point. */\n getData: () => ContinuousDatum[][];\n getPoints: () => Point[][];\n getSeriesList: () => Series[];\n /** Effective chart margin: hit bounds and tooltip offsets use this, never the shared dimensions. */\n margin: ChartMargin;\n /** Receives the single active-point marker. */\n markers: SVGGElement;\n onClick?: ((event: ChartEvent) => void) | undefined;\n onHover?: ((event: ChartEvent | null) => void) | undefined;\n /** Parent of the per-series point groups; the active series is emphasised and the rest dim. */\n seriesGroup: SVGGElement;\n svg: SVGSVGElement;\n tooltip?: TooltipState | null;\n /** Value/title formatters from the tooltip config, applied to the row and spoken text alike. */\n tooltipFormat?: Pick<TooltipConfig, 'titleFormat' | 'valueFormat'>;\n}\n\ninterface Hit {\n datum: ContinuousDatum;\n point: Point;\n seriesIndex: number;\n}\n\n/**\n * Pointer and keyboard interaction for a scatter plot. A scatter point is an\n * independent `(x, y)` pair rather than one sample at a shared key, so the active\n * point is the one nearest the pointer in two dimensions, events carry no\n * cross-series `values`, and keyboard navigation walks every point in x order.\n */\nexport function createScatterInteraction(opts: ScatterInteractionOptions): ChartEventHandlers {\n let keyboardIndex = -1;\n let active = false;\n\n const hitAt = (seriesIndex: number, pointIndex: number): Hit | null => {\n const datum = opts.getData()[seriesIndex]?.[pointIndex];\n const point = opts.getPoints()[seriesIndex]?.[pointIndex];\n\n return datum && point ? { datum, point, seriesIndex } : null;\n };\n\n const renderedHit = (event: Event): Hit | null => {\n const target = event.target;\n\n if (\n !(target instanceof SVGElement) ||\n !target.classList.contains('prism-scatter-point') ||\n !target.parentElement ||\n target.parentElement.parentNode !== opts.seriesGroup\n )\n return null;\n\n return hitAt(\n [...opts.seriesGroup.children].indexOf(target.parentElement),\n [...target.parentElement.children].indexOf(target),\n );\n };\n\n const nearestHit = (pos: Point): Hit | null => {\n let best: Hit | null = null;\n let minDistance = Infinity;\n\n for (const [seriesIndex, points] of opts.getPoints().entries()) {\n for (const pointIndex of points.keys()) {\n const hit = hitAt(seriesIndex, pointIndex);\n\n if (!hit) continue;\n\n const distance = Math.hypot(hit.point.x - pos.x, hit.point.y - pos.y);\n\n if (distance < minDistance) {\n minDistance = distance;\n best = hit;\n }\n }\n }\n\n return best;\n };\n\n // Every point across every series in x order (then y, then series order), for keyboard traversal.\n const orderedHits = (): Hit[] => {\n const hits: Hit[] = [];\n\n for (const [seriesIndex, data] of opts.getData().entries()) {\n for (const pointIndex of data.keys()) {\n const hit = hitAt(seriesIndex, pointIndex);\n\n if (hit) hits.push(hit);\n }\n }\n\n return hits.sort((a, b) => a.point.x - b.point.x || a.point.y - b.point.y || a.seriesIndex - b.seriesIndex);\n };\n\n const eventFor = (hit: Hit, originalEvent: Event): ChartEvent => ({\n datum: hit.datum,\n originalEvent,\n series: opts.getSeriesList()[hit.seriesIndex],\n });\n\n const highlight = (hit: Hit | null): void => {\n const groups = [...opts.seriesGroup.children];\n\n opts.markers.replaceChildren();\n\n if (hit) {\n opts.markers.appendChild(\n createSvgElement('circle', {\n class: 'prism-active-point',\n cx: hit.point.x,\n cy: hit.point.y,\n fill: opts.colors()[hit.seriesIndex],\n r: 4,\n }),\n );\n }\n\n opts.seriesGroup.classList.toggle('prism-series-focused', hit !== null && groups.length > 1);\n for (const [index, group] of groups.entries())\n group.classList.toggle('prism-series-active', index === hit?.seriesIndex);\n };\n\n const clear = (): void => {\n opts.crosshair?.hide();\n opts.tooltip?.hide();\n opts.announcer.clear();\n highlight(null);\n active = false;\n opts.onHover?.(null);\n };\n\n const focus = (hit: Hit, raw: Point | null, originalEvent: Event): void => {\n const dims = opts.dims();\n const area = chartArea(dims.width, dims.height, opts.margin);\n const follow = opts.crosshair?.snap === false && raw;\n const event = eventFor(hit, originalEvent);\n const { datum, series } = event;\n const label = opts.tooltipFormat?.titleFormat ? opts.tooltipFormat.titleFormat(datum.key) : keyLabel(datum.key);\n const valueFormat = opts.tooltipFormat?.valueFormat;\n const spoken = describeValues(label, [{ datum, series }], valueFormat ?? String);\n\n opts.crosshair?.show(follow ? raw.x : hit.point.x, follow ? raw.y : hit.point.y, area.width, area.height);\n highlight(hit);\n\n if (opts.tooltip) {\n const row = {\n color: opts.colors()[hit.seriesIndex],\n name: series.name,\n value: valueFormat ? valueFormat(datum.value, datum, series) : String(datum.value),\n };\n\n opts.tooltip.show(\n hit.point.x + opts.margin.left,\n hit.point.y + opts.margin.top,\n datum,\n series,\n comparisonContent(opts.svg.ownerDocument, label, [row], spoken),\n );\n } else {\n opts.announcer.announce(spoken);\n }\n\n active = true;\n opts.onHover?.(event);\n };\n\n const pointerHit = (event: MouseEvent): { hit: Hit; raw: Point } | null => {\n const rendered = renderedHit(event);\n\n if (rendered) return { hit: rendered, raw: rendered.point };\n\n const dims = opts.dims();\n const raw = getMousePosition(opts.svg, event, opts.margin.left, opts.margin.top);\n const area = chartArea(dims.width, dims.height, opts.margin);\n\n if (raw.x < 0 || raw.x > area.width || raw.y < 0 || raw.y > area.height) return null;\n\n const hit = nearestHit(raw);\n\n return hit ? { hit, raw } : null;\n };\n\n const onMouseMove = (event: MouseEvent): void => {\n const found = pointerHit(event);\n\n if (found) focus(found.hit, found.raw, event);\n else if (active) clear();\n };\n\n const onClick = (event: MouseEvent): void => {\n if (!opts.onClick) return;\n\n const found = pointerHit(event);\n\n if (found) opts.onClick(eventFor(found.hit, event));\n };\n\n const onKeyDown = (event: KeyboardEvent): void => {\n const hits = orderedHits();\n\n if (hits.length === 0) return;\n\n const last = hits.length - 1;\n const target: Record<string, number> = {\n ArrowLeft: keyboardIndex < 0 ? last : Math.max(0, keyboardIndex - 1),\n ArrowRight: keyboardIndex < 0 ? 0 : Math.min(last, keyboardIndex + 1),\n End: last,\n Home: 0,\n };\n\n if (event.key in target) {\n event.preventDefault();\n keyboardIndex = target[event.key];\n focus(hits[keyboardIndex], null, event);\n } else if ((event.key === 'Enter' || event.key === ' ') && keyboardIndex >= 0 && opts.onClick) {\n event.preventDefault();\n opts.onClick(eventFor(hits[keyboardIndex], event));\n } else if (event.key === 'Escape') {\n keyboardIndex = -1;\n clear();\n }\n };\n\n return { onClick, onKeyDown, onMouseLeave: clear, onMouseMove };\n}\n"],"mappings":"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"}
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
const e=require("../svg/element.cjs"),t=require("./announcer.cjs"),n=require("./tooltip.cjs"),r=require("../core/layout.cjs"),i=require("../core/cartesian-model.cjs"),a=require("./events.cjs");var keyOrder=e=>e instanceof Date?e.getTime():Number(e);function keyLabel(e){return e instanceof Date?e.toLocaleDateString():String(e)}function findNearestKey(e,t,n){let r=null,i=1/0;for(let a=0;a<t.length;a++)for(let o=0;o<(t[a]?.length??0);o++){let s=e[a]?.[o],c=t[a]?.[o];if(!s||!c)continue;let l=Math.abs(c.x-n);l<i&&(i=l,r=s.key)}return r}function createSeriesInteraction(o){let s=-1,c=!1,renderedHit=e=>{let t=e.target;if(!(t instanceof SVGElement)||t.tagName.toLowerCase()!==`circle`||!o.seriesGroup.contains(t)||!t.parentElement)return null;let n=t.parentElement;for(;n&&n.parentNode!==o.seriesGroup;)n=n.parentElement;if(!n)return null;let r=[...o.seriesGroup.children].indexOf(n),i=[...t.parentElement.children].indexOf(t),a=o.getData()[r]?.[i],s=o.getPoints()[r]?.[i];return a&&s?{datum:a,point:s,seriesIndex:r}:null},entriesAt=e=>{let t=i.keyId(e),n=o.getPoints();return o.getData().map((e,r)=>{let a=e.findIndex(e=>i.keyId(e.key)===t);return a===-1?{datum:void 0,point:void 0}:{datum:e[a],point:n[r]?.[a]}})},nearestIndex=(e,t)=>{let n=-1,r=1/0;return e.forEach(({point:e},i)=>{if(!e)return;let a=Number.isFinite(t)?Math.abs(e.y-t):0;a<r&&(r=a,n=i)}),n},highlight=(t,n)=>{let r=o.colors();o.markers.replaceChildren(),t.forEach(({point:t},n)=>{t&&o.markers.appendChild(e.createSvgElement(`circle`,{class:`prism-active-point`,cx:t.x,cy:t.y,fill:r[n],r:4}))});let i=[...o.seriesGroup.children];o.seriesGroup.classList.toggle(`prism-series-focused`,n>=0&&i.length>1);for(let[e,t]of i.entries())t.classList.toggle(`prism-series-active`,e===n)},clear=()=>{o.crosshair?.hide(),o.tooltip?.hide(),o.announcer.clear(),highlight([],-1),c=!1,o.onHover?.(null)},eventFor=(e,t,n)=>{let r=entriesAt(e),i=renderedHit(n),a=i?.seriesIndex??nearestIndex(r,t);i&&(r[a]={datum:i.datum,point:i.point});let s=o.getSeriesList(),c=r[a]?.datum;if(a<0||!c)return null;let l=r.map((e,t)=>({datum:e.datum,series:s[t]}));return{entries:r,event:{datum:c,originalEvent:n,series:s[a],values:l},index:a}},focus=(e,i,a)=>{let s=eventFor(e,i?.y??NaN,a);if(!s)return;let{entries:l,event:u,index:d}=s,f=l[d].point,p=o.dims(),m=r.chartArea(p.width,p.height,o.margin),h=o.crosshair?.snap===!1&&i,g=keyLabel(e),_=u.values??[],v=o.colors();o.crosshair?.show(h?i.x:f.x,h?i.y:f.y,m.width,m.height),highlight(l,d);let
|
|
1
|
+
const e=require("../svg/element.cjs"),t=require("./announcer.cjs"),n=require("./tooltip.cjs"),r=require("../core/layout.cjs"),i=require("../core/cartesian-model.cjs"),a=require("./events.cjs");var keyOrder=e=>e instanceof Date?e.getTime():Number(e);function keyLabel(e){return e instanceof Date?e.toLocaleDateString():String(e)}function findNearestKey(e,t,n){let r=null,i=1/0;for(let a=0;a<t.length;a++)for(let o=0;o<(t[a]?.length??0);o++){let s=e[a]?.[o],c=t[a]?.[o];if(!s||!c)continue;let l=Math.abs(c.x-n);l<i&&(i=l,r=s.key)}return r}function createSeriesInteraction(o){let s=-1,c=!1,renderedHit=e=>{let t=e.target;if(!(t instanceof SVGElement)||t.tagName.toLowerCase()!==`circle`||!o.seriesGroup.contains(t)||!t.parentElement)return null;let n=t.parentElement;for(;n&&n.parentNode!==o.seriesGroup;)n=n.parentElement;if(!n)return null;let r=[...o.seriesGroup.children].indexOf(n),i=[...t.parentElement.children].indexOf(t),a=o.getData()[r]?.[i],s=o.getPoints()[r]?.[i];return a&&s?{datum:a,point:s,seriesIndex:r}:null},entriesAt=e=>{let t=i.keyId(e),n=o.getPoints();return o.getData().map((e,r)=>{let a=e.findIndex(e=>i.keyId(e.key)===t);return a===-1?{datum:void 0,point:void 0}:{datum:e[a],point:n[r]?.[a]}})},nearestIndex=(e,t)=>{let n=-1,r=1/0;return e.forEach(({point:e},i)=>{if(!e)return;let a=Number.isFinite(t)?Math.abs(e.y-t):0;a<r&&(r=a,n=i)}),n},highlight=(t,n)=>{let r=o.colors();o.markers.replaceChildren(),t.forEach(({point:t},n)=>{t&&o.markers.appendChild(e.createSvgElement(`circle`,{class:`prism-active-point`,cx:t.x,cy:t.y,fill:r[n],r:4}))});let i=[...o.seriesGroup.children];o.seriesGroup.classList.toggle(`prism-series-focused`,n>=0&&i.length>1);for(let[e,t]of i.entries())t.classList.toggle(`prism-series-active`,e===n)},clear=()=>{o.crosshair?.hide(),o.tooltip?.hide(),o.announcer.clear(),highlight([],-1),c=!1,o.onHover?.(null)},eventFor=(e,t,n)=>{let r=entriesAt(e),i=renderedHit(n),a=i?.seriesIndex??nearestIndex(r,t);i&&(r[a]={datum:i.datum,point:i.point});let s=o.getSeriesList(),c=r[a]?.datum;if(a<0||!c)return null;let l=r.map((e,t)=>({datum:e.datum,series:s[t]}));return{entries:r,event:{datum:c,originalEvent:n,series:s[a],values:l},index:a}},focus=(e,i,a)=>{let s=eventFor(e,i?.y??NaN,a);if(!s)return;let{entries:l,event:u,index:d}=s,f=l[d].point,p=o.dims(),m=r.chartArea(p.width,p.height,o.margin),h=o.crosshair?.snap===!1&&i,g=o.tooltipFormat?.titleFormat?o.tooltipFormat.titleFormat(e):keyLabel(e),_=u.values??[],v=o.colors(),y=o.tooltipFormat?.valueFormat;o.crosshair?.show(h?i.x:f.x,h?i.y:f.y,m.width,m.height),highlight(l,d);let b=t.describeValues(g,_,y??String);if(o.tooltip){let e=_.flatMap(({datum:e,series:t},n)=>e?[{color:v[n],name:t.name,value:y?y(e.value,e,t):String(e.value)}]:[]);o.tooltip.show(f.x+o.margin.left,f.y+o.margin.top,u.datum,u.series,n.comparisonContent(o.svg.ownerDocument,g,e,b))}else o.announcer.announce(b);c=!0,o.onHover?.(u)},pointerKey=e=>{let t=renderedHit(e);if(t)return{key:t.datum.key,pos:t.point};let n=o.getPoints();if(n.every(e=>e.length===0))return null;let i=o.dims(),s=a.getMousePosition(o.svg,e,o.margin.left,o.margin.top),c=r.chartArea(i.width,i.height,o.margin);if(s.x<0||s.x>c.width||s.y<0||s.y>c.height)return null;let l=findNearestKey(o.getData(),n,s.x);return l===null?null:{key:l,pos:s}},sortedKeys=()=>[...new Map(o.getData().flat().map(e=>[i.keyId(e.key),e.key])).values()].sort((e,t)=>keyOrder(e)-keyOrder(t)),onMouseMove=e=>{let t=pointerKey(e);t?focus(t.key,t.pos,e):c&&clear()},onClick=e=>{if(!o.onClick)return;let t=pointerKey(e),n=t&&eventFor(t.key,t.pos.y,e);n&&o.onClick(n.event)},onKeyDown=e=>{let t=sortedKeys();if(t.length===0)return;let n=t.length-1,r={ArrowLeft:s<0?n:Math.max(0,s-1),ArrowRight:s<0?0:Math.min(n,s+1),End:n,Home:0};if(e.key in r)e.preventDefault(),s=r[e.key],focus(t[s],null,e);else if((e.key===`Enter`||e.key===` `)&&s>=0&&o.onClick){e.preventDefault();let n=eventFor(t[s],NaN,e);n&&o.onClick(n.event)}else e.key===`Escape`&&(s=-1,clear())};return{onClick,onKeyDown,onMouseLeave:clear,onMouseMove}}function ensureMarkerGroup(t,n){if(n&&t.contains(n))return n;let r=e.createSvgElement(`g`,{"aria-hidden":`true`,class:`prism-active-points`,"pointer-events":`none`});return t.appendChild(r),r}exports.createSeriesInteraction=createSeriesInteraction,exports.ensureMarkerGroup=ensureMarkerGroup,exports.keyLabel=keyLabel;
|
|
2
2
|
//# sourceMappingURL=series-interaction.cjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"series-interaction.cjs","names":[],"sources":["../../src/interaction/series-interaction.ts"],"sourcesContent":["import { keyId } from '../core/cartesian-model';\nimport type { ChartEventHandlers } from '../core/chart-scaffold';\nimport { chartArea } from '../core/layout';\nimport { createSvgElement } from '../svg/element';\nimport type { Point } from '../svg/path';\nimport type { ChartDimensions, ChartEvent, ChartMargin, Datum, Series, SeriesValue } from '../types';\nimport { type Announcer, describeValues } from './announcer';\nimport type { CrosshairState } from './crosshair';\nimport { getMousePosition } from './events';\nimport { comparisonContent, type TooltipState } from './tooltip';\n\nexport interface SeriesInteractionOptions {\n announcer: Announcer;\n colors: () => string[];\n crosshair?: CrosshairState | null;\n dims: () => ChartDimensions;\n getData: () => Datum[][];\n getPoints: () => Point[][];\n getSeriesList: () => Series[];\n /** Effective chart margin (including any dual-axis widening): hit bounds and tooltip offsets use this, never the shared dimensions. */\n margin: ChartMargin;\n /** Receives one marker per series at the active key. */\n markers: SVGGElement;\n onClick?: ((event: ChartEvent) => void) | undefined;\n onHover?: ((event: ChartEvent | null) => void) | undefined;\n /** Parent of the per-series groups; the nearest series is emphasised and the rest dim. */\n seriesGroup: SVGGElement;\n svg: SVGSVGElement;\n tooltip?: TooltipState | null;\n}\n\ninterface KeyEntry {\n datum: Datum | undefined;\n point: Point | undefined;\n}\n\nconst keyOrder = (key: Datum['key']): number => (key instanceof Date ? key.getTime() : Number(key));\n\nexport function keyLabel(key: Datum['key']): string {\n return key instanceof Date ? key.toLocaleDateString() : String(key);\n}\n\nfunction findNearestKey(allData: Datum[][], allPoints: Point[][], posX: number): Datum['key'] | null {\n let nearest: Datum['key'] | null = null;\n let minXDistance = Infinity;\n\n for (let seriesIndex = 0; seriesIndex < allPoints.length; seriesIndex++) {\n for (let datumIndex = 0; datumIndex < (allPoints[seriesIndex]?.length ?? 0); datumIndex++) {\n const datum = allData[seriesIndex]?.[datumIndex];\n const point = allPoints[seriesIndex]?.[datumIndex];\n\n if (!datum || !point) continue;\n\n const distance = Math.abs(point.x - posX);\n\n if (distance < minXDistance) {\n minXDistance = distance;\n nearest = datum.key;\n }\n }\n }\n\n return nearest;\n}\n\nexport function createSeriesInteraction(opts: SeriesInteractionOptions): ChartEventHandlers {\n let keyboardIndex = -1;\n let active = false;\n\n const renderedHit = (event: Event): { datum: Datum; point: Point; seriesIndex: number } | null => {\n const target = event.target;\n if (\n !(target instanceof SVGElement) ||\n target.tagName.toLowerCase() !== 'circle' ||\n !opts.seriesGroup.contains(target) ||\n !target.parentElement\n )\n return null;\n let group: Element | null = target.parentElement;\n while (group && group.parentNode !== opts.seriesGroup) group = group.parentElement;\n if (!group) return null;\n const seriesIndex = [...opts.seriesGroup.children].indexOf(group);\n const datumIndex = [...target.parentElement.children].indexOf(target);\n const datum = opts.getData()[seriesIndex]?.[datumIndex];\n const point = opts.getPoints()[seriesIndex]?.[datumIndex];\n return datum && point ? { datum, point, seriesIndex } : null;\n };\n\n const entriesAt = (key: Datum['key']): KeyEntry[] => {\n const id = keyId(key);\n const allPoints = opts.getPoints();\n\n return opts.getData().map((data, seriesIndex) => {\n const datumIndex = data.findIndex((datum) => keyId(datum.key) === id);\n\n return datumIndex === -1\n ? { datum: undefined, point: undefined }\n : { datum: data[datumIndex], point: allPoints[seriesIndex]?.[datumIndex] };\n });\n };\n\n const nearestIndex = (entries: KeyEntry[], posY: number): number => {\n let best = -1;\n let minDistance = Infinity;\n\n entries.forEach(({ point }, index) => {\n if (!point) return;\n\n const distance = Number.isFinite(posY) ? Math.abs(point.y - posY) : 0;\n\n if (distance < minDistance) {\n minDistance = distance;\n best = index;\n }\n });\n\n return best;\n };\n\n const highlight = (entries: KeyEntry[], emphasised: number): void => {\n const colors = opts.colors();\n\n opts.markers.replaceChildren();\n entries.forEach(({ point }, index) => {\n if (!point) return;\n\n opts.markers.appendChild(\n createSvgElement('circle', {\n class: 'prism-active-point',\n cx: point.x,\n cy: point.y,\n fill: colors[index],\n r: 4,\n }),\n );\n });\n\n const groups = [...opts.seriesGroup.children];\n\n opts.seriesGroup.classList.toggle('prism-series-focused', emphasised >= 0 && groups.length > 1);\n for (const [index, group] of groups.entries()) group.classList.toggle('prism-series-active', index === emphasised);\n };\n\n const clear = (): void => {\n opts.crosshair?.hide();\n opts.tooltip?.hide();\n opts.announcer.clear();\n highlight([], -1);\n active = false;\n opts.onHover?.(null);\n };\n\n const eventFor = (\n key: Datum['key'],\n posY: number,\n originalEvent: Event,\n ): { entries: KeyEntry[]; event: ChartEvent; index: number } | null => {\n const entries = entriesAt(key);\n const hit = renderedHit(originalEvent);\n const index = hit?.seriesIndex ?? nearestIndex(entries, posY);\n if (hit) entries[index] = { datum: hit.datum, point: hit.point };\n const seriesList = opts.getSeriesList();\n const datum = entries[index]?.datum;\n\n if (index < 0 || !datum) return null;\n\n const values: SeriesValue[] = entries.map((entry, i) => ({ datum: entry.datum, series: seriesList[i] }));\n\n return { entries, event: { datum, originalEvent, series: seriesList[index], values }, index };\n };\n\n const focus = (key: Datum['key'], pos: Point | null, originalEvent: Event): void => {\n const found = eventFor(key, pos?.y ?? Number.NaN, originalEvent);\n\n if (!found) return;\n\n const { entries, event, index } = found;\n const point = entries[index].point!;\n const dims = opts.dims();\n const area = chartArea(dims.width, dims.height, opts.margin);\n const raw = opts.crosshair?.snap === false && pos;\n const label = keyLabel(key);\n const values = event.values ?? [];\n const colors = opts.colors();\n\n opts.crosshair?.show(raw ? pos.x : point.x, raw ? pos.y : point.y, area.width, area.height);\n highlight(entries, index);\n\n const spoken = describeValues(label, values);\n\n if (opts.tooltip) {\n const rows = values.flatMap(({ datum, series }, i) =>\n datum ? [{ color: colors[i], name: series.name, value: String(datum.value) }] : [],\n );\n\n opts.tooltip.show(\n point.x + opts.margin.left,\n point.y + opts.margin.top,\n event.datum,\n event.series,\n comparisonContent(opts.svg.ownerDocument, label, rows, spoken),\n );\n } else {\n opts.announcer.announce(spoken);\n }\n\n active = true;\n opts.onHover?.(event);\n };\n\n const pointerKey = (event: MouseEvent): { key: Datum['key']; pos: Point } | null => {\n const rendered = renderedHit(event);\n if (rendered) return { key: rendered.datum.key, pos: rendered.point };\n const allPoints = opts.getPoints();\n\n if (allPoints.every((points) => points.length === 0)) return null;\n\n const dims = opts.dims();\n const pos = getMousePosition(opts.svg, event, opts.margin.left, opts.margin.top);\n const area = chartArea(dims.width, dims.height, opts.margin);\n\n if (pos.x < 0 || pos.x > area.width || pos.y < 0 || pos.y > area.height) return null;\n\n const key = findNearestKey(opts.getData(), allPoints, pos.x);\n\n return key === null ? null : { key, pos };\n };\n\n const sortedKeys = (): Datum['key'][] =>\n [\n ...new Map(\n opts\n .getData()\n .flat()\n .map((datum) => [keyId(datum.key), datum.key]),\n ).values(),\n ].sort((a, b) => keyOrder(a) - keyOrder(b));\n\n const onMouseMove = (event: MouseEvent): void => {\n const hit = pointerKey(event);\n\n if (hit) focus(hit.key, hit.pos, event);\n else if (active) clear();\n };\n\n const onClick = (event: MouseEvent): void => {\n if (!opts.onClick) return;\n\n const hit = pointerKey(event);\n const found = hit && eventFor(hit.key, hit.pos.y, event);\n\n if (found) opts.onClick(found.event);\n };\n\n const onKeyDown = (event: KeyboardEvent): void => {\n const keys = sortedKeys();\n\n if (keys.length === 0) return;\n\n const last = keys.length - 1;\n const target: Record<string, number> = {\n ArrowLeft: keyboardIndex < 0 ? last : Math.max(0, keyboardIndex - 1),\n ArrowRight: keyboardIndex < 0 ? 0 : Math.min(last, keyboardIndex + 1),\n End: last,\n Home: 0,\n };\n\n if (event.key in target) {\n event.preventDefault();\n keyboardIndex = target[event.key];\n focus(keys[keyboardIndex], null, event);\n } else if ((event.key === 'Enter' || event.key === ' ') && keyboardIndex >= 0 && opts.onClick) {\n event.preventDefault();\n\n const found = eventFor(keys[keyboardIndex], Number.NaN, event);\n\n if (found) opts.onClick(found.event);\n } else if (event.key === 'Escape') {\n keyboardIndex = -1;\n clear();\n }\n };\n\n return { onClick, onKeyDown, onMouseLeave: clear, onMouseMove };\n}\n\n/** Group for the per-series active markers, reused across renders. */\nexport function ensureMarkerGroup(parent: SVGGElement, current: SVGGElement | null): SVGGElement {\n if (current && parent.contains(current)) return current;\n\n const group = createSvgElement('g', {\n 'aria-hidden': 'true',\n class: 'prism-active-points',\n 'pointer-events': 'none',\n });\n\n parent.appendChild(group);\n\n return group;\n}\n"],"mappings":"iMAoCA,IAAM,SAAY,GAA+B,aAAe,KAAO,EAAI,QAAQ,EAAI,OAAO,CAAG,EAEjG,SAAgB,SAAS,EAA2B,CAClD,OAAO,aAAe,KAAO,EAAI,mBAAmB,EAAI,OAAO,CAAG,CACpE,CAEA,SAAS,eAAe,EAAoB,EAAsB,EAAmC,CACnG,IAAI,EAA+B,KAC/B,EAAe,IAEnB,IAAK,IAAI,EAAc,EAAG,EAAc,EAAU,OAAQ,IACxD,IAAK,IAAI,EAAa,EAAG,GAAc,EAAU,EAAY,EAAE,QAAU,GAAI,IAAc,CACzF,IAAM,EAAQ,EAAQ,EAAY,GAAG,GAC/B,EAAQ,EAAU,EAAY,GAAG,GAEvC,GAAI,CAAC,GAAS,CAAC,EAAO,SAEtB,IAAM,EAAW,KAAK,IAAI,EAAM,EAAI,CAAI,EAEpC,EAAW,IACb,EAAe,EACf,EAAU,EAAM,IAEpB,CAGF,OAAO,CACT,CAEA,SAAgB,wBAAwB,EAAoD,CAC1F,IAAI,EAAgB,GAChB,EAAS,GAEP,YAAe,GAA6E,CAChG,IAAM,EAAS,EAAM,OACrB,GACE,EAAE,aAAkB,aACpB,EAAO,QAAQ,YAAY,IAAM,UACjC,CAAC,EAAK,YAAY,SAAS,CAAM,GACjC,CAAC,EAAO,cAER,OAAO,KACT,IAAI,EAAwB,EAAO,cACnC,KAAO,GAAS,EAAM,aAAe,EAAK,aAAa,EAAQ,EAAM,cACrE,GAAI,CAAC,EAAO,OAAO,KACnB,IAAM,EAAc,CAAC,GAAG,EAAK,YAAY,QAAQ,CAAC,CAAC,QAAQ,CAAK,EAC1D,EAAa,CAAC,GAAG,EAAO,cAAc,QAAQ,CAAC,CAAC,QAAQ,CAAM,EAC9D,EAAQ,EAAK,QAAQ,CAAC,CAAC,EAAY,GAAG,GACtC,EAAQ,EAAK,UAAU,CAAC,CAAC,EAAY,GAAG,GAC9C,OAAO,GAAS,EAAQ,CAAE,QAAO,QAAO,aAAY,EAAI,IAC1D,EAEM,UAAa,GAAkC,CACnD,IAAM,EAAK,EAAA,MAAM,CAAG,EACd,EAAY,EAAK,UAAU,EAEjC,OAAO,EAAK,QAAQ,CAAC,CAAC,KAAK,EAAM,IAAgB,CAC/C,IAAM,EAAa,EAAK,UAAW,GAAU,EAAA,MAAM,EAAM,GAAG,IAAM,CAAE,EAEpE,OAAO,IAAe,GAClB,CAAE,MAAO,IAAA,GAAW,MAAO,IAAA,EAAU,EACrC,CAAE,MAAO,EAAK,GAAa,MAAO,EAAU,EAAY,GAAG,EAAY,CAC7E,CAAC,CACH,EAEM,cAAgB,EAAqB,IAAyB,CAClE,IAAI,EAAO,GACP,EAAc,IAalB,OAXA,EAAQ,SAAS,CAAE,SAAS,IAAU,CACpC,GAAI,CAAC,EAAO,OAEZ,IAAM,EAAW,OAAO,SAAS,CAAI,EAAI,KAAK,IAAI,EAAM,EAAI,CAAI,EAAI,EAEhE,EAAW,IACb,EAAc,EACd,EAAO,EAEX,CAAC,EAEM,CACT,EAEM,WAAa,EAAqB,IAA6B,CACnE,IAAM,EAAS,EAAK,OAAO,EAE3B,EAAK,QAAQ,gBAAgB,EAC7B,EAAQ,SAAS,CAAE,SAAS,IAAU,CAC/B,GAEL,EAAK,QAAQ,YACX,EAAA,iBAAiB,SAAU,CACzB,MAAO,qBACP,GAAI,EAAM,EACV,GAAI,EAAM,EACV,KAAM,EAAO,GACb,EAAG,CACL,CAAC,CACH,CACF,CAAC,EAED,IAAM,EAAS,CAAC,GAAG,EAAK,YAAY,QAAQ,EAE5C,EAAK,YAAY,UAAU,OAAO,uBAAwB,GAAc,GAAK,EAAO,OAAS,CAAC,EAC9F,IAAK,GAAM,CAAC,EAAO,KAAU,EAAO,QAAQ,EAAG,EAAM,UAAU,OAAO,sBAAuB,IAAU,CAAU,CACnH,EAEM,UAAoB,CACxB,EAAK,WAAW,KAAK,EACrB,EAAK,SAAS,KAAK,EACnB,EAAK,UAAU,MAAM,EACrB,UAAU,CAAC,EAAG,EAAE,EAChB,EAAS,GACT,EAAK,UAAU,IAAI,CACrB,EAEM,UACJ,EACA,EACA,IACqE,CACrE,IAAM,EAAU,UAAU,CAAG,EACvB,EAAM,YAAY,CAAa,EAC/B,EAAQ,GAAK,aAAe,aAAa,EAAS,CAAI,EACxD,IAAK,EAAQ,GAAS,CAAE,MAAO,EAAI,MAAO,MAAO,EAAI,KAAM,GAC/D,IAAM,EAAa,EAAK,cAAc,EAChC,EAAQ,EAAQ,EAAM,EAAE,MAE9B,GAAI,EAAQ,GAAK,CAAC,EAAO,OAAO,KAEhC,IAAM,EAAwB,EAAQ,KAAK,EAAO,KAAO,CAAE,MAAO,EAAM,MAAO,OAAQ,EAAW,EAAG,EAAE,EAEvG,MAAO,CAAE,UAAS,MAAO,CAAE,QAAO,gBAAe,OAAQ,EAAW,GAAQ,QAAO,EAAG,OAAM,CAC9F,EAEM,OAAS,EAAmB,EAAmB,IAA+B,CAClF,IAAM,EAAQ,SAAS,EAAK,GAAK,GAAK,IAAY,CAAa,EAE/D,GAAI,CAAC,EAAO,OAEZ,GAAM,CAAE,UAAS,QAAO,SAAU,EAC5B,EAAQ,EAAQ,EAAM,CAAC,MACvB,EAAO,EAAK,KAAK,EACjB,EAAO,EAAA,UAAU,EAAK,MAAO,EAAK,OAAQ,EAAK,MAAM,EACrD,EAAM,EAAK,WAAW,OAAS,IAAS,EACxC,EAAQ,SAAS,CAAG,EACpB,EAAS,EAAM,QAAU,CAAC,EAC1B,EAAS,EAAK,OAAO,EAE3B,EAAK,WAAW,KAAK,EAAM,EAAI,EAAI,EAAM,EAAG,EAAM,EAAI,EAAI,EAAM,EAAG,EAAK,MAAO,EAAK,MAAM,EAC1F,UAAU,EAAS,CAAK,EAExB,IAAM,EAAS,EAAA,eAAe,EAAO,CAAM,EAE3C,GAAI,EAAK,QAAS,CAChB,IAAM,EAAO,EAAO,SAAS,CAAE,QAAO,UAAU,IAC9C,EAAQ,CAAC,CAAE,MAAO,EAAO,GAAI,KAAM,EAAO,KAAM,MAAO,OAAO,EAAM,KAAK,CAAE,CAAC,EAAI,CAAC,CACnF,EAEA,EAAK,QAAQ,KACX,EAAM,EAAI,EAAK,OAAO,KACtB,EAAM,EAAI,EAAK,OAAO,IACtB,EAAM,MACN,EAAM,OACN,EAAA,kBAAkB,EAAK,IAAI,cAAe,EAAO,EAAM,CAAM,CAC/D,CACF,MACE,EAAK,UAAU,SAAS,CAAM,EAGhC,EAAS,GACT,EAAK,UAAU,CAAK,CACtB,EAEM,WAAc,GAAgE,CAClF,IAAM,EAAW,YAAY,CAAK,EAClC,GAAI,EAAU,MAAO,CAAE,IAAK,EAAS,MAAM,IAAK,IAAK,EAAS,KAAM,EACpE,IAAM,EAAY,EAAK,UAAU,EAEjC,GAAI,EAAU,MAAO,GAAW,EAAO,SAAW,CAAC,EAAG,OAAO,KAE7D,IAAM,EAAO,EAAK,KAAK,EACjB,EAAM,EAAA,iBAAiB,EAAK,IAAK,EAAO,EAAK,OAAO,KAAM,EAAK,OAAO,GAAG,EACzE,EAAO,EAAA,UAAU,EAAK,MAAO,EAAK,OAAQ,EAAK,MAAM,EAE3D,GAAI,EAAI,EAAI,GAAK,EAAI,EAAI,EAAK,OAAS,EAAI,EAAI,GAAK,EAAI,EAAI,EAAK,OAAQ,OAAO,KAEhF,IAAM,EAAM,eAAe,EAAK,QAAQ,EAAG,EAAW,EAAI,CAAC,EAE3D,OAAO,IAAQ,KAAO,KAAO,CAAE,MAAK,KAAI,CAC1C,EAEM,eACJ,CACE,GAAG,IAAI,IACL,EACG,QAAQ,CAAC,CACT,KAAK,CAAC,CACN,IAAK,GAAU,CAAC,EAAA,MAAM,EAAM,GAAG,EAAG,EAAM,GAAG,CAAC,CACjD,CAAC,CAAC,OAAO,CACX,CAAC,CAAC,MAAM,EAAG,IAAM,SAAS,CAAC,EAAI,SAAS,CAAC,CAAC,EAEtC,YAAe,GAA4B,CAC/C,IAAM,EAAM,WAAW,CAAK,EAExB,EAAK,MAAM,EAAI,IAAK,EAAI,IAAK,CAAK,EAC7B,GAAQ,MAAM,CACzB,EAEM,QAAW,GAA4B,CAC3C,GAAI,CAAC,EAAK,QAAS,OAEnB,IAAM,EAAM,WAAW,CAAK,EACtB,EAAQ,GAAO,SAAS,EAAI,IAAK,EAAI,IAAI,EAAG,CAAK,EAEnD,GAAO,EAAK,QAAQ,EAAM,KAAK,CACrC,EAEM,UAAa,GAA+B,CAChD,IAAM,EAAO,WAAW,EAExB,GAAI,EAAK,SAAW,EAAG,OAEvB,IAAM,EAAO,EAAK,OAAS,EACrB,EAAiC,CACrC,UAAW,EAAgB,EAAI,EAAO,KAAK,IAAI,EAAG,EAAgB,CAAC,EACnE,WAAY,EAAgB,EAAI,EAAI,KAAK,IAAI,EAAM,EAAgB,CAAC,EACpE,IAAK,EACL,KAAM,CACR,EAEA,GAAI,EAAM,OAAO,EACf,EAAM,eAAe,EACrB,EAAgB,EAAO,EAAM,KAC7B,MAAM,EAAK,GAAgB,KAAM,CAAK,OACjC,IAAK,EAAM,MAAQ,SAAW,EAAM,MAAQ,MAAQ,GAAiB,GAAK,EAAK,QAAS,CAC7F,EAAM,eAAe,EAErB,IAAM,EAAQ,SAAS,EAAK,GAAgB,IAAY,CAAK,EAEzD,GAAO,EAAK,QAAQ,EAAM,KAAK,CACrC,MAAW,EAAM,MAAQ,WACvB,EAAgB,GAChB,MAAM,EAEV,EAEA,MAAO,CAAE,QAAS,UAAW,aAAc,MAAO,WAAY,CAChE,CAGA,SAAgB,kBAAkB,EAAqB,EAA0C,CAC/F,GAAI,GAAW,EAAO,SAAS,CAAO,EAAG,OAAO,EAEhD,IAAM,EAAQ,EAAA,iBAAiB,IAAK,CAClC,cAAe,OACf,MAAO,sBACP,iBAAkB,MACpB,CAAC,EAID,OAFA,EAAO,YAAY,CAAK,EAEjB,CACT"}
|
|
1
|
+
{"version":3,"file":"series-interaction.cjs","names":[],"sources":["../../src/interaction/series-interaction.ts"],"sourcesContent":["import { keyId } from '../core/cartesian-model';\nimport type { ChartEventHandlers } from '../core/chart-scaffold';\nimport { chartArea } from '../core/layout';\nimport { createSvgElement } from '../svg/element';\nimport type { Point } from '../svg/path';\nimport type { ChartDimensions, ChartEvent, ChartMargin, Datum, Series, SeriesValue, TooltipConfig } from '../types';\nimport { type Announcer, describeValues } from './announcer';\nimport type { CrosshairState } from './crosshair';\nimport { getMousePosition } from './events';\nimport { comparisonContent, type TooltipState } from './tooltip';\n\nexport interface SeriesInteractionOptions {\n announcer: Announcer;\n colors: () => string[];\n crosshair?: CrosshairState | null;\n dims: () => ChartDimensions;\n getData: () => Datum[][];\n getPoints: () => Point[][];\n getSeriesList: () => Series[];\n /** Effective chart margin (including any dual-axis widening): hit bounds and tooltip offsets use this, never the shared dimensions. */\n margin: ChartMargin;\n /** Receives one marker per series at the active key. */\n markers: SVGGElement;\n onClick?: ((event: ChartEvent) => void) | undefined;\n onHover?: ((event: ChartEvent | null) => void) | undefined;\n /** Parent of the per-series groups; the nearest series is emphasised and the rest dim. */\n seriesGroup: SVGGElement;\n svg: SVGSVGElement;\n tooltip?: TooltipState | null;\n /** Value/title formatters from the tooltip config, applied to rows and spoken text alike. */\n tooltipFormat?: Pick<TooltipConfig, 'titleFormat' | 'valueFormat'>;\n}\n\ninterface KeyEntry {\n datum: Datum | undefined;\n point: Point | undefined;\n}\n\nconst keyOrder = (key: Datum['key']): number => (key instanceof Date ? key.getTime() : Number(key));\n\nexport function keyLabel(key: Datum['key']): string {\n return key instanceof Date ? key.toLocaleDateString() : String(key);\n}\n\nfunction findNearestKey(allData: Datum[][], allPoints: Point[][], posX: number): Datum['key'] | null {\n let nearest: Datum['key'] | null = null;\n let minXDistance = Infinity;\n\n for (let seriesIndex = 0; seriesIndex < allPoints.length; seriesIndex++) {\n for (let datumIndex = 0; datumIndex < (allPoints[seriesIndex]?.length ?? 0); datumIndex++) {\n const datum = allData[seriesIndex]?.[datumIndex];\n const point = allPoints[seriesIndex]?.[datumIndex];\n\n if (!datum || !point) continue;\n\n const distance = Math.abs(point.x - posX);\n\n if (distance < minXDistance) {\n minXDistance = distance;\n nearest = datum.key;\n }\n }\n }\n\n return nearest;\n}\n\nexport function createSeriesInteraction(opts: SeriesInteractionOptions): ChartEventHandlers {\n let keyboardIndex = -1;\n let active = false;\n\n const renderedHit = (event: Event): { datum: Datum; point: Point; seriesIndex: number } | null => {\n const target = event.target;\n if (\n !(target instanceof SVGElement) ||\n target.tagName.toLowerCase() !== 'circle' ||\n !opts.seriesGroup.contains(target) ||\n !target.parentElement\n )\n return null;\n let group: Element | null = target.parentElement;\n while (group && group.parentNode !== opts.seriesGroup) group = group.parentElement;\n if (!group) return null;\n const seriesIndex = [...opts.seriesGroup.children].indexOf(group);\n const datumIndex = [...target.parentElement.children].indexOf(target);\n const datum = opts.getData()[seriesIndex]?.[datumIndex];\n const point = opts.getPoints()[seriesIndex]?.[datumIndex];\n return datum && point ? { datum, point, seriesIndex } : null;\n };\n\n const entriesAt = (key: Datum['key']): KeyEntry[] => {\n const id = keyId(key);\n const allPoints = opts.getPoints();\n\n return opts.getData().map((data, seriesIndex) => {\n const datumIndex = data.findIndex((datum) => keyId(datum.key) === id);\n\n return datumIndex === -1\n ? { datum: undefined, point: undefined }\n : { datum: data[datumIndex], point: allPoints[seriesIndex]?.[datumIndex] };\n });\n };\n\n const nearestIndex = (entries: KeyEntry[], posY: number): number => {\n let best = -1;\n let minDistance = Infinity;\n\n entries.forEach(({ point }, index) => {\n if (!point) return;\n\n const distance = Number.isFinite(posY) ? Math.abs(point.y - posY) : 0;\n\n if (distance < minDistance) {\n minDistance = distance;\n best = index;\n }\n });\n\n return best;\n };\n\n const highlight = (entries: KeyEntry[], emphasised: number): void => {\n const colors = opts.colors();\n\n opts.markers.replaceChildren();\n entries.forEach(({ point }, index) => {\n if (!point) return;\n\n opts.markers.appendChild(\n createSvgElement('circle', {\n class: 'prism-active-point',\n cx: point.x,\n cy: point.y,\n fill: colors[index],\n r: 4,\n }),\n );\n });\n\n const groups = [...opts.seriesGroup.children];\n\n opts.seriesGroup.classList.toggle('prism-series-focused', emphasised >= 0 && groups.length > 1);\n for (const [index, group] of groups.entries()) group.classList.toggle('prism-series-active', index === emphasised);\n };\n\n const clear = (): void => {\n opts.crosshair?.hide();\n opts.tooltip?.hide();\n opts.announcer.clear();\n highlight([], -1);\n active = false;\n opts.onHover?.(null);\n };\n\n const eventFor = (\n key: Datum['key'],\n posY: number,\n originalEvent: Event,\n ): { entries: KeyEntry[]; event: ChartEvent; index: number } | null => {\n const entries = entriesAt(key);\n const hit = renderedHit(originalEvent);\n const index = hit?.seriesIndex ?? nearestIndex(entries, posY);\n if (hit) entries[index] = { datum: hit.datum, point: hit.point };\n const seriesList = opts.getSeriesList();\n const datum = entries[index]?.datum;\n\n if (index < 0 || !datum) return null;\n\n const values: SeriesValue[] = entries.map((entry, i) => ({ datum: entry.datum, series: seriesList[i] }));\n\n return { entries, event: { datum, originalEvent, series: seriesList[index], values }, index };\n };\n\n const focus = (key: Datum['key'], pos: Point | null, originalEvent: Event): void => {\n const found = eventFor(key, pos?.y ?? Number.NaN, originalEvent);\n\n if (!found) return;\n\n const { entries, event, index } = found;\n const point = entries[index].point!;\n const dims = opts.dims();\n const area = chartArea(dims.width, dims.height, opts.margin);\n const raw = opts.crosshair?.snap === false && pos;\n const label = opts.tooltipFormat?.titleFormat ? opts.tooltipFormat.titleFormat(key) : keyLabel(key);\n const values = event.values ?? [];\n const colors = opts.colors();\n const valueFormat = opts.tooltipFormat?.valueFormat;\n\n opts.crosshair?.show(raw ? pos.x : point.x, raw ? pos.y : point.y, area.width, area.height);\n highlight(entries, index);\n\n const spoken = describeValues(label, values, valueFormat ?? String);\n\n if (opts.tooltip) {\n const rows = values.flatMap(({ datum, series }, i) =>\n datum\n ? [\n {\n color: colors[i],\n name: series.name,\n value: valueFormat ? valueFormat(datum.value, datum, series) : String(datum.value),\n },\n ]\n : [],\n );\n\n opts.tooltip.show(\n point.x + opts.margin.left,\n point.y + opts.margin.top,\n event.datum,\n event.series,\n comparisonContent(opts.svg.ownerDocument, label, rows, spoken),\n );\n } else {\n opts.announcer.announce(spoken);\n }\n\n active = true;\n opts.onHover?.(event);\n };\n\n const pointerKey = (event: MouseEvent): { key: Datum['key']; pos: Point } | null => {\n const rendered = renderedHit(event);\n if (rendered) return { key: rendered.datum.key, pos: rendered.point };\n const allPoints = opts.getPoints();\n\n if (allPoints.every((points) => points.length === 0)) return null;\n\n const dims = opts.dims();\n const pos = getMousePosition(opts.svg, event, opts.margin.left, opts.margin.top);\n const area = chartArea(dims.width, dims.height, opts.margin);\n\n if (pos.x < 0 || pos.x > area.width || pos.y < 0 || pos.y > area.height) return null;\n\n const key = findNearestKey(opts.getData(), allPoints, pos.x);\n\n return key === null ? null : { key, pos };\n };\n\n const sortedKeys = (): Datum['key'][] =>\n [\n ...new Map(\n opts\n .getData()\n .flat()\n .map((datum) => [keyId(datum.key), datum.key]),\n ).values(),\n ].sort((a, b) => keyOrder(a) - keyOrder(b));\n\n const onMouseMove = (event: MouseEvent): void => {\n const hit = pointerKey(event);\n\n if (hit) focus(hit.key, hit.pos, event);\n else if (active) clear();\n };\n\n const onClick = (event: MouseEvent): void => {\n if (!opts.onClick) return;\n\n const hit = pointerKey(event);\n const found = hit && eventFor(hit.key, hit.pos.y, event);\n\n if (found) opts.onClick(found.event);\n };\n\n const onKeyDown = (event: KeyboardEvent): void => {\n const keys = sortedKeys();\n\n if (keys.length === 0) return;\n\n const last = keys.length - 1;\n const target: Record<string, number> = {\n ArrowLeft: keyboardIndex < 0 ? last : Math.max(0, keyboardIndex - 1),\n ArrowRight: keyboardIndex < 0 ? 0 : Math.min(last, keyboardIndex + 1),\n End: last,\n Home: 0,\n };\n\n if (event.key in target) {\n event.preventDefault();\n keyboardIndex = target[event.key];\n focus(keys[keyboardIndex], null, event);\n } else if ((event.key === 'Enter' || event.key === ' ') && keyboardIndex >= 0 && opts.onClick) {\n event.preventDefault();\n\n const found = eventFor(keys[keyboardIndex], Number.NaN, event);\n\n if (found) opts.onClick(found.event);\n } else if (event.key === 'Escape') {\n keyboardIndex = -1;\n clear();\n }\n };\n\n return { onClick, onKeyDown, onMouseLeave: clear, onMouseMove };\n}\n\n/** Group for the per-series active markers, reused across renders. */\nexport function ensureMarkerGroup(parent: SVGGElement, current: SVGGElement | null): SVGGElement {\n if (current && parent.contains(current)) return current;\n\n const group = createSvgElement('g', {\n 'aria-hidden': 'true',\n class: 'prism-active-points',\n 'pointer-events': 'none',\n });\n\n parent.appendChild(group);\n\n return group;\n}\n"],"mappings":"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"}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { ChartEventHandlers } from '../core/chart-scaffold';
|
|
2
2
|
import type { Point } from '../svg/path';
|
|
3
|
-
import type { ChartDimensions, ChartEvent, ChartMargin, Datum, Series } from '../types';
|
|
3
|
+
import type { ChartDimensions, ChartEvent, ChartMargin, Datum, Series, TooltipConfig } from '../types';
|
|
4
4
|
import { type Announcer } from './announcer';
|
|
5
5
|
import type { CrosshairState } from './crosshair';
|
|
6
6
|
import { type TooltipState } from './tooltip';
|
|
@@ -22,6 +22,8 @@ export interface SeriesInteractionOptions {
|
|
|
22
22
|
seriesGroup: SVGGElement;
|
|
23
23
|
svg: SVGSVGElement;
|
|
24
24
|
tooltip?: TooltipState | null;
|
|
25
|
+
/** Value/title formatters from the tooltip config, applied to rows and spoken text alike. */
|
|
26
|
+
tooltipFormat?: Pick<TooltipConfig, 'titleFormat' | 'valueFormat'>;
|
|
25
27
|
}
|
|
26
28
|
export declare function keyLabel(key: Datum['key']): string;
|
|
27
29
|
export declare function createSeriesInteraction(opts: SeriesInteractionOptions): ChartEventHandlers;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"series-interaction.d.ts","sourceRoot":"","sources":["../../src/interaction/series-interaction.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,wBAAwB,CAAC;AAGjE,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,aAAa,CAAC;AACzC,OAAO,KAAK,EAAE,eAAe,EAAE,UAAU,EAAE,WAAW,EAAE,KAAK,EAAE,MAAM,EAAe,MAAM,UAAU,CAAC;
|
|
1
|
+
{"version":3,"file":"series-interaction.d.ts","sourceRoot":"","sources":["../../src/interaction/series-interaction.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,wBAAwB,CAAC;AAGjE,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,aAAa,CAAC;AACzC,OAAO,KAAK,EAAE,eAAe,EAAE,UAAU,EAAE,WAAW,EAAE,KAAK,EAAE,MAAM,EAAe,aAAa,EAAE,MAAM,UAAU,CAAC;AACpH,OAAO,EAAE,KAAK,SAAS,EAAkB,MAAM,aAAa,CAAC;AAC7D,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAElD,OAAO,EAAqB,KAAK,YAAY,EAAE,MAAM,WAAW,CAAC;AAEjE,MAAM,WAAW,wBAAwB;IACvC,SAAS,EAAE,SAAS,CAAC;IACrB,MAAM,EAAE,MAAM,MAAM,EAAE,CAAC;IACvB,SAAS,CAAC,EAAE,cAAc,GAAG,IAAI,CAAC;IAClC,IAAI,EAAE,MAAM,eAAe,CAAC;IAC5B,OAAO,EAAE,MAAM,KAAK,EAAE,EAAE,CAAC;IACzB,SAAS,EAAE,MAAM,KAAK,EAAE,EAAE,CAAC;IAC3B,aAAa,EAAE,MAAM,MAAM,EAAE,CAAC;IAC9B,uIAAuI;IACvI,MAAM,EAAE,WAAW,CAAC;IACpB,wDAAwD;IACxD,OAAO,EAAE,WAAW,CAAC;IACrB,OAAO,CAAC,EAAE,CAAC,CAAC,KAAK,EAAE,UAAU,KAAK,IAAI,CAAC,GAAG,SAAS,CAAC;IACpD,OAAO,CAAC,EAAE,CAAC,CAAC,KAAK,EAAE,UAAU,GAAG,IAAI,KAAK,IAAI,CAAC,GAAG,SAAS,CAAC;IAC3D,0FAA0F;IAC1F,WAAW,EAAE,WAAW,CAAC;IACzB,GAAG,EAAE,aAAa,CAAC;IACnB,OAAO,CAAC,EAAE,YAAY,GAAG,IAAI,CAAC;IAC9B,6FAA6F;IAC7F,aAAa,CAAC,EAAE,IAAI,CAAC,aAAa,EAAE,aAAa,GAAG,aAAa,CAAC,CAAC;CACpE;AASD,wBAAgB,QAAQ,CAAC,GAAG,EAAE,KAAK,CAAC,KAAK,CAAC,GAAG,MAAM,CAElD;AAyBD,wBAAgB,uBAAuB,CAAC,IAAI,EAAE,wBAAwB,GAAG,kBAAkB,CAoO1F;AAED,sEAAsE;AACtE,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,WAAW,EAAE,OAAO,EAAE,WAAW,GAAG,IAAI,GAAG,WAAW,CAY/F"}
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import{createSvgElement as e}from"../svg/element.js";import{describeValues as t}from"./announcer.js";import{comparisonContent as n}from"./tooltip.js";import{chartArea as r}from"../core/layout.js";import{keyId as i}from"../core/cartesian-model.js";import{getMousePosition as a}from"./events.js";var keyOrder=e=>e instanceof Date?e.getTime():Number(e);function keyLabel(e){return e instanceof Date?e.toLocaleDateString():String(e)}function findNearestKey(e,t,n){let r=null,i=1/0;for(let a=0;a<t.length;a++)for(let o=0;o<(t[a]?.length??0);o++){let s=e[a]?.[o],c=t[a]?.[o];if(!s||!c)continue;let l=Math.abs(c.x-n);l<i&&(i=l,r=s.key)}return r}function createSeriesInteraction(o){let s=-1,c=!1,renderedHit=e=>{let t=e.target;if(!(t instanceof SVGElement)||t.tagName.toLowerCase()!==`circle`||!o.seriesGroup.contains(t)||!t.parentElement)return null;let n=t.parentElement;for(;n&&n.parentNode!==o.seriesGroup;)n=n.parentElement;if(!n)return null;let r=[...o.seriesGroup.children].indexOf(n),i=[...t.parentElement.children].indexOf(t),a=o.getData()[r]?.[i],s=o.getPoints()[r]?.[i];return a&&s?{datum:a,point:s,seriesIndex:r}:null},entriesAt=e=>{let t=i(e),n=o.getPoints();return o.getData().map((e,r)=>{let a=e.findIndex(e=>i(e.key)===t);return a===-1?{datum:void 0,point:void 0}:{datum:e[a],point:n[r]?.[a]}})},nearestIndex=(e,t)=>{let n=-1,r=1/0;return e.forEach(({point:e},i)=>{if(!e)return;let a=Number.isFinite(t)?Math.abs(e.y-t):0;a<r&&(r=a,n=i)}),n},highlight=(t,n)=>{let r=o.colors();o.markers.replaceChildren(),t.forEach(({point:t},n)=>{t&&o.markers.appendChild(e(`circle`,{class:`prism-active-point`,cx:t.x,cy:t.y,fill:r[n],r:4}))});let i=[...o.seriesGroup.children];o.seriesGroup.classList.toggle(`prism-series-focused`,n>=0&&i.length>1);for(let[e,t]of i.entries())t.classList.toggle(`prism-series-active`,e===n)},clear=()=>{o.crosshair?.hide(),o.tooltip?.hide(),o.announcer.clear(),highlight([],-1),c=!1,o.onHover?.(null)},eventFor=(e,t,n)=>{let r=entriesAt(e),i=renderedHit(n),a=i?.seriesIndex??nearestIndex(r,t);i&&(r[a]={datum:i.datum,point:i.point});let s=o.getSeriesList(),c=r[a]?.datum;if(a<0||!c)return null;let l=r.map((e,t)=>({datum:e.datum,series:s[t]}));return{entries:r,event:{datum:c,originalEvent:n,series:s[a],values:l},index:a}},focus=(e,i,a)=>{let s=eventFor(e,i?.y??NaN,a);if(!s)return;let{entries:l,event:u,index:d}=s,f=l[d].point,p=o.dims(),m=r(p.width,p.height,o.margin),h=o.crosshair?.snap===!1&&i,g=keyLabel(e),_=u.values??[],v=o.colors();o.crosshair?.show(h?i.x:f.x,h?i.y:f.y,m.width,m.height),highlight(l,d);let
|
|
1
|
+
import{createSvgElement as e}from"../svg/element.js";import{describeValues as t}from"./announcer.js";import{comparisonContent as n}from"./tooltip.js";import{chartArea as r}from"../core/layout.js";import{keyId as i}from"../core/cartesian-model.js";import{getMousePosition as a}from"./events.js";var keyOrder=e=>e instanceof Date?e.getTime():Number(e);function keyLabel(e){return e instanceof Date?e.toLocaleDateString():String(e)}function findNearestKey(e,t,n){let r=null,i=1/0;for(let a=0;a<t.length;a++)for(let o=0;o<(t[a]?.length??0);o++){let s=e[a]?.[o],c=t[a]?.[o];if(!s||!c)continue;let l=Math.abs(c.x-n);l<i&&(i=l,r=s.key)}return r}function createSeriesInteraction(o){let s=-1,c=!1,renderedHit=e=>{let t=e.target;if(!(t instanceof SVGElement)||t.tagName.toLowerCase()!==`circle`||!o.seriesGroup.contains(t)||!t.parentElement)return null;let n=t.parentElement;for(;n&&n.parentNode!==o.seriesGroup;)n=n.parentElement;if(!n)return null;let r=[...o.seriesGroup.children].indexOf(n),i=[...t.parentElement.children].indexOf(t),a=o.getData()[r]?.[i],s=o.getPoints()[r]?.[i];return a&&s?{datum:a,point:s,seriesIndex:r}:null},entriesAt=e=>{let t=i(e),n=o.getPoints();return o.getData().map((e,r)=>{let a=e.findIndex(e=>i(e.key)===t);return a===-1?{datum:void 0,point:void 0}:{datum:e[a],point:n[r]?.[a]}})},nearestIndex=(e,t)=>{let n=-1,r=1/0;return e.forEach(({point:e},i)=>{if(!e)return;let a=Number.isFinite(t)?Math.abs(e.y-t):0;a<r&&(r=a,n=i)}),n},highlight=(t,n)=>{let r=o.colors();o.markers.replaceChildren(),t.forEach(({point:t},n)=>{t&&o.markers.appendChild(e(`circle`,{class:`prism-active-point`,cx:t.x,cy:t.y,fill:r[n],r:4}))});let i=[...o.seriesGroup.children];o.seriesGroup.classList.toggle(`prism-series-focused`,n>=0&&i.length>1);for(let[e,t]of i.entries())t.classList.toggle(`prism-series-active`,e===n)},clear=()=>{o.crosshair?.hide(),o.tooltip?.hide(),o.announcer.clear(),highlight([],-1),c=!1,o.onHover?.(null)},eventFor=(e,t,n)=>{let r=entriesAt(e),i=renderedHit(n),a=i?.seriesIndex??nearestIndex(r,t);i&&(r[a]={datum:i.datum,point:i.point});let s=o.getSeriesList(),c=r[a]?.datum;if(a<0||!c)return null;let l=r.map((e,t)=>({datum:e.datum,series:s[t]}));return{entries:r,event:{datum:c,originalEvent:n,series:s[a],values:l},index:a}},focus=(e,i,a)=>{let s=eventFor(e,i?.y??NaN,a);if(!s)return;let{entries:l,event:u,index:d}=s,f=l[d].point,p=o.dims(),m=r(p.width,p.height,o.margin),h=o.crosshair?.snap===!1&&i,g=o.tooltipFormat?.titleFormat?o.tooltipFormat.titleFormat(e):keyLabel(e),_=u.values??[],v=o.colors(),y=o.tooltipFormat?.valueFormat;o.crosshair?.show(h?i.x:f.x,h?i.y:f.y,m.width,m.height),highlight(l,d);let b=t(g,_,y??String);if(o.tooltip){let e=_.flatMap(({datum:e,series:t},n)=>e?[{color:v[n],name:t.name,value:y?y(e.value,e,t):String(e.value)}]:[]);o.tooltip.show(f.x+o.margin.left,f.y+o.margin.top,u.datum,u.series,n(o.svg.ownerDocument,g,e,b))}else o.announcer.announce(b);c=!0,o.onHover?.(u)},pointerKey=e=>{let t=renderedHit(e);if(t)return{key:t.datum.key,pos:t.point};let n=o.getPoints();if(n.every(e=>e.length===0))return null;let i=o.dims(),s=a(o.svg,e,o.margin.left,o.margin.top),c=r(i.width,i.height,o.margin);if(s.x<0||s.x>c.width||s.y<0||s.y>c.height)return null;let l=findNearestKey(o.getData(),n,s.x);return l===null?null:{key:l,pos:s}},sortedKeys=()=>[...new Map(o.getData().flat().map(e=>[i(e.key),e.key])).values()].sort((e,t)=>keyOrder(e)-keyOrder(t)),onMouseMove=e=>{let t=pointerKey(e);t?focus(t.key,t.pos,e):c&&clear()},onClick=e=>{if(!o.onClick)return;let t=pointerKey(e),n=t&&eventFor(t.key,t.pos.y,e);n&&o.onClick(n.event)},onKeyDown=e=>{let t=sortedKeys();if(t.length===0)return;let n=t.length-1,r={ArrowLeft:s<0?n:Math.max(0,s-1),ArrowRight:s<0?0:Math.min(n,s+1),End:n,Home:0};if(e.key in r)e.preventDefault(),s=r[e.key],focus(t[s],null,e);else if((e.key===`Enter`||e.key===` `)&&s>=0&&o.onClick){e.preventDefault();let n=eventFor(t[s],NaN,e);n&&o.onClick(n.event)}else e.key===`Escape`&&(s=-1,clear())};return{onClick,onKeyDown,onMouseLeave:clear,onMouseMove}}function ensureMarkerGroup(t,n){if(n&&t.contains(n))return n;let r=e(`g`,{"aria-hidden":`true`,class:`prism-active-points`,"pointer-events":`none`});return t.appendChild(r),r}export{createSeriesInteraction,ensureMarkerGroup,keyLabel};
|
|
2
2
|
//# sourceMappingURL=series-interaction.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"series-interaction.js","names":[],"sources":["../../src/interaction/series-interaction.ts"],"sourcesContent":["import { keyId } from '../core/cartesian-model';\nimport type { ChartEventHandlers } from '../core/chart-scaffold';\nimport { chartArea } from '../core/layout';\nimport { createSvgElement } from '../svg/element';\nimport type { Point } from '../svg/path';\nimport type { ChartDimensions, ChartEvent, ChartMargin, Datum, Series, SeriesValue } from '../types';\nimport { type Announcer, describeValues } from './announcer';\nimport type { CrosshairState } from './crosshair';\nimport { getMousePosition } from './events';\nimport { comparisonContent, type TooltipState } from './tooltip';\n\nexport interface SeriesInteractionOptions {\n announcer: Announcer;\n colors: () => string[];\n crosshair?: CrosshairState | null;\n dims: () => ChartDimensions;\n getData: () => Datum[][];\n getPoints: () => Point[][];\n getSeriesList: () => Series[];\n /** Effective chart margin (including any dual-axis widening): hit bounds and tooltip offsets use this, never the shared dimensions. */\n margin: ChartMargin;\n /** Receives one marker per series at the active key. */\n markers: SVGGElement;\n onClick?: ((event: ChartEvent) => void) | undefined;\n onHover?: ((event: ChartEvent | null) => void) | undefined;\n /** Parent of the per-series groups; the nearest series is emphasised and the rest dim. */\n seriesGroup: SVGGElement;\n svg: SVGSVGElement;\n tooltip?: TooltipState | null;\n}\n\ninterface KeyEntry {\n datum: Datum | undefined;\n point: Point | undefined;\n}\n\nconst keyOrder = (key: Datum['key']): number => (key instanceof Date ? key.getTime() : Number(key));\n\nexport function keyLabel(key: Datum['key']): string {\n return key instanceof Date ? key.toLocaleDateString() : String(key);\n}\n\nfunction findNearestKey(allData: Datum[][], allPoints: Point[][], posX: number): Datum['key'] | null {\n let nearest: Datum['key'] | null = null;\n let minXDistance = Infinity;\n\n for (let seriesIndex = 0; seriesIndex < allPoints.length; seriesIndex++) {\n for (let datumIndex = 0; datumIndex < (allPoints[seriesIndex]?.length ?? 0); datumIndex++) {\n const datum = allData[seriesIndex]?.[datumIndex];\n const point = allPoints[seriesIndex]?.[datumIndex];\n\n if (!datum || !point) continue;\n\n const distance = Math.abs(point.x - posX);\n\n if (distance < minXDistance) {\n minXDistance = distance;\n nearest = datum.key;\n }\n }\n }\n\n return nearest;\n}\n\nexport function createSeriesInteraction(opts: SeriesInteractionOptions): ChartEventHandlers {\n let keyboardIndex = -1;\n let active = false;\n\n const renderedHit = (event: Event): { datum: Datum; point: Point; seriesIndex: number } | null => {\n const target = event.target;\n if (\n !(target instanceof SVGElement) ||\n target.tagName.toLowerCase() !== 'circle' ||\n !opts.seriesGroup.contains(target) ||\n !target.parentElement\n )\n return null;\n let group: Element | null = target.parentElement;\n while (group && group.parentNode !== opts.seriesGroup) group = group.parentElement;\n if (!group) return null;\n const seriesIndex = [...opts.seriesGroup.children].indexOf(group);\n const datumIndex = [...target.parentElement.children].indexOf(target);\n const datum = opts.getData()[seriesIndex]?.[datumIndex];\n const point = opts.getPoints()[seriesIndex]?.[datumIndex];\n return datum && point ? { datum, point, seriesIndex } : null;\n };\n\n const entriesAt = (key: Datum['key']): KeyEntry[] => {\n const id = keyId(key);\n const allPoints = opts.getPoints();\n\n return opts.getData().map((data, seriesIndex) => {\n const datumIndex = data.findIndex((datum) => keyId(datum.key) === id);\n\n return datumIndex === -1\n ? { datum: undefined, point: undefined }\n : { datum: data[datumIndex], point: allPoints[seriesIndex]?.[datumIndex] };\n });\n };\n\n const nearestIndex = (entries: KeyEntry[], posY: number): number => {\n let best = -1;\n let minDistance = Infinity;\n\n entries.forEach(({ point }, index) => {\n if (!point) return;\n\n const distance = Number.isFinite(posY) ? Math.abs(point.y - posY) : 0;\n\n if (distance < minDistance) {\n minDistance = distance;\n best = index;\n }\n });\n\n return best;\n };\n\n const highlight = (entries: KeyEntry[], emphasised: number): void => {\n const colors = opts.colors();\n\n opts.markers.replaceChildren();\n entries.forEach(({ point }, index) => {\n if (!point) return;\n\n opts.markers.appendChild(\n createSvgElement('circle', {\n class: 'prism-active-point',\n cx: point.x,\n cy: point.y,\n fill: colors[index],\n r: 4,\n }),\n );\n });\n\n const groups = [...opts.seriesGroup.children];\n\n opts.seriesGroup.classList.toggle('prism-series-focused', emphasised >= 0 && groups.length > 1);\n for (const [index, group] of groups.entries()) group.classList.toggle('prism-series-active', index === emphasised);\n };\n\n const clear = (): void => {\n opts.crosshair?.hide();\n opts.tooltip?.hide();\n opts.announcer.clear();\n highlight([], -1);\n active = false;\n opts.onHover?.(null);\n };\n\n const eventFor = (\n key: Datum['key'],\n posY: number,\n originalEvent: Event,\n ): { entries: KeyEntry[]; event: ChartEvent; index: number } | null => {\n const entries = entriesAt(key);\n const hit = renderedHit(originalEvent);\n const index = hit?.seriesIndex ?? nearestIndex(entries, posY);\n if (hit) entries[index] = { datum: hit.datum, point: hit.point };\n const seriesList = opts.getSeriesList();\n const datum = entries[index]?.datum;\n\n if (index < 0 || !datum) return null;\n\n const values: SeriesValue[] = entries.map((entry, i) => ({ datum: entry.datum, series: seriesList[i] }));\n\n return { entries, event: { datum, originalEvent, series: seriesList[index], values }, index };\n };\n\n const focus = (key: Datum['key'], pos: Point | null, originalEvent: Event): void => {\n const found = eventFor(key, pos?.y ?? Number.NaN, originalEvent);\n\n if (!found) return;\n\n const { entries, event, index } = found;\n const point = entries[index].point!;\n const dims = opts.dims();\n const area = chartArea(dims.width, dims.height, opts.margin);\n const raw = opts.crosshair?.snap === false && pos;\n const label = keyLabel(key);\n const values = event.values ?? [];\n const colors = opts.colors();\n\n opts.crosshair?.show(raw ? pos.x : point.x, raw ? pos.y : point.y, area.width, area.height);\n highlight(entries, index);\n\n const spoken = describeValues(label, values);\n\n if (opts.tooltip) {\n const rows = values.flatMap(({ datum, series }, i) =>\n datum ? [{ color: colors[i], name: series.name, value: String(datum.value) }] : [],\n );\n\n opts.tooltip.show(\n point.x + opts.margin.left,\n point.y + opts.margin.top,\n event.datum,\n event.series,\n comparisonContent(opts.svg.ownerDocument, label, rows, spoken),\n );\n } else {\n opts.announcer.announce(spoken);\n }\n\n active = true;\n opts.onHover?.(event);\n };\n\n const pointerKey = (event: MouseEvent): { key: Datum['key']; pos: Point } | null => {\n const rendered = renderedHit(event);\n if (rendered) return { key: rendered.datum.key, pos: rendered.point };\n const allPoints = opts.getPoints();\n\n if (allPoints.every((points) => points.length === 0)) return null;\n\n const dims = opts.dims();\n const pos = getMousePosition(opts.svg, event, opts.margin.left, opts.margin.top);\n const area = chartArea(dims.width, dims.height, opts.margin);\n\n if (pos.x < 0 || pos.x > area.width || pos.y < 0 || pos.y > area.height) return null;\n\n const key = findNearestKey(opts.getData(), allPoints, pos.x);\n\n return key === null ? null : { key, pos };\n };\n\n const sortedKeys = (): Datum['key'][] =>\n [\n ...new Map(\n opts\n .getData()\n .flat()\n .map((datum) => [keyId(datum.key), datum.key]),\n ).values(),\n ].sort((a, b) => keyOrder(a) - keyOrder(b));\n\n const onMouseMove = (event: MouseEvent): void => {\n const hit = pointerKey(event);\n\n if (hit) focus(hit.key, hit.pos, event);\n else if (active) clear();\n };\n\n const onClick = (event: MouseEvent): void => {\n if (!opts.onClick) return;\n\n const hit = pointerKey(event);\n const found = hit && eventFor(hit.key, hit.pos.y, event);\n\n if (found) opts.onClick(found.event);\n };\n\n const onKeyDown = (event: KeyboardEvent): void => {\n const keys = sortedKeys();\n\n if (keys.length === 0) return;\n\n const last = keys.length - 1;\n const target: Record<string, number> = {\n ArrowLeft: keyboardIndex < 0 ? last : Math.max(0, keyboardIndex - 1),\n ArrowRight: keyboardIndex < 0 ? 0 : Math.min(last, keyboardIndex + 1),\n End: last,\n Home: 0,\n };\n\n if (event.key in target) {\n event.preventDefault();\n keyboardIndex = target[event.key];\n focus(keys[keyboardIndex], null, event);\n } else if ((event.key === 'Enter' || event.key === ' ') && keyboardIndex >= 0 && opts.onClick) {\n event.preventDefault();\n\n const found = eventFor(keys[keyboardIndex], Number.NaN, event);\n\n if (found) opts.onClick(found.event);\n } else if (event.key === 'Escape') {\n keyboardIndex = -1;\n clear();\n }\n };\n\n return { onClick, onKeyDown, onMouseLeave: clear, onMouseMove };\n}\n\n/** Group for the per-series active markers, reused across renders. */\nexport function ensureMarkerGroup(parent: SVGGElement, current: SVGGElement | null): SVGGElement {\n if (current && parent.contains(current)) return current;\n\n const group = createSvgElement('g', {\n 'aria-hidden': 'true',\n class: 'prism-active-points',\n 'pointer-events': 'none',\n });\n\n parent.appendChild(group);\n\n return group;\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"series-interaction.js","names":[],"sources":["../../src/interaction/series-interaction.ts"],"sourcesContent":["import { keyId } from '../core/cartesian-model';\nimport type { ChartEventHandlers } from '../core/chart-scaffold';\nimport { chartArea } from '../core/layout';\nimport { createSvgElement } from '../svg/element';\nimport type { Point } from '../svg/path';\nimport type { ChartDimensions, ChartEvent, ChartMargin, Datum, Series, SeriesValue, TooltipConfig } from '../types';\nimport { type Announcer, describeValues } from './announcer';\nimport type { CrosshairState } from './crosshair';\nimport { getMousePosition } from './events';\nimport { comparisonContent, type TooltipState } from './tooltip';\n\nexport interface SeriesInteractionOptions {\n announcer: Announcer;\n colors: () => string[];\n crosshair?: CrosshairState | null;\n dims: () => ChartDimensions;\n getData: () => Datum[][];\n getPoints: () => Point[][];\n getSeriesList: () => Series[];\n /** Effective chart margin (including any dual-axis widening): hit bounds and tooltip offsets use this, never the shared dimensions. */\n margin: ChartMargin;\n /** Receives one marker per series at the active key. */\n markers: SVGGElement;\n onClick?: ((event: ChartEvent) => void) | undefined;\n onHover?: ((event: ChartEvent | null) => void) | undefined;\n /** Parent of the per-series groups; the nearest series is emphasised and the rest dim. */\n seriesGroup: SVGGElement;\n svg: SVGSVGElement;\n tooltip?: TooltipState | null;\n /** Value/title formatters from the tooltip config, applied to rows and spoken text alike. */\n tooltipFormat?: Pick<TooltipConfig, 'titleFormat' | 'valueFormat'>;\n}\n\ninterface KeyEntry {\n datum: Datum | undefined;\n point: Point | undefined;\n}\n\nconst keyOrder = (key: Datum['key']): number => (key instanceof Date ? key.getTime() : Number(key));\n\nexport function keyLabel(key: Datum['key']): string {\n return key instanceof Date ? key.toLocaleDateString() : String(key);\n}\n\nfunction findNearestKey(allData: Datum[][], allPoints: Point[][], posX: number): Datum['key'] | null {\n let nearest: Datum['key'] | null = null;\n let minXDistance = Infinity;\n\n for (let seriesIndex = 0; seriesIndex < allPoints.length; seriesIndex++) {\n for (let datumIndex = 0; datumIndex < (allPoints[seriesIndex]?.length ?? 0); datumIndex++) {\n const datum = allData[seriesIndex]?.[datumIndex];\n const point = allPoints[seriesIndex]?.[datumIndex];\n\n if (!datum || !point) continue;\n\n const distance = Math.abs(point.x - posX);\n\n if (distance < minXDistance) {\n minXDistance = distance;\n nearest = datum.key;\n }\n }\n }\n\n return nearest;\n}\n\nexport function createSeriesInteraction(opts: SeriesInteractionOptions): ChartEventHandlers {\n let keyboardIndex = -1;\n let active = false;\n\n const renderedHit = (event: Event): { datum: Datum; point: Point; seriesIndex: number } | null => {\n const target = event.target;\n if (\n !(target instanceof SVGElement) ||\n target.tagName.toLowerCase() !== 'circle' ||\n !opts.seriesGroup.contains(target) ||\n !target.parentElement\n )\n return null;\n let group: Element | null = target.parentElement;\n while (group && group.parentNode !== opts.seriesGroup) group = group.parentElement;\n if (!group) return null;\n const seriesIndex = [...opts.seriesGroup.children].indexOf(group);\n const datumIndex = [...target.parentElement.children].indexOf(target);\n const datum = opts.getData()[seriesIndex]?.[datumIndex];\n const point = opts.getPoints()[seriesIndex]?.[datumIndex];\n return datum && point ? { datum, point, seriesIndex } : null;\n };\n\n const entriesAt = (key: Datum['key']): KeyEntry[] => {\n const id = keyId(key);\n const allPoints = opts.getPoints();\n\n return opts.getData().map((data, seriesIndex) => {\n const datumIndex = data.findIndex((datum) => keyId(datum.key) === id);\n\n return datumIndex === -1\n ? { datum: undefined, point: undefined }\n : { datum: data[datumIndex], point: allPoints[seriesIndex]?.[datumIndex] };\n });\n };\n\n const nearestIndex = (entries: KeyEntry[], posY: number): number => {\n let best = -1;\n let minDistance = Infinity;\n\n entries.forEach(({ point }, index) => {\n if (!point) return;\n\n const distance = Number.isFinite(posY) ? Math.abs(point.y - posY) : 0;\n\n if (distance < minDistance) {\n minDistance = distance;\n best = index;\n }\n });\n\n return best;\n };\n\n const highlight = (entries: KeyEntry[], emphasised: number): void => {\n const colors = opts.colors();\n\n opts.markers.replaceChildren();\n entries.forEach(({ point }, index) => {\n if (!point) return;\n\n opts.markers.appendChild(\n createSvgElement('circle', {\n class: 'prism-active-point',\n cx: point.x,\n cy: point.y,\n fill: colors[index],\n r: 4,\n }),\n );\n });\n\n const groups = [...opts.seriesGroup.children];\n\n opts.seriesGroup.classList.toggle('prism-series-focused', emphasised >= 0 && groups.length > 1);\n for (const [index, group] of groups.entries()) group.classList.toggle('prism-series-active', index === emphasised);\n };\n\n const clear = (): void => {\n opts.crosshair?.hide();\n opts.tooltip?.hide();\n opts.announcer.clear();\n highlight([], -1);\n active = false;\n opts.onHover?.(null);\n };\n\n const eventFor = (\n key: Datum['key'],\n posY: number,\n originalEvent: Event,\n ): { entries: KeyEntry[]; event: ChartEvent; index: number } | null => {\n const entries = entriesAt(key);\n const hit = renderedHit(originalEvent);\n const index = hit?.seriesIndex ?? nearestIndex(entries, posY);\n if (hit) entries[index] = { datum: hit.datum, point: hit.point };\n const seriesList = opts.getSeriesList();\n const datum = entries[index]?.datum;\n\n if (index < 0 || !datum) return null;\n\n const values: SeriesValue[] = entries.map((entry, i) => ({ datum: entry.datum, series: seriesList[i] }));\n\n return { entries, event: { datum, originalEvent, series: seriesList[index], values }, index };\n };\n\n const focus = (key: Datum['key'], pos: Point | null, originalEvent: Event): void => {\n const found = eventFor(key, pos?.y ?? Number.NaN, originalEvent);\n\n if (!found) return;\n\n const { entries, event, index } = found;\n const point = entries[index].point!;\n const dims = opts.dims();\n const area = chartArea(dims.width, dims.height, opts.margin);\n const raw = opts.crosshair?.snap === false && pos;\n const label = opts.tooltipFormat?.titleFormat ? opts.tooltipFormat.titleFormat(key) : keyLabel(key);\n const values = event.values ?? [];\n const colors = opts.colors();\n const valueFormat = opts.tooltipFormat?.valueFormat;\n\n opts.crosshair?.show(raw ? pos.x : point.x, raw ? pos.y : point.y, area.width, area.height);\n highlight(entries, index);\n\n const spoken = describeValues(label, values, valueFormat ?? String);\n\n if (opts.tooltip) {\n const rows = values.flatMap(({ datum, series }, i) =>\n datum\n ? [\n {\n color: colors[i],\n name: series.name,\n value: valueFormat ? valueFormat(datum.value, datum, series) : String(datum.value),\n },\n ]\n : [],\n );\n\n opts.tooltip.show(\n point.x + opts.margin.left,\n point.y + opts.margin.top,\n event.datum,\n event.series,\n comparisonContent(opts.svg.ownerDocument, label, rows, spoken),\n );\n } else {\n opts.announcer.announce(spoken);\n }\n\n active = true;\n opts.onHover?.(event);\n };\n\n const pointerKey = (event: MouseEvent): { key: Datum['key']; pos: Point } | null => {\n const rendered = renderedHit(event);\n if (rendered) return { key: rendered.datum.key, pos: rendered.point };\n const allPoints = opts.getPoints();\n\n if (allPoints.every((points) => points.length === 0)) return null;\n\n const dims = opts.dims();\n const pos = getMousePosition(opts.svg, event, opts.margin.left, opts.margin.top);\n const area = chartArea(dims.width, dims.height, opts.margin);\n\n if (pos.x < 0 || pos.x > area.width || pos.y < 0 || pos.y > area.height) return null;\n\n const key = findNearestKey(opts.getData(), allPoints, pos.x);\n\n return key === null ? null : { key, pos };\n };\n\n const sortedKeys = (): Datum['key'][] =>\n [\n ...new Map(\n opts\n .getData()\n .flat()\n .map((datum) => [keyId(datum.key), datum.key]),\n ).values(),\n ].sort((a, b) => keyOrder(a) - keyOrder(b));\n\n const onMouseMove = (event: MouseEvent): void => {\n const hit = pointerKey(event);\n\n if (hit) focus(hit.key, hit.pos, event);\n else if (active) clear();\n };\n\n const onClick = (event: MouseEvent): void => {\n if (!opts.onClick) return;\n\n const hit = pointerKey(event);\n const found = hit && eventFor(hit.key, hit.pos.y, event);\n\n if (found) opts.onClick(found.event);\n };\n\n const onKeyDown = (event: KeyboardEvent): void => {\n const keys = sortedKeys();\n\n if (keys.length === 0) return;\n\n const last = keys.length - 1;\n const target: Record<string, number> = {\n ArrowLeft: keyboardIndex < 0 ? last : Math.max(0, keyboardIndex - 1),\n ArrowRight: keyboardIndex < 0 ? 0 : Math.min(last, keyboardIndex + 1),\n End: last,\n Home: 0,\n };\n\n if (event.key in target) {\n event.preventDefault();\n keyboardIndex = target[event.key];\n focus(keys[keyboardIndex], null, event);\n } else if ((event.key === 'Enter' || event.key === ' ') && keyboardIndex >= 0 && opts.onClick) {\n event.preventDefault();\n\n const found = eventFor(keys[keyboardIndex], Number.NaN, event);\n\n if (found) opts.onClick(found.event);\n } else if (event.key === 'Escape') {\n keyboardIndex = -1;\n clear();\n }\n };\n\n return { onClick, onKeyDown, onMouseLeave: clear, onMouseMove };\n}\n\n/** Group for the per-series active markers, reused across renders. */\nexport function ensureMarkerGroup(parent: SVGGElement, current: SVGGElement | null): SVGGElement {\n if (current && parent.contains(current)) return current;\n\n const group = createSvgElement('g', {\n 'aria-hidden': 'true',\n class: 'prism-active-points',\n 'pointer-events': 'none',\n });\n\n parent.appendChild(group);\n\n return group;\n}\n"],"mappings":"sSAsCA,IAAM,SAAY,GAA+B,aAAe,KAAO,EAAI,QAAQ,EAAI,OAAO,CAAG,EAEjG,SAAgB,SAAS,EAA2B,CAClD,OAAO,aAAe,KAAO,EAAI,mBAAmB,EAAI,OAAO,CAAG,CACpE,CAEA,SAAS,eAAe,EAAoB,EAAsB,EAAmC,CACnG,IAAI,EAA+B,KAC/B,EAAe,IAEnB,IAAK,IAAI,EAAc,EAAG,EAAc,EAAU,OAAQ,IACxD,IAAK,IAAI,EAAa,EAAG,GAAc,EAAU,EAAY,EAAE,QAAU,GAAI,IAAc,CACzF,IAAM,EAAQ,EAAQ,EAAY,GAAG,GAC/B,EAAQ,EAAU,EAAY,GAAG,GAEvC,GAAI,CAAC,GAAS,CAAC,EAAO,SAEtB,IAAM,EAAW,KAAK,IAAI,EAAM,EAAI,CAAI,EAEpC,EAAW,IACb,EAAe,EACf,EAAU,EAAM,IAEpB,CAGF,OAAO,CACT,CAEA,SAAgB,wBAAwB,EAAoD,CAC1F,IAAI,EAAgB,GAChB,EAAS,GAEP,YAAe,GAA6E,CAChG,IAAM,EAAS,EAAM,OACrB,GACE,EAAE,aAAkB,aACpB,EAAO,QAAQ,YAAY,IAAM,UACjC,CAAC,EAAK,YAAY,SAAS,CAAM,GACjC,CAAC,EAAO,cAER,OAAO,KACT,IAAI,EAAwB,EAAO,cACnC,KAAO,GAAS,EAAM,aAAe,EAAK,aAAa,EAAQ,EAAM,cACrE,GAAI,CAAC,EAAO,OAAO,KACnB,IAAM,EAAc,CAAC,GAAG,EAAK,YAAY,QAAQ,CAAC,CAAC,QAAQ,CAAK,EAC1D,EAAa,CAAC,GAAG,EAAO,cAAc,QAAQ,CAAC,CAAC,QAAQ,CAAM,EAC9D,EAAQ,EAAK,QAAQ,CAAC,CAAC,EAAY,GAAG,GACtC,EAAQ,EAAK,UAAU,CAAC,CAAC,EAAY,GAAG,GAC9C,OAAO,GAAS,EAAQ,CAAE,QAAO,QAAO,aAAY,EAAI,IAC1D,EAEM,UAAa,GAAkC,CACnD,IAAM,EAAK,EAAM,CAAG,EACd,EAAY,EAAK,UAAU,EAEjC,OAAO,EAAK,QAAQ,CAAC,CAAC,KAAK,EAAM,IAAgB,CAC/C,IAAM,EAAa,EAAK,UAAW,GAAU,EAAM,EAAM,GAAG,IAAM,CAAE,EAEpE,OAAO,IAAe,GAClB,CAAE,MAAO,IAAA,GAAW,MAAO,IAAA,EAAU,EACrC,CAAE,MAAO,EAAK,GAAa,MAAO,EAAU,EAAY,GAAG,EAAY,CAC7E,CAAC,CACH,EAEM,cAAgB,EAAqB,IAAyB,CAClE,IAAI,EAAO,GACP,EAAc,IAalB,OAXA,EAAQ,SAAS,CAAE,SAAS,IAAU,CACpC,GAAI,CAAC,EAAO,OAEZ,IAAM,EAAW,OAAO,SAAS,CAAI,EAAI,KAAK,IAAI,EAAM,EAAI,CAAI,EAAI,EAEhE,EAAW,IACb,EAAc,EACd,EAAO,EAEX,CAAC,EAEM,CACT,EAEM,WAAa,EAAqB,IAA6B,CACnE,IAAM,EAAS,EAAK,OAAO,EAE3B,EAAK,QAAQ,gBAAgB,EAC7B,EAAQ,SAAS,CAAE,SAAS,IAAU,CAC/B,GAEL,EAAK,QAAQ,YACX,EAAiB,SAAU,CACzB,MAAO,qBACP,GAAI,EAAM,EACV,GAAI,EAAM,EACV,KAAM,EAAO,GACb,EAAG,CACL,CAAC,CACH,CACF,CAAC,EAED,IAAM,EAAS,CAAC,GAAG,EAAK,YAAY,QAAQ,EAE5C,EAAK,YAAY,UAAU,OAAO,uBAAwB,GAAc,GAAK,EAAO,OAAS,CAAC,EAC9F,IAAK,GAAM,CAAC,EAAO,KAAU,EAAO,QAAQ,EAAG,EAAM,UAAU,OAAO,sBAAuB,IAAU,CAAU,CACnH,EAEM,UAAoB,CACxB,EAAK,WAAW,KAAK,EACrB,EAAK,SAAS,KAAK,EACnB,EAAK,UAAU,MAAM,EACrB,UAAU,CAAC,EAAG,EAAE,EAChB,EAAS,GACT,EAAK,UAAU,IAAI,CACrB,EAEM,UACJ,EACA,EACA,IACqE,CACrE,IAAM,EAAU,UAAU,CAAG,EACvB,EAAM,YAAY,CAAa,EAC/B,EAAQ,GAAK,aAAe,aAAa,EAAS,CAAI,EACxD,IAAK,EAAQ,GAAS,CAAE,MAAO,EAAI,MAAO,MAAO,EAAI,KAAM,GAC/D,IAAM,EAAa,EAAK,cAAc,EAChC,EAAQ,EAAQ,EAAM,EAAE,MAE9B,GAAI,EAAQ,GAAK,CAAC,EAAO,OAAO,KAEhC,IAAM,EAAwB,EAAQ,KAAK,EAAO,KAAO,CAAE,MAAO,EAAM,MAAO,OAAQ,EAAW,EAAG,EAAE,EAEvG,MAAO,CAAE,UAAS,MAAO,CAAE,QAAO,gBAAe,OAAQ,EAAW,GAAQ,QAAO,EAAG,OAAM,CAC9F,EAEM,OAAS,EAAmB,EAAmB,IAA+B,CAClF,IAAM,EAAQ,SAAS,EAAK,GAAK,GAAK,IAAY,CAAa,EAE/D,GAAI,CAAC,EAAO,OAEZ,GAAM,CAAE,UAAS,QAAO,SAAU,EAC5B,EAAQ,EAAQ,EAAM,CAAC,MACvB,EAAO,EAAK,KAAK,EACjB,EAAO,EAAU,EAAK,MAAO,EAAK,OAAQ,EAAK,MAAM,EACrD,EAAM,EAAK,WAAW,OAAS,IAAS,EACxC,EAAQ,EAAK,eAAe,YAAc,EAAK,cAAc,YAAY,CAAG,EAAI,SAAS,CAAG,EAC5F,EAAS,EAAM,QAAU,CAAC,EAC1B,EAAS,EAAK,OAAO,EACrB,EAAc,EAAK,eAAe,YAExC,EAAK,WAAW,KAAK,EAAM,EAAI,EAAI,EAAM,EAAG,EAAM,EAAI,EAAI,EAAM,EAAG,EAAK,MAAO,EAAK,MAAM,EAC1F,UAAU,EAAS,CAAK,EAExB,IAAM,EAAS,EAAe,EAAO,EAAQ,GAAe,MAAM,EAElE,GAAI,EAAK,QAAS,CAChB,IAAM,EAAO,EAAO,SAAS,CAAE,QAAO,UAAU,IAC9C,EACI,CACE,CACE,MAAO,EAAO,GACd,KAAM,EAAO,KACb,MAAO,EAAc,EAAY,EAAM,MAAO,EAAO,CAAM,EAAI,OAAO,EAAM,KAAK,CACnF,CACF,EACA,CAAC,CACP,EAEA,EAAK,QAAQ,KACX,EAAM,EAAI,EAAK,OAAO,KACtB,EAAM,EAAI,EAAK,OAAO,IACtB,EAAM,MACN,EAAM,OACN,EAAkB,EAAK,IAAI,cAAe,EAAO,EAAM,CAAM,CAC/D,CACF,MACE,EAAK,UAAU,SAAS,CAAM,EAGhC,EAAS,GACT,EAAK,UAAU,CAAK,CACtB,EAEM,WAAc,GAAgE,CAClF,IAAM,EAAW,YAAY,CAAK,EAClC,GAAI,EAAU,MAAO,CAAE,IAAK,EAAS,MAAM,IAAK,IAAK,EAAS,KAAM,EACpE,IAAM,EAAY,EAAK,UAAU,EAEjC,GAAI,EAAU,MAAO,GAAW,EAAO,SAAW,CAAC,EAAG,OAAO,KAE7D,IAAM,EAAO,EAAK,KAAK,EACjB,EAAM,EAAiB,EAAK,IAAK,EAAO,EAAK,OAAO,KAAM,EAAK,OAAO,GAAG,EACzE,EAAO,EAAU,EAAK,MAAO,EAAK,OAAQ,EAAK,MAAM,EAE3D,GAAI,EAAI,EAAI,GAAK,EAAI,EAAI,EAAK,OAAS,EAAI,EAAI,GAAK,EAAI,EAAI,EAAK,OAAQ,OAAO,KAEhF,IAAM,EAAM,eAAe,EAAK,QAAQ,EAAG,EAAW,EAAI,CAAC,EAE3D,OAAO,IAAQ,KAAO,KAAO,CAAE,MAAK,KAAI,CAC1C,EAEM,eACJ,CACE,GAAG,IAAI,IACL,EACG,QAAQ,CAAC,CACT,KAAK,CAAC,CACN,IAAK,GAAU,CAAC,EAAM,EAAM,GAAG,EAAG,EAAM,GAAG,CAAC,CACjD,CAAC,CAAC,OAAO,CACX,CAAC,CAAC,MAAM,EAAG,IAAM,SAAS,CAAC,EAAI,SAAS,CAAC,CAAC,EAEtC,YAAe,GAA4B,CAC/C,IAAM,EAAM,WAAW,CAAK,EAExB,EAAK,MAAM,EAAI,IAAK,EAAI,IAAK,CAAK,EAC7B,GAAQ,MAAM,CACzB,EAEM,QAAW,GAA4B,CAC3C,GAAI,CAAC,EAAK,QAAS,OAEnB,IAAM,EAAM,WAAW,CAAK,EACtB,EAAQ,GAAO,SAAS,EAAI,IAAK,EAAI,IAAI,EAAG,CAAK,EAEnD,GAAO,EAAK,QAAQ,EAAM,KAAK,CACrC,EAEM,UAAa,GAA+B,CAChD,IAAM,EAAO,WAAW,EAExB,GAAI,EAAK,SAAW,EAAG,OAEvB,IAAM,EAAO,EAAK,OAAS,EACrB,EAAiC,CACrC,UAAW,EAAgB,EAAI,EAAO,KAAK,IAAI,EAAG,EAAgB,CAAC,EACnE,WAAY,EAAgB,EAAI,EAAI,KAAK,IAAI,EAAM,EAAgB,CAAC,EACpE,IAAK,EACL,KAAM,CACR,EAEA,GAAI,EAAM,OAAO,EACf,EAAM,eAAe,EACrB,EAAgB,EAAO,EAAM,KAC7B,MAAM,EAAK,GAAgB,KAAM,CAAK,OACjC,IAAK,EAAM,MAAQ,SAAW,EAAM,MAAQ,MAAQ,GAAiB,GAAK,EAAK,QAAS,CAC7F,EAAM,eAAe,EAErB,IAAM,EAAQ,SAAS,EAAK,GAAgB,IAAY,CAAK,EAEzD,GAAO,EAAK,QAAQ,EAAM,KAAK,CACrC,MAAW,EAAM,MAAQ,WACvB,EAAgB,GAChB,MAAM,EAEV,EAEA,MAAO,CAAE,QAAS,UAAW,aAAc,MAAO,WAAY,CAChE,CAGA,SAAgB,kBAAkB,EAAqB,EAA0C,CAC/F,GAAI,GAAW,EAAO,SAAS,CAAO,EAAG,OAAO,EAEhD,IAAM,EAAQ,EAAiB,IAAK,CAClC,cAAe,OACf,MAAO,sBACP,iBAAkB,MACpB,CAAC,EAID,OAFA,EAAO,YAAY,CAAK,EAEjB,CACT"}
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
let e=require("@vielzeug/orbit");var t=`position:absolute;width:1px;height:1px;overflow:hidden;clip-path:inset(50%);white-space:nowrap`;function comparisonContent(e,n,r,i){let a=e.createElement(`div`),o=e.createElement(`span`),s=e.createElement(`div`),c=e.createElement(`div`);a.className=`prism-tooltip-body`,o.className=`prism-tooltip-sr`,o.style.cssText=t,o.textContent=i,s.setAttribute(`aria-hidden`,`true`),c.className=`prism-tooltip-title`,c.textContent=n,s.appendChild(c);for(let t of r){let n=e.createElement(`div`),r=e.createElement(`span`),i=e.createElement(`span`),a=e.createElement(`span`);n.className=`prism-tooltip-row`,r.className=`prism-tooltip-swatch`,r.style.setProperty(`--prism-swatch`,t.color),i.className=`prism-tooltip-name`,i.textContent=t.name,a.className=`prism-tooltip-value`,a.textContent=t.value,n.append(r,i,a),s.appendChild(n)}return a.append(o,s),a}function createTooltip(t,n){let r=t.style.position,i=getComputedStyle(t).position===`static`;i&&(t.style.position=`relative`);let a=t.ownerDocument.createElement(`div`);a.className=`prism-tooltip`,a.style.position=`absolute`,a.style.pointerEvents=`none`,a.style.top=`0`,a.style.left=`0`,a.setAttribute(`role`,`status`),a.setAttribute(`aria-live`,`polite`),t.appendChild(a);let o=n!==!0&&n?.offset||8,s=n===!0?void 0:n?.render,setVisible=e=>{a.classList.toggle(`prism-tooltip--visible`,e),a.style.opacity=e?`1`:`0`},c=null,l=null,u=!1,cancelClear=()=>{l!==null&&(clearTimeout(l),l=null)},cancelReveal=()=>{c!==null&&(cancelAnimationFrame(c),c=null)},disposeHandle=()=>{cancelReveal(),cancelClear(),a.remove(),i&&t.style.position===`relative`&&(t.style.position=r)};return{dispose:disposeHandle,el:a,hide(){cancelReveal(),cancelClear(),u=!1,setVisible(!1),l=setTimeout(()=>{l=null,u||(a.textContent=``)},150)},show(n,r,i,l,d){if(!t.isConnected)return;cancelClear();let f=s?s(i,l):d??`${l.name}: ${i.value}`;typeof f==`string`?a.textContent=f:a.replaceChildren(f);let{x:p,y:m}=(0,e.computePosition)({getBoundingClientRect:()=>{let e=t.getBoundingClientRect();return{bottom:e.top+r,height:0,left:e.left+n,right:e.left+n,top:e.top+r,width:0,x:e.left+n,y:e.top+r}}},a,{containingBlock:t,middleware:[(0,e.offset)(o),(0,e.flip)(),(0,e.shift)({padding:8})],placement:`top`});if(a.style.left=`${p}px`,a.style.top=`${m}px`,u){c===null&&setVisible(!0);return}u=!0,setVisible(!1),c=requestAnimationFrame(()=>{c=null,u&&setVisible(!0)})},[Symbol.dispose]:disposeHandle}}exports.comparisonContent=comparisonContent,exports.createTooltip=createTooltip;
|
|
1
|
+
let e=require("@vielzeug/orbit");var t=`position:absolute;width:1px;height:1px;overflow:hidden;clip-path:inset(50%);white-space:nowrap`;function shiftBoundary(t,n){let r=t.getBoundingClientRect(),i={height:r.height,width:r.width,x:r.x,y:r.y},a=(0,e.getClippingAncestorRect)(n),o=Math.max(i.x,a.x),s=Math.max(i.y,a.y),c=Math.min(i.x+i.width,a.x+a.width),l=Math.min(i.y+i.height,a.y+a.height);return c<=o||l<=s?i:{height:l-s,width:c-o,x:o,y:s}}function comparisonContent(e,n,r,i){let a=e.createElement(`div`),o=e.createElement(`span`),s=e.createElement(`div`),c=e.createElement(`div`);a.className=`prism-tooltip-body`,o.className=`prism-tooltip-sr`,o.style.cssText=t,o.textContent=i,s.setAttribute(`aria-hidden`,`true`),c.className=`prism-tooltip-title`,c.textContent=n,s.appendChild(c);for(let t of r){let n=e.createElement(`div`),r=e.createElement(`span`),i=e.createElement(`span`),a=e.createElement(`span`);n.className=`prism-tooltip-row`,r.className=`prism-tooltip-swatch`,r.style.setProperty(`--prism-swatch`,t.color),i.className=`prism-tooltip-name`,i.textContent=t.name,a.className=`prism-tooltip-value`,a.textContent=t.value,n.append(r,i,a),s.appendChild(n)}return a.append(o,s),a}function createTooltip(t,n){let r=t.style.position,i=getComputedStyle(t).position===`static`;i&&(t.style.position=`relative`);let a=t.ownerDocument.createElement(`div`);a.className=`prism-tooltip`,a.style.position=`absolute`,a.style.pointerEvents=`none`,a.style.top=`0`,a.style.left=`0`,a.setAttribute(`role`,`status`),a.setAttribute(`aria-live`,`polite`),t.appendChild(a);let o=n!==!0&&n?.offset||8,s=n===!0?void 0:n?.render,setVisible=e=>{a.classList.toggle(`prism-tooltip--visible`,e),a.style.opacity=e?`1`:`0`},c=null,l=null,u=!1,cancelClear=()=>{l!==null&&(clearTimeout(l),l=null)},cancelReveal=()=>{c!==null&&(cancelAnimationFrame(c),c=null)},disposeHandle=()=>{cancelReveal(),cancelClear(),a.remove(),i&&t.style.position===`relative`&&(t.style.position=r)};return{dispose:disposeHandle,el:a,hide(){cancelReveal(),cancelClear(),u=!1,setVisible(!1),l=setTimeout(()=>{l=null,u||(a.textContent=``)},150)},show(n,r,i,l,d){if(!t.isConnected)return;cancelClear();let f=s?s(i,l):d??`${l.name}: ${i.value}`;typeof f==`string`?a.textContent=f:a.replaceChildren(f);let{x:p,y:m}=(0,e.computePosition)({getBoundingClientRect:()=>{let e=t.getBoundingClientRect();return{bottom:e.top+r,height:0,left:e.left+n,right:e.left+n,top:e.top+r,width:0,x:e.left+n,y:e.top+r}}},a,{containingBlock:t,middleware:[(0,e.offset)(o),(0,e.flip)(),(0,e.shift)({boundary:shiftBoundary(t,a),padding:8})],placement:`top`});if(a.style.left=`${p}px`,a.style.top=`${m}px`,u){c===null&&setVisible(!0);return}u=!0,setVisible(!1),c=requestAnimationFrame(()=>{c=null,u&&setVisible(!0)})},[Symbol.dispose]:disposeHandle}}exports.comparisonContent=comparisonContent,exports.createTooltip=createTooltip;
|
|
2
2
|
//# sourceMappingURL=tooltip.cjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"tooltip.cjs","names":[],"sources":["../../src/interaction/tooltip.ts"],"sourcesContent":["import { computePosition, flip, offset, shift } from '@vielzeug/orbit';\nimport type { Datum, Series, TooltipConfig } from '../types';\n\nexport interface TooltipRow {\n color: string;\n name: string;\n value: string;\n}\n\n/** Inline so the spoken text stays hidden even without prism's stylesheet. */\nconst SCREEN_READER_ONLY =\n 'position:absolute;width:1px;height:1px;overflow:hidden;clip-path:inset(50%);white-space:nowrap';\n\n/**\n * Title plus one swatch row per series; text only, so data is never parsed as HTML.\n * `spoken` is what the tooltip's live region announces; the visual rows are aria-hidden.\n */\nexport function comparisonContent(\n doc: Document,\n title: string,\n rows: readonly TooltipRow[],\n spoken: string,\n): HTMLElement {\n const body = doc.createElement('div');\n const sr = doc.createElement('span');\n const visual = doc.createElement('div');\n const head = doc.createElement('div');\n\n body.className = 'prism-tooltip-body';\n sr.className = 'prism-tooltip-sr';\n sr.style.cssText = SCREEN_READER_ONLY;\n sr.textContent = spoken;\n visual.setAttribute('aria-hidden', 'true');\n head.className = 'prism-tooltip-title';\n head.textContent = title;\n visual.appendChild(head);\n\n for (const row of rows) {\n const line = doc.createElement('div');\n const swatch = doc.createElement('span');\n const name = doc.createElement('span');\n const value = doc.createElement('span');\n\n line.className = 'prism-tooltip-row';\n swatch.className = 'prism-tooltip-swatch';\n swatch.style.setProperty('--prism-swatch', row.color);\n name.className = 'prism-tooltip-name';\n name.textContent = row.name;\n value.className = 'prism-tooltip-value';\n value.textContent = row.value;\n line.append(swatch, name, value);\n visual.appendChild(line);\n }\n\n body.append(sr, visual);\n\n return body;\n}\n\nexport interface TooltipState {\n dispose(): void;\n el: HTMLDivElement | null;\n hide(): void;\n /** `content` replaces the default `series: value` body when no custom `render` is configured. */\n show(x: number, y: number, datum: Datum, series: Series, content?: Node | string): void;\n [Symbol.dispose](): void;\n}\n\nexport function createTooltip(container: HTMLElement, config?: TooltipConfig | true): TooltipState {\n const previousPosition = container.style.position;\n const changedPosition = getComputedStyle(container).position === 'static';\n\n if (changedPosition) container.style.position = 'relative';\n\n const el = container.ownerDocument.createElement('div');\n\n el.className = 'prism-tooltip';\n el.style.position = 'absolute';\n el.style.pointerEvents = 'none';\n el.style.top = '0';\n el.style.left = '0';\n // Non-modal status text: announced by assistive tech whenever content/hide state changes.\n el.setAttribute('role', 'status');\n el.setAttribute('aria-live', 'polite');\n container.appendChild(el);\n\n const tooltipOffset: number = (config !== true && config?.offset) || 8;\n const render = config !== true ? config?.render : undefined;\n\n // Opacity stays inline (the transition is declared in CSS against the token\n // duration); the class carries the rise, which needs to combine with the\n // left/top positioning without fighting it.\n const setVisible = (next: boolean): void => {\n el.classList.toggle('prism-tooltip--visible', next);\n el.style.opacity = next ? '1' : '0';\n };\n\n let revealFrame: number | null = null;\n let clearTimer: ReturnType<typeof setTimeout> | null = null;\n let visible = false;\n\n const cancelClear = (): void => {\n if (clearTimer === null) return;\n\n clearTimeout(clearTimer);\n clearTimer = null;\n };\n\n const cancelReveal = (): void => {\n if (revealFrame === null) return;\n\n cancelAnimationFrame(revealFrame);\n revealFrame = null;\n };\n\n const disposeHandle = (): void => {\n cancelReveal();\n cancelClear();\n el.remove();\n if (changedPosition && container.style.position === 'relative') container.style.position = previousPosition;\n };\n\n return {\n dispose: disposeHandle,\n el,\n hide() {\n cancelReveal();\n cancelClear();\n visible = false;\n setVisible(false);\n clearTimer = setTimeout(() => {\n clearTimer = null;\n\n if (!visible) el.textContent = '';\n }, 150);\n },\n show(x: number, y: number, datum: Datum, series: Series, content?: Node | string) {\n if (!container.isConnected) return;\n\n cancelClear();\n\n const body = render ? render(datum, series) : (content ?? `${series.name}: ${datum.value}`);\n\n if (typeof body === 'string') el.textContent = body;\n else el.replaceChildren(body);\n\n const virtualRef = {\n getBoundingClientRect: () => {\n const rect = container.getBoundingClientRect();\n\n return {\n bottom: rect.top + y,\n height: 0,\n left: rect.left + x,\n right: rect.left + x,\n top: rect.top + y,\n width: 0,\n x: rect.left + x,\n y: rect.top + y,\n };\n },\n };\n\n const { x: positionX, y: positionY } = computePosition(virtualRef, el, {\n containingBlock: container,\n middleware: [offset(tooltipOffset), flip(), shift({ padding: 8 })],\n placement: 'top',\n });\n\n el.style.left = `${positionX}px`;\n el.style.top = `${positionY}px`;\n\n if (visible) {\n if (revealFrame === null) setVisible(true);\n\n return;\n }\n\n visible = true;\n setVisible(false);\n // One frame at the hidden state first: without it the browser has no\n // starting value to transition from and the tooltip would just appear.\n revealFrame = requestAnimationFrame(() => {\n revealFrame = null;\n\n if (visible) setVisible(true);\n });\n },\n [Symbol.dispose]: disposeHandle,\n };\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"tooltip.cjs","names":[],"sources":["../../src/interaction/tooltip.ts"],"sourcesContent":["import { computePosition, flip, getClippingAncestorRect, offset, type Rect, shift } from '@vielzeug/orbit';\nimport type { Datum, Series, TooltipConfig } from '../types';\n\nexport interface TooltipRow {\n color: string;\n name: string;\n value: string;\n}\n\n/** Inline so the spoken text stays hidden even without prism's stylesheet. */\nconst SCREEN_READER_ONLY =\n 'position:absolute;width:1px;height:1px;overflow:hidden;clip-path:inset(50%);white-space:nowrap';\n\n/**\n * The shift boundary: the chart container intersected with the tooltip's clipping-ancestor\n * rect (what a host `overflow: hidden` actually shows). Clamping to the container alone would\n * let the tooltip sit inside a container that is itself clipped by a smaller ancestor; the\n * intersection keeps it inside what is actually visible. A degenerate intersection (zero area)\n * falls back to the container rect.\n */\nfunction shiftBoundary(container: HTMLElement, tooltip: HTMLElement): Rect {\n const box = container.getBoundingClientRect();\n const containerRect: Rect = { height: box.height, width: box.width, x: box.x, y: box.y };\n const visible = getClippingAncestorRect(tooltip);\n const x = Math.max(containerRect.x, visible.x);\n const y = Math.max(containerRect.y, visible.y);\n const right = Math.min(containerRect.x + containerRect.width, visible.x + visible.width);\n const bottom = Math.min(containerRect.y + containerRect.height, visible.y + visible.height);\n\n if (right <= x || bottom <= y) return containerRect;\n\n return { height: bottom - y, width: right - x, x, y };\n}\n\n/**\n * Title plus one swatch row per series; text only, so data is never parsed as HTML.\n * `spoken` is what the tooltip's live region announces; the visual rows are aria-hidden.\n */\nexport function comparisonContent(\n doc: Document,\n title: string,\n rows: readonly TooltipRow[],\n spoken: string,\n): HTMLElement {\n const body = doc.createElement('div');\n const sr = doc.createElement('span');\n const visual = doc.createElement('div');\n const head = doc.createElement('div');\n\n body.className = 'prism-tooltip-body';\n sr.className = 'prism-tooltip-sr';\n sr.style.cssText = SCREEN_READER_ONLY;\n sr.textContent = spoken;\n visual.setAttribute('aria-hidden', 'true');\n head.className = 'prism-tooltip-title';\n head.textContent = title;\n visual.appendChild(head);\n\n for (const row of rows) {\n const line = doc.createElement('div');\n const swatch = doc.createElement('span');\n const name = doc.createElement('span');\n const value = doc.createElement('span');\n\n line.className = 'prism-tooltip-row';\n swatch.className = 'prism-tooltip-swatch';\n swatch.style.setProperty('--prism-swatch', row.color);\n name.className = 'prism-tooltip-name';\n name.textContent = row.name;\n value.className = 'prism-tooltip-value';\n value.textContent = row.value;\n line.append(swatch, name, value);\n visual.appendChild(line);\n }\n\n body.append(sr, visual);\n\n return body;\n}\n\nexport interface TooltipState {\n dispose(): void;\n el: HTMLDivElement | null;\n hide(): void;\n /** `content` replaces the default `series: value` body when no custom `render` is configured. */\n show(x: number, y: number, datum: Datum, series: Series, content?: Node | string): void;\n [Symbol.dispose](): void;\n}\n\nexport function createTooltip(container: HTMLElement, config?: TooltipConfig | true): TooltipState {\n const previousPosition = container.style.position;\n const changedPosition = getComputedStyle(container).position === 'static';\n\n if (changedPosition) container.style.position = 'relative';\n\n const el = container.ownerDocument.createElement('div');\n\n el.className = 'prism-tooltip';\n el.style.position = 'absolute';\n el.style.pointerEvents = 'none';\n el.style.top = '0';\n el.style.left = '0';\n // Non-modal status text: announced by assistive tech whenever content/hide state changes.\n el.setAttribute('role', 'status');\n el.setAttribute('aria-live', 'polite');\n container.appendChild(el);\n\n const tooltipOffset: number = (config !== true && config?.offset) || 8;\n const render = config !== true ? config?.render : undefined;\n\n // Opacity stays inline (the transition is declared in CSS against the token\n // duration); the class carries the rise, which needs to combine with the\n // left/top positioning without fighting it.\n const setVisible = (next: boolean): void => {\n el.classList.toggle('prism-tooltip--visible', next);\n el.style.opacity = next ? '1' : '0';\n };\n\n let revealFrame: number | null = null;\n let clearTimer: ReturnType<typeof setTimeout> | null = null;\n let visible = false;\n\n const cancelClear = (): void => {\n if (clearTimer === null) return;\n\n clearTimeout(clearTimer);\n clearTimer = null;\n };\n\n const cancelReveal = (): void => {\n if (revealFrame === null) return;\n\n cancelAnimationFrame(revealFrame);\n revealFrame = null;\n };\n\n const disposeHandle = (): void => {\n cancelReveal();\n cancelClear();\n el.remove();\n if (changedPosition && container.style.position === 'relative') container.style.position = previousPosition;\n };\n\n return {\n dispose: disposeHandle,\n el,\n hide() {\n cancelReveal();\n cancelClear();\n visible = false;\n setVisible(false);\n clearTimer = setTimeout(() => {\n clearTimer = null;\n\n if (!visible) el.textContent = '';\n }, 150);\n },\n show(x: number, y: number, datum: Datum, series: Series, content?: Node | string) {\n if (!container.isConnected) return;\n\n cancelClear();\n\n const body = render ? render(datum, series) : (content ?? `${series.name}: ${datum.value}`);\n\n if (typeof body === 'string') el.textContent = body;\n else el.replaceChildren(body);\n\n const virtualRef = {\n getBoundingClientRect: () => {\n const rect = container.getBoundingClientRect();\n\n return {\n bottom: rect.top + y,\n height: 0,\n left: rect.left + x,\n right: rect.left + x,\n top: rect.top + y,\n width: 0,\n x: rect.left + x,\n y: rect.top + y,\n };\n },\n };\n\n const { x: positionX, y: positionY } = computePosition(virtualRef, el, {\n containingBlock: container,\n middleware: [offset(tooltipOffset), flip(), shift({ boundary: shiftBoundary(container, el), padding: 8 })],\n placement: 'top',\n });\n\n el.style.left = `${positionX}px`;\n el.style.top = `${positionY}px`;\n\n if (visible) {\n if (revealFrame === null) setVisible(true);\n\n return;\n }\n\n visible = true;\n setVisible(false);\n // One frame at the hidden state first: without it the browser has no\n // starting value to transition from and the tooltip would just appear.\n revealFrame = requestAnimationFrame(() => {\n revealFrame = null;\n\n if (visible) setVisible(true);\n });\n },\n [Symbol.dispose]: disposeHandle,\n };\n}\n"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"tooltip.d.ts","sourceRoot":"","sources":["../../src/interaction/tooltip.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AAE7D,MAAM,WAAW,UAAU;IACzB,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;CACf;
|
|
1
|
+
{"version":3,"file":"tooltip.d.ts","sourceRoot":"","sources":["../../src/interaction/tooltip.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AAE7D,MAAM,WAAW,UAAU;IACzB,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;CACf;AA2BD;;;GAGG;AACH,wBAAgB,iBAAiB,CAC/B,GAAG,EAAE,QAAQ,EACb,KAAK,EAAE,MAAM,EACb,IAAI,EAAE,SAAS,UAAU,EAAE,EAC3B,MAAM,EAAE,MAAM,GACb,WAAW,CAmCb;AAED,MAAM,WAAW,YAAY;IAC3B,OAAO,IAAI,IAAI,CAAC;IAChB,EAAE,EAAE,cAAc,GAAG,IAAI,CAAC;IAC1B,IAAI,IAAI,IAAI,CAAC;IACb,iGAAiG;IACjG,IAAI,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,EAAE,KAAK,EAAE,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,IAAI,GAAG,MAAM,GAAG,IAAI,CAAC;IACxF,CAAC,MAAM,CAAC,OAAO,CAAC,IAAI,IAAI,CAAC;CAC1B;AAED,wBAAgB,aAAa,CAAC,SAAS,EAAE,WAAW,EAAE,MAAM,CAAC,EAAE,aAAa,GAAG,IAAI,GAAG,YAAY,CA0HjG"}
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import{computePosition as e,flip as t,
|
|
1
|
+
import{computePosition as e,flip as t,getClippingAncestorRect as n,offset as r,shift as i}from"@vielzeug/orbit";var a=`position:absolute;width:1px;height:1px;overflow:hidden;clip-path:inset(50%);white-space:nowrap`;function shiftBoundary(e,t){let r=e.getBoundingClientRect(),i={height:r.height,width:r.width,x:r.x,y:r.y},a=n(t),o=Math.max(i.x,a.x),s=Math.max(i.y,a.y),c=Math.min(i.x+i.width,a.x+a.width),l=Math.min(i.y+i.height,a.y+a.height);return c<=o||l<=s?i:{height:l-s,width:c-o,x:o,y:s}}function comparisonContent(e,t,n,r){let i=e.createElement(`div`),o=e.createElement(`span`),s=e.createElement(`div`),c=e.createElement(`div`);i.className=`prism-tooltip-body`,o.className=`prism-tooltip-sr`,o.style.cssText=a,o.textContent=r,s.setAttribute(`aria-hidden`,`true`),c.className=`prism-tooltip-title`,c.textContent=t,s.appendChild(c);for(let t of n){let n=e.createElement(`div`),r=e.createElement(`span`),i=e.createElement(`span`),a=e.createElement(`span`);n.className=`prism-tooltip-row`,r.className=`prism-tooltip-swatch`,r.style.setProperty(`--prism-swatch`,t.color),i.className=`prism-tooltip-name`,i.textContent=t.name,a.className=`prism-tooltip-value`,a.textContent=t.value,n.append(r,i,a),s.appendChild(n)}return i.append(o,s),i}function createTooltip(n,a){let o=n.style.position,s=getComputedStyle(n).position===`static`;s&&(n.style.position=`relative`);let c=n.ownerDocument.createElement(`div`);c.className=`prism-tooltip`,c.style.position=`absolute`,c.style.pointerEvents=`none`,c.style.top=`0`,c.style.left=`0`,c.setAttribute(`role`,`status`),c.setAttribute(`aria-live`,`polite`),n.appendChild(c);let l=a!==!0&&a?.offset||8,u=a===!0?void 0:a?.render,setVisible=e=>{c.classList.toggle(`prism-tooltip--visible`,e),c.style.opacity=e?`1`:`0`},d=null,f=null,p=!1,cancelClear=()=>{f!==null&&(clearTimeout(f),f=null)},cancelReveal=()=>{d!==null&&(cancelAnimationFrame(d),d=null)},disposeHandle=()=>{cancelReveal(),cancelClear(),c.remove(),s&&n.style.position===`relative`&&(n.style.position=o)};return{dispose:disposeHandle,el:c,hide(){cancelReveal(),cancelClear(),p=!1,setVisible(!1),f=setTimeout(()=>{f=null,p||(c.textContent=``)},150)},show(a,o,s,f,m){if(!n.isConnected)return;cancelClear();let h=u?u(s,f):m??`${f.name}: ${s.value}`;typeof h==`string`?c.textContent=h:c.replaceChildren(h);let{x:g,y:_}=e({getBoundingClientRect:()=>{let e=n.getBoundingClientRect();return{bottom:e.top+o,height:0,left:e.left+a,right:e.left+a,top:e.top+o,width:0,x:e.left+a,y:e.top+o}}},c,{containingBlock:n,middleware:[r(l),t(),i({boundary:shiftBoundary(n,c),padding:8})],placement:`top`});if(c.style.left=`${g}px`,c.style.top=`${_}px`,p){d===null&&setVisible(!0);return}p=!0,setVisible(!1),d=requestAnimationFrame(()=>{d=null,p&&setVisible(!0)})},[Symbol.dispose]:disposeHandle}}export{comparisonContent,createTooltip};
|
|
2
2
|
//# sourceMappingURL=tooltip.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"tooltip.js","names":[],"sources":["../../src/interaction/tooltip.ts"],"sourcesContent":["import { computePosition, flip, offset, shift } from '@vielzeug/orbit';\nimport type { Datum, Series, TooltipConfig } from '../types';\n\nexport interface TooltipRow {\n color: string;\n name: string;\n value: string;\n}\n\n/** Inline so the spoken text stays hidden even without prism's stylesheet. */\nconst SCREEN_READER_ONLY =\n 'position:absolute;width:1px;height:1px;overflow:hidden;clip-path:inset(50%);white-space:nowrap';\n\n/**\n * Title plus one swatch row per series; text only, so data is never parsed as HTML.\n * `spoken` is what the tooltip's live region announces; the visual rows are aria-hidden.\n */\nexport function comparisonContent(\n doc: Document,\n title: string,\n rows: readonly TooltipRow[],\n spoken: string,\n): HTMLElement {\n const body = doc.createElement('div');\n const sr = doc.createElement('span');\n const visual = doc.createElement('div');\n const head = doc.createElement('div');\n\n body.className = 'prism-tooltip-body';\n sr.className = 'prism-tooltip-sr';\n sr.style.cssText = SCREEN_READER_ONLY;\n sr.textContent = spoken;\n visual.setAttribute('aria-hidden', 'true');\n head.className = 'prism-tooltip-title';\n head.textContent = title;\n visual.appendChild(head);\n\n for (const row of rows) {\n const line = doc.createElement('div');\n const swatch = doc.createElement('span');\n const name = doc.createElement('span');\n const value = doc.createElement('span');\n\n line.className = 'prism-tooltip-row';\n swatch.className = 'prism-tooltip-swatch';\n swatch.style.setProperty('--prism-swatch', row.color);\n name.className = 'prism-tooltip-name';\n name.textContent = row.name;\n value.className = 'prism-tooltip-value';\n value.textContent = row.value;\n line.append(swatch, name, value);\n visual.appendChild(line);\n }\n\n body.append(sr, visual);\n\n return body;\n}\n\nexport interface TooltipState {\n dispose(): void;\n el: HTMLDivElement | null;\n hide(): void;\n /** `content` replaces the default `series: value` body when no custom `render` is configured. */\n show(x: number, y: number, datum: Datum, series: Series, content?: Node | string): void;\n [Symbol.dispose](): void;\n}\n\nexport function createTooltip(container: HTMLElement, config?: TooltipConfig | true): TooltipState {\n const previousPosition = container.style.position;\n const changedPosition = getComputedStyle(container).position === 'static';\n\n if (changedPosition) container.style.position = 'relative';\n\n const el = container.ownerDocument.createElement('div');\n\n el.className = 'prism-tooltip';\n el.style.position = 'absolute';\n el.style.pointerEvents = 'none';\n el.style.top = '0';\n el.style.left = '0';\n // Non-modal status text: announced by assistive tech whenever content/hide state changes.\n el.setAttribute('role', 'status');\n el.setAttribute('aria-live', 'polite');\n container.appendChild(el);\n\n const tooltipOffset: number = (config !== true && config?.offset) || 8;\n const render = config !== true ? config?.render : undefined;\n\n // Opacity stays inline (the transition is declared in CSS against the token\n // duration); the class carries the rise, which needs to combine with the\n // left/top positioning without fighting it.\n const setVisible = (next: boolean): void => {\n el.classList.toggle('prism-tooltip--visible', next);\n el.style.opacity = next ? '1' : '0';\n };\n\n let revealFrame: number | null = null;\n let clearTimer: ReturnType<typeof setTimeout> | null = null;\n let visible = false;\n\n const cancelClear = (): void => {\n if (clearTimer === null) return;\n\n clearTimeout(clearTimer);\n clearTimer = null;\n };\n\n const cancelReveal = (): void => {\n if (revealFrame === null) return;\n\n cancelAnimationFrame(revealFrame);\n revealFrame = null;\n };\n\n const disposeHandle = (): void => {\n cancelReveal();\n cancelClear();\n el.remove();\n if (changedPosition && container.style.position === 'relative') container.style.position = previousPosition;\n };\n\n return {\n dispose: disposeHandle,\n el,\n hide() {\n cancelReveal();\n cancelClear();\n visible = false;\n setVisible(false);\n clearTimer = setTimeout(() => {\n clearTimer = null;\n\n if (!visible) el.textContent = '';\n }, 150);\n },\n show(x: number, y: number, datum: Datum, series: Series, content?: Node | string) {\n if (!container.isConnected) return;\n\n cancelClear();\n\n const body = render ? render(datum, series) : (content ?? `${series.name}: ${datum.value}`);\n\n if (typeof body === 'string') el.textContent = body;\n else el.replaceChildren(body);\n\n const virtualRef = {\n getBoundingClientRect: () => {\n const rect = container.getBoundingClientRect();\n\n return {\n bottom: rect.top + y,\n height: 0,\n left: rect.left + x,\n right: rect.left + x,\n top: rect.top + y,\n width: 0,\n x: rect.left + x,\n y: rect.top + y,\n };\n },\n };\n\n const { x: positionX, y: positionY } = computePosition(virtualRef, el, {\n containingBlock: container,\n middleware: [offset(tooltipOffset), flip(), shift({ padding: 8 })],\n placement: 'top',\n });\n\n el.style.left = `${positionX}px`;\n el.style.top = `${positionY}px`;\n\n if (visible) {\n if (revealFrame === null) setVisible(true);\n\n return;\n }\n\n visible = true;\n setVisible(false);\n // One frame at the hidden state first: without it the browser has no\n // starting value to transition from and the tooltip would just appear.\n revealFrame = requestAnimationFrame(() => {\n revealFrame = null;\n\n if (visible) setVisible(true);\n });\n },\n [Symbol.dispose]: disposeHandle,\n };\n}\n"],"mappings":"mFAUA,IAAM,EACJ,iGAMF,SAAgB,kBACd,EACA,EACA,EACA,EACa,CACb,IAAM,EAAO,EAAI,cAAc,KAAK,EAC9B,EAAK,EAAI,cAAc,MAAM,EAC7B,EAAS,EAAI,cAAc,KAAK,EAChC,EAAO,EAAI,cAAc,KAAK,EAEpC,EAAK,UAAY,qBACjB,EAAG,UAAY,mBACf,EAAG,MAAM,QAAU,EACnB,EAAG,YAAc,EACjB,EAAO,aAAa,cAAe,MAAM,EACzC,EAAK,UAAY,sBACjB,EAAK,YAAc,EACnB,EAAO,YAAY,CAAI,EAEvB,IAAK,IAAM,KAAO,EAAM,CACtB,IAAM,EAAO,EAAI,cAAc,KAAK,EAC9B,EAAS,EAAI,cAAc,MAAM,EACjC,EAAO,EAAI,cAAc,MAAM,EAC/B,EAAQ,EAAI,cAAc,MAAM,EAEtC,EAAK,UAAY,oBACjB,EAAO,UAAY,uBACnB,EAAO,MAAM,YAAY,iBAAkB,EAAI,KAAK,EACpD,EAAK,UAAY,qBACjB,EAAK,YAAc,EAAI,KACvB,EAAM,UAAY,sBAClB,EAAM,YAAc,EAAI,MACxB,EAAK,OAAO,EAAQ,EAAM,CAAK,EAC/B,EAAO,YAAY,CAAI,CACzB,CAIA,OAFA,EAAK,OAAO,EAAI,CAAM,EAEf,CACT,CAWA,SAAgB,cAAc,EAAwB,EAA6C,CACjG,IAAM,EAAmB,EAAU,MAAM,SACnC,EAAkB,iBAAiB,CAAS,CAAC,CAAC,WAAa,SAE7D,IAAiB,EAAU,MAAM,SAAW,YAEhD,IAAM,EAAK,EAAU,cAAc,cAAc,KAAK,EAEtD,EAAG,UAAY,gBACf,EAAG,MAAM,SAAW,WACpB,EAAG,MAAM,cAAgB,OACzB,EAAG,MAAM,IAAM,IACf,EAAG,MAAM,KAAO,IAEhB,EAAG,aAAa,OAAQ,QAAQ,EAChC,EAAG,aAAa,YAAa,QAAQ,EACrC,EAAU,YAAY,CAAE,EAExB,IAAM,EAAyB,IAAW,IAAQ,GAAQ,QAAW,EAC/D,EAAS,IAAW,GAAwB,IAAA,GAAjB,GAAQ,OAKnC,WAAc,GAAwB,CAC1C,EAAG,UAAU,OAAO,yBAA0B,CAAI,EAClD,EAAG,MAAM,QAAU,EAAO,IAAM,GAClC,EAEI,EAA6B,KAC7B,EAAmD,KACnD,EAAU,GAER,gBAA0B,CAC1B,IAAe,OAEnB,aAAa,CAAU,EACvB,EAAa,KACf,EAEM,iBAA2B,CAC3B,IAAgB,OAEpB,qBAAqB,CAAW,EAChC,EAAc,KAChB,EAEM,kBAA4B,CAChC,aAAa,EACb,YAAY,EACZ,EAAG,OAAO,EACN,GAAmB,EAAU,MAAM,WAAa,aAAY,EAAU,MAAM,SAAW,EAC7F,EAEA,MAAO,CACL,QAAS,cACT,KACA,MAAO,CACL,aAAa,EACb,YAAY,EACZ,EAAU,GACV,WAAW,EAAK,EAChB,EAAa,eAAiB,CAC5B,EAAa,KAER,IAAS,EAAG,YAAc,GACjC,EAAG,GAAG,CACR,EACA,KAAK,EAAW,EAAW,EAAc,EAAgB,EAAyB,CAChF,GAAI,CAAC,EAAU,YAAa,OAE5B,YAAY,EAEZ,IAAM,EAAO,EAAS,EAAO,EAAO,CAAM,EAAK,GAAW,GAAG,EAAO,KAAK,IAAI,EAAM,QAE/E,OAAO,GAAS,SAAU,EAAG,YAAc,EAC1C,EAAG,gBAAgB,CAAI,EAmB5B,GAAM,CAAE,EAAG,EAAW,EAAG,GAAc,EAAgB,CAhBrD,0BAA6B,CAC3B,IAAM,EAAO,EAAU,sBAAsB,EAE7C,MAAO,CACL,OAAQ,EAAK,IAAM,EACnB,OAAQ,EACR,KAAM,EAAK,KAAO,EAClB,MAAO,EAAK,KAAO,EACnB,IAAK,EAAK,IAAM,EAChB,MAAO,EACP,EAAG,EAAK,KAAO,EACf,EAAG,EAAK,IAAM,CAChB,CACF,CAGqD,EAAY,EAAI,CACrE,gBAAiB,EACjB,WAAY,CAAC,EAAO,CAAa,EAAG,EAAK,EAAG,EAAM,CAAE,QAAS,CAAE,CAAC,CAAC,EACjE,UAAW,KACb,CAAC,EAKD,GAHA,EAAG,MAAM,KAAO,GAAG,EAAU,IAC7B,EAAG,MAAM,IAAM,GAAG,EAAU,IAExB,EAAS,CACP,IAAgB,MAAM,WAAW,EAAI,EAEzC,MACF,CAEA,EAAU,GACV,WAAW,EAAK,EAGhB,EAAc,0BAA4B,CACxC,EAAc,KAEV,GAAS,WAAW,EAAI,CAC9B,CAAC,CACH,GACC,OAAO,SAAU,aACpB,CACF"}
|
|
1
|
+
{"version":3,"file":"tooltip.js","names":[],"sources":["../../src/interaction/tooltip.ts"],"sourcesContent":["import { computePosition, flip, getClippingAncestorRect, offset, type Rect, shift } from '@vielzeug/orbit';\nimport type { Datum, Series, TooltipConfig } from '../types';\n\nexport interface TooltipRow {\n color: string;\n name: string;\n value: string;\n}\n\n/** Inline so the spoken text stays hidden even without prism's stylesheet. */\nconst SCREEN_READER_ONLY =\n 'position:absolute;width:1px;height:1px;overflow:hidden;clip-path:inset(50%);white-space:nowrap';\n\n/**\n * The shift boundary: the chart container intersected with the tooltip's clipping-ancestor\n * rect (what a host `overflow: hidden` actually shows). Clamping to the container alone would\n * let the tooltip sit inside a container that is itself clipped by a smaller ancestor; the\n * intersection keeps it inside what is actually visible. A degenerate intersection (zero area)\n * falls back to the container rect.\n */\nfunction shiftBoundary(container: HTMLElement, tooltip: HTMLElement): Rect {\n const box = container.getBoundingClientRect();\n const containerRect: Rect = { height: box.height, width: box.width, x: box.x, y: box.y };\n const visible = getClippingAncestorRect(tooltip);\n const x = Math.max(containerRect.x, visible.x);\n const y = Math.max(containerRect.y, visible.y);\n const right = Math.min(containerRect.x + containerRect.width, visible.x + visible.width);\n const bottom = Math.min(containerRect.y + containerRect.height, visible.y + visible.height);\n\n if (right <= x || bottom <= y) return containerRect;\n\n return { height: bottom - y, width: right - x, x, y };\n}\n\n/**\n * Title plus one swatch row per series; text only, so data is never parsed as HTML.\n * `spoken` is what the tooltip's live region announces; the visual rows are aria-hidden.\n */\nexport function comparisonContent(\n doc: Document,\n title: string,\n rows: readonly TooltipRow[],\n spoken: string,\n): HTMLElement {\n const body = doc.createElement('div');\n const sr = doc.createElement('span');\n const visual = doc.createElement('div');\n const head = doc.createElement('div');\n\n body.className = 'prism-tooltip-body';\n sr.className = 'prism-tooltip-sr';\n sr.style.cssText = SCREEN_READER_ONLY;\n sr.textContent = spoken;\n visual.setAttribute('aria-hidden', 'true');\n head.className = 'prism-tooltip-title';\n head.textContent = title;\n visual.appendChild(head);\n\n for (const row of rows) {\n const line = doc.createElement('div');\n const swatch = doc.createElement('span');\n const name = doc.createElement('span');\n const value = doc.createElement('span');\n\n line.className = 'prism-tooltip-row';\n swatch.className = 'prism-tooltip-swatch';\n swatch.style.setProperty('--prism-swatch', row.color);\n name.className = 'prism-tooltip-name';\n name.textContent = row.name;\n value.className = 'prism-tooltip-value';\n value.textContent = row.value;\n line.append(swatch, name, value);\n visual.appendChild(line);\n }\n\n body.append(sr, visual);\n\n return body;\n}\n\nexport interface TooltipState {\n dispose(): void;\n el: HTMLDivElement | null;\n hide(): void;\n /** `content` replaces the default `series: value` body when no custom `render` is configured. */\n show(x: number, y: number, datum: Datum, series: Series, content?: Node | string): void;\n [Symbol.dispose](): void;\n}\n\nexport function createTooltip(container: HTMLElement, config?: TooltipConfig | true): TooltipState {\n const previousPosition = container.style.position;\n const changedPosition = getComputedStyle(container).position === 'static';\n\n if (changedPosition) container.style.position = 'relative';\n\n const el = container.ownerDocument.createElement('div');\n\n el.className = 'prism-tooltip';\n el.style.position = 'absolute';\n el.style.pointerEvents = 'none';\n el.style.top = '0';\n el.style.left = '0';\n // Non-modal status text: announced by assistive tech whenever content/hide state changes.\n el.setAttribute('role', 'status');\n el.setAttribute('aria-live', 'polite');\n container.appendChild(el);\n\n const tooltipOffset: number = (config !== true && config?.offset) || 8;\n const render = config !== true ? config?.render : undefined;\n\n // Opacity stays inline (the transition is declared in CSS against the token\n // duration); the class carries the rise, which needs to combine with the\n // left/top positioning without fighting it.\n const setVisible = (next: boolean): void => {\n el.classList.toggle('prism-tooltip--visible', next);\n el.style.opacity = next ? '1' : '0';\n };\n\n let revealFrame: number | null = null;\n let clearTimer: ReturnType<typeof setTimeout> | null = null;\n let visible = false;\n\n const cancelClear = (): void => {\n if (clearTimer === null) return;\n\n clearTimeout(clearTimer);\n clearTimer = null;\n };\n\n const cancelReveal = (): void => {\n if (revealFrame === null) return;\n\n cancelAnimationFrame(revealFrame);\n revealFrame = null;\n };\n\n const disposeHandle = (): void => {\n cancelReveal();\n cancelClear();\n el.remove();\n if (changedPosition && container.style.position === 'relative') container.style.position = previousPosition;\n };\n\n return {\n dispose: disposeHandle,\n el,\n hide() {\n cancelReveal();\n cancelClear();\n visible = false;\n setVisible(false);\n clearTimer = setTimeout(() => {\n clearTimer = null;\n\n if (!visible) el.textContent = '';\n }, 150);\n },\n show(x: number, y: number, datum: Datum, series: Series, content?: Node | string) {\n if (!container.isConnected) return;\n\n cancelClear();\n\n const body = render ? render(datum, series) : (content ?? `${series.name}: ${datum.value}`);\n\n if (typeof body === 'string') el.textContent = body;\n else el.replaceChildren(body);\n\n const virtualRef = {\n getBoundingClientRect: () => {\n const rect = container.getBoundingClientRect();\n\n return {\n bottom: rect.top + y,\n height: 0,\n left: rect.left + x,\n right: rect.left + x,\n top: rect.top + y,\n width: 0,\n x: rect.left + x,\n y: rect.top + y,\n };\n },\n };\n\n const { x: positionX, y: positionY } = computePosition(virtualRef, el, {\n containingBlock: container,\n middleware: [offset(tooltipOffset), flip(), shift({ boundary: shiftBoundary(container, el), padding: 8 })],\n placement: 'top',\n });\n\n el.style.left = `${positionX}px`;\n el.style.top = `${positionY}px`;\n\n if (visible) {\n if (revealFrame === null) setVisible(true);\n\n return;\n }\n\n visible = true;\n setVisible(false);\n // One frame at the hidden state first: without it the browser has no\n // starting value to transition from and the tooltip would just appear.\n revealFrame = requestAnimationFrame(() => {\n revealFrame = null;\n\n if (visible) setVisible(true);\n });\n },\n [Symbol.dispose]: disposeHandle,\n };\n}\n"],"mappings":"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"}
|