@@ -94,6 +94,126 @@ const getVerticalAxes = (nameKey, tickColor, xLabel, yDomain, yTicks, valueForma
9494 ) ,
9595} ) ;
9696
97+ const getChartAxes = ( {
98+ isHorizontal,
99+ tickColor,
100+ valueFormatter,
101+ nameKey,
102+ xLabel,
103+ yLabel,
104+ ...props
105+ } ) =>
106+ isHorizontal
107+ ? getHorizontalAxes ( {
108+ xDomain : props . xDomain ,
109+ xTicks : props . xTicks ,
110+ valueFormatter,
111+ tickColor,
112+ xLabel,
113+ nameKey,
114+ yCategoryWidth : props . yCategoryWidth ,
115+ yTickFormatter : props . yTickFormatter ,
116+ showYAxisTitle : props . showYAxisTitle ,
117+ yLabel,
118+ } )
119+ : getVerticalAxes (
120+ nameKey ,
121+ tickColor ,
122+ xLabel ,
123+ props . yDomain ,
124+ props . yTicks ,
125+ valueFormatter ,
126+ yLabel ,
127+ ) ;
128+
129+ const GraphHeader = ( { title, metricLabel, showMetricPill, headerChips, darkMode } ) => (
130+ < div
131+ className = { styles . graphTitle }
132+ style = { { display : 'flex' , alignItems : 'center' , color : darkMode ? '#e1e1e1' : undefined } }
133+ >
134+ < span style = { { flex : 1 } } > { title } </ span >
135+ { showMetricPill && (
136+ < span className = { styles . metricPill } style = { { marginRight : 12 } } >
137+ { metricLabel }
138+ </ span >
139+ ) }
140+
141+ < div style = { { display : 'flex' , gap : 16 } } >
142+ { headerChips . map ( ( chip , index ) => (
143+ < div key = { `${ chip . label } -${ chip . value } ` } style = { { textAlign : 'center' , lineHeight : 1.1 } } >
144+ < div style = { { fontSize : 12 , fontWeight : 600 , color : darkMode ? '#e1e1e1' : undefined } } >
145+ { chip . label }
146+ </ div >
147+ < div style = { { fontSize : 11 , color : darkMode ? '#a0b0c8' : '#777' , letterSpacing : 0.2 } } >
148+ { String ( chip . value ) . toUpperCase ( ) }
149+ </ div >
150+ </ div >
151+ ) ) }
152+ </ div >
153+ </ div >
154+ ) ;
155+
156+ const NoDataMessage = ( { height, darkMode } ) => (
157+ < div
158+ style = { {
159+ height,
160+ display : 'flex' ,
161+ alignItems : 'center' ,
162+ justifyContent : 'center' ,
163+ color : darkMode ? '#e1e1e1' : '#666' ,
164+ } }
165+ >
166+ No data available
167+ </ div >
168+ ) ;
169+
170+ const CompareChart = ( {
171+ data,
172+ height,
173+ isHorizontal,
174+ margins,
175+ gridColor,
176+ axes,
177+ valueKey,
178+ barColor,
179+ barSize,
180+ valueFormatter,
181+ tooltipLabel,
182+ metricLabel,
183+ title,
184+ darkMode,
185+ } ) => (
186+ < ResponsiveContainer width = "100%" height = { height } >
187+ < BarChart data = { data } layout = { isHorizontal ? 'vertical' : 'horizontal' } margin = { margins } >
188+ < CartesianGrid strokeDasharray = "3 3" stroke = { gridColor } />
189+ { axes . xAxis }
190+ { axes . yAxis }
191+
192+ < Tooltip
193+ formatter = { value => [ valueFormatter ( value ) , tooltipLabel || metricLabel || title ] }
194+ labelFormatter = { label => `${ label } ` }
195+ contentStyle = { {
196+ background : darkMode ? '#1c2541' : '#fff' ,
197+ border : `1px solid ${ darkMode ? '#3a506b' : '#ccc' } ` ,
198+ color : darkMode ? '#e1e1e1' : '#333' ,
199+ } }
200+ itemStyle = { { color : darkMode ? '#e1e1e1' : '#333' } }
201+ labelStyle = { { color : darkMode ? '#e1e1e1' : '#333' , fontWeight : 600 } }
202+ />
203+
204+ < Bar dataKey = { valueKey } radius = { [ 4 , 4 , 4 , 4 ] } fill = { barColor } barSize = { barSize } >
205+ < LabelList
206+ dataKey = { valueKey }
207+ position = { isHorizontal ? 'right' : 'top' }
208+ formatter = { valueFormatter }
209+ style = { { fontSize : 15 , fontWeight : 600 } }
210+ offset = { 8 }
211+ />
212+ </ Bar >
213+ </ BarChart >
214+ </ ResponsiveContainer >
215+ ) ;
216+
97217export function CompareBarGraph ( {
98218 title,
99219 metricLabel,
@@ -106,7 +226,7 @@ export function CompareBarGraph({
106226 xLabel,
107227 yLabel,
108228 barColor = '#3b82f6' ,
109- valueFormatter = v => v ,
229+ valueFormatter = value => value ,
110230 headerChips = [ ] ,
111231 xDomain,
112232 yDomain,
@@ -116,7 +236,6 @@ export function CompareBarGraph({
116236 height = 420 ,
117237 yCategoryWidth = 70 ,
118238 margins = { top : 16 , right : 20 , bottom : 46 , left : 0 } ,
119- maxBars,
120239 showYAxisTitle = true ,
121240 yTickFormatter,
122241 darkMode = false ,
@@ -125,6 +244,22 @@ export function CompareBarGraph({
125244 const tickColor = darkMode ? '#e1e1e1' : '#444' ;
126245 const gridColor = darkMode ? '#3a506b' : '#e0e0e0' ;
127246
247+ const axes = getChartAxes ( {
248+ isHorizontal,
249+ tickColor,
250+ valueFormatter,
251+ nameKey,
252+ xLabel,
253+ yLabel,
254+ xDomain,
255+ yDomain,
256+ xTicks,
257+ yTicks,
258+ yCategoryWidth,
259+ yTickFormatter,
260+ showYAxisTitle,
261+ } ) ;
262+
128263 return (
129264 < Card
130265 className = { `${ styles . graphCard } ${ darkMode ? styles . darkCard : '' } ` }
@@ -133,113 +268,84 @@ export function CompareBarGraph({
133268 }
134269 >
135270 < CardBody className = { `${ styles . graphCardBody } ${ darkMode ? styles . darkCardBody : '' } ` } >
136- { /* Title row + chips */ }
137- < div
138- className = { styles . graphTitle }
139- style = { { display : 'flex' , alignItems : 'center' , color : darkMode ? '#e1e1e1' : undefined } }
140- >
141- < span style = { { flex : 1 } } > { title } </ span >
142- { showMetricPill && (
143- < span className = { styles . metricPill } style = { { marginRight : 12 } } >
144- { metricLabel }
145- </ span >
146- ) }
147-
148- { /* chips on the right */ }
149- < div style = { { display : 'flex' , gap : 16 } } >
150- { headerChips . map ( ( c , i ) => (
151- < div key = { i } style = { { textAlign : 'center' , lineHeight : 1.1 } } >
152- < div
153- style = { { fontSize : 12 , fontWeight : 600 , color : darkMode ? '#e1e1e1' : undefined } }
154- >
155- { c . label }
156- </ div >
157- < div
158- style = { { fontSize : 11 , color : darkMode ? '#a0b0c8' : '#777' , letterSpacing : 0.2 } }
159- >
160- { String ( c . value ) . toUpperCase ( ) }
161- </ div >
162- </ div >
163- ) ) }
164- </ div >
165- </ div >
271+ < GraphHeader
272+ title = { title }
273+ metricLabel = { metricLabel }
274+ showMetricPill = { showMetricPill }
275+ headerChips = { headerChips }
276+ darkMode = { darkMode }
277+ />
166278
167- { /* chart */ }
168- < div className = { styles . graphCanvas } >
169- < ResponsiveContainer width = "100%" height = { height } >
170- < BarChart
279+ < div className = { styles . graphCanvas } style = { { width : '100%' , minHeight : `${ height } px` } } >
280+ { data ?. length ? (
281+ < CompareChart
171282 data = { data }
172- layout = { isHorizontal ? 'vertical' : 'horizontal' }
173- margin = { margins }
174- >
175- < CartesianGrid strokeDasharray = "3 3" stroke = { gridColor } />
176- { isHorizontal
177- ? ( ( ) => {
178- const axes = getHorizontalAxes ( {
179- xDomain,
180- xTicks,
181- valueFormatter,
182- tickColor,
183- xLabel,
184- nameKey,
185- yCategoryWidth,
186- yTickFormatter,
187- showYAxisTitle,
188- yLabel,
189- } ) ;
190- return (
191- < >
192- { axes . xAxis }
193- { axes . yAxis }
194- </ >
195- ) ;
196- } ) ( )
197- : ( ( ) => {
198- const axes = getVerticalAxes (
199- nameKey ,
200- tickColor ,
201- xLabel ,
202- yDomain ,
203- yTicks ,
204- valueFormatter ,
205- yLabel ,
206- ) ;
207- return (
208- < >
209- { axes . xAxis }
210- { axes . yAxis }
211- </ >
212- ) ;
213- } ) ( ) }
214-
215- < Tooltip
216- formatter = { v => [ valueFormatter ( v ) , tooltipLabel || metricLabel || title ] }
217- labelFormatter = { lbl => `${ lbl } ` }
218- contentStyle = { {
219- background : darkMode ? '#1c2541' : '#fff' ,
220- border : `1px solid ${ darkMode ? '#3a506b' : '#ccc' } ` ,
221- color : darkMode ? '#e1e1e1' : '#333' ,
222- } }
223- itemStyle = { { color : darkMode ? '#e1e1e1' : '#333' } }
224- labelStyle = { { color : darkMode ? '#e1e1e1' : '#333' , fontWeight : 600 } }
225- />
226- < Bar dataKey = { valueKey } radius = { [ 4 , 4 , 4 , 4 ] } fill = { barColor } barSize = { barSize } >
227- < LabelList
228- dataKey = { valueKey }
229- position = { isHorizontal ? 'right' : 'top' }
230- formatter = { valueFormatter }
231- style = { { fontSize : 15 , fontWeight : 600 } }
232- offset = { 8 }
233- />
234- </ Bar >
235- </ BarChart >
236- </ ResponsiveContainer >
283+ height = { height }
284+ isHorizontal = { isHorizontal }
285+ margins = { margins }
286+ gridColor = { gridColor }
287+ axes = { axes }
288+ valueKey = { valueKey }
289+ barColor = { barColor }
290+ barSize = { barSize }
291+ valueFormatter = { valueFormatter }
292+ tooltipLabel = { tooltipLabel }
293+ metricLabel = { metricLabel }
294+ title = { title }
295+ darkMode = { darkMode }
296+ />
297+ ) : (
298+ < NoDataMessage height = { height } darkMode = { darkMode } />
299+ ) }
237300 </ div >
238301 </ CardBody >
239302 </ Card >
240303 ) ;
241304}
242305
306+ GraphHeader . propTypes = {
307+ title : PropTypes . string . isRequired ,
308+ metricLabel : PropTypes . string ,
309+ showMetricPill : PropTypes . bool . isRequired ,
310+ headerChips : PropTypes . arrayOf (
311+ PropTypes . shape ( {
312+ label : PropTypes . string . isRequired ,
313+ value : PropTypes . oneOfType ( [ PropTypes . string , PropTypes . number ] ) . isRequired ,
314+ } ) ,
315+ ) . isRequired ,
316+ darkMode : PropTypes . bool . isRequired ,
317+ } ;
318+
319+ NoDataMessage . propTypes = {
320+ height : PropTypes . number . isRequired ,
321+ darkMode : PropTypes . bool . isRequired ,
322+ } ;
323+
324+ CompareChart . propTypes = {
325+ data : PropTypes . arrayOf ( PropTypes . object ) . isRequired ,
326+ height : PropTypes . number . isRequired ,
327+ isHorizontal : PropTypes . bool . isRequired ,
328+ margins : PropTypes . shape ( {
329+ top : PropTypes . number ,
330+ right : PropTypes . number ,
331+ bottom : PropTypes . number ,
332+ left : PropTypes . number ,
333+ } ) . isRequired ,
334+ gridColor : PropTypes . string . isRequired ,
335+ axes : PropTypes . shape ( {
336+ xAxis : PropTypes . node . isRequired ,
337+ yAxis : PropTypes . node . isRequired ,
338+ } ) . isRequired ,
339+ valueKey : PropTypes . string . isRequired ,
340+ barColor : PropTypes . string . isRequired ,
341+ barSize : PropTypes . number ,
342+ valueFormatter : PropTypes . func . isRequired ,
343+ tooltipLabel : PropTypes . string ,
344+ metricLabel : PropTypes . string ,
345+ title : PropTypes . string . isRequired ,
346+ darkMode : PropTypes . bool . isRequired ,
347+ } ;
348+
243349CompareBarGraph . propTypes = {
244350 title : PropTypes . string . isRequired ,
245351 metricLabel : PropTypes . string ,
@@ -272,7 +378,6 @@ CompareBarGraph.propTypes = {
272378 bottom : PropTypes . number ,
273379 left : PropTypes . number ,
274380 } ) ,
275- maxBars : PropTypes . number ,
276381 showYAxisTitle : PropTypes . bool ,
277382 yTickFormatter : PropTypes . func ,
278383 darkMode : PropTypes . bool ,
0 commit comments