@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
@@ -0,0 +1 @@
1
+ {"version":3,"file":"CartesianRendering.js","names":["#chart","#surface","#renderSeries"],"sources":["../../../src/renderers/cartesian/CartesianRendering.js"],"sourcesContent":["import LegendRenderer from \"../LegendRenderer.js\";\n\nimport CartesianAxesRenderer from \"./CartesianAxesRenderer.js\";\nimport CartesianInspectorRenderer from \"./CartesianInspectorRenderer.js\";\nimport CartesianLayout from \"./CartesianLayout.js\";\nimport {\n renderBarSeries,\n renderLineSeries,\n renderMixedSeries,\n renderPointSeries,\n} from \"./CartesianSeriesRendering.js\";\n\n/**\n * Coordinates one Cartesian pass across layout, axes, series, and inspection.\n */\nclass CartesianRenderer {\n #chart;\n #surface;\n #renderSeries;\n\n /**\n * Captures the chart snapshot and its owned SVG surface.\n *\n * @param {object} rendering - Collaborators for one Cartesian pass.\n * @param {object} rendering.chart - Frozen Cartesian data and options.\n * @param {import(\"../SvgSurface.js\").default} rendering.surface - Owned SVG drawing surface.\n * @param {(rendering: object) => void} renderSeries - Family-specific series drawing function.\n */\n constructor({ chart, surface }, renderSeries) {\n this.#chart = chart;\n this.#surface = surface;\n this.#renderSeries = renderSeries;\n }\n\n /**\n * Renders background, data marks, foreground, and optional category targets.\n *\n * @returns {void} Complete Cartesian content is appended to the chart SVG.\n */\n render() {\n const layout = new CartesianLayout(this.#chart);\n const rendering = { chart: this.#chart, layout, surface: this.#surface };\n const axes = new CartesianAxesRenderer(rendering);\n axes.renderBackground();\n this.#renderSeries(rendering);\n axes.renderForeground();\n const interactive = this.#chart.options.tooltip || typeof this.#chart.options.onSelect === \"function\";\n\n if (layout.usesInspector && interactive) {\n new CartesianInspectorRenderer(rendering).render();\n }\n }\n}\n\n/**\n * Renders one Cartesian chart and its optional series legend.\n *\n * @param {object} rendering - Frozen chart snapshot and owned SVG surface.\n * @param {(rendering: object) => void} renderSeries - Family-specific series drawing function.\n * @returns {void} Cartesian content is appended to the chart SVG.\n */\nfunction renderCartesianChart(rendering, renderSeries) {\n new CartesianRenderer(rendering, renderSeries).render();\n new LegendRenderer(rendering).render();\n}\n\n/**\n * Renders one line chart.\n *\n * @param {object} rendering - Frozen chart snapshot and owned SVG surface.\n * @returns {void} Line content is appended to the chart SVG.\n */\nfunction renderLineChart(rendering) {\n renderCartesianChart(rendering, renderLineSeries);\n}\n\n/**\n * Renders one bar chart.\n *\n * @param {object} rendering - Frozen chart snapshot and owned SVG surface.\n * @returns {void} Bar content is appended to the chart SVG.\n */\nfunction renderBarChart(rendering) {\n renderCartesianChart(rendering, renderBarSeries);\n}\n\n/**\n * Renders one point chart.\n *\n * @param {object} rendering - Frozen chart snapshot and owned SVG surface.\n * @returns {void} Point content is appended to the chart SVG.\n */\nfunction renderPointChart(rendering) {\n renderCartesianChart(rendering, renderPointSeries);\n}\n\n/**\n * Renders one mixed chart.\n *\n * @param {object} rendering - Frozen chart snapshot and owned SVG surface.\n * @returns {void} Mixed content is appended to the chart SVG.\n */\nfunction renderMixedChart(rendering) {\n renderCartesianChart(rendering, renderMixedSeries);\n}\n\nexport { renderBarChart, renderLineChart, renderMixedChart, renderPointChart };\n"],"mappings":"qSAeA,IAAM,EAAN,MACE,GACA,GACA,GAUA,WAAA,EAAY,MAAE,EAAA,QAAO,GAAW,GAC9B,MAAKA,EAAS,EACd,MAAKC,EAAW,EAChB,MAAKC,EAAgB,CACvB,CAOA,MAAA,GACE,MAAM,EAAS,IAAI,EAAgB,MAAKF,GAClC,EAAY,CAAE,MAAO,MAAKA,EAAQ,SAAQ,QAAS,MAAKC,GACxD,EAAO,IAAI,EAAsB,GACvC,EAAK,mBACL,MAAKC,EAAc,GACnB,EAAK,mBACL,MAAM,EAAc,MAAKF,EAAO,QAAQ,SAAmD,mBAAjC,MAAKA,EAAO,QAAQ,SAE1E,EAAO,eAAiB,GAC1B,IAAI,EAA2B,GAAW,QAE9C,GAUF,SAAS,EAAqB,EAAW,GACvC,IAAI,EAAkB,EAAW,GAAc,SAC/C,IAAI,EAAe,GAAW,QAChC,CAQA,SAAS,EAAgB,GACvB,EAAqB,EAAW,EAClC,CAQA,SAAS,EAAe,GACtB,EAAqB,EAAW,EAClC,CAQA,SAAS,EAAiB,GACxB,EAAqB,EAAW,EAClC,CAQA,SAAS,EAAiB,GACxB,EAAqB,EAAW,EAClC"}
@@ -0,0 +1 @@
1
+ import{ChartType as t}from"../../support/Constants.js";import{formatNumber as a,markMetadata as e,svg as s,titled as o}from"../../support/Dom.js";import{linePath as n,roundedBarPath as i}from"../../support/geometry/CartesianGeometry.js";import{formatLabel as r,formatValue as d}from"../../support/presentation/Formatting.js";import{datasetSummary as c,tooltipText as p}from"../../support/presentation/Presentation.js";var l=Object.freeze({[t.BAR]:0,[t.LINE]:1,[t.SCATTER]:2});function h(t,a){return void 0===t?a:t}var u=class{constructor(t,a){this.isSmooth=h(t.smooth,h(a.smooth,!0)),this.showsLine=h(t.line,h(a.line,!0)),this.showsDots=h(t.dots,!1!==a.dots),this.gradient=h(t.gradient,h(a.gradient,!1)),this.area=h(t.area,h(a.area,!1)),this.hasArea=!1!==t.area&&Boolean(this.gradient||this.area),this.strokeWidth=h(t.strokeWidth,a.strokeWidth),this.dotSize=h(t.dotSize,h(a.dotSize,4.5))}gradientOpacity(t,a){return"object"==typeof this.gradient?h(this.gradient[t],a):a}};function f(t,a){return t.datasets.length>1?`${a.name}, `:""}function y(t,a){const{chart:e}=t,{dataset:s,datasetIndex:o,pointIndex:n,point:i}=a;return`${f(e,s)}${p({options:e.options,label:e.labels[n],value:i.y,dataset:s,datasetIndex:o,index:n,point:i})}`}function x(t,a,n){const{surface:i}=t,r=e(s("circle",{...n.coordinates,r:Math.max(22,n.radius),fill:"transparent",stroke:"transparent","data-tooltip-anchor-x":n.coordinates.cx,"data-tooltip-anchor-y":n.coordinates.cy,class:"charts2-point-hit charts2-mark charts2-series-"+a.datasetIndex%4,style:`color:${a.dataset.color}`}),a.datasetIndex,n.pointIndex);n.tooltip&&(r.dataset.tooltipHeading=n.tooltip.heading,r.dataset.tooltipItems=JSON.stringify([n.tooltip.item])),i.append(o(r,n.label))}function I(a,i){const{chart:r,layout:d}=a,{dataset:p}=i,l=p.points.map((t,a)=>d.pointAt(t,a)),h=new u(p,r.options),f={...i,path:n(l,h.isSmooth),presentation:h};h.hasArea&&function(t,a){const{chart:e,layout:o,surface:n}=t,{dataset:i,datasetIndex:r,path:d,presentation:c}=a,p=o.yAt(0),l=`${d} L${o.pointAt(i.points.at(-1),i.points.length-1).x},${p} L${o.pointAt(i.points[0],0).x},${p} Z`;if(!c.gradient)return void n.append("path",{d:l,fill:i.color,opacity:i.opacity??.2,class:"charts2-area"});const h=`charts2-gradient-${e.id}-${r}`,u=s("linearGradient",{id:h,x1:0,y1:0,x2:0,y2:1});u.append(s("stop",{offset:"0%","stop-color":i.color,"stop-opacity":c.gradientOpacity("fromOpacity",.42)}),s("stop",{offset:"100%","stop-color":i.color,"stop-opacity":c.gradientOpacity("toOpacity",0)}));const f=s("defs");f.append(u),n.append(f),n.append("path",{d:l,fill:`url(#${h})`,class:"charts2-area"})}(a,f),h.showsLine&&function(a,e){const{chart:s,layout:o,surface:n}=a,{dataset:i,datasetIndex:r,path:d,presentation:p}=e,l=!o.usesInspector&&o.type!==t.AXIS_MIXED;n.mark("path",{d,fill:"none",stroke:i.color,"stroke-width":p.strokeWidth,opacity:i.opacity??1,class:`${l?"charts2-line charts2-mark":"charts2-line"} charts2-series-${r%4}`},{dataset:r,point:0,title:c(i,s.labels,{options:s.options,datasetIndex:r})})}(a,f),d.showsIndividualMarks&&h.showsDots&&function(t,a){const{layout:n,surface:i}=t,{dataset:r,datasetIndex:d,presentation:c}=a;for(const[p,l]of r.points.entries()){const a=n.pointAt(l,p),h=y(t,{dataset:r,datasetIndex:d,pointIndex:p,point:l});i.append("circle",{cx:a.x,cy:a.y,r:c.dotSize,class:"charts2-point-halo","aria-hidden":"true"}),i.append(o(e(s("circle",{cx:a.x,cy:a.y,r:c.dotSize,fill:"var(--charts-point-fill)",stroke:r.color,opacity:r.opacity??1,class:"charts2-point charts2-visual-mark charts2-series-"+d%4,"aria-hidden":"true"}),d,p),h))}}(a,f),d.type!==t.AXIS_MIXED||d.usesInspector||function(t,a){const{layout:e}=t,{dataset:s,datasetIndex:o,presentation:n}=a;for(const[i,r]of s.points.entries()){const{x:a,y:d}=e.pointAt(r,i);x(t,{dataset:s,datasetIndex:o},{coordinates:{cx:a,cy:d},radius:n.dotSize,label:y(t,{dataset:s,datasetIndex:o,pointIndex:i,point:r}),pointIndex:i})}}(a,f)}function m(e,s){const{chart:o}=e,{series:n,source:i,pointIndex:c}=s,p={target:"tooltip",dataset:n.dataset,datasetIndex:n.datasetIndex,datasetName:n.dataset.name,index:c,point:i},l=o.labels[c]??i.x,h=r(o.options,l,p),u=n.datasetType===t.BUBBLE?`, size ${a(i.r)}`:"",y=`${d(o.options,i.y,{...p,label:l})}${u}`,x={name:n.dataset.name,value:y,color:n.dataset.color};return{heading:String(h),item:x,label:`${f(o,n.dataset)}${h}: ${y}`}}function g(a,n){const{layout:i,surface:r}=a;for(const[d,c]of n.dataset.points.entries()){const p=n.datasetType===t.BUBBLE?c.r:4,l=m(a,{series:n,source:c,pointIndex:d}),h=n.datasetType===t.SCATTER,u={cx:i.xAt(c.x),cy:i.yAt(c.y)};h&&r.append("circle",{...u,r:p,class:"charts2-point-halo","aria-hidden":"true"});const f=e(s("circle",{...u,r:p,fill:h?"var(--charts-point-fill)":n.dataset.color,stroke:h?n.dataset.color:"none",opacity:n.dataset.opacity??(h?1:.65),class:`charts2-${n.datasetType} charts2-visual-mark charts2-series-${n.datasetIndex%4}`,"aria-hidden":"true"}),n.datasetIndex,d);r.append(o(f,l.label)),i.usesInspector||x(a,n,{coordinates:u,radius:p,label:l.label,pointIndex:d,tooltip:l})}}function b(t,a){const{point:e,pointIndex:s,barDatasetIndex:o}=a;if(!t.bars.isStacked)return 0;let n=0;for(const i of t.bars.datasets.slice(0,o)){const t=i.points[s].y;Math.sign(t)===Math.sign(e.y)&&(n+=t)}return n}function $(t,a){const{point:e,pointIndex:s,barDatasetIndex:o}=a;return t.bars.isStacked&&t.bars.datasets.slice(o+1).some(t=>{const a=t.points[s].y;return 0!==a&&Math.sign(a)===Math.sign(e.y)})}function k(t,a){return t.usesInspector?0:Math.max(0,(44-a.thickness)/2)}function S(t,a){const{chart:e,layout:s,surface:o}=t,{dataset:n,datasetIndex:r,point:d,pointIndex:c,geometry:l,shouldRoundValueEnd:h}=a;o.mark("path",{d:i({rectangle:l,direction:{orientation:s.orientation,value:d.y},rounding:{radius:n.radius??e.options.radius??3,shouldRoundValueEnd:h}}),fill:n.color,opacity:n.opacity??1,stroke:"transparent","stroke-width":k(s,l),class:`charts2-bar ${s.usesInspector?"charts2-visual-mark":"charts2-mark"} charts2-series-${r%4}`},{dataset:r,point:c,title:`${f(e,n)}${p({options:e.options,label:e.labels[c],value:d.y,dataset:n,datasetIndex:r,index:c,point:d})}`})}function T(t,a){const{layout:e}=t,{dataset:s,datasetIndex:o}=a,n=Math.max(0,e.bars.datasets.indexOf(s));for(const[i,r]of s.points.entries()){const a={point:r,pointIndex:i,barDatasetIndex:n};S(t,{dataset:s,datasetIndex:o,point:r,pointIndex:i,geometry:e.barFor(r,{category:i,series:n,base:b(e,a)}),shouldRoundValueEnd:!e.bars.isStacked||!$(e,a)})}}function A(t){for(const[a,e]of t.chart.datasets.entries())I(t,{dataset:e,datasetIndex:a})}function v(t){for(const[a,e]of t.chart.datasets.entries())T(t,{dataset:e,datasetIndex:a})}function E(t){for(const[a,e]of t.chart.datasets.entries())g(t,{dataset:e,datasetIndex:a,datasetType:t.layout.type})}function B(a){const e=a.chart.datasets.map((t,a)=>({dataset:t,datasetIndex:a})).toSorted((t,a)=>l[t.dataset.chartType]-l[a.dataset.chartType]);for(const s of e)s.dataset.chartType!==t.LINE?[t.BUBBLE,t.SCATTER].includes(s.dataset.chartType)?g(a,{...s,datasetType:s.dataset.chartType}):T(a,s):I(a,s)}export{v as renderBarSeries,A as renderLineSeries,B as renderMixedSeries,E as renderPointSeries};
@@ -0,0 +1 @@
1
+ {"version":3,"file":"CartesianSeriesRendering.js","names":[],"sources":["../../../src/renderers/cartesian/CartesianSeriesRendering.js"],"sourcesContent":["import { ChartType, DEFAULT_BAR_RADIUS } from \"../../support/Constants.js\";\nimport { formatNumber, markMetadata, svg, titled } from \"../../support/Dom.js\";\nimport { linePath, roundedBarPath } from \"../../support/geometry/Math.js\";\nimport { formatLabel, formatValue } from \"../../support/presentation/Formatting.js\";\nimport { datasetSummary, tooltipText } from \"../../support/presentation/Presentation.js\";\n\nconst CARTESIAN_LAYER = Object.freeze({\n [ChartType.BAR]: 0,\n [ChartType.LINE]: 1,\n [ChartType.SCATTER]: 2,\n});\n\nconst SERIES_CLASS_COUNT = 4;\nconst DEFAULT_LINE_MARKER_RADIUS = 4.5;\nconst DEFAULT_POINT_RADIUS = 4;\nconst BUBBLE_OPACITY = 0.65;\nconst MINIMUM_POINT_HIT_RADIUS = 22;\nconst MINIMUM_BAR_HIT_THICKNESS = 44;\nconst DEFAULT_AREA_OPACITY = 0.2;\nconst DEFAULT_GRADIENT_FROM_OPACITY = 0.42;\n\n/**\n * Resolves an explicit local value over one inherited fallback.\n *\n * @param {unknown} local - Optional local override.\n * @param {unknown} fallback - Inherited chart value.\n * @returns {unknown} Local value when explicit, otherwise the fallback.\n */\nfunction localValue(local, fallback) {\n return local === undefined ? fallback : local;\n}\n\n/**\n * Resolves chart defaults and local line overrides once per dataset.\n */\nclass LinePresentation {\n /**\n * Captures the effective presentation for one line dataset.\n *\n * @param {object} dataset - Normalized line dataset.\n * @param {object} options - Chart-level line defaults.\n */\n constructor(dataset, options) {\n this.isSmooth = localValue(dataset.smooth, localValue(options.smooth, true));\n this.showsLine = localValue(dataset.line, localValue(options.line, true));\n this.showsDots = localValue(dataset.dots, options.dots !== false);\n this.gradient = localValue(dataset.gradient, localValue(options.gradient, false));\n this.area = localValue(dataset.area, localValue(options.area, false));\n this.hasArea = dataset.area !== false && Boolean(this.gradient || this.area);\n this.strokeWidth = localValue(dataset.strokeWidth, options.strokeWidth);\n this.dotSize = localValue(dataset.dotSize, localValue(options.dotSize, DEFAULT_LINE_MARKER_RADIUS));\n }\n\n /**\n * Resolves one configured gradient endpoint opacity.\n *\n * @param {\"fromOpacity\" | \"toOpacity\"} name - Endpoint property.\n * @param {number} fallback - Built-in endpoint opacity.\n * @returns {number} Effective opacity.\n */\n gradientOpacity(name, fallback) {\n return typeof this.gradient === \"object\" ? localValue(this.gradient[name], fallback) : fallback;\n }\n}\n\n/**\n * Prefixes mark text when a chart contains multiple datasets.\n *\n * @param {object} chart - Frozen chart snapshot.\n * @param {object} dataset - Dataset owning the mark.\n * @returns {string} Optional dataset prefix for accessible mark text.\n */\nfunction seriesPrefix(chart, dataset) {\n return chart.datasets.length > 1 ? `${dataset.name}, ` : \"\";\n}\n\n/**\n * Formats one line point through dataset and chart formatter precedence.\n *\n * @param {object} rendering - Frozen chart snapshot and renderer collaborators.\n * @param {object} source - Dataset and point identity.\n * @returns {string} Formatted accessible line-point text.\n */\nfunction linePointLabel(rendering, source) {\n const { chart } = rendering;\n const { dataset, datasetIndex, pointIndex, point } = source;\n\n return `${seriesPrefix(chart, dataset)}${tooltipText({\n options: chart.options,\n label: chart.labels[pointIndex],\n value: point.y,\n dataset,\n datasetIndex,\n index: pointIndex,\n point,\n })}`;\n}\n\n/**\n * Appends one transparent point-centered interaction target.\n *\n * @param {object} rendering - Renderer collaborators including the SVG surface.\n * @param {object} series - Dataset and source index.\n * @param {object} target - Coordinates, radius, metadata, and accessible label.\n * @returns {void} One hit target is appended.\n */\nfunction renderPointHit(rendering, series, target) {\n const { surface } = rendering;\n\n const hit = markMetadata(\n svg(\"circle\", {\n ...target.coordinates,\n r: Math.max(MINIMUM_POINT_HIT_RADIUS, target.radius),\n fill: \"transparent\",\n stroke: \"transparent\",\n \"data-tooltip-anchor-x\": target.coordinates.cx,\n \"data-tooltip-anchor-y\": target.coordinates.cy,\n class: `charts2-point-hit charts2-mark charts2-series-${series.datasetIndex % SERIES_CLASS_COUNT}`,\n style: `color:${series.dataset.color}`,\n }),\n series.datasetIndex,\n target.pointIndex,\n );\n\n if (target.tooltip) {\n hit.dataset.tooltipHeading = target.tooltip.heading;\n hit.dataset.tooltipItems = JSON.stringify([\n target.tooltip.item,\n ]);\n }\n\n surface.append(titled(hit, target.label));\n}\n\n/**\n * Appends the optional fill below one line.\n *\n * @param {object} rendering - Renderer collaborators.\n * @param {object} entry - Dataset, source index, path, and presentation.\n * @returns {void} Area geometry is appended when enabled.\n */\nfunction renderArea(rendering, entry) {\n const { chart, layout, surface } = rendering;\n const { dataset, datasetIndex, path, presentation } = entry;\n const baseline = layout.yAt(0);\n const lastX = layout.pointAt(dataset.points.at(-1), dataset.points.length - 1).x;\n const firstX = layout.pointAt(dataset.points[0], 0).x;\n const area = `${path} L${lastX},${baseline} L${firstX},${baseline} Z`;\n\n if (!presentation.gradient) {\n surface.append(\"path\", {\n d: area,\n fill: dataset.color,\n opacity: dataset.opacity ?? DEFAULT_AREA_OPACITY,\n class: \"charts2-area\",\n });\n\n return;\n }\n\n const gradientId = `charts2-gradient-${chart.id}-${datasetIndex}`;\n const definition = svg(\"linearGradient\", { id: gradientId, x1: 0, y1: 0, x2: 0, y2: 1 });\n definition.append(\n svg(\"stop\", {\n offset: \"0%\",\n \"stop-color\": dataset.color,\n \"stop-opacity\": presentation.gradientOpacity(\"fromOpacity\", DEFAULT_GRADIENT_FROM_OPACITY),\n }),\n svg(\"stop\", {\n offset: \"100%\",\n \"stop-color\": dataset.color,\n \"stop-opacity\": presentation.gradientOpacity(\"toOpacity\", 0),\n }),\n );\n const definitions = svg(\"defs\");\n definitions.append(definition);\n surface.append(definitions);\n surface.append(\"path\", { d: area, fill: `url(#${gradientId})`, class: \"charts2-area\" });\n}\n\n/**\n * Appends one accessible line stroke.\n *\n * @param {object} rendering - Renderer collaborators.\n * @param {object} entry - Dataset, source index, path, and presentation.\n * @returns {void} One line path is appended.\n */\nfunction renderLineStroke(rendering, entry) {\n const { chart, layout, surface } = rendering;\n const { dataset, datasetIndex, path, presentation } = entry;\n const isDense = !layout.usesInspector && layout.type !== ChartType.AXIS_MIXED;\n\n surface.mark(\n \"path\",\n {\n d: path,\n fill: \"none\",\n stroke: dataset.color,\n \"stroke-width\": presentation.strokeWidth,\n opacity: dataset.opacity ?? 1,\n class: `${isDense ? \"charts2-line charts2-mark\" : \"charts2-line\"} charts2-series-${datasetIndex % SERIES_CLASS_COUNT}`,\n },\n {\n dataset: datasetIndex,\n point: 0,\n title: datasetSummary(dataset, chart.labels, { options: chart.options, datasetIndex }),\n },\n );\n}\n\n/**\n * Appends visible line markers without additional tab stops.\n *\n * @param {object} rendering - Renderer collaborators.\n * @param {object} entry - Dataset, source index, and presentation.\n * @returns {void} Visible marker circles are appended.\n */\n// eslint-disable-next-line max-lines-per-function -- Array layout lines do not add behavior.\nfunction renderVisibleLinePoints(rendering, entry) {\n const { layout, surface } = rendering;\n const { dataset, datasetIndex, presentation } = entry;\n\n for (const [\n pointIndex,\n point,\n ] of dataset.points.entries()) {\n const coordinates = layout.pointAt(point, pointIndex);\n const label = linePointLabel(rendering, { dataset, datasetIndex, pointIndex, point });\n\n surface.append(\"circle\", {\n cx: coordinates.x,\n cy: coordinates.y,\n r: presentation.dotSize,\n class: \"charts2-point-halo\",\n \"aria-hidden\": \"true\",\n });\n surface.append(\n titled(\n markMetadata(\n svg(\"circle\", {\n cx: coordinates.x,\n cy: coordinates.y,\n r: presentation.dotSize,\n fill: \"var(--charts-point-fill)\",\n stroke: dataset.color,\n opacity: dataset.opacity ?? 1,\n class: `charts2-point charts2-visual-mark charts2-series-${datasetIndex % SERIES_CLASS_COUNT}`,\n \"aria-hidden\": \"true\",\n }),\n datasetIndex,\n pointIndex,\n ),\n label,\n ),\n );\n }\n}\n\n/**\n * Appends one interaction target per independent mixed-line point.\n *\n * @param {object} rendering - Renderer collaborators.\n * @param {object} entry - Dataset, source index, and presentation.\n * @returns {void} Line hit targets are appended.\n */\nfunction renderLineHits(rendering, entry) {\n const { layout } = rendering;\n const { dataset, datasetIndex, presentation } = entry;\n\n for (const [\n pointIndex,\n point,\n ] of dataset.points.entries()) {\n const { x, y } = layout.pointAt(point, pointIndex);\n\n renderPointHit(\n rendering,\n { dataset, datasetIndex },\n {\n coordinates: { cx: x, cy: y },\n radius: presentation.dotSize,\n label: linePointLabel(rendering, { dataset, datasetIndex, pointIndex, point }),\n pointIndex,\n },\n );\n }\n}\n\n/**\n * Appends one line dataset, including presentation and interaction marks.\n *\n * @param {object} rendering - Renderer collaborators.\n * @param {object} source - Dataset and stable source index.\n * @returns {void} One complete line dataset is appended.\n */\nfunction renderLine(rendering, source) {\n const { chart, layout } = rendering;\n const { dataset } = source;\n const geometry = dataset.points.map((point, index) => layout.pointAt(point, index));\n const presentation = new LinePresentation(dataset, chart.options);\n const entry = { ...source, path: linePath(geometry, presentation.isSmooth), presentation };\n\n if (presentation.hasArea) {\n renderArea(rendering, entry);\n }\n\n if (presentation.showsLine) {\n renderLineStroke(rendering, entry);\n }\n\n if (layout.showsIndividualMarks && presentation.showsDots) {\n renderVisibleLinePoints(rendering, entry);\n }\n\n if (layout.type === ChartType.AXIS_MIXED && !layout.usesInspector) {\n renderLineHits(rendering, entry);\n }\n}\n\n/**\n * Builds structured tooltip content for one scatter or bubble point.\n *\n * @param {object} rendering - Renderer collaborators including the chart snapshot.\n * @param {object} state - Dataset, type, source point, and point index.\n * @returns {object} Structured and accessible tooltip content.\n */\nfunction pointTooltip(rendering, state) {\n const { chart } = rendering;\n const { series, source, pointIndex } = state;\n\n const context = {\n target: \"tooltip\",\n dataset: series.dataset,\n datasetIndex: series.datasetIndex,\n datasetName: series.dataset.name,\n index: pointIndex,\n point: source,\n };\n\n const rawCategory = chart.labels[pointIndex] ?? source.x;\n const heading = formatLabel(chart.options, rawCategory, context);\n const size = series.datasetType === ChartType.BUBBLE ? `, size ${formatNumber(source.r)}` : \"\";\n const value = `${formatValue(chart.options, source.y, { ...context, label: rawCategory })}${size}`;\n const item = { name: series.dataset.name, value, color: series.dataset.color };\n\n return {\n heading: String(heading),\n item,\n label: `${seriesPrefix(chart, series.dataset)}${heading}: ${value}`,\n };\n}\n\n/**\n * Appends one scatter or bubble dataset.\n *\n * @param {object} rendering - Renderer collaborators.\n * @param {object} series - Dataset, source index, and concrete point type.\n * @returns {void} Point marks and hit targets are appended.\n */\n// eslint-disable-next-line max-lines-per-function -- Array layout lines do not add behavior.\nfunction renderPoints(rendering, series) {\n const { layout, surface } = rendering;\n\n for (const [\n pointIndex,\n source,\n ] of series.dataset.points.entries()) {\n const radius = series.datasetType === ChartType.BUBBLE ? source.r : DEFAULT_POINT_RADIUS;\n const tooltip = pointTooltip(rendering, { series, source, pointIndex });\n const isOutlined = series.datasetType === ChartType.SCATTER;\n const coordinates = { cx: layout.xAt(source.x), cy: layout.yAt(source.y) };\n\n if (isOutlined) {\n surface.append(\"circle\", {\n ...coordinates,\n r: radius,\n class: \"charts2-point-halo\",\n \"aria-hidden\": \"true\",\n });\n }\n\n const point = markMetadata(\n svg(\"circle\", {\n ...coordinates,\n r: radius,\n fill: isOutlined ? \"var(--charts-point-fill)\" : series.dataset.color,\n stroke: isOutlined ? series.dataset.color : \"none\",\n opacity: series.dataset.opacity ?? (isOutlined ? 1 : BUBBLE_OPACITY),\n class: `charts2-${series.datasetType} charts2-visual-mark charts2-series-${series.datasetIndex % SERIES_CLASS_COUNT}`,\n \"aria-hidden\": \"true\",\n }),\n series.datasetIndex,\n pointIndex,\n );\n\n surface.append(titled(point, tooltip.label));\n\n if (!layout.usesInspector) {\n renderPointHit(rendering, series, { coordinates, radius, label: tooltip.label, pointIndex, tooltip });\n }\n }\n}\n\n/**\n * Resolves the signed value accumulated before one stacked bar.\n *\n * @param {object} layout - Cartesian layout and bar groups.\n * @param {object} state - Point and dataset coordinates.\n * @returns {number} Sum of preceding same-sign segments.\n */\nfunction stackedBase(layout, state) {\n const { point, pointIndex, barDatasetIndex } = state;\n\n if (!layout.bars.isStacked) {\n return 0;\n }\n\n let base = 0;\n\n for (const dataset of layout.bars.datasets.slice(0, barDatasetIndex)) {\n const value = dataset.points[pointIndex].y;\n\n if (Math.sign(value) === Math.sign(point.y)) {\n base += value;\n }\n }\n\n return base;\n}\n\n/**\n * Detects a later same-sign segment in one stacked category.\n *\n * @param {object} layout - Cartesian layout and bar groups.\n * @param {object} state - Point and dataset coordinates.\n * @returns {boolean} Whether rounding belongs to a later segment.\n */\nfunction hasLaterStackedSegment(layout, state) {\n const { point, pointIndex, barDatasetIndex } = state;\n\n return (\n layout.bars.isStacked &&\n layout.bars.datasets.slice(barDatasetIndex + 1).some((dataset) => {\n const value = dataset.points[pointIndex].y;\n\n return value !== 0 && Math.sign(value) === Math.sign(point.y);\n })\n );\n}\n\n/**\n * Resolves the transparent bar hit-stroke width.\n *\n * @param {object} layout - Cartesian interaction policy.\n * @param {object} geometry - Resolved bar geometry.\n * @returns {number} Non-negative hit-stroke width.\n */\nfunction barHitStrokeWidth(layout, geometry) {\n return layout.usesInspector ? 0 : Math.max(0, (MINIMUM_BAR_HIT_THICKNESS - geometry.thickness) / 2);\n}\n\n/**\n * Appends one resolved bar mark.\n *\n * @param {object} rendering - Renderer collaborators.\n * @param {object} state - Dataset, point, metadata, geometry, and rounding policy.\n * @returns {void} One accessible bar path is appended.\n */\nfunction renderBar(rendering, state) {\n const { chart, layout, surface } = rendering;\n const { dataset, datasetIndex, point, pointIndex, geometry, shouldRoundValueEnd } = state;\n\n surface.mark(\n \"path\",\n {\n d: roundedBarPath({\n rectangle: geometry,\n direction: { orientation: layout.orientation, value: point.y },\n rounding: {\n radius: dataset.radius ?? chart.options.radius ?? DEFAULT_BAR_RADIUS,\n shouldRoundValueEnd,\n },\n }),\n fill: dataset.color,\n opacity: dataset.opacity ?? 1,\n stroke: \"transparent\",\n \"stroke-width\": barHitStrokeWidth(layout, geometry),\n class: `charts2-bar ${layout.usesInspector ? \"charts2-visual-mark\" : \"charts2-mark\"} charts2-series-${datasetIndex % SERIES_CLASS_COUNT}`,\n },\n {\n dataset: datasetIndex,\n point: pointIndex,\n title: `${seriesPrefix(chart, dataset)}${tooltipText({\n options: chart.options,\n label: chart.labels[pointIndex],\n value: point.y,\n dataset,\n datasetIndex,\n index: pointIndex,\n point,\n })}`,\n },\n );\n}\n\n/**\n * Appends one grouped or stacked bar dataset.\n *\n * @param {object} rendering - Renderer collaborators.\n * @param {object} entry - Dataset and stable source index.\n * @returns {void} Bar paths are appended.\n */\nfunction renderBars(rendering, entry) {\n const { layout } = rendering;\n const { dataset, datasetIndex } = entry;\n const barDatasetIndex = Math.max(0, layout.bars.datasets.indexOf(dataset));\n\n for (const [\n pointIndex,\n point,\n ] of dataset.points.entries()) {\n const position = { point, pointIndex, barDatasetIndex };\n\n const geometry = layout.barFor(point, {\n category: pointIndex,\n series: barDatasetIndex,\n base: stackedBase(layout, position),\n });\n\n const shouldRoundValueEnd = !layout.bars.isStacked || !hasLaterStackedSegment(layout, position);\n\n renderBar(rendering, { dataset, datasetIndex, point, pointIndex, geometry, shouldRoundValueEnd });\n }\n}\n\n/**\n * Renders only line-series code for a Cartesian chart.\n *\n * @param {object} rendering - Renderer collaborators.\n * @returns {void} Line datasets are appended.\n */\nfunction renderLineSeries(rendering) {\n for (const [\n datasetIndex,\n dataset,\n ] of rendering.chart.datasets.entries()) {\n renderLine(rendering, { dataset, datasetIndex });\n }\n}\n\n/**\n * Renders only bar-series code for a Cartesian chart.\n *\n * @param {object} rendering - Renderer collaborators.\n * @returns {void} Bar datasets are appended.\n */\nfunction renderBarSeries(rendering) {\n for (const [\n datasetIndex,\n dataset,\n ] of rendering.chart.datasets.entries()) {\n renderBars(rendering, { dataset, datasetIndex });\n }\n}\n\n/**\n * Renders only scatter or bubble series code for a Cartesian chart.\n *\n * @param {object} rendering - Renderer collaborators.\n * @returns {void} Point datasets are appended.\n */\nfunction renderPointSeries(rendering) {\n for (const [\n datasetIndex,\n dataset,\n ] of rendering.chart.datasets.entries()) {\n renderPoints(rendering, { dataset, datasetIndex, datasetType: rendering.layout.type });\n }\n}\n\n/**\n * Renders mixed datasets in deterministic bar, line, then scatter order.\n *\n * @param {object} rendering - Renderer collaborators.\n * @returns {void} All mixed datasets are appended.\n */\nfunction renderMixedSeries(rendering) {\n const entries = rendering.chart.datasets\n .map((dataset, datasetIndex) => ({ dataset, datasetIndex }))\n .toSorted(\n (left, right) => CARTESIAN_LAYER[left.dataset.chartType] - CARTESIAN_LAYER[right.dataset.chartType],\n );\n\n for (const entry of entries) {\n if (entry.dataset.chartType === ChartType.LINE) {\n renderLine(rendering, entry);\n\n continue;\n }\n\n if (\n [\n ChartType.BUBBLE,\n ChartType.SCATTER,\n ].includes(entry.dataset.chartType)\n ) {\n renderPoints(rendering, { ...entry, datasetType: entry.dataset.chartType });\n\n continue;\n }\n\n renderBars(rendering, entry);\n }\n}\n\nexport { renderBarSeries, renderLineSeries, renderMixedSeries, renderPointSeries };\n"],"mappings":"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"}
@@ -0,0 +1 @@
1
+ import{ChartType as t}from"../../support/Constants.js";import{svg as e}from"../../support/Dom.js";import{formatLabel as r,formatValue as a}from"../../support/presentation/Formatting.js";import{aggregationLayout as s,tooltipText as i}from"../../support/presentation/Presentation.js";import n from"../LegendRenderer.js";import o from"./Composition.js";var h="value-label",c=class{#t;#e;constructor({chart:t,surface:e}){this.#t=t,this.#e=e}render(){const{height:e,type:a,width:i}=this.#t.options,n=new o(this.#t),c=this.#t.options.colors,l=n.parts.map((t,e)=>({label:r(this.#t.options,t.label,{target:h,index:e}),color:c[e%c.length]})),p=s({width:i,height:e,items:l,legend:this.#t.options.legend});if(a===t.PERCENTAGE)return this.#r(n,{colors:c,layout:p,width:i}),void(null!==p.legendBaseline&&this.#a(n.parts,c,p.legendBaseline));this.#s(n,{colors:c,layout:p,width:i,type:a}),null!==p.legendBaseline&&this.#a(n.parts,c,p.legendBaseline)}#r(t,{colors:e,layout:r,width:a}){const s=function(t,e,r){const a=t-32,s=Math.min(e.contentHeight,Math.max(28,.72*e.contentHeight));return{x:16,width:a,height:s,y:e.contentTop+(e.contentHeight-s)/2,radius:Math.min(r.radius,s/2,a/2),clipId:r.id}}(a,r,{radius:this.#t.options.radius??6,id:`charts2-percentage-clip-${this.#t.id}`});s.radius>0&&this.#i(s),this.#n(t,e,s)}#n(t,e,r){let a=r.x;for(const[s,n]of t.parts.entries()){const o=r.width*t.shareOf(n);this.#e.mark("rect",{x:a,y:r.y,width:o,height:r.height,fill:e[s%e.length],class:"charts2-percentage-segment charts2-mark",...r.radius>0&&{"clip-path":`url(#${r.clipId})`}},{dataset:0,point:s,title:i({options:this.#t.options,label:n.label,value:n.value,suffix:` (${Math.round(100*t.shareOf(n))}%)`})}),a+=o}}#i(t){const r=e("clipPath",{id:t.clipId}),a=e("rect",{x:t.x,y:t.y,width:t.width,height:t.height,rx:t.radius});r.append(a);const s=e("defs");s.append(r),this.#e.append(s)}#s(e,{colors:r,layout:s,width:i,type:n}){const o=i/2,c=s.contentTop+s.contentHeight/2,l=Math.max(8,.44*Math.min(i-32,s.contentHeight)),p=e.sectors({x:o,y:c},l,{type:n,colors:r});for(const t of p)this.#o(e,t,n);n===t.DONUT&&this.#e.text(a(this.#t.options,e.total,{target:h}),{x:o,y:c+5,class:"charts2-direct-value","text-anchor":"middle"})}#o(t,e,r){this.#e.mark(e.name,{...e.attributes,"data-tooltip-anchor-x":e.tooltip.x,"data-tooltip-anchor-y":e.tooltip.y,"data-tooltip-placement":e.tooltip.placement,class:`charts2-${r}-slice charts2-mark`},{dataset:0,point:e.index,title:this.#h(t,e.part,e.index)})}#h(t,e,r){return i({options:this.#t.options,label:e.label,value:e.value,dataset:this.#t.datasets[0],datasetIndex:0,index:r,point:this.#t.datasets[0].points[r],suffix:` (${Math.round(100*t.shareOf(e))}%)`})}#a(t,e,a){new n({chart:this.#t,surface:this.#e}).renderItems(t.map((t,a)=>({label:r(this.#t.options,t.label,{target:h,index:a}),color:e[a%e.length]})),a)}};function l(t){new c(t).render()}export{l as renderAggregationChart};
@@ -0,0 +1 @@
1
+ {"version":3,"file":"AggregationRendering.js","names":["#chart","#surface","#renderPercentage","#renderItemLegend","#renderSectors","#appendPercentageClip","#renderPercentageSegments","#appendSector","#sectorTitle"],"sources":["../../../src/renderers/composition/AggregationRendering.js"],"sourcesContent":["import { AGGREGATION_INSET, ChartType, DEFAULT_PERCENTAGE_RADIUS } from \"../../support/Constants.js\";\nimport { svg } from \"../../support/Dom.js\";\nimport { formatLabel, formatValue } from \"../../support/presentation/Formatting.js\";\nimport { aggregationLayout, tooltipText } from \"../../support/presentation/Presentation.js\";\nimport LegendRenderer from \"../LegendRenderer.js\";\n\nimport Composition from \"./Composition.js\";\n\nconst PERCENTAGE_INSET = 16;\nconst MINIMUM_SEGMENT_HEIGHT = 28;\nconst SEGMENT_HEIGHT_RATIO = 0.72;\nconst FULL_PERCENTAGE = 100;\nconst MINIMUM_SECTOR_RADIUS = 8;\nconst SECTOR_RADIUS_RATIO = 0.44;\nconst DONUT_LABEL_OFFSET = 5;\nconst VALUE_LABEL_TARGET = \"value-label\";\n\n/**\n * Resolves bounds and clipping for one horizontal percentage strip.\n *\n * @param {number} width - Available chart width.\n * @param {object} layout - Vertical aggregation layout.\n * @param {object} clipping - Radius and unique clip identifier.\n * @returns {object} Complete percentage strip geometry.\n */\nfunction percentageStrip(width, layout, clipping) {\n const stripWidth = width - PERCENTAGE_INSET * 2;\n\n const height = Math.min(\n layout.contentHeight,\n Math.max(MINIMUM_SEGMENT_HEIGHT, layout.contentHeight * SEGMENT_HEIGHT_RATIO),\n );\n\n return {\n x: PERCENTAGE_INSET,\n width: stripWidth,\n height,\n y: layout.contentTop + (layout.contentHeight - height) / 2,\n radius: Math.min(clipping.radius, height / 2, stripWidth / 2),\n clipId: clipping.id,\n };\n}\n\n/**\n * Renders the chart types that aggregate categories into parts of one whole.\n */\nclass AggregationRenderer {\n #chart;\n #surface;\n\n /**\n * Creates a renderer bound to one frozen render snapshot.\n *\n * @param {object} rendering - Collaborators for one composition pass.\n * @param {object} rendering.chart - Frozen chart data and options.\n * @param {import(\"../SvgSurface.js\").default} rendering.surface - Owned SVG drawing surface.\n */\n constructor({ chart, surface }) {\n this.#chart = chart;\n this.#surface = surface;\n }\n\n /**\n * Renders aggregation values as pie, donut, percentage, or progress geometry.\n *\n * @returns {void} Aggregation marks and optional legend content are appended to the chart SVG.\n */\n render() {\n const { height, type, width } = this.#chart.options;\n const composition = new Composition(this.#chart);\n const colors = this.#chart.options.colors;\n\n const legendItems = composition.parts.map((part, index) => ({\n label: formatLabel(this.#chart.options, part.label, { target: VALUE_LABEL_TARGET, index }),\n color: colors[index % colors.length],\n }));\n\n const layout = aggregationLayout({\n width,\n height,\n items: legendItems,\n legend: this.#chart.options.legend,\n });\n\n if (type === ChartType.PERCENTAGE) {\n this.#renderPercentage(composition, { colors, layout, width });\n\n if (layout.legendBaseline !== null) {\n this.#renderItemLegend(composition.parts, colors, layout.legendBaseline);\n }\n\n return;\n }\n\n this.#renderSectors(composition, { colors, layout, width, type });\n\n if (layout.legendBaseline !== null) {\n this.#renderItemLegend(composition.parts, colors, layout.legendBaseline);\n }\n }\n\n /**\n * Renders a rounded horizontal percentage strip.\n *\n * @param {Composition} composition - Normalized parts and their positive total.\n * @param {object} state - Layout and palette for the percentage chart.\n * @param {string[]} state.colors - Cyclic segment color palette.\n * @param {object} state.layout - Vertical content bounds and legend placement.\n * @param {number} state.width - Available chart width in pixels.\n * @returns {void} Percentage segments are appended to the chart SVG.\n */\n #renderPercentage(composition, { colors, layout, width }) {\n const strip = percentageStrip(width, layout, {\n radius: this.#chart.options.radius ?? DEFAULT_PERCENTAGE_RADIUS,\n id: `charts2-percentage-clip-${this.#chart.id}`,\n });\n\n if (strip.radius > 0) {\n this.#appendPercentageClip(strip);\n }\n\n this.#renderPercentageSegments(composition, colors, strip);\n }\n\n /**\n * Renders each proportional segment from left to right.\n *\n * @param {Composition} composition - Normalized parts and their positive total.\n * @param {string[]} colors - Cyclic segment palette.\n * @param {object} strip - Resolved strip geometry.\n * @returns {void} Percentage marks are appended to the chart SVG.\n */\n #renderPercentageSegments(composition, colors, strip) {\n let x = strip.x;\n\n for (const [\n index,\n part,\n ] of composition.parts.entries()) {\n const segmentWidth = strip.width * composition.shareOf(part);\n\n this.#surface.mark(\n \"rect\",\n {\n x,\n y: strip.y,\n width: segmentWidth,\n height: strip.height,\n fill: colors[index % colors.length],\n class: \"charts2-percentage-segment charts2-mark\",\n ...(strip.radius > 0 && { \"clip-path\": `url(#${strip.clipId})` }),\n },\n {\n dataset: 0,\n point: index,\n title: tooltipText({\n options: this.#chart.options,\n label: part.label,\n value: part.value,\n suffix: ` (${Math.round(composition.shareOf(part) * FULL_PERCENTAGE)}%)`,\n }),\n },\n );\n x += segmentWidth;\n }\n }\n\n /**\n * Appends the rounded clipping boundary shared by all percentage segments.\n *\n * @param {object} strip - Resolved strip geometry and clip identifier.\n * @returns {void} A clip definition is appended to the surface.\n */\n #appendPercentageClip(strip) {\n const clip = svg(\"clipPath\", { id: strip.clipId });\n\n const boundary = svg(\"rect\", {\n x: strip.x,\n y: strip.y,\n width: strip.width,\n height: strip.height,\n rx: strip.radius,\n });\n\n clip.append(boundary);\n\n const defs = svg(\"defs\");\n defs.append(clip);\n this.#surface.append(defs);\n }\n\n /**\n * Renders pie or donut sectors and the optional center total.\n *\n * @param {Composition} composition - Normalized parts and their sector geometry.\n * @param {object} state - Layout, palette, dimensions, and renderer type.\n * @param {string[]} state.colors - Cyclic sector color palette.\n * @param {object} state.layout - Vertical content bounds and legend placement.\n * @param {number} state.width - Available chart width in pixels.\n * @param {string} state.type - Pie or donut renderer type.\n * @returns {void} Aggregation sectors are appended to the chart SVG.\n */\n #renderSectors(composition, { colors, layout, width, type }) {\n const cx = width / 2;\n const cy = layout.contentTop + layout.contentHeight / 2;\n\n const radius = Math.max(\n MINIMUM_SECTOR_RADIUS,\n Math.min(width - AGGREGATION_INSET * 2, layout.contentHeight) * SECTOR_RADIUS_RATIO,\n );\n\n const sectors = composition.sectors({ x: cx, y: cy }, radius, { type, colors });\n\n for (const sector of sectors) {\n this.#appendSector(composition, sector, type);\n }\n\n if (type === ChartType.DONUT) {\n this.#surface.text(\n formatValue(this.#chart.options, composition.total, { target: VALUE_LABEL_TARGET }),\n {\n x: cx,\n y: cy + DONUT_LABEL_OFFSET,\n class: \"charts2-direct-value\",\n \"text-anchor\": \"middle\",\n },\n );\n }\n }\n\n /**\n * Appends one radial sector with stable outer-arc tooltip metadata.\n *\n * @param {Composition} composition - Current normalized composition.\n * @param {object} sector - Renderable sector descriptor.\n * @param {string} type - Pie or donut renderer type.\n * @returns {void} One interactive sector is appended.\n */\n #appendSector(composition, sector, type) {\n this.#surface.mark(\n sector.name,\n {\n ...sector.attributes,\n \"data-tooltip-anchor-x\": sector.tooltip.x,\n \"data-tooltip-anchor-y\": sector.tooltip.y,\n \"data-tooltip-placement\": sector.tooltip.placement,\n class: `charts2-${type}-slice charts2-mark`,\n },\n {\n dataset: 0,\n point: sector.index,\n title: this.#sectorTitle(composition, sector.part, sector.index),\n },\n );\n }\n\n /**\n * Formats one composition mark through tooltip-specific precedence.\n *\n * @param {Composition} composition - Current normalized composition.\n * @param {object} part - Current labeled value.\n * @param {number} index - Stable part position.\n * @returns {string} Plain tooltip and accessibility text.\n */\n #sectorTitle(composition, part, index) {\n return tooltipText({\n options: this.#chart.options,\n label: part.label,\n value: part.value,\n dataset: this.#chart.datasets[0],\n datasetIndex: 0,\n index,\n point: this.#chart.datasets[0].points[index],\n suffix: ` (${Math.round(composition.shareOf(part) * FULL_PERCENTAGE)}%)`,\n });\n }\n\n /**\n * Renders a wrapped legend for aggregation items rather than data series.\n *\n * @param {Array<{label: string, value: number}>} entries - Ordered aggregation values to describe.\n * @param {string[]} colors - Item colors aligned with the supplied entries.\n * @param {number} y - Baseline of the first legend row.\n * @returns {void} Positioned legend groups are appended to the chart SVG.\n */\n #renderItemLegend(entries, colors, y) {\n new LegendRenderer({ chart: this.#chart, surface: this.#surface }).renderItems(\n entries.map((item, index) => ({\n label: formatLabel(this.#chart.options, item.label, { target: VALUE_LABEL_TARGET, index }),\n color: colors[index % colors.length],\n })),\n y,\n );\n }\n}\n\n/**\n * Renders one aggregation chart through its family renderer.\n *\n * @param {object} rendering - Frozen chart snapshot and owned SVG surface.\n * @returns {void} Aggregation content is appended to the chart SVG.\n */\nfunction renderAggregationChart(rendering) {\n new AggregationRenderer(rendering).render();\n}\n\nexport { renderAggregationChart };\n"],"mappings":"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"}
@@ -0,0 +1 @@
1
+ import{ChartType as t}from"../../support/Constants.js";import{paddedSector as e,roundedSectorPath as r}from"../../support/geometry/SectorGeometry.js";var s=-Math.PI/2,i=class{#t;constructor(t){if(this.#t=t,this.parts=Object.freeze(this.#e()),this.total=this.parts.reduce((t,e)=>t+e.value,0),this.total<=0)throw new TypeError(`${t.options.type} chart requires a positive total`);Object.freeze(this)}shareOf(t){return t.value/this.total}sectors(t,e,r){let s=-Math.PI/2+(this.#t.options.startAngle??0)*Math.PI/180;const i=this.parts.filter(t=>t.value>0).length,a=[];for(const[o,n]of this.parts.entries()){const c=s+this.shareOf(n)*Math.PI*2;if(n.value>0){const l={part:n,index:o},p={center:t,radius:e,angles:{start:s,end:c},positiveCount:i};a.push(this.#r(l,p,r))}s=c}return a}#e(){return this.#t.labels.map((t,e)=>({label:t,value:this.#t.datasets.reduce((t,r)=>t+r.points[e].y,0)})).filter(t=>t.value>=0)}#r(e,r,s){const i=s.colors[e.index%s.colors.length],a={center:r.center,radius:r.radius,color:i};return s.type===t.DONUT&&1===this.parts.length?this.#s(e,a):s.type===t.PIE&&1===r.positiveCount?this.#i(e,a):this.#a(e,r,{type:s.type,color:i})}#a(s,i,a){const o=a.type===t.DONUT?.48*i.radius:0,n={outer:i.radius,inner:o},c=e({angles:i.angles,radii:n,padding:{angle:this.#t.options.padAngle,count:i.positiveCount}}),l=r({center:i.center,radii:n,angles:{outer:c.outer,inner:c.inner},cornerRadius:this.#t.options.cornerRadius??4}),p=this.#o(i.center,i.radius,(i.angles.start+i.angles.end)/2);return{part:s.part,index:s.index,name:"path",tooltip:p,attributes:{d:l,fill:a.color}}}#s(t,e){return{part:t.part,index:t.index,name:"circle",tooltip:this.#o(e.center,e.radius,s),attributes:{cx:e.center.x,cy:e.center.y,r:.72*e.radius,fill:"none",stroke:e.color,"stroke-width":.56*e.radius}}}#i(t,e){return{part:t.part,index:t.index,name:"circle",tooltip:this.#o(e.center,e.radius,s),attributes:{cx:e.center.x,cy:e.center.y,r:e.radius,fill:e.color}}}#o(t,e,r){const s=Math.cos(r),i=Math.sin(r);let a=i<0?"top":"bottom";return Math.abs(s)>Math.abs(i)&&(a=s<0?"left":"right"),{x:t.x+s*e,y:t.y+i*e,placement:a}}};export{i as default};
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Composition.js","names":["#chart","#parts","#sector","#donutCircle","#pieCircle","#pathSector","#tooltipAt"],"sources":["../../../src/renderers/composition/Composition.js"],"sourcesContent":["import { ChartType, DEFAULT_SECTOR_CORNER_RADIUS } from \"../../support/Constants.js\";\nimport { paddedSector, roundedSectorPath } from \"../../support/geometry/Math.js\";\n\nconst DEGREES_PER_HALF_CIRCLE = 180;\nconst DONUT_STROKE_RADIUS_RATIO = 0.72;\nconst DONUT_STROKE_WIDTH_RATIO = 0.56;\nconst DONUT_INNER_RADIUS_RATIO = 0.48;\nconst FULL_CIRCLE_TOP_ANGLE = -Math.PI / 2;\n\n/**\n * Owns normalized part-to-whole values and their non-DOM sector geometry.\n */\nexport default class Composition {\n #chart;\n\n /**\n * Aggregates chart datasets into a bounded immutable set of positive parts.\n *\n * @param {object} chart - Frozen composition data and options.\n * @throws {TypeError} When the resulting composition has no positive total.\n */\n constructor(chart) {\n this.#chart = chart;\n this.parts = Object.freeze(this.#parts());\n this.total = this.parts.reduce((sum, part) => sum + part.value, 0);\n if (this.total <= 0) {\n throw new TypeError(`${chart.options.type} chart requires a positive total`);\n }\n\n Object.freeze(this);\n }\n\n /**\n * Calculates the share of the whole represented by one part.\n *\n * @param {{value: number}} part - Normalized composition part.\n * @returns {number} Ratio between zero and one.\n */\n shareOf(part) {\n return part.value / this.total;\n }\n\n /**\n * Resolves ordered pie or donut shapes without creating SVG nodes.\n *\n * @param {{x: number, y: number}} center - Radial center.\n * @param {number} radius - Maximum sector radius.\n * @param {object} presentation - Chart type and cyclic palette.\n * @returns {Array<object>} Ordered circle or path descriptors for positive parts.\n */\n sectors(center, radius, presentation) {\n let angle = -Math.PI / 2 + ((this.#chart.options.startAngle ?? 0) * Math.PI) / DEGREES_PER_HALF_CIRCLE;\n const positiveCount = this.parts.filter((part) => part.value > 0).length;\n const sectors = [];\n\n for (const [\n index,\n part,\n ] of this.parts.entries()) {\n const next = angle + this.shareOf(part) * Math.PI * 2;\n\n if (part.value > 0) {\n const identity = { part, index };\n const geometry = { center, radius, angles: { start: angle, end: next }, positiveCount };\n\n sectors.push(this.#sector(identity, geometry, presentation));\n }\n\n angle = next;\n }\n\n return sectors;\n }\n\n /**\n * Aggregates normalized labels across datasets.\n *\n * @returns {Array<{label: string, value: number}>} Display-ready composition parts.\n */\n #parts() {\n return this.#chart.labels\n .map((label, index) => ({\n label,\n value: this.#chart.datasets.reduce((sum, dataset) => sum + dataset.points[index].y, 0),\n }))\n .filter((part) => part.value >= 0);\n }\n\n /**\n * Resolves one positive part into a circle or rounded path descriptor.\n *\n * @param {object} identity - Composition part and stable index.\n * @param {object} geometry - Center, radius, interval, and positive count.\n * @param {object} presentation - Chart type and cyclic palette.\n * @returns {object} SVG-independent sector descriptor.\n */\n #sector(identity, geometry, presentation) {\n const color = presentation.colors[identity.index % presentation.colors.length];\n const circle = { center: geometry.center, radius: geometry.radius, color };\n\n if (presentation.type === ChartType.DONUT && this.parts.length === 1) {\n return this.#donutCircle(identity, circle);\n }\n\n if (presentation.type === ChartType.PIE && geometry.positiveCount === 1) {\n return this.#pieCircle(identity, circle);\n }\n\n return this.#pathSector(identity, geometry, { type: presentation.type, color });\n }\n\n /**\n * Describes a multi-part pie or donut sector as a padded path.\n *\n * @param {object} identity - Composition part and stable index.\n * @param {object} geometry - Center, radius, interval, and positive count.\n * @param {object} presentation - Chart type and resolved color.\n * @returns {object} SVG-independent path descriptor.\n */\n #pathSector(identity, geometry, presentation) {\n const innerRadius =\n presentation.type === ChartType.DONUT ? geometry.radius * DONUT_INNER_RADIUS_RATIO : 0;\n\n const radii = { outer: geometry.radius, inner: innerRadius };\n\n const sector = paddedSector({\n angles: geometry.angles,\n radii,\n padding: { angle: this.#chart.options.padAngle, count: geometry.positiveCount },\n });\n\n const d = roundedSectorPath({\n center: geometry.center,\n radii,\n angles: { outer: sector.outer, inner: sector.inner },\n cornerRadius: this.#chart.options.cornerRadius ?? DEFAULT_SECTOR_CORNER_RADIUS,\n });\n\n const tooltip = this.#tooltipAt(\n geometry.center,\n geometry.radius,\n (geometry.angles.start + geometry.angles.end) / 2,\n );\n\n return {\n part: identity.part,\n index: identity.index,\n name: \"path\",\n tooltip,\n attributes: {\n d,\n fill: presentation.color,\n },\n };\n }\n\n /**\n * Describes the single-part donut as one stroked circle.\n *\n * @param {object} identity - Composition part and stable index.\n * @param {object} circle - Center, radius, and resolved color.\n * @returns {object} SVG-independent circle descriptor.\n */\n #donutCircle(identity, circle) {\n return {\n part: identity.part,\n index: identity.index,\n name: \"circle\",\n tooltip: this.#tooltipAt(circle.center, circle.radius, FULL_CIRCLE_TOP_ANGLE),\n attributes: {\n cx: circle.center.x,\n cy: circle.center.y,\n r: circle.radius * DONUT_STROKE_RADIUS_RATIO,\n fill: \"none\",\n stroke: circle.color,\n \"stroke-width\": circle.radius * DONUT_STROKE_WIDTH_RATIO,\n },\n };\n }\n\n /**\n * Describes the single-part pie as a filled circle.\n *\n * @param {object} identity - Composition part and stable index.\n * @param {object} circle - Center, radius, and resolved color.\n * @returns {object} SVG-independent circle descriptor.\n */\n #pieCircle(identity, circle) {\n return {\n part: identity.part,\n index: identity.index,\n name: \"circle\",\n tooltip: this.#tooltipAt(circle.center, circle.radius, FULL_CIRCLE_TOP_ANGLE),\n attributes: {\n cx: circle.center.x,\n cy: circle.center.y,\n r: circle.radius,\n fill: circle.color,\n },\n };\n }\n\n /**\n * Places a radial tooltip just beyond the sector's outer arc.\n *\n * @param {{x: number, y: number}} center - Radial chart center.\n * @param {number} radius - Outer sector radius.\n * @param {number} angle - Sector midpoint angle in radians.\n * @returns {{x: number, y: number, placement: string}} Stable outer-arc anchor and side.\n */\n #tooltipAt(center, radius, angle) {\n const horizontal = Math.cos(angle);\n const vertical = Math.sin(angle);\n let placement = vertical < 0 ? \"top\" : \"bottom\";\n\n if (Math.abs(horizontal) > Math.abs(vertical)) {\n placement = horizontal < 0 ? \"left\" : \"right\";\n }\n\n return {\n x: center.x + horizontal * radius,\n y: center.y + vertical * radius,\n placement,\n };\n }\n}\n"],"mappings":"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"}
@@ -0,0 +1 @@
1
+ import{labelElement as t,markMetadata as e,svg as r,titled as a}from"../../support/Dom.js";import{paddedSector as s,polarPoint as n,roundedSectorPath as i}from"../../support/geometry/SectorGeometry.js";import{formatLabel as o}from"../../support/presentation/Formatting.js";import{tooltipText as c}from"../../support/presentation/Presentation.js";import h from"../LegendRenderer.js";function l({labelX:t,anchor:e,width:r}){return"end"===e?t-12:"start"===e?r-12-t:2*Math.min(t-12,r-12-t)}var u=class{#t;#e;constructor({chart:t,surface:e}){this.#t=t,this.#e=e}render(){const t=function(t){const e=t.options.width,r=t.options.height,a=t.datasets[0].points,s={horizontal:e/2-12-14-44,vertical:r/2-12-14};return Object.freeze({width:e,height:r,values:a,maximum:Math.max(...a.map(t=>t.y),1),center:{x:e/2,y:r/2},maximumRadius:Math.max(8,Math.min(.42*Math.min(e,r),s.horizontal,s.vertical)),slice:2*Math.PI/a.length})}(this.#t);for(const[e,r]of t.values.entries())this.#r(r,e,t)}#r(t,r,n){const i=n.maximumRadius*Math.sqrt(Math.max(0,t.y)/n.maximum),o=-Math.PI/2+r*n.slice,h=s({angles:{start:o,end:o+n.slice},radii:{outer:i,inner:0},padding:{angle:this.#t.options.padAngle,count:n.values.length}}),l=this.#t.options.cornerRadius??4,u=this.#a(n,{radius:i,sector:h,cornerRadius:l}),m=this.#t.options.colors;u.setAttribute("fill",m[r%m.length]),u.classList.add("charts2-polar-area","charts2-mark"),this.#e.append(a(e(u,0,r),c({options:this.#t.options,label:this.#t.labels[r],value:t.y}))),this.#s(r,{start:o,slice:n.slice},n)}#a(t,e){return 1===t.values.length?r("circle",{cx:t.center.x,cy:t.center.y,r:e.radius}):r("path",{d:i({center:t.center,radii:{outer:e.radius,inner:0},angles:{outer:e.sector.outer,inner:e.sector.inner},cornerRadius:e.cornerRadius})})}#s(e,r,a){const s=n({cx:a.center.x,cy:a.center.y,radius:a.maximumRadius+14,angle:r.start+r.slice/2}),i=Math.min(a.width-12,Math.max(12,s.x)),c=Math.min(a.height-12,Math.max(12,s.y)),h=function(t,e){return t<e-2?"end":t>e+2?"start":"middle"}(i,a.center.x);this.#e.append(t({value:o(this.#t.options,this.#t.labels[e],{target:"value-label",index:e}),attributes:{x:i,y:c,class:"charts2-label charts2-polar-label","text-anchor":h},measurement:{maxWidth:Math.max(1,l({labelX:i,anchor:h,width:a.width}))}}))}};function m(t){new u(t).render(),new h(t).render()}export{m as renderPolarAreaChart};
@@ -0,0 +1 @@
1
+ {"version":3,"file":"PolarAreaRendering.js","names":["#chart","#surface","#renderSector","#sectorElement","#renderLabel"],"sources":["../../../src/renderers/composition/PolarAreaRendering.js"],"sourcesContent":["import {\n DEFAULT_SECTOR_CORNER_RADIUS,\n POLAR_LABEL_EDGE_INSET,\n POLAR_LABEL_GAP,\n POLAR_LABEL_MIN_WIDTH,\n} from \"../../support/Constants.js\";\nimport { labelElement, markMetadata, svg, titled } from \"../../support/Dom.js\";\nimport { paddedSector, polarPoint, roundedSectorPath } from \"../../support/geometry/Math.js\";\nimport { formatLabel } from \"../../support/presentation/Formatting.js\";\nimport { tooltipText } from \"../../support/presentation/Presentation.js\";\nimport LegendRenderer from \"../LegendRenderer.js\";\n\nconst MINIMUM_POLAR_RADIUS = 8;\nconst POLAR_RADIUS_RATIO = 0.42;\n\n/**\n * Chooses text alignment from a label's position relative to the radial center.\n *\n * @param {number} labelX - Clamped horizontal label coordinate.\n * @param {number} centerX - Horizontal chart center.\n * @returns {\"start\" | \"middle\" | \"end\"} SVG text anchor for the label.\n */\nfunction radialAnchor(labelX, centerX) {\n if (labelX < centerX - 2) {\n return \"end\";\n }\n\n if (labelX > centerX + 2) {\n return \"start\";\n }\n\n return \"middle\";\n}\n\n/**\n * Calculates horizontal space available to a clamped polar label.\n *\n * @param {object} placement - Label position, alignment, and viewport width.\n * @param {number} placement.labelX - Clamped horizontal label coordinate.\n * @param {\"start\" | \"middle\" | \"end\"} placement.anchor - SVG text anchor.\n * @param {number} placement.width - Total chart width.\n * @returns {number} Positive width budget before the nearest chart edge.\n */\nfunction polarLabelWidth({ labelX, anchor, width }) {\n if (anchor === \"end\") {\n return labelX - POLAR_LABEL_EDGE_INSET;\n }\n\n if (anchor === \"start\") {\n return width - POLAR_LABEL_EDGE_INSET - labelX;\n }\n\n return 2 * Math.min(labelX - POLAR_LABEL_EDGE_INSET, width - POLAR_LABEL_EDGE_INSET - labelX);\n}\n\n/**\n * Resolves the shared scale and viewport geometry of a polar-area render.\n *\n * @param {object} chart - Frozen polar-area chart data and options.\n * @returns {object} Complete radial geometry.\n */\nfunction polarAreaLayout(chart) {\n const width = chart.options.width;\n const height = chart.options.height;\n const values = chart.datasets[0].points;\n\n const radiusLimits = {\n horizontal: width / 2 - POLAR_LABEL_EDGE_INSET - POLAR_LABEL_GAP - POLAR_LABEL_MIN_WIDTH,\n vertical: height / 2 - POLAR_LABEL_EDGE_INSET - POLAR_LABEL_GAP,\n };\n\n return Object.freeze({\n width,\n height,\n values,\n maximum: Math.max(...values.map((point) => point.y), 1),\n center: { x: width / 2, y: height / 2 },\n maximumRadius: Math.max(\n MINIMUM_POLAR_RADIUS,\n Math.min(Math.min(width, height) * POLAR_RADIUS_RATIO, radiusLimits.horizontal, radiusLimits.vertical),\n ),\n slice: (Math.PI * 2) / values.length,\n });\n}\n\n/**\n * Renders equal-angle polar sectors whose radii encode normalized values.\n */\nclass PolarAreaRenderer {\n #chart;\n #surface;\n\n /**\n * Creates a polar-area renderer bound to one frozen render snapshot.\n *\n * @param {object} rendering - Collaborators for one polar-area pass.\n * @param {object} rendering.chart - Frozen polar-area data and options.\n * @param {import(\"../SvgSurface.js\").default} rendering.surface - Owned SVG drawing surface.\n */\n constructor({ chart, surface }) {\n this.#chart = chart;\n this.#surface = surface;\n }\n\n /**\n * Draws sectors and their bounded category labels.\n *\n * @returns {void} Polar-area content is appended to the chart SVG.\n */\n render() {\n const layout = polarAreaLayout(this.#chart);\n\n for (const [\n index,\n point,\n ] of layout.values.entries()) {\n this.#renderSector(point, index, layout);\n }\n }\n\n /**\n * Draws one polar sector and its optional category label.\n *\n * @param {{y: number}} point - Normalized polar value.\n * @param {number} index - Zero-based sector index.\n * @param {object} layout - Shared radial frame and scale.\n * @returns {void} One interactive sector and optional label are appended.\n */\n #renderSector(point, index, layout) {\n const radius = layout.maximumRadius * Math.sqrt(Math.max(0, point.y) / layout.maximum);\n const start = -Math.PI / 2 + index * layout.slice;\n\n const sector = paddedSector({\n angles: { start, end: start + layout.slice },\n radii: { outer: radius, inner: 0 },\n padding: { angle: this.#chart.options.padAngle, count: layout.values.length },\n });\n\n const cornerRadius = this.#chart.options.cornerRadius ?? DEFAULT_SECTOR_CORNER_RADIUS;\n\n const sectorElement = this.#sectorElement(layout, { radius, sector, cornerRadius });\n\n const colors = this.#chart.options.colors;\n sectorElement.setAttribute(\"fill\", colors[index % colors.length]);\n sectorElement.classList.add(\"charts2-polar-area\", \"charts2-mark\");\n this.#surface.append(\n titled(\n markMetadata(sectorElement, 0, index),\n tooltipText({\n options: this.#chart.options,\n label: this.#chart.labels[index],\n value: point.y,\n }),\n ),\n );\n this.#renderLabel(index, { start, slice: layout.slice }, layout);\n }\n\n /**\n * Creates the primitive representing one polar-area value.\n *\n * @param {object} layout - Shared radial frame and scale.\n * @param {object} shape - Sector radius, padded angles, and corner radius.\n * @returns {SVGElement} Circle for a sole value, otherwise a sector path.\n */\n #sectorElement(layout, shape) {\n if (layout.values.length === 1) {\n return svg(\"circle\", { cx: layout.center.x, cy: layout.center.y, r: shape.radius });\n }\n\n return svg(\"path\", {\n d: roundedSectorPath({\n center: layout.center,\n radii: { outer: shape.radius, inner: 0 },\n angles: { outer: shape.sector.outer, inner: shape.sector.inner },\n cornerRadius: shape.cornerRadius,\n }),\n });\n }\n\n /**\n * Places one category label within the radial viewport bounds.\n *\n * @param {number} index - Zero-based sector index.\n * @param {object} angles - Sector start and sweep angles.\n * @param {object} layout - Shared radial frame and viewport.\n * @returns {void} A label is appended only when the category exists.\n */\n #renderLabel(index, angles, layout) {\n const point = polarPoint({\n cx: layout.center.x,\n cy: layout.center.y,\n radius: layout.maximumRadius + POLAR_LABEL_GAP,\n angle: angles.start + angles.slice / 2,\n });\n\n const labelX = Math.min(layout.width - POLAR_LABEL_EDGE_INSET, Math.max(POLAR_LABEL_EDGE_INSET, point.x));\n\n const labelY = Math.min(\n layout.height - POLAR_LABEL_EDGE_INSET,\n Math.max(POLAR_LABEL_EDGE_INSET, point.y),\n );\n\n const anchor = radialAnchor(labelX, layout.center.x);\n this.#surface.append(\n labelElement({\n value: formatLabel(this.#chart.options, this.#chart.labels[index], { target: \"value-label\", index }),\n attributes: {\n x: labelX,\n y: labelY,\n class: \"charts2-label charts2-polar-label\",\n \"text-anchor\": anchor,\n },\n measurement: { maxWidth: Math.max(1, polarLabelWidth({ labelX, anchor, width: layout.width })) },\n }),\n );\n }\n}\n\n/**\n * Renders one polar-area chart and its optional series legend.\n *\n * @param {object} rendering - Frozen chart snapshot and owned SVG surface.\n * @returns {void} Polar-area content is appended to the chart SVG.\n */\nfunction renderPolarAreaChart(rendering) {\n new PolarAreaRenderer(rendering).render();\n new LegendRenderer(rendering).render();\n}\n\nexport { renderPolarAreaChart };\n"],"mappings":"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"}
@@ -0,0 +1 @@
1
+ import{labelElement as t,markMetadata as a,svg as e,titled as r}from"../../support/Dom.js";import{polarPoint as s}from"../../support/geometry/SectorGeometry.js";import{formatLabel as o,formatValue as n}from"../../support/presentation/Formatting.js";import{datasetSummary as i,legendLayout as c}from"../../support/presentation/Presentation.js";import h from"../LegendRenderer.js";function p(t){return Math.abs(t)<1?"middle":t<0?"end":"start"}var d=class{#t;#a;constructor({chart:t,surface:a}){this.#t=t,this.#a=a}render(){const{height:t,width:a}=this.#t.options,e=(o=this.#t.datasets.map(t=>({label:t.name,color:t.color})),!(r=this.#t).options.legend||r.datasets.length<2?0:20+20*c(r.options.width,o).rows);var r,o;const n=t-e,i={x:a/2,y:e+n/2},h={radius:.38*Math.min(a,n),count:this.#t.datasets[0].points.length,maximum:Math.max(...this.#t.datasets.flatMap(t=>t.points.map(t=>t.y)),1)},p=Array.from({length:h.count},(t,a)=>s({cx:i.x,cy:i.y,radius:h.radius,angle:-Math.PI/2+a/h.count*Math.PI*2}));this.#e({frame:p,centerX:i.x,centerY:i.y}),this.#r(i,h),this.#s({frame:p,centerX:i.x,centerY:i.y,width:a})}#e({frame:t,centerX:a,centerY:e}){this.#a.append("polygon",{points:t.map(t=>`${t.x},${t.y}`).join(" "),class:"charts2-grid charts2-radar-frame"});for(const r of t)this.#a.append("line",{x1:a,y1:e,x2:r.x,y2:r.y,class:"charts2-grid"})}#r(t,o){for(const[n,c]of this.#t.datasets.entries()){const h=c.points.map((a,e)=>s({cx:t.x,cy:t.y,radius:o.radius*Math.max(0,a.y)/o.maximum,angle:-Math.PI/2+e/o.count*Math.PI*2})),p=r(a(e("polygon",{points:h.map(t=>`${t.x},${t.y}`).join(" "),fill:c.color,stroke:c.color,"stroke-linejoin":"round","stroke-linecap":"round",opacity:.28,class:"charts2-radar charts2-mark"}),n,0),i(c,this.#t.labels,{options:this.#t.options,datasetIndex:n}));p.dataset.tooltipHeading=c.name,p.dataset.tooltipItems=JSON.stringify(this.#o(c)),this.#a.append(p)}}#o(t){return t.points.map((a,e)=>({name:o(this.#t.options,this.#t.labels[e],{target:"tooltip",datasetIndex:this.#t.datasets.indexOf(t),index:e,point:a}),value:n(this.#t.options,a.y,{target:"tooltip",dataset:t,datasetIndex:this.#t.datasets.indexOf(t),index:e,label:this.#t.labels[e],point:a}),color:t.color}))}#s({frame:a,centerX:e,centerY:r,width:s}){for(const[n,i]of a.slice(0,this.#t.labels.length).entries()){const a=i.x-e,c=i.y-r,h=Math.hypot(a,c);this.#a.append(t({value:o(this.#t.options,this.#t.labels[n],{target:"value-label",index:n}),attributes:{x:i.x+a/h*12,y:i.y+c/h*12,class:"charts2-label","text-anchor":p(a)},measurement:{maxWidth:Math.max(54,.16*s)}}))}}};function l(t){new d(t).render(),new h(t).render()}export{l as renderRadarChart};
@@ -0,0 +1 @@
1
+ {"version":3,"file":"RadarRendering.js","names":["#chart","#surface","#renderFrame","#renderDatasets","#renderLabels","#tooltipItems"],"sources":["../../../src/renderers/composition/RadarRendering.js"],"sourcesContent":["import { labelElement, markMetadata, svg, titled } from \"../../support/Dom.js\";\nimport { polarPoint } from \"../../support/geometry/Math.js\";\nimport { formatLabel, formatValue } from \"../../support/presentation/Formatting.js\";\nimport { datasetSummary, legendLayout } from \"../../support/presentation/Presentation.js\";\nimport LegendRenderer from \"../LegendRenderer.js\";\n\nconst LEGEND_TOP = 20;\nconst LEGEND_ROW_HEIGHT = 20;\nconst RADAR_RADIUS_RATIO = 0.38;\nconst LABEL_OFFSET = 12;\nconst MINIMUM_LABEL_WIDTH = 54;\nconst LABEL_WIDTH_RATIO = 0.16;\n\n/**\n * Resolves the vertical space consumed by an optional radar legend.\n *\n * @param {object} chart - Frozen radar data and options.\n * @param {Array<object>} items - Legend items for the datasets.\n * @returns {number} Top edge of the drawable radar region.\n */\nfunction radarContentTop(chart, items) {\n if (!chart.options.legend || chart.datasets.length < 2) {\n return 0;\n }\n\n return LEGEND_TOP + legendLayout(chart.options.width, items).rows * LEGEND_ROW_HEIGHT;\n}\n\n/**\n * Resolves the text anchor for one radar label direction.\n *\n * @param {number} directionX - Horizontal vector from chart center to frame point.\n * @returns {\"start\" | \"middle\" | \"end\"} SVG text alignment for the label.\n */\nfunction radarAnchor(directionX) {\n if (Math.abs(directionX) < 1) {\n return \"middle\";\n }\n\n return directionX < 0 ? \"end\" : \"start\";\n}\n\n/**\n * Renders comparable datasets as concentric radial axes and closed polygons.\n */\nclass RadarRenderer {\n #chart;\n #surface;\n\n /**\n * Creates a radar renderer bound to one frozen render snapshot.\n *\n * @param {object} rendering - Collaborators for one radar pass.\n * @param {object} rendering.chart - Frozen radar data and options.\n * @param {import(\"../SvgSurface.js\").default} rendering.surface - Owned SVG drawing surface.\n */\n constructor({ chart, surface }) {\n this.#chart = chart;\n this.#surface = surface;\n }\n\n /**\n * Draws the radar grid, datasets, labels, and interaction metadata.\n *\n * @returns {void} Radar content is appended to the chart SVG.\n */\n render() {\n const { height, width } = this.#chart.options;\n\n const legendItems = this.#chart.datasets.map((dataset) => ({\n label: dataset.name,\n color: dataset.color,\n }));\n\n const contentTop = radarContentTop(this.#chart, legendItems);\n const contentHeight = height - contentTop;\n const center = { x: width / 2, y: contentTop + contentHeight / 2 };\n\n const scale = {\n radius: Math.min(width, contentHeight) * RADAR_RADIUS_RATIO,\n count: this.#chart.datasets[0].points.length,\n maximum: Math.max(\n ...this.#chart.datasets.flatMap((dataset) => dataset.points.map((point) => point.y)),\n 1,\n ),\n };\n\n const frame = Array.from({ length: scale.count }, (_, index) =>\n polarPoint({\n cx: center.x,\n cy: center.y,\n radius: scale.radius,\n angle: -Math.PI / 2 + (index / scale.count) * Math.PI * 2,\n }),\n );\n\n this.#renderFrame({ frame, centerX: center.x, centerY: center.y });\n this.#renderDatasets(center, scale);\n this.#renderLabels({ frame, centerX: center.x, centerY: center.y, width });\n }\n\n /**\n * Draws the polygon boundary and center-to-vertex guide lines.\n *\n * @param {object} geometry - Resolved radar frame geometry.\n * @param {Array<{x: number, y: number}>} geometry.frame - Outer radar vertices.\n * @param {number} geometry.centerX - Horizontal radial center.\n * @param {number} geometry.centerY - Vertical radial center.\n * @returns {void} Grid elements are appended to the SVG.\n */\n #renderFrame({ frame, centerX, centerY }) {\n this.#surface.append(\"polygon\", {\n points: frame.map((point) => `${point.x},${point.y}`).join(\" \"),\n class: \"charts2-grid charts2-radar-frame\",\n });\n for (const point of frame) {\n this.#surface.append(\"line\", {\n x1: centerX,\n y1: centerY,\n x2: point.x,\n y2: point.y,\n class: \"charts2-grid\",\n });\n }\n }\n\n /**\n * Draws every normalized dataset as one interactive radar polygon.\n *\n * @param {{x: number, y: number}} center - Shared radial center.\n * @param {object} scale - Radius, maximum value, and axis count.\n * @returns {void} Dataset polygons and tooltip metadata are appended.\n */\n #renderDatasets(center, scale) {\n for (const [\n datasetIndex,\n dataset,\n ] of this.#chart.datasets.entries()) {\n const shape = dataset.points.map((point, index) =>\n polarPoint({\n cx: center.x,\n cy: center.y,\n radius: (scale.radius * Math.max(0, point.y)) / scale.maximum,\n angle: -Math.PI / 2 + (index / scale.count) * Math.PI * 2,\n }),\n );\n\n const polygon = titled(\n markMetadata(\n svg(\"polygon\", {\n points: shape.map((point) => `${point.x},${point.y}`).join(\" \"),\n fill: dataset.color,\n stroke: dataset.color,\n \"stroke-linejoin\": \"round\",\n \"stroke-linecap\": \"round\",\n opacity: 0.28,\n class: \"charts2-radar charts2-mark\",\n }),\n datasetIndex,\n 0,\n ),\n datasetSummary(dataset, this.#chart.labels, { options: this.#chart.options, datasetIndex }),\n );\n\n polygon.dataset.tooltipHeading = dataset.name;\n polygon.dataset.tooltipItems = JSON.stringify(this.#tooltipItems(dataset));\n this.#surface.append(polygon);\n }\n }\n\n /**\n * Formats the category values exposed by a radar dataset tooltip.\n *\n * @param {object} dataset - Normalized radar dataset.\n * @returns {Array<object>} Display-ready tooltip rows.\n */\n #tooltipItems(dataset) {\n return dataset.points.map((point, index) => ({\n name: formatLabel(this.#chart.options, this.#chart.labels[index], {\n target: \"tooltip\",\n datasetIndex: this.#chart.datasets.indexOf(dataset),\n index,\n point,\n }),\n value: formatValue(this.#chart.options, point.y, {\n target: \"tooltip\",\n dataset,\n datasetIndex: this.#chart.datasets.indexOf(dataset),\n index,\n label: this.#chart.labels[index],\n point,\n }),\n color: dataset.color,\n }));\n }\n\n /**\n * Places category labels outside the radar frame when labels are available.\n *\n * @param {object} geometry - Frame and viewport geometry used for label placement.\n * @param {Array<{x: number, y: number}>} geometry.frame - Outer radar vertices.\n * @param {number} geometry.centerX - Horizontal radial center.\n * @param {number} geometry.centerY - Vertical radial center.\n * @param {number} geometry.width - Total chart width.\n * @returns {void} Category labels are appended when present.\n */\n #renderLabels({ frame, centerX, centerY, width }) {\n for (const [\n index,\n point,\n ] of frame.slice(0, this.#chart.labels.length).entries()) {\n const directionX = point.x - centerX;\n const directionY = point.y - centerY;\n const length = Math.hypot(directionX, directionY);\n this.#surface.append(\n labelElement({\n value: formatLabel(this.#chart.options, this.#chart.labels[index], {\n target: \"value-label\",\n index,\n }),\n attributes: {\n x: point.x + (directionX / length) * LABEL_OFFSET,\n y: point.y + (directionY / length) * LABEL_OFFSET,\n class: \"charts2-label\",\n \"text-anchor\": radarAnchor(directionX),\n },\n measurement: { maxWidth: Math.max(MINIMUM_LABEL_WIDTH, width * LABEL_WIDTH_RATIO) },\n }),\n );\n }\n }\n}\n\n/**\n * Renders one radar chart and its optional series legend.\n *\n * @param {object} rendering - Frozen chart snapshot and owned SVG surface.\n * @returns {void} Radar content is appended to the chart SVG.\n */\nfunction renderRadarChart(rendering) {\n new RadarRenderer(rendering).render();\n new LegendRenderer(rendering).render();\n}\n\nexport { renderRadarChart };\n"],"mappings":"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"}
@@ -0,0 +1 @@
1
+ import{HEATMAP_COLORS as t}from"../../support/Constants.js";import{formatNumber as e,markMetadata as a,measuredTextWidth as o,svg as s,titled as r}from"../../support/Dom.js";import{extent as h}from"../../support/geometry/Scale.js";import{formatContext as i,formatterText as n}from"../../support/presentation/Formatting.js";import{intensityLevel as l}from"../../support/presentation/Presentation.js";var c=class{#t;#e;constructor({chart:t,surface:e}){this.#t=t,this.#e=e}render(){const t=this.#a();this.#o(t),this.#s(t)}#a(){const{height:t,width:e}=this.#t.options,a=this.#r(e,t);this.#h(a,t);const o=this.#i(a),s=this.#n();return{height:t,...a,...o,...s}}#r(t,e){return function(t,e,a){const o=function(t){return Math.min(24,Math.max(4,.08*t))}(t),s=Math.max(1,Math.ceil(a.heatmap.length/7)),r=Math.min(7,Math.max(1,a.heatmap.length)),h=s>20&&t<400,i=2*o+16*s+3*(s-1);return Object.freeze({horizontalPadding:o,weeks:s,rows:r,hasWeekInspector:h,layoutWidth:h?Math.max(t,i):t,gridTop:24,gridBottom:e-24-11-12})}(t,e,this.#t)}#h(t,e){this.#e.attribute("data-scrollable",t.hasWeekInspector?"true":"false"),this.#e.attribute("viewBox",`0 0 ${t.layoutWidth} ${e}`),this.#e.styles({width:t.hasWeekInspector?`${t.layoutWidth}px`:"100%",maxWidth:t.hasWeekInspector?"none":"100%"})}#i(t){const e=Math.max(1,t.layoutWidth-2*t.horizontalPadding),a=Math.max(t.rows,t.gridBottom-t.gridTop),o=Math.min(3,Math.max(0,(a-t.rows)/Math.max(1,t.rows-1))),s=Math.min(3,Math.max(0,(e-4*t.weeks)/Math.max(1,t.weeks-1)));return{availableGridHeight:a,rowGap:o,columnGap:s,cellWidth:Math.max(Number.EPSILON,(e-(t.weeks-1)*s)/t.weeks),cellHeight:Math.max(1,(a-(t.rows-1)*o)/t.rows)}}#n(){const e=this.#t.heatmap.map(t=>t.value),[a,o]=h(e),s=this.#t.hasCustomColors?this.#t.options.colors:t;return{colors:s,colorLevel:t=>l(t,[a,o],s.length)}}#o(t){const e=this.#l();for(const[o,h]of this.#t.heatmap.entries()){const i=t.colorLevel(h.value),n=Math.floor(o/7),l=o%7,c=s("rect",{x:t.horizontalPadding+n*(t.cellWidth+t.columnGap),y:t.gridTop+l*(t.cellHeight+t.rowGap),width:t.cellWidth,height:t.cellHeight,rx:Math.min(this.#t.options.radius??2,t.cellWidth/2,t.cellHeight/2),fill:t.colors[i],class:"charts2-heat-cell charts2-mark"}),d=r(a(c,0,o),`${this.#c(h)}: ${this.#d(h,o)}${e}`);this.#e.append(d)}}#s(t){const e=this.#p(t),a=s("g",{class:"charts2-heat-legend","aria-label":"Heatmap intensity: Less to More"}),o=s("text",{x:t.horizontalPadding,y:e.baseline,class:"charts2-legend charts2-heat-legend-less"});o.textContent="Less",a.append(o),this.#m(a,t.colors,e);const r=s("text",{x:e.scaleX+e.scaleWidth+8,y:e.baseline,class:"charts2-legend charts2-heat-legend-more"});r.textContent="More",a.append(r),this.#e.append(a)}#p(t){const e={top:t.gridBottom+12},a={less:o("Less"),more:o("More")},s=t.horizontalPadding+a.less+8,r=this.#u(t,s,a.more),h=r>=5*t.colors.length+2*Math.max(0,t.colors.length-1)?2:0,i=(r-h*(t.colors.length-1))/t.colors.length;return{top:e.top,baseline:e.top+10,scaleX:s,scaleWidth:r,swatchGap:h,swatchWidth:i}}#u(t,e,a){const o=Math.max(t.colors.length,t.layoutWidth-t.horizontalPadding-e-a-8),s=11*t.colors.length+3*Math.max(0,t.colors.length-1);return Math.min(s,o)}#m(t,e,a){for(const[o,r]of e.entries())t.append(s("rect",{x:a.scaleX+o*(a.swatchWidth+a.swatchGap),y:a.top,width:a.swatchWidth,height:11,rx:Math.min(2,a.swatchWidth/2),fill:r,class:"charts2-heat-legend-swatch"}))}#c(t){const e=this.#t.options.tooltipFormatDate;return e?n(e(new Date(t.date)),"Heatmap tooltip date"):t.key}#d(t,a){const o=this.#t.options.tooltipFormatValue;return o?n(o(t.value,i(this.#t.options,"tooltip",{index:a,label:t.key,point:{x:a,y:t.value}})),"Heatmap tooltip value"):e(t.value)}#l(){return this.#t.options.countLabel?` ${this.#t.options.countLabel}`:""}};function d(t){new c(t).render()}export{d as renderHeatmapChart};
@@ -0,0 +1 @@
1
+ {"version":3,"file":"HeatmapRendering.js","names":["#chart","#surface","#layout","#renderCells","#renderLegend","#dimensions","#configureSurface","#cellGeometry","#palette","#countSuffix","#formatDate","#formatValue","#legendGeometry","#appendLegendSwatches","#legendScaleWidth"],"sources":["../../../src/renderers/temporal/HeatmapRendering.js"],"sourcesContent":["import { HEATMAP_COLORS, HEATMAP_COMPACT_WIDTH, HEATMAP_MIN_CELL_WIDTH } from \"../../support/Constants.js\";\nimport { formatNumber, markMetadata, measuredTextWidth, svg, titled } from \"../../support/Dom.js\";\nimport { extent } from \"../../support/geometry/Math.js\";\nimport { formatContext, formatterText } from \"../../support/presentation/Formatting.js\";\nimport { intensityLevel } from \"../../support/presentation/Presentation.js\";\n\nconst GRID_PADDING = 24;\nconst MINIMUM_HORIZONTAL_PADDING = 4;\nconst HORIZONTAL_PADDING_RATIO = 0.08;\nconst GRID_TOP = 24;\nconst PREFERRED_CELL_GAP = 3;\nconst LEGEND_HEIGHT = 11;\nconst LEGEND_GAP = 12;\nconst DAYS_PER_WEEK = 7;\nconst INSPECTOR_WEEK_THRESHOLD = 20;\nconst MINIMUM_COLUMN_CALCULATION_WIDTH = 4;\nconst LEGEND_TOP_GAP = 12;\nconst LEGEND_BASELINE_OFFSET = 10;\nconst LEGEND_LABEL_GAP = 8;\nconst DESIRED_SWATCH_WIDTH = 11;\nconst DESIRED_SWATCH_GAP = 3;\nconst MINIMUM_SWATCH_WIDTH = 5;\nconst MINIMUM_SWATCH_GAP = 2;\n\n/**\n * Calculates responsive horizontal padding for the heatmap grid.\n *\n * @param {number} width - Requested chart width.\n * @returns {number} Bounded horizontal padding.\n */\nfunction heatmapHorizontalPadding(width) {\n return Math.min(GRID_PADDING, Math.max(MINIMUM_HORIZONTAL_PADDING, width * HORIZONTAL_PADDING_RATIO));\n}\n\n/**\n * Resolves viewport and overflow dimensions for one heatmap.\n *\n * @param {number} width - Requested chart width.\n * @param {number} height - Requested chart height.\n * @param {object} chart - Frozen chart snapshot.\n * @returns {object} Complete heatmap dimensions.\n */\nfunction heatmapDimensions(width, height, chart) {\n const horizontalPadding = heatmapHorizontalPadding(width);\n const weeks = Math.max(1, Math.ceil(chart.heatmap.length / DAYS_PER_WEEK));\n const rows = Math.min(DAYS_PER_WEEK, Math.max(1, chart.heatmap.length));\n const hasWeekInspector = weeks > INSPECTOR_WEEK_THRESHOLD && width < HEATMAP_COMPACT_WIDTH;\n\n const minimumScrollableWidth =\n horizontalPadding * 2 + weeks * HEATMAP_MIN_CELL_WIDTH + (weeks - 1) * PREFERRED_CELL_GAP;\n\n return Object.freeze({\n horizontalPadding,\n weeks,\n rows,\n hasWeekInspector,\n layoutWidth: hasWeekInspector ? Math.max(width, minimumScrollableWidth) : width,\n gridTop: GRID_TOP,\n gridBottom: height - GRID_PADDING - LEGEND_HEIGHT - LEGEND_GAP,\n });\n}\n\n/**\n * Renders calendar heatmaps and owns their overflow presentation policy.\n */\nclass HeatmapRenderer {\n #chart;\n #surface;\n\n /**\n * Creates a renderer bound to one frozen render snapshot.\n *\n * @param {object} rendering - Collaborators for one heatmap pass.\n * @param {object} rendering.chart - Frozen heatmap data and options.\n * @param {import(\"../SvgSurface.js\").default} rendering.surface - Owned SVG drawing surface.\n */\n constructor({ chart, surface }) {\n this.#chart = chart;\n this.#surface = surface;\n }\n\n /**\n * Renders daily values as an accessible, horizontally scrollable calendar grid.\n *\n * @returns {void} Heatmap cells, inspectors, and legend are appended to the chart SVG.\n */\n render() {\n const layout = this.#layout();\n this.#renderCells(layout);\n this.#renderLegend(layout);\n }\n\n /**\n * Derives grid dimensions, overflow behavior, and color scaling once per render.\n *\n * @returns {object} Complete immutable layout snapshot shared by heatmap drawing methods.\n */\n #layout() {\n const { height, width } = this.#chart.options;\n const dimensions = this.#dimensions(width, height);\n\n this.#configureSurface(dimensions, height);\n\n const cells = this.#cellGeometry(dimensions);\n const palette = this.#palette();\n\n return {\n height,\n ...dimensions,\n ...cells,\n ...palette,\n };\n }\n\n /**\n * Resolves viewport, grid, and overflow dimensions.\n *\n * @param {number} width - Requested chart width.\n * @param {number} height - Requested chart height.\n * @returns {object} Stable heatmap dimensions.\n */\n #dimensions(width, height) {\n return heatmapDimensions(width, height, this.#chart);\n }\n\n /**\n * Applies horizontal overflow policy to the host and SVG surface.\n *\n * @param {object} dimensions - Resolved viewport dimensions.\n * @param {number} height - Requested chart height.\n * @returns {void} Host class and SVG sizing are updated.\n */\n #configureSurface(dimensions, height) {\n this.#surface.attribute(\"data-scrollable\", dimensions.hasWeekInspector ? \"true\" : \"false\");\n this.#surface.attribute(\"viewBox\", `0 0 ${dimensions.layoutWidth} ${height}`);\n this.#surface.styles({\n width: dimensions.hasWeekInspector ? `${dimensions.layoutWidth}px` : \"100%\",\n maxWidth: dimensions.hasWeekInspector ? \"none\" : \"100%\",\n });\n }\n\n /**\n * Resolves gaps and cell sizes inside the drawable grid.\n *\n * @param {object} dimensions - Resolved viewport dimensions.\n * @returns {object} Row, column, and cell geometry.\n */\n #cellGeometry(dimensions) {\n const availableGridWidth = Math.max(1, dimensions.layoutWidth - dimensions.horizontalPadding * 2);\n const availableGridHeight = Math.max(dimensions.rows, dimensions.gridBottom - dimensions.gridTop);\n\n const rowGap = Math.min(\n PREFERRED_CELL_GAP,\n Math.max(0, (availableGridHeight - dimensions.rows) / Math.max(1, dimensions.rows - 1)),\n );\n\n const columnGap = Math.min(\n PREFERRED_CELL_GAP,\n Math.max(\n 0,\n (availableGridWidth - dimensions.weeks * MINIMUM_COLUMN_CALCULATION_WIDTH) /\n Math.max(1, dimensions.weeks - 1),\n ),\n );\n\n const cellWidth = Math.max(\n Number.EPSILON,\n (availableGridWidth - (dimensions.weeks - 1) * columnGap) / dimensions.weeks,\n );\n\n const cellHeight = Math.max(1, (availableGridHeight - (dimensions.rows - 1) * rowGap) / dimensions.rows);\n\n return {\n availableGridHeight,\n rowGap,\n columnGap,\n cellWidth,\n cellHeight,\n };\n }\n\n /**\n * Resolves the active palette and its value-to-color mapping.\n *\n * @returns {object} Palette and bounded color-level function.\n */\n #palette() {\n const values = this.#chart.heatmap.map((item) => item.value);\n\n const [\n minimum,\n maximum,\n ] = extent(values);\n\n const colors = this.#chart.hasCustomColors ? this.#chart.options.colors : HEATMAP_COLORS;\n\n const colorLevel = (value) =>\n intensityLevel(\n value,\n [\n minimum,\n maximum,\n ],\n colors.length,\n );\n\n return {\n colors,\n colorLevel,\n };\n }\n\n /**\n * Appends visible daily cells and individual interaction metadata when enabled.\n *\n * @param {object} layout - Heatmap dimensions and color scale from `#layout`.\n * @returns {void} Daily heat cells are appended to the chart SVG.\n */\n #renderCells(layout) {\n const suffix = this.#countSuffix();\n\n for (const [\n index,\n item,\n ] of this.#chart.heatmap.entries()) {\n const level = layout.colorLevel(item.value);\n const column = Math.floor(index / DAYS_PER_WEEK);\n const row = index % DAYS_PER_WEEK;\n\n const cell = svg(\"rect\", {\n x: layout.horizontalPadding + column * (layout.cellWidth + layout.columnGap),\n y: layout.gridTop + row * (layout.cellHeight + layout.rowGap),\n width: layout.cellWidth,\n height: layout.cellHeight,\n rx: Math.min(this.#chart.options.radius ?? 2, layout.cellWidth / 2, layout.cellHeight / 2),\n fill: layout.colors[level],\n class: \"charts2-heat-cell charts2-mark\",\n });\n\n const visibleCell = titled(\n markMetadata(cell, 0, index),\n `${this.#formatDate(item)}: ${this.#formatValue(item, index)}${suffix}`,\n );\n\n this.#surface.append(visibleCell);\n }\n }\n\n /**\n * Renders the bounded Less-to-More color legend.\n *\n * @param {object} layout - Heatmap dimensions and palette from `#layout`.\n * @returns {void} The intensity legend is appended to the chart SVG.\n */\n #renderLegend(layout) {\n const geometry = this.#legendGeometry(layout);\n\n const legend = svg(\"g\", {\n class: \"charts2-heat-legend\",\n \"aria-label\": \"Heatmap intensity: Less to More\",\n });\n\n const less = svg(\"text\", {\n x: layout.horizontalPadding,\n y: geometry.baseline,\n class: \"charts2-legend charts2-heat-legend-less\",\n });\n\n less.textContent = \"Less\";\n legend.append(less);\n\n this.#appendLegendSwatches(legend, layout.colors, geometry);\n\n const more = svg(\"text\", {\n x: geometry.scaleX + geometry.scaleWidth + LEGEND_LABEL_GAP,\n y: geometry.baseline,\n class: \"charts2-legend charts2-heat-legend-more\",\n });\n\n more.textContent = \"More\";\n legend.append(more);\n this.#surface.append(legend);\n }\n\n /**\n * Calculates bounded legend label and swatch geometry.\n *\n * @param {object} layout - Heatmap dimensions and palette.\n * @returns {object} Legend baseline, scale bounds, and swatch sizing.\n */\n #legendGeometry(layout) {\n const vertical = { top: layout.gridBottom + LEGEND_TOP_GAP };\n const labelWidths = { less: measuredTextWidth(\"Less\"), more: measuredTextWidth(\"More\") };\n const scaleX = layout.horizontalPadding + labelWidths.less + LEGEND_LABEL_GAP;\n const scaleWidth = this.#legendScaleWidth(layout, scaleX, labelWidths.more);\n\n const minimumGappedWidth =\n layout.colors.length * MINIMUM_SWATCH_WIDTH +\n Math.max(0, layout.colors.length - 1) * MINIMUM_SWATCH_GAP;\n\n const swatchGap = scaleWidth >= minimumGappedWidth ? MINIMUM_SWATCH_GAP : 0;\n const swatchWidth = (scaleWidth - swatchGap * (layout.colors.length - 1)) / layout.colors.length;\n\n return {\n top: vertical.top,\n baseline: vertical.top + LEGEND_BASELINE_OFFSET,\n scaleX,\n scaleWidth,\n swatchGap,\n swatchWidth,\n };\n }\n\n /**\n * Bounds the desired swatch scale between palette and viewport widths.\n *\n * @param {object} layout - Heatmap dimensions and palette.\n * @param {number} scaleX - Horizontal start of the swatch scale.\n * @param {number} moreWidth - Measured width of the trailing label.\n * @returns {number} Drawable width allocated to all swatches.\n */\n #legendScaleWidth(layout, scaleX, moreWidth) {\n const maximumScaleWidth = Math.max(\n layout.colors.length,\n layout.layoutWidth - layout.horizontalPadding - scaleX - moreWidth - LEGEND_LABEL_GAP,\n );\n\n const desiredScaleWidth =\n layout.colors.length * DESIRED_SWATCH_WIDTH +\n Math.max(0, layout.colors.length - 1) * DESIRED_SWATCH_GAP;\n\n return Math.min(desiredScaleWidth, maximumScaleWidth);\n }\n\n /**\n * Appends the ordered palette swatches to the legend group.\n *\n * @param {SVGElement} legend - Parent legend group.\n * @param {string[]} colors - Ordered heatmap palette.\n * @param {object} geometry - Resolved legend geometry.\n * @returns {void} Palette rectangles are appended.\n */\n #appendLegendSwatches(legend, colors, geometry) {\n for (const [\n index,\n color,\n ] of colors.entries()) {\n legend.append(\n svg(\"rect\", {\n x: geometry.scaleX + index * (geometry.swatchWidth + geometry.swatchGap),\n y: geometry.top,\n width: geometry.swatchWidth,\n height: LEGEND_HEIGHT,\n rx: Math.min(2, geometry.swatchWidth / 2),\n fill: color,\n class: \"charts2-heat-legend-swatch\",\n }),\n );\n }\n }\n\n /**\n * Formats one heatmap date key through the optional tooltip hook.\n *\n * @param {object} item - Normalized daily heatmap point.\n * @returns {string} Caller-formatted or canonical date label.\n */\n #formatDate(item) {\n const formatter = this.#chart.options.tooltipFormatDate;\n\n if (!formatter) {\n return item.key;\n }\n\n return formatterText(formatter(new Date(item.date)), \"Heatmap tooltip date\");\n }\n\n /**\n * Formats one heatmap value through the optional tooltip hook.\n *\n * @param {object} item - Normalized daily heatmap point.\n * @param {number} index - Stable point position.\n * @returns {string} Caller-formatted or localized numeric value.\n */\n #formatValue(item, index) {\n const formatter = this.#chart.options.tooltipFormatValue;\n\n if (!formatter) {\n return formatNumber(item.value);\n }\n\n return formatterText(\n formatter(\n item.value,\n formatContext(this.#chart.options, \"tooltip\", {\n index,\n label: item.key,\n point: { x: index, y: item.value },\n }),\n ),\n \"Heatmap tooltip value\",\n );\n }\n\n /**\n * Builds the optional unit suffix shared by heatmap tooltips.\n *\n * @returns {string} Empty text or a leading-space count label.\n */\n #countSuffix() {\n return this.#chart.options.countLabel ? ` ${this.#chart.options.countLabel}` : \"\";\n }\n}\n\n/**\n * Renders one heatmap through its family renderer.\n *\n * @param {object} rendering - Frozen chart snapshot and owned SVG surface.\n * @returns {void} Heatmap content is appended to the chart SVG.\n */\nfunction renderHeatmapChart(rendering) {\n new HeatmapRenderer(rendering).render();\n}\n\nexport { renderHeatmapChart };\n"],"mappings":"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"}
@@ -0,0 +1 @@
1
+ import{measuredTextWidth as t}from"../../support/Dom.js";import{scale as a}from"../../support/geometry/Scale.js";import{formatTimeTick as e,formatTimesheetDate as r,formatTimesheetDuration as i,timeTicks as o}from"../../support/presentation/Time.js";var s=class{#t;#a;#e;#r;#i;constructor(e){const r=function(a){const{height:e,width:r}=a.options,i=function(a){if(!a.options.valueLabels)return 0;const e=(r=a.options.width,Math.max(52,Math.min(160,.36*r)));var r;const i=(o=a.timesheet.tasks,Math.ceil(Math.max(...o.map(a=>t(a.label)))+12));var o;return Math.min(e,Math.max(52,i))}(a),o={left:16+i,right:Math.max(16+i+20,r-16)},s=function(t,a){return{bottom:a-28,rowHeight:(a-28-12)/t.timesheet.tasks.length}}(a,e),h=o.right-o.left,n={start:a.timesheet.start.valueOf(),end:a.timesheet.end.valueOf()},m=Math.max(2,Math.min(7,Math.floor(h/68)+1));return{frame:Object.freeze({width:r,height:e,inset:16,top:12,...o,...s,plotWidth:h,barHeight:Math.max(8,Math.min(18,.5*s.rowHeight)),labelWidth:i,span:n.end-n.start}),values:n,maximumTicks:m}}(e);this.#e=e.options,this.#i=t=>a(t,[r.values.start,r.values.end],[r.frame.left,r.frame.right]),this.#t=e.options.tooltipFormatDate??e.options.formatDate,this.#r=e.options.formatTick??e.options.formatDate,this.#a=e.options.tooltipFormatDuration??e.options.formatDuration,this.ticks=Object.freeze(o(r.values.start,r.values.end,r.maximumTicks)),this.frame=r.frame,Object.freeze(this)}tickAt(t,a){const r=this.#i(t);let i=r,o="middle";return 0===a&&(i+=2,o="start"),a===this.ticks.length-1&&(i-=2,o="end"),{position:r,label:e(t,this.frame.span,this.#r),labelX:i,anchor:o,maxWidth:Math.max(32,this.frame.plotWidth/Math.max(1,this.ticks.length-1)-4)}}taskAt(t,a){const e=this.frame.top+this.frame.rowHeight*a,o=e+this.frame.rowHeight/2,s=this.#i(t.start.valueOf()),h=Math.max(2,this.#i(t.end.valueOf())-s),n=r(t.start,this.#t),m=r(t.end,this.#t),d=i(t.end-t.start,this.#a);return u={top:e,center:o},p={x:s,width:h,radius:Math.min(this.#e.radius??3,h/2,this.frame.barHeight/2)},l={start:n,end:m,duration:d},Object.freeze({rowTop:u.top,centerY:u.center,barX:p.x,barWidth:p.width,radius:p.radius,dateStart:l.start,dateEnd:l.end,duration:l.duration});var u,p,l}};export{s as default};
@@ -0,0 +1 @@
1
+ {"version":3,"file":"TimesheetLayout.js","names":["#options","#x","#dateFormatter","#tickFormatter","#durationFormatter"],"sources":["../../../src/renderers/temporal/TimesheetLayout.js"],"sourcesContent":["import { DEFAULT_BAR_RADIUS } from \"../../support/Constants.js\";\nimport { measuredTextWidth } from \"../../support/Dom.js\";\nimport { scale } from \"../../support/geometry/Math.js\";\nimport {\n formatTimeTick,\n formatTimesheetDate,\n formatTimesheetDuration,\n timeTicks,\n} from \"../../support/presentation/Time.js\";\n\nconst FRAME_INSET = 16;\nconst FRAME_TOP = 12;\nconst AXIS_HEIGHT = 28;\nconst MINIMUM_LABEL_WIDTH = 52;\nconst MAXIMUM_LABEL_WIDTH = 160;\nconst LABEL_WIDTH_RATIO = 0.36;\nconst LABEL_PADDING = 12;\nconst MINIMUM_PLOT_WIDTH = 20;\nconst MINIMUM_BAR_HEIGHT = 8;\nconst MAXIMUM_BAR_HEIGHT = 18;\nconst BAR_HEIGHT_RATIO = 0.5;\nconst MAXIMUM_TICKS = 7;\nconst PIXELS_PER_TICK = 68;\nconst MINIMUM_TICK_LABEL_WIDTH = 32;\nconst TICK_LABEL_GAP = 4;\n\n/**\n * Calculates the maximum width available to task labels.\n *\n * @param {number} width - Requested chart width.\n * @returns {number} Bounded task-label width.\n */\nfunction availableTaskLabelWidth(width) {\n return Math.max(MINIMUM_LABEL_WIDTH, Math.min(MAXIMUM_LABEL_WIDTH, width * LABEL_WIDTH_RATIO));\n}\n\n/**\n * Measures the widest normalized task label with its trailing padding.\n *\n * @param {Array<object>} tasks - Normalized timesheet tasks.\n * @returns {number} Required label-column width.\n */\nfunction measuredTaskLabelWidth(tasks) {\n return Math.ceil(Math.max(...tasks.map((task) => measuredTextWidth(task.label))) + LABEL_PADDING);\n}\n\n/**\n * Calculates vertical timesheet bounds shared by every row.\n *\n * @param {object} chart - Frozen timesheet data and options.\n * @param {number} height - Requested chart height.\n * @returns {object} Bottom edge and row height.\n */\nfunction timesheetVerticalFrame(chart, height) {\n return {\n bottom: height - AXIS_HEIGHT,\n rowHeight: (height - AXIS_HEIGHT - FRAME_TOP) / chart.timesheet.tasks.length,\n };\n}\n\n/**\n * Combines row, bar, and formatted values for one timesheet task.\n *\n * @param {object} row - Row top and vertical center.\n * @param {object} bar - Bar x position, width, and corner radius.\n * @param {object} text - Formatted start, end, and duration values.\n * @returns {object} Complete drawing and tooltip placement.\n */\nfunction taskPlacement(row, bar, text) {\n return Object.freeze({\n rowTop: row.top,\n centerY: row.center,\n barX: bar.x,\n barWidth: bar.width,\n radius: bar.radius,\n dateStart: text.start,\n dateEnd: text.end,\n duration: text.duration,\n });\n}\n\n/**\n * Measures the optional task-label column.\n *\n * @param {object} chart - Frozen timesheet data and options.\n * @returns {number} Width reserved for task labels.\n */\nfunction taskLabelWidth(chart) {\n if (!chart.options.valueLabels) {\n return 0;\n }\n\n const available = availableTaskLabelWidth(chart.options.width);\n const measured = measuredTaskLabelWidth(chart.timesheet.tasks);\n\n return Math.min(available, Math.max(MINIMUM_LABEL_WIDTH, measured));\n}\n\n/**\n * Resolves the frame and scalar endpoints shared by the layout.\n *\n * @param {object} chart - Frozen timesheet data and options.\n * @returns {object} Frame, timestamps, and tick budget.\n */\nfunction timesheetGeometry(chart) {\n const { height, width } = chart.options;\n const labelWidth = taskLabelWidth(chart);\n\n const horizontal = {\n left: FRAME_INSET + labelWidth,\n right: Math.max(FRAME_INSET + labelWidth + MINIMUM_PLOT_WIDTH, width - FRAME_INSET),\n };\n\n const vertical = timesheetVerticalFrame(chart, height);\n const plotWidth = horizontal.right - horizontal.left;\n const values = { start: chart.timesheet.start.valueOf(), end: chart.timesheet.end.valueOf() };\n const maximumTicks = Math.max(2, Math.min(MAXIMUM_TICKS, Math.floor(plotWidth / PIXELS_PER_TICK) + 1));\n\n const frame = Object.freeze({\n width,\n height,\n inset: FRAME_INSET,\n top: FRAME_TOP,\n ...horizontal,\n ...vertical,\n plotWidth,\n barHeight: Math.max(\n MINIMUM_BAR_HEIGHT,\n Math.min(MAXIMUM_BAR_HEIGHT, vertical.rowHeight * BAR_HEIGHT_RATIO),\n ),\n labelWidth,\n span: values.end - values.start,\n });\n\n return {\n frame,\n values,\n maximumTicks,\n };\n}\n\n/**\n * Owns temporal scaling, row geometry, and bounded label placement for a timesheet.\n */\nexport default class TimesheetLayout {\n #dateFormatter;\n #durationFormatter;\n #options;\n #tickFormatter;\n #x;\n\n /**\n * Resolves the complete immutable time-range layout for one render.\n *\n * @param {object} chart - Frozen timesheet data and options.\n */\n constructor(chart) {\n const geometry = timesheetGeometry(chart);\n\n this.#options = chart.options;\n this.#x = (value) =>\n scale(\n value,\n [\n geometry.values.start,\n geometry.values.end,\n ],\n [\n geometry.frame.left,\n geometry.frame.right,\n ],\n );\n this.#dateFormatter = chart.options.tooltipFormatDate ?? chart.options.formatDate;\n this.#tickFormatter = chart.options.formatTick ?? chart.options.formatDate;\n this.#durationFormatter = chart.options.tooltipFormatDuration ?? chart.options.formatDuration;\n this.ticks = Object.freeze(timeTicks(geometry.values.start, geometry.values.end, geometry.maximumTicks));\n this.frame = geometry.frame;\n Object.freeze(this);\n }\n\n /**\n * Presents one temporal tick with bounded label placement.\n *\n * @param {number} tick - Tick timestamp in milliseconds.\n * @param {number} index - Position within the visible tick collection.\n * @returns {{position: number, label: string, labelX: number, anchor: string, maxWidth: number}} Tick drawing values.\n */\n tickAt(tick, index) {\n const position = this.#x(tick);\n const isFirst = index === 0;\n const isLast = index === this.ticks.length - 1;\n let labelX = position;\n let anchor = \"middle\";\n\n if (isFirst) {\n labelX += 2;\n anchor = \"start\";\n }\n\n if (isLast) {\n labelX -= 2;\n anchor = \"end\";\n }\n\n return {\n position,\n label: formatTimeTick(tick, this.frame.span, this.#tickFormatter),\n labelX,\n anchor,\n maxWidth: Math.max(\n MINIMUM_TICK_LABEL_WIDTH,\n this.frame.plotWidth / Math.max(1, this.ticks.length - 1) - TICK_LABEL_GAP,\n ),\n };\n }\n\n /**\n * Presents one normalized task as row, bar, label, and tooltip geometry.\n *\n * @param {object} task - Normalized task record.\n * @param {number} index - Zero-based row position.\n * @returns {object} Complete task drawing and accessible presentation values.\n */\n taskAt(task, index) {\n const rowTop = this.frame.top + this.frame.rowHeight * index;\n const centerY = rowTop + this.frame.rowHeight / 2;\n const barX = this.#x(task.start.valueOf());\n const barWidth = Math.max(2, this.#x(task.end.valueOf()) - barX);\n const dateStart = formatTimesheetDate(task.start, this.#dateFormatter);\n const dateEnd = formatTimesheetDate(task.end, this.#dateFormatter);\n const duration = formatTimesheetDuration(task.end - task.start, this.#durationFormatter);\n\n const radius = Math.min(\n this.#options.radius ?? DEFAULT_BAR_RADIUS,\n barWidth / 2,\n this.frame.barHeight / 2,\n );\n\n return taskPlacement(\n { top: rowTop, center: centerY },\n { x: barX, width: barWidth, radius },\n { start: dateStart, end: dateEnd, duration },\n );\n }\n}\n"],"mappings":"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"}
@@ -0,0 +1 @@
1
+ import{labelElement as t,markMetadata as r,svg as e,titled as a}from"../../support/Dom.js";import s from"./TimesheetLayout.js";var i=class{#t;#r;constructor({chart:t,surface:r}){this.#t=t,this.#r=r}render(){const t=new s(this.#t);if(this.#e(t),this.#a(this.#t.timesheet.tasks,t),this.#t.options.axes){const{bottom:r,left:e,right:a}=t.frame;this.#r.append("line",{x1:e,y1:r,x2:a,y2:r,class:"charts2-axis charts2-x-axis","aria-hidden":"true"})}}#e(r){const{bottom:e,height:a,top:s}=r.frame;for(const[i,h]of r.ticks.entries()){const o=r.tickAt(h,i);this.#t.options.grid&&this.#r.append("line",{x1:o.position,y1:s,x2:o.position,y2:e,class:"charts2-grid charts2-grid-vertical","aria-hidden":"true"}),this.#t.options.valueLabels&&this.#r.append(t({value:o.label,attributes:{x:o.labelX,y:a-8,"text-anchor":o.anchor,class:"charts2-label charts2-timesheet-tick"},measurement:{maxWidth:o.maxWidth}}))}}#a(t,r){for(const[e,a]of t.entries())this.#s(a,e,r)}#s(t,r,e){const s=e.taskAt(t,r);this.#i(s,r,e.frame),this.#h(t,s,e.frame),this.#o(t,s,e.frame.barHeight);const i=this.#n({task:t,row:s,index:r,frame:e.frame}),h=t.group?`, ${t.group}`:"",o=`${t.label}: ${s.dateStart} – ${s.dateEnd}, ${s.duration}${h}`;this.#r.append(a(i,o))}#i(t,r,e){this.#t.options.grid&&0!==r&&this.#r.append("line",{x1:e.inset,y1:t.rowTop,x2:e.right,y2:t.rowTop,class:"charts2-grid charts2-grid-horizontal","aria-hidden":"true"})}#h(r,e,a){this.#t.options.valueLabels&&this.#r.append(t({value:r.label,attributes:{x:a.left-8,y:e.centerY+4,"text-anchor":"end",class:"charts2-label charts2-timesheet-task-label"},measurement:{maxWidth:a.labelWidth-8}}))}#o(t,r,e){this.#r.append("rect",{x:r.barX,y:r.centerY-e/2,width:r.barWidth,height:e,rx:r.radius,fill:t.color,class:"charts2-timesheet-bar charts2-visual-mark","aria-hidden":"true"})}#n(t){const{task:a,row:s,index:i,frame:h}=t,o=r(e("rect",{x:h.left,y:s.rowTop,width:h.plotWidth,height:h.rowHeight,fill:"transparent",class:"charts2-x-hit charts2-timesheet-hit charts2-mark",style:`color:${a.color}`}),0,i);return o.dataset.tooltipHeading=a.label,o.dataset.tooltipItems=JSON.stringify([{name:`${s.dateStart} – ${s.dateEnd}`,value:s.duration,color:a.color}]),o.dataset.tooltipAnchorX=String(s.barX+s.barWidth/2),o.dataset.tooltipAnchorY=String(s.centerY-h.barHeight/2),o}};function h(t){new i(t).render()}export{h as renderTimesheetChart};
@@ -0,0 +1 @@
1
+ {"version":3,"file":"TimesheetRendering.js","names":["#chart","#surface","#renderTicks","#renderTasks","#renderTask","#renderRowGrid","#renderTaskLabel","#renderTaskBar","#taskHit"],"sources":["../../../src/renderers/temporal/TimesheetRendering.js"],"sourcesContent":["import { labelElement, markMetadata, svg, titled } from \"../../support/Dom.js\";\n\nimport TimesheetLayout from \"./TimesheetLayout.js\";\n\nconst AXIS_LABEL_OFFSET = 8;\nconst TASK_LABEL_BASELINE_OFFSET = 4;\nconst TASK_LABEL_GAP = 8;\n\n/**\n * Renders tasks against a shared temporal axis.\n */\nclass TimesheetRenderer {\n #chart;\n #surface;\n\n /**\n * Captures the chart snapshot and its owned SVG surface.\n *\n * @param {object} rendering - Collaborators for one timesheet pass.\n * @param {object} rendering.chart - Frozen timesheet data and options.\n * @param {import(\"../SvgSurface.js\").default} rendering.surface - Owned SVG drawing surface.\n */\n constructor({ chart, surface }) {\n this.#chart = chart;\n this.#surface = surface;\n }\n\n /**\n * Renders time-bounded tasks as grouped rows on a shared temporal axis.\n *\n * @returns {void} Axis ticks, row labels, and interactive task bars are appended.\n */\n render() {\n const layout = new TimesheetLayout(this.#chart);\n this.#renderTicks(layout);\n this.#renderTasks(this.#chart.timesheet.tasks, layout);\n if (this.#chart.options.axes) {\n const { bottom, left, right } = layout.frame;\n this.#surface.append(\"line\", {\n x1: left,\n y1: bottom,\n x2: right,\n y2: bottom,\n class: \"charts2-axis charts2-x-axis\",\n \"aria-hidden\": \"true\",\n });\n }\n }\n\n /**\n * Renders temporal grid lines and their bounded labels.\n *\n * @param {TimesheetLayout} layout - Resolved temporal scale and viewport geometry.\n * @returns {void} Tick lines and labels are appended to the chart SVG.\n */\n #renderTicks(layout) {\n const { bottom, height, top } = layout.frame;\n\n for (const [\n index,\n value,\n ] of layout.ticks.entries()) {\n const tick = layout.tickAt(value, index);\n\n if (this.#chart.options.grid) {\n this.#surface.append(\"line\", {\n x1: tick.position,\n y1: top,\n x2: tick.position,\n y2: bottom,\n class: \"charts2-grid charts2-grid-vertical\",\n \"aria-hidden\": \"true\",\n });\n }\n\n if (this.#chart.options.valueLabels) {\n this.#surface.append(\n labelElement({\n value: tick.label,\n attributes: {\n x: tick.labelX,\n y: height - AXIS_LABEL_OFFSET,\n \"text-anchor\": tick.anchor,\n class: \"charts2-label charts2-timesheet-tick\",\n },\n measurement: { maxWidth: tick.maxWidth },\n }),\n );\n }\n }\n }\n\n /**\n * Renders task rows, labels, bars, and full-row interaction targets.\n *\n * @param {Array<object>} tasks - Ordered normalized task records.\n * @param {TimesheetLayout} layout - Resolved temporal and row geometry.\n * @returns {void} Task rows are appended to the chart SVG.\n */\n #renderTasks(tasks, layout) {\n for (const [\n index,\n task,\n ] of tasks.entries()) {\n this.#renderTask(task, index, layout);\n }\n }\n\n /**\n * Renders one complete task row.\n *\n * @param {object} task - Normalized task record.\n * @param {number} index - Zero-based row position.\n * @param {TimesheetLayout} layout - Resolved temporal and row geometry.\n * @returns {void} Row visuals and interaction target are appended.\n */\n #renderTask(task, index, layout) {\n const row = layout.taskAt(task, index);\n\n this.#renderRowGrid(row, index, layout.frame);\n this.#renderTaskLabel(task, row, layout.frame);\n this.#renderTaskBar(task, row, layout.frame.barHeight);\n\n const hit = this.#taskHit({ task, row, index, frame: layout.frame });\n const group = task.group ? `, ${task.group}` : \"\";\n const title = `${task.label}: ${row.dateStart} – ${row.dateEnd}, ${row.duration}${group}`;\n\n this.#surface.append(titled(hit, title));\n }\n\n /**\n * Draws the separator above a non-first task row when grid lines are enabled.\n *\n * @param {object} row - Resolved row geometry.\n * @param {number} index - Zero-based task position.\n * @param {object} frame - Shared timesheet frame.\n * @returns {void} An optional horizontal line is appended.\n */\n #renderRowGrid(row, index, frame) {\n if (!this.#chart.options.grid || index === 0) {\n return;\n }\n\n this.#surface.append(\"line\", {\n x1: frame.inset,\n y1: row.rowTop,\n x2: frame.right,\n y2: row.rowTop,\n class: \"charts2-grid charts2-grid-horizontal\",\n \"aria-hidden\": \"true\",\n });\n }\n\n /**\n * Draws the optional task label beside its row.\n *\n * @param {object} task - Normalized task record.\n * @param {object} row - Resolved row geometry.\n * @param {object} frame - Shared timesheet frame.\n * @returns {void} A bounded label is appended when enabled.\n */\n #renderTaskLabel(task, row, frame) {\n if (!this.#chart.options.valueLabels) {\n return;\n }\n\n this.#surface.append(\n labelElement({\n value: task.label,\n attributes: {\n x: frame.left - TASK_LABEL_GAP,\n y: row.centerY + TASK_LABEL_BASELINE_OFFSET,\n \"text-anchor\": \"end\",\n class: \"charts2-label charts2-timesheet-task-label\",\n },\n measurement: { maxWidth: frame.labelWidth - TASK_LABEL_GAP },\n }),\n );\n }\n\n /**\n * Draws the visible duration bar for one task.\n *\n * @param {object} task - Normalized task record.\n * @param {object} row - Resolved row geometry.\n * @param {number} barHeight - Shared visible bar height.\n * @returns {void} One decorative bar is appended.\n */\n #renderTaskBar(task, row, barHeight) {\n this.#surface.append(\"rect\", {\n x: row.barX,\n y: row.centerY - barHeight / 2,\n width: row.barWidth,\n height: barHeight,\n rx: row.radius,\n fill: task.color,\n class: \"charts2-timesheet-bar charts2-visual-mark\",\n \"aria-hidden\": \"true\",\n });\n }\n\n /**\n * Builds the full-row interaction target and tooltip metadata.\n *\n * @param {object} state - Task, row, index, and shared frame.\n * @returns {SVGElement} Transparent interactive row element.\n */\n #taskHit(state) {\n const { task, row, index, frame } = state;\n\n const hit = markMetadata(\n svg(\"rect\", {\n x: frame.left,\n y: row.rowTop,\n width: frame.plotWidth,\n height: frame.rowHeight,\n fill: \"transparent\",\n class: \"charts2-x-hit charts2-timesheet-hit charts2-mark\",\n style: `color:${task.color}`,\n }),\n 0,\n index,\n );\n\n hit.dataset.tooltipHeading = task.label;\n hit.dataset.tooltipItems = JSON.stringify([\n { name: `${row.dateStart} – ${row.dateEnd}`, value: row.duration, color: task.color },\n ]);\n hit.dataset.tooltipAnchorX = String(row.barX + row.barWidth / 2);\n hit.dataset.tooltipAnchorY = String(row.centerY - frame.barHeight / 2);\n\n return hit;\n }\n}\n\n/**\n * Renders one timesheet through its family renderer.\n *\n * @param {object} rendering - Frozen chart snapshot and owned SVG surface.\n * @returns {void} Timesheet content is appended to the chart SVG.\n */\nfunction renderTimesheetChart(rendering) {\n new TimesheetRenderer(rendering).render();\n}\n\nexport { renderTimesheetChart };\n"],"mappings":"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"}
@@ -0,0 +1 @@
1
+ var e="http://www.w3.org/2000/svg",t=Object.freeze({LINE:"line",BAR:"bar",SCATTER:"scatter",AXIS_MIXED:"mixed",BUBBLE:"bubble",PIE:"pie",DONUT:"donut",PERCENTAGE:"percentage",RADAR:"radar",POLAR_AREA:"polar-area",HEATMAP:"heatmap",TIMESHEET:"timesheet"}),E=Object.freeze({HORIZONTAL:"horizontal",VERTICAL:"vertical"}),r=Object.freeze({LEFT:"left",RIGHT:"right"}),A=Object.freeze(Object.values(E)),a=Object.freeze(Object.values(r)),i=Object.freeze(["#007AFF","#AF52DE","#FF3B30","#FF9500","#248A3D","#5856D6","#008C95"]),c=Object.freeze(["#E5E5EA","#B7E4C7","#74C69D","#40916C","#1B6B47"]),b=Object.freeze([t.LINE,t.BAR,t.SCATTER,t.AXIS_MIXED,t.BUBBLE]),m=Object.freeze([t.PIE,t.DONUT,t.PERCENTAGE]),O=Object.freeze([...b,...m,t.RADAR,t.POLAR_AREA,t.HEATMAP,t.TIMESHEET]),T=1.5,o=.001,R=36e5,n=24*R,I=Object.freeze([R,3*R,6*R,12*R,n,2*n,7*n,14*n,30*n,90*n,365*n]),f=new Intl.NumberFormat(void 0,{notation:"compact",maximumFractionDigits:1}),j=new Intl.NumberFormat(void 0,{maximumSignificantDigits:3}),D=new Intl.NumberFormat(void 0,{maximumFractionDigits:2});export{m as AGGREGATION_TYPES,b as CARTESIAN_TYPES,A as CHART_ORIENTATIONS,f as COMPACT_NUMBER_FORMATTER,E as ChartOrientation,t as ChartType,n as DAY,i as DEFAULT_COLORS,T as DEFAULT_PAD_ANGLE,c as HEATMAP_COLORS,R as HOUR,o as MIN_SECTOR_SWEEP,j as SMALL_NUMBER_FORMATTER,D as STANDARD_NUMBER_FORMATTER,e as SVG_NS,I as TIME_TICK_STEPS,O as TYPES,r as YAxisPosition,a as Y_AXIS_POSITIONS};
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Constants.js","names":[],"sources":["../../src/support/Constants.js"],"sourcesContent":["const SVG_NS = \"http://www.w3.org/2000/svg\";\n\nconst ChartType = Object.freeze({\n LINE: \"line\",\n BAR: \"bar\",\n SCATTER: \"scatter\",\n AXIS_MIXED: \"mixed\",\n BUBBLE: \"bubble\",\n PIE: \"pie\",\n DONUT: \"donut\",\n PERCENTAGE: \"percentage\",\n RADAR: \"radar\",\n POLAR_AREA: \"polar-area\",\n HEATMAP: \"heatmap\",\n TIMESHEET: \"timesheet\",\n});\n\nconst ChartOrientation = Object.freeze({\n HORIZONTAL: \"horizontal\",\n VERTICAL: \"vertical\",\n});\n\nconst YAxisPosition = Object.freeze({\n LEFT: \"left\",\n RIGHT: \"right\",\n});\n\nconst CHART_ORIENTATIONS = Object.freeze(Object.values(ChartOrientation));\nconst Y_AXIS_POSITIONS = Object.freeze(Object.values(YAxisPosition));\n\nconst DEFAULT_COLORS = Object.freeze([\n \"#007AFF\",\n \"#AF52DE\",\n \"#FF3B30\",\n \"#FF9500\",\n \"#248A3D\",\n \"#5856D6\",\n \"#008C95\",\n]);\n\nconst HEATMAP_COLORS = Object.freeze([\n \"#E5E5EA\",\n \"#B7E4C7\",\n \"#74C69D\",\n \"#40916C\",\n \"#1B6B47\",\n]);\n\nconst CARTESIAN_TYPES = Object.freeze([\n ChartType.LINE,\n ChartType.BAR,\n ChartType.SCATTER,\n ChartType.AXIS_MIXED,\n ChartType.BUBBLE,\n]);\n\nconst AGGREGATION_TYPES = Object.freeze([\n ChartType.PIE,\n ChartType.DONUT,\n ChartType.PERCENTAGE,\n]);\n\nconst TYPES = Object.freeze([\n ...CARTESIAN_TYPES,\n ...AGGREGATION_TYPES,\n ChartType.RADAR,\n ChartType.POLAR_AREA,\n ChartType.HEATMAP,\n ChartType.TIMESHEET,\n]);\n\nconst MAJOR_GRID_DIVISIONS = 4;\nconst MAX_INDIVIDUAL_LINE_POINTS = 40;\nconst MAX_X_INSPECTOR_POINTS = 64;\nconst HORIZONTAL_LABEL_EDGE_INSET = 4;\nconst HORIZONTAL_LABEL_GAP = 4;\nconst VALUE_LABEL_GAP = 5;\nconst SERIES_SWATCH_DIAMETER = 8;\nconst LEGEND_LABEL_GAP = 8;\nconst LEGEND_ROW_HEIGHT = 20;\nconst AGGREGATION_INSET = 16;\nconst AGGREGATION_LEGEND_BASELINE_INSET = 18;\nconst AGGREGATION_LEGEND_GAP = 20;\nconst MULTILINE_LABEL_HEIGHT = 13;\nconst POLAR_LABEL_EDGE_INSET = 12;\nconst POLAR_LABEL_GAP = 14;\nconst POLAR_LABEL_MIN_WIDTH = 44;\nconst DEFAULT_PAD_ANGLE = 1.5;\nconst DEFAULT_BAR_RADIUS = 3;\nconst DEFAULT_SECTOR_CORNER_RADIUS = 4;\nconst DEFAULT_PERCENTAGE_RADIUS = 6;\nconst MIN_SECTOR_SWEEP = 0.001;\nconst HEATMAP_COMPACT_WIDTH = 400;\nconst HEATMAP_MIN_CELL_WIDTH = 16;\nconst HOUR = 60 * 60 * 1000;\nconst DAY = 24 * HOUR;\n\nconst TIME_TICK_STEPS = Object.freeze([\n HOUR,\n 3 * HOUR,\n 6 * HOUR,\n 12 * HOUR,\n DAY,\n 2 * DAY,\n 7 * DAY,\n 14 * DAY,\n 30 * DAY,\n 90 * DAY,\n 365 * DAY,\n]);\n\nconst COMPACT_NUMBER_FORMATTER = new Intl.NumberFormat(undefined, {\n notation: \"compact\",\n maximumFractionDigits: 1,\n});\n\nconst SMALL_NUMBER_FORMATTER = new Intl.NumberFormat(undefined, { maximumSignificantDigits: 3 });\nconst STANDARD_NUMBER_FORMATTER = new Intl.NumberFormat(undefined, { maximumFractionDigits: 2 });\n\nexport {\n SVG_NS,\n ChartType,\n ChartOrientation,\n YAxisPosition,\n CHART_ORIENTATIONS,\n Y_AXIS_POSITIONS,\n DEFAULT_COLORS,\n HEATMAP_COLORS,\n CARTESIAN_TYPES,\n AGGREGATION_TYPES,\n TYPES,\n MAJOR_GRID_DIVISIONS,\n MAX_INDIVIDUAL_LINE_POINTS,\n MAX_X_INSPECTOR_POINTS,\n HORIZONTAL_LABEL_EDGE_INSET,\n HORIZONTAL_LABEL_GAP,\n VALUE_LABEL_GAP,\n SERIES_SWATCH_DIAMETER,\n LEGEND_LABEL_GAP,\n LEGEND_ROW_HEIGHT,\n AGGREGATION_INSET,\n AGGREGATION_LEGEND_BASELINE_INSET,\n AGGREGATION_LEGEND_GAP,\n MULTILINE_LABEL_HEIGHT,\n POLAR_LABEL_EDGE_INSET,\n POLAR_LABEL_GAP,\n POLAR_LABEL_MIN_WIDTH,\n DEFAULT_PAD_ANGLE,\n DEFAULT_BAR_RADIUS,\n DEFAULT_SECTOR_CORNER_RADIUS,\n DEFAULT_PERCENTAGE_RADIUS,\n MIN_SECTOR_SWEEP,\n HEATMAP_COMPACT_WIDTH,\n HEATMAP_MIN_CELL_WIDTH,\n HOUR,\n DAY,\n TIME_TICK_STEPS,\n COMPACT_NUMBER_FORMATTER,\n SMALL_NUMBER_FORMATTER,\n STANDARD_NUMBER_FORMATTER,\n};\n"],"mappings":"AAAA,IAAM,EAAS,6BAET,EAAY,OAAO,OAAO,CAC9B,KAAM,OACN,IAAK,MACL,QAAS,UACT,WAAY,QACZ,OAAQ,SACR,IAAK,MACL,MAAO,QACP,WAAY,aACZ,MAAO,QACP,WAAY,aACZ,QAAS,UACT,UAAW,cAGP,EAAmB,OAAO,OAAO,CACrC,WAAY,aACZ,SAAU,aAGN,EAAgB,OAAO,OAAO,CAClC,KAAM,OACN,MAAO,UAGH,EAAqB,OAAO,OAAO,OAAO,OAAO,IACjD,EAAmB,OAAO,OAAO,OAAO,OAAO,IAE/C,EAAiB,OAAO,OAAO,CACnC,UACA,UACA,UACA,UACA,UACA,UACA,YAGI,EAAiB,OAAO,OAAO,CACnC,UACA,UACA,UACA,UACA,YAGI,EAAkB,OAAO,OAAO,CACpC,EAAU,KACV,EAAU,IACV,EAAU,QACV,EAAU,WACV,EAAU,SAGN,EAAoB,OAAO,OAAO,CACtC,EAAU,IACV,EAAU,MACV,EAAU,aAGN,EAAQ,OAAO,OAAO,IACvB,KACA,EACH,EAAU,MACV,EAAU,WACV,EAAU,QACV,EAAU,YAmBN,EAAoB,IAIpB,EAAmB,KAGnB,EAAO,KACP,EAAM,GAAK,EAEX,EAAkB,OAAO,OAAO,CACpC,EACA,EAAI,EACJ,EAAI,EACJ,GAAK,EACL,EACA,EAAI,EACJ,EAAI,EACJ,GAAK,EACL,GAAK,EACL,GAAK,EACL,IAAM,IAGF,EAA2B,IAAI,KAAK,kBAAa,EAAW,CAChE,SAAU,UACV,sBAAuB,IAGnB,EAAyB,IAAI,KAAK,kBAAa,EAAW,CAAE,yBAA0B,IACtF,EAA4B,IAAI,KAAK,kBAAa,EAAW,CAAE,sBAAuB"}
@@ -0,0 +1 @@
1
+ import{COMPACT_NUMBER_FORMATTER as t,SMALL_NUMBER_FORMATTER as n,STANDARD_NUMBER_FORMATTER as e,SVG_NS as r}from"./Constants.js";var i={context:null};function o(t,n){const e=t.map(t=>m(t));return 1e4*e.reduce((t,e)=>t+Math.max(0,e-n),0)+Math.max(...e)-Math.min(...e)}function a(t,n,e){let r=[t.join(" ")],i=1/0;for(let a=1;a<t.length;a+=1)for(let s=2===n?t.length:a+1;s<=t.length;s+=1){const c=[t.slice(0,a).join(" "),t.slice(a,s).join(" ")];3===n&&c.push(t.slice(s).join(" "));const u=o(c,e);c.every(Boolean)&&u<i&&(r=c,i=u)}return r}function s(t,n={}){const e=document.createElementNS(r,t);for(const[r,i]of Object.entries(n))e.setAttribute(r,String(i));return e}function c(t,n){const e=s("title");return e.textContent=n,t.append(e),Object.assign(t.dataset,{tooltip:n}),t}function u(r){const i=Math.abs(r);return i>=1e4?t.format(r):i>0&&i<.01?n.format(r):e.format(r)}function l(t,n,e=11){const r=String(t);if(m(r,e)<=n)return r;let i=1,o=r.length;for(;i<o;){const t=Math.ceil((i+o)/2);m(`${r.slice(0,t).trimEnd()}…`,e)<=n?i=t:o=t-1}return`${r.slice(0,i).trimEnd()}…`}function f({value:t,attributes:n,measurement:e,originalValue:r=t}){const i=s("text",n),o=l(t,e.maxWidth,e.fontSize??11);if(i.textContent=o,o!==String(t)){const n=s("title");n.textContent=String(t),i.append(n)}return String(r)===String(t)&&o===String(t)||i.setAttribute("aria-label",String(r)),i}function m(t,n=11){const e=(i.context??=document.createElement("canvas").getContext("2d"),i.context);return e.font=`${n}px -apple-system, BlinkMacSystemFont, "SF Pro Text", "Segoe UI", sans-serif`,e.measureText(String(t)).width}function g(t){return m(t)}function p({value:t,attributes:n,maxWidth:e,originalValue:r=t}){const i=Array.isArray(t)?t.map(t=>t.trim()):function(t,n){const e=t.trim();if(m(e)<=n)return[e];const r=e.split(/\s+/u);if(r.length<2)return[e];for(let i=2;i<=Math.min(3,r.length);i+=1){const t=a(r,i,n);if(t.every(t=>m(t)<=n))return t}return a(r,Math.min(3,r.length),n)}(String(t),e),o=i.join(" "),c=i.map(t=>l(t,e)),u=s("text",{...n,class:`${n.class} charts2-multiline-label`});for(const[a,l]of c.entries()){const t=s("tspan",{x:n.x,dy:0===a?-13*(c.length-1)/2:13});t.textContent=l,u.append(t)}if(c.some((t,n)=>t!==i[n])){const t=s("title");t.textContent=o,u.append(t)}return(Array.isArray(t)||String(r)!==o||c.some((t,n)=>t!==i[n]))&&u.setAttribute("aria-label",String(r)),u}function d(t,n,e){return Object.assign(t.dataset,{datasetIndex:String(n),pointIndex:String(e)}),t}function h(t){const n="string"==typeof t?document.querySelector(t):t;if(!(n instanceof Element))throw new TypeError("Chart parent must be an element or a valid selector");return n}function x(t,n=640){const e=t.getBoundingClientRect().width;return Number.isFinite(e)&&e>0?e:n}export{u as formatNumber,f as labelElement,d as markMetadata,x as measureParentWidth,g as measuredLegendTextWidth,m as measuredTextWidth,h as resolveParent,s as svg,c as titled,l as truncateText,p as wrappedLabelElement};
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Dom.js","names":[],"sources":["../../src/support/Dom.js"],"sourcesContent":["import {\n SVG_NS,\n MULTILINE_LABEL_HEIGHT,\n COMPACT_NUMBER_FORMATTER,\n SMALL_NUMBER_FORMATTER,\n STANDARD_NUMBER_FORMATTER,\n} from \"./Constants.js\";\n\nconst COMPACT_NUMBER_THRESHOLD = 10_000;\nconst SMALL_NUMBER_THRESHOLD = 0.01;\nconst DEFAULT_LABEL_FONT_SIZE = 11;\nconst BALANCE_OVERFLOW_WEIGHT = 10_000;\nconst MINIMUM_BALANCED_LABEL_LINES = 2;\nconst MAXIMUM_BALANCED_LABEL_LINES = 3;\nconst measurementSurface = { context: null };\n\n/**\n * Scores a contiguous line partition by overflow first and raggedness second.\n *\n * @param {string[]} lines - Candidate text lines.\n * @param {number} maxWidth - Available width for every line.\n * @returns {number} Lower scores represent a better balanced partition.\n */\nfunction lineBalanceScore(lines, maxWidth) {\n const widths = lines.map((line) => measuredTextWidth(line));\n const overflow = widths.reduce((sum, width) => sum + Math.max(0, width - maxWidth), 0);\n const widest = Math.max(...widths);\n const narrowest = Math.min(...widths);\n\n return overflow * BALANCE_OVERFLOW_WEIGHT + widest - narrowest;\n}\n\n/**\n * Finds the best two- or three-line contiguous partition for a word sequence.\n *\n * @param {string[]} words - Ordered label words.\n * @param {number} lineCount - Requested line count.\n * @param {number} maxWidth - Available width for every line.\n * @returns {string[]} Best balanced lines for the requested count.\n */\nfunction balancedPartition(words, lineCount, maxWidth) {\n let best = [\n words.join(\" \"),\n ];\n let bestScore = Infinity;\n\n for (let first = 1; first < words.length; first += 1) {\n const lastStart = lineCount === MINIMUM_BALANCED_LABEL_LINES ? words.length : first + 1;\n\n for (let second = lastStart; second <= words.length; second += 1) {\n const lines = [\n words.slice(0, first).join(\" \"),\n words.slice(first, second).join(\" \"),\n ];\n\n if (lineCount === MAXIMUM_BALANCED_LABEL_LINES) {\n lines.push(words.slice(second).join(\" \"));\n }\n\n const score = lineBalanceScore(lines, maxWidth);\n\n if (lines.every(Boolean) && score < bestScore) {\n best = lines;\n bestScore = score;\n }\n }\n }\n\n return best;\n}\n\n/**\n * Balances an ordinary phrase into the fewest lines that fit its width budget.\n *\n * @param {string} value - Plain category label.\n * @param {number} maxWidth - Available width for every line.\n * @returns {string[]} One to three deterministic display lines.\n */\nfunction balancedTextLines(value, maxWidth) {\n const text = value.trim();\n\n if (measuredTextWidth(text) <= maxWidth) {\n return [\n text,\n ];\n }\n\n const words = text.split(/\\s+/u);\n\n if (words.length < 2) {\n return [\n text,\n ];\n }\n\n for (\n let lineCount = MINIMUM_BALANCED_LABEL_LINES;\n lineCount <= Math.min(MAXIMUM_BALANCED_LABEL_LINES, words.length);\n lineCount += 1\n ) {\n const lines = balancedPartition(words, lineCount, maxWidth);\n\n if (lines.every((line) => measuredTextWidth(line) <= maxWidth)) {\n return lines;\n }\n }\n\n return balancedPartition(words, Math.min(MAXIMUM_BALANCED_LABEL_LINES, words.length), maxWidth);\n}\n\n/**\n * Creates an SVG element and serializes its initial attributes.\n *\n * @param {string} name - SVG tag name to create within the SVG namespace.\n * @param {Record<string, string | number>} [attributes={}] - Initial attributes applied to the element.\n * @returns {SVGElement} Newly created, detached SVG element.\n */\nfunction svg(name, attributes = {}) {\n const element = document.createElementNS(SVG_NS, name);\n\n for (const [\n key,\n value,\n ] of Object.entries(attributes)) {\n element.setAttribute(key, String(value));\n }\n\n return element;\n}\n\n/**\n * Adds both a native SVG title and tooltip metadata to an element.\n *\n * @param {SVGElement} element - SVG node that should expose supplementary text.\n * @param {string} text - Complete accessible and tooltip description.\n * @returns {SVGElement} The original element after enrichment.\n */\nfunction titled(element, text) {\n const title = svg(\"title\");\n title.textContent = text;\n element.append(title);\n Object.assign(element.dataset, { tooltip: text });\n\n return element;\n}\n\n/**\n * Formats chart numbers compactly while preserving useful small decimals.\n *\n * @param {number} value - Finite numeric value to present to a user.\n * @returns {string} Locale-aware compact, precise, or standard representation.\n */\nfunction formatNumber(value) {\n const absolute = Math.abs(value);\n\n if (absolute >= COMPACT_NUMBER_THRESHOLD) {\n return COMPACT_NUMBER_FORMATTER.format(value);\n }\n\n if (absolute > 0 && absolute < SMALL_NUMBER_THRESHOLD) {\n return SMALL_NUMBER_FORMATTER.format(value);\n }\n\n return STANDARD_NUMBER_FORMATTER.format(value);\n}\n\n/**\n * Truncates text to a measured pixel budget and adds an ellipsis when needed.\n *\n * @param {unknown} value - Value converted to text before measurement.\n * @param {number} maxWidth - Maximum permitted rendered width in pixels.\n * @param {number} [fontSize=11] - Font size used by the measurement context.\n * @returns {string} Original text or the longest fitting ellipsized prefix.\n */\nfunction truncateText(value, maxWidth, fontSize = 11) {\n const text = String(value);\n\n if (measuredTextWidth(text, fontSize) <= maxWidth) {\n return text;\n }\n\n let low = 1;\n let high = text.length;\n\n while (low < high) {\n const middle = Math.ceil((low + high) / 2);\n const candidate = `${text.slice(0, middle).trimEnd()}…`;\n\n if (measuredTextWidth(candidate, fontSize) <= maxWidth) {\n low = middle;\n\n continue;\n }\n\n high = middle - 1;\n }\n\n return `${text.slice(0, low).trimEnd()}…`;\n}\n\n/**\n * Creates an SVG text label with truncation and an accessible full-value fallback.\n *\n * @param {object} specification - Content and presentation values for one label.\n * @param {unknown} specification.value - Display value rendered into the text node.\n * @param {Record<string, string | number>} specification.attributes - SVG positioning and presentation attributes.\n * @param {object} specification.measurement - Width and typography used for truncation.\n * @param {unknown} [specification.originalValue=specification.value] - Unformatted value exposed to assistive technology.\n * @returns {SVGElement} Detached SVG text element ready for insertion.\n */\nfunction labelElement({ value, attributes, measurement, originalValue = value }) {\n const element = svg(\"text\", attributes);\n const visible = truncateText(value, measurement.maxWidth, measurement.fontSize ?? DEFAULT_LABEL_FONT_SIZE);\n element.textContent = visible;\n if (visible !== String(value)) {\n const title = svg(\"title\");\n title.textContent = String(value);\n element.append(title);\n }\n\n if (String(originalValue) !== String(value) || visible !== String(value)) {\n element.setAttribute(\"aria-label\", String(originalValue));\n }\n\n return element;\n}\n\n/**\n * Lazily creates the process-local canvas context shared by text measurements.\n *\n * @returns {CanvasRenderingContext2D} Detached measurement context.\n */\nfunction textMeasurementContext() {\n measurementSurface.context ??= document.createElement(\"canvas\").getContext(\"2d\");\n\n return measurementSurface.context;\n}\n\n/**\n * Measures text using the same platform font stack as chart labels.\n *\n * @param {unknown} value - Value converted to text before measurement.\n * @param {number} [fontSize=11] - Font size used by the canvas context.\n * @returns {number} Measured text width in CSS pixels.\n */\nfunction measuredTextWidth(value, fontSize = 11) {\n const context = textMeasurementContext();\n context.font = `${fontSize}px -apple-system, BlinkMacSystemFont, \"SF Pro Text\", \"Segoe UI\", sans-serif`;\n\n return context.measureText(String(value)).width;\n}\n\n/**\n * Measures legend text with the legend's default typography.\n *\n * @param {unknown} value - Legend label converted to text before measurement.\n * @returns {number} Measured legend-label width in CSS pixels.\n */\nfunction measuredLegendTextWidth(value) {\n return measuredTextWidth(value);\n}\n\n/**\n * Creates a vertically centered multi-line SVG label with accessible source text.\n *\n * @param {object} specification - Content and presentation values for one multi-line label.\n * @param {string | string[]} specification.value - One or more requested display lines.\n * @param {Record<string, string | number>} specification.attributes - SVG positioning and presentation attributes.\n * @param {number} specification.maxWidth - Maximum permitted width for each rendered line.\n * @param {unknown} [specification.originalValue=specification.value] - Unformatted value exposed to assistive technology.\n * @returns {SVGElement} Detached SVG text element containing positioned tspan children.\n */\nfunction wrappedLabelElement({ value, attributes, maxWidth, originalValue = value }) {\n const lines = Array.isArray(value)\n ? value.map((line) => line.trim())\n : balancedTextLines(String(value), maxWidth);\n\n const text = lines.join(\" \");\n\n const visibleLines = lines.map((line) => truncateText(line, maxWidth));\n const element = svg(\"text\", { ...attributes, class: `${attributes.class} charts2-multiline-label` });\n\n for (const [\n index,\n line,\n ] of visibleLines.entries()) {\n const tspan = svg(\"tspan\", {\n x: attributes.x,\n dy: index === 0 ? (-MULTILINE_LABEL_HEIGHT * (visibleLines.length - 1)) / 2 : MULTILINE_LABEL_HEIGHT,\n });\n\n tspan.textContent = line;\n element.append(tspan);\n }\n\n if (visibleLines.some((line, index) => line !== lines[index])) {\n const title = svg(\"title\");\n title.textContent = text;\n element.append(title);\n }\n\n if (\n Array.isArray(value) ||\n String(originalValue) !== text ||\n visibleLines.some((line, index) => line !== lines[index])\n ) {\n element.setAttribute(\"aria-label\", String(originalValue));\n }\n\n return element;\n}\n\n/**\n * Associates an SVG mark with its source series and point positions.\n *\n * @param {SVGElement} element - Rendered mark that receives dataset metadata.\n * @param {number} datasetIndex - Zero-based index of the source dataset.\n * @param {number} pointIndex - Zero-based index within the source dataset.\n * @returns {SVGElement} The original mark after metadata assignment.\n */\nfunction markMetadata(element, datasetIndex, pointIndex) {\n Object.assign(element.dataset, {\n datasetIndex: String(datasetIndex),\n pointIndex: String(pointIndex),\n });\n\n return element;\n}\n\n/**\n * Resolves a caller-supplied selector or validates an existing host element.\n *\n * @param {string | Element} parent - CSS selector or concrete chart host.\n * @returns {Element} Valid DOM element that can own generated chart markup.\n * @throws {TypeError} When a selector has no match or the value is not an element.\n */\nfunction resolveParent(parent) {\n const element = typeof parent === \"string\" ? document.querySelector(parent) : parent;\n\n if (!(element instanceof Element)) {\n throw new TypeError(\"Chart parent must be an element or a valid selector\");\n }\n\n return element;\n}\n\n/**\n * Measures an element and supplies a deterministic width in non-layout environments.\n *\n * @param {Element} parent - Chart host whose bounding box determines layout width.\n * @param {number} [fallback=640] - Width used for hidden elements and DOM test environments.\n * @returns {number} Positive finite layout width in CSS pixels.\n */\nfunction measureParentWidth(parent, fallback = 640) {\n const width = parent.getBoundingClientRect().width;\n\n if (Number.isFinite(width) && width > 0) {\n return width;\n }\n\n return fallback;\n}\n\nexport {\n svg,\n titled,\n formatNumber,\n truncateText,\n labelElement,\n measuredTextWidth,\n measuredLegendTextWidth,\n wrappedLabelElement,\n markMetadata,\n resolveParent,\n measureParentWidth,\n};\n"],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ import{AGGREGATION_TYPES as e,ChartType as t,DEFAULT_COLORS as r}from"./Constants.js";import{isBoolean as n,isChoice as o,isNonEmptyText as a,isNumberAtLeast as i,isOpacity as s,isRecord as u,unknownKey as f}from"./Validation.js";var c=/* @__PURE__ */new Set([4,7]),p=["name","values","color","opacity","formatValue"],m=[...p,"gradient","smooth","dots","dotSize","line","area","strokeWidth"],d=[...p,"radius"],h=/* @__PURE__ */new Set([t.LINE,t.BAR,t.SCATTER,t.AXIS_MIXED,t.BUBBLE]),l=class{#e;#t=/* @__PURE__ */new Map;#r=0;constructor(e){this.#e=e}colorFor(e){const t=this.#n(e.group);return e.color??this.#e[t%this.#e.length]}#n(e){if(e&&this.#t.has(e))return this.#t.get(e);const t=this.#r;return this.#r+=1,e&&this.#t.set(e,t),t}};function y(e,t){if(!Number.isFinite(e))throw new TypeError(`${t} must be a finite number`);return e}function w(e,t){if("number"==typeof e)return{x:t,y:y(e,"Point")};if(!u(e))throw new TypeError("Each point must be a number or an object");return{x:y(e.x??t,"Point x"),y:y(e.y,"Point y"),r:y(e.r??5,"Point radius")}}function T(e,f=r,c){if(!e||!Array.isArray(e.datasets)||0===e.datasets.length)throw new TypeError("Chart data requires at least one dataset");return e.datasets.map((e,r)=>{if(function(e,r){if(!u(e))throw new TypeError("Each dataset must be an object");const f=r===t.AXIS_MIXED?e.chartType:r;if(r===t.AXIS_MIXED&&!o(f,[t.LINE,t.BAR,t.SCATTER]))throw new TypeError("Mixed dataset chartType must be line, bar, or scatter");const c=function(e){return e===t.LINE?m:e===t.BAR?d:p}(f);$(e,r===t.AXIS_MIXED?[...c,"chartType"]:c,"dataset"),function(e,t){if(function(e){const t=void 0!==e.name,r=a(e.name);if(t&&!r)throw new TypeError("Dataset name must be a non-empty string");if(void 0!==e.formatValue&&"function"!=typeof e.formatValue)throw new TypeError("Dataset formatValue must be a function")}(e),function(e){const t=void 0!==e.opacity,r=s(e.opacity);if(t&&!r)throw new TypeError("Dataset opacity must be from 0 through 1");for(const o of["smooth","dots","line","area"])if(void 0!==e[o]&&!n(e[o]))throw new TypeError(`Dataset ${o} must be a boolean`);for(const n of["dotSize","strokeWidth","radius"])if(void 0!==e[n]&&!i(e[n],0))throw new TypeError(`Dataset ${n} must be a non-negative finite number`)}(e),function(e){if(void 0!==e&&"boolean"!=typeof e){if(!u(e))throw new TypeError("Dataset gradient must be a boolean or object");$(e,["fromOpacity","toOpacity"],"gradient");for(const t of Object.values(e))if(!s(t))throw new TypeError("Gradient opacity must be from 0 through 1")}}(e.gradient),Array.isArray(e.values))for(const[r,n]of e.values.entries())E(n,t,r)}(e,f)}(e,c),!e||!Array.isArray(e.values)||0===e.values.length)throw new TypeError("Each dataset requires a non-empty array of values");return function(e,t){return Object.freeze({name:e.name??`Series ${t+1}`,identityName:e.name,color:e.color,chartType:e.chartType??e.type,opacity:e.opacity,formatValue:e.formatValue,gradient:e.gradient,smooth:e.smooth,dots:e.dots,dotSize:e.dotSize,line:e.line,area:e.area,strokeWidth:e.strokeWidth,radius:e.radius,points:e.values.map((e,t)=>w(e,t))})}({...e,color:e.color??f[r%f.length]},r)})}function b(e,t){if(!u(t))throw new TypeError("Chart data must be an object");$(t,h.has(e)?["labels","datasets","markers","regions"]:["labels","datasets"],"chart data")}function E(e,r,n){if(r!==t.SCATTER||!Number.isFinite(e))if(o(r,[t.SCATTER,t.BUBBLE]))!function(e,r){if(!u(e))throw new TypeError(`${r} points must be objects with finite coordinates`);if($(e,r===t.BUBBLE?["x","y","r"]:["x","y"],`${r} point`),!Number.isFinite(e.x)||!Number.isFinite(e.y))throw new TypeError(`${r} points must provide finite x and y`);if(r===t.BUBBLE&&!i(e.r,0))throw new TypeError("Bubble points must provide a finite non-negative r")}(e,r);else if(!Number.isFinite(e))throw new TypeError(`Dataset value ${n+1} must be finite`)}function v(e={}){$(e,["start","end","points"],"heatmap data");const t=e.points;if(!u(t)||0===Object.keys(t).length)throw new TypeError("Heatmap points must contain at least one entry");const r=Object.entries(t).map(([e,t])=>{y(t,"Heatmap value");const r=function(e){if(function(e){const t=Number(e);if(!Number.isFinite(t)||e.trim()!==e||""===e)return!1;const r=(e.startsWith("-")?e.slice(1):e).split(".");return r.length<=2&&r.every(e=>""!==e&&[...e].every(e=>e>="0"&&e<="9"))}(e)){const t=Number(e);return!Number.isFinite(t)||t<=1e5?/* @__PURE__ */new Date(NaN):/* @__PURE__ */new Date(1e3*t)}return N(e)?/* @__PURE__ */new Date(`${e}T00:00:00Z`):/* @__PURE__ */new Date(NaN)}(e);if(Number.isNaN(r.valueOf()))throw new TypeError(`Invalid heatmap date: ${e}`);return{date:r,key:r.toISOString().slice(0,10),value:t}}).toSorted((e,t)=>e.date-t.date);return function(e,t){if(void 0===e.start&&void 0===e.end)return;if(void 0===e.start||void 0===e.end)throw new TypeError("Heatmap range requires both start and end");const r=g(e.start,"Heatmap range start"),n=g(e.end,"Heatmap range end");if(r>n)throw new TypeError("Heatmap range end cannot precede start");if(t[0].date<r||t.at(-1).date>n)throw new TypeError("Heatmap range must contain every point")}(e,r),r}function g(e,t){const r=A(e,t);/* @__PURE__ */
2
+ return new Date(`${r.toISOString().slice(0,10)}T00:00:00Z`)}function A(e,t){const r=function(e,t){if("string"!=typeof e)return e;if(N(e))return`${e}T00:00:00Z`;if(!function(e){if(!e.includes("T"))return!1;if(e.toUpperCase().endsWith("Z"))return!0;const t=e.slice(-6),r=t[0],n=`${t.slice(1,3)}${t.slice(4)}`;return["+","-"].includes(r)&&":"===t[3]&&[...n].every(e=>e>="0"&&e<="9")}(e))throw new TypeError(`${t} date-time string must include a timezone offset or Z`);return e}(e,t),n=e instanceof Date?new Date(e.valueOf()):new Date(r);if(Number.isNaN(n.valueOf()))throw new TypeError(`${t} must be a valid date`);return n}function N(e){return 10===e.length&&"-"===e[4]&&"-"===e[7]&&[...e].every((e,t)=>c.has(t)||e>="0"&&e<="9")}function $(e,t,r){const n=f(e,t);if(n)throw new TypeError(`Unsupported ${r} key: ${n}`)}function k(e={},t=r){if($(e,["start","end","tasks"],"timesheet data"),!Array.isArray(e.tasks)||0===e.tasks.length)throw new TypeError("Timesheet data requires a non-empty tasks array");const n=new l(t),o=e.tasks.map((e,t)=>function(e,t,r){if(!u(e))throw new TypeError("Each timesheet task must be an object");if($(e,["label","start","end","group","color"],"timesheet task"),!a(e.label))throw new TypeError("Timesheet task label must be a non-empty string");if(void 0!==e.group&&!a(e.group))throw new TypeError("Timesheet task group must be a non-empty string");const n=A(e.start,`Task ${t+1} start`),o=A(e.end,`Task ${t+1} end`);if(o<=n)throw new TypeError("Timesheet task end must be after start");return{label:e.label,start:n,end:o,group:e.group,color:r.colorFor(e)}}(e,t,n)),i=new Date(Math.min(...o.map(e=>e.start.valueOf()))),s=new Date(Math.max(...o.map(e=>e.end.valueOf()))),f=void 0===e.start?i:A(e.start,"Timesheet start"),c=void 0===e.end?s:A(e.end,"Timesheet end");if(c<=f)throw new TypeError("Timesheet end must be after start");if(f>i||c<s)throw new TypeError("Timesheet bounds must contain every task");return{start:f,end:c,tasks:o}}function D(r,n,o){if(!Array.isArray(o))throw new TypeError("Chart labels must be an array");if(n.length>1&&n.some(e=>void 0===e.identityName))throw new TypeError("An unnamed dataset is valid only for a single-series chart");if((![t.SCATTER,t.BUBBLE].includes(r)||!o.every(e=>"number"==typeof e))&&n.some(e=>e.points.length!==o.length))throw new TypeError("Chart labels length must match every dataset");if([...e,t.POLAR_AREA].includes(r)&&1!==n.length)throw new TypeError(`${r} requires exactly one dataset`);[...e,t.POLAR_AREA,t.RADAR].includes(r)&&function(r,n){const o=n.flatMap(e=>e.points.map(e=>e.y));if(o.some(e=>e<0))throw new TypeError(`${r} values must be non-negative`);if([...e,t.POLAR_AREA].includes(r)&&o.every(e=>0===e))throw new TypeError(`${r} requires at least one positive value`)}(r,n)}export{T as normalizeDatasets,A as normalizeDate,v as normalizeHeatmapData,w as normalizePoint,k as normalizeTimesheetData,y as requireFiniteNumber,D as validateChartData,$ as validateObjectKeys,b as validateSeriesScene};
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Normalize.js","names":["#colors","#indexFor","#groups","#next"],"sources":["../../src/support/Normalize.js"],"sourcesContent":["import { AGGREGATION_TYPES, ChartType, DEFAULT_COLORS } from \"./Constants.js\";\nimport {\n isBoolean,\n isChoice,\n isNonEmptyText,\n isNumberAtLeast,\n isOpacity,\n isRecord,\n unknownKey,\n} from \"./Validation.js\";\n\nconst DEFAULT_POINT_RADIUS = 5;\nconst ISO_DATE_LENGTH = 10;\nconst MILLISECONDS_PER_SECOND = 1000;\nconst UNIX_SECONDS_THRESHOLD = 100_000;\nconst YEAR_LENGTH = 4;\nconst YEAR_MONTH_LENGTH = 7;\n\nconst DATE_SEPARATOR_INDICES = new Set([\n YEAR_LENGTH,\n YEAR_MONTH_LENGTH,\n]);\n\nconst TIMEZONE_OFFSET_LENGTH = 6;\nconst TIMEZONE_HOUR_END = 3;\nconst TIMEZONE_MINUTE_START = 4;\n\nconst DATASET_BASE_KEYS = [\n \"name\",\n \"values\",\n \"color\",\n \"opacity\",\n \"formatValue\",\n];\n\nconst DATASET_LINE_KEYS = [\n ...DATASET_BASE_KEYS,\n \"gradient\",\n \"smooth\",\n \"dots\",\n \"dotSize\",\n \"line\",\n \"area\",\n \"strokeWidth\",\n];\n\nconst DATASET_BAR_KEYS = [\n ...DATASET_BASE_KEYS,\n \"radius\",\n];\n\nconst CARTESIAN_SERIES_TYPES = new Set([\n ChartType.LINE,\n ChartType.BAR,\n ChartType.SCATTER,\n ChartType.AXIS_MIXED,\n ChartType.BUBBLE,\n]);\n\n/**\n * Creates one normalized series while retaining local presentation.\n *\n * @param {object} dataset - Raw dataset input.\n * @param {number} datasetIndex - Stable palette position.\n * @returns {object} Immutable-shape renderer record.\n */\nfunction normalizedDataset(dataset, datasetIndex) {\n return Object.freeze({\n name: dataset.name ?? `Series ${datasetIndex + 1}`,\n identityName: dataset.name,\n color: dataset.color,\n chartType: dataset.chartType ?? dataset.type,\n opacity: dataset.opacity,\n formatValue: dataset.formatValue,\n gradient: dataset.gradient,\n smooth: dataset.smooth,\n dots: dataset.dots,\n dotSize: dataset.dotSize,\n line: dataset.line,\n area: dataset.area,\n strokeWidth: dataset.strokeWidth,\n radius: dataset.radius,\n points: dataset.values.map((point, pointIndex) => normalizePoint(point, pointIndex)),\n });\n}\n\n/**\n * Assigns one encounter-ordered palette stream across grouped and ungrouped tasks.\n */\nclass TimesheetPalette {\n #colors;\n #groups = new Map();\n #next = 0;\n\n /**\n * Captures the effective chart palette.\n *\n * @param {readonly string[]} colors - Cyclic categorical colors.\n */\n constructor(colors) {\n this.#colors = colors;\n }\n\n /**\n * Resolves an explicit task color or its stable categorical color.\n *\n * @param {object} task - Validated task input.\n * @returns {string} Effective task color.\n */\n colorFor(task) {\n const index = this.#indexFor(task.group);\n\n return task.color ?? this.#colors[index % this.#colors.length];\n }\n\n /**\n * Resolves and advances the palette key stream.\n *\n * @param {string | undefined} group - Optional stable group key.\n * @returns {number} Palette position.\n */\n #indexFor(group) {\n if (group && this.#groups.has(group)) {\n return this.#groups.get(group);\n }\n\n const index = this.#next;\n this.#next += 1;\n if (group) {\n this.#groups.set(group, index);\n }\n\n return index;\n }\n}\n\n/**\n * Verifies that a value is safe to use in geometry calculations.\n *\n * @param {unknown} value - Candidate numeric value from caller-controlled chart data.\n * @param {string} name - Human-readable field name included in validation errors.\n * @returns {number} Validated finite number.\n * @throws {TypeError} When the candidate is not a finite number.\n */\nfunction requireFiniteNumber(value, name) {\n if (!Number.isFinite(value)) {\n throw new TypeError(`${name} must be a finite number`);\n }\n\n return value;\n}\n\n/**\n * Converts a scalar or coordinate object into the chart's canonical point shape.\n *\n * @param {number | {x?: number, y: number, r?: number}} point - Source point supplied by a dataset.\n * @param {number} index - Zero-based position used as the default x coordinate.\n * @returns {{x: number, y: number, r?: number}} Normalized point with validated numeric coordinates.\n * @throws {TypeError} When the point shape or one of its numeric fields is invalid.\n */\nfunction normalizePoint(point, index) {\n if (typeof point === \"number\") {\n return {\n x: index,\n y: requireFiniteNumber(point, \"Point\"),\n };\n }\n\n if (!isRecord(point)) {\n throw new TypeError(\"Each point must be a number or an object\");\n }\n\n return {\n x: requireFiniteNumber(point.x ?? index, \"Point x\"),\n y: requireFiniteNumber(point.y, \"Point y\"),\n r: requireFiniteNumber(point.r ?? DEFAULT_POINT_RADIUS, \"Point radius\"),\n };\n}\n\n/**\n * Normalizes series names, colors, chart overrides, and values for rendering.\n *\n * @param {{datasets?: Array<object>}} data - Dataset container from chart options.\n * @param {readonly string[]} [colors=DEFAULT_COLORS] - Effective categorical palette.\n * @param {string} type - Immutable chart family.\n * @returns {Array<object>} Non-empty collection of canonical datasets and points.\n * @throws {TypeError} When datasets or their values are missing or malformed.\n */\nfunction normalizeDatasets(data, colors = DEFAULT_COLORS, type) {\n if (!data || !Array.isArray(data.datasets) || data.datasets.length === 0) {\n throw new TypeError(\"Chart data requires at least one dataset\");\n }\n\n return data.datasets.map((dataset, datasetIndex) => {\n validateDatasetInput(dataset, type);\n if (!dataset || !Array.isArray(dataset.values) || dataset.values.length === 0) {\n throw new TypeError(\"Each dataset requires a non-empty array of values\");\n }\n\n return normalizedDataset(\n { ...dataset, color: dataset.color ?? colors[datasetIndex % colors.length] },\n datasetIndex,\n );\n });\n}\n\n/**\n * Rejects unknown scene keys before any renderer-facing normalization occurs.\n *\n * @param {string} type - Immutable chart family.\n * @param {unknown} data - Candidate complete series scene.\n * @returns {void} Exhaustive public records pass unchanged.\n */\nfunction validateSeriesScene(type, data) {\n if (!isRecord(data)) {\n throw new TypeError(\"Chart data must be an object\");\n }\n\n const allowed = CARTESIAN_SERIES_TYPES.has(type)\n ? [\n \"labels\",\n \"datasets\",\n \"markers\",\n \"regions\",\n ]\n : [\n \"labels\",\n \"datasets\",\n ];\n\n validateObjectKeys(data, allowed, \"chart data\");\n}\n\n/**\n * Validates one dataset record using the exhaustive grammar for its family.\n *\n * @param {unknown} dataset - Candidate dataset object.\n * @param {string} type - Owning chart type.\n * @returns {void} Supported keys and independently decidable values pass.\n */\nfunction validateDatasetInput(dataset, type) {\n if (!isRecord(dataset)) {\n throw new TypeError(\"Each dataset must be an object\");\n }\n\n const subtype = type === ChartType.AXIS_MIXED ? dataset.chartType : type;\n\n if (\n type === ChartType.AXIS_MIXED &&\n !isChoice(subtype, [\n ChartType.LINE,\n ChartType.BAR,\n ChartType.SCATTER,\n ])\n ) {\n throw new TypeError(\"Mixed dataset chartType must be line, bar, or scatter\");\n }\n\n const localKeys = datasetKeysFor(subtype);\n\n const allowed =\n type === ChartType.AXIS_MIXED\n ? [\n ...localKeys,\n \"chartType\",\n ]\n : localKeys;\n\n validateObjectKeys(dataset, allowed, \"dataset\");\n validateDatasetProperties(dataset, subtype);\n}\n\n/**\n * Selects exhaustive keys for one concrete dataset subtype.\n *\n * @param {string} type - Concrete dataset family.\n * @returns {string[]} Allowed dataset keys.\n */\nfunction datasetKeysFor(type) {\n if (type === ChartType.LINE) {\n return DATASET_LINE_KEYS;\n }\n\n if (type === ChartType.BAR) {\n return DATASET_BAR_KEYS;\n }\n\n return DATASET_BASE_KEYS;\n}\n\n/**\n * Validates common and family-specific dataset properties.\n *\n * @param {object} dataset - Exhaustive dataset record.\n * @param {string} type - Concrete dataset family.\n * @returns {void} Presentation and point values satisfy public invariants.\n */\nfunction validateDatasetProperties(dataset, type) {\n validateDatasetIdentity(dataset);\n validateDatasetPresentation(dataset);\n validateDatasetGradient(dataset.gradient);\n\n if (Array.isArray(dataset.values)) {\n for (const [\n index,\n value,\n ] of dataset.values.entries()) {\n validatePublicPoint(value, type, index);\n }\n }\n}\n\n/**\n * Validates optional dataset identity and formatter fields.\n *\n * @param {object} dataset - Candidate dataset input.\n * @returns {void} Identity fields are either absent or valid.\n */\nfunction validateDatasetIdentity(dataset) {\n const hasName = dataset.name !== undefined;\n const isValidName = isNonEmptyText(dataset.name);\n\n if (hasName && !isValidName) {\n throw new TypeError(\"Dataset name must be a non-empty string\");\n }\n\n if (dataset.formatValue !== undefined && typeof dataset.formatValue !== \"function\") {\n throw new TypeError(\"Dataset formatValue must be a function\");\n }\n}\n\n/**\n * Validates optional dataset geometry and visibility fields.\n *\n * @param {object} dataset - Candidate dataset input.\n * @returns {void} Presentation fields are either absent or valid.\n */\nfunction validateDatasetPresentation(dataset) {\n const hasOpacity = dataset.opacity !== undefined;\n const isValidOpacity = isOpacity(dataset.opacity);\n\n if (hasOpacity && !isValidOpacity) {\n throw new TypeError(\"Dataset opacity must be from 0 through 1\");\n }\n\n for (const property of [\n \"smooth\",\n \"dots\",\n \"line\",\n \"area\",\n ]) {\n if (dataset[property] !== undefined && !isBoolean(dataset[property])) {\n throw new TypeError(`Dataset ${property} must be a boolean`);\n }\n }\n\n for (const property of [\n \"dotSize\",\n \"strokeWidth\",\n \"radius\",\n ]) {\n if (dataset[property] !== undefined && !isNumberAtLeast(dataset[property], 0)) {\n throw new TypeError(`Dataset ${property} must be a non-negative finite number`);\n }\n }\n}\n\n/**\n * Validates dataset-local gradient switches and endpoints.\n *\n * @param {unknown} gradient - Optional gradient configuration.\n * @returns {void} Supported gradient input passes unchanged.\n */\nfunction validateDatasetGradient(gradient) {\n if (gradient === undefined || typeof gradient === \"boolean\") {\n return;\n }\n\n if (!isRecord(gradient)) {\n throw new TypeError(\"Dataset gradient must be a boolean or object\");\n }\n\n validateObjectKeys(\n gradient,\n [\n \"fromOpacity\",\n \"toOpacity\",\n ],\n \"gradient\",\n );\n for (const value of Object.values(gradient)) {\n if (!isOpacity(value)) {\n throw new TypeError(\"Gradient opacity must be from 0 through 1\");\n }\n }\n}\n\n/**\n * Rejects extra point keys and invalid family-specific coordinate values.\n *\n * @param {unknown} value - Candidate scalar or point.\n * @param {string} type - Concrete dataset type.\n * @param {number} index - Scalar scatter shorthand position.\n * @returns {void} Point input can be normalized without ambiguity.\n */\nfunction validatePublicPoint(value, type, index) {\n if (type === ChartType.SCATTER && Number.isFinite(value)) {\n return;\n }\n\n const isPointSeries = isChoice(type, [\n ChartType.SCATTER,\n ChartType.BUBBLE,\n ]);\n\n if (!isPointSeries) {\n if (!Number.isFinite(value)) {\n throw new TypeError(`Dataset value ${index + 1} must be finite`);\n }\n\n return;\n }\n\n validateCoordinatePoint(value, type);\n}\n\n/**\n * Validates one explicit scatter or bubble coordinate object.\n *\n * @param {unknown} value - Candidate point object.\n * @param {string} type - Scatter or bubble family.\n * @returns {void} Exhaustive finite coordinates pass unchanged.\n */\nfunction validateCoordinatePoint(value, type) {\n if (!isRecord(value)) {\n throw new TypeError(`${type} points must be objects with finite coordinates`);\n }\n\n const pointKeys =\n type === ChartType.BUBBLE\n ? [\n \"x\",\n \"y\",\n \"r\",\n ]\n : [\n \"x\",\n \"y\",\n ];\n\n validateObjectKeys(value, pointKeys, `${type} point`);\n if (!Number.isFinite(value.x) || !Number.isFinite(value.y)) {\n throw new TypeError(`${type} points must provide finite x and y`);\n }\n\n if (type === ChartType.BUBBLE && !isNumberAtLeast(value.r, 0)) {\n throw new TypeError(\"Bubble points must provide a finite non-negative r\");\n }\n}\n\n/**\n * Converts keyed heatmap values into sorted, date-addressable entries.\n *\n * @param {{start?: string | Date, end?: string | Date, points?: Record<string, number>}} [data={}] - Heatmap dates and values.\n * @returns {Array<{date: Date, key: string, value: number}>} Chronologically sorted daily entries.\n * @throws {TypeError} When bounds, dates, or values are invalid.\n */\nfunction normalizeHeatmapData(data = {}) {\n validateObjectKeys(\n data,\n [\n \"start\",\n \"end\",\n \"points\",\n ],\n \"heatmap data\",\n );\n const source = data.points;\n\n if (!isRecord(source) || Object.keys(source).length === 0) {\n throw new TypeError(\"Heatmap points must contain at least one entry\");\n }\n\n const entries = Object.entries(source).map(\n ([\n key,\n value,\n ]) => {\n requireFiniteNumber(value, \"Heatmap value\");\n const date = heatmapDate(key);\n\n if (Number.isNaN(date.valueOf())) {\n throw new TypeError(`Invalid heatmap date: ${key}`);\n }\n\n return {\n date,\n key: date.toISOString().slice(0, ISO_DATE_LENGTH),\n value,\n };\n },\n );\n\n const sorted = entries.toSorted((left, right) => left.date - right.date);\n validateHeatmapRange(data, sorted);\n\n return sorted;\n}\n\n/**\n * Validates an optional explicit heatmap range against normalized UTC days.\n *\n * @param {object} data - Heatmap data containing optional bounds.\n * @param {Array<object>} entries - Sorted normalized point entries.\n * @returns {void} Ascending containing ranges pass unchanged.\n */\nfunction validateHeatmapRange(data, entries) {\n if (data.start === undefined && data.end === undefined) {\n return;\n }\n\n if (data.start === undefined || data.end === undefined) {\n throw new TypeError(\"Heatmap range requires both start and end\");\n }\n\n const start = heatmapBound(data.start, \"Heatmap range start\");\n const end = heatmapBound(data.end, \"Heatmap range end\");\n\n if (start > end) {\n throw new TypeError(\"Heatmap range end cannot precede start\");\n }\n\n if (entries[0].date < start || entries.at(-1).date > end) {\n throw new TypeError(\"Heatmap range must contain every point\");\n }\n}\n\n/**\n * Converts a heatmap range bound to its UTC calendar day.\n *\n * @param {unknown} value - Date or transitional date-only string.\n * @param {string} name - Public concept named in failures.\n * @returns {Date} Midnight UTC calendar bound.\n */\nfunction heatmapBound(value, name) {\n const date = normalizeDate(value, name);\n\n return new Date(`${date.toISOString().slice(0, ISO_DATE_LENGTH)}T00:00:00Z`);\n}\n\n/**\n * Interprets a heatmap key as Unix seconds or an ISO calendar date.\n *\n * @param {string} key - Caller-supplied heatmap date key.\n * @returns {Date} Candidate date for subsequent validity checking.\n */\nfunction heatmapDate(key) {\n if (isNumericKey(key)) {\n const numeric = Number(key);\n\n if (!Number.isFinite(numeric) || numeric <= UNIX_SECONDS_THRESHOLD) {\n return new Date(NaN);\n }\n\n return new Date(numeric * MILLISECONDS_PER_SECOND);\n }\n\n if (!isDateOnly(key)) {\n return new Date(NaN);\n }\n\n return new Date(`${key}T00:00:00Z`);\n}\n\n/**\n * Creates an independent valid Date from a supported date-like value.\n *\n * @param {string | number | Date} value - Date-like value accepted by the platform Date constructor.\n * @param {string} name - Human-readable field name included in validation errors.\n * @returns {Date} Defensive Date instance with a valid timestamp.\n * @throws {TypeError} When the value cannot be interpreted as a date.\n */\nfunction normalizeDate(value, name) {\n const normalized = normalizeDateInput(value, name);\n const date = value instanceof Date ? new Date(value.valueOf()) : new Date(normalized);\n\n if (Number.isNaN(date.valueOf())) {\n throw new TypeError(`${name} must be a valid date`);\n }\n\n return date;\n}\n\n/**\n * Rejects timezone-ambiguous strings and normalizes calendar dates to UTC.\n *\n * @param {unknown} value - Candidate date input.\n * @param {string} name - Public concept named in failures.\n * @returns {unknown} Date-constructor input with explicit timezone semantics.\n */\nfunction normalizeDateInput(value, name) {\n if (typeof value !== \"string\") {\n return value;\n }\n\n if (isDateOnly(value)) {\n return `${value}T00:00:00Z`;\n }\n\n if (!hasExplicitTimezone(value)) {\n throw new TypeError(`${name} date-time string must include a timezone offset or Z`);\n }\n\n return value;\n}\n\n/**\n * Recognizes the exact timezone-free ISO calendar form.\n *\n * @param {string} value - Candidate date string.\n * @returns {boolean} Whether separators and decimal fields match YYYY-MM-DD.\n */\nfunction isDateOnly(value) {\n if (value.length !== ISO_DATE_LENGTH || value[4] !== \"-\" || value[7] !== \"-\") {\n return false;\n }\n\n return [\n ...value,\n ].every((character, index) => DATE_SEPARATOR_INDICES.has(index) || (character >= \"0\" && character <= \"9\"));\n}\n\n/**\n * Recognizes supported decimal Unix-second keys without a permissive parser.\n *\n * @param {string} value - Candidate heatmap key.\n * @returns {boolean} Whether every character belongs to one decimal number.\n */\nfunction isNumericKey(value) {\n const numeric = Number(value);\n\n if (!Number.isFinite(numeric) || value.trim() !== value || value === \"\") {\n return false;\n }\n\n const body = value.startsWith(\"-\") ? value.slice(1) : value;\n const parts = body.split(\".\");\n\n return (\n parts.length <= 2 &&\n parts.every(\n (part) =>\n part !== \"\" &&\n [\n ...part,\n ].every((character) => character >= \"0\" && character <= \"9\"),\n )\n );\n}\n\n/**\n * Detects a terminal Z or numeric offset on a date-time string.\n *\n * @param {string} value - Candidate date-time string.\n * @returns {boolean} Whether timezone semantics are explicit.\n */\nfunction hasExplicitTimezone(value) {\n if (!value.includes(\"T\")) {\n return false;\n }\n\n if (value.toUpperCase().endsWith(\"Z\")) {\n return true;\n }\n\n const offset = value.slice(-TIMEZONE_OFFSET_LENGTH);\n const sign = offset[0];\n const digits = `${offset.slice(1, TIMEZONE_HOUR_END)}${offset.slice(TIMEZONE_MINUTE_START)}`;\n\n return (\n [\n \"+\",\n \"-\",\n ].includes(sign) &&\n offset[TIMEZONE_HOUR_END] === \":\" &&\n [\n ...digits,\n ].every((character) => character >= \"0\" && character <= \"9\")\n );\n}\n\n/**\n * Rejects unknown public input-object keys.\n *\n * @param {object} input - Candidate public record.\n * @param {string[]} allowed - Exhaustive supported keys.\n * @param {string} concept - Public concept named in failures.\n * @returns {void} Exhaustive records pass unchanged.\n */\nfunction validateObjectKeys(input, allowed, concept) {\n const unknown = unknownKey(input, allowed);\n\n if (unknown) {\n throw new TypeError(`Unsupported ${concept} key: ${unknown}`);\n }\n}\n\n/**\n * Validates timesheet tasks and derives an enclosing time range when omitted.\n *\n * @param {{start?: string | number | Date, end?: string | number | Date, tasks?: Array<object>}} [data={}] - Timesheet bounds and task records.\n * @param {readonly string[]} [colors=DEFAULT_COLORS] - Effective categorical palette.\n * @returns {{start: Date, end: Date, tasks: Array<object>}} Canonical task collection enclosed by validated bounds.\n * @throws {TypeError} When tasks, dates, durations, or explicit bounds are invalid.\n */\nfunction normalizeTimesheetData(data = {}, colors = DEFAULT_COLORS) {\n validateObjectKeys(\n data,\n [\n \"start\",\n \"end\",\n \"tasks\",\n ],\n \"timesheet data\",\n );\n if (!Array.isArray(data.tasks) || data.tasks.length === 0) {\n throw new TypeError(\"Timesheet data requires a non-empty tasks array\");\n }\n\n const palette = new TimesheetPalette(colors);\n const tasks = data.tasks.map((task, index) => normalizeTimesheetTask(task, index, palette));\n\n const taskStart = new Date(Math.min(...tasks.map((task) => task.start.valueOf())));\n const taskEnd = new Date(Math.max(...tasks.map((task) => task.end.valueOf())));\n const start = data.start === undefined ? taskStart : normalizeDate(data.start, \"Timesheet start\");\n const end = data.end === undefined ? taskEnd : normalizeDate(data.end, \"Timesheet end\");\n\n if (end <= start) {\n throw new TypeError(\"Timesheet end must be after start\");\n }\n\n if (start > taskStart || end < taskEnd) {\n throw new TypeError(\"Timesheet bounds must contain every task\");\n }\n\n return {\n start,\n end,\n tasks,\n };\n}\n\n/**\n * Normalizes one task against strict dates and the shared palette stream.\n *\n * @param {unknown} task - Candidate task input.\n * @param {number} index - Stable task position.\n * @param {TimesheetPalette} palette - Encounter-ordered palette state.\n * @returns {object} Normalized task snapshot.\n */\nfunction normalizeTimesheetTask(task, index, palette) {\n if (!isRecord(task)) {\n throw new TypeError(\"Each timesheet task must be an object\");\n }\n\n validateObjectKeys(\n task,\n [\n \"label\",\n \"start\",\n \"end\",\n \"group\",\n \"color\",\n ],\n \"timesheet task\",\n );\n if (!isNonEmptyText(task.label)) {\n throw new TypeError(\"Timesheet task label must be a non-empty string\");\n }\n\n if (task.group !== undefined && !isNonEmptyText(task.group)) {\n throw new TypeError(\"Timesheet task group must be a non-empty string\");\n }\n\n const start = normalizeDate(task.start, `Task ${index + 1} start`);\n const end = normalizeDate(task.end, `Task ${index + 1} end`);\n\n if (end <= start) {\n throw new TypeError(\"Timesheet task end must be after start\");\n }\n\n return {\n label: task.label,\n start,\n end,\n group: task.group,\n color: palette.colorFor(task),\n };\n}\n\n/**\n * Applies invariants that depend on the selected chart renderer.\n *\n * @param {string} type - Canonical chart type selected for rendering.\n * @param {Array<{points: Array<{r?: number}>}>} datasets - Normalized datasets to validate.\n * @param {unknown} labels - Candidate category labels supplied with the data.\n * @returns {void} This function returns after all renderer-specific invariants pass.\n * @throws {TypeError} When labels or chart-specific dataset constraints are invalid.\n */\nfunction validateChartData(type, datasets, labels) {\n if (!Array.isArray(labels)) {\n throw new TypeError(\"Chart labels must be an array\");\n }\n\n if (datasets.length > 1 && datasets.some((dataset) => dataset.identityName === undefined)) {\n throw new TypeError(\"An unnamed dataset is valid only for a single-series chart\");\n }\n\n const hasGeneratedIndependentLabels =\n [\n ChartType.SCATTER,\n ChartType.BUBBLE,\n ].includes(type) && labels.every((label) => typeof label === \"number\");\n\n if (!hasGeneratedIndependentLabels && datasets.some((dataset) => dataset.points.length !== labels.length)) {\n throw new TypeError(\"Chart labels length must match every dataset\");\n }\n\n if (\n [\n ...AGGREGATION_TYPES,\n ChartType.POLAR_AREA,\n ].includes(type) &&\n datasets.length !== 1\n ) {\n throw new TypeError(`${type} requires exactly one dataset`);\n }\n\n if (\n [\n ...AGGREGATION_TYPES,\n ChartType.POLAR_AREA,\n ChartType.RADAR,\n ].includes(type)\n ) {\n validateNonNegativeData(type, datasets);\n }\n}\n\n/**\n * Validates radial and composition values after numeric normalization.\n *\n * @param {string} type - Current chart type.\n * @param {Array<object>} datasets - Normalized datasets.\n * @returns {void} Non-negative data with a positive composition total passes.\n */\nfunction validateNonNegativeData(type, datasets) {\n const values = datasets.flatMap((dataset) => dataset.points.map((point) => point.y));\n\n if (values.some((value) => value < 0)) {\n throw new TypeError(`${type} values must be non-negative`);\n }\n\n if (\n [\n ...AGGREGATION_TYPES,\n ChartType.POLAR_AREA,\n ].includes(type) &&\n values.every((value) => value === 0)\n ) {\n throw new TypeError(`${type} requires at least one positive value`);\n }\n}\n\nexport {\n requireFiniteNumber,\n normalizePoint,\n normalizeDatasets,\n normalizeHeatmapData,\n normalizeDate,\n normalizeTimesheetData,\n validateObjectKeys,\n validateSeriesScene,\n validateChartData,\n};\n"],"mappings":"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"}
@@ -0,0 +1 @@
1
+ function n(n){return Boolean(n)&&"object"==typeof n&&!Array.isArray(n)}function t(n){return"string"==typeof n&&""!==n.trim()}function e(n,t){return Number.isFinite(n)&&n>=t}function r(n){return e(n,0)&&n<=1}function o(n){return"boolean"==typeof n}function i(n,t){const e=t instanceof Set?t:new Set(t);return Object.keys(n).find(n=>!e.has(n))}function u(n,t){return t.includes(n)}export{o as isBoolean,u as isChoice,t as isNonEmptyText,e as isNumberAtLeast,r as isOpacity,n as isRecord,i as unknownKey};
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Validation.js","names":[],"sources":["../../src/support/Validation.js"],"sourcesContent":["/**\n * Reports whether a value is a record boundary.\n *\n * @param {unknown} value - Candidate value.\n * @returns {boolean} Whether the value is a non-array object.\n */\nfunction isRecord(value) {\n return Boolean(value) && typeof value === \"object\" && !Array.isArray(value);\n}\n\n/**\n * Reports whether a value is non-empty text.\n *\n * @param {unknown} value - Candidate value.\n * @returns {boolean} Whether the value contains non-whitespace text.\n */\nfunction isNonEmptyText(value) {\n return typeof value === \"string\" && value.trim() !== \"\";\n}\n\n/**\n * Reports whether a value is a finite number at or above a minimum.\n *\n * @param {unknown} value - Candidate value.\n * @param {number} minimum - Inclusive minimum.\n * @returns {boolean} Whether the value meets the numeric boundary.\n */\nfunction isNumberAtLeast(value, minimum) {\n return Number.isFinite(value) && value >= minimum;\n}\n\n/**\n * Reports whether a value is a valid opacity.\n *\n * @param {unknown} value - Candidate value.\n * @returns {boolean} Whether the value is between zero and one.\n */\nfunction isOpacity(value) {\n return isNumberAtLeast(value, 0) && value <= 1;\n}\n\n/**\n * Reports whether a value is boolean.\n *\n * @param {unknown} value - Candidate value.\n * @returns {boolean} Whether the value is boolean.\n */\nfunction isBoolean(value) {\n return typeof value === \"boolean\";\n}\n\n/**\n * Finds the first key outside an allowed collection.\n *\n * @param {object} value - Record to inspect.\n * @param {Set<string>|string[]} allowed - Allowed keys.\n * @returns {string|undefined} First unknown key, when present.\n */\nfunction unknownKey(value, allowed) {\n const names = allowed instanceof Set ? allowed : new Set(allowed);\n\n return Object.keys(value).find((key) => !names.has(key));\n}\n\n/**\n * Reports whether a value belongs to a fixed collection.\n *\n * @param {unknown} value - Candidate value.\n * @param {unknown[]} choices - Allowed values.\n * @returns {boolean} Whether the collection contains the value.\n */\nfunction isChoice(value, choices) {\n return choices.includes(value);\n}\n\nexport { isBoolean, isChoice, isNonEmptyText, isNumberAtLeast, isOpacity, isRecord, unknownKey };\n"],"mappings":"AAMA,SAAS,EAAS,GAChB,OAAO,QAAQ,IAA2B,iBAAV,IAAuB,MAAM,QAAQ,EACvE,CAQA,SAAS,EAAe,GACtB,MAAwB,iBAAV,GAAuC,KAAjB,EAAM,MAC5C,CASA,SAAS,EAAgB,EAAO,GAC9B,OAAO,OAAO,SAAS,IAAU,GAAS,CAC5C,CAQA,SAAS,EAAU,GACjB,OAAO,EAAgB,EAAO,IAAM,GAAS,CAC/C,CAQA,SAAS,EAAU,GACjB,MAAwB,kBAAV,CAChB,CASA,SAAS,EAAW,EAAO,GACzB,MAAM,EAAQ,aAAmB,IAAM,EAAU,IAAI,IAAI,GAEzD,OAAO,OAAO,KAAK,GAAO,KAAM,IAAS,EAAM,IAAI,GACrD,CASA,SAAS,EAAS,EAAO,GACvB,OAAO,EAAQ,SAAS,EAC1B"}