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/README.md +4 -4
- package/dist/chartist.min.css +1 -1
- package/dist/chartist.min.js +2 -1
- package/dist/compat.cjs +7 -0
- package/dist/compat.cjs.map +1 -1
- package/dist/compat.js +7 -0
- package/dist/compat.js.map +1 -1
- package/dist/compat.min.js +1 -1
- package/dist/compat.min.js.map +1 -1
- package/dist/compat.umd.js +1 -1
- package/dist/compat.umd.js.map +1 -1
- package/dist/index.cjs +1185 -202
- package/dist/index.cjs.map +1 -1
- package/dist/index.css +192 -52
- package/dist/index.css.map +1 -1
- package/dist/index.d.cts +3442 -2701
- package/dist/index.d.ts +3442 -2701
- package/dist/index.js +1179 -203
- package/dist/index.js.map +1 -1
- package/dist/index.min.css +1 -1
- package/dist/index.min.css.map +1 -1
- package/dist/index.min.js +2 -1
- package/dist/index.min.js.map +1 -1
- package/dist/index.umd.js +2 -1
- package/dist/index.umd.js.map +1 -1
- package/dist/live.cjs +770 -0
- package/dist/live.cjs.map +1 -0
- package/dist/live.d.cts +203 -0
- package/dist/live.d.ts +202 -0
- package/dist/live.js +767 -0
- package/dist/live.js.map +1 -0
- package/dist/live.min.js +4 -0
- package/dist/live.min.js.map +1 -0
- package/dist/live.umd.js +4 -0
- package/dist/live.umd.js.map +1 -0
- package/dist/scss/_settings.scss +22 -0
- package/dist/scss/index.scss +107 -6
- package/package.json +12 -1
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
|
-
|
|
809
|
-
|
|
810
|
-
|
|
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
|
|
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/
|
|
2036
|
-
|
|
2037
|
-
|
|
2038
|
-
|
|
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
|
-
|
|
2077
|
-
|
|
2078
|
-
|
|
2079
|
-
|
|
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
|
-
|
|
2083
|
-
|
|
2084
|
-
|
|
2085
|
-
|
|
2086
|
-
|
|
2087
|
-
|
|
2088
|
-
|
|
2089
|
-
|
|
2090
|
-
|
|
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$
|
|
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$
|
|
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
|
|
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
|
|
2198
|
-
const
|
|
2199
|
-
|
|
2200
|
-
|
|
2201
|
-
|
|
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) ?? []).
|
|
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.
|
|
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
|
|
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
|
-
|
|
3236
|
-
|
|
3237
|
-
|
|
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
|
-
|
|
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 (
|
|
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
|
|
3324
|
-
*
|
|
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
|
|
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))
|
|
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
|
|
3332
|
-
|
|
3333
|
-
|
|
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
|
|
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
|
|
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 =
|
|
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
|
|
3557
|
-
* Provided names are known before any plugin installs, so a
|
|
3558
|
-
* listen to events another plugin provides. Listeners added
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
3826
|
-
|
|
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.
|
|
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: {
|
|
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
|
-
|
|
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$
|
|
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$
|
|
5604
|
+
...CLASS_NAMES$4,
|
|
5172
5605
|
...options.classNames
|
|
5173
5606
|
};
|
|
5174
|
-
const
|
|
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 =
|
|
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(
|
|
5279
|
-
|
|
5280
|
-
|
|
5281
|
-
|
|
5282
|
-
|
|
5283
|
-
|
|
5284
|
-
|
|
5285
|
-
|
|
5286
|
-
|
|
5287
|
-
|
|
5288
|
-
|
|
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: [
|
|
5351
|
-
|
|
5352
|
-
|
|
5353
|
-
|
|
5354
|
-
|
|
5355
|
-
|
|
5356
|
-
|
|
5357
|
-
|
|
5358
|
-
|
|
5359
|
-
|
|
5360
|
-
|
|
5361
|
-
|
|
5362
|
-
|
|
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
|
-
|
|
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
|
|
6027
|
-
return [withFields(node("g", {}, classNames.labelGroup), { children: selectedAxes(context, options.axes).flatMap((axis) => nodesOf(axis, context, classNames,
|
|
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
|