@cdc/chart 4.24.12-2 → 4.25.1

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 (70) hide show
  1. package/dist/cdcchart.js +79411 -78816
  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 +394 -30
  5. package/examples/private/not-loading.json +360 -0
  6. package/index.html +7 -14
  7. package/package.json +2 -2
  8. package/src/CdcChart.tsx +92 -1512
  9. package/src/CdcChartComponent.tsx +1105 -0
  10. package/src/_stories/Chart.Anchors.stories.tsx +1 -1
  11. package/src/_stories/Chart.CustomColors.stories.tsx +1 -1
  12. package/src/_stories/Chart.DynamicSeries.stories.tsx +1 -1
  13. package/src/_stories/Chart.Legend.Gradient.stories.tsx +2 -2
  14. package/src/_stories/Chart.ScatterPlot.stories.tsx +19 -0
  15. package/src/_stories/Chart.tooltip.stories.tsx +1 -2
  16. package/src/_stories/ChartAnnotation.stories.tsx +1 -1
  17. package/src/_stories/ChartAxisLabels.stories.tsx +1 -1
  18. package/src/_stories/ChartAxisTitles.stories.tsx +1 -1
  19. package/src/_stories/ChartEditor.stories.tsx +1 -1
  20. package/src/_stories/ChartLine.Suppression.stories.tsx +1 -1
  21. package/src/_stories/ChartLine.Symbols.stories.tsx +18 -0
  22. package/src/_stories/ChartPrefixSuffix.stories.tsx +1 -1
  23. package/src/_stories/_mock/line_chart_symbols.json +437 -0
  24. package/src/_stories/_mock/scatterplot-image-download.json +1244 -0
  25. package/src/components/Annotations/components/AnnotationDraggable.tsx +3 -11
  26. package/src/components/Annotations/components/AnnotationDropdown.tsx +3 -3
  27. package/src/components/Axis/Categorical.Axis.tsx +3 -4
  28. package/src/components/BarChart/components/BarChart.Horizontal.tsx +14 -5
  29. package/src/components/BarChart/components/BarChart.StackedHorizontal.tsx +10 -4
  30. package/src/components/BoxPlot/BoxPlot.tsx +34 -32
  31. package/src/components/BoxPlot/helpers/index.ts +108 -18
  32. package/src/components/DeviationBar.jsx +2 -6
  33. package/src/components/EditorPanel/EditorPanel.tsx +62 -6
  34. package/src/components/EditorPanel/components/Panels/Panel.General.tsx +4 -0
  35. package/src/components/EditorPanel/components/Panels/Panel.Visual.tsx +44 -7
  36. package/src/components/ForestPlot/ForestPlot.tsx +176 -26
  37. package/src/components/Legend/Legend.Component.tsx +29 -38
  38. package/src/components/Legend/Legend.Suppression.tsx +3 -5
  39. package/src/components/Legend/Legend.tsx +2 -2
  40. package/src/components/Legend/LegendLine.Shape.tsx +51 -0
  41. package/src/components/Legend/helpers/createFormatLabels.tsx +29 -26
  42. package/src/components/Legend/helpers/getLegendClasses.ts +20 -38
  43. package/src/components/Legend/helpers/index.ts +14 -7
  44. package/src/components/Legend/tests/getLegendClasses.test.ts +3 -20
  45. package/src/components/LineChart/components/LineChart.Circle.tsx +90 -88
  46. package/src/components/LineChart/index.tsx +4 -0
  47. package/src/components/LinearChart.tsx +54 -29
  48. package/src/components/PairedBarChart.jsx +2 -9
  49. package/src/components/ZoomBrush.tsx +5 -7
  50. package/src/data/initial-state.js +6 -3
  51. package/src/helpers/getBoxPlotConfig.ts +68 -0
  52. package/src/helpers/getColorScale.ts +28 -0
  53. package/src/helpers/getComboChartConfig.ts +42 -0
  54. package/src/helpers/getExcludedData.ts +37 -0
  55. package/src/helpers/getTopAxis.ts +7 -0
  56. package/src/hooks/useBarChart.ts +28 -9
  57. package/src/hooks/{useHighlightedBars.js → useHighlightedBars.ts} +2 -1
  58. package/src/hooks/useIntersectionObserver.ts +37 -0
  59. package/src/hooks/useMinMax.ts +4 -0
  60. package/src/hooks/useReduceData.ts +1 -1
  61. package/src/hooks/useTooltip.tsx +9 -1
  62. package/src/index.jsx +1 -0
  63. package/src/scss/DataTable.scss +0 -5
  64. package/src/scss/main.scss +30 -115
  65. package/src/types/ChartConfig.ts +6 -3
  66. package/src/types/ChartContext.ts +1 -3
  67. package/src/helpers/getQuartiles.ts +0 -27
  68. package/src/hooks/useColorScale.ts +0 -50
  69. package/src/hooks/useIntersectionObserver.jsx +0 -29
  70. package/src/hooks/useTopAxis.js +0 -6
