apex-analyst 0.1.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.
Files changed (44) hide show
  1. package/LICENSE +97 -0
  2. package/README.md +184 -0
  3. package/apex-analyst.es.min.js +1 -0
  4. package/apex-analyst.min.js +1 -0
  5. package/demo/index.html +127 -0
  6. package/index.d.ts +15 -0
  7. package/index.d.ts.map +1 -0
  8. package/lib/Analyst.d.ts +69 -0
  9. package/lib/Analyst.d.ts.map +1 -0
  10. package/lib/AnalystPanel.d.ts +81 -0
  11. package/lib/AnalystPanel.d.ts.map +1 -0
  12. package/lib/SeriesModel.d.ts +39 -0
  13. package/lib/SeriesModel.d.ts.map +1 -0
  14. package/lib/clip.d.ts +25 -0
  15. package/lib/clip.d.ts.map +1 -0
  16. package/lib/emitLike.d.ts +73 -0
  17. package/lib/emitLike.d.ts.map +1 -0
  18. package/lib/format.d.ts +27 -0
  19. package/lib/format.d.ts.map +1 -0
  20. package/lib/plugin.d.ts +37 -0
  21. package/lib/plugin.d.ts.map +1 -0
  22. package/lib/registry.d.ts +40 -0
  23. package/lib/registry.d.ts.map +1 -0
  24. package/lib/stats/Descriptive.d.ts +50 -0
  25. package/lib/stats/Descriptive.d.ts.map +1 -0
  26. package/lib/stats/Regression.d.ts +42 -0
  27. package/lib/stats/Regression.d.ts.map +1 -0
  28. package/lib/testing/fakeWeave.d.ts +59 -0
  29. package/lib/testing/fakeWeave.d.ts.map +1 -0
  30. package/lib/testing/jsdomSetup.d.ts +31 -0
  31. package/lib/testing/jsdomSetup.d.ts.map +1 -0
  32. package/lib/testing/realChart.d.ts +95 -0
  33. package/lib/testing/realChart.d.ts.map +1 -0
  34. package/lib/tools/DataTable.d.ts +5 -0
  35. package/lib/tools/DataTable.d.ts.map +1 -0
  36. package/lib/tools/SummaryStats.d.ts +5 -0
  37. package/lib/tools/SummaryStats.d.ts.map +1 -0
  38. package/lib/tools/TrendLine.d.ts +5 -0
  39. package/lib/tools/TrendLine.d.ts.map +1 -0
  40. package/lib/tools/types.d.ts +45 -0
  41. package/lib/tools/types.d.ts.map +1 -0
  42. package/lib/weave.d.ts +169 -0
  43. package/lib/weave.d.ts.map +1 -0
  44. package/package.json +30 -0
