chartist 2.0.0-rc.2 → 2.0.0

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/dist/index.js CHANGED
@@ -533,6 +533,21 @@ function deserialize(data) {
533
533
  return parsedData;
534
534
  }
535
535
  //#endregion
536
+ //#region src/render/style.ts
537
+ /**
538
+ * Applies a style declaration list through the CSSOM. A Content Security
539
+ * Policy without `'unsafe-inline'` for styles blocks a `style` attribute set
540
+ * by a script, but not `element.style`; the browser still reflects the
541
+ * declarations into the `style` attribute, so the markup stays the same.
542
+ * Elements without a `style` property (none in HTML or SVG documents) get
543
+ * the attribute.
544
+ */
545
+ function setStyle(element, value) {
546
+ const { style } = element;
547
+ if (style) style.cssText = value;
548
+ else element.setAttribute("style", value);
549
+ }
550
+ //#endregion
536
551
  //#region src/svg/SvgList.ts
537
552
  /**
538
553
  * A list of {@link Svg} wrappers that calls an `Svg` method on all of them
@@ -781,6 +796,160 @@ const easings = {
781
796
  1.55
782
797
  ]
783
798
  };
799
+ /** The CSS easing keywords as cubic Bézier curves. */
800
+ const CSS_EASINGS = {
801
+ linear: [
802
+ 0,
803
+ 0,
804
+ 1,
805
+ 1
806
+ ],
807
+ ease: [
808
+ .25,
809
+ .1,
810
+ .25,
811
+ 1
812
+ ],
813
+ "ease-in": [
814
+ .42,
815
+ 0,
816
+ 1,
817
+ 1
818
+ ],
819
+ "ease-out": [
820
+ 0,
821
+ 0,
822
+ .58,
823
+ 1
824
+ ],
825
+ "ease-in-out": [
826
+ .42,
827
+ 0,
828
+ .58,
829
+ 1
830
+ ]
831
+ };
832
+ const NUMBER = /-?(?:\d+\.?\d*|\.\d+)(?:e[-+]?\d+)?/gi;
833
+ /** The four numbers of a cubic Bézier easing, or nothing for other easings. */
834
+ function curveOf(easing) {
835
+ const css = easing.trim();
836
+ if (CSS_EASINGS[css]) return CSS_EASINGS[css];
837
+ if (easings[css]) return easings[css];
838
+ const numbers = css.startsWith("cubic-bezier(") ? css.match(NUMBER) : null;
839
+ return numbers?.length === 4 ? numbers.map(Number) : void 0;
840
+ }
841
+ /** A cubic Bézier easing as a function of time, solved for x by bisection. */
842
+ function bezier([x1, y1, x2, y2]) {
843
+ const at = (a, b, t) => 3 * a * t * (1 - t) ** 2 + 3 * b * t ** 2 * (1 - t) + t ** 3;
844
+ return (time) => {
845
+ let low = 0;
846
+ let high = 1;
847
+ let t = time;
848
+ for (let i = 0; i < 30; i++) {
849
+ const x = at(x1, x2, t);
850
+ if (Math.abs(x - time) < 1e-6) break;
851
+ if (x < time) low = t;
852
+ else high = t;
853
+ t = (low + high) / 2;
854
+ }
855
+ return at(y1, y2, t);
856
+ };
857
+ }
858
+ /** `steps(n, position)` as a function of time. */
859
+ function steps(css) {
860
+ const count = Math.max(1, Math.round(Number(css.match(NUMBER)?.[0] ?? 1)));
861
+ const position = css.match(/(jump-)?(start|end|none|both)\s*\)/)?.[2] ?? "end";
862
+ const jumps = position === "none" ? count - 1 : position === "both" ? count + 1 : count;
863
+ const offset = position === "start" || position === "both" ? 1 : 0;
864
+ return (time) => Math.min(1, (Math.min(Math.floor(time * count), count - 1) + offset) / Math.max(1, jumps));
865
+ }
866
+ /** `linear(…)` as a function of time: its stops, evenly spread where not given. */
867
+ function linearStops(css) {
868
+ const stops = css.slice(css.indexOf("(") + 1, css.lastIndexOf(")")).split(",").map((stop) => stop.trim().split(/\s+/).map(parseFloat));
869
+ const ys = stops.map(([value]) => value);
870
+ const xs = stops.map(([, at], index) => at === void 0 || Number.isNaN(at) ? index / Math.max(1, stops.length - 1) : at / 100);
871
+ return (time) => {
872
+ let i = 1;
873
+ while (i < xs.length - 1 && time > xs[i]) i++;
874
+ const span = xs[i] - xs[i - 1] || 1;
875
+ return ys[i - 1] + (ys[i] - ys[i - 1]) * (time - xs[i - 1]) / span;
876
+ };
877
+ }
878
+ /** An easing as a function of time, `ease` for easings it doesn't know. */
879
+ function easingFunction(easing) {
880
+ if (typeof easing !== "string") return bezier(easing);
881
+ const css = easing.trim();
882
+ if (css.startsWith("steps(")) return steps(css);
883
+ if (css.startsWith("linear(")) return linearStops(css);
884
+ return bezier(curveOf(css) ?? CSS_EASINGS.ease);
885
+ }
886
+ /**
887
+ * The SMIL `keySplines` of an easing SMIL can express: a cubic Bézier
888
+ * curve whose four numbers lie within 0 and 1 (the CSS keywords, many
889
+ * `cubic-bezier()` curves, most names of {@link easings}). Browsers ignore
890
+ * an animation whose `keySplines` leave that range, so for other easings
891
+ * it returns nothing: sample them with {@link sampleEasing} instead.
892
+ *
893
+ * @example
894
+ * ```ts
895
+ * import { keySplinesOf } from 'chartist';
896
+ *
897
+ * keySplinesOf('ease-out'); // '0 0 0.58 1'
898
+ * keySplinesOf('easeOutBack'); // undefined: it overshoots
899
+ * ```
900
+ * @param easing A CSS easing, a name of {@link easings}, or four numbers
901
+ * @returns The `keySplines` value, or nothing
902
+ */
903
+ function keySplinesOf(easing) {
904
+ const curve = typeof easing === "string" ? curveOf(easing) : easing;
905
+ return curve?.length === 4 && curve.every((value) => value >= 0 && value <= 1) ? curve.join(" ") : void 0;
906
+ }
907
+ /**
908
+ * The eased progress of an animation at evenly spaced times, for SMIL
909
+ * `values` with even `keyTimes`: 12 points, and one per 50 ms for
910
+ * animations longer than 600 ms, at most 40. It takes the CSS easings
911
+ * (the keywords, `cubic-bezier()` curves, also ones that overshoot,
912
+ * `linear()` and `steps()`), the names of {@link easings} and four
913
+ * numbers of a cubic Bézier curve; anything else is sampled as `ease`.
914
+ * Points lie below 0 or above 1 where the easing overshoots.
915
+ *
916
+ * @example
917
+ * ```ts
918
+ * import { sampleEasing } from 'chartist';
919
+ *
920
+ * // A value that grows from 10 to 20 and overshoots a little on the way.
921
+ * const progress = sampleEasing('cubic-bezier(0.34, 1.56, 0.64, 1)', 400);
922
+ * const values = progress.map(p => 10 + (20 - 10) * p).join(';');
923
+ * ```
924
+ * @param easing The easing
925
+ * @param duration The animation's duration in ms
926
+ * @returns The progress at each point, from 0 to 1
927
+ */
928
+ function sampleEasing(easing, duration) {
929
+ const count = duration > 600 ? Math.min(40, Math.max(12, Math.round(duration / 50))) : 12;
930
+ const at = easingFunction(easing);
931
+ return Array.from({ length: count }, (_, index) => {
932
+ const time = index / (count - 1);
933
+ return index === 0 ? 0 : index === count - 1 ? 1 : at(time);
934
+ });
935
+ }
936
+ /**
937
+ * The value between `from` and `to` at `progress`: the numbers in `to`
938
+ * moved from their counterparts in `from`, when both have the same shape
939
+ * (`10px` and `40px`, two paths with the same commands), else `from`
940
+ * until halfway and `to` after.
941
+ */
942
+ function between(from, to, progress) {
943
+ const fromNumbers = from.match(NUMBER) ?? [];
944
+ const toNumbers = to.match(NUMBER) ?? [];
945
+ if (fromNumbers.length !== toNumbers.length || from.replace(NUMBER, "#") !== to.replace(NUMBER, "#")) return progress < .5 ? from : to;
946
+ let index = 0;
947
+ return to.replace(NUMBER, () => {
948
+ const start = Number(fromNumbers[index]);
949
+ const end = Number(toNumbers[index++]);
950
+ return String(+(start + (end - start) * progress).toFixed(4));
951
+ });
952
+ }
784
953
  /**
785
954
  * The property of an element that holds the emitter its descendants'
786
955
  * animations report to when `animate()` gets none. The compat plugin
@@ -800,13 +969,22 @@ function createAnimation(element, attribute, animationDefinition, createGuided =
800
969
  const attributeProperties = {};
801
970
  let animationEasing;
802
971
  let timeout;
803
- if (easing) animationEasing = Array.isArray(easing) ? easing : easings[easing];
972
+ if (easing) animationEasing = Array.isArray(easing) ? easing : easings[easing] ?? easing;
804
973
  def.begin = ensureUnit(def.begin, "ms");
805
974
  def.dur = ensureUnit(def.dur, "ms");
806
975
  if (animationEasing) {
807
- def.calcMode = "spline";
808
- def.keySplines = animationEasing.join(" ");
809
- def.keyTimes = "0;1";
976
+ const keySplines = keySplinesOf(animationEasing);
977
+ if (keySplines) {
978
+ def.calcMode = "spline";
979
+ def.keySplines = keySplines;
980
+ def.keyTimes = "0;1";
981
+ } else if (def.from !== void 0 && def.to !== void 0) {
982
+ const duration = quantity(def.dur);
983
+ const progress = sampleEasing(animationEasing, duration.unit === "s" ? duration.value * 1e3 : duration.value);
984
+ def.calcMode = String(animationEasing).trim().startsWith("steps(") ? "discrete" : "linear";
985
+ def.values = progress.map((at) => between(String(def.from), String(def.to), at)).join(";");
986
+ def.keyTimes = progress.map((_, index) => +(index / (progress.length - 1)).toFixed(4)).join(";");
987
+ }
810
988
  }
811
989
  if (createGuided) {
812
990
  def.fill = "freeze";
@@ -920,7 +1098,8 @@ var Svg = class Svg {
920
1098
  }
921
1099
  Object.keys(attributes).forEach((key) => {
922
1100
  if (attributes[key] === void 0) return;
923
- if (key.indexOf(":") !== -1) {
1101
+ if (key === "style") setStyle(this._node, String(attributes[key]));
1102
+ else if (key.indexOf(":") !== -1) {
924
1103
  const namespacedAttribute = key.split(":");
925
1104
  this._node.setAttributeNS(namespaces[namespacedAttribute[0]], key, String(attributes[key]));
926
1105
  } else this._node.setAttribute(key, String(attributes[key]));
@@ -1505,6 +1684,65 @@ var BaseChart = class {
1505
1684
  }
1506
1685
  };
1507
1686
  //#endregion
1687
+ //#region src/scene/node.ts
1688
+ const splitClasses = (names) => names.split(/\s+/).filter(Boolean);
1689
+ const builder = {
1690
+ attr(attributes) {
1691
+ const attrs = { ...this.attrs };
1692
+ for (const [name, value] of Object.entries(attributes)) if (value === void 0 || value === null) delete attrs[name];
1693
+ else attrs[name] = value;
1694
+ return withFields(this, { attrs });
1695
+ },
1696
+ addClass(names) {
1697
+ const added = splitClasses(names).filter((name, i, all) => !this.classes.includes(name) && all.indexOf(name) === i);
1698
+ return added.length ? withFields(this, { classes: [...this.classes, ...added] }) : this;
1699
+ },
1700
+ removeClass(names) {
1701
+ const removed = splitClasses(names);
1702
+ return withFields(this, { classes: this.classes.filter((name) => !removed.includes(name)) });
1703
+ },
1704
+ append(child, insertFirst = false) {
1705
+ return withFields(this, { children: insertFirst ? [child, ...this.children] : [...this.children, child] });
1706
+ },
1707
+ remove(child) {
1708
+ return withFields(this, { children: this.children.filter((existing) => existing !== child) });
1709
+ },
1710
+ replace(child, next) {
1711
+ return withFields(this, { children: this.children.map((existing) => existing === child ? next : existing) });
1712
+ },
1713
+ text(content) {
1714
+ return withFields(this, { content });
1715
+ },
1716
+ empty() {
1717
+ return withFields(this, {
1718
+ children: [],
1719
+ content: void 0
1720
+ });
1721
+ }
1722
+ };
1723
+ function create(fields) {
1724
+ const created = Object.create(builder);
1725
+ for (const [field, value] of Object.entries(fields)) if (value !== void 0) created[field] = value;
1726
+ return created;
1727
+ }
1728
+ function withFields(source, changes) {
1729
+ return create({
1730
+ ...source,
1731
+ ...changes
1732
+ });
1733
+ }
1734
+ function node(name, attributes = {}, className = "", meta = {}) {
1735
+ const attrs = {};
1736
+ for (const [attr, value] of Object.entries(attributes)) if (value !== void 0 && value !== null) attrs[attr] = value;
1737
+ return create({
1738
+ name,
1739
+ attrs,
1740
+ classes: [...new Set(splitClasses(className))],
1741
+ children: [],
1742
+ ...meta
1743
+ });
1744
+ }
1745
+ //#endregion
1508
1746
  //#region src/svg/SvgPath.ts
1509
1747
  /**
1510
1748
  * The parameters of each SVG path command, in the order the `d` attribute
@@ -2031,63 +2269,57 @@ function monotoneCubic(options) {
2031
2269
  };
2032
2270
  }
2033
2271
  //#endregion
2034
- //#region src/scene/node.ts
2035
- const splitClasses = (names) => names.split(/\s+/).filter(Boolean);
2036
- const builder = {
2037
- attr(attributes) {
2038
- const attrs = { ...this.attrs };
2039
- for (const [name, value] of Object.entries(attributes)) if (value === void 0 || value === null) delete attrs[name];
2040
- else attrs[name] = value;
2041
- return withFields(this, { attrs });
2042
- },
2043
- addClass(names) {
2044
- const added = splitClasses(names).filter((name, i, all) => !this.classes.includes(name) && all.indexOf(name) === i);
2045
- return added.length ? withFields(this, { classes: [...this.classes, ...added] }) : this;
2046
- },
2047
- removeClass(names) {
2048
- const removed = splitClasses(names);
2049
- return withFields(this, { classes: this.classes.filter((name) => !removed.includes(name)) });
2050
- },
2051
- append(child, insertFirst = false) {
2052
- return withFields(this, { children: insertFirst ? [child, ...this.children] : [...this.children, child] });
2053
- },
2054
- remove(child) {
2055
- return withFields(this, { children: this.children.filter((existing) => existing !== child) });
2056
- },
2057
- replace(child, next) {
2058
- return withFields(this, { children: this.children.map((existing) => existing === child ? next : existing) });
2059
- },
2060
- text(content) {
2061
- return withFields(this, { content });
2062
- },
2063
- empty() {
2064
- return withFields(this, {
2065
- children: [],
2066
- content: void 0
2067
- });
2068
- }
2069
- };
2070
- function create(fields) {
2071
- const created = Object.create(builder);
2072
- for (const [field, value] of Object.entries(fields)) if (value !== void 0) created[field] = value;
2073
- return created;
2272
+ //#region src/layers/path.ts
2273
+ /** The interpolation for a `lineSmooth` option: smooth, straight or given. */
2274
+ function interpolationOf(lineSmooth) {
2275
+ return typeof lineSmooth === "function" ? lineSmooth : lineSmooth ? monotoneCubic() : none();
2074
2276
  }
