cats4u-charts 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (54) hide show
  1. package/.editorconfig +17 -0
  2. package/.vscode/extensions.json +4 -0
  3. package/.vscode/launch.json +20 -0
  4. package/.vscode/tasks.json +42 -0
  5. package/README.md +59 -0
  6. package/angular.json +118 -0
  7. package/dist/charts-lib/README.md +63 -0
  8. package/dist/charts-lib/fesm2022/charts-lib.mjs +3418 -0
  9. package/dist/charts-lib/fesm2022/charts-lib.mjs.map +1 -0
  10. package/dist/charts-lib/index.d.ts +110 -0
  11. package/package.json +58 -0
  12. package/projects/charts-lib/README.md +63 -0
  13. package/projects/charts-lib/ng-package.json +8 -0
  14. package/projects/charts-lib/package.json +12 -0
  15. package/projects/charts-lib/src/lib/charts-lib.html +1 -0
  16. package/projects/charts-lib/src/lib/charts-lib.spec.ts +23 -0
  17. package/projects/charts-lib/src/lib/charts-lib.ts +121 -0
  18. package/projects/charts-lib/src/lib/component/area-chart/area-chart.html +1 -0
  19. package/projects/charts-lib/src/lib/component/area-chart/area-chart.scss +0 -0
  20. package/projects/charts-lib/src/lib/component/area-chart/area-chart.spec.ts +23 -0
  21. package/projects/charts-lib/src/lib/component/area-chart/area-chart.ts +266 -0
  22. package/projects/charts-lib/src/lib/component/bar-chart/bar-chart.html +1 -0
  23. package/projects/charts-lib/src/lib/component/bar-chart/bar-chart.scss +0 -0
  24. package/projects/charts-lib/src/lib/component/bar-chart/bar-chart.spec.ts +23 -0
  25. package/projects/charts-lib/src/lib/component/bar-chart/bar-chart.ts +301 -0
  26. package/projects/charts-lib/src/lib/component/line-chart/line-chart.html +1 -0
  27. package/projects/charts-lib/src/lib/component/line-chart/line-chart.scss +0 -0
  28. package/projects/charts-lib/src/lib/component/line-chart/line-chart.spec.ts +23 -0
  29. package/projects/charts-lib/src/lib/component/line-chart/line-chart.ts +266 -0
  30. package/projects/charts-lib/src/lib/modal/charts-lib.modal.ts +79 -0
  31. package/projects/charts-lib/src/lib/services/chart.service.ts +296 -0
  32. package/projects/charts-lib/src/lib/themes/chalk.ts +357 -0
  33. package/projects/charts-lib/src/lib/themes/dark.ts +380 -0
  34. package/projects/charts-lib/src/lib/themes/default.ts +377 -0
  35. package/projects/charts-lib/src/lib/themes/essos.ts +357 -0
  36. package/projects/charts-lib/src/lib/themes/roma.ts +399 -0
  37. package/projects/charts-lib/src/lib/themes/vintage.ts +378 -0
  38. package/projects/charts-lib/src/public-api.ts +2 -0
  39. package/projects/charts-lib/tsconfig.lib.json +14 -0
  40. package/projects/charts-lib/tsconfig.lib.prod.json +11 -0
  41. package/projects/charts-lib/tsconfig.spec.json +15 -0
  42. package/projects/demo-app/public/favicon.ico +0 -0
  43. package/projects/demo-app/src/app/app.config.ts +16 -0
  44. package/projects/demo-app/src/app/app.html +43 -0
  45. package/projects/demo-app/src/app/app.routes.ts +3 -0
  46. package/projects/demo-app/src/app/app.scss +47 -0
  47. package/projects/demo-app/src/app/app.spec.ts +25 -0
  48. package/projects/demo-app/src/app/app.ts +98 -0
  49. package/projects/demo-app/src/index.html +13 -0
  50. package/projects/demo-app/src/main.ts +6 -0
  51. package/projects/demo-app/src/styles.scss +4 -0
  52. package/projects/demo-app/tsconfig.app.json +15 -0
  53. package/projects/demo-app/tsconfig.spec.json +15 -0
  54. package/tsconfig.json +43 -0
