@cdc/chart 4.24.11 → 4.24.12-2

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 (65) hide show
  1. package/dist/cdcchart.js +32134 -32039
  2. package/examples/feature/sankey/sankey-example-data.json +126 -13
  3. package/examples/feature/tests-date-exclusions/date-exclusions-config.json +372 -12
  4. package/examples/private/DEV-8850-2.json +493 -0
  5. package/examples/private/DEV-9822.json +574 -0
  6. package/examples/private/DEV-9840.json +553 -0
  7. package/examples/private/DEV-9850-3.json +461 -0
  8. package/examples/private/chart.json +1084 -0
  9. package/examples/private/ci_formatted.json +202 -0
  10. package/examples/private/ci_issue.json +3016 -0
  11. package/examples/private/completed.json +634 -0
  12. package/examples/private/dem-data-long.csv +20 -0
  13. package/examples/private/dem-data-long.json +36 -0
  14. package/examples/private/demographic_data.csv +157 -0
  15. package/examples/private/demographic_data.json +2654 -0
  16. package/examples/private/demographic_dynamic.json +443 -0
  17. package/examples/private/demographic_standard.json +560 -0
  18. package/examples/private/ehdi.json +29939 -0
  19. package/examples/private/test.json +448 -20047
  20. package/index.html +9 -6
  21. package/package.json +2 -2
  22. package/src/CdcChart.tsx +62 -82
  23. package/src/_stories/Chart.Anchors.stories.tsx +31 -0
  24. package/src/_stories/Chart.DynamicSeries.stories.tsx +8 -1
  25. package/src/_stories/Chart.stories.tsx +32 -0
  26. package/src/_stories/ChartAxisLabels.stories.tsx +4 -1
  27. package/{examples/feature/area/area-chart-date-city-temperature.json → src/_stories/_mock/area_chart_stacked.json} +125 -27
  28. package/src/_stories/_mock/line_chart_dynamic_ci.json +493 -0
  29. package/src/_stories/_mock/line_chart_non_dynamic_ci.json +522 -0
  30. package/src/_stories/_mock/short_dates.json +288 -0
  31. package/src/components/AreaChart/components/AreaChart.Stacked.jsx +15 -3
  32. package/src/components/Axis/Categorical.Axis.tsx +2 -2
  33. package/src/components/BarChart/components/BarChart.Horizontal.tsx +46 -37
  34. package/src/components/BarChart/components/BarChart.Vertical.tsx +28 -40
  35. package/src/components/BarChart/helpers/getBarData.ts +28 -0
  36. package/src/components/BarChart/helpers/tests/getBarData.test.ts +74 -0
  37. package/src/components/BoxPlot/BoxPlot.tsx +12 -70
  38. package/src/components/BoxPlot/helpers/index.ts +54 -0
  39. package/src/components/BrushChart.tsx +23 -26
  40. package/src/components/EditorPanel/EditorPanel.tsx +55 -79
  41. package/src/components/EditorPanel/components/Panels/Panel.Series.tsx +1 -0
  42. package/src/components/EditorPanel/useEditorPermissions.ts +5 -1
  43. package/src/components/Legend/Legend.Component.tsx +2 -2
  44. package/src/{hooks/useLegendClasses.ts → components/Legend/helpers/getLegendClasses.ts} +5 -5
  45. package/src/components/Legend/helpers/index.ts +2 -1
  46. package/src/components/Legend/tests/getLegendClasses.test.ts +115 -0
  47. package/src/components/LineChart/components/LineChart.Circle.tsx +1 -1
  48. package/src/components/LineChart/helpers.ts +1 -0
  49. package/src/components/LineChart/index.tsx +47 -1
  50. package/src/components/LinearChart.tsx +180 -172
  51. package/src/components/PieChart/PieChart.tsx +7 -1
  52. package/src/components/Sankey/components/ColumnList.tsx +19 -0
  53. package/src/components/Sankey/components/Sankey.tsx +479 -0
  54. package/src/components/Sankey/helpers/getSankeyTooltip.tsx +33 -0
  55. package/src/components/Sankey/index.tsx +1 -510
  56. package/src/components/Sankey/sankey.scss +16 -16
  57. package/src/components/Sankey/types/index.ts +1 -1
  58. package/src/data/initial-state.js +4 -3
  59. package/src/helpers/countNumOfTicks.ts +57 -0
  60. package/src/helpers/getQuartiles.ts +15 -18
  61. package/src/hooks/useMinMax.ts +18 -4
  62. package/src/hooks/useScales.ts +38 -4
  63. package/src/hooks/useTooltip.tsx +5 -1
  64. package/src/scss/DataTable.scss +5 -0
  65. package/src/scss/main.scss +6 -2
