@cdc/chart 4.25.6 → 4.25.7

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 (36) hide show
  1. package/dist/cdcchart.js +52667 -32246
  2. package/package.json +3 -2
  3. package/src/CdcChartComponent.tsx +13 -9
  4. package/src/_stories/Chart.BoxPlot.stories.tsx +35 -0
  5. package/src/_stories/Chart.stories.tsx +0 -7
  6. package/src/_stories/Chart.tooltip.stories.tsx +35 -275
  7. package/src/_stories/_mock/bar-chart-suppressed.json +2 -80
  8. package/src/_stories/_mock/boxplot_multiseries.json +252 -166
  9. package/src/components/AreaChart/components/AreaChart.Stacked.jsx +1 -1
  10. package/src/components/AreaChart/components/AreaChart.jsx +4 -8
  11. package/src/components/BarChart/components/BarChart.Horizontal.tsx +34 -2
  12. package/src/components/BarChart/components/BarChart.Vertical.tsx +15 -0
  13. package/src/components/BoxPlot/BoxPlot.Horizontal.tsx +131 -0
  14. package/src/components/BoxPlot/{BoxPlot.tsx → BoxPlot.Vertical.tsx} +4 -4
  15. package/src/components/BoxPlot/helpers/index.ts +32 -12
  16. package/src/components/BrushChart.tsx +1 -1
  17. package/src/components/EditorPanel/EditorPanel.tsx +19 -14
  18. package/src/components/EditorPanel/components/Panels/Panel.General.tsx +2 -2
  19. package/src/components/Forecasting/{Forecasting.jsx → Forecasting.tsx} +32 -12
  20. package/src/components/Legend/LegendGroup/LegendGroup.tsx +1 -0
  21. package/src/components/Legend/helpers/index.ts +2 -2
  22. package/src/components/LineChart/helpers.ts +7 -7
  23. package/src/components/LinearChart.tsx +127 -72
  24. package/src/data/initial-state.js +1 -5
  25. package/src/helpers/countNumOfTicks.ts +4 -19
  26. package/src/helpers/filterAndShiftLinearDateTicks.ts +58 -0
  27. package/src/helpers/getBridgedData.ts +13 -0
  28. package/src/helpers/tests/getBridgedData.test.ts +64 -0
  29. package/src/hooks/useScales.ts +42 -42
  30. package/src/hooks/useTooltip.tsx +3 -2
  31. package/src/scss/main.scss +2 -4
  32. package/src/store/chart.actions.ts +2 -2
  33. package/src/store/chart.reducer.ts +4 -12
  34. package/src/types/ChartConfig.ts +1 -6
  35. package/src/components/BoxPlot/index.tsx +0 -3
  36. /package/src/components/Brush/{BrushController..tsx → BrushController.tsx} +0 -0
@@ -14,7 +14,8 @@ import { isDateScale } from '@cdc/core/helpers/cove/date'
14
14
  import { AreaChartStacked } from './AreaChart'
15
15
  import BarChart from './BarChart'
16
16
  import ConfigContext from '../ConfigContext'
17
- import BoxPlot from './BoxPlot'
17
+ import BoxPlotVertical from './BoxPlot/BoxPlot.Vertical'
18
+ import BoxPlotHorizontal from './BoxPlot/BoxPlot.Horizontal'
18
19
  import ScatterPlot from './ScatterPlot'
19
20
  import DeviationBar from './DeviationBar'
20
21
  import ErrorBoundary from '@cdc/core/components/ErrorBoundary'
@@ -25,17 +26,20 @@ import PairedBarChart from './PairedBarChart'
25
26
  import useIntersectionObserver from '../hooks/useIntersectionObserver'
26
27
  import Regions from './Regions'
27
28
  import CategoricalYAxis from './Axis/Categorical.Axis'
29
+ import BrushChart from './Brush/BrushController'
28
30
 
29
31
  // Helpers
30
32
  import { isLegendWrapViewport, isMobileHeightViewport } from '@cdc/core/helpers/viewports'
31
33
  import { getTextWidth } from '@cdc/core/helpers/getTextWidth'
32
34
  import { calcInitialHeight, handleAutoPaddingRight } from '../helpers/sizeHelpers'
35
+ import { filterAndShiftLinearDateTicks } from '../helpers/filterAndShiftLinearDateTicks'
33
36
 
34
37
  // Hooks
35
38
  import useMinMax from '../hooks/useMinMax'
