77 */
88
99/** Angular Imports */
10- import { Component , OnChanges , Input , inject } from '@angular/core' ;
10+ import { Component , OnChanges , OnInit , OnDestroy , Input , inject } from '@angular/core' ;
1111
1212/** Custom Services */
1313import { ReportsService } from '../../reports.service' ;
14+ import { ThemeStorageService } from 'app/shared/theme-picker/theme-storage.service' ;
1415
1516/** Custom Models */
1617import { ChartData } from '../../common-models/chart-data.model' ;
@@ -38,8 +39,11 @@ Chart.register(...registerables);
3839 NgStyle
3940 ]
4041} )
41- export class ChartComponent implements OnChanges {
42+ export class ChartComponent implements OnChanges , OnInit , OnDestroy {
4243 private reportsService = inject ( ReportsService ) ;
44+ private themeStorageService = inject ( ThemeStorageService ) ;
45+ private resizeTimeoutId : ReturnType < typeof setTimeout > | undefined ;
46+ private initialRenderTimeoutId : ReturnType < typeof setTimeout > | undefined ;
4347
4448 /** Run Report Data */
4549 @Input ( ) dataObject : any ;
@@ -50,6 +54,52 @@ export class ChartComponent implements OnChanges {
5054 hideOutput = true ;
5155 /** Data object for witching charts in view. */
5256 inputData : ChartData ;
57+ /** Tracks the currently selected chart type */
58+ selectedChartType : string = 'Pie' ;
59+ /** Resize listener */
60+ private readonly resizeListener = ( ) => this . resizeChart ( ) ;
61+
62+ /**
63+ * Initialize component and add resize listener.
64+ */
65+ ngOnInit ( ) {
66+ window . addEventListener ( 'resize' , this . resizeListener ) ;
67+ }
68+
69+ /**
70+ * Clean up on component destroy.
71+ */
72+ ngOnDestroy ( ) {
73+ window . removeEventListener ( 'resize' , this . resizeListener ) ;
74+ if ( this . resizeTimeoutId !== undefined ) {
75+ clearTimeout ( this . resizeTimeoutId ) ;
76+ this . resizeTimeoutId = undefined ;
77+ }
78+ if ( this . initialRenderTimeoutId !== undefined ) {
79+ clearTimeout ( this . initialRenderTimeoutId ) ;
80+ this . initialRenderTimeoutId = undefined ;
81+ }
82+ if ( this . chart ) {
83+ this . chart . destroy ( ) ;
84+ this . chart = undefined ;
85+ }
86+ }
87+
88+ /**
89+ * Resize and redraw chart when window size changes.
90+ */
91+ resizeChart ( ) {
92+ if ( this . chart ) {
93+ if ( this . resizeTimeoutId !== undefined ) {
94+ clearTimeout ( this . resizeTimeoutId ) ;
95+ }
96+ // Debounce resize calls to avoid queuing multiple chart operations.
97+ this . resizeTimeoutId = setTimeout ( ( ) => {
98+ this . chart ?. resize ( ) ;
99+ this . resizeTimeoutId = undefined ;
100+ } , 100 ) ;
101+ }
102+ }
53103
54104 /**
55105 * Fetches run report data post changes in run report form.
@@ -63,19 +113,59 @@ export class ChartComponent implements OnChanges {
63113 . getChartRunReportData ( this . dataObject . report . name , this . dataObject . formData )
64114 . subscribe ( ( response : ChartData ) => {
65115 this . inputData = response ;
66- this . setPieChart ( this . inputData ) ;
116+ this . selectedChartType = 'Pie' ;
67117 this . hideOutput = false ;
118+ if ( this . initialRenderTimeoutId !== undefined ) {
119+ clearTimeout ( this . initialRenderTimeoutId ) ;
120+ }
121+ this . initialRenderTimeoutId = setTimeout ( ( ) => {
122+ this . setPieChart ( response ) ;
123+ this . initialRenderTimeoutId = undefined ;
124+ } ) ;
68125 } ) ;
69126 }
70127
128+ /**
129+ * Handles chart type selection and renders the selected chart.
130+ * @param {string } chartType The type of chart to display
131+ */
132+ selectChart ( chartType : string ) {
133+ if ( ! this . inputData ) {
134+ return ;
135+ }
136+ const chartColors = this . randomColorArray ( this . inputData . values . length ) ;
137+ this . selectedChartType = chartType ;
138+ switch ( chartType ) {
139+ case 'Bar' :
140+ this . setBarChart ( this . inputData , chartColors ) ;
141+ break ;
142+ case 'Pie' :
143+ this . setPieChart ( this . inputData , chartColors ) ;
144+ break ;
145+ case 'Polar' :
146+ this . setPolarAreaChart ( this . inputData , chartColors ) ;
147+ break ;
148+ }
149+ }
150+
151+ /**
152+ * Refreshes colors when user clicks the currently selected toggle.
153+ */
154+ refreshChartIfSameType ( chartType : string ) {
155+ if ( chartType === this . selectedChartType ) {
156+ this . selectChart ( chartType ) ;
157+ }
158+ }
159+
71160 /**
72161 * Creates instance of chart.js pie chart.
73162 * Refer: https://www.chartjs.org/docs/latest/charts/doughnut.html for configuration details.
74163 */
75- setPieChart ( inputData : ChartData ) {
164+ setPieChart ( inputData : ChartData , chartColors ?: string [ ] ) {
76165 if ( this . chart ) {
77166 this . chart . destroy ( ) ;
78167 }
168+ const colors = chartColors ?? this . randomColorArray ( inputData . values . length ) ;
79169 this . chart = new Chart ( 'output' , {
80170 type : 'pie' ,
81171 data : {
@@ -84,11 +174,13 @@ export class ChartComponent implements OnChanges {
84174 {
85175 label : inputData . valuesLabel ,
86176 data : inputData . values ,
87- backgroundColor : this . randomColorArray ( inputData . values . length )
177+ backgroundColor : colors
88178 }
89179 ]
90180 } ,
91181 options : {
182+ responsive : true ,
183+ maintainAspectRatio : false ,
92184 plugins : {
93185 title : {
94186 display : true ,
@@ -103,10 +195,11 @@ export class ChartComponent implements OnChanges {
103195 * Creates instance of chart.js bar chart.
104196 * Refer: https://www.chartjs.org/docs/latest/charts/bar.html for configuration details.
105197 */
106- setBarChart ( inputData : ChartData ) {
198+ setBarChart ( inputData : ChartData , chartColors ?: string [ ] ) {
107199 if ( this . chart ) {
108200 this . chart . destroy ( ) ;
109201 }
202+ const colors = chartColors ?? this . randomColorArray ( inputData . values . length ) ;
110203 this . chart = new Chart ( 'output' , {
111204 type : 'bar' ,
112205 data : {
@@ -115,11 +208,13 @@ export class ChartComponent implements OnChanges {
115208 {
116209 label : inputData . valuesLabel ,
117210 data : inputData . values ,
118- backgroundColor : this . randomColorArray ( inputData . values . length )
211+ backgroundColor : colors
119212 }
120213 ]
121214 } ,
122215 options : {
216+ responsive : true ,
217+ maintainAspectRatio : false ,
123218 plugins : {
124219 legend : { display : false }
125220 } ,
@@ -138,12 +233,53 @@ export class ChartComponent implements OnChanges {
138233 } ) ;
139234 }
140235
236+ /**
237+ * Creates instance of chart.js polar area chart.
238+ * Refer: https://www.chartjs.org/docs/latest/charts/polar.html for configuration details.
239+ */
240+ setPolarAreaChart ( inputData : ChartData , chartColors ?: string [ ] ) {
241+ if ( this . chart ) {
242+ this . chart . destroy ( ) ;
243+ }
244+ const colors = chartColors ?? this . randomColorArray ( inputData . values . length ) ;
245+ this . chart = new Chart ( 'output' , {
246+ type : 'polarArea' ,
247+ data : {
248+ labels : inputData . keys ,
249+ datasets : [
250+ {
251+ label : inputData . valuesLabel ,
252+ data : inputData . values ,
253+ backgroundColor : colors ,
254+ borderColor : colors
255+ }
256+ ]
257+ } ,
258+ options : {
259+ responsive : true ,
260+ maintainAspectRatio : false ,
261+ plugins : {
262+ title : {
263+ display : true ,
264+ text : inputData . keysLabel
265+ } ,
266+ legend : { display : true }
267+ } ,
268+ scales : {
269+ r : {
270+ min : 0
271+ }
272+ }
273+ }
274+ } ) ;
275+ }
276+
141277 /**
142278 * Generates bar/pie-slice colors array for dynamic charts.
143279 * @param {number } length Length of dataset array.
144280 */
145281 randomColorArray ( length : number ) {
146- const colorArray : any [ ] = [ ] ;
282+ const colorArray : string [ ] = [ ] ;
147283 while ( length -- ) {
148284 const color = this . randomColor ( ) ;
149285 colorArray . push ( color ) ;
@@ -152,12 +288,66 @@ export class ChartComponent implements OnChanges {
152288 }
153289
154290 /**
155- * Returns a random rgb color.
291+ * Returns a semi- random color based on the active theme palette .
156292 */
157293 randomColor ( ) {
158- const r = Math . floor ( Math . random ( ) * 255 ) ;
159- const g = Math . floor ( Math . random ( ) * 255 ) ;
160- const b = Math . floor ( Math . random ( ) * 255 ) ;
161- return `rgb(${ r } ,${ g } ,${ b } ,0.6)` ;
294+ const baseColors = this . getThemeBaseColors ( ) ;
295+ const baseColor = baseColors [ Math . floor ( Math . random ( ) * baseColors . length ) ] ;
296+ const variation = Math . floor ( Math . random ( ) * 61 ) - 30 ;
297+ const [
298+ r ,
299+ g ,
300+ b
301+ ] = this . hexToRgb ( baseColor ) . map ( ( channel ) => this . clamp ( channel + variation ) ) ;
302+ return `rgba(${ r } ,${ g } ,${ b } ,0.6)` ;
303+ }
304+
305+ /**
306+ * Derives chart base colors from the user's selected theme and current dark mode.
307+ */
308+ private getThemeBaseColors ( ) : string [ ] {
309+ const savedTheme = this . themeStorageService . getTheme ( ) ;
310+ const primary = savedTheme ?. primary || '#1074B9' ;
311+ const accent = savedTheme ?. accent || '#B4D575' ;
312+ const isDark = document . body . classList . contains ( 'dark-theme' ) ;
313+
314+ if ( isDark ) {
315+ return [
316+ primary ,
317+ accent ,
318+ '#5BA2EC' ,
319+ '#83A447' ,
320+ '#C4C6D0'
321+ ] ;
322+ }
323+
324+ return [
325+ primary ,
326+ accent ,
327+ '#004989' ,
328+ '#E7FFA5' ,
329+ '#6E8A3B'
330+ ] ;
331+ }
332+
333+ private hexToRgb ( hexColor : string ) : number [ ] {
334+ const hex = hexColor . replace ( '#' , '' ) ;
335+ const normalized =
336+ hex . length === 3
337+ ? hex
338+ . split ( '' )
339+ . map ( ( char ) => char + char )
340+ . join ( '' )
341+ : hex ;
342+ const numeric = parseInt ( normalized , 16 ) ;
343+ return [
344+ ( numeric >> 16 ) & 255 ,
345+ ( numeric >> 8 ) & 255 ,
346+ numeric & 255
347+ ] ;
348+ }
349+
350+ private clamp ( value : number ) : number {
351+ return Math . max ( 0 , Math . min ( 255 , value ) ) ;
162352 }
163353}
0 commit comments