@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.
- package/CHANGELOG.md +71 -0
- package/LICENSE +21 -0
- package/README.md +122 -0
- package/dist/core/Chart.js +1 -0
- package/dist/core/Chart.js.map +1 -0
- package/dist/core/ChartData.js +1 -0
- package/dist/core/ChartData.js.map +1 -0
- package/dist/core/ChartDefinition.js +1 -0
- package/dist/core/ChartDefinition.js.map +1 -0
- package/dist/core/ChartSelection.js +1 -0
- package/dist/core/ChartSelection.js.map +1 -0
- package/dist/core/ChartTooltip.js +1 -0
- package/dist/core/ChartTooltip.js.map +1 -0
- package/dist/core/InteractionController.js +1 -0
- package/dist/core/InteractionController.js.map +1 -0
- package/dist/core/NormalizeAnnotations.js +1 -0
- package/dist/core/NormalizeAnnotations.js.map +1 -0
- package/dist/core/Options.js +1 -0
- package/dist/core/Options.js.map +1 -0
- package/dist/core/builders/Builder.js +1 -0
- package/dist/core/builders/Builder.js.map +1 -0
- package/dist/core/builders/BuilderArguments.js +1 -0
- package/dist/core/builders/BuilderArguments.js.map +1 -0
- package/dist/core/builders/BuilderScopes.js +1 -0
- package/dist/core/builders/BuilderScopes.js.map +1 -0
- package/dist/core/builders/BuilderState.js +1 -0
- package/dist/core/builders/BuilderState.js.map +1 -0
- package/dist/core/builders/BuilderValidation.js +1 -0
- package/dist/core/builders/BuilderValidation.js.map +1 -0
- package/dist/core/builders/CartesianBuilders.js +1 -0
- package/dist/core/builders/CartesianBuilders.js.map +1 -0
- package/dist/core/builders/CompositionBuilders.js +1 -0
- package/dist/core/builders/CompositionBuilders.js.map +1 -0
- package/dist/core/builders/TemporalBuilders.js +1 -0
- package/dist/core/builders/TemporalBuilders.js.map +1 -0
- package/dist/index.js +1 -0
- package/dist/renderers/ChartRendering.js +1 -0
- package/dist/renderers/ChartRendering.js.map +1 -0
- package/dist/renderers/LegendRenderer.js +1 -0
- package/dist/renderers/LegendRenderer.js.map +1 -0
- package/dist/renderers/SvgSurface.js +1 -0
- package/dist/renderers/SvgSurface.js.map +1 -0
- package/dist/renderers/cartesian/CartesianAxesRenderer.js +1 -0
- package/dist/renderers/cartesian/CartesianAxesRenderer.js.map +1 -0
- package/dist/renderers/cartesian/CartesianInspectorRenderer.js +1 -0
- package/dist/renderers/cartesian/CartesianInspectorRenderer.js.map +1 -0
- package/dist/renderers/cartesian/CartesianLayout.js +1 -0
- package/dist/renderers/cartesian/CartesianLayout.js.map +1 -0
- package/dist/renderers/cartesian/CartesianRendering.js +1 -0
- package/dist/renderers/cartesian/CartesianRendering.js.map +1 -0
- package/dist/renderers/cartesian/CartesianSeriesRendering.js +1 -0
- package/dist/renderers/cartesian/CartesianSeriesRendering.js.map +1 -0
- package/dist/renderers/composition/AggregationRendering.js +1 -0
- package/dist/renderers/composition/AggregationRendering.js.map +1 -0
- package/dist/renderers/composition/Composition.js +1 -0
- package/dist/renderers/composition/Composition.js.map +1 -0
- package/dist/renderers/composition/PolarAreaRendering.js +1 -0
- package/dist/renderers/composition/PolarAreaRendering.js.map +1 -0
- package/dist/renderers/composition/RadarRendering.js +1 -0
- package/dist/renderers/composition/RadarRendering.js.map +1 -0
- package/dist/renderers/temporal/HeatmapRendering.js +1 -0
- package/dist/renderers/temporal/HeatmapRendering.js.map +1 -0
- package/dist/renderers/temporal/TimesheetLayout.js +1 -0
- package/dist/renderers/temporal/TimesheetLayout.js.map +1 -0
- package/dist/renderers/temporal/TimesheetRendering.js +1 -0
- package/dist/renderers/temporal/TimesheetRendering.js.map +1 -0
- package/dist/support/Constants.js +1 -0
- package/dist/support/Constants.js.map +1 -0
- package/dist/support/Dom.js +1 -0
- package/dist/support/Dom.js.map +1 -0
- package/dist/support/Normalize.js +2 -0
- package/dist/support/Normalize.js.map +1 -0
- package/dist/support/Validation.js +1 -0
- package/dist/support/Validation.js.map +1 -0
- package/dist/support/geometry/CartesianGeometry.js +1 -0
- package/dist/support/geometry/CartesianGeometry.js.map +1 -0
- package/dist/support/geometry/Scale.js +1 -0
- package/dist/support/geometry/Scale.js.map +1 -0
- package/dist/support/geometry/SectorGeometry.js +1 -0
- package/dist/support/geometry/SectorGeometry.js.map +1 -0
- package/dist/support/presentation/Formatting.js +1 -0
- package/dist/support/presentation/Formatting.js.map +1 -0
- package/dist/support/presentation/Presentation.js +1 -0
- package/dist/support/presentation/Presentation.js.map +1 -0
- package/dist/support/presentation/Time.js +1 -0
- package/dist/support/presentation/Time.js.map +1 -0
- package/docs/api-reference.md +322 -0
- package/docs/bar.md +110 -0
- package/docs/bubble.md +76 -0
- package/docs/chart-types.md +35 -0
- package/docs/customization.md +178 -0
- package/docs/donut.md +55 -0
- package/docs/exporting.md +87 -0
- package/docs/frameworks.md +203 -0
- package/docs/getting-started.md +121 -0
- package/docs/heatmap.md +95 -0
- package/docs/line.md +148 -0
- package/docs/mixed.md +114 -0
- package/docs/percentage.md +48 -0
- package/docs/pie.md +58 -0
- package/docs/polar-area.md +46 -0
- package/docs/radar.md +49 -0
- package/docs/readme.md +37 -0
- package/docs/scatter.md +83 -0
- package/docs/timesheet.md +125 -0
- package/docs/updates-and-interaction.md +124 -0
- package/package.json +133 -0
- package/src/index.d.ts +481 -0
- package/src/styles.css +350 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{ChartOrientation as $}from"../Constants.js";function t($,t=!0){if(1===$.length)return`M${$[0].x},${$[0].y}`;let n=!0,e=1;for(;e<$.length;){if($[e].x<=$[e-1].x){n=!1;break}e+=1}if(!t||!n){let t=`M${$[0].x},${$[0].y}`,n=1;for(;n<$.length;)t+=` L${$[n].x},${$[n].y}`,n+=1;return t}const r=new Float64Array($.length-1);let o=0;for(;o<r.length;)r[o]=$[o+1].x-$[o].x,o+=1;return function($,t,n){let e=`M${$[0].x},${$[0].y}`,r=1;for(;r<$.length;){const o=$[r],i=$[r-1],l=t[r-1]/3;e+=` C${i.x+l},${i.y+n[r-1]*l} ${o.x-l},${o.y-n[r]*l} ${o.x},${o.y}`,r+=1}return e}($,r,function($,t){const n=new Float64Array(t.length),e=new Float64Array($.length);let r=0;for(;r<t.length;)n[r]=($[r+1].y-$[r].y)/t[r],r+=1;e[0]=n[0],e[e.length-1]=n.at(-1);let o=1;for(;o<$.length-1;){const $=n[o-1],r=n[o];if(0===$||0===r||Math.sign($)!==Math.sign(r)){e[o]=0,o+=1;continue}const i=t[o-1],l=t[o],u=2*l+i,a=l+2*i;e[o]=(u+a)/(u/$+a/r),o+=1}return e}($,r))}function n({rectangle:t,direction:n,rounding:e}){const{x:r,y:o,width:i,height:l}=t,u=e.shouldRoundValueEnd?Math.min(e.radius,i/2,l/2):0;if(i<=0||l<=0)return`M${r},${o}Z`;if(u<=0)return`M${r},${o}H${r+i}V${o+l}H${r}Z`;const a=r+i,h=o+l;return n.orientation===$.HORIZONTAL&&n.value>=0?`M${r},${o}H${a-u}Q${a},${o} ${a},${o+u}V${h-u}Q${a},${h} ${a-u},${h}H${r}Z`:n.orientation===$.HORIZONTAL?`M${a},${o}H${r+u}Q${r},${o} ${r},${o+u}V${h-u}Q${r},${h} ${r+u},${h}H${a}Z`:n.value>=0?`M${r},${h}V${o+u}Q${r},${o} ${r+u},${o}H${a-u}Q${a},${o} ${a},${o+u}V${h}Z`:`M${r},${o}V${h-u}Q${r},${h} ${r+u},${h}H${a-u}Q${a},${h} ${a},${h-u}V${o}Z`}export{t as linePath,n as roundedBarPath};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"CartesianGeometry.js","names":[],"sources":["../../../src/support/geometry/CartesianGeometry.js"],"sourcesContent":["import { ChartOrientation } from \"../Constants.js\";\n\nconst CUBIC_CONTROL_DIVISOR = 3;\n\n/**\n * Calculates monotone tangents for ordered screen-space points.\n *\n * @param {Array<{x: number, y: number}>} points - Strictly increasing coordinates.\n * @param {Float64Array} intervals - Horizontal distance between adjacent points.\n * @returns {Float64Array} Tangent slope at every point.\n */\n// The indexed loops here avoid callback and iterator allocation on very large paths.\n// eslint-disable-next-line max-statements\nfunction monotoneTangents(points, intervals) {\n const slopes = new Float64Array(intervals.length);\n const tangents = new Float64Array(points.length);\n let slopeIndex = 0;\n\n while (slopeIndex < intervals.length) {\n slopes[slopeIndex] = (points[slopeIndex + 1].y - points[slopeIndex].y) / intervals[slopeIndex];\n slopeIndex += 1;\n }\n\n tangents[0] = slopes[0];\n tangents[tangents.length - 1] = slopes.at(-1);\n\n let tangentIndex = 1;\n\n while (tangentIndex < points.length - 1) {\n const before = slopes[tangentIndex - 1];\n const after = slopes[tangentIndex];\n\n if (before === 0 || after === 0 || Math.sign(before) !== Math.sign(after)) {\n tangents[tangentIndex] = 0;\n tangentIndex += 1;\n continue;\n }\n\n const beforeInterval = intervals[tangentIndex - 1];\n const afterInterval = intervals[tangentIndex];\n const firstWeight = 2 * afterInterval + beforeInterval;\n const secondWeight = afterInterval + 2 * beforeInterval;\n\n tangents[tangentIndex] = (firstWeight + secondWeight) / (firstWeight / before + secondWeight / after);\n tangentIndex += 1;\n }\n\n return tangents;\n}\n\n/**\n * Serializes monotone cubic control points into one SVG path.\n *\n * @param {Array<{x: number, y: number}>} points - Ordered coordinates.\n * @param {Float64Array} intervals - Horizontal distances between points.\n * @param {Float64Array} tangents - Monotone tangent at every point.\n * @returns {string} Smooth SVG path data.\n */\nfunction smoothLinePath(points, intervals, tangents) {\n let path = `M${points[0].x},${points[0].y}`;\n let index = 1;\n\n while (index < points.length) {\n const point = points[index];\n const previous = points[index - 1];\n const interval = intervals[index - 1];\n const controlOffset = interval / CUBIC_CONTROL_DIVISOR;\n const firstControlX = previous.x + controlOffset;\n const firstControlY = previous.y + tangents[index - 1] * controlOffset;\n const secondControlX = point.x - controlOffset;\n const secondControlY = point.y - tangents[index] * controlOffset;\n\n path += ` C${firstControlX},${firstControlY} ${secondControlX},${secondControlY} ${point.x},${point.y}`;\n index += 1;\n }\n\n return path;\n}\n\n/**\n * Builds an SVG path through ordered points using monotone cubic smoothing.\n *\n * @param {Array<{x: number, y: number}>} points - Ordered screen-space coordinates.\n * @param {boolean} [isSmooth=true] - Enables smoothing when x coordinates are strictly increasing.\n * @returns {string} SVG path data that passes through every supplied point.\n */\n// eslint-disable-next-line max-statements\nfunction linePath(points, isSmooth = true) {\n if (points.length === 1) {\n return `M${points[0].x},${points[0].y}`;\n }\n\n let hasIncreasingX = true;\n let pointIndex = 1;\n\n while (pointIndex < points.length) {\n if (points[pointIndex].x <= points[pointIndex - 1].x) {\n hasIncreasingX = false;\n break;\n }\n\n pointIndex += 1;\n }\n\n if (!isSmooth || !hasIncreasingX) {\n let path = `M${points[0].x},${points[0].y}`;\n let pathIndex = 1;\n\n while (pathIndex < points.length) {\n path += ` L${points[pathIndex].x},${points[pathIndex].y}`;\n pathIndex += 1;\n }\n\n return path;\n }\n\n const intervals = new Float64Array(points.length - 1);\n let intervalIndex = 0;\n\n while (intervalIndex < intervals.length) {\n intervals[intervalIndex] = points[intervalIndex + 1].x - points[intervalIndex].x;\n intervalIndex += 1;\n }\n\n const tangents = monotoneTangents(points, intervals);\n\n return smoothLinePath(points, intervals, tangents);\n}\n\n/**\n * Builds a bar outline with rounding only on the end representing its value.\n *\n * @param {object} bar - Named rectangle, direction, and rounding policies.\n * @param {object} bar.rectangle - Non-negative SVG rectangle geometry.\n * @param {object} bar.direction - Orientation and signed value direction.\n * @param {object} bar.rounding - Radius and exposed-edge rounding policy.\n * @returns {string} Closed SVG path data for the bar.\n */\nfunction roundedBarPath({ rectangle, direction, rounding }) {\n const { x, y, width, height } = rectangle;\n const appliedRadius = rounding.shouldRoundValueEnd ? Math.min(rounding.radius, width / 2, height / 2) : 0;\n\n if (width <= 0 || height <= 0) {\n return `M${x},${y}Z`;\n }\n\n if (appliedRadius <= 0) {\n return `M${x},${y}H${x + width}V${y + height}H${x}Z`;\n }\n\n const right = x + width;\n const bottom = y + height;\n\n if (direction.orientation === ChartOrientation.HORIZONTAL && direction.value >= 0) {\n return `M${x},${y}H${right - appliedRadius}Q${right},${y} ${right},${y + appliedRadius}V${bottom - appliedRadius}Q${right},${bottom} ${right - appliedRadius},${bottom}H${x}Z`;\n }\n\n if (direction.orientation === ChartOrientation.HORIZONTAL) {\n return `M${right},${y}H${x + appliedRadius}Q${x},${y} ${x},${y + appliedRadius}V${bottom - appliedRadius}Q${x},${bottom} ${x + appliedRadius},${bottom}H${right}Z`;\n }\n\n if (direction.value >= 0) {\n return `M${x},${bottom}V${y + appliedRadius}Q${x},${y} ${x + appliedRadius},${y}H${right - appliedRadius}Q${right},${y} ${right},${y + appliedRadius}V${bottom}Z`;\n }\n\n return `M${x},${y}V${bottom - appliedRadius}Q${x},${bottom} ${x + appliedRadius},${bottom}H${right - appliedRadius}Q${right},${bottom} ${right},${bottom - appliedRadius}V${y}Z`;\n}\n\nexport { linePath, roundedBarPath };\n"],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
function t(t){const r=Math.min(...t),n=Math.max(...t);return r===n?[r-1,n+1]:[r,n]}function r(t,r){const n=t/4,a=10**Math.floor(Math.log10(n)),o=((e=n/a)>=Math.sqrt(50)?10:e>=Math.sqrt(10)?5:e>=Math.sqrt(2)?2:1)*a;var e;return r?Math.max(1,o):o}function n(t){const n=Math.min(...t),a=Math.max(...t);if(n===a)return{domain:[n-1,a+1],ticks:[n-1,n,a+1]};const o=r(a-n,t.every(t=>Number.isSafeInteger(t))),e=Math.floor(n/o)*o,i=Math.ceil(a/o)*o,h=Math.round((i-e)/o),M=Array.from({length:h+1},(t,r)=>Number((e+r*o).toPrecision(12)));return{domain:[e,i],ticks:M}}function a(t,r,n){return n[0]+(t-r[0])/(r[1]-r[0])*(n[1]-n[0])}export{t as extent,r as niceStep,n as niceValueScale,a as scale};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"Scale.js","names":[],"sources":["../../../src/support/geometry/Scale.js"],"sourcesContent":["import { MAJOR_GRID_DIVISIONS } from \"../Constants.js\";\n\nconst DECIMAL_BASE = 10;\nconst FIVE_STEP = 5;\nconst FIVE_STEP_THRESHOLD = 10;\nconst TEN_STEP = 10;\nconst TEN_STEP_THRESHOLD = 50;\nconst TICK_PRECISION = 12;\n\n/**\n * Finds a numeric extent and expands a degenerate single-value domain.\n *\n * @param {number[]} values - Non-empty numeric sample used to derive the domain.\n * @returns {[number, number]} Ascending minimum and maximum suitable for scaling.\n */\nfunction extent(values) {\n const minimum = Math.min(...values);\n const maximum = Math.max(...values);\n\n return minimum === maximum\n ? [\n minimum - 1,\n maximum + 1,\n ]\n : [\n minimum,\n maximum,\n ];\n}\n\n/**\n * Selects the multiplier in a human-readable 1, 2, 5, 10 progression.\n *\n * @param {number} error - Rough step normalized by its decimal power.\n * @returns {number} Progression multiplier.\n */\nfunction niceFactor(error) {\n if (error >= Math.sqrt(TEN_STEP_THRESHOLD)) {\n return TEN_STEP;\n }\n\n if (error >= Math.sqrt(FIVE_STEP_THRESHOLD)) {\n return FIVE_STEP;\n }\n\n if (error >= Math.sqrt(2)) {\n return 2;\n }\n\n return 1;\n}\n\n/**\n * Chooses a human-readable grid step near the requested division count.\n *\n * @param {number} span - Positive numeric distance covered by the axis.\n * @param {boolean} integerValues - Whether every source value is an integer.\n * @returns {number} Positive step rounded to a 1, 2, 5, or 10 progression.\n */\nfunction niceStep(span, integerValues) {\n const roughStep = span / MAJOR_GRID_DIVISIONS;\n const power = DECIMAL_BASE ** Math.floor(Math.log10(roughStep));\n const step = niceFactor(roughStep / power) * power;\n\n return integerValues ? Math.max(1, step) : step;\n}\n\n/**\n * Expands values onto rounded bounds and enumerates their major ticks.\n *\n * @param {number[]} values - Non-empty finite values displayed on an axis.\n * @returns {{domain: [number, number], ticks: number[]}} Rounded domain and inclusive tick sequence.\n */\nfunction niceValueScale(values) {\n const minimum = Math.min(...values);\n const maximum = Math.max(...values);\n\n if (minimum === maximum) {\n return {\n domain: [\n minimum - 1,\n maximum + 1,\n ],\n ticks: [\n minimum - 1,\n minimum,\n maximum + 1,\n ],\n };\n }\n\n const integerValues = values.every((value) => Number.isSafeInteger(value));\n const step = niceStep(maximum - minimum, integerValues);\n const niceMinimum = Math.floor(minimum / step) * step;\n const niceMaximum = Math.ceil(maximum / step) * step;\n const intervals = Math.round((niceMaximum - niceMinimum) / step);\n\n const ticks = Array.from({ length: intervals + 1 }, (_, index) =>\n Number((niceMinimum + index * step).toPrecision(TICK_PRECISION)),\n );\n\n return {\n domain: [\n niceMinimum,\n niceMaximum,\n ],\n ticks,\n };\n}\n\n/**\n * Maps a value linearly between a numeric domain and output range.\n *\n * @param {number} value - Input value to project.\n * @param {[number, number]} domain - Input endpoints defining the interpolation ratio.\n * @param {[number, number]} range - Output endpoints receiving the projected value.\n * @returns {number} Linearly projected output value.\n */\nfunction scale(value, domain, range) {\n return range[0] + ((value - domain[0]) / (domain[1] - domain[0])) * (range[1] - range[0]);\n}\n\nexport { extent, niceStep, niceValueScale, scale };\n"],"mappings":"AAeA,SAAS,EAAO,GACd,MAAM,EAAU,KAAK,OAAO,GACtB,EAAU,KAAK,OAAO,GAE5B,OAAO,IAAY,EACf,CACE,EAAU,EACV,EAAU,GAEZ,CACE,EACA,EAER,CA+BA,SAAS,EAAS,EAAM,GACtB,MAAM,EAAY,EAAA,EACZ,EA3Da,IA2DW,KAAK,MAAM,KAAK,MAAM,IAC9C,IA1BY,EA0BM,EAAY,IAzBvB,KAAK,KA/BO,IADV,GAoCX,GAAS,KAAK,KArCQ,IADV,EA0CZ,GAAS,KAAK,KAAK,GACd,EAGF,GAasC,EA1B/C,IAAoB,EA4BlB,OAAO,EAAgB,KAAK,IAAI,EAAG,GAAQ,CAC7C,CAQA,SAAS,EAAe,GACtB,MAAM,EAAU,KAAK,OAAO,GACtB,EAAU,KAAK,OAAO,GAE5B,GAAI,IAAY,EACd,MAAO,CACL,OAAQ,CACN,EAAU,EACV,EAAU,GAEZ,MAAO,CACL,EAAU,EACV,EACA,EAAU,IAKhB,MACM,EAAO,EAAS,EAAU,EADV,EAAO,MAAO,GAAU,OAAO,cAAc,KAE7D,EAAc,KAAK,MAAM,EAAU,GAAQ,EAC3C,EAAc,KAAK,KAAK,EAAU,GAAQ,EAC1C,EAAY,KAAK,OAAO,EAAc,GAAe,GAErD,EAAQ,MAAM,KAAK,CAAE,OAAQ,EAAY,GAAE,CAAI,EAAG,IACtD,QAAQ,EAAc,EAAQ,GAAM,YA3FjB,MA8FrB,MAAO,CACL,OAAQ,CACN,EACA,GAEF,QAEJ,CAUA,SAAS,EAAM,EAAO,EAAQ,GAC5B,OAAO,EAAM,IAAO,EAAQ,EAAO,KAAO,EAAO,GAAK,EAAO,KAAQ,EAAM,GAAK,EAAM,GACxF"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{MIN_SECTOR_SWEEP as n}from"../Constants.js";function r({cx:n,cy:r,radius:e,angle:t}){return{x:n+Math.cos(t)*e,y:r+Math.sin(t)*e}}function e(n,r,e){const t=Math.hypot(r.x-n.x,r.y-n.y);return{x:n.x+(r.x-n.x)/t*e,y:n.y+(r.y-n.y)/t*e}}function t({center:n,radius:e,angles:t}){const a=r({cx:n.x,cy:n.y,radius:e,angle:t.start}),i=r({cx:n.x,cy:n.y,radius:e,angle:t.end});return`M${n.x},${n.y} L${a.x},${a.y} A${e},${e} 0 0 1 ${i.x},${i.y} Z`}function a({center:n,radii:e,outerAngles:t,innerAngles:a=t}){const i=r({cx:n.x,cy:n.y,radius:e.outer,angle:t.start}),u=r({cx:n.x,cy:n.y,radius:e.outer,angle:t.end}),s=r({cx:n.x,cy:n.y,radius:e.inner,angle:a.start}),d=r({cx:n.x,cy:n.y,radius:e.inner,angle:a.end}),o=t.end-t.start>Math.PI?1:0,c=a.end-a.start>Math.PI?1:0;return`M${i.x},${i.y} A${e.outer},${e.outer} 0 ${o} 1 ${u.x},${u.y} L${d.x},${d.y} A${e.inner},${e.inner} 0 ${c} 0 ${s.x},${s.y} Z`}function i(n){return n.cornerRadius<=0&&n.radii.inner>0?a({center:n.center,radii:n.radii,outerAngles:n.angles.outer,innerAngles:n.angles.inner}):n.cornerRadius<=0?t({center:n.center,radius:n.radii.outer,angles:n.angles.outer}):n.radii.inner<=0?function({center:n,radius:a,angles:i,cornerRadius:u}){const s=r({cx:n.x,cy:n.y,radius:a,angle:i.start}),d=r({cx:n.x,cy:n.y,radius:a,angle:i.end}),o=Math.min(u,a/2,a*(i.end-i.start)/2);if(o<=0)return t({center:n,radius:a,angles:i});const c=Math.asin(Math.min(1,o/a)),y=r({cx:n.x,cy:n.y,radius:a,angle:i.start+c}),x=r({cx:n.x,cy:n.y,radius:a,angle:i.end-c}),$=e(s,n,o),l=e(d,n,o),g=i.end-i.start-2*c>Math.PI?1:0;return`M${n.x},${n.y}L${$.x},${$.y}Q${s.x},${s.y} ${y.x},${y.y}A${a},${a} 0 ${g} 1 ${x.x},${x.y}Q${d.x},${d.y} ${l.x},${l.y}L${n.x},${n.y}Z`}({center:n.center,radius:n.radii.outer,angles:n.angles.outer,cornerRadius:n.cornerRadius}):function(n){const t=function({center:n,radii:e,outerAngles:t,innerAngles:a}){return{outerStart:r({cx:n.x,cy:n.y,radius:e.outer,angle:t.start}),outerEnd:r({cx:n.x,cy:n.y,radius:e.outer,angle:t.end}),innerStart:r({cx:n.x,cy:n.y,radius:e.inner,angle:a.start}),innerEnd:r({cx:n.x,cy:n.y,radius:e.inner,angle:a.end})}}({center:n.center,radii:n.radii,outerAngles:n.angles.outer,innerAngles:n.angles.inner}),a=function({geometry:n,points:r}){const e=Math.hypot(r.outerStart.x-r.innerStart.x,r.outerStart.y-r.innerStart.y),t=Math.hypot(r.outerEnd.x-r.innerEnd.x,r.outerEnd.y-r.innerEnd.y);return Math.min(n.cornerRadius,e/2,t/2,n.radii.outer*(n.angles.outer.end-n.angles.outer.start)/2,n.radii.inner*(n.angles.inner.end-n.angles.inner.start)/2)}({geometry:n,points:t}),i=function(n,e){const t=Math.asin(Math.min(1,e/n.radii.outer)),a=Math.asin(Math.min(1,e/n.radii.inner));return{outerDelta:t,innerDelta:a,outerStart:r({cx:n.center.x,cy:n.center.y,radius:n.radii.outer,angle:n.angles.outer.start+t}),outerEnd:r({cx:n.center.x,cy:n.center.y,radius:n.radii.outer,angle:n.angles.outer.end-t}),innerEnd:r({cx:n.center.x,cy:n.center.y,radius:n.radii.inner,angle:n.angles.inner.end-a}),innerStart:r({cx:n.center.x,cy:n.center.y,radius:n.radii.inner,angle:n.angles.inner.start+a})}}(n,a),u=e(t.outerEnd,t.innerEnd,a),s=e(t.innerEnd,t.outerEnd,a),d=e(t.innerStart,t.outerStart,a),o=e(t.outerStart,t.innerStart,a),c=n.angles.outer.end-n.angles.outer.start-2*i.outerDelta>Math.PI?1:0,y=n.angles.inner.end-n.angles.inner.start-2*i.innerDelta>Math.PI?1:0;return`M${i.outerStart.x},${i.outerStart.y}A${n.radii.outer},${n.radii.outer} 0 ${c} 1 ${i.outerEnd.x},${i.outerEnd.y}Q${t.outerEnd.x},${t.outerEnd.y} ${u.x},${u.y}L${s.x},${s.y}Q${t.innerEnd.x},${t.innerEnd.y} ${i.innerEnd.x},${i.innerEnd.y}A${n.radii.inner},${n.radii.inner} 0 ${y} 0 ${i.innerStart.x},${i.innerStart.y}Q${t.innerStart.x},${t.innerStart.y} ${d.x},${d.y}L${o.x},${o.y}Q${t.outerStart.x},${t.outerStart.y} ${i.outerStart.x},${i.outerStart.y}Z`}(n)}function u({angles:r,radii:e,padding:t}){const a=r.end-r.start;if(t.count<=1||0===t.angle||a<=0)return{outer:{...r},inner:{...r}};const i=t.angle*Math.PI/360,u=Math.hypot(e.outer,e.inner),s=n=>n<=0?0:Math.asin(Math.min(1,u/n*Math.sin(i))),d=Math.max(0,(a-n)/2),o=Math.min(s(e.outer),d),c=Math.min(s(e.inner),d);return{outer:{start:r.start+o,end:r.end-o},inner:{start:r.start+c,end:r.end-c}}}export{t as arcPath,u as paddedSector,e as pointToward,r as polarPoint,a as ringPath,i as roundedSectorPath};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"SectorGeometry.js","names":[],"sources":["../../../src/support/geometry/SectorGeometry.js"],"sourcesContent":["import { MIN_SECTOR_SWEEP } from \"../Constants.js\";\n\nconst DEGREES_PER_CIRCLE = 360;\n\n/**\n * Converts polar coordinates into an SVG Cartesian point.\n *\n * @param {object} coordinate - Polar coordinate to project.\n * @param {number} coordinate.cx - Circle center x coordinate.\n * @param {number} coordinate.cy - Circle center y coordinate.\n * @param {number} coordinate.radius - Distance from the center.\n * @param {number} coordinate.angle - Direction in radians.\n * @returns {{x: number, y: number}} Cartesian point on the requested ray.\n */\nfunction polarPoint({ cx, cy, radius, angle }) {\n return {\n x: cx + Math.cos(angle) * radius,\n y: cy + Math.sin(angle) * radius,\n };\n}\n\n/**\n * Moves from one point toward another by an exact Euclidean distance.\n *\n * @param {{x: number, y: number}} from - Starting coordinate.\n * @param {{x: number, y: number}} to - Target coordinate defining the direction.\n * @param {number} distance - Distance to travel from the starting coordinate.\n * @returns {{x: number, y: number}} Point located on the directed segment.\n */\nfunction pointToward(from, to, distance) {\n const length = Math.hypot(to.x - from.x, to.y - from.y);\n\n return {\n x: from.x + ((to.x - from.x) / length) * distance,\n y: from.y + ((to.y - from.y) / length) * distance,\n };\n}\n\n/**\n * Builds a closed circular sector from the center to two radial endpoints.\n *\n * @param {object} geometry - Center, radius, and angular interval.\n * @param {{x: number, y: number}} geometry.center - Circle center.\n * @param {number} geometry.radius - Distance from center to boundary.\n * @param {{start: number, end: number}} geometry.angles - Angular interval in radians.\n * @returns {string} Closed SVG path data for the sector.\n */\nfunction arcPath({ center, radius, angles }) {\n const start = polarPoint({ cx: center.x, cy: center.y, radius, angle: angles.start });\n const end = polarPoint({ cx: center.x, cy: center.y, radius, angle: angles.end });\n\n return `M${center.x},${center.y} L${start.x},${start.y} A${radius},${radius} 0 0 1 ${end.x},${end.y} Z`;\n}\n\n/**\n * Builds a closed annular sector with independently adjustable inner angles.\n *\n * @param {object} geometry - Center, radii, and independent angular intervals.\n * @param {{x: number, y: number}} geometry.center - Circle center.\n * @param {{outer: number, inner: number}} geometry.radii - Ring boundary radii.\n * @param {{start: number, end: number}} geometry.outerAngles - Outer angular interval.\n * @param {{start: number, end: number}} [geometry.innerAngles=geometry.outerAngles] - Inner angular interval.\n * @returns {string} Closed SVG path data for the ring segment.\n */\nfunction ringPath({ center, radii, outerAngles, innerAngles = outerAngles }) {\n const outerStart = polarPoint({\n cx: center.x,\n cy: center.y,\n radius: radii.outer,\n angle: outerAngles.start,\n });\n\n const outerEnd = polarPoint({ cx: center.x, cy: center.y, radius: radii.outer, angle: outerAngles.end });\n\n const innerStart = polarPoint({\n cx: center.x,\n cy: center.y,\n radius: radii.inner,\n angle: innerAngles.start,\n });\n\n const innerEnd = polarPoint({ cx: center.x, cy: center.y, radius: radii.inner, angle: innerAngles.end });\n const outerLarge = outerAngles.end - outerAngles.start > Math.PI ? 1 : 0;\n const innerLarge = innerAngles.end - innerAngles.start > Math.PI ? 1 : 0;\n\n return `M${outerStart.x},${outerStart.y} A${radii.outer},${radii.outer} 0 ${outerLarge} 1 ${outerEnd.x},${outerEnd.y} L${innerEnd.x},${innerEnd.y} A${radii.inner},${radii.inner} 0 ${innerLarge} 0 ${innerStart.x},${innerStart.y} Z`;\n}\n\n/**\n * Rounds the two outer corners of a solid circular sector.\n *\n * @param {object} geometry - Center, radius, interval, and corner policy.\n * @param {{x: number, y: number}} geometry.center - Circle center.\n * @param {number} geometry.radius - Distance from center to boundary.\n * @param {{start: number, end: number}} geometry.angles - Angular interval.\n * @param {number} geometry.cornerRadius - Requested corner radius.\n * @returns {string} Closed SVG path data for the rounded solid sector.\n */\nfunction roundedSolidSectorPath({ center, radius, angles, cornerRadius }) {\n const outerStart = polarPoint({ cx: center.x, cy: center.y, radius, angle: angles.start });\n const outerEnd = polarPoint({ cx: center.x, cy: center.y, radius, angle: angles.end });\n const appliedRadius = Math.min(cornerRadius, radius / 2, (radius * (angles.end - angles.start)) / 2);\n\n if (appliedRadius <= 0) {\n return arcPath({ center, radius, angles });\n }\n\n const outerDelta = Math.asin(Math.min(1, appliedRadius / radius));\n const arcStart = polarPoint({ cx: center.x, cy: center.y, radius, angle: angles.start + outerDelta });\n const arcEnd = polarPoint({ cx: center.x, cy: center.y, radius, angle: angles.end - outerDelta });\n const startSide = pointToward(outerStart, center, appliedRadius);\n const endSide = pointToward(outerEnd, center, appliedRadius);\n const large = angles.end - angles.start - outerDelta * 2 > Math.PI ? 1 : 0;\n\n return `M${center.x},${center.y}L${startSide.x},${startSide.y}Q${outerStart.x},${outerStart.y} ${arcStart.x},${arcStart.y}A${radius},${radius} 0 ${large} 1 ${arcEnd.x},${arcEnd.y}Q${outerEnd.x},${outerEnd.y} ${endSide.x},${endSide.y}L${center.x},${center.y}Z`;\n}\n\n/**\n * Creates the four endpoints of an annular sector.\n *\n * @param {object} geometry - Center, radii, and inner and outer intervals.\n * @param {{x: number, y: number}} geometry.center - Circle center.\n * @param {{outer: number, inner: number}} geometry.radii - Ring boundary radii.\n * @param {{start: number, end: number}} geometry.outerAngles - Outer interval.\n * @param {{start: number, end: number}} geometry.innerAngles - Inner interval.\n * @returns {object} Inner and outer start and end points.\n */\nfunction ringPoints({ center, radii, outerAngles, innerAngles }) {\n return {\n outerStart: polarPoint({ cx: center.x, cy: center.y, radius: radii.outer, angle: outerAngles.start }),\n outerEnd: polarPoint({ cx: center.x, cy: center.y, radius: radii.outer, angle: outerAngles.end }),\n innerStart: polarPoint({ cx: center.x, cy: center.y, radius: radii.inner, angle: innerAngles.start }),\n innerEnd: polarPoint({ cx: center.x, cy: center.y, radius: radii.inner, angle: innerAngles.end }),\n };\n}\n\n/**\n * Constrains a requested ring corner radius to the available geometry.\n *\n * @param {object} state - Ring geometry and its resolved endpoints.\n * @param {object} state.geometry - Ring radii, angles, and corner request.\n * @param {object} state.points - Inner and outer sector endpoints.\n * @returns {number} Non-overlapping corner radius.\n */\nfunction ringCornerRadius({ geometry, points }) {\n const startSide = Math.hypot(\n points.outerStart.x - points.innerStart.x,\n points.outerStart.y - points.innerStart.y,\n );\n\n const endSide = Math.hypot(points.outerEnd.x - points.innerEnd.x, points.outerEnd.y - points.innerEnd.y);\n\n return Math.min(\n geometry.cornerRadius,\n startSide / 2,\n endSide / 2,\n (geometry.radii.outer * (geometry.angles.outer.end - geometry.angles.outer.start)) / 2,\n (geometry.radii.inner * (geometry.angles.inner.end - geometry.angles.inner.start)) / 2,\n );\n}\n\n/**\n * Resolves rounded arc endpoints for an annular sector.\n *\n * @param {object} geometry - Complete rounded ring geometry.\n * @param {number} appliedRadius - Constrained corner radius.\n * @returns {object} Rounded arc endpoints and angular deltas.\n */\nfunction roundedRingArcs(geometry, appliedRadius) {\n const outerDelta = Math.asin(Math.min(1, appliedRadius / geometry.radii.outer));\n const innerDelta = Math.asin(Math.min(1, appliedRadius / geometry.radii.inner));\n\n return {\n outerDelta,\n innerDelta,\n outerStart: polarPoint({\n cx: geometry.center.x,\n cy: geometry.center.y,\n radius: geometry.radii.outer,\n angle: geometry.angles.outer.start + outerDelta,\n }),\n outerEnd: polarPoint({\n cx: geometry.center.x,\n cy: geometry.center.y,\n radius: geometry.radii.outer,\n angle: geometry.angles.outer.end - outerDelta,\n }),\n innerEnd: polarPoint({\n cx: geometry.center.x,\n cy: geometry.center.y,\n radius: geometry.radii.inner,\n angle: geometry.angles.inner.end - innerDelta,\n }),\n innerStart: polarPoint({\n cx: geometry.center.x,\n cy: geometry.center.y,\n radius: geometry.radii.inner,\n angle: geometry.angles.inner.start + innerDelta,\n }),\n };\n}\n\n/**\n * Builds a radius-aware rounded annular sector.\n *\n * @param {object} geometry - Complete rounded ring geometry.\n * @returns {string} Closed rounded ring path.\n */\nfunction roundedRingPath(geometry) {\n const points = ringPoints({\n center: geometry.center,\n radii: geometry.radii,\n outerAngles: geometry.angles.outer,\n innerAngles: geometry.angles.inner,\n });\n\n const appliedRadius = ringCornerRadius({ geometry, points });\n const arcs = roundedRingArcs(geometry, appliedRadius);\n\n const sides = {\n outerEnd: pointToward(points.outerEnd, points.innerEnd, appliedRadius),\n innerEnd: pointToward(points.innerEnd, points.outerEnd, appliedRadius),\n innerStart: pointToward(points.innerStart, points.outerStart, appliedRadius),\n outerStart: pointToward(points.outerStart, points.innerStart, appliedRadius),\n };\n\n const outerLarge =\n geometry.angles.outer.end - geometry.angles.outer.start - arcs.outerDelta * 2 > Math.PI ? 1 : 0;\n\n const innerLarge =\n geometry.angles.inner.end - geometry.angles.inner.start - arcs.innerDelta * 2 > Math.PI ? 1 : 0;\n\n return `M${arcs.outerStart.x},${arcs.outerStart.y}A${geometry.radii.outer},${geometry.radii.outer} 0 ${outerLarge} 1 ${arcs.outerEnd.x},${arcs.outerEnd.y}Q${points.outerEnd.x},${points.outerEnd.y} ${sides.outerEnd.x},${sides.outerEnd.y}L${sides.innerEnd.x},${sides.innerEnd.y}Q${points.innerEnd.x},${points.innerEnd.y} ${arcs.innerEnd.x},${arcs.innerEnd.y}A${geometry.radii.inner},${geometry.radii.inner} 0 ${innerLarge} 0 ${arcs.innerStart.x},${arcs.innerStart.y}Q${points.innerStart.x},${points.innerStart.y} ${sides.innerStart.x},${sides.innerStart.y}L${sides.outerStart.x},${sides.outerStart.y}Q${points.outerStart.x},${points.outerStart.y} ${arcs.outerStart.x},${arcs.outerStart.y}Z`;\n}\n\n/**\n * Builds a sector or ring segment with radius-aware rounded corners.\n *\n * @param {object} geometry - Values describing one rounded sector.\n * @returns {string} Closed SVG path data with non-overlapping rounded corners.\n */\nfunction roundedSectorPath(geometry) {\n if (geometry.cornerRadius <= 0 && geometry.radii.inner > 0) {\n return ringPath({\n center: geometry.center,\n radii: geometry.radii,\n outerAngles: geometry.angles.outer,\n innerAngles: geometry.angles.inner,\n });\n }\n\n if (geometry.cornerRadius <= 0) {\n return arcPath({\n center: geometry.center,\n radius: geometry.radii.outer,\n angles: geometry.angles.outer,\n });\n }\n\n if (geometry.radii.inner <= 0) {\n return roundedSolidSectorPath({\n center: geometry.center,\n radius: geometry.radii.outer,\n angles: geometry.angles.outer,\n cornerRadius: geometry.cornerRadius,\n });\n }\n\n return roundedRingPath(geometry);\n}\n\n/**\n * Insets a sector's arcs to create a stable visual gap between neighbours.\n *\n * @param {object} geometry - Angular interval, radii, and padding policy.\n * @param {{start: number, end: number}} geometry.angles - Unpadded interval.\n * @param {{outer: number, inner: number}} geometry.radii - Sector boundary radii.\n * @param {{angle: number, count: number}} geometry.padding - Gap and neighbour count.\n * @returns {{outer: {start: number, end: number}, inner: {start: number, end: number}}} Insets constrained to preserve a visible sweep.\n */\nfunction paddedSector({ angles, radii, padding }) {\n const sweep = angles.end - angles.start;\n\n if (padding.count <= 1 || padding.angle === 0 || sweep <= 0) {\n return {\n outer: { ...angles },\n inner: { ...angles },\n };\n }\n\n const halfPad = (padding.angle * Math.PI) / DEGREES_PER_CIRCLE;\n const padRadius = Math.hypot(radii.outer, radii.inner);\n\n const radiusInset = (radius) => {\n if (radius <= 0) {\n return 0;\n }\n\n return Math.asin(Math.min(1, (padRadius / radius) * Math.sin(halfPad)));\n };\n\n const maximumInset = Math.max(0, (sweep - MIN_SECTOR_SWEEP) / 2);\n const outerInset = Math.min(radiusInset(radii.outer), maximumInset);\n const innerInset = Math.min(radiusInset(radii.inner), maximumInset);\n\n return {\n outer: { start: angles.start + outerInset, end: angles.end - outerInset },\n inner: { start: angles.start + innerInset, end: angles.end - innerInset },\n };\n}\n\nexport { arcPath, paddedSector, pointToward, polarPoint, ringPath, roundedSectorPath };\n"],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{formatNumber as t}from"../Dom.js";function r(t,r,e={}){const o=e.point?Object.freeze({...e.point}):void 0;return Object.freeze({target:r,chartType:t.type,...e,point:o})}function e(t,r){if("string"!=typeof t)throw new TypeError(`${r} formatter must return a string`);return t}function o(t){if("string"==typeof t)return t;if(Array.isArray(t)&&t.length>0&&t.every(t=>"string"==typeof t))return Object.freeze([...t]);throw new TypeError("Label formatter must return a string or a non-empty string array")}function n(t,e,n){const a=("tooltip"===n.target?t.tooltipFormatLabel:void 0)??t.formatLabel;if(!a)return String(e);const i=o(a(e,r(t,n.target,{...n,label:e})));return Array.isArray(i)&&"axis"!==n.target?i.join(" "):i}function a(o,n,a){const i=["axis"===a.target?void 0:a.dataset?.formatValue,"tooltip"===a.target?o.tooltipFormatValue:void 0,"axis"===a.target?o.axisFormatValue:void 0,o.formatValue].find(t=>"function"==typeof t);if(!i)return t(n);const f={...a};return Reflect.deleteProperty(f,"dataset"),e(i(n,r(o,a.target,f)),"Value")}export{r as formatContext,n as formatLabel,a as formatValue,o as formatterLabel,e as formatterText};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"Formatting.js","names":[],"sources":["../../../src/support/presentation/Formatting.js"],"sourcesContent":["import { formatNumber } from \"../Dom.js\";\n\n/**\n * Creates one frozen formatter context with a frozen point when present.\n *\n * @param {object} options - Current chart options.\n * @param {string} target - Display surface requesting text.\n * @param {object} [details={}] - Dataset and point identity.\n * @returns {object} Frozen public formatter context.\n */\nfunction formatContext(options, target, details = {}) {\n const point = details.point ? Object.freeze({ ...details.point }) : undefined;\n\n return Object.freeze({ target, chartType: publicChartType(options.type), ...details, point });\n}\n\n/**\n * Hides the transitional internal mixed renderer name from callbacks.\n *\n * @param {string} type - Internal renderer chart type.\n * @returns {string} Renderer-only mixed naming mapped onto the public union.\n */\nfunction publicChartType(type) {\n return type;\n}\n\n/**\n * Requires formatter results to remain plain synchronous strings.\n *\n * @param {unknown} value - Formatter result.\n * @param {string} scope - Public formatter scope.\n * @returns {string} Valid display text.\n */\nfunction formatterText(value, scope) {\n if (typeof value !== \"string\") {\n throw new TypeError(`${scope} formatter must return a string`);\n }\n\n return value;\n}\n\n/**\n * Accepts either one label or explicit lines for a wrapped category label.\n *\n * @param {unknown} value - Label formatter result.\n * @returns {string | readonly string[]} Valid display label.\n */\nfunction formatterLabel(value) {\n if (typeof value === \"string\") {\n return value;\n }\n\n if (Array.isArray(value) && value.length > 0 && value.every((line) => typeof line === \"string\")) {\n return Object.freeze([\n ...value,\n ]);\n }\n\n throw new TypeError(\"Label formatter must return a string or a non-empty string array\");\n}\n\n/**\n * Formats one category label for an axis, visible label, or tooltip.\n *\n * @param {object} options - Current chart options.\n * @param {unknown} label - Raw category label.\n * @param {object} details - Target and source identity.\n * @returns {string | readonly string[]} Display text or explicit axis-label lines.\n */\nfunction formatLabel(options, label, details) {\n const tooltipFormatter = details.target === \"tooltip\" ? options.tooltipFormatLabel : undefined;\n const formatter = tooltipFormatter ?? options.formatLabel;\n\n if (!formatter) {\n return String(label);\n }\n\n const formatted = formatterLabel(\n formatter(label, formatContext(options, details.target, { ...details, label })),\n );\n\n if (Array.isArray(formatted) && details.target !== \"axis\") {\n return formatted.join(\" \");\n }\n\n return formatted;\n}\n\n/**\n * Formats one numeric value using dataset, surface, chart, then built-in precedence.\n *\n * @param {object} options - Current chart options.\n * @param {number} value - Raw numeric value.\n * @param {object} details - Target, dataset, and point identity.\n * @returns {string} Plain display text.\n */\nfunction formatValue(options, value, details) {\n const datasetFormatter = details.target === \"axis\" ? undefined : details.dataset?.formatValue;\n const tooltipFormatter = details.target === \"tooltip\" ? options.tooltipFormatValue : undefined;\n const axisFormatter = details.target === \"axis\" ? options.axisFormatValue : undefined;\n\n const formatter = [\n datasetFormatter,\n tooltipFormatter,\n axisFormatter,\n options.formatValue,\n ].find((candidate) => typeof candidate === \"function\");\n\n if (!formatter) {\n return formatNumber(value);\n }\n\n const contextDetails = { ...details };\n Reflect.deleteProperty(contextDetails, \"dataset\");\n\n return formatterText(formatter(value, formatContext(options, details.target, contextDetails)), \"Value\");\n}\n\nexport { formatContext, formatLabel, formatValue, formatterLabel, formatterText };\n"],"mappings":"yCAUA,SAAS,EAAc,EAAS,EAAQ,EAAU,CAAC,GACjD,MAAM,EAAQ,EAAQ,MAAQ,OAAO,OAAO,IAAK,EAAQ,aAAW,EAEpE,OAAO,OAAO,OAAO,CAAE,SAAQ,UAA2B,EAAQ,QAAU,EAAS,SACvF,CAmBA,SAAS,EAAc,EAAO,GAC5B,GAAqB,iBAAV,EACT,MAAM,IAAI,UAAU,GAAG,oCAGzB,OAAO,CACT,CAQA,SAAS,EAAe,GACtB,GAAqB,iBAAV,EACT,OAAO,EAGT,GAAI,MAAM,QAAQ,IAAU,EAAM,OAAS,GAAK,EAAM,MAAO,GAAyB,iBAAT,GAC3E,OAAO,OAAO,OAAO,IAChB,IAIP,MAAM,IAAI,UAAU,mEACtB,CAUA,SAAS,EAAY,EAAS,EAAO,GAEnC,MAAM,GADsC,YAAnB,EAAQ,OAAuB,EAAQ,wBAAqB,IAC/C,EAAQ,YAE9C,IAAK,EACH,OAAO,OAAO,GAGhB,MAAM,EAAY,EAChB,EAAU,EAAO,EAAc,EAAS,EAAQ,OAAQ,IAAK,EAAS,YAGxE,OAAI,MAAM,QAAQ,IAAiC,SAAnB,EAAQ,OAC/B,EAAU,KAAK,KAGjB,CACT,CAUA,SAAS,EAAY,EAAS,EAAO,GAKnC,MAAM,EAAY,CAJ0B,SAAnB,EAAQ,YAAoB,EAAY,EAAQ,SAAS,YACtC,YAAnB,EAAQ,OAAuB,EAAQ,wBAAqB,EAC5C,SAAnB,EAAQ,OAAoB,EAAQ,qBAAkB,EAM1E,EAAQ,aACR,KAAM,GAAmC,mBAAd,GAE7B,IAAK,EACH,OAAO,EAAa,GAGtB,MAAM,EAAiB,IAAK,GAG5B,OAFA,QAAQ,eAAe,EAAgB,WAEhC,EAAc,EAAU,EAAO,EAAc,EAAS,EAAQ,OAAQ,IAAkB,QACjG"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{formatNumber as t,measuredLegendTextWidth as n,measuredTextWidth as a,truncateText as e}from"../Dom.js";import{extent as i}from"../geometry/Scale.js";import{formatLabel as o,formatValue as r}from"./Formatting.js";function s(t,[n,a],e){if(n===a)return 0===t?0:e-1;const i=(t-n)/(a-n);return Math.min(e-1,Math.floor(i*e))}function m(t,n,a){return o(t,n,{target:"axis",index:a})}function l(t,n){const e=t.some(t=>Array.isArray(t))?.55:.42,i=Math.max(24,Math.min(176,n*e)),o=i-4-4,r=t.map(t=>Math.min(o,Math.ceil(Math.max(...(Array.isArray(t)?t:[String(t)]).map(t=>a(t.trim()))))));return Math.max(24,Math.min(i,4+Math.max(0,...r)+4))}function c(n,e){const i=Math.max(0,...n.map(n=>a(t(n))));return Math.max(e,Math.ceil(i+5+4))}function p(t,a){const i=Math.max(1,t-12-8);let o=12,r=1;return{labelOffset:16,positions:a.map(a=>{const s=Math.max(1,Math.min(160,i-16)),m=e(a.label,s),l=Math.min(i,16+n(m));o+l>t-8&&o>12&&(o=12,r+=1);const c={itemWidth:l,labelMaxWidth:s,x:o,yOffset:20*(r-1)};return o+=l+16,c}),rows:r}}function x({width:t,height:n,items:a,legend:e}){const i=e?p(t,a).rows:0,o=i>0?n-18-20*(i-1):null,r=null===o?n-16:o-20,s=Math.max(24,r);return{contentTop:16,contentBottom:s,contentHeight:s-16,legendBaseline:o}}function h(n,a,{options:e={},datasetIndex:s=0}={}){const m=(t,i)=>`${o(e,a[i]??t.x,{target:"accessibility",datasetIndex:s,index:i,point:t})}: ${r(e,t.y,{target:"accessibility",dataset:n,datasetIndex:s,index:i,label:a[i]??t.x,point:t})}`;if(n.points.length>12){const[a,e]=i(n.points.map(t=>t.y)),o=m(n.points[0],0),r=m(n.points.at(-1),n.points.length-1);return`${n.name}: ${n.points.length} points · ${o} · ${r} · range ${t(a)}–${t(e)}`}const l=n.points.map((t,n)=>m(t,n));return`${n.name}: ${l.join(", ")}`}function d(t){const{options:n,label:a,value:e,suffix:i="",dataset:s,datasetIndex:m,index:l,point:c}=t;return`${o(n,a,{target:"tooltip",datasetIndex:m,index:l,point:c})}: ${r(n,e,{target:"tooltip",dataset:s,datasetIndex:m,index:l,label:a,point:c})}${i}`}export{x as aggregationLayout,h as datasetSummary,m as formatCategoryLabel,l as horizontalCategoryPadding,s as intensityLevel,p as legendLayout,d as tooltipText,c as verticalValuePadding};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"Presentation.js","names":[],"sources":["../../../src/support/presentation/Presentation.js"],"sourcesContent":["import {\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} from \"../Constants.js\";\nimport { formatNumber, truncateText, measuredTextWidth, measuredLegendTextWidth } from \"../Dom.js\";\nimport { extent } from \"../geometry/Math.js\";\n\nimport { formatLabel, formatValue } from \"./Formatting.js\";\n\nconst AGGREGATION_MINIMUM_CONTENT_HEIGHT = 8;\nconst HORIZONTAL_LABEL_MAXIMUM_PADDING = 176;\nconst HORIZONTAL_LABEL_MINIMUM_PADDING = 24;\nconst HORIZONTAL_LABEL_WIDTH_RATIO = 0.42;\nconst HORIZONTAL_MULTILINE_LABEL_WIDTH_RATIO = 0.55;\nconst INITIAL_LEGEND_ROW_COUNT = 1;\nconst LEGEND_ITEM_GAP = 16;\nconst LEGEND_LEFT_INSET = 12;\nconst LEGEND_MAXIMUM_LABEL_WIDTH = 160;\nconst LEGEND_RIGHT_INSET = 8;\nconst VALUE_LABEL_TRAILING_INSET = 4;\nconst DATASET_SUMMARY_LIMIT = 12;\n\n/**\n * Maps one heatmap value onto the complete ordered intensity palette.\n *\n * @param {number} value - Normalized heatmap value.\n * @param {[number, number]} domain - Complete data minimum and maximum.\n * @param {number} colorCount - Number of supplied intensity colors.\n * @returns {number} Bounded zero-based palette bucket.\n */\nfunction intensityLevel(\n value,\n [\n minimum,\n maximum,\n ],\n colorCount,\n) {\n if (minimum === maximum) {\n return value === 0 ? 0 : colorCount - 1;\n }\n\n const ratio = (value - minimum) / (maximum - minimum);\n\n return Math.min(colorCount - 1, Math.floor(ratio * colorCount));\n}\n\n/**\n * Applies the optional category formatter and validates its display contract.\n *\n * @param {object} options - Chart options containing axis orientation and formatter hooks.\n * @param {unknown} label - Original category value supplied by the dataset.\n * @param {number} index - Zero-based category position.\n * @returns {string | string[]} Validated single-line or multi-line category label.\n * @throws {TypeError} When a formatter returns an unsupported value.\n */\nfunction formatCategoryLabel(options, label, index) {\n return formatLabel(options, label, { target: \"axis\", index });\n}\n\n/**\n * Reserves enough left-side space for bounded horizontal category labels.\n *\n * @param {Array<string | string[]>} labels - Formatted labels that appear beside bars.\n * @param {number} width - Total chart width in pixels.\n * @returns {number} Clamped left padding required by the widest visible label.\n */\nfunction horizontalCategoryPadding(labels, width) {\n const widthRatio = labels.some((label) => Array.isArray(label))\n ? HORIZONTAL_MULTILINE_LABEL_WIDTH_RATIO\n : HORIZONTAL_LABEL_WIDTH_RATIO;\n\n const maximum = Math.max(\n HORIZONTAL_LABEL_MINIMUM_PADDING,\n Math.min(HORIZONTAL_LABEL_MAXIMUM_PADDING, width * widthRatio),\n );\n\n const maximumLabelWidth = maximum - HORIZONTAL_LABEL_EDGE_INSET - HORIZONTAL_LABEL_GAP;\n\n const displayedWidths = labels.map((label) => {\n const lines = Array.isArray(label)\n ? label\n : [\n String(label),\n ];\n\n return Math.min(\n maximumLabelWidth,\n Math.ceil(Math.max(...lines.map((line) => measuredTextWidth(line.trim())))),\n );\n });\n\n return Math.max(\n HORIZONTAL_LABEL_MINIMUM_PADDING,\n Math.min(maximum, HORIZONTAL_LABEL_EDGE_INSET + Math.max(0, ...displayedWidths) + HORIZONTAL_LABEL_GAP),\n );\n}\n\n/**\n * Expands value-axis padding to prevent formatted ticks from clipping.\n *\n * @param {number[]} ticks - Values rendered along the vertical axis.\n * @param {number} basePadding - Minimum padding required by the chart layout.\n * @returns {number} Pixel padding large enough for the widest tick label.\n */\nfunction verticalValuePadding(ticks, basePadding) {\n const maximumLabelWidth = Math.max(0, ...ticks.map((value) => measuredTextWidth(formatNumber(value))));\n\n return Math.max(basePadding, Math.ceil(maximumLabelWidth + VALUE_LABEL_GAP + VALUE_LABEL_TRAILING_INSET));\n}\n\n/**\n * Packs legend items into deterministic rows using measured visible labels.\n *\n * @param {number} width - Available legend width in pixels.\n * @param {Array<{label: string}>} items - Ordered legend entries to position.\n * @returns {{labelOffset: number, positions: Array<object>, rows: number}} Shared label offset, item positions, and occupied row count.\n */\nfunction legendLayout(width, items) {\n const left = LEGEND_LEFT_INSET;\n const right = LEGEND_RIGHT_INSET;\n const labelOffset = SERIES_SWATCH_DIAMETER + LEGEND_LABEL_GAP;\n const itemGap = LEGEND_ITEM_GAP;\n const maximumItemWidth = Math.max(1, width - left - right);\n let x = LEGEND_LEFT_INSET;\n let rows = INITIAL_LEGEND_ROW_COUNT;\n\n const positions = items.map((item) => {\n const labelMaxWidth = Math.max(1, Math.min(LEGEND_MAXIMUM_LABEL_WIDTH, maximumItemWidth - labelOffset));\n const visibleLabel = truncateText(item.label, labelMaxWidth);\n const itemWidth = Math.min(maximumItemWidth, labelOffset + measuredLegendTextWidth(visibleLabel));\n\n if (x + itemWidth > width - LEGEND_RIGHT_INSET && x > LEGEND_LEFT_INSET) {\n x = left;\n rows += 1;\n }\n\n const position = { itemWidth, labelMaxWidth, x, yOffset: (rows - 1) * LEGEND_ROW_HEIGHT };\n x += itemWidth + itemGap;\n\n return position;\n });\n\n return {\n labelOffset,\n positions,\n rows,\n };\n}\n\n/**\n * Divides aggregation charts between visualization content and an optional legend.\n *\n * @param {object} specification - Aggregation viewport and legend settings.\n * @param {number} specification.width - Available chart width in pixels.\n * @param {number} specification.height - Available chart height in pixels.\n * @param {Array<{label: string}>} specification.items - Legend entries used to calculate row count.\n * @param {boolean} specification.legend - Whether layout must reserve legend space.\n * @returns {{contentTop: number, contentBottom: number, contentHeight: number, legendBaseline: number | null}} Vertical content bounds and optional legend baseline.\n */\nfunction aggregationLayout({ width, height, items, legend }) {\n const legendRows = legend ? legendLayout(width, items).rows : 0;\n\n const legendBaseline =\n legendRows > 0 ? height - AGGREGATION_LEGEND_BASELINE_INSET - (legendRows - 1) * LEGEND_ROW_HEIGHT : null;\n\n const contentTop = AGGREGATION_INSET;\n\n const requestedBottom =\n legendBaseline === null ? height - AGGREGATION_INSET : legendBaseline - AGGREGATION_LEGEND_GAP;\n\n const contentBottom = Math.max(contentTop + AGGREGATION_MINIMUM_CONTENT_HEIGHT, requestedBottom);\n\n return {\n contentTop,\n contentBottom,\n contentHeight: contentBottom - contentTop,\n legendBaseline,\n };\n}\n\n/**\n * Summarizes a dataset for accessibility without overwhelming long series.\n *\n * @param {{name: string, points: Array<{x: number, y: number}>}} dataset - Normalized series to describe.\n * @param {unknown[]} labels - Optional category values corresponding to points.\n * @param {object} [formatting={}] - Formatter options and stable dataset identity.\n * @param {object} [formatting.options={}] - Chart-level formatter options.\n * @param {number} [formatting.datasetIndex=0] - Stable dataset position.\n * @returns {string} Detailed short-series summary or bounded long-series statistics.\n */\nfunction datasetSummary(dataset, labels, { options = {}, datasetIndex = 0 } = {}) {\n const formatPoint = (point, index) => {\n const label = formatLabel(options, labels[index] ?? point.x, {\n target: \"accessibility\",\n datasetIndex,\n index,\n point,\n });\n\n const value = formatValue(options, point.y, {\n target: \"accessibility\",\n dataset,\n datasetIndex,\n index,\n label: labels[index] ?? point.x,\n point,\n });\n\n return `${label}: ${value}`;\n };\n\n if (dataset.points.length > DATASET_SUMMARY_LIMIT) {\n const [\n minimum,\n maximum,\n ] = extent(dataset.points.map((point) => point.y));\n\n const first = formatPoint(dataset.points[0], 0);\n const last = formatPoint(dataset.points.at(-1), dataset.points.length - 1);\n\n return `${dataset.name}: ${dataset.points.length} points · ${first} · ${last} · range ${formatNumber(minimum)}–${formatNumber(maximum)}`;\n }\n\n const values = dataset.points.map((point, index) => formatPoint(point, index));\n\n return `${dataset.name}: ${values.join(\", \")}`;\n}\n\n/**\n * Formats a tooltip pair through caller hooks and a stable numeric fallback.\n *\n * @param {object} content - Source values and formatting options for one tooltip pair.\n * @param {object} content.options - Chart options containing optional tooltip formatters.\n * @param {unknown} content.label - Source x or category value.\n * @param {number} content.value - Numeric y value to present.\n * @param {string} [content.suffix=\"\"] - Unit or contextual text appended to the value.\n * @param {object} [content.dataset] - Dataset-local formatter owner.\n * @param {number} [content.datasetIndex] - Stable dataset position.\n * @param {number} [content.index] - Stable point position.\n * @param {object} [content.point] - Normalized source point.\n * @returns {string} Complete tooltip text in `label: value` form.\n */\nfunction tooltipText(content) {\n const { options, label, value, suffix = \"\", dataset, datasetIndex, index, point } = content;\n const x = formatLabel(options, label, { target: \"tooltip\", datasetIndex, index, point });\n const y = formatValue(options, value, { target: \"tooltip\", dataset, datasetIndex, index, label, point });\n\n return `${x}: ${y}${suffix}`;\n}\n\nexport {\n intensityLevel,\n formatCategoryLabel,\n horizontalCategoryPadding,\n verticalValuePadding,\n legendLayout,\n aggregationLayout,\n datasetSummary,\n tooltipText,\n};\n"],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{DAY as t,HOUR as n,TIME_TICK_STEPS as e}from"../Constants.js";import{formatterText as i}from"./Formatting.js";function r(t,n,i){const r=n-t,o=e.find(t=>r/t<=i-1)??e.at(-1),m=[];for(let e=t+o;e<n;e+=o)m.push(e);return[.../* @__PURE__ */new Set([t,...m,n])]}function o(t,n,e){const r=new Date(t);if(e)return i(e(r),"Timesheet tick date");const o=function(t){return t<=1728e5?{hour:"numeric",minute:"2-digit"}:t<=7776e6?{month:"short",day:"numeric"}:t<=63072e6?{month:"short",year:"2-digit"}:{year:"numeric"}}(n);return new Intl.DateTimeFormat(void 0,o).format(r)}function m(t,n){return n?i(n(new Date(t)),"Timesheet tooltip date"):new Intl.DateTimeFormat(void 0,{month:"short",day:"numeric",hour:"numeric",minute:"2-digit"}).format(t)}function u(e,r){if(r)return i(r(e),"Timesheet tooltip duration");const o=e>=t,m=e/(o?t:n),u=o?"day":"hour";return new Intl.NumberFormat(void 0,{style:"unit",unit:u,unitDisplay:"long",maximumFractionDigits:1}).format(m)}export{o as formatTimeTick,m as formatTimesheetDate,u as formatTimesheetDuration,r as timeTicks};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"Time.js","names":[],"sources":["../../../src/support/presentation/Time.js"],"sourcesContent":["import { HOUR, DAY, TIME_TICK_STEPS } from \"../Constants.js\";\n\nimport { formatterText } from \"./Formatting.js\";\n\nconst DAYS_PER_YEAR = 365;\nconst QUARTER_YEAR_DAYS = 90;\n\n/**\n * Produces bounded timestamps while preserving the exact requested endpoints.\n *\n * @param {number} start - Inclusive range start as Unix milliseconds.\n * @param {number} end - Inclusive range end as Unix milliseconds.\n * @param {number} maximumTicks - Preferred upper bound for the number of ticks.\n * @returns {number[]} Ordered unique timestamps suitable for an axis.\n */\nfunction timeTicks(start, end, maximumTicks) {\n const span = end - start;\n\n const step =\n TIME_TICK_STEPS.find((candidate) => span / candidate <= maximumTicks - 1) ?? TIME_TICK_STEPS.at(-1);\n\n const interior = [];\n\n for (let value = start + step; value < end; value += step) {\n interior.push(value);\n }\n\n return [\n ...new Set([\n start,\n ...interior,\n end,\n ]),\n ];\n}\n\n/**\n * Formats a time-axis timestamp at a precision appropriate for the visible span.\n *\n * @param {number} value - Timestamp to format as Unix milliseconds.\n * @param {number} span - Visible duration in milliseconds.\n * @param {((date: Date) => string) | undefined} formatter - Optional caller-provided formatter.\n * @returns {string} Localized or caller-formatted tick label.\n */\nfunction formatTimeTick(value, span, formatter) {\n const date = new Date(value);\n\n if (formatter) {\n return formatterText(formatter(date), \"Timesheet tick date\");\n }\n\n const options = timeFormatOptions(span);\n\n return new Intl.DateTimeFormat(undefined, options).format(date);\n}\n\n/**\n * Selects date-time fields appropriate for a visible time span.\n *\n * @param {number} span - Visible duration in milliseconds.\n * @returns {Intl.DateTimeFormatOptions} Bounded localized date-time format.\n */\nfunction timeFormatOptions(span) {\n if (span <= 2 * DAY) {\n return {\n hour: \"numeric\",\n minute: \"2-digit\",\n };\n }\n\n if (span <= QUARTER_YEAR_DAYS * DAY) {\n return {\n month: \"short\",\n day: \"numeric\",\n };\n }\n\n if (span <= 2 * DAYS_PER_YEAR * DAY) {\n return {\n month: \"short\",\n year: \"2-digit\",\n };\n }\n\n return { year: \"numeric\" };\n}\n\n/**\n * Formats a task boundary for display in a timesheet tooltip.\n *\n * @param {Date | string | number} date - Task boundary accepted by the Date constructor.\n * @param {((date: Date) => string) | undefined} formatter - Optional caller-provided formatter.\n * @returns {string} Localized or caller-formatted task boundary.\n */\nfunction formatTimesheetDate(date, formatter) {\n if (formatter) {\n return formatterText(formatter(new Date(date)), \"Timesheet tooltip date\");\n }\n\n return new Intl.DateTimeFormat(undefined, {\n month: \"short\",\n day: \"numeric\",\n hour: \"numeric\",\n minute: \"2-digit\",\n }).format(date);\n}\n\n/**\n * Formats a duration using a human-scale day or hour unit.\n *\n * @param {number} milliseconds - Non-negative task duration in milliseconds.\n * @param {((duration: number) => string) | undefined} formatter - Optional caller-provided formatter.\n * @returns {string} Localized or caller-formatted duration label.\n */\nfunction formatTimesheetDuration(milliseconds, formatter) {\n if (formatter) {\n return formatterText(formatter(milliseconds), \"Timesheet tooltip duration\");\n }\n\n const isMeasuredInDays = milliseconds >= DAY;\n const value = milliseconds / (isMeasuredInDays ? DAY : HOUR);\n const unit = isMeasuredInDays ? \"day\" : \"hour\";\n\n return new Intl.NumberFormat(undefined, {\n style: \"unit\",\n unit,\n unitDisplay: \"long\",\n maximumFractionDigits: 1,\n }).format(value);\n}\n\nexport { timeTicks, formatTimeTick, formatTimesheetDate, formatTimesheetDuration };\n"],"mappings":"qHAeA,SAAS,EAAU,EAAO,EAAK,GAC7B,MAAM,EAAO,EAAM,EAEb,EACJ,EAAgB,KAAM,GAAc,EAAO,GAAa,EAAe,IAAM,EAAgB,IAAG,GAE5F,EAAW,GAEjB,IAAK,IAAI,EAAQ,EAAQ,EAAM,EAAQ,EAAK,GAAS,EACnD,EAAS,KAAK,GAGhB,MAAO,mBACF,IAAI,IAAI,CACT,KACG,EACH,IAGN,CAUA,SAAS,EAAe,EAAO,EAAM,GACnC,MAAM,EAAO,IAAI,KAAK,GAEtB,GAAI,EACF,OAAO,EAAc,EAAU,GAAO,uBAGxC,MAAM,EAWR,SAA2B,GACzB,OAAI,GAAQ,OACH,CACL,KAAM,UACN,OAAQ,WAIR,GAAQ,OACH,CACL,MAAO,QACP,IAAK,WAIL,GAAQ,QACH,CACL,MAAO,QACP,KAAM,WAIH,CAAE,KAAM,UACjB,CAlCkB,CAAkB,GAElC,OAAO,IAAI,KAAK,oBAAe,EAAW,GAAS,OAAO,EAC5D,CAwCA,SAAS,EAAoB,EAAM,GACjC,OAAI,EACK,EAAc,EAAU,IAAI,KAAK,IAAQ,0BAG3C,IAAI,KAAK,oBAAe,EAAW,CACxC,MAAO,QACP,IAAK,UACL,KAAM,UACN,OAAQ,YACP,OAAO,EACZ,CASA,SAAS,EAAwB,EAAc,GAC7C,GAAI,EACF,OAAO,EAAc,EAAU,GAAe,8BAGhD,MAAM,EAAmB,GAAgB,EACnC,EAAQ,GAAgB,EAAmB,EAAM,GACjD,EAAO,EAAmB,MAAQ,OAExC,OAAO,IAAI,KAAK,kBAAa,EAAW,CACtC,MAAO,OACP,OACA,YAAa,OACb,sBAAuB,IACtB,OAAO,EACZ"}
|
|
@@ -0,0 +1,322 @@
|
|
|
1
|
+
# API reference
|
|
2
|
+
|
|
3
|
+
This page is a compact map of the public Charts2 API. Begin with
|
|
4
|
+
[Getting started](./getting-started.md) if you have not rendered a chart yet.
|
|
5
|
+
|
|
6
|
+
## Imports
|
|
7
|
+
|
|
8
|
+
```js
|
|
9
|
+
import {
|
|
10
|
+
LineChart,
|
|
11
|
+
BarChart,
|
|
12
|
+
ScatterChart,
|
|
13
|
+
MixedChart,
|
|
14
|
+
BubbleChart,
|
|
15
|
+
PieChart,
|
|
16
|
+
DonutChart,
|
|
17
|
+
PercentageChart,
|
|
18
|
+
RadarChart,
|
|
19
|
+
PolarAreaChart,
|
|
20
|
+
HeatmapChart,
|
|
21
|
+
TimesheetChart,
|
|
22
|
+
} from "@orchidsoftware/charts";
|
|
23
|
+
|
|
24
|
+
import "@orchidsoftware/charts/style.css";
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
Each definition exposes `make(parent)`, where `parent` is a CSS selector or an
|
|
28
|
+
`Element`. Configuration methods return the same builder, and `render()` mounts
|
|
29
|
+
the chart.
|
|
30
|
+
|
|
31
|
+
## Methods shared by every chart
|
|
32
|
+
|
|
33
|
+
| Method | Result |
|
|
34
|
+
| -------------------- | --------------------------------------------------- |
|
|
35
|
+
| `title(text)` | Shows a visible title. |
|
|
36
|
+
| `description(text)` | Adds a longer accessible SVG description. |
|
|
37
|
+
| `ariaLabel(text)` | Sets the chart's accessible name. |
|
|
38
|
+
| `width(pixels)` | Uses an explicit width instead of the host width. |
|
|
39
|
+
| `height(pixels)` | Sets the chart height. |
|
|
40
|
+
| `colors(palette)` | Sets the ordered chart palette. |
|
|
41
|
+
| `tooltip(false)` | Hides the tooltip. |
|
|
42
|
+
| `tooltip(callback)` | Formats tooltip content. |
|
|
43
|
+
| `onSelect(callback)` | Receives a selection or `undefined` when it clears. |
|
|
44
|
+
| `render()` | Mounts the chart and returns its runtime API. |
|
|
45
|
+
|
|
46
|
+
Charts with a legend also provide `legend(false)`.
|
|
47
|
+
|
|
48
|
+
## Series data
|
|
49
|
+
|
|
50
|
+
Line, bar, scatter, bubble, radar, pie, donut, percentage, and polar area charts
|
|
51
|
+
use `dataset()`:
|
|
52
|
+
|
|
53
|
+
```js
|
|
54
|
+
chart
|
|
55
|
+
.labels(["Jan", "Feb", "Mar"])
|
|
56
|
+
.dataset([42, 48, 57])
|
|
57
|
+
.dataset("Plan", [45, 50, 55])
|
|
58
|
+
.dataset("Previous", [38, 41, 49], "#94a3b8");
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
Accepted forms are:
|
|
62
|
+
|
|
63
|
+
```text
|
|
64
|
+
dataset(values)
|
|
65
|
+
dataset(values, color)
|
|
66
|
+
dataset(values, callback)
|
|
67
|
+
dataset(name, values)
|
|
68
|
+
dataset(name, values, color)
|
|
69
|
+
dataset(name, values, callback)
|
|
70
|
+
dataset(input, callback?)
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
A numeric dataset input has this shape:
|
|
74
|
+
|
|
75
|
+
```js
|
|
76
|
+
{
|
|
77
|
+
name: "Revenue", // optional
|
|
78
|
+
values: [42, 48, 57],
|
|
79
|
+
color: "#2563eb", // optional
|
|
80
|
+
opacity: 0.8, // optional, 0–1
|
|
81
|
+
formatValue(value, context) { return String(value); } // optional
|
|
82
|
+
}
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
`labels()` is optional. When omitted, Charts2 generates positional labels.
|
|
86
|
+
Named datasets are recommended when a legend or tooltip compares series.
|
|
87
|
+
|
|
88
|
+
## Cartesian methods
|
|
89
|
+
|
|
90
|
+
Line, bar, scatter, bubble, and mixed charts provide:
|
|
91
|
+
|
|
92
|
+
| Method | Result |
|
|
93
|
+
| ------------------------------------- | -------------------------------------------------------- |
|
|
94
|
+
| `axes(visible)` | Shows or hides axes. |
|
|
95
|
+
| `grid(visible)` | Shows or hides grid lines. |
|
|
96
|
+
| `valueLabels(visible)` | Shows or hides direct values. |
|
|
97
|
+
| `frameless()` | Removes optional presentation layers for a compact view. |
|
|
98
|
+
| `formatLabel(formatter)` | Formats category labels. |
|
|
99
|
+
| `formatValue(formatter)` | Formats values across the chart. |
|
|
100
|
+
| `yAxis(callback)` | Changes Y-axis position or formatting. |
|
|
101
|
+
| `marker(label, value, color?)` | Adds a reference line. |
|
|
102
|
+
| `region(label, [start, end], color?)` | Adds a reference band. |
|
|
103
|
+
|
|
104
|
+
### LineChart
|
|
105
|
+
|
|
106
|
+
```js
|
|
107
|
+
import { LineChart } from "@orchidsoftware/charts";
|
|
108
|
+
|
|
109
|
+
LineChart.make(parent).labels(labels).dataset(name, values).smooth().gradient().render();
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
Line-specific methods: `smooth(enabled?)`, `dots(visible)`, `dotSize(pixels)`,
|
|
113
|
+
`line(visible)`, `area(enabled?)`, `gradient(enabledOrOptions?)`, and
|
|
114
|
+
`strokeWidth(pixels)`.
|
|
115
|
+
|
|
116
|
+
Gradient options are `{ fromOpacity?, toOpacity? }`.
|
|
117
|
+
|
|
118
|
+
See [Line charts](./line.md) for examples of every line method.
|
|
119
|
+
|
|
120
|
+
### BarChart
|
|
121
|
+
|
|
122
|
+
```js
|
|
123
|
+
import { BarChart } from "@orchidsoftware/charts";
|
|
124
|
+
|
|
125
|
+
BarChart.make(parent).labels(labels).dataset(name, values).horizontal().stacked().render();
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
Bar-specific methods: `horizontal(enabled?)`, `stacked(enabled?)`, and
|
|
129
|
+
`radius(pixels)`.
|
|
130
|
+
|
|
131
|
+
See [Bar charts](./bar.md) for grouped, horizontal, and stacked examples.
|
|
132
|
+
|
|
133
|
+
### ScatterChart
|
|
134
|
+
|
|
135
|
+
```js
|
|
136
|
+
import { ScatterChart } from "@orchidsoftware/charts";
|
|
137
|
+
|
|
138
|
+
ScatterChart.make(parent)
|
|
139
|
+
.dataset("Results", [{ x: 12, y: 38 }])
|
|
140
|
+
.render();
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
Values may be `{ x, y }` points or numbers. A number uses its array index as
|
|
144
|
+
`x`. Scatter charts also provide `dots(visible)`.
|
|
145
|
+
|
|
146
|
+
See [Scatter charts](./scatter.md) for point and dataset examples.
|
|
147
|
+
|
|
148
|
+
### BubbleChart
|
|
149
|
+
|
|
150
|
+
```js
|
|
151
|
+
import { BubbleChart } from "@orchidsoftware/charts";
|
|
152
|
+
|
|
153
|
+
BubbleChart.make(parent)
|
|
154
|
+
.dataset("Accounts", [{ x: 12, y: 38, r: 8 }])
|
|
155
|
+
.render();
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
Every bubble is `{ x, y, r }`. Bubble charts also provide `dots(visible)`.
|
|
159
|
+
|
|
160
|
+
See [Bubble charts](./bubble.md) for radius and dataset guidance.
|
|
161
|
+
|
|
162
|
+
### MixedChart
|
|
163
|
+
|
|
164
|
+
```js
|
|
165
|
+
import { MixedChart } from "@orchidsoftware/charts";
|
|
166
|
+
|
|
167
|
+
MixedChart.make(parent)
|
|
168
|
+
.labels(labels)
|
|
169
|
+
.bar("Actual", actual)
|
|
170
|
+
.line("Plan", plan)
|
|
171
|
+
.scatter("Events", events)
|
|
172
|
+
.render();
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
Use `bar(name, values, colorOrCallback?)`, `line(...)`, and `scatter(...)`.
|
|
176
|
+
`gradient(enabledOrOptions?)` applies to line datasets.
|
|
177
|
+
|
|
178
|
+
See [Mixed charts](./mixed.md) for each dataset type and its methods.
|
|
179
|
+
|
|
180
|
+
## Composition and radial charts
|
|
181
|
+
|
|
182
|
+
These charts use numeric `labels()` and `dataset()` data. They also provide
|
|
183
|
+
`formatLabel(formatter)` and `formatValue(formatter)`.
|
|
184
|
+
|
|
185
|
+
| Definition | Additional methods |
|
|
186
|
+
| ----------------- | -------------------------------------------------------------------------------------- |
|
|
187
|
+
| `PieChart` | `maxSlices(count)`, `startAngle(degrees)`, `padAngle(degrees)`, `cornerRadius(pixels)` |
|
|
188
|
+
| `DonutChart` | `maxSlices(count)`, `startAngle(degrees)`, `padAngle(degrees)`, `cornerRadius(pixels)` |
|
|
189
|
+
| `PercentageChart` | `maxSlices(count)`, `radius(pixels)` |
|
|
190
|
+
| `RadarChart` | `strokeWidth(pixels)` |
|
|
191
|
+
| `PolarAreaChart` | `padAngle(degrees)`, `cornerRadius(pixels)` |
|
|
192
|
+
|
|
193
|
+
`maxSlices()` keeps the largest categories and combines the remainder.
|
|
194
|
+
|
|
195
|
+
Each type has its own guide: [Pie](./pie.md), [donut](./donut.md),
|
|
196
|
+
[percentage](./percentage.md), [radar](./radar.md), and
|
|
197
|
+
[polar area](./polar-area.md).
|
|
198
|
+
|
|
199
|
+
## HeatmapChart
|
|
200
|
+
|
|
201
|
+
```js
|
|
202
|
+
import { HeatmapChart } from "@orchidsoftware/charts";
|
|
203
|
+
|
|
204
|
+
HeatmapChart.make(parent)
|
|
205
|
+
.range(new Date("2026-01-01T00:00:00Z"), new Date("2026-12-31T00:00:00Z"))
|
|
206
|
+
.points({ "2026-08-28": 4, "2026-08-29": 9 })
|
|
207
|
+
.countLabel("contributions")
|
|
208
|
+
.radius(2)
|
|
209
|
+
.render();
|
|
210
|
+
```
|
|
211
|
+
|
|
212
|
+
| Method | Result |
|
|
213
|
+
| ------------------- | ------------------------------------------------------- |
|
|
214
|
+
| `range(start, end)` | Sets the displayed calendar range. |
|
|
215
|
+
| `points(values)` | Adds values keyed by `YYYY-MM-DD`. |
|
|
216
|
+
| `countLabel(text)` | Names the counted unit in accessible text and tooltips. |
|
|
217
|
+
| `radius(pixels)` | Rounds day cells. |
|
|
218
|
+
|
|
219
|
+
The tooltip callback provides `formatDate(formatter)` and
|
|
220
|
+
`formatValue(formatter)`.
|
|
221
|
+
|
|
222
|
+
See [Calendar heatmaps](./heatmap.md) for range, color scale, and tooltip examples.
|
|
223
|
+
|
|
224
|
+
## TimesheetChart
|
|
225
|
+
|
|
226
|
+
```js
|
|
227
|
+
import { TimesheetChart } from "@orchidsoftware/charts";
|
|
228
|
+
|
|
229
|
+
TimesheetChart.make(parent)
|
|
230
|
+
.range("2026-09-01", "2026-09-12")
|
|
231
|
+
.task("Design", "2026-09-01", "2026-09-03")
|
|
232
|
+
.task({
|
|
233
|
+
label: "Build",
|
|
234
|
+
start: "2026-09-03",
|
|
235
|
+
end: "2026-09-08",
|
|
236
|
+
group: "Engineering",
|
|
237
|
+
color: "#2563eb",
|
|
238
|
+
})
|
|
239
|
+
.render();
|
|
240
|
+
```
|
|
241
|
+
|
|
242
|
+
| Method | Result |
|
|
243
|
+
| --------------------------- | --------------------------------------------- |
|
|
244
|
+
| `range(start, end)` | Sets the displayed time range. |
|
|
245
|
+
| `task(label, start, end)` | Adds a task. |
|
|
246
|
+
| `task(input)` | Adds a task with an optional group and color. |
|
|
247
|
+
| `axes(visible)` | Shows or hides the time axis. |
|
|
248
|
+
| `grid(visible)` | Shows or hides the time grid. |
|
|
249
|
+
| `valueLabels(visible)` | Shows or hides task labels. |
|
|
250
|
+
| `formatDate(formatter)` | Formats dates in chart content. |
|
|
251
|
+
| `formatDuration(formatter)` | Formats task durations. |
|
|
252
|
+
| `formatTick(formatter)` | Formats time-axis ticks. |
|
|
253
|
+
| `radius(pixels)` | Rounds task bars. |
|
|
254
|
+
|
|
255
|
+
The tooltip callback provides `formatDate(formatter)` and
|
|
256
|
+
`formatDuration(formatter)`.
|
|
257
|
+
|
|
258
|
+
Timesheet dates accept `Date`, milliseconds, `YYYY-MM-DD`, or date-time strings
|
|
259
|
+
with an explicit timezone.
|
|
260
|
+
|
|
261
|
+
See [Timesheet charts](./timesheet.md) for task, range, and formatting examples.
|
|
262
|
+
|
|
263
|
+
## Scoped customization
|
|
264
|
+
|
|
265
|
+
Callbacks expose methods only for the item they configure:
|
|
266
|
+
|
|
267
|
+
```js
|
|
268
|
+
import { LineChart } from "@orchidsoftware/charts";
|
|
269
|
+
|
|
270
|
+
LineChart.make("#chart")
|
|
271
|
+
.dataset("Actual", values, (dataset) => {
|
|
272
|
+
dataset
|
|
273
|
+
.color("#2563eb")
|
|
274
|
+
.opacity(0.9)
|
|
275
|
+
.formatValue((value) => `${value}k`)
|
|
276
|
+
.smooth()
|
|
277
|
+
.gradient()
|
|
278
|
+
.dots(true)
|
|
279
|
+
.dotSize(5)
|
|
280
|
+
.line(true)
|
|
281
|
+
.area(false)
|
|
282
|
+
.strokeWidth(3);
|
|
283
|
+
})
|
|
284
|
+
.render();
|
|
285
|
+
```
|
|
286
|
+
|
|
287
|
+
- Every dataset callback: `color()`, `opacity()`, `formatValue()`.
|
|
288
|
+
- Line dataset callback: `gradient()`, `smooth()`, `dots()`, `dotSize()`,
|
|
289
|
+
`line()`, `area()`, `strokeWidth()`.
|
|
290
|
+
- Bar dataset callback: `radius()`.
|
|
291
|
+
- Y-axis callback: `position("left" | "right")`, `formatValue()`.
|
|
292
|
+
- Series tooltip callback: `formatLabel()`, `formatValue()`.
|
|
293
|
+
- Marker callback: `color()`, `width()`, `opacity()`, `lineStyle()`, `dash()`,
|
|
294
|
+
`labelPosition()`, `labelColor()`, `includeInDomain()`, `formatLabel()`.
|
|
295
|
+
- Region callback: `color()`, `opacity()`, `labelPosition()`, `labelColor()`,
|
|
296
|
+
`includeInDomain()`, `formatLabel()`.
|
|
297
|
+
|
|
298
|
+
Callbacks run while the builder is being configured; do not keep their scoped
|
|
299
|
+
objects for later use.
|
|
300
|
+
|
|
301
|
+
## Runtime API
|
|
302
|
+
|
|
303
|
+
`render()` returns a chart with the same lifecycle for every definition:
|
|
304
|
+
|
|
305
|
+
| Member | Result |
|
|
306
|
+
| --------------------- | ----------------------------------------------------- |
|
|
307
|
+
| `element` | The owned root `SVGSVGElement`. |
|
|
308
|
+
| `update(data)` | Validates and replaces the complete data scene. |
|
|
309
|
+
| `point(index?)` | Returns an immutable normalized point or `undefined`. |
|
|
310
|
+
| `toSvg()` | Returns the current SVG source. |
|
|
311
|
+
| `download(filename?)` | Downloads the current chart as SVG. |
|
|
312
|
+
| `destroy()` | Releases the DOM, observers, and listeners. |
|
|
313
|
+
|
|
314
|
+
See [Updates and interaction](./updates-and-interaction.md) for the mounted
|
|
315
|
+
lifecycle and [Exporting SVG](./exporting.md) for serialization and download.
|
|
316
|
+
|
|
317
|
+
## TypeScript
|
|
318
|
+
|
|
319
|
+
Type declarations ship with the package. Each named definition returns a
|
|
320
|
+
chart-specific builder, so autocomplete shows only methods that can affect that
|
|
321
|
+
chart. Public input, update, point, and selection types can also be imported
|
|
322
|
+
from `@orchidsoftware/charts`.
|
package/docs/bar.md
ADDED
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
# Bar charts
|
|
2
|
+
|
|
3
|
+
## Introduction
|
|
4
|
+
|
|
5
|
+
Bar charts make differences between categories easy to compare. They may be
|
|
6
|
+
vertical or horizontal, and multiple datasets may be grouped or stacked.
|
|
7
|
+
|
|
8
|
+
## Creating a bar chart
|
|
9
|
+
|
|
10
|
+
```js
|
|
11
|
+
import { BarChart } from "@orchidsoftware/charts";
|
|
12
|
+
import "@orchidsoftware/charts/style.css";
|
|
13
|
+
|
|
14
|
+
const chart = BarChart.make("#regions")
|
|
15
|
+
.labels(["Europe", "Americas", "Asia-Pacific"])
|
|
16
|
+
.dataset("Revenue", [36, 42, 54])
|
|
17
|
+
.render();
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
The value returned by `render()` is the mounted chart and may be updated,
|
|
21
|
+
selected, or exported.
|
|
22
|
+
|
|
23
|
+
## Grouped bars
|
|
24
|
+
|
|
25
|
+
Add another dataset to place related bars beside one another:
|
|
26
|
+
|
|
27
|
+
```js
|
|
28
|
+
import { BarChart } from "@orchidsoftware/charts";
|
|
29
|
+
|
|
30
|
+
BarChart.make("#orders")
|
|
31
|
+
.labels(["Web", "Retail", "Partners"])
|
|
32
|
+
.dataset("Shipped", [124, 86, 43])
|
|
33
|
+
.dataset("Processing", [32, 18, 12])
|
|
34
|
+
.colors(["#2563eb", "#f59e0b"])
|
|
35
|
+
.render();
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
## Horizontal bars
|
|
39
|
+
|
|
40
|
+
Call `horizontal()` when category labels are long or ranking is the main task:
|
|
41
|
+
|
|
42
|
+
```js
|
|
43
|
+
import { BarChart } from "@orchidsoftware/charts";
|
|
44
|
+
|
|
45
|
+
BarChart.make("#delivery-time")
|
|
46
|
+
.labels(["North America", "Europe", "Asia-Pacific"])
|
|
47
|
+
.dataset("Hours", [42, 36, 54])
|
|
48
|
+
.horizontal()
|
|
49
|
+
.render();
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
Passing `false` restores vertical bars, which is useful in conditional code:
|
|
53
|
+
|
|
54
|
+
```js
|
|
55
|
+
import { BarChart } from "@orchidsoftware/charts";
|
|
56
|
+
|
|
57
|
+
BarChart.make("#orders").dataset(values).horizontal(isCompact).render();
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
## Stacked bars
|
|
61
|
+
|
|
62
|
+
Call `stacked()` when both the total and its parts are meaningful:
|
|
63
|
+
|
|
64
|
+
```js
|
|
65
|
+
import { BarChart } from "@orchidsoftware/charts";
|
|
66
|
+
|
|
67
|
+
BarChart.make("#orders")
|
|
68
|
+
.labels(["Web", "Retail", "Partners"])
|
|
69
|
+
.dataset("Shipped", [124, 86, 43])
|
|
70
|
+
.dataset("Processing", [32, 18, 12])
|
|
71
|
+
.horizontal()
|
|
72
|
+
.stacked()
|
|
73
|
+
.render();
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
Without `stacked()`, multiple datasets remain grouped.
|
|
77
|
+
|
|
78
|
+
## Rounded bars
|
|
79
|
+
|
|
80
|
+
The `radius()` method rounds bar corners in CSS pixels:
|
|
81
|
+
|
|
82
|
+
```js
|
|
83
|
+
import { BarChart } from "@orchidsoftware/charts";
|
|
84
|
+
|
|
85
|
+
BarChart.make("#revenue").labels(regions).dataset(values).radius(6).render();
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
## Customizing one dataset
|
|
89
|
+
|
|
90
|
+
Use a dataset callback when one set of bars needs a different color, opacity,
|
|
91
|
+
value format, or radius:
|
|
92
|
+
|
|
93
|
+
```js
|
|
94
|
+
import { BarChart } from "@orchidsoftware/charts";
|
|
95
|
+
|
|
96
|
+
BarChart.make("#orders")
|
|
97
|
+
.labels(["Web", "Retail", "Partners"])
|
|
98
|
+
.dataset("Actual", [124, 86, 43], (bars) => {
|
|
99
|
+
bars.color("#2563eb").radius(6);
|
|
100
|
+
})
|
|
101
|
+
.dataset("Previous", [108, 79, 39], (bars) => {
|
|
102
|
+
bars.color("#94a3b8").opacity(0.65);
|
|
103
|
+
})
|
|
104
|
+
.render();
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
A bar dataset supports `color()`, `opacity()`, `formatValue()`, and `radius()`.
|
|
108
|
+
|
|
109
|
+
Bar charts also support the shared [cartesian presentation, formatting, and
|
|
110
|
+
annotation methods](./customization.md).
|