36
39
  import useReduceData from '../hooks/useReduceData'
37
40
  import useRightAxis from '../hooks/useRightAxis'
38
- import useScales, { getTickValues, filterAndShiftLinearDateTicks } from '../hooks/useScales'
41
+ import useScales, { getTickValues } from '../hooks/useScales'
42
+
39
43
  import getTopAxis from '../helpers/getTopAxis'
40
44
  import { useTooltip as useCoveTooltip } from '../hooks/useTooltip'
41
45
  import { useEditorPermissions } from './EditorPanel/useEditorPermissions'
@@ -43,7 +47,6 @@ import Annotation from './Annotations'
43
47
  import { BlurStrokeText } from '@cdc/core/components/BlurStrokeText'
44
48
  import { countNumOfTicks } from '../helpers/countNumOfTicks'
45
49
  import HoverLine from './HoverLine/HoverLine'
46
- import BrushChart from './BrushChart'
47
50
 
48
51
  type LinearChartProps = {
49
52
  parentWidth: number
@@ -94,7 +97,7 @@ const LinearChart = forwardRef<SVGAElement, LinearChartProps>(({ parentHeight, p
94
97
  tableData,
95
98
  transformedData: data,
96
99
  seriesHighlight,
97
- brushConfig
100
+
98
101
  } = useContext(ConfigContext)
99
102
 
100
103
  // CONFIG
@@ -208,10 +211,7 @@ const LinearChart = forwardRef<SVGAElement, LinearChartProps>(({ parentHeight, p
208
211
  ? parseDate(d[config.runtime.originalXAxis.dataKey]).getTime()
209
212
  : d[config.runtime.originalXAxis.dataKey]
210
213
  const getYAxisData = (d, seriesKey) => d[seriesKey]
211
- const xAxisDataMapped =
212
- config.brush.active && brushConfig.data?.length
213
- ? brushConfig.data.map(d => getXAxisData(d))
214
- : data.map(d => getXAxisData(d))
214
+ const xAxisDataMapped = data.map(d => getXAxisData(d))
215
215
  const section = config.orientation === 'horizontal' || config.visualizationType === 'Forest Plot' ? 'yAxis' : 'xAxis'
216
216
  const properties = {
217
217
  data,
@@ -288,7 +288,7 @@ const LinearChart = forwardRef<SVGAElement, LinearChartProps>(({ parentHeight, p
288
288
  if (config.data && !config.data[index] && visualizationType === 'Forest Plot') return
289
289
  if (config.visualizationType === 'Forest Plot') return config.data[index][config.xAxis.dataKey]
290
290
  if (isDateScale(runtime.yAxis)) return formatDate(parseDate(tick))
291
- if (orientation === 'vertical' && max - min < 3)
291
+ if (orientation === 'vertical' && max - min < 3 && !config.dataFormat?.roundTo)
292
292
  return formatNumber(tick, 'left', shouldAbbreviate, false, false, '1', { index, length: ticks.length })
293
293
  if (orientation === 'vertical') {
294
294
  // TODO suggestion: pass all options as object key/values to allow for more flexibility
@@ -418,8 +418,9 @@ const LinearChart = forwardRef<SVGAElement, LinearChartProps>(({ parentHeight, p
418
418
  const topLabelOnGridline = topYLabelRef.current && yAxis.labelsAboveGridlines
419
419
 
420
420
  // Heights to add
421
- const brushHeight = brush?.active ? brush?.height + brush?.height : 0
422
- const brushHeightWithMargin = config.brush?.active ? brushHeight + brushHeight : 0
421
+
422
+ const brushHeight = 25
423
+ const brushHeightWithMargin = config.xAxis.brushActive ? brushHeight + brushHeight : 0
423
424
  const forestRowsHeight = isForestPlot ? config.data.length * forestPlot.rowHeight : 0
424
425
  const topLabelOnGridlineHeight = topLabelOnGridline ? topYLabelRef.current.getBBox().height : 0
425
426
  const additionalHeight = axisBottomHeight + brushHeightWithMargin + forestRowsHeight + topLabelOnGridlineHeight
@@ -447,7 +448,15 @@ const LinearChart = forwardRef<SVGAElement, LinearChartProps>(({ parentHeight, p
447
448
  const legendIsLeftOrRight =
448
449
  legend?.position !== 'top' && legend?.position !== 'bottom' && !isLegendWrapViewport(currentViewport)
449
450
  legendRef.current.style.transform = legendIsLeftOrRight ? `translateY(${topLabelOnGridlineHeight}px)` : 'none'
450
- }, [axisBottomRef.current, config, bottomLabelStart, brush, currentViewport, topYLabelRef.current, initialHeight])
451
+ }, [
452
+ axisBottomRef.current,
453
+ config,
454
+ bottomLabelStart,
455
+ config.xAxis.brushActive,
456
+ currentViewport,
457
+ topYLabelRef.current,
458
+ initialHeight
459
+ ])
451
460
 
452
461
  useEffect(() => {
453
462
  if (lastMaxValue.current === maxValue) return
@@ -663,8 +672,9 @@ const LinearChart = forwardRef<SVGAElement, LinearChartProps>(({ parentHeight, p
663
672
  onMouseMove={onMouseMove}
664
673
  width={parentWidth + config.yAxis.rightAxisSize}
665
674
  height={isNoDataAvailable ? 1 : parentHeight}
666
- className={`linear ${config.animate ? 'animated' : ''} ${animatedChart && config.animate ? 'animate' : ''} ${debugSvg && 'debug'
667
- } ${isDraggingAnnotation && 'dragging-annotation'}`}
675
+ className={`linear ${config.animate ? 'animated' : ''} ${animatedChart && config.animate ? 'animate' : ''} ${
676
+ debugSvg && 'debug'
677
+ } ${isDraggingAnnotation && 'dragging-annotation'}`}
668
678
  role='img'
669
679
  aria-label={handleChartAriaLabels(config)}
670
680
  style={{ overflow: 'visible' }}
@@ -744,8 +754,19 @@ const LinearChart = forwardRef<SVGAElement, LinearChartProps>(({ parentHeight, p
744
754
  showTooltip={showTooltip}
745
755
  />
746
756
  )}
747
- {visualizationType === 'Box Plot' && (
748
- <BoxPlot
757
+ {visualizationType === 'Box Plot' && config.orientation === 'vertical' && (
758
+ <BoxPlotVertical
759
+ seriesScale={seriesScale}
760
+ xMax={xMax}
761
+ yMax={yMax}
762
+ min={min}
763
+ max={max}
764
+ xScale={xScale}
765
+ yScale={yScale}
766
+ />
767
+ )}
768
+ {visualizationType === 'Box Plot' && config.orientation === 'horizontal' && (
769
+ <BoxPlotHorizontal
749
770
  seriesScale={seriesScale}
750
771
  xMax={xMax}
751
772
  yMax={yMax}
@@ -757,20 +778,20 @@ const LinearChart = forwardRef<SVGAElement, LinearChartProps>(({ parentHeight, p
757
778
  )}
758
779
  {((visualizationType === 'Area Chart' && config.visualizationSubType === 'stacked') ||
759
780
  visualizationType === 'Combo') && (
760
- <AreaChartStacked
761
- xScale={xScale}
762
- yScale={yScale}
763
- yMax={yMax}
764
- xMax={xMax}
765
- chartRef={svgRef}
766
- width={xMax}
767
- height={yMax}
768
- handleTooltipMouseOver={handleTooltipMouseOver}
769
- handleTooltipMouseOff={handleTooltipMouseOff}
770
- tooltipData={tooltipData}
771
- showTooltip={showTooltip}
772
- />
773
- )}
781
+ <AreaChartStacked
782
+ xScale={xScale}
783
+ yScale={yScale}
784
+ yMax={yMax}
785
+ xMax={xMax}
786
+ chartRef={svgRef}
787
+ width={xMax}
788
+ height={yMax}
789
+ handleTooltipMouseOver={handleTooltipMouseOver}
790
+ handleTooltipMouseOff={handleTooltipMouseOff}
791
+ tooltipData={tooltipData}
792
+ showTooltip={showTooltip}
793
+ />
794
+ )}
774
795
  {(visualizationType === 'Bar' || visualizationType === 'Combo' || convertLineToBarGraph) && (
775
796
  <BarChart
776
797
  xScale={xScale}
@@ -863,7 +884,7 @@ const LinearChart = forwardRef<SVGAElement, LinearChartProps>(({ parentHeight, p
863
884
  />
864
885
  )}
865
886
  {/*Brush chart */}
866
- {config.brush.active && config.xAxis.type !== 'categorical' && <BrushChart xMax={xMax} yMax={yMax} />}
887
+ {config.xAxis.brushActive && config.xAxis.type !== 'categorical' && <BrushChart xMax={xMax} yMax={yMax} />}
867
888
  {/* Line chart */}
868
889
  {/* TODO: Make this just line or combo? */}
869
890
  {!['Paired Bar', 'Box Plot', 'Area Chart', 'Scatter Plot', 'Deviation Bar', 'Forecasting', 'Bar'].includes(
@@ -1018,22 +1039,31 @@ const LinearChart = forwardRef<SVGAElement, LinearChartProps>(({ parentHeight, p
1018
1039
  to={
1019
1040
  runtime.horizontal
1020
1041
  ? {
1021
- x: 0,
1022
- y:
1023
- config.visualizationType === 'Forest Plot' ? parentHeight : Number(heights.horizontal)
1024
- }
1042
+ x: 0,
1043
+ y:
1044
+ config.visualizationType === 'Forest Plot' ? parentHeight : Number(heights.horizontal)
1045
+ }
1025
1046
  : props.axisToPoint
1026
1047
  }
1027
1048
  stroke='#000'
1028
1049
  />
1029
1050
  )}
1030
- {yScale.domain()[0] < 0 && (
1051
+ {orientation === 'vertical' && yScale.domain()[0] < 0 && (
1052
+ // draw from the Left of the chart …
1031
1053
  <Line
1032
1054
  from={{ x: props.axisFromPoint.x, y: yScale(0) }}
1033
1055
  to={{ x: xMax, y: yScale(0) }}
1034
1056
  stroke='#333'
1035
1057
  />
1036
1058
  )}
1059
+ {orientation === 'horizontal' && xScale.domain()[0] < 0 && (
1060
+ <Line
1061
+ // draw from the top of the char
1062
+ from={{ x: xScale(0), y: 0 }}
1063
+ to={{ x: xScale(0), y: yMax }}
1064
+ stroke='#333'
1065
+ />
1066
+ )}
1037
1067
  {visualizationType === 'Bar' && orientation === 'horizontal' && xScale.domain()[0] < 0 && (
1038
1068
  <Line
1039
1069
  from={{ x: xScale(0), y: 0 }}
@@ -1061,8 +1091,8 @@ const LinearChart = forwardRef<SVGAElement, LinearChartProps>(({ parentHeight, p
1061
1091
  const labelVerticalAnchor = labelsAboveGridlines ? 'end' : 'middle'
1062
1092
  const combineDomInlineLabelWithValue = inlineLabel && labelsAboveGridlines && lastTick
1063
1093
  const formattedValue = useInlineLabel
1064
- ? tick.formattedValue.replace(config.dataFormat.suffix, '')
1065
- : tick.formattedValue
1094
+ ? String(tick?.formattedValue || '').replace(config.dataFormat.suffix, '')
1095
+ : tick?.formattedValue
1066
1096
 
1067
1097
  return (
1068
1098
  <Group key={`vx-tick-${tick.value}-${i}`} className={'vx-axis-tick'}>
@@ -1078,16 +1108,36 @@ const LinearChart = forwardRef<SVGAElement, LinearChartProps>(({ parentHeight, p
1078
1108
  )}
1079
1109
 
1080
1110
  {orientation === 'horizontal' &&
1111
+ visualizationType === 'Box Plot' &&
1112
+ config.yAxis.labelPlacement === 'On Date/Category Axis' &&
1113
+ !config.yAxis.hideLabel && (
1114
+ <Text
1115
+ x={tick.to.x}
1116
+ y={yScale(tick.value) + yScale.bandwidth() / 2}
1117
+ transform={`rotate(${
1118
+ config.orientation === 'horizontal' ? config.runtime.yAxis.tickRotation || 0 : 0
1119
+ }, ${tick.to.x}, ${tick.to.y})`}
1120
+ verticalAnchor={'middle'}
1121
+ textAnchor={'end'}
1122
+ fontSize={tickLabelFontSize}
1123
+ >
1124
+ {tick.formattedValue}
1125
+ </Text>
1126
+ )}
1127
+
1128
+ {orientation === 'horizontal' &&
1129
+ visualizationType !== 'Box Plot' &&
1081
1130
  visualizationSubType !== 'stacked' &&
1082
1131
  config.yAxis.labelPlacement === 'On Date/Category Axis' &&
1083
1132
  !config.yAxis.hideLabel && (
1084
1133
  <Text
1085
- transform={`translate(${tick.to.x - 5}, ${config.isLollipopChart
1134
+ transform={`translate(${tick.to.x - 5}, ${
1135
+ config.isLollipopChart
1086
1136
  ? tick.to.y - minY
1087
1137
  : tick.to.y -
1088
- minY +
1089
- (Number(config.barHeight * config.runtime.series.length) - barMinHeight) / 2
1090
- }) rotate(-${config.runtime.horizontal ? config.runtime.yAxis.tickRotation || 0 : 0})`}
1138
+ minY +
1139
+ (Number(config.barHeight * config.runtime.series.length) - barMinHeight) / 2
1140
+ }) rotate(-${config.runtime.horizontal ? config.runtime.yAxis.tickRotation || 0 : 0})`}
1091
1141
  verticalAnchor={'start'}
1092
1142
  textAnchor={'end'}
1093
1143
  fontSize={tickLabelFontSize}
@@ -1101,8 +1151,9 @@ const LinearChart = forwardRef<SVGAElement, LinearChartProps>(({ parentHeight, p
1101
1151
  config.yAxis.labelPlacement === 'On Date/Category Axis' &&
1102
1152
  !config.yAxis.hideLabel && (
1103
1153
  <Text
1104
- transform={`translate(${tick.to.x - 5}, ${tick.to.y - minY + (Number(config.barHeight) - barMinHeight) / 2
1105
- }) rotate(-${runtime.horizontal ? runtime.yAxis.tickRotation : 0})`}
1154
+ transform={`translate(${tick.to.x - 5}, ${
1155
+ tick.to.y - minY + (Number(config.barHeight) - barMinHeight) / 2
1156
+ }) rotate(-${runtime.horizontal ? runtime.yAxis.tickRotation : 0})`}
1106
1157
  verticalAnchor={'start'}
1107
1158
  textAnchor={'end'}
1108
1159
  fontSize={tickLabelFontSize}
@@ -1115,8 +1166,9 @@ const LinearChart = forwardRef<SVGAElement, LinearChartProps>(({ parentHeight, p
1115
1166
  visualizationType === 'Paired Bar' &&
1116
1167
  !config.yAxis.hideLabel && (
1117
1168
  <Text
1118
- transform={`translate(${tick.to.x - 5}, ${tick.to.y - minY + Number(config.barHeight) / 2
1119
- }) rotate(-${runtime.horizontal ? runtime.yAxis.tickRotation : 0})`}
1169
+ transform={`translate(${tick.to.x - 5}, ${
1170
+ tick.to.y - minY + Number(config.barHeight) / 2
1171
+ }) rotate(-${runtime.horizontal ? runtime.yAxis.tickRotation : 0})`}
1120
1172
  textAnchor={'end'}
1121
1173
  verticalAnchor='middle'
1122
1174
  fontSize={tickLabelFontSize}
@@ -1128,10 +1180,11 @@ const LinearChart = forwardRef<SVGAElement, LinearChartProps>(({ parentHeight, p
1128
1180
  visualizationType === 'Deviation Bar' &&
1129
1181
  !config.yAxis.hideLabel && (
1130
1182
  <Text
1131
- transform={`translate(${tick.to.x - 5}, ${config.isLollipopChart
1183
+ transform={`translate(${tick.to.x - 5}, ${
1184
+ config.isLollipopChart
1132
1185
  ? tick.to.y - minY + 2
1133
1186
  : tick.to.y - minY + Number(config.barHeight) / 2
1134
- }) rotate(-${runtime.horizontal ? runtime.yAxis.tickRotation : 0})`}
1187
+ }) rotate(-${runtime.horizontal ? runtime.yAxis.tickRotation : 0})`}
1135
1188
  textAnchor={'end'}
1136
1189
  verticalAnchor='middle'
1137
1190
  fontSize={tickLabelFontSize}
@@ -1162,14 +1215,14 @@ const LinearChart = forwardRef<SVGAElement, LinearChartProps>(({ parentHeight, p
1162
1215
  seriesHighlight.includes(
1163
1216
  config.runtime.seriesLabelsAll[tick.formattedValue - 1]
1164
1217
  )) && (
1165
- <rect
1166
- x={0 - Number(config.yAxis.size)}
1167
- y={tick.to.y - 8 + (config.runtime.horizontal ? horizontalTickOffset : 7)}
1168
- width={Number(config.yAxis.size) + xScale(xScale.domain()[0])}
1169
- height='2'
1170
- fill={colorScale(config.runtime.seriesLabelsAll[tick.formattedValue - 1])}
1171
- />
1172
- )}
1218
+ <rect
1219
+ x={0 - Number(config.yAxis.size)}
1220
+ y={tick.to.y - 8 + (config.runtime.horizontal ? horizontalTickOffset : 7)}
1221
+ width={Number(config.yAxis.size) + xScale(xScale.domain()[0])}
1222
+ height='2'
1223
+ fill={colorScale(config.runtime.seriesLabelsAll[tick.formattedValue - 1])}
1224
+ />
1225
+ )}
1173
1226
  </>
1174
1227
  )}
1175
1228
  {orientation === 'vertical' &&
@@ -1312,8 +1365,9 @@ const LinearChart = forwardRef<SVGAElement, LinearChartProps>(({ parentHeight, p
1312
1365
  className='y-label'
1313
1366
  textAnchor='middle'
1314
1367
  verticalAnchor='start'
1315
- transform={`translate(${config.yAxis.rightLabelOffsetSize ? config.yAxis.rightLabelOffsetSize : 0
1316
- }, ${axisCenter}) rotate(-90)`}
1368
+ transform={`translate(${
1369
+ config.yAxis.rightLabelOffsetSize ? config.yAxis.rightLabelOffsetSize : 0
1370
+ }, ${axisCenter}) rotate(-90)`}
1317
1371
  fontWeight='bold'
1318
1372
  fill={config.yAxis.rightAxisLabelColor}
1319
1373
  fontSize={axisLabelFontSize}
@@ -1345,8 +1399,8 @@ const LinearChart = forwardRef<SVGAElement, LinearChartProps>(({ parentHeight, p
1345
1399
  runtime.horizontal && config.visualizationType !== 'Forest Plot'
1346
1400
  ? Number(heights.horizontal) + Number(config.xAxis.axisPadding)
1347
1401
  : config.visualizationType === 'Forest Plot'
1348
- ? yMax + Number(config.xAxis.axisPadding)
1349
- : yMax
1402
+ ? yMax + Number(config.xAxis.axisPadding)
1403
+ : yMax
1350
1404
  }
1351
1405
  left={config.visualizationType !== 'Forest Plot' ? Number(runtime.yAxis.size) : 0}
1352
1406
  label={config[section].label}
@@ -1359,8 +1413,8 @@ const LinearChart = forwardRef<SVGAElement, LinearChartProps>(({ parentHeight, p
1359
1413
  config.runtime.xAxis.manual
1360
1414
  ? getTickValues(xAxisDataMapped, xScale, isDateTime ? xTickCount : getManualStep(), config)
1361
1415
  : config.runtime.xAxis.type === 'date'
1362
- ? xAxisDataMapped
1363
- : undefined
1416
+ ? xAxisDataMapped
1417
+ : undefined
1364
1418
  }
1365
1419
  >
1366
1420
  {props => {
@@ -1386,14 +1440,14 @@ const LinearChart = forwardRef<SVGAElement, LinearChartProps>(({ parentHeight, p
1386
1440
  // filter out every [distanceBetweenTicks] tick starting from the end, so the last tick is always labeled
1387
1441
  const filteredTicks = useDateSpanMonths
1388
1442
  ? [...props.ticks]
1389
- .reverse()
1390
- .filter((_, i) => i % distanceBetweenTicks === 0)
1391
- .reverse()
1392
- .map((tick, i, arr) => ({
1393
- ...tick,
1394
- // reformat in case showYearsOnce, since first month of year may have changed
1395
- formattedValue: handleBottomTickFormatting(tick.value, i, arr)
1396
- }))
1443
+ .reverse()
1444
+ .filter((_, i) => i % distanceBetweenTicks === 0)
1445
+ .reverse()
1446
+ .map((tick, i, arr) => ({
1447
+ ...tick,
1448
+ // reformat in case showYearsOnce, since first month of year may have changed
1449
+ formattedValue: handleBottomTickFormatting(tick.value, i, arr)
1450
+ }))
1397
1451
  : props.ticks
1398
1452
 
1399
1453
  const axisMaxHeight = bottomLabelStart + BOTTOM_LABEL_PADDING
@@ -1526,8 +1580,9 @@ const LinearChart = forwardRef<SVGAElement, LinearChartProps>(({ parentHeight, p
1526
1580
  tooltipData.dataXPosition &&
1527
1581
  !config.tooltips.singleSeries && (
1528
1582
  <>
1529
- <style>{`.tooltip {background-color: rgba(255,255,255, ${config.tooltips.opacity / 100
1530
- }) !important;`}</style>
1583
+ <style>{`.tooltip {background-color: rgba(255,255,255, ${
1584
+ config.tooltips.opacity / 100
1585
+ }) !important;`}</style>
1531
1586
  <style>{`.tooltip {max-width:300px} !important; word-wrap: break-word; `}</style>
1532
1587
  <TooltipWithBounds
1533
1588
  ref={tooltipRef}
@@ -89,11 +89,7 @@ export default {
89
89
  topAxis: {
90
90
  hasLine: false
91
91
  },
92
- brush: {
93
- isActive: false,
94
- isBrushing: false,
95
- data: []
96
- },
92
+
97
93
  isLegendValue: false,
98
94
  barThickness: 0.35,
99
95
  barHeight: 25,
@@ -15,17 +15,8 @@ export const countNumOfTicks = ({ axis, max, runtime, currentViewport, isHorizon
15
15
  ? undefined
16
16
  : !isHorizontal && numTicks && numTicks
17
17
  // to fix edge case of small numbers with decimals
18
- if (tickCount === undefined && !config.dataFormat.roundTo) {
19
- // then it is set to Auto
20
- if (Number(max) <= 3) {
21
- tickCount = 2
22
- } else {
23
- tickCount = 4 // same default as standalone components
24
- }
25
- }
26
- if (Number(tickCount) > Number(max) && !isHorizontal) {
27
- // cap it and round it so its an integer
28
- tickCount = Math.max(2, Number(min) < 0 ? Math.round(max) * 2 : Math.round(max))
18
+ if (tickCount === undefined) {
19
+ tickCount = 4 // same default as standalone components
29
20
  }
30
21
  }
31
22
 
@@ -38,14 +29,8 @@ export const countNumOfTicks = ({ axis, max, runtime, currentViewport, isHorizon
38
29
  : !isHorizontal && !numTicks
39
30
  ? undefined
40
31
  : !isHorizontal && numTicks && numTicks
41
- if (isHorizontal && tickCount === undefined && !config.dataFormat.roundTo) {
42
- // then it is set to Auto
43
- // - check for small numbers situation
44
- if (max <= 3) {
45
- tickCount = 2
46
- } else {
47
- tickCount = 4 // same default as standalone components
48
- }
32
+ if (isHorizontal && tickCount === undefined) {
33
+ tickCount = 4 // same default as standalone components
49
34
  }
50
35
 
51
36
  if (config.visualizationType === 'Forest Plot') {
@@ -0,0 +1,58 @@
1
+ // Ensure that the last tick is shown for charts with a "Date (Linear Scale)" scale
2
+ import { ChartConfig } from '../types/ChartConfig'
3
+ import { getTicks } from '@visx/scale'
4
+ export const filterAndShiftLinearDateTicks = (
5
+ config: ChartConfig,
6
+ axisProps: { scale: any; numTicks: number; ticks: { value: any; formattedValue?: string }[] },
7
+ xAxisDataMapped: any[],
8
+ formatDate: (value: any, index: number, all: any[]) => string
9
+ ) => {
10
+ // Guard #1: must have a scale & ticks array
11
+ if (!axisProps?.scale || !Array.isArray(axisProps.ticks)) {
12
+ return []
13
+ }
14
+
15
+ // Guard #2: if no domain data, just format what we have
16
+ if (!Array.isArray(xAxisDataMapped) || xAxisDataMapped.length === 0) {
17
+ axisProps.ticks.forEach((t, i) => {
18
+ t.formattedValue = formatDate(t.value, i, axisProps.ticks)
19
+ })
20
+ return axisProps.ticks
21
+ }
22
+
23
+ // get our filtered tick *values*
24
+ const filteredTickValues = getTicks(axisProps.scale, axisProps.numTicks) || []
25
+
26
+ let ticks = axisProps.ticks
27
+
28
+ if (filteredTickValues.length > 0 && filteredTickValues.length < xAxisDataMapped.length) {
29
+ const lastFiltered = filteredTickValues[filteredTickValues.length - 1]
30
+ const lastIdx = xAxisDataMapped.indexOf(lastFiltered)
31
+
32
+ let shift = 0
33
+ if (lastIdx >= 0 && lastIdx < xAxisDataMapped.length - 1) {
34
+ shift = config.xAxis.sortByRecentDate
35
+ ? -xAxisDataMapped.indexOf(filteredTickValues[0])
36
+ : xAxisDataMapped.length - 1 - lastIdx
37
+ }
38
+
39
+ ticks = filteredTickValues.map(value => {
40
+ const baseIndex = axisProps.ticks.findIndex(t => t.value === value)
41
+ const targetIndex = baseIndex + shift
42
+
43
+ // Guard #3: if shifting would go out of bounds, clamp
44
+ const safeIndex = Math.max(0, Math.min(axisProps.ticks.length - 1, targetIndex))
45
+ return axisProps.ticks[safeIndex]
46
+ })
47
+ }
48
+
49
+ // Finally, format all ticks
50
+ ticks.forEach((tick, i) => {
51
+ // Guard #4: only format if we actually have a value
52
+ if (tick && tick.value != null) {
53
+ tick.formattedValue = formatDate(tick.value, i, ticks)
54
+ }
55
+ })
56
+
57
+ return ticks
58
+ }
@@ -0,0 +1,13 @@
1
+ export const getBridgedData = (stageKey: string, stageColumn: string, data: Record<string, any>[]) => {
2
+ const allStages: string[] = Array.from(new Set(data.map(d => d?.[stageColumn]).filter(Boolean)))
3
+
4
+ const stageIndex: number = allStages.indexOf(stageKey)
5
+ if (stageIndex === -1) return []
6
+ const currentStage = data.filter(d => d?.[stageColumn] === stageKey)
7
+ const nextKey = allStages[stageIndex + 1]
8
+ if (nextKey) {
9
+ const nextSlice = data.filter(d => d[stageColumn] === nextKey)
10
+ return [...currentStage, nextSlice[0]]
11
+ }
12
+ return currentStage
13
+ }
@@ -0,0 +1,64 @@
1
+ import { describe, it, expect } from 'vitest'
2
+ import { getBridgedData } from '../getBridgedData'
3
+
4
+ describe('getBridgedData', () => {
5
+ const sampleData = [
6
+ { Month: 'Jan', value: 1 },
7
+ { Month: 'Jan', value: 2 },
8
+ { Month: 'Feb', value: 3 },
9
+ { Month: 'Feb', value: 4 },
10
+ { Month: 'Mar', value: 5 }
11
+ ]
12
+
13
+ it('returns correct slice for stageKey with next stage', () => {
14
+ const result = getBridgedData('Jan', 'Month', sampleData)
15
+
16
+ expect(result).toEqual([
17
+ { Month: 'Jan', value: 1 },
18
+ { Month: 'Jan', value: 2 },
19
+ { Month: 'Feb', value: 3 } // only first item from next stage
20
+ ])
21
+ })
22
+
23
+ it('returns correct slice for stageKey with no next stage', () => {
24
+ const result = getBridgedData('Mar', 'Month', sampleData)
25
+
26
+ expect(result).toEqual([{ Month: 'Mar', value: 5 }])
27
+ })
28
+
29
+ it('returns empty if stageKey not found', () => {
30
+ const result = getBridgedData('Dec', 'Month', sampleData)
31
+
32
+ expect(result).toEqual([])
33
+ })
34
+
35
+ it('handles unordered input data and preserves stage order by first appearance', () => {
36
+ const unorderedData = [
37
+ { step: 'B', val: 1 },
38
+ { step: 'A', val: 2 },
39
+ { step: 'C', val: 3 },
40
+ { step: 'A', val: 4 },
41
+ { step: 'B', val: 5 }
42
+ ]
43
+
44
+ const result = getBridgedData('A', 'step', unorderedData)
45
+
46
+ expect(result).toEqual([
47
+ { step: 'A', val: 2 },
48
+ { step: 'A', val: 4 },
49
+ { step: 'C', val: 3 }
50
+ ])
51
+ })
52
+
53
+ it('returns only items matching current stage if next stage has no data', () => {
54
+ const dataWithEmptyStage = [
55
+ { stage: '1', x: 10 },
56
+ { stage: '2', x: 20 },
57
+ { stage: '3', x: 30 }
58
+ ]
59
+
60
+ const result = getBridgedData('3', 'stage', dataWithEmptyStage)
61
+
62
+ expect(result).toEqual([{ stage: '3', x: 30 }])
63
+ })
64
+ })