@epxing/utils 0.0.0-stage → 5.3.0-beta.1
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/API.md +602 -0
- package/README.md +22 -2
- package/dist/_vendor/.pnpm/jsencrypt@3.5.4/_vendor/jsencrypt/bin/jsencrypt.min.js +1691 -0
- package/dist/_virtual/_commonjsHelpers.js +6 -0
- package/dist/_virtual/jsencrypt.min.js +31 -0
- package/dist/_virtual/jsencrypt.min2.js +4 -0
- package/dist/_virtual/preload-helper.js +88 -0
- package/dist/assets/ai-BBFn3QRy.png +0 -0
- package/dist/assets/ai_import-BxQ7Ohn6.png +0 -0
- package/dist/assets/app-DBNIokDP.png +0 -0
- package/dist/assets/approval-DGcBv-re.png +0 -0
- package/dist/assets/create_market_system-BH__UVbt.png +0 -0
- package/dist/assets/create_system-irg--ePh.png +0 -0
- package/dist/assets/custom-C-ujlHz1.png +0 -0
- package/dist/assets/default-icon-2193FMjv.png +0 -0
- package/dist/assets/ex_link-4fYPd9nh.png +0 -0
- package/dist/assets/excel-BYqRm2Y8.png +0 -0
- package/dist/assets/folder-CFVsNbjj.png +0 -0
- package/dist/assets/form-DD9HV-jN.png +0 -0
- package/dist/assets/import-tfKWbWVS.png +0 -0
- package/dist/assets/meta-Qorpi70B.png +0 -0
- package/dist/assets/module-DQrs7VAS.png +0 -0
- package/dist/assets/null-B2An29h4.png +0 -0
- package/dist/assets/option-CG02tIgh.svg +1 -0
- package/dist/assets/photo-uscRzRXF.png +0 -0
- package/dist/assets/process-Dcndu9m2.png +0 -0
- package/dist/assets/rule-DcYhYse0.png +0 -0
- package/dist/assets/service-CLYLpPl3.png +0 -0
- package/dist/assets/single_file-B3TJtJli.png +0 -0
- package/dist/assets/snapshot-history-BsVY9cVj.png +0 -0
- package/dist/assets/snapshot-manual-DBRfFHYI.png +0 -0
- package/dist/assets/snapshot-realtime-DjHnJI6C.png +0 -0
- package/dist/assets/staff-Bn_-_00l.png +0 -0
- package/dist/assets/sync_append-Bm8mtvZs.png +0 -0
- package/dist/assets/sync_append_update-DzjxqJ_U.png +0 -0
- package/dist/assets/sync_loading-D7LoNPFP.png +0 -0
- package/dist/assets/sync_replace-Cbbzz327.png +0 -0
- package/dist/assets/sync_update-Birao6Cr.png +0 -0
- package/dist/assets/userRole-D1LAdM6N.png +0 -0
- package/dist/assets/user_bg-BFHtaZjz.png +0 -0
- package/dist/src/Config.js +25 -0
- package/dist/src/asset-images.js +125 -0
- package/dist/src/assets/images/add.png.js +4 -0
- package/dist/src/assets/images/ai-button.png.js +4 -0
- package/dist/src/assets/images/ai.png.js +4 -0
- package/dist/src/assets/images/ai_import.png.js +4 -0
- package/dist/src/assets/images/app.png.js +4 -0
- package/dist/src/assets/images/approval.png.js +4 -0
- package/dist/src/assets/images/book.png.js +4 -0
- package/dist/src/assets/images/canvas-editor/alignment.svg.js +4 -0
- package/dist/src/assets/images/canvas-editor/arrow-left.svg.js +4 -0
- package/dist/src/assets/images/canvas-editor/arrow-right.svg.js +4 -0
- package/dist/src/assets/images/canvas-editor/block.svg.js +4 -0
- package/dist/src/assets/images/canvas-editor/bold.svg.js +4 -0
- package/dist/src/assets/images/canvas-editor/catalog.svg.js +4 -0
- package/dist/src/assets/images/canvas-editor/center.svg.js +4 -0
- package/dist/src/assets/images/canvas-editor/checkbox.svg.js +4 -0
- package/dist/src/assets/images/canvas-editor/close.svg.js +4 -0
- package/dist/src/assets/images/canvas-editor/codeblock.svg.js +4 -0
- package/dist/src/assets/images/canvas-editor/color.svg.js +4 -0
- package/dist/src/assets/images/canvas-editor/control.svg.js +4 -0
- package/dist/src/assets/images/canvas-editor/date.svg.js +4 -0
- package/dist/src/assets/images/canvas-editor/exit-fullscreen.svg.js +4 -0
- package/dist/src/assets/images/canvas-editor/format.svg.js +4 -0
- package/dist/src/assets/images/canvas-editor/highlight.svg.js +4 -0
- package/dist/src/assets/images/canvas-editor/hyperlink.svg.js +4 -0
- package/dist/src/assets/images/canvas-editor/image.svg.js +4 -0
- package/dist/src/assets/images/canvas-editor/italic.svg.js +4 -0
- package/dist/src/assets/images/canvas-editor/justify.svg.js +4 -0
- package/dist/src/assets/images/canvas-editor/latex.svg.js +4 -0
- package/dist/src/assets/images/canvas-editor/left.svg.js +4 -0
- package/dist/src/assets/images/canvas-editor/line-dash-dot-dot.svg.js +4 -0
- package/dist/src/assets/images/canvas-editor/line-dash-dot.svg.js +4 -0
- package/dist/src/assets/images/canvas-editor/line-dash-large-gap.svg.js +4 -0
- package/dist/src/assets/images/canvas-editor/line-dash-small-gap.svg.js +4 -0
- package/dist/src/assets/images/canvas-editor/line-dot.svg.js +4 -0
- package/dist/src/assets/images/canvas-editor/line-double.svg.js +4 -0
- package/dist/src/assets/images/canvas-editor/line-single.svg.js +4 -0
- package/dist/src/assets/images/canvas-editor/line-wavy.svg.js +4 -0
- package/dist/src/assets/images/canvas-editor/list.svg.js +4 -0
- package/dist/src/assets/images/canvas-editor/option.svg.js +4 -0
- package/dist/src/assets/images/canvas-editor/page-break.svg.js +4 -0
- package/dist/src/assets/images/canvas-editor/page-mode.svg.js +4 -0
- package/dist/src/assets/images/canvas-editor/page-scale-add.svg.js +4 -0
- package/dist/src/assets/images/canvas-editor/page-scale-minus.svg.js +4 -0
- package/dist/src/assets/images/canvas-editor/painter.svg.js +4 -0
- package/dist/src/assets/images/canvas-editor/paper-direction.svg.js +4 -0
- package/dist/src/assets/images/canvas-editor/paper-margin.svg.js +4 -0
- package/dist/src/assets/images/canvas-editor/paper-size.svg.js +4 -0
- package/dist/src/assets/images/canvas-editor/print.svg.js +4 -0
- package/dist/src/assets/images/canvas-editor/radio.svg.js +4 -0
- package/dist/src/assets/images/canvas-editor/redo.svg.js +4 -0
- package/dist/src/assets/images/canvas-editor/request-fullscreen.svg.js +4 -0
- package/dist/src/assets/images/canvas-editor/right.svg.js +4 -0
- package/dist/src/assets/images/canvas-editor/row-margin.svg.js +4 -0
- package/dist/src/assets/images/canvas-editor/search.svg.js +4 -0
- package/dist/src/assets/images/canvas-editor/separator.svg.js +4 -0
- package/dist/src/assets/images/canvas-editor/signature-undo.svg.js +4 -0
- package/dist/src/assets/images/canvas-editor/signature.svg.js +4 -0
- package/dist/src/assets/images/canvas-editor/size-add.svg.js +4 -0
- package/dist/src/assets/images/canvas-editor/size-minus.svg.js +4 -0
- package/dist/src/assets/images/canvas-editor/strikeout.svg.js +4 -0
- package/dist/src/assets/images/canvas-editor/subscript.svg.js +4 -0
- package/dist/src/assets/images/canvas-editor/superscript.svg.js +4 -0
- package/dist/src/assets/images/canvas-editor/table.svg.js +4 -0
- package/dist/src/assets/images/canvas-editor/title.svg.js +4 -0
- package/dist/src/assets/images/canvas-editor/trash.svg.js +4 -0
- package/dist/src/assets/images/canvas-editor/underline.svg.js +4 -0
- package/dist/src/assets/images/canvas-editor/undo.svg.js +4 -0
- package/dist/src/assets/images/canvas-editor/watermark.svg.js +4 -0
- package/dist/src/assets/images/canvas-editor/word-tool.svg.js +4 -0
- package/dist/src/assets/images/clear.png.js +4 -0
- package/dist/src/assets/images/create_market_system.png.js +4 -0
- package/dist/src/assets/images/create_system.png.js +4 -0
- package/dist/src/assets/images/custom.png.js +4 -0
- package/dist/src/assets/images/default-icon.png.js +4 -0
- package/dist/src/assets/images/ex_link.png.js +4 -0
- package/dist/src/assets/images/excel.png.js +4 -0
- package/dist/src/assets/images/file.png.js +4 -0
- package/dist/src/assets/images/folder.png.js +4 -0
- package/dist/src/assets/images/hall/form.png.js +4 -0
- package/dist/src/assets/images/hall/meta.png.js +4 -0
- package/dist/src/assets/images/hall/process.png.js +4 -0
- package/dist/src/assets/images/hall/rule.png.js +4 -0
- package/dist/src/assets/images/hall/service.png.js +4 -0
- package/dist/src/assets/images/hall/userRole.png.js +4 -0
- package/dist/src/assets/images/hr.png.js +4 -0
- package/dist/src/assets/images/import.png.js +4 -0
- package/dist/src/assets/images/jian.png.js +4 -0
- package/dist/src/assets/images/module.png.js +4 -0
- package/dist/src/assets/images/null.png.js +4 -0
- package/dist/src/assets/images/photo.png.js +4 -0
- package/dist/src/assets/images/qq.png.js +4 -0
- package/dist/src/assets/images/robot.png.js +4 -0
- package/dist/src/assets/images/single_file.png.js +4 -0
- package/dist/src/assets/images/snapshot-history.png.js +4 -0
- package/dist/src/assets/images/snapshot-manual.png.js +4 -0
- package/dist/src/assets/images/snapshot-realtime.png.js +4 -0
- package/dist/src/assets/images/staff.png.js +4 -0
- package/dist/src/assets/images/switch_app.png.js +4 -0
- package/dist/src/assets/images/sync_append.png.js +4 -0
- package/dist/src/assets/images/sync_append_update.png.js +4 -0
- package/dist/src/assets/images/sync_loading.png.js +4 -0
- package/dist/src/assets/images/sync_replace.png.js +4 -0
- package/dist/src/assets/images/sync_update.png.js +4 -0
- package/dist/src/assets/images/user_bg.png.js +4 -0
- package/dist/src/assets/images/weixin.png.js +4 -0
- package/dist/src/chart-config-update.js +17 -0
- package/dist/src/chart-data-adapters.js +112 -0
- package/dist/src/chart-option-override.js +128 -0
- package/dist/src/chart-option-paths.js +64 -0
- package/dist/src/chart-registry.js +46 -0
- package/dist/src/chart-render-host.js +57 -0
- package/dist/src/chart-request-broker.js +176 -0
- package/dist/src/chartTheme.js +683 -0
- package/dist/src/collection.js +25 -0
- package/dist/src/date-helper.js +60 -0
- package/dist/src/dom-helper.js +22 -0
- package/dist/src/eval-with.js +14 -0
- package/dist/src/fieldFactory.js +5 -0
- package/dist/src/formula.js +365 -0
- package/dist/src/hljs-lite.js +29 -0
- package/dist/src/index.js +24 -0
- package/dist/src/lazy-jsencrypt.js +13 -0
- package/dist/src/legacy.js +25 -0
- package/dist/src/locale.js +88 -0
- package/dist/src/menu-open.js +43 -0
- package/dist/src/mermaid.js +79 -0
- package/dist/src/meta.js +4 -0
- package/dist/src/mobile-chart-appearance.js +148 -0
- package/dist/src/moment-compat.js +558 -0
- package/dist/src/nav-history.js +184 -0
- package/dist/src/page.js +116 -0
- package/dist/src/pdf.viewer.js +4 -0
- package/dist/src/placeholder.js +64 -0
- package/dist/src/public.js +49 -0
- package/dist/src/security.js +26 -0
- package/dist/src/storage.js +107 -0
- package/dist/src/token-storage.js +108 -0
- package/dist/src/util.js +4 -0
- package/package.json +109 -3
- package/src/Config.js +25 -0
- package/src/asset-images.js +16 -0
- package/src/chart-config-update.js +16 -0
- package/src/chart-data-adapters.js +106 -0
- package/src/chart-option-override.js +126 -0
- package/src/chart-option-paths.js +63 -0
- package/src/chart-registry.js +41 -0
- package/src/chart-render-host.js +56 -0
- package/src/chart-request-broker.js +171 -0
- package/src/chartTheme.js +711 -0
- package/src/collection.js +28 -0
- package/src/date-helper.js +72 -0
- package/src/dom-helper.js +26 -0
- package/src/eval-with.js +21 -0
- package/src/fieldFactory.js +3 -0
- package/src/formula.js +362 -0
- package/src/hljs-lite.js +41 -0
- package/src/index.js +2 -0
- package/src/lazy-jsencrypt.js +36 -0
- package/src/legacy.js +29 -0
- package/src/locale.js +104 -0
- package/src/menu-open.js +72 -0
- package/src/mermaid.js +115 -0
- package/src/meta.js +2 -0
- package/src/mobile-chart-appearance.js +147 -0
- package/src/moment-compat.d.ts +91 -0
- package/src/moment-compat.js +744 -0
- package/src/nav-history.js +274 -0
- package/src/page.js +129 -0
- package/src/pdf.viewer.js +2 -0
- package/src/placeholder.js +107 -0
- package/src/public.js +11 -0
- package/src/security.js +29 -0
- package/src/storage.js +135 -0
- package/src/token-storage.js +120 -0
- package/src/util.js +2 -0
- package/types/Config.d.ts +4 -0
- package/types/collection.d.ts +40 -0
- package/types/date-helper.d.ts +11 -0
- package/types/date-time.d.ts +2 -0
- package/types/date.d.ts +1 -0
- package/types/dom-helper.d.ts +3 -0
- package/types/dom.d.ts +1 -0
- package/types/index.d.ts +1 -0
- package/types/moment-compat.d.ts +91 -0
- package/types/page.d.ts +56 -0
- package/types/placeholder.d.ts +42 -0
- package/types/public.d.ts +10 -0
- package/types/storage.d.ts +31 -0
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
function excludeBoxplotMissingValues(params) {
|
|
2
|
+
var _a;
|
|
3
|
+
const fields = [...new Set((((_a = params.chartData) == null ? void 0 : _a.statisticalList) || []).map((item) => item.fieldCode).filter(Boolean))];
|
|
4
|
+
if (!fields.length) return;
|
|
5
|
+
const scopes = JSON.parse(params.scopeParams || "[]").map((item) => ({ ...item }));
|
|
6
|
+
if (scopes.length) {
|
|
7
|
+
scopes[0].lb = "(" + (scopes[0].lb || "");
|
|
8
|
+
const last = scopes[scopes.length - 1];
|
|
9
|
+
last.rb = (last.rb || "") + ")";
|
|
10
|
+
last.link = "and";
|
|
11
|
+
}
|
|
12
|
+
fields.forEach((field) => scopes.push({ field, mode: "not_null", link: "and" }));
|
|
13
|
+
scopes[scopes.length - 1].link = "";
|
|
14
|
+
params.scopeParams = JSON.stringify(scopes);
|
|
15
|
+
}
|
|
16
|
+
const numeric = (value) => {
|
|
17
|
+
if (value === void 0 || value === null || String(value).trim() === "") return null;
|
|
18
|
+
const number = typeof value === "number" ? value : Number(String(value ?? "").replace(/%$/, ""));
|
|
19
|
+
return Number.isFinite(number) && value !== null && value !== "" ? number : null;
|
|
20
|
+
};
|
|
21
|
+
const failure = (message) => ({ hasData: false, message });
|
|
22
|
+
const quantile = (sorted, p) => {
|
|
23
|
+
const index = (sorted.length - 1) * p;
|
|
24
|
+
const lower = Math.floor(index);
|
|
25
|
+
return sorted[lower] + (sorted[Math.ceil(index)] - sorted[lower]) * (index - lower);
|
|
26
|
+
};
|
|
27
|
+
function summarizeBoxplot(values) {
|
|
28
|
+
const sorted = values.map(numeric).filter((value) => value !== null).sort((a, b) => a - b);
|
|
29
|
+
if (!sorted.length) return null;
|
|
30
|
+
const q1 = quantile(sorted, 0.25);
|
|
31
|
+
const median = quantile(sorted, 0.5);
|
|
32
|
+
const q3 = quantile(sorted, 0.75);
|
|
33
|
+
const gap = (q3 - q1) * 1.5;
|
|
34
|
+
const retained = sorted.filter((value) => value >= q1 - gap && value <= q3 + gap);
|
|
35
|
+
return { value: [retained[0], q1, median, q3, retained[retained.length - 1]], outliers: sorted.filter((value) => value < q1 - gap || value > q3 + gap), count: sorted.length };
|
|
36
|
+
}
|
|
37
|
+
function buildAdvancedChartOption(type, data, config = {}, chartData = {}, theme = "") {
|
|
38
|
+
var _a, _b, _c, _d;
|
|
39
|
+
const option = Object.fromEntries(Object.entries(config).filter(([key]) => key !== "title" && !key.startsWith("series-") && key !== "echartsOptionOverride"));
|
|
40
|
+
option.tooltip = { trigger: "item", confine: true, ...option.tooltip };
|
|
41
|
+
if ((_a = option.legend) == null ? void 0 : _a._orient) {
|
|
42
|
+
const { direction = "bottom", horizontal = "center", vertical = "center" } = option.legend._orient;
|
|
43
|
+
const across = direction === "top" || direction === "bottom";
|
|
44
|
+
option.legend = { ...option.legend, orient: across ? "horizontal" : "vertical", top: direction === "top" ? "20px" : !across && vertical === "top" ? "20px" : !across && vertical === "center" ? "center" : "auto", bottom: direction === "bottom" || !across && vertical === "bottom" ? "20px" : "auto", left: direction === "left" || across && horizontal === "left" ? "20px" : across && horizontal === "center" ? "center" : "auto", right: direction === "right" || across && horizontal === "right" ? "20px" : "auto" };
|
|
45
|
+
delete option.legend._orient;
|
|
46
|
+
}
|
|
47
|
+
if ((_b = option.dataZoom) == null ? void 0 : _b.show) {
|
|
48
|
+
option.dataZoom = [{ type: "slider", startValue: 0, ...option.dataZoom, endValue: Number(option.dataZoom.endValue || 5) - 1 }];
|
|
49
|
+
} else if (option.dataZoom && !Array.isArray(option.dataZoom)) delete option.dataZoom;
|
|
50
|
+
if (type === "boxplot") {
|
|
51
|
+
if (((_c = chartData.categoryList) == null ? void 0 : _c.length) || ((_d = chartData.statisticalList) == null ? void 0 : _d.length) !== 2) return failure("箱线图需要两个数字统计字段,并且不设置分类维度");
|
|
52
|
+
if (chartData.statisticalList.some((item) => item.type !== "indexField" || !item.fieldCode || item.fieldCode === "record")) return failure("箱线图请使用数字字段,不支持记录数或自定义统计项");
|
|
53
|
+
const rows = data == null ? void 0 : data.data;
|
|
54
|
+
if (!Array.isArray(rows) || !rows.length) return failure();
|
|
55
|
+
if (rows.some((row) => !Array.isArray(row) || row.length !== 2)) return failure("箱线图需要原始数字对数据");
|
|
56
|
+
const summaries = [0, 1].map((index) => summarizeBoxplot(rows.map((row) => row[index])));
|
|
57
|
+
if (summaries.some((item) => !item)) return failure();
|
|
58
|
+
const names = chartData.statisticalList.map((item, index) => item.name || `指标 ${index + 1}`);
|
|
59
|
+
option.grid = { containLabel: true, left: 30, right: 20, top: 30, bottom: 30, ...option.grid };
|
|
60
|
+
option.xAxis = { ...option.xAxis, type: "category", data: names };
|
|
61
|
+
option.yAxis = { type: "value", ...option.yAxis };
|
|
62
|
+
option.series = [{ type: "boxplot", name: "箱线", ...config["series-boxplot"], data: summaries.map((item, index) => ({ name: names[index], value: item.value })) }];
|
|
63
|
+
const outliers = summaries.flatMap((item, index) => item.outliers.map((value) => [index, value]));
|
|
64
|
+
option.series.push({ type: "scatter", name: "离群值", data: outliers });
|
|
65
|
+
return { hasData: true, option };
|
|
66
|
+
}
|
|
67
|
+
const series = data == null ? void 0 : data.series;
|
|
68
|
+
if (!Array.isArray(series) || !series.length || series.every((item) => {
|
|
69
|
+
var _a2;
|
|
70
|
+
return !((_a2 = item.data) == null ? void 0 : _a2.length);
|
|
71
|
+
})) return failure();
|
|
72
|
+
if (series.some((row) => !Array.isArray(row.data) || row.data.some((item) => numeric(item.value) === null))) return failure("当前统计结果包含非数值,不能生成此图表");
|
|
73
|
+
if (type === "gauge") {
|
|
74
|
+
const cells = series.flatMap((row) => row.data);
|
|
75
|
+
if (cells.length !== 1) return failure("仪表盘需要单个统计值;请设置一个指标且不拆分分类");
|
|
76
|
+
const cell = cells[0];
|
|
77
|
+
const value = numeric(cell.value);
|
|
78
|
+
option.series = [{ type: "gauge", min: Math.min(0, value), max: Math.max(100, value), progress: { show: true }, detail: { formatter: "{value}" }, ...config["series-gauge"], data: [{ ...cell, value }] }];
|
|
79
|
+
if (["dark", "technology"].includes(theme)) {
|
|
80
|
+
const gauge = option.series[0];
|
|
81
|
+
gauge.axisLabel = { color: "#d9e1ec", ...gauge.axisLabel };
|
|
82
|
+
gauge.detail = { color: "#d9e1ec", ...gauge.detail };
|
|
83
|
+
gauge.title = { color: "#d9e1ec", ...gauge.title };
|
|
84
|
+
}
|
|
85
|
+
if (!(Number(option.series[0].max) > Number(option.series[0].min))) return failure("仪表盘最大刻度必须大于最小刻度");
|
|
86
|
+
delete option.xAxis;
|
|
87
|
+
delete option.yAxis;
|
|
88
|
+
delete option.grid;
|
|
89
|
+
} else if (type === "treemap") {
|
|
90
|
+
if (series.some((row) => row.data.some((item) => numeric(item.value) < 0))) return failure("矩形树图的统计值不能为负数");
|
|
91
|
+
option.series = [{ type: "treemap", roam: false, breadcrumb: { show: true }, ...config["series-treemap"], data: series.map((row, index) => ({ name: row.name || `指标 ${index + 1}`, children: row.data.map((cell) => ({ ...cell, value: numeric(cell.value) })) })) }];
|
|
92
|
+
delete option.xAxis;
|
|
93
|
+
delete option.yAxis;
|
|
94
|
+
delete option.grid;
|
|
95
|
+
} else if (type === "heatmap") {
|
|
96
|
+
const categories = series[0].data.map((item) => item.name || "");
|
|
97
|
+
if (series.some((row) => row.data.length !== categories.length || row.data.some((cell, index) => (cell.name || "") !== categories[index]))) return failure("热力图要求各指标使用同一组分类维度");
|
|
98
|
+
const cells = series.flatMap((row, rowIndex) => row.data.map((cell, colIndex) => ({ ...cell, value: [colIndex, rowIndex, numeric(cell.value)] })));
|
|
99
|
+
const values = cells.map((cell) => cell.value[2]);
|
|
100
|
+
option.grid = { containLabel: true, left: 30, right: 30, top: 30, bottom: 60, ...option.grid };
|
|
101
|
+
option.xAxis = { type: "category", splitArea: { show: true }, ...option.xAxis, data: categories };
|
|
102
|
+
option.yAxis = { type: "category", splitArea: { show: true }, ...option.yAxis, data: series.map((row, index) => row.name || `指标 ${index + 1}`) };
|
|
103
|
+
option.visualMap = { min: values.reduce((min, value) => Math.min(min, value), Infinity), max: values.reduce((max, value) => Math.max(max, value), 1), calculable: true, orient: "horizontal", left: "center", bottom: 0, ...option.visualMap };
|
|
104
|
+
option.series = [{ type: "heatmap", label: { show: false }, ...config["series-heatmap"], data: cells }];
|
|
105
|
+
} else return failure("不支持的图表类型");
|
|
106
|
+
return { hasData: true, option };
|
|
107
|
+
}
|
|
108
|
+
export {
|
|
109
|
+
buildAdvancedChartOption,
|
|
110
|
+
excludeBoxplotMissingValues,
|
|
111
|
+
summarizeBoxplot
|
|
112
|
+
};
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
import { getChartType } from "./chart-registry.js";
|
|
2
|
+
const own = (value, key) => Object.prototype.hasOwnProperty.call(value, key);
|
|
3
|
+
const object = (value) => value !== null && typeof value === "object" && !Array.isArray(value);
|
|
4
|
+
const unsafeKeys = /* @__PURE__ */ new Set(["__proto__", "prototype", "constructor"]);
|
|
5
|
+
const MAX_OVERRIDE_BYTES = 64 * 1024;
|
|
6
|
+
function validateChartOptionOverride(override, baseOption = {}, chartType = "") {
|
|
7
|
+
const errors = [];
|
|
8
|
+
if (!object(override)) return [{ path: "$", message: "覆盖层必须是 JSON 对象" }];
|
|
9
|
+
let encoded;
|
|
10
|
+
try {
|
|
11
|
+
encoded = JSON.stringify(override);
|
|
12
|
+
} catch {
|
|
13
|
+
return [{ path: "$", message: "覆盖层必须是可序列化的 JSON 对象" }];
|
|
14
|
+
}
|
|
15
|
+
const size = new TextEncoder().encode(encoded).length;
|
|
16
|
+
if (size > MAX_OVERRIDE_BYTES) errors.push({ path: "$", message: "覆盖层不能超过 64 KB" });
|
|
17
|
+
const visit = (value, path, depth = 0) => {
|
|
18
|
+
if (depth > 64) {
|
|
19
|
+
errors.push({ path, message: "配置层级不能超过 64 层" });
|
|
20
|
+
return;
|
|
21
|
+
}
|
|
22
|
+
if (Array.isArray(value)) {
|
|
23
|
+
value.forEach((item, index) => visit(item, `${path}[${index}]`, depth + 1));
|
|
24
|
+
return;
|
|
25
|
+
}
|
|
26
|
+
if (!object(value)) return;
|
|
27
|
+
for (const [key, child] of Object.entries(value)) {
|
|
28
|
+
const childPath = path === "$" ? key : `${path}.${key}`;
|
|
29
|
+
if (unsafeKeys.has(key)) errors.push({ path: childPath, message: "不支持的属性名" });
|
|
30
|
+
if (key === "source" && (path === "dataset" || /^dataset\[\d+\]$/.test(path))) errors.push({ path: childPath, message: "数据源由平台维护" });
|
|
31
|
+
if (key === "data" && /^series\[\d+\]$/.test(path)) errors.push({ path: childPath, message: "系列数据由平台维护" });
|
|
32
|
+
visit(child, childPath, depth + 1);
|
|
33
|
+
}
|
|
34
|
+
};
|
|
35
|
+
visit(override, "$");
|
|
36
|
+
if (own(override, "series")) {
|
|
37
|
+
const baseSeries = Array.isArray(baseOption.series) ? baseOption.series : [baseOption.series].filter(Boolean);
|
|
38
|
+
if (!Array.isArray(override.series) || override.series.length !== baseSeries.length || !override.series.every(object)) {
|
|
39
|
+
errors.push({ path: "series", message: `系列覆盖必须与当前 ${baseSeries.length} 个系列逐项对应` });
|
|
40
|
+
} else {
|
|
41
|
+
override.series.forEach((series, index) => {
|
|
42
|
+
var _a, _b, _c;
|
|
43
|
+
if (own(series, "type") && series.type !== ((_a = baseSeries[index]) == null ? void 0 : _a.type)) errors.push({ path: `series[${index}].type`, message: "不能改变平台系列类型" });
|
|
44
|
+
if (own(series, "coordinateSystem")) {
|
|
45
|
+
const coordinate = ((_b = baseSeries[index]) == null ? void 0 : _b.coordinateSystem) || ((_c = getChartType(chartType)) == null ? void 0 : _c.coordinate);
|
|
46
|
+
if (series.coordinateSystem !== coordinate) errors.push({ path: `series[${index}].coordinateSystem`, message: "不能改变平台坐标系" });
|
|
47
|
+
}
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
if (Array.isArray(override.dataset)) errors.push({ path: "dataset", message: "暂不支持替换数据集数组" });
|
|
52
|
+
if (own(override, "dataset") && (!object(override.dataset) || Array.isArray(baseOption.dataset))) errors.push({ path: "dataset", message: "不能替换平台数据集结构" });
|
|
53
|
+
for (const axis of ["xAxis", "yAxis"]) {
|
|
54
|
+
if (own(override, axis) && !(object(override[axis]) || Array.isArray(override[axis]) && override[axis].length && override[axis].every(object))) {
|
|
55
|
+
errors.push({ path: axis, message: "坐标轴必须是对象或非空对象数组" });
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
if (getChartType(chartType) && getChartType(chartType).coordinate !== "cartesian2d") {
|
|
59
|
+
for (const axis of ["xAxis", "yAxis", "grid", "dataZoom"]) {
|
|
60
|
+
if (own(override, axis)) errors.push({ path: axis, message: "当前图表类型不使用直角坐标系" });
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
return errors;
|
|
64
|
+
}
|
|
65
|
+
function mergeObject(base, patch) {
|
|
66
|
+
const result = object(base) ? { ...base } : {};
|
|
67
|
+
for (const [key, value] of Object.entries(patch)) {
|
|
68
|
+
if (object(value)) result[key] = mergeObject(result[key], value);
|
|
69
|
+
else if (Array.isArray(value)) result[key] = value.map((item) => object(item) ? mergeObject({}, item) : item);
|
|
70
|
+
else result[key] = value;
|
|
71
|
+
}
|
|
72
|
+
return result;
|
|
73
|
+
}
|
|
74
|
+
function applyChartOptionOverride(baseOption, override, chartType = "") {
|
|
75
|
+
if (override === void 0) return baseOption;
|
|
76
|
+
const errors = validateChartOptionOverride(override, baseOption, chartType);
|
|
77
|
+
if (errors.length) return baseOption;
|
|
78
|
+
const { series, ...other } = override;
|
|
79
|
+
const result = mergeObject(baseOption, other);
|
|
80
|
+
for (const key of ["xAxis", "yAxis", "grid", "legend", "visualMap", "dataZoom"]) {
|
|
81
|
+
if (Array.isArray(other[key])) {
|
|
82
|
+
const base = Array.isArray(baseOption[key]) ? baseOption[key] : [baseOption[key]].filter(Boolean);
|
|
83
|
+
result[key] = Array.from({ length: Math.max(base.length, other[key].length) }, (_, index) => mergeObject(base[index], other[key][index] || {}));
|
|
84
|
+
} else if (object(other[key]) && Array.isArray(baseOption[key])) {
|
|
85
|
+
result[key] = baseOption[key].map((item, index) => index === 0 ? mergeObject(item, other[key]) : item);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
if (series) {
|
|
89
|
+
result.series = baseOption.series.map((item, index) => mergeObject(item, series[index]));
|
|
90
|
+
}
|
|
91
|
+
return result;
|
|
92
|
+
}
|
|
93
|
+
function setChartOption(host, baseOption, override) {
|
|
94
|
+
var _a;
|
|
95
|
+
host.chartOption = applyChartOptionOverride(baseOption, override, host.chartType);
|
|
96
|
+
if (host.designMode && ((_a = host.$refs) == null ? void 0 : _a.container)) {
|
|
97
|
+
host.$refs.container.dispatchEvent(new CustomEvent("epx-chart-option-built", {
|
|
98
|
+
bubbles: true,
|
|
99
|
+
detail: { base: baseOption, effective: host.chartOption }
|
|
100
|
+
}));
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
function validateChartOptionRendering(option, echarts) {
|
|
104
|
+
var _a;
|
|
105
|
+
if (!echarts) return [{ path: "$", message: "等待图表加载后再预览" }];
|
|
106
|
+
const element = document.createElement("div");
|
|
107
|
+
element.style.cssText = "position:fixed;left:-10000px;top:0;width:800px;height:400px;visibility:hidden;pointer-events:none";
|
|
108
|
+
document.body.appendChild(element);
|
|
109
|
+
let chart;
|
|
110
|
+
try {
|
|
111
|
+
chart = echarts.init(element);
|
|
112
|
+
chart.setOption(option, { notMerge: true });
|
|
113
|
+
const expected = Array.isArray(option.series) ? option.series.length : option.series ? 1 : 0;
|
|
114
|
+
if (chart.getOption && (((_a = chart.getOption().series) == null ? void 0 : _a.length) || 0) !== expected) throw new Error("当前 ECharts 未能加载全部系列类型");
|
|
115
|
+
return [];
|
|
116
|
+
} catch (error) {
|
|
117
|
+
return [{ path: "$", message: `图表预览失败:${error.message}` }];
|
|
118
|
+
} finally {
|
|
119
|
+
if (chart) chart.dispose();
|
|
120
|
+
element.remove();
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
export {
|
|
124
|
+
applyChartOptionOverride,
|
|
125
|
+
setChartOption,
|
|
126
|
+
validateChartOptionOverride,
|
|
127
|
+
validateChartOptionRendering
|
|
128
|
+
};
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
function chartOverridePaths(key, base = {}, patch = {}) {
|
|
2
|
+
if (key === "title" || key.startsWith("title.")) return [];
|
|
3
|
+
if (key.startsWith("series-")) {
|
|
4
|
+
const [group, ...parts2] = key.split(".");
|
|
5
|
+
const type = group.slice(7) === "wordcloud" ? "wordCloud" : group.slice(7);
|
|
6
|
+
if (["total", "totalText"].includes(parts2[0])) return [];
|
|
7
|
+
const native = parts2[0] === "showArea" ? ["areaStyle"] : parts2[0] === "barType" ? ["stack"] : parts2.join(".") === "label.customFormatter" ? ["label", "formatter"] : parts2;
|
|
8
|
+
return (base.series || []).flatMap((series, index) => series.type === type ? [["series", index, ...native]] : []);
|
|
9
|
+
}
|
|
10
|
+
if (/^yAxis\.(leftAxis|rightAxis)\./.test(key)) {
|
|
11
|
+
const [, side, ...parts2] = key.split(".");
|
|
12
|
+
return [["yAxis", side === "leftAxis" ? 0 : 1, ...parts2]];
|
|
13
|
+
}
|
|
14
|
+
if (["yAxis.leftName", "yAxis.rightName"].includes(key)) return [["yAxis", key === "yAxis.leftName" ? 0 : 1, "name"]];
|
|
15
|
+
if (key.startsWith("legend._orient.")) {
|
|
16
|
+
const fields = key.endsWith(".horizontal") ? ["left", "right"] : key.endsWith(".vertical") ? ["top", "bottom"] : ["orient", "left", "right", "top", "bottom"];
|
|
17
|
+
return fields.map((field) => ["legend", field]);
|
|
18
|
+
}
|
|
19
|
+
const [root, ...parts] = key.split(".");
|
|
20
|
+
if (Array.isArray(patch[root])) return patch[root].map((_, index) => [root, index, ...parts]);
|
|
21
|
+
return [[root, ...parts]];
|
|
22
|
+
}
|
|
23
|
+
const readPath = (value, path) => {
|
|
24
|
+
if (!path.length) return value;
|
|
25
|
+
if (Array.isArray(value) && typeof path[0] !== "number") return value.map((item) => readPath(item, path)).find((item) => item !== void 0);
|
|
26
|
+
if (!Array.isArray(value) && typeof path[0] === "number") return path[0] === 0 ? readPath(value, path.slice(1)) : void 0;
|
|
27
|
+
return readPath(value == null ? void 0 : value[path[0]], path.slice(1));
|
|
28
|
+
};
|
|
29
|
+
function isChartPathOverridden(patch, key, base) {
|
|
30
|
+
return chartOverridePaths(key, base, patch).some((path) => readPath(patch, path) !== void 0);
|
|
31
|
+
}
|
|
32
|
+
function clearChartOverridePath(patch, key, base) {
|
|
33
|
+
const remove = (value, path) => {
|
|
34
|
+
if (!value || !path.length) return;
|
|
35
|
+
if (Array.isArray(value) && typeof path[0] !== "number") {
|
|
36
|
+
value.forEach((item) => remove(item, path));
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
39
|
+
if (!Array.isArray(value) && typeof path[0] === "number") {
|
|
40
|
+
if (path[0] === 0) remove(value, path.slice(1));
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
const [part, ...rest] = path;
|
|
44
|
+
if (!rest.length) {
|
|
45
|
+
if (Array.isArray(value)) value[part] = {};
|
|
46
|
+
else delete value[part];
|
|
47
|
+
} else {
|
|
48
|
+
remove(value[part], rest);
|
|
49
|
+
if (value[part] && !Array.isArray(value[part]) && typeof value[part] === "object" && !Object.keys(value[part]).length && !Array.isArray(value)) delete value[part];
|
|
50
|
+
}
|
|
51
|
+
};
|
|
52
|
+
const paths = chartOverridePaths(key, base, patch);
|
|
53
|
+
for (const path of paths) remove(patch, path);
|
|
54
|
+
for (const root of new Set(paths.map((path) => path[0]))) {
|
|
55
|
+
const value = patch[root];
|
|
56
|
+
if (Array.isArray(value) && value.every((item) => item && typeof item === "object" && !Object.keys(item).length)) delete patch[root];
|
|
57
|
+
}
|
|
58
|
+
return patch;
|
|
59
|
+
}
|
|
60
|
+
export {
|
|
61
|
+
chartOverridePaths,
|
|
62
|
+
clearChartOverridePath,
|
|
63
|
+
isChartPathOverridden
|
|
64
|
+
};
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
const chartTypes = [
|
|
2
|
+
{ type: "table", text: "统计表", icon: "epx-icon-tongjibiao", component: "chart-table", requestType: "table", coordinate: "none" },
|
|
3
|
+
{ type: "pie", text: "饼状图", icon: "epx-icon-bingzhuangtu", component: "chart-common", requestType: "pie", coordinate: "none" },
|
|
4
|
+
{ type: "circularPie", text: "环形饼图", icon: "epx-icon-bingzhuangtu", component: "chart-common", requestType: "pie", coordinate: "none" },
|
|
5
|
+
{ type: "line", text: "折线图", icon: "epx-icon-zhexiantu", component: "chart-common", requestType: "line", coordinate: "cartesian2d" },
|
|
6
|
+
{ type: "column", text: "柱状图", icon: "epx-icon-zhuzhuangtu", component: "chart-common", requestType: "bar", coordinate: "cartesian2d" },
|
|
7
|
+
{ type: "horiColumn", text: "横向柱图", icon: "epx-icon-zhuzhuangtu", component: "chart-common", requestType: "bar", cellScopeType: "column", coordinate: "cartesian2d" },
|
|
8
|
+
{ type: "funnel", text: "漏斗图", icon: "epx-icon-loudoutu", component: "chart-common", requestType: "funnel", coordinate: "none" },
|
|
9
|
+
{ type: "wordcloud", text: "词云", icon: "epx-icon-ciyun", component: "chart-wordcloud", requestType: "wordCloud", coordinate: "none" },
|
|
10
|
+
{ type: "radar", text: "雷达图", icon: "epx-icon-leidatu", component: "chart-radar", requestType: "radar", coordinate: "radar" },
|
|
11
|
+
{ type: "columnAndLine", text: "折柱混合", icon: "epx-icon-zhezhutu", component: "chart-common", requestType: "bar", coordinate: "cartesian2d" },
|
|
12
|
+
{ type: "scatter", text: "散点图", icon: "epx-icon-sandiantu", component: "chart-scatter", requestType: "scatter", coordinate: "cartesian2d", binding: "numericPairs", drill: false },
|
|
13
|
+
{ type: "map", text: "地图", icon: "epx-icon-ditu", component: "chart-map", requestType: "map", coordinate: "geo" },
|
|
14
|
+
{ type: "gauge", text: "仪表盘", icon: "epx-icon-leidatu", component: "chart-advanced", requestType: "bar", cellScopeType: "column", coordinate: "none", dataHint: "单个统计值;设置一个指标且不拆分分类。" },
|
|
15
|
+
{ type: "treemap", text: "矩形树图", icon: "epx-icon-tongjibiao", component: "chart-advanced", requestType: "bar", cellScopeType: "column", coordinate: "none", dataHint: "按指标、分类组成两层矩形树,使用非负统计值;叶子支持钻取。" },
|
|
16
|
+
{ type: "heatmap", text: "热力图", icon: "epx-icon-zhuzhuangtu", component: "chart-advanced", requestType: "bar", cellScopeType: "column", coordinate: "cartesian2d", dataHint: "横轴为分类,纵轴为指标;各指标使用同一组分类。" },
|
|
17
|
+
{ type: "boxplot", text: "箱线图", selectable: false, icon: "epx-icon-zhexiantu", component: "chart-advanced", requestType: "scatter", coordinate: "cartesian2d", binding: "numericPairs", drill: false, dataHint: "选择两个数字统计字段,不设置分类;使用原始值计算四分位数与 1.5 倍四分位距的离群值,不支持钻取。" }
|
|
18
|
+
];
|
|
19
|
+
const selectableChartTypes = chartTypes.filter((item) => item.selectable !== false);
|
|
20
|
+
function getChartType(type) {
|
|
21
|
+
return chartTypes.find((item) => item.type === (type === "bar" ? "column" : type));
|
|
22
|
+
}
|
|
23
|
+
const chartConfigCategories = [
|
|
24
|
+
{ key: "all", label: "全部" },
|
|
25
|
+
{ key: "appearance", label: "外观" },
|
|
26
|
+
{ key: "axis", label: "坐标与刻度" },
|
|
27
|
+
{ key: "shape", label: "图形" },
|
|
28
|
+
{ key: "label", label: "标签与提示" },
|
|
29
|
+
{ key: "interaction", label: "交互" },
|
|
30
|
+
{ key: "advanced", label: "高级" }
|
|
31
|
+
];
|
|
32
|
+
function getChartConfigCategory(key) {
|
|
33
|
+
if (["title", "grid", "color", "backgroundColor"].includes(key)) return "appearance";
|
|
34
|
+
if (["xAxis", "yAxis", "dualAxes", "dataZoom"].includes(key)) return "axis";
|
|
35
|
+
if (key.startsWith("series-") || ["series.map", "radar", "scatter", "table", "visualMap"].includes(key)) return "shape";
|
|
36
|
+
if (["legend", "tooltip"].includes(key)) return "label";
|
|
37
|
+
if (key === "toolbox") return "interaction";
|
|
38
|
+
return "advanced";
|
|
39
|
+
}
|
|
40
|
+
export {
|
|
41
|
+
chartConfigCategories,
|
|
42
|
+
chartTypes,
|
|
43
|
+
getChartConfigCategory,
|
|
44
|
+
getChartType,
|
|
45
|
+
selectableChartTypes
|
|
46
|
+
};
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { getChartType } from "./chart-registry.js";
|
|
2
|
+
const registeredThemes = /* @__PURE__ */ new WeakSet();
|
|
3
|
+
function registerChartThemes(echarts, themes) {
|
|
4
|
+
if (registeredThemes.has(echarts)) return;
|
|
5
|
+
for (const key in themes) echarts.registerTheme(key, themes[key]);
|
|
6
|
+
registeredThemes.add(echarts);
|
|
7
|
+
}
|
|
8
|
+
function disposeChartHost(host) {
|
|
9
|
+
var _a;
|
|
10
|
+
if (host.observedChartDom) {
|
|
11
|
+
(_a = host.resizeObserver) == null ? void 0 : _a.unobserve(host.observedChartDom);
|
|
12
|
+
host.observedChartDom = null;
|
|
13
|
+
}
|
|
14
|
+
if (!host.myChart) return;
|
|
15
|
+
host.myChart.off("click", host.openPivotPage);
|
|
16
|
+
host.myChart.dispose();
|
|
17
|
+
host.myChart = null;
|
|
18
|
+
host.renderedChartTheme = null;
|
|
19
|
+
}
|
|
20
|
+
function renderChartHost(host, option, settings) {
|
|
21
|
+
var _a, _b, _c, _d, _e;
|
|
22
|
+
const element = (_a = host.$refs) == null ? void 0 : _a.chart;
|
|
23
|
+
if (!element || !host.echarts || !option) return false;
|
|
24
|
+
const { width, height } = element.getBoundingClientRect();
|
|
25
|
+
if (width <= 0 || height <= 0) return false;
|
|
26
|
+
const staleDom = host.myChart && host.myChart.getDom && host.myChart.getDom() !== element;
|
|
27
|
+
const theme = settings && Object.prototype.hasOwnProperty.call(settings, "theme") ? settings.theme : host.chartTheme;
|
|
28
|
+
const changedTheme = host.myChart && host.renderedChartTheme !== theme;
|
|
29
|
+
if (staleDom || changedTheme) disposeChartHost(host);
|
|
30
|
+
if (!host.myChart) {
|
|
31
|
+
host.myChart = host.echarts.init(element, theme);
|
|
32
|
+
host.renderedChartTheme = theme;
|
|
33
|
+
if (host.openClickEvent) host.myChart.on("click", host.openPivotPage);
|
|
34
|
+
}
|
|
35
|
+
if (host.resizeObserver && host.observedChartDom !== element) {
|
|
36
|
+
if (host.observedChartDom) host.resizeObserver.unobserve(host.observedChartDom);
|
|
37
|
+
host.resizeObserver.observe(element);
|
|
38
|
+
host.observedChartDom = element;
|
|
39
|
+
}
|
|
40
|
+
try {
|
|
41
|
+
host.myChart.setOption(option, { notMerge: true });
|
|
42
|
+
const expected = Array.isArray(option.series) ? option.series.length : option.series ? 1 : 0;
|
|
43
|
+
const enforceSeries = ((_b = getChartType(host.chartType)) == null ? void 0 : _b.component) === "chart-advanced" || ((_d = (_c = host.chartConfigs) == null ? void 0 : _c.chartConfig) == null ? void 0 : _d.echartsOptionOverride) !== void 0;
|
|
44
|
+
if (enforceSeries && host.myChart.getOption && (((_e = host.myChart.getOption().series) == null ? void 0 : _e.length) || 0) !== expected) throw new Error("未能加载全部系列类型");
|
|
45
|
+
} catch (error) {
|
|
46
|
+
disposeChartHost(host);
|
|
47
|
+
host.error = true;
|
|
48
|
+
host.chartErrorText = `图表配置无法渲染:${error.message}`;
|
|
49
|
+
return false;
|
|
50
|
+
}
|
|
51
|
+
return true;
|
|
52
|
+
}
|
|
53
|
+
export {
|
|
54
|
+
disposeChartHost,
|
|
55
|
+
registerChartThemes,
|
|
56
|
+
renderChartHost
|
|
57
|
+
};
|
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
const stable = (value) => {
|
|
2
|
+
if (Array.isArray(value)) return value.map(stable);
|
|
3
|
+
if (value && typeof value === "object") return Object.fromEntries(Object.keys(value).sort().map((key) => [key, stable(value[key])]));
|
|
4
|
+
return value;
|
|
5
|
+
};
|
|
6
|
+
const encode = (value) => JSON.stringify(stable(value));
|
|
7
|
+
function createChartRequestBroker({ concurrency = 6, ttl = 3e4, maxEntries = 64, maxBytes = 2 * 1024 * 1024, now = Date.now } = {}) {
|
|
8
|
+
const cache = /* @__PURE__ */ new Map();
|
|
9
|
+
const jobs = /* @__PURE__ */ new Set();
|
|
10
|
+
const inFlight = /* @__PURE__ */ new Map();
|
|
11
|
+
const queue = [];
|
|
12
|
+
const counters = { hits: 0, misses: 0, coalesced: 0, cancelled: 0, peakActive: 0 };
|
|
13
|
+
let active = 0;
|
|
14
|
+
let bytes = 0;
|
|
15
|
+
let context = null;
|
|
16
|
+
let expiryTimer = null;
|
|
17
|
+
let disposed = false;
|
|
18
|
+
const remove = (key) => {
|
|
19
|
+
const entry = cache.get(key);
|
|
20
|
+
if (entry) bytes -= entry.bytes;
|
|
21
|
+
cache.delete(key);
|
|
22
|
+
};
|
|
23
|
+
const expire = () => {
|
|
24
|
+
if (expiryTimer) clearTimeout(expiryTimer);
|
|
25
|
+
expiryTimer = null;
|
|
26
|
+
for (const [key, entry] of cache) if (entry.expires <= now()) remove(key);
|
|
27
|
+
if (cache.size) {
|
|
28
|
+
const next = Math.min(...Array.from(cache.values(), (entry) => entry.expires));
|
|
29
|
+
expiryTimer = setTimeout(expire, Math.max(1, next - now()));
|
|
30
|
+
}
|
|
31
|
+
};
|
|
32
|
+
const clear = () => {
|
|
33
|
+
cache.clear();
|
|
34
|
+
bytes = 0;
|
|
35
|
+
if (expiryTimer) clearTimeout(expiryTimer);
|
|
36
|
+
expiryTimer = null;
|
|
37
|
+
};
|
|
38
|
+
const forget = (job) => {
|
|
39
|
+
jobs.delete(job);
|
|
40
|
+
if (inFlight.get(job.key) === job) inFlight.delete(job.key);
|
|
41
|
+
};
|
|
42
|
+
const cancel = (job) => {
|
|
43
|
+
var _a;
|
|
44
|
+
if (job.cancelled) return;
|
|
45
|
+
job.cancelled = true;
|
|
46
|
+
job.invalidated = true;
|
|
47
|
+
for (const consumer of job.consumers) consumer.resolve(null);
|
|
48
|
+
job.consumers.clear();
|
|
49
|
+
(_a = job.controller) == null ? void 0 : _a.abort();
|
|
50
|
+
const index = queue.indexOf(job);
|
|
51
|
+
if (index !== -1) queue.splice(index, 1);
|
|
52
|
+
if (!job.started) forget(job);
|
|
53
|
+
counters.cancelled++;
|
|
54
|
+
};
|
|
55
|
+
const syncContext = (current) => {
|
|
56
|
+
if (context && context.principal !== current.principal) {
|
|
57
|
+
clear();
|
|
58
|
+
for (const job of [...jobs]) cancel(job);
|
|
59
|
+
} else if (context && context.credential !== current.credential) {
|
|
60
|
+
clear();
|
|
61
|
+
}
|
|
62
|
+
context = current;
|
|
63
|
+
};
|
|
64
|
+
const invalidate = (reportId) => {
|
|
65
|
+
for (const [key, entry] of cache) if (!reportId || entry.reportId === reportId) remove(key);
|
|
66
|
+
for (const job of jobs) if (!reportId || job.reportId === reportId) job.invalidated = true;
|
|
67
|
+
expire();
|
|
68
|
+
};
|
|
69
|
+
const pump = () => {
|
|
70
|
+
if (disposed) return;
|
|
71
|
+
while (active < concurrency && queue.length) {
|
|
72
|
+
const job = queue.shift();
|
|
73
|
+
syncContext(job.getContext());
|
|
74
|
+
if (job.cancelled || !job.consumers.size) {
|
|
75
|
+
forget(job);
|
|
76
|
+
continue;
|
|
77
|
+
}
|
|
78
|
+
job.started = true;
|
|
79
|
+
job.credential = context.credential;
|
|
80
|
+
job.version = job.getVersion();
|
|
81
|
+
job.cacheKey = encode([job.componentId, job.params, job.version]);
|
|
82
|
+
job.controller = new AbortController();
|
|
83
|
+
active++;
|
|
84
|
+
counters.peakActive = Math.max(counters.peakActive, active);
|
|
85
|
+
Promise.resolve().then(() => job.execute(job.controller.signal)).then((data) => {
|
|
86
|
+
if (disposed || job.cancelled) return;
|
|
87
|
+
const current = job.getContext();
|
|
88
|
+
syncContext(current);
|
|
89
|
+
if (job.cancelled) return;
|
|
90
|
+
job.settled = true;
|
|
91
|
+
const encoded = JSON.stringify(data);
|
|
92
|
+
const size = encoded ? (encoded.length + job.cacheKey.length) * 2 : 0;
|
|
93
|
+
if (job.cacheable && !job.invalidated && current.credential === job.credential && job.getVersion() === job.version && encoded && size <= maxBytes / 2) {
|
|
94
|
+
remove(job.cacheKey);
|
|
95
|
+
cache.set(job.cacheKey, { encoded, bytes: size, expires: now() + ttl, reportId: job.reportId });
|
|
96
|
+
bytes += size;
|
|
97
|
+
for (const cachedKey of cache.keys()) {
|
|
98
|
+
if (cache.size <= maxEntries && bytes <= maxBytes) break;
|
|
99
|
+
remove(cachedKey);
|
|
100
|
+
}
|
|
101
|
+
expire();
|
|
102
|
+
}
|
|
103
|
+
for (const consumer of job.consumers) consumer.resolve(encoded === void 0 ? data : JSON.parse(encoded));
|
|
104
|
+
}).catch((error) => {
|
|
105
|
+
job.settled = true;
|
|
106
|
+
if (!job.cancelled && !disposed) for (const consumer of job.consumers) consumer.reject(error);
|
|
107
|
+
}).finally(() => {
|
|
108
|
+
job.consumers.clear();
|
|
109
|
+
forget(job);
|
|
110
|
+
active--;
|
|
111
|
+
pump();
|
|
112
|
+
});
|
|
113
|
+
}
|
|
114
|
+
};
|
|
115
|
+
return {
|
|
116
|
+
request({ params, componentId = params.reportId, execute, getContext, getVersion = () => "", cacheable = false, force = false }) {
|
|
117
|
+
if (disposed) return { promise: Promise.resolve(null), cancel: () => {
|
|
118
|
+
} };
|
|
119
|
+
const current = getContext();
|
|
120
|
+
syncContext(current);
|
|
121
|
+
expire();
|
|
122
|
+
const version = getVersion();
|
|
123
|
+
const key = encode([componentId, params, version]);
|
|
124
|
+
const reportId = params.reportId;
|
|
125
|
+
if (force) invalidate(reportId);
|
|
126
|
+
const entry = cache.get(key);
|
|
127
|
+
if (cacheable && current.credential && !force && entry) {
|
|
128
|
+
counters.hits++;
|
|
129
|
+
cache.delete(key);
|
|
130
|
+
cache.set(key, entry);
|
|
131
|
+
return {
|
|
132
|
+
promise: Promise.resolve().then(() => {
|
|
133
|
+
const latest = getContext();
|
|
134
|
+
return latest.principal === current.principal && latest.credential === current.credential ? JSON.parse(entry.encoded) : null;
|
|
135
|
+
}),
|
|
136
|
+
cancel: () => {
|
|
137
|
+
}
|
|
138
|
+
};
|
|
139
|
+
}
|
|
140
|
+
let job = !force && inFlight.get(key);
|
|
141
|
+
if (job && !job.settled && !job.cancelled && !job.invalidated && job.credential === current.credential) counters.coalesced++;
|
|
142
|
+
else {
|
|
143
|
+
counters.misses++;
|
|
144
|
+
job = { key, componentId, params, reportId, execute, getContext, getVersion, version, credential: current.credential, cacheable: cacheable && !!current.credential, consumers: /* @__PURE__ */ new Set() };
|
|
145
|
+
jobs.add(job);
|
|
146
|
+
inFlight.set(key, job);
|
|
147
|
+
queue.push(job);
|
|
148
|
+
}
|
|
149
|
+
const consumer = {};
|
|
150
|
+
const promise = new Promise((resolve, reject) => Object.assign(consumer, { resolve, reject }));
|
|
151
|
+
job.consumers.add(consumer);
|
|
152
|
+
pump();
|
|
153
|
+
return {
|
|
154
|
+
promise,
|
|
155
|
+
cancel: () => {
|
|
156
|
+
if (!job.consumers.delete(consumer)) return;
|
|
157
|
+
consumer.resolve(null);
|
|
158
|
+
if (!job.consumers.size) cancel(job);
|
|
159
|
+
}
|
|
160
|
+
};
|
|
161
|
+
},
|
|
162
|
+
invalidate,
|
|
163
|
+
stats() {
|
|
164
|
+
return { ...counters, active, queued: queue.length, entries: cache.size, bytes, disposed };
|
|
165
|
+
},
|
|
166
|
+
dispose() {
|
|
167
|
+
disposed = true;
|
|
168
|
+
clear();
|
|
169
|
+
for (const job of [...jobs]) cancel(job);
|
|
170
|
+
context = null;
|
|
171
|
+
}
|
|
172
|
+
};
|
|
173
|
+
}
|
|
174
|
+
export {
|
|
175
|
+
createChartRequestBroker
|
|
176
|
+
};
|