@cdc/chart 4.23.6 → 4.23.8

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 (48) hide show
  1. package/dist/cdcchart.js +29981 -29995
  2. package/examples/feature/__data__/area-chart-date-apple.json +5122 -0
  3. package/examples/feature/__data__/city-temperature.json +2198 -0
  4. package/examples/feature/__data__/planet-example-data.json +1 -1
  5. package/examples/feature/area/area-chart-category.json +45 -45
  6. package/examples/feature/area/area-chart-date-apple.json +10376 -0
  7. package/examples/feature/area/area-chart-date-city-temperature.json +4528 -0
  8. package/examples/feature/area/area-chart-date.json +111 -3
  9. package/examples/feature/combo/right-issues.json +1 -1
  10. package/examples/feature/forecasting/combo-forecasting.json +72 -46
  11. package/examples/feature/forecasting/effective_reproduction.json +57 -8
  12. package/examples/feature/forecasting/forecasting.json +12 -3
  13. package/examples/feature/forest-plot/broken.json +700 -0
  14. package/examples/feature/forest-plot/data.csv +24 -0
  15. package/examples/feature/forest-plot/forest-plot.json +717 -0
  16. package/examples/feature/line/line-chart.json +11 -11
  17. package/examples/feature/pie/planet-pie-example-config.json +1 -1
  18. package/examples/gallery/bar-chart-vertical/vertical-bar-chart-categorical.json +167 -20
  19. package/examples/private/confidence_interval_test.json +248 -0
  20. package/examples/private/tooltip-issue.json +45275 -0
  21. package/index.html +13 -11
  22. package/package.json +4 -3
  23. package/src/CdcChart.jsx +78 -27
  24. package/src/components/AreaChart.jsx +65 -151
  25. package/src/components/BarChart.Horizontal.jsx +251 -0
  26. package/src/components/BarChart.StackedHorizontal.jsx +118 -0
  27. package/src/components/BarChart.StackedVertical.jsx +93 -0
  28. package/src/components/BarChart.Vertical.jsx +204 -0
  29. package/src/components/BarChart.jsx +17 -667
  30. package/src/components/BarChartType.jsx +15 -0
  31. package/src/components/BrushHandle.jsx +17 -0
  32. package/src/components/DataTable.jsx +67 -22
  33. package/src/components/EditorPanel.jsx +426 -358
  34. package/src/components/Forecasting.jsx +23 -86
  35. package/src/components/ForestPlot.jsx +191 -0
  36. package/src/components/ForestPlotSettings.jsx +508 -0
  37. package/src/components/Legend.jsx +10 -8
  38. package/src/components/LineChart.jsx +31 -6
  39. package/src/components/LinearChart.jsx +317 -230
  40. package/src/components/Series.jsx +40 -4
  41. package/src/data/initial-state.js +50 -3
  42. package/src/hooks/useBarChart.js +186 -0
  43. package/src/hooks/useEditorPermissions.js +218 -0
  44. package/src/hooks/useMinMax.js +18 -5
  45. package/src/hooks/useRightAxis.js +2 -1
  46. package/src/hooks/useScales.js +45 -2
  47. package/src/hooks/useTooltip.jsx +407 -0
  48. package/src/scss/main.scss +11 -17
@@ -1,4 +1,4 @@
1
- import React, { useContext, useEffect, useState } from 'react'
1
+ import React, { useContext } from 'react'
2
2
 
3
3
  // cdc
4
4
  import ConfigContext from '../ConfigContext'
@@ -6,43 +6,21 @@ import ErrorBoundary from '@cdc/core/components/ErrorBoundary'
6
6
  import { colorPalettesChart } from '@cdc/core/data/colorPalettes'
7
7
 
8
8
  // visx & d3
9
- import { useTooltipInPortal, defaultStyles } from '@visx/tooltip'
10
9
  import { curveMonotoneX } from '@visx/curve'
11
- import { Bar, Area, LinePath, Line } from '@visx/shape'
10
+ import { Bar, Area, LinePath } from '@visx/shape'
12
11
  import { Group } from '@visx/group'
