@cdc/chart 4.24.12 → 4.25.2-25

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 (84) hide show
  1. package/dist/cdcchart.js +79900 -78999
  2. package/examples/feature/boxplot/boxplot.json +2 -157
  3. package/examples/feature/boxplot/testing.csv +23 -38
  4. package/examples/feature/tests-non-numerics/example-combo-bar-nonnumeric.json +579 -49
  5. package/examples/private/ehdi.json +29939 -0
  6. package/examples/private/line-issue.json +497 -0
  7. package/examples/private/not-loading.json +360 -0
  8. package/index.html +11 -15
  9. package/package.json +2 -2
  10. package/src/CdcChart.tsx +92 -1512
  11. package/src/CdcChartComponent.tsx +1113 -0
  12. package/src/ConfigContext.tsx +6 -1
  13. package/src/_stories/Chart.Anchors.stories.tsx +1 -1
  14. package/src/_stories/Chart.CustomColors.stories.tsx +1 -1
  15. package/src/_stories/Chart.DynamicSeries.stories.tsx +17 -2
  16. package/src/_stories/Chart.Filters.stories.tsx +19 -0
  17. package/src/_stories/Chart.Legend.Gradient.stories.tsx +2 -2
  18. package/src/_stories/Chart.ScatterPlot.stories.tsx +19 -0
  19. package/src/_stories/Chart.tooltip.stories.tsx +1 -2
  20. package/src/_stories/ChartAnnotation.stories.tsx +1 -1
  21. package/src/_stories/ChartAxisLabels.stories.tsx +1 -1
  22. package/src/_stories/ChartAxisTitles.stories.tsx +1 -1
  23. package/src/_stories/ChartEditor.stories.tsx +1 -1
  24. package/src/_stories/ChartLine.Suppression.stories.tsx +1 -1
  25. package/src/_stories/ChartLine.Symbols.stories.tsx +18 -0
  26. package/src/_stories/ChartPrefixSuffix.stories.tsx +1 -1
  27. package/src/_stories/_mock/line_chart_symbols.json +437 -0
  28. package/src/_stories/_mock/scatterplot-image-download.json +1244 -0
  29. package/src/components/Annotations/components/AnnotationDraggable.tsx +3 -11
  30. package/src/components/Annotations/components/AnnotationDropdown.tsx +3 -3
  31. package/src/components/Axis/Categorical.Axis.tsx +3 -4
  32. package/src/components/BarChart/components/BarChart.Horizontal.tsx +14 -5
  33. package/src/components/BarChart/components/BarChart.StackedHorizontal.tsx +10 -4
  34. package/src/components/BarChart/components/BarChart.Vertical.tsx +5 -7
  35. package/src/components/BarChart/components/BarChart.jsx +24 -4
  36. package/src/components/BarChart/components/context.tsx +1 -0
  37. package/src/components/BoxPlot/BoxPlot.tsx +34 -32
  38. package/src/components/BoxPlot/helpers/index.ts +108 -18
  39. package/src/components/BrushChart.tsx +44 -24
  40. package/src/components/DeviationBar.jsx +2 -6
  41. package/src/components/EditorPanel/EditorPanel.tsx +64 -8
  42. package/src/components/EditorPanel/components/Panels/Panel.General.tsx +4 -0
  43. package/src/components/EditorPanel/components/Panels/Panel.Series.tsx +3 -1
  44. package/src/components/EditorPanel/components/Panels/Panel.Visual.tsx +44 -7
  45. package/src/components/EditorPanel/helpers/updateFieldRankByValue.ts +6 -1
  46. package/src/components/ForestPlot/ForestPlot.tsx +176 -26
  47. package/src/components/Legend/Legend.Component.tsx +29 -38
  48. package/src/components/Legend/Legend.Suppression.tsx +3 -5
  49. package/src/components/Legend/Legend.tsx +2 -2
  50. package/src/components/Legend/LegendLine.Shape.tsx +51 -0
  51. package/src/components/Legend/helpers/createFormatLabels.tsx +29 -26
  52. package/src/components/Legend/helpers/getLegendClasses.ts +20 -38
  53. package/src/components/Legend/helpers/index.ts +22 -9
  54. package/src/components/Legend/tests/getLegendClasses.test.ts +3 -20
  55. package/src/components/LineChart/components/LineChart.Circle.tsx +104 -94
  56. package/src/components/LineChart/index.tsx +6 -2
  57. package/src/components/LinearChart.tsx +77 -43
  58. package/src/components/PairedBarChart.jsx +2 -9
  59. package/src/components/ZoomBrush.tsx +5 -7
  60. package/src/data/initial-state.js +6 -3
  61. package/src/helpers/getBoxPlotConfig.ts +68 -0
  62. package/src/helpers/getColorScale.ts +24 -0
  63. package/src/helpers/getComboChartConfig.ts +42 -0
  64. package/src/helpers/getExcludedData.ts +37 -0
  65. package/src/helpers/getTopAxis.ts +7 -0
  66. package/src/helpers/isConvertLineToBarGraph.ts +10 -3
  67. package/src/hooks/useBarChart.ts +40 -13
  68. package/src/hooks/{useHighlightedBars.js → useHighlightedBars.ts} +2 -1
  69. package/src/hooks/useIntersectionObserver.ts +37 -0
  70. package/src/hooks/useMinMax.ts +11 -8
  71. package/src/hooks/useReduceData.ts +1 -1
  72. package/src/hooks/useScales.ts +10 -0
  73. package/src/hooks/useTooltip.tsx +21 -2
  74. package/src/index.jsx +1 -0
  75. package/src/scss/DataTable.scss +0 -5
  76. package/src/scss/main.scss +31 -116
  77. package/src/store/chart.actions.ts +40 -0
  78. package/src/store/chart.reducer.ts +83 -0
  79. package/src/types/ChartConfig.ts +6 -3
  80. package/src/types/ChartContext.ts +1 -3
  81. package/src/helpers/getQuartiles.ts +0 -27
  82. package/src/hooks/useColorScale.ts +0 -50
  83. package/src/hooks/useIntersectionObserver.jsx +0 -29
  84. package/src/hooks/useTopAxis.js +0 -6