@@ -27,7 +27,7 @@ import CategoricalYAxis from './Axis/Categorical.Axis'
27
27
 
28
28
  // Helpers
29
29
  import { isConvertLineToBarGraph } from '../helpers/isConvertLineToBarGraph'
30
- import { isLegendWrapViewport } from '@cdc/core/helpers/viewports'
30
+ import { isLegendWrapViewport, isMobileHeightViewport } from '@cdc/core/helpers/viewports'
31
31
  import { getTextWidth } from '@cdc/core/helpers/getTextWidth'
32
32
  import { calcInitialHeight } from '../helpers/sizeHelpers'
33
33
 
@@ -36,13 +36,13 @@ import useMinMax from '../hooks/useMinMax'
36
36
  import useReduceData from '../hooks/useReduceData'
37
37
  import useRightAxis from '../hooks/useRightAxis'
38
38
  import useScales, { getTickValues, filterAndShiftLinearDateTicks } from '../hooks/useScales'
39
- import useTopAxis from '../hooks/useTopAxis'
39
+ import getTopAxis from '../helpers/getTopAxis'
40
40
  import { useTooltip as useCoveTooltip } from '../hooks/useTooltip'
41
41
  import { useEditorPermissions } from './EditorPanel/useEditorPermissions'
42
42
  import Annotation from './Annotations'
43
43
  import { BlurStrokeText } from '@cdc/core/components/BlurStrokeText'
44
- import { fontSizes } from '@cdc/core/helpers/cove/fontSettings'
45
44
  import { countNumOfTicks } from '../helpers/countNumOfTicks'
45
+ import _ from 'lodash'
46
46
 
