@cdc/chart 4.24.10 → 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 (94) hide show
  1. package/dist/cdcchart.js +35019 -34301
  2. package/examples/feature/boxplot/boxplot-data.json +88 -22
  3. package/examples/feature/boxplot/boxplot.json +540 -16
  4. package/examples/feature/boxplot/testing.csv +7 -7
  5. package/examples/feature/sankey/sankey-example-data.json +126 -14
  6. package/examples/feature/tests-date-exclusions/date-exclusions-config.json +372 -12
  7. package/examples/private/DEV-8850-2.json +493 -0
  8. package/examples/private/DEV-9822.json +574 -0
  9. package/examples/private/DEV-9840.json +553 -0
  10. package/examples/private/DEV-9850-3.json +461 -0
  11. package/examples/private/chart.json +1084 -0
  12. package/examples/private/ci_formatted.json +202 -0
  13. package/examples/private/ci_issue.json +3016 -0
  14. package/examples/private/completed.json +634 -0
  15. package/examples/private/dem-data-long.csv +20 -0
  16. package/examples/private/dem-data-long.json +36 -0
  17. package/examples/private/demographic_data.csv +157 -0
  18. package/examples/private/demographic_data.json +2654 -0
  19. package/examples/private/demographic_dynamic.json +443 -0
  20. package/examples/private/demographic_standard.json +560 -0
  21. package/examples/private/ehdi.json +29939 -0
  22. package/examples/private/test.json +493 -0
  23. package/index.html +10 -7
  24. package/package.json +2 -2
  25. package/src/CdcChart.tsx +132 -152
  26. package/src/_stories/Chart.Anchors.stories.tsx +31 -0
  27. package/src/_stories/Chart.CustomColors.stories.tsx +19 -0
  28. package/src/_stories/Chart.DynamicSeries.stories.tsx +34 -0
  29. package/src/_stories/Chart.Legend.Gradient.stories.tsx +42 -1
  30. package/src/_stories/Chart.stories.tsx +37 -6
  31. package/src/_stories/ChartAxisLabels.stories.tsx +4 -1
  32. package/src/_stories/ChartEditor.stories.tsx +27 -0
  33. package/src/_stories/ChartLine.Suppression.stories.tsx +25 -0
  34. package/src/_stories/ChartPrefixSuffix.stories.tsx +8 -0
  35. package/{examples/feature/area/area-chart-date-city-temperature.json → src/_stories/_mock/area_chart_stacked.json} +125 -27
  36. package/src/_stories/_mock/boxplot_multiseries.json +647 -0
  37. package/src/_stories/_mock/dynamic_series_bar_config.json +723 -0
  38. package/src/_stories/_mock/dynamic_series_config.json +979 -0
  39. package/src/_stories/_mock/line_chart_dynamic_ci.json +493 -0
  40. package/src/_stories/_mock/line_chart_non_dynamic_ci.json +522 -0
  41. package/{examples/feature/scatterplot/scatterplot.json → src/_stories/_mock/scatterplot_mock.json} +62 -92
  42. package/src/_stories/_mock/short_dates.json +288 -0
  43. package/src/_stories/_mock/suppression_mock.json +1549 -0
  44. package/src/components/AreaChart/components/AreaChart.Stacked.jsx +15 -3
  45. package/src/components/Axis/Categorical.Axis.tsx +2 -2
  46. package/src/components/BarChart/components/BarChart.Horizontal.tsx +46 -37
  47. package/src/components/BarChart/components/BarChart.StackedVertical.tsx +43 -9
  48. package/src/components/BarChart/components/BarChart.Vertical.tsx +53 -47
  49. package/src/components/BarChart/helpers/getBarData.ts +28 -0
  50. package/src/components/BarChart/helpers/index.ts +1 -2
  51. package/src/components/BarChart/helpers/tests/getBarData.test.ts +74 -0
  52. package/src/components/BoxPlot/BoxPlot.tsx +131 -0
  53. package/src/components/BoxPlot/helpers/index.ts +54 -0
  54. package/src/components/BrushChart.tsx +23 -26
  55. package/src/components/EditorPanel/EditorPanel.tsx +117 -139
  56. package/src/components/EditorPanel/components/Panels/Panel.Annotate.tsx +3 -3
  57. package/src/components/EditorPanel/components/Panels/Panel.BoxPlot.tsx +51 -6
  58. package/src/components/EditorPanel/components/Panels/Panel.Regions.tsx +40 -9
  59. package/src/components/EditorPanel/components/Panels/Panel.Sankey.tsx +3 -3
  60. package/src/components/EditorPanel/components/Panels/Panel.Series.tsx +122 -56
  61. package/src/components/EditorPanel/components/Panels/Panel.Visual.tsx +1 -2
  62. package/src/components/EditorPanel/useEditorPermissions.ts +20 -2
  63. package/src/components/Legend/Legend.Component.tsx +11 -12
  64. package/src/components/Legend/Legend.tsx +16 -16
  65. package/src/components/Legend/helpers/getLegendClasses.ts +59 -0
  66. package/src/components/Legend/helpers/index.ts +2 -1
  67. package/src/components/Legend/tests/getLegendClasses.test.ts +115 -0
  68. package/src/components/LineChart/components/LineChart.Circle.tsx +1 -1
  69. package/src/components/LineChart/helpers.ts +49 -43
  70. package/src/components/LineChart/index.tsx +135 -83
  71. package/src/components/LinearChart.tsx +196 -181
  72. package/src/components/PieChart/PieChart.tsx +7 -1
  73. package/src/components/Sankey/components/ColumnList.tsx +19 -0
  74. package/src/components/Sankey/components/Sankey.tsx +479 -0
  75. package/src/components/Sankey/helpers/getSankeyTooltip.tsx +33 -0
  76. package/src/components/Sankey/index.tsx +1 -492
  77. package/src/components/Sankey/sankey.scss +22 -21
  78. package/src/components/Sankey/types/index.ts +1 -1
  79. package/src/components/Sankey/useSankeyAlert.tsx +60 -0
  80. package/src/components/ScatterPlot/ScatterPlot.jsx +20 -4
  81. package/src/data/initial-state.js +7 -12
  82. package/src/helpers/countNumOfTicks.ts +57 -0
  83. package/src/helpers/getQuartiles.ts +15 -18
  84. package/src/hooks/useMinMax.ts +44 -16
  85. package/src/hooks/useReduceData.ts +43 -10
  86. package/src/hooks/useScales.ts +90 -35
  87. package/src/hooks/useTooltip.tsx +59 -50
  88. package/src/scss/DataTable.scss +5 -0
  89. package/src/scss/main.scss +6 -20
  90. package/src/types/ChartConfig.ts +6 -19
  91. package/src/types/ChartContext.ts +4 -1
  92. package/src/types/ForestPlot.ts +8 -0
  93. package/src/components/BoxPlot/BoxPlot.jsx +0 -111
  94. package/src/hooks/useLegendClasses.ts +0 -72