2075
- function withFields(source, changes) {
2076
- return create({
2077
- ...source,
2078
- ...changes
2079
- });
2277
+ /**
2278
+ * The px shift that puts points in the middle of each step of a step axis,
2279
+ * where bars are centered, for `align: 'center'`.
2280
+ */
2281
+ function centerShift(axisX, align) {
2282
+ return align === "center" && axisX.kind === "step" && !axisX.stretch ? (axisX.step ?? 0) / 2 : 0;
2080
2283
  }
2081
- function node(name, attributes = {}, className = "", meta = {}) {
2082
- const attrs = {};
2083
- for (const [attr, value] of Object.entries(attributes)) if (value !== void 0 && value !== null) attrs[attr] = value;
2084
- return create({
2085
- name,
2086
- attrs,
2087
- classes: [...new Set(splitClasses(className))],
2088
- children: [],
2089
- ...meta
2284
+ /**
2285
+ * Projects a series' values into the chart area and interpolates a path
2286
+ * through them. Holes break the path unless the interpolation fills them.
2287
+ * @param meta The meta of a value, attached to its path element.
2288
+ */
2289
+ function seriesPath(values, axes, area, shift, interpolation, meta = () => void 0) {
2290
+ const pathCoordinates = [];
2291
+ const pathData = [];
2292
+ values.forEach((value, valueIndex) => {
2293
+ pathCoordinates.push(area.x1 + shift + axes.x.project(value, valueIndex, values), area.y1 - axes.y.project(value, valueIndex, values));
2294
+ pathData.push({
2295
+ value,
2296
+ valueIndex,
2297
+ meta: meta(valueIndex)
2298
+ });
2090
2299
  });
2300
+ return interpolation(pathCoordinates, pathData);
2301
+ }
2302
+ /**
2303
+ * The same path traced backward: it starts where the path ends, and each
2304
+ * segment runs to the previous point, with a curve's control points
2305
+ * swapped, so it draws the same shape. Holes (`M` commands within the
2306
+ * path) are kept between the same points. Arcs aren't supported.
2307
+ */
2308
+ function reversePath(path) {
2309
+ const reversed = new SvgPath();
2310
+ const elements = path.pathElements;
2311
+ for (let i = elements.length - 1; i >= 0; i--) {
2312
+ const element = elements[i];
2313
+ const target = elements[i - 1];
2314
+ if (i === elements.length - 1 || elements[i + 1].command === "M") reversed.move(element.x, element.y, false, element.data);
2315
+ if (element.command === "M" || !target) continue;
2316
+ if (element.command === "C") {
2317
+ const curve = element;
2318
+ reversed.curve(curve.x2, curve.y2, curve.x1, curve.y1, target.x, target.y, false, target.data);
2319
+ } else if (element.command === "L") reversed.line(target.x, target.y, false, target.data);
2320
+ else throw new Error(`Chartist: can't reverse a path with "${element.command}" commands`);
2321
+ }
2322
+ return reversed;
2091
2323
  }
2092
2324
  //#endregion
2093
2325
  //#region src/layers/line.ts
@@ -2102,7 +2334,7 @@ const DEFAULTS = {
2102
2334
  showArea: false,
2103
2335
  areaBase: 0
2104
2336
  };
2105
- const CLASS_NAMES$2 = {
2337
+ const CLASS_NAMES$5 = {
2106
2338
  series: "ct-series",
2107
2339
  line: "ct-line",
2108
2340
  point: "ct-point",
@@ -2164,11 +2396,10 @@ function seriesOption(series, options, key) {
2164
2396
  * ]
2165
2397
  * });
2166
2398
  * ```
2167
- * @experimental
2168
2399
  */
2169
2400
  function line(options = {}) {
2170
2401
  const classNames = {
2171
- ...CLASS_NAMES$2,
2402
+ ...CLASS_NAMES$5,
2172
2403
  ...options.classNames
2173
2404
  };
2174
2405
  return {
@@ -2183,7 +2414,7 @@ function line(options = {}) {
2183
2414
  const { x: axisX, y: axisY } = axes;
2184
2415
  if (!axisX || !axisY || !normalized) throw new Error("Layer \"line\" needs an x and a y axis");
2185
2416
  if (seriesIndexes && options.reverseData) throw new Error("Layer \"line\": a series selection can't be combined with reverseData");
2186
- const shift = options.align === "center" && axisX.kind === "step" && !axisX.stretch ? (axisX.step ?? 0) / 2 : 0;
2417
+ const shift = centerShift(axisX, options.align);
2187
2418
  const order = data.series.map((_, i) => i);
2188
2419
  if (options.reverseData) order.reverse();
2189
2420
  return order.map((dataIndex, localIndex) => {
@@ -2193,18 +2424,11 @@ function line(options = {}) {
2193
2424
  const seriesName = safeHasProperty(series, "name") && series.name;
2194
2425
  const seriesClassName = safeHasProperty(series, "className") && series.className;
2195
2426
  const seriesMeta = safeHasProperty(series, "meta") ? series.meta : void 0;
2196
- const pathCoordinates = [];
2197
- const pathData = [];
2198
- values.forEach((value, valueIndex) => {
2199
- pathCoordinates.push(area.x1 + shift + axisX.project(value, valueIndex, values), area.y1 - axisY.project(value, valueIndex, values));
2200
- pathData.push({
2201
- value,
2202
- valueIndex,
2203
- meta: getMetaData(series, valueIndex)
2204
- });
2205
- });
2206
- const lineSmooth = seriesOption(series, options, "lineSmooth");
2207
- const path = (typeof lineSmooth === "function" ? lineSmooth : lineSmooth ? monotoneCubic() : none())(pathCoordinates, pathData);
2427
+ const smoothing = interpolationOf(seriesOption(series, options, "lineSmooth"));
2428
+ const path = seriesPath(values, {
2429
+ x: axisX,
2430
+ y: axisY
2431
+ }, area, shift, smoothing, (valueIndex) => getMetaData(series, valueIndex));
2208
2432
  const seriesContext = {
2209
2433
  series,
2210
2434
  seriesIndex,
@@ -2242,7 +2466,8 @@ function line(options = {}) {
2242
2466
  ctx: {
2243
2467
  ...seriesContext,
2244
2468
  values,
2245
- path: path.clone()
2469
+ path: path.clone(),
2470
+ smoothing
2246
2471
  }
2247
2472
  }), ...children];
2248
2473
  if (seriesOption(series, options, "showArea") && axisY.range) {
@@ -2251,14 +2476,15 @@ function line(options = {}) {
2251
2476
  path.splitByCommand("M").filter((segment) => segment.pathElements.length > 1).map((segment) => {
2252
2477
  const first = segment.pathElements[0];
2253
2478
  const last = segment.pathElements[segment.pathElements.length - 1];
2254
- return segment.clone(true).position(0).remove(1).move(first.x, areaBaseProjected).line(first.x, first.y).position(segment.pathElements.length + 1).line(last.x, areaBaseProjected);
2479
+ return segment.clone(true).position(0).remove(1).move(first.x, areaBaseProjected).line(first.x, first.y, false, first.data).position(segment.pathElements.length + 1).line(last.x, areaBaseProjected);
2255
2480
  }).forEach((areaPath) => {
2256
2481
  children = [node("path", { d: areaPath.stringify() }, classNames.area, {
2257
2482
  role: "area",
2258
2483
  ctx: {
2259
2484
  ...seriesContext,
2260
2485
  values,
2261
- path: areaPath.clone()
2486
+ path: areaPath.clone(),
2487
+ smoothing
2262
2488
  }
2263
2489
  }), ...children];
2264
2490
  });
@@ -2667,7 +2893,6 @@ function presetAxes(config, boundOf) {
2667
2893
  * `{ x, y }` form.
2668
2894
  * @param extra `reverseData` and `normalize`, for the list form (the
2669
2895
  * `{ x, y }` form takes them inline).
2670
- * @experimental
2671
2896
  */
2672
2897
  function cartesianAxes(input, extra = {}) {
2673
2898
  const withOffset = (setup, dimension) => ({
@@ -2734,7 +2959,6 @@ const valueOf = (value, dimension) => Number(getMultiValue(value, dimension));
2734
2959
  * layers: [axisGrid(), line(), axisLabels()]
2735
2960
  * });
2736
2961
  * ```
2737
- * @experimental
2738
2962
  */
2739
2963
  function stepAxis() {
2740
2964
  return {
@@ -2793,7 +3017,6 @@ function stepAxis() {
2793
3017
  * layers: [axisGrid(), line(), axisLabels()]
2794
3018
  * });
2795
3019
  * ```
2796
- * @experimental
2797
3020
  */
2798
3021
  function autoScaleAxis() {
2799
3022
  return {
@@ -2853,7 +3076,6 @@ function autoScaleAxis() {
2853
3076
  * layers: [axisGrid(), line({ showLine: false }), axisLabels()]
2854
3077
  * });
2855
3078
  * ```
2856
- * @experimental
2857
3079
  */
2858
3080
  function fixedScaleAxis() {
2859
3081
  return {
@@ -2987,8 +3209,9 @@ function createPhaseEmitter() {
2987
3209
  return current;
2988
3210
  },
2989
3211
  notify(name, value) {
2990
- (listeners.get(name) ?? []).forEach(({ listener }) => listener(value));
3212
+ const results = (listeners.get(name) ?? []).map(({ listener }) => listener(value));
2991
3213
  observe(name, value);
3214
+ return results;
2992
3215
  },
2993
3216
  clear() {
2994
3217
  listeners.clear();
@@ -3055,7 +3278,6 @@ function findAll(scene, query = {}) {
3055
3278
  * });
3056
3279
  * const markup = mapped ? renderToString(mapped) : '';
3057
3280
  * ```
3058
- * @experimental
3059
3281
  */
3060
3282
  function mapNodes(scene, mapper) {
3061
3283
  let children = scene.children;
@@ -3200,10 +3422,9 @@ function layout(emitter, config, width, height) {
3200
3422
  validate(result);
3201
3423
  return result;
3202
3424
  }
3203
- function compose(emitter, config, data, area, measureText, axes = {}, normalized, padding = config.padding, bindings = [], labels = "html", measureStyle) {
3204
- const composed = [];
3425
+ function compose(emitter, config, data, area, measureText, axes = {}, normalized, padding = config.padding, bindings = [], labels = "html", measureStyle, drawn = []) {
3205
3426
  const byId = new Map((axes.all ?? []).map((axis) => [axis.id, axis]));
3206
- config.layers.forEach((layer, layerIndex) => {
3427
+ const layers = config.layers.map((layer, layerIndex) => {
3207
3428
  const binding = bindings[layerIndex] ?? {};
3208
3429
  const selection = data.flat ? void 0 : binding.series;
3209
3430
  const layerData = selection ? {
@@ -3214,7 +3435,7 @@ function compose(emitter, config, data, area, measureText, axes = {}, normalized
3214
3435
  ...normalized,
3215
3436
  series: selection.map((i) => normalized.series[i])
3216
3437
  } : normalized;
3217
- const nodes = layer.compose({
3438
+ const context = {
3218
3439
  data: layerData,
3219
3440
  area,
3220
3441
  padding,
@@ -3230,12 +3451,38 @@ function compose(emitter, config, data, area, measureText, axes = {}, normalized
3230
3451
  seriesIndexes: selection,
3231
3452
  labels,
3232
3453
  measureStyle
3454
+ };
3455
+ const nodes = layer.compose(context).map((top) => assignKeys(top, layerIndex));
3456
+ drawn.push({
3457
+ nodes: [],
3458
+ context
3233
3459
  });
3234
- for (const top of nodes) {
3235
- const result = mapNodes(assignKeys(top, layerIndex), (current) => current.role === void 0 ? void 0 : emitter.transform("compose", current));
3236
- if (result) composed.push(result);
3237
- }
3460
+ return {
3461
+ layer,
3462
+ context,
3463
+ nodes,
3464
+ layerIndex
3465
+ };
3238
3466
  });
3467
+ const refiners = layers.flatMap(({ layer, context }) => layer.refine ? [{
3468
+ refine: layer.refine.bind(layer),
3469
+ context
3470
+ }] : []);
3471
+ const refine = (current) => refiners.reduce((refined, { refine: refineNode, context }) => {
3472
+ const result = refineNode(refined, context);
3473
+ return result ? withFields(result, {
3474
+ key: refined.key,
3475
+ children: refined.children
3476
+ }) : refined;
3477
+ }, current);
3478
+ const composed = [];
3479
+ for (const { nodes, layerIndex } of layers) for (const top of nodes) {
3480
+ const result = mapNodes(top, (current) => current.role === void 0 ? void 0 : emitter.transform("compose", refiners.length > 0 ? refine(current) : current));
3481
+ if (result) {
3482
+ drawn[layerIndex].nodes.push(result);
3483
+ composed.push(result);
3484
+ }
3485
+ }
3239
3486
  return composed;
3240
3487
  }
3241
3488
  function scene(emitter, config, children, size) {
@@ -3263,6 +3510,7 @@ function runPipeline(emitter, options, environment, previous, from = "resolve")
3263
3510
  rootClass: className,
3264
3511
  ...measureOptions
3265
3512
  }) : measure;
3513
+ const drawn = [];
3266
3514
  const measureText = withRootClass(environment.measureText);
3267
3515
  const measureStyle = withRootClass(environment.measureStyle);
3268
3516
  return {
@@ -3271,10 +3519,11 @@ function runPipeline(emitter, options, environment, previous, from = "resolve")
3271
3519
  layout: laidOut,
3272
3520
  axes,
3273
3521
  normalized,
3274
- scene: scene(emitter, config, compose(emitter, config, data, laidOut.area, measureText, axes, normalized, laidOut.padding, bindings, config.labels ?? environment.labels ?? "html", measureStyle), environment.rootSize ?? {
3522
+ scene: scene(emitter, config, compose(emitter, config, data, laidOut.area, measureText, axes, normalized, laidOut.padding, bindings, config.labels ?? environment.labels ?? "html", measureStyle, drawn), environment.rootSize ?? {
3275
3523
  width: config.width ?? laidOut.width,
3276
3524
  height: config.height ?? laidOut.height
3277
- })
3525
+ }),
3526
+ drawn
3278
3527
  };
3279
3528
  }
3280
3529
  /**
@@ -3282,7 +3531,6 @@ function runPipeline(emitter, options, environment, previous, from = "resolve")
3282
3531
  * below the top of the line (a typical font's ascent). Lines are one font
3283
3532
  * size apart, so a renderer finds a line's vertical center at
3284
3533
  * `y - (textAscent - 0.5) * fontSize` from its `y`.
3285
- * @experimental
3286
3534
  */
3287
3535
  const textAscent = .85;
3288
3536
  /**
@@ -3300,7 +3548,6 @@ const textAscent = .85;
3300
3548
  * return { ...size, width: size.width * 1.1 };
3301
3549
  * };
3302
3550
  * ```
3303
- * @experimental
3304
3551
  */
3305
3552
  const estimateText = (text, options = {}) => {
3306
3553
  const fontSize = options.fontSize ?? 12;
@@ -3312,24 +3559,62 @@ const estimateText = (text, options = {}) => {
3312
3559
  };
3313
3560
  //#endregion
3314
3561
  //#region src/render/dom.ts
3315
- function setAttribute(element, name, value) {
3562
+ /**
3563
+ * Sets one scene attribute on an element as the DOM renderer does: names
3564
+ * with a known prefix (`ct:value`) in their namespace, and `style` through
3565
+ * the CSSOM, which a Content Security Policy allows. For renderers that
3566
+ * change elements they keep.
3567
+ *
3568
+ * @example
3569
+ * ```ts
3570
+ * import { applyAttribute } from 'chartist';
3571
+ *
3572
+ * const point = document.querySelector('.ct-point');
3573
+ * if (point) {
3574
+ * applyAttribute(point, 'ct:value', 12);
3575
+ * applyAttribute(point, 'style', 'stroke-width: 12px;');
3576
+ * }
3577
+ * ```
3578
+ * @param element The element to change
3579
+ * @param name The attribute's name, as in a scene node's `attrs`
3580
+ * @param value The attribute's value
3581
+ */
3582
+ function applyAttribute(element, name, value) {
3316
3583
  const separator = name.indexOf(":");
3317
3584
  const prefix = separator > 0 ? name.slice(0, separator) : "";
3318
- if (prefix && prefix !== "xmlns" && namespaces[prefix]) element.setAttributeNS(namespaces[prefix], name, String(value));
3585
+ if (name === "style") setStyle(element, String(value));
3586
+ else if (prefix && prefix !== "xmlns" && namespaces[prefix]) element.setAttributeNS(namespaces[prefix], name, String(value));
3319
3587
  else element.setAttribute(name, String(value));
3320
3588
  }
3321
3589
  /**
3322
- * Creates the element tree. Content of a `foreignObject` is HTML and is
3323
- * created in the XHTML namespace.
3590
+ * Creates the element tree of a scene node as the DOM renderer does: SVG
3591
+ * elements, HTML elements inside a `foreignObject`, attributes set with
3592
+ * {@link applyAttribute}. For renderers that create elements.
3593
+ *
3594
+ * @example
3595
+ * ```ts
3596
+ * import { node, renderElement } from 'chartist';
3597
+ *
3598
+ * const dot = renderElement(node('circle', { cx: 5, cy: 5, r: 4 }, 'ct-dot'), document);
3599
+ * document.querySelector('svg')?.append(dot);
3600
+ * ```
3601
+ * @param current The node to create
3602
+ * @param document The document to create the elements in
3603
+ * @param options The namespace, and a callback for each created element
3604
+ * @returns The node's element, with its children
3324
3605
  */
3325
- function build(document, current, elements, html = false) {
3606
+ function renderElement(current, document, options = {}) {
3607
+ const { html = false, created } = options;
3326
3608
  const element = document.createElementNS(html ? namespaces.xhtml : namespaces.svg, current.name);
3327
3609
  if (current.classes.length) element.setAttribute("class", current.classes.join(" "));
3328
- for (const [name, value] of Object.entries(current.attrs)) setAttribute(element, name, value);
3610
+ for (const [name, value] of Object.entries(current.attrs)) applyAttribute(element, name, value);
3329
3611
  if (current.content) element.appendChild(document.createTextNode(current.content));
3330
- const childrenAreHtml = html || current.name === "foreignObject";
3331
- for (const child of current.children) element.appendChild(build(document, child, elements, childrenAreHtml));
3332
- elements.set(current, element);
3612
+ const childOptions = html || current.name !== "foreignObject" ? options : {
3613
+ ...options,
3614
+ html: true
3615
+ };
3616
+ for (const child of current.children) element.appendChild(renderElement(child, document, childOptions));
3617
+ created?.(current, element);
3333
3618
  return element;
3334
3619
  }
3335
3620
  /** Removes Chartist SVGs (the ones with the ct namespace) from a container. */
@@ -3343,7 +3628,7 @@ function removePrevious(container) {
3343
3628
  */
3344
3629
  function probe(document, name, options, read) {
3345
3630
  const svg = document.createElementNS(namespaces.svg, "svg");
3346
- svg.setAttribute("style", "position: absolute; visibility: hidden; width: 0; height: 0;");
3631
+ setStyle(svg, "position: absolute; visibility: hidden; width: 0; height: 0;");
3347
3632
  if (options.rootClass) svg.setAttribute("class", options.rootClass);
3348
3633
  const node = document.createElementNS(namespaces.svg, name);
3349
3634
  if (options.classes?.length) node.setAttribute("class", options.classes.join(" "));
@@ -3377,8 +3662,10 @@ function measureInDocument(document) {
3377
3662
  /**
3378
3663
  * Creates the renderer `chart()` uses by default: it renders scenes as
3379
3664
  * inline SVG into the container, replacing an earlier Chartist SVG there
3380
- * (as 1.x does), and measures text and reads styles for layout in the
3381
- * container, so its CSS applies.
3665
+ * (as 1.x does) on every render, and measures text and reads styles for
3666
+ * layout in the container, so its CSS applies. It ignores the previous
3667
+ * render it gets from a chart's second render on; `liveDomRenderer()`
3668
+ * from `chartist/live` keeps its SVG and changes it instead.
3382
3669
  * Wrap it to change what every render puts on the page.
3383
3670
  *
3384
3671
  * @example
@@ -3404,7 +3691,6 @@ function measureInDocument(document) {
3404
3691
  * renderer
3405
3692
  * });
3406
3693
  * ```
3407
- * @experimental
3408
3694
  */
3409
3695
  function domRenderer() {
3410
3696
  return {
@@ -3412,7 +3698,7 @@ function domRenderer() {
3412
3698
  render(scene, container) {
3413
3699
  const document = container.ownerDocument;
3414
3700
  const elements = /* @__PURE__ */ new WeakMap();
3415
- const svg = build(document, scene, elements);
3701
+ const svg = renderElement(scene, document, { created: (node, element) => elements.set(node, element) });
3416
3702
  svg.setAttributeNS(namespaces.xmlns, "xmlns:ct", namespaces.ct);
3417
3703
  removePrevious(container);
3418
3704
  container.appendChild(svg);
@@ -3434,6 +3720,35 @@ function domRenderer() {
3434
3720
  };
3435
3721
  }
3436
3722
  //#endregion
3723
+ //#region src/chart/changes.ts
3724
+ /** Adds an entry for `current` and each of its descendants, in document order. */
3725
+ function each$1(current, op, into) {
3726
+ into.push({
3727
+ op,
3728
+ node: current
3729
+ });
3730
+ current.children.forEach((child) => each$1(child, op, into));
3731
+ }
3732
+ /**
3733
+ * The changes of a render that replaced its output: every node of the
3734
+ * previous scene removed, every node of the new one added.
3735
+ */
3736
+ function replacementChanges(previous, next) {
3737
+ const changes = [];
3738
+ if (previous) each$1(previous, "remove", changes);
3739
+ each$1(next, "add", changes);
3740
+ return changes;
3741
+ }
3742
+ /**
3743
+ * Set on a chart handle by the 1.x classes and the compat plugin: the
3744
+ * chart never hands its renderer the previous render, so every render
3745
+ * draws new elements, as 1.x code expects. A global symbol, so the compat
3746
+ * build sets the same one as the core.
3747
+ */
3748
+ const REPLACES_OUTPUT = Symbol.for("chartist.replacesOutput");
3749
+ /** Whether the chart must replace its output on every render. */
3750
+ const replacesOutput = (handle) => handle[REPLACES_OUTPUT] === true;
3751
+ //#endregion
3437
3752
  //#region src/chart/className.ts
3438
3753
  /**
3439
3754
  * The chart type class for a chart's layers, so the stylesheet's chart
@@ -3486,7 +3801,6 @@ function withChartTypeClass(config) {
3486
3801
  * { plugins: [withOptions(lastPoint, { className: 'ct-last' })] }
3487
3802
  * );
3488
3803
  * ```
3489
- * @experimental
3490
3804
  */
3491
3805
  function withOptions(plugin, options) {
3492
3806
  return [plugin, options];
@@ -3552,16 +3866,37 @@ function asUserListeners(chart, add) {
3552
3866
  else add();
3553
3867
  }
3554
3868
  /**
3555
- * Installs v2 plugins in order and returns the event names they provide.
3556
- * Provided names are known before any plugin installs, so a plugin may
3557
- * listen to events another plugin provides. Listeners added while a
3558
- * plugin installs belong to plugins.
3869
+ * Installs v2 plugins in order and adds the event names they provide to
3870
+ * `provided`. Provided names are known before any plugin installs, so a
3871
+ * plugin may listen to events another plugin provides. Listeners added
3872
+ * while a plugin installs belong to plugins.
3559
3873
  */
3560
3874
  function installPlugins(chart, plugins = [], provided, scope) {
3561
3875
  scopes.set(chart, scope);
3562
3876
  const entries = normalize(plugins);
3563
3877
  for (const [plugin] of entries) plugin.provides?.forEach((name) => provided.add(name));
3564
- for (const [plugin, options] of entries) addingAs(scope, "plugin", () => plugin.install(chart, options));
3878
+ const cleanups = [];
3879
+ for (const [plugin, options] of entries) addingAs(scope, "plugin", () => {
3880
+ const cleanup = plugin.install(chart, options);
3881
+ if (typeof cleanup === "function") cleanups.push(cleanup);
3882
+ });
3883
+ return {
3884
+ plugins: entries.map(([plugin]) => plugin),
3885
+ cleanups
3886
+ };
3887
+ }
3888
+ /**
3889
+ * Calls every function, also when one throws, and throws the first error
3890
+ * afterwards.
3891
+ */
3892
+ function runAll(functions) {
3893
+ const errors = [];
3894
+ for (const run of functions) try {
3895
+ run();
3896
+ } catch (error) {
3897
+ errors.push(error);
3898
+ }
3899
+ if (errors.length) throw errors[0];
3565
3900
  }
3566
3901
  /** Registers `config.on` listeners. */
3567
3902
  function registerListeners(chart, listeners = {}) {
@@ -3662,7 +3997,6 @@ function resolveTarget(target) {
3662
3997
  * @param target The container element, or a CSS selector for it.
3663
3998
  * @param config The data, axes, layers and the rest of the chart.
3664
3999
  * @returns A handle to update the chart, listen to it or remove it.
3665
- * @experimental
3666
4000
  */
3667
4001
  function chart(target, config) {
3668
4002
  const container = resolveTarget(target);
@@ -3688,6 +4022,12 @@ function chart(target, config) {
3688
4022
  let watcher;
3689
4023
  let state;
3690
4024
  let output;
4025
+ let cleanups = [];
4026
+ let installed = {
4027
+ plugins: [],
4028
+ cleanups: []
4029
+ };
4030
+ let layerOf;
3691
4031
  let observer;
3692
4032
  let destroyed = false;
3693
4033
  let attached = true;
@@ -3695,7 +4035,7 @@ function chart(target, config) {
3695
4035
  const view = container.ownerDocument.defaultView;
3696
4036
  const fonts = container.ownerDocument.fonts;
3697
4037
  const onFontsLoaded = () => {
3698
- if (!destroyed && state && measuredText) render("layout");
4038
+ if (!destroyed && state && measuredText) render("layout", "fonts");
3699
4039
  };
3700
4040
  let printed;
3701
4041
  let printing = false;
@@ -3725,7 +4065,7 @@ function chart(target, config) {
3725
4065
  probe.setAttribute("width", width);
3726
4066
  probe.setAttribute("height", height);
3727
4067
  const px = (value) => typeof value === "number" ? "px" : "";
3728
- probe.setAttribute("style", `width: ${width}${px(effective.width)}; height: ${height}${px(effective.height)};`);
4068
+ setStyle(probe, `width: ${width}${px(effective.width)}; height: ${height}${px(effective.height)};`);
3729
4069
  container.appendChild(probe);
3730
4070
  const measured = {
3731
4071
  width: probe.clientWidth,
@@ -3741,9 +4081,33 @@ function chart(target, config) {
3741
4081
  height: effective.viewBox?.height || svg.height || (fixedSize(effective.height) ?? container.clientHeight)
3742
4082
  };
3743
4083
  }
3744
- function render(from) {
4084
+ /**
4085
+ * Asks the plugins with `keyframes`, in order, then the layer that drew
4086
+ * the node, for the keyframes of a node's update animation.
4087
+ */
4088
+ function keyframes(previous, next, progress) {
4089
+ for (const plugin of installed.plugins) {
4090
+ const frames = plugin.keyframes?.(previous, next, progress);
4091
+ if (frames) return frames;
4092
+ }
4093
+ if (!state) return;
4094
+ if (!layerOf) {
4095
+ const map = /* @__PURE__ */ new WeakMap();
4096
+ const mark = (drawnNode, index) => {
4097
+ map.set(drawnNode, index);
4098
+ drawnNode.children.forEach((child) => mark(child, index));
4099
+ };
4100
+ state.drawn.forEach(({ nodes }, index) => nodes.forEach((top) => mark(top, index)));
4101
+ layerOf = map;
4102
+ }
4103
+ const index = layerOf.get(next);
4104
+ return index === void 0 ? void 0 : state.config.layers[index].keyframes?.(previous, next, state.drawn[index].context, progress);
4105
+ }
4106
+ function render(from, cause) {
3745
4107
  const { width, height } = size();
3746
4108
  measuredText = false;
4109
+ const previous = state;
4110
+ const previousOutput = output;
3747
4111
  state = runPipeline(emitter, withChartTypeClass(effective), {
3748
4112
  width,
3749
4113
  height,
@@ -3756,18 +4120,35 @@ function chart(target, config) {
3756
4120
  }
3757
4121
  }, state, from);
3758
4122
  const { scene } = state;
3759
- output = renderer.render(scene, container);
4123
+ layerOf = void 0;
4124
+ const pending = cleanups;
4125
+ cleanups = [];
4126
+ runAll(pending);
4127
+ const update = previous && previousOutput && !replacesOutput(handle) ? {
4128
+ previous: previousOutput,
4129
+ previousScene: previous.scene,
4130
+ cause,
4131
+ labels: state.data.labels,
4132
+ previousLabels: previous.data.labels,
4133
+ keyframes
4134
+ } : void 0;
4135
+ const rendered = renderer.render(scene, container, update);
4136
+ output = rendered;
3760
4137
  if (printing) applyPrintViewBox();
3761
- emitter.notify("render", {
3762
- scene,
3763
- output
3764
- });
3765
- const elementOf = output.elementOf ?? (() => void 0);
3766
- emitter.notify("mount", {
4138
+ const event = {
3767
4139
  scene,
3768
- output,
3769
- elementOf
3770
- });
4140
+ output: rendered,
4141
+ cause,
4142
+ changes: update && rendered.patched && rendered.changes ? rendered.changes : replacementChanges(previous?.scene, scene),
4143
+ previous: previous?.scene
4144
+ };
4145
+ emitter.notify("render", event);
4146
+ const elementOf = (node) => rendered.elementOf?.(node) ?? previousOutput?.elementOf?.(node);
4147
+ cleanups = emitter.notify("mount", {
4148
+ ...event,
4149
+ elementOf,
4150
+ animation: rendered.animation
4151
+ }).filter((result) => typeof result === "function");
3771
4152
  }
3772
4153
  function resolveResponsive() {
3773
4154
  const entries = current.responsive?.entries;
@@ -3783,7 +4164,7 @@ function chart(target, config) {
3783
4164
  previousConfig,
3784
4165
  currentConfig: effective
3785
4166
  });
3786
- render("resolve");
4167
+ render("resolve", "responsive");
3787
4168
  }
3788
4169
  /** (Re)creates the watcher of the responsive entries. */
3789
4170
  function watch() {
@@ -3802,7 +4183,7 @@ function chart(target, config) {
3802
4183
  };
3803
4184
  if ("responsive" in changes) watch();
3804
4185
  else resolveResponsive();
3805
- render("resolve");
4186
+ render("resolve", "update");
3806
4187
  return handle;
3807
4188
  },
3808
4189
  detach() {
@@ -3821,8 +4202,15 @@ function chart(target, config) {
3821
4202
  if (destroyed) return;
3822
4203
  destroyed = true;
3823
4204
  handle.detach();
3824
- output?.remove?.();
3825
- emitter.clear();
4205
+ const pending = cleanups;
4206
+ cleanups = [];
4207
+ try {
4208
+ runAll(pending);
4209
+ } finally {
4210
+ output?.remove?.();
4211
+ emitter.clear();
4212
+ runAll([...installed.cleanups].reverse());
4213
+ }
3826
4214
  },
3827
4215
  ...eventMethods(emitter, () => handle, provided, scope)
3828
4216
  };
@@ -3838,15 +4226,15 @@ function chart(target, config) {
3838
4226
  return;
3839
4227
  }
3840
4228
  const { width, height } = size();
3841
- if (width !== state.layout.width || height !== state.layout.height) render("layout");
4229
+ if (width !== state.layout.width || height !== state.layout.height) render("layout", "resize");
3842
4230
  });
3843
4231
  observer.observe(container);
3844
4232
  }
3845
- installPlugins(handle, config.plugins, provided, scope);
4233
+ installed = installPlugins(handle, config.plugins, provided, scope);
3846
4234
  registerListeners(handle, config.on);
3847
4235
  if (attached) watch();
3848
4236
  else resolveResponsive();
3849
- render("resolve");
4237
+ render("resolve", "create");
3850
4238
  return handle;
3851
4239
  }
3852
4240
  //#endregion
@@ -3874,7 +4262,7 @@ function axisNodes(axis, frame) {
3874
4262
  const options = frame.options ?? (axis.dimension === "x" ? frame.axisX : frame.axisY);
3875
4263
  const place = frame.place;
3876
4264
  const projected = axis.ticks.map((tick, i) => axis.project(tick, i));
3877
- const labelValues = axis.ticks.map(options.labelInterpolationFnc);
4265
+ const labelValues = axis.ticks.map(options.labelTransform);
3878
4266
  const gridOffset = axis.dimension === "x" ? area.y2 : area.x1;
3879
4267
  const gridLength = axis.dimension === "x" ? area.height : area.width;
3880
4268
  const grids = [];
@@ -3969,6 +4357,12 @@ function gridBackground(area, className) {
3969
4357
  }, className, { role: "gridBackground" });
3970
4358
  }
3971
4359
  //#endregion
4360
+ //#region src/layers/label.ts
4361
+ /** The label function to use; `labelTransform` wins when both are set. */
4362
+ function labelTransformOf(options) {
4363
+ return options.labelTransform ?? options.labelInterpolationFnc ?? ((value) => value);
4364
+ }
4365
+ //#endregion
3972
4366
  //#region src/scales/svgLabels.ts
3973
4367
  /**
3974
4368
  * SVG text labels: the label text wrapped into `tspan` lines and aligned
@@ -4067,13 +4461,15 @@ function svgLabels(measureText, kind, override = {}) {
4067
4461
  * container('(width < 400px)'),
4068
4462
  * {
4069
4463
  * showPoint: false,
4070
- * axisX: { labelInterpolationFnc: value => String(value)[0] }
4464
+ * axisX: { labelTransform: value => String(value)[0] }
4071
4465
  * }
4072
4466
  * ]
4073
4467
  * ]
4074
4468
  * );
4075
4469
  * ```
4076
- * @experimental
4470
+ * @experimental It rests on `Element.matchContainer()`, a draft standard
4471
+ * (w3c/csswg-drafts#6205), through a polyfill: it may still change in a
4472
+ * 2.x minor.
4077
4473
  */
4078
4474
  function container(query) {
4079
4475
  return Object.freeze({
@@ -4190,7 +4586,6 @@ function watchResponsive(entries, host, onChange, observeResize = false) {
4190
4586
  * ])
4191
4587
  * });
4192
4588
  * ```
4193
- * @experimental
4194
4589
  */
4195
4590
  function responsive(entries) {
4196
4591
  return {
@@ -4209,13 +4604,18 @@ function applyResponsive(base, entries, matching, merge) {
4209
4604
  * option merge, window responsive options, the preset state that plugins
4210
4605
  * (compat) read, and the grid and label layers.
4211
4606
  */
4607
+ /** An axis's options with its label function resolved from either name. */
4608
+ const drawn = (axis) => ({
4609
+ ...axis,
4610
+ labelTransform: labelTransformOf(axis)
4611
+ });
4212
4612
  function frame(options, context, svgLabel) {
4213
4613
  return {
4214
4614
  svgLabel,
4215
4615
  area: context.area,
4216
4616
  padding: context.padding,
4217
- axisX: options.axisX,
4218
- axisY: options.axisY,
4617
+ axisX: drawn(options.axisX),
4618
+ axisY: drawn(options.axisY),
4219
4619
  classNames: options.classNames
4220
4620
  };
4221
4621
  }
@@ -4243,10 +4643,10 @@ function labelLayer(options, order = ["x", "y"]) {
4243
4643
  type: "labels",
4244
4644
  coordinates: ["cartesian"],
4245
4645
  compose(context) {
4246
- const svgLabel = context.labels === "svg" ? svgLabels(context.measureText, options.labelKind ?? "line") : void 0;
4247
4646
  return [group(options.classNames.labelGroup, order.flatMap((dimension) => {
4248
4647
  const axis = context.axes[dimension];
4249
- return axis ? axisNodes(axis, frame(options, context, svgLabel)).labels : [];
4648
+ if (!axis) return [];
4649
+ return axisNodes(axis, frame(options, context, context.labels === "svg" ? svgLabels(context.measureText, options.labelKind ?? "line", options[dimension === "x" ? "axisX" : "axisY"].labelAlign) : void 0)).labels;
4250
4650
  }))];
4251
4651
  }
4252
4652
  };
@@ -4293,7 +4693,7 @@ function rootStyle(getOptions, state) {
4293
4693
  */
4294
4694
  function presetConfig(spec, data, options) {
4295
4695
  const resolved = extend({}, spec.defaults, options);
4296
- const { plugins, on } = options ?? {};
4696
+ const { plugins, on, renderer } = options ?? {};
4297
4697
  return {
4298
4698
  ...spec.config({
4299
4699
  ...data,
@@ -4301,7 +4701,8 @@ function presetConfig(spec, data, options) {
4301
4701
  series: data.series || []
4302
4702
  }, resolved),
4303
4703
  plugins,
4304
- on
4704
+ on,
4705
+ ...renderer && { renderer }
4305
4706
  };
4306
4707
  }
4307
4708
  /** Internal: creates a preset chart; used by the preset functions and classes. */
@@ -4357,7 +4758,8 @@ function createPreset(spec, target, initialData, options = {}, responsiveOptions
4357
4758
  const inner = chart(target, {
4358
4759
  ...withState(spec.config(data, current)),
4359
4760
  plugins: [rootStyle(() => current, state), ...plugins ?? []],
4360
- on: options.on
4761
+ on: options.on,
4762
+ renderer: options.renderer
4361
4763
  });
4362
4764
  function onResponsiveChange() {
4363
4765
  const previousOptions = current;
@@ -4446,6 +4848,7 @@ const lineSpec = {
4446
4848
  config(data, preset) {
4447
4849
  const options = preset;
4448
4850
  const axisOptions = options;
4851
+ const { below = [], above = [] } = options.layers ?? {};
4449
4852
  return {
4450
4853
  data,
4451
4854
  axes: presetAxes(axesConfig$1(options)),
@@ -4457,6 +4860,7 @@ const lineSpec = {
4457
4860
  labels: options.labels,
4458
4861
  layers: [
4459
4862
  gridLayer(axisOptions),
4863
+ ...below,
4460
4864
  seriesGroup(line({
4461
4865
  lineSmooth: options.lineSmooth,
4462
4866
  showPoint: options.showPoint,
@@ -4467,6 +4871,7 @@ const lineSpec = {
4467
4871
  reverseData: options.reverseData,
4468
4872
  classNames: options.classNames
4469
4873
  })),
4874
+ ...above,
4470
4875
  labelLayer(axisOptions)
4471
4876
  ]
4472
4877
  };
@@ -4489,7 +4894,6 @@ const lineSpec = {
4489
4894
  * toScene(config, { width: 600, height: 300 })
4490
4895
  * );
4491
4896
  * ```
4492
- * @experimental
4493
4897
  */
4494
4898
  function lineChartConfig(data, options = {}) {
4495
4899
  return presetConfig(lineSpec, data, options);
@@ -4502,7 +4906,6 @@ function createLineChart(target, data, options = {}, responsiveOptions, plugins)
4502
4906
  * Creates a line chart with the 1.x options and output, rendered
4503
4907
  * synchronously. Plugins must be v2 plugins; convert 1.x plugins with
4504
4908
  * `fromLegacyPlugin` from `chartist/compat`.
4505
- * @experimental
4506
4909
  */
4507
4910
  function lineChart(target, data, options = {}, responsiveOptions) {
4508
4911
  return createLineChart(target, data, options, responsiveOptions).handle;
@@ -4574,6 +4977,7 @@ var PresetChart = class extends BaseChart {
4574
4977
  name: "chart-listeners",
4575
4978
  install: (handle) => {
4576
4979
  this.installing = handle;
4980
+ handle[REPLACES_OUTPUT] = true;
4577
4981
  asUserListeners(handle, () => queued.forEach(([event, listener]) => this.on(event, listener)));
4578
4982
  }
4579
4983
  };
@@ -4767,7 +5171,6 @@ function rawSeries(series) {
4767
5171
  * className: 'ct-chart-bar'
4768
5172
  * });
4769
5173
  * ```
4770
- * @experimental
4771
5174
  */
4772
5175
  function bar(options = {}) {
4773
5176
  const classNames = {
@@ -4777,6 +5180,7 @@ function bar(options = {}) {
4777
5180
  };
4778
5181
  const horizontal = Boolean(options.horizontalBars);
4779
5182
  const seriesBarDistance = options.seriesBarDistance ?? 15;
5183
+ const offset = options.offset ?? 0;
4780
5184
  const stackMode = options.stackMode ?? "accumulate";
4781
5185
  const stacks = options.stackBars && stackMode !== "overlap" && stackMode !== true;
4782
5186
  return {
@@ -4837,6 +5241,7 @@ function bar(options = {}) {
4837
5241
  if (!labelAxis.stretch) projected[labelPos] += periodHalfLength * (horizontal ? -1 : 1);
4838
5242
  projected[labelPos] += options.stackBars || options.distributeSeries ? 0 : biPol * seriesBarDistance * (horizontal ? -1 : 1);
4839
5243
  }
5244
+ projected[labelPos] += offset;
4840
5245
  if (isAccumulateRelative) stack = Number(horizontal ? valueX : valueY) < 0 ? negStack : posStack;
4841
5246
  const previousStack = stack[valueIndex] || zeroPoint;
4842
5247
  stack[valueIndex] = previousStack - (zeroPoint - projected[counterPos]);
@@ -5037,6 +5442,7 @@ const barSpec = {
5037
5442
  ...seriesGroup(layer),
5038
5443
  extent: extentOf.extent
5039
5444
  };
5445
+ const { below = [], above = [] } = options.layers ?? {};
5040
5446
  return {
5041
5447
  data,
5042
5448
  axes: presetAxes(axesConfig(options)),
@@ -5048,7 +5454,9 @@ const barSpec = {
5048
5454
  labels: options.labels,
5049
5455
  layers: [
5050
5456
  gridLayer(axisOptions, order),
5457
+ ...below,
5051
5458
  bars,
5459
+ ...above,
5052
5460
  labelLayer({
5053
5461
  ...axisOptions,
5054
5462
  labelKind: options.horizontalBars ? "horizontalBar" : "bar"
@@ -5062,7 +5470,6 @@ const barSpec = {
5062
5470
  * options, without a container: the bar layer with its axes, grid and labels, horizontal or stacked as the options say. Pass it to `toScene()` to render
5063
5471
  * the preset chart with another renderer. Responsive options and the
5064
5472
  * inline size style of the root svg are left out; both need a container.
5065
- * @experimental
5066
5473
  */
5067
5474
  function barChartConfig(data, options = {}) {
5068
5475
  return presetConfig(barSpec, data, options);
@@ -5075,7 +5482,6 @@ function createBarChart(target, data, options = {}, responsiveOptions, plugins)
5075
5482
  * Creates a bar chart with the 1.x options and output, rendered
5076
5483
  * synchronously. Plugins must be v2 plugins; convert 1.x plugins with
5077
5484
  * `fromLegacyPlugin` from `chartist/compat`.
5078
- * @experimental
5079
5485
  */
5080
5486
  function barChart(target, data, options = {}, responsiveOptions) {
5081
5487
  return createBarChart(target, data, options, responsiveOptions).handle;
@@ -5136,12 +5542,40 @@ function cssWidth(measureStyle, sliceClass) {
5136
5542
  const width = measureStyle?.("stroke-width", { classes: sliceClass.split(" ") });
5137
5543
  return width && /^[\d.]+px$/.test(width) ? parseFloat(width) : void 0;
5138
5544
  }
5139
- const CLASS_NAMES$1 = {
5545
+ const CLASS_NAMES$4 = {
5140
5546
  series: "ct-series",
5141
5547
  slicePie: "ct-slice-pie",
5142
5548
  sliceDonut: "ct-slice-donut",
5143
5549
  label: "ct-label"
5144
5550
  };
5551
+ /** The line height of a pie label with line breaks, in `em`. */
5552
+ const LINE_HEIGHT = 1.2;
5553
+ /**
5554
+ * A pie label's `text` at its anchor. One line is placed with `dx`/`dy`,
5555
+ * as in 1.x. A label with line breaks gets one `tspan` per line, one line
5556
+ * height apart, the block centered on the anchor (see #546).
5557
+ */
5558
+ function labelText(text, position, anchor, className, ctx) {
5559
+ const lines = text.split("\n");
5560
+ const meta = {
5561
+ role: "label",
5562
+ ctx
5563
+ };
5564
+ if (lines.length === 1) return node("text", {
5565
+ dx: position.x,
5566
+ dy: position.y,
5567
+ "text-anchor": anchor
5568
+ }, className, meta).text(text);
5569
+ const shift = Math.round(-(lines.length - 1) * LINE_HEIGHT * 50) / 100;
5570
+ return withFields(node("text", {
5571
+ x: position.x,
5572
+ y: position.y,
5573
+ "text-anchor": anchor
5574
+ }, className, meta), { children: lines.map((line, index) => node("tspan", {
5575
+ x: position.x,
5576
+ dy: `${index ? LINE_HEIGHT : shift}em`
5577
+ }).text(line)) });
5578
+ }
5145
5579
  /**
5146
5580
  * Creates a layer that draws a flat series as pie or donut slices with
5147
5581
  * labels, in the markup of the 1.x pie chart (a `g.ct-series` per slice,
@@ -5163,14 +5597,13 @@ const CLASS_NAMES$1 = {
5163
5597
  * className: 'ct-chart-donut'
5164
5598
  * });
5165
5599
  * ```
5166
- * @experimental
5167
5600
  */
5168
5601
  function pie(options = {}) {
5169
5602
  const classNames = {
5170
- ...CLASS_NAMES$1,
5603
+ ...CLASS_NAMES$4,
5171
5604
  ...options.classNames
5172
5605
  };
5173
- const labelInterpolationFnc = options.labelInterpolationFnc ?? ((value) => value);
5606
+ const transform = labelTransformOf(options);
5174
5607
  return {
5175
5608
  type: "pie",
5176
5609
  chartType: options.donut ? "donut" : "pie",
@@ -5266,7 +5699,7 @@ function pie(options = {}) {
5266
5699
  y: center.y
5267
5700
  } : polarToCartesian(center.x, center.y, labelRadius, startAngle + (endAngle - startAngle) / 2);
5268
5701
  const rawValue = labels && !isFalseyButZero(labels[index]) ? labels[index] : values[index];
5269
- const interpolated = labelInterpolationFnc(rawValue, index);
5702
+ const interpolated = transform(rawValue, index);
5270
5703
  if (interpolated || interpolated === 0) {
5271
5704
  const text = String(interpolated);
5272
5705
  if (options.preventOverlappingLabelOffset) {
@@ -5274,24 +5707,17 @@ function pie(options = {}) {
5274
5707
  labelPositions.forEach((item) => moveLabel(labelPosition, item, options.preventOverlappingLabelOffset, length));
5275
5708
  labelPositions.push(labelPosition);
5276
5709
  }
5277
- labelNodes.push(node("text", {
5278
- dx: labelPosition.x,
5279
- dy: labelPosition.y,
5280
- "text-anchor": determineAnchorPosition(center, labelPosition, options.labelDirection ?? "neutral")
5281
- }, classNames.label, {
5282
- role: "label",
5283
- ctx: {
5284
- kind: "pie",
5285
- index,
5286
- seriesIndex: index,
5287
- valueIndex: 0,
5288
- text,
5289
- series,
5290
- meta: seriesMeta,
5291
- x: labelPosition.x,
5292
- y: labelPosition.y
5293
- }
5294
- }).text(text));
5710
+ labelNodes.push(labelText(text, labelPosition, determineAnchorPosition(center, labelPosition, options.labelDirection ?? "neutral"), classNames.label, {
5711
+ kind: "pie",
5712
+ index,
5713
+ seriesIndex: index,
5714
+ valueIndex: 0,
5715
+ text,
5716
+ series,
5717
+ meta: seriesMeta,
5718
+ x: labelPosition.x,
5719
+ y: labelPosition.y
5720
+ }));
5295
5721
  }
5296
5722
  }
5297
5723
  startAngle = endAngle;
@@ -5338,6 +5764,7 @@ const pieSpec = {
5338
5764
  defaults: pieDefaults,
5339
5765
  config(data, preset) {
5340
5766
  const options = preset;
5767
+ const { below = [], above = [] } = preset.layers ?? {};
5341
5768
  return {
5342
5769
  data,
5343
5770
  coordinates: "polar",
@@ -5346,20 +5773,25 @@ const pieSpec = {
5346
5773
  height: options.height,
5347
5774
  viewBox: options.viewBox,
5348
5775
  className: options.donut ? options.classNames.chartDonut : options.classNames.chartPie,
5349
- layers: [pie({
5350
- startAngle: options.startAngle,
5351
- total: options.total,
5352
- donut: options.donut,
5353
- donutWidth: options.donutWidth,
5354
- showLabel: options.showLabel,
5355
- labelOffset: options.labelOffset,
5356
- labelPosition: options.labelPosition,
5357
- labelInterpolationFnc: options.labelInterpolationFnc,
5358
- labelDirection: options.labelDirection,
5359
- ignoreEmptyValues: options.ignoreEmptyValues,
5360
- preventOverlappingLabelOffset: options.preventOverlappingLabelOffset,
5361
- classNames: options.classNames
5362
- })]
5776
+ layers: [
5777
+ ...below,
5778
+ pie({
5779
+ startAngle: options.startAngle,
5780
+ total: options.total,
5781
+ donut: options.donut,
5782
+ donutWidth: options.donutWidth,
5783
+ showLabel: options.showLabel,
5784
+ labelOffset: options.labelOffset,
5785
+ labelPosition: options.labelPosition,
5786
+ labelTransform: options.labelTransform,
5787
+ labelInterpolationFnc: options.labelInterpolationFnc,
5788
+ labelDirection: options.labelDirection,
5789
+ ignoreEmptyValues: options.ignoreEmptyValues,
5790
+ preventOverlappingLabelOffset: options.preventOverlappingLabelOffset,
5791
+ classNames: options.classNames
5792
+ }),
5793
+ ...above
5794
+ ]
5363
5795
  };
5364
5796
  }
5365
5797
  };
@@ -5368,7 +5800,6 @@ const pieSpec = {
5368
5800
  * options, without a container: the pie layer with its labels. Pass it to `toScene()` to render
5369
5801
  * the preset chart with another renderer. Responsive options and the
5370
5802
  * inline size style of the root svg are left out; both need a container.
5371
- * @experimental
5372
5803
  */
5373
5804
  function pieChartConfig(data, options = {}) {
5374
5805
  return presetConfig(pieSpec, data, options);
@@ -5381,7 +5812,6 @@ function createPieChart(target, data, options = {}, responsiveOptions, plugins)
5381
5812
  * Creates a pie or donut chart with the 1.x options and output, rendered
5382
5813
  * synchronously. Plugins must be v2 plugins; convert 1.x plugins with
5383
5814
  * `fromLegacyPlugin` from `chartist/compat`.
5384
- * @experimental
5385
5815
  */
5386
5816
  function pieChart(target, data, options = {}, responsiveOptions) {
5387
5817
  return createPieChart(target, data, options, responsiveOptions).handle;
@@ -5693,7 +6123,6 @@ var interpolation_exports = /* @__PURE__ */ __exportAll({
5693
6123
  * // The color of series `c`, the third.
5694
6124
  * export const third = seriesColors[2];
5695
6125
  * ```
5696
- * @experimental
5697
6126
  */
5698
6127
  const seriesColors = [
5699
6128
  "#d70206",
@@ -5715,16 +6144,22 @@ const seriesColors = [
5715
6144
  //#endregion
5716
6145
  //#region src/render/string.ts
5717
6146
  const escape = (value) => String(value).replace(/[&<>"']/g, (char) => escapingMap[char]);
6147
+ const nameStart = "A-Z_:a-z\\u00C0-\\u00D6\\u00D8-\\u00F6\\u00F8-\\u02FF\\u0370-\\u037D\\u037F-\\u1FFF\\u200C\\u200D\\u2070-\\u218F\\u2C00-\\u2FEF\\u3001-\\uD7FF\\uF900-\\uFDCF\\uFDF0-\\uFFFD";
6148
+ const xmlName = new RegExp(`^[${nameStart}][${nameStart}\\-.0-9\\u00B7\\u0300-\\u036F\\u203F\\u2040]*$`);
6149
+ function checked(name, kind) {
6150
+ if (!xmlName.test(name)) throw new Error(`Chartist: "${name}" is not a valid ${kind} name`);
6151
+ return name;
6152
+ }
5718
6153
  function attributes(current, position) {
5719
6154
  const parts = [];
5720
6155
  if (position === "root") parts.push(`xmlns="${namespaces.svg}"`, `xmlns:ct="${namespaces.ct}"`);
5721
6156
  else if (position === "html-root") parts.push(`xmlns="${namespaces.xhtml}"`);
5722
6157
  if (current.classes.length) parts.push(`class="${escape(current.classes.join(" "))}"`);
5723
- for (const [name, value] of Object.entries(current.attrs)) if (name !== "xmlns") parts.push(`${name}="${escape(value)}"`);
6158
+ for (const [name, value] of Object.entries(current.attrs)) if (name !== "xmlns") parts.push(`${checked(name, "attribute")}="${escape(value)}"`);
5724
6159
  return parts.length ? ` ${parts.join(" ")}` : "";
5725
6160
  }
5726
6161
  function serialize$1(current, position) {
5727
- const open = `<${current.name}${attributes(current, position)}`;
6162
+ const open = `<${checked(current.name, "element")}${attributes(current, position)}`;
5728
6163
  const childPosition = current.name === "foreignObject" ? "html-root" : "other";
5729
6164
  const content = (current.content === void 0 ? "" : escape(current.content)) + current.children.map((child) => serialize$1(child, childPosition)).join("");
5730
6165
  return content ? `${open}>${content}</${current.name}>` : `${open}/>`;
@@ -5770,7 +6205,6 @@ function serialize$1(current, position) {
5770
6205
  * return renderToString(scene);
5771
6206
  * }
5772
6207
  * ```
5773
- * @experimental
5774
6208
  */
5775
6209
  function renderToString(scene) {
5776
6210
  return serialize$1(scene, "root");
@@ -5833,7 +6267,6 @@ function unavailable(method) {
5833
6267
  *
5834
6268
  * export const markup = renderToString(scene);
5835
6269
  * ```
5836
- * @experimental
5837
6270
  */
5838
6271
  function toScene(config, size) {
5839
6272
  const emitter = createPhaseEmitter();
@@ -5855,6 +6288,548 @@ function toScene(config, size) {
5855
6288
  }).scene;
5856
6289
  }
5857
6290
  //#endregion
6291
+ //#region src/layers/band.ts
6292
+ const CLASS_NAMES$3 = {
6293
+ band: "ct-band",
6294
+ area: "ct-band-area",
6295
+ inBand: "ct-in-band",
6296
+ outBand: "ct-out-band"
6297
+ };
6298
+ /** The series an edge follows, if it's a series. */
6299
+ function selectorOf(edge) {
6300
+ return typeof edge === "number" || isValues(edge) ? void 0 : typeof edge === "string" ? edge : edge.series;
6301
+ }
6302
+ function isValues(edge) {
6303
+ return Array.isArray(edge);
6304
+ }
6305
+ /** An edge's own values, normalized like a series' values. */
6306
+ function normalizedValues(values) {
6307
+ return values.map((value) => isNumeric(value) ? { y: Number(value) } : value && typeof value === "object" ? value : void 0);
6308
+ }
6309
+ /** The value of a normalized value along Y, or `undefined` for a hole. */
6310
+ function yOf(value) {
6311
+ const y = value !== null && typeof value === "object" ? value.y : value;
6312
+ return isNumeric(y) ? Number(y) : void 0;
6313
+ }
6314
+ function xOf(value) {
6315
+ return value !== null && typeof value === "object" ? value.x : void 0;
6316
+ }
6317
+ /**
6318
+ * The position of a series in the layer's data: the layer gets only the
6319
+ * series it selects, with their chart-wide indexes in `seriesIndexes`.
6320
+ */
6321
+ function localIndex(selector, context) {
6322
+ const { data, seriesIndexes } = context;
6323
+ if (typeof selector === "string") return data.series.findIndex((series) => series.name === selector);
6324
+ return seriesIndexes ? seriesIndexes.indexOf(selector) : selector;
6325
+ }
6326
+ function chartIndex(selector, context) {
6327
+ const index = localIndex(selector, context);
6328
+ return context.seriesIndexes?.[index] ?? index;
6329
+ }
6330
+ /**
6331
+ * Works out the band's edges from the data in the context.
6332
+ * @param constant Changes a constant edge before it's used, e.g. to keep it
6333
+ * within the axis range for drawing.
6334
+ */
6335
+ function bandEdges(options, context, constant = (value) => value) {
6336
+ const all = context.normalized?.series ?? [];
6337
+ const seriesOf = (selector) => all[localIndex(selector, context)] ?? [];
6338
+ if (options.series) {
6339
+ const group = options.series.map(seriesOf);
6340
+ return {
6341
+ constant: false,
6342
+ length: Math.max(0, ...group.map((values) => values.length)),
6343
+ at(valueIndex) {
6344
+ const defined = group.map((values) => values[valueIndex]).filter((value) => yOf(value) !== void 0);
6345
+ if (defined.length === 0) return;
6346
+ const ys = defined.map((value) => yOf(value));
6347
+ return {
6348
+ x: xOf(defined[0]),
6349
+ low: Math.min(...ys),
6350
+ high: Math.max(...ys)
6351
+ };
6352
+ }
6353
+ };
6354
+ }
6355
+ const edge = (option) => {
6356
+ if (option !== void 0 && isValues(option)) return { values: normalizedValues(option) };
6357
+ const selector = option === void 0 ? void 0 : selectorOf(option);
6358
+ return selector === void 0 ? { value: constant(option) } : { values: seriesOf(selector) };
6359
+ };
6360
+ const low = edge(options.low);
6361
+ const high = edge(options.high);
6362
+ const lengths = [low, high].flatMap((e) => "values" in e ? [e.values.length] : []);
6363
+ return {
6364
+ constant: lengths.length === 0,
6365
+ length: Math.max(0, ...lengths),
6366
+ at(valueIndex) {
6367
+ const lowValue = "values" in low ? low.values[valueIndex] : void 0;
6368
+ const highValue = "values" in high ? high.values[valueIndex] : void 0;
6369
+ const lowY = "value" in low ? low.value : yOf(lowValue);
6370
+ const highY = "value" in high ? high.value : yOf(highValue);
6371
+ if (lowY === void 0 || highY === void 0) return;
6372
+ return {
6373
+ x: xOf(lowValue) ?? xOf(highValue),
6374
+ low: lowY,
6375
+ high: highY
6376
+ };
6377
+ }
6378
+ };
6379
+ }
6380
+ /** The value a mark compares with the band: `y`, else `x`. */
6381
+ function markValue(value) {
6382
+ if (value !== null && typeof value === "object") {
6383
+ const { x, y } = value;
6384
+ return isNumeric(y) ? Number(y) : isNumeric(x) ? Number(x) : void 0;
6385
+ }
6386
+ return isNumeric(value) ? Number(value) : void 0;
6387
+ }
6388
+ function edgeContext(option, context) {
6389
+ if (option === void 0) return;
6390
+ if (isValues(option)) return { values: normalizedValues(option).map(yOf) };
6391
+ const selector = selectorOf(option);
6392
+ return selector === void 0 ? { value: option } : { seriesIndex: chartIndex(selector, context) };
6393
+ }
6394
+ /**
6395
+ * Creates a layer that fills the area between a low and a high edge
6396
+ * (`g.ct-band` with a `path.ct-band-area` per piece; the band breaks at
6397
+ * holes). Each edge is a constant value, values of its own (one per
6398
+ * label) or a series, or `series` names a group and the band covers their
6399
+ * minimum to their maximum at each value index. Constants and own values
6400
+ * widen the Y axis to include them. List the band
6401
+ * before the series layers to draw it behind them. It needs an X and a Y
6402
+ * axis, from {@link cartesianAxes}.
6403
+ *
6404
+ * @example
6405
+ * ```ts
6406
+ * import {
6407
+ * autoScaleAxis,
6408
+ * axisGrid,
6409
+ * axisLabels,
6410
+ * band,
6411
+ * cartesianAxes,
6412
+ * chart,
6413
+ * line,
6414
+ * stepAxis
6415
+ * } from 'chartist';
6416
+ *
6417
+ * const days = ['Mon', 'Tue', 'Wed', 'Thu', 'Fri'];
6418
+ *
6419
+ * chart('#chart', {
6420
+ * data: {
6421
+ * labels: days,
6422
+ * series: [{ name: 'visits', data: [12, 9, 7, 8, 5] }]
6423
+ * },
6424
+ * axes: cartesianAxes([
6425
+ * { id: 'day', side: 'bottom', type: stepAxis(), ticks: days },
6426
+ * { id: 'count', side: 'left', type: autoScaleAxis(), low: 0 }
6427
+ * ]),
6428
+ * layers: [
6429
+ * axisGrid(),
6430
+ * band({ low: 6, high: 10, mark: true }),
6431
+ * line(),
6432
+ * axisLabels()
6433
+ * ]
6434
+ * });
6435
+ * ```
6436
+ */
6437
+ function band(options) {
6438
+ const hasEdges = options.low !== void 0 || options.high !== void 0;
6439
+ if (options.series && hasEdges) throw new Error("Layer \"band\": series can't be combined with low and high");
6440
+ if (!options.series && (options.low === void 0 || options.high === void 0)) throw new Error("Layer \"band\": the band needs low and high, or series");
6441
+ const classNames = {
6442
+ ...CLASS_NAMES$3,
6443
+ ...options.classNames
6444
+ };
6445
+ const edgeSelectors = options.series ? void 0 : [options.low, options.high].flatMap((edge) => {
6446
+ const selector = edge === void 0 ? void 0 : selectorOf(edge);
6447
+ return selector === void 0 ? [] : [selector];
6448
+ });
6449
+ const ownValues = options.series ? [] : [options.low, options.high].flatMap((edge) => typeof edge === "number" ? [edge] : edge !== void 0 && isValues(edge) ? normalizedValues(edge).flatMap((value) => {
6450
+ const y = yOf(value);
6451
+ return y === void 0 ? [] : [y];
6452
+ }) : []);
6453
+ const layer = {
6454
+ type: "band",
6455
+ coordinates: ["cartesian"],
6456
+ axes: {
6457
+ x: options.x,
6458
+ y: options.y
6459
+ },
6460
+ series: options.series ?? edgeSelectors,
6461
+ extent({ axes }) {
6462
+ return axes.y !== void 0 && ownValues.length > 0 ? { [axes.y]: {
6463
+ low: Math.min(...ownValues),
6464
+ high: Math.max(...ownValues)
6465
+ } } : {};
6466
+ },
6467
+ compose(context) {
6468
+ const { area, axes } = context;
6469
+ const { x: axisX, y: axisY } = axes;
6470
+ if (!axisX || !axisY || !context.normalized) throw new Error("Layer \"band\" needs an x and a y axis");
6471
+ const range = axisY.range;
6472
+ const clamp = (value) => range ? Math.min(Math.max(value, range.min), range.max) : value;
6473
+ const edges = bandEdges(options, context, clamp);
6474
+ let pieces;
6475
+ const interpolation = interpolationOf(options.lineSmooth ?? true);
6476
+ if (edges.constant) {
6477
+ const { low, high } = edges.at(0);
6478
+ const lowY = area.y1 - axisY.project(low);
6479
+ const highY = area.y1 - axisY.project(high);
6480
+ pieces = [new SvgPath(true).move(area.x1, highY).line(area.x2, highY).line(area.x2, lowY).line(area.x1, lowY)];
6481
+ } else {
6482
+ const upper = [];
6483
+ const lower = [];
6484
+ for (let valueIndex = 0; valueIndex < edges.length; valueIndex++) {
6485
+ const at = edges.at(valueIndex);
6486
+ upper.push(at && {
6487
+ x: at.x,
6488
+ y: at.high
6489
+ });
6490
+ lower.push(at && {
6491
+ x: at.x,
6492
+ y: at.low
6493
+ });
6494
+ }
6495
+ const shift = centerShift(axisX, options.align);
6496
+ const pathOf = (values) => seriesPath(values, {
6497
+ x: axisX,
6498
+ y: axisY
6499
+ }, area, shift, interpolation).splitByCommand("M").filter((piece) => piece.pathElements.length > 1);
6500
+ const lowerPieces = pathOf(lower);
6501
+ pieces = pathOf(upper).flatMap((upperPiece, index) => {
6502
+ const lowerPiece = lowerPieces[index];
6503
+ if (!lowerPiece || lowerPiece.pathElements.length !== upperPiece.pathElements.length) return [];
6504
+ const back = reversePath(lowerPiece);
6505
+ const start = back.pathElements[0];
6506
+ back.position(0).remove(1).line(start.x, start.y, false, start.data);
6507
+ return [SvgPath.join([upperPiece, back], true)];
6508
+ });
6509
+ }
6510
+ const edgesContext = options.series ? { series: options.series.map((s) => chartIndex(s, context)) } : {
6511
+ low: edgeContext(options.low, context),
6512
+ high: edgeContext(options.high, context)
6513
+ };
6514
+ const children = pieces.map((path) => node("path", { d: path.stringify() }, classNames.area, {
6515
+ role: "band",
6516
+ ctx: {
6517
+ ...edgesContext,
6518
+ path: path.clone(),
6519
+ smoothing: interpolation
6520
+ }
6521
+ }));
6522
+ return [withFields(node("g", {}, options.className ? `${classNames.band} ${options.className}` : classNames.band), { children })];
6523
+ }
6524
+ };
6525
+ if (!options.mark) return layer;
6526
+ return {
6527
+ ...layer,
6528
+ refine(current, context) {
6529
+ if (current.role !== "point" && current.role !== "bar") return;
6530
+ const value = markValue(current.ctx.value);
6531
+ const at = bandEdges(options, context).at(current.ctx.valueIndex);
6532
+ if (value === void 0 || !at) return;
6533
+ const inBand = value >= Math.min(at.low, at.high) && value <= Math.max(at.low, at.high);
6534
+ return current.addClass(inBand ? classNames.inBand : classNames.outBand);
6535
+ }
6536
+ };
6537
+ }
6538
+ //#endregion
6539
+ //#region src/layers/targetLine.ts
6540
+ /**
6541
+ * Target line layer: a line across the chart area at a constant value on
6542
+ * one axis, such as a goal, a limit or an average, with an optional label.
6543
+ */
6544
+ const CLASS_NAMES$2 = {
6545
+ line: "ct-target-line",
6546
+ label: "ct-target-label"
6547
+ };
6548
+ /** The distance in px between a label and its line or the area's edge. */
6549
+ const LABEL_INSET = 4;
6550
+ /**
6551
+ * Creates a layer that draws a line across the chart area at a constant
6552
+ * value on one axis, such as a goal or a limit (`line.ct-target-line`).
6553
+ * On a Y axis the line is horizontal, on an X axis vertical. It can have
6554
+ * a label, drawn as SVG text (`text.ct-target-label`) next to it inside
6555
+ * the chart area, and a class, e.g. `ct-series-b` for a series' color.
6556
+ * The axis widens to include the value, unless its own `low` or `high`
6557
+ * leave it out; a line outside the axis isn't drawn. For several lines,
6558
+ * add a layer per line.
6559
+ *
6560
+ * @example
6561
+ * ```ts
6562
+ * import {
6563
+ * autoScaleAxis,
6564
+ * axisGrid,
6565
+ * axisLabels,
6566
+ * cartesianAxes,
6567
+ * chart,
6568
+ * line,
6569
+ * stepAxis,
6570
+ * targetLine
6571
+ * } from 'chartist';
6572
+ *
6573
+ * const months = ['Jan', 'Feb', 'Mar', 'Apr'];
6574
+ *
6575
+ * chart('#chart', {
6576
+ * data: { labels: months, series: [[620, 810, 740, 930]] },
6577
+ * axes: cartesianAxes([
6578
+ * { id: 'month', side: 'bottom', type: stepAxis(), ticks: months },
6579
+ * { id: 'sales', side: 'left', type: autoScaleAxis(), low: 0 }
6580
+ * ]),
6581
+ * layers: [
6582
+ * axisGrid(),
6583
+ * line(),
6584
+ * targetLine({ value: 700, label: 'Min' }),
6585
+ * targetLine({ value: 1000, label: 'Goal', className: 'ct-series-b' }),
6586
+ * axisLabels()
6587
+ * ]
6588
+ * });
6589
+ * ```
6590
+ */
6591
+ function targetLine(options) {
6592
+ const { value, label, className } = options;
6593
+ if (!Number.isFinite(value)) throw new Error("Layer \"targetLine\" needs a value");
6594
+ const classNames = {
6595
+ ...CLASS_NAMES$2,
6596
+ ...options.classNames
6597
+ };
6598
+ const atStart = options.labelPosition === "start";
6599
+ return {
6600
+ type: "targetLine",
6601
+ coordinates: ["cartesian"],
6602
+ series: [],
6603
+ extent({ axes }) {
6604
+ const id = options.axis ?? axes.y;
6605
+ return id === void 0 ? {} : { [id]: {
6606
+ low: value,
6607
+ high: value
6608
+ } };
6609
+ },
6610
+ compose({ area, axes, measureText }) {
6611
+ const all = axes.all ?? [];
6612
+ const axis = options.axis === void 0 ? all.find((candidate) => candidate.dimension === "y") : all.find((candidate) => candidate.id === options.axis);
6613
+ if (!axis) throw new Error(options.axis === void 0 ? "Layer \"targetLine\" needs a y axis" : `Chartist: unknown axis "${options.axis}" in layer "targetLine"`);
6614
+ const horizontal = axis.dimension === "y";
6615
+ const relative = axis.project(value, value);
6616
+ if (!Number.isFinite(relative) || relative < -1e-9 || relative > axis.length + 1e-9) return [];
6617
+ const ends = horizontal ? {
6618
+ x1: area.x1,
6619
+ y1: area.y1 - relative,
6620
+ x2: area.x2,
6621
+ y2: area.y1 - relative
6622
+ } : {
6623
+ x1: area.x1 + relative,
6624
+ y1: area.y2,
6625
+ x2: area.x1 + relative,
6626
+ y2: area.y1
6627
+ };
6628
+ const extra = className ? ` ${className}` : "";
6629
+ const lineNode = node("line", ends, `${classNames.line}${extra}`, {
6630
+ role: "targetLine",
6631
+ ctx: {
6632
+ axis,
6633
+ value,
6634
+ ...label !== void 0 && { label },
6635
+ ...ends
6636
+ }
6637
+ });
6638
+ if (label === void 0 || label === "") return [lineNode];
6639
+ const labelClasses = `${classNames.label}${extra}`;
6640
+ let position;
6641
+ if (horizontal) position = {
6642
+ x: atStart ? area.x1 + LABEL_INSET : area.x2 - LABEL_INSET,
6643
+ y: ends.y1 - LABEL_INSET,
6644
+ anchor: atStart ? "start" : "end"
6645
+ };
6646
+ else {
6647
+ const fontSize = measureText(label, { classes: labelClasses.split(" ") }).fontSize ?? 12;
6648
+ position = {
6649
+ x: ends.x1 + LABEL_INSET,
6650
+ y: atStart ? area.y1 - LABEL_INSET : area.y2 + LABEL_INSET + textAscent * fontSize,
6651
+ anchor: "start"
6652
+ };
6653
+ }
6654
+ return [lineNode, node("text", {
6655
+ x: position.x,
6656
+ y: position.y,
6657
+ "text-anchor": position.anchor
6658
+ }, labelClasses, {
6659
+ role: "targetLabel",
6660
+ ctx: {
6661
+ axis,
6662
+ value,
6663
+ text: label,
6664
+ x: position.x,
6665
+ y: position.y
6666
+ }
6667
+ }).text(label)];
6668
+ }
6669
+ };
6670
+ }
6671
+ //#endregion
6672
+ //#region src/layers/trendLine.ts
6673
+ /**
6674
+ * Trend line layer: a least-squares straight line through each series'
6675
+ * values, drawn in the series' color.
6676
+ */
6677
+ const CLASS_NAMES$1 = {
6678
+ series: "ct-series",
6679
+ line: "ct-trend-line"
6680
+ };
6681
+ /**
6682
+ * Fits a straight line through a series' values by least squares: X is
6683
+ * the value's index (`byIndex`, for step axes) or its `x`. Holes and
6684
+ * values without a number for X are skipped. Fewer than two values, or
6685
+ * values that all share one X, fit no line.
6686
+ */
6687
+ function fit(values, byIndex) {
6688
+ const xs = [];
6689
+ const ys = [];
6690
+ values.forEach((value, index) => {
6691
+ const y = isDataHoleValue(value) ? void 0 : getMultiValue(value, "y");
6692
+ const x = byIndex ? index : getMultiValue(value, "x");
6693
+ if (typeof x === "number" && Number.isFinite(x) && Number.isFinite(y)) {
6694
+ xs.push(x);
6695
+ ys.push(y);
6696
+ }
6697
+ });
6698
+ if (xs.length < 2) return;
6699
+ const mean = (list) => list.reduce((sum, value) => sum + value, 0) / list.length;
6700
+ const meanX = mean(xs);
6701
+ const meanY = mean(ys);
6702
+ let covariance = 0;
6703
+ let variance = 0;
6704
+ xs.forEach((x, i) => {
6705
+ covariance += (x - meanX) * (ys[i] - meanY);
6706
+ variance += (x - meanX) ** 2;
6707
+ });
6708
+ if (variance === 0) return;
6709
+ const slope = covariance / variance;
6710
+ return {
6711
+ slope,
6712
+ intercept: meanY - slope * meanX,
6713
+ x1: Math.min(...xs),
6714
+ x2: Math.max(...xs)
6715
+ };
6716
+ }
6717
+ /** True when every value that isn't a hole has a number for X. */
6718
+ function hasX(values) {
6719
+ const present = values.filter((value) => !isDataHoleValue(value));
6720
+ return present.length > 0 && present.every((value) => typeof getMultiValue(value, "x") === "number");
6721
+ }
6722
+ /**
6723
+ * Creates a layer that draws a trend line for each series: the straight
6724
+ * line that fits its values best (least squares), from its smallest to its
6725
+ * largest X, as a `line.ct-trend-line` in a series group
6726
+ * (`g.ct-series.ct-series-a`), so it takes the series' color. On a step
6727
+ * axis X is the value's index, on a scale axis the value's `x`. Holes are
6728
+ * skipped, and a series with fewer than two values gets no line. The Y
6729
+ * axis widens to include the line's ends. Add it next to the {@link line}
6730
+ * or {@link bar} layer that draws the values.
6731
+ *
6732
+ * @example
6733
+ * ```ts
6734
+ * import {
6735
+ * autoScaleAxis,
6736
+ * axisGrid,
6737
+ * axisLabels,
6738
+ * cartesianAxes,
6739
+ * chart,
6740
+ * line,
6741
+ * stepAxis,
6742
+ * trendLine
6743
+ * } from 'chartist';
6744
+ *
6745
+ * const months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun'];
6746
+ *
6747
+ * chart('#chart', {
6748
+ * data: {
6749
+ * labels: months,
6750
+ * series: [{ name: 'sales', data: [620, 810, 740, 930, 880, 1020] }]
6751
+ * },
6752
+ * axes: cartesianAxes([
6753
+ * { id: 'month', side: 'bottom', type: stepAxis(), ticks: months },
6754
+ * { id: 'eur', side: 'left', type: autoScaleAxis() }
6755
+ * ]),
6756
+ * layers: [axisGrid(), line(), trendLine(), axisLabels()]
6757
+ * });
6758
+ * ```
6759
+ */
6760
+ function trendLine(options = {}) {
6761
+ const classNames = {
6762
+ ...CLASS_NAMES$1,
6763
+ ...options.classNames
6764
+ };
6765
+ return {
6766
+ type: "trendLine",
6767
+ coordinates: ["cartesian"],
6768
+ axes: {
6769
+ x: options.x,
6770
+ y: options.y
6771
+ },
6772
+ series: options.series,
6773
+ extent({ normalized, axes }) {
6774
+ if (axes.y === void 0) return {};
6775
+ const ends = normalized.series.flatMap((values) => {
6776
+ const fitted = fit(values, !hasX(values));
6777
+ return fitted ? [fitted.slope * fitted.x1 + fitted.intercept, fitted.slope * fitted.x2 + fitted.intercept] : [];
6778
+ });
6779
+ return ends.length > 0 ? { [axes.y]: {
6780
+ low: Math.min(...ends),
6781
+ high: Math.max(...ends)
6782
+ } } : {};
6783
+ },
6784
+ compose({ data, area, axes, normalized, seriesIndexes }) {
6785
+ const { x: axisX, y: axisY } = axes;
6786
+ if (!axisX || !axisY || !normalized) throw new Error("Layer \"trendLine\" needs an x and a y axis");
6787
+ const byIndex = axisX.kind === "step";
6788
+ const shift = centerShift(axisX, options.align);
6789
+ return data.series.flatMap((series, localIndex) => {
6790
+ const fitted = fit(normalized.series[localIndex], byIndex);
6791
+ if (!fitted) return [];
6792
+ const seriesIndex = seriesIndexes?.[localIndex] ?? localIndex;
6793
+ const { slope, intercept } = fitted;
6794
+ const at = (x) => ({
6795
+ x: area.x1 + shift + axisX.project(x, x),
6796
+ y: area.y1 - axisY.project(slope * x + intercept)
6797
+ });
6798
+ const start = at(fitted.x1);
6799
+ const end = at(fitted.x2);
6800
+ const ends = {
6801
+ x1: start.x,
6802
+ y1: start.y,
6803
+ x2: end.x,
6804
+ y2: end.y
6805
+ };
6806
+ const seriesContext = {
6807
+ series: {
6808
+ name: series.name,
6809
+ className: series.className,
6810
+ meta: series.meta,
6811
+ data: series.values
6812
+ },
6813
+ seriesIndex,
6814
+ meta: series.meta
6815
+ };
6816
+ return [withFields(node("g", {}, `${classNames.series} ${series.className || `${classNames.series}-${alphaNumerate(seriesIndex)}`}`, {
6817
+ role: "series",
6818
+ ctx: seriesContext
6819
+ }), { children: [node("line", ends, classNames.line, {
6820
+ role: "trendLine",
6821
+ ctx: {
6822
+ seriesIndex,
6823
+ slope,
6824
+ intercept,
6825
+ ...ends
6826
+ }
6827
+ })] })];
6828
+ });
6829
+ }
6830
+ };
6831
+ }
6832
+ //#endregion
5858
6833
  //#region src/layers/axes.ts
5859
6834
  const CLASS_NAMES = {
5860
6835
  grid: "ct-grid",
@@ -5880,7 +6855,7 @@ function drawnOptions(axis) {
5880
6855
  },
5881
6856
  showLabel: options.showLabel ?? true,
5882
6857
  showGrid: options.showGrid ?? true,
5883
- labelInterpolationFnc: options.labelInterpolationFnc ?? ((value) => value)
6858
+ labelTransform: labelTransformOf(options)
5884
6859
  };
5885
6860
  }
5886
6861
  function selectedAxes(context, ids) {
@@ -5955,7 +6930,6 @@ function nodesOf(axis, context, classNames, svgLabel) {
5955
6930
  * ```
5956
6931
  * @param options The axes to draw and their classes; `background` adds a
5957
6932
  * `rect.ct-grid-background` over the chart area, behind the lines.
5958
- * @experimental
5959
6933
  */
5960
6934
  function axisGrid(options = {}) {
5961
6935
  const classNames = {
@@ -6010,7 +6984,6 @@ function axisGrid(options = {}) {
6010
6984
  * ]
6011
6985
  * });
6012
6986
  * ```
6013
- * @experimental
6014
6987
  */
6015
6988
  function axisLabels(options = {}) {
6016
6989
  const classNames = {
@@ -6022,12 +6995,15 @@ function axisLabels(options = {}) {
6022
6995
  coordinates: ["cartesian"],
6023
6996
  series: [],
6024
6997
  compose(context) {
6025
- const svgLabel = (options.labels ?? context.labels) === "svg" ? svgLabels(context.measureText, "line", options.align) : void 0;
6026
- return [withFields(node("g", {}, classNames.labelGroup), { children: selectedAxes(context, options.axes).flatMap((axis) => nodesOf(axis, context, classNames, svgLabel).labels) })];
6998
+ const svg = (options.labels ?? context.labels) === "svg";
6999
+ return [withFields(node("g", {}, classNames.labelGroup), { children: selectedAxes(context, options.axes).flatMap((axis) => nodesOf(axis, context, classNames, svg ? svgLabels(context.measureText, "line", {
7000
+ ...options.align,
7001
+ ...axis.options.labelAlign
7002
+ }) : void 0).labels) })];
6027
7003
  }
6028
7004
  };
6029
7005
  }
6030
7006
  //#endregion
6031
- export { AutoScaleAxis, Axis, BarChart, BaseChart, EPSILON, EventEmitter, FixedScaleAxis, interpolation_exports as Interpolation, LineChart, PieChart, StepAxis, Svg, SvgList, SvgPath, alphaNumerate, autoScaleAxis, axisGrid, axisLabels, axisUnits, bar, barChart, barChartConfig, cartesianAxes, chart, container, createChartRect, createGrid, createGridBackground, createLabel, createSvg, deserialize, determineAnchorPosition, domRenderer, each, easings, ensureUnit, escapingMap, estimateText, extend, findAll, fixedScaleAxis, getBounds, getHighLow, getMetaData, getMultiValue, getNumberOrUndefined, getSeriesOption, isArrayOfArrays, isArrayOfSeries, isDataHoleValue, isFalseyButZero, isMultiValue, isNumeric, line, lineChart, lineChartConfig, mapNodes, namespaces, node, noop, normalizeData, normalizePadding, optionsProvider, orderOfMagnitude, pie, pieChart, pieChartConfig, polarToCartesian, precision, projectLength, quantity, renderToString, responsive, rho, roundWithPrecision, safeHasProperty, serialMap, serialize, seriesColors, splitIntoSegments, stepAxis, sum, textAscent, times, toScene, withOptions };
7007
+ export { AutoScaleAxis, Axis, BarChart, BaseChart, EPSILON, EventEmitter, FixedScaleAxis, interpolation_exports as Interpolation, LineChart, PieChart, StepAxis, Svg, SvgList, SvgPath, alphaNumerate, applyAttribute, autoScaleAxis, axisGrid, axisLabels, axisUnits, band, bar, barChart, barChartConfig, cartesianAxes, chart, container, createChartRect, createGrid, createGridBackground, createLabel, createSvg, deserialize, determineAnchorPosition, domRenderer, each, easings, ensureUnit, escapingMap, estimateText, extend, findAll, fixedScaleAxis, getBounds, getHighLow, getMetaData, getMultiValue, getNumberOrUndefined, getSeriesOption, isArrayOfArrays, isArrayOfSeries, isDataHoleValue, isFalseyButZero, isMultiValue, isNumeric, keySplinesOf, line, lineChart, lineChartConfig, mapNodes, namespaces, node, noop, normalizeData, normalizePadding, optionsProvider, orderOfMagnitude, pie, pieChart, pieChartConfig, polarToCartesian, precision, projectLength, quantity, renderElement, renderToString, responsive, rho, roundWithPrecision, safeHasProperty, sampleEasing, serialMap, serialize, seriesColors, splitIntoSegments, stepAxis, sum, targetLine, textAscent, times, toScene, trendLine, withOptions };
6032
7008
 
6033
7009
  //# sourceMappingURL=index.js.map