@cdc/chart 4.25.3 → 4.25.6

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 (86) hide show
  1. package/dist/cdcchart.js +46641 -42561
  2. package/index.html +130 -129
  3. package/package.json +22 -27
  4. package/src/CdcChartComponent.tsx +75 -35
  5. package/src/_stories/Chart.CI.stories.tsx +10 -0
  6. package/src/_stories/Chart.DynamicSeries.stories.tsx +68 -49
  7. package/src/_stories/Chart.stories.tsx +99 -86
  8. package/src/_stories/ChartPrefixSuffix.stories.tsx +29 -32
  9. package/{examples/private/line-issue.json → src/_stories/_mock/barchart_labels.mock.json} +150 -35
  10. package/src/_stories/_mock/dynamic_series_bar_config.json +1 -1
  11. package/src/_stories/_mock/dynamic_series_suppression_mock.json +610 -0
  12. package/{examples/private/not-loading.json → src/_stories/_mock/pie_calculated_area.json} +152 -95
  13. package/src/components/Annotations/components/AnnotationDropdown.tsx +2 -2
  14. package/src/components/AreaChart/components/AreaChart.jsx +33 -5
  15. package/src/components/Axis/Categorical.Axis.tsx +2 -2
  16. package/src/components/BarChart/components/BarChart.Horizontal.tsx +38 -37
  17. package/src/components/BarChart/components/BarChart.StackedHorizontal.tsx +18 -8
  18. package/src/components/BarChart/components/BarChart.StackedVertical.tsx +8 -8
  19. package/src/components/BarChart/components/BarChart.Vertical.tsx +47 -36
  20. package/src/components/BarChart/components/{BarChart.jsx → BarChart.tsx} +23 -5
  21. package/src/components/BarChart/components/context.tsx +20 -2
  22. package/src/components/BarChart/helpers/getBarHeights.ts +47 -0
  23. package/src/components/BarChart/helpers/index.ts +5 -2
  24. package/src/components/BarChart/helpers/tests/getBarHeights.test.ts +83 -0
  25. package/src/{hooks → components/BarChart/helpers}/useBarChart.ts +9 -46
  26. package/src/components/BoxPlot/BoxPlot.tsx +2 -1
  27. package/src/components/Brush/BrushChart.tsx +73 -0
  28. package/src/components/Brush/BrushController..tsx +39 -0
  29. package/src/components/DeviationBar.jsx +2 -2
  30. package/src/components/EditorPanel/EditorPanel.tsx +232 -147
  31. package/src/components/EditorPanel/components/Panels/Panel.General.tsx +36 -36
  32. package/src/components/EditorPanel/components/Panels/Panel.Series.tsx +52 -25
  33. package/src/components/EditorPanel/components/Panels/Panel.Visual.tsx +12 -4
  34. package/src/components/EditorPanel/components/Panels/panelVisual.styles.css +8 -0
  35. package/src/components/EditorPanel/useEditorPermissions.ts +5 -5
  36. package/src/components/ForestPlot/ForestPlot.tsx +2 -2
  37. package/src/components/HoverLine/HoverLine.tsx +74 -0
  38. package/src/components/Legend/Legend.Component.tsx +1 -1
  39. package/src/components/Legend/Legend.Suppression.tsx +59 -25
  40. package/src/components/Legend/helpers/createFormatLabels.tsx +28 -0
  41. package/src/components/Legend/helpers/index.ts +1 -1
  42. package/src/components/LineChart/LineChartProps.ts +3 -1
  43. package/src/components/LineChart/components/LineChart.Circle.tsx +72 -119
  44. package/src/components/LineChart/helpers.ts +133 -56
  45. package/src/components/LineChart/index.tsx +106 -55
  46. package/src/components/LinearChart.tsx +178 -198
  47. package/src/components/PairedBarChart.jsx +3 -2
  48. package/src/components/PieChart/PieChart.tsx +127 -102
  49. package/src/components/ScatterPlot/ScatterPlot.jsx +5 -0
  50. package/src/components/Sparkline/components/SparkLine.tsx +80 -18
  51. package/src/data/initial-state.js +11 -6
  52. package/src/helpers/countNumOfTicks.ts +1 -1
  53. package/src/helpers/dataHelpers.ts +23 -2
  54. package/src/helpers/getNewRuntime.ts +35 -0
  55. package/src/helpers/getPiePercent.ts +22 -0
  56. package/src/helpers/getTransformedData.ts +22 -0
  57. package/src/helpers/sizeHelpers.ts +1 -1
  58. package/src/helpers/tests/getNewRuntime.test.ts +82 -0
  59. package/src/helpers/tests/getPiePercent.test.ts +38 -0
  60. package/src/hooks/useMinMax.ts +21 -28
  61. package/src/hooks/useRightAxis.ts +5 -3
  62. package/src/hooks/useScales.ts +15 -6
  63. package/src/hooks/useTooltip.tsx +218 -203
  64. package/src/index.jsx +2 -2
  65. package/src/scss/main.scss +13 -6
  66. package/src/store/chart.actions.ts +2 -6
  67. package/src/store/chart.reducer.ts +23 -23
  68. package/src/types/ChartConfig.ts +11 -3
  69. package/src/types/ChartContext.ts +0 -2
  70. package/examples/private/DEV-8850-2.json +0 -493
  71. package/examples/private/DEV-9822.json +0 -574
  72. package/examples/private/DEV-9840.json +0 -553
  73. package/examples/private/DEV-9850-3.json +0 -461
  74. package/examples/private/chart.json +0 -1084
  75. package/examples/private/ci_formatted.json +0 -202
  76. package/examples/private/ci_issue.json +0 -3016
  77. package/examples/private/completed.json +0 -634
  78. package/examples/private/dem-data-long.csv +0 -20
  79. package/examples/private/dem-data-long.json +0 -36
  80. package/examples/private/demographic_data.csv +0 -157
  81. package/examples/private/demographic_data.json +0 -2654
  82. package/examples/private/demographic_dynamic.json +0 -443
  83. package/examples/private/demographic_standard.json +0 -560
  84. package/examples/private/ehdi.json +0 -29939
  85. package/examples/private/test.json +0 -493
  86. package/src/components/ZoomBrush.tsx +0 -251
