@foxeltech/angular-ui 0.4.0 → 0.7.75
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/archivo/archivo-400-latin-ext.woff2 +0 -0
- package/archivo/archivo-400-latin.woff2 +0 -0
- package/archivo/archivo-400-vietnamese.woff2 +0 -0
- package/archivo/archivo-500-latin-ext.woff2 +0 -0
- package/archivo/archivo-500-latin.woff2 +0 -0
- package/archivo/archivo-500-vietnamese.woff2 +0 -0
- package/archivo/archivo-600-latin-ext.woff2 +0 -0
- package/archivo/archivo-600-latin.woff2 +0 -0
- package/archivo/archivo-600-vietnamese.woff2 +0 -0
- package/archivo/archivo-700-latin-ext.woff2 +0 -0
- package/archivo/archivo-700-latin.woff2 +0 -0
- package/archivo/archivo-700-vietnamese.woff2 +0 -0
- package/components.css +332 -30
- package/dialogs.css +2 -2
- package/familjen-grotesk/familjen-grotesk-400-latin-ext.woff2 +0 -0
- package/familjen-grotesk/familjen-grotesk-400-latin.woff2 +0 -0
- package/familjen-grotesk/familjen-grotesk-400-vietnamese.woff2 +0 -0
- package/familjen-grotesk/familjen-grotesk-500-latin-ext.woff2 +0 -0
- package/familjen-grotesk/familjen-grotesk-500-latin.woff2 +0 -0
- package/familjen-grotesk/familjen-grotesk-500-vietnamese.woff2 +0 -0
- package/familjen-grotesk/familjen-grotesk-600-latin-ext.woff2 +0 -0
- package/familjen-grotesk/familjen-grotesk-600-latin.woff2 +0 -0
- package/familjen-grotesk/familjen-grotesk-600-vietnamese.woff2 +0 -0
- package/familjen-grotesk/familjen-grotesk-700-latin-ext.woff2 +0 -0
- package/familjen-grotesk/familjen-grotesk-700-latin.woff2 +0 -0
- package/familjen-grotesk/familjen-grotesk-700-vietnamese.woff2 +0 -0
- package/fesm2022/foxeltech-angular-ui-chart.mjs +381 -40
- package/fesm2022/foxeltech-angular-ui-chart.mjs.map +1 -1
- package/fesm2022/foxeltech-angular-ui-flow.mjs +176 -3
- package/fesm2022/foxeltech-angular-ui-flow.mjs.map +1 -1
- package/fesm2022/foxeltech-angular-ui-rich-text.mjs +2 -2
- package/fesm2022/foxeltech-angular-ui-rich-text.mjs.map +1 -1
- package/fesm2022/foxeltech-angular-ui.mjs +1327 -151
- package/fesm2022/foxeltech-angular-ui.mjs.map +1 -1
- package/fonts.css +558 -133
- package/ibm-plex-mono/ibm-plex-mono-400-cyrillic-ext.woff2 +0 -0
- package/ibm-plex-mono/ibm-plex-mono-400-cyrillic.woff2 +0 -0
- package/ibm-plex-mono/ibm-plex-mono-400-latin-ext.woff2 +0 -0
- package/ibm-plex-mono/ibm-plex-mono-400-latin.woff2 +0 -0
- package/ibm-plex-mono/ibm-plex-mono-400-vietnamese.woff2 +0 -0
- package/ibm-plex-mono/ibm-plex-mono-500-cyrillic-ext.woff2 +0 -0
- package/ibm-plex-mono/ibm-plex-mono-500-cyrillic.woff2 +0 -0
- package/ibm-plex-mono/ibm-plex-mono-500-latin-ext.woff2 +0 -0
- package/ibm-plex-mono/ibm-plex-mono-500-latin.woff2 +0 -0
- package/ibm-plex-mono/ibm-plex-mono-500-vietnamese.woff2 +0 -0
- package/ibm-plex-mono/ibm-plex-mono-600-cyrillic-ext.woff2 +0 -0
- package/ibm-plex-mono/ibm-plex-mono-600-cyrillic.woff2 +0 -0
- package/ibm-plex-mono/ibm-plex-mono-600-latin-ext.woff2 +0 -0
- package/ibm-plex-mono/ibm-plex-mono-600-latin.woff2 +0 -0
- package/ibm-plex-mono/ibm-plex-mono-600-vietnamese.woff2 +0 -0
- package/jetbrains-mono/jetbrains-mono-400-cyrillic-ext.woff2 +0 -0
- package/jetbrains-mono/jetbrains-mono-400-cyrillic.woff2 +0 -0
- package/jetbrains-mono/jetbrains-mono-400-greek.woff2 +0 -0
- package/jetbrains-mono/jetbrains-mono-400-latin-ext.woff2 +0 -0
- package/jetbrains-mono/jetbrains-mono-400-latin.woff2 +0 -0
- package/jetbrains-mono/jetbrains-mono-400-vietnamese.woff2 +0 -0
- package/jetbrains-mono/jetbrains-mono-500-cyrillic-ext.woff2 +0 -0
- package/jetbrains-mono/jetbrains-mono-500-cyrillic.woff2 +0 -0
- package/jetbrains-mono/jetbrains-mono-500-greek.woff2 +0 -0
- package/jetbrains-mono/jetbrains-mono-500-latin-ext.woff2 +0 -0
- package/jetbrains-mono/jetbrains-mono-500-latin.woff2 +0 -0
- package/jetbrains-mono/jetbrains-mono-500-vietnamese.woff2 +0 -0
- package/jetbrains-mono/jetbrains-mono-600-cyrillic-ext.woff2 +0 -0
- package/jetbrains-mono/jetbrains-mono-600-cyrillic.woff2 +0 -0
- package/jetbrains-mono/jetbrains-mono-600-greek.woff2 +0 -0
- package/jetbrains-mono/jetbrains-mono-600-latin-ext.woff2 +0 -0
- package/jetbrains-mono/jetbrains-mono-600-latin.woff2 +0 -0
- package/jetbrains-mono/jetbrains-mono-600-vietnamese.woff2 +0 -0
- package/jetbrains-mono/jetbrains-mono-700-cyrillic-ext.woff2 +0 -0
- package/jetbrains-mono/jetbrains-mono-700-cyrillic.woff2 +0 -0
- package/jetbrains-mono/jetbrains-mono-700-greek.woff2 +0 -0
- package/jetbrains-mono/jetbrains-mono-700-latin-ext.woff2 +0 -0
- package/jetbrains-mono/jetbrains-mono-700-latin.woff2 +0 -0
- package/jetbrains-mono/jetbrains-mono-700-vietnamese.woff2 +0 -0
- package/newsreader/newsreader-400-latin-ext.woff2 +0 -0
- package/newsreader/newsreader-400-latin.woff2 +0 -0
- package/newsreader/newsreader-400-vietnamese.woff2 +0 -0
- package/newsreader/newsreader-500-latin-ext.woff2 +0 -0
- package/newsreader/newsreader-500-latin.woff2 +0 -0
- package/newsreader/newsreader-500-vietnamese.woff2 +0 -0
- package/newsreader/newsreader-600-latin-ext.woff2 +0 -0
- package/newsreader/newsreader-600-latin.woff2 +0 -0
- package/newsreader/newsreader-600-vietnamese.woff2 +0 -0
- package/package.json +5 -2
- package/source-serif-4/source-serif-4-400-cyrillic-ext.woff2 +0 -0
- package/source-serif-4/source-serif-4-400-cyrillic.woff2 +0 -0
- package/source-serif-4/source-serif-4-400-greek.woff2 +0 -0
- package/source-serif-4/source-serif-4-400-latin-ext.woff2 +0 -0
- package/source-serif-4/source-serif-4-400-latin.woff2 +0 -0
- package/source-serif-4/source-serif-4-400-vietnamese.woff2 +0 -0
- package/source-serif-4/source-serif-4-600-cyrillic-ext.woff2 +0 -0
- package/source-serif-4/source-serif-4-600-cyrillic.woff2 +0 -0
- package/source-serif-4/source-serif-4-600-greek.woff2 +0 -0
- package/source-serif-4/source-serif-4-600-latin-ext.woff2 +0 -0
- package/source-serif-4/source-serif-4-600-latin.woff2 +0 -0
- package/source-serif-4/source-serif-4-600-vietnamese.woff2 +0 -0
- package/src/lib/styles/components.css +332 -30
- package/src/lib/styles/dialogs.css +2 -2
- package/src/lib/styles/fonts.css +558 -133
- package/src/lib/styles/tailwind.css +13 -1
- package/src/lib/styles/theme.css +642 -69
- package/src/lib/styles/tokens.css +81 -19
- package/src/lib/styles/utilities.css +16 -1
- package/src/lib/ui/components/bar-list/bar-list.component.html +29 -0
- package/src/lib/ui/components/button/button.component.html +9 -5
- package/src/lib/ui/components/checkbox/checkbox.component.html +1 -1
- package/src/lib/ui/components/code-block/code-block.component.html +8 -0
- package/src/lib/ui/components/drawer/drawer.component.html +35 -12
- package/src/lib/ui/components/drawer/drawer.component.scss +106 -18
- package/src/lib/ui/components/facets-rail/facets-rail.component.html +37 -0
- package/src/lib/ui/components/filter-pills/filter-pills.component.html +16 -0
- package/src/lib/ui/components/kanban-board/kanban-board.component.scss +9 -9
- package/src/lib/ui/components/list-row/list-row.component.html +20 -0
- package/src/lib/ui/components/master-detail/master-detail.component.css +79 -5
- package/src/lib/ui/components/master-detail/master-detail.component.html +57 -7
- package/src/lib/ui/components/pending-badge/pending-badge.component.html +15 -0
- package/src/lib/ui/components/pipeline-stepper/pipeline-stepper.component.html +45 -0
- package/src/lib/ui/components/project-card/project-card.component.html +102 -0
- package/src/lib/ui/components/radio-button/radio-button.component.html +1 -1
- package/src/lib/ui/components/radio-button-toggle/radio-button-toggle.component.html +1 -1
- package/src/lib/ui/components/route-skeleton/route-skeleton.component.css +38 -0
- package/src/lib/ui/components/route-skeleton/route-skeleton.component.html +184 -0
- package/src/lib/ui/components/sample-frame/sample-frame.component.html +18 -0
- package/src/lib/ui/components/score-badge/score-badge.component.html +7 -0
- package/src/lib/ui/components/screenshot-frame/screenshot-frame.component.html +40 -0
- package/src/lib/ui/components/section-card/section-card.component.html +15 -6
- package/src/lib/ui/components/segmented/segmented.component.html +42 -0
- package/src/lib/ui/components/segmented/segmented.component.scss +12 -0
- package/src/lib/ui/components/skeleton-dashboard/skeleton-dashboard.component.html +24 -0
- package/src/lib/ui/components/skeleton-detail/skeleton-detail.component.html +8 -8
- package/src/lib/ui/components/skeleton-facets/skeleton-facets.component.html +15 -0
- package/src/lib/ui/components/skeleton-frame/skeleton-frame.component.html +59 -0
- package/src/lib/ui/components/skeleton-list/skeleton-list.component.html +6 -6
- package/src/lib/ui/components/slider/slider.component.html +2 -2
- package/src/lib/ui/components/sparkbar/sparkbar.component.html +14 -0
- package/src/lib/ui/components/stage-dots/stage-dots.component.html +13 -0
- package/src/lib/ui/components/stat-cards/stat-cards.component.html +44 -11
- package/src/lib/ui/components/status-dot/status-dot.component.html +13 -0
- package/src/lib/ui/components/switch/switch.component.html +1 -1
- package/src/lib/ui/components/tab-group/tab-group.component.html +37 -8
- package/src/lib/ui/components/tab-group/tab-group.component.scss +41 -1
- package/src/lib/ui/components/tag/tag.component.html +2 -2
- package/src/lib/ui/components/theme-picker/theme-picker.component.html +44 -0
- package/src/lib/ui/components/toast/toast.component.html +6 -6
- package/src/lib/ui/components/tree-diagram/tree-diagram.component.html +3 -3
- package/tailwind.css +13 -1
- package/theme.css +642 -69
- package/tokens.css +81 -19
- package/types/foxeltech-angular-ui-chart.d.ts +90 -3
- package/types/foxeltech-angular-ui-flow.d.ts +83 -1
- package/types/foxeltech-angular-ui.d.ts +909 -48
- package/utilities.css +16 -1
- package/BeVietnamPro/BeVietnamPro-Bold.woff2 +0 -0
- package/BeVietnamPro/BeVietnamPro-ExtraLight.woff2 +0 -0
- package/BeVietnamPro/BeVietnamPro-Light.woff2 +0 -0
- package/BeVietnamPro/BeVietnamPro-Medium.woff2 +0 -0
- package/BeVietnamPro/BeVietnamPro-Regular.woff2 +0 -0
- package/BeVietnamPro/BeVietnamPro-SemiBold.woff2 +0 -0
- package/BeVietnamPro/BeVietnamPro-Thin.woff2 +0 -0
- package/SpaceMono-Regular.woff2 +0 -0
- package/dm-sans/DMSans-Bold.woff2 +0 -0
- package/dm-sans/DMSans-Light.woff2 +0 -0
- package/dm-sans/DMSans-Medium.woff2 +0 -0
- package/dm-sans/DMSans-Regular.woff2 +0 -0
- package/dm-sans/DMSans-SemiBold.woff2 +0 -0
- package/dm-sans/DMSans-Thin.woff2 +0 -0
- package/orbitron/Orbitron-Bold.woff2 +0 -0
- package/orbitron/Orbitron-Medium.woff2 +0 -0
- package/orbitron/Orbitron-Regular.woff2 +0 -0
- package/orbitron/Orbitron-SemiBold.woff2 +0 -0
- package/roboto/Roboto-Black.woff2 +0 -0
- package/roboto/Roboto-BlackItalic.woff2 +0 -0
- package/roboto/Roboto-Bold.woff2 +0 -0
- package/roboto/Roboto-BoldCondensed.woff2 +0 -0
- package/roboto/Roboto-BoldCondensedItalic.woff2 +0 -0
- package/roboto/Roboto-BoldItalic.woff2 +0 -0
- package/roboto/Roboto-Condensed.woff2 +0 -0
- package/roboto/Roboto-CondensedItalic.woff2 +0 -0
- package/roboto/Roboto-Italic.woff2 +0 -0
- package/roboto/Roboto-Light.woff2 +0 -0
- package/roboto/Roboto-LightItalic.woff2 +0 -0
- package/roboto/Roboto-Medium.woff2 +0 -0
- package/roboto/Roboto-MediumItalic.woff2 +0 -0
- package/roboto/Roboto-Regular.woff2 +0 -0
- package/roboto/Roboto-Thin.woff2 +0 -0
- package/roboto/Roboto-ThinItalic.woff2 +0 -0
- package/src/lib/ui/components/skeleton-table-loading/skeleton-table-loading.component.html +0 -21
- package/src/lib/ui/components/skeleton-table-loading/skeleton-table-loading.component.scss +0 -0
- package/src/lib/ui/components/tab-component/tab.component.html +0 -1
|
@@ -17,8 +17,14 @@ class ChartComponent {
|
|
|
17
17
|
pieLegendAlign = input('10%', ...(ngDevMode ? [{ debugName: "pieLegendAlign" }] : /* istanbul ignore next */ []));
|
|
18
18
|
pieLegendPosition = input('middle', ...(ngDevMode ? [{ debugName: "pieLegendPosition" }] : /* istanbul ignore next */ []));
|
|
19
19
|
showPieLabel = input(true, ...(ngDevMode ? [{ debugName: "showPieLabel" }] : /* istanbul ignore next */ []));
|
|
20
|
+
/** Hide the built-in legend (panels that render their own legend beside the
|
|
21
|
+
donut); the donut then centres in the container. */
|
|
22
|
+
showPieLegend = input(true, ...(ngDevMode ? [{ debugName: "showPieLegend" }] : /* istanbul ignore next */ []));
|
|
23
|
+
/** Caption under the donut's centre total (design: "42 / confirmed"). */
|
|
24
|
+
pieCenterLabel = input('', ...(ngDevMode ? [{ debugName: "pieCenterLabel" }] : /* istanbul ignore next */ []));
|
|
20
25
|
mobileWidth = input(350, ...(ngDevMode ? [{ debugName: "mobileWidth" }] : /* istanbul ignore next */ []));
|
|
21
|
-
|
|
26
|
+
/** Override font for canvas text; empty = follow the theme's --og-font-figure. */
|
|
27
|
+
labelFontFamily = input('', ...(ngDevMode ? [{ debugName: "labelFontFamily" }] : /* istanbul ignore next */ []));
|
|
22
28
|
emptyText = input('No data available', ...(ngDevMode ? [{ debugName: "emptyText" }] : /* istanbul ignore next */ []));
|
|
23
29
|
/* ================= OUTPUTS ================= */
|
|
24
30
|
chartClick = output();
|
|
@@ -26,11 +32,38 @@ class ChartComponent {
|
|
|
26
32
|
/* ================= INTERNAL ================= */
|
|
27
33
|
chartOptions = {};
|
|
28
34
|
isEmpty = false;
|
|
35
|
+
/**
|
|
36
|
+
* ⚠️ Cổng cho `<div echarts>` trong template. Trước đây div render NGAY với
|
|
37
|
+
* `chartOptions = {}`, nên NgxEchartsDirective khởi tạo chart bằng options
|
|
38
|
+
* RỖNG; options thật gán vào giữa lúc `await createChart()` của lần đó ⇒
|
|
39
|
+
* onOptionsChange() thấy `chart == null` và createChart() LẦN HAI trên cùng
|
|
40
|
+
* DOM node ⇒ "[ECharts] There is a chart instance already initialized on the
|
|
41
|
+
* dom.". Chỉ dựng directive khi đã có options thật ⇒ đúng một lần init.
|
|
42
|
+
*/
|
|
43
|
+
hasOptions = false;
|
|
29
44
|
labelFontSize = 12;
|
|
30
45
|
chartInstance;
|
|
31
46
|
resizeObserver;
|
|
32
47
|
intersectionObserver;
|
|
33
48
|
canInit = false;
|
|
49
|
+
destroyed = false;
|
|
50
|
+
/**
|
|
51
|
+
* ⚠️ Đang chờ init đầu tiên của directive hoàn tất?
|
|
52
|
+
*
|
|
53
|
+
* NgxEchartsDirective đăng ký `changeFilter.notFirstAndEmpty('options', …)`:
|
|
54
|
+
* lần gán `options` ĐẦU bị bỏ qua (ngAfterViewInit lo), lần gán THỨ HAI gọi
|
|
55
|
+
* onOptionsChange(). Mà onOptionsChange() chỉ createChart() khi `this.chart`
|
|
56
|
+
* còn null — và nó null suốt thời gian `await createChart()` của lần đầu.
|
|
57
|
+
* Nên nếu ta gán `chartOptions` (object mới) trong khoảng await đó thì
|
|
58
|
+
* init() chạy HAI LẦN trên cùng DOM node ⇒
|
|
59
|
+
* "[ECharts] There is a chart instance already initialized on the dom."
|
|
60
|
+
*
|
|
61
|
+
* Cách chặn: trong khoảng chờ, giữ options ở `deferredOptions` chứ không đụng
|
|
62
|
+
* vào property đang bind; init xong thì áp một lần qua instance.
|
|
63
|
+
*/
|
|
64
|
+
awaitingFirstInit = false;
|
|
65
|
+
deferredOptions;
|
|
66
|
+
pendingFrames = new Set();
|
|
34
67
|
isMobile = false;
|
|
35
68
|
containerWidth = 0;
|
|
36
69
|
constructor(ref, cdr) {
|
|
@@ -41,7 +74,7 @@ class ChartComponent {
|
|
|
41
74
|
ngAfterViewInit() {
|
|
42
75
|
// Only initialize when visible (tab-safe)
|
|
43
76
|
this.intersectionObserver = new IntersectionObserver(([entry]) => {
|
|
44
|
-
if (entry.isIntersecting && !this.canInit) {
|
|
77
|
+
if (entry.isIntersecting && !this.canInit && !this.destroyed) {
|
|
45
78
|
this.canInit = true;
|
|
46
79
|
this.updateResponsiveState();
|
|
47
80
|
this.rebuildChart();
|
|
@@ -51,69 +84,215 @@ class ChartComponent {
|
|
|
51
84
|
this.intersectionObserver.observe(this.ref.nativeElement);
|
|
52
85
|
// Resize-safe (tabs, flex, async layout)
|
|
53
86
|
this.resizeObserver = new ResizeObserver(() => {
|
|
54
|
-
if (!this.canInit)
|
|
87
|
+
if (!this.canInit || this.destroyed)
|
|
55
88
|
return;
|
|
56
|
-
|
|
89
|
+
// forceMediaRecalc tự đo lại và chỉ dựng lại khi kích thước đổi.
|
|
57
90
|
this.forceMediaRecalc();
|
|
58
91
|
});
|
|
59
92
|
this.resizeObserver.observe(this.ref.nativeElement);
|
|
60
93
|
}
|
|
61
94
|
ngOnChanges() {
|
|
62
|
-
if (this.canInit) {
|
|
95
|
+
if (this.canInit && !this.destroyed) {
|
|
63
96
|
this.updateResponsiveState();
|
|
64
97
|
this.rebuildChart();
|
|
65
98
|
}
|
|
66
99
|
}
|
|
67
100
|
ngOnDestroy() {
|
|
101
|
+
// NgxEchartsDirective tự dispose instance của nó, nên KHÔNG dispose ở đây
|
|
102
|
+
// (sẽ dispose trùng). Việc phải làm là ngắt mọi đường còn chạm được vào
|
|
103
|
+
// instance sau khi nó đã bị dispose: callback rAF đang chờ + tham chiếu
|
|
104
|
+
// `chartInstance` mà forceMediaRecalc/rebuildChart vẫn dùng.
|
|
105
|
+
this.destroyed = true;
|
|
106
|
+
for (const id of this.pendingFrames)
|
|
107
|
+
cancelAnimationFrame(id);
|
|
108
|
+
this.pendingFrames.clear();
|
|
68
109
|
this.resizeObserver?.disconnect();
|
|
69
110
|
this.intersectionObserver?.disconnect();
|
|
111
|
+
this.resizeObserver = undefined;
|
|
112
|
+
this.intersectionObserver = undefined;
|
|
113
|
+
this.chartInstance = undefined;
|
|
114
|
+
this.awaitingFirstInit = false;
|
|
115
|
+
this.deferredOptions = undefined;
|
|
116
|
+
this.sweepOrphanInstance();
|
|
117
|
+
}
|
|
118
|
+
/**
|
|
119
|
+
* ⚠️ RACE TRONG NGX-ECHARTS v20 — nguồn của
|
|
120
|
+
* "[ECharts] There is a chart instance already initialized on the dom."
|
|
121
|
+
*
|
|
122
|
+
* NgxEchartsDirective.onOptionsChange() làm:
|
|
123
|
+
* this.chart = await this.createChart(); // ← destroy có thể rơi vào đây
|
|
124
|
+
* this.chart$.next(this.chart);
|
|
125
|
+
* this.chartInit.emit(this.chart);
|
|
126
|
+
*
|
|
127
|
+
* Destroy đúng lúc đang await thì ngOnDestroy → dispose() thấy `this.chart == null`
|
|
128
|
+
* nên KHÔNG dispose gì. Sau đó await resolve, gán một instance ECharts còn sống cho
|
|
129
|
+
* directive đã chết → instance MỒ CÔI, vẫn nằm trong registry của echarts gắn với
|
|
130
|
+
* DOM node đó. Angular tái dùng node ⇒ init() lần sau báo "already initialized".
|
|
131
|
+
*
|
|
132
|
+
* Không thể bắt qua (chartInit): Angular v20 TỪ CHỐI emit trên OutputRef đã destroy
|
|
133
|
+
* (chỉ log NG0953, listener không hề được gọi). Nên đường duy nhất là hỏi thẳng
|
|
134
|
+
* echarts xem node còn instance nào không, ở các macrotask SAU destroy.
|
|
135
|
+
*/
|
|
136
|
+
sweepOrphanInstance() {
|
|
137
|
+
const host = this.ref.nativeElement.querySelector('.fx-chart');
|
|
138
|
+
if (!host)
|
|
139
|
+
return;
|
|
140
|
+
// Chỉ nạp echarts nếu nó đã ở trong module cache — không tự kéo bundle về
|
|
141
|
+
// chỉ để đi dọn.
|
|
142
|
+
let attempt = 0;
|
|
143
|
+
const sweep = () => {
|
|
144
|
+
import('echarts').then(({ getInstanceByDom }) => {
|
|
145
|
+
const orphan = getInstanceByDom(host);
|
|
146
|
+
if (orphan) {
|
|
147
|
+
if (!orphan.isDisposed())
|
|
148
|
+
orphan.dispose();
|
|
149
|
+
return;
|
|
150
|
+
}
|
|
151
|
+
// createChart() phải chờ dynamic import nên instance mồ côi có thể xuất
|
|
152
|
+
// hiện muộn hơn một macrotask; thử lại vài nhịp rồi dừng.
|
|
153
|
+
if (++attempt < 5)
|
|
154
|
+
setTimeout(sweep, 50 * attempt);
|
|
155
|
+
});
|
|
156
|
+
};
|
|
157
|
+
setTimeout(sweep);
|
|
158
|
+
}
|
|
159
|
+
/** rAF có thể huỷ được khi destroy — tránh chạm instance đã dispose. */
|
|
160
|
+
schedule(fn) {
|
|
161
|
+
if (this.destroyed)
|
|
162
|
+
return;
|
|
163
|
+
const id = requestAnimationFrame(() => {
|
|
164
|
+
this.pendingFrames.delete(id);
|
|
165
|
+
if (this.destroyed)
|
|
166
|
+
return;
|
|
167
|
+
fn();
|
|
168
|
+
});
|
|
169
|
+
this.pendingFrames.add(id);
|
|
170
|
+
}
|
|
171
|
+
/** Instance còn dùng được? (đã dispose thì mọi API echarts đều ném lỗi) */
|
|
172
|
+
get liveChart() {
|
|
173
|
+
const chart = this.chartInstance;
|
|
174
|
+
if (!chart || this.destroyed || chart.isDisposed())
|
|
175
|
+
return undefined;
|
|
176
|
+
return chart;
|
|
70
177
|
}
|
|
71
178
|
onThemeChange() {
|
|
72
|
-
if (!this.canInit)
|
|
179
|
+
if (!this.canInit || this.destroyed)
|
|
73
180
|
return;
|
|
74
|
-
|
|
181
|
+
this.schedule(() => this.rebuildChart());
|
|
75
182
|
}
|
|
76
183
|
/* ================= EVENTS ================= */
|
|
77
184
|
onChartClick(event) {
|
|
78
185
|
this.chartClick.emit(event);
|
|
79
186
|
}
|
|
80
|
-
|
|
187
|
+
// ⚠️ Phải để `any`: NgxEchartsDirective emit `EChartsType` từ
|
|
188
|
+
// 'echarts/types/dist/shared', TS coi nó KHÔNG tương thích với `ECharts`
|
|
189
|
+
// của 'echarts' (private `_ssr` khai báo riêng) → template typecheck fail.
|
|
190
|
+
onChartInit(raw) {
|
|
191
|
+
const chart = raw;
|
|
192
|
+
// Phòng thân: bình thường Angular đã chặn emit sau destroy (NG0953) nên
|
|
193
|
+
// nhánh này không tới; nhưng nếu tới thì tuyệt đối không được gán lại
|
|
194
|
+
// `chartInstance` sau ngOnDestroy. Việc thu dọn instance mồ côi do
|
|
195
|
+
// sweepOrphanInstance() lo — xem ghi chú ở đó.
|
|
196
|
+
if (this.destroyed) {
|
|
197
|
+
if (!chart.isDisposed())
|
|
198
|
+
chart.dispose();
|
|
199
|
+
return;
|
|
200
|
+
}
|
|
81
201
|
this.chartInstance = chart;
|
|
202
|
+
this.awaitingFirstInit = false;
|
|
203
|
+
if (this.deferredOptions) {
|
|
204
|
+
const options = this.deferredOptions;
|
|
205
|
+
this.deferredOptions = undefined;
|
|
206
|
+
this.chartOptions = options;
|
|
207
|
+
if (!chart.isDisposed()) {
|
|
208
|
+
chart.clear();
|
|
209
|
+
chart.setOption(options, true);
|
|
210
|
+
}
|
|
211
|
+
}
|
|
82
212
|
this.chartInit.emit(chart);
|
|
83
|
-
|
|
213
|
+
this.schedule(() => this.forceMediaRecalc());
|
|
84
214
|
}
|
|
85
215
|
/* ================= CORE FIX ================= */
|
|
216
|
+
/**
|
|
217
|
+
* @returns true nếu trạng thái responsive THỰC SỰ đổi (nên dựng lại options).
|
|
218
|
+
*
|
|
219
|
+
* Trả về boolean là điều then chốt cho animation lúc chart xuất hiện — xem
|
|
220
|
+
* ghi chú ở `forceMediaRecalc`.
|
|
221
|
+
*/
|
|
86
222
|
updateResponsiveState() {
|
|
87
223
|
const { width } = this.ref.nativeElement.getBoundingClientRect();
|
|
88
|
-
if (width
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
224
|
+
if (width <= 0)
|
|
225
|
+
return false;
|
|
226
|
+
const isMobile = width < this.mobileWidth();
|
|
227
|
+
const changed = width !== this.containerWidth || isMobile !== this.isMobile;
|
|
228
|
+
this.containerWidth = width;
|
|
229
|
+
this.isMobile = isMobile;
|
|
230
|
+
return changed;
|
|
92
231
|
}
|
|
232
|
+
/**
|
|
233
|
+
* ⚠️ CHỈ dựng lại options khi kích thước thật sự đổi — nếu không thì
|
|
234
|
+
* **animation lúc chart xuất hiện bị giết**.
|
|
235
|
+
*
|
|
236
|
+
* Đường đi: `onChartInit` → schedule(forceMediaRecalc) → chạy ở frame KẾ
|
|
237
|
+
* TIẾP. Nếu chỗ này vô điều kiện `rebuildChart()` (clear + setOption notMerge)
|
|
238
|
+
* thì nó xoá sạch và vẽ lại từ đầu ~16ms sau lần vẽ đầu, trong khi animation
|
|
239
|
+
* xuất hiện dài ~1000ms ⇒ người dùng thấy chart hiện ra đứng im.
|
|
240
|
+
*
|
|
241
|
+
* Đo được: lần xuất hiện đầu 1/12 frame khác nhau (đứng yên) trong khi vẽ lại
|
|
242
|
+
* bằng `theme-changed` cho 9/12 → animation của ECharts vẫn bật, chỉ lần đầu
|
|
243
|
+
* bị cắt.
|
|
244
|
+
*
|
|
245
|
+
* Trước Pha 0 lỗi này bị che: lần init đầu dùng `chartOptions = {}` nên
|
|
246
|
+
* animation xuất hiện xảy ra ở lần cập nhật SAU, tức sau khi forceMediaRecalc
|
|
247
|
+
* đã chạy xong. Cổng `hasOptions` đưa options thật về ngay lần init nên lộ ra.
|
|
248
|
+
*
|
|
249
|
+
* `resize()` thì luôn gọi được — nó không cắt animation.
|
|
250
|
+
*/
|
|
93
251
|
forceMediaRecalc(retry = 0) {
|
|
94
|
-
if (!this.
|
|
252
|
+
if (!this.liveChart)
|
|
95
253
|
return;
|
|
96
254
|
const { width, height } = this.ref.nativeElement.getBoundingClientRect();
|
|
97
255
|
if ((width === 0 || height === 0) && retry < 10) {
|
|
98
|
-
|
|
256
|
+
this.schedule(() => this.forceMediaRecalc(retry + 1));
|
|
99
257
|
return;
|
|
100
258
|
}
|
|
259
|
+
// ⚠️ `resize()` cũng phải CÓ ĐIỀU KIỆN. ECharts nhảy thẳng tới trạng thái
|
|
260
|
+
// cuối khi resize, nên gọi nó ở frame ngay sau init sẽ CẮT animation lúc
|
|
261
|
+
// chart xuất hiện — đo được: 3 lần đổi khung trong 200ms rồi đứng im, dù
|
|
262
|
+
// đã đặt animationDuration 3000ms.
|
|
263
|
+
// Không cần resize ở lần đầu: ECharts tự lấy kích thước khi init, và vòng
|
|
264
|
+
// retry ở trên đã lo ca width/height = 0.
|
|
265
|
+
if (!this.updateResponsiveState())
|
|
266
|
+
return;
|
|
101
267
|
this.rebuildChart();
|
|
102
|
-
this.
|
|
268
|
+
this.liveChart?.resize();
|
|
103
269
|
}
|
|
104
270
|
rebuildChart() {
|
|
271
|
+
if (this.destroyed)
|
|
272
|
+
return;
|
|
105
273
|
this.isEmpty = this.checkEmpty();
|
|
106
274
|
if (this.isEmpty) {
|
|
107
|
-
|
|
108
|
-
this.chartInstance.clear();
|
|
109
|
-
}
|
|
275
|
+
this.liveChart?.clear();
|
|
110
276
|
return;
|
|
111
277
|
}
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
278
|
+
const options = this.buildOptions();
|
|
279
|
+
const chart = this.liveChart;
|
|
280
|
+
if (!chart) {
|
|
281
|
+
if (this.awaitingFirstInit) {
|
|
282
|
+
// Đang trong khoảng await của lần init đầu — KHÔNG gán vào property
|
|
283
|
+
// đang bind, xem ghi chú ở `awaitingFirstInit`.
|
|
284
|
+
this.deferredOptions = options;
|
|
285
|
+
return;
|
|
286
|
+
}
|
|
287
|
+
this.awaitingFirstInit = true;
|
|
288
|
+
this.chartOptions = options;
|
|
289
|
+
this.hasOptions = true;
|
|
290
|
+
return;
|
|
116
291
|
}
|
|
292
|
+
this.chartOptions = options;
|
|
293
|
+
this.hasOptions = true;
|
|
294
|
+
chart.clear();
|
|
295
|
+
chart.setOption(options, true);
|
|
117
296
|
}
|
|
118
297
|
checkEmpty() {
|
|
119
298
|
const type = this.detectType();
|
|
@@ -123,6 +302,14 @@ class ChartComponent {
|
|
|
123
302
|
const values = data?.values;
|
|
124
303
|
return !categories?.length || !values?.length;
|
|
125
304
|
}
|
|
305
|
+
if (type === 'line') {
|
|
306
|
+
const data = this.data();
|
|
307
|
+
return !data?.categories?.length || !data?.values?.length;
|
|
308
|
+
}
|
|
309
|
+
if (type === 'area-trend') {
|
|
310
|
+
const data = this.data();
|
|
311
|
+
return !data?.categories?.length || !data?.current?.length;
|
|
312
|
+
}
|
|
126
313
|
if (type === 'pie') {
|
|
127
314
|
const data = this.data();
|
|
128
315
|
if (!Array.isArray(data) || data.length === 0)
|
|
@@ -146,7 +333,7 @@ class ChartComponent {
|
|
|
146
333
|
width: 80,
|
|
147
334
|
overflow: 'truncate',
|
|
148
335
|
ellipsis: '…',
|
|
149
|
-
fontFamily: this.
|
|
336
|
+
fontFamily: this.resolvedFontFamily(),
|
|
150
337
|
fontSize: this.labelFontSize,
|
|
151
338
|
color: FxUtils.convertColorFromVariable('--text-secondary'),
|
|
152
339
|
},
|
|
@@ -159,7 +346,7 @@ class ChartComponent {
|
|
|
159
346
|
yAxis: {
|
|
160
347
|
type: 'value',
|
|
161
348
|
axisLabel: {
|
|
162
|
-
fontFamily: this.
|
|
349
|
+
fontFamily: this.resolvedFontFamily(),
|
|
163
350
|
fontSize: this.labelFontSize,
|
|
164
351
|
color: FxUtils.convertColorFromVariable('--text-secondary'),
|
|
165
352
|
},
|
|
@@ -185,7 +372,7 @@ class ChartComponent {
|
|
|
185
372
|
label: {
|
|
186
373
|
show: true,
|
|
187
374
|
position: 'top',
|
|
188
|
-
fontFamily: this.
|
|
375
|
+
fontFamily: this.resolvedFontFamily(),
|
|
189
376
|
fontSize: this.labelFontSize,
|
|
190
377
|
color: FxUtils.convertColorFromVariable('--text-primary'),
|
|
191
378
|
},
|
|
@@ -199,6 +386,7 @@ class ChartComponent {
|
|
|
199
386
|
color: colors,
|
|
200
387
|
tooltip: { trigger: 'item' },
|
|
201
388
|
legend: {
|
|
389
|
+
show: this.showPieLegend(),
|
|
202
390
|
type: 'scroll',
|
|
203
391
|
orient: layout.legendOrient,
|
|
204
392
|
icon: 'circle',
|
|
@@ -213,7 +401,7 @@ class ChartComponent {
|
|
|
213
401
|
return item ? `${name}: ${item.value}` : name;
|
|
214
402
|
},
|
|
215
403
|
textStyle: {
|
|
216
|
-
fontFamily: this.
|
|
404
|
+
fontFamily: this.resolvedFontFamily(),
|
|
217
405
|
fontSize: 13,
|
|
218
406
|
fontWeight: 500,
|
|
219
407
|
color: FxUtils.convertColorFromVariable('--text-primary'),
|
|
@@ -231,7 +419,7 @@ class ChartComponent {
|
|
|
231
419
|
position: 'outside',
|
|
232
420
|
formatter: '{d}%',
|
|
233
421
|
fontSize: this.labelFontSize,
|
|
234
|
-
fontFamily: this.
|
|
422
|
+
fontFamily: this.resolvedFontFamily(),
|
|
235
423
|
color: FxUtils.convertColorFromVariable('--text-primary'),
|
|
236
424
|
textBorderWidth: 0,
|
|
237
425
|
textBorderColor: 'transparent',
|
|
@@ -243,26 +431,124 @@ class ChartComponent {
|
|
|
243
431
|
},
|
|
244
432
|
},
|
|
245
433
|
],
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
434
|
+
// Centre figure: positioned by x/y in pixels — graphic left/top
|
|
435
|
+
// percentages place the element's bounding-box EDGE, which shifted the
|
|
436
|
+
// total right/down by half its own size (mis-centred, caption clipped).
|
|
437
|
+
graphic: (() => {
|
|
438
|
+
const cx = (this.containerWidth || 400) * (parseFloat(layout.pieCenter[0]) / 100);
|
|
439
|
+
const ch = this.ref.nativeElement.clientHeight || 300;
|
|
440
|
+
const cy = ch * (parseFloat(layout.pieCenter[1]) / 100);
|
|
441
|
+
return [
|
|
252
442
|
{
|
|
253
443
|
type: 'text',
|
|
444
|
+
// Shift the total up when a caption sits under it.
|
|
445
|
+
x: cx,
|
|
446
|
+
y: cy + (this.pieCenterLabel() ? -9 : 0),
|
|
254
447
|
style: {
|
|
255
448
|
text: total.toString(),
|
|
256
449
|
fontSize: 24,
|
|
257
450
|
fontWeight: 600,
|
|
258
|
-
fontFamily: this.
|
|
451
|
+
fontFamily: this.resolvedFontFamily(),
|
|
259
452
|
fill: FxUtils.convertColorFromVariable('--text-primary'),
|
|
260
453
|
align: 'center',
|
|
261
454
|
verticalAlign: 'middle',
|
|
262
455
|
},
|
|
263
456
|
},
|
|
264
|
-
|
|
457
|
+
...(this.pieCenterLabel()
|
|
458
|
+
? [
|
|
459
|
+
{
|
|
460
|
+
type: 'text',
|
|
461
|
+
x: cx,
|
|
462
|
+
y: cy + 12,
|
|
463
|
+
style: {
|
|
464
|
+
text: this.pieCenterLabel(),
|
|
465
|
+
fontSize: 11,
|
|
466
|
+
fontFamily: this.resolvedFontFamily(),
|
|
467
|
+
fill: FxUtils.convertColorFromVariable('--text-secondary'),
|
|
468
|
+
align: 'center',
|
|
469
|
+
verticalAlign: 'middle',
|
|
470
|
+
},
|
|
471
|
+
},
|
|
472
|
+
]
|
|
473
|
+
: []),
|
|
474
|
+
];
|
|
475
|
+
})(),
|
|
476
|
+
};
|
|
477
|
+
},
|
|
478
|
+
// V2 preset: single line, accent-coloured — "risk score by release".
|
|
479
|
+
line: (data) => ({
|
|
480
|
+
tooltip: { trigger: 'axis' },
|
|
481
|
+
grid: { left: '3%', right: '4%', bottom: '8%', top: '8%', containLabel: true },
|
|
482
|
+
xAxis: this.categoryAxis(data?.categories || []),
|
|
483
|
+
yAxis: this.valueAxis(),
|
|
484
|
+
series: [
|
|
485
|
+
{
|
|
486
|
+
type: 'line',
|
|
487
|
+
data: data?.values || [],
|
|
488
|
+
smooth: false,
|
|
489
|
+
symbol: 'circle',
|
|
490
|
+
symbolSize: 7,
|
|
491
|
+
lineStyle: { width: 2, color: FxUtils.convertColorFromVariable('--og-accent', 1, { legacyCommaSyntax: true }) },
|
|
492
|
+
itemStyle: { color: FxUtils.convertColorFromVariable('--og-accent', 1, { legacyCommaSyntax: true }) },
|
|
493
|
+
label: {
|
|
494
|
+
show: true,
|
|
495
|
+
position: 'top',
|
|
496
|
+
fontFamily: this.resolvedFontFamily(),
|
|
497
|
+
fontSize: this.labelFontSize,
|
|
498
|
+
color: FxUtils.convertColorFromVariable('--text-primary'),
|
|
499
|
+
},
|
|
500
|
+
},
|
|
501
|
+
],
|
|
502
|
+
}),
|
|
503
|
+
// V2 preset: weekly trend — accent gradient fill + gray comparison line.
|
|
504
|
+
// data: { categories: string[], current: number[], previous?: number[] }
|
|
505
|
+
'area-trend': (data) => {
|
|
506
|
+
// legacyCommaSyntax: zrender's colour parser chokes on CSS4 space syntax.
|
|
507
|
+
const accent = FxUtils.convertColorFromVariable('--og-accent', 1, { legacyCommaSyntax: true });
|
|
508
|
+
const accentSoft = FxUtils.convertColorFromVariable('--og-accent', 0.25, { legacyCommaSyntax: true });
|
|
509
|
+
const accentFade = FxUtils.convertColorFromVariable('--og-accent', 0.02, { legacyCommaSyntax: true });
|
|
510
|
+
const series = [
|
|
511
|
+
{
|
|
512
|
+
type: 'line',
|
|
513
|
+
name: data?.currentLabel || 'Current',
|
|
514
|
+
data: data?.current || [],
|
|
515
|
+
smooth: true,
|
|
516
|
+
symbol: 'none',
|
|
517
|
+
lineStyle: { width: 2, color: accent },
|
|
518
|
+
itemStyle: { color: accent },
|
|
519
|
+
areaStyle: {
|
|
520
|
+
color: {
|
|
521
|
+
type: 'linear',
|
|
522
|
+
x: 0, y: 0, x2: 0, y2: 1,
|
|
523
|
+
colorStops: [
|
|
524
|
+
{ offset: 0, color: accentSoft },
|
|
525
|
+
{ offset: 1, color: accentFade },
|
|
526
|
+
],
|
|
527
|
+
},
|
|
528
|
+
},
|
|
265
529
|
},
|
|
530
|
+
];
|
|
531
|
+
if (data?.previous?.length) {
|
|
532
|
+
series.push({
|
|
533
|
+
type: 'line',
|
|
534
|
+
name: data?.previousLabel || 'Previous',
|
|
535
|
+
data: data.previous,
|
|
536
|
+
smooth: true,
|
|
537
|
+
symbol: 'none',
|
|
538
|
+
lineStyle: {
|
|
539
|
+
width: 1.5,
|
|
540
|
+
type: 'dashed',
|
|
541
|
+
color: FxUtils.convertColorFromVariable('--text-placeholder', 1, { legacyCommaSyntax: true }),
|
|
542
|
+
},
|
|
543
|
+
itemStyle: { color: FxUtils.convertColorFromVariable('--text-placeholder', 1, { legacyCommaSyntax: true }) },
|
|
544
|
+
});
|
|
545
|
+
}
|
|
546
|
+
return {
|
|
547
|
+
tooltip: { trigger: 'axis' },
|
|
548
|
+
grid: { left: '3%', right: '4%', bottom: '8%', top: '10%', containLabel: true },
|
|
549
|
+
xAxis: { ...this.categoryAxis(data?.categories || []), boundaryGap: false },
|
|
550
|
+
yAxis: this.valueAxis(),
|
|
551
|
+
series,
|
|
266
552
|
};
|
|
267
553
|
},
|
|
268
554
|
sunburst: (data, colors) => ({
|
|
@@ -279,7 +565,7 @@ class ChartComponent {
|
|
|
279
565
|
label: {
|
|
280
566
|
rotate: 'tangential',
|
|
281
567
|
fontSize: this.labelFontSize,
|
|
282
|
-
fontFamily: this.
|
|
568
|
+
fontFamily: this.resolvedFontFamily(),
|
|
283
569
|
color: FxUtils.convertColorFromVariable('--text-primary'),
|
|
284
570
|
textBorderWidth: 0,
|
|
285
571
|
textBorderColor: 'transparent',
|
|
@@ -291,6 +577,19 @@ class ChartComponent {
|
|
|
291
577
|
/* ================= PIE LAYOUT ================= */
|
|
292
578
|
calcPieLayout(data) {
|
|
293
579
|
const cw = this.containerWidth || 400;
|
|
580
|
+
if (!this.showPieLegend()) {
|
|
581
|
+
return {
|
|
582
|
+
legendOrient: 'vertical',
|
|
583
|
+
legendLeft: undefined,
|
|
584
|
+
legendRight: undefined,
|
|
585
|
+
legendTop: undefined,
|
|
586
|
+
legendBottom: undefined,
|
|
587
|
+
legendWidth: undefined,
|
|
588
|
+
pieCenter: ['50%', '50%'],
|
|
589
|
+
graphicLeft: '50%',
|
|
590
|
+
graphicTop: '50%',
|
|
591
|
+
};
|
|
592
|
+
}
|
|
294
593
|
if (this.isMobile) {
|
|
295
594
|
return {
|
|
296
595
|
legendOrient: 'horizontal',
|
|
@@ -351,6 +650,48 @@ class ChartComponent {
|
|
|
351
650
|
...this.options(),
|
|
352
651
|
};
|
|
353
652
|
}
|
|
653
|
+
categoryAxis(categories) {
|
|
654
|
+
return {
|
|
655
|
+
type: 'category',
|
|
656
|
+
data: categories,
|
|
657
|
+
axisLabel: {
|
|
658
|
+
fontFamily: this.resolvedFontFamily(),
|
|
659
|
+
fontSize: this.labelFontSize,
|
|
660
|
+
color: FxUtils.convertColorFromVariable('--text-secondary'),
|
|
661
|
+
},
|
|
662
|
+
axisLine: {
|
|
663
|
+
lineStyle: { color: FxUtils.convertColorFromVariable('--border-strong') },
|
|
664
|
+
},
|
|
665
|
+
};
|
|
666
|
+
}
|
|
667
|
+
valueAxis() {
|
|
668
|
+
return {
|
|
669
|
+
type: 'value',
|
|
670
|
+
axisLabel: {
|
|
671
|
+
fontFamily: this.resolvedFontFamily(),
|
|
672
|
+
fontSize: this.labelFontSize,
|
|
673
|
+
color: FxUtils.convertColorFromVariable('--text-secondary'),
|
|
674
|
+
},
|
|
675
|
+
splitLine: {
|
|
676
|
+
lineStyle: { color: FxUtils.convertColorFromVariable('--border-default') },
|
|
677
|
+
},
|
|
678
|
+
};
|
|
679
|
+
}
|
|
680
|
+
/** Canvas không ăn var() — resolve font stack thật tại render; chart đã
|
|
681
|
+
rebuild theo event theme-changed nên tự đổi font khi đổi theme
|
|
682
|
+
(blueprint/terminal dùng mono qua --og-font-figure). */
|
|
683
|
+
resolvedFontFamily() {
|
|
684
|
+
const override = this.labelFontFamily();
|
|
685
|
+
if (override)
|
|
686
|
+
return override;
|
|
687
|
+
// Dev-mode CSS pretty-printer bẻ dòng giá trị dài — normalize whitespace
|
|
688
|
+
// trước khi chuỗi này vào ctx.font của canvas.
|
|
689
|
+
const figure = getComputedStyle(document.body)
|
|
690
|
+
.getPropertyValue('--og-font-figure')
|
|
691
|
+
.replace(/\s+/g, ' ')
|
|
692
|
+
.trim();
|
|
693
|
+
return figure || 'Archivo, system-ui, sans-serif';
|
|
694
|
+
}
|
|
354
695
|
resolvePalette() {
|
|
355
696
|
const defaults = [
|
|
356
697
|
'rgba(var(--success) / 1)',
|
|
@@ -374,12 +715,12 @@ class ChartComponent {
|
|
|
374
715
|
});
|
|
375
716
|
}
|
|
376
717
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.20", ngImport: i0, type: ChartComponent, deps: [{ token: i0.ElementRef }, { token: i0.ChangeDetectorRef }], target: i0.ɵɵFactoryTarget.Component });
|
|
377
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.20", type: ChartComponent, isStandalone: true, selector: "fx-ui-chart", inputs: { type: { classPropertyName: "type", publicName: "type", isSignal: true, isRequired: false, transformFunction: null }, data: { classPropertyName: "data", publicName: "data", isSignal: true, isRequired: false, transformFunction: null }, options: { classPropertyName: "options", publicName: "options", isSignal: true, isRequired: false, transformFunction: null }, palette: { classPropertyName: "palette", publicName: "palette", isSignal: true, isRequired: false, transformFunction: null }, height: { classPropertyName: "height", publicName: "height", isSignal: true, isRequired: false, transformFunction: null }, loading: { classPropertyName: "loading", publicName: "loading", isSignal: true, isRequired: false, transformFunction: null }, pieLegendAlign: { classPropertyName: "pieLegendAlign", publicName: "pieLegendAlign", isSignal: true, isRequired: false, transformFunction: null }, pieLegendPosition: { classPropertyName: "pieLegendPosition", publicName: "pieLegendPosition", isSignal: true, isRequired: false, transformFunction: null }, showPieLabel: { classPropertyName: "showPieLabel", publicName: "showPieLabel", isSignal: true, isRequired: false, transformFunction: null }, mobileWidth: { classPropertyName: "mobileWidth", publicName: "mobileWidth", isSignal: true, isRequired: false, transformFunction: null }, labelFontFamily: { classPropertyName: "labelFontFamily", publicName: "labelFontFamily", isSignal: true, isRequired: false, transformFunction: null }, emptyText: { classPropertyName: "emptyText", publicName: "emptyText", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { chartClick: "chartClick", chartInit: "chartInit" }, host: { listeners: { "window:theme-changed": "onThemeChange()" } }, providers: [provideEchartsCore({ echarts: () => import('echarts') })], usesOnChanges: true, ngImport: i0, template: "<div class=\"chart-wrapper\" [style.height]=\"height()\">\n @if (isEmpty) {\n <div class=\"chart-empty\">\n <svg xmlns=\"http://www.w3.org/2000/svg\" fill=\"none\" viewBox=\"0 0 24 24\" stroke-width=\"1.5\" stroke=\"currentColor\" class=\"chart-empty-icon\">\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M3 13.125C3 12.504 3.504 12 4.125 12h2.25c.621 0 1.125.504 1.125 1.125v6.75C7.5 20.496 6.996 21 6.375 21h-2.25A1.125 1.125 0 0 1 3 19.875v-6.75ZM9.75 8.625c0-.621.504-1.125 1.125-1.125h2.25c.621 0 1.125.504 1.125 1.125v11.25c0 .621-.504 1.125-1.125 1.125h-2.25a1.125 1.125 0 0 1-1.125-1.125V8.625ZM16.5 4.125c0-.621.504-1.125 1.125-1.125h2.25C20.496 3 21 3.504 21 4.125v15.75c0 .621-.504 1.125-1.125 1.125h-2.25a1.125 1.125 0 0 1-1.125-1.125V4.125Z\" />\n </svg>\n <span class=\"chart-empty-text\">{{ emptyText() }}</span>\n </div>\n } @else {\n <div\n echarts\n [options]=\"chartOptions\"\n [loading]=\"loading()\"\n (chartInit)=\"onChartInit($event)\"\n (chartClick)=\"onChartClick($event)\"\n class=\"fx-chart\"\n [style.height]=\"height()\"\n ></div>\n }\n</div>\n", styles: [":host{display:block;width:100%}.chart-wrapper{position:relative;width:100%;height:100%}.chart-container{width:100%;height:100%}.chart-loader{position:absolute;inset:0;display:flex;align-items:center;justify-content:center;background:rgba(var(--bg-primary-hover)/.6);font-weight:500;font-size:1.1rem;z-index:2;border-radius:4px}.chart-empty{display:flex;flex-direction:column;align-items:center;justify-content:center;height:100%;gap:12px;color:rgba(var(--text-secondary)/1)}.chart-empty-icon{width:48px;height:48px;opacity:.4}.chart-empty-text{font-size:14px;font-weight:500}\n"], dependencies: [{ kind: "directive", type: NgxEchartsDirective, selector: "echarts, [echarts]", inputs: ["options", "theme", "initOpts", "merge", "autoResize", "loading", "loadingType", "loadingOpts"], outputs: ["chartInit", "optionsError", "chartClick", "chartDblClick", "chartMouseDown", "chartMouseMove", "chartMouseUp", "chartMouseOver", "chartMouseOut", "chartGlobalOut", "chartContextMenu", "chartHighlight", "chartDownplay", "chartSelectChanged", "chartLegendSelectChanged", "chartLegendSelected", "chartLegendUnselected", "chartLegendLegendSelectAll", "chartLegendLegendInverseSelect", "chartLegendScroll", "chartDataZoom", "chartDataRangeSelected", "chartGraphRoam", "chartGeoRoam", "chartTreeRoam", "chartTimelineChanged", "chartTimelinePlayChanged", "chartRestore", "chartDataViewChanged", "chartMagicTypeChanged", "chartGeoSelectChanged", "chartGeoSelected", "chartGeoUnselected", "chartAxisAreaSelected", "chartBrush", "chartBrushEnd", "chartBrushSelected", "chartGlobalCursorTaken", "chartRendered", "chartFinished"], exportAs: ["echarts"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
718
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.20", type: ChartComponent, isStandalone: true, selector: "fx-ui-chart", inputs: { type: { classPropertyName: "type", publicName: "type", isSignal: true, isRequired: false, transformFunction: null }, data: { classPropertyName: "data", publicName: "data", isSignal: true, isRequired: false, transformFunction: null }, options: { classPropertyName: "options", publicName: "options", isSignal: true, isRequired: false, transformFunction: null }, palette: { classPropertyName: "palette", publicName: "palette", isSignal: true, isRequired: false, transformFunction: null }, height: { classPropertyName: "height", publicName: "height", isSignal: true, isRequired: false, transformFunction: null }, loading: { classPropertyName: "loading", publicName: "loading", isSignal: true, isRequired: false, transformFunction: null }, pieLegendAlign: { classPropertyName: "pieLegendAlign", publicName: "pieLegendAlign", isSignal: true, isRequired: false, transformFunction: null }, pieLegendPosition: { classPropertyName: "pieLegendPosition", publicName: "pieLegendPosition", isSignal: true, isRequired: false, transformFunction: null }, showPieLabel: { classPropertyName: "showPieLabel", publicName: "showPieLabel", isSignal: true, isRequired: false, transformFunction: null }, showPieLegend: { classPropertyName: "showPieLegend", publicName: "showPieLegend", isSignal: true, isRequired: false, transformFunction: null }, pieCenterLabel: { classPropertyName: "pieCenterLabel", publicName: "pieCenterLabel", isSignal: true, isRequired: false, transformFunction: null }, mobileWidth: { classPropertyName: "mobileWidth", publicName: "mobileWidth", isSignal: true, isRequired: false, transformFunction: null }, labelFontFamily: { classPropertyName: "labelFontFamily", publicName: "labelFontFamily", isSignal: true, isRequired: false, transformFunction: null }, emptyText: { classPropertyName: "emptyText", publicName: "emptyText", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { chartClick: "chartClick", chartInit: "chartInit" }, host: { listeners: { "window:theme-changed": "onThemeChange()" } }, providers: [provideEchartsCore({ echarts: () => import('echarts') })], usesOnChanges: true, ngImport: i0, template: "<div class=\"chart-wrapper\" [style.height]=\"height()\">\n @if (isEmpty) {\n <div class=\"chart-empty\">\n <svg xmlns=\"http://www.w3.org/2000/svg\" fill=\"none\" viewBox=\"0 0 24 24\" stroke-width=\"1.5\" stroke=\"currentColor\" class=\"chart-empty-icon\">\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M3 13.125C3 12.504 3.504 12 4.125 12h2.25c.621 0 1.125.504 1.125 1.125v6.75C7.5 20.496 6.996 21 6.375 21h-2.25A1.125 1.125 0 0 1 3 19.875v-6.75ZM9.75 8.625c0-.621.504-1.125 1.125-1.125h2.25c.621 0 1.125.504 1.125 1.125v11.25c0 .621-.504 1.125-1.125 1.125h-2.25a1.125 1.125 0 0 1-1.125-1.125V8.625ZM16.5 4.125c0-.621.504-1.125 1.125-1.125h2.25C20.496 3 21 3.504 21 4.125v15.75c0 .621-.504 1.125-1.125 1.125h-2.25a1.125 1.125 0 0 1-1.125-1.125V4.125Z\" />\n </svg>\n <span class=\"chart-empty-text\">{{ emptyText() }}</span>\n </div>\n } @else if (hasOptions) {\n <div\n echarts\n [options]=\"chartOptions\"\n [loading]=\"loading()\"\n (chartInit)=\"onChartInit($event)\"\n (chartClick)=\"onChartClick($event)\"\n class=\"fx-chart\"\n [style.height]=\"height()\"\n ></div>\n }\n</div>\n", styles: [":host{display:block;width:100%}.chart-wrapper{position:relative;width:100%;height:100%}.chart-container{width:100%;height:100%}.chart-loader{position:absolute;inset:0;display:flex;align-items:center;justify-content:center;background:rgba(var(--bg-primary-hover)/.6);font-weight:500;font-size:1.1rem;z-index:2;border-radius:4px}.chart-empty{display:flex;flex-direction:column;align-items:center;justify-content:center;height:100%;gap:12px;color:rgba(var(--text-secondary)/1)}.chart-empty-icon{width:48px;height:48px;opacity:.4}.chart-empty-text{font-size:14px;font-weight:500}\n"], dependencies: [{ kind: "directive", type: NgxEchartsDirective, selector: "echarts, [echarts]", inputs: ["options", "theme", "initOpts", "merge", "autoResize", "loading", "loadingType", "loadingOpts"], outputs: ["chartInit", "optionsError", "chartClick", "chartDblClick", "chartMouseDown", "chartMouseMove", "chartMouseUp", "chartMouseOver", "chartMouseOut", "chartGlobalOut", "chartContextMenu", "chartHighlight", "chartDownplay", "chartSelectChanged", "chartLegendSelectChanged", "chartLegendSelected", "chartLegendUnselected", "chartLegendLegendSelectAll", "chartLegendLegendInverseSelect", "chartLegendScroll", "chartDataZoom", "chartDataRangeSelected", "chartGraphRoam", "chartGeoRoam", "chartTreeRoam", "chartTimelineChanged", "chartTimelinePlayChanged", "chartRestore", "chartDataViewChanged", "chartMagicTypeChanged", "chartGeoSelectChanged", "chartGeoSelected", "chartGeoUnselected", "chartAxisAreaSelected", "chartBrush", "chartBrushEnd", "chartBrushSelected", "chartGlobalCursorTaken", "chartRendered", "chartFinished"], exportAs: ["echarts"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
378
719
|
}
|
|
379
720
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.20", ngImport: i0, type: ChartComponent, decorators: [{
|
|
380
721
|
type: Component,
|
|
381
|
-
args: [{ selector: 'fx-ui-chart', changeDetection: ChangeDetectionStrategy.OnPush, imports: [NgxEchartsDirective, InputComponent], providers: [provideEchartsCore({ echarts: () => import('echarts') })], template: "<div class=\"chart-wrapper\" [style.height]=\"height()\">\n @if (isEmpty) {\n <div class=\"chart-empty\">\n <svg xmlns=\"http://www.w3.org/2000/svg\" fill=\"none\" viewBox=\"0 0 24 24\" stroke-width=\"1.5\" stroke=\"currentColor\" class=\"chart-empty-icon\">\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M3 13.125C3 12.504 3.504 12 4.125 12h2.25c.621 0 1.125.504 1.125 1.125v6.75C7.5 20.496 6.996 21 6.375 21h-2.25A1.125 1.125 0 0 1 3 19.875v-6.75ZM9.75 8.625c0-.621.504-1.125 1.125-1.125h2.25c.621 0 1.125.504 1.125 1.125v11.25c0 .621-.504 1.125-1.125 1.125h-2.25a1.125 1.125 0 0 1-1.125-1.125V8.625ZM16.5 4.125c0-.621.504-1.125 1.125-1.125h2.25C20.496 3 21 3.504 21 4.125v15.75c0 .621-.504 1.125-1.125 1.125h-2.25a1.125 1.125 0 0 1-1.125-1.125V4.125Z\" />\n </svg>\n <span class=\"chart-empty-text\">{{ emptyText() }}</span>\n </div>\n } @else {\n <div\n echarts\n [options]=\"chartOptions\"\n [loading]=\"loading()\"\n (chartInit)=\"onChartInit($event)\"\n (chartClick)=\"onChartClick($event)\"\n class=\"fx-chart\"\n [style.height]=\"height()\"\n ></div>\n }\n</div>\n", styles: [":host{display:block;width:100%}.chart-wrapper{position:relative;width:100%;height:100%}.chart-container{width:100%;height:100%}.chart-loader{position:absolute;inset:0;display:flex;align-items:center;justify-content:center;background:rgba(var(--bg-primary-hover)/.6);font-weight:500;font-size:1.1rem;z-index:2;border-radius:4px}.chart-empty{display:flex;flex-direction:column;align-items:center;justify-content:center;height:100%;gap:12px;color:rgba(var(--text-secondary)/1)}.chart-empty-icon{width:48px;height:48px;opacity:.4}.chart-empty-text{font-size:14px;font-weight:500}\n"] }]
|
|
382
|
-
}], ctorParameters: () => [{ type: i0.ElementRef }, { type: i0.ChangeDetectorRef }], propDecorators: { type: [{ type: i0.Input, args: [{ isSignal: true, alias: "type", required: false }] }], data: [{ type: i0.Input, args: [{ isSignal: true, alias: "data", required: false }] }], options: [{ type: i0.Input, args: [{ isSignal: true, alias: "options", required: false }] }], palette: [{ type: i0.Input, args: [{ isSignal: true, alias: "palette", required: false }] }], height: [{ type: i0.Input, args: [{ isSignal: true, alias: "height", required: false }] }], loading: [{ type: i0.Input, args: [{ isSignal: true, alias: "loading", required: false }] }], pieLegendAlign: [{ type: i0.Input, args: [{ isSignal: true, alias: "pieLegendAlign", required: false }] }], pieLegendPosition: [{ type: i0.Input, args: [{ isSignal: true, alias: "pieLegendPosition", required: false }] }], showPieLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "showPieLabel", required: false }] }], mobileWidth: [{ type: i0.Input, args: [{ isSignal: true, alias: "mobileWidth", required: false }] }], labelFontFamily: [{ type: i0.Input, args: [{ isSignal: true, alias: "labelFontFamily", required: false }] }], emptyText: [{ type: i0.Input, args: [{ isSignal: true, alias: "emptyText", required: false }] }], chartClick: [{ type: i0.Output, args: ["chartClick"] }], chartInit: [{ type: i0.Output, args: ["chartInit"] }], onThemeChange: [{
|
|
722
|
+
args: [{ selector: 'fx-ui-chart', changeDetection: ChangeDetectionStrategy.OnPush, imports: [NgxEchartsDirective, InputComponent], providers: [provideEchartsCore({ echarts: () => import('echarts') })], template: "<div class=\"chart-wrapper\" [style.height]=\"height()\">\n @if (isEmpty) {\n <div class=\"chart-empty\">\n <svg xmlns=\"http://www.w3.org/2000/svg\" fill=\"none\" viewBox=\"0 0 24 24\" stroke-width=\"1.5\" stroke=\"currentColor\" class=\"chart-empty-icon\">\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M3 13.125C3 12.504 3.504 12 4.125 12h2.25c.621 0 1.125.504 1.125 1.125v6.75C7.5 20.496 6.996 21 6.375 21h-2.25A1.125 1.125 0 0 1 3 19.875v-6.75ZM9.75 8.625c0-.621.504-1.125 1.125-1.125h2.25c.621 0 1.125.504 1.125 1.125v11.25c0 .621-.504 1.125-1.125 1.125h-2.25a1.125 1.125 0 0 1-1.125-1.125V8.625ZM16.5 4.125c0-.621.504-1.125 1.125-1.125h2.25C20.496 3 21 3.504 21 4.125v15.75c0 .621-.504 1.125-1.125 1.125h-2.25a1.125 1.125 0 0 1-1.125-1.125V4.125Z\" />\n </svg>\n <span class=\"chart-empty-text\">{{ emptyText() }}</span>\n </div>\n } @else if (hasOptions) {\n <div\n echarts\n [options]=\"chartOptions\"\n [loading]=\"loading()\"\n (chartInit)=\"onChartInit($event)\"\n (chartClick)=\"onChartClick($event)\"\n class=\"fx-chart\"\n [style.height]=\"height()\"\n ></div>\n }\n</div>\n", styles: [":host{display:block;width:100%}.chart-wrapper{position:relative;width:100%;height:100%}.chart-container{width:100%;height:100%}.chart-loader{position:absolute;inset:0;display:flex;align-items:center;justify-content:center;background:rgba(var(--bg-primary-hover)/.6);font-weight:500;font-size:1.1rem;z-index:2;border-radius:4px}.chart-empty{display:flex;flex-direction:column;align-items:center;justify-content:center;height:100%;gap:12px;color:rgba(var(--text-secondary)/1)}.chart-empty-icon{width:48px;height:48px;opacity:.4}.chart-empty-text{font-size:14px;font-weight:500}\n"] }]
|
|
723
|
+
}], ctorParameters: () => [{ type: i0.ElementRef }, { type: i0.ChangeDetectorRef }], propDecorators: { type: [{ type: i0.Input, args: [{ isSignal: true, alias: "type", required: false }] }], data: [{ type: i0.Input, args: [{ isSignal: true, alias: "data", required: false }] }], options: [{ type: i0.Input, args: [{ isSignal: true, alias: "options", required: false }] }], palette: [{ type: i0.Input, args: [{ isSignal: true, alias: "palette", required: false }] }], height: [{ type: i0.Input, args: [{ isSignal: true, alias: "height", required: false }] }], loading: [{ type: i0.Input, args: [{ isSignal: true, alias: "loading", required: false }] }], pieLegendAlign: [{ type: i0.Input, args: [{ isSignal: true, alias: "pieLegendAlign", required: false }] }], pieLegendPosition: [{ type: i0.Input, args: [{ isSignal: true, alias: "pieLegendPosition", required: false }] }], showPieLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "showPieLabel", required: false }] }], showPieLegend: [{ type: i0.Input, args: [{ isSignal: true, alias: "showPieLegend", required: false }] }], pieCenterLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "pieCenterLabel", required: false }] }], mobileWidth: [{ type: i0.Input, args: [{ isSignal: true, alias: "mobileWidth", required: false }] }], labelFontFamily: [{ type: i0.Input, args: [{ isSignal: true, alias: "labelFontFamily", required: false }] }], emptyText: [{ type: i0.Input, args: [{ isSignal: true, alias: "emptyText", required: false }] }], chartClick: [{ type: i0.Output, args: ["chartClick"] }], chartInit: [{ type: i0.Output, args: ["chartInit"] }], onThemeChange: [{
|
|
383
724
|
type: HostListener,
|
|
384
725
|
args: ['window:theme-changed']
|
|
385
726
|
}] } });
|