@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.
Files changed (31) hide show
  1. package/dist/cdcchart.js +52384 -50875
  2. package/examples/feature/__data__/planet-example-data.json +2 -19
  3. package/examples/feature/__data__/planet-logaritmic-data.json +56 -0
  4. package/examples/feature/area/area-chart-category.json +240 -0
  5. package/examples/feature/bar/example-bar-chart.json +544 -22
  6. package/examples/feature/bar/planet-chart-logaritmic-config.json +170 -0
  7. package/examples/feature/boxplot/valid-boxplot.csv +17 -0
  8. package/examples/feature/filters/filter-testing.json +37 -3
  9. package/examples/feature/forecasting/random_data.csv +366 -0
  10. package/examples/feature/line/line-chart.json +2 -2
  11. package/examples/feature/test-highlight/test-highlight-2.json +789 -0
  12. package/examples/feature/test-highlight/test-highlight-vertical.json +561 -0
  13. package/examples/feature/test-highlight/test-highlight.json +100 -0
  14. package/examples/feature/tests-non-numerics/stacked-vertical-bar-example-nonnumerics.json +1 -2
  15. package/examples/gallery/bar-chart-horizontal/horizontal-highlight.json +345 -0
  16. package/index.html +8 -8
  17. package/package.json +2 -2
  18. package/src/CdcChart.jsx +294 -14
  19. package/src/components/AreaChart.jsx +27 -20
  20. package/src/components/BarChart.jsx +85 -25
  21. package/src/components/DeviationBar.jsx +32 -32
  22. package/src/components/EditorPanel.jsx +1105 -184
  23. package/src/components/Legend.jsx +39 -3
  24. package/src/components/LineChart.jsx +1 -8
  25. package/src/components/LinearChart.jsx +121 -270
  26. package/src/data/initial-state.js +18 -3
  27. package/src/hooks/useHighlightedBars.js +154 -0
  28. package/src/hooks/useMinMax.js +92 -0
  29. package/src/hooks/useReduceData.js +31 -57
  30. package/src/hooks/useScales.js +202 -0
  31. /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={bar.color} textAnchor='middle'>
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(0))) ? Math.abs(yScale(bar.value) - yScale(0)) : 0
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(0))
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
- const set = new Set()
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 (chroma.contrast(labelColor, barColor) < 4.9) {
361
- textFits ? (labelColor = '#FFFFFF') : '#000000'
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={barColor} textAnchor='middle'>
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
- // create function to position text based where bar is located left/or right
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
+ }