@@ -4,15 +4,14 @@ import React, { forwardRef, useContext, useEffect, useMemo, useRef, useState } f
4
4
  import { AxisLeft, AxisBottom, AxisRight, AxisTop } from '@visx/axis'
5
5
  import { Group } from '@visx/group'
6
6
  import { Line, Bar } from '@visx/shape'
7
- import { Text } from '@visx/text'
8
7
  import { Tooltip as ReactTooltip } from 'react-tooltip'
8
+ import { Text } from '@visx/text'
9
9
  import { useTooltip, TooltipWithBounds } from '@visx/tooltip'
10
10
  import _ from 'lodash'
11
11
 
12
12
  // CDC Components
13
13
  import { isDateScale } from '@cdc/core/helpers/cove/date'
14
- import BrushChart from './BrushChart'
15
- import { AreaChart, AreaChartStacked } from './AreaChart'
14
+ import { AreaChartStacked } from './AreaChart'
16
15
  import BarChart from './BarChart'
17
16
  import ConfigContext from '../ConfigContext'
18
17
  import BoxPlot from './BoxPlot'
@@ -43,6 +42,8 @@ import { useEditorPermissions } from './EditorPanel/useEditorPermissions'
43
42
  import Annotation from './Annotations'
44
43
  import { BlurStrokeText } from '@cdc/core/components/BlurStrokeText'
45
44
  import { countNumOfTicks } from '../helpers/countNumOfTicks'
45
+ import HoverLine from './HoverLine/HoverLine'
46
+ import BrushChart from './BrushChart'
46
47
 
