@cdc/chart 4.23.10 → 4.24.1
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 +34606 -32218
- package/examples/feature/bar/additional-column-tooltip.json +446 -0
- package/examples/feature/bar/example-bar-chart.json +1 -46
- package/examples/feature/bar/lollipop.json +156 -0
- package/examples/feature/bar/tall-data.json +98 -0
- package/examples/feature/combo/planet-combo-example-config.json +99 -9
- package/examples/feature/dev-4261.json +399 -0
- package/examples/feature/forest-plot/forest-plot.json +63 -19
- package/examples/feature/forest-plot/{broken.json → linear.json} +77 -23
- package/examples/feature/forest-plot/log.json +26 -0
- package/examples/feature/forest-plot/logarithmic.json +271 -0
- package/examples/feature/line/line-chart-preliminary.json +346 -0
- package/examples/feature/line/line-points.json +340 -0
- package/examples/feature/regions/index.json +462 -0
- package/examples/feature/scatterplot/scatterplot.json +272 -33
- package/examples/gallery/bar-chart-vertical/combo-line-chart.json +181 -48
- package/examples/private/chart-t.json +3740 -0
- package/examples/private/combo.json +369 -0
- package/examples/private/epi-data.csv +13 -0
- package/examples/private/epi-data.json +62 -0
- package/examples/private/epi.json +403 -0
- package/examples/private/occupancy.json +109283 -0
- package/examples/private/prod-line-config.json +401 -0
- package/examples/private/region-data.json +822 -0
- package/examples/private/region-testing.json +312 -0
- package/examples/private/scaling.json +45325 -0
- package/examples/private/testing-data.json +1739 -0
- package/examples/private/testing.json +816 -0
- package/examples/sparkline-multilple.json +846 -0
- package/index.html +12 -8
- package/package.json +3 -3
- package/src/CdcChart.tsx +42 -211
- package/src/ConfigContext.tsx +6 -0
- package/src/_stories/Chart.stories.tsx +188 -0
- package/src/_stories/Chart.tooltip.stories.tsx +305 -0
- package/src/_stories/ChartBrush.stories.tsx +19 -0
- package/src/_stories/ChartEditor.stories.tsx +22 -0
- package/src/_stories/ChartLine.preliminary.tsx +19 -0
- package/src/_stories/ChartSuppress.stories.tsx +19 -0
- package/src/_stories/_mock/brush_mock.json +393 -0
- package/src/_stories/_mock/pie_config.json +191 -0
- package/src/_stories/_mock/pie_data.json +218 -0
- package/src/_stories/_mock/preliminary_mock.json +346 -0
- package/src/_stories/_mock/suppress_mock.json +911 -0
- package/src/components/{AreaChart.Stacked.jsx → AreaChart/components/AreaChart.Stacked.jsx} +6 -7
- package/src/components/{AreaChart.jsx → AreaChart/components/AreaChart.jsx} +7 -36
- package/src/components/AreaChart/index.tsx +4 -0
- package/src/components/{BarChart.Horizontal.jsx → BarChart/components/BarChart.Horizontal.tsx} +111 -34
- package/src/components/{BarChart.StackedHorizontal.jsx → BarChart/components/BarChart.StackedHorizontal.tsx} +55 -20
- package/src/components/BarChart/components/BarChart.StackedVertical.tsx +106 -0
- package/src/components/{BarChart.Vertical.jsx → BarChart/components/BarChart.Vertical.tsx} +162 -34
- 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} +1 -1
- package/src/components/BoxPlot/index.tsx +3 -0
- package/src/components/DeviationBar.jsx +4 -3
- package/src/components/{EditorPanel.jsx → EditorPanel/EditorPanel.tsx} +807 -865
- package/src/components/EditorPanel/components/Panel.DateHighlighting.tsx +109 -0
- package/src/components/{ForestPlotSettings.jsx → EditorPanel/components/Panel.ForestPlotSettings.tsx} +190 -220
- package/src/components/EditorPanel/components/Panel.Regions.tsx +168 -0
- package/src/components/{Series.jsx → EditorPanel/components/Panel.Series.tsx} +23 -4
- package/src/components/EditorPanel/components/PanelProps.ts +3 -0
- package/src/components/EditorPanel/components/Panels.tsx +13 -0
- package/src/components/EditorPanel/components/panels.scss +72 -0
- package/src/components/EditorPanel/editor-panel.scss +751 -0
- package/src/components/EditorPanel/index.tsx +3 -0
- package/src/{hooks → components/EditorPanel}/useEditorPermissions.js +50 -5
- 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 +18 -0
- package/src/components/ForestPlot/index.scss +1 -0
- package/src/components/ForestPlot/index.tsx +3 -0
- package/src/components/Legend/Legend.tsx +347 -0
- package/src/components/Legend/index.tsx +3 -0
- package/src/components/LineChart/LineChartProps.ts +46 -0
- package/src/components/{LineChart.Circle.tsx → LineChart/components/LineChart.Circle.tsx} +36 -30
- package/src/components/LineChart/helpers.ts +45 -0
- package/src/components/LineChart/index.scss +1 -0
- package/src/components/{LineChart.tsx → LineChart/index.tsx} +83 -42
- package/src/components/LinearChart.jsx +125 -82
- package/src/components/PairedBarChart.jsx +2 -2
- package/src/components/{PieChart.jsx → PieChart/PieChart.tsx} +16 -7
- package/src/components/PieChart/index.tsx +3 -0
- package/src/components/Regions/components/Regions.tsx +135 -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/components/ZoomBrush.tsx +168 -0
- package/src/data/initial-state.js +30 -16
- 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 +72 -7
- package/src/hooks/useColorScale.ts +50 -0
- package/src/hooks/{useMinMax.js → useMinMax.ts} +75 -23
- package/src/hooks/{useRightAxis.js → useRightAxis.ts} +10 -2
- package/src/hooks/{useScales.js → useScales.ts} +64 -17
- package/src/hooks/{useTooltip.jsx → useTooltip.tsx} +84 -55
- package/src/scss/main.scss +70 -38
- package/src/types/ChartConfig.ts +178 -0
- package/src/types/ChartContext.ts +54 -0
- package/src/types/ForestPlot.ts +53 -0
- package/examples/feature/scatterplot/scatterplot-continuous.csv +0 -17
- package/src/ConfigContext.jsx +0 -5
- package/src/components/BarChart.StackedVertical.jsx +0 -95
- package/src/components/BarChart.jsx +0 -30
- package/src/components/ForestPlot.jsx +0 -191
- package/src/components/Legend.jsx +0 -277
- 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
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
x,y1,y2,y3,y4
|
|
2
|
-
1000,8,37,72,82
|
|
3
|
-
2000,2,30,75,83
|
|
4
|
-
3000,15,42,51,95
|
|
5
|
-
4,10,38,61,96
|
|
6
|
-
5,1,38,66,86
|
|
7
|
-
60,6,37,70,85
|
|
8
|
-
10,19,47,59,91
|
|
9
|
-
24,18,32,68,89
|
|
10
|
-
3,7,38,74,89
|
|
11
|
-
26,10,39,56,91
|
|
12
|
-
42,16,38,55,76
|
|
13
|
-
32,20,46,52,94
|
|
14
|
-
11,9,41,57,86
|
|
15
|
-
22,10,47,56,80
|
|
16
|
-
73,1,36,71,94
|
|
17
|
-
23,13,30,66,78
|
package/src/ConfigContext.jsx
DELETED
|
@@ -1,95 +0,0 @@
|
|
|
1
|
-
import React, { useContext } from 'react'
|
|
2
|
-
import ConfigContext from '../ConfigContext'
|
|
3
|
-
import { useBarChart } from '../hooks/useBarChart'
|
|
4
|
-
import { BarStack } from '@visx/shape'
|
|
5
|
-
import { Group } from '@visx/group'
|
|
6
|
-
import { Text } from '@visx/text'
|
|
7
|
-
|
|
8
|
-
const BarChartStackedVertical = props => {
|
|
9
|
-
const { xScale, yScale, xMax, yMax } = props
|
|
10
|
-
const { transformedData: data, colorScale, seriesHighlight, config, formatNumber, formatDate, parseDate, setSharedFilter } = useContext(ConfigContext)
|
|
11
|
-
const { isHorizontal, barBorderWidth, hasMultipleSeries, applyRadius } = useBarChart()
|
|
12
|
-
const { orientation } = config
|
|
13
|
-
|
|
14
|
-
return (
|
|
15
|
-
config.visualizationSubType === 'stacked' &&
|
|
16
|
-
!isHorizontal && (
|
|
17
|
-
<BarStack data={data} keys={config.runtime.barSeriesKeys || config.runtime.seriesKeys} x={d => d[config.runtime.xAxis.dataKey]} xScale={xScale} yScale={yScale} color={colorScale}>
|
|
18
|
-
{barStacks =>
|
|
19
|
-
barStacks.reverse().map(barStack =>
|
|
20
|
-
barStack.bars.map(bar => {
|
|
21
|
-
let transparentBar = config.legend.behavior === 'highlight' && seriesHighlight.length > 0 && seriesHighlight.indexOf(bar.key) === -1
|
|
22
|
-
let displayBar = config.legend.behavior === 'highlight' || seriesHighlight.length === 0 || seriesHighlight.indexOf(bar.key) !== -1
|
|
23
|
-
let barThickness = xMax / barStack.bars.length
|
|
24
|
-
let barThicknessAdjusted = barThickness * (config.barThickness || 0.8)
|
|
25
|
-
let offset = (barThickness * (1 - (config.barThickness || 0.8))) / 2
|
|
26
|
-
// tooltips
|
|
27
|
-
const xAxisValue = config.runtime.xAxis.type === 'date' ? formatDate(parseDate(data[bar.index][config.runtime.xAxis.dataKey])) : data[bar.index][config.runtime.xAxis.dataKey]
|
|
28
|
-
const yAxisValue = formatNumber(bar.bar ? bar.bar.data[bar.key] : 0, 'left')
|
|
29
|
-
|
|
30
|
-
if(!yAxisValue) return <></>
|
|
31
|
-
|
|
32
|
-
const style = applyRadius(barStack.index)
|
|
33
|
-
let yAxisTooltip = config.runtime.yAxis.label ? `${config.runtime.yAxis.label}: ${yAxisValue}` : yAxisValue
|
|
34
|
-
const xAxisTooltip = config.runtime.xAxis.label ? `${config.runtime.xAxis.label}: ${xAxisValue}` : xAxisValue
|
|
35
|
-
if (!hasMultipleSeries) {
|
|
36
|
-
yAxisTooltip = config.isLegendValue ? `${bar.key}: ${yAxisValue}` : config.runtime.yAxis.label ? `${config.runtime.yAxis.label}: ${yAxisValue}` : yAxisValue
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
const {
|
|
40
|
-
legend: { showLegendValuesTooltip },
|
|
41
|
-
runtime: { seriesLabels }
|
|
42
|
-
} = config
|
|
43
|
-
|
|
44
|
-
const barStackTooltip = `<div>
|
|
45
|
-
<p class="tooltip-heading"><strong>${xAxisTooltip}</strong></p>
|
|
46
|
-
${showLegendValuesTooltip && seriesLabels && hasMultipleSeries ? `${seriesLabels[bar.key] || ''}<br/>` : ''}
|
|
47
|
-
${yAxisTooltip}<br />
|
|
48
|
-
</div>`
|
|
49
|
-
|
|
50
|
-
return (
|
|
51
|
-
<Group key={`${barStack.index}--${bar.index}--${orientation}`}>
|
|
52
|
-
<style>
|
|
53
|
-
{`
|
|
54
|
-
#barStack${barStack.index}-${bar.index} rect,
|
|
55
|
-
#barStack${barStack.index}-${bar.index} foreignObject{
|
|
56
|
-
animation-delay: ${barStack.index * 0.5}s;
|
|
57
|
-
transform-origin: ${barThicknessAdjusted / 2}px ${bar.y + bar.height}px
|
|
58
|
-
}
|
|
59
|
-
`}
|
|
60
|
-
</style>
|
|
61
|
-
<Group key={`bar-stack-${barStack.index}-${bar.index}`} id={`barStack${barStack.index}-${bar.index}`} className='stack vertical'>
|
|
62
|
-
<Text display={config.labels && displayBar ? 'block' : 'none'} opacity={transparentBar ? 0.5 : 1} x={barThickness * bar.index + offset} y={bar.y - 5} fill={'#000'} textAnchor='middle'>
|
|
63
|
-
{yAxisValue}
|
|
64
|
-
</Text>
|
|
65
|
-
<foreignObject
|
|
66
|
-
key={`bar-stack-${barStack.index}-${bar.index}`}
|
|
67
|
-
x={barThickness * bar.index + offset}
|
|
68
|
-
y={bar.y}
|
|
69
|
-
width={barThicknessAdjusted}
|
|
70
|
-
height={bar.height}
|
|
71
|
-
style={{ background: bar.color, border: `${config.barHasBorder === 'true' ? barBorderWidth : 0}px solid #333`, ...style }}
|
|
72
|
-
opacity={transparentBar ? 0.5 : 1}
|
|
73
|
-
display={displayBar ? 'block' : 'none'}
|
|
74
|
-
data-tooltip-html={barStackTooltip}
|
|
75
|
-
data-tooltip-id={`cdc-open-viz-tooltip-${config.runtime.uniqueId}`}
|
|
76
|
-
onClick={e => {
|
|
77
|
-
e.preventDefault()
|
|
78
|
-
if (setSharedFilter) {
|
|
79
|
-
bar[config.xAxis.dataKey] = xAxisValue
|
|
80
|
-
setSharedFilter(config.uid, bar)
|
|
81
|
-
}
|
|
82
|
-
}}
|
|
83
|
-
></foreignObject>
|
|
84
|
-
</Group>
|
|
85
|
-
</Group>
|
|
86
|
-
)
|
|
87
|
-
})
|
|
88
|
-
)
|
|
89
|
-
}
|
|
90
|
-
</BarStack>
|
|
91
|
-
)
|
|
92
|
-
)
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
export default BarChartStackedVertical
|
|
@@ -1,30 +0,0 @@
|
|
|
1
|
-
import React, { useContext } from 'react'
|
|
2
|
-
|
|
3
|
-
// visx
|
|
4
|
-
import { Group } from '@visx/group'
|
|
5
|
-
import { Bar } from '@visx/shape'
|
|
6
|
-
|
|
7
|
-
// cdc
|
|
8
|
-
import BarChartType from './BarChartType'
|
|
9
|
-
import ErrorBoundary from '@cdc/core/components/ErrorBoundary'
|
|
10
|
-
import ConfigContext from '../ConfigContext'
|
|
11
|
-
|
|
12
|
-
const BarChart = ({ xScale, yScale, seriesScale, xMax, yMax, handleTooltipMouseOver, handleTooltipMouseOff, handleTooltipClick }) => {
|
|
13
|
-
const { transformedData: data, config } = useContext(ConfigContext)
|
|
14
|
-
|
|
15
|
-
return (
|
|
16
|
-
<ErrorBoundary component='BarChart'>
|
|
17
|
-
<Group left={parseFloat(config.runtime.yAxis.size)}>
|
|
18
|
-
<BarChartType.StackedVertical xScale={xScale} yScale={yScale} xMax={xMax} yMax={yMax} />
|
|
19
|
-
<BarChartType.StackedHorizontal xScale={xScale} yScale={yScale} xMax={xMax} yMax={yMax} />
|
|
20
|
-
<BarChartType.Vertical xScale={xScale} yScale={yScale} xMax={xMax} yMax={yMax} seriesScale={seriesScale} />
|
|
21
|
-
<BarChartType.Horizontal xScale={xScale} yScale={yScale} xMax={xMax} yMax={yMax} seriesScale={seriesScale} />
|
|
22
|
-
|
|
23
|
-
{/* tooltips */}
|
|
24
|
-
<Bar key={'bars'} width={Number(xMax)} height={Number(yMax)} fill={false ? 'red' : 'transparent'} fillOpacity={0.05} onMouseMove={e => handleTooltipMouseOver(e, data)} onMouseOut={handleTooltipMouseOff} onClick={e => handleTooltipClick(e, data)} />
|
|
25
|
-
</Group>
|
|
26
|
-
</ErrorBoundary>
|
|
27
|
-
)
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
export default BarChart
|
|
@@ -1,191 +0,0 @@
|
|
|
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
|
|
@@ -1,277 +0,0 @@
|
|
|
1
|
-
import React, { useContext, useEffect } from 'react'
|
|
2
|
-
import ConfigContext from '../ConfigContext'
|
|
3
|
-
import parse from 'html-react-parser'
|
|
4
|
-
import { LegendOrdinal, LegendItem, LegendLabel } from '@visx/legend'
|
|
5
|
-
import LegendCircle from '@cdc/core/components/LegendCircle'
|
|
6
|
-
|
|
7
|
-
import useLegendClasses from './../hooks/useLegendClasses'
|
|
8
|
-
import { useHighlightedBars } from '../hooks/useHighlightedBars'
|
|
9
|
-
import { Line } from '@visx/shape'
|
|
10
|
-
import { sequentialPalettes } from '@cdc/core/data/colorPalettes'
|
|
11
|
-
|
|
12
|
-
// * FILE REVIEW *
|
|
13
|
-
// TODO: fix eslint-disable jsxa11y issues
|
|
14
|
-
|
|
15
|
-
// * ADDITIONAL NOTES *
|
|
16
|
-
// > recently removed dynamic legend items as they weren't used
|
|
17
|
-
// > recently removed boxplots, they don't provide any legend settings
|
|
18
|
-
|
|
19
|
-
/* eslint-disable jsx-a11y/no-noninteractive-tabindex, jsx-a11y/no-static-element-interactions */
|
|
20
|
-
const Legend = () => {
|
|
21
|
-
// prettier-ignore
|
|
22
|
-
const {
|
|
23
|
-
config,
|
|
24
|
-
legend,
|
|
25
|
-
colorScale,
|
|
26
|
-
seriesHighlight,
|
|
27
|
-
highlight,
|
|
28
|
-
twoColorPalette,
|
|
29
|
-
tableData,
|
|
30
|
-
highlightReset,
|
|
31
|
-
transformedData: data,
|
|
32
|
-
colorPalettes,
|
|
33
|
-
currentViewport,
|
|
34
|
-
handleLineType
|
|
35
|
-
} = useContext(ConfigContext)
|
|
36
|
-
|
|
37
|
-
const { innerClasses, containerClasses } = useLegendClasses(config)
|
|
38
|
-
const { visualizationType, visualizationSubType, series, runtime, orientation } = config
|
|
39
|
-
// create fn to reverse labels while legend is Bottom. Legend-right , legend-left works by default.
|
|
40
|
-
const reverseLabels = labels => (config.legend.reverseLabelOrder && config.legend.position === 'bottom' ? labels.reverse() : labels)
|
|
41
|
-
|
|
42
|
-
const createLegendLabels = defaultLabels => {
|
|
43
|
-
const colorCode = config.legend?.colorCode
|
|
44
|
-
if (visualizationType === 'Deviation Bar') {
|
|
45
|
-
const [belowColor, aboveColor] = twoColorPalette[config.twoColor.palette]
|
|
46
|
-
const labelBelow = {
|
|
47
|
-
datum: 'X',
|
|
48
|
-
index: 0,
|
|
49
|
-
text: `Below ${config.xAxis.targetLabel}`,
|
|
50
|
-
value: belowColor
|
|
51
|
-
}
|
|
52
|
-
const labelAbove = {
|
|
53
|
-
datum: 'X',
|
|
54
|
-
index: 1,
|
|
55
|
-
text: `Above ${config.xAxis.targetLabel}`,
|
|
56
|
-
value: aboveColor
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
return reverseLabels([labelBelow, labelAbove])
|
|
60
|
-
}
|
|
61
|
-
if (visualizationType === 'Bar' && visualizationSubType === 'regular' && colorCode && series?.length === 1) {
|
|
62
|
-
let palette = colorPalettes[config.palette]
|
|
63
|
-
|
|
64
|
-
while (tableData.length > palette.length) {
|
|
65
|
-
palette = palette.concat(palette)
|
|
66
|
-
}
|
|
67
|
-
palette = palette.slice(0, data.length)
|
|
68
|
-
//store unique values to Set by colorCode
|
|
69
|
-
const set = new Set()
|
|
70
|
-
|
|
71
|
-
tableData.forEach(d => set.add(d[colorCode]))
|
|
72
|
-
|
|
73
|
-
// create labels with unique values
|
|
74
|
-
const uniqueLabels = Array.from(set).map((val, i) => {
|
|
75
|
-
const newLabel = {
|
|
76
|
-
datum: val,
|
|
77
|
-
index: i,
|
|
78
|
-
text: val,
|
|
79
|
-
value: palette[i]
|
|
80
|
-
}
|
|
81
|
-
return newLabel
|
|
82
|
-
})
|
|
83
|
-
|
|
84
|
-
return reverseLabels(uniqueLabels)
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
// get forecasting items inside of combo
|
|
88
|
-
if (runtime?.forecastingSeriesKeys?.length > 0) {
|
|
89
|
-
let seriesLabels = []
|
|
90
|
-
|
|
91
|
-
//store unique values to Set by colorCode
|
|
92
|
-
|
|
93
|
-
// loop through each stage/group/area on the chart and create a label
|
|
94
|
-
config.runtime?.forecastingSeriesKeys?.map((outerGroup, index) => {
|
|
95
|
-
return outerGroup?.stages?.map((stage, index) => {
|
|
96
|
-
let colorValue = sequentialPalettes[stage.color]?.[2] ? sequentialPalettes[stage.color]?.[2] : colorPalettes[stage.color]?.[2] ? colorPalettes[stage.color]?.[2] : '#ccc'
|
|
97
|
-
|
|
98
|
-
const newLabel = {
|
|
99
|
-
datum: stage.key,
|
|
100
|
-
index: index,
|
|
101
|
-
text: stage.key,
|
|
102
|
-
value: colorValue
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
seriesLabels.push(newLabel)
|
|
106
|
-
})
|
|
107
|
-
})
|
|
108
|
-
|
|
109
|
-
// loop through bars for now to meet requirements.
|
|
110
|
-
config.runtime.barSeriesKeys &&
|
|
111
|
-
config.runtime.barSeriesKeys.forEach((bar, index) => {
|
|
112
|
-
let colorValue = colorPalettes[config.palette][index] ? colorPalettes[config.palette][index] : '#ccc'
|
|
113
|
-
|
|
114
|
-
const newLabel = {
|
|
115
|
-
datum: bar,
|
|
116
|
-
index: index,
|
|
117
|
-
text: bar,
|
|
118
|
-
value: colorValue
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
seriesLabels.push(newLabel)
|
|
122
|
-
})
|
|
123
|
-
|
|
124
|
-
return reverseLabels(seriesLabels)
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
// DEV-4161: replaceable series name in the legend
|
|
128
|
-
const hasNewSeriesName = config.series.map(s => s.name).filter(item => item).length > 0
|
|
129
|
-
if (hasNewSeriesName) {
|
|
130
|
-
let palette = colorPalettes[config.palette]
|
|
131
|
-
|
|
132
|
-
while (tableData.length > palette.length) {
|
|
133
|
-
palette = palette.concat(palette)
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
palette = palette.slice(0, data.length)
|
|
137
|
-
//store unique values to Set by colorCode
|
|
138
|
-
const set = new Set()
|
|
139
|
-
|
|
140
|
-
config.series.forEach(d => {
|
|
141
|
-
set.add(d['name'] ? d['name'] : d['dataKey'])
|
|
142
|
-
})
|
|
143
|
-
|
|
144
|
-
// create labels with unique values
|
|
145
|
-
const uniqueLabels = Array.from(set).map((val, i) => {
|
|
146
|
-
const newLabel = {
|
|
147
|
-
datum: val,
|
|
148
|
-
index: i,
|
|
149
|
-
text: val,
|
|
150
|
-
value: palette[i]
|
|
151
|
-
}
|
|
152
|
-
return newLabel
|
|
153
|
-
})
|
|
154
|
-
|
|
155
|
-
return reverseLabels(uniqueLabels)
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
return reverseLabels(defaultLabels)
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
const isBottomOrSmallViewport = legend.position === 'bottom' || ['sm', 'xs', 'xxs'].includes(currentViewport)
|
|
162
|
-
|
|
163
|
-
const legendClasses = {
|
|
164
|
-
marginBottom: isBottomOrSmallViewport ? '15px' : '0px',
|
|
165
|
-
marginTop: isBottomOrSmallViewport && orientation === 'horizontal' ? `${config.yAxis.label && config.isResponsiveTicks ? config.dynamicMarginTop : config.runtime.xAxis.size}px` : `${config.dynamicMarginTop + 15}px`
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
const { HighLightedBarUtils } = useHighlightedBars(config)
|
|
169
|
-
|
|
170
|
-
let highLightedLegendItems = HighLightedBarUtils.findDuplicates(config.highlightedBarValues)
|
|
171
|
-
if (!legend) return null
|
|
172
|
-
|
|
173
|
-
return (
|
|
174
|
-
config.visualizationType !== 'Box Plot' && (
|
|
175
|
-
<aside style={legendClasses} id='legend' className={containerClasses.join(' ')} role='region' aria-label='legend' tabIndex={0}>
|
|
176
|
-
{legend.label && <h2>{parse(legend.label)}</h2>}
|
|
177
|
-
{legend.description && <p>{parse(legend.description)}</p>}
|
|
178
|
-
<LegendOrdinal scale={colorScale} itemDirection='row' labelMargin='0 20px 0 0' shapeMargin='0 10px 0'>
|
|
179
|
-
{labels => {
|
|
180
|
-
return (
|
|
181
|
-
<div className={innerClasses.join(' ')}>
|
|
182
|
-
{createLegendLabels(labels).map((label, i) => {
|
|
183
|
-
let className = ['legend-item', `legend-text--${label.text.replace(' ', '').toLowerCase()}`]
|
|
184
|
-
let itemName = label.datum
|
|
185
|
-
|
|
186
|
-
// Filter excluded data keys from legend
|
|
187
|
-
if (config.exclusions.active && config.exclusions.keys?.includes(itemName)) {
|
|
188
|
-
return null
|
|
189
|
-
}
|
|
190
|
-
|
|
191
|
-
if (runtime.seriesLabels) {
|
|
192
|
-
let index = config.runtime.seriesLabelsAll.indexOf(itemName)
|
|
193
|
-
itemName = config.runtime.seriesKeys[index]
|
|
194
|
-
|
|
195
|
-
if (runtime?.forecastingSeriesKeys?.length > 0) {
|
|
196
|
-
itemName = label.text
|
|
197
|
-
}
|
|
198
|
-
}
|
|
199
|
-
|
|
200
|
-
if (seriesHighlight.length > 0 && false === seriesHighlight.includes(itemName)) {
|
|
201
|
-
className.push('inactive')
|
|
202
|
-
}
|
|
203
|
-
|
|
204
|
-
return (
|
|
205
|
-
<LegendItem
|
|
206
|
-
className={className.join(' ')}
|
|
207
|
-
tabIndex={0}
|
|
208
|
-
key={`legend-quantile-${i}`}
|
|
209
|
-
onKeyPress={e => {
|
|
210
|
-
if (e.key === 'Enter') {
|
|
211
|
-
highlight(label)
|
|
212
|
-
}
|
|
213
|
-
}}
|
|
214
|
-
onClick={() => {
|
|
215
|
-
highlight(label)
|
|
216
|
-
}}
|
|
217
|
-
>
|
|
218
|
-
{config.visualizationType === 'Line' && config.legend.lineMode ? (
|
|
219
|
-
<svg width={40} height={20}>
|
|
220
|
-
<Line from={{ x: 10, y: 10 }} to={{ x: 40, y: 10 }} stroke={label.value} strokeWidth={2} strokeDasharray={handleLineType(config.series[i]?.type ? config.series[i]?.type : '')} />
|
|
221
|
-
</svg>
|
|
222
|
-
) : (
|
|
223
|
-
<LegendCircle fill={label.value} />
|
|
224
|
-
)}
|
|
225
|
-
|
|
226
|
-
<LegendLabel align='left' margin='0 0 0 4px'>
|
|
227
|
-
{label.text}
|
|
228
|
-
</LegendLabel>
|
|
229
|
-
</LegendItem>
|
|
230
|
-
)
|
|
231
|
-
})}
|
|
232
|
-
|
|
233
|
-
{highLightedLegendItems.map((bar, i) => {
|
|
234
|
-
// if duplicates only return first item
|
|
235
|
-
let className = 'legend-item'
|
|
236
|
-
let itemName = bar.legendLabel
|
|
237
|
-
|
|
238
|
-
if (!itemName) return false
|
|
239
|
-
if (seriesHighlight.length > 0 && false === seriesHighlight.includes(itemName)) {
|
|
240
|
-
className += ' inactive'
|
|
241
|
-
}
|
|
242
|
-
return (
|
|
243
|
-
<LegendItem
|
|
244
|
-
className={className}
|
|
245
|
-
tabIndex={0}
|
|
246
|
-
key={`legend-quantile-${i}`}
|
|
247
|
-
onKeyPress={e => {
|
|
248
|
-
if (e.key === 'Enter') {
|
|
249
|
-
highlight(bar.legendLabel)
|
|
250
|
-
}
|
|
251
|
-
}}
|
|
252
|
-
onClick={() => {
|
|
253
|
-
highlight(bar.legendLabel)
|
|
254
|
-
}}
|
|
255
|
-
>
|
|
256
|
-
<LegendCircle fill='transparent' borderColor={bar.color ? bar.color : `rgba(255, 102, 1)`} />{' '}
|
|
257
|
-
<LegendLabel align='left' margin='0 0 0 4px'>
|
|
258
|
-
{bar.legendLabel ? bar.legendLabel : bar.value}
|
|
259
|
-
</LegendLabel>
|
|
260
|
-
</LegendItem>
|
|
261
|
-
)
|
|
262
|
-
})}
|
|
263
|
-
{seriesHighlight.length > 0 && (
|
|
264
|
-
<button className={`legend-reset ${config.theme}`} onClick={labels => highlightReset(labels)} tabIndex={0}>
|
|
265
|
-
Reset
|
|
266
|
-
</button>
|
|
267
|
-
)}
|
|
268
|
-
</div>
|
|
269
|
-
)
|
|
270
|
-
}}
|
|
271
|
-
</LegendOrdinal>
|
|
272
|
-
</aside>
|
|
273
|
-
)
|
|
274
|
-
)
|
|
275
|
-
}
|
|
276
|
-
|
|
277
|
-
export default Legend
|
|
File without changes
|