@cdc/chart 4.25.6 → 4.25.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.
- package/dist/cdcchart.js +53500 -32825
- package/package.json +3 -2
- package/src/CdcChart.tsx +9 -2
- package/src/CdcChartComponent.tsx +30 -12
- package/src/_stories/Chart.BoxPlot.stories.tsx +35 -0
- package/src/_stories/Chart.stories.tsx +0 -7
- package/src/_stories/Chart.tooltip.stories.tsx +35 -275
- package/src/_stories/_mock/bar-chart-suppressed.json +2 -80
- package/src/_stories/_mock/boxplot_multiseries.json +252 -166
- package/src/components/AreaChart/components/AreaChart.Stacked.jsx +1 -1
- package/src/components/AreaChart/components/AreaChart.jsx +4 -8
- package/src/components/BarChart/components/BarChart.Horizontal.tsx +45 -7
- package/src/components/BarChart/components/BarChart.StackedHorizontal.tsx +1 -1
- package/src/components/BarChart/components/BarChart.Vertical.tsx +36 -4
- package/src/components/BoxPlot/BoxPlot.Horizontal.tsx +131 -0
- package/src/components/BoxPlot/{BoxPlot.tsx → BoxPlot.Vertical.tsx} +4 -4
- package/src/components/BoxPlot/helpers/index.ts +32 -12
- package/src/components/Brush/BrushChart.tsx +65 -10
- package/src/components/Brush/BrushController.tsx +71 -0
- package/src/components/Brush/types.tsx +8 -0
- package/src/components/BrushChart.tsx +1 -1
- package/src/components/EditorPanel/EditorPanel.tsx +19 -14
- package/src/components/EditorPanel/components/Panels/Panel.Annotate.tsx +2 -2
- package/src/components/EditorPanel/components/Panels/Panel.General.tsx +2 -2
- package/src/components/EditorPanel/components/Panels/Panel.Series.tsx +2 -34
- package/src/components/Forecasting/{Forecasting.jsx → Forecasting.tsx} +32 -12
- package/src/components/Legend/Legend.Component.tsx +16 -1
- package/src/components/Legend/Legend.tsx +3 -1
- package/src/components/Legend/LegendGroup/LegendGroup.tsx +1 -0
- package/src/components/Legend/helpers/index.ts +2 -2
- package/src/components/LineChart/components/LineChart.BumpCircle.tsx +27 -26
- package/src/components/LineChart/helpers.ts +7 -7
- package/src/components/LinearChart.tsx +130 -75
- package/src/data/initial-state.js +12 -15
- package/src/helpers/countNumOfTicks.ts +4 -19
- package/src/helpers/filterAndShiftLinearDateTicks.ts +58 -0
- package/src/helpers/getBridgedData.ts +13 -0
- package/src/helpers/tests/getBridgedData.test.ts +64 -0
- package/src/hooks/useScales.ts +42 -42
- package/src/hooks/useTooltip.tsx +3 -2
- package/src/index.jsx +6 -1
- package/src/scss/main.scss +2 -4
- package/src/store/chart.actions.ts +2 -2
- package/src/store/chart.reducer.ts +4 -12
- package/src/types/ChartConfig.ts +1 -6
- package/src/components/BoxPlot/index.tsx +0 -3
- package/src/components/Brush/BrushController..tsx +0 -39
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import { useState, useEffect, useContext, useRef } from 'react'
|
|
2
|
+
import ConfigContext, { ChartDispatchContext } from '../../ConfigContext'
|
|
3
|
+
import BrushChart from './BrushChart'
|
|
4
|
+
import { isDateScale } from '@cdc/core/helpers/cove/date'
|
|
5
|
+
import { BrushRef } from './types'
|
|
6
|
+
|
|
7
|
+
const BrushController = ({ yMax, xMax }) => {
|
|
8
|
+
const { tableData, config, parseDate, dashboardConfig, formatDate } = useContext(ConfigContext)
|
|
9
|
+
const [brushHandleProps, setBrushHandleProps] = useState({
|
|
10
|
+
startPos: 0,
|
|
11
|
+
endPos: 0,
|
|
12
|
+
startValue: '',
|
|
13
|
+
endValue: '',
|
|
14
|
+
xMax: xMax
|
|
15
|
+
})
|
|
16
|
+
const dataKey = config.xAxis.dataKey
|
|
17
|
+
const [brushKey, setBrushKey] = useState(0)
|
|
18
|
+
const dispatch = useContext(ChartDispatchContext)
|
|
19
|
+
const sharedFilters = dashboardConfig?.dashboard?.sharedFilters ?? []
|
|
20
|
+
const isDashboardFilters = sharedFilters?.length > 0
|
|
21
|
+
const brushRef = useRef<BrushRef | null>(null)
|
|
22
|
+
|
|
23
|
+
const [brushPosition, setBrushPosition] = useState({
|
|
24
|
+
start: { x: 0 },
|
|
25
|
+
end: { x: xMax }
|
|
26
|
+
})
|
|
27
|
+
|
|
28
|
+
const handleBrushChange = (bounds: any) => {
|
|
29
|
+
if (!bounds) return dispatch({ type: 'SET_BRUSH_DATA', payload: [] })
|
|
30
|
+
const filteredValues = bounds?.xValues?.filter(val => val !== undefined)
|
|
31
|
+
if (filteredValues?.length === 0) dispatch({ type: 'SET_BRUSH_DATA', payload: [] })
|
|
32
|
+
const selected = bounds?.xValues || []
|
|
33
|
+
|
|
34
|
+
const filteredData = tableData.filter(row => selected.includes(row[dataKey]))
|
|
35
|
+
const endValue = filteredValues
|
|
36
|
+
.slice()
|
|
37
|
+
.reverse()
|
|
38
|
+
.find(item => item !== undefined)
|
|
39
|
+
const startValue = filteredValues.find(item => item !== undefined)
|
|
40
|
+
const formatIfDate = value => (isDateScale(config.runtime.xAxis) ? formatDate(parseDate(value)) : value)
|
|
41
|
+
|
|
42
|
+
setBrushHandleProps(prev => ({
|
|
43
|
+
...prev,
|
|
44
|
+
startPos: brushRef.current?.state.start.x,
|
|
45
|
+
endPos: brushRef.current?.state.end.x,
|
|
46
|
+
endValue: formatIfDate(endValue),
|
|
47
|
+
startValue: formatIfDate(startValue)
|
|
48
|
+
}))
|
|
49
|
+
dispatch({ type: 'SET_BRUSH_DATA', payload: filteredData })
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// whenever your other filters change:
|
|
53
|
+
useEffect(() => {
|
|
54
|
+
setBrushPosition({ start: { x: 0 }, end: { x: xMax } })
|
|
55
|
+
dispatch({ type: 'SET_BRUSH_DATA', payload: [] })
|
|
56
|
+
setBrushKey(k => k + 1)
|
|
57
|
+
}, [config.filters, config.exclusions, config.brush?.active, isDashboardFilters])
|
|
58
|
+
|
|
59
|
+
return (
|
|
60
|
+
<BrushChart
|
|
61
|
+
brushRef={brushRef}
|
|
62
|
+
brushHandleProps={brushHandleProps}
|
|
63
|
+
xMax={xMax}
|
|
64
|
+
yMax={yMax}
|
|
65
|
+
brushPosition={brushPosition}
|
|
66
|
+
onBrushChange={handleBrushChange}
|
|
67
|
+
brushKey={brushKey}
|
|
68
|
+
/>
|
|
69
|
+
)
|
|
70
|
+
}
|
|
71
|
+
export default BrushController
|
|
@@ -120,7 +120,7 @@ const BrushChart = ({ xMax, yMax }: BrushChartProps) => {
|
|
|
120
120
|
svg.call(brushHandle, selection, formattedStartDate, formattedEndDate)
|
|
121
121
|
|
|
122
122
|
const payload = {
|
|
123
|
-
active: config.
|
|
123
|
+
active: config.xAxis.brushActive,
|
|
124
124
|
isBrushing: isUserBrushing,
|
|
125
125
|
data: finalData
|
|
126
126
|
}
|
|
@@ -20,6 +20,7 @@ import DataTableEditor from '@cdc/core/components/EditorPanel/DataTableEditor'
|
|
|
20
20
|
import VizFilterEditor from '@cdc/core/components/EditorPanel/VizFilterEditor'
|
|
21
21
|
import Tooltip from '@cdc/core/components/ui/Tooltip'
|
|
22
22
|
import { Select, TextField, CheckBox } from '@cdc/core/components/EditorPanel/Inputs'
|
|
23
|
+
import MultiSelect from '@cdc/core/components/MultiSelect'
|
|
23
24
|
import { viewports } from '@cdc/core/helpers/getViewport'
|
|
24
25
|
import { approvedCurveTypes } from '@cdc/core/helpers/lineChartHelpers'
|
|
25
26
|
|
|
@@ -109,7 +110,7 @@ const PreliminaryData: React.FC<PreliminaryProps> = ({ config, updateConfig, dat
|
|
|
109
110
|
let preliminaryData = config.preliminaryData ? [...config.preliminaryData] : []
|
|
110
111
|
const defaultValues = {
|
|
111
112
|
type: defaultType,
|
|
112
|
-
|
|
113
|
+
seriesKeys: [],
|
|
113
114
|
label: 'Suppressed',
|
|
114
115
|
column: '',
|
|
115
116
|
value: '',
|
|
@@ -159,7 +160,7 @@ const PreliminaryData: React.FC<PreliminaryProps> = ({ config, updateConfig, dat
|
|
|
159
160
|
displayTable,
|
|
160
161
|
displayTooltip,
|
|
161
162
|
label,
|
|
162
|
-
|
|
163
|
+
seriesKeys,
|
|
163
164
|
style,
|
|
164
165
|
symbol,
|
|
165
166
|
type,
|
|
@@ -384,14 +385,18 @@ const PreliminaryData: React.FC<PreliminaryProps> = ({ config, updateConfig, dat
|
|
|
384
385
|
</>
|
|
385
386
|
) : (
|
|
386
387
|
<>
|
|
387
|
-
<
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
388
|
+
<label>
|
|
389
|
+
<span className='edit-label'>ASSOCIATE TO THESE SERIES</span>
|
|
390
|
+
<MultiSelect
|
|
391
|
+
fieldName='seriesKeys'
|
|
392
|
+
updateField={(_, __, fieldName, value) => update(fieldName, value, i)}
|
|
393
|
+
options={(config.runtime.lineSeriesKeys ?? config.runtime?.seriesKeys).map(c => ({
|
|
394
|
+
label: c,
|
|
395
|
+
value: c
|
|
396
|
+
}))}
|
|
397
|
+
selected={seriesKeys}
|
|
398
|
+
/>
|
|
399
|
+
</label>
|
|
395
400
|
<Select
|
|
396
401
|
value={column}
|
|
397
402
|
initial='Select'
|
|
@@ -1193,7 +1198,7 @@ const EditorPanel: React.FC<ChartEditorPanelProps> = ({ datasets }) => {
|
|
|
1193
1198
|
if (isDebug && config?.series?.length === 0) {
|
|
1194
1199
|
let setdatacol = setDataColumn()
|
|
1195
1200
|
if (setdatacol !== '') addNewSeries(setdatacol)
|
|
1196
|
-
if (isDebug) console.
|
|
1201
|
+
if (isDebug) console.log('### COVE DEBUG: Chart: Setting default datacol=', setdatacol) // eslint-disable-line
|
|
1197
1202
|
}
|
|
1198
1203
|
|
|
1199
1204
|
const chartsWithOptions = [
|
|
@@ -2926,9 +2931,9 @@ const EditorPanel: React.FC<ChartEditorPanelProps> = ({ datasets }) => {
|
|
|
2926
2931
|
/>
|
|
2927
2932
|
{visHasBrushChart() && (
|
|
2928
2933
|
<CheckBox
|
|
2929
|
-
value={config.
|
|
2930
|
-
section='
|
|
2931
|
-
fieldName='
|
|
2934
|
+
value={config.xAxis.brushActive}
|
|
2935
|
+
section='xAxis'
|
|
2936
|
+
fieldName='brushActive'
|
|
2932
2937
|
label='Brush Slider '
|
|
2933
2938
|
updateField={updateFieldDeprecated}
|
|
2934
2939
|
tooltip={
|
|
@@ -67,8 +67,8 @@ const PanelAnnotate: React.FC<PanelProps> = props => {
|
|
|
67
67
|
config.xAxis.type === 'date'
|
|
68
68
|
? new Date(config?.data?.[0]?.[config.xAxis.dataKey]).getTime()
|
|
69
69
|
: config.xAxis.type === 'categorical'
|
|
70
|
-
|
|
71
|
-
|
|
70
|
+
? '1/15/2016'
|
|
71
|
+
: '',
|
|
72
72
|
yKey: '',
|
|
73
73
|
dx: 20,
|
|
74
74
|
dy: -20,
|
|
@@ -131,7 +131,7 @@ const PanelGeneral: FC<PanelProps> = props => {
|
|
|
131
131
|
options={Object.keys(approvedCurveTypes)}
|
|
132
132
|
/>
|
|
133
133
|
)}
|
|
134
|
-
{visualizationType === 'Bar' && (
|
|
134
|
+
{(visualizationType === 'Bar' || visualizationType === 'Box Plot') && (
|
|
135
135
|
<Select
|
|
136
136
|
value={config.orientation || 'vertical'}
|
|
137
137
|
fieldName='orientation'
|
|
@@ -178,7 +178,7 @@ const PanelGeneral: FC<PanelProps> = props => {
|
|
|
178
178
|
options={['standard', 'shallow', 'finger']}
|
|
179
179
|
/>
|
|
180
180
|
)}
|
|
181
|
-
{visualizationType === 'Bar' && config.orientation === 'horizontal' && (
|
|
181
|
+
{(visualizationType === 'Bar' || visualizationType === 'Box Plot') && config.orientation === 'horizontal' && (
|
|
182
182
|
<Select
|
|
183
183
|
value={config.yAxis.labelPlacement || 'Below Bar'}
|
|
184
184
|
section='yAxis'
|
|
@@ -281,6 +281,7 @@ const SeriesDropdownConfidenceInterval = props => {
|
|
|
281
281
|
const { config, updateConfig } = useContext(ConfigContext)
|
|
282
282
|
const { series, index } = props
|
|
283
283
|
const { getColumns } = useContext(SeriesContext)
|
|
284
|
+
|
|
284
285
|
if (series.type !== 'Forecasting') return
|
|
285
286
|
|
|
286
287
|
return (
|
|
@@ -289,18 +290,6 @@ const SeriesDropdownConfidenceInterval = props => {
|
|
|
289
290
|
<fieldset>
|
|
290
291
|
<Accordion allowZeroExpanded>
|
|
291
292
|
{series?.confidenceIntervals?.map((ciGroup, ciIndex) => {
|
|
292
|
-
const showInTooltip = ciGroup.showInTooltip ? ciGroup.showInTooltip : false
|
|
293
|
-
|
|
294
|
-
const updateShowInTooltip = (e, seriesIndex, ciIndex) => {
|
|
295
|
-
e.preventDefault()
|
|
296
|
-
let copiedSeries = [...config.series]
|
|
297
|
-
copiedSeries[seriesIndex].confidenceIntervals[ciIndex].showInTooltip = !showInTooltip
|
|
298
|
-
updateConfig({
|
|
299
|
-
...config,
|
|
300
|
-
series: copiedSeries
|
|
301
|
-
})
|
|
302
|
-
}
|
|
303
|
-
|
|
304
293
|
return (
|
|
305
294
|
<AccordionItem className='series-item series-item--chart' key={`${ciIndex}`}>
|
|
306
295
|
<AccordionItemHeading className='series-item__title'>
|
|
@@ -312,6 +301,7 @@ const SeriesDropdownConfidenceInterval = props => {
|
|
|
312
301
|
onClick={e => {
|
|
313
302
|
e.preventDefault()
|
|
314
303
|
const copiedIndex = [...config.series[index].confidenceIntervals]
|
|
304
|
+
|
|
315
305
|
copiedIndex.splice(ciIndex, 1)
|
|
316
306
|
const copyOfSeries = [...config.series] // copy the entire series array
|
|
317
307
|
copyOfSeries[index] = { ...copyOfSeries[index], confidenceIntervals: [...copiedIndex] }
|
|
@@ -327,28 +317,6 @@ const SeriesDropdownConfidenceInterval = props => {
|
|
|
327
317
|
</>
|
|
328
318
|
</AccordionItemHeading>
|
|
329
319
|
<AccordionItemPanel>
|
|
330
|
-
<div className='input-group'>
|
|
331
|
-
<label htmlFor='showInTooltip'>Show In Tooltip</label>
|
|
332
|
-
<div
|
|
333
|
-
className={'cove-input__checkbox--small'}
|
|
334
|
-
onClick={e => updateShowInTooltip(e, index, ciIndex)}
|
|
335
|
-
>
|
|
336
|
-
<div
|
|
337
|
-
className={`cove-input__checkbox-box${'blue' ? ' custom-color' : ''}`}
|
|
338
|
-
style={{ backgroundColor: '' }}
|
|
339
|
-
>
|
|
340
|
-
{showInTooltip && <Check className='' style={{ fill: '#025eaa' }} />}
|
|
341
|
-
</div>
|
|
342
|
-
<input
|
|
343
|
-
className='cove-input--hidden'
|
|
344
|
-
type='checkbox'
|
|
345
|
-
name={'showInTooltip'}
|
|
346
|
-
checked={showInTooltip ? showInTooltip : false}
|
|
347
|
-
readOnly
|
|
348
|
-
/>
|
|
349
|
-
</div>
|
|
350
|
-
</div>
|
|
351
|
-
|
|
352
320
|
<InputSelect
|
|
353
321
|
initial='Select an option'
|
|
354
322
|
value={
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import React, { useContext } from 'react'
|
|
2
|
-
|
|
2
|
+
import { replace } from 'lodash'
|
|
3
3
|
// cdc
|
|
4
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
|
+
import { getBridgedData } from '../../helpers/getBridgedData'
|
|
7
8
|
|
|
8
9
|
// visx & d3
|
|
9
10
|
import { curveMonotoneX } from '@visx/curve'
|
|
@@ -11,7 +12,7 @@ import { Bar, Area, LinePath } from '@visx/shape'
|
|
|
11
12
|
import { Group } from '@visx/group'
|
|
12
13
|
|
|
13
14
|
const Forecasting = ({ xScale, yScale, height, width, handleTooltipMouseOver, handleTooltipMouseOff }) => {
|
|
14
|
-
const { transformedData: data, rawData, config, seriesHighlight } = useContext(ConfigContext)
|
|
15
|
+
const { transformedData: data, rawData, config, seriesHighlight, parseDate } = useContext(ConfigContext)
|
|
15
16
|
const { xAxis, yAxis, legend, runtime } = config
|
|
16
17
|
const DEBUG = false
|
|
17
18
|
|
|
@@ -23,12 +24,17 @@ const Forecasting = ({ xScale, yScale, height, width, handleTooltipMouseOver, ha
|
|
|
23
24
|
if (!group || !group.stages) return false
|
|
24
25
|
return group.stages.map((stage, stageIndex) => {
|
|
25
26
|
const { behavior } = legend
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
let displayArea =
|
|
27
|
+
let transparentArea =
|
|
28
|
+
behavior === 'highlight' && seriesHighlight.length > 0 && seriesHighlight.indexOf(stage.key) === -1
|
|
29
|
+
let displayArea =
|
|
30
|
+
behavior === 'highlight' || seriesHighlight.length === 0 || seriesHighlight.indexOf(stage.key) !== -1
|
|
31
|
+
const bridgedData = getBridgedData(stage.key, group.stageColumn, rawData)
|
|
29
32
|
|
|
30
33
|
return (
|
|
31
|
-
<Group
|
|
34
|
+
<Group
|
|
35
|
+
className={`forecasting-areas-combo-${index}`}
|
|
36
|
+
key={`forecasting-areas--stage-${replace(stage.key, / /g, '—')}-${index}`}
|
|
37
|
+
>
|
|
32
38
|
{group.confidenceIntervals?.map((ciGroup, ciGroupIndex) => {
|
|
33
39
|
const palette = sequentialPalettes[stage.color] || colorPalettesChart[stage.color] || false
|
|
34
40
|
|
|
@@ -44,13 +50,19 @@ const Forecasting = ({ xScale, yScale, height, width, handleTooltipMouseOver, ha
|
|
|
44
50
|
|
|
45
51
|
if (ciGroup.high === '' || ciGroup.low === '') return
|
|
46
52
|
return (
|
|
47
|
-
<Group
|
|
53
|
+
<Group
|
|
54
|
+
key={`forecasting-areas--stage-${replace(
|
|
55
|
+
stage.key,
|
|
56
|
+
/ /g,
|
|
57
|
+
'—'
|
|
58
|
+
)}--group-${stageIndex}-${ciGroupIndex}`}
|
|
59
|
+
>
|
|
48
60
|
{/* prettier-ignore */}
|
|
49
61
|
<Area
|
|
50
62
|
curve={curveMonotoneX}
|
|
51
|
-
data={
|
|
63
|
+
data={bridgedData}
|
|
52
64
|
fill={getFill()}
|
|
53
|
-
opacity={transparentArea
|
|
65
|
+
opacity={transparentArea? 0.1 : 0.5 }
|
|
54
66
|
x={d => xScale(Date.parse(d[xAxis.dataKey]))}
|
|
55
67
|
y0={d => yScale(d[ciGroup.low])}
|
|
56
68
|
y1={d => yScale(d[ciGroup.high])}
|
|
@@ -59,10 +71,10 @@ const Forecasting = ({ xScale, yScale, height, width, handleTooltipMouseOver, ha
|
|
|
59
71
|
{ciGroupIndex === 0 && (
|
|
60
72
|
<>
|
|
61
73
|
{/* prettier-ignore */}
|
|
62
|
-
<LinePath data={
|
|
74
|
+
<LinePath data={bridgedData} x={d => Number(xScale(Date.parse(d[xAxis.dataKey])))} y={d => Number(yScale(d[ciGroup.high]))} curve={curveMonotoneX} stroke={getStroke()} strokeWidth={1} strokeOpacity={1} />
|
|
63
75
|
|
|
64
76
|
{/* prettier-ignore */}
|
|
65
|
-
<LinePath data={
|
|
77
|
+
<LinePath data={bridgedData} x={d => Number(xScale(Date.parse(d[xAxis.dataKey])))} y={d => Number(yScale(d[ciGroup.low]))} curve={curveMonotoneX} stroke={getStroke()} strokeWidth={1} strokeOpacity={1} />
|
|
66
78
|
</>
|
|
67
79
|
)}
|
|
68
80
|
</Group>
|
|
@@ -73,7 +85,15 @@ const Forecasting = ({ xScale, yScale, height, width, handleTooltipMouseOver, ha
|
|
|
73
85
|
})
|
|
74
86
|
})}
|
|
75
87
|
<Group key='tooltip-hover-section'>
|
|
76
|
-
<Bar
|
|
88
|
+
<Bar
|
|
89
|
+
key={'bars'}
|
|
90
|
+
width={Number(width)}
|
|
91
|
+
height={Number(height)}
|
|
92
|
+
fill={DEBUG ? 'red' : 'transparent'}
|
|
93
|
+
fillOpacity={0.05}
|
|
94
|
+
onMouseMove={e => handleTooltipMouseOver(e, data)}
|
|
95
|
+
onMouseOut={handleTooltipMouseOff}
|
|
96
|
+
/>
|
|
77
97
|
</Group>
|
|
78
98
|
</Group>
|
|
79
99
|
</ErrorBoundary>
|
|
@@ -17,6 +17,7 @@ import { isLegendWrapViewport } from '@cdc/core/helpers/viewports'
|
|
|
17
17
|
import LegendLineShape from './LegendLine.Shape'
|
|
18
18
|
import LegendGroup from './LegendGroup'
|
|
19
19
|
import { getSeriesWithData } from '../../helpers/dataHelpers'
|
|
20
|
+
import { publishAnalyticsEvent } from '@cdc/core/helpers/metrics/helpers'
|
|
20
21
|
|
|
21
22
|
const LEGEND_PADDING = 36
|
|
22
23
|
|
|
@@ -32,6 +33,7 @@ export interface LegendProps {
|
|
|
32
33
|
skipId: string
|
|
33
34
|
dimensions: DimensionsType // for responsive width legend
|
|
34
35
|
transformedData: any
|
|
36
|
+
interactionLabel: string
|
|
35
37
|
}
|
|
36
38
|
|
|
37
39
|
/* eslint-disable jsx-a11y/no-noninteractive-tabindex, jsx-a11y/no-static-element-interactions */
|
|
@@ -47,7 +49,8 @@ const Legend: React.FC<LegendProps> = forwardRef(
|
|
|
47
49
|
formatLabels,
|
|
48
50
|
skipId = 'legend',
|
|
49
51
|
dimensions,
|
|
50
|
-
transformedData: data
|
|
52
|
+
transformedData: data,
|
|
53
|
+
interactionLabel = ''
|
|
51
54
|
},
|
|
52
55
|
ref
|
|
53
56
|
) => {
|
|
@@ -137,11 +140,23 @@ const Legend: React.FC<LegendProps> = forwardRef(
|
|
|
137
140
|
onKeyDown={e => {
|
|
138
141
|
if (e.key === 'Enter') {
|
|
139
142
|
e.preventDefault()
|
|
143
|
+
publishAnalyticsEvent(
|
|
144
|
+
`chart_legend_item_toggled--${legend.behavior}-mode`,
|
|
145
|
+
'keydown',
|
|
146
|
+
`${interactionLabel}|${label.text}`,
|
|
147
|
+
'chart'
|
|
148
|
+
)
|
|
140
149
|
highlight(label)
|
|
141
150
|
}
|
|
142
151
|
}}
|
|
143
152
|
onClick={e => {
|
|
144
153
|
e.preventDefault()
|
|
154
|
+
publishAnalyticsEvent(
|
|
155
|
+
`chart_legend_item_toggled--${legend.behavior}-mode`,
|
|
156
|
+
'click',
|
|
157
|
+
`${interactionLabel}|${label.text}`,
|
|
158
|
+
'chart'
|
|
159
|
+
)
|
|
145
160
|
highlight(label)
|
|
146
161
|
}}
|
|
147
162
|
role='button'
|
|
@@ -21,7 +21,8 @@ const Legend = forwardRef((props, ref) => {
|
|
|
21
21
|
transformedData
|
|
22
22
|
} = useContext(ConfigContext)
|
|
23
23
|
if (!config.legend) return null
|
|
24
|
-
// create fn to reverse labels while legend is Bottom. Legend-right , legend-left works by default
|
|
24
|
+
// create fn to reverse labels while legend is Bottom. Legend-right , legend-left works by default
|
|
25
|
+
const { interactionLabel } = props
|
|
25
26
|
|
|
26
27
|
const createLegendLabels = createFormatLabels(config, tableData, data, colorScale)
|
|
27
28
|
|
|
@@ -40,6 +41,7 @@ const Legend = forwardRef((props, ref) => {
|
|
|
40
41
|
handleShowAll={handleShowAll}
|
|
41
42
|
currentViewport={currentViewport}
|
|
42
43
|
formatLabels={createLegendLabels}
|
|
44
|
+
interactionLabel={interactionLabel}
|
|
43
45
|
/>
|
|
44
46
|
</Fragment>
|
|
45
47
|
)
|
|
@@ -19,9 +19,9 @@ export const getMarginTop = (isLegendBottom, config) => {
|
|
|
19
19
|
if (!isLegendBottom) {
|
|
20
20
|
return '0px'
|
|
21
21
|
}
|
|
22
|
-
if (isLegendBottom && config.
|
|
22
|
+
if (isLegendBottom && config.xAxis.brushActive && !config.legend.hide) {
|
|
23
23
|
const additiolMargin = 25
|
|
24
|
-
return `${DEFAULT_MARGIN_TOP + config.brush
|
|
24
|
+
return `${DEFAULT_MARGIN_TOP + config.brush?.height + additiolMargin}px`
|
|
25
25
|
} else {
|
|
26
26
|
return `${DEFAULT_MARGIN_TOP}px`
|
|
27
27
|
}
|
|
@@ -3,13 +3,14 @@ import { Group } from '@visx/group'
|
|
|
3
3
|
import { type Column } from '@cdc/core/types/Column'
|
|
4
4
|
import React from 'react'
|
|
5
5
|
import { type ChartConfig } from '../../../types/ChartConfig'
|
|
6
|
+
import { APP_FONT_COLOR } from '@cdc/core/helpers/constants'
|
|
6
7
|
|
|
7
8
|
type LineChartBumpCircleProp = {
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
9
|
+
config: ChartConfig
|
|
10
|
+
xScale: any
|
|
11
|
+
yScale: any
|
|
12
|
+
parseDate: any
|
|
13
|
+
}
|
|
13
14
|
|
|
14
15
|
const LineChartBumpCircle = (props: LineChartBumpCircleProp) => {
|
|
15
16
|
const { config, xScale, yScale, parseDate } = props
|
|
@@ -33,47 +34,47 @@ const LineChartBumpCircle = (props: LineChartBumpCircleProp) => {
|
|
|
33
34
|
return xScale.bandwidth ? xScale.bandwidth() / 2 + Number(xValue) : Number(xValue)
|
|
34
35
|
}
|
|
35
36
|
|
|
36
|
-
|
|
37
37
|
const getListItems = dataRow => {
|
|
38
38
|
return Object.values(config.columns)
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
39
|
+
?.filter(column => column.tooltips)
|
|
40
|
+
.map(column => {
|
|
41
|
+
const label = column.label || column.name
|
|
42
|
+
return `
|
|
42
43
|
<li className='tooltip-body'>
|
|
43
44
|
<strong>${label}</strong>: ${dataRow[column.name]}
|
|
44
|
-
</li
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
}
|
|
45
|
+
</li>`
|
|
46
|
+
})
|
|
47
|
+
.join(' ')
|
|
48
|
+
}
|
|
48
49
|
|
|
49
50
|
const getTooltip = dataRow => `<ul> ${getListItems(dataRow)} </ul>`
|
|
50
51
|
|
|
51
|
-
const circles = config.runtime?.series.map(
|
|
52
|
+
const circles = config.runtime?.series.map(series => {
|
|
52
53
|
return config.data.map((d, dataIndex) => {
|
|
53
54
|
let series_dataKey = d[series.dataKey]
|
|
54
55
|
let axis_dataKey = d[config.xAxis.dataKey]
|
|
55
56
|
return (
|
|
56
57
|
<React.Fragment key={`bump-circle-${series_dataKey}-${dataIndex}`}>
|
|
57
|
-
<Group left={Number(config.runtime.yAxis.size)}>
|
|
58
|
+
<Group left={Number(config.runtime.yAxis.size)}>
|
|
58
59
|
{series_dataKey && (
|
|
59
60
|
<>
|
|
60
|
-
<circle
|
|
61
|
+
<circle
|
|
61
62
|
key={`bump-circle-${series_dataKey}-${dataIndex}`}
|
|
62
|
-
data-tooltip-html={getTooltip(d)}
|
|
63
|
-
data-tooltip-id={`bump-chart`}
|
|
64
|
-
r={10}
|
|
65
|
-
cx={Number(checkBandScale(xScale(handleX(axis_dataKey))))}
|
|
66
|
-
cy={Number(yScale(series_dataKey))}
|
|
67
|
-
stroke='#CACACA'
|
|
68
|
-
strokeWidth={1}
|
|
69
|
-
fill='#E5E4E2'
|
|
63
|
+
data-tooltip-html={getTooltip(d)}
|
|
64
|
+
data-tooltip-id={`bump-chart`}
|
|
65
|
+
r={10}
|
|
66
|
+
cx={Number(checkBandScale(xScale(handleX(axis_dataKey))))}
|
|
67
|
+
cy={Number(yScale(series_dataKey))}
|
|
68
|
+
stroke='#CACACA'
|
|
69
|
+
strokeWidth={1}
|
|
70
|
+
fill='#E5E4E2'
|
|
70
71
|
/>
|
|
71
72
|
{series_dataKey.toString().length === 2 ? (
|
|
72
73
|
// prettier-ignore
|
|
73
74
|
<text
|
|
74
75
|
x={Number(checkBandScale(xScale(handleX(axis_dataKey)))) - 7}
|
|
75
76
|
y={Number(yScale(series_dataKey)) + 4}
|
|
76
|
-
fill=
|
|
77
|
+
fill={APP_FONT_COLOR}
|
|
77
78
|
fontSize={11.5}
|
|
78
79
|
>
|
|
79
80
|
{series_dataKey}
|
|
@@ -83,7 +84,7 @@ const LineChartBumpCircle = (props: LineChartBumpCircleProp) => {
|
|
|
83
84
|
<text
|
|
84
85
|
x={Number(checkBandScale(xScale(handleX(axis_dataKey)))) - 4}
|
|
85
86
|
y={Number(yScale(series_dataKey)) + 4}
|
|
86
|
-
fill=
|
|
87
|
+
fill={APP_FONT_COLOR}
|
|
87
88
|
fontSize={11.5}
|
|
88
89
|
>
|
|
89
90
|
{series_dataKey}
|
|
@@ -17,19 +17,19 @@ export const createStyles = (props: StyleProps): Style[] => {
|
|
|
17
17
|
|
|
18
18
|
const dynamicSeriesKey = dynamicCategory ? originalSeriesKey : seriesKey
|
|
19
19
|
const validPreliminaryData: PreliminaryDataItem[] = preliminaryData.filter(
|
|
20
|
-
pd => pd.
|
|
20
|
+
pd => pd.seriesKeys?.length && pd.column && pd.value && pd.type && pd.style && pd.type === 'effect'
|
|
21
21
|
)
|
|
22
22
|
const isEffectLine = (pd, dataPoint) => {
|
|
23
23
|
if (dynamicCategory) {
|
|
24
24
|
return (
|
|
25
25
|
pd.type === 'effect' &&
|
|
26
26
|
pd.style !== 'Open Circles' &&
|
|
27
|
-
pd.seriesKey
|
|
27
|
+
pd.seriesKeys.includes(seriesKey) &&
|
|
28
28
|
String(dataPoint[dynamicSeriesKey]) === String(pd.value)
|
|
29
29
|
)
|
|
30
30
|
} else {
|
|
31
31
|
return (
|
|
32
|
-
pd.seriesKey
|
|
32
|
+
pd.seriesKeys.includes(seriesKey) &&
|
|
33
33
|
dataPoint[pd.column] === pd.value &&
|
|
34
34
|
pd.type === 'effect' &&
|
|
35
35
|
pd.style !== 'Open Circles'
|
|
@@ -71,11 +71,11 @@ export const filterCircles = (
|
|
|
71
71
|
): DataItem[] => {
|
|
72
72
|
// Filter and map preliminaryData to get circlesFiltered
|
|
73
73
|
const circlesFiltered = preliminaryData
|
|
74
|
-
?.filter(item => item.style.includes('Circles') && item.type === 'effect')
|
|
74
|
+
?.filter(item => item.style.includes('Circles') && item.type === 'effect' && item.seriesKeys?.length)
|
|
75
75
|
.map(item => ({
|
|
76
76
|
column: item.column,
|
|
77
77
|
value: item.value,
|
|
78
|
-
|
|
78
|
+
seriesKeys: item.seriesKeys,
|
|
79
79
|
circleSize: item.circleSize,
|
|
80
80
|
style: item.style
|
|
81
81
|
}))
|
|
@@ -85,7 +85,7 @@ export const filterCircles = (
|
|
|
85
85
|
circlesFiltered.forEach(fc => {
|
|
86
86
|
if (
|
|
87
87
|
item[fc.column] === fc.value &&
|
|
88
|
-
fc.seriesKey
|
|
88
|
+
fc.seriesKeys.includes(seriesKey) &&
|
|
89
89
|
item[seriesKey] &&
|
|
90
90
|
fc.style === 'Open Circles'
|
|
91
91
|
) {
|
|
@@ -98,7 +98,7 @@ export const filterCircles = (
|
|
|
98
98
|
}
|
|
99
99
|
if (
|
|
100
100
|
(!fc.value || item[fc.column] === fc.value) &&
|
|
101
|
-
fc.seriesKey
|
|
101
|
+
fc.seriesKeys.includes(seriesKey) &&
|
|
102
102
|
item[seriesKey] &&
|
|
103
103
|
fc.style === 'Filled Circles'
|
|
104
104
|
) {
|