@orchidsoftware/charts 0.0.1

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.
Files changed (109) hide show
  1. package/CHANGELOG.md +71 -0
  2. package/LICENSE +21 -0
  3. package/README.md +122 -0
  4. package/dist/core/Chart.js +1 -0
  5. package/dist/core/Chart.js.map +1 -0
  6. package/dist/core/ChartData.js +1 -0
  7. package/dist/core/ChartData.js.map +1 -0
  8. package/dist/core/ChartDefinition.js +1 -0
  9. package/dist/core/ChartDefinition.js.map +1 -0
  10. package/dist/core/ChartSelection.js +1 -0
  11. package/dist/core/ChartSelection.js.map +1 -0
  12. package/dist/core/ChartTooltip.js +1 -0
  13. package/dist/core/ChartTooltip.js.map +1 -0
  14. package/dist/core/InteractionController.js +1 -0
  15. package/dist/core/InteractionController.js.map +1 -0
  16. package/dist/core/NormalizeAnnotations.js +1 -0
  17. package/dist/core/NormalizeAnnotations.js.map +1 -0
  18. package/dist/core/Options.js +1 -0
  19. package/dist/core/Options.js.map +1 -0
  20. package/dist/core/builders/Builder.js +1 -0
  21. package/dist/core/builders/Builder.js.map +1 -0
  22. package/dist/core/builders/BuilderArguments.js +1 -0
  23. package/dist/core/builders/BuilderArguments.js.map +1 -0
  24. package/dist/core/builders/BuilderScopes.js +1 -0
  25. package/dist/core/builders/BuilderScopes.js.map +1 -0
  26. package/dist/core/builders/BuilderState.js +1 -0
  27. package/dist/core/builders/BuilderState.js.map +1 -0
  28. package/dist/core/builders/BuilderValidation.js +1 -0
  29. package/dist/core/builders/BuilderValidation.js.map +1 -0
  30. package/dist/core/builders/CartesianBuilders.js +1 -0
  31. package/dist/core/builders/CartesianBuilders.js.map +1 -0
  32. package/dist/core/builders/CompositionBuilders.js +1 -0
  33. package/dist/core/builders/CompositionBuilders.js.map +1 -0
  34. package/dist/core/builders/TemporalBuilders.js +1 -0
  35. package/dist/core/builders/TemporalBuilders.js.map +1 -0
  36. package/dist/index.js +1 -0
  37. package/dist/renderers/ChartRendering.js +1 -0
  38. package/dist/renderers/ChartRendering.js.map +1 -0
  39. package/dist/renderers/LegendRenderer.js +1 -0
  40. package/dist/renderers/LegendRenderer.js.map +1 -0
  41. package/dist/renderers/SvgSurface.js +1 -0
  42. package/dist/renderers/SvgSurface.js.map +1 -0
  43. package/dist/renderers/cartesian/CartesianAxesRenderer.js +1 -0
  44. package/dist/renderers/cartesian/CartesianAxesRenderer.js.map +1 -0
  45. package/dist/renderers/cartesian/CartesianInspectorRenderer.js +1 -0
  46. package/dist/renderers/cartesian/CartesianInspectorRenderer.js.map +1 -0
  47. package/dist/renderers/cartesian/CartesianLayout.js +1 -0
  48. package/dist/renderers/cartesian/CartesianLayout.js.map +1 -0
  49. package/dist/renderers/cartesian/CartesianRendering.js +1 -0
  50. package/dist/renderers/cartesian/CartesianRendering.js.map +1 -0
  51. package/dist/renderers/cartesian/CartesianSeriesRendering.js +1 -0
  52. package/dist/renderers/cartesian/CartesianSeriesRendering.js.map +1 -0
  53. package/dist/renderers/composition/AggregationRendering.js +1 -0
  54. package/dist/renderers/composition/AggregationRendering.js.map +1 -0
  55. package/dist/renderers/composition/Composition.js +1 -0
  56. package/dist/renderers/composition/Composition.js.map +1 -0
  57. package/dist/renderers/composition/PolarAreaRendering.js +1 -0
  58. package/dist/renderers/composition/PolarAreaRendering.js.map +1 -0
  59. package/dist/renderers/composition/RadarRendering.js +1 -0
  60. package/dist/renderers/composition/RadarRendering.js.map +1 -0
  61. package/dist/renderers/temporal/HeatmapRendering.js +1 -0
  62. package/dist/renderers/temporal/HeatmapRendering.js.map +1 -0
  63. package/dist/renderers/temporal/TimesheetLayout.js +1 -0
  64. package/dist/renderers/temporal/TimesheetLayout.js.map +1 -0
  65. package/dist/renderers/temporal/TimesheetRendering.js +1 -0
  66. package/dist/renderers/temporal/TimesheetRendering.js.map +1 -0
  67. package/dist/support/Constants.js +1 -0
  68. package/dist/support/Constants.js.map +1 -0
  69. package/dist/support/Dom.js +1 -0
  70. package/dist/support/Dom.js.map +1 -0
  71. package/dist/support/Normalize.js +2 -0
  72. package/dist/support/Normalize.js.map +1 -0
  73. package/dist/support/Validation.js +1 -0
  74. package/dist/support/Validation.js.map +1 -0
  75. package/dist/support/geometry/CartesianGeometry.js +1 -0
  76. package/dist/support/geometry/CartesianGeometry.js.map +1 -0
  77. package/dist/support/geometry/Scale.js +1 -0
  78. package/dist/support/geometry/Scale.js.map +1 -0
  79. package/dist/support/geometry/SectorGeometry.js +1 -0
  80. package/dist/support/geometry/SectorGeometry.js.map +1 -0
  81. package/dist/support/presentation/Formatting.js +1 -0
  82. package/dist/support/presentation/Formatting.js.map +1 -0
  83. package/dist/support/presentation/Presentation.js +1 -0
  84. package/dist/support/presentation/Presentation.js.map +1 -0
  85. package/dist/support/presentation/Time.js +1 -0
  86. package/dist/support/presentation/Time.js.map +1 -0
  87. package/docs/api-reference.md +322 -0
  88. package/docs/bar.md +110 -0
  89. package/docs/bubble.md +76 -0
  90. package/docs/chart-types.md +35 -0
  91. package/docs/customization.md +178 -0
  92. package/docs/donut.md +55 -0
  93. package/docs/exporting.md +87 -0
  94. package/docs/frameworks.md +203 -0
  95. package/docs/getting-started.md +121 -0
  96. package/docs/heatmap.md +95 -0
  97. package/docs/line.md +148 -0
  98. package/docs/mixed.md +114 -0
  99. package/docs/percentage.md +48 -0
  100. package/docs/pie.md +58 -0
  101. package/docs/polar-area.md +46 -0
  102. package/docs/radar.md +49 -0
  103. package/docs/readme.md +37 -0
  104. package/docs/scatter.md +83 -0
  105. package/docs/timesheet.md +125 -0
  106. package/docs/updates-and-interaction.md +124 -0
  107. package/package.json +133 -0
  108. package/src/index.d.ts +481 -0
  109. package/src/styles.css +350 -0