47
48
  type LinearChartProps = {
48
49
  parentWidth: number
@@ -59,10 +60,23 @@ const AXIS_LABEL_FONT_SIZE = 18
59
60
  const AXIS_LABEL_FONT_SIZE_SMALL = 14
60
61
  const TICK_LABEL_MARGIN_RIGHT = 4.5
61
62
 
63
+ type TooltipData = {
64
+ dataXPosition?: number
65
+ dataYPosition?: number
66
+ }
67
+
68
+ type UseTooltipReturn<T = TooltipData> = {
69
+ tooltipData: T | null
70
+ showTooltip: (tooltipData: T) => void
71
+ hideTooltip: () => void
72
+ tooltipOpen: boolean
73
+ tooltipLeft: number | null
74
+ tooltipTop: number | null
75
+ }
76
+
62
77
  const LinearChart = forwardRef<SVGAElement, LinearChartProps>(({ parentHeight, parentWidth }, svgRef) => {
63
78
  // prettier-ignore
64
79
  const {
65
- brushConfig,
66
80
  colorScale,
67
81
  config,
68
82
  convertLineToBarGraph,
@@ -79,8 +93,8 @@ const LinearChart = forwardRef<SVGAElement, LinearChartProps>(({ parentHeight, p
79
93
  parentRef,
80
94
  tableData,
81
95
  transformedData: data,
82
- updateConfig,
83
96
  seriesHighlight,
97
+ brushConfig
84
98
  } = useContext(ConfigContext)
85
99
 
86
100
  // CONFIG
@@ -99,14 +113,14 @@ const LinearChart = forwardRef<SVGAElement, LinearChartProps>(({ parentHeight, p
99
113
  dataFormat,
100
114
  debugSvg
101
115
  } = config
102
- const { suffix, onlyShowTopPrefixSuffix } = dataFormat
103
- const { labelsAboveGridlines, hideAxis } = config.yAxis
116
+ const { labelsAboveGridlines, hideAxis, inlineLabel } = config.yAxis
104
117
 
105
118
  // HOOKS % STATES
106
119
  const { minValue, maxValue, existPositiveValue, isAllLine } = useReduceData(config, data)
107
120
  const { visSupportsReactTooltip } = useEditorPermissions()
108
121
  const { hasTopAxis } = getTopAxis(config)
109
122
  const [animatedChart, setAnimatedChart] = useState(false)
123
+ const [showHoverLine, setShowHoverLine] = useState(false)
110
124
  const [point, setPoint] = useState({ x: 0, y: 0 })
111
125
  const [suffixWidth, setSuffixWidth] = useState(0)
112
126
  const [yAxisAutoPadding, setYAxisAutoPadding] = useState(0)
@@ -121,6 +135,7 @@ const LinearChart = forwardRef<SVGAElement, LinearChartProps>(({ parentHeight, p
121
135
  const xAxisTitleRef = useRef(null)
122
136
  const lastMaxValue = useRef(maxValue)
123
137
  const gridLineRefs = useRef([])
138
+ const tooltipRef = useRef(null)
124
139
 
125
140
  const dataRef = useIntersectionObserver(triggerRef, {
126
141
  freezeOnceVisible: false
@@ -132,8 +147,8 @@ const LinearChart = forwardRef<SVGAElement, LinearChartProps>(({ parentHeight, p
132
147
  const isLogarithmicAxis = config.yAxis.type === 'logarithmic'
133
148
  const isForestPlot = visualizationType === 'Forest Plot'
134
149
  const isDateTime = config.xAxis.type === 'date-time'
135
- const suffixHasNoSpace = !suffix.includes(' ')
136
- const labelsOverflow = onlyShowTopPrefixSuffix && !suffixHasNoSpace
150
+ const inlineLabelHasNoSpace = !inlineLabel?.includes(' ')
151
+ const labelsOverflow = inlineLabel && !inlineLabelHasNoSpace
137
152
  const padding = orientation === 'horizontal' ? Number(config.xAxis.size) : Number(config.yAxis.size)
138
153
  const yLabelOffset = isNaN(parseInt(`${runtime.yAxis.labelOffset}`)) ? 0 : parseInt(`${runtime.yAxis.labelOffset}`)
139
154
  const tickLabelFontSize = isMobileHeightViewport(currentViewport) ? TICK_LABEL_FONT_SIZE_SMALL : TICK_LABEL_FONT_SIZE
@@ -218,7 +233,7 @@ const LinearChart = forwardRef<SVGAElement, LinearChartProps>(({ parentHeight, p
218
233
  yMax
219
234
  }
220
235
  const { min, max, leftMax, rightMax } = useMinMax(properties)
221
- const { yScaleRight, hasRightAxis } = useRightAxis({ config, yMax, data, updateConfig })
236
+ const { yScaleRight, hasRightAxis } = useRightAxis({ config, yMax, data })
222
237
  const { xScale, yScale, seriesScale, g1xScale, g2xScale, xScaleNoPadding, xScaleAnnotation } = useScales({
223
238
  ...properties,
224
239
  min,
@@ -226,7 +241,10 @@ const LinearChart = forwardRef<SVGAElement, LinearChartProps>(({ parentHeight, p
226
241
  leftMax,
227
242
  rightMax,
228
243
  dimensions,
229
- xMax: parentWidth - Number(config.orientation === 'horizontal' ? config.xAxis.size : config.yAxis.size)
244
+ xMax:
245
+ parentWidth -
246
+ Number(config.orientation === 'horizontal' ? config.xAxis.size : config.yAxis.size) -
247
+ (hasRightAxis ? config.yAxis.rightAxisSize : 0)
230
248
  })
231
249
 
232
250
  const [yTickCount, xTickCount] = ['yAxis', 'xAxis'].map(axis =>
@@ -235,7 +253,9 @@ const LinearChart = forwardRef<SVGAElement, LinearChartProps>(({ parentHeight, p
235
253
  const handleNumTicks = isForestPlot ? config.data.length : yTickCount
236
254
 
237
255
  // Tooltip Helpers
238
- const { tooltipData, showTooltip, hideTooltip, tooltipOpen, tooltipLeft, tooltipTop } = useTooltip()
256
+
257
+ const { tooltipData, showTooltip, hideTooltip, tooltipOpen, tooltipLeft, tooltipTop } =
258
+ useTooltip<UseTooltipReturn<TooltipData>>()
239
259
 
240
260
  // prettier-ignore
241
261
  const {
@@ -243,12 +263,12 @@ const LinearChart = forwardRef<SVGAElement, LinearChartProps>(({ parentHeight, p
243
263
  handleTooltipClick,
244
264
  handleTooltipMouseOff,
245
265
  TooltipListItem,
246
- getXValueFromCoordinate
247
266
  } = useCoveTooltip({
248
- xScale,
249
- yScale,
250
- showTooltip,
251
- hideTooltip
267
+ xScale,
268
+ yScale,
269
+ seriesScale,
270
+ showTooltip,
271
+ hideTooltip
252
272
  })
253
273
  // get the number of months between the first and last date
254
274
  const { dataKey } = runtime.xAxis
@@ -256,7 +276,7 @@ const LinearChart = forwardRef<SVGAElement, LinearChartProps>(({ parentHeight, p
256
276
  data.length && isDateTime
257
277
  ? [0, data.length - 1].map(i => parseDate(data[i][dataKey])).reduce((a, b) => Math.abs(a - b)) / MONTH_AS_MS
258
278
  : 0
259
- const useDateSpanMonths = isDateTime && dateSpanMonths > xTickCount
279
+ const useDateSpanMonths = isDateTime && dateSpanMonths > xTickCount && !config.runtime.xAxis.manual
260
280
 
261
281
  // GETTERS & FUNCTIONS
262
282
  const handleLeftTickFormatting = (tick, index, ticks) => {
@@ -372,7 +392,7 @@ const LinearChart = forwardRef<SVGAElement, LinearChartProps>(({ parentHeight, p
372
392
  if (!suffixEl) return setSuffixWidth(0)
373
393
  const suffixElWidth = suffixEl.getBBox().width
374
394
  setSuffixWidth(suffixElWidth)
375
- }, [config.dataFormat.suffix, config.dataFormat.onlyShowTopPrefixSuffix])
395
+ }, [config.dataFormat.suffix, inlineLabel])
376
396
 
377
397
  // forest plot x-axis label positioning
378
398
  useEffect(() => {
@@ -398,11 +418,11 @@ const LinearChart = forwardRef<SVGAElement, LinearChartProps>(({ parentHeight, p
398
418
  const topLabelOnGridline = topYLabelRef.current && yAxis.labelsAboveGridlines
399
419
 
400
420
  // Heights to add
401
-
402
421
  const brushHeight = brush?.active ? brush?.height + brush?.height : 0
422
+ const brushHeightWithMargin = config.brush?.active ? brushHeight + brushHeight : 0
403
423
  const forestRowsHeight = isForestPlot ? config.data.length * forestPlot.rowHeight : 0
404
424
  const topLabelOnGridlineHeight = topLabelOnGridline ? topYLabelRef.current.getBBox().height : 0
405
- const additionalHeight = axisBottomHeight + brushHeight + forestRowsHeight + topLabelOnGridlineHeight
425
+ const additionalHeight = axisBottomHeight + brushHeightWithMargin + forestRowsHeight + topLabelOnGridlineHeight
406
426
  const newHeight = initialHeight + additionalHeight
407
427
  if (!parentRef.current) return
408
428
 
@@ -438,7 +458,9 @@ const LinearChart = forwardRef<SVGAElement, LinearChartProps>(({ parentHeight, p
438
458
  }, [maxValue])
439
459
 
440
460
  useEffect(() => {
441
- if (orientation === 'horizontal' || !labelsOverflow || config.yAxis?.max) {
461
+ if (!yScale?.ticks) return
462
+ const ticks = yScale.ticks(handleNumTicks)
463
+ if (orientation === 'horizontal' || !labelsOverflow || config.yAxis?.max || ticks.length === 0) {
442
464
  setYAxisAutoPadding(0)
443
465
  return
444
466
  }
@@ -446,13 +468,12 @@ const LinearChart = forwardRef<SVGAElement, LinearChartProps>(({ parentHeight, p
446
468
  // minimum percentage of the max value that the distance should be from the top grid line
447
469
  const MINIMUM_DISTANCE_PERCENTAGE = 0.025
448
470
 
449
- const topGridLine = Math.max(...yScale.ticks(handleNumTicks))
471
+ const topGridLine = Math.max(...ticks)
450
472
  const needsPaddingThreshold = topGridLine - maxValue * MINIMUM_DISTANCE_PERCENTAGE
451
473
  const maxValueIsGreaterThanThreshold = maxValue > needsPaddingThreshold
452
474
 
453
475
  if (!maxValueIsGreaterThanThreshold) return
454
476
 
455
- const ticks = yScale.ticks(handleNumTicks)
456
477
  const tickGap = ticks.length === 1 ? ticks[0] : ticks[1] - ticks[0]
457
478
  const nextTick = Math.max(...yScale.ticks(handleNumTicks)) + tickGap
458
479
  const divideBy = minValue < 0 ? maxValue / 2 : maxValue
@@ -467,6 +488,23 @@ const LinearChart = forwardRef<SVGAElement, LinearChartProps>(({ parentHeight, p
467
488
  setYAxisAutoPadding(newPadding * 100 + 0.1)
468
489
  }, [maxValue, labelsOverflow, yScale, handleNumTicks])
469
490
 
491
+ useEffect(() => {
492
+ if (!tooltipOpen) return
493
+ if (!tooltipRef.current) return
494
+
495
+ const { dataXPosition } = tooltipData as { [key: string]: number }
496
+
497
+ if (!dataXPosition) return
498
+
499
+ const { width: tooltipWidth } = tooltipRef.current.node.getBoundingClientRect()
500
+
501
+ const rightSideRemainingSpace = parentWidth - dataXPosition
502
+
503
+ const rightSide = rightSideRemainingSpace <= tooltipWidth && dataXPosition > parentWidth / 2 - 10
504
+ const maxWidth = rightSide ? dataXPosition - 10 : parentWidth - (dataXPosition + 6)
505
+ tooltipRef.current.node.style.maxWidth = `${maxWidth}px`
506
+ }, [tooltipOpen, tooltipData])
507
+
470
508
  // Render Functions
471
509
  const generatePairedBarAxis = () => {
472
510
  const axisMaxHeight = bottomLabelStart + BOTTOM_LABEL_PADDING
@@ -611,7 +649,6 @@ const LinearChart = forwardRef<SVGAElement, LinearChartProps>(({ parentHeight, p
611
649
  </>
612
650
  )
613
651
  }
614
-
615
652
  return isNaN(width) ? (
616
653
  <React.Fragment></React.Fragment>
617
654
  ) : (
@@ -624,14 +661,15 @@ const LinearChart = forwardRef<SVGAElement, LinearChartProps>(({ parentHeight, p
624
661
  <svg
625
662
  ref={svgRef}
626
663
  onMouseMove={onMouseMove}
627
- width={parentWidth}
664
+ width={parentWidth + config.yAxis.rightAxisSize}
628
665
  height={isNoDataAvailable ? 1 : parentHeight}
629
- className={`linear ${config.animate ? 'animated' : ''} ${animatedChart && config.animate ? 'animate' : ''} ${
630
- debugSvg && 'debug'
631
- } ${isDraggingAnnotation && 'dragging-annotation'}`}
666
+ className={`linear ${config.animate ? 'animated' : ''} ${animatedChart && config.animate ? 'animate' : ''} ${debugSvg && 'debug'
667
+ } ${isDraggingAnnotation && 'dragging-annotation'}`}
632
668
  role='img'
633
669
  aria-label={handleChartAriaLabels(config)}
634
670
  style={{ overflow: 'visible' }}
671
+ onMouseLeave={() => setShowHoverLine(false)}
672
+ onMouseEnter={() => setShowHoverLine(true)}
635
673
  >
636
674
  {!isDraggingAnnotation && <Bar width={parentWidth} height={initialHeight} fill={'transparent'}></Bar>}{' '}
637
675
  {/* GRID LINES */}
@@ -717,38 +755,22 @@ const LinearChart = forwardRef<SVGAElement, LinearChartProps>(({ parentHeight, p
717
755
  yScale={yScale}
718
756
  />
719
757
  )}
720
- {((visualizationType === 'Area Chart' && config.visualizationSubType === 'regular') ||
721
- (visualizationType === 'Combo' && config.visualizationSubType === 'regular')) && (
722
- <AreaChart
723
- xScale={xScale}
724
- yScale={yScale}
725
- yMax={yMax}
726
- xMax={xMax}
727
- chartRef={svgRef}
728
- width={xMax}
729
- height={yMax}
730
- handleTooltipMouseOver={handleTooltipMouseOver}
731
- handleTooltipMouseOff={handleTooltipMouseOff}
732
- tooltipData={tooltipData}
733
- showTooltip={showTooltip}
734
- />
735
- )}
736
758
  {((visualizationType === 'Area Chart' && config.visualizationSubType === 'stacked') ||
737
- (visualizationType === 'Combo' && config.visualizationSubType === 'stacked')) && (
738
- <AreaChartStacked
739
- xScale={xScale}
740
- yScale={yScale}
741
- yMax={yMax}
742
- xMax={xMax}
743
- chartRef={svgRef}
744
- width={xMax}
745
- height={yMax}
746
- handleTooltipMouseOver={handleTooltipMouseOver}
747
- handleTooltipMouseOff={handleTooltipMouseOff}
748
- tooltipData={tooltipData}
749
- showTooltip={showTooltip}
750
- />
751
- )}
759
+ 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
+ )}
752
774
  {(visualizationType === 'Bar' || visualizationType === 'Combo' || convertLineToBarGraph) && (
753
775
  <BarChart
754
776
  xScale={xScale}
@@ -768,9 +790,7 @@ const LinearChart = forwardRef<SVGAElement, LinearChartProps>(({ parentHeight, p
768
790
  chartRef={svgRef}
769
791
  />
770
792
  )}
771
- {((visualizationType === 'Line' && !convertLineToBarGraph) ||
772
- visualizationType === 'Combo' ||
773
- visualizationType === 'Bump Chart') && (
793
+ {(visualizationType === 'Combo' || visualizationType === 'Bump Chart') && (
774
794
  <LineChart
775
795
  xScale={xScale}
776
796
  yScale={yScale}
@@ -784,9 +804,29 @@ const LinearChart = forwardRef<SVGAElement, LinearChartProps>(({ parentHeight, p
784
804
  handleTooltipClick={handleTooltipClick}
785
805
  tooltipData={tooltipData}
786
806
  showTooltip={showTooltip}
787
- chartRef={svgRef}
788
807
  />
789
808
  )}
809
+ {/* Line chart */}
810
+ {/* TODO: Make this just line or combo? */}
811
+ {!['Paired Bar', 'Box Plot', 'Area Chart', 'Scatter Plot', 'Deviation Bar', 'Forecasting', 'Bar'].includes(
812
+ visualizationType
813
+ ) &&
814
+ !convertLineToBarGraph && (
815
+ <>
816
+ <LineChart
817
+ xScale={xScale}
818
+ yScale={yScale}
819
+ getXAxisData={getXAxisData}
820
+ getYAxisData={getYAxisData}
821
+ xMax={xMax}
822
+ yMax={yMax}
823
+ seriesStyle={config.runtime.series}
824
+ tooltipData={tooltipData}
825
+ handleTooltipMouseOver={handleTooltipMouseOver}
826
+ handleTooltipMouseOff={handleTooltipMouseOff}
827
+ />
828
+ </>
829
+ )}
790
830
  {(visualizationType === 'Forecasting' || visualizationType === 'Combo') && (
791
831
  <Forecasting
792
832
  showTooltip={showTooltip}
@@ -794,14 +834,11 @@ const LinearChart = forwardRef<SVGAElement, LinearChartProps>(({ parentHeight, p
794
834
  xScale={xScale}
795
835
  yScale={yScale}
796
836
  width={xMax}
797
- le
798
837
  height={yMax}
799
838
  xScaleNoPadding={xScaleNoPadding}
800
839
  chartRef={svgRef}
801
- getXValueFromCoordinate={getXValueFromCoordinate}
802
840
  handleTooltipMouseOver={handleTooltipMouseOver}
803
841
  handleTooltipMouseOff={handleTooltipMouseOff}
804
- isBrush={false}
805
842
  />
806
843
  )}
807
844
  {visualizationType === 'Forest Plot' && (
@@ -842,6 +879,9 @@ const LinearChart = forwardRef<SVGAElement, LinearChartProps>(({ parentHeight, p
842
879
  xMax={xMax}
843
880
  yMax={yMax}
844
881
  seriesStyle={config.runtime.series}
882
+ tooltipData={tooltipData}
883
+ handleTooltipMouseOver={handleTooltipMouseOver}
884
+ handleTooltipMouseOff={handleTooltipMouseOff}
845
885
  />
846
886
  </>
847
887
  )}
@@ -869,7 +909,7 @@ const LinearChart = forwardRef<SVGAElement, LinearChartProps>(({ parentHeight, p
869
909
  strokeDasharray={handleLineType(anchor.lineStyle)}
870
910
  stroke={anchor.color ? anchor.color : 'rgba(0,0,0,1)'}
871
911
  className='anchor-y'
872
- from={{ x: 0 + padding, y: position - middleOffset}}
912
+ from={{ x: 0 + padding, y: position - middleOffset }}
873
913
  to={{ x: width - config.yAxis.rightAxisSize, y: position - middleOffset }}
874
914
  />
875
915
  )
@@ -901,14 +941,14 @@ const LinearChart = forwardRef<SVGAElement, LinearChartProps>(({ parentHeight, p
901
941
  return (
902
942
  // prettier-ignore
903
943
  <Line
904
- key={`xAxis-${anchor.value}--${index}`}
905
- strokeDasharray={handleLineType(anchor.lineStyle)}
906
- stroke={anchor.color ? anchor.color : 'rgba(0,0,0,1)'}
907
- fill={anchor.color ? anchor.color : 'rgba(0,0,0,1)'}
908
- className='anchor-x'
909
- from={{ x: Number(anchorPosition) + Number(padding), y: 0 }}
910
- to={{ x: Number(anchorPosition) + Number(padding), y: yMax }}
911
- />
944
+ key={`xAxis-${anchor.value}--${index}`}
945
+ strokeDasharray={handleLineType(anchor.lineStyle)}
946
+ stroke={anchor.color ? anchor.color : 'rgba(0,0,0,1)'}
947
+ fill={anchor.color ? anchor.color : 'rgba(0,0,0,1)'}
948
+ className='anchor-x'
949
+ from={{ x: Number(anchorPosition) + Number(padding), y: 0 }}
950
+ to={{ x: Number(anchorPosition) + Number(padding), y: yMax }}
951
+ />
912
952
  )
913
953
  })}
914
954
  {/* we are handling regions in bar charts differently, so that we can calculate the bar group into the region space. */}
@@ -926,36 +966,6 @@ const LinearChart = forwardRef<SVGAElement, LinearChartProps>(({ parentHeight, p
926
966
  width={width}
927
967
  />
928
968
  )}
929
- {chartHasTooltipGuides && showTooltip && tooltipData && config.visual.verticalHoverLine && (
930
- <Group key='tooltipLine-vertical' className='vertical-tooltip-line'>
931
- <Line
932
- from={{ x: tooltipData.dataXPosition - 10, y: 0 }}
933
- to={{ x: tooltipData.dataXPosition - 10, y: yMax }}
934
- stroke={'black'}
935
- strokeWidth={1}
936
- pointerEvents='none'
937
- strokeDasharray='5,5'
938
- className='vertical-tooltip-line'
939
- />
940
- </Group>
941
- )}
942
- {chartHasTooltipGuides && showTooltip && tooltipData && config.visual.horizontalHoverLine && (
943
- <Group
944
- key='tooltipLine-horizontal'
945
- className='horizontal-tooltip-line'
946
- left={config.yAxis.size ? config.yAxis.size : 0}
947
- >
948
- <Line
949
- from={{ x: 0, y: tooltipData.dataYPosition }}
950
- to={{ x: xMax, y: tooltipData.dataYPosition }}
951
- stroke={'black'}
952
- strokeWidth={1}
953
- pointerEvents='none'
954
- strokeDasharray='5,5'
955
- className='horizontal-tooltip-line'
956
- />
957
- </Group>
958
- )}
959
969
  {isNoDataAvailable && (
960
970
  <Text
961
971
  x={Number(config.yAxis.size) + Number(xMax / 2)}
@@ -965,40 +975,12 @@ const LinearChart = forwardRef<SVGAElement, LinearChartProps>(({ parentHeight, p
965
975
  {config.chartMessage.noData}
966
976
  </Text>
967
977
  )}
968
- {(config.visualizationType === 'Bar' || convertLineToBarGraph) &&
969
- config.tooltips.singleSeries &&
970
- config.visual.horizontalHoverLine && (
971
- <Group
972
- key='tooltipLine-horizontal'
973
- className='horizontal-tooltip-line'
974
- left={config.yAxis.size ? config.yAxis.size : 0}
975
- >
976
- <Line
977
- from={{ x: 0, y: point.y }}
978
- to={{ x: xMax, y: point.y }}
979
- stroke={'black'}
980
- strokeWidth={1}
981
- pointerEvents='none'
982
- strokeDasharray='5,5'
983
- className='horizontal-tooltip-line'
984
- />
985
- </Group>
986
- )}
987
- {(config.visualizationType === 'Bar' || convertLineToBarGraph) &&
988
- config.tooltips.singleSeries &&
989
- config.visual.verticalHoverLine && (
990
- <Group key='tooltipLine-vertical' className='vertical-tooltip-line'>
991
- <Line
992
- from={{ x: point.x, y: 0 }}
993
- to={{ x: point.x, y: yMax }}
994
- stroke={'black'}
995
- strokeWidth={1}
996
- pointerEvents='none'
997
- strokeDasharray='5,5'
998
- className='vertical-tooltip-line'
999
- />
1000
- </Group>
1001
- )}
978
+ {showHoverLine && (
979
+ <>
980
+ <HoverLine xMax={xMax} yMax={yMax} point={point} tooltipData={tooltipData} orientation='horizontal' />
981
+ <HoverLine xMax={xMax} yMax={yMax} point={point} tooltipData={tooltipData} orientation='vertical' />
982
+ </>
983
+ )}
1002
984
  <Group left={Number(config.runtime.yAxis.size)}>
1003
985
  <Annotation.Draggable
1004
986
  xScale={xScale}
@@ -1036,10 +1018,10 @@ const LinearChart = forwardRef<SVGAElement, LinearChartProps>(({ parentHeight, p
1036
1018
  to={
1037
1019
  runtime.horizontal
1038
1020
  ? {
1039
- x: 0,
1040
- y:
1041
- config.visualizationType === 'Forest Plot' ? parentHeight : Number(heights.horizontal)
1042
- }
1021
+ x: 0,
1022
+ y:
1023
+ config.visualizationType === 'Forest Plot' ? parentHeight : Number(heights.horizontal)
1024
+ }
1043
1025
  : props.axisToPoint
1044
1026
  }
1045
1027
  stroke='#000'
@@ -1069,15 +1051,18 @@ const LinearChart = forwardRef<SVGAElement, LinearChartProps>(({ parentHeight, p
1069
1051
 
1070
1052
  // Vertical value/suffix vars
1071
1053
  const lastTick = props.ticks.length - 1 === i
1072
- const hideTopTick = lastTick && onlyShowTopPrefixSuffix && suffix && !suffixHasNoSpace
1054
+ const useInlineLabel = lastTick && inlineLabel
1055
+ const hideTopTick = lastTick && inlineLabel && !inlineLabelHasNoSpace
1073
1056
  const valueOnLinePadding = hideAxis ? -8 : -12
1074
1057
  const labelXPadding = labelsAboveGridlines ? valueOnLinePadding : TICK_LABEL_MARGIN_RIGHT
1075
1058
  const labelYPadding = labelsAboveGridlines ? 4 : 0
1076
1059
  const labelX = tick.to.x - labelXPadding
1077
1060
  const labelY = tick.to.y - labelYPadding
1078
1061
  const labelVerticalAnchor = labelsAboveGridlines ? 'end' : 'middle'
1079
- const combineDomSuffixWithValue =
1080
- onlyShowTopPrefixSuffix && labelsAboveGridlines && suffix && lastTick
1062
+ const combineDomInlineLabelWithValue = inlineLabel && labelsAboveGridlines && lastTick
1063
+ const formattedValue = useInlineLabel
1064
+ ? tick.formattedValue.replace(config.dataFormat.suffix, '')
1065
+ : tick.formattedValue
1081
1066
 
1082
1067
  return (
1083
1068
  <Group key={`vx-tick-${tick.value}-${i}`} className={'vx-axis-tick'}>
@@ -1097,13 +1082,12 @@ const LinearChart = forwardRef<SVGAElement, LinearChartProps>(({ parentHeight, p
1097
1082
  config.yAxis.labelPlacement === 'On Date/Category Axis' &&
1098
1083
  !config.yAxis.hideLabel && (
1099
1084
  <Text
1100
- transform={`translate(${tick.to.x - 5}, ${
1101
- config.isLollipopChart
1085
+ transform={`translate(${tick.to.x - 5}, ${config.isLollipopChart
1102
1086
  ? tick.to.y - minY
1103
1087
  : tick.to.y -
1104
- minY +
1105
- (Number(config.barHeight * config.runtime.series.length) - barMinHeight) / 2
1106
- }) rotate(-${config.runtime.horizontal ? config.runtime.yAxis.tickRotation || 0 : 0})`}
1088
+ minY +
1089
+ (Number(config.barHeight * config.runtime.series.length) - barMinHeight) / 2
1090
+ }) rotate(-${config.runtime.horizontal ? config.runtime.yAxis.tickRotation || 0 : 0})`}
1107
1091
  verticalAnchor={'start'}
1108
1092
  textAnchor={'end'}
1109
1093
  fontSize={tickLabelFontSize}
@@ -1117,9 +1101,8 @@ const LinearChart = forwardRef<SVGAElement, LinearChartProps>(({ parentHeight, p
1117
1101
  config.yAxis.labelPlacement === 'On Date/Category Axis' &&
1118
1102
  !config.yAxis.hideLabel && (
1119
1103
  <Text
1120
- transform={`translate(${tick.to.x - 5}, ${
1121
- tick.to.y - minY + (Number(config.barHeight) - barMinHeight) / 2
1122
- }) rotate(-${runtime.horizontal ? runtime.yAxis.tickRotation : 0})`}
1104
+ transform={`translate(${tick.to.x - 5}, ${tick.to.y - minY + (Number(config.barHeight) - barMinHeight) / 2
1105
+ }) rotate(-${runtime.horizontal ? runtime.yAxis.tickRotation : 0})`}
1123
1106
  verticalAnchor={'start'}
1124
1107
  textAnchor={'end'}
1125
1108
  fontSize={tickLabelFontSize}
@@ -1132,9 +1115,8 @@ const LinearChart = forwardRef<SVGAElement, LinearChartProps>(({ parentHeight, p
1132
1115
  visualizationType === 'Paired Bar' &&
1133
1116
  !config.yAxis.hideLabel && (
1134
1117
  <Text
1135
- transform={`translate(${tick.to.x - 5}, ${
1136
- tick.to.y - minY + Number(config.barHeight) / 2
1137
- }) rotate(-${runtime.horizontal ? runtime.yAxis.tickRotation : 0})`}
1118
+ transform={`translate(${tick.to.x - 5}, ${tick.to.y - minY + Number(config.barHeight) / 2
1119
+ }) rotate(-${runtime.horizontal ? runtime.yAxis.tickRotation : 0})`}
1138
1120
  textAnchor={'end'}
1139
1121
  verticalAnchor='middle'
1140
1122
  fontSize={tickLabelFontSize}
@@ -1146,11 +1128,10 @@ const LinearChart = forwardRef<SVGAElement, LinearChartProps>(({ parentHeight, p
1146
1128
  visualizationType === 'Deviation Bar' &&
1147
1129
  !config.yAxis.hideLabel && (
1148
1130
  <Text
1149
- transform={`translate(${tick.to.x - 5}, ${
1150
- config.isLollipopChart
1131
+ transform={`translate(${tick.to.x - 5}, ${config.isLollipopChart
1151
1132
  ? tick.to.y - minY + 2
1152
1133
  : tick.to.y - minY + Number(config.barHeight) / 2
1153
- }) rotate(-${runtime.horizontal ? runtime.yAxis.tickRotation : 0})`}
1134
+ }) rotate(-${runtime.horizontal ? runtime.yAxis.tickRotation : 0})`}
1154
1135
  textAnchor={'end'}
1155
1136
  verticalAnchor='middle'
1156
1137
  fontSize={tickLabelFontSize}
@@ -1181,14 +1162,14 @@ const LinearChart = forwardRef<SVGAElement, LinearChartProps>(({ parentHeight, p
1181
1162
  seriesHighlight.includes(
1182
1163
  config.runtime.seriesLabelsAll[tick.formattedValue - 1]
1183
1164
  )) && (
1184
- <rect
1185
- x={0 - Number(config.yAxis.size)}
1186
- y={tick.to.y - 8 + (config.runtime.horizontal ? horizontalTickOffset : 7)}
1187
- width={Number(config.yAxis.size) + xScale(xScale.domain()[0])}
1188
- height='2'
1189
- fill={colorScale(config.runtime.seriesLabelsAll[tick.formattedValue - 1])}
1190
- />
1191
- )}
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
+ )}
1192
1173
  </>
1193
1174
  )}
1194
1175
  {orientation === 'vertical' &&
@@ -1196,11 +1177,11 @@ const LinearChart = forwardRef<SVGAElement, LinearChartProps>(({ parentHeight, p
1196
1177
  visualizationType !== 'Bump Chart' &&
1197
1178
  !config.yAxis.hideLabel && (
1198
1179
  <>
1199
- {/* TOP ONLY SUFFIX: Dom suffix for 'show only top suffix' behavior */}
1200
- {/* top suffix is shown alone and is allowed to 'overflow' to the right */}
1201
- {/* SPECIAL ONE CHAR CASE: a one character top-only suffix does not overflow */}
1180
+ {/* INLINE LABEL BEHAVIOR: Dom suffix for 'inlineLabel' behavior */}
1181
+ {/* inline label is shown alone and is allowed to 'overflow' to the right */}
1182
+ {/* SPECIAL ONE CHAR CASE: a one character inlineLabel does not overflow */}
1202
1183
  {/* IF VALUES ON LINE: suffix is combined with value to avoid having to calculate varying (now left-aligned) value widths */}
1203
- {onlyShowTopPrefixSuffix && lastTick && !labelsAboveGridlines && (
1184
+ {inlineLabel && lastTick && !labelsAboveGridlines && (
1204
1185
  <BlurStrokeText
1205
1186
  innerRef={suffixRef}
1206
1187
  display={isLogarithmicAxis ? showTicks : 'block'}
@@ -1209,7 +1190,7 @@ const LinearChart = forwardRef<SVGAElement, LinearChartProps>(({ parentHeight, p
1209
1190
  y={labelY}
1210
1191
  angle={-Number(config.yAxis.tickRotation) || 0}
1211
1192
  verticalAnchor={labelVerticalAnchor}
1212
- textAnchor={suffixHasNoSpace ? 'end' : 'start'}
1193
+ textAnchor={inlineLabelHasNoSpace ? 'end' : 'start'}
1213
1194
  fill={config.yAxis.tickLabelColor}
1214
1195
  stroke={'#fff'}
1215
1196
  paintOrder={'stroke'} // keeps stroke under fill
@@ -1217,7 +1198,7 @@ const LinearChart = forwardRef<SVGAElement, LinearChartProps>(({ parentHeight, p
1217
1198
  style={{ whiteSpace: 'pre-wrap' }} // prevents leading spaces from being trimmed
1218
1199
  fontSize={tickLabelFontSize}
1219
1200
  >
1220
- {suffix}
1201
+ {inlineLabel}
1221
1202
  </BlurStrokeText>
1222
1203
  )}
1223
1204
 
@@ -1226,7 +1207,7 @@ const LinearChart = forwardRef<SVGAElement, LinearChartProps>(({ parentHeight, p
1226
1207
  innerRef={el => lastTick && (topYLabelRef.current = el)}
1227
1208
  display={isLogarithmicAxis ? showTicks : 'block'}
1228
1209
  dx={isLogarithmicAxis ? -6 : 0}
1229
- x={suffixHasNoSpace ? labelX - suffixWidth : labelX}
1210
+ x={inlineLabelHasNoSpace ? labelX - suffixWidth : labelX}
1230
1211
  y={labelY + (config.runtime.horizontal ? horizontalTickOffset : 0)}
1231
1212
  angle={-Number(config.yAxis.tickRotation) || 0}
1232
1213
  verticalAnchor={config.runtime.horizontal ? 'start' : labelVerticalAnchor}
@@ -1239,7 +1220,7 @@ const LinearChart = forwardRef<SVGAElement, LinearChartProps>(({ parentHeight, p
1239
1220
  style={{ whiteSpace: 'pre-wrap' }} // prevents leading spaces from being trimmed
1240
1221
  fontSize={tickLabelFontSize}
1241
1222
  >
1242
- {`${tick.formattedValue}${combineDomSuffixWithValue ? suffix : ''}`}
1223
+ {`${formattedValue}${combineDomInlineLabelWithValue ? inlineLabel : ''}`}
1243
1224
  </BlurStrokeText>
1244
1225
  </>
1245
1226
  )}
@@ -1331,9 +1312,8 @@ const LinearChart = forwardRef<SVGAElement, LinearChartProps>(({ parentHeight, p
1331
1312
  className='y-label'
1332
1313
  textAnchor='middle'
1333
1314
  verticalAnchor='start'
1334
- transform={`translate(${
1335
- config.yAxis.rightLabelOffsetSize ? config.yAxis.rightLabelOffsetSize : 0
1336
- }, ${axisCenter}) rotate(-90)`}
1315
+ transform={`translate(${config.yAxis.rightLabelOffsetSize ? config.yAxis.rightLabelOffsetSize : 0
1316
+ }, ${axisCenter}) rotate(-90)`}
1337
1317
  fontWeight='bold'
1338
1318
  fill={config.yAxis.rightAxisLabelColor}
1339
1319
  fontSize={axisLabelFontSize}
@@ -1365,8 +1345,8 @@ const LinearChart = forwardRef<SVGAElement, LinearChartProps>(({ parentHeight, p
1365
1345
  runtime.horizontal && config.visualizationType !== 'Forest Plot'
1366
1346
  ? Number(heights.horizontal) + Number(config.xAxis.axisPadding)
1367
1347
  : config.visualizationType === 'Forest Plot'
1368
- ? yMax + Number(config.xAxis.axisPadding)
1369
- : yMax
1348
+ ? yMax + Number(config.xAxis.axisPadding)
1349
+ : yMax
1370
1350
  }
1371
1351
  left={config.visualizationType !== 'Forest Plot' ? Number(runtime.yAxis.size) : 0}
1372
1352
  label={config[section].label}
@@ -1379,8 +1359,8 @@ const LinearChart = forwardRef<SVGAElement, LinearChartProps>(({ parentHeight, p
1379
1359
  config.runtime.xAxis.manual
1380
1360
  ? getTickValues(xAxisDataMapped, xScale, isDateTime ? xTickCount : getManualStep(), config)
1381
1361
  : config.runtime.xAxis.type === 'date'
1382
- ? xAxisDataMapped
1383
- : undefined
1362
+ ? xAxisDataMapped
1363
+ : undefined
1384
1364
  }
1385
1365
  >
1386
1366
  {props => {
@@ -1406,14 +1386,14 @@ const LinearChart = forwardRef<SVGAElement, LinearChartProps>(({ parentHeight, p
1406
1386
  // filter out every [distanceBetweenTicks] tick starting from the end, so the last tick is always labeled
1407
1387
  const filteredTicks = useDateSpanMonths
1408
1388
  ? [...props.ticks]
1409
- .reverse()
1410
- .filter((_, i) => i % distanceBetweenTicks === 0)
1411
- .reverse()
1412
- .map((tick, i, arr) => ({
1413
- ...tick,
1414
- // reformat in case showYearsOnce, since first month of year may have changed
1415
- formattedValue: handleBottomTickFormatting(tick.value, i, arr)
1416
- }))
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
+ }))
1417
1397
  : props.ticks
1418
1398
 
1419
1399
  const axisMaxHeight = bottomLabelStart + BOTTOM_LABEL_PADDING
@@ -1543,13 +1523,14 @@ const LinearChart = forwardRef<SVGAElement, LinearChartProps>(({ parentHeight, p
1543
1523
  showTooltip &&
1544
1524
  !tooltipData?.data?.some(subArray => subArray.some(item => item === undefined)) &&
1545
1525
  tooltipData.dataYPosition &&
1546
- tooltipData.dataXPosition && (
1526
+ tooltipData.dataXPosition &&
1527
+ !config.tooltips.singleSeries && (
1547
1528
  <>
1548
- <style>{`.tooltip {background-color: rgba(255,255,255, ${
1549
- config.tooltips.opacity / 100
1550
- }) !important;`}</style>
1529
+ <style>{`.tooltip {background-color: rgba(255,255,255, ${config.tooltips.opacity / 100
1530
+ }) !important;`}</style>
1551
1531
  <style>{`.tooltip {max-width:300px} !important; word-wrap: break-word; `}</style>
1552
1532
  <TooltipWithBounds
1533
+ ref={tooltipRef}
1553
1534
  key={Math.random()}
1554
1535
  className={'tooltip cdc-open-viz-module'}
1555
1536
  left={tooltipLeft}
@@ -1564,7 +1545,6 @@ const LinearChart = forwardRef<SVGAElement, LinearChartProps>(({ parentHeight, p
1564
1545
  </TooltipWithBounds>
1565
1546
  </>
1566
1547
  )}
1567
-
1568
1548
  {config.visualizationType === 'Bump Chart' && (
1569
1549
  <ReactTooltip
1570
1550
  id={`bump-chart`}
@@ -1574,7 +1554,7 @@ const LinearChart = forwardRef<SVGAElement, LinearChartProps>(({ parentHeight, p
1574
1554
  style={{ background: `rgba(255,255,255, ${config.tooltips.opacity / 100})`, color: 'black' }}
1575
1555
  />
1576
1556
  )}
1577
- {visSupportsReactTooltip() && !isDraggingAnnotation && (
1557
+ {!isDraggingAnnotation && (
1578
1558
  <ReactTooltip
1579
1559
  id={`cdc-open-viz-tooltip-${runtime.uniqueId}`}
1580
1560
  variant='light'