@@ -0,0 +1,8 @@
1
+ {
2
+ "$schema": "../../node_modules/ng-packagr/ng-package.schema.json",
3
+ "dest": "../../dist/charts-lib",
4
+ "lib": {
5
+ "entryFile": "src/public-api.ts"
6
+ },
7
+ "assets": ["src/lib/themes/*.js"]
8
+ }
@@ -0,0 +1,12 @@
1
+ {
2
+ "name": "charts-lib",
3
+ "version": "0.0.1",
4
+ "peerDependencies": {
5
+ "@angular/common": "^20.3.0",
6
+ "@angular/core": "^20.3.0"
7
+ },
8
+ "dependencies": {
9
+ "tslib": "^2.3.0"
10
+ },
11
+ "sideEffects": false
12
+ }
@@ -0,0 +1 @@
1
+ <ng-template #chartContainer></ng-template>
@@ -0,0 +1,23 @@
1
+ import { ComponentFixture, TestBed } from '@angular/core/testing';
2
+
3
+ import { ChartsLib } from './charts-lib';
4
+
5
+ describe('ChartsLib', () => {
6
+ let component: ChartsLib;
7
+ let fixture: ComponentFixture<ChartsLib>;
8
+
9
+ beforeEach(async () => {
10
+ await TestBed.configureTestingModule({
11
+ imports: [ChartsLib]
12
+ })
13
+ .compileComponents();
14
+
15
+ fixture = TestBed.createComponent(ChartsLib);
16
+ component = fixture.componentInstance;
17
+ fixture.detectChanges();
18
+ });
19
+
20
+ it('should create', () => {
21
+ expect(component).toBeTruthy();
22
+ });
23
+ });
@@ -0,0 +1,121 @@
1
+ import {
2
+ Component,
3
+ Input,
4
+ OnChanges,
5
+ SimpleChanges,
6
+ ViewChild,
7
+ ViewContainerRef,
8
+ ComponentRef,
9
+ Type,
10
+ Output,
11
+ EventEmitter,
12
+ effect,
13
+ OnInit,
14
+ KeyValueDiffers,
15
+ KeyValueDiffer,
16
+ DoCheck,
17
+ } from '@angular/core';
18
+ import { BarChart } from './component/bar-chart/bar-chart';
19
+ import { LineChart } from './component/line-chart/line-chart';
20
+ import { AreaChart } from './component/area-chart/area-chart';
21
+ import { ChartService } from './services/chart.service';
22
+ import { OptionsConfig, ChartsLibType, ContextMenuListItem } from './modal/charts-lib.modal';
23
+
24
+ type ChartType = 'line' | 'bar' | 'area' | 'pie';
25
+
26
+ @Component({
27
+ selector: 'lib-charts-lib',
28
+ imports: [],
29
+ templateUrl: './charts-lib.html',
30
+ styles: ``,
31
+ })
32
+ export class ChartsLib implements OnInit, OnChanges, DoCheck {
33
+ @ViewChild('chartContainer', { read: ViewContainerRef }) container!: ViewContainerRef;
34
+ @Input() chartType!: ChartType;
35
+ @Input() columns: string[] = [];
36
+ @Input() data: any[][] = [];
37
+ @Input() themeName: ChartsLibType['themeName'] = 'default';
38
+ @Input() chartOptionsConfig: OptionsConfig = {};
39
+ @Input() contextMenuList: ContextMenuListItem[] = [];
40
+ @Output() handleSingleClick = new EventEmitter();
41
+ @Output() handleDrillBy = new EventEmitter();
42
+
43
+ private optionsConfigDiffer!: KeyValueDiffer<string, any>;
44
+ private componentRef: ComponentRef<any> | null = null;
45
+ constructor(private chartService: ChartService, private differs: KeyValueDiffers) {
46
+ effect(() => {
47
+ if (this.chartService.clickEvent().seriesName) {
48
+ this.handleSingleClick.emit(this.chartService.clickEvent());
49
+ }
50
+ });
51
+ effect(() => {
52
+ if (this.chartService.drillByConfig().seriesName) {
53
+ this.handleDrillBy.emit(this.chartService.drillByConfig());
54
+ }
55
+ });
56
+ }
57
+
58
+ ngOnInit(): void {
59
+ this.optionsConfigDiffer = this.differs.find(this.chartOptionsConfig).create();
60
+ }
61
+
62
+ ngAfterViewInit(): void {
63
+ this.loadComponent();
64
+ }
65
+ ngOnChanges(changes: SimpleChanges): void {
66
+ if (changes['chartType']) {
67
+ this.loadComponent();
68
+ }
69
+ if (changes['themeName'] && this.componentRef) {
70
+ this.chartService.setTheme(this.themeName);
71
+ }
72
+ if (changes['data']) {
73
+ this.chartService.setData(this.data);
74
+ }
75
+ if (changes['columns']) {
76
+ this.chartService.setColumns(this.columns);
77
+ }
78
+ if (changes['contextMenuList']) {
79
+ this.chartService.setContextMenuList(this.contextMenuList);
80
+ }
81
+ }
82
+
83
+ ngDoCheck(): void {
84
+ if (this.chartOptionsConfig) {
85
+ const changes = this.optionsConfigDiffer.diff(this.chartOptionsConfig);
86
+ if (changes) {
87
+ this.chartService.setChartOptionsConfig({ ...this.chartOptionsConfig });
88
+ }
89
+ }
90
+ }
91
+
92
+ private loadComponent(): void {
93
+ if (!this.container) {
94
+ return;
95
+ }
96
+ if (this.componentRef) {
97
+ this.componentRef.destroy();
98
+ }
99
+ const componentType = this.getComponentType();
100
+ if (componentType) {
101
+ this.componentRef = this.container.createComponent(componentType);
102
+ }
103
+ }
104
+ private getComponentType(): Type<any> | null {
105
+ switch (this.chartType) {
106
+ case 'bar':
107
+ return BarChart;
108
+ case 'line':
109
+ return LineChart;
110
+ case 'area':
111
+ return AreaChart;
112
+ default:
113
+ return null;
114
+ }
115
+ }
116
+ ngOnDestroy(): void {
117
+ if (this.componentRef) {
118
+ this.componentRef.destroy();
119
+ }
120
+ }
121
+ }
@@ -0,0 +1 @@
1
+ <div #areaChartContainer style="width: 100%; height: 100%"></div>
@@ -0,0 +1,23 @@
1
+ import { ComponentFixture, TestBed } from '@angular/core/testing';
2
+
3
+ import { AreaChart } from './area-chart';
4
+
5
+ describe('AreaChart', () => {
6
+ let component: AreaChart;
7
+ let fixture: ComponentFixture<AreaChart>;
8
+
9
+ beforeEach(async () => {
10
+ await TestBed.configureTestingModule({
11
+ imports: [AreaChart]
12
+ })
13
+ .compileComponents();
14
+
15
+ fixture = TestBed.createComponent(AreaChart);
16
+ component = fixture.componentInstance;
17
+ fixture.detectChanges();
18
+ });
19
+
20
+ it('should create', () => {
21
+ expect(component).toBeTruthy();
22
+ });
23
+ });
@@ -0,0 +1,266 @@
1
+ import {
2
+ Component,
3
+ Input,
4
+ OnInit,
5
+ OnDestroy,
6
+ ChangeDetectionStrategy,
7
+ ChangeDetectorRef,
8
+ ViewChild,
9
+ ElementRef,
10
+ inject,
11
+ effect,
12
+ } from '@angular/core';
13
+ import * as echarts from 'echarts'; // Core ECharts
14
+ import { Subject } from 'rxjs'; // For cleanup
15
+ import { ChartService } from '../../services/chart.service';
16
+ import '../../themes/default';
17
+ import '../../themes/vintage';
18
+ import '../../themes/dark';
19
+ import '../../themes/essos';
20
+ import '../../themes/chalk';
21
+ import '../../themes/roma';
22
+
23
+ @Component({
24
+ selector: 'lib-area-chart',
25
+ imports: [],
26
+ templateUrl: './area-chart.html',
27
+ styleUrl: './area-chart.scss',
28
+ changeDetection: ChangeDetectionStrategy.OnPush,
29
+ })
30
+ export class AreaChart implements OnInit, OnDestroy {
31
+ @Input() title: string = '';
32
+ @Input() height = '600px';
33
+ @Input() enableSampling = true; // Sample data if > threshold for perf
34
+ @Input() sampleThreshold = 10000; // Sample if data > 10k (keep full for 50k if hardware allows)
35
+ @Input() isLoading: boolean = true;
36
+
37
+ @ViewChild('areaChartContainer', { static: false }) chartContainer!: ElementRef<HTMLDivElement>;
38
+
39
+ chartInstance: echarts.ECharts | null = null;
40
+ private chartService = inject(ChartService);
41
+ private destroy$ = new Subject<void>();
42
+ private currentData: any[][] = [];
43
+ private currentColumns: string[] = [];
44
+
45
+ // ECharts options optimized for 50k+ data
46
+ chartOptions = {
47
+ title: {
48
+ text: this.title,
49
+ left: 'center',
50
+ textStyle: { fontSize: 16 },
51
+ },
52
+ tooltip: {
53
+ // Hover info (efficient for large data)
54
+ trigger: 'axis',
55
+ axisPointer: { type: 'shadow' },
56
+ // formatter: (params = [{ name: '', value: '' }]) => {
57
+ // const param = params[0];
58
+ // return `${param.name}<br/>Value: ${param.value}`;
59
+ // },
60
+ },
61
+ legend: { show: false }, // Disable for single series; add if multi-series
62
+ dataZoom: [
63
+ // Critical for large data: Zoom/slider to navigate 50k data points
64
+ {
65
+ type: 'slider', // Bottom slider
66
+ start: 0,
67
+ end: 100, // Initial view: First 10% (e.g., 5k of 50k)
68
+ height: 25,
69
+ bottom: 20,
70
+ textStyle: { fontSize: 12 },
71
+ },
72
+ {
73
+ type: 'inside', // Mouse wheel/pinch zoom
74
+ start: 0,
75
+ end: 10,
76
+ },
77
+ ],
78
+ grid: {
79
+ left: '5%',
80
+ right: '5%',
81
+ bottom: '80px', // Space for dataZoom slider
82
+ containLabel: true,
83
+ },
84
+ xAxis: {
85
+ type: 'category',
86
+ data: [], // Populated dynamically
87
+ axisLabel: { rotate: 45, fontSize: 10 },
88
+ axisTick: { show: false },
89
+ },
90
+ yAxis: {
91
+ type: 'value',
92
+ axisLabel: { fontSize: 12 },
93
+ },
94
+ series: [],
95
+ };
96
+
97
+ constructor(private cdr: ChangeDetectorRef) {
98
+ effect(() => {
99
+ const theme = this.chartService.theme(); // Read signal
100
+ if (this.chartInstance) {
101
+ this.reinitializeChart();
102
+ }
103
+ });
104
+ // Effect for data changes
105
+ effect(() => {
106
+ const data = this.chartService.data(); // Read signal
107
+ const columns = this.chartService.columns();
108
+ this.currentColumns = columns;
109
+ this.currentData = data;
110
+ this.processAndUpdateData();
111
+ });
112
+ }
113
+
114
+ ngOnInit(): void {
115
+ if (this.currentData.length > 0) {
116
+ this.processAndUpdateData();
117
+ }
118
+ }
119
+
120
+ ngAfterViewInit(): void {
121
+ this.initializeChart();
122
+ if (this.currentData.length > 0) {
123
+ this.updateChartWithData();
124
+ }
125
+ this.isLoading = false;
126
+ this.cdr.markForCheck();
127
+ }
128
+
129
+ ngOnDestroy(): void {
130
+ this.destroy$.next();
131
+ this.destroy$.complete();
132
+ this.disposeChart();
133
+ if (this.chartInstance) {
134
+ this.chartInstance.dispose(); // Clean up to free memory
135
+ }
136
+ }
137
+
138
+ private reinitializeChart(): void {
139
+ this.initializeChart();
140
+ if (this.currentData.length > 0) {
141
+ this.updateChartWithData();
142
+ }
143
+ this.cdr.markForCheck();
144
+ }
145
+
146
+ private initializeChart(): void {
147
+ if (!this.chartContainer) {
148
+ console.error('Chart container not found!');
149
+ return;
150
+ }
151
+ const chartDom = this.chartContainer.nativeElement;
152
+ chartDom.innerHTML = '';
153
+
154
+ if (this.chartInstance) {
155
+ this.chartInstance.dispose();
156
+ this.chartInstance = null;
157
+ }
158
+
159
+ this.chartInstance = echarts.init(chartDom, this.chartService.theme()); // Init ECharts
160
+ this.chartInstance.setOption(this.chartOptions);
161
+
162
+ // Responsive: Resize on window change
163
+ window.addEventListener('resize', () => this.chartInstance?.resize(), { passive: true });
164
+
165
+ // Handle dataZoom events (optional: Log zoom changes)
166
+ this.chartInstance.on('dataZoom', (params) => {
167
+ // console.log('Zoomed to:', params.start, params.end);
168
+ });
169
+
170
+ this.chartInstance.on('rendered', () => {
171
+ // console.log('Chart rendered successfully');
172
+ });
173
+
174
+ this.isLoading = false;
175
+ this.cdr.markForCheck();
176
+ }
177
+
178
+ private updateChartWithData(): void {
179
+ this.processData(); // Process without updating yet
180
+ if (this.chartInstance) {
181
+ const updatedOptions: Partial<echarts.EChartsOption> = {
182
+ xAxis: { data: this.getProcessedCategories() },
183
+ series: this.getProcessedValues(),
184
+ };
185
+ this.chartInstance.setOption(updatedOptions, {
186
+ notMerge: false,
187
+ lazyUpdate: true,
188
+ silent: false,
189
+ replaceMerge: ['series'],
190
+ });
191
+ }
192
+ }
193
+
194
+ private processedData: any[][] = [];
195
+ private processedColumns: string[] = [];
196
+ private getProcessedCategories(): string[] {
197
+ return this.processedData.map((d) => d[0]);
198
+ }
199
+ private getProcessedValues(): any[] {
200
+ const columnData = this.processedColumns.slice(1);
201
+ return columnData.map((c, i) => {
202
+ const seriesData = this.processedData.map((d) => d[i + 1]);
203
+ return {
204
+ name: c,
205
+ type: 'line',
206
+ data: seriesData, // Populated dynamically
207
+ large: true, // Enable large mode: Canvas rendering for 50k+ efficiency
208
+ largeThreshold: 5000, // Start large mode at 5k points
209
+ progressive: 300, // Render 300 points per chunk (tune for your hardware)
210
+ progressiveChunkMode: 'sequential', // Sequential rendering for smooth init
211
+ progressiveThreshold: 5000, // Apply progressive above 5k
212
+ animation: true, // Disable initial animation for faster load on large data (re-enable for updates)
213
+ itemStyle: {
214
+ // color: '#5470c6', // line color
215
+ // shadowBlur: 0, // Reduce shadows for perf
216
+ },
217
+ areaStyle: { opacity: 0.2 },
218
+ // emphasis: {
219
+ // // Hover highlight (lightweight)
220
+ // focus: 'series',
221
+ // itemStyle: { opacity: 0.8 },
222
+ // },
223
+ };
224
+ });
225
+ }
226
+ private processData(): void {
227
+ let dataToProcess = this.currentData;
228
+ if (this.enableSampling && this.currentData.length > this.sampleThreshold) {
229
+ const step = Math.ceil(this.currentData.length / this.sampleThreshold);
230
+ dataToProcess = this.currentData.filter((_, index) => index % step === 0);
231
+ console.warn(
232
+ `Sampled data from ${this.currentData.length} to ${dataToProcess.length} points.`
233
+ );
234
+ }
235
+ this.processedData = dataToProcess; // Cache processed
236
+ this.processedColumns = this.currentColumns;
237
+ }
238
+
239
+ private processAndUpdateData(): void {
240
+ this.processData();
241
+ if (this.chartInstance) {
242
+ this.updateChartWithData();
243
+ }
244
+ }
245
+
246
+ private handleResize(): void {
247
+ if (this.chartInstance) {
248
+ this.chartInstance.resize();
249
+ }
250
+ }
251
+
252
+ private disposeChart(): void {
253
+ if (this.chartInstance) {
254
+ this.chartInstance.dispose();
255
+ this.chartInstance = null;
256
+ // console.log('ECharts instance disposed');
257
+ }
258
+ window.removeEventListener('resize', this.handleResize.bind(this));
259
+ }
260
+
261
+ // Public method: Update data externally (e.g., from API)
262
+ updateData(newData: any[][]): void {
263
+ this.currentData = newData;
264
+ this.processAndUpdateData();
265
+ }
266
+ }
@@ -0,0 +1 @@
1
+ <div #barChartContainer style="width: 100%; height: 100%"></div>
@@ -0,0 +1,23 @@
1
+ import { ComponentFixture, TestBed } from '@angular/core/testing';
2
+
3
+ import { BarChart } from './bar-chart';
4
+
5
+ describe('BarChart', () => {
6
+ let component: BarChart;
7
+ let fixture: ComponentFixture<BarChart>;
8
+
9
+ beforeEach(async () => {
10
+ await TestBed.configureTestingModule({
11
+ imports: [BarChart]
12
+ })
13
+ .compileComponents();
14
+
15
+ fixture = TestBed.createComponent(BarChart);
16
+ component = fixture.componentInstance;
17
+ fixture.detectChanges();
18
+ });
19
+
20
+ it('should create', () => {
21
+ expect(component).toBeTruthy();
22
+ });
23
+ });