@cdc/chart 4.24.5 → 4.24.9

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.
Files changed (87) hide show
  1. package/dist/cdcchart.js +44197 -38258
  2. package/examples/cases-year.json +13379 -0
  3. package/examples/feature/annotations/index.json +542 -0
  4. package/examples/gallery/bar-chart-vertical/combo-line-chart.json +76 -15
  5. package/examples/gallery/bar-chart-vertical/vertical-bar-chart-stacked.json +5 -5
  6. package/examples/xaxis.json +493 -0
  7. package/index.html +20 -10
  8. package/package.json +5 -4
  9. package/src/CdcChart.tsx +462 -172
  10. package/src/_stories/Chart.Legend.Gradient.tsx +19 -0
  11. package/src/_stories/Chart.stories.tsx +18 -171
  12. package/src/_stories/ChartAnnotation.stories.tsx +32 -0
  13. package/src/_stories/_mock/annotation_category_mock.json +473 -0
  14. package/src/_stories/_mock/annotation_date-linear_mock.json +530 -0
  15. package/{examples/feature/line/line-chart.json → src/_stories/_mock/annotation_date-time_mock.json} +150 -69
  16. package/src/_stories/_mock/legend.gradient_mock.json +236 -0
  17. package/src/_stories/_mock/line_chart_two_points_new_chart.json +128 -0
  18. package/src/_stories/_mock/line_chart_two_points_regression_test.json +127 -0
  19. package/src/_stories/_mock/lollipop.json +171 -0
  20. package/src/components/Annotations/components/AnnotationDraggable.styles.css +31 -0
  21. package/src/components/Annotations/components/AnnotationDraggable.tsx +207 -0
  22. package/src/components/Annotations/components/AnnotationDropdown.styles.css +14 -0
  23. package/src/components/Annotations/components/AnnotationDropdown.tsx +72 -0
  24. package/src/components/Annotations/components/AnnotationList.styles.css +45 -0
  25. package/src/components/Annotations/components/AnnotationList.tsx +42 -0
  26. package/src/components/Annotations/components/findNearestDatum.ts +138 -0
  27. package/src/components/Annotations/components/helpers/index.tsx +46 -0
  28. package/src/components/Annotations/index.tsx +13 -0
  29. package/src/components/AreaChart/components/AreaChart.Stacked.jsx +1 -1
  30. package/src/components/AreaChart/components/AreaChart.jsx +1 -1
  31. package/src/components/Axis/Categorical.Axis.tsx +145 -0
  32. package/src/components/BarChart/components/BarChart.Horizontal.tsx +47 -44
  33. package/src/components/BarChart/components/BarChart.StackedHorizontal.tsx +0 -1
  34. package/src/components/BarChart/components/BarChart.StackedVertical.tsx +11 -14
  35. package/src/components/BarChart/components/BarChart.Vertical.tsx +67 -30
  36. package/src/components/BarChart/helpers/index.ts +91 -0
  37. package/src/components/BrushChart.tsx +205 -0
  38. package/src/components/EditorPanel/EditorPanel.tsx +1794 -403
  39. package/src/components/EditorPanel/components/Panels/Panel.Annotate.tsx +320 -0
  40. package/src/components/EditorPanel/components/Panels/Panel.General.tsx +282 -18
  41. package/src/components/EditorPanel/components/Panels/Panel.Sankey.tsx +43 -8
  42. package/src/components/EditorPanel/components/Panels/Panel.Series.tsx +4 -4
  43. package/src/components/EditorPanel/components/Panels/Panel.Visual.tsx +4 -13
  44. package/src/components/EditorPanel/components/Panels/index.tsx +3 -1
  45. package/src/components/EditorPanel/components/panels.scss +4 -0
  46. package/src/components/EditorPanel/editor-panel.scss +35 -3
  47. package/src/components/EditorPanel/{useEditorPermissions.js → useEditorPermissions.ts} +105 -17
  48. package/src/components/Legend/Legend.Component.tsx +185 -194
  49. package/src/components/Legend/Legend.Suppression.tsx +146 -0
  50. package/src/components/Legend/Legend.tsx +21 -5
  51. package/src/components/Legend/helpers/createFormatLabels.tsx +1 -1
  52. package/src/components/Legend/helpers/index.ts +35 -0
  53. package/src/components/LegendWrapper.tsx +26 -0
  54. package/src/components/LineChart/LineChartProps.ts +1 -15
  55. package/src/components/LineChart/components/LineChart.BumpCircle.tsx +103 -0
  56. package/src/components/LineChart/components/LineChart.Circle.tsx +47 -8
  57. package/src/components/LineChart/helpers.ts +72 -14
  58. package/src/components/LineChart/index.tsx +117 -42
  59. package/src/components/LinearChart.jsx +179 -136
  60. package/src/components/LinearChart.tsx +1366 -0
  61. package/src/components/PairedBarChart.jsx +9 -9
  62. package/src/components/PieChart/PieChart.tsx +75 -18
  63. package/src/components/Sankey/index.tsx +89 -30
  64. package/src/components/ScatterPlot/ScatterPlot.jsx +22 -8
  65. package/src/components/Sparkline/components/SparkLine.tsx +2 -2
  66. package/src/components/ZoomBrush.tsx +90 -44
  67. package/src/data/initial-state.js +25 -7
  68. package/src/helpers/handleChartTabbing.ts +8 -0
  69. package/src/helpers/isConvertLineToBarGraph.ts +4 -0
  70. package/src/hooks/{useBarChart.js → useBarChart.ts} +2 -40
  71. package/src/hooks/useColorScale.ts +1 -1
  72. package/src/hooks/useLegendClasses.ts +68 -0
  73. package/src/hooks/useMinMax.ts +12 -7
  74. package/src/hooks/useScales.ts +58 -26
  75. package/src/hooks/useTooltip.tsx +135 -25
  76. package/src/scss/DataTable.scss +2 -1
  77. package/src/scss/main.scss +128 -28
  78. package/src/types/ChartConfig.ts +83 -10
  79. package/src/types/ChartContext.ts +14 -4
  80. package/tests-examples/helpers/testZeroValue.test.ts +30 -0
  81. package/LICENSE +0 -201
  82. package/src/components/BrushHandle.jsx +0 -17
  83. package/src/components/LineChart/index.scss +0 -1
  84. package/src/helpers/filterData.ts +0 -18
  85. package/src/helpers/tests/computeMarginBottom.test.ts +0 -21
  86. package/src/hooks/useLegendClasses.js +0 -31
  87. /package/src/hooks/{useReduceData.js → useReduceData.ts} +0 -0