@@ -26,8 +26,7 @@ import Regions from './Regions'
26
26
  import CategoricalYAxis from './Axis/Categorical.Axis'
27
27
 
28
28
  // Helpers
29
- import { isConvertLineToBarGraph } from '../helpers/isConvertLineToBarGraph'
30
- import { isLegendWrapViewport } from '@cdc/core/helpers/viewports'
29
+ import { isLegendWrapViewport, isMobileHeightViewport } from '@cdc/core/helpers/viewports'
31
30
  import { getTextWidth } from '@cdc/core/helpers/getTextWidth'
32
31
  import { calcInitialHeight } from '../helpers/sizeHelpers'
33
32
 
@@ -36,13 +35,13 @@ import useMinMax from '../hooks/useMinMax'
36
35
  import useReduceData from '../hooks/useReduceData'
37
36
  import useRightAxis from '../hooks/useRightAxis'
38
37
  import useScales, { getTickValues, filterAndShiftLinearDateTicks } from '../hooks/useScales'
39
- import useTopAxis from '../hooks/useTopAxis'
38
+ import getTopAxis from '../helpers/getTopAxis'
40
39
  import { useTooltip as useCoveTooltip } from '../hooks/useTooltip'
41
40
  import { useEditorPermissions } from './EditorPanel/useEditorPermissions'
42
41
  import Annotation from './Annotations'
43
42
  import { BlurStrokeText } from '@cdc/core/components/BlurStrokeText'
44
- import { fontSizes } from '@cdc/core/helpers/cove/fontSettings'
45
43
  import { countNumOfTicks } from '../helpers/countNumOfTicks'
44
+ import _ from 'lodash'
46
45
 
