@foxeltech/angular-ui 0.7.104 → 0.7.108
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/components.css +183 -0
- package/fesm2022/foxeltech-angular-ui-chart.mjs +222 -51
- package/fesm2022/foxeltech-angular-ui-chart.mjs.map +1 -1
- package/fesm2022/foxeltech-angular-ui.mjs +2 -2
- package/fesm2022/foxeltech-angular-ui.mjs.map +1 -1
- package/package.json +1 -1
- package/src/lib/styles/components.css +183 -0
- package/src/lib/ui/components/drawer/drawer.component.html +2 -2
- package/src/lib/ui/components/drawer/drawer.component.scss +46 -26
- package/types/foxeltech-angular-ui-chart.d.ts +65 -2
package/components.css
CHANGED
|
@@ -849,3 +849,186 @@ input[type='password'] {
|
|
|
849
849
|
.fx-rs-busy > *:not(fx-ui-route-skeleton) {
|
|
850
850
|
display: none !important;
|
|
851
851
|
}
|
|
852
|
+
|
|
853
|
+
/* ============================================================================
|
|
854
|
+
Grid tables, badges and status dots to the reference HTML (Binary
|
|
855
|
+
Intelligence Dashboard, porcelain) — anh Tú 15/09, moved into the lib in
|
|
856
|
+
0.7.107 from three identical copies in analyze/knowledge/host styles.scss.
|
|
857
|
+
|
|
858
|
+
The reference draws its lists as CSS-grid rows, not <table>: a sunken 10px
|
|
859
|
+
header in 10.5px/600 tracked faint, 11px rows split by hairlines, hover to
|
|
860
|
+
surface-sunken, a trailing caret that turns accent and slides 2px. Columns
|
|
861
|
+
come from the consumer as `--fx-cols` on `.fx-grid-table`.
|
|
862
|
+
|
|
863
|
+
Inside layer(components) like the rest of this file, so a Tailwind utility
|
|
864
|
+
on the same element still wins.
|
|
865
|
+
============================================================================ */
|
|
866
|
+
.fx-grid-table {
|
|
867
|
+
border: 1px solid rgb(var(--og-border));
|
|
868
|
+
border-radius: var(--og-radius-card);
|
|
869
|
+
background: rgb(var(--og-surface));
|
|
870
|
+
overflow: hidden;
|
|
871
|
+
}
|
|
872
|
+
.fx-grid-table__head,
|
|
873
|
+
.fx-grid-table__row {
|
|
874
|
+
display: grid;
|
|
875
|
+
grid-template-columns: var(--fx-cols);
|
|
876
|
+
gap: 12px;
|
|
877
|
+
align-items: center;
|
|
878
|
+
padding: 10px 12px;
|
|
879
|
+
}
|
|
880
|
+
.fx-grid-table__head {
|
|
881
|
+
border-bottom: 1px solid rgb(var(--og-border));
|
|
882
|
+
background: rgb(var(--og-surface-sunken));
|
|
883
|
+
font-size: var(--og-fs-2xs);
|
|
884
|
+
font-weight: 600;
|
|
885
|
+
letter-spacing: var(--og-tracking-label);
|
|
886
|
+
color: rgb(var(--og-text-faint));
|
|
887
|
+
}
|
|
888
|
+
.fx-grid-table__row {
|
|
889
|
+
appearance: none;
|
|
890
|
+
width: 100%;
|
|
891
|
+
padding: 11px 12px;
|
|
892
|
+
border: 0;
|
|
893
|
+
border-bottom: 1px solid rgb(var(--og-border-subtle));
|
|
894
|
+
background: transparent;
|
|
895
|
+
text-align: left;
|
|
896
|
+
cursor: pointer;
|
|
897
|
+
font: inherit;
|
|
898
|
+
color: inherit;
|
|
899
|
+
transition: background-color 130ms ease;
|
|
900
|
+
}
|
|
901
|
+
.fx-grid-table__row:last-child {
|
|
902
|
+
border-bottom: 0;
|
|
903
|
+
}
|
|
904
|
+
.fx-grid-table__row:hover {
|
|
905
|
+
background: rgb(var(--og-surface-sunken));
|
|
906
|
+
}
|
|
907
|
+
.fx-grid-table__row--on {
|
|
908
|
+
background: rgb(var(--og-accent-soft-bg));
|
|
909
|
+
}
|
|
910
|
+
.fx-grid-table__row:focus-visible {
|
|
911
|
+
outline: 2px solid rgb(var(--og-accent));
|
|
912
|
+
outline-offset: -2px;
|
|
913
|
+
}
|
|
914
|
+
.fx-grid-caret {
|
|
915
|
+
font-size: 15px;
|
|
916
|
+
line-height: 1;
|
|
917
|
+
color: rgb(var(--og-text-faint));
|
|
918
|
+
justify-self: end;
|
|
919
|
+
transition:
|
|
920
|
+
color 130ms ease,
|
|
921
|
+
transform 130ms ease;
|
|
922
|
+
}
|
|
923
|
+
.fx-grid-table__row:hover .fx-grid-caret {
|
|
924
|
+
color: rgb(var(--og-accent));
|
|
925
|
+
transform: translateX(2px);
|
|
926
|
+
}
|
|
927
|
+
.fx-grid-table__num {
|
|
928
|
+
font-family: var(--og-font-mono);
|
|
929
|
+
font-size: var(--og-fs-sm);
|
|
930
|
+
color: rgb(var(--og-text-body));
|
|
931
|
+
text-align: right;
|
|
932
|
+
font-variant-numeric: tabular-nums;
|
|
933
|
+
}
|
|
934
|
+
.fx-grid-table__head .fx-grid-table__num {
|
|
935
|
+
font-family: inherit;
|
|
936
|
+
font-size: inherit;
|
|
937
|
+
color: inherit;
|
|
938
|
+
}
|
|
939
|
+
.fx-grid-table__strong {
|
|
940
|
+
font-size: var(--og-fs-sm);
|
|
941
|
+
font-weight: 600;
|
|
942
|
+
color: rgb(var(--og-text));
|
|
943
|
+
}
|
|
944
|
+
.fx-grid-table__muted {
|
|
945
|
+
font-size: var(--og-fs-xs);
|
|
946
|
+
color: rgb(var(--og-text-muted));
|
|
947
|
+
}
|
|
948
|
+
.fx-grid-table__mono {
|
|
949
|
+
font-family: var(--og-font-mono);
|
|
950
|
+
font-size: var(--og-fs-xs);
|
|
951
|
+
color: rgb(var(--og-text-faint));
|
|
952
|
+
}
|
|
953
|
+
.fx-grid-empty {
|
|
954
|
+
padding: 15px;
|
|
955
|
+
text-align: center;
|
|
956
|
+
color: rgb(var(--og-text-muted));
|
|
957
|
+
font-size: var(--og-fs-sm);
|
|
958
|
+
border: 1px dashed rgb(var(--og-border));
|
|
959
|
+
border-radius: var(--og-radius-card);
|
|
960
|
+
}
|
|
961
|
+
|
|
962
|
+
/* `.og-badge`: 3px 10px, pill, 11.5px, soft tint by tone; outline = the
|
|
963
|
+
neutral "Low" band; solid = Critical. Tone names are the SEMANTIC tokens
|
|
964
|
+
(`--og-negative`, not the chart ramp) — see the note above `.tone-tint-*`. */
|
|
965
|
+
.fx-badge {
|
|
966
|
+
display: inline-flex;
|
|
967
|
+
align-items: center;
|
|
968
|
+
gap: 5px;
|
|
969
|
+
padding: 3px 10px;
|
|
970
|
+
border-radius: var(--og-radius-pill);
|
|
971
|
+
font-size: var(--og-fs-xs);
|
|
972
|
+
line-height: 1.5;
|
|
973
|
+
white-space: nowrap;
|
|
974
|
+
font-weight: 500;
|
|
975
|
+
}
|
|
976
|
+
.fx-badge--negative {
|
|
977
|
+
background: rgb(var(--og-negative-soft-bg));
|
|
978
|
+
color: rgb(var(--og-negative));
|
|
979
|
+
}
|
|
980
|
+
.fx-badge--warning {
|
|
981
|
+
background: rgb(var(--og-warning-soft-bg));
|
|
982
|
+
color: rgb(var(--og-warning));
|
|
983
|
+
}
|
|
984
|
+
.fx-badge--positive {
|
|
985
|
+
background: rgb(var(--og-positive-soft-bg));
|
|
986
|
+
color: rgb(var(--og-positive));
|
|
987
|
+
}
|
|
988
|
+
.fx-badge--neutral {
|
|
989
|
+
background: rgb(var(--og-neutral-soft-bg));
|
|
990
|
+
color: rgb(var(--og-neutral-soft-fg));
|
|
991
|
+
}
|
|
992
|
+
.fx-badge--accent {
|
|
993
|
+
background: rgb(var(--og-accent-soft-bg));
|
|
994
|
+
color: rgb(var(--og-accent-soft-fg));
|
|
995
|
+
}
|
|
996
|
+
.fx-badge--outline {
|
|
997
|
+
background: none;
|
|
998
|
+
border: 1px solid rgb(var(--og-border));
|
|
999
|
+
color: rgb(var(--og-text-muted));
|
|
1000
|
+
}
|
|
1001
|
+
.fx-badge--solid {
|
|
1002
|
+
background: rgb(var(--og-text));
|
|
1003
|
+
color: rgb(var(--og-surface));
|
|
1004
|
+
}
|
|
1005
|
+
|
|
1006
|
+
/* `.og-statusdot`: a 7px mark and the state's name. */
|
|
1007
|
+
.fx-statusdot {
|
|
1008
|
+
display: inline-flex;
|
|
1009
|
+
align-items: center;
|
|
1010
|
+
gap: 6px;
|
|
1011
|
+
font-size: var(--og-fs-sm);
|
|
1012
|
+
color: rgb(var(--og-text-body));
|
|
1013
|
+
white-space: nowrap;
|
|
1014
|
+
}
|
|
1015
|
+
.fx-statusdot::before {
|
|
1016
|
+
content: '';
|
|
1017
|
+
width: 7px;
|
|
1018
|
+
height: 7px;
|
|
1019
|
+
border-radius: 50%;
|
|
1020
|
+
background: rgb(var(--og-text-faint));
|
|
1021
|
+
flex: none;
|
|
1022
|
+
}
|
|
1023
|
+
.fx-statusdot--positive::before {
|
|
1024
|
+
background: rgb(var(--og-positive));
|
|
1025
|
+
}
|
|
1026
|
+
.fx-statusdot--negative::before {
|
|
1027
|
+
background: rgb(var(--og-negative));
|
|
1028
|
+
}
|
|
1029
|
+
.fx-statusdot--warning::before {
|
|
1030
|
+
background: rgb(var(--og-warning));
|
|
1031
|
+
}
|
|
1032
|
+
.fx-statusdot--accent::before {
|
|
1033
|
+
background: rgb(var(--og-accent));
|
|
1034
|
+
}
|
|
@@ -22,10 +22,34 @@ class ChartComponent {
|
|
|
22
22
|
showPieLegend = input(true, ...(ngDevMode ? [{ debugName: "showPieLegend" }] : /* istanbul ignore next */ []));
|
|
23
23
|
/** Caption under the donut's centre total (design: "42 / confirmed"). */
|
|
24
24
|
pieCenterLabel = input('', ...(ngDevMode ? [{ debugName: "pieCenterLabel" }] : /* istanbul ignore next */ []));
|
|
25
|
+
/**
|
|
26
|
+
* Type size of the donut's centre TOTAL, in px. The default matches what the
|
|
27
|
+
* figure has always been; raise it where the total is the panel's headline
|
|
28
|
+
* rather than an annotation.
|
|
29
|
+
*/
|
|
30
|
+
pieCenterValueSize = input(24, ...(ngDevMode ? [{ debugName: "pieCenterValueSize" }] : /* istanbul ignore next */ []));
|
|
31
|
+
/**
|
|
32
|
+
* The donut's [inner, outer] radius — ECharts' own vocabulary, so either a
|
|
33
|
+
* percentage of the shorter side or a pixel count. The default is what the
|
|
34
|
+
* ring has always been. The reference dashboard draws a 140px ring with a
|
|
35
|
+
* 22px stroke, i.e. `[48, 70]` at `height="140px"` — the panel that wants it
|
|
36
|
+
* says so; the other pies keep their proportions.
|
|
37
|
+
*/
|
|
38
|
+
pieRadius = input(['40%', '70%'], ...(ngDevMode ? [{ debugName: "pieRadius" }] : /* istanbul ignore next */ []));
|
|
25
39
|
mobileWidth = input(350, ...(ngDevMode ? [{ debugName: "mobileWidth" }] : /* istanbul ignore next */ []));
|
|
26
40
|
/** Override font for canvas text; empty = follow the theme's --og-font-figure. */
|
|
27
41
|
labelFontFamily = input('', ...(ngDevMode ? [{ debugName: "labelFontFamily" }] : /* istanbul ignore next */ []));
|
|
28
42
|
emptyText = input('No data available', ...(ngDevMode ? [{ debugName: "emptyText" }] : /* istanbul ignore next */ []));
|
|
43
|
+
// line / area-trend controls
|
|
44
|
+
/**
|
|
45
|
+
* Print each point's value beside a marker on the line.
|
|
46
|
+
*
|
|
47
|
+
* Off by default: on a dense series the labels collide and the line becomes
|
|
48
|
+
* unreadable. Turn it on for the sparse, weekly kind of series where the
|
|
49
|
+
* number at each step IS the content — a reader should not have to hover to
|
|
50
|
+
* learn what a step was.
|
|
51
|
+
*/
|
|
52
|
+
showPointLabels = input(false, ...(ngDevMode ? [{ debugName: "showPointLabels" }] : /* istanbul ignore next */ []));
|
|
29
53
|
/* ================= OUTPUTS ================= */
|
|
30
54
|
chartClick = output();
|
|
31
55
|
chartInit = output();
|
|
@@ -78,6 +102,8 @@ class ChartComponent {
|
|
|
78
102
|
*/
|
|
79
103
|
appearUntil = 0;
|
|
80
104
|
static APPEAR_MS = 1100;
|
|
105
|
+
/** Quiet time a host size has to hold before a resize is acted on. */
|
|
106
|
+
static RESIZE_SETTLE_MS = 120;
|
|
81
107
|
deferTimer;
|
|
82
108
|
/**
|
|
83
109
|
* Thay đổi kích thước ghi nhận TRONG cửa sổ appear, chờ áp sau. `measure()`
|
|
@@ -107,10 +133,15 @@ class ChartComponent {
|
|
|
107
133
|
// resize() mù sau throttle 100ms, kể cả về 0×H khi phần tử display:none
|
|
108
134
|
// (skeleton route ẩn nội dung cũ) rồi resize lại khi hiện → animation
|
|
109
135
|
// xuất hiện bị giết (đo 05/09 khi đổi project trên chip breadcrumb).
|
|
136
|
+
// Coalesced: a host that is being ANIMATED (the drawer's 300ms width
|
|
137
|
+
// tween, a panel sliding open) fires one RO event per frame, and acting
|
|
138
|
+
// on each one resized + rebuilt the pie ~18 times and replayed its appear
|
|
139
|
+
// animation twice (anh Tú 15/09, donut on sidebar toggle). Wait until the
|
|
140
|
+
// size has been still for RESIZE_SETTLE_MS, then act once.
|
|
110
141
|
this.resizeObserver = new ResizeObserver(() => {
|
|
111
142
|
if (!this.canInit || this.destroyed)
|
|
112
143
|
return;
|
|
113
|
-
this.
|
|
144
|
+
this.deferResize(ChartComponent.RESIZE_SETTLE_MS);
|
|
114
145
|
});
|
|
115
146
|
this.resizeObserver.observe(this.ref.nativeElement);
|
|
116
147
|
}
|
|
@@ -247,7 +278,14 @@ class ChartComponent {
|
|
|
247
278
|
* lại cùng cỡ thì coi như không đổi (không resize, không cắt animation).
|
|
248
279
|
*/
|
|
249
280
|
measure() {
|
|
250
|
-
const
|
|
281
|
+
const rect = this.ref.nativeElement.getBoundingClientRect();
|
|
282
|
+
// Whole pixels only. getBoundingClientRect() follows transforms, so a host
|
|
283
|
+
// riding an fxReveal translateY reports 200.00003 then 199.99997 — the
|
|
284
|
+
// old strict compare took that jitter for a resize, and after the appear
|
|
285
|
+
// window a pie was resized + rebuilt = drawn twice (đo 07/09, Overview
|
|
286
|
+
// "Open findings by severity" 2 × 60 frames 1.1 s apart).
|
|
287
|
+
const width = Math.round(rect.width);
|
|
288
|
+
const height = Math.round(rect.height);
|
|
251
289
|
if (width <= 0 || height <= 0)
|
|
252
290
|
return { size: false, mobile: false };
|
|
253
291
|
const isMobile = width < this.mobileWidth();
|
|
@@ -262,10 +300,18 @@ class ChartComponent {
|
|
|
262
300
|
markAppear() {
|
|
263
301
|
this.appearUntil = performance.now() + ChartComponent.APPEAR_MS;
|
|
264
302
|
}
|
|
265
|
-
/**
|
|
303
|
+
/**
|
|
304
|
+
* Sunburst, and a pie WITH its built-in legend, place things in px of
|
|
305
|
+
* containerWidth → must rebuild on resize. A legend-less pie centres its
|
|
306
|
+
* ring at 50%/50% and its total in a `left: 'center' / top: 'middle'`
|
|
307
|
+
* graphic group, so `resize()` alone re-centres it — no rebuild, no
|
|
308
|
+
* second appear animation (15/09, donut on sidebar toggle).
|
|
309
|
+
*/
|
|
266
310
|
needsLayoutRebuild() {
|
|
267
311
|
const type = this.detectType();
|
|
268
|
-
|
|
312
|
+
if (type === 'sunburst')
|
|
313
|
+
return true;
|
|
314
|
+
return type === 'pie' && this.showPieLegend();
|
|
269
315
|
}
|
|
270
316
|
deferResize(ms) {
|
|
271
317
|
if (this.deferTimer !== undefined)
|
|
@@ -318,10 +364,19 @@ class ChartComponent {
|
|
|
318
364
|
if (!chart)
|
|
319
365
|
return;
|
|
320
366
|
chart.resize();
|
|
321
|
-
|
|
367
|
+
// A rebuild forced by GEOMETRY draws the same data at a new size: no
|
|
368
|
+
// appear animation, or the panel visibly redraws (that was the "renders
|
|
369
|
+
// twice" of 15/09). Only a preset change (mobile) keeps the animation.
|
|
370
|
+
if (mobile)
|
|
322
371
|
this.rebuildChart();
|
|
372
|
+
else if (this.needsLayoutRebuild())
|
|
373
|
+
this.rebuildChart(true);
|
|
323
374
|
}
|
|
324
|
-
|
|
375
|
+
/**
|
|
376
|
+
* @param silent redraw for a new size only — same data, no appear animation,
|
|
377
|
+
* and the appear window is not re-armed.
|
|
378
|
+
*/
|
|
379
|
+
rebuildChart(silent = false) {
|
|
325
380
|
if (this.destroyed)
|
|
326
381
|
return;
|
|
327
382
|
this.isEmpty = this.checkEmpty();
|
|
@@ -329,7 +384,7 @@ class ChartComponent {
|
|
|
329
384
|
this.liveChart?.clear();
|
|
330
385
|
return;
|
|
331
386
|
}
|
|
332
|
-
const options = this.buildOptions();
|
|
387
|
+
const options = silent ? { ...this.buildOptions(), animation: false } : this.buildOptions();
|
|
333
388
|
const chart = this.liveChart;
|
|
334
389
|
if (!chart) {
|
|
335
390
|
if (this.awaitingFirstInit) {
|
|
@@ -347,7 +402,8 @@ class ChartComponent {
|
|
|
347
402
|
this.hasOptions = true;
|
|
348
403
|
chart.clear();
|
|
349
404
|
chart.setOption(options, true);
|
|
350
|
-
|
|
405
|
+
if (!silent)
|
|
406
|
+
this.markAppear();
|
|
351
407
|
}
|
|
352
408
|
checkEmpty() {
|
|
353
409
|
const type = this.detectType();
|
|
@@ -466,7 +522,7 @@ class ChartComponent {
|
|
|
466
522
|
series: [
|
|
467
523
|
{
|
|
468
524
|
type: 'pie',
|
|
469
|
-
radius:
|
|
525
|
+
radius: this.pieRadius(),
|
|
470
526
|
center: layout.pieCenter,
|
|
471
527
|
data,
|
|
472
528
|
label: {
|
|
@@ -486,46 +542,62 @@ class ChartComponent {
|
|
|
486
542
|
},
|
|
487
543
|
},
|
|
488
544
|
],
|
|
489
|
-
// Centre figure
|
|
490
|
-
//
|
|
491
|
-
//
|
|
545
|
+
// Centre figure. With the built-in legend the ring sits off-centre
|
|
546
|
+
// (layout.pieCenter in px of containerWidth), so the texts are placed
|
|
547
|
+
// by x/y in pixels — graphic left/top PERCENTAGES place the element's
|
|
548
|
+
// bounding-box EDGE and mis-centred the total. Without the legend the
|
|
549
|
+
// ring is at 50%/50% and the pair goes in a group with the KEYWORDS
|
|
550
|
+
// left: 'center' / top: 'middle', which ECharts re-lays out on
|
|
551
|
+
// `resize()` — so a legend-less donut never needs a rebuild to stay
|
|
552
|
+
// centred (15/09).
|
|
492
553
|
graphic: (() => {
|
|
493
|
-
const
|
|
494
|
-
const
|
|
495
|
-
const
|
|
496
|
-
|
|
497
|
-
{
|
|
554
|
+
const centre = this.pieCentreAnchors();
|
|
555
|
+
const font = this.resolvedFontFamily();
|
|
556
|
+
const totalText = {
|
|
557
|
+
type: 'text',
|
|
558
|
+
style: {
|
|
559
|
+
text: total.toString(),
|
|
560
|
+
fontSize: this.pieCenterValueSize(),
|
|
561
|
+
fontWeight: 600,
|
|
562
|
+
fontFamily: font,
|
|
563
|
+
fill: FxUtils.convertColorFromVariable('--text-primary'),
|
|
564
|
+
align: 'center',
|
|
565
|
+
verticalAlign: 'middle',
|
|
566
|
+
},
|
|
567
|
+
};
|
|
568
|
+
const captionText = this.pieCenterLabel()
|
|
569
|
+
? {
|
|
498
570
|
type: 'text',
|
|
499
|
-
// Shift the total up when a caption sits under it.
|
|
500
|
-
x: cx,
|
|
501
|
-
y: cy + (this.pieCenterLabel() ? -9 : 0),
|
|
502
571
|
style: {
|
|
503
|
-
text:
|
|
504
|
-
fontSize:
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
fill: FxUtils.convertColorFromVariable('--text-primary'),
|
|
572
|
+
text: this.pieCenterLabel(),
|
|
573
|
+
fontSize: 11,
|
|
574
|
+
fontFamily: font,
|
|
575
|
+
fill: FxUtils.convertColorFromVariable('--text-secondary'),
|
|
508
576
|
align: 'center',
|
|
509
577
|
verticalAlign: 'middle',
|
|
510
578
|
},
|
|
511
|
-
}
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
579
|
+
}
|
|
580
|
+
: undefined;
|
|
581
|
+
if (!this.showPieLegend()) {
|
|
582
|
+
return [
|
|
583
|
+
{
|
|
584
|
+
type: 'group',
|
|
585
|
+
left: 'center',
|
|
586
|
+
top: 'middle',
|
|
587
|
+
silent: true,
|
|
588
|
+
children: [
|
|
589
|
+
{ ...totalText, x: 0, y: centre.total },
|
|
590
|
+
...(captionText ? [{ ...captionText, x: 0, y: centre.caption }] : []),
|
|
591
|
+
],
|
|
592
|
+
},
|
|
593
|
+
];
|
|
594
|
+
}
|
|
595
|
+
const cx = (this.containerWidth || 400) * (parseFloat(layout.pieCenter[0]) / 100);
|
|
596
|
+
const ch = this.ref.nativeElement.clientHeight || 300;
|
|
597
|
+
const cy = ch * (parseFloat(layout.pieCenter[1]) / 100);
|
|
598
|
+
return [
|
|
599
|
+
{ ...totalText, x: cx, y: cy + centre.total },
|
|
600
|
+
...(captionText ? [{ ...captionText, x: cx, y: cy + centre.caption }] : []),
|
|
529
601
|
];
|
|
530
602
|
})(),
|
|
531
603
|
};
|
|
@@ -568,7 +640,7 @@ class ChartComponent {
|
|
|
568
640
|
name: data?.currentLabel || 'Current',
|
|
569
641
|
data: data?.current || [],
|
|
570
642
|
smooth: true,
|
|
571
|
-
|
|
643
|
+
...this.pointMarks('top', accent),
|
|
572
644
|
lineStyle: { width: 2, color: accent },
|
|
573
645
|
itemStyle: { color: accent },
|
|
574
646
|
areaStyle: {
|
|
@@ -589,7 +661,9 @@ class ChartComponent {
|
|
|
589
661
|
name: data?.previousLabel || 'Previous',
|
|
590
662
|
data: data.previous,
|
|
591
663
|
smooth: true,
|
|
592
|
-
|
|
664
|
+
// Labels below this line, above the other one: two series sharing an
|
|
665
|
+
// x tick would otherwise print their numbers on top of each other.
|
|
666
|
+
...this.pointMarks('bottom', FxUtils.convertColorFromVariable('--text-placeholder', 1, { legacyCommaSyntax: true })),
|
|
593
667
|
lineStyle: {
|
|
594
668
|
width: 1.5,
|
|
595
669
|
type: 'dashed',
|
|
@@ -696,14 +770,77 @@ class ChartComponent {
|
|
|
696
770
|
return 'pie';
|
|
697
771
|
return 'bar';
|
|
698
772
|
}
|
|
773
|
+
/**
|
|
774
|
+
* Marker and value label for one line series, per `showPointLabels`.
|
|
775
|
+
*
|
|
776
|
+
* Returns the OFF state explicitly (`symbol: 'none'`) rather than nothing, so
|
|
777
|
+
* the caller spreads one object either way and the default stays exactly what
|
|
778
|
+
* it was before this input existed.
|
|
779
|
+
*/
|
|
780
|
+
pointMarks(position, color) {
|
|
781
|
+
if (!this.showPointLabels())
|
|
782
|
+
return { symbol: 'none' };
|
|
783
|
+
return {
|
|
784
|
+
symbol: 'circle',
|
|
785
|
+
symbolSize: 6,
|
|
786
|
+
showSymbol: true,
|
|
787
|
+
label: {
|
|
788
|
+
show: true,
|
|
789
|
+
position,
|
|
790
|
+
distance: 7,
|
|
791
|
+
fontSize: this.labelFontSize,
|
|
792
|
+
fontFamily: this.resolvedFontFamily(),
|
|
793
|
+
color,
|
|
794
|
+
},
|
|
795
|
+
};
|
|
796
|
+
}
|
|
797
|
+
/**
|
|
798
|
+
* Where the donut's centre total and its caption sit, relative to the ring's
|
|
799
|
+
* centre.
|
|
800
|
+
*
|
|
801
|
+
* MEASURED, not guessed. Both numbers below were read off a render at two
|
|
802
|
+
* sizes (glyph ink bounding boxes against the ring's own bounding box):
|
|
803
|
+
* neutral-figure ink is ~0.73 of the font size tall, and an ECharts text with
|
|
804
|
+
* `verticalAlign: 'middle'` places its ANCHOR ~0.095 of the font size BELOW
|
|
805
|
+
* the ink's optical centre — digits have no descender, so the ink rides high
|
|
806
|
+
* in the line box.
|
|
807
|
+
*
|
|
808
|
+
* The line gap is a CONSTANT. That is the whole point of this function: the
|
|
809
|
+
* gap between two lines is typographic and does not scale with the size of
|
|
810
|
+
* the number above it. Deriving it from the font size instead — which the
|
|
811
|
+
* first cut of `pieCenterValueSize` did — inflated a 9.6px gap to 18.2px at
|
|
812
|
+
* 38px type and pushed the pair 4.8px above the ring's centre. The hand-tuned
|
|
813
|
+
* constants it replaced were already 2.5px high at the default size; this
|
|
814
|
+
* lands the block dead centre at every size (verified 24/30/38/48).
|
|
815
|
+
*/
|
|
816
|
+
pieCentreAnchors() {
|
|
817
|
+
const FIGURE_INK = 0.73;
|
|
818
|
+
const MIDDLE_ANCHOR_DROP = 0.095;
|
|
819
|
+
const LINE_GAP = 10;
|
|
820
|
+
const CAPTION_SIZE = 11;
|
|
821
|
+
const value = this.pieCenterValueSize();
|
|
822
|
+
// No caption: one line, its ink centred on the ring.
|
|
823
|
+
if (!this.pieCenterLabel()) {
|
|
824
|
+
return { total: MIDDLE_ANCHOR_DROP * value, caption: 0 };
|
|
825
|
+
}
|
|
826
|
+
const valueInk = FIGURE_INK * value;
|
|
827
|
+
const captionInk = FIGURE_INK * CAPTION_SIZE;
|
|
828
|
+
const blockHeight = valueInk + LINE_GAP + captionInk;
|
|
829
|
+
return {
|
|
830
|
+
total: -blockHeight / 2 + valueInk / 2 + MIDDLE_ANCHOR_DROP * value,
|
|
831
|
+
caption: blockHeight / 2 - captionInk / 2 + MIDDLE_ANCHOR_DROP * CAPTION_SIZE,
|
|
832
|
+
};
|
|
833
|
+
}
|
|
699
834
|
buildOptions() {
|
|
700
835
|
const type = this.detectType();
|
|
701
836
|
const palette = this.resolvePalette();
|
|
702
837
|
const base = this.registry[type]?.(this.data(), palette) || {};
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
838
|
+
// DEEP merge, not a spread. A shallow one replaced whole branches, so
|
|
839
|
+
// `[options]="{series:[{label:…}]}"` threw away the recipe's own series —
|
|
840
|
+
// data included — and there was no way to adjust one nested detail from
|
|
841
|
+
// outside. Arrays merge BY INDEX for the same reason: a caller tweaking
|
|
842
|
+
// series 0 must not have to restate series 1.
|
|
843
|
+
return deepMergeOptions(base, this.options());
|
|
707
844
|
}
|
|
708
845
|
categoryAxis(categories) {
|
|
709
846
|
return {
|
|
@@ -770,15 +907,49 @@ class ChartComponent {
|
|
|
770
907
|
});
|
|
771
908
|
}
|
|
772
909
|
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 });
|
|
773
|
-
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 [autoResize]=\"false\"\n [loading]=\"loading()\"\n (chartInit)=\"onChartInit($event)\"\n (chartClick)=\"onChartClick($event)\"\n class=\"fx-chart\"\n [style.height]=\"height()\"\n ></div>\n } @else {\n <!-- Until echarts is loaded + initialised (or the element scrolls into view)\n the panel keeps the chart's footprint with a quiet pulsing sketch\n instead of an empty box (anh T\u00FA 04/09: \"the page is too blank\"). -->\n <div class=\"chart-placeholder\" aria-hidden=\"true\">\n @for (h of placeholderBars; track $index) {\n <span class=\"chart-placeholder__bar\" [style.height.%]=\"h\"></span>\n }\n </div>\n }\n</div>\n", styles: ["@charset \"UTF-8\";: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}.chart-placeholder{position:absolute;inset:0;display:flex;align-items:flex-end;gap:6px;padding:12px 8px 22px;animation:chart-placeholder-pulse 1.6s ease-in-out infinite}.chart-placeholder__bar{flex:1;border-radius:3px 3px 0 0;background:rgb(var(--og-track))}@keyframes chart-placeholder-pulse{0%,to{opacity:1}50%{opacity:.45}}@media(prefers-reduced-motion:reduce){.chart-placeholder{animation:none}}\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 });
|
|
910
|
+
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 }, pieCenterValueSize: { classPropertyName: "pieCenterValueSize", publicName: "pieCenterValueSize", isSignal: true, isRequired: false, transformFunction: null }, pieRadius: { classPropertyName: "pieRadius", publicName: "pieRadius", 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 }, showPointLabels: { classPropertyName: "showPointLabels", publicName: "showPointLabels", 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 [autoResize]=\"false\"\n [loading]=\"loading()\"\n (chartInit)=\"onChartInit($event)\"\n (chartClick)=\"onChartClick($event)\"\n class=\"fx-chart\"\n [style.height]=\"height()\"\n ></div>\n } @else {\n <!-- Until echarts is loaded + initialised (or the element scrolls into view)\n the panel keeps the chart's footprint with a quiet pulsing sketch\n instead of an empty box (anh T\u00FA 04/09: \"the page is too blank\"). -->\n <div class=\"chart-placeholder\" aria-hidden=\"true\">\n @for (h of placeholderBars; track $index) {\n <span class=\"chart-placeholder__bar\" [style.height.%]=\"h\"></span>\n }\n </div>\n }\n</div>\n", styles: ["@charset \"UTF-8\";: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}.chart-placeholder{position:absolute;inset:0;display:flex;align-items:flex-end;gap:6px;padding:12px 8px 22px;animation:chart-placeholder-pulse 1.6s ease-in-out infinite}.chart-placeholder__bar{flex:1;border-radius:3px 3px 0 0;background:rgb(var(--og-track))}@keyframes chart-placeholder-pulse{0%,to{opacity:1}50%{opacity:.45}}@media(prefers-reduced-motion:reduce){.chart-placeholder{animation:none}}\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 });
|
|
774
911
|
}
|
|
775
912
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.20", ngImport: i0, type: ChartComponent, decorators: [{
|
|
776
913
|
type: Component,
|
|
777
914
|
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 [autoResize]=\"false\"\n [loading]=\"loading()\"\n (chartInit)=\"onChartInit($event)\"\n (chartClick)=\"onChartClick($event)\"\n class=\"fx-chart\"\n [style.height]=\"height()\"\n ></div>\n } @else {\n <!-- Until echarts is loaded + initialised (or the element scrolls into view)\n the panel keeps the chart's footprint with a quiet pulsing sketch\n instead of an empty box (anh T\u00FA 04/09: \"the page is too blank\"). -->\n <div class=\"chart-placeholder\" aria-hidden=\"true\">\n @for (h of placeholderBars; track $index) {\n <span class=\"chart-placeholder__bar\" [style.height.%]=\"h\"></span>\n }\n </div>\n }\n</div>\n", styles: ["@charset \"UTF-8\";: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}.chart-placeholder{position:absolute;inset:0;display:flex;align-items:flex-end;gap:6px;padding:12px 8px 22px;animation:chart-placeholder-pulse 1.6s ease-in-out infinite}.chart-placeholder__bar{flex:1;border-radius:3px 3px 0 0;background:rgb(var(--og-track))}@keyframes chart-placeholder-pulse{0%,to{opacity:1}50%{opacity:.45}}@media(prefers-reduced-motion:reduce){.chart-placeholder{animation:none}}\n"] }]
|
|
778
|
-
}], 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: [{
|
|
915
|
+
}], 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 }] }], pieCenterValueSize: [{ type: i0.Input, args: [{ isSignal: true, alias: "pieCenterValueSize", required: false }] }], pieRadius: [{ type: i0.Input, args: [{ isSignal: true, alias: "pieRadius", 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 }] }], showPointLabels: [{ type: i0.Input, args: [{ isSignal: true, alias: "showPointLabels", required: false }] }], chartClick: [{ type: i0.Output, args: ["chartClick"] }], chartInit: [{ type: i0.Output, args: ["chartInit"] }], onThemeChange: [{
|
|
779
916
|
type: HostListener,
|
|
780
917
|
args: ['window:theme-changed']
|
|
781
918
|
}] } });
|
|
919
|
+
/**
|
|
920
|
+
* Recursively merges an override onto a base ECharts option tree.
|
|
921
|
+
*
|
|
922
|
+
* Plain objects merge key by key; arrays merge BY INDEX, so overriding one
|
|
923
|
+
* series does not delete its siblings; anything else — a scalar, a Date, a
|
|
924
|
+
* colour-stop function — is replaced whole, because a half-merged value of
|
|
925
|
+
* those types has no meaning. `undefined` in the override is skipped so an
|
|
926
|
+
* unset field cannot erase a built-in.
|
|
927
|
+
*/
|
|
928
|
+
function deepMergeOptions(base, override) {
|
|
929
|
+
if (override === undefined)
|
|
930
|
+
return base;
|
|
931
|
+
if (Array.isArray(base) && Array.isArray(override)) {
|
|
932
|
+
const out = [...base];
|
|
933
|
+
override.forEach((value, i) => {
|
|
934
|
+
out[i] = i < base.length ? deepMergeOptions(base[i], value) : value;
|
|
935
|
+
});
|
|
936
|
+
return out;
|
|
937
|
+
}
|
|
938
|
+
if (isPlainObject(base) && isPlainObject(override)) {
|
|
939
|
+
const out = { ...base };
|
|
940
|
+
for (const [key, value] of Object.entries(override)) {
|
|
941
|
+
out[key] = key in base ? deepMergeOptions(base[key], value) : value;
|
|
942
|
+
}
|
|
943
|
+
return out;
|
|
944
|
+
}
|
|
945
|
+
return override;
|
|
946
|
+
}
|
|
947
|
+
function isPlainObject(value) {
|
|
948
|
+
return (typeof value === 'object' &&
|
|
949
|
+
value !== null &&
|
|
950
|
+
!Array.isArray(value) &&
|
|
951
|
+
(Object.getPrototypeOf(value) === Object.prototype || Object.getPrototypeOf(value) === null));
|
|
952
|
+
}
|
|
782
953
|
|
|
783
954
|
let pending;
|
|
784
955
|
/**
|