@cdc/chart 4.23.4 → 4.23.5
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 +52384 -50875
- package/examples/feature/__data__/planet-example-data.json +2 -19
- 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/planet-chart-logaritmic-config.json +170 -0
- package/examples/feature/boxplot/valid-boxplot.csv +17 -0
- package/examples/feature/filters/filter-testing.json +37 -3
- package/examples/feature/forecasting/random_data.csv +366 -0
- package/examples/feature/line/line-chart.json +2 -2
- 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/index.html +8 -8
- package/package.json +2 -2
- package/src/CdcChart.jsx +294 -14
- package/src/components/AreaChart.jsx +27 -20
- package/src/components/BarChart.jsx +85 -25
- package/src/components/DeviationBar.jsx +32 -32
- package/src/components/EditorPanel.jsx +1105 -184
- package/src/components/Legend.jsx +39 -3
- package/src/components/LineChart.jsx +1 -8
- package/src/components/LinearChart.jsx +121 -270
- package/src/data/initial-state.js +18 -3
- package/src/hooks/useHighlightedBars.js +154 -0
- package/src/hooks/useMinMax.js +92 -0
- package/src/hooks/useReduceData.js +31 -57
- package/src/hooks/useScales.js +202 -0
- /package/examples/feature/area/{area-chart.json → area-chart-date.json} +0 -0
|
@@ -6,13 +6,14 @@ 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'
|
|
9
10
|
|
|
10
11
|
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
|
-
|
|
12
|
+
const { transformedData: data, colorScale, seriesHighlight, config, formatNumber, updateConfig, colorPalettes, tableData, formatDate, isNumber, getTextWidth, parseDate } = useContext(ConfigContext)
|
|
13
|
+
const { HighLightedBarUtils } = useHighlightedBars(config)
|
|
13
14
|
const { orientation, visualizationSubType } = config
|
|
14
15
|
const isHorizontal = orientation === 'horizontal'
|
|
15
|
-
|
|
16
|
+
const barBorderWidth = 1
|
|
16
17
|
const lollipopBarWidth = config.lollipopSize === 'large' ? 7 : config.lollipopSize === 'medium' ? 6 : 5
|
|
17
18
|
const lollipopShapeSize = config.lollipopSize === 'large' ? 14 : config.lollipopSize === 'medium' ? 12 : 10
|
|
18
19
|
|
|
@@ -25,12 +26,11 @@ export default function BarChart({ xScale, yScale, seriesScale, xMax, yMax, getX
|
|
|
25
26
|
const tipRounding = config.tipRounding
|
|
26
27
|
const radius = config.roundingStyle === 'standard' ? '8px' : config.roundingStyle === 'shallow' ? '5px' : config.roundingStyle === 'finger' ? '15px' : '0px'
|
|
27
28
|
const stackCount = config.runtime.seriesKeys.length
|
|
28
|
-
const barBorderWidth = 1
|
|
29
29
|
const fontSize = { small: 16, medium: 18, large: 20 }
|
|
30
30
|
const hasMultipleSeries = Object.keys(config.runtime.seriesLabels).length > 1
|
|
31
31
|
|
|
32
32
|
const applyRadius = index => {
|
|
33
|
-
if (index === undefined || index === null || !isRounded) return
|
|
33
|
+
if (index === undefined || index === null || !isRounded) return {}
|
|
34
34
|
let style = {}
|
|
35
35
|
|
|
36
36
|
if ((isStacked && index + 1 === stackCount) || !isStacked) {
|
|
@@ -48,6 +48,27 @@ export default function BarChart({ xScale, yScale, seriesScale, xMax, yMax, getX
|
|
|
48
48
|
return style
|
|
49
49
|
}
|
|
50
50
|
|
|
51
|
+
const assignColorsToValues = () => {
|
|
52
|
+
const palettesArr = colorPalettes[config.palette]
|
|
53
|
+
const values = tableData.map(d => {
|
|
54
|
+
return d[config.legend.colorCode]
|
|
55
|
+
})
|
|
56
|
+
// Map to hold unique values and their colors
|
|
57
|
+
let colorMap = new Map()
|
|
58
|
+
// Resultant array to hold colors to the values
|
|
59
|
+
let result = []
|
|
60
|
+
|
|
61
|
+
for (let i = 0; i < values.length; i++) {
|
|
62
|
+
// If value not in map, add it and assign a color
|
|
63
|
+
if (!colorMap.has(values[i])) {
|
|
64
|
+
colorMap.set(values[i], palettesArr[colorMap.size % palettesArr.length])
|
|
65
|
+
}
|
|
66
|
+
// push the colosr to the result array
|
|
67
|
+
result.push(colorMap.get(values[i]))
|
|
68
|
+
}
|
|
69
|
+
return result
|
|
70
|
+
}
|
|
71
|
+
|
|
51
72
|
const updateBars = defaultBars => {
|
|
52
73
|
// function updates stacked && regular && lollipop horizontal bars
|
|
53
74
|
if (config.visualizationType !== 'Bar' && !isHorizontal) return defaultBars
|
|
@@ -167,7 +188,7 @@ export default function BarChart({ xScale, yScale, seriesScale, xMax, yMax, getX
|
|
|
167
188
|
`}
|
|
168
189
|
</style>
|
|
169
190
|
<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={
|
|
191
|
+
<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
192
|
{yAxisValue}
|
|
172
193
|
</Text>
|
|
173
194
|
<foreignObject
|
|
@@ -313,23 +334,44 @@ export default function BarChart({ xScale, yScale, seriesScale, xMax, yMax, getX
|
|
|
313
334
|
left={config.runtime.horizontal ? 0 : (xMax / barGroups.length) * barGroup.index}
|
|
314
335
|
>
|
|
315
336
|
{barGroup.bars.map((bar, index) => {
|
|
337
|
+
const scaleVal = config.useLogScale ? 0.1 : 0
|
|
338
|
+
const getHighlightedBarColorByValue = value => {
|
|
339
|
+
const match = config?.highlightedBarValues.filter(item => {
|
|
340
|
+
if (!item.value) return
|
|
341
|
+
return config.xAxis.type === 'date' ? formatDate(parseDate(item.value)) === value : item.value === value
|
|
342
|
+
})[0]
|
|
343
|
+
|
|
344
|
+
if (!match?.color) return `rgba(255, 102, 1)`
|
|
345
|
+
return match.color
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
const getHighlightedBarByValue = value => {
|
|
349
|
+
const match = config?.highlightedBarValues.filter(item => {
|
|
350
|
+
if (!item.value) return
|
|
351
|
+
return config.xAxis.type === 'date' ? formatDate(parseDate(item.value)) === value : item.value === value
|
|
352
|
+
})[0]
|
|
353
|
+
|
|
354
|
+
if (!match?.color) return false
|
|
355
|
+
return match
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
let highlightedBarValues = config.highlightedBarValues.map(item => item.value).filter(item => item !== ('' || undefined))
|
|
359
|
+
|
|
360
|
+
highlightedBarValues = config.xAxis.type === 'date' ? HighLightedBarUtils.formatDates(highlightedBarValues) : highlightedBarValues
|
|
361
|
+
|
|
316
362
|
let transparentBar = config.legend.behavior === 'highlight' && seriesHighlight.length > 0 && seriesHighlight.indexOf(bar.key) === -1
|
|
317
363
|
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(
|
|
364
|
+
let barHeight = orientation === 'horizontal' ? config.barHeight : isNumber(Math.abs(yScale(bar.value) - yScale(scaleVal))) ? Math.abs(yScale(bar.value) - yScale(scaleVal)) : 0
|
|
319
365
|
let barY = bar.value >= 0 && isNumber(bar.value) ? bar.y : yScale(0)
|
|
320
366
|
let barGroupWidth = ((config.runtime.horizontal ? yMax : xMax) / barGroups.length) * (config.barThickness || 0.8)
|
|
321
367
|
let offset = (((config.runtime.horizontal ? yMax : xMax) / barGroups.length) * (1 - (config.barThickness || 0.8))) / 2
|
|
322
368
|
const barX = bar.value < 0 ? Math.abs(xScale(bar.value)) : xScale(0)
|
|
323
|
-
const barWidthHorizontal = Math.abs(xScale(bar.value) - xScale(
|
|
369
|
+
const barWidthHorizontal = Math.abs(xScale(bar.value) - xScale(scaleVal))
|
|
324
370
|
// ! Unsure if this should go back.
|
|
325
371
|
if (config.isLollipopChart) {
|
|
326
372
|
offset = (config.runtime.horizontal ? yMax : xMax) / barGroups.length / 2 - lollipopBarWidth / 2
|
|
327
373
|
}
|
|
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)
|
|
374
|
+
let palette = assignColorsToValues()
|
|
333
375
|
|
|
334
376
|
let barWidth = config.isLollipopChart ? lollipopBarWidth : barGroupWidth / barGroup.bars.length
|
|
335
377
|
let barColor = config.runtime.seriesLabels && config.runtime.seriesLabels[bar.key] ? colorScale(config.runtime.seriesLabels[bar.key]) : colorScale(bar.key)
|
|
@@ -349,7 +391,6 @@ export default function BarChart({ xScale, yScale, seriesScale, xMax, yMax, getX
|
|
|
349
391
|
}
|
|
350
392
|
|
|
351
393
|
const barPosition = bar.value < 0 ? 'below' : 'above'
|
|
352
|
-
const textX = barPosition === 'below' ? 0 : 0
|
|
353
394
|
|
|
354
395
|
// check if bar text/value string fits into each bars.
|
|
355
396
|
let textWidth = getTextWidth(xAxisValue, `normal ${fontSize[config.fontSize]}px sans-serif`)
|
|
@@ -357,10 +398,15 @@ export default function BarChart({ xScale, yScale, seriesScale, xMax, yMax, getX
|
|
|
357
398
|
let labelColor = '#000000'
|
|
358
399
|
|
|
359
400
|
// Set label color
|
|
360
|
-
if (
|
|
361
|
-
|
|
401
|
+
if (barColor && labelColor) {
|
|
402
|
+
if (chroma.contrast(labelColor, barColor) < 4.9) {
|
|
403
|
+
labelColor = textFits ? '#FFFFFF' : '#000000'
|
|
404
|
+
}
|
|
362
405
|
}
|
|
363
406
|
|
|
407
|
+
// Set if background is transparent'
|
|
408
|
+
labelColor = HighLightedBarUtils.checkFontColor(yAxisValue, highlightedBarValues, labelColor)
|
|
409
|
+
|
|
364
410
|
// control text position
|
|
365
411
|
let textAnchor = textFits ? 'end' : 'start'
|
|
366
412
|
let textAnchorLollipop = 'start'
|
|
@@ -395,6 +441,26 @@ export default function BarChart({ xScale, yScale, seriesScale, xMax, yMax, getX
|
|
|
395
441
|
${xAxisTooltip}
|
|
396
442
|
</div>`
|
|
397
443
|
|
|
444
|
+
const isRegularLollipopColor = config.isLollipopChart && config.lollipopColorStyle === 'regular'
|
|
445
|
+
const isTwoToneLollipopColor = config.isLollipopChart && config.lollipopColorStyle === 'two-tone'
|
|
446
|
+
const isHighlightedBar = config.orientation === 'vertical' ? highlightedBarValues?.includes(xAxisValue) : highlightedBarValues?.includes(yAxisValue)
|
|
447
|
+
const highlightedBarColor = config.orientation === 'vertical' ? getHighlightedBarColorByValue(xAxisValue) : getHighlightedBarColorByValue(yAxisValue)
|
|
448
|
+
const highlightedBar = config.orientation === 'vertical' ? getHighlightedBarByValue(xAxisValue) : getHighlightedBarByValue(yAxisValue)
|
|
449
|
+
|
|
450
|
+
const background = isRegularLollipopColor ? barColor : isTwoToneLollipopColor ? chroma(barColor).brighten(1) : isHighlightedBar ? 'transparent' : barColor
|
|
451
|
+
|
|
452
|
+
const borderColor = isHighlightedBar ? highlightedBarColor : config.barHasBorder === 'true' ? '#000' : 'transparent'
|
|
453
|
+
|
|
454
|
+
const borderWidth = isHighlightedBar ? highlightedBar.borderWidth : config.isLollipopChart ? 0 : config.barHasBorder === 'true' ? barBorderWidth : 0
|
|
455
|
+
|
|
456
|
+
const finalStyle = {
|
|
457
|
+
background,
|
|
458
|
+
borderColor,
|
|
459
|
+
borderStyle: 'solid',
|
|
460
|
+
borderWidth,
|
|
461
|
+
...style
|
|
462
|
+
}
|
|
463
|
+
|
|
398
464
|
return (
|
|
399
465
|
<Group key={`${barGroup.index}--${index}--${orientation}`}>
|
|
400
466
|
{/* This feels gross but inline transition was not working well*/}
|
|
@@ -414,11 +480,7 @@ export default function BarChart({ xScale, yScale, seriesScale, xMax, yMax, getX
|
|
|
414
480
|
y={config.runtime.horizontal ? barWidth * bar.index : barY}
|
|
415
481
|
width={config.runtime.horizontal ? barWidthHorizontal : barWidth}
|
|
416
482
|
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
|
-
}}
|
|
483
|
+
style={finalStyle}
|
|
422
484
|
opacity={transparentBar ? 0.5 : 1}
|
|
423
485
|
display={displayBar ? 'block' : 'none'}
|
|
424
486
|
data-tooltip-html={tooltip}
|
|
@@ -437,7 +499,6 @@ export default function BarChart({ xScale, yScale, seriesScale, xMax, yMax, getX
|
|
|
437
499
|
{xAxisValue}
|
|
438
500
|
</Text>
|
|
439
501
|
)}
|
|
440
|
-
;
|
|
441
502
|
{orientation === 'horizontal' && config.isLollipopChart && displayNumbersOnBar && (
|
|
442
503
|
<Text
|
|
443
504
|
display={displayBar ? 'block' : 'none'}
|
|
@@ -461,13 +522,12 @@ export default function BarChart({ xScale, yScale, seriesScale, xMax, yMax, getX
|
|
|
461
522
|
: formatNumber(data[barGroup.index][config.runtime.originalXAxis.dataKey])}
|
|
462
523
|
</Text>
|
|
463
524
|
)}
|
|
464
|
-
|
|
525
|
+
|
|
465
526
|
{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={
|
|
527
|
+
<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
528
|
{yAxisValue}
|
|
468
529
|
</Text>
|
|
469
530
|
)}
|
|
470
|
-
;
|
|
471
531
|
{config.isLollipopChart && config.lollipopShape === 'circle' && (
|
|
472
532
|
<circle
|
|
473
533
|
cx={orientation === 'horizontal' ? bar.y : barWidth * (barGroup.bars.length - bar.index - 1) + (isLabelBelowBar && orientation === 'horizontal' ? 0 : offset) + lollipopShapeSize / 3.5}
|
|
@@ -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
|
+
}
|