@@ -0,0 +1,81 @@
1
+ /**
2
+ * The Analyst toolbar and result panel.
3
+ *
4
+ * Rendered into a Shadow DOM host, which is not decoration. This layer is meant
5
+ * to be dropped onto pages we did not write, so two things have to be true:
6
+ * host CSS must not be able to restyle the toolbar into uselessness, and the
7
+ * toolbar's own CSS must not leak out. A global `:root` token from an add-on has
8
+ * broken a real host page before (apexstock shipped one and the website still
9
+ * carries a guard for it), so an add-on rendering UI on customer pages gets a
10
+ * closed styling boundary from the start rather than after the first bug report.
11
+ *
12
+ * Theming still works: CSS custom properties pierce the shadow boundary, so a
13
+ * host sets `--apx-analyst-*` on the chart container and every fallback here
14
+ * gives way. Colors are declared once as tokens on `:host` so the dark scheme is
15
+ * a token swap rather than a second copy of the rules.
16
+ *
17
+ * The DOM is built with createElement and textContent throughout. Series names,
18
+ * category labels and title text all come from user data, and an innerHTML
19
+ * template would make an XSS sink out of a chart label.
20
+ *
21
+ * @module AnalystPanel
22
+ */
23
+ export default class AnalystPanel {
24
+ private container;
25
+ /** The toolbar's shadow host. Retained so a re-render can put the same node back. */
26
+ readonly host: HTMLElement;
27
+ /** The readout's shadow host, which lives on the far side of the chart. */
28
+ readonly panelHost: HTMLElement;
29
+ private bar;
30
+ private panel;
31
+ private buttons;
32
+ /**
33
+ * Two hosts rather than one, because they belong on opposite sides of a chart
34
+ * this layer does not own.
35
+ *
36
+ * The buttons have to sit above the chart, where a toolbar is looked for. The
37
+ * readout must not: opening a panel above the chart pushes the chart down the
38
+ * page by the panel's height, so the thing you were reading moves out from
39
+ * under the pointer at the moment you asked to learn more about it. A summary
40
+ * table is tall enough to shift the chart off screen entirely.
41
+ *
42
+ * Putting the readout after the chart means a second shadow root, and the
43
+ * stylesheet is duplicated into it, which is a few hundred bytes for the
44
+ * chart staying where the reader left it.
45
+ *
46
+ * @param container the chart's own container element
47
+ * @param before insert the toolbar before this node, or append
48
+ */
49
+ constructor(container: HTMLElement, before: Element | null);
50
+ /** Attach a shadow root carrying the stylesheet, once per host. */
51
+ private static rootFor;
52
+ /** Toolbar before the chart, readout after it. */
53
+ private insert;
54
+ /**
55
+ * Put the host back after a re-render detached it.
56
+ *
57
+ * Every `updateSeries()` / `updateOptions()` empties the chart's container
58
+ * outright (the core clears every child of the caller's element, not just the
59
+ * SVG), so anything a plugin puts there is removed on every update and the
60
+ * toolbar vanished the first time a tool changed the chart. Only the parent
61
+ * link is cut, and the host element object is still held here, so re-inserting
62
+ * the SAME node restores the shadow root, its stylesheet and the open panel in
63
+ * one DOM operation. Rebuilding it would drop the readout and leak a shadow
64
+ * root per update.
65
+ */
66
+ reattach(container: HTMLElement, before: Element | null): void;
67
+ /**
68
+ * (Re)build the button row.
69
+ * @param tools already filtered to the tools that apply to this chart
70
+ */
71
+ setTools(tools: Array<{
72
+ id: string;
73
+ label: string;
74
+ }>, onClick: (id: string) => void): void;
75
+ setPressed(id: string, on: boolean): void;
76
+ /** Clear the panel and return the element a tool should render into. */
77
+ openPanel(): HTMLElement;
78
+ closePanel(): void;
79
+ teardown(): void;
80
+ }
81
+ //# sourceMappingURL=AnalystPanel.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"AnalystPanel.d.ts","sourceRoot":"","sources":["../../../libs/analyst/src/lib/AnalystPanel.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AA6GH,MAAM,CAAC,OAAO,OAAO,YAAY;IAC/B,OAAO,CAAC,SAAS,CAAc;IAC/B,qFAAqF;IACrF,QAAQ,CAAC,IAAI,EAAE,WAAW,CAAC;IAC3B,2EAA2E;IAC3E,QAAQ,CAAC,SAAS,EAAE,WAAW,CAAC;IAChC,OAAO,CAAC,GAAG,CAAc;IACzB,OAAO,CAAC,KAAK,CAAc;IAC3B,OAAO,CAAC,OAAO,CAAwC;IAEvD;;;;;;;;;;;;;;;;OAgBG;gBACS,SAAS,EAAE,WAAW,EAAE,MAAM,EAAE,OAAO,GAAG,IAAI;IA4B1D,mEAAmE;IACnE,OAAO,CAAC,MAAM,CAAC,OAAO;IAQtB,kDAAkD;IAClD,OAAO,CAAC,MAAM;IAcd;;;;;;;;;;;OAWG;IACH,QAAQ,CAAC,SAAS,EAAE,WAAW,EAAE,MAAM,EAAE,OAAO,GAAG,IAAI,GAAG,IAAI;IAM9D;;;OAGG;IACH,QAAQ,CAAC,KAAK,EAAE,KAAK,CAAC;QAAC,EAAE,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAC,CAAC,EAAE,OAAO,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,IAAI,GAAG,IAAI;IAcxF,UAAU,CAAC,EAAE,EAAE,MAAM,EAAE,EAAE,EAAE,OAAO,GAAG,IAAI;IAKzC,wEAAwE;IACxE,SAAS,IAAI,WAAW;IAMxB,UAAU,IAAI,IAAI;IAKlB,QAAQ,IAAI,IAAI;CAMjB"}
@@ -0,0 +1,39 @@
1
+ import { WeaveAPI } from './weave';
2
+
3
+ export interface AnalystSeries {
4
+ /** index into the chart's series array */
5
+ index: number;
6
+ name: string;
7
+ /** false when the viewer has toggled it off */
8
+ visible: boolean;
9
+ /** y values, gaps preserved as null */
10
+ values: Array<number | null>;
11
+ /**
12
+ * The x per position, as the host parsed it: the basis `api.scales.x()`
13
+ * projects, so a drawing tool can hand these straight to the scales.
14
+ */
15
+ xs: number[];
16
+ /** finite (x,y) pairs for fitting, gaps dropped */
17
+ points: Array<{
18
+ x: number;
19
+ y: number;
20
+ }>;
21
+ }
22
+ export interface AnalystModel {
23
+ /** true for line/area/bar/..., false for pie/donut */
24
+ axis: boolean;
25
+ /** the type the caller asked for */
26
+ type: string;
27
+ timeX: boolean;
28
+ /**
29
+ * Bar chart with horizontal:true, which swaps the value axis onto x, so a
30
+ * tool projecting y values through the scales would draw sideways.
31
+ */
32
+ horizontalBars: boolean;
33
+ series: AnalystSeries[];
34
+ /** x labels (axis charts) or slice labels */
35
+ categories: string[];
36
+ }
37
+ /** Snapshot a chart into the Analyst model. */
38
+ export declare function buildModel(api: WeaveAPI): AnalystModel;
39
+ //# sourceMappingURL=SeriesModel.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"SeriesModel.d.ts","sourceRoot":"","sources":["../../../libs/analyst/src/lib/SeriesModel.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AAEH,OAAO,KAAK,EAAC,QAAQ,EAAC,MAAM,SAAS,CAAC;AAEtC,MAAM,WAAW,aAAa;IAC5B,0CAA0C;IAC1C,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,MAAM,CAAC;IACb,+CAA+C;IAC/C,OAAO,EAAE,OAAO,CAAC;IACjB,uCAAuC;IACvC,MAAM,EAAE,KAAK,CAAC,MAAM,GAAG,IAAI,CAAC,CAAC;IAC7B;;;OAGG;IACH,EAAE,EAAE,MAAM,EAAE,CAAC;IACb,mDAAmD;IACnD,MAAM,EAAE,KAAK,CAAC;QAAC,CAAC,EAAE,MAAM,CAAC;QAAC,CAAC,EAAE,MAAM,CAAA;KAAC,CAAC,CAAC;CACvC;AAED,MAAM,WAAW,YAAY;IAC3B,sDAAsD;IACtD,IAAI,EAAE,OAAO,CAAC;IACd,oCAAoC;IACpC,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,OAAO,CAAC;IACf;;;OAGG;IACH,cAAc,EAAE,OAAO,CAAC;IACxB,MAAM,EAAE,aAAa,EAAE,CAAC;IACxB,6CAA6C;IAC7C,UAAU,EAAE,MAAM,EAAE,CAAC;CACtB;AAaD,+CAA+C;AAC/C,wBAAgB,UAAU,CAAC,GAAG,EAAE,QAAQ,GAAG,YAAY,CAsDtD"}
package/lib/clip.d.ts ADDED
@@ -0,0 +1,25 @@
1
+ /**
2
+ * Clipping drawn geometry to the chart's grid.
3
+ *
4
+ * Plugin layers are not covered by the chart's own grid clip mask, so anything
5
+ * drawn past the grid edge paints over the axes. A fitted line does exactly
6
+ * that whenever its endpoints fall outside the visible y range, which zooming
7
+ * makes routine, so overlay tools clip in pixel space before drawing.
8
+ *
9
+ * @module clip
10
+ */
11
+ /** The grid rectangle in pixel space, as the scales report it. */
12
+ export interface PixelRect {
13
+ left: number;
14
+ top: number;
15
+ right: number;
16
+ bottom: number;
17
+ }
18
+ /**
19
+ * Clip a line segment to a rectangle (Liang-Barsky).
20
+ *
21
+ * @returns the visible part as `[x1, y1, x2, y2]`, or null when the segment
22
+ * misses the rectangle entirely.
23
+ */
24
+ export declare function clipSegment(x1: number, y1: number, x2: number, y2: number, rect: PixelRect): [number, number, number, number] | null;
25
+ //# sourceMappingURL=clip.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"clip.d.ts","sourceRoot":"","sources":["../../../libs/analyst/src/lib/clip.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,kEAAkE;AAClE,MAAM,WAAW,SAAS;IACxB,IAAI,EAAE,MAAM,CAAC;IACb,GAAG,EAAE,MAAM,CAAC;IACZ,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;CAChB;AAED;;;;;GAKG;AACH,wBAAgB,WAAW,CACzB,EAAE,EAAE,MAAM,EACV,EAAE,EAAE,MAAM,EACV,EAAE,EAAE,MAAM,EACV,EAAE,EAAE,MAAM,EACV,IAAI,EAAE,SAAS,GACd,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,GAAG,IAAI,CAgCzC"}
@@ -0,0 +1,73 @@
1
+ /**
2
+ * Emit a derived datum in the same shape as the series it was derived from.
3
+ *
4
+ * ## Why not infer the axis
5
+ *
6
+ * A derived series has to match the axis: `{x,y}` pairs on a positional axis
7
+ * parse to all-null y, and a flat array on a datetime axis parses to all-null
8
+ * x. Both fail silently, so getting it wrong means a series sits on the chart
9
+ * drawing nothing.
10
+ *
11
+ * The first attempt inferred which case applied from the chart's internals, and
12
+ * every candidate signal turned out to be unusable:
13
+ *
14
+ * - `config.xaxis.type` is rewritten to 'numeric' even when the caller passed
15
+ * `xaxis.categories`
16
+ * - `globals.isXNumeric` is true for every axis chart
17
+ * - `globals.seriesX` is populated with 1-based slots on a category axis, so
18
+ * its presence proves nothing about the axis
19
+ * - `globals.categoryLabels` is correct on mount and EMPTY after an
20
+ * `updateSeries()`, so the same chart classified differently depending on
21
+ * which render path had just run
22
+ *
23
+ * That last one is the reason this module exists. Inference read state that is
24
+ * not stable across render paths, so a trend line emitted correctly on mount
25
+ * was emitted in the broken shape after any update.
26
+ *
27
+ * So nothing is inferred. The source datum IS the template: whatever shape the
28
+ * chart already accepts for this series, the derived series reuses, carrying
29
+ * the source's own x across verbatim. A string x ('Mon') travels as a string, an
30
+ * epoch number as an epoch number, and a bare value stays bare.
31
+ *
32
+ * @module emitLike
33
+ */
34
+ /**
35
+ * @param datum the source series datum at the same position
36
+ * @param y the derived value
37
+ * @returns a datum in the source's shape
38
+ */
39
+ export declare function emitLike(datum: unknown, y: number): unknown;
40
+ /** How many decimal places the source values actually use. */
41
+ export declare function decimalsOf(values: ReadonlyArray<number | null | undefined> | undefined): number;
42
+ /**
43
+ * Round a computed value to the precision of the data it was derived from.
44
+ *
45
+ * A fitted value lands on 29.999999999999996 rather than 30, and an axis sizes
46
+ * its tick labels to the decimal count present in the data, so adding a trend
47
+ * line turned a bar chart whose ticks read 0 / 10 / 20 / 30 into one reading
48
+ * 0.0000000000000000 / 10.0000000000000000. Rounding to 12 significant digits
49
+ * was not enough: 100.434782608696 still asks the axis for nine decimals.
50
+ *
51
+ * Matching the source's own precision is also the honest rule. A least-squares
52
+ * fit over integer counts is not more precise than integer counts, and the
53
+ * residual visual error is half of one unit of a resolution the chart was
54
+ * already rounding to. Full precision stays available to a readout, which is
55
+ * where it belongs.
56
+ *
57
+ * @param decimals from decimalsOf() on the source series
58
+ */
59
+ export declare function roundLike(y: number, decimals: number): number;
60
+ /**
61
+ * The numeric x to fit against for a datum at position `index`.
62
+ *
63
+ * Derived from the source data rather than from the chart's parsed x array, for
64
+ * the same stability reason as above: that array is empty on some render paths,
65
+ * which silently changed the x basis of a refit.
66
+ *
67
+ * A non-numeric x (a category name) falls back to the ordinal position, which
68
+ * is the honest reading: a trend across named categories is a trend against
69
+ * their order. The fit and the prediction use the same basis either way, so a
70
+ * uniform shift in x leaves the predicted y values unchanged.
71
+ */
72
+ export declare function xFor(datum: unknown, index: number): number;
73
+ //# sourceMappingURL=emitLike.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"emitLike.d.ts","sourceRoot":"","sources":["../../../libs/analyst/src/lib/emitLike.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgCG;AAKH;;;;GAIG;AACH,wBAAgB,QAAQ,CAAC,KAAK,EAAE,OAAO,EAAE,CAAC,EAAE,MAAM,GAAG,OAAO,CAa3D;AAED,8DAA8D;AAC9D,wBAAgB,UAAU,CAAC,MAAM,EAAE,aAAa,CAAC,MAAM,GAAG,IAAI,GAAG,SAAS,CAAC,GAAG,SAAS,GAAG,MAAM,CAqB/F;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,SAAS,CAAC,CAAC,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,MAAM,CAS7D;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,IAAI,CAAC,KAAK,EAAE,OAAO,EAAE,KAAK,EAAE,MAAM,GAAG,MAAM,CAY1D"}
@@ -0,0 +1,27 @@
1
+ /**
2
+ * Number formatting for Analyst readouts.
3
+ *
4
+ * Analysis output is read side by side with the chart, so a statistic printed
5
+ * at full float precision (`3.3000000000000003`) reads as a bug in the library
6
+ * rather than as floating point. These helpers are shared so every tool rounds
7
+ * the same way and a mean in the summary panel matches the same mean quoted in
8
+ * a trend readout.
9
+ *
10
+ * @module format
11
+ */
12
+ /** Shown wherever a statistic has no defined value. */
13
+ export declare const NO_VALUE = "n/a";
14
+ /**
15
+ * Format a statistic for display.
16
+ *
17
+ * Significant digits rather than fixed decimals, because a chart's y values may
18
+ * be percentages (0.043), counts (128) or revenue (1284000) and one fixed
19
+ * decimal count is wrong for at least two of those. Locale-aware grouping,
20
+ * since these are numbers a person reads rather than parses.
21
+ *
22
+ * @param sig significant digits, default 6
23
+ */
24
+ export declare function formatNumber(v: number | null | undefined, sig?: number): string;
25
+ /** Format a 0..1 ratio as a percentage. */
26
+ export declare function formatPercent(v: number | null | undefined): string;
27
+ //# sourceMappingURL=format.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"format.d.ts","sourceRoot":"","sources":["../../../libs/analyst/src/lib/format.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,uDAAuD;AACvD,eAAO,MAAM,QAAQ,QAAQ,CAAC;AAE9B;;;;;;;;;GASG;AACH,wBAAgB,YAAY,CAAC,CAAC,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,EAAE,GAAG,SAAI,GAAG,MAAM,CAkB1E;AAED,2CAA2C;AAC3C,wBAAgB,aAAa,CAAC,CAAC,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,GAAG,MAAM,CAGlE"}
@@ -0,0 +1,37 @@
1
+ import { ApexChartsCtor, WeavePluginDefinition } from './weave';
2
+ import { AnalystOptions } from './Analyst';
3
+
4
+ export declare const PLUGIN_NAME = "analyst";
5
+ /** The Weave plugin definition. */
6
+ export declare const analystPlugin: WeavePluginDefinition;
7
+ /**
8
+ * Register the plugin with an ApexCharts constructor.
9
+ *
10
+ * Idempotent. Charts opt in individually with `plugins: [{name: 'analyst'}]`.
11
+ */
12
+ export declare function install(ApexCharts: ApexChartsCtor): void;
13
+ /**
14
+ * Register the plugin AND make every chart created afterwards use it.
15
+ *
16
+ * Writes the plugin into the library's global chart defaults, so no chart needs
17
+ * a `plugins` entry of its own.
18
+ *
19
+ * One caveat, stated because it is silent otherwise: a chart that declares its
20
+ * own top-level `plugins` array overrides the global default rather than
21
+ * merging with it, so such a chart must list `analyst` itself.
22
+ *
23
+ * @param options passed to every chart's Analyst instance
24
+ */
25
+ export declare function installOnEveryChart(ApexCharts: ApexChartsCtor, options?: AnalystOptions): void;
26
+ /**
27
+ * Show the toolbar on every chart that already has the plugin.
28
+ *
29
+ * Only useful alongside `plugins: [{name: 'analyst', options: {enabled: false}}]`,
30
+ * where charts carry the plugin but render no toolbar until this is called.
31
+ * Charts created before the plugin was installed cannot be reached at all;
32
+ * this reports how many were missed rather than pretending to cover them.
33
+ *
34
+ * @returns how many charts the toolbar was attached to
35
+ */
36
+ export declare function page(): number;
37
+ //# sourceMappingURL=plugin.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"plugin.d.ts","sourceRoot":"","sources":["../../../libs/analyst/src/lib/plugin.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AAGH,OAAO,KAAK,EAAC,cAAc,EAAC,MAAM,WAAW,CAAC;AAE9C,OAAO,KAAK,EAAC,cAAc,EAAY,qBAAqB,EAAC,MAAM,SAAS,CAAC;AAE7E,eAAO,MAAM,WAAW,YAAY,CAAC;AAErC,mCAAmC;AACnC,eAAO,MAAM,aAAa,EAAE,qBAW3B,CAAC;AAEF;;;;GAIG;AACH,wBAAgB,OAAO,CAAC,UAAU,EAAE,cAAc,GAAG,IAAI,CAExD;AAMD;;;;;;;;;;;GAWG;AACH,wBAAgB,mBAAmB,CAAC,UAAU,EAAE,cAAc,EAAE,OAAO,CAAC,EAAE,cAAc,GAAG,IAAI,CAS9F;AAED;;;;;;;;;GASG;AACH,wBAAgB,IAAI,IAAI,MAAM,CA0B7B"}
@@ -0,0 +1,40 @@
1
+ /**
2
+ * The page-level registry of live Analyst instances.
3
+ *
4
+ * `page()` needs every chart on the page, and the chart library's own
5
+ * `Apex._chartInstances` cannot supply that: a chart joins it only when the
6
+ * caller declares `chart.id`, which is optional and uncommon.
7
+ *
8
+ * Instances self-register from their constructor and unregister on teardown, so
9
+ * the set holds only mounted charts.
10
+ *
11
+ * Stored on a versioned `globalThis` slot, matching the registries inside the
12
+ * chart library itself. A module-level Set would live once per copy of this
13
+ * module, and a page can hold two (a bundled import plus a script tag).
14
+ *
15
+ * @module registry
16
+ */
17
+ type Registrable = {
18
+ attach(): void;
19
+ };
20
+ export declare function register(analyst: Registrable): void;
21
+ export declare function unregister(analyst: Registrable): void;
22
+ /**
23
+ * Every registered Analyst, in creation order (a Set preserves insertion order,
24
+ * which is the order the charts were created and so the reading order of the
25
+ * page in the overwhelmingly common case).
26
+ */
27
+ export declare function all(): Registrable[];
28
+ /**
29
+ * How many ApexCharts charts are actually in the document.
30
+ *
31
+ * Compared against the registry size to detect the load-order mistake: a chart
32
+ * created before the plugin was installed never got an Analyst instance and
33
+ * cannot be reached. Counts `.apexcharts-canvas` nodes, the wrap element the
34
+ * chart library creates for every chart on every render path.
35
+ *
36
+ * @returns -1 when there is no document to count (SSR)
37
+ */
38
+ export declare function countChartsInDom(): number;
39
+ export {};
40
+ //# sourceMappingURL=registry.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"registry.d.ts","sourceRoot":"","sources":["../../../libs/analyst/src/lib/registry.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAIH,KAAK,WAAW,GAAG;IAAC,MAAM,IAAI,IAAI,CAAA;CAAC,CAAC;AAYpC,wBAAgB,QAAQ,CAAC,OAAO,EAAE,WAAW,GAAG,IAAI,CAEnD;AAED,wBAAgB,UAAU,CAAC,OAAO,EAAE,WAAW,GAAG,IAAI,CAErD;AAED;;;;GAIG;AACH,wBAAgB,GAAG,IAAI,WAAW,EAAE,CAEnC;AAED;;;;;;;;;GASG;AACH,wBAAgB,gBAAgB,IAAI,MAAM,CAGzC"}
@@ -0,0 +1,50 @@
1
+ /**
2
+ * Descriptive statistics.
3
+ *
4
+ * Pure functions over `Array<number|null|undefined>`, because that is what a
5
+ * chart series actually holds: a null is a gap, and every one of these
6
+ * summaries has to skip gaps rather than read them as zero. Each function
7
+ * filters internally instead of trusting the caller to have cleaned the input,
8
+ * and reports how many values it dropped.
9
+ *
10
+ * No dependencies and no chart knowledge. The panel, a derived series and (in a
11
+ * later phase) an AI payload all read the same numbers from here rather than
12
+ * each reimplementing a mean.
13
+ *
14
+ * @module stats/Descriptive
15
+ */
16
+ export interface Summary {
17
+ /** finite values used */
18
+ count: number;
19
+ /** values skipped (null / NaN / non-numeric) */
20
+ missing: number;
21
+ min: number;
22
+ max: number;
23
+ range: number;
24
+ sum: number;
25
+ mean: number;
26
+ median: number;
27
+ q1: number;
28
+ q3: number;
29
+ iqr: number;
30
+ /** sample variance (n-1) */
31
+ variance: number;
32
+ stdDev: number;
33
+ }
34
+ /**
35
+ * The finite numbers in a raw series, in order.
36
+ *
37
+ * `Number.isFinite` rather than `!= null` on purpose: NaN and Infinity reach a
38
+ * series through user formatters and arithmetic on missing values, and either
39
+ * one poisons a mean into NaN for the whole chart.
40
+ */
41
+ export declare function finite(values: ReadonlyArray<number | null | undefined> | undefined): number[];
42
+ /**
43
+ * Summarize one series.
44
+ *
45
+ * Returns null rather than a zero-filled object when there is nothing to
46
+ * summarize, so a caller has to decide what to show for an empty series instead
47
+ * of silently rendering "mean 0" for no data.
48
+ */
49
+ export declare function summarize(values: ReadonlyArray<number | null | undefined> | undefined): Summary | null;
50
+ //# sourceMappingURL=Descriptive.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Descriptive.d.ts","sourceRoot":"","sources":["../../../../libs/analyst/src/lib/stats/Descriptive.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAEH,MAAM,WAAW,OAAO;IACtB,yBAAyB;IACzB,KAAK,EAAE,MAAM,CAAC;IACd,gDAAgD;IAChD,OAAO,EAAE,MAAM,CAAC;IAChB,GAAG,EAAE,MAAM,CAAC;IACZ,GAAG,EAAE,MAAM,CAAC;IACZ,KAAK,EAAE,MAAM,CAAC;IACd,GAAG,EAAE,MAAM,CAAC;IACZ,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;IACf,EAAE,EAAE,MAAM,CAAC;IACX,EAAE,EAAE,MAAM,CAAC;IACX,GAAG,EAAE,MAAM,CAAC;IACZ,4BAA4B;IAC5B,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,MAAM,CAAC;CAChB;AAED;;;;;;GAMG;AACH,wBAAgB,MAAM,CAAC,MAAM,EAAE,aAAa,CAAC,MAAM,GAAG,IAAI,GAAG,SAAS,CAAC,GAAG,SAAS,GAAG,MAAM,EAAE,CAQ7F;AAgBD;;;;;;GAMG;AACH,wBAAgB,SAAS,CAAC,MAAM,EAAE,aAAa,CAAC,MAAM,GAAG,IAAI,GAAG,SAAS,CAAC,GAAG,SAAS,GAAG,OAAO,GAAG,IAAI,CA+CtG"}
@@ -0,0 +1,42 @@
1
+ /**
2
+ * Ordinary-least-squares line fitting.
3
+ *
4
+ * Used by the trend tool now and by the forecast tool later, which is why the
5
+ * fit is returned as a describable object (slope, intercept, r2, predict)
6
+ * rather than as the two endpoints the trend line happens to need. A forecast
7
+ * extrapolates from the same fit, and a readout needs to say how well the line
8
+ * actually explains the data.
9
+ *
10
+ * @module stats/Regression
11
+ */
12
+ export interface LinearFit {
13
+ /** change in y per unit x */
14
+ slope: number;
15
+ /** y at x = 0 */
16
+ intercept: number;
17
+ /** coefficient of determination, 0..1 */
18
+ r2: number;
19
+ /** points used */
20
+ n: number;
21
+ predict(x: number): number;
22
+ }
23
+ export interface FitPoint {
24
+ x: number;
25
+ y: number;
26
+ }
27
+ /**
28
+ * Fit y = slope*x + intercept by least squares.
29
+ *
30
+ * Pairs where either coordinate is non-finite are dropped: a series gap must
31
+ * not contribute a point at y=0, and an x gap has no position to contribute at
32
+ * all. Returns null when fewer than two usable pairs remain, or when every x is
33
+ * identical (a vertical line has no finite slope) rather than returning an
34
+ * Infinity a caller would then have to detect.
35
+ *
36
+ * x is centered before accumulating the sums. Chart x values are frequently
37
+ * epoch milliseconds, where x^2 is around 1e24 and the textbook
38
+ * `n*Sxy - Sx*Sy` form loses most of its significant digits to cancellation.
39
+ * Centering keeps the magnitudes small and costs one extra pass.
40
+ */
41
+ export declare function linearFit(points: ReadonlyArray<FitPoint> | undefined): LinearFit | null;
42
+ //# sourceMappingURL=Regression.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Regression.d.ts","sourceRoot":"","sources":["../../../../libs/analyst/src/lib/stats/Regression.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,MAAM,WAAW,SAAS;IACxB,6BAA6B;IAC7B,KAAK,EAAE,MAAM,CAAC;IACd,iBAAiB;IACjB,SAAS,EAAE,MAAM,CAAC;IAClB,yCAAyC;IACzC,EAAE,EAAE,MAAM,CAAC;IACX,kBAAkB;IAClB,CAAC,EAAE,MAAM,CAAC;IACV,OAAO,CAAC,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;CAC5B;AAED,MAAM,WAAW,QAAQ;IACvB,CAAC,EAAE,MAAM,CAAC;IACV,CAAC,EAAE,MAAM,CAAC;CACX;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAgB,SAAS,CAAC,MAAM,EAAE,aAAa,CAAC,QAAQ,CAAC,GAAG,SAAS,GAAG,SAAS,GAAG,IAAI,CAwDvF"}
@@ -0,0 +1,59 @@
1
+ import { WeaveAPI, WeaveChartInfo } from '../weave';
2
+
3
+ /**
4
+ * The fixed grid the fake scales project into. Exported so tests can predict
5
+ * pixels. `left`/`top` are non-zero ON PURPOSE: the real rc.2 host bakes the
6
+ * layout translate into its scales while the layer group is already translated,
7
+ * and Analyst has to rebase them to layer space. A fake whose scales started at
8
+ * zero could not tell a rebased drawing from an unrebased one.
9
+ */
10
+ export declare const FAKE_GRID: {
11
+ left: number;
12
+ top: number;
13
+ width: number;
14
+ height: number;
15
+ };
16
+ export interface FakeChartSpec {
17
+ /** Series as the caller wrote them. */
18
+ series: Array<{
19
+ name: string;
20
+ data: unknown[];
21
+ hidden?: boolean;
22
+ }>;
23
+ categories?: string[];
24
+ info?: Partial<WeaveChartInfo>;
25
+ options?: Record<string, unknown>;
26
+ }
27
+ export interface FakeChart {
28
+ api: WeaveAPI;
29
+ el: HTMLElement;
30
+ /** Every series currently on the chart, in order. */
31
+ seriesNames(): string[];
32
+ seriesData(name: string): unknown[] | undefined;
33
+ /** Simulate the caller changing the data. */
34
+ update(series: Array<{
35
+ name: string;
36
+ data: unknown[];
37
+ }>): Promise<void>;
38
+ /** Fire the initial render hook. */
39
+ draw(): void;
40
+ destroy(): void;
41
+ }
42
+ export declare function makeFakeChart(spec: FakeChartSpec): FakeChart;
43
+ /** The toolbar's shadow root for a given fake chart, or null. */
44
+ export declare function shadowOf(chart: FakeChart): ShadowRoot | null;
45
+ /**
46
+ * The readout's shadow root, which is a SEPARATE host from the toolbar's: the
47
+ * buttons go above the chart and the panel below it, so opening a readout
48
+ * cannot push the chart down the page.
49
+ */
50
+ export declare function panelRootOf(chart: FakeChart): ShadowRoot | null;
51
+ /** The readout element itself, or null when no toolbar is mounted. */
52
+ export declare function panelOf(chart: FakeChart): HTMLElement | null;
53
+ /** The plugin's drawing group inside the fake chart's SVG, or null. */
54
+ export declare function overlayOf(chart: FakeChart): SVGGElement | null;
55
+ /** The `<line>` elements currently drawn on the overlay. */
56
+ export declare function overlayLines(chart: FakeChart): SVGLineElement[];
57
+ export declare function buttonLabels(chart: FakeChart): string[];
58
+ export declare function clickButton(chart: FakeChart, label: string): HTMLButtonElement;
59
+ //# sourceMappingURL=fakeWeave.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"fakeWeave.d.ts","sourceRoot":"","sources":["../../../../libs/analyst/src/lib/testing/fakeWeave.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AAEH,OAAO,KAAK,EAAC,QAAQ,EAAE,cAAc,EAAkD,MAAM,UAAU,CAAC;AAIxG;;;;;;GAMG;AACH,eAAO,MAAM,SAAS;;;;;CAA+C,CAAC;AAEtE,MAAM,WAAW,aAAa;IAC5B,uCAAuC;IACvC,MAAM,EAAE,KAAK,CAAC;QAAC,IAAI,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,OAAO,EAAE,CAAC;QAAC,MAAM,CAAC,EAAE,OAAO,CAAA;KAAC,CAAC,CAAC;IACjE,UAAU,CAAC,EAAE,MAAM,EAAE,CAAC;IACtB,IAAI,CAAC,EAAE,OAAO,CAAC,cAAc,CAAC,CAAC;IAC/B,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CACnC;AAED,MAAM,WAAW,SAAS;IACxB,GAAG,EAAE,QAAQ,CAAC;IACd,EAAE,EAAE,WAAW,CAAC;IAChB,qDAAqD;IACrD,WAAW,IAAI,MAAM,EAAE,CAAC;IACxB,UAAU,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,EAAE,GAAG,SAAS,CAAC;IAChD,6CAA6C;IAC7C,MAAM,CAAC,MAAM,EAAE,KAAK,CAAC;QAAC,IAAI,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,OAAO,EAAE,CAAA;KAAC,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACtE,oCAAoC;IACpC,IAAI,IAAI,IAAI,CAAC;IACb,OAAO,IAAI,IAAI,CAAC;CACjB;AA8ED,wBAAgB,aAAa,CAAC,IAAI,EAAE,aAAa,GAAG,SAAS,CA4J5D;AAED,iEAAiE;AACjE,wBAAgB,QAAQ,CAAC,KAAK,EAAE,SAAS,GAAG,UAAU,GAAG,IAAI,CAG5D;AAED;;;;GAIG;AACH,wBAAgB,WAAW,CAAC,KAAK,EAAE,SAAS,GAAG,UAAU,GAAG,IAAI,CAG/D;AAED,sEAAsE;AACtE,wBAAgB,OAAO,CAAC,KAAK,EAAE,SAAS,GAAG,WAAW,GAAG,IAAI,CAE5D;AAED,uEAAuE;AACvE,wBAAgB,SAAS,CAAC,KAAK,EAAE,SAAS,GAAG,WAAW,GAAG,IAAI,CAE9D;AAED,4DAA4D;AAC5D,wBAAgB,YAAY,CAAC,KAAK,EAAE,SAAS,GAAG,cAAc,EAAE,CAE/D;AAED,wBAAgB,YAAY,CAAC,KAAK,EAAE,SAAS,GAAG,MAAM,EAAE,CAIvD;AAED,wBAAgB,WAAW,CAAC,KAAK,EAAE,SAAS,EAAE,KAAK,EAAE,MAAM,GAAG,iBAAiB,CAS9E"}
@@ -0,0 +1,31 @@
1
+ /**
2
+ * The shims a real ApexCharts instance needs to render under jsdom.
3
+ *
4
+ * jsdom implements the SVG DOM but none of its geometry, so anything that
5
+ * measures (`getBBox`) returns undefined and the chart throws while laying out
6
+ * axis labels. `ResizeObserver` is missing outright. Both are the same shims the
7
+ * chart library uses for its own jsdom suite; they are here rather than imported
8
+ * because the library does not publish its test scaffolding.
9
+ *
10
+ * The fixed 10x10 box means nothing rendered here is a claim about layout. These
11
+ * tests assert what the plugin puts INTO the chart and gets back out of it, and
12
+ * a real browser is where pixels are checked.
13
+ *
14
+ * @module testing/jsdomSetup
15
+ */
16
+ declare const BOX: {
17
+ x: number;
18
+ y: number;
19
+ width: number;
20
+ height: number;
21
+ top: number;
22
+ right: number;
23
+ bottom: number;
24
+ left: number;
25
+ };
26
+ /** TypeScript's DOM lib has no `getBBox`, so the prototype is widened to add it. */
27
+ type Measurable = {
28
+ getBBox?: () => typeof BOX;
29
+ };
30
+ declare function stubBBox(proto: object | undefined): void;
31
+ //# sourceMappingURL=jsdomSetup.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"jsdomSetup.d.ts","sourceRoot":"","sources":["../../../../libs/analyst/src/lib/testing/jsdomSetup.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAEH,QAAA,MAAM,GAAG;;;;;;;;;CAA8E,CAAC;AAExF,oFAAoF;AACpF,KAAK,UAAU,GAAG;IAAC,OAAO,CAAC,EAAE,MAAM,OAAO,GAAG,CAAA;CAAC,CAAC;AAE/C,iBAAS,QAAQ,CAAC,KAAK,EAAE,MAAM,GAAG,SAAS,GAAG,IAAI,CAKjD"}
@@ -0,0 +1,95 @@
1
+ /**
2
+ * Mounting a real ApexCharts instance, for the tests that have to.
3
+ *
4
+ * The chart library is a peer dependency of the published package and a dev
5
+ * dependency of this workspace. See the note in fakeWeave for which tests belong
6
+ * here and which belong against the fake host.
7
+ *
8
+ * The dev dependency is pinned exactly to `7.2.0-rc.2`. Exactly, not a range:
9
+ * this package declares `apiVersion: 2`, so the host it is tested against is
10
+ * the contract, and moving off it should be a decision rather than something an
11
+ * install does on its own. Moving the pin means running this file's tests
12
+ * against the new host and reading what changed under `src/modules/weave`.
13
+ *
14
+ * The peer range stays `>=7.2.0-rc.1`, which is the earliest host that carries
15
+ * Weave v2 and is the honest floor for a consumer. It is deliberately wider
16
+ * than the pin: the pin says what we test, the range says what works.
17
+ *
18
+ * When 7.2.0 stable lands, move the pin to it and tighten the peer range to
19
+ * `>=7.2.0`, so a consumer is not pointed at a prerelease.
20
+ *
21
+ * @module testing/realChart
22
+ */
23
+ /** The chart internals these tests read. Not part of any public contract. */
24
+ interface ChartInternals {
25
+ w: {
26
+ config: {
27
+ series: Array<{
28
+ name?: string;
29
+ data?: unknown[];
30
+ }>;
31
+ dataLabels?: {
32
+ enabled?: boolean;
33
+ enabledOnSeries?: number[];
34
+ };
35
+ };
36
+ globals: {
37
+ series: number[][];
38
+ seriesX: number[][];
39
+ initialSeries: Array<{
40
+ name?: string;
41
+ data?: unknown[];
42
+ }>;
43
+ };
44
+ };
45
+ render(): Promise<unknown>;
46
+ updateSeries(series: unknown[]): Promise<unknown>;
47
+ resetSeries(): void;
48
+ destroy(): void;
49
+ }
50
+ export interface RealChart {
51
+ chart: ChartInternals;
52
+ el: HTMLElement;
53
+ /** Every series on the chart, in order, derived ones included. */
54
+ names(): string[];
55
+ /** The data array as it sits in config: the shape the plugin emitted. */
56
+ raw(name: string): unknown[] | undefined;
57
+ /** Parsed y values. A wrong emitted shape shows up here as all-null. */
58
+ y(name: string): Array<number | null> | undefined;
59
+ /** Parsed x positions. A derived series must land on the source's. */
60
+ x(name: string): number[] | undefined;
61
+ destroy(): void;
62
+ }
63
+ /** Let a queued chart update and the render it causes finish. */
64
+ export declare function settle(rounds?: number): Promise<void>;
65
+ /**
66
+ * Render a chart with the analyst plugin attached.
67
+ *
68
+ * Animations are off because an animated update resolves on a timer, and a test
69
+ * that waits for one is measuring the machine.
70
+ */
71
+ export declare function mountChart(options: Record<string, unknown>): Promise<RealChart>;
72
+ /** The toolbar's shadow root for a real chart, or null when none attached. */
73
+ export declare function shadowOf(chart: RealChart): ShadowRoot | null;
74
+ export declare function buttonLabels(chart: RealChart): string[];
75
+ /** Click a toolbar button and wait for whatever it queued against the chart. */
76
+ export declare function clickButton(chart: RealChart, label: string): Promise<void>;
77
+ /**
78
+ * The readout's shadow root, which is a SEPARATE host from the toolbar's: the
79
+ * buttons go above the chart and the panel below it, so opening a readout
80
+ * cannot push the chart down the page.
81
+ */
82
+ export declare function panelRootOf(chart: RealChart): ShadowRoot | null;
83
+ /** Text of the open readout panel. */
84
+ export declare function panelText(chart: RealChart): string;
85
+ /**
86
+ * The plugin's drawing group inside the REAL chart's SVG, or null. Its
87
+ * placement is the host's `_layer()` contract: a `<g class="apexcharts-plugin-
88
+ * analyst">` appended to the chart's graphical group, so it ships in the
89
+ * chart's own exports and needs no position syncing.
90
+ */
91
+ export declare function overlayOf(chart: RealChart): SVGGElement | null;
92
+ /** The `<line>` elements currently drawn on the overlay. */
93
+ export declare function overlayLines(chart: RealChart): SVGLineElement[];
94
+ export {};
95
+ //# sourceMappingURL=realChart.d.ts.map