@@ -0,0 +1,479 @@
1
+ import { useContext, useState, useRef, useEffect } from 'react'
2
+
3
+ // External Libraries
4
+ import { Tooltip as ReactTooltip } from 'react-tooltip'
5
+ import { SankeyGraph, sankey, sankeyLinkHorizontal, sankeyLeft } from 'd3-sankey'
6
+ import { Group } from '@visx/group'
7
+ import { Text } from '@visx/text'
8
+
9
+ // Cdc
10
+ import './../sankey.scss'
11
+ import 'react-tooltip/dist/react-tooltip.css'
12
+ import ConfigContext from '@cdc/chart/src/ConfigContext'
13
+ import type { ChartContext } from '../../../types/ChartContext'
14
+ import type { SankeyNode, SankeyProps } from '../types'
15
+ import useSankeyAlert from '../useSankeyAlert'
16
+ import { getSankeyTooltip } from '../helpers/getSankeyTooltip'
17
+
18
+ const Sankey = ({ width, height, runtime }: SankeyProps) => {
19
+ const { config } = useContext<ChartContext>(ConfigContext)
20
+ const { sankey: sankeyConfig } = config
21
+ const [largestGroupWidth, setLargestGroupWidth] = useState(0)
22
+ const [tooltipID, setTooltipID] = useState<string>('')
23
+ const { showAlert, alert } = useSankeyAlert()
24
+ const groupRefs = useRef([])
25
+
26
+ const handleNodeClick = (nodeId: string) => {
27
+ // Store the previous tooltipID
28
+ const previousTooltipID = tooltipID
29
+
30
+ // If the previous tooltipID exists, clear it
31
+ if (previousTooltipID) {
32
+ setTooltipID('')
33
+ }
34
+
35
+ // Update the tooltipID with the new nodeId if it's different from the previous one
36
+ if (previousTooltipID !== nodeId) {
37
+ setTooltipID(nodeId)
38
+ }
39
+ }
40
+
41
+ // Uses Visx Groups innerRef to get all Group elements that are mapped.
42
+ // Sets the largest group width in state and subtracts that group the svg width to calculate overall width.
43
+ useEffect(() => {
44
+ let largest = 0
45
+ groupRefs?.current?.map(g => {
46
+ const groupWidth = g?.getBoundingClientRect().width
47
+ if (groupWidth > largest) {
48
+ largest = groupWidth
49
+ }
50
+ })
51
+ setLargestGroupWidth(largest)
52
+ }, [groupRefs, sankeyConfig, window.innerWidth])
53
+
54
+ if (config.visualizationType !== 'Sankey') return
55
+
56
+ const data = config?.data[0]
57
+
58
+ //Retrieve all the unique values for the Nodes
59
+ const uniqueNodes = Array.from(new Set(data?.links?.flatMap(link => [link.source, link.target])))
60
+
61
+ // Convert JSON data to the format required
62
+ const sankeyData: SankeyGraph<SankeyNode, { source: number; target: number }> = {
63
+ nodes: uniqueNodes.map(nodeId => ({ id: nodeId })),
64
+ links: data?.links?.map(link => ({
65
+ source: uniqueNodes.findIndex(node => node === link.source),
66
+ target: uniqueNodes.findIndex(node => node === link.target),
67
+ value: link.value
68
+ }))
69
+ }
70
+
71
+ let textPositionHorizontal = 5
72
+ const BUFFER = 50
73
+
74
+ // Set the sankey diagram properties console.log('largestGroupWidth', largestGroupWidth)
75
+
76
+ const sankeyGenerator = sankey<SankeyNode, { source: number; target: number }>()
77
+ .nodeWidth(sankeyConfig.nodeSize.nodeWidth)
78
+ .nodePadding(sankeyConfig.nodePadding)
79
+ .iterations(sankeyConfig.iterations)
80
+ .nodeAlign(sankeyLeft)
81
+ .extent([
82
+ [sankeyConfig.margin.margin_x, Number(sankeyConfig.margin.margin_y)],
83
+ [width - textPositionHorizontal - largestGroupWidth, config.heights.vertical - BUFFER]
84
+ ])
85
+
86
+ const { links } = sankeyGenerator(sankeyData)
87
+
88
+ const nodeStyle = (id: string) => {
89
+ let textPositionHorizontal = 30
90
+ let textPositionVertical = 0
91
+ let classStyle = 'node-value--storynode'
92
+ let storyNodes = true
93
+
94
+ if (data?.storyNodeText?.every(node => node.StoryNode !== id)) {
95
+ storyNodes = false
96
+ textPositionVertical = 10
97
+ textPositionHorizontal = 8
98
+ classStyle = 'node-value'
99
+ }
100
+
101
+ return { textPositionHorizontal, textPositionVertical, classStyle, storyNodes }
102
+ }
103
+
104
+ const activeConnection = (id: String) => {
105
+ if (!sankeyData?.nodes) return { sourceNodes: [], activeLinks: [] }
106
+
107
+ const currentNode = sankeyData.nodes.find(node => node.id === id)
108
+
109
+ const sourceNodes = []
110
+ const activeLinks = []
111
+
112
+ if (currentNode) {
113
+ links.forEach(link => {
114
+ const targetObj: any = link.target
115
+ const sourceObj: any = link.source
116
+ if (targetObj.id === id) {
117
+ sourceNodes.push(sourceObj.id)
118
+ }
119
+ })
120
+
121
+ sourceNodes.forEach(id => {
122
+ links.forEach(link => {
123
+ const targetObj: any = link.target
124
+ const sourceObj: any = link.source
125
+ if (targetObj.id === tooltipID && sourceObj.id === id) {
126
+ activeLinks.push(link)
127
+ }
128
+ })
129
+ })
130
+ }
131
+
132
+ return { sourceNodes, activeLinks }
133
+ }
134
+
135
+ const sankeyToolTip = getSankeyTooltip(data, tooltipID)
136
+
137
+ // Draw the nodes
138
+ const allNodes = sankeyData.nodes.map((node, i) => {
139
+ let { textPositionHorizontal, textPositionVertical, classStyle, storyNodes } = nodeStyle(node.id)
140
+ let { sourceNodes } = activeConnection(tooltipID)
141
+
142
+ let opacityValue = sankeyConfig.opacity.nodeOpacityDefault
143
+ let nodeColor = sankeyConfig.nodeColor.default
144
+
145
+ if (tooltipID !== node.id && tooltipID !== '' && !sourceNodes.includes(node.id)) {
146
+ nodeColor = sankeyConfig.nodeColor.inactive
147
+ opacityValue = sankeyConfig.opacity.nodeOpacityInactive
148
+ }
149
+
150
+ const maxNodeWidth = sankeyGenerator.nodeWidth()
151
+
152
+ // get the link length
153
+ /**
154
+ * Calculates the length of the link between the source and target nodes
155
+ * using the Euclidean distance formula.
156
+ *
157
+ * @returns {number} The length of the link.
158
+ */
159
+ const linkLength = () =>
160
+ Math.sqrt(
161
+ Math.pow(links[0].target.x0! - links[0].source.x1!, 2) + Math.pow(links[0].target.y0! - links[0].source.y1!, 2)
162
+ ) - largestGroupWidth
163
+
164
+ return (
165
+ <Group className='' key={i}>
166
+ <rect
167
+ height={node.y1! - node.y0! + 2} // increasing node size to account for smaller nodes
168
+ width={maxNodeWidth}
169
+ x={node.x0}
170
+ y={node.y0! - 1} //adjusting here the node starts so it looks more center with the link
171
+ fill={nodeColor}
172
+ fillOpacity={opacityValue}
173
+ rx={sankeyConfig.rxValue}
174
+ // todo: move enable tooltips to sankey
175
+ data-tooltip-html={data.tooltips && config.enableTooltips && tooltipID !== '' ? sankeyToolTip : null}
176
+ data-tooltip-id={`cdc-open-viz-tooltip-${runtime.uniqueId}-sankey`}
177
+ onClick={() => handleNodeClick(node.id)}
178
+ style={{ pointerEvents: 'visible', cursor: 'pointer' }}
179
+ />
180
+ {storyNodes ? (
181
+ <>
182
+ <Text
183
+ width={linkLength()}
184
+ /* Text Position Horizontal
185
+ x0 is the left edge of the node
186
+ # - positions text # units to the right of the left edge of the node */
187
+ x={node.x0! + textPositionHorizontal}
188
+ textAnchor={sankeyData.nodes.length - 1 === i ? 'end' : 'start'}
189
+ verticalAnchor='end'
190
+ /*Text Position Vertical
191
+ y1 and y0 are the top and bottom edges of the node
192
+ y1+y0 = total height
193
+ dividing by 2 gives you the midpoint of the node
194
+ minus 30 raises the vertical position to be higher
195
+ */
196
+ y={(node.y1! + node.y0!) / 2 - 30}
197
+ /* Using x and y in combination with dominant baseline allows for a more
198
+ precise positioning of the text within the svg
199
+ dominant baseline allows for different vertical alignments
200
+ text-before-edge aligns the text's bottom edge with the bottom edge of the container
201
+ */
202
+ fill={sankeyConfig.nodeFontColor}
203
+ fontWeight='bold' // font weight
204
+ className='node-text'
205
+ style={{ pointerEvents: 'auto', cursor: 'pointer' }} // Enable pointer events
206
+ onClick={() => handleNodeClick(node.id)}
207
+ data-tooltip-html={data.tooltips && config.enableTooltips && tooltipID !== '' ? sankeyToolTip : null}
208
+ data-tooltip-id={`cdc-open-viz-tooltip-${runtime.uniqueId}-sankey`}
209
+ >
210
+ {(data?.storyNodeText?.find(storyNode => storyNode.StoryNode === node.id) || {}).segmentTextBefore}
211
+ </Text>
212
+ <Text
213
+ width={linkLength()}
214
+ verticalAnchor='middle'
215
+ className={classStyle}
216
+ x={node.x0! + textPositionHorizontal}
217
+ y={(node.y1! + node.y0! + 25) / 2}
218
+ fill={sankeyConfig.storyNodeFontColor || sankeyConfig.nodeFontColor}
219
+ fontWeight='bold'
220
+ textAnchor='start'
221
+ style={{ pointerEvents: 'auto', cursor: 'pointer' }} // Enable pointer events
222
+ onClick={() => handleNodeClick(node.id)}
223
+ data-tooltip-html={data.tooltips && config.enableTooltips && tooltipID !== '' ? sankeyToolTip : null}
224
+ data-tooltip-id={`cdc-open-viz-tooltip-${runtime.uniqueId}-sankey`}
225
+ >
226
+ {typeof node.value === 'number' ? node.value.toLocaleString() : node.value}
227
+ </Text>
228
+ <Text
229
+ width={linkLength()}
230
+ x={node.x0! + textPositionHorizontal}
231
+ // plus 50 will move the vertical position down
232
+ y={(node.y1! + node.y0!) / 2 + 50}
233
+ fill={sankeyConfig.nodeFontColor}
234
+ fontWeight='bold'
235
+ textAnchor={sankeyData.nodes.length === i ? 'end' : 'start'}
236
+ className='node-text'
237
+ verticalAnchor='start'
238
+ style={{ pointerEvents: 'auto', cursor: 'pointer' }} // Enable pointer events
239
+ onClick={() => handleNodeClick(node.id)}
240
+ data-tooltip-html={data.tooltips && config.enableTooltips && tooltipID !== '' ? sankeyToolTip : null}
241
+ data-tooltip-id={`cdc-open-viz-tooltip-${runtime.uniqueId}-sankey`}
242
+ >
243
+ {(data?.storyNodeText?.find(storyNode => storyNode.StoryNode === node.id) || {}).segmentTextAfter}
244
+ </Text>
245
+ </>
246
+ ) : (
247
+ <>
248
+ <Text
249
+ style={{ pointerEvents: 'auto', cursor: 'pointer' }} // Enable pointer events
250
+ onClick={() => handleNodeClick(node.id)}
251
+ data-tooltip-html={data.tooltips && config.enableTooltips && tooltipID !== '' ? sankeyToolTip : null}
252
+ data-tooltip-id={`cdc-open-viz-tooltip-${runtime.uniqueId}-sankey`}
253
+ x={node.x0! + textPositionHorizontal}
254
+ y={(node.y1! + node.y0!) / 2 + textPositionVertical}
255
+ dominantBaseline='text-before-edge'
256
+ fill={sankeyConfig.nodeFontColor}
257
+ fontWeight='bold'
258
+ textAnchor='start'
259
+ >
260
+ {node.id}
261
+ </Text>
262
+ <text
263
+ x={node.x0! + textPositionHorizontal}
264
+ /* adding 30 allows the node value to be on the next line underneath the node id */
265
+ y={(node.y1! + node.y0!) / 2 + 30}
266
+ dominantBaseline='text-before-edge'
267
+ fill={sankeyConfig.nodeFontColor}
268
+ //fontSize={16}
269
+ fontWeight='bold'
270
+ textAnchor='start'
271
+ style={{ pointerEvents: 'auto', cursor: 'pointer' }} // Enable pointer events
272
+ onClick={() => handleNodeClick(node.id)}
273
+ data-tooltip-html={data.tooltips && config.enableTooltips && tooltipID !== '' ? sankeyToolTip : null}
274
+ data-tooltip-id={`cdc-open-viz-tooltip-${runtime.uniqueId}-sankey`}
275
+ >
276
+ <tspan className={classStyle}>
277
+ {sankeyConfig.nodeValueStyle.textBefore +
278
+ (typeof node.value === 'number' ? node.value.toLocaleString() : node.value) +
279
+ sankeyConfig.nodeValueStyle.textAfter}
280
+ </tspan>
281
+ </text>
282
+ </>
283
+ )}
284
+ </Group>
285
+ )
286
+ })
287
+
288
+ // Draw the links
289
+ const allLinks = links.map((link, i) => {
290
+ const linkGenerator = sankeyLinkHorizontal()
291
+ const path = linkGenerator(link)
292
+ let opacityValue = sankeyConfig.opacity.LinkOpacityDefault
293
+ let strokeColor = sankeyConfig.linkColor.default
294
+
295
+ let { activeLinks } = activeConnection(tooltipID)
296
+
297
+ if (!activeLinks.includes(link) && tooltipID !== '') {
298
+ strokeColor = sankeyConfig.linkColor.inactive
299
+ opacityValue = sankeyConfig.opacity.LinkOpacityInactive
300
+ }
301
+
302
+ return (
303
+ <path
304
+ key={i}
305
+ d={path!}
306
+ stroke={strokeColor}
307
+ fill='none'
308
+ strokeOpacity={opacityValue}
309
+ strokeWidth={link.width! + 2}
310
+ style={{ pointerEvents: 'auto', cursor: 'pointer' }} // Enable pointer events
311
+ onClick={() => handleNodeClick(link.target.id || null)}
312
+ data-tooltip-html={data.tooltips && config.enableTooltips && tooltipID !== '' ? sankeyToolTip : null}
313
+ data-tooltip-id={`cdc-open-viz-tooltip-${runtime.uniqueId}-sankey`}
314
+ />
315
+ )
316
+ })
317
+
318
+ // max depth - calculates how many nodes deep the chart goes.
319
+ const maxDepth: number = sankeyData.nodes.reduce((maxDepth, node) => {
320
+ return Math.max(maxDepth, node.depth)
321
+ }, -1)
322
+
323
+ // finalNodesAtMaxDepth - get only the right most nodes on the chart.
324
+ const finalNodesAtMaxDepth = sankeyData.nodes.filter(node => node.depth === maxDepth)
325
+
326
+ const finalNodes = finalNodesAtMaxDepth.map((node, i) => {
327
+ let { textPositionHorizontal, textPositionVertical, classStyle, storyNodes } = nodeStyle(node.id)
328
+ let { sourceNodes } = activeConnection(tooltipID)
329
+
330
+ let opacityValue = sankeyConfig.opacity.nodeOpacityDefault
331
+ let nodeColor = sankeyConfig.nodeColor.default
332
+
333
+ if (tooltipID !== node.id && tooltipID !== '' && !sourceNodes.includes(node.id)) {
334
+ nodeColor = sankeyConfig.nodeColor.inactive
335
+ opacityValue = sankeyConfig.opacity.nodeOpacityInactive
336
+ }
337
+
338
+ return (
339
+ <Group className='' key={i} innerRef={el => (groupRefs.current[i] = el)}>
340
+ <rect
341
+ height={node.y1! - node.y0! + 2} // increasing node size to account for smaller nodes
342
+ width={sankeyGenerator.nodeWidth()}
343
+ x={node.x0}
344
+ y={node.y0! - 1} //adjusting here the node starts so it looks more center with the link
345
+ fill={nodeColor}
346
+ fillOpacity={opacityValue}
347
+ rx={sankeyConfig.rxValue}
348
+ data-tooltip-html={data.tooltips && config.enableTooltips && tooltipID !== '' ? sankeyToolTip : null}
349
+ data-tooltip-id={`tooltip`}
350
+ onClick={() => handleNodeClick(node.id)}
351
+ style={{ pointerEvents: 'visible', cursor: 'pointer' }}
352
+ />
353
+ {storyNodes ? (
354
+ <>
355
+ <Text
356
+ /* Text Position Horizontal
357
+ x0 is the left edge of the node
358
+ # - positions text # units to the right of the left edge of the node */
359
+ x={node.x0! + textPositionHorizontal}
360
+ textAnchor={sankeyData.nodes.length - 1 === i ? 'end' : 'start'}
361
+ verticalAnchor='end'
362
+ /*Text Position Vertical
363
+ y1 and y0 are the top and bottom edges of the node
364
+ y1+y0 = total height
365
+ dividing by 2 gives you the midpoint of the node
366
+ minus 30 raises the vertical position to be higher
367
+ */
368
+ y={(node.y1! + node.y0!) / 2 - 30}
369
+ /* Using x and y in combination with dominant baseline allows for a more
370
+ precise positioning of the text within the svg
371
+ dominant baseline allows for different vertical alignments
372
+ text-before-edge aligns the text's bottom edge with the bottom edge of the container
373
+ */
374
+ fill={sankeyConfig.nodeFontColor}
375
+ fontWeight='bold' // font weight
376
+ style={{ pointerEvents: 'none' }}
377
+ className='node-text'
378
+ >
379
+ {(data?.storyNodeText?.find(storyNode => storyNode.StoryNode === node.id) || {}).segmentTextBefore}
380
+ </Text>
381
+ <Text
382
+ verticalAnchor='end'
383
+ className={classStyle}
384
+ x={node.x0! + textPositionHorizontal}
385
+ y={(node.y1! + node.y0! + 25) / 2}
386
+ fill={sankeyConfig.storyNodeFontColor || sankeyConfig.nodeFontColor}
387
+ fontWeight='bold'
388
+ textAnchor='start'
389
+ style={{ pointerEvents: 'none' }}
390
+ >
391
+ {typeof node.value === 'number' ? node.value.toLocaleString() : node.value}
392
+ </Text>
393
+ <Text
394
+ x={node.x0! + textPositionHorizontal}
395
+ // plus 50 will move the vertical position down
396
+ y={(node.y1! + node.y0!) / 2 + 50}
397
+ fill={sankeyConfig.nodeFontColor}
398
+ fontWeight='bold'
399
+ textAnchor={sankeyData.nodes.length === i ? 'end' : 'start'}
400
+ style={{ pointerEvents: 'none' }}
401
+ className='node-text'
402
+ verticalAnchor='end'
403
+ >
404
+ {(data?.storyNodeText?.find(storyNode => storyNode.StoryNode === node.id) || {}).segmentTextAfter}
405
+ </Text>
406
+ </>
407
+ ) : (
408
+ <>
409
+ <text
410
+ x={node.x0! + textPositionHorizontal}
411
+ y={(node.y1! + node.y0!) / 2 + textPositionVertical}
412
+ dominantBaseline='text-before-edge'
413
+ fill={sankeyConfig.nodeFontColor}
414
+ fontWeight='bold'
415
+ textAnchor='start'
416
+ style={{ pointerEvents: 'none' }}
417
+ >
418
+ <tspan id={node.id} className='node-id'>
419
+ {node.id}
420
+ </tspan>
421
+ </text>
422
+ <text
423
+ x={node.x0! + textPositionHorizontal}
424
+ /* adding 30 allows the node value to be on the next line underneath the node id */
425
+ y={(node.y1! + node.y0!) / 2 + 30}
426
+ dominantBaseline='text-before-edge'
427
+ fill={sankeyConfig.nodeFontColor}
428
+ fontWeight='bold'
429
+ textAnchor='start'
430
+ style={{ pointerEvents: 'none' }}
431
+ >
432
+ <tspan onClick={() => handleNodeClick(node.id)} className={classStyle}>
433
+ {sankeyConfig.nodeValueStyle.textBefore +
434
+ (typeof node.value === 'number' ? node.value.toLocaleString() : node.value) +
435
+ sankeyConfig.nodeValueStyle.textAfter}
436
+ </tspan>
437
+ </text>
438
+ </>
439
+ )}
440
+ </Group>
441
+ )
442
+ })
443
+
444
+ return !showAlert ? (
445
+ <>
446
+ <div className='sankey-chart'>
447
+ <svg
448
+ className='sankey-chart__diagram'
449
+ width={width}
450
+ height={Number(config.heights.vertical)}
451
+ style={{ overflow: 'visible' }}
452
+ >
453
+ <Group className='links'>{allLinks}</Group>
454
+ <Group className='nodes'>{allNodes}</Group>
455
+ <Group className='finalNodes' style={{ display: 'none' }}>
456
+ {finalNodes}
457
+ </Group>
458
+ </svg>
459
+
460
+ {/* ReactTooltip needs to remain even if tooltips are disabled -- it handles when a user clicks off of the node and resets
461
+ the sankey diagram. When tooltips are disabled this will nothing */}
462
+ <ReactTooltip
463
+ id={`cdc-open-viz-tooltip-${runtime.uniqueId}-sankey`}
464
+ afterHide={() => setTooltipID('')}
465
+ events={['click']}
466
+ place={'bottom'}
467
+ style={{
468
+ backgroundColor: `rgba(238, 238, 238, 1)`,
469
+ color: 'black',
470
+ boxShadow: `0 3px 10px rgb(0 0 0 / 0.2)`
471
+ }}
472
+ />
473
+ </div>
474
+ </>
475
+ ) : (
476
+ alert
477
+ )
478
+ }
479
+ export default Sankey
@@ -0,0 +1,33 @@
1
+ import ReactDOMServer from 'react-dom/server'
2
+ import ColumnList from '../components/ColumnList'
3
+
4
+ export const getSankeyTooltip = (data: Object, tooltipID: string) => {
5
+ const tooltipVal = `${(data?.tooltips?.find(item => item.node === tooltipID) || {}).value}`
6
+ const tooltipSummary = `${(data?.tooltips?.find(item => item.node === tooltipID) || {}).summary}`
7
+ const tooltipColumn1Label = (data?.tooltips?.find(item => item.node === tooltipID) || {}).column1Label
8
+ const tooltipColumn2Label = (data?.tooltips?.find(item => item.node === tooltipID) || {}).column2Label
9
+ const tooltipColumn1 = (data?.tooltips?.find(item => item.node === tooltipID) || {}).column1
10
+ const tooltipColumn2 = (data?.tooltips?.find(item => item.node === tooltipID) || {}).column2
11
+
12
+ const tooltipColumn1Data = ReactDOMServer.renderToString(<ColumnList columnData={tooltipColumn1} />)
13
+ const tooltipColumn2Data = ReactDOMServer.renderToString(<ColumnList columnData={tooltipColumn2} />)
14
+
15
+ return `<div class="sankey-chart__tooltip">
16
+ <span class="sankey-chart__tooltip--tooltip-header">${tooltipID}</span>
17
+ <span class="sankey-chart__tooltip--tooltip-header">${tooltipVal}</span>
18
+ <div class="divider"></div>
19
+ <span><strong>Summary: </strong>${tooltipSummary}</span>
20
+ <div class="divider"></div>
21
+ <div class="sankey-chart__tooltip--info-section">
22
+ <div>
23
+ <span><strong>${tooltipColumn1Label}</strong></span>
24
+ ${tooltipColumn1Data}
25
+ </div>
26
+ <div>
27
+ <span><strong>${tooltipColumn2Label}</strong></span>
28
+ ${tooltipColumn2Data}
29
+ </div>
30
+ </div>
31
+ </div>
32
+ `
33
+ }