@@ -1,493 +1,2 @@
1
- import { useContext, useState, useRef, useEffect } from 'react'
2
-
3
- // External Libraries
4
- import { PlacesType, 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
- import ReactDOMServer from 'react-dom/server'
9
-
10
- // Cdc
11
- import './sankey.scss'
12
- import 'react-tooltip/dist/react-tooltip.css'
13
- import ConfigContext from '@cdc/chart/src/ConfigContext'
14
- import { ChartContext } from '../../types/ChartContext'
15
- import type { SankeyNode, SankeyProps } from './types'
16
- import { SankeyChartConfig, AllChartsConfig } from '../../types/ChartConfig'
17
-
18
- const Sankey = ({ width, height, runtime }: SankeyProps) => {
19
- const { config } = useContext<ChartContext>(ConfigContext)
20
- const { sankey: sankeyConfig } = config
21
-
22
- const isSankeyChartConfig = (config: AllChartsConfig | SankeyChartConfig): config is SankeyChartConfig => {
23
- return config.visualizationType === 'Sankey'
24
- }
25
-
26
- const [largestGroupWidth, setLargestGroupWidth] = useState(0)
27
- const groupRefs = useRef([])
28
-
29
- //Tooltip
30
- const [tooltipID, setTooltipID] = useState<string>('')
31
- //Mobile Pop Up
32
- const [showPopup, setShowPopup] = useState(false)
33
-
34
- const handleNodeClick = (nodeId: string) => {
35
- // Store the previous tooltipID
36
- const previousTooltipID = tooltipID
37
-
38
- // If the previous tooltipID exists, clear it
39
- if (previousTooltipID) {
40
- setTooltipID('')
41
- }
42
-
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
- }
48
-
49
- useEffect(() => {
50
- if (window.innerWidth < 768 && window.innerHeight > window.innerWidth) {
51
- setShowPopup(true)
52
- }
53
- }, [window.innerWidth])
54
-
55
- const closePopUp = () => {
56
- setShowPopup(false)
57
- }
58
-
59
- // Uses Visx Groups innerRef to get all Group elements that are mapped.
60
- // Sets the largest group width in state and subtracts that group the svg width to calculate overall width.
61
- useEffect(() => {
62
- let largest = 0
63
- groupRefs?.current?.map(g => {
64
- const groupWidth = g?.getBoundingClientRect().width
65
- if (groupWidth > largest) {
66
- largest = groupWidth
67
- }
68
- })
69
- setLargestGroupWidth(largest)
70
- }, [groupRefs, sankeyConfig, window.innerWidth])
71
-
72
- if (!isSankeyChartConfig(config)) return
73
- const data = config?.data[0]
74
-
75
- //Retrieve all the unique values for the Nodes
76
- const uniqueNodes = Array.from(new Set(data?.links?.flatMap(link => [link.source, link.target])))
77
-
78
- // Convert JSON data to the format required
79
- const sankeyData: SankeyGraph<SankeyNode, { source: number; target: number }> = {
80
- nodes: uniqueNodes.map(nodeId => ({ id: nodeId })),
81
- links: data?.links?.map(link => ({
82
- source: uniqueNodes.findIndex(node => node === link.source),
83
- target: uniqueNodes.findIndex(node => node === link.target),
84
- value: link.value
85
- }))
86
- }
87
-
88
- let textPositionHorizontal = 5
89
- const BUFFER = 50
90
-
91
- // Set the sankey diagram properties console.log('largestGroupWidth', largestGroupWidth)
92
-
93
- const sankeyGenerator = sankey<SankeyNode, { source: number; target: number }>()
94
- .nodeWidth(sankeyConfig.nodeSize.nodeWidth)
95
- .nodePadding(sankeyConfig.nodePadding)
96
- .iterations(sankeyConfig.iterations)
97
- .nodeAlign(sankeyLeft)
98
- .extent([
99
- [sankeyConfig.margin.margin_x, Number(sankeyConfig.margin.margin_y)],
100
- [width - textPositionHorizontal - largestGroupWidth, config.heights.vertical - BUFFER]
101
- ])
102
-
103
- const { nodes, links } = sankeyGenerator(sankeyData)
104
-
105
- const nodeStyle = (id: string) => {
106
- let textPositionHorizontal = 30
107
- let textPositionVertical = 0
108
- let classStyle = 'node-value--storynode'
109
- let storyNodes = true
110
-
111
- // TODO: need a dynamic way to apply classes here instead of checking static values.
112
-
113
- if (data?.storyNodeText?.every(node => node.StoryNode !== id)) {
114
- storyNodes = false
115
- textPositionVertical = 10
116
- textPositionHorizontal = 8
117
- classStyle = 'node-value'
118
- }
119
-
120
- return { textPositionHorizontal, textPositionVertical, classStyle, storyNodes }
121
- }
122
-
123
- const activeConnection = (id: String) => {
124
- if (!sankeyData?.nodes) return { sourceNodes: [], activeLinks: [] }
125
-
126
- const currentNode = sankeyData.nodes.find(node => node.id === id)
127
-
128
- const sourceNodes = []
129
- const activeLinks = []
130
-
131
- if (currentNode) {
132
- links.forEach(link => {
133
- const targetObj: any = link.target
134
- const sourceObj: any = link.source
135
- if (targetObj.id === id) {
136
- sourceNodes.push(sourceObj.id)
137
- }
138
- })
139
-
140
- sourceNodes.forEach(id => {
141
- links.forEach(link => {
142
- const targetObj: any = link.target
143
- const sourceObj: any = link.source
144
- if (targetObj.id === tooltipID && sourceObj.id === id) {
145
- activeLinks.push(link)
146
- }
147
- })
148
- })
149
- }
150
-
151
- return { sourceNodes, activeLinks }
152
- }
153
-
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
160
-
161
- const ColumnList = ({ columnData }) => {
162
- return (
163
- <ul>
164
- {columnData?.map((entry, index) => (
165
- <li key={index}>
166
- {entry.label}: {entry.value} ({entry.additional_info}%)
167
- </li>
168
- ))}
169
- </ul>
170
- )
171
- }
172
-
173
- const tooltipColumn1Data = ReactDOMServer.renderToString(<ColumnList columnData={tooltipColumn1} />)
174
- const tooltipColumn2Data = ReactDOMServer.renderToString(<ColumnList columnData={tooltipColumn2} />)
175
-
176
- const sankeyToolTip = `<div class="sankey-chart__tooltip">
177
- <span class="sankey-chart__tooltip--tooltip-header">${tooltipID}</span>
178
- <span class="sankey-chart__tooltip--tooltip-header">${tooltipVal}</span>
179
- <div class="divider"></div>
180
- <span><strong>Summary: </strong>${tooltipSummary}</span>
181
- <div class="divider"></div>
182
- <div class="sankey-chart__tooltip--info-section">
183
- <div>
184
- <span><strong>${tooltipColumn1Label}<strong></span>
185
- ${tooltipColumn1Data}
186
- </div>
187
- <div>
188
- <span><strong>${tooltipColumn2Label}<strong></span>
189
- ${tooltipColumn2Data}
190
- </div>
191
- </div>
192
- </div>`
193
-
194
- // Draw the nodes
195
- const allNodes = sankeyData.nodes.map((node, i) => {
196
- let { textPositionHorizontal, textPositionVertical, classStyle, storyNodes } = nodeStyle(node.id)
197
- let { sourceNodes } = activeConnection(tooltipID)
198
-
199
- let opacityValue = sankeyConfig.opacity.nodeOpacityDefault
200
- let nodeColor = sankeyConfig.nodeColor.default
201
-
202
- if (tooltipID !== node.id && tooltipID !== '' && !sourceNodes.includes(node.id)) {
203
- nodeColor = sankeyConfig.nodeColor.inactive
204
- opacityValue = sankeyConfig.opacity.nodeOpacityInactive
205
- }
206
-
207
- return (
208
- <Group className='' key={i}>
209
- <rect
210
- height={node.y1! - node.y0! + 2} // increasing node size to account for smaller nodes
211
- width={sankeyGenerator.nodeWidth()}
212
- x={node.x0}
213
- y={node.y0! - 1} //adjusting here the node starts so it looks more center with the link
214
- fill={nodeColor}
215
- fillOpacity={opacityValue}
216
- rx={sankeyConfig.rxValue}
217
- // todo: move enable tooltips to sankey
218
- data-tooltip-html={data.tooltips && config.enableTooltips && tooltipID !== '' ? sankeyToolTip : null}
219
- data-tooltip-id={`cdc-open-viz-tooltip-${runtime.uniqueId}-sankey`}
220
- onClick={() => handleNodeClick(node.id)}
221
- style={{ pointerEvents: 'visible', cursor: 'pointer' }}
222
- />
223
- {storyNodes ? (
224
- <>
225
- <Text
226
- /* Text Position Horizontal
227
- x0 is the left edge of the node
228
- # - positions text # units to the right of the left edge of the node */
229
- x={node.x0! + textPositionHorizontal}
230
- textAnchor={sankeyData.nodes.length - 1 === i ? 'end' : 'start'}
231
- verticalAnchor='end'
232
- /*Text Position Vertical
233
- y1 and y0 are the top and bottom edges of the node
234
- y1+y0 = total height
235
- dividing by 2 gives you the midpoint of the node
236
- minus 30 raises the vertical position to be higher
237
- */
238
- y={(node.y1! + node.y0!) / 2 - 30}
239
- /* Using x and y in combination with dominant baseline allows for a more
240
- precise positioning of the text within the svg
241
- dominant baseline allows for different vertical alignments
242
- text-before-edge aligns the text's bottom edge with the bottom edge of the container
243
- */
244
- fill={sankeyConfig.nodeFontColor}
245
- fontWeight='bold' // font weight
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`}
251
- >
252
- {(data?.storyNodeText?.find(storyNode => storyNode.StoryNode === node.id) || {}).segmentTextBefore}
253
- </Text>
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
- >
267
- {typeof node.value === 'number' ? node.value.toLocaleString() : node.value}
268
- </Text>
269
- <Text
270
- x={node.x0! + textPositionHorizontal}
271
- // plus 50 will move the vertical position down
272
- y={(node.y1! + node.y0!) / 2 + 50}
273
- fill={sankeyConfig.nodeFontColor}
274
- fontWeight='bold'
275
- textAnchor={sankeyData.nodes.length === i ? 'end' : 'start'}
276
- className='node-text'
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`}
282
- >
283
- {(data?.storyNodeText?.find(storyNode => storyNode.StoryNode === node.id) || {}).segmentTextAfter}
284
- </Text>
285
- </>
286
- ) : (
287
- <>
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>
302
- <text
303
- x={node.x0! + textPositionHorizontal}
304
- /* adding 30 allows the node value to be on the next line underneath the node id */
305
- y={(node.y1! + node.y0!) / 2 + 30}
306
- dominantBaseline='text-before-edge'
307
- fill={sankeyConfig.nodeFontColor}
308
- //fontSize={16}
309
- fontWeight='bold'
310
- textAnchor='start'
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`}
315
- >
316
- <tspan className={classStyle}>{sankeyConfig.nodeValueStyle.textBefore + (typeof node.value === 'number' ? node.value.toLocaleString() : node.value) + sankeyConfig.nodeValueStyle.textAfter}</tspan>
317
- </text>
318
- </>
319
- )}
320
- </Group>
321
- )
322
- })
323
-
324
- // Draw the links
325
- const allLinks = links.map((link, i) => {
326
- const linkGenerator = sankeyLinkHorizontal()
327
- const path = linkGenerator(link)
328
- let opacityValue = sankeyConfig.opacity.LinkOpacityDefault
329
- let strokeColor = sankeyConfig.linkColor.default
330
-
331
- let { activeLinks } = activeConnection(tooltipID)
332
-
333
- if (!activeLinks.includes(link) && tooltipID !== '') {
334
- strokeColor = sankeyConfig.linkColor.inactive
335
- opacityValue = sankeyConfig.opacity.LinkOpacityInactive
336
- }
337
-
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
- )
352
- })
353
-
354
- // max depth - calculates how many nodes deep the chart goes.
355
- const maxDepth: number = sankeyData.nodes.reduce((maxDepth, node) => {
356
- return Math.max(maxDepth, node.depth)
357
- }, -1)
358
-
359
- // finalNodesAtMaxDepth - get only the right most nodes on the chart.
360
- const finalNodesAtMaxDepth = sankeyData.nodes.filter(node => node.depth === maxDepth)
361
-
362
- const finalNodes = finalNodesAtMaxDepth.map((node, i) => {
363
- let { textPositionHorizontal, textPositionVertical, classStyle, storyNodes } = nodeStyle(node.id)
364
- let { sourceNodes } = activeConnection(tooltipID)
365
-
366
- let opacityValue = sankeyConfig.opacity.nodeOpacityDefault
367
- let nodeColor = sankeyConfig.nodeColor.default
368
-
369
- if (tooltipID !== node.id && tooltipID !== '' && !sourceNodes.includes(node.id)) {
370
- nodeColor = sankeyConfig.nodeColor.inactive
371
- opacityValue = sankeyConfig.opacity.nodeOpacityInactive
372
- }
373
-
374
- return (
375
- <Group className='' key={i} innerRef={el => (groupRefs.current[i] = el)}>
376
- <rect
377
- height={node.y1! - node.y0! + 2} // increasing node size to account for smaller nodes
378
- width={sankeyGenerator.nodeWidth()}
379
- x={node.x0}
380
- y={node.y0! - 1} //adjusting here the node starts so it looks more center with the link
381
- fill={nodeColor}
382
- fillOpacity={opacityValue}
383
- rx={sankeyConfig.rxValue}
384
- // todo: move enable tooltips to sankey
385
- data-tooltip-html={data.tooltips && config.enableTooltips && tooltipID !== '' ? sankeyToolTip : null}
386
- data-tooltip-id={`tooltip`}
387
- onClick={() => handleNodeClick(node.id)}
388
- style={{ pointerEvents: 'visible', cursor: 'pointer' }}
389
- />
390
- {storyNodes ? (
391
- <>
392
- <Text
393
- /* Text Position Horizontal
394
- x0 is the left edge of the node
395
- # - positions text # units to the right of the left edge of the node */
396
- x={node.x0! + textPositionHorizontal}
397
- textAnchor={sankeyData.nodes.length - 1 === i ? 'end' : 'start'}
398
- verticalAnchor='end'
399
- /*Text Position Vertical
400
- y1 and y0 are the top and bottom edges of the node
401
- y1+y0 = total height
402
- dividing by 2 gives you the midpoint of the node
403
- minus 30 raises the vertical position to be higher
404
- */
405
- y={(node.y1! + node.y0!) / 2 - 30}
406
- /* Using x and y in combination with dominant baseline allows for a more
407
- precise positioning of the text within the svg
408
- dominant baseline allows for different vertical alignments
409
- text-before-edge aligns the text's bottom edge with the bottom edge of the container
410
- */
411
- fill={sankeyConfig.nodeFontColor}
412
- fontWeight='bold' // font weight
413
- style={{ pointerEvents: 'none' }}
414
- className='node-text'
415
- >
416
- {(data?.storyNodeText?.find(storyNode => storyNode.StoryNode === node.id) || {}).segmentTextBefore}
417
- </Text>
418
- <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' }}>
419
- {typeof node.value === 'number' ? node.value.toLocaleString() : node.value}
420
- </Text>
421
- <Text
422
- x={node.x0! + textPositionHorizontal}
423
- // plus 50 will move the vertical position down
424
- y={(node.y1! + node.y0!) / 2 + 50}
425
- fill={sankeyConfig.nodeFontColor}
426
- fontWeight='bold'
427
- textAnchor={sankeyData.nodes.length === i ? 'end' : 'start'}
428
- style={{ pointerEvents: 'none' }}
429
- className='node-text'
430
- verticalAnchor='end'
431
- >
432
- {(data?.storyNodeText?.find(storyNode => storyNode.StoryNode === node.id) || {}).segmentTextAfter}
433
- </Text>
434
- </>
435
- ) : (
436
- <>
437
- <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' }}>
438
- <tspan id={node.id} className='node-id'>
439
- {node.id}
440
- </tspan>
441
- </text>
442
- <text
443
- x={node.x0! + textPositionHorizontal}
444
- /* adding 30 allows the node value to be on the next line underneath the node id */
445
- y={(node.y1! + node.y0!) / 2 + 30}
446
- dominantBaseline='text-before-edge'
447
- fill={sankeyConfig.nodeFontColor}
448
- //fontSize={16}
449
- fontWeight='bold'
450
- textAnchor='start'
451
- style={{ pointerEvents: 'none' }}
452
- >
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>
456
- </text>
457
- </>
458
- )}
459
- </Group>
460
- )
461
- })
462
-
463
- return (
464
- <>
465
- <div className='sankey-chart'>
466
- <svg className='sankey-chart__diagram' width={width} height={Number(config.heights.vertical)} style={{ overflow: 'visible' }}>
467
- <Group className='links'>{allLinks}</Group>
468
- <Group className='nodes'>{allNodes}</Group>
469
- <Group className='finalNodes' style={{ display: 'none' }}>
470
- {finalNodes}
471
- </Group>
472
- </svg>
473
-
474
- {/* ReactTooltip needs to remain even if tooltips are disabled -- it handles when a user clicks off of the node and resets
475
- the sankey diagram. When tooltips are disabled this will nothing */}
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)` }} />
477
- {showPopup && (
478
- <div className='popup'>
479
- <div className='popup-content'>
480
- <button className='visually-hidden' onClick={closePopUp}>
481
- Select for accessible version.
482
- </button>
483
- <p>
484
- <strong>Please change the orientation of your screen or increase the size of your browser to view the diagram better.</strong>
485
- </p>
486
- </div>
487
- </div>
488
- )}
489
- </div>
490
- </>
491
- )
492
- }
1
+ import Sankey from './components/Sankey'
493
2
  export default Sankey
@@ -7,7 +7,7 @@
7
7
 
8
8
  .cdc-open-viz-module .sankey-chart {
9
9
  --font-size-small: 12px;
10
- --font-size-medium: 14px;
10
+ --font-size-medium: 16px;
11
11
  --font-size-large: 18px;
12
12
  --font-size-xl: 24px;
13
13
 
@@ -25,8 +25,12 @@
25
25
  margin: 10px 0;
26
26
  }
27
27
 
28
+ .alert-dismissible {
29
+ padding-right: 4rem !important;
30
+ }
31
+
28
32
  svg.sankey-chart__diagram {
29
- position:relative;
33
+ position: relative;
30
34
  font-family: 'Roboto', sans-serif;
31
35
  height: auto;
32
36
  width: 100%;
@@ -103,30 +107,27 @@
103
107
  }
104
108
  }
105
109
 
106
- //small
107
- @media only screen and (max-width: 799px) {
108
- .node-text {
109
- font-size: var(--font-size-small);
110
- }
111
- .node-value--storynode {
112
- font-size: var(--storynode-font-size--small);
113
- }
114
- .node-id {
115
- font-size: var(--font-size-small);
116
- }
117
- .node-value {
118
- font-size: var(--font-size-small);
119
- }
120
- }
110
+ // //small
111
+ // @media only screen and (max-width: 799px) {
112
+ // .node-text {
113
+ // font-size: var(--font-size-small);
114
+ // }
115
+ // .node-value--storynode {
116
+ // font-size: var(--storynode-font-size--small);
117
+ // }
118
+ // .node-id {
119
+ // font-size: var(--font-size-small);
120
+ // }
121
+ // .node-value {
122
+ // font-size: var(--font-size-small);
123
+ // }
124
+ // }
121
125
 
122
126
  //x-small
123
127
  @media only screen and (max-width: 600px) {
124
128
  .popup {
125
129
  display: block; /* Show the popup on smaller screens */
126
130
  }
127
- .sankey-chart__diagram {
128
- opacity: .1;
129
- }
130
131
  }
131
132
  }