@@ -18,7 +18,7 @@ const PairedBarChart = ({ width, height, originalWidth }) => {
18
18
  const offset = 1.02 // Offset of the left bar from the Axis
19
19
 
20
20
  const groupOne = {
21
- parentKey: config.dataDescription.seriesKey,
21
+ parentKey: config.dataDescription?.seriesKey,
22
22
  dataKey: config.series[0].dataKey,
23
23
  dataKeyLabel: config.runtime.seriesLabels[config.series[0].dataKey] || config.series[0].dataKey,
24
24
  color: colorScale(config.runtime.seriesLabels[config.series[0].dataKey]),
@@ -30,7 +30,7 @@ const PairedBarChart = ({ width, height, originalWidth }) => {
30
30
  }
31
31
 
32
32
  const groupTwo = {
33
- parentKey: config.dataDescription.seriesKey,
33
+ parentKey: config.dataDescription?.seriesKey,
34
34
  dataKey: config.series[1].dataKey,
35
35
  dataKeyLabel: config.runtime.seriesLabels[config.series[1].dataKey] || config.series[1].dataKey,
36
36
  color: colorScale(config.runtime.seriesLabels[config.series[1].dataKey]),
@@ -54,7 +54,7 @@ const PairedBarChart = ({ width, height, originalWidth }) => {
54
54
 
55
55
  const dataTipOne = d => {
56
56
  return `<p>
57
- ${config.dataDescription.seriesKey}: ${groupOne.dataKeyLabel}<br/>
57
+ ${config.dataDescription?.seriesKey}: ${groupOne.dataKeyLabel}<br/>
58
58
  ${config.xAxis.dataKey}: ${d[config.xAxis.dataKey]}<br/>
59
59
  ${label}${formatNumber(d[groupOne.dataKey], 'left')}
60
60
  </p>`
@@ -62,7 +62,7 @@ const PairedBarChart = ({ width, height, originalWidth }) => {
62
62
 
63
63
  const dataTipTwo = d => {
64
64
  return `<p>
65
- ${config.dataDescription.seriesKey}: ${groupTwo.dataKeyLabel}<br/>
65
+ ${config.dataDescription?.seriesKey}: ${groupTwo.dataKeyLabel}<br/>
66
66
  ${config.xAxis.dataKey}: ${d[config.xAxis.dataKey]}<br/>
67
67
  ${label}${formatNumber(d[groupTwo.dataKey], 'left')}
68
68
  </p>`
@@ -102,9 +102,9 @@ const PairedBarChart = ({ width, height, originalWidth }) => {
102
102
  <>
103
103
  <Group key={`group-${groupOne.dataKey}-${d[config.xAxis.dataKey]}`} className='horizontal'>
104
104
  <Bar
105
- id={`bar-${groupOne.dataKey}-${d[config.dataDescription.xKey]}`}
105
+ id={`bar-${groupOne.dataKey}-${d[config.dataDescription?.xKey]}`}
106
106
  className='bar group-1'
107
- key={`bar-${groupOne.dataKey}-${d[config.dataDescription.xKey]}`}
107
+ key={`bar-${groupOne.dataKey}-${d[config.dataDescription?.xKey]}`}
108
108
  x={halfWidth - barWidth}
109
109
  y={y}
110
110
  width={xScale(d[config.series[0].dataKey])}
@@ -152,11 +152,11 @@ const PairedBarChart = ({ width, height, originalWidth }) => {
152
152
  }
153
153
  `}
154
154
  </style>
155
- <Group key={`group-${groupTwo.dataKey}-${d[config.dataDescription.xKey]}`} className='horizontal'>
155
+ <Group key={`group-${groupTwo.dataKey}-${d[config.dataDescription?.xKey]}`} className='horizontal'>
156
156
  <Bar
157
- id={`bar-${groupTwo.dataKey}-${d[config.dataDescription.xKey]}`}
157
+ id={`bar-${groupTwo.dataKey}-${d[config.dataDescription?.xKey]}`}
158
158
  className='bar group-2'
159
- key={`bar-${groupTwo.dataKey}-${d[config.dataDescription.xKey]}`}
159
+ key={`bar-${groupTwo.dataKey}-${d[config.dataDescription?.xKey]}`}
160
160
  x={halfWidth}
161
161
  y={y}
162
162
  width={xScale(d[config.series[1].dataKey])}
@@ -33,7 +33,17 @@ type TooltipData = {
33
33
  }
34
34
 
35
35
  const PieChart = props => {
36
- const { transformedData: data, config, colorScale, currentViewport, dimensions, highlight, highlightReset, seriesHighlight } = useContext(ConfigContext)
36
+ const {
37
+ transformedData: data,
38
+ config,
39
+ colorScale,
40
+ currentViewport,
41
+ dimensions,
42
+ highlight,
43
+ highlightReset,
44
+ seriesHighlight,
45
+ isDraggingAnnotation
46
+ } = useContext(ConfigContext)
37
47
  const { tooltipData, showTooltip, hideTooltip, tooltipOpen, tooltipLeft, tooltipTop } = useTooltip<TooltipData>()
38
48
  const { handleTooltipMouseOver, handleTooltipMouseOff, TooltipListItem } = useCoveTooltip({
39
49
  xScale: false,
@@ -135,7 +145,18 @@ const PieChart = props => {
135
145
  <>
136
146
  {transitions.map(({ item: arc, props, key }, animatedPieIndex) => {
137
147
  return (
138
- <Group className={arc.data[config.xAxis.dataKey]} key={`${key}-${animatedPieIndex}`} style={{ opacity: config.legend.behavior === 'highlight' && seriesHighlight.length > 0 && seriesHighlight.indexOf(arc.data[config.runtime.xAxis.dataKey]) === -1 ? 0.5 : 1 }}>
148
+ <Group
149
+ className={arc.data[config.xAxis.dataKey]}
150
+ key={`${key}-${animatedPieIndex}`}
151
+ style={{
152
+ opacity:
153
+ config.legend.behavior === 'highlight' &&
154
+ seriesHighlight.length > 0 &&
155
+ seriesHighlight.indexOf(arc.data[config.runtime.xAxis.dataKey]) === -1
156
+ ? 0.5
157
+ : 1
158
+ }}
159
+ >
139
160
  <animated.path
140
161
  d={interpolate([props.startAngle, props.endAngle], (startAngle, endAngle) =>
141
162
  path({
@@ -163,7 +184,14 @@ const PieChart = props => {
163
184
  return (
164
185
  <animated.g key={`${key}${i}`}>
165
186
  {hasSpaceForLabel && (
166
- <Text style={{ fill: textColor }} x={centroidX} y={centroidY} dy='.33em' textAnchor='middle' pointerEvents='none'>
187
+ <Text
188
+ style={{ fill: textColor }}
189
+ x={centroidX}
190
+ y={centroidY}
191
+ dy='.33em'
192
+ textAnchor='middle'
193
+ pointerEvents='none'
194
+ >
167
195
  {Math.round((((arc.endAngle - arc.startAngle) * 180) / Math.PI / 360) * 100) + '%'}
168
196
  </Text>
169
197
  )}
@@ -174,17 +202,18 @@ const PieChart = props => {
174
202
  )
175
203
  }
176
204
 
177
- let [width] = dimensions
205
+ let chartWidth = props.parentWidth
206
+ let width = props.parentWidth
178
207
 
179
208
  if (config && config.legend && !config.legend.hide && currentViewport === 'lg') {
180
- width = width * 0.73
209
+ width = Number(chartWidth) * 0.73
181
210
  }
182
211
 
183
212
  const height = config.heights.vertical
184
213
 
185
214
  const radius = Math.min(width, height) / 2
186
215
  const centerY = height / 2
187
- const centerX = width / 2
216
+ const centerX = props.parentWidth / 2
188
217
  const donutThickness = config.pieType === 'Donut' ? 75 : radius
189
218
 
190
219
  useEffect(() => {
@@ -205,11 +234,25 @@ const PieChart = props => {
205
234
 
206
235
  const createLegendLabels = createFormatLabels(config, [], _data, _colorScale)
207
236
 
237
+ const getSvgClasses = () => {
238
+ let classes = ['animated-pie', 'group']
239
+ if (config.animate === false || animatedPie) {
240
+ classes.push('animated')
241
+ }
242
+ return classes.join(' ')
243
+ }
244
+
208
245
  return (
209
246
  <>
210
247
  <ErrorBoundary component='PieChart'>
211
- <svg width={width} height={height} className={`animated-pie group ${config.animate === false || animatedPie ? 'animated' : ''}`} role='img' aria-label={handleChartAriaLabels(config)}>
212
- <Group top={centerY} left={centerX}>
248
+ <svg
249
+ width={radius * 2}
250
+ height={height}
251
+ className={getSvgClasses()}
252
+ role='img'
253
+ aria-label={handleChartAriaLabels(config)}
254
+ >
255
+ <Group top={centerY} left={radius}>
213
256
  {/* prettier-ignore */}
214
257
  <Pie
215
258
  data={filteredData || _data}
@@ -223,17 +266,31 @@ const PieChart = props => {
223
266
  </Group>
224
267
  </svg>
225
268
  <div ref={triggerRef} />
226
- {tooltipData && Object.entries(tooltipData.data).length > 0 && tooltipOpen && showTooltip && tooltipData.dataYPosition && tooltipData.dataXPosition && (
227
- <>
228
- <style>{`.tooltip {background-color: rgba(255,255,255, ${config.tooltips.opacity / 100}) !important`}</style>
229
- <TooltipWithBounds key={Math.random()} className={'tooltip cdc-open-viz-module'} left={tooltipLeft} top={tooltipTop}>
230
- <ul>{typeof tooltipData === 'object' && Object.entries(tooltipData.data).map((item, index) => <TooltipListItem item={item} key={index} />)}</ul>
231
- </TooltipWithBounds>
232
- </>
233
- )}
234
- {/* <ReactTooltip id={`cdc-open-viz-tooltip-${config.runtime.uniqueId}`} variant='light' arrowColor='rgba(0,0,0,0)' className='tooltip' /> */}
269
+ {!isDraggingAnnotation &&
270
+ tooltipData &&
271
+ Object.entries(tooltipData.data).length > 0 &&
272
+ tooltipOpen &&
273
+ showTooltip &&
274
+ tooltipData.dataYPosition &&
275
+ tooltipData.dataXPosition && (
276
+ <>
277
+ <style>{`.tooltip {background-color: rgba(255,255,255, ${
278
+ config.tooltips.opacity / 100
279
+ }) !important`}</style>
280
+ <TooltipWithBounds
281
+ key={Math.random()}
282
+ className={'tooltip cdc-open-viz-module'}
283
+ left={tooltipLeft}
284
+ top={tooltipTop}
285
+ >
286
+ <ul>
287
+ {typeof tooltipData === 'object' &&
288
+ Object.entries(tooltipData.data).map((item, index) => <TooltipListItem item={item} key={index} />)}
289
+ </ul>
290
+ </TooltipWithBounds>
291
+ </>
292
+ )}
235
293
  </ErrorBoundary>
236
- <LegendComponent config={config} colorScale={_colorScale} seriesHighlight={seriesHighlight} highlight={highlight} highlightReset={highlightReset} currentViewport={currentViewport} formatLabels={createLegendLabels} />
237
294
  </>
238
295
  )
239
296
  }
@@ -13,29 +13,38 @@ import 'react-tooltip/dist/react-tooltip.css'
13
13
  import ConfigContext from '@cdc/chart/src/ConfigContext'
14
14
  import { ChartContext } from '../../types/ChartContext'
15
15
  import type { SankeyNode, SankeyProps } from './types'
16
+ import { SankeyChartConfig, AllChartsConfig } from '../../types/ChartConfig'
16
17
 
17
18
  const Sankey = ({ width, height, runtime }: SankeyProps) => {
18
- const DEBUG = true
19
19
  const { config } = useContext<ChartContext>(ConfigContext)
20
20
  const { sankey: sankeyConfig } = config
21
- // !info - changed config.sankey.data here to work with our current upload pattern saved on config.data
22
- const data = config?.data[0]
21
+
22
+ const isSankeyChartConfig = (config: AllChartsConfig | SankeyChartConfig): config is SankeyChartConfig => {
23
+ return config.visualizationType === 'Sankey'
24
+ }
25
+
23
26
  const [largestGroupWidth, setLargestGroupWidth] = useState(0)
24
27
  const groupRefs = useRef([])
25
28
 
26
29
  //Tooltip
27
30
  const [tooltipID, setTooltipID] = useState<string>('')
31
+ //Mobile Pop Up
32
+ const [showPopup, setShowPopup] = useState(false)
28
33
 
29
34
  const handleNodeClick = (nodeId: string) => {
30
- setTooltipID(nodeId)
31
- }
35
+ // Store the previous tooltipID
36
+ const previousTooltipID = tooltipID
32
37
 
33
- const clearNodeClick = () => {
34
- setTooltipID('')
35
- }
38
+ // If the previous tooltipID exists, clear it
39
+ if (previousTooltipID) {
40
+ setTooltipID('')
41
+ }
36
42
 
37
- //Mobile Pop Up
38
- const [showPopup, setShowPopup] = useState(false)
43
+ // Update the tooltipID with the new nodeId if it's different from the previous one
44
+ if (previousTooltipID !== nodeId) {
45
+ setTooltipID(nodeId)
46
+ }
47
+ }
39
48
 
40
49
  useEffect(() => {
41
50
  if (window.innerWidth < 768 && window.innerHeight > window.innerWidth) {
@@ -60,6 +69,9 @@ const Sankey = ({ width, height, runtime }: SankeyProps) => {
60
69
  setLargestGroupWidth(largest)
61
70
  }, [groupRefs, sankeyConfig, window.innerWidth])
62
71
 
72
+ if (!isSankeyChartConfig(config)) return
73
+ const data = config?.data[0]
74
+
63
75
  //Retrieve all the unique values for the Nodes
64
76
  const uniqueNodes = Array.from(new Set(data?.links?.flatMap(link => [link.source, link.target])))
65
77
 
@@ -109,6 +121,8 @@ const Sankey = ({ width, height, runtime }: SankeyProps) => {
109
121
  }
110
122
 
111
123
  const activeConnection = (id: String) => {
124
+ if (!sankeyData?.nodes) return { sourceNodes: [], activeLinks: [] }
125
+
112
126
  const currentNode = sankeyData.nodes.find(node => node.id === id)
113
127
 
114
128
  const sourceNodes = []
@@ -137,12 +151,12 @@ const Sankey = ({ width, height, runtime }: SankeyProps) => {
137
151
  return { sourceNodes, activeLinks }
138
152
  }
139
153
 
140
- const tooltipVal = `${(data?.tooltips.find(item => item.node === tooltipID) || {}).value}`
141
- const tooltipSummary = `${(data?.tooltips.find(item => item.node === tooltipID) || {}).summary}`
142
- const tooltipColumn1Label = (data?.tooltips.find(item => item.node === tooltipID) || {}).column1Label
143
- const tooltipColumn2Label = (data?.tooltips.find(item => item.node === tooltipID) || {}).column2Label
144
- const tooltipColumn1 = (data?.tooltips.find(item => item.node === tooltipID) || {}).column1
145
- const tooltipColumn2 = (data?.tooltips.find(item => item.node === tooltipID) || {}).column2
154
+ const tooltipVal = `${(data?.tooltips?.find(item => item.node === tooltipID) || {}).value}`
155
+ const tooltipSummary = `${(data?.tooltips?.find(item => item.node === tooltipID) || {}).summary}`
156
+ const tooltipColumn1Label = (data?.tooltips?.find(item => item.node === tooltipID) || {}).column1Label
157
+ const tooltipColumn2Label = (data?.tooltips?.find(item => item.node === tooltipID) || {}).column2Label
158
+ const tooltipColumn1 = (data?.tooltips?.find(item => item.node === tooltipID) || {}).column1
159
+ const tooltipColumn2 = (data?.tooltips?.find(item => item.node === tooltipID) || {}).column2
146
160
 
147
161
  const ColumnList = ({ columnData }) => {
148
162
  return (
@@ -201,7 +215,7 @@ const Sankey = ({ width, height, runtime }: SankeyProps) => {
201
215
  fillOpacity={opacityValue}
202
216
  rx={sankeyConfig.rxValue}
203
217
  // todo: move enable tooltips to sankey
204
- data-tooltip-html={data.tooltips && config.enableTooltips ? sankeyToolTip : null}
218
+ data-tooltip-html={data.tooltips && config.enableTooltips && tooltipID !== '' ? sankeyToolTip : null}
205
219
  data-tooltip-id={`cdc-open-viz-tooltip-${runtime.uniqueId}-sankey`}
206
220
  onClick={() => handleNodeClick(node.id)}
207
221
  style={{ pointerEvents: 'visible', cursor: 'pointer' }}
@@ -229,12 +243,27 @@ const Sankey = ({ width, height, runtime }: SankeyProps) => {
229
243
  */
230
244
  fill={sankeyConfig.nodeFontColor}
231
245
  fontWeight='bold' // font weight
232
- style={{ pointerEvents: 'none' }}
233
246
  className='node-text'
247
+ style={{ pointerEvents: 'auto', cursor: 'pointer' }} // Enable pointer events
248
+ onClick={() => handleNodeClick(node.id)}
249
+ data-tooltip-html={data.tooltips && config.enableTooltips && tooltipID !== '' ? sankeyToolTip : null}
250
+ data-tooltip-id={`cdc-open-viz-tooltip-${runtime.uniqueId}-sankey`}
234
251
  >
235
252
  {(data?.storyNodeText?.find(storyNode => storyNode.StoryNode === node.id) || {}).segmentTextBefore}
236
253
  </Text>
237
- <Text verticalAnchor='end' className={classStyle} x={node.x0! + textPositionHorizontal} y={(node.y1! + node.y0! + 25) / 2} fill={sankeyConfig.storyNodeFontColor || sankeyConfig.nodeFontColor} fontWeight='bold' textAnchor='start' style={{ pointerEvents: 'none' }}>
254
+ <Text
255
+ verticalAnchor='end'
256
+ className={classStyle}
257
+ x={node.x0! + textPositionHorizontal}
258
+ y={(node.y1! + node.y0! + 25) / 2}
259
+ fill={sankeyConfig.storyNodeFontColor || sankeyConfig.nodeFontColor}
260
+ fontWeight='bold'
261
+ textAnchor='start'
262
+ style={{ pointerEvents: 'auto', cursor: 'pointer' }} // Enable pointer events
263
+ onClick={() => handleNodeClick(node.id)}
264
+ data-tooltip-html={data.tooltips && config.enableTooltips && tooltipID !== '' ? sankeyToolTip : null}
265
+ data-tooltip-id={`cdc-open-viz-tooltip-${runtime.uniqueId}-sankey`}
266
+ >
238
267
  {typeof node.value === 'number' ? node.value.toLocaleString() : node.value}
239
268
  </Text>
240
269
  <Text
@@ -244,20 +273,32 @@ const Sankey = ({ width, height, runtime }: SankeyProps) => {
244
273
  fill={sankeyConfig.nodeFontColor}
245
274
  fontWeight='bold'
246
275
  textAnchor={sankeyData.nodes.length === i ? 'end' : 'start'}
247
- style={{ pointerEvents: 'none' }}
248
276
  className='node-text'
249
277
  verticalAnchor='end'
278
+ style={{ pointerEvents: 'auto', cursor: 'pointer' }} // Enable pointer events
279
+ onClick={() => handleNodeClick(node.id)}
280
+ data-tooltip-html={data.tooltips && config.enableTooltips && tooltipID !== '' ? sankeyToolTip : null}
281
+ data-tooltip-id={`cdc-open-viz-tooltip-${runtime.uniqueId}-sankey`}
250
282
  >
251
283
  {(data?.storyNodeText?.find(storyNode => storyNode.StoryNode === node.id) || {}).segmentTextAfter}
252
284
  </Text>
253
285
  </>
254
286
  ) : (
255
287
  <>
256
- <text x={node.x0! + textPositionHorizontal} y={(node.y1! + node.y0!) / 2 + textPositionVertical} dominantBaseline='text-before-edge' fill={sankeyConfig.nodeFontColor} fontWeight='bold' textAnchor='start' style={{ pointerEvents: 'none' }}>
257
- <tspan id={node.id} className='node-id'>
258
- {node.id}
259
- </tspan>
260
- </text>
288
+ <Text
289
+ style={{ pointerEvents: 'auto', cursor: 'pointer' }} // Enable pointer events
290
+ onClick={() => handleNodeClick(node.id)}
291
+ data-tooltip-html={data.tooltips && config.enableTooltips && tooltipID !== '' ? sankeyToolTip : null}
292
+ data-tooltip-id={`cdc-open-viz-tooltip-${runtime.uniqueId}-sankey`}
293
+ x={node.x0! + textPositionHorizontal}
294
+ y={(node.y1! + node.y0!) / 2 + textPositionVertical}
295
+ dominantBaseline='text-before-edge'
296
+ fill={sankeyConfig.nodeFontColor}
297
+ fontWeight='bold'
298
+ textAnchor='start'
299
+ >
300
+ {node.id}
301
+ </Text>
261
302
  <text
262
303
  x={node.x0! + textPositionHorizontal}
263
304
  /* adding 30 allows the node value to be on the next line underneath the node id */
@@ -267,7 +308,10 @@ const Sankey = ({ width, height, runtime }: SankeyProps) => {
267
308
  //fontSize={16}
268
309
  fontWeight='bold'
269
310
  textAnchor='start'
270
- style={{ pointerEvents: 'none' }}
311
+ style={{ pointerEvents: 'auto', cursor: 'pointer' }} // Enable pointer events
312
+ onClick={() => handleNodeClick(node.id)}
313
+ data-tooltip-html={data.tooltips && config.enableTooltips && tooltipID !== '' ? sankeyToolTip : null}
314
+ data-tooltip-id={`cdc-open-viz-tooltip-${runtime.uniqueId}-sankey`}
271
315
  >
272
316
  <tspan className={classStyle}>{sankeyConfig.nodeValueStyle.textBefore + (typeof node.value === 'number' ? node.value.toLocaleString() : node.value) + sankeyConfig.nodeValueStyle.textAfter}</tspan>
273
317
  </text>
@@ -291,7 +335,20 @@ const Sankey = ({ width, height, runtime }: SankeyProps) => {
291
335
  opacityValue = sankeyConfig.opacity.LinkOpacityInactive
292
336
  }
293
337
 
294
- return <path key={i} d={path!} stroke={strokeColor} fill='none' strokeOpacity={opacityValue} strokeWidth={link.width! + 2} />
338
+ return (
339
+ <path
340
+ key={i}
341
+ d={path!}
342
+ stroke={strokeColor}
343
+ fill='none'
344
+ strokeOpacity={opacityValue}
345
+ strokeWidth={link.width! + 2}
346
+ style={{ pointerEvents: 'auto', cursor: 'pointer' }} // Enable pointer events
347
+ onClick={() => handleNodeClick(link.target.id || null)}
348
+ data-tooltip-html={data.tooltips && config.enableTooltips && tooltipID !== '' ? sankeyToolTip : null}
349
+ data-tooltip-id={`cdc-open-viz-tooltip-${runtime.uniqueId}-sankey`}
350
+ />
351
+ )
295
352
  })
296
353
 
297
354
  // max depth - calculates how many nodes deep the chart goes.
@@ -325,7 +382,7 @@ const Sankey = ({ width, height, runtime }: SankeyProps) => {
325
382
  fillOpacity={opacityValue}
326
383
  rx={sankeyConfig.rxValue}
327
384
  // todo: move enable tooltips to sankey
328
- data-tooltip-html={data.tooltips && config.enableTooltips ? sankeyToolTip : null}
385
+ data-tooltip-html={data.tooltips && config.enableTooltips && tooltipID !== '' ? sankeyToolTip : null}
329
386
  data-tooltip-id={`tooltip`}
330
387
  onClick={() => handleNodeClick(node.id)}
331
388
  style={{ pointerEvents: 'visible', cursor: 'pointer' }}
@@ -393,7 +450,9 @@ const Sankey = ({ width, height, runtime }: SankeyProps) => {
393
450
  textAnchor='start'
394
451
  style={{ pointerEvents: 'none' }}
395
452
  >
396
- <tspan className={classStyle}>{sankeyConfig.nodeValueStyle.textBefore + (typeof node.value === 'number' ? node.value.toLocaleString() : node.value) + sankeyConfig.nodeValueStyle.textAfter}</tspan>
453
+ <tspan onClick={() => handleNodeClick(node.id)} className={classStyle}>
454
+ {sankeyConfig.nodeValueStyle.textBefore + (typeof node.value === 'number' ? node.value.toLocaleString() : node.value) + sankeyConfig.nodeValueStyle.textAfter}
455
+ </tspan>
397
456
  </text>
398
457
  </>
399
458
  )}
@@ -414,7 +473,7 @@ const Sankey = ({ width, height, runtime }: SankeyProps) => {
414
473
 
415
474
  {/* ReactTooltip needs to remain even if tooltips are disabled -- it handles when a user clicks off of the node and resets
416
475
  the sankey diagram. When tooltips are disabled this will nothing */}
417
- <ReactTooltip id={`cdc-open-viz-tooltip-${runtime.uniqueId}-sankey`} afterHide={() => clearNodeClick()} events={['click']} place={'bottom'} style={{ backgroundColor: `rgba(238, 238, 238, 1)`, color: 'black', boxShadow: `0 3px 10px rgb(0 0 0 / 0.2)` }} />
476
+ <ReactTooltip id={`cdc-open-viz-tooltip-${runtime.uniqueId}-sankey`} afterHide={() => setTooltipID('')} events={['click']} place={'bottom'} style={{ backgroundColor: `rgba(238, 238, 238, 1)`, color: 'black', boxShadow: `0 3px 10px rgb(0 0 0 / 0.2)` }} />
418
477
  {showPopup && (
419
478
  <div className='popup'>
420
479
  <div className='popup-content'>
@@ -1,18 +1,32 @@
1
1
  import React, { useContext } from 'react'
2
2
  import ConfigContext from '../../ConfigContext'
3
3
  import { Group } from '@visx/group'
4
+ import { formatNumber as formatColNumber } from '@cdc/core/helpers/cove/number'
4
5
 
5
- const ScatterPlot = ({ xScale, yScale, getXAxisData, getYAxisData }) => {
6
- const { colorScale, transformedData: data, config, formatNumber, seriesHighlight, colorPalettes } = useContext(ConfigContext)
6
+ const ScatterPlot = ({ xScale, yScale }) => {
7
+ const { transformedData: data, config, tableData, formatNumber, seriesHighlight, colorPalettes } = useContext(ConfigContext)
7
8
 
8
9
  // TODO: copied from line chart should probably be a constant somewhere.
9
- let circleRadii = 4.5
10
+ const circleRadii = 4.5
10
11
  const hasMultipleSeries = Object.keys(config.runtime.seriesLabels).length > 1
11
-
12
- const handleTooltip = (item, s) => `<div>
12
+ // tooltips for additional columns
13
+ const additionalColumns = Object.entries(config.columns)
14
+ .filter(([_, value]) => value.tooltips)
15
+ .map(([_, value]) => [
16
+ value.label || value.name,
17
+ value.name,
18
+ {
19
+ addColPrefix: value.prefix,
20
+ addColSuffix: value.suffix,
21
+ addColRoundTo: value.roundToPlace,
22
+ addColCommas: value.commas
23
+ }
24
+ ])
25
+ const handleTooltip = (item, s, dataIndex) => `<div>
13
26
  ${config.legend.showLegendValuesTooltip && config.runtime.seriesLabels && hasMultipleSeries ? `${config.runtime.seriesLabels[s] || ''}<br/>` : ''}
14
27
  ${config.xAxis.label}: ${formatNumber(item[config.xAxis.dataKey], 'bottom')} <br/>
15
- ${config.yAxis.label}: ${formatNumber(item[s], 'left')}
28
+ ${config.yAxis.label}: ${formatNumber(item[s], 'left')}<br/>
29
+ ${additionalColumns.map(([label, name, options]) => `${label} : ${formatColNumber(tableData[dataIndex][name], 'left', false, config, options)}<br/>`).join('')}
16
30
  </div>`
17
31
 
18
32
  return (
@@ -37,9 +51,9 @@ const ScatterPlot = ({ xScale, yScale, getXAxisData, getYAxisData }) => {
37
51
  cx={xScale(item[config.xAxis.dataKey])}
38
52
  cy={yScale(item[s])}
39
53
  fill={displayArea ? seriesColor : 'transparent'}
40
- fillOpacity={transparentArea ? .25 : 1}
54
+ fillOpacity={transparentArea ? 0.25 : 1}
41
55
  style={pointStyles}
42
- data-tooltip-html={handleTooltip(item, s)}
56
+ data-tooltip-html={handleTooltip(item, s, dataIndex)}
43
57
  data-tooltip-id={`cdc-open-viz-tooltip-${config.runtime.uniqueId}`}
44
58
  tabIndex={-1}
45
59
  />
@@ -107,10 +107,10 @@ const SparkLine: React.FC<SparkLineProps> = props => {
107
107
  opacity={config.legend.behavior === 'highlight' && seriesHighlight.length > 0 && seriesHighlight.indexOf(seriesKey) === -1 ? 0.5 : 1}
108
108
  display={config.legend.behavior === 'highlight' || seriesHighlight.length === 0 || seriesHighlight.indexOf(seriesKey) !== -1 ? 'block' : 'none'}
109
109
  >
110
- {data.map((d, dataIndex) => {
110
+ {config.labels && data.map((d, dataIndex) => {
111
111
  return (
112
112
  <Group key={`series-${seriesKey}-point-${dataIndex}`}>
113
- <Text display={config.labels ? 'block' : 'none'} x={xScale(getXAxisData(d))} y={yScale(getYAxisData(d, seriesKey))} fill={colorScale ? colorScale(config.runtime.seriesLabels ? config.runtime.seriesLabels[seriesKey] : seriesKey) : '#000'} textAnchor='middle'>
113
+ <Text x={xScale(getXAxisData(d))} y={yScale(getYAxisData(d, seriesKey))} fill={colorScale ? colorScale(config.runtime.seriesLabels ? config.runtime.seriesLabels[seriesKey] : seriesKey) : '#000'} textAnchor='middle'>
114
114
  {formatNumber(d[seriesKey])}
115
115
  </Text>
116
116
  </Group>