47
47
  type LinearChartProps = {
48
48
  parentWidth: number
@@ -50,9 +50,14 @@ type LinearChartProps = {
50
50
  }
51
51
 
52
52
  const BOTTOM_LABEL_PADDING = 9
53
- const X_TICK_LABEL_PADDING = 3
53
+ const X_TICK_LABEL_PADDING = 4.5
54
54
  const DEFAULT_TICK_LENGTH = 8
55
55
  const MONTH_AS_MS = 1000 * 60 * 60 * 24 * 30
56
+ const TICK_LABEL_FONT_SIZE = 16
57
+ const TICK_LABEL_FONT_SIZE_SMALL = 13
58
+ const AXIS_LABEL_FONT_SIZE = 18
59
+ const AXIS_LABEL_FONT_SIZE_SMALL = 14
60
+ const TICK_LABEL_MARGIN_RIGHT = 4.5
56
61
 
57
62
  const LinearChart = forwardRef<SVGAElement, LinearChartProps>(({ parentHeight, parentWidth }, svgRef) => {
58
63
  // prettier-ignore
@@ -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
@@ -414,12 +422,20 @@ const LinearChart = forwardRef<SVGAElement, LinearChartProps>(({ parentHeight, p
414
422
  if (!yAxisAutoPadding) return
415
423
  setYAxisAutoPadding(0)
416
424
  }, [maxValue])
425
+
417
426
  useEffect(() => {
418
427
  if (orientation === 'horizontal') return
428
+ if (!labelsOverflow) return
429
+
430
+ // minimum percentage of the max value that the distance should be from the top grid line
431
+ const MINIMUM_DISTANCE_PERCENTAGE = 0.025
432
+
433
+ const topGridLine = Math.max(...yScale.ticks(handleNumTicks))
434
+ const needsPaddingThreshold = topGridLine - maxValue * MINIMUM_DISTANCE_PERCENTAGE
435
+ const maxValueIsGreaterThanThreshold = maxValue > needsPaddingThreshold
419
436
 
420
- const maxValueIsGreaterThanTopGridLine = maxValue > Math.max(...yScale.ticks(handleNumTicks))
437
+ if (!maxValueIsGreaterThanThreshold) return
421
438
 
422
- if (!maxValueIsGreaterThanTopGridLine || !labelsOverflow) return
423
439
  const ticks = yScale.ticks(handleNumTicks)
424
440
  const tickGap = ticks.length === 1 ? ticks[0] : ticks[1] - ticks[0]
425
441
  const nextTick = Math.max(...yScale.ticks(handleNumTicks)) + tickGap
@@ -427,9 +443,8 @@ const LinearChart = forwardRef<SVGAElement, LinearChartProps>(({ parentHeight, p
427
443
  const calculatedPadding = (nextTick - maxValue) / divideBy
428
444
 
429
445
  // if auto padding is too close to next tick, add one more ticks worth of padding
430
- const PADDING_THRESHOLD = 0.025
431
446
  const newPadding =
432
- calculatedPadding > PADDING_THRESHOLD ? calculatedPadding : calculatedPadding + tickGap / divideBy
447
+ calculatedPadding > MINIMUM_DISTANCE_PERCENTAGE ? calculatedPadding : calculatedPadding + tickGap / divideBy
433
448
 
434
449
  /* sometimes even though the padding is getting to the next tick exactly,
435
450
  d3 still doesn't show the tick. we add 0.1 to ensure to tip it over the edge */
@@ -447,7 +462,7 @@ const LinearChart = forwardRef<SVGAElement, LinearChartProps>(({ parentHeight, p
447
462
  const numberOfTicks = filteredTicks?.length
448
463
  const xMaxHalf = xScale.range()[0] || xMax / 2
449
464
  const tickWidthAll = filteredTicks.map(tick =>
450
- getTextWidth(formatNumber(tick.value, 'left'), `normal ${fontSizes[config.fontSize]}px sans-serif`)
465
+ getTextWidth(formatNumber(tick.value, 'left'), GET_TEXT_WIDTH_FONT)
451
466
  )
452
467
  const accumulator = 100
453
468
  const sumOfTickWidth = tickWidthAll.reduce((a, b) => a + b, accumulator)
@@ -484,10 +499,6 @@ const LinearChart = forwardRef<SVGAElement, LinearChartProps>(({ parentHeight, p
484
499
  return (
485
500
  <Group className='bottom-axis'>
486
501
  {props.ticks.map((tick, i) => {
487
- const textWidth = getTextWidth(
488
- formatNumber(tick.value, 'left'),
489
- `normal ${fontSizes[config.fontSize]}px sans-serif`
490
- )
491
502
  const isTicksOverlapping = getTickPositions(props.ticks, g1xScale)
492
503
  const maxTickRotation = Number(config.xAxis.maxTickRotation) || 90
493
504
  const isResponsiveTicks = config.isResponsiveTicks && isTicksOverlapping
@@ -506,6 +517,7 @@ const LinearChart = forwardRef<SVGAElement, LinearChartProps>(({ parentHeight, p
506
517
  angle={-angle}
507
518
  verticalAnchor={angle ? 'middle' : 'start'}
508
519
  textAnchor={textAnchor}
520
+ fontSize={tickLabelFontSize}
509
521
  >
510
522
  {formatNumber(tick.value, 'left')}
511
523
  </Text>
@@ -536,10 +548,6 @@ const LinearChart = forwardRef<SVGAElement, LinearChartProps>(({ parentHeight, p
536
548
  <>
537
549
  <Group className='bottom-axis'>
538
550
  {props.ticks.map((tick, i) => {
539
- const textWidth = getTextWidth(
540
- formatNumber(tick.value, 'left'),
541
- `normal ${fontSizes[config.fontSize]}px sans-serif`
542
- )
543
551
  const isTicksOverlapping = getTickPositions(props.ticks, g2xScale)
544
552
  const maxTickRotation = Number(config.xAxis.maxTickRotation) || 90
545
553
  const isResponsiveTicks = config.isResponsiveTicks && isTicksOverlapping
@@ -557,6 +565,7 @@ const LinearChart = forwardRef<SVGAElement, LinearChartProps>(({ parentHeight, p
557
565
  angle={-angle}
558
566
  verticalAnchor={angle ? 'middle' : 'start'}
559
567
  textAnchor={textAnchor}
568
+ fontSize={tickLabelFontSize}
560
569
  >
561
570
  {formatNumber(tick.value, 'left')}
562
571
  </Text>
@@ -574,6 +583,7 @@ const LinearChart = forwardRef<SVGAElement, LinearChartProps>(({ parentHeight, p
574
583
  stroke='#333'
575
584
  textAnchor={'middle'}
576
585
  verticalAnchor='start'
586
+ fontSize={axisLabelFontSize}
577
587
  >
578
588
  {runtime.xAxis.label}
579
589
  </Text>
@@ -650,6 +660,7 @@ const LinearChart = forwardRef<SVGAElement, LinearChartProps>(({ parentHeight, p
650
660
  transform={`translate(${-1 * runtime.yAxis.size + yLabelOffset}, ${axisCenter}) rotate(-90)`}
651
661
  fontWeight='bold'
652
662
  fill={config.yAxis.labelColor}
663
+ fontSize={axisLabelFontSize}
653
664
  >
654
665
  {props.label}
655
666
  </Text>
@@ -1043,7 +1054,7 @@ const LinearChart = forwardRef<SVGAElement, LinearChartProps>(({ parentHeight, p
1043
1054
  const lastTick = props.ticks.length - 1 === i
1044
1055
  const hideTopTick = lastTick && onlyShowTopPrefixSuffix && suffix && !suffixHasNoSpace
1045
1056
  const valueOnLinePadding = hideAxis ? -8 : -12
1046
- const labelXPadding = labelsAboveGridlines ? valueOnLinePadding : 2
1057
+ const labelXPadding = labelsAboveGridlines ? valueOnLinePadding : TICK_LABEL_MARGIN_RIGHT
1047
1058
  const labelYPadding = labelsAboveGridlines ? 4 : 0
1048
1059
  const labelX = tick.to.x - labelXPadding
1049
1060
  const labelY = tick.to.y - labelYPadding
@@ -1060,6 +1071,7 @@ const LinearChart = forwardRef<SVGAElement, LinearChartProps>(({ parentHeight, p
1060
1071
  to={isLogarithmicAxis ? to : tick.to}
1061
1072
  stroke={config.yAxis.tickColor}
1062
1073
  display={orientation === 'horizontal' ? 'none' : 'block'}
1074
+ fontSize={tickLabelFontSize}
1063
1075
  />
1064
1076
  )}
1065
1077
 
@@ -1077,6 +1089,7 @@ const LinearChart = forwardRef<SVGAElement, LinearChartProps>(({ parentHeight, p
1077
1089
  }) rotate(-${config.runtime.horizontal ? config.runtime.yAxis.tickRotation || 0 : 0})`}
1078
1090
  verticalAnchor={'start'}
1079
1091
  textAnchor={'end'}
1092
+ fontSize={tickLabelFontSize}
1080
1093
  >
1081
1094
  {tick.formattedValue}
1082
1095
  </Text>
@@ -1092,6 +1105,7 @@ const LinearChart = forwardRef<SVGAElement, LinearChartProps>(({ parentHeight, p
1092
1105
  }) rotate(-${runtime.horizontal ? runtime.yAxis.tickRotation : 0})`}
1093
1106
  verticalAnchor={'start'}
1094
1107
  textAnchor={'end'}
1108
+ fontSize={tickLabelFontSize}
1095
1109
  >
1096
1110
  {tick.formattedValue}
1097
1111
  </Text>
@@ -1106,6 +1120,7 @@ const LinearChart = forwardRef<SVGAElement, LinearChartProps>(({ parentHeight, p
1106
1120
  }) rotate(-${runtime.horizontal ? runtime.yAxis.tickRotation : 0})`}
1107
1121
  textAnchor={'end'}
1108
1122
  verticalAnchor='middle'
1123
+ fontSize={tickLabelFontSize}
1109
1124
  >
1110
1125
  {tick.formattedValue}
1111
1126
  </Text>
@@ -1121,6 +1136,7 @@ const LinearChart = forwardRef<SVGAElement, LinearChartProps>(({ parentHeight, p
1121
1136
  }) rotate(-${runtime.horizontal ? runtime.yAxis.tickRotation : 0})`}
1122
1137
  textAnchor={'end'}
1123
1138
  verticalAnchor='middle'
1139
+ fontSize={tickLabelFontSize}
1124
1140
  >
1125
1141
  {tick.formattedValue}
1126
1142
  </Text>
@@ -1139,6 +1155,7 @@ const LinearChart = forwardRef<SVGAElement, LinearChartProps>(({ parentHeight, p
1139
1155
  verticalAnchor={config.runtime.horizontal ? 'start' : 'middle'}
1140
1156
  textAnchor={config.runtime.horizontal ? 'start' : 'end'}
1141
1157
  fill={config.yAxis.tickLabelColor}
1158
+ fontSize={tickLabelFontSize}
1142
1159
  >
1143
1160
  {config.runtime.seriesLabelsAll[tick.formattedValue - 1]}
1144
1161
  </Text>
@@ -1181,6 +1198,7 @@ const LinearChart = forwardRef<SVGAElement, LinearChartProps>(({ parentHeight, p
1181
1198
  paintOrder={'stroke'} // keeps stroke under fill
1182
1199
  strokeLinejoin='round'
1183
1200
  style={{ whiteSpace: 'pre-wrap' }} // prevents leading spaces from being trimmed
1201
+ fontSize={tickLabelFontSize}
1184
1202
  >
1185
1203
  {suffix}
1186
1204
  </BlurStrokeText>
@@ -1202,6 +1220,7 @@ const LinearChart = forwardRef<SVGAElement, LinearChartProps>(({ parentHeight, p
1202
1220
  strokeLinejoin='round'
1203
1221
  paintOrder={'stroke'} // keeps stroke under fill
1204
1222
  style={{ whiteSpace: 'pre-wrap' }} // prevents leading spaces from being trimmed
1223
+ fontSize={tickLabelFontSize}
1205
1224
  >
1206
1225
  {`${tick.formattedValue}${combineDomSuffixWithValue ? suffix : ''}`}
1207
1226
  </BlurStrokeText>
@@ -1217,6 +1236,7 @@ const LinearChart = forwardRef<SVGAElement, LinearChartProps>(({ parentHeight, p
1217
1236
  transform={`translate(${-1 * runtime.yAxis.size + yLabelOffset}, ${axisCenter}) rotate(-90)`}
1218
1237
  fontWeight='bold'
1219
1238
  fill={config.yAxis.labelColor}
1239
+ fontSize={axisLabelFontSize}
1220
1240
  >
1221
1241
  {props.label}
1222
1242
  </Text>
@@ -1279,6 +1299,7 @@ const LinearChart = forwardRef<SVGAElement, LinearChartProps>(({ parentHeight, p
1279
1299
  verticalAnchor={runtime.horizontal ? 'start' : 'middle'}
1280
1300
  textAnchor={'start'}
1281
1301
  fill={config.yAxis.rightAxisTickLabelColor}
1302
+ fontSize={tickLabelFontSize}
1282
1303
  >
1283
1304
  {tick.formattedValue}
1284
1305
  </Text>
@@ -1298,6 +1319,7 @@ const LinearChart = forwardRef<SVGAElement, LinearChartProps>(({ parentHeight, p
1298
1319
  }, ${axisCenter}) rotate(-90)`}
1299
1320
  fontWeight='bold'
1300
1321
  fill={config.yAxis.rightAxisLabelColor}
1322
+ fontSize={axisLabelFontSize}
1301
1323
  >
1302
1324
  {props.label}
1303
1325
  </Text>
@@ -1337,17 +1359,18 @@ const LinearChart = forwardRef<SVGAElement, LinearChartProps>(({ parentHeight, p
1337
1359
  numTicks={useDateSpanMonths ? dateSpanMonths : xTickCount}
1338
1360
  tickStroke='#333'
1339
1361
  tickValues={
1340
- config.xAxis.manual
1362
+ config.runtime.xAxis.manual
1341
1363
  ? getTickValues(xAxisDataMapped, xScale, isDateTime ? xTickCount : getManualStep(), config)
1342
- : config.xAxis.type === 'date'
1364
+ : config.runtime.xAxis.type === 'date'
1343
1365
  ? xAxisDataMapped
1344
1366
  : undefined
1345
1367
  }
1346
1368
  >
1347
1369
  {props => {
1370
+ const hasDynamicCategory = config.series.some(s => s.dynamicCategory)
1348
1371
  // For these charts, we generated all ticks in tickValues above, and now need to filter/shift them
1349
1372
  // so the last tick is always labeled
1350
- if (config.xAxis.type === 'date' && !config.xAxis.manual) {
1373
+ if (config.runtime.xAxis.type === 'date' && !config.runtime.xAxis.manual && !hasDynamicCategory) {
1351
1374
  props.ticks = filterAndShiftLinearDateTicks(config, props, xAxisDataMapped, formatDate)
1352
1375
  }
1353
1376
 
@@ -1355,7 +1378,11 @@ const LinearChart = forwardRef<SVGAElement, LinearChartProps>(({ parentHeight, p
1355
1378
  useDateSpanMonths &&
1356
1379
  xScale
1357
1380
  .ticks(xTickCount)
1358
- .map(t => props.ticks.findIndex(tick => tick.value.getTime() === t.getTime()))
1381
+ .map(t =>
1382
+ props.ticks.findIndex(
1383
+ tick => (typeof tick.value === 'number' ? tick.value : tick.value.getTime()) === t.getTime()
1384
+ )
1385
+ )
1359
1386
  .slice(0, 2)
1360
1387
  .reduce((acc, curr) => curr - acc)
1361
1388
 
@@ -1379,16 +1406,12 @@ const LinearChart = forwardRef<SVGAElement, LinearChartProps>(({ parentHeight, p
1379
1406
 
1380
1407
  // Calculate sumOfTickWidth here, before map function
1381
1408
  const tickWidthMax = Math.max(
1382
- ...filteredTicks.map(tick =>
1383
- getTextWidth(tick.formattedValue, `normal ${fontSizes[config.fontSize]}px sans-serif`)
1384
- )
1409
+ ...filteredTicks.map(tick => getTextWidth(tick.formattedValue, GET_TEXT_WIDTH_FONT))
1385
1410
  )
1386
1411
  // const marginTop = 20 // moved to top bc need for yMax calcs
1387
1412
  const accumulator = ismultiLabel ? 180 : 100
1388
1413
 
1389
- const textWidths = filteredTicks.map(tick =>
1390
- getTextWidth(tick.formattedValue, `normal ${fontSizes[config.fontSize]}px sans-serif`)
1391
- )
1414
+ const textWidths = filteredTicks.map(tick => getTextWidth(tick.formattedValue, GET_TEXT_WIDTH_FONT))
1392
1415
  const sumOfTickWidth = textWidths.reduce((a, b) => a + b, accumulator)
1393
1416
  const spaceBetweenEachTick = (xMax - sumOfTickWidth) / (filteredTicks.length - 1)
1394
1417
 
@@ -1480,6 +1503,7 @@ const LinearChart = forwardRef<SVGAElement, LinearChartProps>(({ parentHeight, p
1480
1503
  : undefined
1481
1504
  }
1482
1505
  fill={config.xAxis.tickLabelColor}
1506
+ fontSize={tickLabelFontSize}
1483
1507
  >
1484
1508
  {tick.formattedValue}
1485
1509
  </Text>
@@ -1497,6 +1521,7 @@ const LinearChart = forwardRef<SVGAElement, LinearChartProps>(({ parentHeight, p
1497
1521
  verticalAnchor='start'
1498
1522
  fontWeight='bold'
1499
1523
  fill={config.xAxis.labelColor}
1524
+ fontSize={axisLabelFontSize}
1500
1525
  >
1501
1526
  {props.label}
1502
1527
  </Text>
@@ -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, `normal ${appFontSize / 1.1}px sans-serif`)
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,28 @@
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
+ let newColorScale
13
+
14
+ while (numberOfKeys > palette.length) {
15
+ palette = palette.concat(palette)
16
+ }
17
+
18
+ palette = palette.slice(0, numberOfKeys)
19
+
20
+ newColorScale = () =>
21
+ scaleOrdinal({
22
+ domain: config.runtime.seriesLabelsAll,
23
+ range: palette,
24
+ unknown: null
25
+ })
26
+
27
+ return newColorScale
28
+ }
@@ -0,0 +1,42 @@
1
+ import _ from 'lodash'
2
+ import { ChartConfig } from '../types/ChartConfig'
3
+ import * as d3 from 'd3-array'
4
+
5
+ export const getComboChartConfig = (newConfig: ChartConfig) => {
6
+ if (newConfig.visualizationType !== 'Combo' || !newConfig.series) return
7
+
8
+ const runtimeKeys = {
9
+ barSeriesKeys: [],
10
+ lineSeriesKeys: [],
11
+ areaSeriesKeys: [],
12
+ forecastingSeriesKeys: []
13
+ }
14
+
15
+ // Define a mapping of series types to runtime keys
16
+ const seriesTypeMap = new Map([
17
+ ['Area Chart', 'areaSeriesKeys'],
18
+ ['Forecasting', 'forecastingSeriesKeys'],
19
+ ['Bar', 'barSeriesKeys'],
20
+ ['Combo', 'barSeriesKeys'],
21
+ ['Line', 'lineSeriesKeys'],
22
+ ['dashed-sm', 'lineSeriesKeys'],
23
+ ['dashed-md', 'lineSeriesKeys'],
24
+ ['dashed-lg', 'lineSeriesKeys']
25
+ ])
26
+
27
+ newConfig.series.forEach(series => {
28
+ const runtimeKey = seriesTypeMap.get(series.type)
29
+ if (runtimeKey) {
30
+ const valueToPush = runtimeKey === 'barSeriesKeys' || runtimeKey === 'lineSeriesKeys' ? series.dataKey : series
31
+ runtimeKeys[runtimeKey].push(valueToPush)
32
+ }
33
+
34
+ // Change Combo series type to Bar
35
+ if (series.type === 'Combo') {
36
+ series.type = 'Bar'
37
+ }
38
+ })
39
+
40
+ // Assign the processed runtime keys to the configuration
41
+ return { ...newConfig.runtime, ...runtimeKeys }
42
+ }
@@ -0,0 +1,37 @@
1
+ import { isDateScale } from '@cdc/core/helpers/cove/date'
2
+ import _ from 'lodash'
3
+ import { ChartConfig } from '../types/ChartConfig'
4
+ export const getExcludedData = (newConfig: ChartConfig, data: object[]) => {
5
+ let newExcludedData = data
6
+ if (newConfig.exclusions && newConfig.exclusions.active) {
7
+ if (newConfig.xAxis.type === 'categorical' && newConfig.exclusions.keys?.length > 0) {
8
+ newExcludedData = data.filter(e => !newConfig.exclusions.keys.includes(e[newConfig.xAxis.dataKey]))
9
+ } else if (
10
+ isDateScale(newConfig.xAxis) &&
11
+ (newConfig.exclusions.dateStart || newConfig.exclusions.dateEnd) &&
12
+ newConfig.xAxis.dateParseFormat
13
+ ) {
14
+ // Filter dates
15
+ const timestamp = e => new Date(e).getTime()
16
+
17
+ let startDate = timestamp(newConfig.exclusions.dateStart)
18
+ let endDate = timestamp(newConfig.exclusions.dateEnd) + 86399999 //Increase by 24h in ms (86400000ms - 1ms) to include selected end date for .getTime() comparative
19
+
20
+ let startDateValid = undefined !== typeof startDate && false === isNaN(startDate)
21
+ let endDateValid = undefined !== typeof endDate && false === isNaN(endDate)
22
+
23
+ if (startDateValid && endDateValid) {
24
+ newExcludedData = data.filter(
25
+ e => timestamp(e[newConfig.xAxis.dataKey]) >= startDate && timestamp(e[newConfig.xAxis.dataKey]) <= endDate
26
+ )
27
+ } else if (startDateValid) {
28
+ newExcludedData = data.filter(e => timestamp(e[newConfig.xAxis.dataKey]) >= startDate)
29
+ } else if (endDateValid) {
30
+ newExcludedData = data.filter(e => timestamp(e[newConfig.xAxis.dataKey]) <= endDate)
31
+ }
32
+ } else {
33
+ newExcludedData = data
34
+ }
35
+ }
36
+ return newExcludedData
37
+ }
@@ -0,0 +1,7 @@
1
+ export default function getTopAxis(config) {
2
+ // When to show top axis
3
+ const hasTopAxis =
4
+ config.visualizationType === 'Bar' || config.visualizationType === 'Combo' || config.visualizationType === 'Line'
5
+
6
+ return { hasTopAxis }
7
+ }