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.
@@ -62,20 +112,61 @@ export class ChartComponent implements OnChanges {
62112 this . reportsService
63113 . getChartRunReportData ( this . dataObject . report . name , this . dataObject . formData )
64114 . subscribe ( ( response : ChartData ) => {
115+ console . log ( response ) ;
65116 this . inputData = response ;
66- this . setPieChart ( this . inputData ) ;
117+ this . selectedChartType = 'Pie' ;
67118 this . hideOutput = false ;
119+ if ( this . initialRenderTimeoutId !== undefined ) {
120+ clearTimeout ( this . initialRenderTimeoutId ) ;
121+ }
122+ this . initialRenderTimeoutId = setTimeout ( ( ) => {
123+ this . setPieChart ( response ) ;
124+ this . initialRenderTimeoutId = undefined ;
125+ } ) ;
68126 } ) ;
69127 }
70128
129+ /**
130+ * Handles chart type selection and renders the selected chart.
131+ * @param {string } chartType The type of chart to display
132+ */
133+ selectChart ( chartType : string ) {
134+ if ( ! this . inputData ) {
135+ return ;
136+ }
137+ const chartColors = this . randomColorArray ( this . inputData . values . length ) ;
138+ this . selectedChartType = chartType ;
139+ switch ( chartType ) {
140+ case 'Bar' :
141+ this . setBarChart ( this . inputData , chartColors ) ;
142+ break ;
143+ case 'Pie' :
144+ this . setPieChart ( this . inputData , chartColors ) ;
145+ break ;
146+ case 'Polar' :
147+ this . setPolarAreaChart ( this . inputData , chartColors ) ;
148+ break ;
149+ }
150+ }
151+
152+ /**
153+ * Refreshes colors when user clicks the currently selected toggle.
154+ */
155+ refreshChartIfSameType ( chartType : string ) {
156+ if ( chartType === this . selectedChartType ) {
157+ this . selectChart ( chartType ) ;
158+ }
159+ }
160+
71161 /**
72162 * Creates instance of chart.js pie chart.
73163 * Refer: https://www.chartjs.org/docs/latest/charts/doughnut.html for configuration details.
74164 */
75- setPieChart ( inputData : ChartData ) {
165+ setPieChart ( inputData : ChartData , chartColors ?: string [ ] ) {
76166 if ( this . chart ) {
77167 this . chart . destroy ( ) ;
78168 }
169+ const colors = chartColors ?? this . randomColorArray ( inputData . values . length ) ;
79170 this . chart = new Chart ( 'output' , {
80171 type : 'pie' ,
81172 data : {
@@ -84,11 +175,13 @@ export class ChartComponent implements OnChanges {
84175 {
85176 label : inputData . valuesLabel ,
86177 data : inputData . values ,
87- backgroundColor : this . randomColorArray ( inputData . values . length )
178+ backgroundColor : colors
88179 }
89180 ]
90181 } ,
91182 options : {
183+ responsive : true ,
184+ maintainAspectRatio : false ,
92185 plugins : {
93186 title : {
94187 display : true ,
@@ -103,10 +196,11 @@ export class ChartComponent implements OnChanges {
103196 * Creates instance of chart.js bar chart.
104197 * Refer: https://www.chartjs.org/docs/latest/charts/bar.html for configuration details.
105198 */
106- setBarChart ( inputData : ChartData ) {
199+ setBarChart ( inputData : ChartData , chartColors ?: string [ ] ) {
107200 if ( this . chart ) {
108201 this . chart . destroy ( ) ;
109202 }
203+ const colors = chartColors ?? this . randomColorArray ( inputData . values . length ) ;
110204 this . chart = new Chart ( 'output' , {
111205 type : 'bar' ,
112206 data : {
@@ -115,11 +209,13 @@ export class ChartComponent implements OnChanges {
115209 {
116210 label : inputData . valuesLabel ,
117211 data : inputData . values ,
118- backgroundColor : this . randomColorArray ( inputData . values . length )
212+ backgroundColor : colors
119213 }
120214 ]
121215 } ,
122216 options : {
217+ responsive : true ,
218+ maintainAspectRatio : false ,
123219 plugins : {
124220 legend : { display : false }
125221 } ,
@@ -138,12 +234,53 @@ export class ChartComponent implements OnChanges {
138234 } ) ;
139235 }
140236
237+ /**
238+ * Creates instance of chart.js polar area chart.
239+ * Refer: https://www.chartjs.org/docs/latest/charts/polar.html for configuration details.
240+ */
241+ setPolarAreaChart ( inputData : ChartData , chartColors ?: string [ ] ) {
242+ if ( this . chart ) {
243+ this . chart . destroy ( ) ;
244+ }
245+ const colors = chartColors ?? this . randomColorArray ( inputData . values . length ) ;
246+ this . chart = new Chart ( 'output' , {
247+ type : 'polarArea' ,
248+ data : {
249+ labels : inputData . keys ,
250+ datasets : [
251+ {
252+ label : inputData . valuesLabel ,
253+ data : inputData . values ,
254+ backgroundColor : colors ,
255+ borderColor : colors
256+ }
257+ ]
258+ } ,
259+ options : {
260+ responsive : true ,
261+ maintainAspectRatio : false ,
262+ plugins : {
263+ title : {
264+ display : true ,
265+ text : inputData . keysLabel
266+ } ,
267+ legend : { display : true }
268+ } ,
269+ scales : {
270+ r : {
271+ min : 0
272+ }
273+ }
274+ }
275+ } ) ;
276+ }
277+
141278 /**
142279 * Generates bar/pie-slice colors array for dynamic charts.
143280 * @param {number } length Length of dataset array.
144281 */
145282 randomColorArray ( length : number ) {
146- const colorArray : any [ ] = [ ] ;
283+ const colorArray : string [ ] = [ ] ;
147284 while ( length -- ) {
148285 const color = this . randomColor ( ) ;
149286 colorArray . push ( color ) ;
@@ -152,12 +289,66 @@ export class ChartComponent implements OnChanges {
152289 }
153290
154291 /**
155- * Returns a random rgb color.
292+ * Returns a semi- random color based on the active theme palette .
156293 */
157294 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)` ;
295+ const baseColors = this . getThemeBaseColors ( ) ;
296+ const baseColor = baseColors [ Math . floor ( Math . random ( ) * baseColors . length ) ] ;
297+ const variation = Math . floor ( Math . random ( ) * 61 ) - 30 ;
298+ const [
299+ r ,
300+ g ,
301+ b
302+ ] = this . hexToRgb ( baseColor ) . map ( ( channel ) => this . clamp ( channel + variation ) ) ;
303+ return `rgba(${ r } ,${ g } ,${ b } ,0.6)` ;
304+ }
305+
306+ /**
307+ * Derives chart base colors from the user's selected theme and current dark mode.
308+ */
309+ private getThemeBaseColors ( ) : string [ ] {
310+ const savedTheme = this . themeStorageService . getTheme ( ) ;
311+ const primary = savedTheme ?. primary || '#1074B9' ;
312+ const accent = savedTheme ?. accent || '#B4D575' ;
313+ const isDark = document . body . classList . contains ( 'dark-theme' ) ;
314+
315+ if ( isDark ) {
316+ return [
317+ primary ,
318+ accent ,
319+ '#5BA2EC' ,
320+ '#83A447' ,
321+ '#C4C6D0'
322+ ] ;
323+ }
324+
325+ return [
326+ primary ,
327+ accent ,
328+ '#004989' ,
329+ '#E7FFA5' ,
330+ '#6E8A3B'
331+ ] ;
332+ }
333+
334+ private hexToRgb ( hexColor : string ) : number [ ] {
335+ const hex = hexColor . replace ( '#' , '' ) ;
336+ const normalized =
337+ hex . length === 3
338+ ? hex
339+ . split ( '' )
340+ . map ( ( char ) => char + char )
341+ . join ( '' )
342+ : hex ;
343+ const numeric = parseInt ( normalized , 16 ) ;
344+ return [
345+ ( numeric >> 16 ) & 255 ,
346+ ( numeric >> 8 ) & 255 ,
347+ numeric & 255
348+ ] ;
349+ }
350+
351+ private clamp ( value : number ) : number {
352+ return Math . max ( 0 , Math . min ( 255 , value ) ) ;
162353 }
163354}
0 commit comments