package/CHANGELOG.md ADDED
@@ -0,0 +1,71 @@
1
+ # Changelog
2
+
3
+ All notable changes to Charts2 are documented in this file. The format follows
4
+ [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and releases follow
5
+ [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
6
+
7
+ ## Unreleased
8
+
9
+ ## 0.0.1 - 2026-09-04
10
+
11
+ ### Added
12
+
13
+ - Twelve frozen named fluent definitions for SVG chart grammars: line, bar, scatter,
14
+ mixed axis, pie, donut, percentage, heatmap, bubble, radar, polar area, and
15
+ timesheet.
16
+ - Responsive rendering, export, updates, destruction, accessible chart
17
+ semantics, roving keyboard navigation, tooltips, and persistent selection.
18
+ - ESM, CSS, and TypeScript package entry points with zero runtime
19
+ dependencies.
20
+ - Browser tests for pointer, hover, pressed, focus, keyboard, selection, mobile,
21
+ dark, full-demo, and individual chart states.
22
+ - Automated formatting, linting, type checks, 100% code coverage, performance
23
+ budgets, builds, package inspection, dependency review, and CodeQL analysis.
24
+ - GitHub Pages deployment for the product demo and QA laboratory at
25
+ `charts.orchid.software`.
26
+
27
+ ### Changed
28
+
29
+ - Require JavaScript array literals with three or more elements to place each
30
+ element on its own line, with matching ESLint and Prettier enforcement.
31
+ - Require directly returned object literals with two or more properties to
32
+ place each property on its own line.
33
+ - Updated the public declaration compatibility gate to TypeScript 7. SonarJS
34
+ remains isolated on TypeScript 5.9.3 until its analyzer supports the newer
35
+ compiler API.
36
+ - Switched the unpublished package to ESM-only module-preserving output with
37
+ closed exports and explicit CSS loading.
38
+ - Bound each named definition directly to its model and renderer functions so
39
+ root imports can tree-shake unused chart families without subpath imports.
40
+ - Added per-family gzip/source gates, SSR import coverage, and a compact
41
+ Chromium/Firefox/WebKit lifecycle suite.
42
+ - Added documented no-build/import-map consumption with a native static-server
43
+ test in Chromium, Firefox, and WebKit.
44
+ - Corrected the release size gate to use the actual module-preserving package
45
+ baseline while retaining strict per-family and production-source ratchets.
46
+
47
+ - Reframed the demo as a concise product story with exact API language,
48
+ verifiable release metrics, a stronger usage ending, and a compact release
49
+ footer.
50
+ - Moved demanding datasets into an optional Quality Lab disclosure so precision,
51
+ localization, density, signed values, and flat-series evidence remains
52
+ inspectable without overwhelming the primary showcase.
53
+ - Aligned the package description, README, and demo around one evidence-backed
54
+ promise: expressive SVG charts, one factory, useful defaults, and zero runtime
55
+ dependencies.
56
+ - Reduced the demo index to four real visual families in one proportional row;
57
+ frameless presentation remains a documented Line/Bar configuration instead of
58
+ pretending to be a fifth chart family.
59
+ - Rebuilt the family index icons as one SVG language with shared guides, stroke
60
+ weights, radii, fills, and semantic secondary-series color. Individual links
61
+ no longer render cramped hover cards around long labels such as Percentage.
62
+
63
+ - Replaced the inherited upstream constructor surface with one explicit,
64
+ discriminated configuration contract.
65
+ - Split lifecycle, render strategies, domain layout, and stateless support into
66
+ directional architectural boundaries.
67
+
68
+ ### Removed
69
+
70
+ - Deprecated constructors, the default export, implicit chart types, Sparkline,
71
+ compact aliases, and dormant compatibility code.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Alexander Chernyaev
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,122 @@
1
+ # Charts2
2
+
3
+ **Charts that belong in your product.**
4
+
5
+ Charts2 turns everyday product data into clear, responsive SVG charts. One
6
+ small JavaScript API covers dashboards, reports, activity views, and release
7
+ plans—without turning chart configuration into a project.
8
+
9
+ **[Explore the live demo →](https://charts.orchid.software)**
10
+
11
+ ![Charts2 rendering a revenue chart inside a product dashboard](https://raw.githubusercontent.com/orchidsoftware/charts/master/.github/assets/charts2-hero.png)
12
+
13
+ ```bash
14
+ npm install @orchidsoftware/charts
15
+ ```
16
+
17
+ Add an element for the chart:
18
+
19
+ ```html
20
+ <div id="revenue"></div>
21
+ ```
22
+
23
+ Then choose a chart, add your data, and render it:
24
+
25
+ ```js
26
+ import { LineChart } from "@orchidsoftware/charts";
27
+ import "@orchidsoftware/charts/style.css";
28
+
29
+ const revenue = LineChart.make("#revenue")
30
+ .labels(["Jan", "Feb", "Mar", "Apr"])
31
+ .dataset("Revenue", [42, 48, 57, 63])
32
+ .colors(["#2563eb"])
33
+ .height(300)
34
+ .gradient()
35
+ .render();
36
+ ```
37
+
38
+ The result is responsive and already includes axes, labels, a tooltip,
39
+ keyboard navigation, and accessible SVG text.
40
+
41
+ ## Product-ready by default
42
+
43
+ - **One fluent language.** Every chart starts with `make()`, accepts domain data,
44
+ and returns the same update, selection, export, and cleanup lifecycle.
45
+ - **Twelve focused chart types.** Trends, comparisons, composition, activity,
46
+ and planning use named definitions instead of a generic configuration maze.
47
+ - **Made for interfaces.** Responsive SVG, tooltips, keyboard navigation,
48
+ reduced motion, and dark product surfaces work together.
49
+ - **Small and typed.** Charts2 has zero runtime dependencies, tree-shakeable
50
+ ESM, explicit CSS, source maps, and TypeScript declarations.
51
+
52
+ ## Shape the result
53
+
54
+ Chart-wide methods keep common changes close to the data:
55
+
56
+ ```js
57
+ import { BarChart } from "@orchidsoftware/charts";
58
+
59
+ const orders = BarChart.make("#orders")
60
+ .title("Orders by status")
61
+ .labels(["Web", "Retail", "Partners"])
62
+ .dataset("Shipped", [124, 86, 43])
63
+ .dataset("Processing", [32, 18, 12])
64
+ .colors(["#2563eb", "#f59e0b"])
65
+ .horizontal()
66
+ .stacked()
67
+ .height(280)
68
+ .render();
69
+ ```
70
+
71
+ Use the same vocabulary across all twelve chart types:
72
+
73
+ ```text
74
+ Choose a chart → make(parent) → add data → shape it → render()
75
+ ```
76
+
77
+ ## Choose a chart
78
+
79
+ ![Line, stacked bar, donut, and timesheet charts rendered by Charts2](https://raw.githubusercontent.com/orchidsoftware/charts/master/.github/assets/charts2-gallery.png)
80
+
81
+ | If you want to show… | Start with… |
82
+ | --------------------------------- | ---------------------------------------------- |
83
+ | Change over time | `LineChart` |
84
+ | Comparisons between categories | `BarChart` |
85
+ | A line and bars on one scale | `MixedChart` |
86
+ | Relationships between values | `ScatterChart` or `BubbleChart` |
87
+ | Parts of a whole | `PieChart`, `DonutChart`, or `PercentageChart` |
88
+ | A profile across several measures | `RadarChart` or `PolarAreaChart` |
89
+ | Activity by day | `HeatmapChart` |
90
+ | Work across a timeline | `TimesheetChart` |
91
+
92
+ See [Chart types](./docs/chart-types.md) for a dedicated guide to every chart.
93
+
94
+ ## Update a chart
95
+
96
+ Keep the value returned by `render()` and replace its data when your product
97
+ state changes:
98
+
99
+ ```js
100
+ revenue.update({
101
+ labels: ["Jan", "Feb", "Mar", "Apr"],
102
+ datasets: [{ name: "Revenue", values: [45, 52, 61, 70] }],
103
+ });
104
+ ```
105
+
106
+ The same chart can also respond to selection, export its SVG, and clean up when
107
+ its view is removed.
108
+
109
+ ## Documentation
110
+
111
+ - [Getting started](./docs/getting-started.md) — install Charts2 and render your first chart.
112
+ - [Chart types](./docs/chart-types.md) — choose the chart that answers your question.
113
+ - [Customization](./docs/customization.md) — change color, layout, labels, and presentation.
114
+ - [Updates and interaction](./docs/updates-and-interaction.md) — connect charts to live product state.
115
+ - [Exporting SVG](./docs/exporting.md) — serialize or download the current chart.
116
+ - [React, Vue, and Hotwire](./docs/frameworks.md) — mount, update, and clean up charts in components.
117
+ - [API reference](./docs/api-reference.md) — scan the complete public vocabulary.
118
+
119
+ Charts2 has zero runtime dependencies, includes TypeScript declarations, and
120
+ targets current evergreen browsers. It is part of
121
+ [Orchid Software](https://orchid.software), maintained by
122
+ [@tabuna](https://github.com/tabuna), and released under the [MIT License](./LICENSE).
@@ -0,0 +1 @@
1
+ import{ChartOrientation as t,ChartType as e}from"../support/Constants.js";import{measureParentWidth as s,resolveParent as i,svg as o}from"../support/Dom.js";import{renderChart as n}from"../renderers/ChartRendering.js";import r from"./ChartTooltip.js";import a from"./InteractionController.js";import{normalizeChartOptions as h,validateChartColors as l}from"./Options.js";var d=".charts2-mark",c="charts2-scrollable-heatmap",m={latest:0},p=Object.freeze(["color","fill","fill-opacity","font-family","font-size","font-style","font-weight","letter-spacing","opacity","paint-order","shape-rendering","stroke","stroke-dasharray","stroke-linecap","stroke-linejoin","stroke-opacity","stroke-width","text-anchor","vector-effect"]),u=class{#t;#e;#s;#i;#o;#n;#r;#a;#h;#l;#d=null;#c=-1;#m=-1;#p=null;#u;#v;#b;#y=null;#f=null;#w=!1;constructor(t,e,s){const o=s.createModel(e.type,e.data,{colors:e.colors,maxSlices:e.maxSlices}),n=i(t),a=h(n,e);this.#t=n,this.#e=a.options.type,this.#s=s,this.#o=(m.latest+=1,m.latest),this.#i=a.hasCustomColors,this.#n=void 0===e.width,this.#r=a.options,this.#a=o,this.#h=this.#g(),this.#l=new r(this.#t,this.#h,this.#o),this.#I(this.#h,this.#a),this.#M(this.#h),this.#x(),this.#L(),this.#n&&(this.#y=this.#k.bind(this),this.#C())}#g(s=this.#r){const i=o("svg",{viewBox:`0 0 ${s.width} ${s.height}`,width:"100%",height:s.height,role:"group","aria-roledescription":"chart","aria-label":s.ariaLabel,preserveAspectRatio:"xMidYMid meet"});return i.classList.add("charts2-chart"),this.#e===e.HEATMAP&&i.classList.add("charts2-heatmap-chart"),this.#e===e.TIMESHEET&&i.classList.add("charts2-timesheet-chart"),this.#e===e.BAR&&s.orientation===t.HORIZONTAL&&i.classList.add("charts2-horizontal-bar"),i}#M(t){const e=t;this.#t.classList.add("charts2-host"),this.#t.classList.toggle(c,"true"===t.dataset.scrollable),delete e.dataset.scrollable,this.#t.replaceChildren(t,this.#l.element)}#x(){this.#u=this.#E.bind(this),this.#v=this.#l.hide.bind(this.#l),this.#b=this.#S.bind(this),this.#r.tooltip&&this.#h.addEventListener("mousemove",this.#u),this.#h.addEventListener("mouseleave",this.#v),document.addEventListener("pointerdown",this.#b)}get element(){return this.#h}update(t){this.#P();const e=this.#s.createModel(this.#e,t,{colors:this.#r.colors,maxSlices:this.#r.maxSlices});l(this.#t,t);const s=this.#g();return this.#I(s,e),this.#z(s,e),this}point(t){this.#P();const e=this.#c>=0?this.#c:Math.max(0,this.#m),s=t??e;if(!Number.isSafeInteger(s)||s<0)throw new TypeError("Chart point index must be a non-negative integer");return this.#a.pointAt(s)}toSvg(){this.#P();const t=this.#h.cloneNode(!0);t.setAttribute("xmlns","http://www.w3.org/2000/svg");const e=[this.#h,...this.#h.querySelectorAll("*")],s=[t,...t.querySelectorAll("*")];for(const[i,o]of e.entries()){const t=getComputedStyle(o),e=s[i];for(const s of p){const i=t.getPropertyValue(s);e.style.setProperty(s,i)}}return(new XMLSerializer).serializeToString(t)}download(t=this.#r.title??"Chart"){if(this.#P(),"string"!=typeof t||""===t.trim()||/[\\/]/u.test(t))throw new TypeError("Download filename must be non-empty and must not contain path separators");const e=t.toLowerCase().endsWith(".svg")?t:`${t}.svg`,s=document.createElement("a");s.download=e;const i=URL.createObjectURL(new Blob([this.toSvg()],{type:"image/svg+xml"}));return s.href=i,s.click(),URL.revokeObjectURL(i),this}destroy(){this.#w||(this.#w=!0,this.#h.removeEventListener("mousemove",this.#u),this.#h.removeEventListener("mouseleave",this.#v),document.removeEventListener("pointerdown",this.#b),this.#y&&window.removeEventListener("resize",this.#y),this.#f?.disconnect(),this.#h.remove(),this.#l.destroy(),this.#t.classList.remove("charts2-host",c))}#k(){if(this.#w)return;const t=s(this.#t,this.#r.width);if(t===this.#r.width)return;const e={...this.#r,width:t},i=this.#g(e);this.#I(i,this.#a,e);const o=this.#A(i,this.#a);this.#r=e,this.#R(i),this.#c=o,this.#L()}#C(){window.addEventListener("resize",this.#y),"function"==typeof ResizeObserver&&(this.#f=new ResizeObserver(this.#y),this.#f.observe(this.#t,{box:"content-box"}))}#E(t){const e=t.target.closest(d);e&&this.#h.contains(e)?this.#l.show(e,e.dataset.tooltip,this.#r):this.#l.hide()}#S(t){const e=t.target.closest(d);e&&this.#h.contains(e)||this.#d?.dismiss()}#I(t,e,s=this.#r){const i=o("desc");if(i.textContent=s.description??this.#D(e),t.append(i),s.title){const e=o("text",{x:16,y:22,class:"charts2-title"});e.textContent=s.title,t.append(e)}n({host:this.#t,element:t,options:s,source:e.source,datasets:e.datasets,labels:e.labels,heatmap:e.heatmap,timesheet:e.timesheet,hasCustomColors:this.#i,id:this.#o},this.#s.render)}#D(t){if(this.#e===e.HEATMAP)return`${this.#r.ariaLabel}. ${t.heatmap.length} heatmap cells.`;if(this.#e===e.TIMESHEET)return`${this.#r.ariaLabel}. ${t.timesheet.tasks.length} tasks.`;const s=t.datasets.map(t=>t.identityName??t.name).join(", "),i=` ${t.source.yMarkers.length} markers and ${t.source.yRegions.length} regions.`,o=` in ${s}`;return`${this.#r.ariaLabel}. ${t.labels.length} values${o}.${i}`}#z(t,e){const s=this.#A(t,e);this.#R(t),this.#a=e,this.#c=s,s<0&&(this.#p=null),this.#l.hide(),this.#L()}#A(t,e){if(!this.#p)return-1;const s=this.#$(t).flatMap((t,s)=>e.identityFor(t)===this.#p?[s]:[]);return 1===s.length?s[0]:-1}#R(t){const e=[...this.#h.attributes];for(const s of e)this.#h.removeAttribute(s.name);for(const s of t.attributes)this.#h.setAttribute(s.name,s.value);this.#t.classList.toggle(c,"true"===t.dataset.scrollable),delete this.#h.dataset.scrollable,this.#h.replaceChildren(...t.childNodes)}#P(){if(this.#w)throw new TypeError("Chart has been destroyed")}#L(){const t=this.#$(this.#h);for(const i of t)i.querySelector(":scope > title")?.remove();if(!this.#r.tooltip&&"function"!=typeof this.#r.onSelect){const t=this.#h.querySelectorAll(".charts2-visual-mark > title, .charts2-line > title");for(const e of t)e.remove();return void(this.#d=null)}const e={activeIndex:this.#c,allowPointerPan:this.#t.classList.contains(c),previewable:this.#r.tooltip,selectable:"function"==typeof this.#r.onSelect},s={labelFor:t=>t.dataset.tooltip,onShow:this.#r.tooltip?(t,e)=>this.#l.show(t,e,this.#r):()=>{},onHide:()=>this.#l.hide(),onActiveChange:(t,e)=>this.#O(t,e),onFocusChange:t=>{this.#m=t}};this.#d=new a(t,e,s)}#$(t){const s=[...t.querySelectorAll(d)];return this.#e!==e.AXIS_MIXED?s:s.toSorted((t,e)=>Number(t.dataset.datasetIndex)-Number(e.dataset.datasetIndex)||Number(t.dataset.pointIndex)-Number(e.dataset.pointIndex))}#O(t,e){if(this.#c=t,t>=0&&e){const t=this.#a.selectionFor(e);return this.#p=this.#a.identityFor(e),this.#t.dispatchEvent(new CustomEvent("data-select",{detail:t})),void this.#r.onSelect?.(t)}this.#p=null,this.#t.dispatchEvent(new CustomEvent("data-select")),this.#r.onSelect?.()}};export{u as default};
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Chart.js","names":["#host","#type","#implementation","#id","#hasCustomColors","#autoWidth","#options","#model","#element","#createElement","#tooltip","#renderInto","#commitHostPresentation","#bindLifecycle","#bindInteractions","#boundResize","#resize","#bindResponsiveWidth","#boundPointerMove","#handlePointerMove","#boundPointerLeave","#boundDocumentPointerDown","#handleDocumentPointerDown","#assertMounted","#commitUpdate","#activeMarkIndex","#keyboardMarkIndex","#destroyed","#resizeObserver","#preservedIndex","#replaceSurface","#interactions","#generatedDescription","#selectionIdentity","#orderedMarks","#handleActiveChange"],"sources":["../../src/core/Chart.js"],"sourcesContent":["import { renderChart } from \"../renderers/ChartRendering.js\";\nimport { ChartOrientation, ChartType } from \"../support/Constants.js\";\nimport { measureParentWidth, resolveParent, svg } from \"../support/Dom.js\";\n\nimport ChartTooltip from \"./ChartTooltip.js\";\nimport InteractionController from \"./InteractionController.js\";\nimport { normalizeChartOptions, validateChartColors } from \"./Options.js\";\n\nconst MARK_SELECTOR = \".charts2-mark\";\nconst SCROLLABLE_HEATMAP_CLASS = \"charts2-scrollable-heatmap\";\nconst SVG_EXTENSION = \".svg\";\nconst chartIdSequence = { latest: 0 };\n\n/**\n * Allocates a process-local identifier for chart-owned DOM nodes.\n *\n * @returns {number} Monotonically increasing module-local identifier.\n */\nfunction nextChartId() {\n chartIdSequence.latest += 1;\n\n return chartIdSequence.latest;\n}\n\nconst EXPORT_STYLE_PROPERTIES = Object.freeze([\n \"color\",\n \"fill\",\n \"fill-opacity\",\n \"font-family\",\n \"font-size\",\n \"font-style\",\n \"font-weight\",\n \"letter-spacing\",\n \"opacity\",\n \"paint-order\",\n \"shape-rendering\",\n \"stroke\",\n \"stroke-dasharray\",\n \"stroke-linecap\",\n \"stroke-linejoin\",\n \"stroke-opacity\",\n \"stroke-width\",\n \"text-anchor\",\n \"vector-effect\",\n]);\n\n/**\n * Public lifecycle façade for one chart. All mutable model, rendering,\n * interaction, tooltip, and browser-resource details are private class state.\n */\nexport default class Chart {\n #host;\n #type;\n #implementation;\n #hasCustomColors;\n #id;\n #autoWidth;\n #options;\n #model;\n #element;\n #tooltip;\n #interactions = null;\n #activeMarkIndex = -1;\n #keyboardMarkIndex = -1;\n #selectionIdentity = null;\n #boundPointerMove;\n #boundPointerLeave;\n #boundDocumentPointerDown;\n #boundResize = null;\n #resizeObserver = null;\n #destroyed = false;\n\n /**\n * Validates options, mounts owned DOM, and renders the initial chart.\n *\n * @param {string | Element} parent - CSS selector or element that will host generated markup.\n * @param {import(\"../index.js\").ChartOptions} options - Complete chart configuration and initial data.\n * @param {object} implementation - Frozen model and rendering functions for one chart family.\n * @throws {TypeError} When options, dimensions, renderer settings, or data violate the public contract.\n */\n constructor(parent, options, implementation) {\n const model = implementation.createModel(options.type, options.data, {\n colors: options.colors,\n maxSlices: options.maxSlices,\n });\n\n const host = resolveParent(parent);\n const chartConfig = normalizeChartOptions(host, options);\n\n this.#host = host;\n this.#type = chartConfig.options.type;\n this.#implementation = implementation;\n this.#id = nextChartId();\n this.#hasCustomColors = chartConfig.hasCustomColors;\n this.#autoWidth = options.width === undefined;\n this.#options = chartConfig.options;\n this.#model = model;\n this.#element = this.#createElement();\n this.#tooltip = new ChartTooltip(this.#host, this.#element, this.#id);\n this.#renderInto(this.#element, this.#model);\n this.#commitHostPresentation(this.#element);\n this.#bindLifecycle();\n this.#bindInteractions();\n\n if (this.#autoWidth) {\n this.#boundResize = this.#resize.bind(this);\n this.#bindResponsiveWidth();\n }\n }\n\n /**\n * Creates a detached SVG surface for validation and atomic rendering.\n *\n * @param {object} [options=this.#options] - Candidate rendering options.\n * @returns {SVGSVGElement} Detached chart surface with stable root attributes.\n */\n #createElement(options = this.#options) {\n const element = svg(\"svg\", {\n viewBox: `0 0 ${options.width} ${options.height}`,\n width: \"100%\",\n height: options.height,\n role: \"group\",\n \"aria-roledescription\": \"chart\",\n \"aria-label\": options.ariaLabel,\n preserveAspectRatio: \"xMidYMid meet\",\n });\n\n element.classList.add(\"charts2-chart\");\n if (this.#type === ChartType.HEATMAP) {\n element.classList.add(\"charts2-heatmap-chart\");\n }\n\n if (this.#type === ChartType.TIMESHEET) {\n element.classList.add(\"charts2-timesheet-chart\");\n }\n\n if (this.#type === ChartType.BAR && options.orientation === ChartOrientation.HORIZONTAL) {\n element.classList.add(\"charts2-horizontal-bar\");\n }\n\n return element;\n }\n\n /**\n * Atomically mounts a completely rendered surface and its tooltip.\n *\n * @param {SVGSVGElement} element - Successfully rendered detached SVG.\n * @returns {void} Host-owned nodes and presentation classes commit together.\n */\n #commitHostPresentation(element) {\n const renderedElement = element;\n this.#host.classList.add(\"charts2-host\");\n this.#host.classList.toggle(SCROLLABLE_HEATMAP_CLASS, element.dataset.scrollable === \"true\");\n delete renderedElement.dataset.scrollable;\n this.#host.replaceChildren(element, this.#tooltip.element);\n }\n\n /**\n * Registers browser listeners that remain stable across render passes.\n *\n * @returns {void} Pointer listeners are bound to their lifecycle owners.\n */\n #bindLifecycle() {\n this.#boundPointerMove = this.#handlePointerMove.bind(this);\n this.#boundPointerLeave = this.#tooltip.hide.bind(this.#tooltip);\n this.#boundDocumentPointerDown = this.#handleDocumentPointerDown.bind(this);\n\n if (this.#options.tooltip) {\n this.#element.addEventListener(\"mousemove\", this.#boundPointerMove);\n }\n\n this.#element.addEventListener(\"mouseleave\", this.#boundPointerLeave);\n document.addEventListener(\"pointerdown\", this.#boundDocumentPointerDown);\n }\n\n /**\n * Exposes the owned SVG surface as the sole public DOM inspection boundary.\n *\n * @returns {SVGSVGElement} Current chart SVG element.\n */\n get element() {\n return this.#element;\n }\n\n /**\n * Replaces chart data after full normalization and redraws the current type.\n *\n * @param {object} data - New data payload compatible with the chart's immutable type.\n * @returns {import(\"../index.js\").Chart} Current chart instance after synchronous rendering.\n * @throws {TypeError} When the new payload violates normalization or renderer invariants.\n */\n update(data) {\n this.#assertMounted();\n\n const model = this.#implementation.createModel(this.#type, data, {\n colors: this.#options.colors,\n maxSlices: this.#options.maxSlices,\n });\n\n validateChartColors(this.#host, data);\n\n const staged = this.#createElement();\n this.#renderInto(staged, model);\n this.#commitUpdate(staged, model);\n\n return this;\n }\n\n /**\n * Reads one normalized point without exposing mutable model or renderer state.\n *\n * @param {number} [index=Math.max(0, this.#activeMarkIndex)] - Requested point, cell, or task index.\n * @returns {object | undefined} Type-appropriate normalized data at the requested index.\n */\n point(index) {\n this.#assertMounted();\n\n const fallbackIndex =\n this.#activeMarkIndex >= 0 ? this.#activeMarkIndex : Math.max(0, this.#keyboardMarkIndex);\n\n const requestedIndex = index ?? fallbackIndex;\n\n if (!Number.isSafeInteger(requestedIndex) || requestedIndex < 0) {\n throw new TypeError(\"Chart point index must be a non-negative integer\");\n }\n\n return this.#model.pointAt(requestedIndex);\n }\n\n /**\n * Serializes the current SVG tree for storage, inspection, or export.\n *\n * @returns {string} Complete SVG markup representing the current render.\n */\n toSvg() {\n this.#assertMounted();\n const clone = this.#element.cloneNode(true);\n clone.setAttribute(\"xmlns\", \"http://www.w3.org/2000/svg\");\n\n const originals = [\n this.#element,\n ...this.#element.querySelectorAll(\"*\"),\n ];\n\n const copies = [\n clone,\n ...clone.querySelectorAll(\"*\"),\n ];\n\n for (const [\n index,\n original,\n ] of originals.entries()) {\n const computed = getComputedStyle(original);\n const copy = copies[index];\n\n for (const property of EXPORT_STYLE_PROPERTIES) {\n const value = computed.getPropertyValue(property);\n\n copy.style.setProperty(property, value);\n }\n }\n\n return new XMLSerializer().serializeToString(clone);\n }\n\n /**\n * Starts a browser download containing the current chart as SVG markup.\n *\n * @param {string} [filename=this.#options.title ?? \"Chart\"] - Download name without the `.svg` extension.\n * @returns {import(\"../index.js\").Chart} Current chart instance for fluent lifecycle calls.\n */\n download(filename = this.#options.title ?? \"Chart\") {\n this.#assertMounted();\n if (typeof filename !== \"string\" || filename.trim() === \"\" || /[\\\\/]/u.test(filename)) {\n throw new TypeError(\"Download filename must be non-empty and must not contain path separators\");\n }\n\n const normalizedFilename = filename.toLowerCase().endsWith(SVG_EXTENSION) ? filename : `${filename}.svg`;\n const link = document.createElement(\"a\");\n link.download = normalizedFilename;\n\n const url = URL.createObjectURL(\n new Blob(\n [\n this.toSvg(),\n ],\n { type: \"image/svg+xml\" },\n ),\n );\n\n link.href = url;\n link.click();\n URL.revokeObjectURL(url);\n\n return this;\n }\n\n /**\n * Detaches global listeners and removes every DOM node owned by the chart.\n *\n * @returns {void} The chart becomes unusable after cleanup completes.\n */\n destroy() {\n if (this.#destroyed) {\n return;\n }\n\n this.#destroyed = true;\n this.#element.removeEventListener(\"mousemove\", this.#boundPointerMove);\n this.#element.removeEventListener(\"mouseleave\", this.#boundPointerLeave);\n document.removeEventListener(\"pointerdown\", this.#boundDocumentPointerDown);\n if (this.#boundResize) {\n window.removeEventListener(\"resize\", this.#boundResize);\n }\n\n this.#resizeObserver?.disconnect();\n\n this.#element.remove();\n this.#tooltip.destroy();\n this.#host.classList.remove(\"charts2-host\", SCROLLABLE_HEATMAP_CLASS);\n }\n\n /**\n * Re-measures an auto-width chart and redraws it against the new viewport.\n *\n * @returns {void} The existing chart instance and SVG are updated in place.\n */\n #resize() {\n if (this.#destroyed) {\n return;\n }\n\n const width = measureParentWidth(this.#host, this.#options.width);\n\n if (width === this.#options.width) {\n return;\n }\n\n const options = { ...this.#options, width };\n const staged = this.#createElement(options);\n this.#renderInto(staged, this.#model, options);\n const activeIndex = this.#preservedIndex(staged, this.#model);\n this.#options = options;\n this.#replaceSurface(staged);\n this.#activeMarkIndex = activeIndex;\n this.#bindInteractions();\n }\n\n /**\n * Observes parent content-box changes with a browser-compatible fallback.\n *\n * @returns {void} Exactly one responsive resize source is registered.\n */\n #bindResponsiveWidth() {\n window.addEventListener(\"resize\", this.#boundResize);\n if (typeof ResizeObserver === \"function\") {\n this.#resizeObserver = new ResizeObserver(this.#boundResize);\n this.#resizeObserver.observe(this.#host, { box: \"content-box\" });\n }\n }\n\n /**\n * Resolves the mark under a mouse event and previews its tooltip.\n *\n * @param {MouseEvent} event - Mouse movement event dispatched within the chart SVG.\n * @returns {void} Tooltip state is updated when an eligible mark is present.\n */\n #handlePointerMove(event) {\n const mark = event.target.closest(MARK_SELECTOR);\n\n if (!mark || !this.#element.contains(mark)) {\n this.#tooltip.hide();\n\n return;\n }\n\n this.#tooltip.show(mark, mark.dataset.tooltip, this.#options);\n }\n\n /**\n * Clears persistent selection when a pointer press occurs outside chart marks.\n *\n * @param {PointerEvent} event - Document-level pointer event used for outside detection.\n * @returns {void} Active interaction state is dismissed when appropriate.\n */\n #handleDocumentPointerDown(event) {\n const mark = event.target.closest(MARK_SELECTOR);\n\n if (!mark || !this.#element.contains(mark)) {\n this.#interactions?.dismiss();\n }\n }\n\n /**\n * Rebuilds SVG content through class-based renderer dispatch.\n *\n * @param {SVGSVGElement} element - Detached SVG receiving rendered content.\n * @param {import(\"./ChartData.js\").default} model - Fully normalized model exposed to the renderer snapshot.\n * @param {object} [options=this.#options] - Candidate rendering options.\n * @returns {void} The detached SVG contains a complete candidate scene.\n */\n #renderInto(element, model, options = this.#options) {\n const description = svg(\"desc\");\n description.textContent = options.description ?? this.#generatedDescription(model);\n element.append(description);\n if (options.title) {\n const title = svg(\"text\", { x: 16, y: 22, class: \"charts2-title\" });\n title.textContent = options.title;\n element.append(title);\n }\n\n renderChart(\n {\n host: this.#host,\n element,\n options,\n source: model.source,\n datasets: model.datasets,\n labels: model.labels,\n heatmap: model.heatmap,\n timesheet: model.timesheet,\n hasCustomColors: this.#hasCustomColors,\n id: this.#id,\n },\n this.#implementation.render,\n );\n }\n\n /**\n * Builds a concise accessible summary when no authored description exists.\n *\n * @param {import(\"./ChartData.js\").default} model - Fully normalized current model.\n * @returns {string} Plain-text chart description.\n */\n #generatedDescription(model) {\n if (this.#type === ChartType.HEATMAP) {\n return `${this.#options.ariaLabel}. ${model.heatmap.length} heatmap cells.`;\n }\n\n if (this.#type === ChartType.TIMESHEET) {\n return `${this.#options.ariaLabel}. ${model.timesheet.tasks.length} tasks.`;\n }\n\n const datasets = model.datasets.map((dataset) => dataset.identityName ?? dataset.name).join(\", \");\n const markerCount = model.source.yMarkers.length;\n const regionCount = model.source.yRegions.length;\n const annotations = ` ${markerCount} markers and ${regionCount} regions.`;\n const datasetSummary = ` in ${datasets}`;\n\n return `${this.#options.ariaLabel}. ${model.labels.length} values${datasetSummary}.${annotations}`;\n }\n\n /**\n * Commits a validated data model and completely rendered SVG together.\n *\n * @param {SVGSVGElement} staged - Detached rendered candidate.\n * @param {import(\"./ChartData.js\").default} model - Fully normalized replacement model.\n * @returns {void} Mounted data, SVG, and interactions advance together.\n */\n #commitUpdate(staged, model) {\n const activeIndex = this.#preservedIndex(staged, model);\n this.#replaceSurface(staged);\n this.#model = model;\n this.#activeMarkIndex = activeIndex;\n if (activeIndex < 0) {\n this.#selectionIdentity = null;\n }\n\n this.#tooltip.hide();\n this.#bindInteractions();\n }\n\n /**\n * Finds the sole candidate mark matching the current logical selection.\n *\n * @param {SVGSVGElement} staged - Completely rendered candidate surface.\n * @param {import(\"./ChartData.js\").default} model - Candidate normalized data model.\n * @returns {number} Matching mark index, or -1 for absent or ambiguous identity.\n */\n #preservedIndex(staged, model) {\n if (!this.#selectionIdentity) {\n return -1;\n }\n\n const marks = this.#orderedMarks(staged);\n\n const matches = marks.flatMap((mark, index) =>\n model.identityFor(mark) === this.#selectionIdentity\n ? [\n index,\n ]\n : [],\n );\n\n return matches.length === 1 ? matches[0] : -1;\n }\n\n /**\n * Moves staged children and root attributes into the stable public SVG element.\n *\n * @param {SVGSVGElement} staged - Successfully rendered detached surface.\n * @returns {void} Public element identity is preserved across redraws.\n */\n #replaceSurface(staged) {\n const currentAttributes = [\n ...this.#element.attributes,\n ];\n\n for (const attribute of currentAttributes) {\n this.#element.removeAttribute(attribute.name);\n }\n\n for (const attribute of staged.attributes) {\n this.#element.setAttribute(attribute.name, attribute.value);\n }\n\n this.#host.classList.toggle(SCROLLABLE_HEATMAP_CLASS, staged.dataset.scrollable === \"true\");\n delete this.#element.dataset.scrollable;\n this.#element.replaceChildren(...staged.childNodes);\n }\n\n /**\n * Rejects lifecycle operations after destruction while keeping `element` inspectable.\n *\n * @returns {void} Active charts continue normally.\n * @throws {TypeError} When the chart has been destroyed.\n */\n #assertMounted() {\n if (this.#destroyed) {\n throw new TypeError(\"Chart has been destroyed\");\n }\n }\n\n /**\n * Rebinds accessible pointer, focus, keyboard, and selection behavior after render.\n *\n * @returns {void} Rendered marks receive a fresh interaction controller.\n */\n #bindInteractions() {\n const marks = this.#orderedMarks(this.#element);\n\n for (const mark of marks) {\n mark.querySelector(\":scope > title\")?.remove();\n }\n\n if (!this.#options.tooltip && typeof this.#options.onSelect !== \"function\") {\n const titles = this.#element.querySelectorAll(\".charts2-visual-mark > title, .charts2-line > title\");\n\n for (const title of titles) {\n title.remove();\n }\n\n this.#interactions = null;\n\n return;\n }\n\n const interactionBehavior = {\n activeIndex: this.#activeMarkIndex,\n allowPointerPan: this.#host.classList.contains(SCROLLABLE_HEATMAP_CLASS),\n previewable: this.#options.tooltip,\n selectable: typeof this.#options.onSelect === \"function\",\n };\n\n const interactionCallbacks = {\n labelFor: (mark) => mark.dataset.tooltip,\n onShow: this.#options.tooltip\n ? (mark, label) => this.#tooltip.show(mark, label, this.#options)\n : () => {},\n onHide: () => this.#tooltip.hide(),\n onActiveChange: (index, mark) => this.#handleActiveChange(index, mark),\n onFocusChange: (index) => {\n this.#keyboardMarkIndex = index;\n },\n };\n\n this.#interactions = new InteractionController(marks, interactionBehavior, interactionCallbacks);\n }\n\n /**\n * Returns marks in the public navigation order independently of visual layer order.\n *\n * @param {SVGSVGElement} element - Rendered surface containing interaction marks.\n * @returns {SVGElement[]} Stable keyboard and `point()` order.\n */\n #orderedMarks(element) {\n const marks = [\n ...element.querySelectorAll(MARK_SELECTOR),\n ];\n\n if (this.#type !== ChartType.AXIS_MIXED) {\n return marks;\n }\n\n return marks.toSorted((left, right) => {\n const dataset = Number(left.dataset.datasetIndex) - Number(right.dataset.datasetIndex);\n\n return dataset || Number(left.dataset.pointIndex) - Number(right.dataset.pointIndex);\n });\n }\n\n /**\n * Commits and publishes one user-initiated persistent selection change.\n *\n * @param {number} index - Selected mark index, or -1 for explicit deselection.\n * @param {SVGElement | null} mark - Selected rendered mark.\n * @returns {void} Internal identity is committed before user code runs.\n */\n #handleActiveChange(index, mark) {\n this.#activeMarkIndex = index;\n if (index >= 0 && mark) {\n const detail = this.#model.selectionFor(mark);\n this.#selectionIdentity = this.#model.identityFor(mark);\n this.#host.dispatchEvent(new CustomEvent(\"data-select\", { detail }));\n this.#options.onSelect?.(detail);\n\n return;\n }\n\n this.#selectionIdentity = null;\n this.#host.dispatchEvent(new CustomEvent(\"data-select\"));\n this.#options.onSelect?.();\n }\n}\n"],"mappings":"mXAQA,IAAM,EAAgB,gBAChB,EAA2B,6BAE3B,EAAkB,CAAE,OAAQ,GAa5B,EAA0B,OAAO,OAAO,CAC5C,QACA,OACA,eACA,cACA,YACA,aACA,cACA,iBACA,UACA,cACA,kBACA,SACA,mBACA,iBACA,kBACA,iBACA,eACA,cACA,kBAOmB,EAArB,MACE,GACA,GACA,GACA,GACA,GACA,GACA,GACA,GACA,GACA,GACA,GAAgB,KAChB,IAAmB,EACnB,IAAqB,EACrB,GAAqB,KACrB,GACA,GACA,GACA,GAAe,KACf,GAAkB,KAClB,IAAa,EAUb,WAAA,CAAY,EAAQ,EAAS,GAC3B,MAAM,EAAQ,EAAe,YAAY,EAAQ,KAAM,EAAQ,KAAM,CACnE,OAAQ,EAAQ,OAChB,UAAW,EAAQ,YAGf,EAAO,EAAc,GACrB,EAAc,EAAsB,EAAM,GAEhD,MAAKA,EAAQ,EACb,MAAKC,EAAQ,EAAY,QAAQ,KACjC,MAAKC,EAAkB,EACvB,MAAKC,GAzEP,EAAgB,QAAU,EAEnB,EAAgB,QAwErB,MAAKC,EAAmB,EAAY,gBACpC,MAAKC,OAA+B,IAAlB,EAAQ,MAC1B,MAAKC,EAAW,EAAY,QAC5B,MAAKC,EAAS,EACd,MAAKC,EAAW,MAAKC,IACrB,MAAKC,EAAW,IAAI,EAAa,MAAKV,EAAO,MAAKQ,EAAU,MAAKL,GACjE,MAAKQ,EAAY,MAAKH,EAAU,MAAKD,GACrC,MAAKK,EAAwB,MAAKJ,GAClC,MAAKK,IACL,MAAKC,IAED,MAAKT,IACP,MAAKU,EAAe,MAAKC,EAAQ,KAAK,MACtC,MAAKC,IAET,CAQA,EAAA,CAAe,EAAU,MAAKX,GAC5B,MAAM,EAAU,EAAI,MAAO,CACzB,QAAS,OAAO,EAAQ,SAAS,EAAQ,SACzC,MAAO,OACP,OAAQ,EAAQ,OAChB,KAAM,QACN,uBAAwB,QACxB,aAAc,EAAQ,UACtB,oBAAqB,kBAgBvB,OAbA,EAAQ,UAAU,IAAI,iBAClB,MAAKL,IAAU,EAAU,SAC3B,EAAQ,UAAU,IAAI,yBAGpB,MAAKA,IAAU,EAAU,WAC3B,EAAQ,UAAU,IAAI,2BAGpB,MAAKA,IAAU,EAAU,KAAO,EAAQ,cAAgB,EAAiB,YAC3E,EAAQ,UAAU,IAAI,0BAGjB,CACT,CAQA,EAAA,CAAwB,GACtB,MAAM,EAAkB,EACxB,MAAKD,EAAM,UAAU,IAAI,gBACzB,MAAKA,EAAM,UAAU,OAAO,EAAyD,SAA/B,EAAQ,QAAQ,mBAC/D,EAAgB,QAAQ,WAC/B,MAAKA,EAAM,gBAAgB,EAAS,MAAKU,EAAS,QACpD,CAOA,EAAA,GACE,MAAKQ,EAAoB,MAAKC,EAAmB,KAAK,MACtD,MAAKC,EAAqB,MAAKV,EAAS,KAAK,KAAK,MAAKA,GACvD,MAAKW,EAA4B,MAAKC,EAA2B,KAAK,MAElE,MAAKhB,EAAS,SAChB,MAAKE,EAAS,iBAAiB,YAAa,MAAKU,GAGnD,MAAKV,EAAS,iBAAiB,aAAc,MAAKY,GAClD,SAAS,iBAAiB,cAAe,MAAKC,EAChD,CAOA,WAAI,GACF,OAAO,MAAKb,CACd,CASA,MAAA,CAAO,GACL,MAAKe,IAEL,MAAM,EAAQ,MAAKrB,EAAgB,YAAY,MAAKD,EAAO,EAAM,CAC/D,OAAQ,MAAKK,EAAS,OACtB,UAAW,MAAKA,EAAS,YAG3B,EAAoB,MAAKN,EAAO,GAEhC,MAAM,EAAS,MAAKS,IAIpB,OAHA,MAAKE,EAAY,EAAQ,GACzB,MAAKa,EAAc,EAAQ,GAEpB,IACT,CAQA,KAAA,CAAM,GACJ,MAAKD,IAEL,MAAM,EACJ,MAAKE,GAAoB,EAAI,MAAKA,EAAmB,KAAK,IAAI,EAAG,MAAKC,GAElE,EAAiB,GAAS,EAEhC,IAAK,OAAO,cAAc,IAAmB,EAAiB,EAC5D,MAAM,IAAI,UAAU,oDAGtB,OAAO,MAAKnB,EAAO,QAAQ,EAC7B,CAOA,KAAA,GACE,MAAKgB,IACL,MAAM,EAAQ,MAAKf,EAAS,WAAU,GACtC,EAAM,aAAa,QAAS,8BAE5B,MAAM,EAAY,CAChB,MAAKA,KACF,MAAKA,EAAS,iBAAiB,MAG9B,EAAS,CACb,KACG,EAAM,iBAAiB,MAG5B,IAAK,MACH,EACA,KACG,EAAU,UAAW,CACxB,MAAM,EAAW,iBAAiB,GAC5B,EAAO,EAAO,GAEpB,IAAK,MAAM,KAAY,EAAyB,CAC9C,MAAM,EAAQ,EAAS,iBAAiB,GAExC,EAAK,MAAM,YAAY,EAAU,EACnC,CACF,CAEA,OAAO,IAAI,eAAgB,kBAAkB,EAC/C,CAQA,QAAA,CAAS,EAAW,MAAKF,EAAS,OAAS,SAEzC,GADA,MAAKiB,IACmB,iBAAb,GAA6C,KAApB,EAAS,QAAiB,SAAS,KAAK,GAC1E,MAAM,IAAI,UAAU,4EAGtB,MAAM,EAAqB,EAAS,cAAc,SA5QhC,QA4Q0D,EAAW,GAAG,QACpF,EAAO,SAAS,cAAc,KACpC,EAAK,SAAW,EAEhB,MAAM,EAAM,IAAI,gBACd,IAAI,KACF,CACE,KAAK,SAEP,CAAE,KAAM,mBAQZ,OAJA,EAAK,KAAO,EACZ,EAAK,QACL,IAAI,gBAAgB,GAEb,IACT,CAOA,OAAA,GACM,MAAKI,IAIT,MAAKA,GAAa,EAClB,MAAKnB,EAAS,oBAAoB,YAAa,MAAKU,GACpD,MAAKV,EAAS,oBAAoB,aAAc,MAAKY,GACrD,SAAS,oBAAoB,cAAe,MAAKC,GAC7C,MAAKN,GACP,OAAO,oBAAoB,SAAU,MAAKA,GAG5C,MAAKa,GAAiB,aAEtB,MAAKpB,EAAS,SACd,MAAKE,EAAS,UACd,MAAKV,EAAM,UAAU,OAAO,eAAgB,GAC9C,CAOA,EAAA,GACE,GAAI,MAAK2B,EACP,OAGF,MAAM,EAAQ,EAAmB,MAAK3B,EAAO,MAAKM,EAAS,OAE3D,GAAI,IAAU,MAAKA,EAAS,MAC1B,OAGF,MAAM,EAAU,IAAK,MAAKA,EAAU,SAC9B,EAAS,MAAKG,EAAe,GACnC,MAAKE,EAAY,EAAQ,MAAKJ,EAAQ,GACtC,MAAM,EAAc,MAAKsB,EAAgB,EAAQ,MAAKtB,GACtD,MAAKD,EAAW,EAChB,MAAKwB,EAAgB,GACrB,MAAKL,EAAmB,EACxB,MAAKX,GACP,CAOA,EAAA,GACE,OAAO,iBAAiB,SAAU,MAAKC,GACT,mBAAnB,iBACT,MAAKa,EAAkB,IAAI,eAAe,MAAKb,GAC/C,MAAKa,EAAgB,QAAQ,MAAK5B,EAAO,CAAE,IAAK,gBAEpD,CAQA,EAAA,CAAmB,GACjB,MAAM,EAAO,EAAM,OAAO,QAAQ,GAE7B,GAAS,MAAKQ,EAAS,SAAS,GAMrC,MAAKE,EAAS,KAAK,EAAM,EAAK,QAAQ,QAAS,MAAKJ,GALlD,MAAKI,EAAS,MAMlB,CAQA,EAAA,CAA2B,GACzB,MAAM,EAAO,EAAM,OAAO,QAAQ,GAE7B,GAAS,MAAKF,EAAS,SAAS,IACnC,MAAKuB,GAAe,SAExB,CAUA,EAAA,CAAY,EAAS,EAAO,EAAU,MAAKzB,GACzC,MAAM,EAAc,EAAI,QAGxB,GAFA,EAAY,YAAc,EAAQ,aAAe,MAAK0B,EAAsB,GAC5E,EAAQ,OAAO,GACX,EAAQ,MAAO,CACjB,MAAM,EAAQ,EAAI,OAAQ,CAAE,EAAG,GAAI,EAAG,GAAI,MAAO,kBACjD,EAAM,YAAc,EAAQ,MAC5B,EAAQ,OAAO,EACjB,CAEA,EACE,CACE,KAAM,MAAKhC,EACX,UACA,UACA,OAAQ,EAAM,OACd,SAAU,EAAM,SAChB,OAAQ,EAAM,OACd,QAAS,EAAM,QACf,UAAW,EAAM,UACjB,gBAAiB,MAAKI,EACtB,GAAI,MAAKD,GAEX,MAAKD,EAAgB,OAEzB,CAQA,EAAA,CAAsB,GACpB,GAAI,MAAKD,IAAU,EAAU,QAC3B,MAAO,GAAG,MAAKK,EAAS,cAAc,EAAM,QAAQ,wBAGtD,GAAI,MAAKL,IAAU,EAAU,UAC3B,MAAO,GAAG,MAAKK,EAAS,cAAc,EAAM,UAAU,MAAM,gBAG9D,MAAM,EAAW,EAAM,SAAS,IAAK,GAAY,EAAQ,cAAgB,EAAQ,MAAM,KAAK,MAGtF,EAAc,IAFA,EAAM,OAAO,SAAS,sBACtB,EAAM,OAAO,SAAS,kBAEpC,EAAiB,OAAO,IAE9B,MAAO,GAAG,MAAKA,EAAS,cAAc,EAAM,OAAO,gBAAgB,KAAkB,GACvF,CASA,EAAA,CAAc,EAAQ,GACpB,MAAM,EAAc,MAAKuB,EAAgB,EAAQ,GACjD,MAAKC,EAAgB,GACrB,MAAKvB,EAAS,EACd,MAAKkB,EAAmB,EACpB,EAAc,IAChB,MAAKQ,EAAqB,MAG5B,MAAKvB,EAAS,OACd,MAAKI,GACP,CASA,EAAA,CAAgB,EAAQ,GACtB,IAAK,MAAKmB,EACR,OAAO,EAKT,MAAM,EAFQ,MAAKC,EAAc,GAEX,QAAA,CAAS,EAAM,IACnC,EAAM,YAAY,KAAU,MAAKD,EAC7B,CACE,GAEF,IAGN,OAA0B,IAAnB,EAAQ,OAAe,EAAQ,IAAK,CAC7C,CAQA,EAAA,CAAgB,GACd,MAAM,EAAoB,IACrB,MAAKzB,EAAS,YAGnB,IAAK,MAAM,KAAa,EACtB,MAAKA,EAAS,gBAAgB,EAAU,MAG1C,IAAK,MAAM,KAAa,EAAO,WAC7B,MAAKA,EAAS,aAAa,EAAU,KAAM,EAAU,OAGvD,MAAKR,EAAM,UAAU,OAAO,EAAwD,SAA9B,EAAO,QAAQ,mBAC9D,MAAKQ,EAAS,QAAQ,WAC7B,MAAKA,EAAS,mBAAmB,EAAO,WAC1C,CAQA,EAAA,GACE,GAAI,MAAKmB,EACP,MAAM,IAAI,UAAU,2BAExB,CAOA,EAAA,GACE,MAAM,EAAQ,MAAKO,EAAc,MAAK1B,GAEtC,IAAK,MAAM,KAAQ,EACjB,EAAK,cAAc,mBAAmB,SAGxC,IAAK,MAAKF,EAAS,SAA6C,mBAA3B,MAAKA,EAAS,SAAyB,CAC1E,MAAM,EAAS,MAAKE,EAAS,iBAAiB,uDAE9C,IAAK,MAAM,KAAS,EAClB,EAAM,SAKR,YAFA,MAAKuB,EAAgB,KAGvB,CAEA,MAAM,EAAsB,CAC1B,YAAa,MAAKN,EAClB,gBAAiB,MAAKzB,EAAM,UAAU,SAAS,GAC/C,YAAa,MAAKM,EAAS,QAC3B,WAA8C,mBAA3B,MAAKA,EAAS,UAG7B,EAAuB,CAC3B,SAAW,GAAS,EAAK,QAAQ,QACjC,OAAQ,MAAKA,EAAS,QAAA,CACjB,EAAM,IAAU,MAAKI,EAAS,KAAK,EAAM,EAAO,MAAKJ,GAAQ,OAElE,OAAA,IAAc,MAAKI,EAAS,OAC5B,eAAA,CAAiB,EAAO,IAAS,MAAKyB,EAAoB,EAAO,GACjE,cAAgB,IACd,MAAKT,EAAqB,IAI9B,MAAKK,EAAgB,IAAI,EAAsB,EAAO,EAAqB,EAC7E,CAQA,EAAA,CAAc,GACZ,MAAM,EAAQ,IACT,EAAQ,iBAAiB,IAG9B,OAAI,MAAK9B,IAAU,EAAU,WACpB,EAGF,EAAM,SAAA,CAAU,EAAM,IACX,OAAO,EAAK,QAAQ,cAAgB,OAAO,EAAM,QAAQ,eAEvD,OAAO,EAAK,QAAQ,YAAc,OAAO,EAAM,QAAQ,YAE7E,CASA,EAAA,CAAoB,EAAO,GAEzB,GADA,MAAKwB,EAAmB,EACpB,GAAS,GAAK,EAAM,CACtB,MAAM,EAAS,MAAKlB,EAAO,aAAa,GAKxC,OAJA,MAAK0B,EAAqB,MAAK1B,EAAO,YAAY,GAClD,MAAKP,EAAM,cAAc,IAAI,YAAY,cAAe,CAAE,iBAC1D,MAAKM,EAAS,WAAW,EAG3B,CAEA,MAAK2B,EAAqB,KAC1B,MAAKjC,EAAM,cAAc,IAAI,YAAY,gBACzC,MAAKM,EAAS,YAChB"}
@@ -0,0 +1 @@
1
+ import{AGGREGATION_TYPES as t,ChartType as e,DEFAULT_COLORS as s,HEATMAP_COLORS as n}from"../support/Constants.js";import{normalizeDatasets as o,normalizeHeatmapData as r,normalizeTimesheetData as a,validateChartData as i,validateSeriesScene as l}from"../support/Normalize.js";import{createCompositionSelection as c,createHeatmapSelection as p,createSeriesSelection as h,createTimesheetSelection as u}from"./ChartSelection.js";import{normalizeCartesianSource as d}from"./NormalizeAnnotations.js";var m=/* @__PURE__ */new Set([e.SCATTER,e.AXIS_MIXED,e.BUBBLE]),y=class{#t;#e;#s;#n;#o;#r;#a;constructor(t){const{type:e,source:s,collections:n,selection:o}=t,{datasets:r=[],labels:a=[],heatmap:i=[],timesheet:l=null}=n;this.#t=e,this.#e=s,this.#s=r,this.#n=a,this.#o=i,this.#r=l,this.#a=o}get datasets(){return this.#s}get labels(){return this.#n}get heatmap(){return this.#o}get timesheet(){return this.#r}get source(){return this.#e}pointAt(t){if(this.#t===e.HEATMAP){const e=this.#o[t];return e&&Object.freeze({...e,date:new Date(e.date)})}if(this.#t===e.TIMESHEET){const e=this.#r.tasks[t];return e&&Object.freeze({...e,start:new Date(e.start),end:new Date(e.end)})}return m.has(this.#t)?this.#i(t):this.#s.every(e=>void 0===e.points[t])?void 0:Object.freeze({index:t,label:this.#n[t],values:Object.freeze(this.#s.map(e=>e.points[t]?.y))})}selectionFor(t){return this.#a.from(t)}identityFor(t){return this.#a.identityFor(t)}#i(t){const s=this.#s.flatMap((t,e)=>t.points.map((s,n)=>({dataset:t,datasetIndex:e,point:s,pointIndex:n})))[t];if(s)return function(t){const e={index:t.index,datasetIndex:t.datasetIndex,dataset:t.dataset.name,pointIndex:t.pointIndex,label:t.label,x:t.point.x,y:t.point.y};return void 0!==t.point.r&&(e.r=t.point.r),void 0!==t.chartType&&(e.chartType=t.chartType),Object.freeze(e)}({...s,index:t,label:this.#n[s.pointIndex],chartType:this.#t===e.AXIS_MIXED?s.dataset.chartType:void 0})}};function b(t,s,n){l(t,s);const r=o(s,n,t);if(void 0!==s.labels&&!Array.isArray(s.labels))throw new TypeError("Chart labels must be an array");if(s.labels?.some(t=>!function(t){return"number"==typeof t?Number.isFinite(t):"string"==typeof t&&""!==t.trim()}(t)))throw new TypeError("Chart labels must contain non-empty strings or generated numbers");const a=Math.max(...r.map(t=>t.points.length)),c=s.labels??([e.SCATTER,e.BUBBLE].includes(t)?[]:Array.from({length:a},(t,e)=>e+1));return i(t,r,c),{source:d(s),collections:{datasets:r,labels:c}}}function f(t,e,n){const o=n.colors??s,r=b(t,e,o),a=h(t,{...r.collections,colors:o});return new y({type:t,...r,selection:a})}function x(e,n,o){const r=o.colors??s,a=b(e,n,r),i=o.maxSlices??20,l=t.includes(e)?function(t,e){const{datasets:s,labels:n}=t;if(n.length<=e)return t;const o=n.map((t,e)=>({label:t,point:s[0].points[e]})).toSorted((t,e)=>e.point.y-t.point.y),r=o.slice(0,e-1),a=o.slice(e-1);let i=0;for(const l of a)i+=l.point.y;return{datasets:[{...s[0],points:[...r.map(t=>t.point),{x:r.length,y:i}]}],labels:[...r.map(t=>t.label),"Rest"]}}(a.collections,i):a.collections,p=c(e,{...l,colors:r});return new y({type:e,source:a.source,collections:l,selection:p})}function T(t,e,s){const o=s.colors??n,a={heatmap:r(e)},i=p({...a,colors:o});return new y({type:t,source:e,collections:a,selection:i})}function A(t,e,n){const o=n.colors??s,r=a(e,o),i={timesheet:r,labels:r.tasks.map(t=>t.label)},l=u(i);return new y({type:t,source:e,collections:i,selection:l})}export{x as createCompositionModel,T as createHeatmapModel,f as createSeriesModel,A as createTimesheetModel,y as default};
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ChartData.js","names":["#type","#source","#datasets","#labels","#heatmap","#timesheet","#selection","#seriesMarkAt"],"sources":["../../src/core/ChartData.js"],"sourcesContent":["import { AGGREGATION_TYPES, ChartType, DEFAULT_COLORS, HEATMAP_COLORS } from \"../support/Constants.js\";\nimport {\n normalizeDatasets,\n normalizeHeatmapData,\n normalizeTimesheetData,\n validateChartData,\n validateSeriesScene,\n} from \"../support/Normalize.js\";\n\nimport {\n createCompositionSelection,\n createHeatmapSelection,\n createSeriesSelection,\n createTimesheetSelection,\n} from \"./ChartSelection.js\";\nimport { normalizeCartesianSource } from \"./NormalizeAnnotations.js\";\n\nconst DEFAULT_MAXIMUM_SLICES = 20;\n\nconst INDEPENDENT_TYPES = new Set([\n ChartType.SCATTER,\n ChartType.AXIS_MIXED,\n ChartType.BUBBLE,\n]);\n\n/**\n * Creates one immutable independently-positioned series mark snapshot.\n *\n * @param {object} source - Dataset, point, coordinates, and public index.\n * @returns {object} Frozen public point snapshot.\n */\nfunction seriesMarkSnapshot(source) {\n const snapshot = {\n index: source.index,\n datasetIndex: source.datasetIndex,\n dataset: source.dataset.name,\n pointIndex: source.pointIndex,\n label: source.label,\n x: source.point.x,\n y: source.point.y,\n };\n\n if (source.point.r !== undefined) {\n snapshot.r = source.point.r;\n }\n\n if (source.chartType !== undefined) {\n snapshot.chartType = source.chartType;\n }\n\n return Object.freeze(snapshot);\n}\n\n/**\n * Owns normalized chart data and projects lifecycle selection through one\n * cohesive presenter.\n */\nexport default class ChartData {\n #type;\n #source;\n #datasets;\n #labels;\n #heatmap;\n #timesheet;\n #selection;\n\n /**\n * Captures one completely normalized family snapshot.\n *\n * @param {object} state - Type, source, collections, and effective palette.\n */\n constructor(state) {\n const { type, source, collections, selection } = state;\n const { datasets = [], labels = [], heatmap = [], timesheet = null } = collections;\n\n this.#type = type;\n this.#source = source;\n this.#datasets = datasets;\n this.#labels = labels;\n this.#heatmap = heatmap;\n this.#timesheet = timesheet;\n this.#selection = selection;\n }\n\n /**\n * Exposes normalized series datasets.\n *\n * @returns {Array<object>} Canonical series datasets.\n */\n get datasets() {\n return this.#datasets;\n }\n\n /**\n * Exposes normalized category or task labels.\n *\n * @returns {unknown[]} Canonical category or task labels.\n */\n get labels() {\n return this.#labels;\n }\n\n /**\n * Exposes normalized heatmap entries.\n *\n * @returns {Array<object>} Canonical heatmap entries.\n */\n get heatmap() {\n return this.#heatmap;\n }\n\n /**\n * Exposes normalized timesheet state.\n *\n * @returns {object | null} Canonical timesheet state.\n */\n get timesheet() {\n return this.#timesheet;\n }\n\n /**\n * Exposes the completely validated caller source.\n *\n * @returns {object} Completely validated caller source.\n */\n get source() {\n return this.#source;\n }\n\n /**\n * Reads one type-appropriate normalized value without exposing mutable internals.\n *\n * @param {number} index - Requested point, entry, or task index.\n * @returns {object | undefined} Defensive public data snapshot.\n */\n pointAt(index) {\n if (this.#type === ChartType.HEATMAP) {\n const point = this.#heatmap[index];\n\n return point && Object.freeze({ ...point, date: new Date(point.date) });\n }\n\n if (this.#type === ChartType.TIMESHEET) {\n const task = this.#timesheet.tasks[index];\n\n return task && Object.freeze({ ...task, start: new Date(task.start), end: new Date(task.end) });\n }\n\n if (INDEPENDENT_TYPES.has(this.#type)) {\n return this.#seriesMarkAt(index);\n }\n\n if (this.#datasets.every((dataset) => dataset.points[index] === undefined)) {\n return;\n }\n\n return Object.freeze({\n index,\n label: this.#labels[index],\n values: Object.freeze(this.#datasets.map((dataset) => dataset.points[index]?.y)),\n });\n }\n\n /**\n * Projects renderer metadata through the selection presenter.\n *\n * @param {SVGElement} mark - Rendered mark carrying source indices.\n * @returns {object} Frozen public selection payload.\n */\n selectionFor(mark) {\n return this.#selection.from(mark);\n }\n\n /**\n * Resolves a rendered mark into its stable lifecycle identity.\n *\n * @param {SVGElement} mark - Rendered mark carrying source indices.\n * @returns {string | null} Stable identity or null when ambiguous.\n */\n identityFor(mark) {\n return this.#selection.identityFor(mark);\n }\n\n /**\n * Reads one independently-positioned mark in renderer navigation order.\n *\n * @param {number} index - Flattened mark position.\n * @returns {object | undefined} Immutable mark snapshot.\n */\n #seriesMarkAt(index) {\n const marks = this.#datasets.flatMap((dataset, datasetIndex) =>\n dataset.points.map((point, pointIndex) => ({ dataset, datasetIndex, point, pointIndex })),\n );\n\n const mark = marks[index];\n\n if (!mark) {\n return;\n }\n\n return seriesMarkSnapshot({\n ...mark,\n index,\n label: this.#labels[mark.pointIndex],\n chartType: this.#type === ChartType.AXIS_MIXED ? mark.dataset.chartType : undefined,\n });\n }\n}\n\n/**\n * Accepts public labels and builder-generated numeric labels.\n *\n * @param {unknown} label - Candidate normalized label.\n * @returns {boolean} Whether the label is valid for the internal scene.\n */\nfunction isNormalizedLabel(label) {\n if (typeof label === \"number\") {\n return Number.isFinite(label);\n }\n\n return typeof label === \"string\" && label.trim() !== \"\";\n}\n\n/**\n * Normalizes the shared series grammar without selecting another family.\n *\n * @param {string} type - Concrete series or composition type.\n * @param {object} data - Caller-controlled data.\n * @param {string[]} colors - Effective palette.\n * @returns {{source: object, collections: object}} Validated series snapshot.\n */\nfunction normalizeSeries(type, data, colors) {\n validateSeriesScene(type, data);\n const datasets = normalizeDatasets(data, colors, type);\n\n if (data.labels !== undefined && !Array.isArray(data.labels)) {\n throw new TypeError(\"Chart labels must be an array\");\n }\n\n if (data.labels?.some((label) => !isNormalizedLabel(label))) {\n throw new TypeError(\"Chart labels must contain non-empty strings or generated numbers\");\n }\n\n const pointCount = Math.max(...datasets.map((dataset) => dataset.points.length));\n\n const labels =\n data.labels ??\n ([\n ChartType.SCATTER,\n ChartType.BUBBLE,\n ].includes(type)\n ? []\n : Array.from({ length: pointCount }, (_value, index) => index + 1));\n\n validateChartData(type, datasets, labels);\n\n return {\n source: normalizeCartesianSource(data),\n collections: { datasets, labels },\n };\n}\n\n/**\n * Aggregates the smallest composition slices behind one stable Rest item.\n *\n * @param {object} collections - Validated datasets and aligned labels.\n * @param {number} maximum - Maximum visible composition item count.\n * @returns {object} Original or aggregated collections.\n */\nfunction aggregateComposition(collections, maximum) {\n const { datasets, labels } = collections;\n\n if (labels.length <= maximum) {\n return collections;\n }\n\n const entries = labels\n .map((label, index) => ({ label, point: datasets[0].points[index] }))\n .toSorted((left, right) => right.point.y - left.point.y);\n\n const visible = entries.slice(0, maximum - 1);\n const remainder = entries.slice(maximum - 1);\n let restValue = 0;\n\n for (const entry of remainder) {\n restValue += entry.point.y;\n }\n\n return {\n datasets: [\n {\n ...datasets[0],\n points: [\n ...visible.map((entry) => entry.point),\n { x: visible.length, y: restValue },\n ],\n },\n ],\n labels: [\n ...visible.map((entry) => entry.label),\n \"Rest\",\n ],\n };\n}\n\n/**\n * Creates a normalized Cartesian series model.\n *\n * @param {string} type - Concrete Cartesian type.\n * @param {object} data - Caller-controlled data.\n * @param {object} config - Effective chart configuration.\n * @returns {ChartData} Normalized series model.\n */\nfunction createSeriesModel(type, data, config) {\n const colors = config.colors ?? DEFAULT_COLORS;\n const normalized = normalizeSeries(type, data, colors);\n const selection = createSeriesSelection(type, { ...normalized.collections, colors });\n\n return new ChartData({ type, ...normalized, selection });\n}\n\n/**\n * Creates a normalized composition or radial model.\n *\n * @param {string} type - Concrete composition type.\n * @param {object} data - Caller-controlled data.\n * @param {object} config - Effective chart configuration.\n * @returns {ChartData} Normalized composition model.\n */\nfunction createCompositionModel(type, data, config) {\n const colors = config.colors ?? DEFAULT_COLORS;\n const normalized = normalizeSeries(type, data, colors);\n const maximum = config.maxSlices ?? DEFAULT_MAXIMUM_SLICES;\n\n const collections = AGGREGATION_TYPES.includes(type)\n ? aggregateComposition(normalized.collections, maximum)\n : normalized.collections;\n\n const selection = createCompositionSelection(type, { ...collections, colors });\n\n return new ChartData({ type, source: normalized.source, collections, selection });\n}\n\n/**\n * Creates a normalized heatmap model.\n *\n * @param {string} type - Heatmap chart type.\n * @param {object} data - Caller-controlled heatmap data.\n * @param {object} config - Effective chart configuration.\n * @returns {ChartData} Normalized heatmap model.\n */\nfunction createHeatmapModel(type, data, config) {\n const colors = config.colors ?? HEATMAP_COLORS;\n const collections = { heatmap: normalizeHeatmapData(data) };\n const selection = createHeatmapSelection({ ...collections, colors });\n\n return new ChartData({ type, source: data, collections, selection });\n}\n\n/**\n * Creates a normalized timesheet model.\n *\n * @param {string} type - Timesheet chart type.\n * @param {object} data - Caller-controlled timesheet data.\n * @param {object} config - Effective chart configuration.\n * @returns {ChartData} Normalized timesheet model.\n */\nfunction createTimesheetModel(type, data, config) {\n const colors = config.colors ?? DEFAULT_COLORS;\n const timesheet = normalizeTimesheetData(data, colors);\n const labels = timesheet.tasks.map((task) => task.label);\n const collections = { timesheet, labels };\n const selection = createTimesheetSelection(collections);\n\n return new ChartData({ type, source: data, collections, selection });\n}\n\nexport { createCompositionModel, createHeatmapModel, createSeriesModel, createTimesheetModel };\n"],"mappings":"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"}
@@ -0,0 +1 @@
1
+ import{renderBarChart as e,renderLineChart as r,renderMixedChart as o,renderPointChart as t}from"../renderers/cartesian/CartesianRendering.js";import{renderAggregationChart as n}from"../renderers/composition/AggregationRendering.js";import{renderPolarAreaChart as d}from"../renderers/composition/PolarAreaRendering.js";import{renderRadarChart as i}from"../renderers/composition/RadarRendering.js";import{renderHeatmapChart as a}from"../renderers/temporal/HeatmapRendering.js";import{renderTimesheetChart as m}from"../renderers/temporal/TimesheetRendering.js";import{LineChartBuilder as s}from"./builders/Builder.js";import{BarChartBuilder as l,MixedChartBuilder as p,ScatterChartBuilder as c}from"./builders/CartesianBuilders.js";import{PercentageChartBuilder as f,PolarAreaChartBuilder as u,RadarChartBuilder as j,SectorChartBuilder as M}from"./builders/CompositionBuilders.js";import{HeatmapChartBuilder as b,TimesheetChartBuilder as g}from"./builders/TemporalBuilders.js";import h from"./Chart.js";import{createCompositionModel as R,createHeatmapModel as C,createSeriesModel as w,createTimesheetModel as B}from"./ChartData.js";function y(e,r,o){if("function"!=typeof o?.createModel||"function"!=typeof o?.render)throw new TypeError("Chart implementation requires createModel and render functions");const t=Object.freeze({...o}),n=(e,r)=>new h(e,r,t);return Object.freeze({make:o=>new e(o,r,n)})}var T=/*#__PURE__*/y(s,"line",{createModel:w,render:r}),x=/*#__PURE__*/y(l,"bar",{createModel:w,render:e}),z=/*#__PURE__*/y(c,"scatter",{createModel:w,render:t}),A=/*#__PURE__*/y(p,"mixed",{createModel:w,render:o}),O=/*#__PURE__*/y(c,"bubble",{createModel:w,render:t}),k=/*#__PURE__*/y(M,"pie",{createModel:R,render:n}),q=/*#__PURE__*/y(M,"donut",{createModel:R,render:n}),v=/*#__PURE__*/y(f,"percentage",{createModel:R,render:n}),D=/*#__PURE__*/y(j,"radar",{createModel:R,render:i}),E=/*#__PURE__*/y(u,"polar-area",{createModel:R,render:d}),H=/*#__PURE__*/y(b,"heatmap",{createModel:C,render:a}),P=/*#__PURE__*/y(g,"timesheet",{createModel:B,render:m});export{x as BarChart,O as BubbleChart,q as DonutChart,H as HeatmapChart,T as LineChart,A as MixedChart,v as PercentageChart,k as PieChart,E as PolarAreaChart,D as RadarChart,z as ScatterChart,P as TimesheetChart,y as chartDefinition};
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ChartDefinition.js","names":[],"sources":["../../src/core/ChartDefinition.js"],"sourcesContent":["import {\n renderBarChart,\n renderLineChart,\n renderMixedChart,\n renderPointChart,\n} from \"../renderers/cartesian/CartesianRendering.js\";\nimport { renderAggregationChart } from \"../renderers/composition/AggregationRendering.js\";\nimport { renderPolarAreaChart } from \"../renderers/composition/PolarAreaRendering.js\";\nimport { renderRadarChart } from \"../renderers/composition/RadarRendering.js\";\nimport { renderHeatmapChart } from \"../renderers/temporal/HeatmapRendering.js\";\nimport { renderTimesheetChart } from \"../renderers/temporal/TimesheetRendering.js\";\n\nimport { LineChartBuilder } from \"./builders/Builder.js\";\nimport { BarChartBuilder, MixedChartBuilder, ScatterChartBuilder } from \"./builders/CartesianBuilders.js\";\nimport {\n PercentageChartBuilder,\n PolarAreaChartBuilder,\n RadarChartBuilder,\n SectorChartBuilder,\n} from \"./builders/CompositionBuilders.js\";\nimport { HeatmapChartBuilder, TimesheetChartBuilder } from \"./builders/TemporalBuilders.js\";\nimport Chart from \"./Chart.js\";\nimport {\n createCompositionModel,\n createHeatmapModel,\n createSeriesModel,\n createTimesheetModel,\n} from \"./ChartData.js\";\n\n/**\n * Creates one immutable chart definition and binds its private implementation.\n *\n * @param {typeof LineChartBuilder} Builder - Type-specific fluent builder constructor.\n * @param {string} type - Immutable chart type.\n * @param {{createModel: (type: string, data: object, config: object) => import(\"./ChartData.js\").default, render: (rendering: object) => void}} implementation - Family model and renderer functions.\n * @returns {Readonly<{make(parent: string | Element): object}>} Frozen public definition.\n */\nfunction chartDefinition(Builder, type, implementation) {\n if (typeof implementation?.createModel !== \"function\" || typeof implementation?.render !== \"function\") {\n throw new TypeError(\"Chart implementation requires createModel and render functions\");\n }\n\n const boundImplementation = Object.freeze({ ...implementation });\n const mount = (parent, options) => new Chart(parent, options, boundImplementation);\n\n return Object.freeze({\n /**\n * Starts detached authoring for one chart host.\n *\n * @param {string | Element} parent - Deferred selector or host element.\n * @returns {object} Fresh type-specific single-use builder.\n */\n make(parent) {\n return new Builder(parent, type, mount);\n },\n });\n}\n\nconst LineChart = /*#__PURE__*/ chartDefinition(LineChartBuilder, \"line\", {\n createModel: createSeriesModel,\n render: renderLineChart,\n});\n\nconst BarChart = /*#__PURE__*/ chartDefinition(BarChartBuilder, \"bar\", {\n createModel: createSeriesModel,\n render: renderBarChart,\n});\n\nconst ScatterChart = /*#__PURE__*/ chartDefinition(ScatterChartBuilder, \"scatter\", {\n createModel: createSeriesModel,\n render: renderPointChart,\n});\n\nconst MixedChart = /*#__PURE__*/ chartDefinition(MixedChartBuilder, \"mixed\", {\n createModel: createSeriesModel,\n render: renderMixedChart,\n});\n\nconst BubbleChart = /*#__PURE__*/ chartDefinition(ScatterChartBuilder, \"bubble\", {\n createModel: createSeriesModel,\n render: renderPointChart,\n});\n\nconst PieChart = /*#__PURE__*/ chartDefinition(SectorChartBuilder, \"pie\", {\n createModel: createCompositionModel,\n render: renderAggregationChart,\n});\n\nconst DonutChart = /*#__PURE__*/ chartDefinition(SectorChartBuilder, \"donut\", {\n createModel: createCompositionModel,\n render: renderAggregationChart,\n});\n\nconst PercentageChart = /*#__PURE__*/ chartDefinition(PercentageChartBuilder, \"percentage\", {\n createModel: createCompositionModel,\n render: renderAggregationChart,\n});\n\nconst RadarChart = /*#__PURE__*/ chartDefinition(RadarChartBuilder, \"radar\", {\n createModel: createCompositionModel,\n render: renderRadarChart,\n});\n\nconst PolarAreaChart = /*#__PURE__*/ chartDefinition(PolarAreaChartBuilder, \"polar-area\", {\n createModel: createCompositionModel,\n render: renderPolarAreaChart,\n});\n\nconst HeatmapChart = /*#__PURE__*/ chartDefinition(HeatmapChartBuilder, \"heatmap\", {\n createModel: createHeatmapModel,\n render: renderHeatmapChart,\n});\n\nconst TimesheetChart = /*#__PURE__*/ chartDefinition(TimesheetChartBuilder, \"timesheet\", {\n createModel: createTimesheetModel,\n render: renderTimesheetChart,\n});\n\nexport {\n BarChart,\n BubbleChart,\n DonutChart,\n HeatmapChart,\n LineChart,\n MixedChart,\n PercentageChart,\n PieChart,\n PolarAreaChart,\n RadarChart,\n ScatterChart,\n TimesheetChart,\n chartDefinition,\n};\n"],"mappings":"0mCAqCA,SAAS,EAAgB,EAAS,EAAM,GACtC,GAA2C,mBAAhC,GAAgB,aAAgE,mBAA3B,GAAgB,OAC9E,MAAM,IAAI,UAAU,kEAGtB,MAAM,EAAsB,OAAO,OAAO,IAAK,IACzC,EAAA,CAAS,EAAQ,IAAY,IAAI,EAAM,EAAQ,EAAS,GAE9D,OAAO,OAAO,OAAO,CAOnB,KAAK,GACI,IAAI,EAAQ,EAAQ,EAAM,IAGvC,CAEA,IAAM,eAA0B,EAAgB,EAAkB,OAAQ,CACxE,YAAa,EACb,OAAQ,IAGJ,eAAyB,EAAgB,EAAiB,MAAO,CACrE,YAAa,EACb,OAAQ,IAGJ,eAA6B,EAAgB,EAAqB,UAAW,CACjF,YAAa,EACb,OAAQ,IAGJ,eAA2B,EAAgB,EAAmB,QAAS,CAC3E,YAAa,EACb,OAAQ,IAGJ,eAA4B,EAAgB,EAAqB,SAAU,CAC/E,YAAa,EACb,OAAQ,IAGJ,eAAyB,EAAgB,EAAoB,MAAO,CACxE,YAAa,EACb,OAAQ,IAGJ,eAA2B,EAAgB,EAAoB,QAAS,CAC5E,YAAa,EACb,OAAQ,IAGJ,eAAgC,EAAgB,EAAwB,aAAc,CAC1F,YAAa,EACb,OAAQ,IAGJ,eAA2B,EAAgB,EAAmB,QAAS,CAC3E,YAAa,EACb,OAAQ,IAGJ,eAA+B,EAAgB,EAAuB,aAAc,CACxF,YAAa,EACb,OAAQ,IAGJ,eAA6B,EAAgB,EAAqB,UAAW,CACjF,YAAa,EACb,OAAQ,IAGJ,eAA+B,EAAgB,EAAuB,YAAa,CACvF,YAAa,EACb,OAAQ"}
@@ -0,0 +1 @@
1
+ import{ChartType as t}from"../support/Constants.js";function e(t,e){return e.some(t=>[void 0,null,""].includes(t))?null:JSON.stringify([t,...e])}function a(t){if(!t||"object"!=typeof t||t instanceof Date)return t;for(const e of Object.values(t))a(e);return Object.freeze(t)}function n(t,e,a){return t.labels[a]??t.datasets[e]?.points[a]?.x}function s(e,a,n){const{dataset:s,datasetIndex:r,pointIndex:o}=n,d=s?.points[o],i=n.label??a.labels[o];return Object.freeze({datasetIndex:r,dataset:s.name,label:i??d.x,x:d.x,y:d.y,...e===t.BUBBLE&&{r:d.r}})}function r(t,e){const a=e.heatmap.map(t=>t.value),n=Math.min(...a),s=Math.max(...a),r=e.heatmap[t].value,o=e.colors.length-1;if(n===s)return e.colors[0===r?0:o];const d=Math.min(o,Math.floor((r-n)/(s-n)*e.colors.length));return e.colors[d]}var o=class{#t;constructor(t){this.#t=Object.freeze(t)}from(t){return a(this.#t.from(t))}identityFor(t){return this.#t.identityFor(t)}};function d(t,a){return new o({from:e=>{const n=Number(e.dataset.pointIndex),r=Number(e.dataset.datasetIndex);if(-1===r)return function(t,e,a){const n=a.datasets.map((n,r)=>s(t,a,{dataset:n,datasetIndex:r,pointIndex:e})),r=n[0];return Object.freeze({type:t,index:e,label:a.labels[e],datasetIndex:r.datasetIndex,dataset:r.dataset,x:r.x,y:r.y,value:r.y,values:a.datasets.map(t=>t.points[e]?.y),points:n})}(t,n,a);const o=a.datasets[r],d=s(t,a,{dataset:o,datasetIndex:r,pointIndex:n});return function(t,e,a){return Object.freeze({type:t,index:e.pointIndex,datasetIndex:e.datasetIndex,dataset:e.dataset.name,label:a.label,x:a.x,y:a.y,value:a.y,values:[a.y],points:[a]})}(t,{dataset:o,datasetIndex:r,pointIndex:n},d)},identityFor:t=>{const s=Number(t.dataset.pointIndex),r=Number(t.dataset.datasetIndex);return-1===r?e("series-category",[...a.datasets.map(t=>t.identityName),n(a,0,s)]):e("series",[a.datasets[r]?.identityName,n(a,r,s)])}})}function i(a,r){return new o({from:e=>{const n=Number(e.dataset.datasetIndex),o=Number(e.dataset.pointIndex);return a===t.RADAR?function(e,a){const n=a.datasets[e],r=n.points.map((r,o)=>s(t.RADAR,a,{dataset:n,datasetIndex:e,pointIndex:o,label:a.labels[o]}));return Object.freeze({type:t.RADAR,index:e,label:n.name,datasetIndex:e,dataset:n.name,x:r[0]?.x,y:r[0]?.y,value:r[0]?.y,values:r.map(t=>t.y),points:r})}(n,r):function(t,e,a){const n=s(t,a,{dataset:a.datasets[0],datasetIndex:0,pointIndex:e});return Object.freeze({type:t,index:e,label:n.label,x:n.x,y:n.y,value:n.y,values:[n.y],points:[n],color:a.colors[e%a.colors.length]})}(a,o,r)},identityFor:s=>{const o=Number(s.dataset.datasetIndex),d=Number(s.dataset.pointIndex);return a===t.RADAR?e("series",[r.datasets[o]?.identityName,"dataset"]):e("composition",[n(r,0,d)])}})}function u(a){return new o({from:e=>{const n=Number(e.dataset.pointIndex),s=a.heatmap[n];return{type:t.HEATMAP,index:n,date:new Date(s.date),key:s.key,value:s.value,color:r(n,a)}},identityFor:t=>e("heatmap",[a.heatmap[Number(t.dataset.pointIndex)]?.key])})}function l(a){return new o({from:e=>{const n=Number(e.dataset.pointIndex),s=a.timesheet.tasks[n];return Object.freeze({type:t.TIMESHEET,index:n,label:s.label,start:new Date(s.start),end:new Date(s.end),duration:s.end-s.start,group:s.group,color:s.color,task:{...s,start:new Date(s.start),end:new Date(s.end)}})},identityFor:t=>{const n=a.timesheet.tasks[Number(t.dataset.pointIndex)];return e("timesheet",[n?.label,n?.start.valueOf(),n?.end.valueOf()])}})}export{i as createCompositionSelection,u as createHeatmapSelection,d as createSeriesSelection,l as createTimesheetSelection,o as default};
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ChartSelection.js","names":["#policy"],"sources":["../../src/core/ChartSelection.js"],"sourcesContent":["import { ChartType } from \"../support/Constants.js\";\n\n/**\n * Produces a collision-resistant key for primitive public identity parts.\n *\n * @param {string} family - Chart-family identity namespace.\n * @param {unknown[]} parts - Required logical identity values.\n * @returns {string | null} Serialized identity, or null when a part is absent.\n */\nfunction identityKey(family, parts) {\n return parts.some((part) =>\n [\n undefined,\n null,\n \"\",\n ].includes(part),\n )\n ? null\n : JSON.stringify([\n family,\n ...parts,\n ]);\n}\n\n/**\n * Deeply freezes public selection records while leaving dates mutable.\n *\n * @param {unknown} value - Selection value or nested collection.\n * @returns {unknown} The same recursively frozen value.\n */\nfunction freezeSelection(value) {\n if (!value || typeof value !== \"object\" || value instanceof Date) {\n return value;\n }\n\n for (const child of Object.values(value)) {\n freezeSelection(child);\n }\n\n return Object.freeze(value);\n}\n\n/**\n * Resolves the public label participating in logical identity.\n *\n * @param {object} collections - Normalized datasets and labels.\n * @param {number} datasetIndex - Dataset containing the point.\n * @param {number} pointIndex - Point position within that dataset.\n * @returns {unknown} Explicit label or point x coordinate.\n */\nfunction pointLabel(collections, datasetIndex, pointIndex) {\n return collections.labels[pointIndex] ?? collections.datasets[datasetIndex]?.points[pointIndex]?.x;\n}\n\n/**\n * Builds one normalized series point for a public payload.\n *\n * @param {string} type - Immutable chart type.\n * @param {object} collections - Normalized datasets and labels.\n * @param {object} identity - Dataset and point coordinates.\n * @returns {object} Complete public selection point.\n */\nfunction selectionPoint(type, collections, identity) {\n const { dataset, datasetIndex, pointIndex } = identity;\n const point = dataset?.points[pointIndex];\n const label = identity.label ?? collections.labels[pointIndex];\n\n return Object.freeze({\n datasetIndex,\n dataset: dataset.name,\n label: label ?? point.x,\n x: point.x,\n y: point.y,\n ...(type === ChartType.BUBBLE && { r: point.r }),\n });\n}\n\n/**\n * Creates one standard Cartesian point payload.\n *\n * @param {string} type - Immutable chart type.\n * @param {object} identity - Dataset and point coordinates.\n * @param {object} point - Selected normalized point.\n * @returns {object} Public Cartesian point payload.\n */\nfunction seriesPayload(type, identity, point) {\n return Object.freeze({\n type,\n index: identity.pointIndex,\n datasetIndex: identity.datasetIndex,\n dataset: identity.dataset.name,\n label: point.label,\n x: point.x,\n y: point.y,\n value: point.y,\n values: [\n point.y,\n ],\n points: [\n point,\n ],\n });\n}\n\n/**\n * Creates one aligned category payload.\n *\n * @param {string} type - Immutable chart type.\n * @param {number} index - Shared category position.\n * @param {object} collections - Normalized datasets and labels.\n * @returns {object} Public multi-series category payload.\n */\nfunction categoryPayload(type, index, collections) {\n const points = collections.datasets.map((dataset, datasetIndex) =>\n selectionPoint(type, collections, { dataset, datasetIndex, pointIndex: index }),\n );\n\n const first = points[0];\n\n return Object.freeze({\n type,\n index,\n label: collections.labels[index],\n datasetIndex: first.datasetIndex,\n dataset: first.dataset,\n x: first.x,\n y: first.y,\n value: first.y,\n values: collections.datasets.map((dataset) => dataset.points[index]?.y),\n points,\n });\n}\n\n/**\n * Creates one composition payload.\n *\n * @param {string} type - Immutable chart type.\n * @param {number} index - Selected point position.\n * @param {object} collections - Normalized datasets, labels, and colors.\n * @returns {object} Public composition payload.\n */\nfunction compositionPayload(type, index, collections) {\n const point = selectionPoint(type, collections, {\n dataset: collections.datasets[0],\n datasetIndex: 0,\n pointIndex: index,\n });\n\n return Object.freeze({\n type,\n index,\n label: point.label,\n x: point.x,\n y: point.y,\n value: point.y,\n values: [\n point.y,\n ],\n points: [\n point,\n ],\n color: collections.colors[index % collections.colors.length],\n });\n}\n\n/**\n * Creates one complete radar-dataset payload.\n *\n * @param {number} index - Selected dataset position.\n * @param {object} collections - Normalized datasets and labels.\n * @returns {object} Public radar payload.\n */\nfunction radarPayload(index, collections) {\n const dataset = collections.datasets[index];\n\n const points = dataset.points.map((_point, pointIndex) =>\n selectionPoint(ChartType.RADAR, collections, {\n dataset,\n datasetIndex: index,\n pointIndex,\n label: collections.labels[pointIndex],\n }),\n );\n\n return Object.freeze({\n type: ChartType.RADAR,\n index,\n label: dataset.name,\n datasetIndex: index,\n dataset: dataset.name,\n x: points[0]?.x,\n y: points[0]?.y,\n value: points[0]?.y,\n values: points.map((point) => point.y),\n points,\n });\n}\n\n/**\n * Resolves one heatmap cell color from the complete intensity scale.\n *\n * @param {number} index - Selected heatmap entry.\n * @param {object} collections - Normalized heatmap and colors.\n * @returns {string} Effective cell color.\n */\nfunction heatmapColor(index, collections) {\n const values = collections.heatmap.map((point) => point.value);\n const minimum = Math.min(...values);\n const maximum = Math.max(...values);\n const value = collections.heatmap[index].value;\n const last = collections.colors.length - 1;\n\n if (minimum === maximum) {\n return collections.colors[value === 0 ? 0 : last];\n }\n\n const level = Math.min(\n last,\n Math.floor(((value - minimum) / (maximum - minimum)) * collections.colors.length),\n );\n\n return collections.colors[level];\n}\n\n/**\n * Translates mark metadata through one family-specific immutable policy.\n */\nexport default class ChartSelection {\n #policy;\n\n /**\n * Captures one family-specific immutable policy.\n *\n * @param {object} policy - Family payload and identity functions.\n */\n constructor(policy) {\n this.#policy = Object.freeze(policy);\n }\n\n /**\n * Resolves one renderer mark into its public payload.\n *\n * @param {SVGElement} mark - Rendered mark carrying source indices.\n * @returns {object} Frozen callback and event payload.\n */\n from(mark) {\n return freezeSelection(this.#policy.from(mark));\n }\n\n /**\n * Resolves one renderer mark into its stable lifecycle identity.\n *\n * @param {SVGElement} mark - Rendered mark carrying source indices.\n * @returns {string | null} Stable identity or null when ambiguous.\n */\n identityFor(mark) {\n return this.#policy.identityFor(mark);\n }\n}\n\n/**\n * Creates the Cartesian selection presenter.\n *\n * @param {string} type - Concrete Cartesian type.\n * @param {object} collections - Normalized model collections.\n * @returns {ChartSelection} Family-specialized presenter.\n */\nfunction createSeriesSelection(type, collections) {\n return new ChartSelection({\n from: (mark) => {\n const pointIndex = Number(mark.dataset.pointIndex);\n const datasetIndex = Number(mark.dataset.datasetIndex);\n\n if (datasetIndex === -1) {\n return categoryPayload(type, pointIndex, collections);\n }\n\n const dataset = collections.datasets[datasetIndex];\n const point = selectionPoint(type, collections, { dataset, datasetIndex, pointIndex });\n\n return seriesPayload(type, { dataset, datasetIndex, pointIndex }, point);\n },\n identityFor: (mark) => {\n const pointIndex = Number(mark.dataset.pointIndex);\n const datasetIndex = Number(mark.dataset.datasetIndex);\n\n if (datasetIndex === -1) {\n const names = collections.datasets.map((dataset) => dataset.identityName);\n\n return identityKey(\"series-category\", [\n ...names,\n pointLabel(collections, 0, pointIndex),\n ]);\n }\n\n return identityKey(\"series\", [\n collections.datasets[datasetIndex]?.identityName,\n pointLabel(collections, datasetIndex, pointIndex),\n ]);\n },\n });\n}\n\n/**\n * Creates the composition and radial selection presenter.\n *\n * @param {string} type - Concrete composition type.\n * @param {object} collections - Normalized model collections.\n * @returns {ChartSelection} Family-specialized presenter.\n */\nfunction createCompositionSelection(type, collections) {\n return new ChartSelection({\n from: (mark) => {\n const datasetIndex = Number(mark.dataset.datasetIndex);\n const pointIndex = Number(mark.dataset.pointIndex);\n\n return type === ChartType.RADAR\n ? radarPayload(datasetIndex, collections)\n : compositionPayload(type, pointIndex, collections);\n },\n identityFor: (mark) => {\n const datasetIndex = Number(mark.dataset.datasetIndex);\n const pointIndex = Number(mark.dataset.pointIndex);\n\n return type === ChartType.RADAR\n ? identityKey(\"series\", [\n collections.datasets[datasetIndex]?.identityName,\n \"dataset\",\n ])\n : identityKey(\"composition\", [\n pointLabel(collections, 0, pointIndex),\n ]);\n },\n });\n}\n\n/**\n * Creates the heatmap selection presenter.\n *\n * @param {object} collections - Normalized model collections.\n * @returns {ChartSelection} Heatmap-specialized presenter.\n */\nfunction createHeatmapSelection(collections) {\n return new ChartSelection({\n from: (mark) => {\n const index = Number(mark.dataset.pointIndex);\n const point = collections.heatmap[index];\n\n return {\n type: ChartType.HEATMAP,\n index,\n date: new Date(point.date),\n key: point.key,\n value: point.value,\n color: heatmapColor(index, collections),\n };\n },\n identityFor: (mark) =>\n identityKey(\"heatmap\", [\n collections.heatmap[Number(mark.dataset.pointIndex)]?.key,\n ]),\n });\n}\n\n/**\n * Creates the timesheet selection presenter.\n *\n * @param {object} collections - Normalized model collections.\n * @returns {ChartSelection} Timesheet-specialized presenter.\n */\nfunction createTimesheetSelection(collections) {\n return new ChartSelection({\n from: (mark) => {\n const index = Number(mark.dataset.pointIndex);\n const task = collections.timesheet.tasks[index];\n\n return Object.freeze({\n type: ChartType.TIMESHEET,\n index,\n label: task.label,\n start: new Date(task.start),\n end: new Date(task.end),\n duration: task.end - task.start,\n group: task.group,\n color: task.color,\n task: { ...task, start: new Date(task.start), end: new Date(task.end) },\n });\n },\n identityFor: (mark) => {\n const task = collections.timesheet.tasks[Number(mark.dataset.pointIndex)];\n\n return identityKey(\"timesheet\", [\n task?.label,\n task?.start.valueOf(),\n task?.end.valueOf(),\n ]);\n },\n });\n}\n\nexport {\n createCompositionSelection,\n createHeatmapSelection,\n createSeriesSelection,\n createTimesheetSelection,\n};\n"],"mappings":"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"}
@@ -0,0 +1 @@
1
+ import{DEFAULT_COLORS as t}from"../support/Constants.js";var e=class{#t;#e;#s;constructor(t,e,s){this.#t=t,this.#e=e,this.#s=document.createElement("div"),this.#s.className="charts2-tooltip",this.#s.hidden=!0,this.#s.setAttribute("role","status"),this.#s.id=`charts2-tooltip-${s}`}get element(){return this.#s}show(t,e,{width:s,height:o}){const i=this.#t.getBoundingClientRect(),h=t.getBoundingClientRect();this.#o(t,e);const n=void 0!==t.dataset.tooltipAnchorX,r=n?this.#e.getBoundingClientRect():null,l=t.dataset.tooltipPlacement;if(n&&l){const e=r.left-i.left+this.#t.scrollLeft+Number(t.dataset.tooltipAnchorX)/s*r.width,h=r.top-i.top+this.#t.scrollTop+Number(t.dataset.tooltipAnchorY)/o*r.height;return void this.#i({left:e,top:h,placement:l},i)}this.#s.style.removeProperty("transform");const a={mark:t,hasSvgCoordinates:n},m={host:i,mark:h,svg:r},c=this.#h(a,m,s),d=this.#n(a,m,o);this.#r(c,d,i)}#i(t,e){this.#s.hidden=!1,this.#s.style.transform="none";const s=this.#s.getBoundingClientRect(),o={width:Math.max(this.#s.offsetWidth,s.width),height:Math.max(this.#s.offsetHeight,s.height)},i=this.#l(t,o),h=this.#a(i,o,e);this.#s.style.left=`${h.left}px`,this.#s.style.top=`${h.top}px`}#l(t,e){switch(t.placement){case"top":return{left:t.left-e.width/2,top:t.top-e.height-8};case"right":return{left:t.left+8,top:t.top-e.height/2};case"bottom":return{left:t.left-e.width/2,top:t.top+8};default:return{left:t.left-e.width-8,top:t.top-e.height/2}}}#a(t,e,s){const o={left:this.#t.scrollLeft+4,top:this.#t.scrollTop+4},i=Math.max(o.left,this.#t.scrollLeft+s.width-e.width-4),h=Math.max(o.top,this.#t.scrollTop+s.height-e.height-4);return{left:Math.min(i,Math.max(o.left,t.left)),top:Math.min(h,Math.max(o.top,t.top))}}#h(t,e,s){if(!t.hasSvgCoordinates)return e.mark.left-e.host.left+this.#t.scrollLeft+e.mark.width/2;const o=Number(t.mark.dataset.tooltipAnchorX)/s*e.svg.width;return e.svg.left-e.host.left+this.#t.scrollLeft+o}#n(t,e,s){if(t.hasSvgCoordinates){const o=Number(t.mark.dataset.tooltipAnchorY)/s*e.svg.height;return Math.max(0,e.svg.top-e.host.top+this.#t.scrollTop+o-8)}return t.mark.classList.contains("charts2-x-hit")?e.mark.top-e.host.top+this.#t.scrollTop+e.mark.height/2:Math.max(0,e.mark.top-e.host.top+this.#t.scrollTop-8)}hide(){this.#s.hidden=!0}destroy(){this.#s.remove()}#r(t,e,s){this.#s.hidden=!1;const o=this.#s.getBoundingClientRect(),i=this.#m(t,s,o),h=this.#c(e,s,o);this.#s.style.left=`${i}px`,this.#s.style.top=`${h}px`}#m(t,e,s){if(e.width<=24)return t;const o=Math.max(this.#s.offsetWidth,s.width)/2,i=this.#t.scrollLeft,h=i+e.width-o-4,n=i+o+4;return Math.min(h,Math.max(n,t))}#c(t,e,s){const o=Math.max(this.#s.offsetHeight,s.height),i=this.#t.scrollTop,h=i+o+4;if(e.height<=o+8)return h;const n=Math.max(h,i+e.height-4);return Math.min(n,Math.max(h,t))}#o(t,e){const s=String(e??""),o=s.lastIndexOf(": "),i=-1===o?[]:[{name:s.slice(0,o),value:s.slice(o+2),color:this.#d(t)}],h=t.dataset.tooltipItems?JSON.parse(t.dataset.tooltipItems):i,n=t.dataset.tooltipHeading??(-1===o?s:""),r=h.map(t=>function(t){const e=document.createElement("div"),s=document.createElement("i"),o=document.createElement("span"),i=document.createElement("strong");return e.className="charts2-tooltip-row",s.className="charts2-series-swatch",s.setAttribute("aria-hidden","true"),s.style.background=t.color,o.textContent=t.name,i.textContent=t.value,e.append(s,o,i),e}(t));n&&r.unshift(function(t){const e=document.createElement("div");return e.className="charts2-tooltip-heading",e.textContent=t,e}(n)),this.#s.replaceChildren(...r)}#d(e){return[e.getAttribute("fill"),e.getAttribute("stroke"),e.style.color].find(t=>t&&!["none","transparent"].includes(t))??t[0]}};export{e as default};
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ChartTooltip.js","names":["#host","#svg","#element","#renderContent","#positionBesideTarget","#requestedLeft","#requestedTop","#positionWithinViewport","#besideOrigin","#clampedOrigin","#clampedLeft","#clampedTop","#colorForMark"],"sources":["../../src/core/ChartTooltip.js"],"sourcesContent":["import { DEFAULT_COLORS } from \"../support/Constants.js\";\n\nconst TOOLTIP_ANCHOR_OFFSET = 8;\nconst TOOLTIP_EDGE_GAP = 4;\nconst TOOLTIP_MINIMUM_VIEWPORT_WIDTH = 24;\nconst TOOLTIP_SEPARATOR = \": \";\nconst TOOLTIP_SEPARATOR_LENGTH = TOOLTIP_SEPARATOR.length;\n\n/**\n * Creates one text-only tooltip heading.\n *\n * @param {string} text - Heading content supplied by normalized metadata.\n * @returns {HTMLDivElement} Safe heading node.\n */\nfunction tooltipHeading(text) {\n const heading = document.createElement(\"div\");\n\n heading.className = \"charts2-tooltip-heading\";\n heading.textContent = text;\n\n return heading;\n}\n\n/**\n * Creates one structured tooltip row.\n *\n * @param {{name: string, value: string, color: string}} item - Display-ready series item.\n * @returns {HTMLDivElement} Safe row containing swatch, name, and value nodes.\n */\nfunction tooltipRow(item) {\n const row = document.createElement(\"div\");\n const swatch = document.createElement(\"i\");\n const name = document.createElement(\"span\");\n const value = document.createElement(\"strong\");\n\n row.className = \"charts2-tooltip-row\";\n swatch.className = \"charts2-series-swatch\";\n swatch.setAttribute(\"aria-hidden\", \"true\");\n swatch.style.background = item.color;\n name.textContent = item.name;\n value.textContent = item.value;\n row.append(swatch, name, value);\n\n return row;\n}\n\n/**\n * Owns tooltip DOM, safe content construction, and viewport-aware placement for\n * one chart surface.\n */\nexport default class ChartTooltip {\n #host;\n #svg;\n #element;\n\n /**\n * Creates the detached status element associated with one chart SVG.\n *\n * @param {Element} host - Scroll viewport used to clamp tooltip placement.\n * @param {SVGSVGElement} svg - SVG surface containing tooltip anchor marks.\n * @param {number} chartId - Process-local identifier used by accessibility attributes.\n */\n constructor(host, svg, chartId) {\n this.#host = host;\n this.#svg = svg;\n this.#element = document.createElement(\"div\");\n this.#element.className = \"charts2-tooltip\";\n this.#element.hidden = true;\n this.#element.setAttribute(\"role\", \"status\");\n this.#element.id = `charts2-tooltip-${chartId}`;\n }\n\n /**\n * Exposes the owned tooltip element for initial host mounting.\n *\n * @returns {HTMLDivElement} Detached or mounted tooltip status element.\n */\n get element() {\n return this.#element;\n }\n\n /**\n * Builds safe content and presents it beside one rendered mark.\n *\n * @param {SVGElement} mark - Interactive mark carrying tooltip metadata.\n * @param {string} label - Fallback tooltip label when structured items are unavailable.\n * @param {object} dimensions - Current chart dimensions used for SVG anchor projection.\n * @param {number} dimensions.width - Current logical SVG width.\n * @param {number} dimensions.height - Current logical SVG height.\n * @returns {void} Tooltip content, visibility, and position are updated together.\n */\n show(mark, label, { width, height }) {\n const hostBox = this.#host.getBoundingClientRect();\n const markBox = mark.getBoundingClientRect();\n\n this.#renderContent(mark, label);\n\n const hasAnchor = mark.dataset.tooltipAnchorX !== undefined;\n const svgBox = hasAnchor ? this.#svg.getBoundingClientRect() : null;\n const placement = mark.dataset.tooltipPlacement;\n\n if (hasAnchor && placement) {\n const anchorLeft =\n svgBox.left -\n hostBox.left +\n this.#host.scrollLeft +\n (Number(mark.dataset.tooltipAnchorX) / width) * svgBox.width;\n\n const anchorTop =\n svgBox.top -\n hostBox.top +\n this.#host.scrollTop +\n (Number(mark.dataset.tooltipAnchorY) / height) * svgBox.height;\n\n this.#positionBesideTarget({ left: anchorLeft, top: anchorTop, placement }, hostBox);\n\n return;\n }\n\n this.#element.style.removeProperty(\"transform\");\n const anchor = { mark, hasSvgCoordinates: hasAnchor };\n const boxes = { host: hostBox, mark: markBox, svg: svgBox };\n const requestedLeft = this.#requestedLeft(anchor, boxes, width);\n const requestedTop = this.#requestedTop(anchor, boxes, height);\n\n this.#positionWithinViewport(requestedLeft, requestedTop, hostBox);\n }\n\n /**\n * Places a radial tooltip outside its sector without obscuring the chart.\n *\n * @param {object} anchor - Projected outer-arc coordinates and preferred side.\n * @param {DOMRect} hostBox - Current chart-host viewport bounds.\n * @returns {void} Tooltip receives clamped top-left coordinates.\n */\n #positionBesideTarget(anchor, hostBox) {\n this.#element.hidden = false;\n this.#element.style.transform = \"none\";\n const bounds = this.#element.getBoundingClientRect();\n\n const size = {\n width: Math.max(this.#element.offsetWidth, bounds.width),\n height: Math.max(this.#element.offsetHeight, bounds.height),\n };\n\n const origin = this.#besideOrigin(anchor, size);\n const position = this.#clampedOrigin(origin, size, hostBox);\n\n this.#element.style.left = `${position.left}px`;\n this.#element.style.top = `${position.top}px`;\n }\n\n /**\n * Resolves the top-left tooltip origin for one outer-arc side.\n *\n * @param {object} anchor - Projected coordinates and preferred placement.\n * @param {{width: number, height: number}} size - Measured tooltip dimensions.\n * @returns {{left: number, top: number}} Unclamped top-left coordinates.\n */\n #besideOrigin(anchor, size) {\n switch (anchor.placement) {\n case \"top\": {\n return {\n left: anchor.left - size.width / 2,\n top: anchor.top - size.height - TOOLTIP_ANCHOR_OFFSET,\n };\n }\n\n case \"right\": {\n return {\n left: anchor.left + TOOLTIP_ANCHOR_OFFSET,\n top: anchor.top - size.height / 2,\n };\n }\n\n case \"bottom\": {\n return {\n left: anchor.left - size.width / 2,\n top: anchor.top + TOOLTIP_ANCHOR_OFFSET,\n };\n }\n\n default: {\n return {\n left: anchor.left - size.width - TOOLTIP_ANCHOR_OFFSET,\n top: anchor.top - size.height / 2,\n };\n }\n }\n }\n\n /**\n * Clamps a top-left origin to the currently visible chart viewport.\n *\n * @param {{left: number, top: number}} origin - Requested tooltip origin.\n * @param {{width: number, height: number}} size - Measured tooltip dimensions.\n * @param {DOMRect} hostBox - Current chart-host viewport bounds.\n * @returns {{left: number, top: number}} Visible top-left coordinates.\n */\n #clampedOrigin(origin, size, hostBox) {\n const viewport = {\n left: this.#host.scrollLeft + TOOLTIP_EDGE_GAP,\n top: this.#host.scrollTop + TOOLTIP_EDGE_GAP,\n };\n\n const maximumLeft = Math.max(\n viewport.left,\n this.#host.scrollLeft + hostBox.width - size.width - TOOLTIP_EDGE_GAP,\n );\n\n const maximumTop = Math.max(\n viewport.top,\n this.#host.scrollTop + hostBox.height - size.height - TOOLTIP_EDGE_GAP,\n );\n\n return {\n left: Math.min(maximumLeft, Math.max(viewport.left, origin.left)),\n top: Math.min(maximumTop, Math.max(viewport.top, origin.top)),\n };\n }\n\n /**\n * Resolves the preferred horizontal tooltip anchor.\n *\n * @param {object} anchor - Mark and its coordinate-system policy.\n * @param {object} boxes - Measured host, mark, and optional SVG bounds.\n * @param {number} width - Logical SVG width.\n * @returns {number} Preferred horizontal center in host coordinates.\n */\n #requestedLeft(anchor, boxes, width) {\n if (!anchor.hasSvgCoordinates) {\n return boxes.mark.left - boxes.host.left + this.#host.scrollLeft + boxes.mark.width / 2;\n }\n\n const anchorRatio = Number(anchor.mark.dataset.tooltipAnchorX) / width;\n const projectedAnchor = anchorRatio * boxes.svg.width;\n\n return boxes.svg.left - boxes.host.left + this.#host.scrollLeft + projectedAnchor;\n }\n\n /**\n * Resolves the preferred vertical tooltip anchor.\n *\n * @param {object} anchor - Mark and its coordinate-system policy.\n * @param {object} boxes - Measured host, mark, and optional SVG bounds.\n * @param {number} height - Logical SVG height.\n * @returns {number} Preferred tooltip bottom edge in host coordinates.\n */\n #requestedTop(anchor, boxes, height) {\n if (anchor.hasSvgCoordinates) {\n const anchorRatio = Number(anchor.mark.dataset.tooltipAnchorY) / height;\n const projectedAnchor = anchorRatio * boxes.svg.height;\n\n return Math.max(\n 0,\n boxes.svg.top - boxes.host.top + this.#host.scrollTop + projectedAnchor - TOOLTIP_ANCHOR_OFFSET,\n );\n }\n\n if (anchor.mark.classList.contains(\"charts2-x-hit\")) {\n return boxes.mark.top - boxes.host.top + this.#host.scrollTop + boxes.mark.height / 2;\n }\n\n return Math.max(0, boxes.mark.top - boxes.host.top + this.#host.scrollTop - TOOLTIP_ANCHOR_OFFSET);\n }\n\n /**\n * Hides tooltip presentation without changing chart selection state.\n *\n * @returns {void} The owned status element becomes hidden.\n */\n hide() {\n this.#element.hidden = true;\n }\n\n /**\n * Removes the owned tooltip element from its host.\n *\n * @returns {void} Tooltip DOM is detached permanently.\n */\n destroy() {\n this.#element.remove();\n }\n\n /**\n * Clamps a requested anchor to the host's visible scroll viewport.\n *\n * @param {number} requestedLeft - Preferred horizontal center in host coordinates.\n * @param {number} requestedTop - Preferred bottom edge in host coordinates.\n * @param {DOMRect} hostBox - Current chart-host viewport bounds.\n * @returns {void} Tooltip style coordinates and visibility are updated in place.\n */\n #positionWithinViewport(requestedLeft, requestedTop, hostBox) {\n this.#element.hidden = false;\n const tooltipBounds = this.#element.getBoundingClientRect();\n const left = this.#clampedLeft(requestedLeft, hostBox, tooltipBounds);\n const top = this.#clampedTop(requestedTop, hostBox, tooltipBounds);\n\n this.#element.style.left = `${left}px`;\n this.#element.style.top = `${top}px`;\n }\n\n /**\n * Clamps a horizontal tooltip center to the visible host width.\n *\n * @param {number} requestedLeft - Preferred horizontal center.\n * @param {DOMRect} hostBox - Current host bounds.\n * @param {DOMRect} tooltipBounds - Current tooltip bounds.\n * @returns {number} Visible horizontal center in host coordinates.\n */\n #clampedLeft(requestedLeft, hostBox, tooltipBounds) {\n if (hostBox.width <= TOOLTIP_MINIMUM_VIEWPORT_WIDTH) {\n return requestedLeft;\n }\n\n const halfWidth = Math.max(this.#element.offsetWidth, tooltipBounds.width) / 2;\n const viewportLeft = this.#host.scrollLeft;\n const maximumLeft = viewportLeft + hostBox.width - halfWidth - TOOLTIP_EDGE_GAP;\n const minimumLeft = viewportLeft + halfWidth + TOOLTIP_EDGE_GAP;\n\n return Math.min(maximumLeft, Math.max(minimumLeft, requestedLeft));\n }\n\n /**\n * Clamps a vertical tooltip edge to the visible host height.\n *\n * @param {number} requestedTop - Preferred bottom edge.\n * @param {DOMRect} hostBox - Current host bounds.\n * @param {DOMRect} tooltipBounds - Current tooltip bounds.\n * @returns {number} Visible bottom edge in host coordinates.\n */\n #clampedTop(requestedTop, hostBox, tooltipBounds) {\n const tooltipHeight = Math.max(this.#element.offsetHeight, tooltipBounds.height);\n const viewportTop = this.#host.scrollTop;\n const minimumTop = viewportTop + tooltipHeight + TOOLTIP_EDGE_GAP;\n\n if (hostBox.height <= tooltipHeight + TOOLTIP_ANCHOR_OFFSET) {\n return minimumTop;\n }\n\n const maximumTop = Math.max(minimumTop, viewportTop + hostBox.height - TOOLTIP_EDGE_GAP);\n\n return Math.min(maximumTop, Math.max(minimumTop, requestedTop));\n }\n\n /**\n * Builds safe DOM nodes from structured or fallback tooltip metadata.\n *\n * @param {SVGElement} mark - Mark containing serialized tooltip fields.\n * @param {string} label - Plain-text fallback in `name: value` form.\n * @returns {void} Existing tooltip children are replaced with safe text nodes.\n */\n #renderContent(mark, label) {\n const rawLabel = String(label ?? \"\");\n const separator = rawLabel.lastIndexOf(TOOLTIP_SEPARATOR);\n\n const fallbackItems =\n separator === -1\n ? []\n : [\n {\n name: rawLabel.slice(0, separator),\n value: rawLabel.slice(separator + TOOLTIP_SEPARATOR_LENGTH),\n color: this.#colorForMark(mark),\n },\n ];\n\n const items = mark.dataset.tooltipItems ? JSON.parse(mark.dataset.tooltipItems) : fallbackItems;\n const headingText = mark.dataset.tooltipHeading ?? (separator === -1 ? rawLabel : \"\");\n const children = items.map((item) => tooltipRow(item));\n\n if (headingText) {\n children.unshift(tooltipHeading(headingText));\n }\n\n this.#element.replaceChildren(...children);\n }\n\n /**\n * Resolves the first visible presentation color available on a mark.\n *\n * @param {SVGElement} mark - Mark whose fill, stroke, or text color represents its series.\n * @returns {string} CSS color used for a tooltip swatch.\n */\n #colorForMark(mark) {\n const candidates = [\n mark.getAttribute(\"fill\"),\n mark.getAttribute(\"stroke\"),\n mark.style.color,\n ];\n\n return (\n candidates.find(\n (color) =>\n color &&\n ![\n \"none\",\n \"transparent\",\n ].includes(color),\n ) ?? DEFAULT_COLORS[0]\n );\n }\n}\n"],"mappings":"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"}
@@ -0,0 +1 @@
1
+ var e=/* @__PURE__ */new Set(["ArrowRight","ArrowDown"]),t=/* @__PURE__ */new Set(["ArrowLeft","ArrowUp"]),s="is-active",i="is-pressed",n="aria-pressed",l=class l{#e;#t;#s;#i;#n;#l;#a;#o;#h;#d;#r=/* @__PURE__ */new WeakMap;constructor(e,t,s){var i,n;this.#e=[...e],this.#s=s.labelFor,this.#i=s.onActiveChange,this.#n=s.onHide,this.#l=s.onShow,this.#a=s.onFocusChange,this.#o=t.allowPointerPan??!1,this.#h=t.previewable??!0,this.#d=t.selectable??!0,this.#t=(i=this.#e,n=t.activeIndex??-1,!this.#d||n<0||n>=i.length?-1:n);const l=Math.max(this.#t,0);for(const[a,o]of this.#e.entries())this.#c(o,a,l);if(this.#t>=0){const e=this.#e[this.#t];this.#l(e,this.#s(e,this.#t),this.#t)}}dismiss(){this.#t>=0?this.#u(-1):this.#n()}static#v(e,t,s){e.setAttribute(t,s?"true":"false")}#b(e){if(!e.classList.contains("charts2-point-hit"))return e;let t=this.#r.get(e);if(!t){const s=e.dataset.datasetIndex,i=e.dataset.pointIndex;t=e.ownerSVGElement.querySelector(`.charts2-visual-mark[data-dataset-index="${CSS.escape(s)}"][data-point-index="${CSS.escape(i)}"]`),this.#r.set(e,t??e)}return t??e}#g(e,t,s){e.classList.toggle(t,s),this.#b(e).classList.toggle(t,s)}#u(e){if(e!==this.#t){this.#t=e;for(const[e,t]of this.#e.entries()){const i=e===this.#t;this.#g(t,s,i),l.#v(t,n,i)}if(this.#i(this.#t,this.#t>=0?this.#e[this.#t]:null),this.#t>=0){const e=this.#e[this.#t];return void this.#l(e,this.#s(e,this.#t),this.#t)}this.#n()}}#w(e,t){this.#g(e,"is-hovered",!0),this.#l(e,this.#s(e,t),t)}#m(e){if(this.#g(e,"is-hovered",!1),this.#g(e,i,!1),this.#t>=0){const e=this.#e[this.#t];return void this.#l(e,this.#s(e,this.#t),this.#t)}this.#n(e)}#I(e,t){const s=(t+this.#e.length)%this.#e.length;this.#e[e].setAttribute("tabindex","-1"),this.#e[s].setAttribute("tabindex","0"),this.#e[s].focus()}#c(e,t,s){this.#p(e,t,s),e.addEventListener("focus",()=>this.#a(t,e)),this.#x(e,t),this.#S(e,t),this.#f(e,t)}#p(e,t,i){const a=this.#s(e,t);e.classList.add("charts2-interactive-mark"),e.classList.toggle("charts2-previewable-mark",this.#h),e.classList.toggle("charts2-selectable-mark",this.#d),e.setAttribute("role",this.#d?"button":"img"),e.setAttribute("focusable","true"),e.setAttribute("tabindex",t===i?"0":"-1"),e.setAttribute("aria-label",a),this.#d&&l.#v(e,n,t===this.#t),this.#d||e.removeAttribute(n),this.#g(e,s,t===this.#t)}#x(e,t){this.#h&&(e.addEventListener("pointerenter",()=>this.#w(e,t)),e.addEventListener("pointerleave",()=>this.#m(e)),e.addEventListener("pointerdown",()=>this.#w(e,t)),e.addEventListener("focus",()=>this.#w(e,t)),e.addEventListener("blur",()=>this.#m(e)))}#S(e,t){if(this.#d)return e.addEventListener("pointerdown",()=>this.#g(e,i,!0)),e.addEventListener("pointerup",()=>this.#g(e,i,!1)),e.addEventListener("pointercancel",()=>this.#g(e,i,!1)),void e.addEventListener("click",()=>this.#u(t));this.#o||e.addEventListener("pointerdown",e=>e.preventDefault())}#f(e,t){e.addEventListener("keydown",e=>this.#L(e,t)),e.addEventListener("keyup",()=>this.#g(e,i,!1))}#L(s,n){const l=function(s,i,n){return e.has(s)?i+1:t.has(s)?i-1:"Home"===s?0:"End"===s?n-1:null}(s.key,n,this.#e.length);return null!==l?(s.preventDefault(),void this.#I(n,l)):!this.#d||"Enter"!==s.key&&" "!==s.key?void("Escape"===s.key&&(s.preventDefault(),this.#t>=0&&this.#u(-1))):(s.preventDefault(),this.#g(this.#e[n],i,!0),void this.#u(n))}};export{l as default};
@@ -0,0 +1 @@
1
+ {"version":3,"file":"InteractionController.js","names":["#items","#labelFor","#onActiveChange","#onHide","#onShow","#onFocusChange","#allowPointerPan","#previewable","#selectable","#selectedIndex","#bindItem","#updateSelection","#reflectBoolean","#visualItems","#visualItem","#toggleVisualState","#configureItem","#bindPreview","#bindSelection","#bindKeyboard","#showPreview","#hidePreview","#handleKeydown","#moveFocus"],"sources":["../../src/core/InteractionController.js"],"sourcesContent":["const FORWARD_KEYS = new Set([\n \"ArrowRight\",\n \"ArrowDown\",\n]);\n\nconst BACKWARD_KEYS = new Set([\n \"ArrowLeft\",\n \"ArrowUp\",\n]);\n\nconst ACTIVE_CLASS = \"is-active\";\nconst PRESSED_CLASS = \"is-pressed\";\nconst PRESSED_ATTRIBUTE = \"aria-pressed\";\n\n/**\n * Resolves a valid initial selection without exposing partial conditions.\n *\n * @param {Element[]} items - Ordered interactive marks.\n * @param {number} activeIndex - Requested active mark index.\n * @param {boolean} selectable - Whether persistent selection is enabled.\n * @returns {number} Valid selected index or `-1` when selection starts empty.\n */\nfunction initialSelection(items, activeIndex, selectable) {\n if (!selectable || activeIndex < 0) {\n return -1;\n }\n\n if (activeIndex >= items.length) {\n return -1;\n }\n\n return activeIndex;\n}\n\n/**\n * Resolves keyboard navigation into a requested focus index.\n *\n * @param {string} key - Browser keyboard key.\n * @param {number} index - Current mark index.\n * @param {number} itemCount - Number of navigable marks.\n * @returns {number | null} Requested index, or null for a non-navigation key.\n */\nfunction focusIndexFor(key, index, itemCount) {\n if (FORWARD_KEYS.has(key)) {\n return index + 1;\n }\n\n if (BACKWARD_KEYS.has(key)) {\n return index - 1;\n }\n\n if (key === \"Home\") {\n return 0;\n }\n\n if (key === \"End\") {\n return itemCount - 1;\n }\n\n return null;\n}\n\n/**\n * Owns the transient preview, focus, pressed, and persistent selection state\n * for one rendered collection of SVG marks.\n */\nexport default class InteractionController {\n #items;\n #selectedIndex;\n #labelFor;\n #onActiveChange;\n #onHide;\n #onShow;\n #onFocusChange;\n #allowPointerPan;\n #previewable;\n #selectable;\n #visualItems = new WeakMap();\n\n /**\n * Binds one roving-tabindex interaction model to ordered SVG marks.\n *\n * @param {Iterable<Element>} marks - Marks ordered by keyboard navigation position.\n * @param {object} behavior - Initial selection and enabled interaction modes.\n * @param {object} callbacks - Label, preview, and persistent-selection effects.\n */\n constructor(marks, behavior, callbacks) {\n this.#items = [\n ...marks,\n ];\n this.#labelFor = callbacks.labelFor;\n this.#onActiveChange = callbacks.onActiveChange;\n this.#onHide = callbacks.onHide;\n this.#onShow = callbacks.onShow;\n this.#onFocusChange = callbacks.onFocusChange;\n this.#allowPointerPan = behavior.allowPointerPan ?? false;\n this.#previewable = behavior.previewable ?? true;\n this.#selectable = behavior.selectable ?? true;\n this.#selectedIndex = initialSelection(this.#items, behavior.activeIndex ?? -1, this.#selectable);\n\n const initialFocusIndex = Math.max(this.#selectedIndex, 0);\n\n for (const [\n index,\n item,\n ] of this.#items.entries()) {\n this.#bindItem(item, index, initialFocusIndex);\n }\n\n if (this.#selectedIndex >= 0) {\n const selected = this.#items[this.#selectedIndex];\n this.#onShow(selected, this.#labelFor(selected, this.#selectedIndex), this.#selectedIndex);\n }\n }\n\n /**\n * Clears persistent selection or hides a transient preview.\n *\n * @returns {void} Interaction state is returned to its idle form.\n */\n dismiss() {\n if (this.#selectedIndex >= 0) {\n this.#updateSelection(-1);\n\n return;\n }\n\n this.#onHide();\n }\n\n /**\n * Serializes a boolean into the string representation required by ARIA.\n *\n * @param {Element} element - DOM node that receives the accessibility attribute.\n * @param {string} name - Boolean attribute name such as `aria-pressed`.\n * @param {boolean} value - State to serialize as `\"true\"` or `\"false\"`.\n * @returns {void} The supplied element is updated in place.\n */\n static #reflectBoolean(element, name, value) {\n element.setAttribute(name, value ? \"true\" : \"false\");\n }\n\n /**\n * Resolves the visible point paired with a transparent 44 px hit target.\n *\n * @param {Element} item - Interactive mark or point hit target.\n * @returns {Element} Visible presentation mark, or the item itself.\n */\n #visualItem(item) {\n if (!item.classList.contains(\"charts2-point-hit\")) {\n return item;\n }\n\n let visual = this.#visualItems.get(item);\n\n if (!visual) {\n const datasetIndex = item.dataset.datasetIndex;\n const pointIndex = item.dataset.pointIndex;\n visual = item.ownerSVGElement.querySelector(\n `.charts2-visual-mark[data-dataset-index=\"${CSS.escape(datasetIndex)}\"][data-point-index=\"${CSS.escape(pointIndex)}\"]`,\n );\n this.#visualItems.set(item, visual ?? item);\n }\n\n return visual ?? item;\n }\n\n /**\n * Reflects a visual interaction class on both hit and presentation marks.\n *\n * @param {Element} item - Interactive mark receiving state.\n * @param {string} className - CSS state class.\n * @param {boolean} active - Whether the state is enabled.\n * @returns {void} The hit target and visible mark stay visually synchronized.\n */\n #toggleVisualState(item, className, active) {\n item.classList.toggle(className, active);\n this.#visualItem(item).classList.toggle(className, active);\n }\n\n /**\n * Applies one persistent selection and notifies chart lifecycle callbacks.\n *\n * @param {number} nextIndex - Selected mark index, or `-1` to clear selection.\n * @returns {void} CSS, ARIA, and callback state are updated together.\n */\n #updateSelection(nextIndex) {\n if (nextIndex === this.#selectedIndex) {\n return;\n }\n\n this.#selectedIndex = nextIndex;\n for (const [\n index,\n item,\n ] of this.#items.entries()) {\n const isSelected = index === this.#selectedIndex;\n this.#toggleVisualState(item, ACTIVE_CLASS, isSelected);\n InteractionController.#reflectBoolean(item, PRESSED_ATTRIBUTE, isSelected);\n }\n\n this.#onActiveChange(\n this.#selectedIndex,\n this.#selectedIndex >= 0 ? this.#items[this.#selectedIndex] : null,\n );\n if (this.#selectedIndex >= 0) {\n const selected = this.#items[this.#selectedIndex];\n this.#onShow(selected, this.#labelFor(selected, this.#selectedIndex), this.#selectedIndex);\n\n return;\n }\n\n this.#onHide();\n }\n\n /**\n * Presents a transient hover, press, or focus preview for one mark.\n *\n * @param {Element} item - Mark entering a preview state.\n * @param {number} index - Mark position used to build its label.\n * @returns {void} Preview styling and tooltip state are updated.\n */\n #showPreview(item, index) {\n this.#toggleVisualState(item, \"is-hovered\", true);\n this.#onShow(item, this.#labelFor(item, index), index);\n }\n\n /**\n * Ends a transient preview and restores any persistent selection.\n *\n * @param {Element} item - Mark leaving its preview state.\n * @returns {void} Pressed styling and tooltip state are restored.\n */\n #hidePreview(item) {\n this.#toggleVisualState(item, \"is-hovered\", false);\n this.#toggleVisualState(item, PRESSED_CLASS, false);\n if (this.#selectedIndex >= 0) {\n const selected = this.#items[this.#selectedIndex];\n this.#onShow(selected, this.#labelFor(selected, this.#selectedIndex), this.#selectedIndex);\n\n return;\n }\n\n this.#onHide(item);\n }\n\n /**\n * Moves the sole tab stop through the circular ordered mark collection.\n *\n * @param {number} currentIndex - Mark currently owning the tab stop.\n * @param {number} nextIndex - Requested position before circular bounding.\n * @returns {void} Tabindex state and DOM focus move together.\n */\n #moveFocus(currentIndex, nextIndex) {\n const boundedIndex = (nextIndex + this.#items.length) % this.#items.length;\n this.#items[currentIndex].setAttribute(\"tabindex\", \"-1\");\n this.#items[boundedIndex].setAttribute(\"tabindex\", \"0\");\n this.#items[boundedIndex].focus();\n }\n\n /**\n * Applies semantics and event behavior to one rendered mark.\n *\n * @param {Element} item - SVG mark receiving interaction behavior.\n * @param {number} index - Mark position in the controller's ordered collection.\n * @param {number} initialFocusIndex - Initial owner of the roving tab stop.\n * @returns {void} Attributes and event listeners are attached in place.\n */\n #bindItem(item, index, initialFocusIndex) {\n this.#configureItem(item, index, initialFocusIndex);\n item.addEventListener(\"focus\", () => this.#onFocusChange(index, item));\n this.#bindPreview(item, index);\n this.#bindSelection(item, index);\n this.#bindKeyboard(item, index);\n }\n\n /**\n * Applies static accessibility and state attributes to one mark.\n *\n * @param {Element} item - SVG mark receiving attributes.\n * @param {number} index - Mark position in the ordered collection.\n * @param {number} initialFocusIndex - Initial owner of the roving tab stop.\n * @returns {void} The mark reflects its current interaction capabilities.\n */\n #configureItem(item, index, initialFocusIndex) {\n const label = this.#labelFor(item, index);\n\n item.classList.add(\"charts2-interactive-mark\");\n item.classList.toggle(\"charts2-previewable-mark\", this.#previewable);\n item.classList.toggle(\"charts2-selectable-mark\", this.#selectable);\n item.setAttribute(\"role\", this.#selectable ? \"button\" : \"img\");\n item.setAttribute(\"focusable\", \"true\");\n item.setAttribute(\"tabindex\", index === initialFocusIndex ? \"0\" : \"-1\");\n item.setAttribute(\"aria-label\", label);\n if (this.#selectable) {\n InteractionController.#reflectBoolean(item, PRESSED_ATTRIBUTE, index === this.#selectedIndex);\n }\n\n if (!this.#selectable) {\n item.removeAttribute(PRESSED_ATTRIBUTE);\n }\n\n this.#toggleVisualState(item, ACTIVE_CLASS, index === this.#selectedIndex);\n }\n\n /**\n * Binds transient pointer and focus preview behavior.\n *\n * @param {Element} item - SVG mark receiving preview listeners.\n * @param {number} index - Mark position used by callbacks.\n * @returns {void} Preview listeners are attached when enabled.\n */\n #bindPreview(item, index) {\n if (!this.#previewable) {\n return;\n }\n\n item.addEventListener(\"pointerenter\", () => this.#showPreview(item, index));\n item.addEventListener(\"pointerleave\", () => this.#hidePreview(item));\n item.addEventListener(\"pointerdown\", () => this.#showPreview(item, index));\n item.addEventListener(\"focus\", () => this.#showPreview(item, index));\n item.addEventListener(\"blur\", () => this.#hidePreview(item));\n }\n\n /**\n * Binds persistent selection or pointer-pan behavior.\n *\n * @param {Element} item - SVG mark receiving pointer listeners.\n * @param {number} index - Mark position used by selection callbacks.\n * @returns {void} The appropriate pointer policy is attached.\n */\n #bindSelection(item, index) {\n if (this.#selectable) {\n item.addEventListener(\"pointerdown\", () => this.#toggleVisualState(item, PRESSED_CLASS, true));\n item.addEventListener(\"pointerup\", () => this.#toggleVisualState(item, PRESSED_CLASS, false));\n item.addEventListener(\"pointercancel\", () => this.#toggleVisualState(item, PRESSED_CLASS, false));\n item.addEventListener(\"click\", () => this.#updateSelection(index));\n\n return;\n }\n\n if (!this.#allowPointerPan) {\n item.addEventListener(\"pointerdown\", (event) => event.preventDefault());\n }\n }\n\n /**\n * Binds keyboard navigation and release behavior.\n *\n * @param {Element} item - SVG mark receiving keyboard listeners.\n * @param {number} index - Mark position used by navigation commands.\n * @returns {void} Keyboard listeners are attached.\n */\n #bindKeyboard(item, index) {\n item.addEventListener(\"keydown\", (event) => this.#handleKeydown(event, index));\n item.addEventListener(\"keyup\", () => this.#toggleVisualState(item, PRESSED_CLASS, false));\n }\n\n /**\n * Translates one keyboard command into focus or selection behavior.\n *\n * @param {KeyboardEvent} event - Key event dispatched by an interactive mark.\n * @param {number} index - Position of the mark that received the command.\n * @returns {void} Recognized commands prevent default behavior and update state.\n */\n #handleKeydown(event, index) {\n const focusIndex = focusIndexFor(event.key, index, this.#items.length);\n\n if (focusIndex !== null) {\n event.preventDefault();\n this.#moveFocus(index, focusIndex);\n\n return;\n }\n\n if (this.#selectable && (event.key === \"Enter\" || event.key === \" \")) {\n event.preventDefault();\n this.#toggleVisualState(this.#items[index], PRESSED_CLASS, true);\n this.#updateSelection(index);\n\n return;\n }\n\n if (event.key === \"Escape\") {\n event.preventDefault();\n\n if (this.#selectedIndex >= 0) {\n this.#updateSelection(-1);\n }\n }\n }\n}\n"],"mappings":"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"}
@@ -0,0 +1 @@
1
+ import{isBoolean as e,isChoice as r,isNonEmptyText as o,isNumberAtLeast as n,isOpacity as i,isRecord as t}from"../support/Validation.js";import{validateObjectKeys as a}from"../support/Normalize.js";var l=["label","value","color","width","opacity","lineStyle","dash","labelPosition","labelColor","includeInDomain","formatLabel"],s=["label","range","color","opacity","labelPosition","labelColor","includeInDomain","formatLabel"],m="var(--charts-secondary-label-color, #6e6e73)",c=Object.freeze({solid:[],dashed:[4,3],dotted:[1,3]});function u(e,r){if(!o(e))throw new TypeError(`${r} label must be a non-empty string`)}function b(o,n){const t=void 0!==o.opacity,a=i(o.opacity);if(t&&!a)throw new TypeError(`${n} opacity must be from 0 through 1`);if(void 0!==o.labelPosition&&!r(o.labelPosition,["start","center","end"]))throw new TypeError(`${n} labelPosition must be start, center, or end`);if(void 0!==o.includeInDomain&&!e(o.includeInDomain))throw new TypeError(`${n} includeInDomain must be a boolean`);if(void 0!==o.formatLabel&&"function"!=typeof o.formatLabel)throw new TypeError(`${n} formatLabel must be a function`)}function d(e){const r=e.markers??[],o=e.regions??[];return{...e,yMarkers:r.map(e=>function(e){if(!t(e))throw new TypeError("marker must be an object or positional marker arguments");if(a(e,l,"marker"),u(e.label,"marker"),!Number.isFinite(e.value))throw new TypeError("marker value must be finite");if(b(e,"marker"),void 0!==e.width&&!n(e.width,0))throw new TypeError("marker width must be a non-negative finite number");if(void 0!==e.lineStyle&&!Object.hasOwn(c,e.lineStyle))throw new TypeError("marker lineStyle must be solid, dashed, or dotted");if(void 0!==e.dash&&(r=e.dash,!(Array.isArray(r)&&0!==r.length&&r.every(e=>n(e,0))&&r.some(e=>e>0))))throw new TypeError("marker dash must contain non-negative finite values with at least one positive value");var r;return function(e){return Object.freeze({...e,color:e.color??m,width:e.width??1,opacity:e.opacity??1,dash:e.dash??c[e.lineStyle??"dashed"],labelPosition:e.labelPosition??"end",labelColor:e.labelColor??m,includeInDomain:e.includeInDomain??!0})}(e)}(e)),yRegions:o.map(e=>function(e){if(!t(e))throw new TypeError("region must be an object or positional region arguments");if(a(e,s,"region"),u(e.label,"region"),!Array.isArray(e.range)||2!==e.range.length||e.range.some(e=>!Number.isFinite(e)))throw new TypeError("region range must contain exactly two finite numbers");return b(e,"region"),function(e){return Object.freeze({...e,range:[Math.min(...e.range),Math.max(...e.range)],color:e.color??"var(--charts-focus-ring, #007aff)",opacity:e.opacity??.08,labelPosition:e.labelPosition??"end",labelColor:e.labelColor??m,includeInDomain:e.includeInDomain??!0})}(e)}(e))}}export{d as normalizeCartesianSource};
@@ -0,0 +1 @@
1
+ {"version":3,"file":"NormalizeAnnotations.js","names":[],"sources":["../../src/core/NormalizeAnnotations.js"],"sourcesContent":["import { validateObjectKeys } from \"../support/Normalize.js\";\nimport {\n isBoolean,\n isChoice,\n isNonEmptyText,\n isNumberAtLeast,\n isOpacity,\n isRecord,\n} from \"../support/Validation.js\";\n\nconst MARKER_KEYS = [\n \"label\",\n \"value\",\n \"color\",\n \"width\",\n \"opacity\",\n \"lineStyle\",\n \"dash\",\n \"labelPosition\",\n \"labelColor\",\n \"includeInDomain\",\n \"formatLabel\",\n];\n\nconst REGION_KEYS = [\n \"label\",\n \"range\",\n \"color\",\n \"opacity\",\n \"labelPosition\",\n \"labelColor\",\n \"includeInDomain\",\n \"formatLabel\",\n];\n\nconst DASHED_LENGTH = 4;\nconst DASHED_GAP = 3;\nconst DOTTED_LENGTH = 1;\nconst DEFAULT_REGION_OPACITY = 0.08;\nconst SECONDARY_LABEL_COLOR = \"var(--charts-secondary-label-color, #6e6e73)\";\n\nconst DASH_PATTERNS = Object.freeze({\n solid: [],\n dashed: [\n DASHED_LENGTH,\n DASHED_GAP,\n ],\n dotted: [\n DOTTED_LENGTH,\n DASHED_GAP,\n ],\n});\n\n/**\n * Applies marker defaults without mutating public input.\n *\n * @param {object} marker - Validated marker input.\n * @returns {object} Complete marker renderer record.\n */\nfunction normalizedMarker(marker) {\n return Object.freeze({\n ...marker,\n color: marker.color ?? SECONDARY_LABEL_COLOR,\n width: marker.width ?? 1,\n opacity: marker.opacity ?? 1,\n dash: marker.dash ?? DASH_PATTERNS[marker.lineStyle ?? \"dashed\"],\n labelPosition: marker.labelPosition ?? \"end\",\n labelColor: marker.labelColor ?? SECONDARY_LABEL_COLOR,\n includeInDomain: marker.includeInDomain ?? true,\n });\n}\n\n/**\n * Applies ascending range and region defaults without mutating public input.\n *\n * @param {object} region - Validated region input.\n * @returns {object} Complete region renderer record.\n */\nfunction normalizedRegion(region) {\n return Object.freeze({\n ...region,\n range: [\n Math.min(...region.range),\n Math.max(...region.range),\n ],\n color: region.color ?? \"var(--charts-focus-ring, #007aff)\",\n opacity: region.opacity ?? DEFAULT_REGION_OPACITY,\n labelPosition: region.labelPosition ?? \"end\",\n labelColor: region.labelColor ?? SECONDARY_LABEL_COLOR,\n includeInDomain: region.includeInDomain ?? true,\n });\n}\n\n/**\n * Requires a non-empty annotation label.\n *\n * @param {unknown} value - Candidate label.\n * @param {string} concept - Marker or region.\n * @returns {void} Valid labels pass unchanged.\n */\nfunction validateLabel(value, concept) {\n if (!isNonEmptyText(value)) {\n throw new TypeError(`${concept} label must be a non-empty string`);\n }\n}\n\n/**\n * Normalizes one marker with explicit renderer-independent defaults.\n *\n * @param {object} marker - Copied public marker input.\n * @returns {object} Complete marker record.\n */\nfunction normalizeMarker(marker) {\n if (!isRecord(marker)) {\n throw new TypeError(\"marker must be an object or positional marker arguments\");\n }\n\n validateObjectKeys(marker, MARKER_KEYS, \"marker\");\n validateLabel(marker.label, \"marker\");\n if (!Number.isFinite(marker.value)) {\n throw new TypeError(\"marker value must be finite\");\n }\n\n validateAnnotationPresentation(marker, \"marker\");\n if (marker.width !== undefined && !isNumberAtLeast(marker.width, 0)) {\n throw new TypeError(\"marker width must be a non-negative finite number\");\n }\n\n if (marker.lineStyle !== undefined && !Object.hasOwn(DASH_PATTERNS, marker.lineStyle)) {\n throw new TypeError(\"marker lineStyle must be solid, dashed, or dotted\");\n }\n\n if (marker.dash !== undefined && !isValidDash(marker.dash)) {\n throw new TypeError(\n \"marker dash must contain non-negative finite values with at least one positive value\",\n );\n }\n\n return normalizedMarker(marker);\n}\n\n/**\n * Checks one explicit marker dash pattern.\n *\n * @param {unknown} dash - Candidate dash collection.\n * @returns {boolean} Whether the pattern is finite, non-negative, and visible.\n */\nfunction isValidDash(dash) {\n if (!Array.isArray(dash) || dash.length === 0) {\n return false;\n }\n\n return dash.every((value) => isNumberAtLeast(value, 0)) && dash.some((value) => value > 0);\n}\n\n/**\n * Normalizes one ascending region with explicit presentation defaults.\n *\n * @param {object} region - Copied public region input.\n * @returns {object} Complete region record.\n */\nfunction normalizeRegion(region) {\n if (!isRecord(region)) {\n throw new TypeError(\"region must be an object or positional region arguments\");\n }\n\n validateObjectKeys(region, REGION_KEYS, \"region\");\n validateLabel(region.label, \"region\");\n if (\n !Array.isArray(region.range) ||\n region.range.length !== 2 ||\n region.range.some((value) => !Number.isFinite(value))\n ) {\n throw new TypeError(\"region range must contain exactly two finite numbers\");\n }\n\n validateAnnotationPresentation(region, \"region\");\n\n return normalizedRegion(region);\n}\n\n/**\n * Validates presentation fields shared by markers and regions.\n *\n * @param {object} annotation - Exhaustive annotation input.\n * @param {string} concept - Marker or region name used by failures.\n * @returns {void} Supported values pass unchanged.\n */\nfunction validateAnnotationPresentation(annotation, concept) {\n const hasOpacity = annotation.opacity !== undefined;\n\n const isValidOpacity = isOpacity(annotation.opacity);\n\n if (hasOpacity && !isValidOpacity) {\n throw new TypeError(`${concept} opacity must be from 0 through 1`);\n }\n\n if (\n annotation.labelPosition !== undefined &&\n !isChoice(annotation.labelPosition, [\n \"start\",\n \"center\",\n \"end\",\n ])\n ) {\n throw new TypeError(`${concept} labelPosition must be start, center, or end`);\n }\n\n if (annotation.includeInDomain !== undefined && !isBoolean(annotation.includeInDomain)) {\n throw new TypeError(`${concept} includeInDomain must be a boolean`);\n }\n\n if (annotation.formatLabel !== undefined && typeof annotation.formatLabel !== \"function\") {\n throw new TypeError(`${concept} formatLabel must be a function`);\n }\n}\n\n/**\n * Normalizes every optional Cartesian annotation collection.\n *\n * @param {object} data - Public Cartesian data.\n * @returns {object} Data snapshot containing complete annotations.\n */\nfunction normalizeCartesianSource(data) {\n const markers = data.markers ?? [];\n const regions = data.regions ?? [];\n\n return {\n ...data,\n yMarkers: markers.map((marker) => normalizeMarker(marker)),\n yRegions: regions.map((region) => normalizeRegion(region)),\n };\n}\n\nexport { normalizeCartesianSource };\n"],"mappings":"sMAUA,IAAM,EAAc,CAClB,QACA,QACA,QACA,QACA,UACA,YACA,OACA,gBACA,aACA,kBACA,eAGI,EAAc,CAClB,QACA,QACA,QACA,UACA,gBACA,aACA,kBACA,eAOI,EAAwB,+CAExB,EAAgB,OAAO,OAAO,CAClC,MAAO,GACP,OAAQ,CARY,EACH,GAWjB,OAAQ,CAVY,EADH,KAgEnB,SAAS,EAAc,EAAO,GAC5B,IAAK,EAAe,GAClB,MAAM,IAAI,UAAU,GAAG,qCAE3B,CAoFA,SAAS,EAA+B,EAAY,GAClD,MAAM,OAAoC,IAAvB,EAAW,QAExB,EAAiB,EAAU,EAAW,SAE5C,GAAI,IAAe,EACjB,MAAM,IAAI,UAAU,GAAG,sCAGzB,QAC+B,IAA7B,EAAW,gBACV,EAAS,EAAW,cAAe,CAClC,QACA,SACA,QAGF,MAAM,IAAI,UAAU,GAAG,iDAGzB,QAAmC,IAA/B,EAAW,kBAAkC,EAAU,EAAW,iBACpE,MAAM,IAAI,UAAU,GAAG,uCAGzB,QAA+B,IAA3B,EAAW,aAA+D,mBAA3B,EAAW,YAC5D,MAAM,IAAI,UAAU,GAAG,mCAE3B,CAQA,SAAS,EAAyB,GAChC,MAAM,EAAU,EAAK,SAAW,GAC1B,EAAU,EAAK,SAAW,GAEhC,MAAO,IACF,EACH,SAAU,EAAQ,IAAK,GArH3B,SAAyB,GACvB,IAAK,EAAS,GACZ,MAAM,IAAI,UAAU,2DAKtB,GAFA,EAAmB,EAAQ,EAAa,UACxC,EAAc,EAAO,MAAO,WACvB,OAAO,SAAS,EAAO,OAC1B,MAAM,IAAI,UAAU,+BAItB,GADA,EAA+B,EAAQ,eAClB,IAAjB,EAAO,QAAwB,EAAgB,EAAO,MAAO,GAC/D,MAAM,IAAI,UAAU,qDAGtB,QAAyB,IAArB,EAAO,YAA4B,OAAO,OAAO,EAAe,EAAO,WACzE,MAAM,IAAI,UAAU,qDAGtB,QAAoB,IAAhB,EAAO,OAeQ,EAf2B,EAAO,OAgBhD,MAAM,QAAQ,IAAyB,IAAhB,EAAK,QAI1B,EAAK,MAAO,GAAU,EAAgB,EAAO,KAAO,EAAK,KAAM,GAAU,EAAQ,KAnBtF,MAAM,IAAI,UACR,wFAaN,IAAqB,EATnB,OA/EF,SAA0B,GACxB,OAAO,OAAO,OAAO,IAChB,EACH,MAAO,EAAO,OAAS,EACvB,MAAO,EAAO,OAAS,EACvB,QAAS,EAAO,SAAW,EAC3B,KAAM,EAAO,MAAQ,EAAc,EAAO,WAAa,UACvD,cAAe,EAAO,eAAiB,MACvC,WAAY,EAAO,YAAc,EACjC,gBAAiB,EAAO,kBAAmB,GAE/C,CAoES,CAAiB,EAC1B,CA0FsC,CAAgB,IAClD,SAAU,EAAQ,IAAK,GArE3B,SAAyB,GACvB,IAAK,EAAS,GACZ,MAAM,IAAI,UAAU,2DAKtB,GAFA,EAAmB,EAAQ,EAAa,UACxC,EAAc,EAAO,MAAO,WAEzB,MAAM,QAAQ,EAAO,QACE,IAAxB,EAAO,MAAM,QACb,EAAO,MAAM,KAAM,IAAW,OAAO,SAAS,IAE9C,MAAM,IAAI,UAAU,wDAKtB,OAFA,EAA+B,EAAQ,UAlGzC,SAA0B,GACxB,OAAO,OAAO,OAAO,IAChB,EACH,MAAO,CACL,KAAK,OAAO,EAAO,OACnB,KAAK,OAAO,EAAO,QAErB,MAAO,EAAO,OAAS,oCACvB,QAAS,EAAO,SAhDW,IAiD3B,cAAe,EAAO,eAAiB,MACvC,WAAY,EAAO,YAAc,EACjC,gBAAiB,EAAO,kBAAmB,GAE/C,CAuFS,CAAiB,EAC1B,CAmDsC,CAAgB,IAEtD"}
@@ -0,0 +1 @@
1
+ import{AGGREGATION_TYPES as t,CHART_ORIENTATIONS as o,ChartOrientation as r,ChartType as e,DEFAULT_COLORS as i,DEFAULT_PAD_ANGLE as n,TYPES as a,Y_AXIS_POSITIONS as s}from"../support/Constants.js";import{measureParentWidth as l}from"../support/Dom.js";import{isChoice as c,isNonEmptyText as u,isNumberAtLeast as p,isRecord as f,unknownKey as d}from"../support/Validation.js";var h=Object.freeze(["color","labelColor"]),m=Object.freeze(["area","ariaLabel","axes","axisFormatValue","colors","cornerRadius","countLabel","data","description","dots","dotSize","formatDate","formatDuration","gradient","formatLabel","formatTick","formatValue","grid","height","legend","line","maxSlices","onSelect","orientation","padAngle","radius","smooth","stacked","startAngle","strokeWidth","title","tooltip","tooltipFormatDate","tooltipFormatDuration","tooltipFormatLabel","tooltipFormatValue","type","valueLabels","width","yAxisPosition"]);function y(t,o){if(void 0!==t&&!p(t,0))throw new TypeError(`${o} must be a non-negative finite number`)}function b(t){if(!f(t))throw new TypeError("Chart options must be an object");const r=d(t,m);if(r)throw new TypeError(`Unsupported chart option: ${r}`);if(!a.includes(t.type))throw new TypeError(`Chart type must be one of: ${a.join(", ")}`);!function(t){if(t.orientation&&!c(t.orientation,o))throw new TypeError("Bar orientation must be vertical or horizontal");if(t.yAxisPosition&&!c(t.yAxisPosition,s))throw new TypeError("Y-axis position must be left or right")}(t),function(t){if(void 0!==(o=t.padAngle)&&(!p(o,0)||o>=360))throw new TypeError("Pad angle must be a finite number from 0 up to 360 degrees");var o;for(const[r,e]of[[t.radius,"Radius"],[t.cornerRadius,"Corner radius"]])y(r,e)}(t)}function w(t){return Object.freeze({axes:t.axes??!0,grid:t.grid??!0,valueLabels:t.valueLabels??!0,legend:g(t),tooltip:t.tooltip??!0,orientation:t.orientation??r.VERTICAL,ariaLabel:t.ariaLabel??t.title??`${t.type} chart`})}function g(o){return o.type!==e.TIMESHEET&&(void 0!==o.legend?o.legend:!![...t,e.POLAR_AREA,e.HEATMAP].includes(o.type)||o.data.datasets.length>=2)}function E(t,o){const r=o.data?.tasks?.length??4,i=Math.max(220,40*r+52),n=o.type===e.TIMESHEET?i:320;return{width:o.width??l(t),height:o.height??n}}function A(t,o){b(o),T(t,o);const r={colors:o.colors?[...o.colors]:i,strokeWidth:2,padAngle:o.padAngle??1.5,...o,...w(o),...E(t,o)};if([r.width,r.height].some(t=>!p(t,Number.EPSILON)))throw new TypeError("Chart width and height must be positive finite numbers");if(!Array.isArray(r.colors)||0===r.colors.length)throw new TypeError("Chart colors must be a non-empty array");return{options:r,hasCustomColors:void 0!==o.colors}}function T(t,o){const r=function(t){if(!f(t))return[];const o=[];Array.isArray(t.colors)&&o.push(...t.colors);const r=t.data&&"object"==typeof t.data?t.data:t;return C(o,r.datasets,["color"]),C(o,r.markers,h),C(o,r.regions,h),C(o,r.tasks,["color"]),o}(o);for(const e of r)v(t,e)}function C(t,o,r){if(Array.isArray(o))for(const e of o)if(f(e))for(const o of r)Object.hasOwn(e,o)&&t.push(e[o])}function v(t,o){if(!u(o))throw new TypeError("Color must be a non-empty supported CSS color");const r=function(t,o){if(!o.startsWith("var(")||!o.endsWith(")"))return o;const[r,...e]=o.slice(4,-1).split(","),i=getComputedStyle(t).getPropertyValue(r.trim()).trim(),n=e.join(",").trim(),a=i||n;if(!a)throw new TypeError(`Unresolved CSS color variable: ${r.trim()}`);return a}(t,o.trim());if(!(globalThis.CSS?.supports?.("color",r)??function(t){const o=document.createElement("span");return o.style.color=t,""!==o.style.color}(r)))throw new TypeError(`Unsupported CSS color: ${o}`)}export{A as normalizeChartOptions,T as validateChartColors,b as validateChartOptions};