132
133
 
@@ -150,4 +151,4 @@
150
151
  font-size: 30px;
151
152
  padding: 10px;
152
153
  text-align: center;
153
- }
154
+ }
@@ -1,4 +1,4 @@
1
- export type Link = { source: string; target: string; value: number }
1
+ export type Link = { source: string; target: string; value: number; id: string }
2
2
 
3
3
  export type Data = {
4
4
  links: Link[]
@@ -0,0 +1,60 @@
1
+ import { useState, useEffect, useContext } from 'react'
2
+ import { ChartContext } from '../../types/ChartContext'
3
+ import ConfigContext from '../../ConfigContext'
4
+
5
+ const useSankeyAlert = () => {
6
+ const { config, handleChartTabbing, legendId } = useContext<ChartContext>(ConfigContext)
7
+
8
+ //Mobile Pop Up
9
+ const [showAlert, setShowAlert] = useState(false)
10
+ const alertMessage = (
11
+ <>
12
+ For best viewing we recommend portrait mode. If you are unable to put your device in portrait mode, please review
13
+ the <a href={`#${handleChartTabbing(config, legendId)}`}>data table</a> below.{' '}
14
+ <a onClick={() => setShowAlert(false)} href={'#!'}>
15
+ Close this alert
16
+ </a>{' '}
17
+ to continue viewing the chart.
18
+ </>
19
+ )
20
+
21
+ const handleCloseModal = () => {
22
+ setShowAlert(false)
23
+ }
24
+
25
+ const alert = showAlert ? (
26
+ <div className='alert alert-warning alert-dismissible' role='alert'>
27
+ <p style={{ padding: '35px' }}>{alertMessage}</p>
28
+ <button type='button' className='close' data-dismiss='alert' aria-label='Close' onClick={handleCloseModal}>
29
+ <span aria-hidden='true'>&times;</span>
30
+ </button>
31
+ </div>
32
+ ) : null
33
+
34
+ useEffect(() => {
35
+ const handleResize = () => {
36
+ if (window.innerWidth < 768 && window.innerHeight > window.innerWidth) {
37
+ setShowAlert(true)
38
+ } else {
39
+ setShowAlert(false)
40
+ }
41
+ }
42
+
43
+ window.addEventListener('resize', handleResize)
44
+ handleResize() // Call the function initially to set the state based on the initial window size
45
+
46
+ return () => {
47
+ window.removeEventListener('resize', handleResize)
48
+ }
49
+ }, [])
50
+
51
+ return {
52
+ setShowAlert,
53
+ showAlert,
54
+ handleCloseModal,
55
+ alertMessage,
56
+ alert
57
+ }
58
+ }
59
+
60
+ export default useSankeyAlert
@@ -4,7 +4,14 @@ import { Group } from '@visx/group'
4
4
  import { formatNumber as formatColNumber } from '@cdc/core/helpers/cove/number'
5
5
 
6
6
  const ScatterPlot = ({ xScale, yScale }) => {
7
- const { transformedData: data, config, tableData, formatNumber, seriesHighlight, colorPalettes } = useContext(ConfigContext)
7
+ const {
8
+ transformedData: data,
9
+ config,
10
+ tableData,
11
+ formatNumber,
12
+ seriesHighlight,
13
+ colorPalettes
14
+ } = useContext(ConfigContext)
8
15
 
9
16
  // TODO: copied from line chart should probably be a constant somewhere.
10
17
  const circleRadii = 4.5
@@ -23,10 +30,19 @@ const ScatterPlot = ({ xScale, yScale }) => {
23
30
  }
24
31
  ])
