@cdc/chart 4.23.11 → 4.24.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.
- package/dist/cdcchart.js +35740 -35027
- package/examples/feature/bar/additional-column-tooltip.json +446 -0
- package/examples/feature/bar/tall-data.json +98 -0
- package/examples/feature/forest-plot/forest-plot.json +63 -19
- package/examples/feature/forest-plot/linear.json +52 -3
- package/examples/feature/forest-plot/log.json +26 -0
- package/examples/feature/forest-plot/logarithmic.json +0 -35
- package/examples/feature/line/line-chart-preliminary.json +393 -0
- package/examples/feature/regions/index.json +9 -5
- package/examples/feature/scatterplot/scatterplot.json +272 -33
- package/index.html +10 -8
- package/package.json +2 -2
- package/src/CdcChart.tsx +70 -234
- package/src/ConfigContext.tsx +6 -0
- package/src/_stories/ChartEditor.stories.tsx +22 -0
- package/src/_stories/ChartLine.preliminary.tsx +19 -0
- package/src/_stories/_mock/pie_config.json +192 -0
- package/src/_stories/_mock/pie_data.json +218 -0
- package/src/_stories/_mock/preliminary_mock.json +346 -0
- package/src/components/{AreaChart.Stacked.jsx → AreaChart/components/AreaChart.Stacked.jsx} +2 -2
- package/src/components/{AreaChart.jsx → AreaChart/components/AreaChart.jsx} +2 -26
- package/src/components/AreaChart/index.tsx +4 -0
- package/src/components/{BarChart.Horizontal.tsx → BarChart/components/BarChart.Horizontal.tsx} +8 -8
- package/src/components/{BarChart.StackedHorizontal.tsx → BarChart/components/BarChart.StackedHorizontal.tsx} +37 -7
- package/src/components/BarChart/components/BarChart.StackedVertical.tsx +108 -0
- package/src/components/{BarChart.Vertical.tsx → BarChart/components/BarChart.Vertical.tsx} +53 -70
- package/src/components/BarChart/components/BarChart.jsx +39 -0
- package/src/components/{BarChartType.jsx → BarChart/components/BarChartType.jsx} +0 -2
- package/src/components/BarChart/components/context.tsx +13 -0
- package/src/components/BarChart/index.tsx +3 -0
- package/src/components/{BoxPlot.jsx → BoxPlot/BoxPlot.jsx} +10 -9
- package/src/components/BoxPlot/index.tsx +3 -0
- package/src/components/EditorPanel/EditorPanel.tsx +2776 -0
- package/src/components/EditorPanel/EditorPanelContext.ts +40 -0
- package/src/components/EditorPanel/components/PanelProps.ts +3 -0
- package/src/components/EditorPanel/components/Panels/Panel.BoxPlot.tsx +148 -0
- package/src/components/{ForestPlotSettings.jsx → EditorPanel/components/Panels/Panel.ForestPlotSettings.tsx} +97 -167
- package/src/components/EditorPanel/components/Panels/Panel.General.tsx +160 -0
- package/src/components/EditorPanel/components/Panels/Panel.Regions.tsx +168 -0
- package/src/components/{Series.jsx → EditorPanel/components/Panels/Panel.Series.tsx} +4 -4
- package/src/components/EditorPanel/components/Panels/Panel.Visual.tsx +297 -0
- package/src/components/EditorPanel/components/Panels/index.tsx +17 -0
- package/src/components/EditorPanel/components/panels.scss +72 -0
- package/src/components/EditorPanel/editor-panel.scss +739 -0
- package/src/components/EditorPanel/index.tsx +3 -0
- package/src/{hooks → components/EditorPanel}/useEditorPermissions.js +34 -2
- package/src/components/{Forecasting.jsx → Forecasting/Forecasting.jsx} +1 -1
- package/src/components/Forecasting/index.tsx +3 -0
- package/src/components/ForestPlot/ForestPlot.tsx +254 -0
- package/src/components/ForestPlot/ForestPlotProps.ts +7 -0
- package/src/components/ForestPlot/index.tsx +1 -209
- package/src/components/Legend/Legend.Component.tsx +199 -0
- package/src/components/Legend/Legend.tsx +28 -0
- package/src/components/Legend/helpers/createFormatLabels.tsx +140 -0
- package/src/components/Legend/index.tsx +3 -0
- package/src/components/LineChart/LineChartProps.ts +29 -0
- package/src/components/LineChart/components/LineChart.Circle.tsx +147 -0
- package/src/components/LineChart/helpers.ts +45 -0
- package/src/components/LineChart/index.tsx +111 -23
- package/src/components/LinearChart.jsx +55 -72
- package/src/components/PairedBarChart.jsx +4 -2
- package/src/components/{PieChart.jsx → PieChart/PieChart.tsx} +93 -31
- package/src/components/PieChart/index.tsx +3 -0
- package/src/components/Regions/components/Regions.tsx +144 -0
- package/src/components/Regions/index.tsx +3 -0
- package/src/components/{ScatterPlot.jsx → ScatterPlot/ScatterPlot.jsx} +3 -3
- package/src/components/ScatterPlot/index.tsx +3 -0
- package/src/components/{SparkLine.jsx → Sparkline/SparkLine.jsx} +2 -2
- package/src/components/Sparkline/index.tsx +3 -0
- package/src/data/initial-state.js +10 -8
- package/src/helpers/abbreviateNumber.ts +17 -0
- package/src/helpers/computeMarginBottom.ts +55 -0
- package/src/helpers/filterData.ts +18 -0
- package/src/helpers/generateColorsArray.ts +8 -0
- package/src/helpers/getQuartiles.ts +30 -0
- package/src/helpers/handleChartAriaLabels.ts +19 -0
- package/src/helpers/handleLineType.ts +18 -0
- package/src/helpers/lineOptions.ts +18 -0
- package/src/helpers/sort.ts +7 -0
- package/src/helpers/tests/computeMarginBottom.test.ts +20 -0
- package/src/hooks/useBarChart.js +7 -6
- package/src/hooks/useHighlightedBars.js +1 -1
- package/src/hooks/useMinMax.ts +3 -3
- package/src/hooks/useScales.ts +19 -6
- package/src/hooks/{useTooltip.jsx → useTooltip.tsx} +31 -25
- package/src/scss/main.scss +0 -3
- package/src/types/ChartConfig.ts +167 -23
- package/src/types/ChartContext.ts +34 -12
- package/src/types/ForestPlot.ts +7 -14
- package/src/types/Label.ts +7 -0
- package/examples/feature/scatterplot/scatterplot-continuous.csv +0 -17
- package/src/ConfigContext.jsx +0 -5
- package/src/components/BarChart.StackedVertical.tsx +0 -91
- package/src/components/BarChart.jsx +0 -30
- package/src/components/EditorPanel.jsx +0 -3356
- package/src/components/ForestPlot/Readme.md +0 -0
- package/src/components/Legend.jsx +0 -310
- package/src/components/LineChart/LineChart.Circle.tsx +0 -105
- package/src/scss/LinearChart.scss +0 -0
- package/src/scss/editor-panel.scss +0 -745
- package/src/scss/legend.scss +0 -206
- package/src/scss/mixins.scss +0 -0
- package/src/scss/variables.scss +0 -1
- package/src/types/ChartProps.ts +0 -7
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import React, { useContext } from 'react'
|
|
2
|
-
import ConfigContext from '
|
|
2
|
+
import ConfigContext from '../../ConfigContext'
|
|
3
3
|
|
|
4
4
|
export const useEditorPermissions = () => {
|
|
5
5
|
const { config } = useContext(ConfigContext)
|
|
@@ -24,6 +24,12 @@ export const useEditorPermissions = () => {
|
|
|
24
24
|
|
|
25
25
|
const headerColors = ['theme-blue', 'theme-purple', 'theme-brown', 'theme-teal', 'theme-pink', 'theme-orange', 'theme-slate', 'theme-indigo', 'theme-cyan', 'theme-green', 'theme-amber']
|
|
26
26
|
|
|
27
|
+
const visSupportsDateCategoryAxis = () => {
|
|
28
|
+
const disabledCharts = ['Forest Plot']
|
|
29
|
+
if (disabledCharts.includes(visualizationType)) return false
|
|
30
|
+
return true
|
|
31
|
+
}
|
|
32
|
+
|
|
27
33
|
const visSupportsSuperTitle = () => {
|
|
28
34
|
const disabledCharts = ['Spark Line']
|
|
29
35
|
if (disabledCharts.includes(visualizationType)) return false
|
|
@@ -186,6 +192,18 @@ export const useEditorPermissions = () => {
|
|
|
186
192
|
return true
|
|
187
193
|
}
|
|
188
194
|
|
|
195
|
+
const visSupportsValueAxisMax = () => {
|
|
196
|
+
const disabledCharts = ['Forest Plot']
|
|
197
|
+
if (disabledCharts.includes(visualizationType)) return false
|
|
198
|
+
return true
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
const visSupportsValueAxisMin = () => {
|
|
202
|
+
const disabledCharts = ['Forest Plot']
|
|
203
|
+
if (disabledCharts.includes(visualizationType)) return false
|
|
204
|
+
return true
|
|
205
|
+
}
|
|
206
|
+
|
|
189
207
|
const visSupportsFilters = () => {
|
|
190
208
|
const disabledCharts = ['Forest Plot']
|
|
191
209
|
if (disabledCharts.includes(visualizationType)) return false
|
|
@@ -201,16 +219,22 @@ export const useEditorPermissions = () => {
|
|
|
201
219
|
|
|
202
220
|
// implement later
|
|
203
221
|
const visSupportsValueAxisTicks = () => {
|
|
222
|
+
const disabledCharts = ['Forest Plot']
|
|
223
|
+
if (disabledCharts.includes(visualizationType)) return false
|
|
204
224
|
return true
|
|
205
225
|
}
|
|
206
226
|
|
|
207
227
|
// implement later
|
|
208
228
|
const visSupportsValueAxisLine = () => {
|
|
229
|
+
const disabledCharts = ['Forest Plot']
|
|
230
|
+
if (disabledCharts.includes(visualizationType)) return false
|
|
209
231
|
return true
|
|
210
232
|
}
|
|
211
233
|
|
|
212
234
|
// implement later
|
|
213
235
|
const visSupportsValueAxisLabels = () => {
|
|
236
|
+
const disabledCharts = ['Forest Plot']
|
|
237
|
+
if (disabledCharts.includes(visualizationType)) return false
|
|
214
238
|
return true
|
|
215
239
|
}
|
|
216
240
|
|
|
@@ -251,6 +275,10 @@ export const useEditorPermissions = () => {
|
|
|
251
275
|
return true
|
|
252
276
|
}
|
|
253
277
|
|
|
278
|
+
const visSupportsDateCategoryAxisPadding = () => {
|
|
279
|
+
return config.xAxis.type === 'date' && config.xAxis.sortDates
|
|
280
|
+
}
|
|
281
|
+
|
|
254
282
|
const visSupportsReactTooltip = () => {
|
|
255
283
|
if (['Deviation Bar', 'Box Plot', 'Scatter Plot', 'Paired Bar'].includes(visualizationType) || (visualizationType === 'Bar' && config.tooltips.singleSeries)) {
|
|
256
284
|
return true
|
|
@@ -272,12 +300,14 @@ export const useEditorPermissions = () => {
|
|
|
272
300
|
visSupportsBarSpace,
|
|
273
301
|
visSupportsBarThickness,
|
|
274
302
|
visSupportsChartHeight,
|
|
303
|
+
visSupportsDateCategoryAxis,
|
|
275
304
|
visSupportsDateCategoryAxisLabel,
|
|
276
305
|
visSupportsDateCategoryAxisLine,
|
|
277
306
|
visSupportsDateCategoryAxisTicks,
|
|
278
307
|
visSupportsDateCategoryHeight,
|
|
279
308
|
visSupportsDateCategoryNumTicks,
|
|
280
309
|
visSupportsDateCategoryTickRotation,
|
|
310
|
+
visSupportsDateCategoryAxisPadding,
|
|
281
311
|
visSupportsFilters,
|
|
282
312
|
visSupportsFootnotes,
|
|
283
313
|
visSupportsLeftValueAxis,
|
|
@@ -294,6 +324,8 @@ export const useEditorPermissions = () => {
|
|
|
294
324
|
visSupportsValueAxisLabels,
|
|
295
325
|
visSupportsValueAxisLine,
|
|
296
326
|
visSupportsValueAxisTicks,
|
|
297
|
-
visSupportsReactTooltip
|
|
327
|
+
visSupportsReactTooltip,
|
|
328
|
+
visSupportsValueAxisMax,
|
|
329
|
+
visSupportsValueAxisMin
|
|
298
330
|
}
|
|
299
331
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import React, { useContext } from 'react'
|
|
2
2
|
|
|
3
3
|
// cdc
|
|
4
|
-
import ConfigContext from '
|
|
4
|
+
import ConfigContext from '../../ConfigContext'
|
|
5
5
|
import ErrorBoundary from '@cdc/core/components/ErrorBoundary'
|
|
6
6
|
import { colorPalettesChart, sequentialPalettes } from '@cdc/core/data/colorPalettes'
|
|
7
7
|
|
|
@@ -0,0 +1,254 @@
|
|
|
1
|
+
import { useContext, useEffect, useState } from 'react'
|
|
2
|
+
|
|
3
|
+
// visx
|
|
4
|
+
import { Group } from '@visx/group'
|
|
5
|
+
import { Line, Bar, Circle, LinePath } from '@visx/shape'
|
|
6
|
+
import { Text } from '@visx/text'
|
|
7
|
+
import { scaleLinear } from '@visx/scale'
|
|
8
|
+
import { curveLinearClosed } from '@visx/curve'
|
|
9
|
+
|
|
10
|
+
// types
|
|
11
|
+
import { type ForestPlotProps } from '@cdc/chart/src/components/ForestPlot/ForestPlotProps'
|
|
12
|
+
import { type ChartConfig } from '@cdc/chart/src/types/ChartConfig'
|
|
13
|
+
import { type ChartContext } from '@cdc/chart/src/types/ChartContext'
|
|
14
|
+
|
|
15
|
+
// cdc
|
|
16
|
+
import ConfigContext from '../../ConfigContext'
|
|
17
|
+
import { getFontSize } from '@cdc/core/helpers/cove/number'
|
|
18
|
+
|
|
19
|
+
const ForestPlot = (props: ForestPlotProps) => {
|
|
20
|
+
const { rawData: data, updateConfig } = useContext<ChartContext>(ConfigContext)
|
|
21
|
+
const { xScale, yScale, config, height, width, handleTooltipMouseOff, handleTooltipMouseOver } = props
|
|
22
|
+
const { forestPlot } = config as ChartConfig
|
|
23
|
+
const labelPosition = config.xAxis.tickWidthMax + 10
|
|
24
|
+
const [initialLogTicksSet, setInitialLogTicks] = useState(false)
|
|
25
|
+
|
|
26
|
+
useEffect(() => {
|
|
27
|
+
try {
|
|
28
|
+
const defaultColumns = ['estimateField', 'lower', 'upper', 'estimateRadius']
|
|
29
|
+
const newConfig = config
|
|
30
|
+
|
|
31
|
+
// Setting a default number of colums.additionalColumn1, columns.additionalColumn2, etc.
|
|
32
|
+
// to loop through to clean up which columns are being displayed by default for forest plots
|
|
33
|
+
// Set tooltip and data table properties according to the default forestPlot column names above.
|
|
34
|
+
const colsToCheck = 10
|
|
35
|
+
for (let i = 0; i < colsToCheck; i++) {
|
|
36
|
+
defaultColumns.forEach(col => {
|
|
37
|
+
if (config.forestPlot[col] && config.forestPlot[col] !== newConfig.columns[config.forestPlot[`additionalColumn${i}`]]?.name) {
|
|
38
|
+
delete newConfig.columns[`additionalColumn${i}`] // Remove old value if found to prevent duplicates
|
|
39
|
+
newConfig.columns[config.forestPlot[col]] = {}
|
|
40
|
+
newConfig.columns[config.forestPlot[col]].dataKey = newConfig.forestPlot[col]
|
|
41
|
+
newConfig.columns[config.forestPlot[col]].name = newConfig.forestPlot[col]
|
|
42
|
+
newConfig.columns[config.forestPlot[col]].dataTable = true
|
|
43
|
+
newConfig.columns[config.forestPlot[col]].tooltips = true
|
|
44
|
+
newConfig.columns[config.forestPlot[col]].label = newConfig.forestPlot[col]
|
|
45
|
+
}
|
|
46
|
+
})
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// Add weight column into tooltip and data table.
|
|
50
|
+
if (config.forestPlot.radius.scalingColumn) {
|
|
51
|
+
newConfig.columns[config.forestPlot.radius.scalingColumn] = {}
|
|
52
|
+
newConfig.columns[config.forestPlot.radius.scalingColumn].dataKey = newConfig.forestPlot.radius.scalingColumn
|
|
53
|
+
newConfig.columns[config.forestPlot.radius.scalingColumn].name = newConfig.forestPlot.radius.scalingColumn
|
|
54
|
+
newConfig.columns[config.forestPlot.radius.scalingColumn].label = newConfig.forestPlot.radius.scalingColumn
|
|
55
|
+
newConfig.columns[config.forestPlot.radius.scalingColumn].dataTable = true
|
|
56
|
+
newConfig.columns[config.forestPlot.radius.scalingColumn].tooltips = true
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
if (newConfig.table.showVertical) {
|
|
60
|
+
newConfig.table.indexLabel = config.xAxis.dataKey
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
updateConfig(newConfig)
|
|
64
|
+
} catch (e) {
|
|
65
|
+
console.log(e.message)
|
|
66
|
+
}
|
|
67
|
+
}, [])
|
|
68
|
+
|
|
69
|
+
useEffect(() => {
|
|
70
|
+
if (!initialLogTicksSet && config.forestPlot.type === 'Logarithmic') {
|
|
71
|
+
updateConfig({
|
|
72
|
+
...config,
|
|
73
|
+
dataFormat: {
|
|
74
|
+
...config.dataFormat,
|
|
75
|
+
roundTo: 2
|
|
76
|
+
}
|
|
77
|
+
})
|
|
78
|
+
setInitialLogTicks(true)
|
|
79
|
+
}
|
|
80
|
+
}, [config.forestPlot.type])
|
|
81
|
+
|
|
82
|
+
const pooledData = config.data.find(d => d[config.xAxis.dataKey] === config.forestPlot.pooledResult.column)
|
|
83
|
+
|
|
84
|
+
const regressionPoints = pooledData
|
|
85
|
+
? [
|
|
86
|
+
{ x: xScale(pooledData[config.forestPlot.lower]), y: height - Number(config.forestPlot.rowHeight) },
|
|
87
|
+
{ x: xScale(pooledData[config.forestPlot.estimateField]), y: height - forestPlot.pooledResult.diamondHeight - Number(config.forestPlot.rowHeight) },
|
|
88
|
+
{ x: xScale(pooledData[config.forestPlot.upper]), y: height - Number(config.forestPlot.rowHeight) },
|
|
89
|
+
{ x: xScale(pooledData[config.forestPlot.estimateField]), y: height + forestPlot.pooledResult.diamondHeight - Number(config.forestPlot.rowHeight) },
|
|
90
|
+
{ x: xScale(pooledData[config.forestPlot.lower]), y: height - Number(config.forestPlot.rowHeight) }
|
|
91
|
+
]
|
|
92
|
+
: []
|
|
93
|
+
|
|
94
|
+
const topMarginOffset = config.forestPlot.rowHeight
|
|
95
|
+
|
|
96
|
+
const topLine = [
|
|
97
|
+
{ x: 0, y: topMarginOffset },
|
|
98
|
+
{ x: width, y: topMarginOffset }
|
|
99
|
+
]
|
|
100
|
+
|
|
101
|
+
const bottomLine = [
|
|
102
|
+
{ x: 0, y: height },
|
|
103
|
+
{ x: width, y: height }
|
|
104
|
+
]
|
|
105
|
+
|
|
106
|
+
type Columns = {
|
|
107
|
+
forestPlot?: boolean
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
const columnsOnChart: Columns[] = Object.entries(config.columns)
|
|
111
|
+
.map(entry => entry[1])
|
|
112
|
+
.filter(entry => entry.forestPlot === true)
|
|
113
|
+
|
|
114
|
+
return (
|
|
115
|
+
<>
|
|
116
|
+
<Group width={width}>
|
|
117
|
+
{forestPlot.title && (
|
|
118
|
+
<Text className={`forest-plot--title`} x={forestPlot.type === 'Linear' ? xScale(0) : xScale(1)} y={0} textAnchor='middle' verticalAnchor='start' fontSize={getFontSize(config.fontSize)} fill={'black'}>
|
|
119
|
+
{forestPlot.title}
|
|
120
|
+
</Text>
|
|
121
|
+
)}
|
|
122
|
+
|
|
123
|
+
{/* Line of no effect on Continuous Scale. */}
|
|
124
|
+
{forestPlot.lineOfNoEffect.show && forestPlot.type === 'Linear' && <Line from={{ x: xScale(0), y: 0 + topMarginOffset }} to={{ x: xScale(0), y: height }} className='forestplot__line-of-no-effect' stroke={forestPlot.regression.baseLineColor || 'black'} />}
|
|
125
|
+
|
|
126
|
+
{/* Line of no effect on Logarithmic Scale. */}
|
|
127
|
+
{forestPlot.lineOfNoEffect.show && forestPlot.type === 'Logarithmic' && <Line from={{ x: xScale(1), y: 0 + topMarginOffset }} to={{ x: xScale(1), y: height }} className='forestplot__line-of-no-effect' stroke={forestPlot.regression.baseLineColor || 'black'} />}
|
|
128
|
+
|
|
129
|
+
{data.map((d, i) => {
|
|
130
|
+
// calculate both square and circle size based on radius.min and radius.max
|
|
131
|
+
const scaleRadius = scaleLinear({
|
|
132
|
+
domain: data.map(d => d[forestPlot.radius.scalingColumn]),
|
|
133
|
+
range: [forestPlot.radius.min, forestPlot.radius.max]
|
|
134
|
+
})
|
|
135
|
+
|
|
136
|
+
// glyph settings
|
|
137
|
+
const rectSize = forestPlot.radius.scalingColumn !== '' ? scaleRadius(data[i][forestPlot.radius.scalingColumn]) : 4
|
|
138
|
+
const shapeColor = forestPlot.colors.shape ? forestPlot.colors.shape : 'black'
|
|
139
|
+
const lineColor = forestPlot.colors.line ? forestPlot.colors.line : 'black'
|
|
140
|
+
|
|
141
|
+
// ci size
|
|
142
|
+
const ciEndSize = 4
|
|
143
|
+
|
|
144
|
+
// Don't run calculations on the pooled column
|
|
145
|
+
const isTotalColumn = d[config.xAxis.dataKey] === forestPlot.pooledResult.column
|
|
146
|
+
|
|
147
|
+
return !isTotalColumn ? (
|
|
148
|
+
<Group>
|
|
149
|
+
{/* Confidence Interval Paths */}
|
|
150
|
+
<path
|
|
151
|
+
stroke={lineColor}
|
|
152
|
+
strokeWidth={1}
|
|
153
|
+
className='lower-ci'
|
|
154
|
+
d={`
|
|
155
|
+
M${xScale(d[forestPlot.lower])} ${yScale(i) - Number(ciEndSize)}
|
|
156
|
+
L${xScale(d[forestPlot.lower])} ${yScale(i) + Number(ciEndSize)}
|
|
157
|
+
`}
|
|
158
|
+
/>
|
|
159
|
+
|
|
160
|
+
<path
|
|
161
|
+
stroke={lineColor}
|
|
162
|
+
strokeWidth={1}
|
|
163
|
+
className='upper-ci'
|
|
164
|
+
d={`
|
|
165
|
+
M${xScale(d[forestPlot.upper])} ${yScale(i) - Number(ciEndSize)}
|
|
166
|
+
L${xScale(d[forestPlot.upper])} ${yScale(i) + Number(ciEndSize)}
|
|
167
|
+
`}
|
|
168
|
+
/>
|
|
169
|
+
|
|
170
|
+
{/* main line */}
|
|
171
|
+
<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)} />
|
|
172
|
+
{forestPlot.shape === 'circle' && (
|
|
173
|
+
<Circle className='forest-plot--circle' cx={xScale(Number(d[forestPlot.estimateField]))} cy={yScale(i)} r={forestPlot.radius.scalingColumn !== '' ? scaleRadius(data[i][forestPlot.radius.scalingColumn]) : 4} fill={shapeColor} style={{ opacity: 1, filter: 'unset' }} />
|
|
174
|
+
)}
|
|
175
|
+
{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' }} />}
|
|
176
|
+
{forestPlot.shape === 'text' && (
|
|
177
|
+
<Text className='forest-plot--text' x={xScale(Number(d[forestPlot.estimateField]))} y={yScale(i)} textAnchor='middle' verticalAnchor='middle' fontSize={getFontSize(config.fontSize)} fill={shapeColor}>
|
|
178
|
+
{d[forestPlot.estimateField]}
|
|
179
|
+
</Text>
|
|
180
|
+
)}
|
|
181
|
+
</Group>
|
|
182
|
+
) : (
|
|
183
|
+
<LinePath data={regressionPoints} x={d => d.x} y={d => d.y - getFontSize(config.fontSize) / 2} stroke='black' strokeWidth={2} fill={'black'} curve={curveLinearClosed} />
|
|
184
|
+
)
|
|
185
|
+
})}
|
|
186
|
+
|
|
187
|
+
{/* regression diamond */}
|
|
188
|
+
{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} />}
|
|
189
|
+
{/* regression text */}
|
|
190
|
+
{forestPlot.regression.description && (
|
|
191
|
+
<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 }}>
|
|
192
|
+
{forestPlot.regression.description}
|
|
193
|
+
</Text>
|
|
194
|
+
)}
|
|
195
|
+
|
|
196
|
+
<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} />
|
|
197
|
+
</Group>
|
|
198
|
+
<Line from={topLine[0]} to={topLine[1]} style={{ stroke: 'black', strokeWidth: 2 }} className='forestplot__top-line' />
|
|
199
|
+
<Line from={bottomLine[0]} to={bottomLine[1]} style={{ stroke: 'black', strokeWidth: 2 }} className='forestplot__bottom-line' />
|
|
200
|
+
|
|
201
|
+
{/* column data */}
|
|
202
|
+
{columnsOnChart.map(column => {
|
|
203
|
+
return data.map((d, i) => {
|
|
204
|
+
return (
|
|
205
|
+
<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'}>
|
|
206
|
+
{d[column.name]}
|
|
207
|
+
</Text>
|
|
208
|
+
)
|
|
209
|
+
})
|
|
210
|
+
})}
|
|
211
|
+
|
|
212
|
+
{/* X Axis DataKey Cols*/}
|
|
213
|
+
{!forestPlot.hideDateCategoryCol &&
|
|
214
|
+
data.map((d, i) => {
|
|
215
|
+
return (
|
|
216
|
+
<Text className={`${d[config.xAxis.dataKey]}`} x={0} y={yScale(i)} textAnchor={'start'} verticalAnchor='middle' fontSize={getFontSize(config.fontSize)} fill={'black'}>
|
|
217
|
+
{d[config.xAxis.dataKey]}
|
|
218
|
+
</Text>
|
|
219
|
+
)
|
|
220
|
+
})}
|
|
221
|
+
|
|
222
|
+
{/* X Axis Datakey Header */}
|
|
223
|
+
{!forestPlot.hideDateCategoryCol && config.xAxis.dataKey && (
|
|
224
|
+
<Text className={config.xAxis.dataKey} x={0} y={0} textAnchor={'start'} verticalAnchor='start' fontSize={getFontSize(config.fontSize)} fill={'black'}>
|
|
225
|
+
{config.xAxis.dataKey}
|
|
226
|
+
</Text>
|
|
227
|
+
)}
|
|
228
|
+
|
|
229
|
+
{/* column headers */}
|
|
230
|
+
{columnsOnChart.map(column => {
|
|
231
|
+
return (
|
|
232
|
+
<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'}>
|
|
233
|
+
{column.label}
|
|
234
|
+
</Text>
|
|
235
|
+
)
|
|
236
|
+
})}
|
|
237
|
+
|
|
238
|
+
{/* left bottom label */}
|
|
239
|
+
{forestPlot.leftLabel && (
|
|
240
|
+
<Text className='forest-plot__left-label' x={forestPlot.type === 'Linear' ? xScale(0) - 25 : xScale(1) - 25} y={height + labelPosition} textAnchor='end' verticalAnchor='start'>
|
|
241
|
+
{forestPlot.leftLabel}
|
|
242
|
+
</Text>
|
|
243
|
+
)}
|
|
244
|
+
|
|
245
|
+
{forestPlot.rightLabel && (
|
|
246
|
+
<Text className='forest-plot__right-label' x={forestPlot.type === 'Linear' ? xScale(0) + 25 : xScale(1) + 25} y={height + labelPosition} textAnchor='start' verticalAnchor='start'>
|
|
247
|
+
{forestPlot.rightLabel}
|
|
248
|
+
</Text>
|
|
249
|
+
)}
|
|
250
|
+
</>
|
|
251
|
+
)
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
export default ForestPlot
|
|
@@ -1,11 +1,18 @@
|
|
|
1
1
|
import { type ChartConfig } from '@cdc/chart/src/types/ChartConfig'
|
|
2
2
|
|
|
3
3
|
export type ForestPlotProps = {
|
|
4
|
+
/** xScale - scaling function for bottom axis */
|
|
4
5
|
xScale: Function
|
|
6
|
+
/** yScale - scaling function for left axis */
|
|
5
7
|
yScale: Function
|
|
8
|
+
/** config - standard chart config */
|
|
6
9
|
config: ChartConfig
|
|
10
|
+
/** height - height of chart */
|
|
7
11
|
height: number
|
|
12
|
+
/** width - width of chart */
|
|
8
13
|
width: number
|
|
14
|
+
/** handleTooltipMouseOff - helper function for hiding tooltip */
|
|
9
15
|
handleTooltipMouseOff: Function
|
|
16
|
+
/** handleTooltipMousOver - helper function for showing tooltip */
|
|
10
17
|
handleTooltipMouseOver: Function
|
|
11
18
|
}
|
|
@@ -1,211 +1,3 @@
|
|
|
1
|
-
import
|
|
2
|
-
|
|
3
|
-
// visx
|
|
4
|
-
import { Group } from '@visx/group'
|
|
5
|
-
import { Line, Bar, Circle, LinePath } from '@visx/shape'
|
|
6
|
-
import { Text } from '@visx/text'
|
|
7
|
-
import { scaleLinear } from '@visx/scale'
|
|
8
|
-
import { curveLinearClosed } from '@visx/curve'
|
|
9
|
-
|
|
10
|
-
// types
|
|
11
|
-
import { type ForestPlotProps } from '@cdc/chart/src/components/ForestPlot/ForestPlotProps'
|
|
12
|
-
import { type ChartConfig } from '@cdc/chart/src/types/ChartConfig'
|
|
13
|
-
import { type ChartContext } from '@cdc/chart/src/types/ChartContext'
|
|
14
|
-
|
|
15
|
-
// cdc
|
|
16
|
-
import ConfigContext from '../../ConfigContext'
|
|
17
|
-
import { getFontSize } from '@cdc/core/helpers/cove/number'
|
|
18
|
-
|
|
19
|
-
const ForestPlot = (props: ForestPlotProps) => {
|
|
20
|
-
const { rawData: data, updateConfig } = useContext<ChartContext>(ConfigContext)
|
|
21
|
-
const { xScale, yScale, config, height, width, handleTooltipMouseOff, handleTooltipMouseOver } = props
|
|
22
|
-
const { forestPlot } = config as ChartConfig
|
|
23
|
-
|
|
24
|
-
// Requirements for forest plot
|
|
25
|
-
// - force legend to be hidden for this chart type
|
|
26
|
-
// - reset the date category axis to zero
|
|
27
|
-
useEffect(() => {
|
|
28
|
-
if (!config.legend.hide) {
|
|
29
|
-
updateConfig({
|
|
30
|
-
...config,
|
|
31
|
-
legend: {
|
|
32
|
-
...config.legend,
|
|
33
|
-
hide: true
|
|
34
|
-
},
|
|
35
|
-
xAxis: {
|
|
36
|
-
...config.xAxis,
|
|
37
|
-
size: 0
|
|
38
|
-
}
|
|
39
|
-
})
|
|
40
|
-
}
|
|
41
|
-
}, [])
|
|
42
|
-
|
|
43
|
-
const pooledData = config.data.find(d => d[config.xAxis.dataKey] === config.forestPlot.pooledResult.column)
|
|
44
|
-
|
|
45
|
-
const regressionPoints = pooledData
|
|
46
|
-
? [
|
|
47
|
-
{ x: xScale(pooledData[config.forestPlot.lower]), y: height - Number(config.forestPlot.rowHeight) },
|
|
48
|
-
{ x: xScale(pooledData[config.forestPlot.estimateField]), y: height - forestPlot.pooledResult.diamondHeight - Number(config.forestPlot.rowHeight) },
|
|
49
|
-
{ x: xScale(pooledData[config.forestPlot.upper]), y: height - Number(config.forestPlot.rowHeight) },
|
|
50
|
-
{ x: xScale(pooledData[config.forestPlot.estimateField]), y: height + forestPlot.pooledResult.diamondHeight - Number(config.forestPlot.rowHeight) },
|
|
51
|
-
{ x: xScale(pooledData[config.forestPlot.lower]), y: height - Number(config.forestPlot.rowHeight) }
|
|
52
|
-
]
|
|
53
|
-
: []
|
|
54
|
-
|
|
55
|
-
const topMarginOffset = config.forestPlot.rowHeight
|
|
56
|
-
|
|
57
|
-
const topLine = [
|
|
58
|
-
{ x: 0, y: topMarginOffset },
|
|
59
|
-
{ x: width, y: topMarginOffset }
|
|
60
|
-
]
|
|
61
|
-
|
|
62
|
-
const bottomLine = [
|
|
63
|
-
{ x: 0, y: height },
|
|
64
|
-
{ x: width, y: height }
|
|
65
|
-
]
|
|
66
|
-
|
|
67
|
-
const columnsOnChart = Object.entries(config.columns)
|
|
68
|
-
.map(entry => entry[1])
|
|
69
|
-
.filter(entry => entry.forestPlot === true)
|
|
70
|
-
|
|
71
|
-
return (
|
|
72
|
-
<>
|
|
73
|
-
<Group>
|
|
74
|
-
{forestPlot.title && (
|
|
75
|
-
<Text className={`forest-plot--title`} x={forestPlot.type === 'Linear' ? xScale(0) : xScale(1)} y={0} textAnchor='middle' verticalAnchor='start' fontSize={getFontSize(config.fontSize)} fill={'black'}>
|
|
76
|
-
{forestPlot.title}
|
|
77
|
-
</Text>
|
|
78
|
-
)}
|
|
79
|
-
|
|
80
|
-
{/* Line of no effect on Continuous Scale. */}
|
|
81
|
-
{forestPlot.lineOfNoEffect.show && forestPlot.type === 'Linear' && <Line from={{ x: xScale(0), y: 0 + topMarginOffset }} to={{ x: xScale(0), y: height }} className='forestplot__line-of-no-effect' stroke={forestPlot.regression.baseLineColor || 'black'} />}
|
|
82
|
-
|
|
83
|
-
{/* Line of no effect on Logarithmic Scale. */}
|
|
84
|
-
{forestPlot.lineOfNoEffect.show && forestPlot.type === 'Logarithmic' && <Line from={{ x: xScale(1), y: 0 + topMarginOffset }} to={{ x: xScale(1), y: height }} className='forestplot__line-of-no-effect' stroke={forestPlot.regression.baseLineColor || 'black'} />}
|
|
85
|
-
|
|
86
|
-
{data.map((d, i) => {
|
|
87
|
-
// calculate both square and circle size based on radius.min and radius.max
|
|
88
|
-
const scaleRadius = scaleLinear({
|
|
89
|
-
domain: data.map(d => d[forestPlot.radius.scalingColumn]),
|
|
90
|
-
range: [forestPlot.radius.min, forestPlot.radius.max]
|
|
91
|
-
})
|
|
92
|
-
|
|
93
|
-
// glyph settings
|
|
94
|
-
const rectSize = forestPlot.radius.scalingColumn !== '' ? scaleRadius(data[i][forestPlot.radius.scalingColumn]) : 4
|
|
95
|
-
const shapeColor = forestPlot.colors.shape ? forestPlot.colors.shape : 'black'
|
|
96
|
-
const lineColor = forestPlot.colors.line ? forestPlot.colors.line : 'black'
|
|
97
|
-
|
|
98
|
-
// ci size
|
|
99
|
-
const ciEndSize = 4
|
|
100
|
-
|
|
101
|
-
// Don't run calculations on the pooled column
|
|
102
|
-
const isTotalColumn = d[config.xAxis.dataKey] === forestPlot.pooledResult.column
|
|
103
|
-
|
|
104
|
-
return !isTotalColumn ? (
|
|
105
|
-
<Group>
|
|
106
|
-
{/* Confidence Interval Paths */}
|
|
107
|
-
<path
|
|
108
|
-
stroke={lineColor}
|
|
109
|
-
strokeWidth={1}
|
|
110
|
-
className='lower-ci'
|
|
111
|
-
d={`
|
|
112
|
-
M${xScale(d[forestPlot.lower])} ${yScale(i) - Number(ciEndSize)}
|
|
113
|
-
L${xScale(d[forestPlot.lower])} ${yScale(i) + Number(ciEndSize)}
|
|
114
|
-
`}
|
|
115
|
-
/>
|
|
116
|
-
|
|
117
|
-
<path
|
|
118
|
-
stroke={lineColor}
|
|
119
|
-
strokeWidth={1}
|
|
120
|
-
className='upper-ci'
|
|
121
|
-
d={`
|
|
122
|
-
M${xScale(d[forestPlot.upper])} ${yScale(i) - Number(ciEndSize)}
|
|
123
|
-
L${xScale(d[forestPlot.upper])} ${yScale(i) + Number(ciEndSize)}
|
|
124
|
-
`}
|
|
125
|
-
/>
|
|
126
|
-
|
|
127
|
-
{/* main line */}
|
|
128
|
-
<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)} />
|
|
129
|
-
{forestPlot.shape === 'circle' && (
|
|
130
|
-
<Circle className='forest-plot--circle' cx={xScale(Number(d[forestPlot.estimateField]))} cy={yScale(i)} r={forestPlot.radius.scalingColumn !== '' ? scaleRadius(data[i][forestPlot.radius.scalingColumn]) : 4} fill={shapeColor} style={{ opacity: 1, filter: 'unset' }} />
|
|
131
|
-
)}
|
|
132
|
-
{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' }} />}
|
|
133
|
-
{forestPlot.shape === 'text' && (
|
|
134
|
-
<Text className='forest-plot--text' x={xScale(Number(d[forestPlot.estimateField]))} y={yScale(i)} textAnchor='middle' verticalAnchor='middle' fontSize={getFontSize(config.fontSize)} fill={shapeColor}>
|
|
135
|
-
{d[forestPlot.estimateField]}
|
|
136
|
-
</Text>
|
|
137
|
-
)}
|
|
138
|
-
</Group>
|
|
139
|
-
) : (
|
|
140
|
-
<LinePath data={regressionPoints} x={d => d.x} y={d => d.y - getFontSize(config.fontSize) / 2} stroke='black' strokeWidth={2} fill={'black'} curve={curveLinearClosed} />
|
|
141
|
-
)
|
|
142
|
-
})}
|
|
143
|
-
|
|
144
|
-
{/* regression diamond */}
|
|
145
|
-
{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} />}
|
|
146
|
-
{/* regression text */}
|
|
147
|
-
{forestPlot.regression.description && (
|
|
148
|
-
<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 }}>
|
|
149
|
-
{forestPlot.regression.description}
|
|
150
|
-
</Text>
|
|
151
|
-
)}
|
|
152
|
-
|
|
153
|
-
<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} />
|
|
154
|
-
</Group>
|
|
155
|
-
<Line from={topLine[0]} to={topLine[1]} style={{ stroke: 'black', strokeWidth: 2 }} className='forestplot__top-line' />
|
|
156
|
-
<Line from={bottomLine[0]} to={bottomLine[1]} style={{ stroke: 'black', strokeWidth: 2 }} className='forestplot__bottom-line' />
|
|
157
|
-
|
|
158
|
-
{/* column data */}
|
|
159
|
-
{columnsOnChart.map(column => {
|
|
160
|
-
return rawData.map((d, i) => {
|
|
161
|
-
return (
|
|
162
|
-
<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'}>
|
|
163
|
-
{d[column.name]}
|
|
164
|
-
</Text>
|
|
165
|
-
)
|
|
166
|
-
})
|
|
167
|
-
})}
|
|
168
|
-
|
|
169
|
-
{/* X Axis DataKey Cols*/}
|
|
170
|
-
{!forestPlot.hideDateCategoryCol &&
|
|
171
|
-
data.map((d, i) => {
|
|
172
|
-
return (
|
|
173
|
-
<Text className={`${d[config.xAxis.dataKey]}`} x={0} y={yScale(i)} textAnchor={'start'} verticalAnchor='middle' fontSize={getFontSize(config.fontSize)} fill={'black'}>
|
|
174
|
-
{d[config.xAxis.dataKey]}
|
|
175
|
-
</Text>
|
|
176
|
-
)
|
|
177
|
-
})}
|
|
178
|
-
|
|
179
|
-
{/* X Axis Datakey Header */}
|
|
180
|
-
{!forestPlot.hideDateCategoryCol && config.xAxis.dataKey && (
|
|
181
|
-
<Text className={config.xAxis.dataKey} x={0} y={0} textAnchor={'start'} verticalAnchor='start' fontSize={getFontSize(config.fontSize)} fill={'black'}>
|
|
182
|
-
{config.xAxis.dataKey}
|
|
183
|
-
</Text>
|
|
184
|
-
)}
|
|
185
|
-
|
|
186
|
-
{/* column headers */}
|
|
187
|
-
{columnsOnChart.map(column => {
|
|
188
|
-
return (
|
|
189
|
-
<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'}>
|
|
190
|
-
{column.label}
|
|
191
|
-
</Text>
|
|
192
|
-
)
|
|
193
|
-
})}
|
|
194
|
-
|
|
195
|
-
{/* left bottom label */}
|
|
196
|
-
{forestPlot.leftLabel && (
|
|
197
|
-
<Text className='forest-plot__left-label' x={forestPlot.type === 'Linear' ? xScale(0) - 25 : xScale(1) - 25} y={height + 50} textAnchor='end'>
|
|
198
|
-
{forestPlot.leftLabel}
|
|
199
|
-
</Text>
|
|
200
|
-
)}
|
|
201
|
-
|
|
202
|
-
{forestPlot.rightLabel && (
|
|
203
|
-
<Text className='forest-plot__right-label' x={forestPlot.type === 'Linear' ? xScale(0) + 25 : xScale(1) + 25} y={height + 50} textAnchor='start'>
|
|
204
|
-
{forestPlot.rightLabel}
|
|
205
|
-
</Text>
|
|
206
|
-
)}
|
|
207
|
-
</>
|
|
208
|
-
)
|
|
209
|
-
}
|
|
1
|
+
import ForestPlot from './ForestPlot'
|
|
210
2
|
|
|
211
3
|
export default ForestPlot
|