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