@cdc/chart 4.23.11 → 4.24.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cdcchart.js +35740 -35027
- package/examples/feature/bar/additional-column-tooltip.json +446 -0
- package/examples/feature/bar/tall-data.json +98 -0
- package/examples/feature/forest-plot/forest-plot.json +63 -19
- package/examples/feature/forest-plot/linear.json +52 -3
- package/examples/feature/forest-plot/log.json +26 -0
- package/examples/feature/forest-plot/logarithmic.json +0 -35
- package/examples/feature/line/line-chart-preliminary.json +393 -0
- package/examples/feature/regions/index.json +9 -5
- package/examples/feature/scatterplot/scatterplot.json +272 -33
- package/index.html +10 -8
- package/package.json +2 -2
- package/src/CdcChart.tsx +70 -234
- package/src/ConfigContext.tsx +6 -0
- package/src/_stories/ChartEditor.stories.tsx +22 -0
- package/src/_stories/ChartLine.preliminary.tsx +19 -0
- package/src/_stories/_mock/pie_config.json +192 -0
- package/src/_stories/_mock/pie_data.json +218 -0
- package/src/_stories/_mock/preliminary_mock.json +346 -0
- package/src/components/{AreaChart.Stacked.jsx → AreaChart/components/AreaChart.Stacked.jsx} +2 -2
- package/src/components/{AreaChart.jsx → AreaChart/components/AreaChart.jsx} +2 -26
- package/src/components/AreaChart/index.tsx +4 -0
- package/src/components/{BarChart.Horizontal.tsx → BarChart/components/BarChart.Horizontal.tsx} +8 -8
- package/src/components/{BarChart.StackedHorizontal.tsx → BarChart/components/BarChart.StackedHorizontal.tsx} +37 -7
- package/src/components/BarChart/components/BarChart.StackedVertical.tsx +108 -0
- package/src/components/{BarChart.Vertical.tsx → BarChart/components/BarChart.Vertical.tsx} +53 -70
- package/src/components/BarChart/components/BarChart.jsx +39 -0
- package/src/components/{BarChartType.jsx → BarChart/components/BarChartType.jsx} +0 -2
- package/src/components/BarChart/components/context.tsx +13 -0
- package/src/components/BarChart/index.tsx +3 -0
- package/src/components/{BoxPlot.jsx → BoxPlot/BoxPlot.jsx} +10 -9
- package/src/components/BoxPlot/index.tsx +3 -0
- package/src/components/EditorPanel/EditorPanel.tsx +2776 -0
- package/src/components/EditorPanel/EditorPanelContext.ts +40 -0
- package/src/components/EditorPanel/components/PanelProps.ts +3 -0
- package/src/components/EditorPanel/components/Panels/Panel.BoxPlot.tsx +148 -0
- package/src/components/{ForestPlotSettings.jsx → EditorPanel/components/Panels/Panel.ForestPlotSettings.tsx} +97 -167
- package/src/components/EditorPanel/components/Panels/Panel.General.tsx +160 -0
- package/src/components/EditorPanel/components/Panels/Panel.Regions.tsx +168 -0
- package/src/components/{Series.jsx → EditorPanel/components/Panels/Panel.Series.tsx} +4 -4
- package/src/components/EditorPanel/components/Panels/Panel.Visual.tsx +297 -0
- package/src/components/EditorPanel/components/Panels/index.tsx +17 -0
- package/src/components/EditorPanel/components/panels.scss +72 -0
- package/src/components/EditorPanel/editor-panel.scss +739 -0
- package/src/components/EditorPanel/index.tsx +3 -0
- package/src/{hooks → components/EditorPanel}/useEditorPermissions.js +34 -2
- package/src/components/{Forecasting.jsx → Forecasting/Forecasting.jsx} +1 -1
- package/src/components/Forecasting/index.tsx +3 -0
- package/src/components/ForestPlot/ForestPlot.tsx +254 -0
- package/src/components/ForestPlot/ForestPlotProps.ts +7 -0
- package/src/components/ForestPlot/index.tsx +1 -209
- package/src/components/Legend/Legend.Component.tsx +199 -0
- package/src/components/Legend/Legend.tsx +28 -0
- package/src/components/Legend/helpers/createFormatLabels.tsx +140 -0
- package/src/components/Legend/index.tsx +3 -0
- package/src/components/LineChart/LineChartProps.ts +29 -0
- package/src/components/LineChart/components/LineChart.Circle.tsx +147 -0
- package/src/components/LineChart/helpers.ts +45 -0
- package/src/components/LineChart/index.tsx +111 -23
- package/src/components/LinearChart.jsx +55 -72
- package/src/components/PairedBarChart.jsx +4 -2
- package/src/components/{PieChart.jsx → PieChart/PieChart.tsx} +93 -31
- package/src/components/PieChart/index.tsx +3 -0
- package/src/components/Regions/components/Regions.tsx +144 -0
- package/src/components/Regions/index.tsx +3 -0
- package/src/components/{ScatterPlot.jsx → ScatterPlot/ScatterPlot.jsx} +3 -3
- package/src/components/ScatterPlot/index.tsx +3 -0
- package/src/components/{SparkLine.jsx → Sparkline/SparkLine.jsx} +2 -2
- package/src/components/Sparkline/index.tsx +3 -0
- package/src/data/initial-state.js +10 -8
- package/src/helpers/abbreviateNumber.ts +17 -0
- package/src/helpers/computeMarginBottom.ts +55 -0
- package/src/helpers/filterData.ts +18 -0
- package/src/helpers/generateColorsArray.ts +8 -0
- package/src/helpers/getQuartiles.ts +30 -0
- package/src/helpers/handleChartAriaLabels.ts +19 -0
- package/src/helpers/handleLineType.ts +18 -0
- package/src/helpers/lineOptions.ts +18 -0
- package/src/helpers/sort.ts +7 -0
- package/src/helpers/tests/computeMarginBottom.test.ts +20 -0
- package/src/hooks/useBarChart.js +7 -6
- package/src/hooks/useHighlightedBars.js +1 -1
- package/src/hooks/useMinMax.ts +3 -3
- package/src/hooks/useScales.ts +19 -6
- package/src/hooks/{useTooltip.jsx → useTooltip.tsx} +31 -25
- package/src/scss/main.scss +0 -3
- package/src/types/ChartConfig.ts +167 -23
- package/src/types/ChartContext.ts +34 -12
- package/src/types/ForestPlot.ts +7 -14
- package/src/types/Label.ts +7 -0
- package/examples/feature/scatterplot/scatterplot-continuous.csv +0 -17
- package/src/ConfigContext.jsx +0 -5
- package/src/components/BarChart.StackedVertical.tsx +0 -91
- package/src/components/BarChart.jsx +0 -30
- package/src/components/EditorPanel.jsx +0 -3356
- package/src/components/ForestPlot/Readme.md +0 -0
- package/src/components/Legend.jsx +0 -310
- package/src/components/LineChart/LineChart.Circle.tsx +0 -105
- package/src/scss/LinearChart.scss +0 -0
- package/src/scss/editor-panel.scss +0 -745
- package/src/scss/legend.scss +0 -206
- package/src/scss/mixins.scss +0 -0
- package/src/scss/variables.scss +0 -1
- package/src/types/ChartProps.ts +0 -7
|
@@ -1,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,21 +7,34 @@ 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
|
-
import ConfigContext from '
|
|
13
|
-
import { useTooltip as useCoveTooltip } from '
|
|
14
|
-
import useIntersectionObserver from '
|
|
13
|
+
import ConfigContext from '../../ConfigContext'
|
|
14
|
+
import { useTooltip as useCoveTooltip } from '../../hooks/useTooltip'
|
|
15
|
+
import useIntersectionObserver from '../../hooks/useIntersectionObserver'
|
|
16
|
+
import { handleChartAriaLabels } from '../../helpers/handleChartAriaLabels'
|
|
15
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'
|
|
16
21
|
|
|
17
22
|
const enterUpdateTransition = ({ startAngle, endAngle }) => ({
|
|
18
23
|
startAngle,
|
|
19
24
|
endAngle
|
|
20
25
|
})
|
|
21
26
|
|
|
27
|
+
type TooltipData = {
|
|
28
|
+
data: {
|
|
29
|
+
[key: string]: string | number
|
|
30
|
+
}
|
|
31
|
+
dataXPosition: number
|
|
32
|
+
dataYPosition: number
|
|
33
|
+
}
|
|
34
|
+
|
|
22
35
|
const PieChart = props => {
|
|
23
|
-
const { transformedData: data, config,
|
|
24
|
-
const { tooltipData, showTooltip, hideTooltip, tooltipOpen, tooltipLeft, tooltipTop } = useTooltip()
|
|
36
|
+
const { transformedData: data, config, colorScale, currentViewport, dimensions, highlight, highlightReset, seriesHighlight } = useContext(ConfigContext)
|
|
37
|
+
const { tooltipData, showTooltip, hideTooltip, tooltipOpen, tooltipLeft, tooltipTop } = useTooltip<TooltipData>()
|
|
25
38
|
const { handleTooltipMouseOver, handleTooltipMouseOff, TooltipListItem } = useCoveTooltip({
|
|
26
39
|
xScale: false,
|
|
27
40
|
yScale: false,
|
|
@@ -30,6 +43,50 @@ const PieChart = props => {
|
|
|
30
43
|
})
|
|
31
44
|
const [filteredData, setFilteredData] = useState(undefined)
|
|
32
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])
|
|
33
90
|
|
|
34
91
|
const triggerRef = useRef()
|
|
35
92
|
const dataRef = useIntersectionObserver(triggerRef, {
|
|
@@ -87,24 +144,24 @@ const PieChart = props => {
|
|
|
87
144
|
endAngle
|
|
88
145
|
})
|
|
89
146
|
)}
|
|
90
|
-
fill={
|
|
147
|
+
fill={_colorScale(arc.data[config.runtime.xAxis.dataKey])}
|
|
91
148
|
onMouseEnter={e => handleTooltipMouseOver(e, { data: arc.data[config.runtime.xAxis.dataKey], arc })}
|
|
92
149
|
onMouseLeave={e => handleTooltipMouseOff()}
|
|
93
150
|
/>
|
|
94
151
|
</Group>
|
|
95
152
|
)
|
|
96
153
|
})}
|
|
97
|
-
{transitions.map(({ item: arc, key }) => {
|
|
154
|
+
{transitions.map(({ item: arc, key }, i) => {
|
|
98
155
|
const [centroidX, centroidY] = path.centroid(arc)
|
|
99
156
|
const hasSpaceForLabel = arc.endAngle - arc.startAngle >= 0.1
|
|
100
157
|
|
|
101
158
|
let textColor = '#FFF'
|
|
102
|
-
if (
|
|
159
|
+
if (_colorScale(arc.data[config.runtime.xAxis.dataKey]) && chroma.contrast(textColor, _colorScale(arc.data[config.runtime.xAxis.dataKey])) < 3.5) {
|
|
103
160
|
textColor = '000'
|
|
104
161
|
}
|
|
105
162
|
|
|
106
163
|
return (
|
|
107
|
-
<animated.g key={key}>
|
|
164
|
+
<animated.g key={`${key}${i}`}>
|
|
108
165
|
{hasSpaceForLabel && (
|
|
109
166
|
<Text style={{ fill: textColor }} x={centroidX} y={centroidY} dy='.33em' textAnchor='middle' pointerEvents='none'>
|
|
110
167
|
{Math.round((((arc.endAngle - arc.startAngle) * 180) / Math.PI / 360) * 100) + '%'}
|
|
@@ -134,7 +191,7 @@ const PieChart = props => {
|
|
|
134
191
|
if (seriesHighlight.length > 0 && config.legend.behavior !== 'highlight') {
|
|
135
192
|
let newFilteredData = []
|
|
136
193
|
|
|
137
|
-
|
|
194
|
+
_data.forEach(d => {
|
|
138
195
|
if (seriesHighlight.indexOf(d[config.runtime.xAxis.dataKey]) !== -1) {
|
|
139
196
|
newFilteredData.push(d)
|
|
140
197
|
}
|
|
@@ -146,33 +203,38 @@ const PieChart = props => {
|
|
|
146
203
|
}
|
|
147
204
|
}, [seriesHighlight]) // eslint-disable-line
|
|
148
205
|
|
|
206
|
+
const createLegendLabels = createFormatLabels(config, [], _data, _colorScale)
|
|
207
|
+
|
|
149
208
|
return (
|
|
150
|
-
|
|
151
|
-
<
|
|
152
|
-
<
|
|
153
|
-
{
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
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]}
|
|
157
217
|
pieSortValues={() => -1}
|
|
158
218
|
innerRadius={radius - donutThickness}
|
|
159
219
|
outerRadius={radius}
|
|
160
220
|
>
|
|
161
221
|
{pie => <AnimatedPie {...pie} getKey={d => d.data[config.runtime.xAxis.dataKey]}/>}
|
|
162
222
|
</Pie>
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
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
|
+
</>
|
|
176
238
|
)
|
|
177
239
|
}
|
|
178
240
|
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
import React, { useContext } from 'react'
|
|
2
|
+
import { ChartConfig } from '../../../types/ChartConfig'
|
|
3
|
+
import ConfigContext from '../../../ConfigContext'
|
|
4
|
+
import { ChartContext } from '../../../types/ChartContext'
|
|
5
|
+
import { Text } from '@visx/text'
|
|
6
|
+
import { Group } from '@visx/group'
|
|
7
|
+
import * as d3 from 'd3'
|
|
8
|
+
import { formatDate } from '@cdc/core/helpers/cove/date.js'
|
|
9
|
+
|
|
10
|
+
type RegionsProps = {
|
|
11
|
+
xScale: Function
|
|
12
|
+
yMax: number
|
|
13
|
+
barWidth?: number
|
|
14
|
+
totalBarsInGroup?: number
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
const Regions = ({ xScale, barWidth = 0, totalBarsInGroup = 1, yMax, handleTooltipMouseOff, handleTooltipMouseOver, handleTooltipClick, tooltipData, showTooltip, hideTooltip }: RegionsProps) => {
|
|
18
|
+
const { parseDate, config } = useContext<ChartContext>(ConfigContext)
|
|
19
|
+
|
|
20
|
+
const { runtime, regions, visualizationType, orientation, xAxis } = config
|
|
21
|
+
|
|
22
|
+
let from
|
|
23
|
+
let to
|
|
24
|
+
let width
|
|
25
|
+
|
|
26
|
+
if (regions && orientation === 'vertical') {
|
|
27
|
+
return regions.map(region => {
|
|
28
|
+
if (xAxis.type === 'date' && region.fromType !== 'Previous Days') {
|
|
29
|
+
from = xScale(parseDate(region.from).getTime())
|
|
30
|
+
to = xScale(parseDate(region.to).getTime())
|
|
31
|
+
width = to - from
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
if (xAxis.type === 'categorical') {
|
|
35
|
+
from = xScale(region.from)
|
|
36
|
+
to = xScale(region.to)
|
|
37
|
+
width = to - from
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
if ((visualizationType === 'Bar' || config.visualizationType === 'Combo') && xAxis.type === 'date') {
|
|
41
|
+
from = region.fromType !== 'Previous Days' ? xScale(parseDate(region.from).getTime()) - (barWidth * totalBarsInGroup) / 2 : null
|
|
42
|
+
to = region.toType !== 'Last Date' ? xScale(parseDate(region.to).getTime()) + (barWidth * totalBarsInGroup) / 2 : null
|
|
43
|
+
|
|
44
|
+
width = to - from
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
if ((visualizationType === 'Bar' || config.visualizationType === 'Combo') && config.xAxis.type === 'categorical') {
|
|
48
|
+
from = xScale(region.from)
|
|
49
|
+
to = xScale(region.to)
|
|
50
|
+
width = to - from
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
if (region.fromType === 'Previous Days') {
|
|
54
|
+
to = region.toType !== 'Last Date' ? xScale(parseDate(region.to).getTime()) + (barWidth * totalBarsInGroup) / 2 : null
|
|
55
|
+
|
|
56
|
+
let domain = xScale.domain()
|
|
57
|
+
let bisectDate = d3.bisector(d => d).left
|
|
58
|
+
let closestValue
|
|
59
|
+
|
|
60
|
+
let previousDays = Number(region.from)
|
|
61
|
+
let lastDate = region.toType === 'Last Date' ? domain[domain.length - 1] : region.to
|
|
62
|
+
let toDate = new Date(lastDate)
|
|
63
|
+
|
|
64
|
+
from = new Date(toDate.setDate(toDate.getDate() - previousDays)).getTime()
|
|
65
|
+
let targetValue = from
|
|
66
|
+
|
|
67
|
+
let index = bisectDate(domain, targetValue)
|
|
68
|
+
if (index === 0) {
|
|
69
|
+
closestValue = domain[0]
|
|
70
|
+
} else if (index === domain.length) {
|
|
71
|
+
closestValue = domain[domain.length - 1]
|
|
72
|
+
} else {
|
|
73
|
+
let d0 = domain[index - 1]
|
|
74
|
+
let d1 = domain[index]
|
|
75
|
+
closestValue = targetValue - d0 > d1 - targetValue ? d1 : d0
|
|
76
|
+
}
|
|
77
|
+
from = Number(xScale(closestValue) - (visualizationType === 'Bar' || visualizationType === 'Combo' ? (barWidth * totalBarsInGroup) / 2 : 0))
|
|
78
|
+
|
|
79
|
+
width = to - from
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// set the region max to the charts max range.
|
|
83
|
+
if (region.toType === 'Last Date') {
|
|
84
|
+
let domainValues = xScale.domain()
|
|
85
|
+
let lastDate = domainValues[domainValues.length - 1]
|
|
86
|
+
to = Number(xScale(lastDate) + (visualizationType === 'Bar' || visualizationType === 'Combo' ? (barWidth * totalBarsInGroup) / 2 : 0))
|
|
87
|
+
width = to - from
|
|
88
|
+
}
|
|
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
|
+
|
|
101
|
+
if (!from) return null
|
|
102
|
+
if (!to) return null
|
|
103
|
+
|
|
104
|
+
const TopRegionBorderShape = () => {
|
|
105
|
+
return (
|
|
106
|
+
<path
|
|
107
|
+
stroke='#333'
|
|
108
|
+
d={`M${from} -5
|
|
109
|
+
L${from} 5
|
|
110
|
+
M${from} 0
|
|
111
|
+
L${to} 0
|
|
112
|
+
M${to} -5
|
|
113
|
+
L${to} 5`}
|
|
114
|
+
/>
|
|
115
|
+
)
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
const HighlightedArea = () => {
|
|
119
|
+
return <rect x={from} y={0} width={width} height={yMax} fill={region.background} opacity={0.3} />
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
return (
|
|
123
|
+
<Group
|
|
124
|
+
className='regions regions-group--line'
|
|
125
|
+
left={config.visualizationType === 'Bar' || config.visualizationType === 'Combo' ? 0 : config?.visualizationType === 'Line' ? Number(runtime.yAxis.size) : 0}
|
|
126
|
+
key={region.label}
|
|
127
|
+
onMouseMove={handleTooltipMouseOver}
|
|
128
|
+
onMouseLeave={handleTooltipMouseOff}
|
|
129
|
+
handleTooltipClick={handleTooltipClick}
|
|
130
|
+
tooltipData={JSON.stringify(tooltipData)}
|
|
131
|
+
showTooltip={showTooltip}
|
|
132
|
+
>
|
|
133
|
+
<TopRegionBorderShape />
|
|
134
|
+
<HighlightedArea />
|
|
135
|
+
<Text x={from + width / 2} y={5} fill={region.color} verticalAnchor='start' textAnchor='middle'>
|
|
136
|
+
{region.label}
|
|
137
|
+
</Text>
|
|
138
|
+
</Group>
|
|
139
|
+
)
|
|
140
|
+
})
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
export default Regions
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import React, { useContext } from 'react'
|
|
2
|
-
import ConfigContext from '
|
|
2
|
+
import ConfigContext from '../../ConfigContext'
|
|
3
3
|
import { Group } from '@visx/group'
|
|
4
4
|
|
|
5
|
-
const
|
|
5
|
+
const ScatterPlot = ({ xScale, yScale, getXAxisData, getYAxisData }) => {
|
|
6
6
|
const { colorScale, transformedData: data, config, formatNumber, seriesHighlight, colorPalettes } = useContext(ConfigContext)
|
|
7
7
|
|
|
8
8
|
// TODO: copied from line chart should probably be a constant somewhere.
|
|
@@ -48,4 +48,4 @@ const CoveScatterPlot = ({ xScale, yScale, getXAxisData, getYAxisData }) => {
|
|
|
48
48
|
</Group>
|
|
49
49
|
)
|
|
50
50
|
}
|
|
51
|
-
export default
|
|
51
|
+
export default ScatterPlot
|
|
@@ -11,9 +11,9 @@ import { MarkerArrow } from '@visx/marker'
|
|
|
11
11
|
|
|
12
12
|
import ErrorBoundary from '@cdc/core/components/ErrorBoundary'
|
|
13
13
|
|
|
14
|
-
import useReduceData from '
|
|
14
|
+
import useReduceData from '../../hooks/useReduceData'
|
|
15
15
|
|
|
16
|
-
import ConfigContext from '
|
|
16
|
+
import ConfigContext from '../../ConfigContext'
|
|
17
17
|
|
|
18
18
|
const SparkLine = props => {
|
|
19
19
|
const { width: parentWidth, height: parentHeight } = props
|
|
@@ -29,6 +29,7 @@ export default {
|
|
|
29
29
|
right: 5
|
|
30
30
|
},
|
|
31
31
|
suppressedData: [],
|
|
32
|
+
preliminaryData: [],
|
|
32
33
|
|
|
33
34
|
yAxis: {
|
|
34
35
|
hideAxis: false,
|
|
@@ -52,6 +53,7 @@ export default {
|
|
|
52
53
|
rightAxisTickColor: '#333',
|
|
53
54
|
numTicks: '',
|
|
54
55
|
axisPadding: 0,
|
|
56
|
+
scalePadding: 10,
|
|
55
57
|
tickRotation: 0,
|
|
56
58
|
anchors: []
|
|
57
59
|
},
|
|
@@ -128,7 +130,8 @@ export default {
|
|
|
128
130
|
showDataTableLink: true,
|
|
129
131
|
indexLabel: '',
|
|
130
132
|
download: false,
|
|
131
|
-
showVertical: true
|
|
133
|
+
showVertical: true,
|
|
134
|
+
dateDisplayFormat: ''
|
|
132
135
|
},
|
|
133
136
|
orientation: 'vertical',
|
|
134
137
|
color: 'pinkpurple',
|
|
@@ -138,7 +141,7 @@ export default {
|
|
|
138
141
|
legend: {
|
|
139
142
|
hide: false,
|
|
140
143
|
behavior: 'isolate',
|
|
141
|
-
singleRow:
|
|
144
|
+
singleRow: true,
|
|
142
145
|
colorCode: '',
|
|
143
146
|
reverseLabelOrder: false,
|
|
144
147
|
description: '',
|
|
@@ -190,7 +193,8 @@ export default {
|
|
|
190
193
|
series: [],
|
|
191
194
|
tooltips: {
|
|
192
195
|
opacity: 90,
|
|
193
|
-
singleSeries: false
|
|
196
|
+
singleSeries: false,
|
|
197
|
+
dateDisplayFormat: ''
|
|
194
198
|
},
|
|
195
199
|
forestPlot: {
|
|
196
200
|
startAt: 0,
|
|
@@ -208,7 +212,7 @@ export default {
|
|
|
208
212
|
},
|
|
209
213
|
estimateField: '',
|
|
210
214
|
estimateRadius: '',
|
|
211
|
-
shape: '',
|
|
215
|
+
shape: 'square',
|
|
212
216
|
rowHeight: 20,
|
|
213
217
|
description: {
|
|
214
218
|
show: true,
|
|
@@ -221,8 +225,8 @@ export default {
|
|
|
221
225
|
location: 100
|
|
222
226
|
},
|
|
223
227
|
radius: {
|
|
224
|
-
min:
|
|
225
|
-
max:
|
|
228
|
+
min: 2,
|
|
229
|
+
max: 10,
|
|
226
230
|
scalingColumn: ''
|
|
227
231
|
},
|
|
228
232
|
regression: {
|
|
@@ -233,11 +237,9 @@ export default {
|
|
|
233
237
|
leftWidthOffset: 0,
|
|
234
238
|
rightWidthOffset: 0,
|
|
235
239
|
showZeroLine: false,
|
|
236
|
-
hideDateCategoryCol: false,
|
|
237
240
|
leftLabel: '',
|
|
238
241
|
rightLabel: ''
|
|
239
242
|
},
|
|
240
|
-
|
|
241
243
|
area: {
|
|
242
244
|
isStacked: false
|
|
243
245
|
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
export const abbreviateNumber = num => {
|
|
2
|
+
let unit = ''
|
|
3
|
+
let absNum = Math.abs(num)
|
|
4
|
+
|
|
5
|
+
if (absNum >= 1e9) {
|
|
6
|
+
unit = 'B'
|
|
7
|
+
num = num / 1e9
|
|
8
|
+
} else if (absNum >= 1e6) {
|
|
9
|
+
unit = 'M'
|
|
10
|
+
num = num / 1e6
|
|
11
|
+
} else if (absNum >= 1e3) {
|
|
12
|
+
unit = 'K'
|
|
13
|
+
num = num / 1e3
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
return num + unit
|
|
17
|
+
}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { ChartConfig, Legend } from '../types/ChartConfig'
|
|
2
|
+
|
|
3
|
+
export const computeMarginBottom = (config: ChartConfig, legend: Legend, currentViewport: string): string | number => {
|
|
4
|
+
const isLegendBottom = legend.position === 'bottom' || ['sm', 'xs', 'xxs'].includes(currentViewport)
|
|
5
|
+
const isHorizontal = config.orientation === 'horizontal'
|
|
6
|
+
const tickRotation = Number(config.xAxis.tickRotation) > 0 ? Number(config.xAxis.tickRotation) : 0
|
|
7
|
+
const isBrush = config?.brush?.active
|
|
8
|
+
const offset = 20
|
|
9
|
+
const brushHeight = config?.brush?.height
|
|
10
|
+
let bottom = 0
|
|
11
|
+
if (!isLegendBottom && isHorizontal && !config.yAxis.label) {
|
|
12
|
+
bottom = Number(config.xAxis.labelOffset)
|
|
13
|
+
}
|
|
14
|
+
if (!isLegendBottom && isHorizontal && config.yAxis.label && !config.isResponsiveTicks) {
|
|
15
|
+
bottom = Number(config.runtime.xAxis.size) + Number(config.xAxis.labelOffset)
|
|
16
|
+
}
|
|
17
|
+
if (!isLegendBottom && isHorizontal && config.yAxis.label && config.isResponsiveTicks) {
|
|
18
|
+
bottom = config.dynamicMarginTop + offset
|
|
19
|
+
}
|
|
20
|
+
if (!isLegendBottom && isHorizontal && !config.yAxis.label && config.isResponsiveTicks) {
|
|
21
|
+
bottom = config.dynamicMarginTop ? config.dynamicMarginTop - offset : Number(config.xAxis.labelOffset) - offset
|
|
22
|
+
}
|
|
23
|
+
if (!isLegendBottom && isHorizontal && config.yAxis.label && config.isResponsiveTicks) {
|
|
24
|
+
bottom = config.dynamicMarginTop ? config.dynamicMarginTop + offset : Number(config.xAxis.labelOffset)
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
if (!isHorizontal && !isLegendBottom && config.xAxis.label && tickRotation && !config.isResponsiveTicks) {
|
|
28
|
+
bottom = isBrush ? brushHeight + config.xAxis.tickWidthMax + -config.xAxis.size + config.xAxis.labelOffset + offset : config.xAxis.tickWidthMax + offset + -config.xAxis.size + config.xAxis.labelOffset
|
|
29
|
+
}
|
|
30
|
+
if (!isHorizontal && !isLegendBottom && !config.xAxis.label && tickRotation && !config.isResponsiveTicks) {
|
|
31
|
+
}
|
|
32
|
+
if (!isHorizontal && !isLegendBottom && !config.xAxis.label && tickRotation && !config.dynamicMarginTop && !config.isResponsiveTicks) {
|
|
33
|
+
bottom = isBrush ? config.xAxis.tickWidthMax + brushHeight + offset + -config.xAxis.size : 0
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
if (!isHorizontal && !isLegendBottom && config.xAxis.label && !tickRotation && !config.isResponsiveTicks) {
|
|
37
|
+
bottom = isBrush ? brushHeight + -config.xAxis.size + config.xAxis.labelOffset + offset : -config.xAxis.size + config.xAxis.labelOffset + offset
|
|
38
|
+
}
|
|
39
|
+
if (!isHorizontal && !isLegendBottom && config.xAxis.label && config.dynamicMarginTop && config.isResponsiveTicks) {
|
|
40
|
+
bottom = isBrush ? brushHeight + config.xAxis.labelOffset + -config.xAxis.size + config.xAxis.tickWidthMax : config.dynamicMarginTop + -config.xAxis.size + offset
|
|
41
|
+
}
|
|
42
|
+
if (!isHorizontal && !isLegendBottom && !config.xAxis.label && config.dynamicMarginTop && config.isResponsiveTicks) {
|
|
43
|
+
bottom = isBrush ? brushHeight + config.xAxis.labelOffset + -config.xAxis.size + config.xAxis.tickWidthMax : config.dynamicMarginTop + -config.xAxis.size - offset
|
|
44
|
+
}
|
|
45
|
+
if (!isHorizontal && !isLegendBottom && config.xAxis.label && !config.dynamicMarginTop && config.isResponsiveTicks) {
|
|
46
|
+
bottom = isBrush ? brushHeight + config.xAxis.labelOffset + -config.xAxis.size + 25 : config.xAxis.labelOffset + -config.xAxis.size + offset
|
|
47
|
+
}
|
|
48
|
+
if (!isHorizontal && !isLegendBottom && !config.xAxis.label && !config.dynamicMarginTop && config.isResponsiveTicks) {
|
|
49
|
+
bottom = -config.xAxis.size + offset + config.xAxis.labelOffset
|
|
50
|
+
}
|
|
51
|
+
if (!isHorizontal && !isLegendBottom && !config.xAxis.label && !tickRotation && !config.dynamicMarginTop && !config.isResponsiveTicks) {
|
|
52
|
+
bottom = isBrush ? brushHeight + -config.xAxis.size + config.xAxis.labelOffset : 0
|
|
53
|
+
}
|
|
54
|
+
return `${bottom}px`
|
|
55
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
export const filterData = (filters, data) => {
|
|
2
|
+
let filteredData: any[] = []
|
|
3
|
+
|
|
4
|
+
data.forEach(row => {
|
|
5
|
+
let add = true
|
|
6
|
+
filters
|
|
7
|
+
.filter(filter => filter.type !== 'url')
|
|
8
|
+
.forEach(filter => {
|
|
9
|
+
if (row[filter.columnName] != filter.active) {
|
|
10
|
+
add = false
|
|
11
|
+
}
|
|
12
|
+
})
|
|
13
|
+
|
|
14
|
+
if (add) filteredData.push(row)
|
|
15
|
+
})
|
|
16
|
+
|
|
17
|
+
return filteredData
|
|
18
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import chroma from 'chroma-js'
|
|
2
|
+
|
|
3
|
+
export const generateColorsArray = (color = '#000000', special = false) => {
|
|
4
|
+
let colorObj = chroma(color)
|
|
5
|
+
let hoverColor = special ? colorObj.brighten(0.5).hex() : colorObj.saturate(1.3).hex()
|
|
6
|
+
|
|
7
|
+
return [color, hoverColor, colorObj.darken(0.3).hex()]
|
|
8
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Calculates the first quartile (q1) and third quartile (q3) from an array of integers or decimals.
|
|
3
|
+
*
|
|
4
|
+
* @param {Array} arr - The array of integers or decimals.
|
|
5
|
+
* @returns {Object} An object containing the q1 and q3 values.
|
|
6
|
+
*/
|
|
7
|
+
export const getQuartiles = arr => {
|
|
8
|
+
arr.sort((a, b) => a - b)
|
|
9
|
+
|
|
10
|
+
// Calculate the index of the median value of the array
|
|
11
|
+
const medianIndex = Math.floor(arr.length / 2)
|
|
12
|
+
|
|
13
|
+
// Check if the length of the array is even or odd
|
|
14
|
+
const isEvenLength = arr.length % 2 === 0
|
|
15
|
+
|
|
16
|
+
// Split the array into two subarrays based on the median index
|
|
17
|
+
const q1Array = isEvenLength ? arr.slice(0, medianIndex) : arr.slice(0, medianIndex + 1)
|
|
18
|
+
const q3Array = isEvenLength ? arr.slice(medianIndex) : arr.slice(medianIndex + 1)
|
|
19
|
+
|
|
20
|
+
// Calculate the median of the first subarray to get the q1 value
|
|
21
|
+
const q1Index = Math.floor(q1Array.length / 2)
|
|
22
|
+
const q1 = isEvenLength ? (q1Array[q1Index - 1] + q1Array[q1Index]) / 2 : q1Array[q1Index]
|
|
23
|
+
|
|
24
|
+
// Calculate the median of the second subarray to get the q3 value
|
|
25
|
+
const q3Index = Math.floor(q3Array.length / 2)
|
|
26
|
+
const q3 = isEvenLength ? (q3Array[q3Index - 1] + q3Array[q3Index]) / 2 : q3Array[q3Index]
|
|
27
|
+
|
|
28
|
+
// Return an object containing the q1 and q3 values
|
|
29
|
+
return { q1, q3 }
|
|
30
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
export const handleChartAriaLabels = (state, testing = false) => {
|
|
2
|
+
if (testing) console.log(`handleChartAriaLabels Testing On:`, state) // eslint-disable-line
|
|
3
|
+
try {
|
|
4
|
+
if (!state.visualizationType) throw Error('handleChartAriaLabels: no visualization type found in state')
|
|
5
|
+
let ariaLabel = ''
|
|
6
|
+
|
|
7
|
+
if (state.visualizationType) {
|
|
8
|
+
ariaLabel += `${state.visualizationType} chart`
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
if (state.title && state.visualizationType) {
|
|
12
|
+
ariaLabel += ` with the title: ${state.title}`
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
return ariaLabel
|
|
16
|
+
} catch (e) {
|
|
17
|
+
console.error('COVE: ', e.message) // eslint-disable-line
|
|
18
|
+
}
|
|
19
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
export const handleLineType = lineType => {
|
|
2
|
+
switch (lineType) {
|
|
3
|
+
case 'dashed-sm':
|
|
4
|
+
return '5 5'
|
|
5
|
+
case 'Dashed Small':
|
|
6
|
+
return '5 5'
|
|
7
|
+
case 'dashed-md':
|
|
8
|
+
return '10 5'
|
|
9
|
+
case 'Dashed Medium':
|
|
10
|
+
return '10 5'
|
|
11
|
+
case 'dashed-lg':
|
|
12
|
+
return '15 5'
|
|
13
|
+
case 'Dashed Large':
|
|
14
|
+
return '15 5'
|
|
15
|
+
default:
|
|
16
|
+
return 0
|
|
17
|
+
}
|
|
18
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
export const lineOptions = [
|
|
2
|
+
{
|
|
3
|
+
value: 'Dashed Small',
|
|
4
|
+
key: 'dashed-sm'
|
|
5
|
+
},
|
|
6
|
+
{
|
|
7
|
+
value: 'Dashed Medium',
|
|
8
|
+
key: 'dashed-md'
|
|
9
|
+
},
|
|
10
|
+
{
|
|
11
|
+
value: 'Dashed Large',
|
|
12
|
+
key: 'dashed-lg'
|
|
13
|
+
},
|
|
14
|
+
{
|
|
15
|
+
value: 'Solid Line',
|
|
16
|
+
key: 'solid-line'
|
|
17
|
+
}
|
|
18
|
+
]
|