13
12
 
14
- const Forecasting = ({ xScale, yScale, height, width, chartRef, handleTooltipMouseOver, tooltipData, showTooltip, handleTooltipMouseOff }) => {
15
- const { transformedData: data, rawData, config, seriesHighlight, formatNumber } = useContext(ConfigContext)
13
+ const Forecasting = ({ xScale, yScale, height, width, handleTooltipMouseOver, handleTooltipMouseOff }) => {
14
+ const { transformedData: data, rawData, config, seriesHighlight } = useContext(ConfigContext)
16
15
  const { xAxis, yAxis, legend, runtime } = config
17
16
  const DEBUG = false
18
17
 
19
- // sets the portal x/y for where tooltips should appear on the page.
20
- const [chartPosition, setChartPosition] = useState(null)
21
- useEffect(() => {
22
- setChartPosition(chartRef.current.getBoundingClientRect())
23
- }, [chartRef])
24
-
25
- // a unique id is needed for tooltips.
26
- const tooltip_id = `cdc-open-viz-tooltip-${config.runtime.uniqueId}`
27
-
28
- // it appears we need to use TooltipInPortal.
29
- const { TooltipInPortal } = useTooltipInPortal({
30
- detectBounds: true,
31
- // when tooltip containers are scrolled, this will correctly update the Tooltip position
32
- scroll: true
33
- })
34
-
35
- const TooltipListItem = ({ item }) => {
36
- const [label, value] = item
37
- return label === config.xAxis.dataKey ? `${label}: ${value}` : `${label}: ${formatNumber(value, 'left')}`
38
- }
39
-
40
18
  return (
41
19
  data && (
42
20
  <ErrorBoundary component='ForecastingChart'>
43
- <Group className='forecasting-items' key='forecasting-items-wrapper' left={yAxis.size}>
21
+ <Group className='forecasting-items' key='forecasting-items-wrapper' left={Number(yAxis.size)}>
44
22
  {runtime.forecastingSeriesKeys?.map((group, index) => {
45
- if (!group || !group.stages) return
23
+ if (!group || !group.stages) return false
46
24
  return group.stages.map((stage, stageIndex) => {
47
25
  const { behavior } = legend
48
26
  const groupData = rawData.filter(d => d[group.stageColumn] === stage.key)
@@ -54,13 +32,24 @@ const Forecasting = ({ xScale, yScale, height, width, chartRef, handleTooltipMou
54
32
  {group.confidenceIntervals?.map((ciGroup, ciGroupIndex) => {
55
33
  const palette = colorPalettesChart[stage.color]
56
34
 
35
+ const getFill = () => {
36
+ if (displayArea) return palette[ciGroupIndex] ? palette[ciGroupIndex] : 'transparent'
37
+ return 'transparent'
38
+ }
39
+
40
+ const getStroke = () => {
41
+ if (displayArea) return palette[2] ? palette[2] : 'transparent'
42
+ return 'transparent'
43
+ }
44
+
45
+ if (ciGroup.high === '' || ciGroup.low === '') return
57
46
  return (
58
47
  <Group key={`forecasting-areas--stage-${stage.key.replaceAll(' ', '-')}--group-${stageIndex}-${ciGroupIndex}`}>
59
48
  {/* prettier-ignore */}
60
49
  <Area
61
50
  curve={curveMonotoneX}
62
51
  data={groupData}
63
- fill={displayArea ? palette[ciGroupIndex] : 'transparent'}
52
+ fill={getFill()}
64
53
  opacity={transparentArea ? 0.1 : 0.5}
65
54
  x={d => xScale(Date.parse(d[xAxis.dataKey]))}
66
55
  y0={d => yScale(d[ciGroup.low])}
@@ -70,26 +59,10 @@ const Forecasting = ({ xScale, yScale, height, width, chartRef, handleTooltipMou
70
59
  {ciGroupIndex === 0 && (
71
60
  <>
72
61
  {/* prettier-ignore */}
73
- <LinePath
74
- data={groupData}
75
- x={ d => xScale(Date.parse(d[xAxis.dataKey])) }
76
- y={ d => yScale(d[ciGroup.high])}
77
- curve={curveMonotoneX}
78
- stroke={displayArea ? palette[2] : 'transparent'}
79
- strokeWidth={1}
80
- strokeOpacity={1}
81
- />
62
+ <LinePath data={groupData} x={d => Number(xScale(Date.parse(d[xAxis.dataKey])))} y={d => Number(yScale(d[ciGroup.high]))} curve={curveMonotoneX} stroke={getStroke()} strokeWidth={1} strokeOpacity={1} />
82
63
 
83
64
  {/* prettier-ignore */}
84
- <LinePath
85
- data={groupData}
86
- x={ d => xScale(Date.parse(d[xAxis.dataKey])) }
87
- y={ d => yScale(d[ciGroup.low])}
88
- curve={curveMonotoneX}
89
- stroke={displayArea ? palette[2] : 'transparent'}
90
- strokeWidth={1}
91
- strokeOpacity={1}
92
- />
65
+ <LinePath data={groupData} x={d => Number(xScale(Date.parse(d[xAxis.dataKey])))} y={d => Number(yScale(d[ciGroup.low]))} curve={curveMonotoneX} stroke={getStroke()} strokeWidth={1} strokeOpacity={1} />
93
66
  </>
94
67
  )}
95
68
  </Group>
@@ -99,46 +72,10 @@ const Forecasting = ({ xScale, yScale, height, width, chartRef, handleTooltipMou
99
72
  )
100
73
  })
101
74
  })}
102
-
103
- {tooltipData && Object.entries(tooltipData.data).length > 0 && config?.runtime?.forecastingSeriesKeys?.length > 0 && (config.visualizationType === 'Combo' || config.visualizationType === 'Forecasting') && (
104
- <TooltipInPortal key={Math.random()} top={tooltipData.dataYPosition + chartPosition?.top} left={tooltipData.dataXPosition + chartPosition?.left} style={defaultStyles}>
105
- <ul
106
- style={{
107
- listStyle: 'none',
108
- paddingLeft: 'unset',
109
- fontFamily: 'sans-serif',
110
- margin: 'auto',
111
- lineHeight: '1rem'
112
- }}
113
- data-tooltip-id={tooltip_id}
114
- >
115
- {typeof tooltipData === 'object' &&
116
- Object.entries(tooltipData.data).map((item, index) => (
117
- <li style={{ padding: '2.5px 0' }} key={`li-${index}`}>
118
- <TooltipListItem item={item} />
119
- </li>
120
- ))}
121
- </ul>
122
- </TooltipInPortal>
123
- )}
124
- {config?.runtime?.forecastingSeriesKeys?.length > 0 && (config.visualizationType === 'Combo' || config.visualizationType === 'Forecasting') && (
125
- <Group key='tooltip-hover-section'>
126
- <Bar key={'bars'} width={Number(width)} height={Number(height)} fill={DEBUG ? 'red' : 'transparent'} fillOpacity={0.05} onMouseMove={e => handleTooltipMouseOver(e, data)} onMouseOut={handleTooltipMouseOff} />
127
- </Group>
128
- )}
129
- </Group>
130
-
131
- {showTooltip && tooltipData && config.visual.verticalHoverLine && (
132
- <Group key='tooltipLine-vertical' className='vertical-tooltip-line'>
133
- <Line from={{ x: tooltipData.dataXPosition - 10, y: 0 }} to={{ x: tooltipData.dataXPosition - 10, y: height }} stroke={'black'} strokeWidth={1} pointerEvents='none' strokeDasharray='5,5' className='vertical-tooltip-line' />
75
+ <Group key='tooltip-hover-section'>
76
+ <Bar key={'bars'} width={Number(width)} height={Number(height)} fill={DEBUG ? 'red' : 'transparent'} fillOpacity={0.05} onMouseMove={e => handleTooltipMouseOver(e, data)} onMouseOut={handleTooltipMouseOff} />
134
77
  </Group>
135
- )}
136
-
137
- {showTooltip && tooltipData && config.visual.horizontalHoverLine && (
138
- <Group key='tooltipLine-horizontal' className='horizontal-tooltip-line' left={config.yAxis.size ? config.yAxis.size : 0}>
139
- <Line from={{ x: 0, y: tooltipData.dataYPosition }} to={{ x: width, y: tooltipData.dataYPosition }} stroke={'black'} strokeWidth={1} pointerEvents='none' strokeDasharray='5,5' className='horizontal-tooltip-line' />
140
- </Group>
141
- )}
78
+ </Group>
142
79
  </ErrorBoundary>
143
80
  )
144
81
  )
@@ -0,0 +1,191 @@
1
+ import React, { useContext, useEffect } from 'react'
2
+
3
+ // visx
4
+ import { Group } from '@visx/group'
5
+ import { Line, Bar, Circle, LinePath } from '@visx/shape'
6
+ import { GlyphDiamond } from '@visx/glyph'
7
+ import { Text } from '@visx/text'
8
+ import { scaleLinear } from '@visx/scale'
9
+ import { curveLinearClosed } from '@visx/curve'
10
+
11
+ // cdc
12
+ import ConfigContext from '../ConfigContext'
13
+ import { getFontSize } from '@cdc/core/helpers/cove/number'
14
+
15
+ const ForestPlot = props => {
16
+ const { transformedData: data, updateConfig, dimensions, rawData } = useContext(ConfigContext)
17
+ const { xScale, yScale, config, height, width, handleTooltipMouseOff, handleTooltipMouseOver, maxWidth, maxHeight } = props
18
+ const { forestPlot, runtime, dataFormat } = config
19
+ const [screenWidth, screenHeight] = dimensions
20
+
21
+ // Requirements for forest plot
22
+ // - force legend to be hidden for this chart type
23
+ // - reset the date category axis to zero
24
+ useEffect(() => {
25
+ if (!config.legend.hide) {
26
+ updateConfig({
27
+ ...config,
28
+ legend: {
29
+ ...config.legend,
30
+ hide: true
31
+ },
32
+ xAxis: {
33
+ ...config.xAxis,
34
+ size: 0
35
+ }
36
+ })
37
+ }
38
+ }, [])
39
+
40
+ const diamondHeight = 5
41
+
42
+ // diamond path
43
+ const regressionPoints = [
44
+ { x: xScale(forestPlot.regression.lower), y: height - Number(config.forestPlot.rowHeight) },
45
+ { x: xScale(forestPlot.regression.estimateField), y: height - diamondHeight - Number(config.forestPlot.rowHeight) },
46
+ { x: xScale(forestPlot.regression.upper), y: height - Number(config.forestPlot.rowHeight) },
47
+ { x: xScale(forestPlot.regression.estimateField), y: height + diamondHeight - Number(config.forestPlot.rowHeight) },
48
+ { x: xScale(forestPlot.regression.lower), y: height - Number(config.forestPlot.rowHeight) }
49
+ ]
50
+
51
+ const topMarginOffset = config.forestPlot.rowHeight
52
+
53
+ const topLine = [
54
+ { x: 0, y: topMarginOffset },
55
+ { x: width, y: topMarginOffset }
56
+ ]
57
+
58
+ const bottomLine = [
59
+ { x: 0, y: height },
60
+ { x: width, y: height }
61
+ ]
62
+
63
+ const columnsOnChart = Object.entries(config.columns)
64
+ .map(entry => entry[1])
65
+ .filter(entry => entry.forestPlot === true)
66
+
67
+ const rightOffset = forestPlot.rightWidthOffset !== 0 ? (Number(forestPlot.rightWidthOffset) / 100) * width : width
68
+ const leftOffset = forestPlot.leftWidthOffset !== 0 ? (Number(forestPlot.leftWidthOffset) / 100) * width : width
69
+ const chartWidth = width - rightOffset - leftOffset
70
+
71
+ return (
72
+ <>
73
+ <Group>
74
+ {forestPlot.title !== '' && (
75
+ <Text className={`forest-plot--title`} x={xScale(0)} y={0} textAnchor='middle' verticalAnchor='start' fontSize={getFontSize(config.fontSize)} fill={'black'}>
76
+ {forestPlot.title}
77
+ </Text>
78
+ )}
79
+ {forestPlot.regression.showBaseLine && <Line from={{ x: xScale(forestPlot.regression.estimateField), y: 0 + topMarginOffset }} to={{ x: xScale(forestPlot.regression.estimateField), y: height }} className='forestplot__baseline' stroke={forestPlot.regression.baseLineColor || 'black'} />}
80
+ {forestPlot.showZeroLine && <Line from={{ x: xScale(0), y: 0 + topMarginOffset }} to={{ x: xScale(0), y: height }} className='forestplot__zero-line' stroke='gray' strokeDasharray={'5 5'} />}
81
+
82
+ {data.map((d, i) => {
83
+ // calculate both square and circle size based on radius.min and radius.max
84
+ const scaleRadius = scaleLinear({
85
+ domain: xScale.domain(),
86
+ range: [forestPlot.radius.min, forestPlot.radius.max]
87
+ })
88
+
89
+ // glyph settings
90
+ const diamondSize = forestPlot.radius.scalingColumn !== '' ? scaleRadius(data[i][forestPlot.estimateField]) * 5 : 4
91
+ const rectSize = forestPlot.radius.scalingColumn !== '' ? scaleRadius(data[i][forestPlot.estimateField]) : 4
92
+ const shapeColor = forestPlot.colors.shape ? forestPlot.colors.shape : 'black'
93
+ const lineColor = forestPlot.colors.line ? forestPlot.colors.line : 'black'
94
+
95
+ // ci size
96
+ const ciEndSize = 4
97
+
98
+ return (
99
+ <Group>
100
+ {/* Confidence Interval Paths */}
101
+ <path
102
+ stroke={lineColor}
103
+ strokeWidth={1}
104
+ className='lower-ci'
105
+ d={`
106
+ M${xScale(d[forestPlot.lower])} ${yScale(i) - Number(ciEndSize)}
107
+ L${xScale(d[forestPlot.lower])} ${yScale(i) + Number(ciEndSize)}
108
+ `}
109
+ />
110
+
111
+ <path
112
+ stroke={lineColor}
113
+ strokeWidth={1}
114
+ className='upper-ci'
115
+ d={`
116
+ M${xScale(d[forestPlot.upper])} ${yScale(i) - Number(ciEndSize)}
117
+ L${xScale(d[forestPlot.upper])} ${yScale(i) + Number(ciEndSize)}
118
+ `}
119
+ />
120
+
121
+ {/* main line */}
122
+ <line stroke={lineColor} className={`line-${d[config.yAxis.dataKey]}`} key={i} x1={xScale(d[forestPlot.lower])} x2={xScale(d[forestPlot.upper])} y1={yScale(i)} y2={yScale(i)} />
123
+ {forestPlot.shape === 'circle' && (
124
+ <Circle className='forest-plot--circle' cx={xScale(Number(d[forestPlot.estimateField]))} cy={yScale(i)} r={forestPlot.radius.scalingColumn !== '' ? scaleRadius(data[i][forestPlot.estimateField]) : 4} fill={shapeColor} style={{ opacity: 1, filter: 'unset' }} />
125
+ )}
126
+ {forestPlot.shape === 'square' && <rect className='forest-plot--square' x={xScale(Number(d[forestPlot.estimateField]))} y={yScale(i) - rectSize / 2} width={rectSize} height={rectSize} fill={shapeColor} style={{ opacity: 1, filter: 'unset' }} />}
127
+ {forestPlot.shape === 'diamond' && <GlyphDiamond className='forest-plot--diamond' size={diamondSize} top={yScale(i)} left={xScale(Number(d[forestPlot.estimateField]))} fill={shapeColor} />}
128
+ {forestPlot.shape === 'text' && (
129
+ <Text className='forest-plot--text' x={xScale(Number(d[forestPlot.estimateField]))} y={yScale(i)} textAnchor='middle' verticalAnchor='middle' fontSize={getFontSize(config.fontSize)} fill={shapeColor}>
130
+ {d[forestPlot.estimateField]}
131
+ </Text>
132
+ )}
133
+ </Group>
134
+ )
135
+ })}
136
+
137
+ {/* regression diamond */}
138
+ {regressionPoints && forestPlot.regression.showDiamond && <LinePath data={regressionPoints} x={d => d.x} y={d => d.y} stroke='black' strokeWidth={2} fill={forestPlot.regression.baseLineColor} curve={curveLinearClosed} />}
139
+ {/* regression text */}
140
+ {forestPlot.regression.description && (
141
+ <Text x={0 - Number(config.xAxis.size)} width={width} y={height - config.forestPlot.rowHeight - Number(forestPlot.rowHeight) / 3} verticalAnchor='start' textAnchor='start' style={{ fontWeight: 'bold', fontSize: 12 }}>
142
+ {forestPlot.regression.description}
143
+ </Text>
144
+ )}
145
+
146
+ <Bar key='forest-plot-tooltip-area' className='forest-plot-tooltip-area' width={width} height={height} fill={false ? 'red' : 'transparent'} fillOpacity={0.5} onMouseMove={e => handleTooltipMouseOver(e, data)} onMouseOut={handleTooltipMouseOff} />
147
+ </Group>
148
+ <Line from={topLine[0]} to={topLine[1]} style={{ stroke: 'black', strokeWidth: 2 }} className='forestplot__top-line' />
149
+ <Line from={bottomLine[0]} to={bottomLine[1]} style={{ stroke: 'black', strokeWidth: 2 }} className='forestplot__bottom-line' />
150
+
151
+ {/* column data */}
152
+ {columnsOnChart.map(column => {
153
+ return rawData.map((d, i) => {
154
+ return (
155
+ <Text className={`${d[column.name]}`} x={column.forestPlotAlignRight ? width : column.forestPlotStartingPoint} y={yScale(i)} textAnchor={column.forestPlotAlignRight ? 'end' : 'start'} verticalAnchor='middle' fontSize={getFontSize(config.fontSize)} fill={'black'}>
156
+ {d[column.name]}
157
+ </Text>
158
+ )
159
+ })
160
+ })}
161
+
162
+ {/* X Axis DataKey Cols*/}
163
+ {!forestPlot.hideDateCategoryCol &&
164
+ data.map((d, i) => {
165
+ return (
166
+ <Text className={`${d[config.xAxis.dataKey]}`} x={0} y={yScale(i)} textAnchor={'start'} verticalAnchor='middle' fontSize={getFontSize(config.fontSize)} fill={'black'}>
167
+ {d[config.xAxis.dataKey]}
168
+ </Text>
169
+ )
170
+ })}
171
+
172
+ {/* X Axis Datakey Header */}
173
+ {!forestPlot.hideDateCategoryCol && config.xAxis.dataKey && (
174
+ <Text className={config.xAxis.dataKey} x={0} y={0} textAnchor={'start'} verticalAnchor='start' fontSize={getFontSize(config.fontSize)} fill={'black'}>
175
+ {config.xAxis.dataKey}
176
+ </Text>
177
+ )}
178
+
179
+ {/* column headers */}
180
+ {columnsOnChart.map(column => {
181
+ return (
182
+ <Text className={`${column.label}`} x={column.forestPlotAlignRight ? width : column.forestPlotStartingPoint} y={0} textAnchor={column.forestPlotAlignRight ? 'end' : 'start'} verticalAnchor='start' fontSize={getFontSize(config.fontSize)} fill={'black'}>
183
+ {column.label}
184
+ </Text>
185
+ )
186
+ })}
187
+ </>
188
+ )
189
+ }
190
+
191
+ export default ForestPlot