cats-charts 0.0.52 → 0.0.53

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.
@@ -31,16 +31,232 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImpo
31
31
  class Charts {
32
32
  legendSelected = {};
33
33
  defaultColors = ['#22c55e', '#3b82f6', '#ef4444', '#6b7280'];
34
+ // buildGraphicLegend(total: number | null, config: any, chartInstance: any, chartType?: string) {
35
+ // const pillHeight = 28;
36
+ // const spacing = 14;
37
+ // const indicatorWidth = 12;
38
+ // const padding = 8;
39
+ // const font = '14px sans-serif';
40
+ // const containerWidth = chartInstance?.getWidth?.() ?? 800;
41
+ // const maxWidth = containerWidth * (config?.legendContainerWidth ?? 0.7);
42
+ // const selectedMap = chartInstance?.getOption()?.legend?.[0]?.selected || {};
43
+ // let colorIndex = 0;
44
+ // const pills = (
45
+ // chartType === 'line' || chartType === 'bar' || chartType === 'scatter'
46
+ // ? config?.series
47
+ // : config?.data
48
+ // )?.map((item: any) => {
49
+ // const hasCustomColors = Array.isArray(config?.colors) && config?.colors?.length > 0;
50
+ // const color =
51
+ // total === 0
52
+ // ? '#E6E7E8'
53
+ // : (item?.itemStyle?.color ??
54
+ // (hasCustomColors
55
+ // ? config?.colors[colorIndex % config?.colors?.length]
56
+ // : this.defaultColors[colorIndex % this.defaultColors.length]));
57
+ // if (
58
+ // !item?.itemStyle?.color ||
59
+ // (item?.itemStyle?.color && Object.keys(selectedMap).length && !selectedMap[item.name])
60
+ // ) {
61
+ // colorIndex += 1;
62
+ // }
63
+ // const nameRect = echarts.format.getTextRect(item.name, font);
64
+ // const valueRect =
65
+ // chartType === 'line' || chartType === 'bar' || chartType === 'scatter'
66
+ // ? { width: 0 }
67
+ // : echarts.format.getTextRect(
68
+ // String(config?.valueFormatter ? config.valueFormatter(item.value) : item.value),
69
+ // font,
70
+ // );
71
+ // const pillWidth =
72
+ // padding +
73
+ // indicatorWidth +
74
+ // 6 +
75
+ // nameRect.width +
76
+ // (chartType === 'line' || chartType === 'bar' || chartType === 'scatter'
77
+ // ? 0
78
+ // : 8 + valueRect.width) +
79
+ // padding;
80
+ // return { item, color, pillWidth };
81
+ // });
82
+ // // 🔹 Build rows dynamically
83
+ // const rows: any[] = [];
84
+ // let currentRow: any[] = [];
85
+ // let rowWidth = 0;
86
+ // pills?.forEach((pill: any) => {
87
+ // if (rowWidth + pill.pillWidth > maxWidth && currentRow.length) {
88
+ // rows.push({ pills: currentRow, width: rowWidth });
89
+ // currentRow = [];
90
+ // rowWidth = 0;
91
+ // }
92
+ // currentRow.push(pill);
93
+ // rowWidth += pill.pillWidth + spacing;
94
+ // });
95
+ // if (currentRow.length) rows.push({ pills: currentRow, width: rowWidth });
96
+ // // 🔹 Build graphic children
97
+ // let yOffset = 0;
98
+ // const children: any[] = [];
99
+ // rows?.forEach((row) => {
100
+ // let xOffset =
101
+ // config?.legendAlign === 'center' && config?.legendDirection !== 'vertical'
102
+ // ? (maxWidth - row.width) / 2
103
+ // : maxWidth;
104
+ // row?.pills?.forEach((pill: any) => {
105
+ // const isSelected = selectedMap[pill.item.name] !== false;
106
+ // const isDisabled = pill.item.value === 0;
107
+ // children.push({
108
+ // type: 'group',
109
+ // left: xOffset,
110
+ // top: yOffset,
111
+ // width: pill.pillWidth,
112
+ // height: pillHeight,
113
+ // cursor: isDisabled ? 'default' : 'pointer',
114
+ // onclick: () => {
115
+ // if (isDisabled) return;
116
+ // chartInstance.dispatchAction({
117
+ // type: 'legendToggleSelect',
118
+ // name: pill.item.name,
119
+ // });
120
+ // chartInstance.setOption({
121
+ // graphic: this.buildGraphicLegend(total, config, chartInstance, chartType),
122
+ // });
123
+ // },
124
+ // children: [
125
+ // {
126
+ // type: 'rect',
127
+ // shape: {
128
+ // width: pill.pillWidth,
129
+ // height: pillHeight,
130
+ // r: 4,
131
+ // },
132
+ // style: {
133
+ // fill:
134
+ // isSelected && !isDisabled
135
+ // ? this.hexToRgba(pill.color, total === 0 ? 0.3 : 0.03)
136
+ // : '#F2F2F7',
137
+ // stroke: isSelected && !isDisabled ? this.hexToRgba(pill.color, 0.3) : '#D1D1D6',
138
+ // lineWidth: 1,
139
+ // },
140
+ // },
141
+ // {
142
+ // type: 'rect',
143
+ // shape: { width: 12, height: config?.legendIndicatorHeight ?? 12, r: 2 },
144
+ // left: padding,
145
+ // top: (pillHeight - (config?.legendIndicatorHeight ?? 12)) / 2,
146
+ // style: {
147
+ // fill: isSelected && !isDisabled ? pill.color : '#C7C7CC',
148
+ // },
149
+ // },
150
+ // {
151
+ // type: 'text',
152
+ // left: padding + indicatorWidth + 6,
153
+ // top: (pillHeight - indicatorWidth) / 2,
154
+ // style: {
155
+ // text: `${pill.item.name}${chartType === 'line' || chartType === 'bar' || chartType === 'scatter' ? '' : ':'}`,
156
+ // fontSize: 14,
157
+ // fontWeight: 400,
158
+ // fill: isSelected && !isDisabled && total !== 0 ? '#1C1C1E' : '#81858A',
159
+ // textVerticalAlign: 'middle',
160
+ // },
161
+ // },
162
+ // ...(chartType === 'line' || chartType === 'bar' || chartType === 'scatter'
163
+ // ? []
164
+ // : [
165
+ // {
166
+ // type: 'text',
167
+ // right: padding,
168
+ // top: (pillHeight - indicatorWidth) / 2,
169
+ // style: {
170
+ // text:
171
+ // total === 0
172
+ // ? '-'
173
+ // : config?.valueFormatter
174
+ // ? config.valueFormatter(pill.item.value)
175
+ // : pill.item.value,
176
+ // fontSize: 14,
177
+ // fontWeight: 500,
178
+ // fill: isSelected && !isDisabled ? '#1C1C1E' : '#81858A',
179
+ // textAlign: 'right',
180
+ // textVerticalAlign: 'middle',
181
+ // },
182
+ // },
183
+ // ]),
184
+ // ],
185
+ // });
186
+ // xOffset += pill.pillWidth + spacing;
187
+ // });
188
+ // yOffset += pillHeight + spacing;
189
+ // });
190
+ // // 🔹 Position logic
191
+ // const positionConfig: any = {};
192
+ // switch (config?.legendPosition) {
193
+ // case 'top':
194
+ // positionConfig.top = config?.legendEdgeGap?.top ?? 20;
195
+ // break;
196
+ // case 'bottom':
197
+ // positionConfig.bottom = config?.legendEdgeGap?.bottom ?? 20;
198
+ // break;
199
+ // case 'left':
200
+ // positionConfig.left = config?.legendEdgeGap?.left ?? 20;
201
+ // break;
202
+ // case 'right':
203
+ // positionConfig.right = config?.legendEdgeGap?.right ?? 20;
204
+ // break;
205
+ // }
206
+ // // ALIGNMENT (Cross-Axis)
207
+ // if (config?.legendPosition === 'top' || config?.legendPosition === 'bottom') {
208
+ // // Horizontal area → align horizontally
209
+ // if (config?.legendAlign === 'center') {
210
+ // positionConfig.left = 'center';
211
+ // } else if (config?.legendAlign === 'right') {
212
+ // positionConfig.right = config?.legendEdgeGap?.right ?? 20;
213
+ // } else {
214
+ // positionConfig.left = config?.legendEdgeGap?.left ?? 20;
215
+ // }
216
+ // } else {
217
+ // // Vertical area → align vertically
218
+ // if (config?.legendAlign === 'center') {
219
+ // positionConfig.top = 'middle';
220
+ // } else if (config?.legendAlign === 'right') {
221
+ // positionConfig.bottom = config?.legendEdgeGap?.bottom ?? 20;
222
+ // } else {
223
+ // positionConfig.top = config?.legendEdgeGap?.top ?? 20;
224
+ // }
225
+ // }
226
+ // return [
227
+ // {
228
+ // id: 'customLegend',
229
+ // type: 'group',
230
+ // ...positionConfig,
231
+ // width: maxWidth,
232
+ // height: yOffset,
233
+ // children,
234
+ // },
235
+ // ];
236
+ // }
34
237
  buildGraphicLegend(total, config, chartInstance, chartType) {
35
238
  const pillHeight = 28;
36
239
  const spacing = 14;
37
240
  const indicatorWidth = 12;
38
241
  const padding = 8;
39
242
  const font = '14px sans-serif';
243
+ // =========================================
244
+ // CONTAINER + AVAILABLE WIDTH
245
+ // =========================================
40
246
  const containerWidth = chartInstance?.getWidth?.() ?? 800;
41
- const maxWidth = containerWidth * (config?.legendContainerWidth ?? 0.7);
247
+ const leftGap = config?.legendEdgeGap?.left ?? 0;
248
+ const rightGap = config?.legendEdgeGap?.right ?? 0;
249
+ // actual usable width after gaps
250
+ const usableWidth = containerWidth - leftGap - rightGap;
251
+ const maxWidth = usableWidth * (config?.legendContainerWidth ?? 0.7);
252
+ // =========================================
253
+ // SELECTED LEGEND MAP
254
+ // =========================================
42
255
  const selectedMap = chartInstance?.getOption()?.legend?.[0]?.selected || {};
43
256
  let colorIndex = 0;
257
+ // =========================================
258
+ // BUILD PILLS
259
+ // =========================================
44
260
  const pills = (chartType === 'line' || chartType === 'bar' || chartType === 'scatter'
45
261
  ? config?.series
46
262
  : config?.data)?.map((item) => {
@@ -67,30 +283,58 @@ class Charts {
67
283
  ? 0
68
284
  : 8 + valueRect.width) +
69
285
  padding;
70
- return { item, color, pillWidth };
286
+ return {
287
+ item,
288
+ color,
289
+ pillWidth,
290
+ };
71
291
  });
72
- // 🔹 Build rows dynamically
292
+ // =========================================
293
+ // BUILD ROWS PERFECTLY
294
+ // =========================================
73
295
  const rows = [];
74
296
  let currentRow = [];
75
297
  let rowWidth = 0;
76
298
  pills?.forEach((pill) => {
77
- if (rowWidth + pill.pillWidth > maxWidth && currentRow.length) {
78
- rows.push({ pills: currentRow, width: rowWidth });
79
- currentRow = [];
80
- rowWidth = 0;
299
+ const additionalWidth = currentRow.length > 0 ? spacing : 0;
300
+ // break row if width exceeded
301
+ if (currentRow.length && rowWidth + additionalWidth + pill.pillWidth > maxWidth) {
302
+ rows.push({
303
+ pills: currentRow,
304
+ width: rowWidth,
305
+ });
306
+ currentRow = [pill];
307
+ rowWidth = pill.pillWidth;
308
+ }
309
+ else {
310
+ rowWidth += additionalWidth + pill.pillWidth;
311
+ currentRow.push(pill);
81
312
  }
82
- currentRow.push(pill);
83
- rowWidth += pill.pillWidth + spacing;
84
313
  });
85
- if (currentRow.length)
86
- rows.push({ pills: currentRow, width: rowWidth });
87
- // 🔹 Build graphic children
314
+ if (currentRow.length) {
315
+ rows.push({
316
+ pills: currentRow,
317
+ width: rowWidth,
318
+ });
319
+ }
320
+ // =========================================
321
+ // BUILD GRAPHIC CHILDREN
322
+ // =========================================
88
323
  let yOffset = 0;
89
324
  const children = [];
90
325
  rows?.forEach((row) => {
91
- let xOffset = config?.legendAlign === 'center' && config?.legendDirection !== 'vertical'
92
- ? (maxWidth - row.width) / 2
93
- : maxWidth;
326
+ let xOffset = 0;
327
+ // =====================================
328
+ // HORIZONTAL ALIGNMENTS
329
+ // =====================================
330
+ if (config?.legendDirection !== 'vertical') {
331
+ if (config?.legendAlign === 'center') {
332
+ xOffset = Math.max((maxWidth - row.width) / 2, 0);
333
+ }
334
+ else if (config?.legendAlign === 'right') {
335
+ xOffset = Math.max(maxWidth - row.width, 0);
336
+ }
337
+ }
94
338
  row?.pills?.forEach((pill) => {
95
339
  const isSelected = selectedMap[pill.item.name] !== false;
96
340
  const isDisabled = pill.item.value === 0;
@@ -113,6 +357,9 @@ class Charts {
113
357
  });
114
358
  },
115
359
  children: [
360
+ // =================================
361
+ // BACKGROUND
362
+ // =================================
116
363
  {
117
364
  type: 'rect',
118
365
  shape: {
@@ -128,15 +375,25 @@ class Charts {
128
375
  lineWidth: 1,
129
376
  },
130
377
  },
378
+ // =================================
379
+ // INDICATOR
380
+ // =================================
131
381
  {
132
382
  type: 'rect',
133
- shape: { width: 12, height: config?.legendIndicatorHeight ?? 12, r: 2 },
383
+ shape: {
384
+ width: 12,
385
+ height: config?.legendIndicatorHeight ?? 12,
386
+ r: 2,
387
+ },
134
388
  left: padding,
135
389
  top: (pillHeight - (config?.legendIndicatorHeight ?? 12)) / 2,
136
390
  style: {
137
391
  fill: isSelected && !isDisabled ? pill.color : '#C7C7CC',
138
392
  },
139
393
  },
394
+ // =================================
395
+ // LABEL
396
+ // =================================
140
397
  {
141
398
  type: 'text',
142
399
  left: padding + indicatorWidth + 6,
@@ -149,6 +406,9 @@ class Charts {
149
406
  textVerticalAlign: 'middle',
150
407
  },
151
408
  },
409
+ // =================================
410
+ // VALUE
411
+ // =================================
152
412
  ...(chartType === 'line' || chartType === 'bar' || chartType === 'scatter'
153
413
  ? []
154
414
  : [
@@ -176,47 +436,54 @@ class Charts {
176
436
  });
177
437
  yOffset += pillHeight + spacing;
178
438
  });
179
- // 🔹 Position logic
439
+ // =========================================
440
+ // POSITION CONFIG
441
+ // =========================================
180
442
  const positionConfig = {};
181
443
  switch (config?.legendPosition) {
182
444
  case 'top':
183
- positionConfig.top = 20;
445
+ positionConfig.top = config?.legendEdgeGap?.top ?? 20;
184
446
  break;
185
447
  case 'bottom':
186
- positionConfig.bottom = 20;
448
+ positionConfig.bottom = config?.legendEdgeGap?.bottom ?? 20;
187
449
  break;
188
450
  case 'left':
189
- positionConfig.left = 20;
451
+ positionConfig.left = config?.legendEdgeGap?.left ?? 20;
190
452
  break;
191
453
  case 'right':
192
- positionConfig.right = 20;
454
+ positionConfig.right = config?.legendEdgeGap?.right ?? 20;
193
455
  break;
194
456
  }
195
- // ALIGNMENT (Cross-Axis)
457
+ // =========================================
458
+ // CROSS AXIS ALIGNMENT
459
+ // =========================================
196
460
  if (config?.legendPosition === 'top' || config?.legendPosition === 'bottom') {
197
- // Horizontal area → align horizontally
461
+ // horizontal placement
198
462
  if (config?.legendAlign === 'center') {
199
463
  positionConfig.left = 'center';
200
464
  }
201
465
  else if (config?.legendAlign === 'right') {
202
- positionConfig.right = 20;
466
+ positionConfig.right = config?.legendEdgeGap?.right ?? 20;
203
467
  }
204
468
  else {
205
- positionConfig.left = 20;
469
+ positionConfig.left = config?.legendEdgeGap?.left ?? 20;
206
470
  }
207
471
  }
208
472
  else {
209
- // Vertical area → align vertically
473
+ // vertical placement
210
474
  if (config?.legendAlign === 'center') {
211
475
  positionConfig.top = 'middle';
212
476
  }
213
477
  else if (config?.legendAlign === 'right') {
214
- positionConfig.bottom = 20;
478
+ positionConfig.bottom = config?.legendEdgeGap?.bottom ?? 20;
215
479
  }
216
480
  else {
217
- positionConfig.top = 20;
481
+ positionConfig.top = config?.legendEdgeGap?.top ?? 20;
218
482
  }
219
483
  }
484
+ // =========================================
485
+ // RETURN GRAPHIC
486
+ // =========================================
220
487
  return [
221
488
  {
222
489
  id: 'customLegend',
@@ -657,6 +924,51 @@ class LinesChart {
657
924
  }
658
925
  this.buildOption();
659
926
  }
927
+ previousConfig = {
928
+ xAxisName: '',
929
+ yAxisName: '',
930
+ secondaryYAxisName: '',
931
+ title: '',
932
+ };
933
+ ngDoCheck() {
934
+ if (!this.chartInstance || !this.config)
935
+ return;
936
+ const updateOption = {};
937
+ if (this.previousConfig.xAxisName !== this.config.xAxisName) {
938
+ this.previousConfig.xAxisName = this.config.xAxisName;
939
+ updateOption.xAxis = {
940
+ name: this.config?.xAxisName || '',
941
+ };
942
+ }
943
+ if (this.previousConfig.yAxisName !== this.config.yAxisName) {
944
+ this.previousConfig.yAxisName = this.config.yAxisName;
945
+ updateOption.yAxis = [
946
+ {
947
+ name: this.config?.yAxisName || '',
948
+ },
949
+ ];
950
+ }
951
+ if (this.previousConfig.secondaryYAxisName !== this.config.secondaryYAxisName) {
952
+ this.previousConfig.secondaryYAxisName = this.config.secondaryYAxisName;
953
+ updateOption.yAxis = [
954
+ {
955
+ name: this.config?.yAxisName || '',
956
+ },
957
+ {
958
+ name: this.config?.secondaryYAxisName || '',
959
+ },
960
+ ];
961
+ }
962
+ if (this.previousConfig.title !== this.config.title) {
963
+ this.previousConfig.title = this.config.title;
964
+ updateOption.title = {
965
+ text: this.config?.title || '',
966
+ };
967
+ }
968
+ if (Object.keys(updateOption).length) {
969
+ this.chartInstance.setOption(updateOption);
970
+ }
971
+ }
660
972
  /* ------------------ OPTION BUILDER ------------------ */
661
973
  buildOption() {
662
974
  this.chartOption = {