47
46
  type LinearChartProps = {
48
47
  parentWidth: number
@@ -50,9 +49,14 @@ type LinearChartProps = {
50
49
  }
51
50
 
52
51
  const BOTTOM_LABEL_PADDING = 9
53
- const X_TICK_LABEL_PADDING = 3
52
+ const X_TICK_LABEL_PADDING = 4.5
54
53
  const DEFAULT_TICK_LENGTH = 8
55
54
  const MONTH_AS_MS = 1000 * 60 * 60 * 24 * 30
55
+ const TICK_LABEL_FONT_SIZE = 16
56
+ const TICK_LABEL_FONT_SIZE_SMALL = 13
57
+ const AXIS_LABEL_FONT_SIZE = 18
58
+ const AXIS_LABEL_FONT_SIZE_SMALL = 14
59
+ const TICK_LABEL_MARGIN_RIGHT = 4.5
56
60
 
57
61
  const LinearChart = forwardRef<SVGAElement, LinearChartProps>(({ parentHeight, parentWidth }, svgRef) => {
58
62
  // prettier-ignore
@@ -60,6 +64,7 @@ const LinearChart = forwardRef<SVGAElement, LinearChartProps>(({ parentHeight, p
60
64
  brushConfig,
61
65
  colorScale,
62
66
  config,
67
+ convertLineToBarGraph,
63
68
  currentViewport,
64
69
  dimensions,
65
70
  formatDate,
@@ -99,7 +104,7 @@ const LinearChart = forwardRef<SVGAElement, LinearChartProps>(({ parentHeight, p
99
104
  // HOOKS % STATES
100
105
  const { minValue, maxValue, existPositiveValue, isAllLine } = useReduceData(config, data)
101
106
  const { visSupportsReactTooltip } = useEditorPermissions()
102
- const { hasTopAxis } = useTopAxis(config)
107
+ const { hasTopAxis } = getTopAxis(config)
103
108
  const [animatedChart, setAnimatedChart] = useState(false)
104
109
  const [point, setPoint] = useState({ x: 0, y: 0 })
105
110
  const [suffixWidth, setSuffixWidth] = useState(0)
@@ -129,6 +134,9 @@ const LinearChart = forwardRef<SVGAElement, LinearChartProps>(({ parentHeight, p
129
134
  const labelsOverflow = onlyShowTopPrefixSuffix && !suffixHasNoSpace
130
135
  const padding = orientation === 'horizontal' ? Number(config.xAxis.size) : Number(config.yAxis.size)
131
136
  const yLabelOffset = isNaN(parseInt(`${runtime.yAxis.labelOffset}`)) ? 0 : parseInt(`${runtime.yAxis.labelOffset}`)
137
+ const tickLabelFontSize = isMobileHeightViewport(currentViewport) ? TICK_LABEL_FONT_SIZE_SMALL : TICK_LABEL_FONT_SIZE
138
+ const axisLabelFontSize = isMobileHeightViewport(currentViewport) ? AXIS_LABEL_FONT_SIZE_SMALL : AXIS_LABEL_FONT_SIZE
139
+ const GET_TEXT_WIDTH_FONT = `normal ${tickLabelFontSize}px Nunito, sans-serif`
132
140
 
133
141
  // zero if not forest plot
134
142
  const forestRowsHeight = isForestPlot ? config.data.length * config.forestPlot.rowHeight : 0
@@ -249,10 +257,6 @@ const LinearChart = forwardRef<SVGAElement, LinearChartProps>(({ parentHeight, p
249
257
  const useDateSpanMonths = isDateTime && dateSpanMonths > xTickCount
250
258
 
251
259
  // GETTERS & FUNCTIONS
252
- const checkLineToBarGraph = () => {
253
- return isConvertLineToBarGraph(config.visualizationType, data, config.allowLineToBarGraph)
254
- }
255
-
256
260
  const handleLeftTickFormatting = (tick, index, ticks) => {
257
261
  if (isLogarithmicAxis && tick === 0.1) {
258
262
  //when logarithmic scale applied change value of first tick
@@ -377,7 +381,8 @@ const LinearChart = forwardRef<SVGAElement, LinearChartProps>(({ parentHeight, p
377
381
  const topLabelOnGridline = topYLabelRef.current && yAxis.labelsAboveGridlines
378
382
 
379
383
  // Heights to add
380
- const brushHeight = brush?.active ? brush?.height : 0
384
+
385
+ const brushHeight = brush?.active ? brush?.height + brush?.height : 0
381
386
  const forestRowsHeight = isForestPlot ? config.data.length * forestPlot.rowHeight : 0
382
387
  const topLabelOnGridlineHeight = topLabelOnGridline ? topYLabelRef.current.getBBox().height : 0
383
388
  const additionalHeight = axisBottomHeight + brushHeight + forestRowsHeight + topLabelOnGridlineHeight
@@ -410,21 +415,37 @@ const LinearChart = forwardRef<SVGAElement, LinearChartProps>(({ parentHeight, p
410
415
  useEffect(() => {
411
416
  if (lastMaxValue.current === maxValue) return
412
417
  lastMaxValue.current = maxValue
418
+
413
419
  if (!yAxisAutoPadding) return
414
420
  setYAxisAutoPadding(0)
415
421
  }, [maxValue])
422
+
416
423
  useEffect(() => {
417
424
  if (orientation === 'horizontal') return
425
+ if (!labelsOverflow) return
426
+
427
+ // minimum percentage of the max value that the distance should be from the top grid line
428
+ const MINIMUM_DISTANCE_PERCENTAGE = 0.025
418
429
 
419
- const maxValueIsGreaterThanTopGridLine = maxValue > Math.max(...yScale.ticks(handleNumTicks))
430
+ const topGridLine = Math.max(...yScale.ticks(handleNumTicks))
431
+ const needsPaddingThreshold = topGridLine - maxValue * MINIMUM_DISTANCE_PERCENTAGE
432
+ const maxValueIsGreaterThanThreshold = maxValue > needsPaddingThreshold
420
433
 
421
- if (!maxValueIsGreaterThanTopGridLine || !labelsOverflow) return
434
+ if (!maxValueIsGreaterThanThreshold) return
422
435
 
423
- const tickGap = yScale.ticks(handleNumTicks)[1] - yScale.ticks(handleNumTicks)[0]
436
+ const ticks = yScale.ticks(handleNumTicks)
437
+ const tickGap = ticks.length === 1 ? ticks[0] : ticks[1] - ticks[0]
424
438
  const nextTick = Math.max(...yScale.ticks(handleNumTicks)) + tickGap
425
- const newPadding = minValue < 0 ? (nextTick - maxValue) / maxValue / 2 : (nextTick - maxValue) / maxValue
439
+ const divideBy = minValue < 0 ? maxValue / 2 : maxValue
440
+ const calculatedPadding = (nextTick - maxValue) / divideBy
441
+
442
+ // if auto padding is too close to next tick, add one more ticks worth of padding
443
+ const newPadding =
444
+ calculatedPadding > MINIMUM_DISTANCE_PERCENTAGE ? calculatedPadding : calculatedPadding + tickGap / divideBy
426
445
 
427
- setYAxisAutoPadding(newPadding * 100)
446
+ /* sometimes even though the padding is getting to the next tick exactly,
447
+ d3 still doesn't show the tick. we add 0.1 to ensure to tip it over the edge */
448
+ setYAxisAutoPadding(newPadding * 100 + 0.1)
428
449
  }, [maxValue, labelsOverflow, yScale, handleNumTicks])
429
450
 
430
451
  // Render Functions
@@ -433,12 +454,12 @@ const LinearChart = forwardRef<SVGAElement, LinearChartProps>(({ parentHeight, p
433
454
 
434
455
  const getTickPositions = (ticks, xScale) => {
435
456
  if (!ticks.length) return false
436
- // filterout first index
457
+ // filter out first index
437
458
  const filteredTicks = ticks.filter(tick => tick.index !== 0)
438
459
  const numberOfTicks = filteredTicks?.length
439
460
  const xMaxHalf = xScale.range()[0] || xMax / 2
440
461
  const tickWidthAll = filteredTicks.map(tick =>
441
- getTextWidth(formatNumber(tick.value, 'left'), `normal ${fontSizes[config.fontSize]}px sans-serif`)
462
+ getTextWidth(formatNumber(tick.value, 'left'), GET_TEXT_WIDTH_FONT)
442
463
  )
443
464
  const accumulator = 100
444
465
  const sumOfTickWidth = tickWidthAll.reduce((a, b) => a + b, accumulator)
@@ -475,10 +496,6 @@ const LinearChart = forwardRef<SVGAElement, LinearChartProps>(({ parentHeight, p
475
496
  return (
476
497
  <Group className='bottom-axis'>
477
498
  {props.ticks.map((tick, i) => {
478
- const textWidth = getTextWidth(
479
- formatNumber(tick.value, 'left'),
480
- `normal ${fontSizes[config.fontSize]}px sans-serif`
481
- )
482
499
  const isTicksOverlapping = getTickPositions(props.ticks, g1xScale)
483
500
  const maxTickRotation = Number(config.xAxis.maxTickRotation) || 90
484
501
  const isResponsiveTicks = config.isResponsiveTicks && isTicksOverlapping
@@ -497,6 +514,7 @@ const LinearChart = forwardRef<SVGAElement, LinearChartProps>(({ parentHeight, p
497
514
  angle={-angle}
498
515
  verticalAnchor={angle ? 'middle' : 'start'}
499
516
  textAnchor={textAnchor}
517
+ fontSize={tickLabelFontSize}
500
518
  >
501
519
  {formatNumber(tick.value, 'left')}
502
520
  </Text>
@@ -527,10 +545,6 @@ const LinearChart = forwardRef<SVGAElement, LinearChartProps>(({ parentHeight, p
527
545
  <>
528
546
  <Group className='bottom-axis'>
529
547
  {props.ticks.map((tick, i) => {
530
- const textWidth = getTextWidth(
531
- formatNumber(tick.value, 'left'),
532
- `normal ${fontSizes[config.fontSize]}px sans-serif`
533
- )
534
548
  const isTicksOverlapping = getTickPositions(props.ticks, g2xScale)
535
549
  const maxTickRotation = Number(config.xAxis.maxTickRotation) || 90
536
550
  const isResponsiveTicks = config.isResponsiveTicks && isTicksOverlapping
@@ -548,6 +562,7 @@ const LinearChart = forwardRef<SVGAElement, LinearChartProps>(({ parentHeight, p
548
562
  angle={-angle}
549
563
  verticalAnchor={angle ? 'middle' : 'start'}
550
564
  textAnchor={textAnchor}
565
+ fontSize={tickLabelFontSize}
551
566
  >
552
567
  {formatNumber(tick.value, 'left')}
553
568
  </Text>
@@ -565,6 +580,7 @@ const LinearChart = forwardRef<SVGAElement, LinearChartProps>(({ parentHeight, p
565
580
  stroke='#333'
566
581
  textAnchor={'middle'}
567
582
  verticalAnchor='start'
583
+ fontSize={axisLabelFontSize}
568
584
  >
569
585
  {runtime.xAxis.label}
570
586
  </Text>
@@ -641,6 +657,7 @@ const LinearChart = forwardRef<SVGAElement, LinearChartProps>(({ parentHeight, p
641
657
  transform={`translate(${-1 * runtime.yAxis.size + yLabelOffset}, ${axisCenter}) rotate(-90)`}
642
658
  fontWeight='bold'
643
659
  fill={config.yAxis.labelColor}
660
+ fontSize={axisLabelFontSize}
644
661
  >
645
662
  {props.label}
646
663
  </Text>
@@ -712,7 +729,7 @@ const LinearChart = forwardRef<SVGAElement, LinearChartProps>(({ parentHeight, p
712
729
  showTooltip={showTooltip}
713
730
  />
714
731
  )}
715
- {(visualizationType === 'Bar' || visualizationType === 'Combo' || checkLineToBarGraph()) && (
732
+ {(visualizationType === 'Bar' || visualizationType === 'Combo' || convertLineToBarGraph) && (
716
733
  <BarChart
717
734
  xScale={xScale}
718
735
  yScale={yScale}
@@ -731,7 +748,7 @@ const LinearChart = forwardRef<SVGAElement, LinearChartProps>(({ parentHeight, p
731
748
  chartRef={svgRef}
732
749
  />
733
750
  )}
734
- {((visualizationType === 'Line' && !checkLineToBarGraph()) ||
751
+ {((visualizationType === 'Line' && !convertLineToBarGraph) ||
735
752
  visualizationType === 'Combo' ||
736
753
  visualizationType === 'Bump Chart') && (
737
754
  <LineChart
@@ -795,7 +812,7 @@ const LinearChart = forwardRef<SVGAElement, LinearChartProps>(({ parentHeight, p
795
812
  {!['Paired Bar', 'Box Plot', 'Area Chart', 'Scatter Plot', 'Deviation Bar', 'Forecasting', 'Bar'].includes(
796
813
  visualizationType
797
814
  ) &&
798
- !checkLineToBarGraph() && (
815
+ !convertLineToBarGraph && (
799
816
  <>
800
817
  <LineChart
801
818
  xScale={xScale}
@@ -928,7 +945,7 @@ const LinearChart = forwardRef<SVGAElement, LinearChartProps>(({ parentHeight, p
928
945
  {config.chartMessage.noData}
929
946
  </Text>
930
947
  )}
931
- {(config.visualizationType === 'Bar' || checkLineToBarGraph()) &&
948
+ {(config.visualizationType === 'Bar' || convertLineToBarGraph) &&
932
949
  config.tooltips.singleSeries &&
933
950
  config.visual.horizontalHoverLine && (
934
951
  <Group
@@ -947,7 +964,7 @@ const LinearChart = forwardRef<SVGAElement, LinearChartProps>(({ parentHeight, p
947
964
  />
948
965
  </Group>
949
966
  )}
950
- {(config.visualizationType === 'Bar' || checkLineToBarGraph()) &&
967
+ {(config.visualizationType === 'Bar' || convertLineToBarGraph) &&
951
968
  config.tooltips.singleSeries &&
952
969
  config.visual.verticalHoverLine && (
953
970
  <Group key='tooltipLine-vertical' className='vertical-tooltip-line'>
@@ -1034,7 +1051,7 @@ const LinearChart = forwardRef<SVGAElement, LinearChartProps>(({ parentHeight, p
1034
1051
  const lastTick = props.ticks.length - 1 === i
1035
1052
  const hideTopTick = lastTick && onlyShowTopPrefixSuffix && suffix && !suffixHasNoSpace
1036
1053
  const valueOnLinePadding = hideAxis ? -8 : -12
1037
- const labelXPadding = labelsAboveGridlines ? valueOnLinePadding : 2
1054
+ const labelXPadding = labelsAboveGridlines ? valueOnLinePadding : TICK_LABEL_MARGIN_RIGHT
1038
1055
  const labelYPadding = labelsAboveGridlines ? 4 : 0
1039
1056
  const labelX = tick.to.x - labelXPadding
1040
1057
  const labelY = tick.to.y - labelYPadding
@@ -1051,6 +1068,7 @@ const LinearChart = forwardRef<SVGAElement, LinearChartProps>(({ parentHeight, p
1051
1068
  to={isLogarithmicAxis ? to : tick.to}
1052
1069
  stroke={config.yAxis.tickColor}
1053
1070
  display={orientation === 'horizontal' ? 'none' : 'block'}
1071
+ fontSize={tickLabelFontSize}
1054
1072
  />
1055
1073
  )}
1056
1074
 
@@ -1068,6 +1086,7 @@ const LinearChart = forwardRef<SVGAElement, LinearChartProps>(({ parentHeight, p
1068
1086
  }) rotate(-${config.runtime.horizontal ? config.runtime.yAxis.tickRotation || 0 : 0})`}
1069
1087
  verticalAnchor={'start'}
1070
1088
  textAnchor={'end'}
1089
+ fontSize={tickLabelFontSize}
1071
1090
  >
1072
1091
  {tick.formattedValue}
1073
1092
  </Text>
@@ -1083,6 +1102,7 @@ const LinearChart = forwardRef<SVGAElement, LinearChartProps>(({ parentHeight, p
1083
1102
  }) rotate(-${runtime.horizontal ? runtime.yAxis.tickRotation : 0})`}
1084
1103
  verticalAnchor={'start'}
1085
1104
  textAnchor={'end'}
1105
+ fontSize={tickLabelFontSize}
1086
1106
  >
1087
1107
  {tick.formattedValue}
1088
1108
  </Text>
@@ -1097,6 +1117,7 @@ const LinearChart = forwardRef<SVGAElement, LinearChartProps>(({ parentHeight, p
1097
1117
  }) rotate(-${runtime.horizontal ? runtime.yAxis.tickRotation : 0})`}
1098
1118
  textAnchor={'end'}
1099
1119
  verticalAnchor='middle'
1120
+ fontSize={tickLabelFontSize}
1100
1121
  >
1101
1122
  {tick.formattedValue}
1102
1123
  </Text>
@@ -1112,6 +1133,7 @@ const LinearChart = forwardRef<SVGAElement, LinearChartProps>(({ parentHeight, p
1112
1133
  }) rotate(-${runtime.horizontal ? runtime.yAxis.tickRotation : 0})`}
1113
1134
  textAnchor={'end'}
1114
1135
  verticalAnchor='middle'
1136
+ fontSize={tickLabelFontSize}
1115
1137
  >
1116
1138
  {tick.formattedValue}
1117
1139
  </Text>
@@ -1130,6 +1152,7 @@ const LinearChart = forwardRef<SVGAElement, LinearChartProps>(({ parentHeight, p
1130
1152
  verticalAnchor={config.runtime.horizontal ? 'start' : 'middle'}
1131
1153
  textAnchor={config.runtime.horizontal ? 'start' : 'end'}
1132
1154
  fill={config.yAxis.tickLabelColor}
1155
+ fontSize={tickLabelFontSize}
1133
1156
  >
1134
1157
  {config.runtime.seriesLabelsAll[tick.formattedValue - 1]}
1135
1158
  </Text>
@@ -1172,6 +1195,7 @@ const LinearChart = forwardRef<SVGAElement, LinearChartProps>(({ parentHeight, p
1172
1195
  paintOrder={'stroke'} // keeps stroke under fill
1173
1196
  strokeLinejoin='round'
1174
1197
  style={{ whiteSpace: 'pre-wrap' }} // prevents leading spaces from being trimmed
1198
+ fontSize={tickLabelFontSize}
1175
1199
  >
1176
1200
  {suffix}
1177
1201
  </BlurStrokeText>
@@ -1193,6 +1217,7 @@ const LinearChart = forwardRef<SVGAElement, LinearChartProps>(({ parentHeight, p
1193
1217
  strokeLinejoin='round'
1194
1218
  paintOrder={'stroke'} // keeps stroke under fill
1195
1219
  style={{ whiteSpace: 'pre-wrap' }} // prevents leading spaces from being trimmed
1220
+ fontSize={tickLabelFontSize}
1196
1221
  >
1197
1222
  {`${tick.formattedValue}${combineDomSuffixWithValue ? suffix : ''}`}
1198
1223
  </BlurStrokeText>
@@ -1208,6 +1233,7 @@ const LinearChart = forwardRef<SVGAElement, LinearChartProps>(({ parentHeight, p
1208
1233
  transform={`translate(${-1 * runtime.yAxis.size + yLabelOffset}, ${axisCenter}) rotate(-90)`}
1209
1234
  fontWeight='bold'
1210
1235
  fill={config.yAxis.labelColor}
1236
+ fontSize={axisLabelFontSize}
1211
1237
  >
1212
1238
  {props.label}
1213
1239
  </Text>
@@ -1270,6 +1296,7 @@ const LinearChart = forwardRef<SVGAElement, LinearChartProps>(({ parentHeight, p
1270
1296
  verticalAnchor={runtime.horizontal ? 'start' : 'middle'}
1271
1297
  textAnchor={'start'}
1272
1298
  fill={config.yAxis.rightAxisTickLabelColor}
1299
+ fontSize={tickLabelFontSize}
1273
1300
  >
1274
1301
  {tick.formattedValue}
1275
1302
  </Text>
@@ -1289,6 +1316,7 @@ const LinearChart = forwardRef<SVGAElement, LinearChartProps>(({ parentHeight, p
1289
1316
  }, ${axisCenter}) rotate(-90)`}
1290
1317
  fontWeight='bold'
1291
1318
  fill={config.yAxis.rightAxisLabelColor}
1319
+ fontSize={axisLabelFontSize}
1292
1320
  >
1293
1321
  {props.label}
1294
1322
  </Text>
@@ -1328,17 +1356,18 @@ const LinearChart = forwardRef<SVGAElement, LinearChartProps>(({ parentHeight, p
1328
1356
  numTicks={useDateSpanMonths ? dateSpanMonths : xTickCount}
1329
1357
  tickStroke='#333'
1330
1358
  tickValues={
1331
- config.xAxis.manual
1359
+ config.runtime.xAxis.manual
1332
1360
  ? getTickValues(xAxisDataMapped, xScale, isDateTime ? xTickCount : getManualStep(), config)
1333
- : config.xAxis.type === 'date'
1361
+ : config.runtime.xAxis.type === 'date'
1334
1362
  ? xAxisDataMapped
1335
1363
  : undefined
1336
1364
  }
1337
1365
  >
1338
1366
  {props => {
1367
+ const hasDynamicCategory = config.series.some(s => s.dynamicCategory)
1339
1368
  // For these charts, we generated all ticks in tickValues above, and now need to filter/shift them
1340
1369
  // so the last tick is always labeled
1341
- if (config.xAxis.type === 'date' && !config.xAxis.manual) {
1370
+ if (config.runtime.xAxis.type === 'date' && !config.runtime.xAxis.manual && !hasDynamicCategory) {
1342
1371
  props.ticks = filterAndShiftLinearDateTicks(config, props, xAxisDataMapped, formatDate)
1343
1372
  }
1344
1373
 
@@ -1346,7 +1375,11 @@ const LinearChart = forwardRef<SVGAElement, LinearChartProps>(({ parentHeight, p
1346
1375
  useDateSpanMonths &&
1347
1376
  xScale
1348
1377
  .ticks(xTickCount)
1349
- .map(t => props.ticks.findIndex(tick => tick.value.getTime() === t.getTime()))
1378
+ .map(t =>
1379
+ props.ticks.findIndex(
1380
+ tick => (typeof tick.value === 'number' ? tick.value : tick.value.getTime()) === t.getTime()
1381
+ )
1382
+ )
1350
1383
  .slice(0, 2)
1351
1384
  .reduce((acc, curr) => curr - acc)
1352
1385
 
@@ -1370,16 +1403,12 @@ const LinearChart = forwardRef<SVGAElement, LinearChartProps>(({ parentHeight, p
1370
1403
 
1371
1404
  // Calculate sumOfTickWidth here, before map function
1372
1405
  const tickWidthMax = Math.max(
1373
- ...filteredTicks.map(tick =>
1374
- getTextWidth(tick.formattedValue, `normal ${fontSizes[config.fontSize]}px sans-serif`)
1375
- )
1406
+ ...filteredTicks.map(tick => getTextWidth(tick.formattedValue, GET_TEXT_WIDTH_FONT))
1376
1407
  )
1377
1408
  // const marginTop = 20 // moved to top bc need for yMax calcs
1378
1409
  const accumulator = ismultiLabel ? 180 : 100
1379
1410
 
1380
- const textWidths = filteredTicks.map(tick =>
1381
- getTextWidth(tick.formattedValue, `normal ${fontSizes[config.fontSize]}px sans-serif`)
1382
- )
1411
+ const textWidths = filteredTicks.map(tick => getTextWidth(tick.formattedValue, GET_TEXT_WIDTH_FONT))
1383
1412
  const sumOfTickWidth = textWidths.reduce((a, b) => a + b, accumulator)
1384
1413
  const spaceBetweenEachTick = (xMax - sumOfTickWidth) / (filteredTicks.length - 1)
1385
1414
 
@@ -1471,6 +1500,7 @@ const LinearChart = forwardRef<SVGAElement, LinearChartProps>(({ parentHeight, p
1471
1500
  : undefined
1472
1501
  }
1473
1502
  fill={config.xAxis.tickLabelColor}
1503
+ fontSize={tickLabelFontSize}
1474
1504
  >
1475
1505
  {tick.formattedValue}
1476
1506
  </Text>
@@ -1488,6 +1518,7 @@ const LinearChart = forwardRef<SVGAElement, LinearChartProps>(({ parentHeight, p
1488
1518
  verticalAnchor='start'
1489
1519
  fontWeight='bold'
1490
1520
  fill={config.xAxis.labelColor}
1521
+ fontSize={axisLabelFontSize}
1491
1522
  >
1492
1523
  {props.label}
1493
1524
  </Text>
@@ -1502,6 +1533,7 @@ const LinearChart = forwardRef<SVGAElement, LinearChartProps>(({ parentHeight, p
1502
1533
  Object.entries(tooltipData.data).length > 0 &&
1503
1534
  tooltipOpen &&
1504
1535
  showTooltip &&
1536
+ !tooltipData?.data?.some(subArray => subArray.some(item => item === undefined)) &&
1505
1537
  tooltipData.dataYPosition &&
1506
1538
  tooltipData.dataXPosition && (
1507
1539
  <>
@@ -1517,7 +1549,9 @@ const LinearChart = forwardRef<SVGAElement, LinearChartProps>(({ parentHeight, p
1517
1549
  >
1518
1550
  <ul>
1519
1551
  {typeof tooltipData === 'object' &&
1520
- Object.entries(tooltipData.data).map((item, index) => <TooltipListItem item={item} key={index} />)}
1552
+ Object.entries(tooltipData.data)
1553
+ .filter(([_, values]) => Array.isArray(values) && !values.includes(undefined))
1554
+ .map((item, index) => <TooltipListItem item={item} key={index} />)}
1521
1555
  </ul>
1522
1556
  </TooltipWithBounds>
1523
1557
  </>
@@ -15,7 +15,6 @@ const PairedBarChart = ({ width, height, originalWidth }) => {
15
15
 
16
16
  const borderWidth = config.barHasBorder === 'true' ? 1 : 0
17
17
  const halfWidth = width / 2
18
- const fontSize = { small: 16, medium: 18, large: 20 }
19
18
  const offset = 1.02 // Offset of the left bar from the Axis
20
19
 
21
20
  const groupOne = {
@@ -109,10 +108,7 @@ const PairedBarChart = ({ width, height, originalWidth }) => {
109
108
  const totalheight = (Number(config.barSpace) + barHeight + borderWidth) * data.length
110
109
  config.heights.horizontal = totalheight
111
110
  // check if text fits inside of the bar including suffix/prefix,comma,fontSize ..etc
112
- const textWidth = getTextWidth(
113
- formatNumber(d[groupOne.dataKey], 'left'),
114
- `normal ${fontSize[config.fontSize]}px sans-serif`
115
- )
111
+ const textWidth = getTextWidth(formatNumber(d[groupOne.dataKey], 'left'))
116
112
  const textFits = textWidth < barWidth - 5 // minus padding dx(5)
117
113
 
118
114
  return (
@@ -170,10 +166,7 @@ const PairedBarChart = ({ width, height, originalWidth }) => {
170
166
  const totalheight = (Number(config.barSpace) + barHeight + borderWidth) * data.length
171
167
  config.heights.horizontal = totalheight
172
168
  // check if text fits inside of the bar including suffix/prefix,comma,fontSize ..etc
173
- const textWidth = getTextWidth(
174
- formatNumber(d[groupTwo.dataKey], 'left'),
175
- `normal ${fontSize[config.fontSize]}px sans-serif`
176
- )
169
+ const textWidth = getTextWidth(formatNumber(d[groupTwo.dataKey], 'left'))
177
170
  const isTextFits = textWidth < barWidth - 5 // minus padding dx(5)
178
171
 
179
172
  return (
@@ -1,13 +1,13 @@
1
1
  import { Brush } from '@visx/brush'
2
2
  import { Group } from '@visx/group'
3
3
  import { Text } from '@visx/text'
4
- import { useBarChart } from '../hooks/useBarChart'
5
4
  import { FC, useContext, useEffect, useRef, useState } from 'react'
6
5
  import ConfigContext from '../ConfigContext'
7
6
  import { ScaleLinear, ScaleBand } from 'd3-scale'
8
7
  import { isDateScale } from '@cdc/core/helpers/cove/date'
9
8
  import ErrorBoundary from '@cdc/core/components/ErrorBoundary'
10
9
  import { getTextWidth } from '@cdc/core/helpers/getTextWidth'
10
+ import { appFontSize } from '@cdc/core/helpers/cove/fontSettings'
11
11
 
12
12
  interface Props {
13
13
  xScaleBrush: ScaleLinear<number, number>
@@ -19,7 +19,6 @@ const ZoomBrush: FC<Props> = props => {
19
19
  const { tableData, config, parseDate, formatDate, setBrushConfig, dashboardConfig } = useContext(ConfigContext)
20
20
  const sharedFilters = dashboardConfig?.dashboard?.sharedFilters ?? []
21
21
  const isDashboardFilters = sharedFilters?.length > 0
22
- const { fontSize } = useBarChart()
23
22
  const [showTooltip, setShowTooltip] = useState(false)
24
23
  const [brushKey, setBrushKey] = useState(0)
25
24
  const brushRef = useRef(null)
@@ -178,7 +177,6 @@ const ZoomBrush: FC<Props> = props => {
178
177
  showTooltip={showTooltip}
179
178
  pixelDistance={textProps.endPosition - textProps.startPosition}
180
179
  textProps={textProps}
181
- fontSize={fontSize[config.fontSize]}
182
180
  {...props}
183
181
  isBrushing={brushRef.current?.state.isBrushing}
184
182
  />
@@ -202,7 +200,7 @@ const ZoomBrush: FC<Props> = props => {
202
200
  }
203
201
 
204
202
  const BrushHandle = props => {
205
- const { x, isBrushActive, isBrushing, className, textProps, fontSize, showTooltip, left } = props
203
+ const { x, isBrushActive, isBrushing, className, textProps, showTooltip, left } = props
206
204
  const pathWidth = 8
207
205
  if (!isBrushActive) {
208
206
  return null
@@ -212,7 +210,7 @@ const BrushHandle = props => {
212
210
  const transform = isLeft ? 'scale(-1, 1)' : 'translate(0,0)'
213
211
  const textAnchor = isLeft ? 'end' : 'start'
214
212
  const tooltipText = isLeft ? ` Drag edges to focus on a specific segment ` : ''
215
- const textWidth = getTextWidth(tooltipText, `normal ${fontSize / 1.1}px sans-serif`)
213
+ const textWidth = getTextWidth(tooltipText, `${appFontSize / 1.1}px`)
216
214
 
217
215
  return (
218
216
  <>
@@ -221,7 +219,7 @@ const BrushHandle = props => {
221
219
  x={(Number(textProps.xMax) - textWidth) / 2}
222
220
  dy={-12}
223
221
  pointerEvents='visiblePainted'
224
- fontSize={fontSize / 1.1}
222
+ fontSize={appFontSize / 1.1}
225
223
  >
226
224
  {tooltipText}
227
225
  </Text>
@@ -234,7 +232,7 @@ const BrushHandle = props => {
234
232
  y={25}
235
233
  verticalAnchor='start'
236
234
  textAnchor={textAnchor}
237
- fontSize={fontSize / 1.4}
235
+ fontSize={appFontSize / 1.4}
238
236
  >
239
237
  {isLeft ? textProps.startValue : textProps.endValue}
240
238
  </Text>
@@ -11,10 +11,9 @@ export default {
11
11
  showDownloadMediaButton: false,
12
12
  theme: 'theme-blue',
13
13
  animate: false,
14
- fontSize: 'medium',
15
14
  lineDatapointStyle: 'hover',
16
15
  lineDatapointColor: 'Same as Line',
17
- barHasBorder: 'false',
16
+ barHasBorder: 'true',
18
17
  isLollipopChart: false,
19
18
  lollipopShape: 'circle',
20
19
  lollipopColorStyle: 'two-tone',
@@ -167,6 +166,7 @@ export default {
167
166
  seriesHighlight: [],
168
167
  style: 'circles',
169
168
  subStyle: 'linear blocks',
169
+ shape: 'circle',
170
170
  tickRotation: '',
171
171
  hideBorder: {
172
172
  side: false,
@@ -198,13 +198,16 @@ export default {
198
198
  bottomPrefix: '',
199
199
  bottomAbbreviated: false
200
200
  },
201
+ filters: [],
201
202
  confidenceKeys: {},
202
203
  visual: {
203
204
  border: true,
204
205
  accent: true,
205
206
  background: true,
206
207
  verticalHoverLine: false,
207
- horizontalHoverLine: false
208
+ horizontalHoverLine: false,
209
+ lineDatapointSymbol: 'none',
210
+ maximumShapeAmount: 7
208
211
  },
209
212
  useLogScale: false,
210
213
  filterBehavior: 'Filter Change',
@@ -0,0 +1,68 @@
1
+ import _ from 'lodash'
2
+ import { ChartConfig } from '../types/ChartConfig'
3
+ import * as d3 from 'd3-array'
4
+
5
+ export const getBoxPlotConfig = (newConfig: ChartConfig, data: object[]) => {
6
+ const combinedData = data
7
+ const groups = _.uniq(_.map(combinedData, newConfig.xAxis.dataKey))
8
+ const seriesKeys = _.map(newConfig.series, 'dataKey')
9
+ const plots: any[] = []
10
+
11
+ groups.forEach(g => {
12
+ seriesKeys.forEach(seriesKey => {
13
+ try {
14
+ if (!g) throw new Error('No groups resolved in box plots')
15
+
16
+ // Start handle operations on combinedData
17
+ const { count, sortedData } = _.chain(combinedData)
18
+ // Filter by xAxis data key
19
+ .filter(item => item[newConfig.xAxis.dataKey] === g)
20
+ // perform multiple operations on the filtered data
21
+ .thru(filteredData => ({
22
+ count: filteredData.length,
23
+ sortedData: _.map(filteredData, item => Number(item[seriesKey])).sort()
24
+ }))
25
+ // get the results from the chain
26
+ .value()
27
+
28
+ if (!sortedData) throw new Error('boxplots dont have data yet')
29
+ if (!plots) throw new Error('boxplots dont have plots yet')
30
+
31
+ const q1 = d3.quantile(sortedData, 0.25)
32
+ const q3 = d3.quantile(sortedData, 0.75)
33
+
34
+ const iqr = q3 - q1
35
+ const lowerBounds = q1 - 1.5 * iqr
36
+ const upperBounds = q3 + 1.5 * iqr
37
+ const nonOutliers = sortedData.filter(value => value >= lowerBounds && value <= upperBounds)
38
+ plots.push({
39
+ columnCategory: g,
40
+ columnMax: d3.max(nonOutliers),
41
+ columnThirdQuartile: _.round(q3, newConfig.dataFormat.roundTo),
42
+ columnMedian: Number(d3.median(sortedData)).toFixed(newConfig.dataFormat.roundTo),
43
+ columnFirstQuartile: _.round(q1, newConfig.dataFormat.roundTo),
44
+ columnMin: _.min(nonOutliers),
45
+ columnCount: count,
46
+ columnSd: Number(d3.deviation(sortedData)).toFixed(newConfig.dataFormat.roundTo),
47
+ columnMean: Number(d3.mean(sortedData)).toFixed(newConfig.dataFormat.roundTo),
48
+ columnIqr: _.round(iqr, newConfig.dataFormat.roundTo),
49
+ values: sortedData,
50
+ columnLowerBounds: lowerBounds,
51
+ columnUpperBounds: upperBounds,
52
+ columnOutliers: _.filter(sortedData, value => value < lowerBounds || value > upperBounds),
53
+ columnNonOutliers: _.filter(sortedData, value => value >= lowerBounds && value <= upperBounds)
54
+ })
55
+ } catch (e) {
56
+ console.error('COVE: ', e.message) // eslint-disable-line
57
+ }
58
+ })
59
+ })
60
+
61
+ // Generate a flat list of categories based on seriesKeys and groups
62
+ const categories =
63
+ seriesKeys.length > 1
64
+ ? _.flatMap(groups, value => _.map(seriesKeys, key => `${_.capitalize(key)} - ${_.capitalize(value)}`))
65
+ : groups
66
+
67
+ return [plots, categories]
68
+ }
@@ -0,0 +1,24 @@
1
+ import { colorPalettesChart as colorPalettes, twoColorPalette } from '@cdc/core/data/colorPalettes'
2
+ import { scaleOrdinal } from '@visx/scale'
3
+ import { ChartConfig } from '../types/ChartConfig'
4
+
5
+ export const getColorScale = (config: ChartConfig): ((value: string) => string) => {
6
+ const configPalette = ['Paired Bar', 'Deviation Bar'].includes(config.visualizationType)
7
+ ? config.twoColor.palette
8
+ : config.palette
9
+ const allPalettes: Record<string, string[]> = { ...colorPalettes, ...twoColorPalette }
10
+ let palette = config.customColors || allPalettes[configPalette]
11
+ let numberOfKeys = config.runtime.seriesKeys.length
12
+
13
+ while (numberOfKeys > palette.length) {
14
+ palette = palette.concat(palette)
15
+ }
16
+
17
+ palette = palette.slice(0, numberOfKeys)
18
+
19
+ return scaleOrdinal({
20
+ domain: config.runtime.seriesLabelsAll,
21
+ range: palette,
22
+ unknown: null
23
+ })
24
+ }