@cdc/chart 4.23.4 → 4.23.6
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 +54845 -51755
- package/examples/feature/__data__/planet-example-data.json +14 -32
- package/examples/feature/__data__/planet-logaritmic-data.json +56 -0
- package/examples/feature/area/area-chart-category.json +240 -0
- package/examples/feature/bar/example-bar-chart.json +544 -22
- package/examples/feature/bar/new.json +561 -0
- package/examples/feature/bar/planet-chart-logaritmic-config.json +170 -0
- package/examples/feature/boxplot/valid-boxplot.csv +17 -0
- package/examples/feature/combo/right-issues.json +190 -0
- package/examples/feature/filters/filter-testing.json +37 -3
- package/examples/feature/forecasting/combo-forecasting.json +245 -0
- package/examples/feature/forecasting/forecasting.json +5325 -0
- package/examples/feature/forecasting/index.json +203 -0
- package/examples/feature/forecasting/random_data.csv +366 -0
- package/examples/feature/line/line-chart.json +3 -3
- package/examples/feature/test-highlight/test-highlight-2.json +789 -0
- package/examples/feature/test-highlight/test-highlight-vertical.json +561 -0
- package/examples/feature/test-highlight/test-highlight.json +100 -0
- package/examples/feature/tests-non-numerics/stacked-vertical-bar-example-nonnumerics.json +1 -2
- package/examples/gallery/bar-chart-horizontal/horizontal-highlight.json +345 -0
- package/examples/gallery/line/line.json +173 -1
- package/index.html +14 -8
- package/package.json +2 -2
- package/src/CdcChart.jsx +342 -25
- package/src/components/AreaChart.jsx +32 -40
- package/src/components/BarChart.jsx +147 -25
- package/src/components/DataTable.jsx +30 -12
- package/src/components/DeviationBar.jsx +32 -32
- package/src/components/EditorPanel.jsx +1902 -1126
- package/src/components/Forecasting.jsx +147 -0
- package/src/components/Legend.jsx +193 -243
- package/src/components/LineChart.jsx +4 -9
- package/src/components/LinearChart.jsx +263 -285
- package/src/components/Series.jsx +518 -0
- package/src/components/SparkLine.jsx +3 -3
- package/src/data/initial-state.js +24 -5
- package/src/hooks/useHighlightedBars.js +154 -0
- package/src/hooks/useMinMax.js +128 -0
- package/src/hooks/useReduceData.js +31 -57
- package/src/hooks/useRightAxis.js +8 -2
- package/src/hooks/useScales.js +196 -0
- /package/examples/feature/area/{area-chart.json → area-chart-date.json} +0 -0
|
@@ -27,7 +27,7 @@ const CoveAreaChart = ({ xScale, yScale, yMax, xMax, chartRef }) => {
|
|
|
27
27
|
const tooltip_id = `cdc-open-viz-tooltip-${config.runtime.uniqueId}`
|
|
28
28
|
|
|
29
29
|
// import tooltip helpers
|
|
30
|
-
const { tooltipData, showTooltip } = useTooltip()
|
|
30
|
+
const { tooltipData, showTooltip, hideTooltip } = useTooltip()
|
|
31
31
|
|
|
32
32
|
// here we're inside of the svg,
|
|
33
33
|
// it appears we need to use TooltipInPortal.
|
|
@@ -40,23 +40,19 @@ const CoveAreaChart = ({ xScale, yScale, yMax, xMax, chartRef }) => {
|
|
|
40
40
|
// Draw transparent bars over the chart to get tooltip data
|
|
41
41
|
// Turn DEBUG on for additional context.
|
|
42
42
|
if (!data) return
|
|
43
|
-
let barThickness = xMax / data
|
|
44
|
-
let barThicknessAdjusted = barThickness * (config.barThickness || 0.8)
|
|
45
|
-
let offset = (barThickness * (1 - (config.barThickness || 0.8))) / 2
|
|
43
|
+
let barThickness = xMax / data.length
|
|
46
44
|
|
|
47
45
|
// Tooltip helper for getting data to the closest date/category hovered.
|
|
48
46
|
const getXValueFromCoordinate = x => {
|
|
49
|
-
if (config.xAxis.type === 'categorical') {
|
|
47
|
+
if (config.xAxis.type === 'categorical' || config.visualizationType === 'Combo') {
|
|
50
48
|
let eachBand = xScale.step()
|
|
51
49
|
let numerator = x
|
|
52
50
|
const index = Math.floor(Number(numerator) / eachBand)
|
|
53
51
|
return xScale.domain()[index - 1] // fixes off by 1 error
|
|
54
52
|
}
|
|
55
53
|
|
|
56
|
-
if (config.xAxis.type === 'date') {
|
|
54
|
+
if (config.xAxis.type === 'date' && config.visualizationType !== 'Combo') {
|
|
57
55
|
const bisectDate = bisector(d => parseDate(d[config.xAxis.dataKey])).left
|
|
58
|
-
if (!x) return
|
|
59
|
-
if (!xScale) return
|
|
60
56
|
const x0 = xScale.invert(x)
|
|
61
57
|
const index = bisectDate(config.data, x0, 1)
|
|
62
58
|
const val = parseDate(config.data[index - 1][config.xAxis.dataKey])
|
|
@@ -89,6 +85,9 @@ const CoveAreaChart = ({ xScale, yScale, yMax, xMax, chartRef }) => {
|
|
|
89
85
|
if (!yScaleValues[0]) return
|
|
90
86
|
for (const item of Object.entries(yScaleValues[0])) {
|
|
91
87
|
if (item[0] === seriesKey) {
|
|
88
|
+
// let userUpdatedSeriesName = config.series.filter(series => series.dataKey === item[0])?.[0]?.name
|
|
89
|
+
// if (userUpdatedSeriesName) item[0] = userUpdatedSeriesName
|
|
90
|
+
|
|
92
91
|
seriesToInclude.push(item)
|
|
93
92
|
}
|
|
94
93
|
}
|
|
@@ -97,6 +96,7 @@ const CoveAreaChart = ({ xScale, yScale, yMax, xMax, chartRef }) => {
|
|
|
97
96
|
// filter out the series that aren't added to the map.
|
|
98
97
|
seriesToInclude.map(series => yScaleMaxValues.push(Number(yScaleValues[0][series])))
|
|
99
98
|
if (!seriesToInclude) return
|
|
99
|
+
|
|
100
100
|
let tooltipDataFromSeries = Object.fromEntries(seriesToInclude) ? Object.fromEntries(seriesToInclude) : {}
|
|
101
101
|
|
|
102
102
|
let tooltipData = {}
|
|
@@ -123,8 +123,8 @@ const CoveAreaChart = ({ xScale, yScale, yMax, xMax, chartRef }) => {
|
|
|
123
123
|
return config.xAxis.type === 'date' ? xScale(parseDate(d[config.xAxis.dataKey])) : xScale(d[config.xAxis.dataKey])
|
|
124
124
|
}
|
|
125
125
|
|
|
126
|
-
const handleY = (d, index) => {
|
|
127
|
-
return yScale(d[
|
|
126
|
+
const handleY = (d, index, s = undefined) => {
|
|
127
|
+
return yScale(d[s.dataKey])
|
|
128
128
|
}
|
|
129
129
|
|
|
130
130
|
return (
|
|
@@ -132,21 +132,30 @@ const CoveAreaChart = ({ xScale, yScale, yMax, xMax, chartRef }) => {
|
|
|
132
132
|
<ErrorBoundary component='AreaChart'>
|
|
133
133
|
<Group className='area-chart' key='area-wrapper' left={Number(config.yAxis.size)}>
|
|
134
134
|
{(config.runtime.areaSeriesKeys || config.runtime.seriesKeys).map((s, index) => {
|
|
135
|
-
let
|
|
135
|
+
let seriesData = data.map(d => {
|
|
136
|
+
return {
|
|
137
|
+
[config.xAxis.dataKey]: d[config.xAxis.dataKey],
|
|
138
|
+
[s.dataKey]: d[s.dataKey]
|
|
139
|
+
}
|
|
140
|
+
})
|
|
141
|
+
|
|
136
142
|
let curveType = allCurves[s.lineType]
|
|
137
143
|
let transparentArea = config.legend.behavior === 'highlight' && seriesHighlight.length > 0 && seriesHighlight.indexOf(s.dataKey) === -1
|
|
138
144
|
let displayArea = config.legend.behavior === 'highlight' || seriesHighlight.length === 0 || seriesHighlight.indexOf(s.dataKey) !== -1
|
|
139
145
|
|
|
140
|
-
|
|
141
|
-
|
|
146
|
+
if (config.xAxis.type === 'date') {
|
|
147
|
+
data.map(d => xScale(parseDate(d[config.xAxis.dataKey])))
|
|
148
|
+
} else {
|
|
149
|
+
data.map(d => xScale(d[config.xAxis.dataKey]))
|
|
150
|
+
}
|
|
142
151
|
return (
|
|
143
152
|
<React.Fragment key={index}>
|
|
144
153
|
{/* prettier-ignore */}
|
|
145
154
|
<LinePath
|
|
146
|
-
data={
|
|
155
|
+
data={seriesData}
|
|
147
156
|
x={d => handleX(d)}
|
|
148
|
-
y={d =>
|
|
149
|
-
stroke={displayArea ?
|
|
157
|
+
y={d => handleY(d, index, s)}
|
|
158
|
+
stroke={displayArea ? colorScale ? colorScale(config.runtime.seriesLabels ? config.runtime.seriesLabels[s.dataKey] : s.dataKey) : '#000' : 'transparent'}
|
|
150
159
|
strokeWidth={2}
|
|
151
160
|
strokeOpacity={1}
|
|
152
161
|
shapeRendering='geometricPrecision'
|
|
@@ -159,11 +168,12 @@ const CoveAreaChart = ({ xScale, yScale, yMax, xMax, chartRef }) => {
|
|
|
159
168
|
key={'area-chart'}
|
|
160
169
|
fill={ displayArea ? colorScale ? colorScale(config.runtime.seriesLabels ? config.runtime.seriesLabels[s.dataKey] : s.dataKey) : '#000' : 'transparent'}
|
|
161
170
|
fillOpacity={transparentArea ? 0.25 : 0.5}
|
|
162
|
-
data={
|
|
163
|
-
|
|
171
|
+
data={seriesData}
|
|
172
|
+
x={d => handleX(d)}
|
|
173
|
+
y={d => handleY(d, index, s)}
|
|
164
174
|
yScale={yScale}
|
|
165
175
|
curve={curveType}
|
|
166
|
-
strokeDasharray={s.type ? handleLineType(s.
|
|
176
|
+
strokeDasharray={s.type ? handleLineType(s.type) : 0}
|
|
167
177
|
/>
|
|
168
178
|
|
|
169
179
|
{/* Transparent bar for tooltips */}
|
|
@@ -175,10 +185,11 @@ const CoveAreaChart = ({ xScale, yScale, yMax, xMax, chartRef }) => {
|
|
|
175
185
|
fillOpacity={0.05}
|
|
176
186
|
style={DEBUG ? { stroke: 'black', strokeWidth: 2 } : {}}
|
|
177
187
|
onMouseMove={e => handleMouseOver(e, data)}
|
|
188
|
+
onMouseOut={hideTooltip}
|
|
178
189
|
/>
|
|
179
190
|
|
|
180
191
|
{/* circles that appear on hover */}
|
|
181
|
-
{tooltipData && (
|
|
192
|
+
{tooltipData && Object.entries(tooltipData.data).length > 0 && (
|
|
182
193
|
<circle
|
|
183
194
|
cx={config.xAxis.type === 'categorical' ? xScale(tooltipData.data[config.xAxis.dataKey]) : xScale(parseDate(tooltipData.data[config.xAxis.dataKey]))}
|
|
184
195
|
cy={yScale(tooltipData.data[s.dataKey])}
|
|
@@ -190,26 +201,7 @@ const CoveAreaChart = ({ xScale, yScale, yMax, xMax, chartRef }) => {
|
|
|
190
201
|
/>
|
|
191
202
|
)}
|
|
192
203
|
|
|
193
|
-
{
|
|
194
|
-
{DEBUG &&
|
|
195
|
-
data.map((item, index) => {
|
|
196
|
-
return (
|
|
197
|
-
<Bar
|
|
198
|
-
className='bar-here'
|
|
199
|
-
x={Number(barThickness * index + offset)}
|
|
200
|
-
y={d => Number(yScale(d[config.series[index].dataKey]))}
|
|
201
|
-
yScale={yScale}
|
|
202
|
-
width={barThicknessAdjusted}
|
|
203
|
-
height={yMax}
|
|
204
|
-
fill={'transparent'}
|
|
205
|
-
fillOpacity={1}
|
|
206
|
-
style={{ stroke: 'black', strokeWidth: 2 }}
|
|
207
|
-
onMouseMove={e => handleMouseOver(e, data)}
|
|
208
|
-
/>
|
|
209
|
-
)
|
|
210
|
-
})}
|
|
211
|
-
|
|
212
|
-
{tooltipData && (
|
|
204
|
+
{tooltipData && Object.entries(tooltipData.data).length > 0 && (
|
|
213
205
|
<TooltipInPortal key={Math.random()} top={tooltipData.dataYPosition + chartPosition?.top} left={tooltipData.dataXPosition + chartPosition?.left} style={defaultStyles}>
|
|
214
206
|
<ul style={{ listStyle: 'none', paddingLeft: 'unset', fontFamily: 'sans-serif', margin: 'auto', lineHeight: '1rem' }} data-tooltip-id={tooltip_id}>
|
|
215
207
|
{typeof tooltipData === 'object' &&
|
|
@@ -6,13 +6,15 @@ import chroma from 'chroma-js'
|
|
|
6
6
|
import ErrorBoundary from '@cdc/core/components/ErrorBoundary'
|
|
7
7
|
import ConfigContext from '../ConfigContext'
|
|
8
8
|
import { BarStackHorizontal } from '@visx/shape'
|
|
9
|
+
import { useHighlightedBars } from '../hooks/useHighlightedBars'
|
|
10
|
+
import { act } from 'react-dom/test-utils'
|
|
9
11
|
|
|
10
12
|
export default function BarChart({ xScale, yScale, seriesScale, xMax, yMax, getXAxisData, getYAxisData, animatedChart, visible }) {
|
|
11
|
-
const { transformedData: data, colorScale, seriesHighlight, config, formatNumber, updateConfig, colorPalettes, formatDate, isNumber, getTextWidth, parseDate } = useContext(ConfigContext)
|
|
12
|
-
|
|
13
|
+
const { transformedData: data, colorScale, seriesHighlight, config, formatNumber, updateConfig, colorPalettes, tableData, formatDate, isNumber, getTextWidth, parseDate, setSharedFilter, setSharedFilterValue, dashboardConfig } = useContext(ConfigContext)
|
|
14
|
+
const { HighLightedBarUtils } = useHighlightedBars(config)
|
|
13
15
|
const { orientation, visualizationSubType } = config
|
|
14
16
|
const isHorizontal = orientation === 'horizontal'
|
|
15
|
-
|
|
17
|
+
const barBorderWidth = 1
|
|
16
18
|
const lollipopBarWidth = config.lollipopSize === 'large' ? 7 : config.lollipopSize === 'medium' ? 6 : 5
|
|
17
19
|
const lollipopShapeSize = config.lollipopSize === 'large' ? 14 : config.lollipopSize === 'medium' ? 12 : 10
|
|
18
20
|
|
|
@@ -25,12 +27,11 @@ export default function BarChart({ xScale, yScale, seriesScale, xMax, yMax, getX
|
|
|
25
27
|
const tipRounding = config.tipRounding
|
|
26
28
|
const radius = config.roundingStyle === 'standard' ? '8px' : config.roundingStyle === 'shallow' ? '5px' : config.roundingStyle === 'finger' ? '15px' : '0px'
|
|
27
29
|
const stackCount = config.runtime.seriesKeys.length
|
|
28
|
-
const barBorderWidth = 1
|
|
29
30
|
const fontSize = { small: 16, medium: 18, large: 20 }
|
|
30
31
|
const hasMultipleSeries = Object.keys(config.runtime.seriesLabels).length > 1
|
|
31
32
|
|
|
32
33
|
const applyRadius = index => {
|
|
33
|
-
if (index === undefined || index === null || !isRounded) return
|
|
34
|
+
if (index === undefined || index === null || !isRounded) return {}
|
|
34
35
|
let style = {}
|
|
35
36
|
|
|
36
37
|
if ((isStacked && index + 1 === stackCount) || !isStacked) {
|
|
@@ -48,6 +49,27 @@ export default function BarChart({ xScale, yScale, seriesScale, xMax, yMax, getX
|
|
|
48
49
|
return style
|
|
49
50
|
}
|
|
50
51
|
|
|
52
|
+
const assignColorsToValues = () => {
|
|
53
|
+
const palettesArr = colorPalettes[config.palette]
|
|
54
|
+
const values = tableData.map(d => {
|
|
55
|
+
return d[config.legend.colorCode]
|
|
56
|
+
})
|
|
57
|
+
// Map to hold unique values and their colors
|
|
58
|
+
let colorMap = new Map()
|
|
59
|
+
// Resultant array to hold colors to the values
|
|
60
|
+
let result = []
|
|
61
|
+
|
|
62
|
+
for (let i = 0; i < values.length; i++) {
|
|
63
|
+
// If value not in map, add it and assign a color
|
|
64
|
+
if (!colorMap.has(values[i])) {
|
|
65
|
+
colorMap.set(values[i], palettesArr[colorMap.size % palettesArr.length])
|
|
66
|
+
}
|
|
67
|
+
// push the colosr to the result array
|
|
68
|
+
result.push(colorMap.get(values[i]))
|
|
69
|
+
}
|
|
70
|
+
return result
|
|
71
|
+
}
|
|
72
|
+
|
|
51
73
|
const updateBars = defaultBars => {
|
|
52
74
|
// function updates stacked && regular && lollipop horizontal bars
|
|
53
75
|
if (config.visualizationType !== 'Bar' && !isHorizontal) return defaultBars
|
|
@@ -167,7 +189,7 @@ export default function BarChart({ xScale, yScale, seriesScale, xMax, yMax, getX
|
|
|
167
189
|
`}
|
|
168
190
|
</style>
|
|
169
191
|
<Group key={`bar-stack-${barStack.index}-${bar.index}`} id={`barStack${barStack.index}-${bar.index}`} className='stack vertical'>
|
|
170
|
-
<Text display={config.labels && displayBar ? 'block' : 'none'} opacity={transparentBar ? 0.5 : 1} x={barThickness * bar.index + offset} y={bar.y - 5} fill={
|
|
192
|
+
<Text display={config.labels && displayBar ? 'block' : 'none'} opacity={transparentBar ? 0.5 : 1} x={barThickness * bar.index + offset} y={bar.y - 5} fill={'#000'} textAnchor='middle'>
|
|
171
193
|
{yAxisValue}
|
|
172
194
|
</Text>
|
|
173
195
|
<foreignObject
|
|
@@ -181,6 +203,13 @@ export default function BarChart({ xScale, yScale, seriesScale, xMax, yMax, getX
|
|
|
181
203
|
display={displayBar ? 'block' : 'none'}
|
|
182
204
|
data-tooltip-html={tooltip}
|
|
183
205
|
data-tooltip-id={`cdc-open-viz-tooltip-${config.runtime.uniqueId}`}
|
|
206
|
+
onClick={e => {
|
|
207
|
+
e.preventDefault()
|
|
208
|
+
if (setSharedFilter) {
|
|
209
|
+
bar[config.xAxis.dataKey] = xAxisValue
|
|
210
|
+
setSharedFilter(config.uid, bar)
|
|
211
|
+
}
|
|
212
|
+
}}
|
|
184
213
|
></foreignObject>
|
|
185
214
|
</Group>
|
|
186
215
|
</Group>
|
|
@@ -245,6 +274,13 @@ export default function BarChart({ xScale, yScale, seriesScale, xMax, yMax, getX
|
|
|
245
274
|
display={displayBar ? 'block' : 'none'}
|
|
246
275
|
data-tooltip-html={tooltip}
|
|
247
276
|
data-tooltip-id={`cdc-open-viz-tooltip-${config.runtime.uniqueId}`}
|
|
277
|
+
onClick={e => {
|
|
278
|
+
e.preventDefault()
|
|
279
|
+
if (setSharedFilter) {
|
|
280
|
+
bar[config.xAxis.dataKey] = xAxisValue
|
|
281
|
+
setSharedFilter(config.uid, bar)
|
|
282
|
+
}
|
|
283
|
+
}}
|
|
248
284
|
></foreignObject>
|
|
249
285
|
|
|
250
286
|
{orientation === 'horizontal' && visualizationSubType === 'stacked' && isLabelBelowBar && barStack.index === 0 && !config.yAxis.hideLabel && (
|
|
@@ -313,23 +349,44 @@ export default function BarChart({ xScale, yScale, seriesScale, xMax, yMax, getX
|
|
|
313
349
|
left={config.runtime.horizontal ? 0 : (xMax / barGroups.length) * barGroup.index}
|
|
314
350
|
>
|
|
315
351
|
{barGroup.bars.map((bar, index) => {
|
|
352
|
+
const scaleVal = config.useLogScale ? 0.1 : 0
|
|
353
|
+
const getHighlightedBarColorByValue = value => {
|
|
354
|
+
const match = config?.highlightedBarValues.filter(item => {
|
|
355
|
+
if (!item.value) return
|
|
356
|
+
return config.xAxis.type === 'date' ? formatDate(parseDate(item.value)) === value : item.value === value
|
|
357
|
+
})[0]
|
|
358
|
+
|
|
359
|
+
if (!match?.color) return `rgba(255, 102, 1)`
|
|
360
|
+
return match.color
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
const getHighlightedBarByValue = value => {
|
|
364
|
+
const match = config?.highlightedBarValues.filter(item => {
|
|
365
|
+
if (!item.value) return
|
|
366
|
+
return config.xAxis.type === 'date' ? formatDate(parseDate(item.value)) === value : item.value === value
|
|
367
|
+
})[0]
|
|
368
|
+
|
|
369
|
+
if (!match?.color) return false
|
|
370
|
+
return match
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
let highlightedBarValues = config.highlightedBarValues.map(item => item.value).filter(item => item !== ('' || undefined))
|
|
374
|
+
|
|
375
|
+
highlightedBarValues = config.xAxis.type === 'date' ? HighLightedBarUtils.formatDates(highlightedBarValues) : highlightedBarValues
|
|
376
|
+
|
|
316
377
|
let transparentBar = config.legend.behavior === 'highlight' && seriesHighlight.length > 0 && seriesHighlight.indexOf(bar.key) === -1
|
|
317
378
|
let displayBar = config.legend.behavior === 'highlight' || seriesHighlight.length === 0 || seriesHighlight.indexOf(bar.key) !== -1
|
|
318
|
-
let barHeight = orientation === 'horizontal' ? config.barHeight : isNumber(Math.abs(yScale(bar.value) - yScale(
|
|
379
|
+
let barHeight = orientation === 'horizontal' ? config.barHeight : isNumber(Math.abs(yScale(bar.value) - yScale(scaleVal))) ? Math.abs(yScale(bar.value) - yScale(scaleVal)) : 0
|
|
319
380
|
let barY = bar.value >= 0 && isNumber(bar.value) ? bar.y : yScale(0)
|
|
320
381
|
let barGroupWidth = ((config.runtime.horizontal ? yMax : xMax) / barGroups.length) * (config.barThickness || 0.8)
|
|
321
382
|
let offset = (((config.runtime.horizontal ? yMax : xMax) / barGroups.length) * (1 - (config.barThickness || 0.8))) / 2
|
|
322
383
|
const barX = bar.value < 0 ? Math.abs(xScale(bar.value)) : xScale(0)
|
|
323
|
-
const barWidthHorizontal = Math.abs(xScale(bar.value) - xScale(
|
|
384
|
+
const barWidthHorizontal = Math.abs(xScale(bar.value) - xScale(scaleVal))
|
|
324
385
|
// ! Unsure if this should go back.
|
|
325
386
|
if (config.isLollipopChart) {
|
|
326
387
|
offset = (config.runtime.horizontal ? yMax : xMax) / barGroups.length / 2 - lollipopBarWidth / 2
|
|
327
388
|
}
|
|
328
|
-
|
|
329
|
-
data.forEach(d => set.add(d[config.legend.colorCode]))
|
|
330
|
-
const uniqValues = Array.from(set)
|
|
331
|
-
|
|
332
|
-
let palette = colorPalettes[config.palette].slice(0, uniqValues.length)
|
|
389
|
+
let palette = assignColorsToValues()
|
|
333
390
|
|
|
334
391
|
let barWidth = config.isLollipopChart ? lollipopBarWidth : barGroupWidth / barGroup.bars.length
|
|
335
392
|
let barColor = config.runtime.seriesLabels && config.runtime.seriesLabels[bar.key] ? colorScale(config.runtime.seriesLabels[bar.key]) : colorScale(bar.key)
|
|
@@ -349,7 +406,6 @@ export default function BarChart({ xScale, yScale, seriesScale, xMax, yMax, getX
|
|
|
349
406
|
}
|
|
350
407
|
|
|
351
408
|
const barPosition = bar.value < 0 ? 'below' : 'above'
|
|
352
|
-
const textX = barPosition === 'below' ? 0 : 0
|
|
353
409
|
|
|
354
410
|
// check if bar text/value string fits into each bars.
|
|
355
411
|
let textWidth = getTextWidth(xAxisValue, `normal ${fontSize[config.fontSize]}px sans-serif`)
|
|
@@ -357,10 +413,15 @@ export default function BarChart({ xScale, yScale, seriesScale, xMax, yMax, getX
|
|
|
357
413
|
let labelColor = '#000000'
|
|
358
414
|
|
|
359
415
|
// Set label color
|
|
360
|
-
if (
|
|
361
|
-
|
|
416
|
+
if (barColor && labelColor) {
|
|
417
|
+
if (chroma.contrast(labelColor, barColor) < 4.9) {
|
|
418
|
+
labelColor = textFits ? '#FFFFFF' : '#000000'
|
|
419
|
+
}
|
|
362
420
|
}
|
|
363
421
|
|
|
422
|
+
// Set if background is transparent'
|
|
423
|
+
labelColor = HighLightedBarUtils.checkFontColor(yAxisValue, highlightedBarValues, labelColor)
|
|
424
|
+
|
|
364
425
|
// control text position
|
|
365
426
|
let textAnchor = textFits ? 'end' : 'start'
|
|
366
427
|
let textAnchorLollipop = 'start'
|
|
@@ -395,6 +456,66 @@ export default function BarChart({ xScale, yScale, seriesScale, xMax, yMax, getX
|
|
|
395
456
|
${xAxisTooltip}
|
|
396
457
|
</div>`
|
|
397
458
|
|
|
459
|
+
const isRegularLollipopColor = config.isLollipopChart && config.lollipopColorStyle === 'regular'
|
|
460
|
+
const isTwoToneLollipopColor = config.isLollipopChart && config.lollipopColorStyle === 'two-tone'
|
|
461
|
+
const isHighlightedBar = config.orientation === 'vertical' ? highlightedBarValues?.includes(xAxisValue) : highlightedBarValues?.includes(yAxisValue)
|
|
462
|
+
const highlightedBarColor = config.orientation === 'vertical' ? getHighlightedBarColorByValue(xAxisValue) : getHighlightedBarColorByValue(yAxisValue)
|
|
463
|
+
const highlightedBar = config.orientation === 'vertical' ? getHighlightedBarByValue(xAxisValue) : getHighlightedBarByValue(yAxisValue)
|
|
464
|
+
|
|
465
|
+
const background = () => {
|
|
466
|
+
if (isRegularLollipopColor) return barColor
|
|
467
|
+
if (isTwoToneLollipopColor) return chroma(barColor).brighten(1)
|
|
468
|
+
if (isHighlightedBar) return 'transparent'
|
|
469
|
+
// loop through shared filters and get active values
|
|
470
|
+
if (dashboardConfig && dashboardConfig?.dashboard.sharedFilters?.length > 0) {
|
|
471
|
+
let activeFilters = []
|
|
472
|
+
let backgroundColor = barColor
|
|
473
|
+
|
|
474
|
+
const checkForResetValue = () => {
|
|
475
|
+
return dashboardConfig.dashboard.sharedFilters?.map((filter, index) => {
|
|
476
|
+
if (filter.resetLabel === filter.active) {
|
|
477
|
+
backgroundColor = barColor
|
|
478
|
+
} else {
|
|
479
|
+
return backgroundColor
|
|
480
|
+
}
|
|
481
|
+
})
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
dashboardConfig.dashboard.sharedFilters?.forEach((filter, index) => {
|
|
485
|
+
activeFilters.push(filter.active)
|
|
486
|
+
})
|
|
487
|
+
|
|
488
|
+
// if reset value is found use that.
|
|
489
|
+
|
|
490
|
+
if (config.orientation === 'horizontal') {
|
|
491
|
+
if (!activeFilters.includes(yAxisValue)) {
|
|
492
|
+
backgroundColor = '#ccc'
|
|
493
|
+
}
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
if (config.orientation !== 'horizontal') {
|
|
497
|
+
if (!activeFilters.includes(xAxisValue)) {
|
|
498
|
+
backgroundColor = '#ccc'
|
|
499
|
+
}
|
|
500
|
+
}
|
|
501
|
+
checkForResetValue()
|
|
502
|
+
return backgroundColor
|
|
503
|
+
}
|
|
504
|
+
return barColor
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
const borderColor = isHighlightedBar ? highlightedBarColor : config.barHasBorder === 'true' ? '#000' : 'transparent'
|
|
508
|
+
|
|
509
|
+
const borderWidth = isHighlightedBar ? highlightedBar.borderWidth : config.isLollipopChart ? 0 : config.barHasBorder === 'true' ? barBorderWidth : 0
|
|
510
|
+
|
|
511
|
+
const finalStyle = {
|
|
512
|
+
background: background(),
|
|
513
|
+
borderColor,
|
|
514
|
+
borderStyle: 'solid',
|
|
515
|
+
borderWidth,
|
|
516
|
+
...style
|
|
517
|
+
}
|
|
518
|
+
|
|
398
519
|
return (
|
|
399
520
|
<Group key={`${barGroup.index}--${index}--${orientation}`}>
|
|
400
521
|
{/* This feels gross but inline transition was not working well*/}
|
|
@@ -414,15 +535,18 @@ export default function BarChart({ xScale, yScale, seriesScale, xMax, yMax, getX
|
|
|
414
535
|
y={config.runtime.horizontal ? barWidth * bar.index : barY}
|
|
415
536
|
width={config.runtime.horizontal ? barWidthHorizontal : barWidth}
|
|
416
537
|
height={isHorizontal && !config.isLollipopChart ? barWidth : isHorizontal && config.isLollipopChart ? lollipopBarWidth : barHeight}
|
|
417
|
-
style={
|
|
418
|
-
background: config.isLollipopChart && config.lollipopColorStyle === 'regular' ? barColor : config.isLollipopChart && config.lollipopColorStyle === 'two-tone' ? chroma(barColor).brighten(1) : barColor,
|
|
419
|
-
border: `${config.isLollipopChart ? 0 : config.barHasBorder === 'true' ? barBorderWidth : 0}px solid #333`,
|
|
420
|
-
...style
|
|
421
|
-
}}
|
|
538
|
+
style={finalStyle}
|
|
422
539
|
opacity={transparentBar ? 0.5 : 1}
|
|
423
540
|
display={displayBar ? 'block' : 'none'}
|
|
424
541
|
data-tooltip-html={tooltip}
|
|
425
542
|
data-tooltip-id={`cdc-open-viz-tooltip-${config.runtime.uniqueId}`}
|
|
543
|
+
onClick={e => {
|
|
544
|
+
e.preventDefault()
|
|
545
|
+
if (setSharedFilter) {
|
|
546
|
+
bar[config.xAxis.dataKey] = config.orientation === 'horizontal' ? yAxisValue : xAxisValue
|
|
547
|
+
setSharedFilter(config.uid, bar)
|
|
548
|
+
}
|
|
549
|
+
}}
|
|
426
550
|
></foreignObject>
|
|
427
551
|
{orientation === 'horizontal' && !config.isLollipopChart && displayNumbersOnBar && (
|
|
428
552
|
<Text // prettier-ignore
|
|
@@ -437,7 +561,6 @@ export default function BarChart({ xScale, yScale, seriesScale, xMax, yMax, getX
|
|
|
437
561
|
{xAxisValue}
|
|
438
562
|
</Text>
|
|
439
563
|
)}
|
|
440
|
-
;
|
|
441
564
|
{orientation === 'horizontal' && config.isLollipopChart && displayNumbersOnBar && (
|
|
442
565
|
<Text
|
|
443
566
|
display={displayBar ? 'block' : 'none'}
|
|
@@ -461,13 +584,12 @@ export default function BarChart({ xScale, yScale, seriesScale, xMax, yMax, getX
|
|
|
461
584
|
: formatNumber(data[barGroup.index][config.runtime.originalXAxis.dataKey])}
|
|
462
585
|
</Text>
|
|
463
586
|
)}
|
|
464
|
-
|
|
587
|
+
|
|
465
588
|
{orientation === 'vertical' && (
|
|
466
|
-
<Text display={config.labels && displayBar ? 'block' : 'none'} opacity={transparentBar ? 0.5 : 1} x={barWidth * (bar.index + 0.5) + offset} y={barY - 5} fill={
|
|
589
|
+
<Text display={config.labels && displayBar ? 'block' : 'none'} opacity={transparentBar ? 0.5 : 1} x={barWidth * (bar.index + 0.5) + offset} y={barY - 5} fill={labelColor} textAnchor='middle'>
|
|
467
590
|
{yAxisValue}
|
|
468
591
|
</Text>
|
|
469
592
|
)}
|
|
470
|
-
;
|
|
471
593
|
{config.isLollipopChart && config.lollipopShape === 'circle' && (
|
|
472
594
|
<circle
|
|
473
595
|
cx={orientation === 'horizontal' ? bar.y : barWidth * (barGroup.bars.length - bar.index - 1) + (isLabelBelowBar && orientation === 'horizontal' ? 0 : offset) + lollipopShapeSize / 3.5}
|
|
@@ -2,6 +2,7 @@ import React, { useContext, useEffect, useState, useMemo } from 'react'
|
|
|
2
2
|
import { useTable, useSortBy, useResizeColumns, useBlockLayout } from 'react-table'
|
|
3
3
|
import Papa from 'papaparse'
|
|
4
4
|
import { Base64 } from 'js-base64'
|
|
5
|
+
import { colorPalettesChart } from '@cdc/core/data/colorPalettes'
|
|
5
6
|
|
|
6
7
|
import ErrorBoundary from '@cdc/core/components/ErrorBoundary'
|
|
7
8
|
import LegendCircle from '@cdc/core/components/LegendCircle'
|
|
@@ -9,7 +10,7 @@ import Icon from '@cdc/core/components/ui/Icon'
|
|
|
9
10
|
|
|
10
11
|
import ConfigContext from '../ConfigContext'
|
|
11
12
|
|
|
12
|
-
import
|
|
13
|
+
import MediaControls from '@cdc/core/components/MediaControls'
|
|
13
14
|
|
|
14
15
|
export default function DataTable() {
|
|
15
16
|
const { rawData, tableData: data, config, colorScale, parseDate, formatDate, formatNumber: numberFormatter, colorPalettes } = useContext(ConfigContext)
|
|
@@ -93,24 +94,29 @@ export default function DataTable() {
|
|
|
93
94
|
{
|
|
94
95
|
Header: '',
|
|
95
96
|
Cell: ({ row }) => {
|
|
96
|
-
const
|
|
97
|
+
const getSeriesLabel = () => {
|
|
98
|
+
let userUpdatedSeriesName = config.series.filter(series => series.dataKey === row.original)?.[0]?.name
|
|
99
|
+
|
|
100
|
+
if (userUpdatedSeriesName) return userUpdatedSeriesName
|
|
101
|
+
if (config.runtimeSeriesLabels) return config.runtime.seriesLabels[row.original]
|
|
102
|
+
return row.original
|
|
103
|
+
}
|
|
97
104
|
return (
|
|
98
105
|
<>
|
|
99
106
|
{config.visualizationType !== 'Pie' && (
|
|
100
107
|
<LegendCircle
|
|
101
108
|
fill={
|
|
102
|
-
// non-dynamic
|
|
103
|
-
!config.legend.dynamicLegend
|
|
104
|
-
? colorScale(
|
|
105
|
-
:
|
|
106
|
-
config.legend.dynamicLegend
|
|
109
|
+
// non-dynamic legend
|
|
110
|
+
!config.legend.dynamicLegend && config.visualizationType !== 'Forecasting'
|
|
111
|
+
? colorScale(getSeriesLabel())
|
|
112
|
+
: config.legend.dynamicLegend
|
|
107
113
|
? colorPalettes[config.palette][row.index]
|
|
108
114
|
: // fallback
|
|
109
115
|
'#000'
|
|
110
116
|
}
|
|
111
117
|
/>
|
|
112
118
|
)}
|
|
113
|
-
<span>{
|
|
119
|
+
<span>{getSeriesLabel()}</span>
|
|
114
120
|
</>
|
|
115
121
|
)
|
|
116
122
|
},
|
|
@@ -127,7 +133,19 @@ export default function DataTable() {
|
|
|
127
133
|
const newCol = {
|
|
128
134
|
Header: resolveTableHeader(),
|
|
129
135
|
Cell: ({ row }) => {
|
|
130
|
-
|
|
136
|
+
let leftAxisItems = config.series.filter(item => item?.axis === 'Left')
|
|
137
|
+
let rightAxisItems = config.series.filter(item => item?.axis === 'Right')
|
|
138
|
+
let resolvedAxis = ''
|
|
139
|
+
|
|
140
|
+
leftAxisItems.map(leftSeriesItem => {
|
|
141
|
+
if (leftSeriesItem.dataKey === row.original) resolvedAxis = 'left'
|
|
142
|
+
})
|
|
143
|
+
|
|
144
|
+
rightAxisItems.map(rightSeriesItem => {
|
|
145
|
+
if (rightSeriesItem.dataKey === row.original) resolvedAxis = 'right'
|
|
146
|
+
})
|
|
147
|
+
|
|
148
|
+
return <>{numberFormatter(d[row.original], resolvedAxis)}</>
|
|
131
149
|
},
|
|
132
150
|
id: `${d[config.runtime.originalXAxis.dataKey]}--${index}`,
|
|
133
151
|
canSort: true
|
|
@@ -210,10 +228,10 @@ export default function DataTable() {
|
|
|
210
228
|
|
|
211
229
|
return (
|
|
212
230
|
<ErrorBoundary component='DataTable'>
|
|
213
|
-
<
|
|
214
|
-
<
|
|
231
|
+
<MediaControls.Section classes={['download-links']}>
|
|
232
|
+
<MediaControls.Link config={config} />
|
|
215
233
|
{config.table.download && <DownloadButton data={rawData} type='link' />}
|
|
216
|
-
</
|
|
234
|
+
</MediaControls.Section>
|
|
217
235
|
|
|
218
236
|
<section id={config?.title ? `dataTableSection__${config?.title.replace(/\s/g, '')}` : `dataTableSection`} className={`data-table-container`} aria-label={accessibilityLabel}>
|
|
219
237
|
<div
|
|
@@ -6,38 +6,7 @@ import { Text } from '@visx/text'
|
|
|
6
6
|
import ErrorBoundary from '@cdc/core/components/ErrorBoundary'
|
|
7
7
|
import chroma from 'chroma-js'
|
|
8
8
|
|
|
9
|
-
|
|
10
|
-
function getTextProps(isLollipopChart, textFits, lollipopShapeSize, fill) {
|
|
11
|
-
if (isLollipopChart) {
|
|
12
|
-
return {
|
|
13
|
-
right: {
|
|
14
|
-
textAnchor: 'start',
|
|
15
|
-
dx: lollipopShapeSize + 6,
|
|
16
|
-
fill: '#000000'
|
|
17
|
-
},
|
|
18
|
-
left: {
|
|
19
|
-
textAnchor: 'end',
|
|
20
|
-
dx: -lollipopShapeSize,
|
|
21
|
-
fill: '#000000'
|
|
22
|
-
}
|
|
23
|
-
}
|
|
24
|
-
} else {
|
|
25
|
-
return {
|
|
26
|
-
right: {
|
|
27
|
-
textAnchor: textFits ? 'end' : 'start',
|
|
28
|
-
dx: textFits ? -6 : 6,
|
|
29
|
-
fill: textFits ? fill : '#000000'
|
|
30
|
-
},
|
|
31
|
-
left: {
|
|
32
|
-
textAnchor: textFits ? 'start' : 'end',
|
|
33
|
-
dx: textFits ? 6 : -6,
|
|
34
|
-
fill: textFits ? fill : '#000000'
|
|
35
|
-
}
|
|
36
|
-
}
|
|
37
|
-
}
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
export function DeviationBar({ height, xScale }) {
|
|
9
|
+
export default function DeviationBar({ height, xScale }) {
|
|
41
10
|
const { transformedData: data, config, formatNumber, twoColorPalette, getTextWidth, updateConfig, parseDate, formatDate } = useContext(ConfigContext)
|
|
42
11
|
|
|
43
12
|
if (!config || config?.series?.length !== 1 || config.orientation !== 'horizontal') return
|
|
@@ -189,3 +158,34 @@ export function DeviationBar({ height, xScale }) {
|
|
|
189
158
|
</ErrorBoundary>
|
|
190
159
|
)
|
|
191
160
|
}
|
|
161
|
+
|
|
162
|
+
// create function to position text based where bar is located left/or right
|
|
163
|
+
function getTextProps(isLollipopChart, textFits, lollipopShapeSize, fill) {
|
|
164
|
+
if (isLollipopChart) {
|
|
165
|
+
return {
|
|
166
|
+
right: {
|
|
167
|
+
textAnchor: 'start',
|
|
168
|
+
dx: lollipopShapeSize + 6,
|
|
169
|
+
fill: '#000000'
|
|
170
|
+
},
|
|
171
|
+
left: {
|
|
172
|
+
textAnchor: 'end',
|
|
173
|
+
dx: -lollipopShapeSize,
|
|
174
|
+
fill: '#000000'
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
} else {
|
|
178
|
+
return {
|
|
179
|
+
right: {
|
|
180
|
+
textAnchor: textFits ? 'end' : 'start',
|
|
181
|
+
dx: textFits ? -6 : 6,
|
|
182
|
+
fill: textFits ? fill : '#000000'
|
|
183
|
+
},
|
|
184
|
+
left: {
|
|
185
|
+
textAnchor: textFits ? 'start' : 'end',
|
|
186
|
+
dx: textFits ? 6 : -6,
|
|
187
|
+
fill: textFits ? fill : '#000000'
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
}
|