25
32
  const handleTooltip = (item, s, dataIndex) => `<div>
26
- ${config.legend.showLegendValuesTooltip && config.runtime.seriesLabels && hasMultipleSeries ? `${config.runtime.seriesLabels[s] || ''}<br/>` : ''}
33
+ ${
34
+ config.legend.showLegendValuesTooltip && config.runtime.seriesLabels && hasMultipleSeries
35
+ ? `${config.runtime.seriesLabels[s] || ''}<br/>`
36
+ : ''
37
+ }
27
38
  ${config.xAxis.label}: ${formatNumber(item[config.xAxis.dataKey], 'bottom')} <br/>
28
39
  ${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('')}
40
+ ${additionalColumns
41
+ .map(
42
+ ([label, name, options]) =>
43
+ `${label} : ${formatColNumber(tableData[dataIndex][name], 'left', false, config, options)}<br/>`
44
+ )
45
+ .join('')}
30
46
  </div>`
31
47
 
32
48
  return (
@@ -36,7 +52,7 @@ const ScatterPlot = ({ xScale, yScale }) => {
36
52
  return config.runtime.seriesKeys.map((s, index) => {
37
53
  const transparentArea = config.legend.behavior === 'highlight' && seriesHighlight.length > 0 && seriesHighlight.indexOf(s) === -1
38
54
  const displayArea = config.legend.behavior === 'highlight' || seriesHighlight.length === 0 || seriesHighlight.indexOf(s) !== -1
39
- const seriesColor = config.palette ? colorPalettes[config.palette][index] : '#000'
55
+ const seriesColor = config?.customColors ? config.customColors[index] : config.palette ? colorPalettes[config.palette][index] : '#000'
40
56
 
41
57
  let pointStyles = {
42
58
  filter: 'unset',