@cdc/chart 4.23.8 → 4.23.10
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 +44114 -44410
- package/examples/feature/__data__/area-chart-date-apple.json +1 -5073
- package/examples/feature/area/area-chart-date-apple.json +73 -10316
- package/examples/feature/area/area-chart-date-city-temperature.json +204 -80
- package/examples/{private/confidence_interval_test.json → feature/area/area-chart-stacked.json} +65 -74
- package/examples/feature/filters/bar-filter.json +5027 -0
- package/examples/feature/legend-highlights/highlights.json +567 -0
- package/index.html +28 -7
- package/package.json +3 -2
- package/src/{CdcChart.jsx → CdcChart.tsx} +77 -71
- package/src/components/AreaChart.Stacked.jsx +73 -0
- package/src/components/AreaChart.jsx +24 -26
- package/src/components/BarChart.StackedVertical.jsx +2 -0
- package/src/components/DeviationBar.jsx +67 -13
- package/src/components/EditorPanel.jsx +493 -454
- package/src/components/Forecasting.jsx +5 -5
- package/src/components/Legend.jsx +17 -8
- package/src/components/LineChart.Circle.tsx +108 -0
- package/src/components/{LineChart.jsx → LineChart.tsx} +10 -42
- package/src/components/LinearChart.jsx +460 -443
- package/src/components/PieChart.jsx +54 -25
- package/src/components/Series.jsx +63 -17
- package/src/components/SparkLine.jsx +7 -19
- package/src/data/initial-state.js +10 -1
- package/src/hooks/useEditorPermissions.js +87 -24
- package/src/hooks/useLegendClasses.js +14 -11
- package/src/hooks/useReduceData.js +6 -1
- package/src/hooks/useScales.js +2 -2
- package/src/hooks/useTooltip.jsx +21 -8
- package/src/scss/legend.scss +206 -0
- package/src/scss/main.scss +25 -24
- package/examples/private/tooltip-issue.json +0 -45275
- package/src/components/DataTable.jsx +0 -374
- /package/src/{components → hooks}/useIntersectionObserver.jsx +0 -0
|
@@ -3,7 +3,7 @@ import React, { useContext } from 'react'
|
|
|
3
3
|
// cdc
|
|
4
4
|
import ConfigContext from '../ConfigContext'
|
|
5
5
|
import ErrorBoundary from '@cdc/core/components/ErrorBoundary'
|
|
6
|
-
import { colorPalettesChart } from '@cdc/core/data/colorPalettes'
|
|
6
|
+
import { colorPalettesChart, sequentialPalettes } from '@cdc/core/data/colorPalettes'
|
|
7
7
|
|
|
8
8
|
// visx & d3
|
|
9
9
|
import { curveMonotoneX } from '@visx/curve'
|
|
@@ -30,15 +30,15 @@ const Forecasting = ({ xScale, yScale, height, width, handleTooltipMouseOver, ha
|
|
|
30
30
|
return (
|
|
31
31
|
<Group className={`forecasting-areas-combo-${index}`} key={`forecasting-areas--stage-${stage.key.replaceAll(' ', '-')}-${index}`}>
|
|
32
32
|
{group.confidenceIntervals?.map((ciGroup, ciGroupIndex) => {
|
|
33
|
-
const palette = colorPalettesChart[stage.color]
|
|
33
|
+
const palette = sequentialPalettes[stage.color] || colorPalettesChart[stage.color] || false
|
|
34
34
|
|
|
35
35
|
const getFill = () => {
|
|
36
|
-
if (displayArea) return palette[
|
|
36
|
+
if (displayArea) return palette[2] ? palette[2] : 'transparent'
|
|
37
37
|
return 'transparent'
|
|
38
38
|
}
|
|
39
39
|
|
|
40
40
|
const getStroke = () => {
|
|
41
|
-
if (displayArea) return palette[
|
|
41
|
+
if (displayArea) return palette[1] ? palette[1] : 'transparent'
|
|
42
42
|
return 'transparent'
|
|
43
43
|
}
|
|
44
44
|
|
|
@@ -50,7 +50,7 @@ const Forecasting = ({ xScale, yScale, height, width, handleTooltipMouseOver, ha
|
|
|
50
50
|
curve={curveMonotoneX}
|
|
51
51
|
data={groupData}
|
|
52
52
|
fill={getFill()}
|
|
53
|
-
opacity={transparentArea ? 0.1 :
|
|
53
|
+
opacity={transparentArea ? 0.1 : .5}
|
|
54
54
|
x={d => xScale(Date.parse(d[xAxis.dataKey]))}
|
|
55
55
|
y0={d => yScale(d[ciGroup.low])}
|
|
56
56
|
y1={d => yScale(d[ciGroup.high])}
|
|
@@ -6,6 +6,8 @@ import LegendCircle from '@cdc/core/components/LegendCircle'
|
|
|
6
6
|
|
|
7
7
|
import useLegendClasses from './../hooks/useLegendClasses'
|
|
8
8
|
import { useHighlightedBars } from '../hooks/useHighlightedBars'
|
|
9
|
+
import { Line } from '@visx/shape'
|
|
10
|
+
import { sequentialPalettes } from '@cdc/core/data/colorPalettes'
|
|
9
11
|
|
|
10
12
|
// * FILE REVIEW *
|
|
11
13
|
// TODO: fix eslint-disable jsxa11y issues
|
|
@@ -28,7 +30,8 @@ const Legend = () => {
|
|
|
28
30
|
highlightReset,
|
|
29
31
|
transformedData: data,
|
|
30
32
|
colorPalettes,
|
|
31
|
-
currentViewport
|
|
33
|
+
currentViewport,
|
|
34
|
+
handleLineType
|
|
32
35
|
} = useContext(ConfigContext)
|
|
33
36
|
|
|
34
37
|
const { innerClasses, containerClasses } = useLegendClasses(config)
|
|
@@ -90,7 +93,7 @@ const Legend = () => {
|
|
|
90
93
|
// loop through each stage/group/area on the chart and create a label
|
|
91
94
|
config.runtime?.forecastingSeriesKeys?.map((outerGroup, index) => {
|
|
92
95
|
return outerGroup?.stages?.map((stage, index) => {
|
|
93
|
-
let colorValue = colorPalettes[stage.color]?.[2] ? colorPalettes[stage.color]?.[2] : '#ccc'
|
|
96
|
+
let colorValue = sequentialPalettes[stage.color]?.[2] ? sequentialPalettes[stage.color]?.[2] : colorPalettes[stage.color]?.[2] ? colorPalettes[stage.color]?.[2] : '#ccc'
|
|
94
97
|
|
|
95
98
|
const newLabel = {
|
|
96
99
|
datum: stage.key,
|
|
@@ -159,13 +162,12 @@ const Legend = () => {
|
|
|
159
162
|
|
|
160
163
|
const legendClasses = {
|
|
161
164
|
marginBottom: isBottomOrSmallViewport ? '15px' : '0px',
|
|
162
|
-
marginTop: isBottomOrSmallViewport && orientation === 'horizontal' ? `${config.yAxis.label && config.isResponsiveTicks ? config.dynamicMarginTop : config.runtime.xAxis.size}px` : `
|
|
165
|
+
marginTop: isBottomOrSmallViewport && orientation === 'horizontal' ? `${config.yAxis.label && config.isResponsiveTicks ? config.dynamicMarginTop : config.runtime.xAxis.size}px` : `${config.dynamicMarginTop + 15}px`
|
|
163
166
|
}
|
|
164
167
|
|
|
165
168
|
const { HighLightedBarUtils } = useHighlightedBars(config)
|
|
166
169
|
|
|
167
170
|
let highLightedLegendItems = HighLightedBarUtils.findDuplicates(config.highlightedBarValues)
|
|
168
|
-
|
|
169
171
|
if (!legend) return null
|
|
170
172
|
|
|
171
173
|
return (
|
|
@@ -178,7 +180,7 @@ const Legend = () => {
|
|
|
178
180
|
return (
|
|
179
181
|
<div className={innerClasses.join(' ')}>
|
|
180
182
|
{createLegendLabels(labels).map((label, i) => {
|
|
181
|
-
let className = 'legend-item'
|
|
183
|
+
let className = ['legend-item', `legend-text--${label.text.replace(' ', '').toLowerCase()}`]
|
|
182
184
|
let itemName = label.datum
|
|
183
185
|
|
|
184
186
|
// Filter excluded data keys from legend
|
|
@@ -196,12 +198,12 @@ const Legend = () => {
|
|
|
196
198
|
}
|
|
197
199
|
|
|
198
200
|
if (seriesHighlight.length > 0 && false === seriesHighlight.includes(itemName)) {
|
|
199
|
-
className
|
|
201
|
+
className.push('inactive')
|
|
200
202
|
}
|
|
201
203
|
|
|
202
204
|
return (
|
|
203
205
|
<LegendItem
|
|
204
|
-
className={className}
|
|
206
|
+
className={className.join(' ')}
|
|
205
207
|
tabIndex={0}
|
|
206
208
|
key={`legend-quantile-${i}`}
|
|
207
209
|
onKeyPress={e => {
|
|
@@ -213,7 +215,14 @@ const Legend = () => {
|
|
|
213
215
|
highlight(label)
|
|
214
216
|
}}
|
|
215
217
|
>
|
|
216
|
-
|
|
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
|
+
|
|
217
226
|
<LegendLabel align='left' margin='0 0 0 4px'>
|
|
218
227
|
{label.text}
|
|
219
228
|
</LegendLabel>
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
import React from 'react'
|
|
2
|
+
|
|
3
|
+
// todo: change this config obj to ChartConfig once its created
|
|
4
|
+
type LineChartCircleProps = {
|
|
5
|
+
config: {
|
|
6
|
+
xAxis: string
|
|
7
|
+
data: Object[]
|
|
8
|
+
lineDatapointStyle: string
|
|
9
|
+
runtime: Object
|
|
10
|
+
}
|
|
11
|
+
d?: Object
|
|
12
|
+
displayArea: boolean
|
|
13
|
+
seriesKey: string
|
|
14
|
+
tooltipData: {
|
|
15
|
+
data: []
|
|
16
|
+
tooltipDataX: number
|
|
17
|
+
tooltipDataY: number
|
|
18
|
+
}
|
|
19
|
+
xScale: any
|
|
20
|
+
yScale: any
|
|
21
|
+
yScaleRight: any
|
|
22
|
+
colorScale: any
|
|
23
|
+
parseDate: any
|
|
24
|
+
seriesAxis: string
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const LineChartCircle = (props: LineChartCircleProps) => {
|
|
28
|
+
const { config, d, displayArea, seriesKey, tooltipData, xScale, yScale, colorScale, parseDate, yScaleRight } = props
|
|
29
|
+
const { lineDatapointStyle } = config
|
|
30
|
+
const filtered = config?.series.filter(s => s.dataKey === seriesKey)?.[0]
|
|
31
|
+
|
|
32
|
+
if (lineDatapointStyle === 'hidden') return null
|
|
33
|
+
|
|
34
|
+
const getColor = (displayArea, colorScale, config, seriesIndex, hoveredKey, seriesKey) => {
|
|
35
|
+
const customColors = config.customColors || []
|
|
36
|
+
const seriesLabels = config.runtime.seriesLabels || []
|
|
37
|
+
let color
|
|
38
|
+
|
|
39
|
+
const getIndex = seriesKey => config.runtime.seriesLabelsAll.indexOf(seriesKey)
|
|
40
|
+
|
|
41
|
+
if (displayArea) {
|
|
42
|
+
if (colorScale) {
|
|
43
|
+
if (getIndex(hoveredKey) === false) return
|
|
44
|
+
color = customColors.length > 0 ? customColors[getIndex(hoveredKey)] : colorScale(seriesLabels[hoveredKey] || seriesKey)
|
|
45
|
+
} else if (customColors) {
|
|
46
|
+
color = customColors.length > 0 ? customColors[getIndex(hoveredKey)] : 'transparent'
|
|
47
|
+
}
|
|
48
|
+
} else {
|
|
49
|
+
color = 'transparent'
|
|
50
|
+
}
|
|
51
|
+
console.log('color', color)
|
|
52
|
+
return color
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
if (lineDatapointStyle === 'always show') {
|
|
56
|
+
return (
|
|
57
|
+
<circle
|
|
58
|
+
cx={config.xAxis.type === 'categorical' ? xScale(d[config.xAxis.dataKey]) : xScale(parseDate(d[config.xAxis.dataKey]))}
|
|
59
|
+
cy={filtered.axis === 'Right' ? yScaleRight(d[filtered.dataKey]) : yScale(d[filtered.dataKey])}
|
|
60
|
+
r={4.5}
|
|
61
|
+
opacity={d[seriesKey] ? 1 : 0}
|
|
62
|
+
fillOpacity={1}
|
|
63
|
+
fill={displayArea ? (colorScale ? colorScale(config.runtime.seriesLabels ? config.runtime.seriesLabels[seriesKey] : seriesKey) : '#000') : 'transparent'}
|
|
64
|
+
style={{ filter: 'unset', opacity: 1 }}
|
|
65
|
+
/>
|
|
66
|
+
)
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
if (lineDatapointStyle === 'hover') {
|
|
70
|
+
if (!tooltipData) return
|
|
71
|
+
if (!tooltipData.data) return
|
|
72
|
+
let hoveredXValue = tooltipData?.data?.[0]?.[1]
|
|
73
|
+
if (!hoveredXValue) return
|
|
74
|
+
let hoveredSeriesValue
|
|
75
|
+
let hoveredSeriesIndex
|
|
76
|
+
let hoveredSeriesData = tooltipData.data.filter(d => d[0] === seriesKey)
|
|
77
|
+
let hoveredSeriesKey = hoveredSeriesData?.[0]?.[0]
|
|
78
|
+
let hoveredSeriesAxis = hoveredSeriesData?.[0]?.[2]
|
|
79
|
+
hoveredSeriesIndex = tooltipData.data.indexOf(hoveredSeriesKey)
|
|
80
|
+
hoveredSeriesValue = hoveredSeriesData?.[0]?.[1]
|
|
81
|
+
|
|
82
|
+
console.log('hoveredSeriesKey', hoveredSeriesKey)
|
|
83
|
+
console.log('hoveredSeriesAxis', hoveredSeriesAxis)
|
|
84
|
+
console.log('hoveredSeriesValue', hoveredSeriesValue)
|
|
85
|
+
|
|
86
|
+
console.log('hoveredSeriesData', hoveredSeriesData)
|
|
87
|
+
return tooltipData.data.map((tooltipItem, index) => {
|
|
88
|
+
let seriesIndex = config.runtime.seriesLabelsAll.indexOf(hoveredXValue)
|
|
89
|
+
|
|
90
|
+
if (isNaN(hoveredSeriesValue)) return <></>
|
|
91
|
+
return (
|
|
92
|
+
<circle
|
|
93
|
+
cx={config.xAxis.type === 'categorical' ? xScale(hoveredXValue) : xScale(parseDate(hoveredXValue))}
|
|
94
|
+
cy={hoveredSeriesAxis === 'right' ? yScaleRight(hoveredSeriesValue) : yScale(hoveredSeriesValue)}
|
|
95
|
+
r={4.5}
|
|
96
|
+
opacity={1}
|
|
97
|
+
fillOpacity={1}
|
|
98
|
+
fill={getColor(displayArea, colorScale, config, seriesIndex, hoveredSeriesKey, seriesKey)}
|
|
99
|
+
style={{ filter: 'unset', opacity: 1 }}
|
|
100
|
+
/>
|
|
101
|
+
)
|
|
102
|
+
})
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
return null
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
export default LineChartCircle
|
|
@@ -8,6 +8,7 @@ import { Text } from '@visx/text'
|
|
|
8
8
|
import ErrorBoundary from '@cdc/core/components/ErrorBoundary'
|
|
9
9
|
import ConfigContext from '../ConfigContext'
|
|
10
10
|
import useRightAxis from '../hooks/useRightAxis'
|
|
11
|
+
import LineChartCircle from './LineChart.Circle'
|
|
11
12
|
|
|
12
13
|
const LineChart = ({ xScale, yScale, getXAxisData, getYAxisData, xMax, yMax, handleTooltipMouseOver, handleTooltipMouseOff, showTooltip, seriesStyle = 'Line', svgRef, handleTooltipClick, tooltipData }) => {
|
|
13
14
|
// Not sure why there's a redraw here.
|
|
@@ -27,6 +28,7 @@ const LineChart = ({ xScale, yScale, getXAxisData, getYAxisData, xMax, yMax, han
|
|
|
27
28
|
}
|
|
28
29
|
|
|
29
30
|
const DEBUG = false
|
|
31
|
+
|
|
30
32
|
return (
|
|
31
33
|
<ErrorBoundary component='LineChart'>
|
|
32
34
|
<Group left={config.runtime.yAxis.size ? parseInt(config.runtime.yAxis.size) : 66}>
|
|
@@ -58,18 +60,6 @@ const LineChart = ({ xScale, yScale, getXAxisData, getYAxisData, xMax, yMax, han
|
|
|
58
60
|
// if has muiltiple series dont show legend value on tooltip
|
|
59
61
|
if (!hasMultipleSeries) label = config.isLegendValue ? config.runtime.seriesLabels[seriesKey] : label
|
|
60
62
|
|
|
61
|
-
let yAxisTooltip = handleAxisFormating(axis, label, yAxisValue)
|
|
62
|
-
let xAxisTooltip = handleAxisFormating(axis, config.runtime.xAxis.label, xAxisValue)
|
|
63
|
-
|
|
64
|
-
const tooltip = `<div>
|
|
65
|
-
${config.legend.showLegendValuesTooltip && config.runtime.seriesLabels && Object.keys(config.runtime.seriesLabels).length > 1 ? `${config.runtime.seriesLabels[seriesKey] || ''}<br/>` : ''}
|
|
66
|
-
${yAxisTooltip}<br />
|
|
67
|
-
${xAxisTooltip}
|
|
68
|
-
</div>`
|
|
69
|
-
|
|
70
|
-
// TODO: move all instances of circleRadii to state.
|
|
71
|
-
let circleRadii = 4.5
|
|
72
|
-
|
|
73
63
|
return (
|
|
74
64
|
d[seriesKey] !== undefined &&
|
|
75
65
|
d[seriesKey] !== '' &&
|
|
@@ -84,46 +74,24 @@ const LineChart = ({ xScale, yScale, getXAxisData, getYAxisData, xMax, yMax, han
|
|
|
84
74
|
{formatNumber(d[seriesKey], 'left')}
|
|
85
75
|
</Text>
|
|
86
76
|
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
r={circleRadii}
|
|
90
|
-
cx={Number(xScale(getXAxisData(d)))}
|
|
91
|
-
cy={seriesAxis === 'Right' ? yScaleRight(getYAxisData(d, seriesKey)) : yScale(getYAxisData(d, seriesKey))}
|
|
92
|
-
fill={colorScale ? colorScale(config.runtime.seriesLabels ? config.runtime.seriesLabels[seriesKey] : seriesKey) : '#000'}
|
|
93
|
-
style={{
|
|
94
|
-
fill: colorScale ? colorScale(config.runtime.seriesLabels ? config.runtime.seriesLabels[seriesKey] : seriesKey) : '#000'
|
|
95
|
-
}}
|
|
96
|
-
data-tooltip-html={tooltip}
|
|
97
|
-
data-tooltip-id={`cdc-open-viz-tooltip-${config.runtime.uniqueId}`}
|
|
98
|
-
/>
|
|
99
|
-
|
|
100
|
-
{/* circles that appear on hover */}
|
|
101
|
-
{/* todo: circle radii used here should be global with other circle radii */}
|
|
102
|
-
{/* {tooltipData && Object.entries(tooltipData.data).length > 0 && isNumber(tooltipData.data[seriesKey]) && config.lineDatapointStyle === 'hover' && config.series.filter(s => s.type === 'Line') && (
|
|
103
|
-
<circle
|
|
104
|
-
cx={config.xAxis.type === 'categorical' ? xScale(tooltipData.data[config.xAxis.dataKey]) : xScale(parseDate(tooltipData.data[config.xAxis.dataKey]))}
|
|
105
|
-
cy={yScale(tooltipData.data[seriesKey])}
|
|
106
|
-
r={4.5}
|
|
107
|
-
opacity={tooltipData[seriesKey] ? 1 : 0}
|
|
108
|
-
fillOpacity={1}
|
|
109
|
-
fill={displayArea ? (colorScale ? colorScale(config.runtime.seriesLabels ? config.runtime.seriesLabels[seriesKey] : seriesKey) : '#000') : 'transparent'}
|
|
110
|
-
style={{ filter: 'unset', opacity: 1 }}
|
|
111
|
-
/>
|
|
112
|
-
)} */}
|
|
77
|
+
{config.lineDatapointStyle === 'hidden' ||
|
|
78
|
+
(config.lineDatapointStyle === 'always show' && <LineChartCircle d={d} config={config} seriesKey={seriesKey} displayArea={displayArea} tooltipData={tooltipData} xScale={xScale} yScale={yScale} colorScale={colorScale} parseDate={parseDate} yScaleRight={yScaleRight} />)}
|
|
113
79
|
</Group>
|
|
114
80
|
)
|
|
115
81
|
)
|
|
116
82
|
})}
|
|
83
|
+
|
|
84
|
+
<>{config.lineDatapointStyle === 'hover' && <LineChartCircle config={config} seriesKey={seriesKey} displayArea={displayArea} tooltipData={tooltipData} xScale={xScale} yScale={yScale} colorScale={colorScale} parseDate={parseDate} yScaleRight={yScaleRight} seriesAxis={seriesAxis} />}</>
|
|
85
|
+
|
|
117
86
|
<LinePath
|
|
118
87
|
curve={allCurves[seriesData[0].lineType]}
|
|
119
88
|
data={data}
|
|
120
89
|
x={d => xScale(getXAxisData(d))}
|
|
121
90
|
y={d => (seriesAxis === 'Right' ? yScaleRight(getYAxisData(d, seriesKey)) : yScale(getYAxisData(d, seriesKey)))}
|
|
122
91
|
stroke={
|
|
123
|
-
|
|
124
|
-
?
|
|
125
|
-
:
|
|
126
|
-
config.legend.dynamicLegend
|
|
92
|
+
config.customColors
|
|
93
|
+
? config.customColors[index]
|
|
94
|
+
: colorScale
|
|
127
95
|
? colorPalettes[config.palette][index]
|
|
128
96
|
: // fallback
|
|
129
97
|
'#000'
|