cats-charts 0.0.5 → 0.0.7

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/README.md CHANGED
@@ -1,63 +1,59 @@
1
- # ChartsLib
1
+ # CATS4U Charts
2
2
 
3
- This project was generated using [Angular CLI](https://github.com/angular/angular-cli) version 20.3.0.
3
+ This library was generated using [Angular CLI](https://github.com/angular/angular-cli) version 20.3.0 and it also usages [Apache Echarts](https://www.npmjs.com/package/echarts) for charts.
4
4
 
5
- ## Code scaffolding
5
+ ## Install
6
6
 
7
- Angular CLI includes powerful code scaffolding tools. To generate a new component, run:
8
-
9
- ```bash
10
- ng generate component component-name
11
7
  ```
12
-
13
- For a complete list of available schematics (such as `components`, `directives`, or `pipes`), run:
14
-
15
- ```bash
16
- ng generate --help
8
+ npm install cats-charts
17
9
  ```
18
10
 
19
- ## Building
11
+ ## Usages
20
12
 
21
- To build the library, run:
13
+ ### TS file
22
14
 
23
- ```bash
24
- ng build charts-lib
15
+ ```
16
+ import { ChartsLib } from 'cats-charts';
25
17
  ```
26
18
 
27
- This command will compile your project, and the build artifacts will be placed in the `dist/` directory.
28
-
29
- ### Publishing the Library
19
+ ### HTML template
30
20
 
31
- Once the project is built, you can publish your library by following these steps:
21
+ ```
22
+ <lib-charts-lib></lib-charts-lib>
23
+ ```
32
24
 
33
- 1. Navigate to the `dist` directory:
34
- ```bash
35
- cd dist/charts-lib
36
- ```
25
+ ### Inputs
37
26
 
38
- 2. Run the `npm publish` command to publish your library to the npm registry:
39
- ```bash
40
- npm publish
41
- ```
27
+ ```
28
+ chartOptionsConfig: {} // accepts all options accepted by echarts
29
+ chartType: 'bar' | 'area' | 'line' | 'pie' // by default chart type is bar
30
+ columns: [] // list of columns
31
+ contextMenuList: [] // list of context menu
32
+ data: [] // list of set of data
33
+ themeName: "default" | "dark" | "vintage" | "essos" | "chalk" | "roma" // default theme is default
42
34
 
43
- ## Running unit tests
35
+ ```
44
36
 
45
- To execute unit tests with the [Karma](https://karma-runner.github.io) test runner, use the following command:
37
+ ### Outputs
46
38
 
47
- ```bash
48
- ng test
39
+ ```
40
+ handleSingleClick: (params) => {}
41
+ handleDrillBy: (params) => {}
49
42
  ```
50
43
 
51
- ## Running end-to-end tests
52
-
53
- For end-to-end (e2e) testing, run:
44
+ ### Inputs Types
54
45
 
55
- ```bash
56
- ng e2e
57
46
  ```
58
-
59
- Angular CLI does not come with an end-to-end testing framework by default. You can choose one that suits your needs.
47
+ import type { ChartsLibType, OptionsConfig } from 'cats-charts';
48
+
49
+ chartOptionsConfig: OptionsConfig
50
+ chartType: ChartsLibType['chartType']
51
+ columns: string[]
52
+ data: any[][]
53
+ themeName: ChartsLibType['themeName']
54
+ contextMenuList: ContextMenuListItem[]
55
+ ```
60
56
 
61
57
  ## Additional Resources
62
58
 
63
- For more information on using the Angular CLI, including detailed command references, visit the [Angular CLI Overview and Command Reference](https://angular.dev/tools/cli) page.
59
+ For more information on Echarts, including detailed references, visit the [Official Apache Echarts](https://echarts.apache.org/en/api.html#echarts) page.
@@ -1001,10 +1001,10 @@ const vintageTheme = {
1001
1001
  textStyle: {
1002
1002
  color: '#333333',
1003
1003
  },
1004
- left: 'center',
1004
+ eft: 'center',
1005
1005
  right: 'auto',
1006
- top: 0,
1007
- bottom: 10,
1006
+ top: 'auto',
1007
+ bottom: 15,
1008
1008
  },
1009
1009
  tooltip: {
1010
1010
  axisPointer: {
@@ -2830,68 +2830,21 @@ class LineChart {
2830
2830
  enableSampling = true; // Sample data if > threshold for perf
2831
2831
  sampleThreshold = 10000; // Sample if data > 10k (keep full for 50k if hardware allows)
2832
2832
  isLoading = true;
2833
+ defaultConfig = new OptionsConfig();
2834
+ optionsConfig = {};
2833
2835
  chartContainer;
2834
2836
  chartInstance = null;
2835
- destroy$ = new Subject();
2836
2837
  chartService = inject(ChartService);
2838
+ destroy$ = new Subject();
2837
2839
  currentData = [];
2838
2840
  currentColumns = [];
2839
- // ECharts options optimized for 50k+ data
2840
- chartOptions = {
2841
- title: {
2842
- text: this.title,
2843
- left: 'center',
2844
- textStyle: { fontSize: 16 },
2845
- },
2846
- tooltip: {
2847
- // Hover info (efficient for large data)
2848
- trigger: 'axis',
2849
- axisPointer: { type: 'shadow' },
2850
- // formatter: (params = [{ name: '', value: '' }]) => {
2851
- // const param = params[0];
2852
- // return `${param.name}<br/>Value: ${param.value}`;
2853
- // },
2854
- },
2855
- legend: { show: false }, // Disable for single series; add if multi-series
2856
- dataZoom: [
2857
- // Critical for large data: Zoom/slider to navigate 50k data points
2858
- {
2859
- type: 'slider', // Bottom slider
2860
- start: 0,
2861
- end: 100, // Initial view: First 10% (e.g., 5k of 50k)
2862
- height: 25,
2863
- bottom: 20,
2864
- textStyle: { fontSize: 12 },
2865
- },
2866
- {
2867
- type: 'inside', // Mouse wheel/pinch zoom
2868
- start: 0,
2869
- end: 10,
2870
- },
2871
- ],
2872
- grid: {
2873
- left: '5%',
2874
- right: '5%',
2875
- bottom: '80px', // Space for dataZoom slider
2876
- containLabel: true,
2877
- },
2878
- xAxis: {
2879
- type: 'category',
2880
- data: [], // Populated dynamically
2881
- axisLabel: { rotate: 45, fontSize: 10 },
2882
- axisTick: { show: false },
2883
- },
2884
- yAxis: {
2885
- type: 'value',
2886
- axisLabel: { fontSize: 12 },
2887
- },
2888
- series: [],
2889
- };
2841
+ boundHandleContextMenu;
2890
2842
  constructor(cdr) {
2891
2843
  this.cdr = cdr;
2844
+ this.boundHandleContextMenu = this.handleContextMenu.bind(this);
2892
2845
  // Effect for theme changes
2893
2846
  effect(() => {
2894
- const theme = this.chartService.theme(); // Read signal
2847
+ this.chartService.theme(); // Read signal
2895
2848
  if (this.chartInstance) {
2896
2849
  this.reinitializeChart();
2897
2850
  }
@@ -2904,6 +2857,12 @@ class LineChart {
2904
2857
  this.currentData = data;
2905
2858
  this.processAndUpdateData();
2906
2859
  });
2860
+ // Effect for chartOptionsConfig changes
2861
+ effect(() => {
2862
+ this.chartService.chartOptionsConfig();
2863
+ this.optionsConfig = this.chartService.chartOptionsConfig();
2864
+ this.reinitializeChart();
2865
+ });
2907
2866
  }
2908
2867
  ngOnInit() {
2909
2868
  if (this.currentData.length > 0) {
@@ -2921,10 +2880,18 @@ class LineChart {
2921
2880
  ngOnDestroy() {
2922
2881
  this.destroy$.next();
2923
2882
  this.destroy$.complete();
2883
+ this.chartService.hideContextMenu();
2924
2884
  this.disposeChart();
2925
2885
  if (this.chartInstance) {
2926
2886
  this.chartInstance.dispose(); // Clean up to free memory
2927
2887
  }
2888
+ if (this.chartContainer) {
2889
+ this.chartContainer.nativeElement.removeEventListener('contextmenu', this.boundHandleContextMenu);
2890
+ }
2891
+ document.removeEventListener('click', () => {
2892
+ this.chartService.hideContextMenu();
2893
+ this.chartService.resetContextEvent();
2894
+ });
2928
2895
  }
2929
2896
  reinitializeChart() {
2930
2897
  this.initializeChart();
@@ -2935,7 +2902,6 @@ class LineChart {
2935
2902
  }
2936
2903
  initializeChart() {
2937
2904
  if (!this.chartContainer) {
2938
- console.error('Chart container not found!');
2939
2905
  return;
2940
2906
  }
2941
2907
  const chartDom = this.chartContainer.nativeElement;
@@ -2945,7 +2911,38 @@ class LineChart {
2945
2911
  this.chartInstance = null;
2946
2912
  }
2947
2913
  this.chartInstance = echarts.init(chartDom, this.chartService.theme()); // Init ECharts
2948
- this.chartInstance.setOption(this.chartOptions);
2914
+ this.chartInstance.setOption({
2915
+ ...this.defaultConfig,
2916
+ ...this.optionsConfig,
2917
+ title: {
2918
+ ...this.defaultConfig.title,
2919
+ ...this.optionsConfig.title,
2920
+ textStyle: {
2921
+ ...this.defaultConfig.title?.textStyle,
2922
+ ...this.optionsConfig.title?.textStyle,
2923
+ },
2924
+ },
2925
+ tooltip: {
2926
+ ...this.defaultConfig.tooltip,
2927
+ ...this.optionsConfig.tooltip,
2928
+ },
2929
+ legend: { ...this.defaultConfig.legend, ...this.optionsConfig.legend },
2930
+ dataZoom: this.optionsConfig.dataZoom || this.defaultConfig.dataZoom,
2931
+ grid: {
2932
+ ...this.defaultConfig.grid,
2933
+ ...this.optionsConfig.grid,
2934
+ },
2935
+ xAxis: {
2936
+ ...this.defaultConfig.xAxis,
2937
+ ...this.optionsConfig.xAxis,
2938
+ data: [],
2939
+ },
2940
+ yAxis: {
2941
+ ...this.defaultConfig.yAxis,
2942
+ ...this.optionsConfig.yAxis,
2943
+ },
2944
+ series: [],
2945
+ });
2949
2946
  // Responsive: Resize on window change
2950
2947
  window.addEventListener('resize', () => this.chartInstance?.resize(), { passive: true });
2951
2948
  // Handle dataZoom events (optional: Log zoom changes)
@@ -2955,9 +2952,32 @@ class LineChart {
2955
2952
  this.chartInstance.on('rendered', () => {
2956
2953
  // console.log('Chart rendered successfully');
2957
2954
  });
2955
+ this.chartInstance.on('click', (params) => {
2956
+ this.chartService.hideContextMenu();
2957
+ this.chartService.resetContextEvent();
2958
+ this.chartService.handleClick(params);
2959
+ });
2960
+ this.chartInstance.on('contextmenu', (params) => {
2961
+ this.chartService.openContextMenu(params, this.chartContainer);
2962
+ });
2963
+ chartDom.addEventListener('contextmenu', this.boundHandleContextMenu);
2964
+ document.addEventListener('click', () => {
2965
+ this.chartService.hideContextMenu();
2966
+ this.chartService.resetContextEvent();
2967
+ }, {
2968
+ passive: true,
2969
+ });
2958
2970
  this.isLoading = false;
2959
2971
  this.cdr.markForCheck();
2960
2972
  }
2973
+ handleContextMenu(event) {
2974
+ event.preventDefault();
2975
+ setTimeout(() => {
2976
+ if (!this.chartService.contextMenuEvent().seriesName) {
2977
+ this.chartService.openContextMenu(null, this.chartContainer, event);
2978
+ }
2979
+ }, 0);
2980
+ }
2961
2981
  updateChartWithData() {
2962
2982
  this.processData(); // Process without updating yet
2963
2983
  if (this.chartInstance) {
@@ -3028,6 +3048,7 @@ class LineChart {
3028
3048
  disposeChart() {
3029
3049
  if (this.chartInstance) {
3030
3050
  this.chartInstance.dispose();
3051
+ this.chartInstance.off('click');
3031
3052
  this.chartInstance = null;
3032
3053
  // console.log('ECharts instance disposed');
3033
3054
  }
@@ -3066,67 +3087,21 @@ class AreaChart {
3066
3087
  enableSampling = true; // Sample data if > threshold for perf
3067
3088
  sampleThreshold = 10000; // Sample if data > 10k (keep full for 50k if hardware allows)
3068
3089
  isLoading = true;
3090
+ defaultConfig = new OptionsConfig();
3091
+ optionsConfig = {};
3069
3092
  chartContainer;
3070
3093
  chartInstance = null;
3071
3094
  chartService = inject(ChartService);
3072
3095
  destroy$ = new Subject();
3073
3096
  currentData = [];
3074
3097
  currentColumns = [];
3075
- // ECharts options optimized for 50k+ data
3076
- chartOptions = {
3077
- title: {
3078
- text: this.title,
3079
- left: 'center',
3080
- textStyle: { fontSize: 16 },
3081
- },
3082
- tooltip: {
3083
- // Hover info (efficient for large data)
3084
- trigger: 'axis',
3085
- axisPointer: { type: 'shadow' },
3086
- // formatter: (params = [{ name: '', value: '' }]) => {
3087
- // const param = params[0];
3088
- // return `${param.name}<br/>Value: ${param.value}`;
3089
- // },
3090
- },
3091
- legend: { show: false }, // Disable for single series; add if multi-series
3092
- dataZoom: [
3093
- // Critical for large data: Zoom/slider to navigate 50k data points
3094
- {
3095
- type: 'slider', // Bottom slider
3096
- start: 0,
3097
- end: 100, // Initial view: First 10% (e.g., 5k of 50k)
3098
- height: 25,
3099
- bottom: 20,
3100
- textStyle: { fontSize: 12 },
3101
- },
3102
- {
3103
- type: 'inside', // Mouse wheel/pinch zoom
3104
- start: 0,
3105
- end: 10,
3106
- },
3107
- ],
3108
- grid: {
3109
- left: '5%',
3110
- right: '5%',
3111
- bottom: '80px', // Space for dataZoom slider
3112
- containLabel: true,
3113
- },
3114
- xAxis: {
3115
- type: 'category',
3116
- data: [], // Populated dynamically
3117
- axisLabel: { rotate: 45, fontSize: 10 },
3118
- axisTick: { show: false },
3119
- },
3120
- yAxis: {
3121
- type: 'value',
3122
- axisLabel: { fontSize: 12 },
3123
- },
3124
- series: [],
3125
- };
3098
+ boundHandleContextMenu;
3126
3099
  constructor(cdr) {
3127
3100
  this.cdr = cdr;
3101
+ this.boundHandleContextMenu = this.handleContextMenu.bind(this);
3102
+ // Effect for theme changes
3128
3103
  effect(() => {
3129
- const theme = this.chartService.theme(); // Read signal
3104
+ this.chartService.theme(); // Read signal
3130
3105
  if (this.chartInstance) {
3131
3106
  this.reinitializeChart();
3132
3107
  }
@@ -3139,6 +3114,12 @@ class AreaChart {
3139
3114
  this.currentData = data;
3140
3115
  this.processAndUpdateData();
3141
3116
  });
3117
+ // Effect for chartOptionsConfig changes
3118
+ effect(() => {
3119
+ this.chartService.chartOptionsConfig();
3120
+ this.optionsConfig = this.chartService.chartOptionsConfig();
3121
+ this.reinitializeChart();
3122
+ });
3142
3123
  }
3143
3124
  ngOnInit() {
3144
3125
  if (this.currentData.length > 0) {
@@ -3156,10 +3137,18 @@ class AreaChart {
3156
3137
  ngOnDestroy() {
3157
3138
  this.destroy$.next();
3158
3139
  this.destroy$.complete();
3140
+ this.chartService.hideContextMenu();
3159
3141
  this.disposeChart();
3160
3142
  if (this.chartInstance) {
3161
3143
  this.chartInstance.dispose(); // Clean up to free memory
3162
3144
  }
3145
+ if (this.chartContainer) {
3146
+ this.chartContainer.nativeElement.removeEventListener('contextmenu', this.boundHandleContextMenu);
3147
+ }
3148
+ document.removeEventListener('click', () => {
3149
+ this.chartService.hideContextMenu();
3150
+ this.chartService.resetContextEvent();
3151
+ });
3163
3152
  }
3164
3153
  reinitializeChart() {
3165
3154
  this.initializeChart();
@@ -3170,7 +3159,6 @@ class AreaChart {
3170
3159
  }
3171
3160
  initializeChart() {
3172
3161
  if (!this.chartContainer) {
3173
- console.error('Chart container not found!');
3174
3162
  return;
3175
3163
  }
3176
3164
  const chartDom = this.chartContainer.nativeElement;
@@ -3180,7 +3168,38 @@ class AreaChart {
3180
3168
  this.chartInstance = null;
3181
3169
  }
3182
3170
  this.chartInstance = echarts.init(chartDom, this.chartService.theme()); // Init ECharts
3183
- this.chartInstance.setOption(this.chartOptions);
3171
+ this.chartInstance.setOption({
3172
+ ...this.defaultConfig,
3173
+ ...this.optionsConfig,
3174
+ title: {
3175
+ ...this.defaultConfig.title,
3176
+ ...this.optionsConfig.title,
3177
+ textStyle: {
3178
+ ...this.defaultConfig.title?.textStyle,
3179
+ ...this.optionsConfig.title?.textStyle,
3180
+ },
3181
+ },
3182
+ tooltip: {
3183
+ ...this.defaultConfig.tooltip,
3184
+ ...this.optionsConfig.tooltip,
3185
+ },
3186
+ legend: { ...this.defaultConfig.legend, ...this.optionsConfig.legend },
3187
+ dataZoom: this.optionsConfig.dataZoom || this.defaultConfig.dataZoom,
3188
+ grid: {
3189
+ ...this.defaultConfig.grid,
3190
+ ...this.optionsConfig.grid,
3191
+ },
3192
+ xAxis: {
3193
+ ...this.defaultConfig.xAxis,
3194
+ ...this.optionsConfig.xAxis,
3195
+ data: [],
3196
+ },
3197
+ yAxis: {
3198
+ ...this.defaultConfig.yAxis,
3199
+ ...this.optionsConfig.yAxis,
3200
+ },
3201
+ series: [],
3202
+ });
3184
3203
  // Responsive: Resize on window change
3185
3204
  window.addEventListener('resize', () => this.chartInstance?.resize(), { passive: true });
3186
3205
  // Handle dataZoom events (optional: Log zoom changes)
@@ -3190,9 +3209,32 @@ class AreaChart {
3190
3209
  this.chartInstance.on('rendered', () => {
3191
3210
  // console.log('Chart rendered successfully');
3192
3211
  });
3212
+ this.chartInstance.on('click', (params) => {
3213
+ this.chartService.hideContextMenu();
3214
+ this.chartService.resetContextEvent();
3215
+ this.chartService.handleClick(params);
3216
+ });
3217
+ this.chartInstance.on('contextmenu', (params) => {
3218
+ this.chartService.openContextMenu(params, this.chartContainer);
3219
+ });
3220
+ chartDom.addEventListener('contextmenu', this.boundHandleContextMenu);
3221
+ document.addEventListener('click', () => {
3222
+ this.chartService.hideContextMenu();
3223
+ this.chartService.resetContextEvent();
3224
+ }, {
3225
+ passive: true,
3226
+ });
3193
3227
  this.isLoading = false;
3194
3228
  this.cdr.markForCheck();
3195
3229
  }
3230
+ handleContextMenu(event) {
3231
+ event.preventDefault();
3232
+ setTimeout(() => {
3233
+ if (!this.chartService.contextMenuEvent().seriesName) {
3234
+ this.chartService.openContextMenu(null, this.chartContainer, event);
3235
+ }
3236
+ }, 0);
3237
+ }
3196
3238
  updateChartWithData() {
3197
3239
  this.processData(); // Process without updating yet
3198
3240
  if (this.chartInstance) {
@@ -3264,6 +3306,7 @@ class AreaChart {
3264
3306
  disposeChart() {
3265
3307
  if (this.chartInstance) {
3266
3308
  this.chartInstance.dispose();
3309
+ this.chartInstance.off('click');
3267
3310
  this.chartInstance = null;
3268
3311
  // console.log('ECharts instance disposed');
3269
3312
  }