1- import React , { useState , useEffect , useCallback , useRef , useId , useContext , useReducer } from 'react'
1+ import React , { useState , useEffect , useCallback , useRef , useId , useContext , useReducer , useMemo } from 'react'
22
33// IE11
44import ResizeObserver from 'resize-observer-polyfill'
@@ -47,6 +47,7 @@ import { lineOptions } from './helpers/lineOptions'
4747import { handleLineType } from './helpers/handleLineType'
4848import { handleRankByValue } from './helpers/handleRankByValue'
4949import { generateColorsArray } from '@cdc/core/helpers/generateColorsArray'
50+ import { processMarkupVariables } from '@cdc/core/helpers/markupProcessor'
5051import Loading from '@cdc/core/components/Loading'
5152import Filters from '@cdc/core/components/Filters'
5253import MediaControls from '@cdc/core/components/MediaControls'
@@ -154,6 +155,73 @@ const CdcChart: React.FC<CdcChartProps> = ({
154155 // Destructure items from config for more readable JSX
155156 let { legend, title } = config
156157
158+ // Process markup variables for text fields (memoized to prevent re-processing on every render)
159+ // Note: XSS Safety - The processed content is parsed using html-react-parser which sanitizes
160+ // HTML input by default. The markup processor returns plain text with user data substituted.
161+ const processedTextFields = useMemo ( ( ) => {
162+ if ( ! config . enableMarkupVariables || ! config . markupVariables ?. length ) {
163+ return {
164+ title,
165+ superTitle : config . superTitle ,
166+ introText : config . introText ,
167+ legacyFootnotes : config . legacyFootnotes ,
168+ description : config . description
169+ }
170+ }
171+
172+ return {
173+ title : title
174+ ? processMarkupVariables ( title , config . data || [ ] , config . markupVariables , {
175+ isEditor,
176+ filters : config . filters || [ ]
177+ } ) . processedContent
178+ : title ,
179+ superTitle : config . superTitle
180+ ? processMarkupVariables ( config . superTitle , config . data || [ ] , config . markupVariables , {
181+ isEditor,
182+ filters : config . filters || [ ]
183+ } ) . processedContent
184+ : config . superTitle ,
185+ introText : config . introText
186+ ? processMarkupVariables ( config . introText , config . data || [ ] , config . markupVariables , {
187+ isEditor,
188+ filters : config . filters || [ ]
189+ } ) . processedContent
190+ : config . introText ,
191+ legacyFootnotes : config . legacyFootnotes
192+ ? processMarkupVariables ( config . legacyFootnotes , config . data || [ ] , config . markupVariables , {
193+ isEditor,
194+ filters : config . filters || [ ]
195+ } ) . processedContent
196+ : config . legacyFootnotes ,
197+ description : config . description
198+ ? processMarkupVariables ( config . description , config . data || [ ] , config . markupVariables , {
199+ isEditor,
200+ filters : config . filters || [ ]
201+ } ) . processedContent
202+ : config . description
203+ }
204+ } , [
205+ config . enableMarkupVariables ,
206+ config . markupVariables ,
207+ config . data ,
208+ config . filters ,
209+ title ,
210+ config . superTitle ,
211+ config . introText ,
212+ config . legacyFootnotes ,
213+ config . description ,
214+ isEditor
215+ ] )
216+
217+ // Destructure processed values
218+ title = processedTextFields . title
219+ const processedSuperTitle = processedTextFields . superTitle
220+ const processedIntroText = processedTextFields . introText
221+ const processedLegacyFootnotes = processedTextFields . legacyFootnotes
222+ const processedDescription = processedTextFields . description
223+ // Note: Axis labels are processed within updateConfig to ensure they use the correct data
224+
157225 // set defaults on titles if blank AND only in editor
158226 if ( isEditor ) {
159227 if ( ! title || title === '' ) title = 'Chart Title'
@@ -213,6 +281,25 @@ const CdcChart: React.FC<CdcChartProps> = ({
213281
214282 data = handleRankByValue ( data , newConfig )
215283
284+ // Process axis labels for markup variables if enabled
285+ let processedXAxis = newConfig . xAxis ?. label
286+ let processedYAxis = newConfig . yAxis ?. label
287+
288+ if ( newConfig . enableMarkupVariables && newConfig . markupVariables ?. length ) {
289+ if ( newConfig . xAxis ?. label ) {
290+ processedXAxis = processMarkupVariables ( newConfig . xAxis . label , data || [ ] , newConfig . markupVariables , {
291+ isEditor,
292+ filters : newConfig . filters || [ ]
293+ } ) . processedContent
294+ }
295+ if ( newConfig . yAxis ?. label ) {
296+ processedYAxis = processMarkupVariables ( newConfig . yAxis . label , data || [ ] , newConfig . markupVariables , {
297+ isEditor,
298+ filters : newConfig . filters || [ ]
299+ } ) . processedContent
300+ }
301+ }
302+
216303 // Deeper copy
217304 Object . keys ( defaults ) . forEach ( key => {
218305 if ( newConfig [ key ] && 'object' === typeof newConfig [ key ] && ! Array . isArray ( newConfig [ key ] ) ) {
@@ -311,8 +398,15 @@ const CdcChart: React.FC<CdcChartProps> = ({
311398 newConfig . orientation === 'horizontal' ) ||
312399 [ 'Deviation Bar' , 'Paired Bar' , 'Forest Plot' ] . includes ( newConfig . visualizationType )
313400 ) {
314- newConfig . runtime . xAxis = _ . cloneDeep ( newConfig . yAxis . yAxis || newConfig . yAxis )
315- newConfig . runtime . yAxis = _ . cloneDeep ( newConfig . xAxis . xAxis || newConfig . xAxis )
401+ // For horizontal charts, axes are swapped, so processedYAxis goes to runtime.xAxis and vice versa
402+ newConfig . runtime . xAxis = {
403+ ..._ . cloneDeep ( newConfig . yAxis . yAxis || newConfig . yAxis ) ,
404+ label : processedYAxis || ( newConfig . yAxis . yAxis || newConfig . yAxis ) . label
405+ }
406+ newConfig . runtime . yAxis = {
407+ ..._ . cloneDeep ( newConfig . xAxis . xAxis || newConfig . xAxis ) ,
408+ label : processedXAxis || ( newConfig . xAxis . xAxis || newConfig . xAxis ) . label
409+ }
316410 newConfig . runtime . yAxis . labelOffset *= - 1
317411
318412 newConfig . runtime . horizontal = false
@@ -323,13 +417,13 @@ const CdcChart: React.FC<CdcChartProps> = ({
323417 [ 'Scatter Plot' , 'Area Chart' , 'Line' , 'Forecasting' ] . includes ( newConfig . visualizationType ) &&
324418 ! convertLineToBarGraph
325419 ) {
326- newConfig . runtime . xAxis = newConfig . xAxis
327- newConfig . runtime . yAxis = newConfig . yAxis
420+ newConfig . runtime . xAxis = { ... newConfig . xAxis , label : processedXAxis || newConfig . xAxis . label }
421+ newConfig . runtime . yAxis = { ... newConfig . yAxis , label : processedYAxis || newConfig . yAxis . label }
328422 newConfig . runtime . horizontal = false
329423 newConfig . orientation = 'vertical'
330424 } else {
331- newConfig . runtime . xAxis = newConfig . xAxis
332- newConfig . runtime . yAxis = newConfig . yAxis
425+ newConfig . runtime . xAxis = { ... newConfig . xAxis , label : processedXAxis || newConfig . xAxis . label }
426+ newConfig . runtime . yAxis = { ... newConfig . yAxis , label : processedYAxis || newConfig . yAxis . label }
333427 newConfig . runtime . horizontal = false
334428 }
335429
@@ -446,8 +540,16 @@ const CdcChart: React.FC<CdcChartProps> = ({
446540 } else if ( newConfig . formattedData ) {
447541 newConfig . data = newConfig . formattedData
448542 } else if ( newConfig . dataDescription ) {
449- newConfig . data = transform . autoStandardize ( newConfig . data )
450- newConfig . data = transform . developerStandardize ( newConfig . data , newConfig . dataDescription )
543+ // For dashboard contexts, get data from datasets if config.data is undefined
544+ let dataToProcess = newConfig . data
545+ if ( ! dataToProcess && isDashboard && datasets && newConfig . dataKey ) {
546+ dataToProcess = datasets [ newConfig . dataKey ] ?. data
547+ }
548+
549+ if ( dataToProcess ) {
550+ newConfig . data = transform . autoStandardize ( dataToProcess )
551+ newConfig . data = transform . developerStandardize ( newConfig . data , newConfig . dataDescription )
552+ }
451553 }
452554 } catch ( err ) {
453555 console . error ( 'Error on prepareData function ' , err )
@@ -919,7 +1021,7 @@ const CdcChart: React.FC<CdcChartProps> = ({
9191021 showTitle = { config . showTitle }
9201022 isDashboard = { isDashboard }
9211023 title = { title }
922- superTitle = { config . superTitle }
1024+ superTitle = { processedSuperTitle }
9231025 classes = { [ 'chart-title' , `${ config . theme } ` , 'cove-component__header' , 'mb-3' ] }
9241026 style = { undefined }
9251027 config = { config }
@@ -928,8 +1030,8 @@ const CdcChart: React.FC<CdcChartProps> = ({
9281030 { /* Visualization Wrapper */ }
9291031 < div className = { getChartWrapperClasses ( ) . join ( ' ' ) } >
9301032 { /* Intro Text/Message */ }
931- { config ?. introText && config . visualizationType !== 'Spark Line' && (
932- < section className = { `introText mb-4` } > { parse ( config . introText ) } </ section >
1033+ { processedIntroText && config . visualizationType !== 'Spark Line' && (
1034+ < section className = { `introText mb-4` } > { parse ( processedIntroText ) } </ section >
9331035 ) }
9341036
9351037 { /* Filters */ }
@@ -976,7 +1078,14 @@ const CdcChart: React.FC<CdcChartProps> = ({
9761078
9771079 { config . visualizationType === 'Pie' && (
9781080 < ParentSize className = 'justify-content-center d-flex' style = { { width : `100%` } } >
979- { parent => < PieChart ref = { svgRef } parentWidth = { parent . width } parentHeight = { parent . height } /> }
1081+ { parent => (
1082+ < PieChart
1083+ ref = { svgRef }
1084+ parentWidth = { parent . width }
1085+ parentHeight = { parent . height }
1086+ interactionLabel = { interactionLabel }
1087+ />
1088+ ) }
9801089 </ ParentSize >
9811090 ) }
9821091 { /* Line Chart */ }
@@ -1020,9 +1129,9 @@ const CdcChart: React.FC<CdcChartProps> = ({
10201129 dimensions = { dimensions }
10211130 interactionLabel = { interactionLabel }
10221131 />
1023- { config ?. introText && (
1132+ { processedIntroText && (
10241133 < section className = 'introText mb-4' style = { { padding : '0px 0 35px' } } >
1025- { parse ( config . introText ) }
1134+ { parse ( processedIntroText ) }
10261135 </ section >
10271136 ) }
10281137 < div style = { { height : `100px` , width : `100%` , ...sparkLineStyles } } >
@@ -1059,8 +1168,8 @@ const CdcChart: React.FC<CdcChartProps> = ({
10591168 : link && link }
10601169 { /* Description */ }
10611170
1062- { config . description && config . visualizationType !== 'Spark Line' && (
1063- < div className = { getChartSubTextClasses ( ) . join ( ' ' ) } > { parse ( config . description ) } </ div >
1171+ { processedDescription && config . visualizationType !== 'Spark Line' && (
1172+ < div className = { getChartSubTextClasses ( ) . join ( ' ' ) } > { parse ( processedDescription ) } </ div >
10641173 ) }
10651174
10661175 { /* buttons */ }
@@ -1121,8 +1230,8 @@ const CdcChart: React.FC<CdcChartProps> = ({
11211230 ) }
11221231 { config ?. annotations ?. length > 0 && < Annotation . Dropdown /> }
11231232 { /* show pdf or image button */ }
1124- { config ?. legacyFootnotes && (
1125- < section className = 'footnotes pt-2 mt-4' > { parse ( config . legacyFootnotes ) } </ section >
1233+ { processedLegacyFootnotes && (
1234+ < section className = 'footnotes pt-2 mt-4' > { parse ( processedLegacyFootnotes ) } </ section >
11261235 ) }
11271236 </ div >
11281237 < FootnotesStandAlone
0 commit comments