@cdc/chart 4.24.1 → 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 +30014 -29757
- package/examples/feature/line/line-chart-preliminary.json +84 -37
- package/examples/feature/regions/index.json +9 -5
- package/index.html +4 -2
- package/package.json +2 -2
- package/src/CdcChart.tsx +41 -24
- package/src/_stories/ChartEditor.stories.tsx +1 -1
- package/src/_stories/_mock/pie_config.json +4 -3
- package/src/components/AreaChart/components/AreaChart.jsx +1 -25
- package/src/components/BarChart/components/BarChart.StackedVertical.tsx +7 -5
- package/src/components/BarChart/components/BarChart.Vertical.tsx +12 -13
- package/src/components/BoxPlot/BoxPlot.jsx +9 -8
- package/src/components/EditorPanel/EditorPanel.tsx +1563 -1959
- package/src/components/EditorPanel/EditorPanelContext.ts +40 -0
- package/src/components/EditorPanel/components/Panels/Panel.BoxPlot.tsx +148 -0
- package/src/components/EditorPanel/components/{Panel.ForestPlotSettings.tsx → Panels/Panel.ForestPlotSettings.tsx} +16 -7
- package/src/components/EditorPanel/components/Panels/Panel.General.tsx +160 -0
- package/src/components/EditorPanel/components/{Panel.Regions.tsx → Panels/Panel.Regions.tsx} +5 -5
- package/src/components/EditorPanel/components/{Panel.Series.tsx → 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/editor-panel.scss +1 -13
- package/src/components/EditorPanel/useEditorPermissions.js +5 -0
- package/src/components/Legend/Legend.Component.tsx +199 -0
- package/src/components/Legend/Legend.tsx +5 -324
- package/src/components/Legend/helpers/createFormatLabels.tsx +140 -0
- package/src/components/LineChart/LineChartProps.ts +1 -1
- package/src/components/LineChart/components/LineChart.Circle.tsx +85 -52
- package/src/components/LineChart/helpers.ts +2 -2
- package/src/components/LineChart/index.tsx +97 -21
- package/src/components/LinearChart.jsx +3 -3
- package/src/components/PairedBarChart.jsx +4 -2
- package/src/components/PieChart/PieChart.tsx +78 -25
- package/src/components/Regions/components/Regions.tsx +14 -5
- package/src/data/initial-state.js +5 -2
- package/src/helpers/computeMarginBottom.ts +2 -2
- package/src/hooks/useHighlightedBars.js +1 -1
- package/src/hooks/useMinMax.ts +3 -3
- package/src/hooks/useScales.ts +18 -5
- package/src/hooks/useTooltip.tsx +11 -7
- package/src/scss/main.scss +0 -67
- package/src/types/ChartConfig.ts +17 -8
- package/src/types/ChartContext.ts +10 -4
- package/src/types/Label.ts +7 -0
- package/examples/private/chart-t.json +0 -3740
- package/examples/private/combo.json +0 -369
- package/examples/private/epi-data.csv +0 -13
- package/examples/private/epi-data.json +0 -62
- package/examples/private/epi.json +0 -403
- package/examples/private/occupancy.json +0 -109283
- package/examples/private/prod-line-config.json +0 -401
- package/examples/private/region-data.json +0 -822
- package/examples/private/region-testing.json +0 -312
- package/examples/private/scaling.json +0 -45325
- package/examples/private/testing-data.json +0 -1739
- package/examples/private/testing.json +0 -816
- package/src/components/EditorPanel/components/Panel.DateHighlighting.tsx +0 -109
- package/src/components/EditorPanel/components/Panels.tsx +0 -13
|
@@ -62,7 +62,11 @@ const LineChart = (props: LineChartProps) => {
|
|
|
62
62
|
let displayArea = legend.behavior === 'highlight' || seriesHighlight.length === 0 || seriesHighlight.indexOf(seriesKey) !== -1
|
|
63
63
|
const circleData = filterCircles(config.preliminaryData, rawData, seriesKey)
|
|
64
64
|
// styles for preliminary Data items
|
|
65
|
-
let styles = createStyles({ preliminaryData: config.preliminaryData,
|
|
65
|
+
let styles = createStyles({ preliminaryData: config.preliminaryData, data: tableData, stroke: colorScale(config.runtime.seriesLabels[seriesKey]), handleLineType, lineType, seriesKey })
|
|
66
|
+
|
|
67
|
+
let xPos = d => {
|
|
68
|
+
return xScale(getXAxisData(d)) + (xScale.bandwidth ? xScale.bandwidth() / 2 : 0)
|
|
69
|
+
}
|
|
66
70
|
|
|
67
71
|
return (
|
|
68
72
|
<Group
|
|
@@ -91,12 +95,14 @@ const LineChart = (props: LineChartProps) => {
|
|
|
91
95
|
<Bar key={'bars'} width={Number(xMax)} height={Number(yMax)} fill={DEBUG ? 'red' : 'transparent'} fillOpacity={0.05} onMouseMove={e => handleTooltipMouseOver(e, tableData)} onMouseOut={handleTooltipMouseOff} onClick={e => handleTooltipClick(e, data)} />
|
|
92
96
|
|
|
93
97
|
{/* Render legend */}
|
|
94
|
-
<Text display={config.labels ? 'block' : 'none'} x={
|
|
98
|
+
<Text display={config.labels ? 'block' : 'none'} x={xPos(d)} y={seriesAxis === 'Right' ? yScaleRight(getYAxisData(d, seriesKey)) : yScale(getYAxisData(d, seriesKey))} fill={'#000'} textAnchor='middle'>
|
|
95
99
|
{formatNumber(d[seriesKey], 'left')}
|
|
96
100
|
</Text>
|
|
97
101
|
|
|
98
102
|
{(lineDatapointStyle === 'hidden' || lineDatapointStyle === 'always show') && (
|
|
99
103
|
<LineChartCircle
|
|
104
|
+
mode='ALWAYS_SHOW_POINTS'
|
|
105
|
+
dataIndex={dataIndex}
|
|
100
106
|
circleData={circleData}
|
|
101
107
|
data={data}
|
|
102
108
|
d={d}
|
|
@@ -113,34 +119,104 @@ const LineChart = (props: LineChartProps) => {
|
|
|
113
119
|
key={`line-circle--${dataIndex}`}
|
|
114
120
|
/>
|
|
115
121
|
)}
|
|
122
|
+
|
|
123
|
+
<LineChartCircle
|
|
124
|
+
mode='ISOLATED_POINTS'
|
|
125
|
+
dataIndex={dataIndex}
|
|
126
|
+
circleData={circleData}
|
|
127
|
+
data={data}
|
|
128
|
+
d={d}
|
|
129
|
+
config={config}
|
|
130
|
+
seriesKey={seriesKey}
|
|
131
|
+
displayArea={displayArea}
|
|
132
|
+
tooltipData={tooltipData}
|
|
133
|
+
xScale={xScale}
|
|
134
|
+
yScale={yScale}
|
|
135
|
+
colorScale={colorScale}
|
|
136
|
+
parseDate={parseDate}
|
|
137
|
+
yScaleRight={yScaleRight}
|
|
138
|
+
seriesAxis={seriesAxis}
|
|
139
|
+
key={`isolated-circle-${dataIndex}`}
|
|
140
|
+
/>
|
|
116
141
|
</Group>
|
|
117
142
|
)
|
|
118
143
|
)
|
|
119
144
|
})}
|
|
120
145
|
<>
|
|
121
146
|
{lineDatapointStyle === 'hover' && (
|
|
122
|
-
<LineChartCircle
|
|
147
|
+
<LineChartCircle
|
|
148
|
+
dataIndex={0}
|
|
149
|
+
mode='HOVER_POINTS'
|
|
150
|
+
circleData={circleData}
|
|
151
|
+
data={data}
|
|
152
|
+
config={config}
|
|
153
|
+
seriesKey={seriesKey}
|
|
154
|
+
displayArea={displayArea}
|
|
155
|
+
tooltipData={tooltipData}
|
|
156
|
+
xScale={xScale}
|
|
157
|
+
yScale={yScale}
|
|
158
|
+
colorScale={colorScale}
|
|
159
|
+
parseDate={parseDate}
|
|
160
|
+
yScaleRight={yScaleRight}
|
|
161
|
+
seriesAxis={seriesAxis}
|
|
162
|
+
/>
|
|
123
163
|
)}
|
|
124
164
|
</>
|
|
125
|
-
{/*
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
165
|
+
{/* SPLIT LINE */}
|
|
166
|
+
{config?.preliminaryData?.some(d => d.value && d.column) ? (
|
|
167
|
+
<SplitLinePath
|
|
168
|
+
curve={allCurves[seriesData[0].lineType]}
|
|
169
|
+
segments={(config.xAxis.type === 'date' && config.xAxis.sortDates
|
|
170
|
+
? data.sort((d1, d2) => {
|
|
171
|
+
let x1 = getXAxisData(d1)
|
|
172
|
+
let x2 = getXAxisData(d2)
|
|
173
|
+
if (x1 < x2) return -1
|
|
174
|
+
if (x2 < x1) return 1
|
|
175
|
+
return 0
|
|
176
|
+
})
|
|
177
|
+
: data
|
|
178
|
+
).map(d => [d])}
|
|
179
|
+
segmentation='x'
|
|
180
|
+
x={d => xPos(d)}
|
|
181
|
+
y={d => (seriesAxis === 'Right' ? yScaleRight(getYAxisData(d, seriesKey)) : yScale(Number(getYAxisData(d, seriesKey))))}
|
|
182
|
+
styles={styles}
|
|
183
|
+
defined={(item, i) => {
|
|
184
|
+
return item[seriesKey] !== '' && item[seriesKey] !== null && item[seriesKey] !== undefined
|
|
185
|
+
}}
|
|
186
|
+
/>
|
|
187
|
+
) : (
|
|
188
|
+
<>
|
|
189
|
+
{/* STANDARD LINE */}
|
|
190
|
+
<LinePath
|
|
191
|
+
curve={allCurves[seriesData[0].lineType]}
|
|
192
|
+
data={
|
|
193
|
+
config.xAxis.type === 'date' && config.xAxis.sortDates
|
|
194
|
+
? data.sort((d1, d2) => {
|
|
195
|
+
let x1 = getXAxisData(d1)
|
|
196
|
+
let x2 = getXAxisData(d2)
|
|
197
|
+
if (x1 < x2) return -1
|
|
198
|
+
if (x2 < x1) return 1
|
|
199
|
+
return 0
|
|
200
|
+
})
|
|
201
|
+
: data
|
|
202
|
+
}
|
|
203
|
+
x={d => xPos(d)}
|
|
204
|
+
y={d => (seriesAxis === 'Right' ? yScaleRight(getYAxisData(d, seriesKey)) : yScale(Number(getYAxisData(d, seriesKey))))}
|
|
205
|
+
stroke={colorScale(config.runtime.seriesLabels[seriesKey])}
|
|
206
|
+
strokeWidth={2}
|
|
207
|
+
strokeOpacity={1}
|
|
208
|
+
shapeRendering='geometricPrecision'
|
|
209
|
+
strokeDasharray={lineType ? handleLineType(lineType) : 0}
|
|
210
|
+
defined={(item, i) => {
|
|
211
|
+
return item[seriesKey] !== '' && item[seriesKey] !== null && item[seriesKey] !== undefined
|
|
212
|
+
}}
|
|
213
|
+
/>
|
|
214
|
+
</>
|
|
215
|
+
)}
|
|
140
216
|
|
|
141
217
|
{/* circles for preliminaryData data */}
|
|
142
218
|
{circleData.map((d, i) => {
|
|
143
|
-
return <circle key={i} cx={
|
|
219
|
+
return <circle key={i} cx={xPos(d)} cy={yScale(Number(getYAxisData(d, seriesKey)))} r={6} strokeWidth={2} stroke={colorScale ? colorScale(config.runtime.seriesLabels[seriesKey]) : '#000'} fill='#fff' />
|
|
144
220
|
})}
|
|
145
221
|
|
|
146
222
|
{/* ANIMATED LINE */}
|
|
@@ -149,7 +225,7 @@ const LineChart = (props: LineChartProps) => {
|
|
|
149
225
|
className='animation'
|
|
150
226
|
curve={seriesData.lineType}
|
|
151
227
|
data={data}
|
|
152
|
-
x={d =>
|
|
228
|
+
x={d => xPos(d)}
|
|
153
229
|
y={d => (seriesAxis === 'Right' ? yScaleRight(getYAxisData(d, seriesKey)) : yScale(Number(getYAxisData(d, seriesKey))))}
|
|
154
230
|
stroke='#fff'
|
|
155
231
|
strokeWidth={3}
|
|
@@ -175,7 +251,7 @@ const LineChart = (props: LineChartProps) => {
|
|
|
175
251
|
return <></>
|
|
176
252
|
}
|
|
177
253
|
return (
|
|
178
|
-
<text x={
|
|
254
|
+
<text x={xPos(lastDatum) + 5} y={yScale(getYAxisData(lastDatum, seriesKey))} alignmentBaseline='middle' fill={config.colorMatchLineSeriesLabels && colorScale ? colorScale(config.runtime.seriesLabels[seriesKey] || seriesKey) : 'black'}>
|
|
179
255
|
{config.runtime.seriesLabels[seriesKey] || seriesKey}
|
|
180
256
|
</text>
|
|
181
257
|
)
|
|
@@ -80,8 +80,8 @@ const LinearChart = props => {
|
|
|
80
80
|
yMax = yMax + config.data.length * config.forestPlot.rowHeight
|
|
81
81
|
width = dimensions[0]
|
|
82
82
|
}
|
|
83
|
-
if (config.brush
|
|
84
|
-
height = height + config.brush
|
|
83
|
+
if (config.brush?.active) {
|
|
84
|
+
height = height + config.brush?.height
|
|
85
85
|
}
|
|
86
86
|
|
|
87
87
|
// hooks % states
|
|
@@ -267,7 +267,7 @@ const LinearChart = props => {
|
|
|
267
267
|
<Bar width={width} height={height} fill={'transparent'}></Bar> {/* Highlighted regions */}
|
|
268
268
|
{/* Y axis */}
|
|
269
269
|
{!['Spark Line', 'Forest Plot'].includes(visualizationType) && (
|
|
270
|
-
<AxisLeft scale={yScale} tickLength={config.useLogScale ? 6 : 8} left={Number(runtime.yAxis.size) - config.yAxis.axisPadding} label={runtime.yAxis.label} stroke='#333' tickFormat={(tick, i) => handleLeftTickFormatting(tick, i)} numTicks={handleNumTicks()}>
|
|
270
|
+
<AxisLeft scale={yScale} tickLength={config.useLogScale ? 6 : 8} left={Number(runtime.yAxis.size) - config.yAxis.axisPadding} label={runtime.yAxis.yAxis?.label || runtime.yAxis.label} stroke='#333' tickFormat={(tick, i) => handleLeftTickFormatting(tick, i)} numTicks={handleNumTicks()}>
|
|
271
271
|
{props => {
|
|
272
272
|
const axisCenter = config.orientation === 'horizontal' ? (props.axisToPoint.y - props.axisFromPoint.y) / 2 : (props.axisFromPoint.y - props.axisToPoint.y) / 2
|
|
273
273
|
const horizontalTickOffset = yMax / props.ticks.length / 2 - (yMax / props.ticks.length) * (1 - config.barThickness) + 5
|
|
@@ -20,6 +20,7 @@ const PairedBarChart = ({ width, height, originalWidth }) => {
|
|
|
20
20
|
const groupOne = {
|
|
21
21
|
parentKey: config.dataDescription.seriesKey,
|
|
22
22
|
dataKey: config.series[0].dataKey,
|
|
23
|
+
dataKeyLabel: config.runtime.seriesLabels[config.series[0].dataKey] || config.series[0].dataKey,
|
|
23
24
|
color: colorScale(config.runtime.seriesLabels[config.series[0].dataKey]),
|
|
24
25
|
max: Math.max.apply(
|
|
25
26
|
Math,
|
|
@@ -31,6 +32,7 @@ const PairedBarChart = ({ width, height, originalWidth }) => {
|
|
|
31
32
|
const groupTwo = {
|
|
32
33
|
parentKey: config.dataDescription.seriesKey,
|
|
33
34
|
dataKey: config.series[1].dataKey,
|
|
35
|
+
dataKeyLabel: config.runtime.seriesLabels[config.series[1].dataKey] || config.series[1].dataKey,
|
|
34
36
|
color: colorScale(config.runtime.seriesLabels[config.series[1].dataKey]),
|
|
35
37
|
max: Math.max.apply(
|
|
36
38
|
Math,
|
|
@@ -59,7 +61,7 @@ const PairedBarChart = ({ width, height, originalWidth }) => {
|
|
|
59
61
|
|
|
60
62
|
const dataTipOne = d => {
|
|
61
63
|
return `<p>
|
|
62
|
-
${config.dataDescription.seriesKey}: ${groupOne.
|
|
64
|
+
${config.dataDescription.seriesKey}: ${groupOne.dataKeyLabel}<br/>
|
|
63
65
|
${config.xAxis.dataKey}: ${d[config.xAxis.dataKey]}<br/>
|
|
64
66
|
${label}${formatNumber(d[groupOne.dataKey], 'left')}
|
|
65
67
|
</p>`
|
|
@@ -67,7 +69,7 @@ const PairedBarChart = ({ width, height, originalWidth }) => {
|
|
|
67
69
|
|
|
68
70
|
const dataTipTwo = d => {
|
|
69
71
|
return `<p>
|
|
70
|
-
${config.dataDescription.seriesKey}: ${groupTwo.
|
|
72
|
+
${config.dataDescription.seriesKey}: ${groupTwo.dataKeyLabel}<br/>
|
|
71
73
|
${config.xAxis.dataKey}: ${d[config.xAxis.dataKey]}<br/>
|
|
72
74
|
${label}${formatNumber(d[groupTwo.dataKey], 'left')}
|
|
73
75
|
</p>`
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import React, { useContext, useState, useEffect, useRef } from 'react'
|
|
1
|
+
import React, { useContext, useState, useEffect, useRef, useMemo } from 'react'
|
|
2
2
|
import { animated, useTransition, interpolate } from 'react-spring'
|
|
3
3
|
import chroma from 'chroma-js'
|
|
4
4
|
|
|
@@ -7,6 +7,7 @@ import { Pie } from '@visx/shape'
|
|
|
7
7
|
import { Group } from '@visx/group'
|
|
8
8
|
import { Text } from '@visx/text'
|
|
9
9
|
import { useTooltip, TooltipWithBounds } from '@visx/tooltip'
|
|
10
|
+
import { colorPalettesChart as colorPalettes } from '@cdc/core/data/colorPalettes'
|
|
10
11
|
|
|
11
12
|
// cove
|
|
12
13
|
import ConfigContext from '../../ConfigContext'
|
|
@@ -14,6 +15,9 @@ import { useTooltip as useCoveTooltip } from '../../hooks/useTooltip'
|
|
|
14
15
|
import useIntersectionObserver from '../../hooks/useIntersectionObserver'
|
|
15
16
|
import { handleChartAriaLabels } from '../../helpers/handleChartAriaLabels'
|
|
16
17
|
import ErrorBoundary from '@cdc/core/components/ErrorBoundary'
|
|
18
|
+
import LegendComponent from '../Legend/Legend.Component'
|
|
19
|
+
import { createFormatLabels } from '../Legend/helpers/createFormatLabels'
|
|
20
|
+
import { scaleOrdinal } from '@visx/scale'
|
|
17
21
|
|
|
18
22
|
const enterUpdateTransition = ({ startAngle, endAngle }) => ({
|
|
19
23
|
startAngle,
|
|
@@ -29,7 +33,7 @@ type TooltipData = {
|
|
|
29
33
|
}
|
|
30
34
|
|
|
31
35
|
const PieChart = props => {
|
|
32
|
-
const { transformedData: data, config, dimensions,
|
|
36
|
+
const { transformedData: data, config, colorScale, currentViewport, dimensions, highlight, highlightReset, seriesHighlight } = useContext(ConfigContext)
|
|
33
37
|
const { tooltipData, showTooltip, hideTooltip, tooltipOpen, tooltipLeft, tooltipTop } = useTooltip<TooltipData>()
|
|
34
38
|
const { handleTooltipMouseOver, handleTooltipMouseOff, TooltipListItem } = useCoveTooltip({
|
|
35
39
|
xScale: false,
|
|
@@ -39,6 +43,50 @@ const PieChart = props => {
|
|
|
39
43
|
})
|
|
40
44
|
const [filteredData, setFilteredData] = useState(undefined)
|
|
41
45
|
const [animatedPie, setAnimatePie] = useState(false)
|
|
46
|
+
const pivotColumns = Object.values(config.columns).filter(column => column.showInViz)
|
|
47
|
+
const dataNeedsPivot = pivotColumns.length > 0
|
|
48
|
+
const pivotKey = dataNeedsPivot ? 'pivotColumn' : undefined
|
|
49
|
+
const _data = useMemo(() => {
|
|
50
|
+
if (dataNeedsPivot) {
|
|
51
|
+
let newData = []
|
|
52
|
+
const primaryColumn = config.yAxis.dataKey
|
|
53
|
+
const additionalColumns = pivotColumns.map(column => column.name)
|
|
54
|
+
const allColumns = [primaryColumn, ...additionalColumns]
|
|
55
|
+
const columnToUpdate = config.xAxis.dataKey
|
|
56
|
+
data.forEach(d => {
|
|
57
|
+
allColumns.forEach(col => {
|
|
58
|
+
const data = d[col]
|
|
59
|
+
if (data) {
|
|
60
|
+
newData.push({
|
|
61
|
+
[pivotKey]: data,
|
|
62
|
+
[columnToUpdate]: `${d[columnToUpdate]} - ${col}`
|
|
63
|
+
})
|
|
64
|
+
}
|
|
65
|
+
})
|
|
66
|
+
})
|
|
67
|
+
return newData
|
|
68
|
+
}
|
|
69
|
+
return data
|
|
70
|
+
}, [data, dataNeedsPivot])
|
|
71
|
+
|
|
72
|
+
const _colorScale = useMemo(() => {
|
|
73
|
+
if (dataNeedsPivot) {
|
|
74
|
+
const keys = {}
|
|
75
|
+
_data.forEach(d => {
|
|
76
|
+
if (!keys[d[config.xAxis.dataKey]]) keys[d[config.xAxis.dataKey]] = true
|
|
77
|
+
})
|
|
78
|
+
const numberOfKeys = Object.entries(keys).length
|
|
79
|
+
let palette = config.customColors || colorPalettes[config.palette]
|
|
80
|
+
palette = palette.slice(0, numberOfKeys)
|
|
81
|
+
|
|
82
|
+
return scaleOrdinal({
|
|
83
|
+
domain: Object.keys(keys),
|
|
84
|
+
range: palette,
|
|
85
|
+
unknown: null
|
|
86
|
+
})
|
|
87
|
+
}
|
|
88
|
+
return colorScale
|
|
89
|
+
}, [colorScale, dataNeedsPivot])
|
|
42
90
|
|
|
43
91
|
const triggerRef = useRef()
|
|
44
92
|
const dataRef = useIntersectionObserver(triggerRef, {
|
|
@@ -96,7 +144,7 @@ const PieChart = props => {
|
|
|
96
144
|
endAngle
|
|
97
145
|
})
|
|
98
146
|
)}
|
|
99
|
-
fill={
|
|
147
|
+
fill={_colorScale(arc.data[config.runtime.xAxis.dataKey])}
|
|
100
148
|
onMouseEnter={e => handleTooltipMouseOver(e, { data: arc.data[config.runtime.xAxis.dataKey], arc })}
|
|
101
149
|
onMouseLeave={e => handleTooltipMouseOff()}
|
|
102
150
|
/>
|
|
@@ -108,7 +156,7 @@ const PieChart = props => {
|
|
|
108
156
|
const hasSpaceForLabel = arc.endAngle - arc.startAngle >= 0.1
|
|
109
157
|
|
|
110
158
|
let textColor = '#FFF'
|
|
111
|
-
if (
|
|
159
|
+
if (_colorScale(arc.data[config.runtime.xAxis.dataKey]) && chroma.contrast(textColor, _colorScale(arc.data[config.runtime.xAxis.dataKey])) < 3.5) {
|
|
112
160
|
textColor = '000'
|
|
113
161
|
}
|
|
114
162
|
|
|
@@ -143,7 +191,7 @@ const PieChart = props => {
|
|
|
143
191
|
if (seriesHighlight.length > 0 && config.legend.behavior !== 'highlight') {
|
|
144
192
|
let newFilteredData = []
|
|
145
193
|
|
|
146
|
-
|
|
194
|
+
_data.forEach(d => {
|
|
147
195
|
if (seriesHighlight.indexOf(d[config.runtime.xAxis.dataKey]) !== -1) {
|
|
148
196
|
newFilteredData.push(d)
|
|
149
197
|
}
|
|
@@ -155,33 +203,38 @@ const PieChart = props => {
|
|
|
155
203
|
}
|
|
156
204
|
}, [seriesHighlight]) // eslint-disable-line
|
|
157
205
|
|
|
206
|
+
const createLegendLabels = createFormatLabels(config, [], _data, _colorScale)
|
|
207
|
+
|
|
158
208
|
return (
|
|
159
|
-
|
|
160
|
-
<
|
|
161
|
-
<
|
|
162
|
-
{
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
209
|
+
<>
|
|
210
|
+
<ErrorBoundary component='PieChart'>
|
|
211
|
+
<svg width={width} height={height} className={`animated-pie group ${config.animate === false || animatedPie ? 'animated' : ''}`} role='img' aria-label={handleChartAriaLabels(config)}>
|
|
212
|
+
<Group top={centerY} left={centerX}>
|
|
213
|
+
{/* prettier-ignore */}
|
|
214
|
+
<Pie
|
|
215
|
+
data={filteredData || _data}
|
|
216
|
+
pieValue={d => d[pivotKey || config.runtime.yAxis.dataKey]}
|
|
166
217
|
pieSortValues={() => -1}
|
|
167
218
|
innerRadius={radius - donutThickness}
|
|
168
219
|
outerRadius={radius}
|
|
169
220
|
>
|
|
170
221
|
{pie => <AnimatedPie {...pie} getKey={d => d.data[config.runtime.xAxis.dataKey]}/>}
|
|
171
222
|
</Pie>
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
223
|
+
</Group>
|
|
224
|
+
</svg>
|
|
225
|
+
<div ref={triggerRef} />
|
|
226
|
+
{tooltipData && Object.entries(tooltipData.data).length > 0 && tooltipOpen && showTooltip && tooltipData.dataYPosition && tooltipData.dataXPosition && (
|
|
227
|
+
<>
|
|
228
|
+
<style>{`.tooltip {background-color: rgba(255,255,255, ${config.tooltips.opacity / 100}) !important`}</style>
|
|
229
|
+
<TooltipWithBounds key={Math.random()} className={'tooltip cdc-open-viz-module'} left={tooltipLeft} top={tooltipTop}>
|
|
230
|
+
<ul>{typeof tooltipData === 'object' && Object.entries(tooltipData.data).map((item, index) => <TooltipListItem item={item} key={index} />)}</ul>
|
|
231
|
+
</TooltipWithBounds>
|
|
232
|
+
</>
|
|
233
|
+
)}
|
|
234
|
+
{/* <ReactTooltip id={`cdc-open-viz-tooltip-${config.runtime.uniqueId}`} variant='light' arrowColor='rgba(0,0,0,0)' className='tooltip' /> */}
|
|
235
|
+
</ErrorBoundary>
|
|
236
|
+
<LegendComponent config={config} colorScale={_colorScale} seriesHighlight={seriesHighlight} highlight={highlight} highlightReset={highlightReset} currentViewport={currentViewport} formatLabels={createLegendLabels} />
|
|
237
|
+
</>
|
|
185
238
|
)
|
|
186
239
|
}
|
|
187
240
|
|
|
@@ -5,11 +5,10 @@ import { ChartContext } from '../../../types/ChartContext'
|
|
|
5
5
|
import { Text } from '@visx/text'
|
|
6
6
|
import { Group } from '@visx/group'
|
|
7
7
|
import * as d3 from 'd3'
|
|
8
|
+
import { formatDate } from '@cdc/core/helpers/cove/date.js'
|
|
8
9
|
|
|
9
10
|
type RegionsProps = {
|
|
10
11
|
xScale: Function
|
|
11
|
-
barWidth: number
|
|
12
|
-
totalBarsInGroup: number
|
|
13
12
|
yMax: number
|
|
14
13
|
barWidth?: number
|
|
15
14
|
totalBarsInGroup?: number
|
|
@@ -60,9 +59,9 @@ const Regions = ({ xScale, barWidth = 0, totalBarsInGroup = 1, yMax, handleToolt
|
|
|
60
59
|
|
|
61
60
|
let previousDays = Number(region.from)
|
|
62
61
|
let lastDate = region.toType === 'Last Date' ? domain[domain.length - 1] : region.to
|
|
63
|
-
let
|
|
62
|
+
let toDate = new Date(lastDate)
|
|
64
63
|
|
|
65
|
-
from = new Date(
|
|
64
|
+
from = new Date(toDate.setDate(toDate.getDate() - previousDays)).getTime()
|
|
66
65
|
let targetValue = from
|
|
67
66
|
|
|
68
67
|
let index = bisectDate(domain, targetValue)
|
|
@@ -75,7 +74,6 @@ const Regions = ({ xScale, barWidth = 0, totalBarsInGroup = 1, yMax, handleToolt
|
|
|
75
74
|
let d1 = domain[index]
|
|
76
75
|
closestValue = targetValue - d0 > d1 - targetValue ? d1 : d0
|
|
77
76
|
}
|
|
78
|
-
|
|
79
77
|
from = Number(xScale(closestValue) - (visualizationType === 'Bar' || visualizationType === 'Combo' ? (barWidth * totalBarsInGroup) / 2 : 0))
|
|
80
78
|
|
|
81
79
|
width = to - from
|
|
@@ -89,6 +87,17 @@ const Regions = ({ xScale, barWidth = 0, totalBarsInGroup = 1, yMax, handleToolt
|
|
|
89
87
|
width = to - from
|
|
90
88
|
}
|
|
91
89
|
|
|
90
|
+
if (region.fromType === 'Previous Days' && xAxis.type === 'date' && xAxis.sortDates && config.visualizationType === 'Line') {
|
|
91
|
+
let domain = xScale.domain()
|
|
92
|
+
let previousDays = Number(region.from)
|
|
93
|
+
let to = region.toType === 'Last Date' ? formatDate(config.xAxis.dateParseFormat, domain[domain.length - 1]) : region.to
|
|
94
|
+
let toDate = new Date(to)
|
|
95
|
+
from = new Date(toDate.setDate(toDate.getDate() - previousDays)).getTime()
|
|
96
|
+
from = xScale(from)
|
|
97
|
+
to = xScale(parseDate(to))
|
|
98
|
+
width = to - from
|
|
99
|
+
}
|
|
100
|
+
|
|
92
101
|
if (!from) return null
|
|
93
102
|
if (!to) return null
|
|
94
103
|
|
|
@@ -53,6 +53,7 @@ export default {
|
|
|
53
53
|
rightAxisTickColor: '#333',
|
|
54
54
|
numTicks: '',
|
|
55
55
|
axisPadding: 0,
|
|
56
|
+
scalePadding: 10,
|
|
56
57
|
tickRotation: 0,
|
|
57
58
|
anchors: []
|
|
58
59
|
},
|
|
@@ -129,7 +130,8 @@ export default {
|
|
|
129
130
|
showDataTableLink: true,
|
|
130
131
|
indexLabel: '',
|
|
131
132
|
download: false,
|
|
132
|
-
showVertical: true
|
|
133
|
+
showVertical: true,
|
|
134
|
+
dateDisplayFormat: ''
|
|
133
135
|
},
|
|
134
136
|
orientation: 'vertical',
|
|
135
137
|
color: 'pinkpurple',
|
|
@@ -191,7 +193,8 @@ export default {
|
|
|
191
193
|
series: [],
|
|
192
194
|
tooltips: {
|
|
193
195
|
opacity: 90,
|
|
194
|
-
singleSeries: false
|
|
196
|
+
singleSeries: false,
|
|
197
|
+
dateDisplayFormat: ''
|
|
195
198
|
},
|
|
196
199
|
forestPlot: {
|
|
197
200
|
startAt: 0,
|
|
@@ -4,9 +4,9 @@ export const computeMarginBottom = (config: ChartConfig, legend: Legend, current
|
|
|
4
4
|
const isLegendBottom = legend.position === 'bottom' || ['sm', 'xs', 'xxs'].includes(currentViewport)
|
|
5
5
|
const isHorizontal = config.orientation === 'horizontal'
|
|
6
6
|
const tickRotation = Number(config.xAxis.tickRotation) > 0 ? Number(config.xAxis.tickRotation) : 0
|
|
7
|
-
const isBrush = config
|
|
7
|
+
const isBrush = config?.brush?.active
|
|
8
8
|
const offset = 20
|
|
9
|
-
const brushHeight = config
|
|
9
|
+
const brushHeight = config?.brush?.height
|
|
10
10
|
let bottom = 0
|
|
11
11
|
if (!isLegendBottom && isHorizontal && !config.yAxis.label) {
|
|
12
12
|
bottom = Number(config.xAxis.labelOffset)
|
|
@@ -127,7 +127,7 @@ export const useHighlightedBars = (config, updateConfig) => {
|
|
|
127
127
|
*/
|
|
128
128
|
HighLightedBarUtils.findDuplicates = arr => {
|
|
129
129
|
const duplicates = {}
|
|
130
|
-
const result = arr
|
|
130
|
+
const result = arr?.filter(obj => {
|
|
131
131
|
const { legendLabel } = obj
|
|
132
132
|
if (!duplicates[legendLabel]) {
|
|
133
133
|
duplicates[legendLabel] = true
|
package/src/hooks/useMinMax.ts
CHANGED
|
@@ -181,10 +181,10 @@ const useMinMax = ({ config, minValue, maxValue, existPositiveValue, data, isAll
|
|
|
181
181
|
if (config.yAxis.enablePadding) {
|
|
182
182
|
if (min < 0) {
|
|
183
183
|
// sets with negative data need more padding on the max
|
|
184
|
-
max *= 1.2
|
|
185
|
-
min *= 1.2
|
|
184
|
+
max *= 1 + (config.yAxis.scalePadding * 2) / 100
|
|
185
|
+
min *= 1 + (config.yAxis.scalePadding * 2) / 100
|
|
186
186
|
} else {
|
|
187
|
-
max *= 1.
|
|
187
|
+
max *= 1 + config.yAxis.scalePadding / 100
|
|
188
188
|
}
|
|
189
189
|
}
|
|
190
190
|
|
package/src/hooks/useScales.ts
CHANGED
|
@@ -56,21 +56,25 @@ const useScales = (properties: useScaleProps) => {
|
|
|
56
56
|
|
|
57
57
|
// handle Vertical bars
|
|
58
58
|
if (!isHorizontal) {
|
|
59
|
-
xScaleBrush = composeScalePoint(xAxisDataKeysMapped, [0, xMax],
|
|
60
|
-
xScale =
|
|
61
|
-
xScale.type = scaleTypes.POINT
|
|
59
|
+
xScaleBrush = composeScalePoint(xAxisDataKeysMapped, [0, xMax], .5)
|
|
60
|
+
xScale = composeScaleBand(xAxisDataMapped, [0, xMax], 1 - config.barThickness)
|
|
62
61
|
yScale = composeYScale(properties)
|
|
63
|
-
seriesScale =
|
|
62
|
+
seriesScale = composeScaleBand(seriesDomain, [0, xScale.bandwidth()], 0)
|
|
64
63
|
}
|
|
65
64
|
|
|
66
65
|
// handle Area chart
|
|
67
66
|
if (config.xAxis.type === 'date' && config.xAxis.sortDates) {
|
|
67
|
+
let xAxisMin = Math.min(...xAxisDataMapped)
|
|
68
|
+
let xAxisMax = Math.max(...xAxisDataMapped)
|
|
69
|
+
xAxisMin -= (config.xAxis.padding ? config.xAxis.padding * 0.01 : 0) * (xAxisMax - xAxisMin)
|
|
70
|
+
xAxisMax += (config.xAxis.padding ? config.xAxis.padding * 0.01 : 0) * (xAxisMax - xAxisMin)
|
|
68
71
|
xScale = scaleTime({
|
|
69
|
-
domain: [
|
|
72
|
+
domain: [xAxisMin, xAxisMax],
|
|
70
73
|
range: [0, xMax]
|
|
71
74
|
})
|
|
72
75
|
xScaleBrush = xScale
|
|
73
76
|
xScale.type = scaleTypes.LINEAR
|
|
77
|
+
seriesScale = composeScaleBand(seriesDomain, [0, config.barThickness * (xMax)], 0)
|
|
74
78
|
}
|
|
75
79
|
|
|
76
80
|
// handle Deviation bar
|
|
@@ -284,3 +288,12 @@ const composeScalePoint = (domain, range, padding = 0) => {
|
|
|
284
288
|
type: 'point'
|
|
285
289
|
})
|
|
286
290
|
}
|
|
291
|
+
|
|
292
|
+
const composeScaleBand = (domain, range, padding = 0) => {
|
|
293
|
+
return scaleBand({
|
|
294
|
+
domain: domain,
|
|
295
|
+
range: range,
|
|
296
|
+
padding: padding,
|
|
297
|
+
type: 'band'
|
|
298
|
+
})
|
|
299
|
+
}
|
package/src/hooks/useTooltip.tsx
CHANGED
|
@@ -11,9 +11,9 @@ const transform = new DataTransform()
|
|
|
11
11
|
import { formatNumber as formatColNumber } from '@cdc/core/helpers/cove/number'
|
|
12
12
|
|
|
13
13
|
export const useTooltip = props => {
|
|
14
|
-
const { tableData, config, formatNumber, capitalize, formatDate, parseDate, setSharedFilter } = useContext<ChartContext>(ConfigContext)
|
|
14
|
+
const { tableData, config, formatNumber, capitalize, formatDate, formatTooltipsDate, parseDate, setSharedFilter } = useContext<ChartContext>(ConfigContext)
|
|
15
15
|
const { xScale, yScale, showTooltip, hideTooltip } = props
|
|
16
|
-
const { xAxis, visualizationType, orientation, yAxis, runtime
|
|
16
|
+
const { xAxis, visualizationType, orientation, yAxis, runtime } = config
|
|
17
17
|
const data = transform.applySuppression(tableData, config.suppressedData)
|
|
18
18
|
/**
|
|
19
19
|
* Provides the tooltip information based on the tooltip data array and svg cursor coordinates
|
|
@@ -227,10 +227,12 @@ export const useTooltip = props => {
|
|
|
227
227
|
}
|
|
228
228
|
|
|
229
229
|
if (config.xAxis.type === 'categorical' || (visualizationType === 'Combo' && orientation !== 'horizontal' && visualizationType !== 'Forest Plot')) {
|
|
230
|
-
let
|
|
230
|
+
let range = xScale.range()[1] - xScale.range()[0]
|
|
231
|
+
let eachBand = range / (xScale.domain().length + 1)
|
|
232
|
+
|
|
231
233
|
let numerator = x
|
|
232
|
-
const index = Math.floor(Number(numerator) / eachBand)
|
|
233
|
-
return xScale.domain()[index
|
|
234
|
+
const index = Math.floor((Number(numerator) - eachBand / 2) / eachBand)
|
|
235
|
+
return xScale.domain()[index] // fixes off by 1 error
|
|
234
236
|
}
|
|
235
237
|
|
|
236
238
|
if (config.xAxis.type === 'date' && visualizationType !== 'Combo' && orientation !== 'horizontal') {
|
|
@@ -426,10 +428,12 @@ export const useTooltip = props => {
|
|
|
426
428
|
if (key === config.xAxis.dataKey) return <li className='tooltip-heading'>{`${capitalize(config.xAxis.dataKey ? `${config.xAxis.dataKey}: ` : '')} ${config.yAxis.type === 'date' ? formatDate(parseDate(key, false)) : value}`}</li>
|
|
427
429
|
return <li className='tooltip-body'>{`${getSeriesNameFromLabel(key)}: ${formatNumber(value, 'left')}`}</li>
|
|
428
430
|
}
|
|
431
|
+
const formattedDate = config.tooltips.dateDisplayFormat ? formatTooltipsDate(parseDate(value, false)) : formatDate(parseDate(value, false))
|
|
429
432
|
|
|
430
433
|
// TOOLTIP HEADING
|
|
431
|
-
if (visualizationType === 'Bar' && orientation === 'horizontal' && key === config.xAxis.dataKey) return <li className='tooltip-heading'>{`${capitalize(config.runtime.yAxis.label ? `${config.runtime.yAxis.label}: ` : '')} ${value}`}</li>
|
|
432
|
-
|
|
434
|
+
if (visualizationType === 'Bar' && orientation === 'horizontal' && key === config.xAxis.dataKey) return <li className='tooltip-heading'>{`${capitalize(config.runtime.yAxis.label ? `${config.runtime.yAxis.label}: ` : '')} ${config.xAxis.type === 'date' ? formattedDate : value}`}</li>
|
|
435
|
+
|
|
436
|
+
if (key === config.xAxis.dataKey) return <li className='tooltip-heading'>{`${capitalize(config.runtime.xAxis.label ? `${config.runtime.xAxis.label}: ` : '')} ${config.xAxis.type === 'date' ? formattedDate : value}`}</li>
|
|
433
437
|
|
|
434
438
|
// TOOLTIP BODY
|
|
435
439
|
return <li className='tooltip-body'>{`${getSeriesNameFromLabel(key)}: ${value}`}</li>
|