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.
- package/LICENSE +97 -0
- package/README.md +184 -0
- package/apex-analyst.es.min.js +1 -0
- package/apex-analyst.min.js +1 -0
- package/demo/index.html +127 -0
- package/index.d.ts +15 -0
- package/index.d.ts.map +1 -0
- package/lib/Analyst.d.ts +69 -0
- package/lib/Analyst.d.ts.map +1 -0
- package/lib/AnalystPanel.d.ts +81 -0
- package/lib/AnalystPanel.d.ts.map +1 -0
- package/lib/SeriesModel.d.ts +39 -0
- package/lib/SeriesModel.d.ts.map +1 -0
- package/lib/clip.d.ts +25 -0
- package/lib/clip.d.ts.map +1 -0
- package/lib/emitLike.d.ts +73 -0
- package/lib/emitLike.d.ts.map +1 -0
- package/lib/format.d.ts +27 -0
- package/lib/format.d.ts.map +1 -0
- package/lib/plugin.d.ts +37 -0
- package/lib/plugin.d.ts.map +1 -0
- package/lib/registry.d.ts +40 -0
- package/lib/registry.d.ts.map +1 -0
- package/lib/stats/Descriptive.d.ts +50 -0
- package/lib/stats/Descriptive.d.ts.map +1 -0
- package/lib/stats/Regression.d.ts +42 -0
- package/lib/stats/Regression.d.ts.map +1 -0
- package/lib/testing/fakeWeave.d.ts +59 -0
- package/lib/testing/fakeWeave.d.ts.map +1 -0
- package/lib/testing/jsdomSetup.d.ts +31 -0
- package/lib/testing/jsdomSetup.d.ts.map +1 -0
- package/lib/testing/realChart.d.ts +95 -0
- package/lib/testing/realChart.d.ts.map +1 -0
- package/lib/tools/DataTable.d.ts +5 -0
- package/lib/tools/DataTable.d.ts.map +1 -0
- package/lib/tools/SummaryStats.d.ts +5 -0
- package/lib/tools/SummaryStats.d.ts.map +1 -0
- package/lib/tools/TrendLine.d.ts +5 -0
- package/lib/tools/TrendLine.d.ts.map +1 -0
- package/lib/tools/types.d.ts +45 -0
- package/lib/tools/types.d.ts.map +1 -0
- package/lib/weave.d.ts +169 -0
- package/lib/weave.d.ts.map +1 -0
- package/package.json +30 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
## π License Options for ApexCharts
|
|
2
|
+
|
|
3
|
+
ApexCharts is offered under a **dual-license model** to support individuals, startups, and commercial products of all sizes.
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
### π Community License (Free)
|
|
8
|
+
|
|
9
|
+
For individuals, non-profits, educators, and small businesses with **less than $2 million USD in annual revenue**.
|
|
10
|
+
|
|
11
|
+
β
Whatβs allowed:
|
|
12
|
+
|
|
13
|
+
- Personal, educational, or non-profit use
|
|
14
|
+
- Commercial use by small orgs (< $2M annual revenue)
|
|
15
|
+
- Modifications and redistribution (with attribution)
|
|
16
|
+
|
|
17
|
+
π« Not allowed:
|
|
18
|
+
|
|
19
|
+
- Use by companies or entities over $2M/year revenue
|
|
20
|
+
- Use in competing charting products
|
|
21
|
+
- Sublicensing under different terms
|
|
22
|
+
|
|
23
|
+
β‘ By using ApexCharts under this license, you confirm that **you qualify as a Small Organization**.
|
|
24
|
+
|
|
25
|
+
---
|
|
26
|
+
|
|
27
|
+
### πΌ Commercial License (Paid)
|
|
28
|
+
|
|
29
|
+
The Commercial License applies to all paid SKUs (Pro, Premium, and OEM/Embedded).
|
|
30
|
+
|
|
31
|
+
The **Community License** is available at no cost **only** to organizations with **annual revenue under \$2M USD**.
|
|
32
|
+
If your organization earns \$2M USD or more annually, you must purchase one of our paid licenses.
|
|
33
|
+
|
|
34
|
+
β
What's included:
|
|
35
|
+
|
|
36
|
+
- Use in internal tools and commercial applications
|
|
37
|
+
- Modifications and app-level distribution
|
|
38
|
+
- 12-month subscription with updates & support
|
|
39
|
+
|
|
40
|
+
π« Not allowed:
|
|
41
|
+
|
|
42
|
+
- Redistribution in toolkits, SDKs, or platforms
|
|
43
|
+
- Use by unlicensed developers
|
|
44
|
+
- Competing charting products
|
|
45
|
+
|
|
46
|
+
---
|
|
47
|
+
|
|
48
|
+
### π Non-Profit Tiers
|
|
49
|
+
|
|
50
|
+
Non-Profits are treated like commercial entities:
|
|
51
|
+
|
|
52
|
+
- Annual operating budget **less than \$2 million USD**
|
|
53
|
+
- _Eligible for Community licenses_
|
|
54
|
+
|
|
55
|
+
or
|
|
56
|
+
|
|
57
|
+
- Annual operating budget **greater than \$2 million USD**
|
|
58
|
+
- _Commerical license with possible discounts for Community, Pro, Premium and OEM products_
|
|
59
|
+
|
|
60
|
+
---
|
|
61
|
+
|
|
62
|
+
### π OEM / Redistribution License (Paid)
|
|
63
|
+
|
|
64
|
+
Required if you are **embedding ApexCharts into a product or platform used by other people**, such as:
|
|
65
|
+
|
|
66
|
+
- No-code dashboards
|
|
67
|
+
- Developer platforms
|
|
68
|
+
- Embedded BI tools
|
|
69
|
+
- SDKs
|
|
70
|
+
|
|
71
|
+
β
What's included:
|
|
72
|
+
|
|
73
|
+
- Redistribution rights for 1 application or product
|
|
74
|
+
- 12-month subscription with updates & support
|
|
75
|
+
|
|
76
|
+
β
OEM **not required** if your app simply renders static charts and users **cannot** configure or interact with them.
|
|
77
|
+
|
|
78
|
+
---
|
|
79
|
+
|
|
80
|
+
### β οΈ License Acceptance
|
|
81
|
+
|
|
82
|
+
By installing ApexCharts (e.g., via `npm install apex-analyst`), you are agreeing to the applicable license based on your usage:
|
|
83
|
+
|
|
84
|
+
- Community License (if under $2M revenue)
|
|
85
|
+
- Commercial License (if over $2M revenue)
|
|
86
|
+
- OEM License (if redistributing to third-party users)
|
|
87
|
+
|
|
88
|
+
---
|
|
89
|
+
|
|
90
|
+
### π Need a License or Have Questions?
|
|
91
|
+
|
|
92
|
+
π§ Contact us at [sales@apexcharts.com](mailto:sales@apexcharts.com)
|
|
93
|
+
π Read full license agreements here: [https://apexcharts.com/license](https://apexcharts.com/license)
|
|
94
|
+
|
|
95
|
+
---
|
|
96
|
+
|
|
97
|
+
Thank you for supporting ApexCharts! Your licensing helps keep it free and open for individuals and small teams.
|
package/README.md
ADDED
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
# apex-analyst
|
|
2
|
+
|
|
3
|
+
An in-chart analysis toolbar for the ApexCharts product family. It attaches to a chart through the Weave plugin API and adds a small toolbar above it: summary statistics, the data behind the chart, and a least-squares trend line.
|
|
4
|
+
|
|
5
|
+
It is a plugin, not a fork. Nothing here reaches into chart internals, so the package can move at its own pace and, in time, sit over the sibling products rather than only over charts.
|
|
6
|
+
|
|
7
|
+
## Installation
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
npm install apex-analyst@next apexcharts@next
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
**This release needs `apexcharts@next`.** It attaches through version 2 of the Weave plugin API, which ships in 7.2.0. That is on the `next` tag while 7.2.0 is in release candidate, so the stable `apexcharts` (7.1.0) does not satisfy the peer range and a plain `npm install apexcharts` alongside this package will fail to resolve. Both move to `latest` together when 7.2.0 goes stable.
|
|
14
|
+
|
|
15
|
+
`apexcharts` is a peer dependency. The Weave plugin host must be present: it is in the full bundle, and with the lean core you add it explicitly.
|
|
16
|
+
|
|
17
|
+
## Use
|
|
18
|
+
|
|
19
|
+
### One chart
|
|
20
|
+
|
|
21
|
+
```js
|
|
22
|
+
import ApexCharts from 'apexcharts';
|
|
23
|
+
import 'apexcharts/features/weave'; // already in the full bundle
|
|
24
|
+
import {install} from 'apex-analyst';
|
|
25
|
+
|
|
26
|
+
install(ApexCharts);
|
|
27
|
+
|
|
28
|
+
new ApexCharts(el, {
|
|
29
|
+
chart: {type: 'line'},
|
|
30
|
+
series: [
|
|
31
|
+
{
|
|
32
|
+
name: 'MRR',
|
|
33
|
+
data: [
|
|
34
|
+
/* ... */
|
|
35
|
+
],
|
|
36
|
+
},
|
|
37
|
+
],
|
|
38
|
+
plugins: [{name: 'analyst'}],
|
|
39
|
+
}).render();
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
### Every chart on the page
|
|
43
|
+
|
|
44
|
+
```js
|
|
45
|
+
import {installOnEveryChart} from 'apex-analyst';
|
|
46
|
+
|
|
47
|
+
installOnEveryChart(ApexCharts); // BEFORE creating any chart
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
That writes the plugin into the chart library's global defaults, so no chart needs a `plugins` entry of its own.
|
|
51
|
+
|
|
52
|
+
**Order matters.** Plugins are resolved when a chart is constructed, so a chart created before this call cannot be reached and nothing can retrofit it. A chart that declares its own top-level `plugins` array overrides the global default rather than merging with it, so such a chart must list `analyst` itself.
|
|
53
|
+
|
|
54
|
+
### Script tag
|
|
55
|
+
|
|
56
|
+
```html
|
|
57
|
+
<script src="apexcharts.js"></script>
|
|
58
|
+
<script src="apex-analyst.min.js"></script>
|
|
59
|
+
<script>
|
|
60
|
+
ApexAnalyst.installOnEveryChart(ApexCharts);
|
|
61
|
+
</script>
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
## Options
|
|
65
|
+
|
|
66
|
+
Passed per chart as `plugins: [{name: 'analyst', options: {...}}]`, or once via the second argument to `installOnEveryChart`.
|
|
67
|
+
|
|
68
|
+
| Option | Type | Meaning |
|
|
69
|
+
| --------- | ---------- | ---------------------------------------------------------------------- |
|
|
70
|
+
| `tools` | `string[]` | Narrow and order the toolbar by id: `summary`, `data`, `trend`. |
|
|
71
|
+
| `enabled` | `boolean` | `false` keeps the plugin loaded but renders no toolbar until `page()`. |
|
|
72
|
+
|
|
73
|
+
## Tools
|
|
74
|
+
|
|
75
|
+
Tools come in two kinds, and the difference is a promise rather than an implementation detail.
|
|
76
|
+
|
|
77
|
+
**Panel tools never touch the chart.** Open one on any chart on any page and nothing has been mutated.
|
|
78
|
+
|
|
79
|
+
- **Summary**: n, min, max, mean, median, sample standard deviation and sum per visible series. Gaps are excluded and the count of excluded values is stated.
|
|
80
|
+
- **Data**: the values behind the chart, with the real category labels. A gap and a zero are shown differently. Long series are capped and the cap is reported.
|
|
81
|
+
|
|
82
|
+
**Toggle tools draw on the chart until switched off.**
|
|
83
|
+
|
|
84
|
+
- **Trend**: a dashed least-squares guide line per visible series, in that series' own colour, repainted whenever the data changes.
|
|
85
|
+
|
|
86
|
+
Every tool declares which charts it can read. A tool that cannot work on this chart type is not rendered at all, so the toolbar is shorter on a pie than on a time series rather than offering a button that fails.
|
|
87
|
+
|
|
88
|
+
## What it will not do to your chart
|
|
89
|
+
|
|
90
|
+
An add-on that mutates a chart it did not create can damage it in ways that look like library bugs. This package's answer is architectural: tools DRAW on the plugin's own layer inside the chart's SVG, through the host's projection, and never write to the chart itself.
|
|
91
|
+
|
|
92
|
+
- No series is added: your legend, tooltip, CSV export and `resetSeries()` snapshot stay exactly yours.
|
|
93
|
+
- No config is written: `markers`, `stroke`, `dataLabels` and every other series-indexed option keep the values you set.
|
|
94
|
+
- Drawings carry no pointer events and are hidden from screen readers, so tooltips and accessibility read the chart, not the analysis.
|
|
95
|
+
- Drawings are clipped to the grid and projected through the same scales your series were painted with, so they stay aligned under zoom, pan and resize.
|
|
96
|
+
- The drawing rides inside the chart's own SVG, so `dataURI()` and the PNG/SVG exports include it with no extra work.
|
|
97
|
+
- Switching a tool off leaves the chart exactly as it was, because there is nothing to undo.
|
|
98
|
+
|
|
99
|
+
## API
|
|
100
|
+
|
|
101
|
+
### Attaching
|
|
102
|
+
|
|
103
|
+
| Member | Description |
|
|
104
|
+
| --- | --- |
|
|
105
|
+
| `install` | Register the plugin on a chart constructor. Charts then opt in with `plugins: [{name: 'analyst'}]`. |
|
|
106
|
+
| `installOnEveryChart` | Register the plugin and write it into the chart library's global defaults, so every chart created afterwards carries it. Takes the shared `AnalystOptions` as its second argument. |
|
|
107
|
+
| `page` | Attach to charts already on the page, for a plugin loaded with `enabled: false`. |
|
|
108
|
+
| `analystPlugin` | The raw Weave plugin definition, for passing to a host yourself. |
|
|
109
|
+
| `PLUGIN_NAME` | The name the plugin registers under: `'analyst'`. |
|
|
110
|
+
| `Analyst` | The per-chart instance the plugin creates. Rarely constructed directly. |
|
|
111
|
+
| `AnalystOptions` | The options above, as a type. |
|
|
112
|
+
|
|
113
|
+
### Reading the chart
|
|
114
|
+
|
|
115
|
+
The model a tool sees. Useful for writing a tool of your own, or for reading the chart's data without one.
|
|
116
|
+
|
|
117
|
+
| Member | Description |
|
|
118
|
+
| --------------- | ----------------------------------------------------------------------------- |
|
|
119
|
+
| `buildModel` | Build the analysis model from a Weave API handle. |
|
|
120
|
+
| `AnalystModel` | The whole model: the visible series and the chart's categories. |
|
|
121
|
+
| `AnalystSeries` | One series in the model: its name, values and the x basis the scales project. |
|
|
122
|
+
|
|
123
|
+
### Statistics
|
|
124
|
+
|
|
125
|
+
Standalone and chart-free, so they can be used on their own.
|
|
126
|
+
|
|
127
|
+
| Member | Description |
|
|
128
|
+
| ----------- | --------------------------------------------------------------------------------- |
|
|
129
|
+
| `summarize` | n, min, max, mean, median, sample standard deviation and sum for a set of values. |
|
|
130
|
+
| `Summary` | What `summarize` returns, including the count of excluded gaps. |
|
|
131
|
+
| `finite` | The finite numbers in a set, dropping gaps and non-numbers. |
|
|
132
|
+
| `linearFit` | Least-squares fit over `[x, y]` points. |
|
|
133
|
+
| `LinearFit` | The fitted line: slope, intercept and the predictor. |
|
|
134
|
+
| `FitPoint` | One point given to `linearFit`. |
|
|
135
|
+
|
|
136
|
+
### Drawing
|
|
137
|
+
|
|
138
|
+
Exported because any tool drawing on someone else's chart needs them.
|
|
139
|
+
|
|
140
|
+
| Member | Description |
|
|
141
|
+
| ------------- | ---------------------------------------------------------------------------- |
|
|
142
|
+
| `clipSegment` | Clip a line segment to a rectangle, so a drawing cannot paint over the axes. |
|
|
143
|
+
| `PixelRect` | The grid rectangle `clipSegment` clips to, in pixel space. |
|
|
144
|
+
|
|
145
|
+
### Formatting
|
|
146
|
+
|
|
147
|
+
| Member | Description |
|
|
148
|
+
| --------------- | --------------------------------------------------------------------- |
|
|
149
|
+
| `formatNumber` | Format a number for a readout, at a sensible precision. |
|
|
150
|
+
| `formatPercent` | Format a fraction as a percentage. |
|
|
151
|
+
| `NO_VALUE` | What a readout shows for a gap, so a gap and a zero read differently. |
|
|
152
|
+
|
|
153
|
+
### Writing a tool
|
|
154
|
+
|
|
155
|
+
| Member | Description |
|
|
156
|
+
| ---------------- | ---------------------------------------------------------------------------------------- |
|
|
157
|
+
| `Tool` | Union of the two tool kinds. |
|
|
158
|
+
| `PanelTool` | A tool that only reads, and renders into the panel. |
|
|
159
|
+
| `ToggleTool` | A tool that draws on the chart's overlay layer until switched off. |
|
|
160
|
+
| `OverlaySurface` | What a toggle tool draws with: the layer, the scales and the chart's palette. Per paint. |
|
|
161
|
+
|
|
162
|
+
### Weave host types
|
|
163
|
+
|
|
164
|
+
The shape of the plugin host, for typing your own plugin against it.
|
|
165
|
+
|
|
166
|
+
| Member | Description |
|
|
167
|
+
| ----------------------- | ---------------------------------------------------------------------------------- |
|
|
168
|
+
| `WeaveAPI` | The frozen facade a plugin is handed. |
|
|
169
|
+
| `WeavePluginDefinition` | A plugin: its name, api version, setup and teardown. |
|
|
170
|
+
| `WeaveHook` | The lifecycle hooks a plugin can subscribe to. |
|
|
171
|
+
| `WeaveSeries` | One series as the host presents it, parsed values and raw data alike. |
|
|
172
|
+
| `WeaveChartInfo` | What the host reports about the chart: its type and axis kind. |
|
|
173
|
+
| `WeaveLayer` | The plugin's own `<g>` in the chart's SVG, wrapped in drawing primitives. |
|
|
174
|
+
| `WeaveScales` | Data-to-pixel projection, built from the same ratios the series were painted with. |
|
|
175
|
+
| `WeaveTheme` | The host's resolved theme: mode, foreground colour and the series palette. |
|
|
176
|
+
| `ApexChartsCtor` | The chart constructor shape `install` accepts. |
|
|
177
|
+
|
|
178
|
+
## Licensing
|
|
179
|
+
|
|
180
|
+
Not open source. The source being readable on GitHub does not make it so.
|
|
181
|
+
|
|
182
|
+
Free under the Community terms for organisations under the revenue threshold; a Commercial licence is required otherwise. Without a valid key the toolbar keeps working and the chart carries a trial watermark, which is deterrence and honest-customer compliance rather than DRM: it never degrades a tool. Keys are shared across the ApexCharts family, so one key covers this too.
|
|
183
|
+
|
|
184
|
+
See <https://apexcharts.com/pricing>.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
const e=globalThis.console;function t(t,...n){e.error(t,...n)}function n(t){e.warn(t)}const i="APEX-",a=new Date("2027-07-31T00:00:00Z"),s="__apex_license_v1__";function o(){const e=globalThis;let t=e[s];return t||(t={key:null,listeners:new Set,result:null},e[s]=t),t}const r=class{static get licenseKey(){return o().key}static set licenseKey(e){o().key=e}static get listeners(){return o().listeners}static get validationResult(){return o().result}static set validationResult(e){o().result=e}static getKey(){return this.licenseKey}static getLicenseStatus(){return this.licenseKey?(this.validationResult=this.validateKey(this.licenseKey),this.validationResult):{expired:!1,signatureVerified:!1,valid:!1}}static isKeyValid(e){return!!e&&this.validateKey(e).valid}static isLicenseValid(){return this.getLicenseStatus().valid}static onChange(e){return this.listeners.add(e),()=>{this.listeners.delete(e)}}static setLicense(e){var i;if(!e)return this.licenseKey=null,void this.publish({expired:!1,signatureVerified:!1,valid:!1});const a=this.validateKey(e);a.valid||e===this.licenseKey||!(null==(i=this.validationResult)?void 0:i.valid)?(this.licenseKey=e,this.publish(a),a.valid||t(`[Apex] ${a.message}`)):n(`[Apex] Ignoring license key: ${a.message??"it is not valid"} A valid license is already active on this page.`)}static validateKey(e){const t=this.parseKey(e),n=this.validateStructure(e,t);if(!n.valid||!(null==t?void 0:t.signature))return n;const i=this.verdicts.get(e);return!1===i?{data:t.data,expired:!1,message:"Invalid license key. The license signature does not verify.",signatureVerified:!0,valid:!1}:(void 0===i&&this.verifySignature(e,t,n),{...n,signatureVerified:!0===i})}static _resetSignatureState(){this.verdicts.clear(),this.verifying.clear(),this.warnedUnverifiable=!1,this.epoch++}static base64ToBytes(e){const t=e.replace(/-/g,"+").replace(/_/g,"/"),n=t.padEnd(4*Math.ceil(t.length/4),"="),i=globalThis.atob;if("function"!=typeof i)throw new Error("no base64 decoder available");const a=i(n),s=new Uint8Array(a.length);for(let e=0;e<a.length;e++)s[e]=a.charCodeAt(e);return s}static canonicalPayload(e){const t=e.domains&&e.domains.length>0?e.domains.join(","):"";return`v1|${e.issueDate}|${e.expiryDate}|${e.plan}|${t}`}static notify(e){for(const t of this.listeners)try{t(e)}catch{}}static parseKey(e){if("string"!=typeof e||!e.startsWith(i))return null;const t=e.slice(5);if(!t)return null;try{const e=(new TextDecoder).decode(this.base64ToBytes(t)),n=JSON.parse(e);return n.issueDate&&n.expiryDate&&n.plan?{data:{domains:Array.isArray(n.domains)?n.domains:void 0,expiryDate:n.expiryDate,issueDate:n.issueDate,plan:n.plan,valid:!0},signature:"string"==typeof n.sig&&n.sig?n.sig:null}:null}catch{return null}}static publish(e){this.validationResult=e,this.notify(e)}static validateStructure(e,t){const n=e=>({expired:!1,message:e,signatureVerified:!1,valid:!1});if("string"!=typeof e||!e.startsWith(i))return n('Invalid license key format. License key must start with "APEX-".');if(!t)return n("Invalid license key. Unable to decode license data.");const{data:s,signature:o}=t;if(!o&&new Date>=a)return n("This license key is in the old unsigned format, which is no longer accepted. Please request a replacement key.");if(new Date(s.expiryDate)<new Date)return{data:s,expired:!0,message:`License expired on ${s.expiryDate}. Please renew your license.`,signatureVerified:!1,valid:!1};if(s.domains&&s.domains.length>0){const e="undefined"==typeof location?"":location.hostname;if(!s.domains.some((t=>e===t||e.endsWith(`.${t}`))))return{data:s,expired:!1,message:`License is not valid for this domain (${e}). Allowed domains: ${s.domains.join(", ")}.`,signatureVerified:!1,valid:!1}}return{data:s,expired:!1,signatureVerified:!1,valid:!0}}static async verifySignature(e,i,a){var s;if(this.verifying.has(e)||this.verdicts.has(e))return;this.verifying.add(e);const o=this.epoch,r=null==(s=globalThis.crypto)?void 0:s.subtle;if(!r||0===this.publicKeysSpki.length)return this.verifying.delete(e),void(this.warnedUnverifiable||(this.warnedUnverifiable=!0,n(r?"[Apex] No license signing key is configured in this build, so license signatures cannot be verified.":"[Apex] Web Crypto is unavailable (a secure context is required), so the license signature cannot be verified.")));const l=(new TextEncoder).encode(this.canonicalPayload(i.data));let c=!1;for(const e of this.publicKeysSpki){try{const t=await r.importKey("spki",this.base64ToBytes(e),{name:"ECDSA",namedCurve:"P-256"},!1,["verify"]);c=await r.verify({hash:"SHA-256",name:"ECDSA"},t,this.base64ToBytes(i.signature),l)}catch{c=!1}if(c)break}if(this.verifying.delete(e),this.epoch!==o)return;if(this.verdicts.set(e,c),c){const t={...a,signatureVerified:!0};return void(this.licenseKey===e?this.publish(t):this.notify(t))}const d="Invalid license key. The license signature does not verify.",h={data:i.data,expired:!1,message:d,signatureVerified:!0,valid:!1};this.licenseKey===e?this.publish(h):this.notify(h),t(`[Apex] ${d}`)}};r.publicKeysSpki=["MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEQIaK9UMD6n0oR/FIy8QdL0uSzKMQlf1BB+tOrji4/WuHsyRNxeDhVykoSsNURozMi1xhmqWvBH1L//xIfugTPA=="],r.verdicts=new Map,r.verifying=new Set,r.warnedUnverifiable=!1,r.epoch=0;let l=r;const c=class{static applyStyles(e){Object.assign(e.style,this.CRITICAL_STYLES,{backgroundImage:this.createWatermarkPattern(),backgroundRepeat:"repeat"})}static node(e){return e?e.querySelector(`[${this.WATERMARK_ATTR}]`):null}static add(e,t){return e&&"undefined"!=typeof document?(this.setManaged(e,t),this.paint(e)):null}static exists(e){return!!this.node(e)}static remove(e,t){e&&(this.setManaged(e,t),this.erase(e))}static untrack(e){this.managed.delete(e)}static createWatermarkPattern(){const e=this.WATERMARK_TEXT;return`url("data:image/svg+xml,${encodeURIComponent(`\n <svg xmlns="http://www.w3.org/2000/svg" width="300" height="200">\n <text\n x="50%"\n y="50%"\n dominant-baseline="middle"\n text-anchor="middle"\n font-family="-apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Arial, sans-serif"\n font-size="18"\n font-weight="600"\n fill="rgba(134, 134, 134, 0.1)"\n transform="rotate(-35, 100, 60)"\n >${e}</text>\n </svg>\n `.trim())}")`}static erase(e){var t;null==(t=this.node(e))||t.remove()}static paint(e){let t=this.node(e);return t||(t=document.createElement("div"),t.setAttribute(this.WATERMARK_ATTR,""),e.appendChild(t)),this.applyStyles(t),"function"==typeof getComputedStyle&&"static"===getComputedStyle(e).position&&(e.style.position="relative"),t}static reconcile(){const e=l.isLicenseValid();for(const t of this.managed)t.isConnected?e?this.erase(t):this.paint(t):this.managed.delete(t)}static setManaged(e,t){!1!==(null==t?void 0:t.manage)?this.track(e):this.managed.delete(e)}static track(e){this.managed.add(e),this.subscribed||(this.subscribed=!0,l.onChange((()=>{this.reconcile()})))}};c.WATERMARK_ATTR="data-apexcharts-watermark",c.WATERMARK_TEXT="APEXCHARTS",c.ATTR="data-apexcharts-watermark",c.CRITICAL_STYLES={bottom:"0",display:"block",left:"0",msUserSelect:"none",opacity:"1",pointerEvents:"none",position:"absolute",right:"0",top:"0",userSelect:"none",visibility:"visible",webkitUserSelect:"none",zIndex:"10000"},c.managed=new Set,c.subscribed=!1;let d=c;class h{constructor(e,t){this.buttons=new Map,this.container=e,this.host=document.createElement("div"),this.host.className="apexcharts-analyst",this.panelHost=document.createElement("div"),this.panelHost.className="apexcharts-analyst-panel",this.insert(e,t),this.bar=document.createElement("div"),this.bar.className="bar",this.bar.setAttribute("role","toolbar"),this.bar.setAttribute("aria-label","Chart analysis"),h.rootFor(this.host).appendChild(this.bar),this.panel=document.createElement("div"),this.panel.className="panel",this.panel.setAttribute("role","region"),this.panel.setAttribute("aria-label","Analysis result"),this.panel.hidden=!0,h.rootFor(this.panelHost).appendChild(this.panel)}static rootFor(e){const t=e.attachShadow({mode:"open"}),n=document.createElement("style");return n.textContent="\n:host {\n --bg: var(--apx-analyst-bg, #ffffff);\n --fg: var(--apx-analyst-fg, #373d3f);\n --muted: var(--apx-analyst-muted, #6e8192);\n --border: var(--apx-analyst-border, #e0e6ed);\n --accent: var(--apx-analyst-accent, #008ffb);\n --accent-fg: var(--apx-analyst-accent-fg, #ffffff);\n --hover: var(--apx-analyst-hover, #f4f7fa);\n --radius: var(--apx-analyst-radius, 6px);\n\n display: block;\n font-family: var(--apx-analyst-font, Helvetica, Arial, sans-serif);\n color: var(--fg);\n /* The host is a plain block in the page flow above the chart. It must never\n take part in the chart's own sizing: a height here would be read by the\n chart's parent-resize observer as a container change and loop. */\n box-sizing: border-box;\n}\n\n@media (prefers-color-scheme: dark) {\n :host {\n --bg: var(--apx-analyst-bg, #1e2329);\n --fg: var(--apx-analyst-fg, #e6edf3);\n --muted: var(--apx-analyst-muted, #96a5b4);\n --border: var(--apx-analyst-border, #333c46);\n --hover: var(--apx-analyst-hover, #262d35);\n }\n}\n\n.bar {\n display: flex;\n flex-wrap: wrap;\n align-items: center;\n gap: 6px;\n padding: 6px 0;\n}\n\nbutton {\n font: inherit;\n font-size: 14px;\n line-height: 1.2;\n color: var(--fg);\n background: var(--bg);\n border: 1px solid var(--border);\n border-radius: var(--radius);\n padding: 6px 10px;\n cursor: pointer;\n}\n\nbutton:hover { background: var(--hover); }\n\nbutton:focus-visible {\n outline: 2px solid var(--accent);\n outline-offset: 1px;\n}\n\nbutton[aria-pressed=\"true\"] {\n background: var(--accent);\n border-color: var(--accent);\n color: var(--accent-fg);\n}\n\n.panel {\n border: 1px solid var(--border);\n border-radius: var(--radius);\n background: var(--bg);\n padding: 10px 12px;\n /* Sits below the chart, so the gap goes above it. */\n margin-top: 8px;\n font-size: 14px;\n /* Wide output (a table with many categories) scrolls inside the panel. The\n page body must never gain a horizontal scrollbar because of the toolbar. */\n overflow-x: auto;\n}\n\n.panel[hidden] { display: none; }\n\n.panel h4 {\n margin: 0 0 8px;\n font-size: 14px;\n font-weight: 600;\n}\n\n.empty { color: var(--muted); }\n\ntable {\n border-collapse: collapse;\n font-size: 13px;\n font-variant-numeric: tabular-nums;\n}\n\nth, td {\n text-align: right;\n padding: 4px 10px 4px 0;\n white-space: nowrap;\n}\n\nth:first-child, td:first-child { text-align: left; }\n\nthead th {\n border-bottom: 1px solid var(--border);\n font-weight: 600;\n color: var(--muted);\n}\n",t.appendChild(n),t}insert(e,t){t&&t.parentNode===e?(e.insertBefore(this.host,t),t.nextSibling?e.insertBefore(this.panelHost,t.nextSibling):e.appendChild(this.panelHost)):(e.appendChild(this.host),e.appendChild(this.panelHost))}reattach(e,t){this.host.parentNode===e&&this.panelHost.parentNode===e||(this.container=e,this.insert(e,t))}setTools(e,t){this.bar.textContent="",this.buttons.clear();for(const n of e){const e=document.createElement("button");e.type="button",e.textContent=n.label,e.setAttribute("aria-pressed","false"),e.addEventListener("click",(()=>t(n.id))),this.bar.appendChild(e),this.buttons.set(n.id,e)}}setPressed(e,t){const n=this.buttons.get(e);n&&n.setAttribute("aria-pressed",t?"true":"false")}openPanel(){return this.panel.textContent="",this.panel.hidden=!1,this.panel}closePanel(){this.panel.textContent="",this.panel.hidden=!0}teardown(){for(const e of[this.host,this.panelHost])e.parentNode&&e.parentNode.removeChild(e);this.buttons.clear()}}function p(e,t){const n=[];for(let i=0;i<e.length;i++){const a=e[i];"number"==typeof a&&Number.isFinite(a)&&n.push({x:t[i],y:a})}return n}function u(e){const t=e.info,n=e.data||[],i=e.categories||[],a=[];if(t.axisChart)for(let e=0;e<n.length;e++){const t=n[e],i=t.points||[],s=i.map((e=>"number"==typeof e.y&&Number.isFinite(e.y)?e.y:null)),o=i.map(((e,t)=>Number.isFinite(e.x)?e.x:t));a.push({index:e,name:null!=t.name?String(t.name):"series-"+(e+1),visible:!t.hidden,values:s,xs:o,points:p(s,o)})}else{const e=[];for(const t of n){const n=(t.points||[])[0],i=n?n.y:null;e.push("number"==typeof i&&Number.isFinite(i)?i:null)}const t=e.map(((e,t)=>t));a.push({index:0,name:"values",visible:!0,values:e,xs:t,points:p(e,t)})}const s=n.map(((e,t)=>null!=e.name?String(e.name):String(t+1)));return{axis:t.axisChart,type:t.type,timeX:t.datetimeX,horizontalBars:t.horizontalBars,series:a,categories:t.axisChart?i:s}}const f="__apex_analyst_v1__";function m(){const e=globalThis;return e[f]||(e[f]=new Set),e[f]}function g(e){if(!Array.isArray(e))return[];const t=[];for(let n=0;n<e.length;n++){const i=e[n];"number"==typeof i&&Number.isFinite(i)&&t.push(i)}return t}function y(e,t){if(1===e.length)return e[0];const n=(e.length-1)*t,i=Math.floor(n),a=Math.ceil(n);return i===a?e[i]:e[i]+(e[a]-e[i])*(n-i)}function v(e){const t=g(e),n=t.length;if(!n)return null;const i=(Array.isArray(e)?e.length:0)-n;let a=0,s=t[0],o=t[0];for(let e=0;e<n;e++){const n=t[e];a+=n,n<s&&(s=n),n>o&&(o=n)}const r=a/n;let l=0;for(let e=0;e<n;e++){const n=t[e]-r;l+=n*n}const c=n>1?l/(n-1):0,d=t.slice().sort(((e,t)=>e-t)),h=y(d,.25),p=y(d,.75);return{count:n,missing:i,min:s,max:o,range:o-s,sum:a,mean:r,median:y(d,.5),q1:h,q3:p,iqr:p-h,variance:c,stdDev:Math.sqrt(c)}}const b="n/a";function x(e,t=6){if("number"!=typeof e||!Number.isFinite(e))return b;if(0===e)return"0";const n=Math.abs(e);if(n>=1e15||n<1e-6)return e.toExponential(2);try{return new Intl.NumberFormat(void 0,{maximumSignificantDigits:t}).format(e)}catch{return String(Math.round(1e6*e)/1e6)}}function C(e){return"number"==typeof e&&Number.isFinite(e)?x(100*e,3)+"%":b}const T=[{key:"count",label:"n"},{key:"min",label:"Min"},{key:"max",label:"Max"},{key:"mean",label:"Mean"},{key:"median",label:"Median"},{key:"stdDev",label:"Std dev"},{key:"sum",label:"Sum"}],w={id:"summary",label:"Summary",kind:"panel",appliesTo:e=>e.series.some((e=>e.visible&&e.values.length>0)),render(e,t){const n=document.createElement("h4");n.textContent="Summary statistics",t.appendChild(n);const i=[];for(const t of e.series){if(!t.visible)continue;const e=v(t.values);e&&i.push({name:t.name,stats:e})}if(!i.length){const e=document.createElement("p");return e.className="empty",e.textContent="No numeric values in the visible series.",void t.appendChild(e)}const a=document.createElement("table"),s=document.createElement("thead"),o=document.createElement("tr"),r=document.createElement("th");r.scope="col",r.textContent="Series",o.appendChild(r);for(const e of T){const t=document.createElement("th");t.scope="col",t.textContent=e.label,o.appendChild(t)}s.appendChild(o),a.appendChild(s);const l=document.createElement("tbody");for(const e of i){const t=document.createElement("tr"),n=document.createElement("th");n.scope="row",n.textContent=e.name,t.appendChild(n);for(const n of T){const i=document.createElement("td"),a=e.stats[n.key];i.textContent="count"===n.key?String(a):x(a),t.appendChild(i)}l.appendChild(t)}a.appendChild(l),t.appendChild(a);const c=i.reduce(((e,t)=>e+t.stats.missing),0);if(c>0){const e=document.createElement("p");e.className="empty",e.textContent=1===c?"1 missing value excluded.":c+" missing values excluded.",t.appendChild(e)}}},A={id:"data",label:"Data",kind:"panel",appliesTo:e=>e.series.some((e=>e.values.length>0)),render(e,t){const n=document.createElement("h4");n.textContent="Data",t.appendChild(n);const i=e.series.filter((e=>e.visible)),a=i.reduce(((e,t)=>Math.max(e,t.values.length)),0);if(!a){const e=document.createElement("p");return e.className="empty",e.textContent="No data in the visible series.",void t.appendChild(e)}const s=Math.min(a,200),o=document.createElement("table"),r=document.createElement("thead"),l=document.createElement("tr"),c=document.createElement("th");c.scope="col",c.textContent=e.axis?"Category":"Slice",l.appendChild(c);for(const e of i){const t=document.createElement("th");t.scope="col",t.textContent=e.name,l.appendChild(t)}r.appendChild(l),o.appendChild(r);const d=document.createElement("tbody");for(let t=0;t<s;t++){const n=document.createElement("tr"),a=document.createElement("th");a.scope="row",a.textContent=e.categories[t]??String(t+1),n.appendChild(a);for(const e of i){const i=document.createElement("td"),a=e.values[t];i.textContent="number"==typeof a&&Number.isFinite(a)?x(a):b,n.appendChild(i)}d.appendChild(n)}if(o.appendChild(d),t.appendChild(o),a>s){const e=document.createElement("p");e.className="empty",e.textContent="Showing the first "+s+" of "+a+" rows.",t.appendChild(e)}}};function k(e,t,n,i,a){const s=n-e,o=i-t;let r=0,l=1;const c=[[-s,e-a.left],[s,a.right-e],[-o,t-a.top],[o,a.bottom-t]];for(const[e,t]of c){if(0===e){if(t<0)return null;continue}const n=t/e;if(e<0){if(n>l)return null;n>r&&(r=n)}else{if(n<r)return null;n<l&&(l=n)}}return[e+r*s,t+r*o,e+l*s,t+l*o]}function S(e){if(!Array.isArray(e))return null;const t=[];for(let n=0;n<e.length;n++){const i=e[n];i&&(Number.isFinite(i.x)&&Number.isFinite(i.y)&&t.push(i))}const n=t.length;if(n<2)return null;let i=0,a=0;for(let e=0;e<n;e++)i+=t[e].x,a+=t[e].y;i/=n,a/=n;let s=0,o=0;for(let e=0;e<n;e++){const n=t[e].x-i;s+=n*n,o+=n*(t[e].y-a)}if(0===s)return null;const r=o/s,l=a-r*i;let c=0,d=0;for(let e=0;e<n;e++){const n=r*(t[e].x-i)+a,s=t[e].y-n;c+=s*s;const o=t[e].y-a;d+=o*o}const h=0===d?1:Math.max(0,Math.min(1,1-c/d));return{slope:r,intercept:l,r2:h,n:n,predict:e=>r*e+l}}const E=new Set(["line","area","bar","column","scatter","bubble","histogram","candlestick","boxPlot"]),R=[w,A,{id:"trend",label:"Trend",kind:"toggle",appliesTo:e=>!!e.axis&&(!e.horizontalBars&&(!!E.has(e.type)&&e.series.some((e=>e.visible&&e.points.length>=2)))),draw(e,t){const{layer:n,scales:i}=t,[a,s]=i.domainX,[o,r]=i.domainY(),l={left:i.x(a),right:i.x(s),top:i.y(r),bottom:i.y(o)};for(const o of e.series){if(!o.visible)continue;const e=S(o.points);if(!e)continue;let r=1/0,c=-1/0;for(const e of o.points)e.x<r&&(r=e.x),e.x>c&&(c=e.x);const d=Math.max(r,a),h=Math.min(c,s);if(!(d<h))continue;const p=k(i.x(d),i.y(e.predict(d)),i.x(h),i.y(e.predict(h)),l);p&&n.line({x1:p[0],y1:p[1],x2:p[2],y2:p[3],stroke:t.seriesColor(o.index)??t.foreColor,width:2,dash:5})}}}];function K(e){const t=e.x(e.domainX[0]);return{x:n=>e.x(n)-t,y:(t,n=0)=>e.y(t,n)-(t=>e.y(e.domainY(t)[1],t))(n),domainX:e.domainX,domainY:t=>e.domainY(t),gridWidth:e.gridWidth,gridHeight:e.gridHeight}}class M{constructor(e){var t;this.panel=null,this.model=null,this.pageAttached=!1,this.openTool=null,this.activeToggles=new Set,this.overlayPainted=!1,this.api=e,this.onToolClick=this.onToolClick.bind(this),e.on("draw",(()=>this.afterRender())),e.on("afterUpdate",(()=>this.afterRender())),e.on("destroy",(()=>this.teardown())),t=this,m().add(t)}cfg(){return this.api.options||{}}isEnabled(){return"undefined"!=typeof document&&(!1!==this.cfg().enabled||this.pageAttached)}attach(){this.pageAttached||(this.pageAttached=!0,this.afterRender())}applicableTools(e){const t=this.cfg().tools;return(Array.isArray(t)?t.map((e=>R.find((t=>t.id===e)))).filter(Boolean):R).filter((t=>{try{return t.appliesTo(e)}catch{return!1}}))}afterRender(){if(!this.isEnabled())return;const e=this.api.el;if(!e)return;const t=e.querySelector(".apexcharts-canvas");this.panel?this.panel.reattach(e,t):this.panel=new h(e,t),this.applyWatermark(e),this.model=u(this.api);const n=this.applicableTools(this.model);this.panel.setTools(n.map((e=>({id:e.id,label:e.label}))),this.onToolClick);for(const e of n){const t="toggle"===e.kind?this.activeToggles.has(e.id):this.openTool===e.id;this.panel.setPressed(e.id,t)}for(const e of Array.from(this.activeToggles))n.some((t=>t.id===e&&"toggle"===t.kind))||this.activeToggles.delete(e);const i=n.some((e=>e.id===this.openTool));this.openTool&&!i?(this.openTool=null,this.panel.closePanel()):this.openTool&&this.renderOpenTool(),this.repaintOverlays(n)}repaintOverlays(e){if(!this.activeToggles.size&&!this.overlayPainted)return;let t=null;try{const e=this.api.layer();e.clear(),this.overlayPainted=!1,e.node.style.pointerEvents="none";const n=this.api.scales;if(!n)return;t={layer:e,scales:K(n),seriesColor:e=>this.api.theme.seriesColor(e),foreColor:this.api.theme.foreColor}}catch(e){return void console.warn("apex-analyst: this host exposes no drawing layer",e)}const n=this.model;if(n)for(const i of this.activeToggles){const a=e.find((e=>e.id===i));if(a&&"toggle"===a.kind)try{a.draw(n,t),this.overlayPainted=!0}catch(e){console.warn('apex-analyst: tool "'+i+'" failed to draw',e)}}}applyWatermark(e){try{l.isLicenseValid()?d.remove(e):d.add(e)}catch{}}renderOpenTool(){if(!this.panel||!this.model||!this.openTool)return;const e=R.find((e=>e.id===this.openTool));if(!e||"panel"!==e.kind)return;const t=this.panel.openPanel();try{e.render(this.model,t)}catch(n){t.textContent="";const i=document.createElement("p");i.className="empty",i.textContent="This tool could not read the chart data.",t.appendChild(i),console.warn('apex-analyst: tool "'+e.id+'" failed',n)}}onToolClick(e){const t=R.find((t=>t.id===e));if(t&&this.model&&this.panel){if("panel"===t.kind)return this.openTool===e?(this.openTool=null,this.panel.closePanel(),void this.panel.setPressed(e,!1)):(this.openTool&&this.panel.setPressed(this.openTool,!1),this.openTool=e,this.panel.setPressed(e,!0),void this.renderOpenTool());this.activeToggles.has(e)?this.activeToggles.delete(e):this.activeToggles.add(e),this.panel.setPressed(e,this.activeToggles.has(e)),this.repaintOverlays(this.applicableTools(this.model))}}teardown(){var e;this.panel&&(this.panel.teardown(),this.panel=null),this.activeToggles.clear(),this.overlayPainted=!1,this.openTool=null,this.model=null,e=this,m().delete(e)}}const N="analyst",P={name:N,apiVersion:2,setup(e){e.store.analyst=new M(e)}};function D(e){e.registerPlugin(P)}function I(e,t){if(D(e),"undefined"==typeof globalThis)return;const n=globalThis;n.Apex||(n.Apex={});const i=Array.isArray(n.Apex.plugins)?n.Apex.plugins:[];i.some((e=>e&&e.name===N))||(n.Apex.plugins=[...i,{name:N,options:t??{}}])}function _(){const e=Array.from(m());for(const t of e)try{t.attach()}catch(e){console.warn("apex-analyst: could not attach to a chart",e)}const t="undefined"!=typeof document&&document.querySelectorAll?document.querySelectorAll(".apexcharts-canvas").length:-1;return t>e.length&&console.warn("apex-analyst: "+e.length+" of "+t+" charts on this page can be analysed. The rest were created before the plugin was installed. Call installOnEveryChart() before creating your charts."),e.length}export{M as Analyst,b as NO_VALUE,N as PLUGIN_NAME,P as analystPlugin,u as buildModel,k as clipSegment,g as finite,x as formatNumber,C as formatPercent,D as install,I as installOnEveryChart,S as linearFit,_ as page,v as summarize};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
!function(e,t){"object"==typeof exports&&"undefined"!=typeof module?t(exports):"function"==typeof define&&define.amd?define(["exports"],t):t((e="undefined"!=typeof globalThis?globalThis:e||self).ApexAnalyst={})}(this,(function(e){"use strict";const t=globalThis.console;function n(e,...n){t.error(e,...n)}function i(e){t.warn(e)}const a="APEX-",s=new Date("2027-07-31T00:00:00Z"),o="__apex_license_v1__";function r(){const e=globalThis;let t=e[o];return t||(t={key:null,listeners:new Set,result:null},e[o]=t),t}const l=class{static get licenseKey(){return r().key}static set licenseKey(e){r().key=e}static get listeners(){return r().listeners}static get validationResult(){return r().result}static set validationResult(e){r().result=e}static getKey(){return this.licenseKey}static getLicenseStatus(){return this.licenseKey?(this.validationResult=this.validateKey(this.licenseKey),this.validationResult):{expired:!1,signatureVerified:!1,valid:!1}}static isKeyValid(e){return!!e&&this.validateKey(e).valid}static isLicenseValid(){return this.getLicenseStatus().valid}static onChange(e){return this.listeners.add(e),()=>{this.listeners.delete(e)}}static setLicense(e){var t;if(!e)return this.licenseKey=null,void this.publish({expired:!1,signatureVerified:!1,valid:!1});const a=this.validateKey(e);a.valid||e===this.licenseKey||!(null==(t=this.validationResult)?void 0:t.valid)?(this.licenseKey=e,this.publish(a),a.valid||n(`[Apex] ${a.message}`)):i(`[Apex] Ignoring license key: ${a.message??"it is not valid"} A valid license is already active on this page.`)}static validateKey(e){const t=this.parseKey(e),n=this.validateStructure(e,t);if(!n.valid||!(null==t?void 0:t.signature))return n;const i=this.verdicts.get(e);return!1===i?{data:t.data,expired:!1,message:"Invalid license key. The license signature does not verify.",signatureVerified:!0,valid:!1}:(void 0===i&&this.verifySignature(e,t,n),{...n,signatureVerified:!0===i})}static _resetSignatureState(){this.verdicts.clear(),this.verifying.clear(),this.warnedUnverifiable=!1,this.epoch++}static base64ToBytes(e){const t=e.replace(/-/g,"+").replace(/_/g,"/"),n=t.padEnd(4*Math.ceil(t.length/4),"="),i=globalThis.atob;if("function"!=typeof i)throw new Error("no base64 decoder available");const a=i(n),s=new Uint8Array(a.length);for(let e=0;e<a.length;e++)s[e]=a.charCodeAt(e);return s}static canonicalPayload(e){const t=e.domains&&e.domains.length>0?e.domains.join(","):"";return`v1|${e.issueDate}|${e.expiryDate}|${e.plan}|${t}`}static notify(e){for(const t of this.listeners)try{t(e)}catch{}}static parseKey(e){if("string"!=typeof e||!e.startsWith(a))return null;const t=e.slice(5);if(!t)return null;try{const e=(new TextDecoder).decode(this.base64ToBytes(t)),n=JSON.parse(e);return n.issueDate&&n.expiryDate&&n.plan?{data:{domains:Array.isArray(n.domains)?n.domains:void 0,expiryDate:n.expiryDate,issueDate:n.issueDate,plan:n.plan,valid:!0},signature:"string"==typeof n.sig&&n.sig?n.sig:null}:null}catch{return null}}static publish(e){this.validationResult=e,this.notify(e)}static validateStructure(e,t){const n=e=>({expired:!1,message:e,signatureVerified:!1,valid:!1});if("string"!=typeof e||!e.startsWith(a))return n('Invalid license key format. License key must start with "APEX-".');if(!t)return n("Invalid license key. Unable to decode license data.");const{data:i,signature:o}=t;if(!o&&new Date>=s)return n("This license key is in the old unsigned format, which is no longer accepted. Please request a replacement key.");if(new Date(i.expiryDate)<new Date)return{data:i,expired:!0,message:`License expired on ${i.expiryDate}. Please renew your license.`,signatureVerified:!1,valid:!1};if(i.domains&&i.domains.length>0){const e="undefined"==typeof location?"":location.hostname;if(!i.domains.some((t=>e===t||e.endsWith(`.${t}`))))return{data:i,expired:!1,message:`License is not valid for this domain (${e}). Allowed domains: ${i.domains.join(", ")}.`,signatureVerified:!1,valid:!1}}return{data:i,expired:!1,signatureVerified:!1,valid:!0}}static async verifySignature(e,t,a){var s;if(this.verifying.has(e)||this.verdicts.has(e))return;this.verifying.add(e);const o=this.epoch,r=null==(s=globalThis.crypto)?void 0:s.subtle;if(!r||0===this.publicKeysSpki.length)return this.verifying.delete(e),void(this.warnedUnverifiable||(this.warnedUnverifiable=!0,i(r?"[Apex] No license signing key is configured in this build, so license signatures cannot be verified.":"[Apex] Web Crypto is unavailable (a secure context is required), so the license signature cannot be verified.")));const l=(new TextEncoder).encode(this.canonicalPayload(t.data));let c=!1;for(const e of this.publicKeysSpki){try{const n=await r.importKey("spki",this.base64ToBytes(e),{name:"ECDSA",namedCurve:"P-256"},!1,["verify"]);c=await r.verify({hash:"SHA-256",name:"ECDSA"},n,this.base64ToBytes(t.signature),l)}catch{c=!1}if(c)break}if(this.verifying.delete(e),this.epoch!==o)return;if(this.verdicts.set(e,c),c){const t={...a,signatureVerified:!0};return void(this.licenseKey===e?this.publish(t):this.notify(t))}const d="Invalid license key. The license signature does not verify.",h={data:t.data,expired:!1,message:d,signatureVerified:!0,valid:!1};this.licenseKey===e?this.publish(h):this.notify(h),n(`[Apex] ${d}`)}};l.publicKeysSpki=["MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEQIaK9UMD6n0oR/FIy8QdL0uSzKMQlf1BB+tOrji4/WuHsyRNxeDhVykoSsNURozMi1xhmqWvBH1L//xIfugTPA=="],l.verdicts=new Map,l.verifying=new Set,l.warnedUnverifiable=!1,l.epoch=0;let c=l;const d=class{static applyStyles(e){Object.assign(e.style,this.CRITICAL_STYLES,{backgroundImage:this.createWatermarkPattern(),backgroundRepeat:"repeat"})}static node(e){return e?e.querySelector(`[${this.WATERMARK_ATTR}]`):null}static add(e,t){return e&&"undefined"!=typeof document?(this.setManaged(e,t),this.paint(e)):null}static exists(e){return!!this.node(e)}static remove(e,t){e&&(this.setManaged(e,t),this.erase(e))}static untrack(e){this.managed.delete(e)}static createWatermarkPattern(){const e=this.WATERMARK_TEXT;return`url("data:image/svg+xml,${encodeURIComponent(`\n <svg xmlns="http://www.w3.org/2000/svg" width="300" height="200">\n <text\n x="50%"\n y="50%"\n dominant-baseline="middle"\n text-anchor="middle"\n font-family="-apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Arial, sans-serif"\n font-size="18"\n font-weight="600"\n fill="rgba(134, 134, 134, 0.1)"\n transform="rotate(-35, 100, 60)"\n >${e}</text>\n </svg>\n `.trim())}")`}static erase(e){var t;null==(t=this.node(e))||t.remove()}static paint(e){let t=this.node(e);return t||(t=document.createElement("div"),t.setAttribute(this.WATERMARK_ATTR,""),e.appendChild(t)),this.applyStyles(t),"function"==typeof getComputedStyle&&"static"===getComputedStyle(e).position&&(e.style.position="relative"),t}static reconcile(){const e=c.isLicenseValid();for(const t of this.managed)t.isConnected?e?this.erase(t):this.paint(t):this.managed.delete(t)}static setManaged(e,t){!1!==(null==t?void 0:t.manage)?this.track(e):this.managed.delete(e)}static track(e){this.managed.add(e),this.subscribed||(this.subscribed=!0,c.onChange((()=>{this.reconcile()})))}};d.WATERMARK_ATTR="data-apexcharts-watermark",d.WATERMARK_TEXT="APEXCHARTS",d.ATTR="data-apexcharts-watermark",d.CRITICAL_STYLES={bottom:"0",display:"block",left:"0",msUserSelect:"none",opacity:"1",pointerEvents:"none",position:"absolute",right:"0",top:"0",userSelect:"none",visibility:"visible",webkitUserSelect:"none",zIndex:"10000"},d.managed=new Set,d.subscribed=!1;let h=d;class p{constructor(e,t){this.buttons=new Map,this.container=e,this.host=document.createElement("div"),this.host.className="apexcharts-analyst",this.panelHost=document.createElement("div"),this.panelHost.className="apexcharts-analyst-panel",this.insert(e,t),this.bar=document.createElement("div"),this.bar.className="bar",this.bar.setAttribute("role","toolbar"),this.bar.setAttribute("aria-label","Chart analysis"),p.rootFor(this.host).appendChild(this.bar),this.panel=document.createElement("div"),this.panel.className="panel",this.panel.setAttribute("role","region"),this.panel.setAttribute("aria-label","Analysis result"),this.panel.hidden=!0,p.rootFor(this.panelHost).appendChild(this.panel)}static rootFor(e){const t=e.attachShadow({mode:"open"}),n=document.createElement("style");return n.textContent="\n:host {\n --bg: var(--apx-analyst-bg, #ffffff);\n --fg: var(--apx-analyst-fg, #373d3f);\n --muted: var(--apx-analyst-muted, #6e8192);\n --border: var(--apx-analyst-border, #e0e6ed);\n --accent: var(--apx-analyst-accent, #008ffb);\n --accent-fg: var(--apx-analyst-accent-fg, #ffffff);\n --hover: var(--apx-analyst-hover, #f4f7fa);\n --radius: var(--apx-analyst-radius, 6px);\n\n display: block;\n font-family: var(--apx-analyst-font, Helvetica, Arial, sans-serif);\n color: var(--fg);\n /* The host is a plain block in the page flow above the chart. It must never\n take part in the chart's own sizing: a height here would be read by the\n chart's parent-resize observer as a container change and loop. */\n box-sizing: border-box;\n}\n\n@media (prefers-color-scheme: dark) {\n :host {\n --bg: var(--apx-analyst-bg, #1e2329);\n --fg: var(--apx-analyst-fg, #e6edf3);\n --muted: var(--apx-analyst-muted, #96a5b4);\n --border: var(--apx-analyst-border, #333c46);\n --hover: var(--apx-analyst-hover, #262d35);\n }\n}\n\n.bar {\n display: flex;\n flex-wrap: wrap;\n align-items: center;\n gap: 6px;\n padding: 6px 0;\n}\n\nbutton {\n font: inherit;\n font-size: 14px;\n line-height: 1.2;\n color: var(--fg);\n background: var(--bg);\n border: 1px solid var(--border);\n border-radius: var(--radius);\n padding: 6px 10px;\n cursor: pointer;\n}\n\nbutton:hover { background: var(--hover); }\n\nbutton:focus-visible {\n outline: 2px solid var(--accent);\n outline-offset: 1px;\n}\n\nbutton[aria-pressed=\"true\"] {\n background: var(--accent);\n border-color: var(--accent);\n color: var(--accent-fg);\n}\n\n.panel {\n border: 1px solid var(--border);\n border-radius: var(--radius);\n background: var(--bg);\n padding: 10px 12px;\n /* Sits below the chart, so the gap goes above it. */\n margin-top: 8px;\n font-size: 14px;\n /* Wide output (a table with many categories) scrolls inside the panel. The\n page body must never gain a horizontal scrollbar because of the toolbar. */\n overflow-x: auto;\n}\n\n.panel[hidden] { display: none; }\n\n.panel h4 {\n margin: 0 0 8px;\n font-size: 14px;\n font-weight: 600;\n}\n\n.empty { color: var(--muted); }\n\ntable {\n border-collapse: collapse;\n font-size: 13px;\n font-variant-numeric: tabular-nums;\n}\n\nth, td {\n text-align: right;\n padding: 4px 10px 4px 0;\n white-space: nowrap;\n}\n\nth:first-child, td:first-child { text-align: left; }\n\nthead th {\n border-bottom: 1px solid var(--border);\n font-weight: 600;\n color: var(--muted);\n}\n",t.appendChild(n),t}insert(e,t){t&&t.parentNode===e?(e.insertBefore(this.host,t),t.nextSibling?e.insertBefore(this.panelHost,t.nextSibling):e.appendChild(this.panelHost)):(e.appendChild(this.host),e.appendChild(this.panelHost))}reattach(e,t){this.host.parentNode===e&&this.panelHost.parentNode===e||(this.container=e,this.insert(e,t))}setTools(e,t){this.bar.textContent="",this.buttons.clear();for(const n of e){const e=document.createElement("button");e.type="button",e.textContent=n.label,e.setAttribute("aria-pressed","false"),e.addEventListener("click",(()=>t(n.id))),this.bar.appendChild(e),this.buttons.set(n.id,e)}}setPressed(e,t){const n=this.buttons.get(e);n&&n.setAttribute("aria-pressed",t?"true":"false")}openPanel(){return this.panel.textContent="",this.panel.hidden=!1,this.panel}closePanel(){this.panel.textContent="",this.panel.hidden=!0}teardown(){for(const e of[this.host,this.panelHost])e.parentNode&&e.parentNode.removeChild(e);this.buttons.clear()}}function u(e,t){const n=[];for(let i=0;i<e.length;i++){const a=e[i];"number"==typeof a&&Number.isFinite(a)&&n.push({x:t[i],y:a})}return n}function f(e){const t=e.info,n=e.data||[],i=e.categories||[],a=[];if(t.axisChart)for(let e=0;e<n.length;e++){const t=n[e],i=t.points||[],s=i.map((e=>"number"==typeof e.y&&Number.isFinite(e.y)?e.y:null)),o=i.map(((e,t)=>Number.isFinite(e.x)?e.x:t));a.push({index:e,name:null!=t.name?String(t.name):"series-"+(e+1),visible:!t.hidden,values:s,xs:o,points:u(s,o)})}else{const e=[];for(const t of n){const n=(t.points||[])[0],i=n?n.y:null;e.push("number"==typeof i&&Number.isFinite(i)?i:null)}const t=e.map(((e,t)=>t));a.push({index:0,name:"values",visible:!0,values:e,xs:t,points:u(e,t)})}const s=n.map(((e,t)=>null!=e.name?String(e.name):String(t+1)));return{axis:t.axisChart,type:t.type,timeX:t.datetimeX,horizontalBars:t.horizontalBars,series:a,categories:t.axisChart?i:s}}const m="__apex_analyst_v1__";function g(){const e=globalThis;return e[m]||(e[m]=new Set),e[m]}function y(e){if(!Array.isArray(e))return[];const t=[];for(let n=0;n<e.length;n++){const i=e[n];"number"==typeof i&&Number.isFinite(i)&&t.push(i)}return t}function b(e,t){if(1===e.length)return e[0];const n=(e.length-1)*t,i=Math.floor(n),a=Math.ceil(n);return i===a?e[i]:e[i]+(e[a]-e[i])*(n-i)}function v(e){const t=y(e),n=t.length;if(!n)return null;const i=(Array.isArray(e)?e.length:0)-n;let a=0,s=t[0],o=t[0];for(let e=0;e<n;e++){const n=t[e];a+=n,n<s&&(s=n),n>o&&(o=n)}const r=a/n;let l=0;for(let e=0;e<n;e++){const n=t[e]-r;l+=n*n}const c=n>1?l/(n-1):0,d=t.slice().sort(((e,t)=>e-t)),h=b(d,.25),p=b(d,.75);return{count:n,missing:i,min:s,max:o,range:o-s,sum:a,mean:r,median:b(d,.5),q1:h,q3:p,iqr:p-h,variance:c,stdDev:Math.sqrt(c)}}const x="n/a";function C(e,t=6){if("number"!=typeof e||!Number.isFinite(e))return x;if(0===e)return"0";const n=Math.abs(e);if(n>=1e15||n<1e-6)return e.toExponential(2);try{return new Intl.NumberFormat(void 0,{maximumSignificantDigits:t}).format(e)}catch{return String(Math.round(1e6*e)/1e6)}}const T=[{key:"count",label:"n"},{key:"min",label:"Min"},{key:"max",label:"Max"},{key:"mean",label:"Mean"},{key:"median",label:"Median"},{key:"stdDev",label:"Std dev"},{key:"sum",label:"Sum"}],w={id:"summary",label:"Summary",kind:"panel",appliesTo:e=>e.series.some((e=>e.visible&&e.values.length>0)),render(e,t){const n=document.createElement("h4");n.textContent="Summary statistics",t.appendChild(n);const i=[];for(const t of e.series){if(!t.visible)continue;const e=v(t.values);e&&i.push({name:t.name,stats:e})}if(!i.length){const e=document.createElement("p");return e.className="empty",e.textContent="No numeric values in the visible series.",void t.appendChild(e)}const a=document.createElement("table"),s=document.createElement("thead"),o=document.createElement("tr"),r=document.createElement("th");r.scope="col",r.textContent="Series",o.appendChild(r);for(const e of T){const t=document.createElement("th");t.scope="col",t.textContent=e.label,o.appendChild(t)}s.appendChild(o),a.appendChild(s);const l=document.createElement("tbody");for(const e of i){const t=document.createElement("tr"),n=document.createElement("th");n.scope="row",n.textContent=e.name,t.appendChild(n);for(const n of T){const i=document.createElement("td"),a=e.stats[n.key];i.textContent="count"===n.key?String(a):C(a),t.appendChild(i)}l.appendChild(t)}a.appendChild(l),t.appendChild(a);const c=i.reduce(((e,t)=>e+t.stats.missing),0);if(c>0){const e=document.createElement("p");e.className="empty",e.textContent=1===c?"1 missing value excluded.":c+" missing values excluded.",t.appendChild(e)}}},A={id:"data",label:"Data",kind:"panel",appliesTo:e=>e.series.some((e=>e.values.length>0)),render(e,t){const n=document.createElement("h4");n.textContent="Data",t.appendChild(n);const i=e.series.filter((e=>e.visible)),a=i.reduce(((e,t)=>Math.max(e,t.values.length)),0);if(!a){const e=document.createElement("p");return e.className="empty",e.textContent="No data in the visible series.",void t.appendChild(e)}const s=Math.min(a,200),o=document.createElement("table"),r=document.createElement("thead"),l=document.createElement("tr"),c=document.createElement("th");c.scope="col",c.textContent=e.axis?"Category":"Slice",l.appendChild(c);for(const e of i){const t=document.createElement("th");t.scope="col",t.textContent=e.name,l.appendChild(t)}r.appendChild(l),o.appendChild(r);const d=document.createElement("tbody");for(let t=0;t<s;t++){const n=document.createElement("tr"),a=document.createElement("th");a.scope="row",a.textContent=e.categories[t]??String(t+1),n.appendChild(a);for(const e of i){const i=document.createElement("td"),a=e.values[t];i.textContent="number"==typeof a&&Number.isFinite(a)?C(a):x,n.appendChild(i)}d.appendChild(n)}if(o.appendChild(d),t.appendChild(o),a>s){const e=document.createElement("p");e.className="empty",e.textContent="Showing the first "+s+" of "+a+" rows.",t.appendChild(e)}}};function k(e,t,n,i,a){const s=n-e,o=i-t;let r=0,l=1;const c=[[-s,e-a.left],[s,a.right-e],[-o,t-a.top],[o,a.bottom-t]];for(const[e,t]of c){if(0===e){if(t<0)return null;continue}const n=t/e;if(e<0){if(n>l)return null;n>r&&(r=n)}else{if(n<r)return null;n<l&&(l=n)}}return[e+r*s,t+r*o,e+l*s,t+l*o]}function S(e){if(!Array.isArray(e))return null;const t=[];for(let n=0;n<e.length;n++){const i=e[n];i&&(Number.isFinite(i.x)&&Number.isFinite(i.y)&&t.push(i))}const n=t.length;if(n<2)return null;let i=0,a=0;for(let e=0;e<n;e++)i+=t[e].x,a+=t[e].y;i/=n,a/=n;let s=0,o=0;for(let e=0;e<n;e++){const n=t[e].x-i;s+=n*n,o+=n*(t[e].y-a)}if(0===s)return null;const r=o/s,l=a-r*i;let c=0,d=0;for(let e=0;e<n;e++){const n=r*(t[e].x-i)+a,s=t[e].y-n;c+=s*s;const o=t[e].y-a;d+=o*o}const h=0===d?1:Math.max(0,Math.min(1,1-c/d));return{slope:r,intercept:l,r2:h,n:n,predict:e=>r*e+l}}const E=new Set(["line","area","bar","column","scatter","bubble","histogram","candlestick","boxPlot"]),N=[w,A,{id:"trend",label:"Trend",kind:"toggle",appliesTo:e=>!!e.axis&&(!e.horizontalBars&&(!!E.has(e.type)&&e.series.some((e=>e.visible&&e.points.length>=2)))),draw(e,t){const{layer:n,scales:i}=t,[a,s]=i.domainX,[o,r]=i.domainY(),l={left:i.x(a),right:i.x(s),top:i.y(r),bottom:i.y(o)};for(const o of e.series){if(!o.visible)continue;const e=S(o.points);if(!e)continue;let r=1/0,c=-1/0;for(const e of o.points)e.x<r&&(r=e.x),e.x>c&&(c=e.x);const d=Math.max(r,a),h=Math.min(c,s);if(!(d<h))continue;const p=k(i.x(d),i.y(e.predict(d)),i.x(h),i.y(e.predict(h)),l);p&&n.line({x1:p[0],y1:p[1],x2:p[2],y2:p[3],stroke:t.seriesColor(o.index)??t.foreColor,width:2,dash:5})}}}];function M(e){const t=e.x(e.domainX[0]);return{x:n=>e.x(n)-t,y:(t,n=0)=>e.y(t,n)-(t=>e.y(e.domainY(t)[1],t))(n),domainX:e.domainX,domainY:t=>e.domainY(t),gridWidth:e.gridWidth,gridHeight:e.gridHeight}}class P{constructor(e){var t;this.panel=null,this.model=null,this.pageAttached=!1,this.openTool=null,this.activeToggles=new Set,this.overlayPainted=!1,this.api=e,this.onToolClick=this.onToolClick.bind(this),e.on("draw",(()=>this.afterRender())),e.on("afterUpdate",(()=>this.afterRender())),e.on("destroy",(()=>this.teardown())),t=this,g().add(t)}cfg(){return this.api.options||{}}isEnabled(){return"undefined"!=typeof document&&(!1!==this.cfg().enabled||this.pageAttached)}attach(){this.pageAttached||(this.pageAttached=!0,this.afterRender())}applicableTools(e){const t=this.cfg().tools;return(Array.isArray(t)?t.map((e=>N.find((t=>t.id===e)))).filter(Boolean):N).filter((t=>{try{return t.appliesTo(e)}catch{return!1}}))}afterRender(){if(!this.isEnabled())return;const e=this.api.el;if(!e)return;const t=e.querySelector(".apexcharts-canvas");this.panel?this.panel.reattach(e,t):this.panel=new p(e,t),this.applyWatermark(e),this.model=f(this.api);const n=this.applicableTools(this.model);this.panel.setTools(n.map((e=>({id:e.id,label:e.label}))),this.onToolClick);for(const e of n){const t="toggle"===e.kind?this.activeToggles.has(e.id):this.openTool===e.id;this.panel.setPressed(e.id,t)}for(const e of Array.from(this.activeToggles))n.some((t=>t.id===e&&"toggle"===t.kind))||this.activeToggles.delete(e);const i=n.some((e=>e.id===this.openTool));this.openTool&&!i?(this.openTool=null,this.panel.closePanel()):this.openTool&&this.renderOpenTool(),this.repaintOverlays(n)}repaintOverlays(e){if(!this.activeToggles.size&&!this.overlayPainted)return;let t=null;try{const e=this.api.layer();e.clear(),this.overlayPainted=!1,e.node.style.pointerEvents="none";const n=this.api.scales;if(!n)return;t={layer:e,scales:M(n),seriesColor:e=>this.api.theme.seriesColor(e),foreColor:this.api.theme.foreColor}}catch(e){return void console.warn("apex-analyst: this host exposes no drawing layer",e)}const n=this.model;if(n)for(const i of this.activeToggles){const a=e.find((e=>e.id===i));if(a&&"toggle"===a.kind)try{a.draw(n,t),this.overlayPainted=!0}catch(e){console.warn('apex-analyst: tool "'+i+'" failed to draw',e)}}}applyWatermark(e){try{c.isLicenseValid()?h.remove(e):h.add(e)}catch{}}renderOpenTool(){if(!this.panel||!this.model||!this.openTool)return;const e=N.find((e=>e.id===this.openTool));if(!e||"panel"!==e.kind)return;const t=this.panel.openPanel();try{e.render(this.model,t)}catch(n){t.textContent="";const i=document.createElement("p");i.className="empty",i.textContent="This tool could not read the chart data.",t.appendChild(i),console.warn('apex-analyst: tool "'+e.id+'" failed',n)}}onToolClick(e){const t=N.find((t=>t.id===e));if(t&&this.model&&this.panel){if("panel"===t.kind)return this.openTool===e?(this.openTool=null,this.panel.closePanel(),void this.panel.setPressed(e,!1)):(this.openTool&&this.panel.setPressed(this.openTool,!1),this.openTool=e,this.panel.setPressed(e,!0),void this.renderOpenTool());this.activeToggles.has(e)?this.activeToggles.delete(e):this.activeToggles.add(e),this.panel.setPressed(e,this.activeToggles.has(e)),this.repaintOverlays(this.applicableTools(this.model))}}teardown(){var e;this.panel&&(this.panel.teardown(),this.panel=null),this.activeToggles.clear(),this.overlayPainted=!1,this.openTool=null,this.model=null,e=this,g().delete(e)}}const R="analyst",K={name:R,apiVersion:2,setup(e){e.store.analyst=new P(e)}};function D(e){e.registerPlugin(K)}e.Analyst=P,e.NO_VALUE=x,e.PLUGIN_NAME=R,e.analystPlugin=K,e.buildModel=f,e.clipSegment=k,e.finite=y,e.formatNumber=C,e.formatPercent=function(e){return"number"==typeof e&&Number.isFinite(e)?C(100*e,3)+"%":x},e.install=D,e.installOnEveryChart=function(e,t){if(D(e),"undefined"==typeof globalThis)return;const n=globalThis;n.Apex||(n.Apex={});const i=Array.isArray(n.Apex.plugins)?n.Apex.plugins:[];i.some((e=>e&&e.name===R))||(n.Apex.plugins=[...i,{name:R,options:t??{}}])},e.linearFit=S,e.page=function(){const e=Array.from(g());for(const t of e)try{t.attach()}catch(e){console.warn("apex-analyst: could not attach to a chart",e)}const t="undefined"!=typeof document&&document.querySelectorAll?document.querySelectorAll(".apexcharts-canvas").length:-1;return t>e.length&&console.warn("apex-analyst: "+e.length+" of "+t+" charts on this page can be analysed. The rest were created before the plugin was installed. Call installOnEveryChart() before creating your charts."),e.length},e.summarize=v,Object.defineProperty(e,Symbol.toStringTag,{value:"Module"})}));
|
package/demo/index.html
ADDED
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
<!doctype html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8" />
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
6
|
+
<title>apex-analyst - In-chart analysis toolbar</title>
|
|
7
|
+
<link rel="preconnect" href="https://fonts.googleapis.com" />
|
|
8
|
+
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
|
|
9
|
+
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet" />
|
|
10
|
+
<script src="https://cdn.jsdelivr.net/npm/apexcharts@7.2.0-rc.2/dist/apexcharts.min.js"></script>
|
|
11
|
+
<script src="../apex-analyst.min.js"></script>
|
|
12
|
+
<style>
|
|
13
|
+
body {
|
|
14
|
+
max-width: 1000px;
|
|
15
|
+
margin: 0 auto;
|
|
16
|
+
font-family: 'Inter', sans-serif;
|
|
17
|
+
background: #fff;
|
|
18
|
+
padding: 24px 16px 64px;
|
|
19
|
+
color: #1a1a2e;
|
|
20
|
+
}
|
|
21
|
+
h1 {
|
|
22
|
+
font-size: 24px;
|
|
23
|
+
font-weight: 700;
|
|
24
|
+
margin: 0 0 4px;
|
|
25
|
+
}
|
|
26
|
+
.lede {
|
|
27
|
+
font-size: 14px;
|
|
28
|
+
color: #6e6e6e;
|
|
29
|
+
margin: 0 0 32px;
|
|
30
|
+
line-height: 1.5;
|
|
31
|
+
}
|
|
32
|
+
h2 {
|
|
33
|
+
font-size: 17px;
|
|
34
|
+
font-weight: 600;
|
|
35
|
+
margin: 0 0 4px;
|
|
36
|
+
}
|
|
37
|
+
.subtitle {
|
|
38
|
+
font-size: 13px;
|
|
39
|
+
color: #6e6e6e;
|
|
40
|
+
margin: 0 0 12px;
|
|
41
|
+
line-height: 1.5;
|
|
42
|
+
}
|
|
43
|
+
section {
|
|
44
|
+
margin-bottom: 44px;
|
|
45
|
+
}
|
|
46
|
+
code {
|
|
47
|
+
background: #f2f2f5;
|
|
48
|
+
padding: 1px 5px;
|
|
49
|
+
border-radius: 3px;
|
|
50
|
+
font-size: 12px;
|
|
51
|
+
}
|
|
52
|
+
</style>
|
|
53
|
+
</head>
|
|
54
|
+
<body>
|
|
55
|
+
<h1>apex-analyst</h1>
|
|
56
|
+
<p class="lede">
|
|
57
|
+
An analysis toolbar that attaches to a chart through the Weave plugin API. It reads the chart the same way a
|
|
58
|
+
reader does, and never reaches into its internals.
|
|
59
|
+
</p>
|
|
60
|
+
|
|
61
|
+
<section>
|
|
62
|
+
<h2>Summary, Data and Trend</h2>
|
|
63
|
+
<p class="subtitle">
|
|
64
|
+
All three tools are offered here. <strong>Summary</strong> and <strong>Data</strong> are panel tools: they read
|
|
65
|
+
the chart and change nothing. <strong>Trend</strong> is a toggle tool, drawing a dashed least-squares guide line
|
|
66
|
+
on the chart's own SVG and clearing it when switched off. No series is added: the legend, tooltip and exports
|
|
67
|
+
stay exactly yours.
|
|
68
|
+
</p>
|
|
69
|
+
<div id="line-chart"></div>
|
|
70
|
+
</section>
|
|
71
|
+
|
|
72
|
+
<section>
|
|
73
|
+
<h2>The toolbar is shorter when a tool cannot work</h2>
|
|
74
|
+
<p class="subtitle">
|
|
75
|
+
A trend line means nothing on a donut, so the Trend button is not rendered at all rather than offered and
|
|
76
|
+
failing. Every tool declares which charts it can read.
|
|
77
|
+
</p>
|
|
78
|
+
<div id="donut-chart"></div>
|
|
79
|
+
</section>
|
|
80
|
+
|
|
81
|
+
<section>
|
|
82
|
+
<h2>Two series, gaps included</h2>
|
|
83
|
+
<p class="subtitle">
|
|
84
|
+
Gaps (<code>null</code>) are excluded from the statistics and the count of excluded values is reported, so a gap
|
|
85
|
+
and a zero never read the same way. Hide a series with the legend and the readouts follow.
|
|
86
|
+
</p>
|
|
87
|
+
<div id="bar-chart"></div>
|
|
88
|
+
</section>
|
|
89
|
+
|
|
90
|
+
<script>
|
|
91
|
+
// Write the plugin into the chart library's global defaults, so every
|
|
92
|
+
// chart created afterwards carries it without a `plugins` entry.
|
|
93
|
+
ApexAnalyst.installOnEveryChart(ApexCharts);
|
|
94
|
+
|
|
95
|
+
const MONTHS = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'];
|
|
96
|
+
|
|
97
|
+
new ApexCharts(document.querySelector('#line-chart'), {
|
|
98
|
+
chart: {type: 'line', height: 320, toolbar: {show: false}},
|
|
99
|
+
stroke: {width: 3, curve: 'smooth'},
|
|
100
|
+
colors: ['#2563eb'],
|
|
101
|
+
series: [{name: 'MRR', data: [31, 40, 38, 51, 49, 62, 69, 75, 71, 88, 94, 103]}],
|
|
102
|
+
xaxis: {categories: MONTHS},
|
|
103
|
+
yaxis: {labels: {formatter: (v) => '$' + Math.round(v) + 'k'}},
|
|
104
|
+
markers: {size: 4},
|
|
105
|
+
}).render();
|
|
106
|
+
|
|
107
|
+
new ApexCharts(document.querySelector('#donut-chart'), {
|
|
108
|
+
chart: {type: 'donut', height: 300},
|
|
109
|
+
colors: ['#2563eb', '#7c3aed', '#0891b2', '#059669'],
|
|
110
|
+
series: [44, 55, 23, 18],
|
|
111
|
+
labels: ['Direct', 'Organic', 'Referral', 'Social'],
|
|
112
|
+
legend: {position: 'bottom'},
|
|
113
|
+
}).render();
|
|
114
|
+
|
|
115
|
+
new ApexCharts(document.querySelector('#bar-chart'), {
|
|
116
|
+
chart: {type: 'bar', height: 320, toolbar: {show: false}},
|
|
117
|
+
colors: ['#2563eb', '#f59e0b'],
|
|
118
|
+
plotOptions: {bar: {columnWidth: '60%'}},
|
|
119
|
+
series: [
|
|
120
|
+
{name: 'Signups', data: [120, 132, null, 145, 160, 158, 171, 169, null, 190, 205, 212]},
|
|
121
|
+
{name: 'Churn', data: [12, 14, 11, 16, 15, 13, 18, 17, 14, 19, 21, 20]},
|
|
122
|
+
],
|
|
123
|
+
xaxis: {categories: MONTHS},
|
|
124
|
+
}).render();
|
|
125
|
+
</script>
|
|
126
|
+
</body>
|
|
127
|
+
</html>
|
package/index.d.ts
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
export { analystPlugin, install, installOnEveryChart, page, PLUGIN_NAME } from './lib/plugin';
|
|
2
|
+
export { default as Analyst } from './lib/Analyst';
|
|
3
|
+
export type { AnalystOptions } from './lib/Analyst';
|
|
4
|
+
export { clipSegment } from './lib/clip';
|
|
5
|
+
export type { PixelRect } from './lib/clip';
|
|
6
|
+
export { buildModel } from './lib/SeriesModel';
|
|
7
|
+
export type { AnalystModel, AnalystSeries } from './lib/SeriesModel';
|
|
8
|
+
export { formatNumber, formatPercent, NO_VALUE } from './lib/format';
|
|
9
|
+
export { finite, summarize } from './lib/stats/Descriptive';
|
|
10
|
+
export type { Summary } from './lib/stats/Descriptive';
|
|
11
|
+
export { linearFit } from './lib/stats/Regression';
|
|
12
|
+
export type { FitPoint, LinearFit } from './lib/stats/Regression';
|
|
13
|
+
export type { OverlaySurface, PanelTool, ToggleTool, Tool } from './lib/tools/types';
|
|
14
|
+
export type { ApexChartsCtor, WeaveAPI, WeaveChartInfo, WeaveHook, WeaveLayer, WeavePluginDefinition, WeaveScales, WeaveSeries, WeaveTheme, } from './lib/weave';
|
|
15
|
+
//# sourceMappingURL=index.d.ts.map
|
package/index.d.ts.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../libs/analyst/src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAC,aAAa,EAAE,OAAO,EAAE,mBAAmB,EAAE,IAAI,EAAE,WAAW,EAAC,MAAM,cAAc,CAAC;AAC5F,OAAO,EAAC,OAAO,IAAI,OAAO,EAAC,MAAM,eAAe,CAAC;AACjD,YAAY,EAAC,cAAc,EAAC,MAAM,eAAe,CAAC;AAClD,OAAO,EAAC,WAAW,EAAC,MAAM,YAAY,CAAC;AACvC,YAAY,EAAC,SAAS,EAAC,MAAM,YAAY,CAAC;AAC1C,OAAO,EAAC,UAAU,EAAC,MAAM,mBAAmB,CAAC;AAC7C,YAAY,EAAC,YAAY,EAAE,aAAa,EAAC,MAAM,mBAAmB,CAAC;AACnE,OAAO,EAAC,YAAY,EAAE,aAAa,EAAE,QAAQ,EAAC,MAAM,cAAc,CAAC;AACnE,OAAO,EAAC,MAAM,EAAE,SAAS,EAAC,MAAM,yBAAyB,CAAC;AAC1D,YAAY,EAAC,OAAO,EAAC,MAAM,yBAAyB,CAAC;AACrD,OAAO,EAAC,SAAS,EAAC,MAAM,wBAAwB,CAAC;AACjD,YAAY,EAAC,QAAQ,EAAE,SAAS,EAAC,MAAM,wBAAwB,CAAC;AAChE,YAAY,EAAC,cAAc,EAAE,SAAS,EAAE,UAAU,EAAE,IAAI,EAAC,MAAM,mBAAmB,CAAC;AACnF,YAAY,EACV,cAAc,EACd,QAAQ,EACR,cAAc,EACd,SAAS,EACT,UAAU,EACV,qBAAqB,EACrB,WAAW,EACX,WAAW,EACX,UAAU,GACX,MAAM,aAAa,CAAC"}
|
package/lib/Analyst.d.ts
ADDED
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { WeaveAPI } from './weave';
|
|
2
|
+
import { AnalystModel } from './SeriesModel';
|
|
3
|
+
|
|
4
|
+
export interface AnalystOptions {
|
|
5
|
+
/**
|
|
6
|
+
* Narrow and order the toolbar by tool id. Omit for all of them.
|
|
7
|
+
*/
|
|
8
|
+
tools?: string[];
|
|
9
|
+
/**
|
|
10
|
+
* Set false to keep the instance registered but render no toolbar. Used by
|
|
11
|
+
* `page()` to attach later.
|
|
12
|
+
*/
|
|
13
|
+
enabled?: boolean;
|
|
14
|
+
}
|
|
15
|
+
export default class Analyst {
|
|
16
|
+
private api;
|
|
17
|
+
private panel;
|
|
18
|
+
model: AnalystModel | null;
|
|
19
|
+
/** Set by page() for a chart that carried no explicit opt-in. */
|
|
20
|
+
private pageAttached;
|
|
21
|
+
/** id of the panel tool whose readout is open */
|
|
22
|
+
private openTool;
|
|
23
|
+
/** ids of the active toggle tools */
|
|
24
|
+
private activeToggles;
|
|
25
|
+
/**
|
|
26
|
+
* Whether the last paint left anything on the layer. Purely an optimisation
|
|
27
|
+
* flag: while no toggle has ever been switched on, no layer group is created
|
|
28
|
+
* at all, so a chart whose viewer only reads summaries carries no extra node.
|
|
29
|
+
*/
|
|
30
|
+
private overlayPainted;
|
|
31
|
+
constructor(api: WeaveAPI);
|
|
32
|
+
private cfg;
|
|
33
|
+
/** Whether the toolbar should exist on this chart at all. */
|
|
34
|
+
isEnabled(): boolean;
|
|
35
|
+
/**
|
|
36
|
+
* Render the toolbar on a chart whose plugin entry set `enabled: false`.
|
|
37
|
+
* Idempotent.
|
|
38
|
+
*/
|
|
39
|
+
attach(): void;
|
|
40
|
+
/**
|
|
41
|
+
* The tools that apply to the chart as it stands.
|
|
42
|
+
*
|
|
43
|
+
* `tools` narrows the set by id and preserves the caller's order, which is
|
|
44
|
+
* what a host configuring a cut-down toolbar expects.
|
|
45
|
+
*/
|
|
46
|
+
private applicableTools;
|
|
47
|
+
/** Rebuild the model, the toolbar and the overlay after any render. */
|
|
48
|
+
private afterRender;
|
|
49
|
+
/**
|
|
50
|
+
* Repaint every active toggle tool's drawing.
|
|
51
|
+
*
|
|
52
|
+
* Called after each render because the host wipes plugin layers at the start
|
|
53
|
+
* of every draw pass, and directly from a toggle click, where painting with
|
|
54
|
+
* the current scales makes the toggle instant instead of costing the full
|
|
55
|
+
* chart re-render the old derived-series route paid.
|
|
56
|
+
*/
|
|
57
|
+
private repaintOverlays;
|
|
58
|
+
/**
|
|
59
|
+
* Trial watermark, on the same terms as every other paid product in the
|
|
60
|
+
* family: the layer keeps working without a licence, the chart just carries
|
|
61
|
+
* the mark. Client-side enforcement is deterrence and honest-customer
|
|
62
|
+
* compliance, not DRM, so this must never degrade a tool and never throw.
|
|
63
|
+
*/
|
|
64
|
+
private applyWatermark;
|
|
65
|
+
private renderOpenTool;
|
|
66
|
+
private onToolClick;
|
|
67
|
+
teardown(): void;
|
|
68
|
+
}
|
|
69
|
+
//# sourceMappingURL=Analyst.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"Analyst.d.ts","sourceRoot":"","sources":["../../../libs/analyst/src/lib/Analyst.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAKH,OAAO,KAAK,EAAC,YAAY,EAAC,MAAM,eAAe,CAAC;AAMhD,OAAO,KAAK,EAAC,QAAQ,EAAc,MAAM,SAAS,CAAC;AA8BnD,MAAM,WAAW,cAAc;IAC7B;;OAEG;IACH,KAAK,CAAC,EAAE,MAAM,EAAE,CAAC;IACjB;;;OAGG;IACH,OAAO,CAAC,EAAE,OAAO,CAAC;CACnB;AAED,MAAM,CAAC,OAAO,OAAO,OAAO;IAC1B,OAAO,CAAC,GAAG,CAAW;IACtB,OAAO,CAAC,KAAK,CAA6B;IAC1C,KAAK,EAAE,YAAY,GAAG,IAAI,CAAQ;IAElC,iEAAiE;IACjE,OAAO,CAAC,YAAY,CAAS;IAE7B,iDAAiD;IACjD,OAAO,CAAC,QAAQ,CAAuB;IACvC,qCAAqC;IACrC,OAAO,CAAC,aAAa,CAAqB;IAC1C;;;;OAIG;IACH,OAAO,CAAC,cAAc,CAAS;gBAEnB,GAAG,EAAE,QAAQ;IAWzB,OAAO,CAAC,GAAG;IAIX,6DAA6D;IAC7D,SAAS,IAAI,OAAO;IAMpB;;;OAGG;IACH,MAAM,IAAI,IAAI;IAMd;;;;;OAKG;IACH,OAAO,CAAC,eAAe;IAgBvB,uEAAuE;IACvE,OAAO,CAAC,WAAW;IA2DnB;;;;;;;OAOG;IACH,OAAO,CAAC,eAAe;IA0CvB;;;;;OAKG;IACH,OAAO,CAAC,cAAc;IAYtB,OAAO,CAAC,cAAc;IAiBtB,OAAO,CAAC,WAAW;IA+BnB,QAAQ,IAAI,IAAI;CAYjB"}
|