@cdc/chart 4.25.5-1 → 4.25.6-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 (43) hide show
  1. package/LICENSE +201 -0
  2. package/dist/cdcchart.js +32053 -27935
  3. package/index.html +130 -130
  4. package/package.json +2 -2
  5. package/src/CdcChartComponent.tsx +66 -26
  6. package/src/_stories/Chart.stories.tsx +99 -93
  7. package/src/_stories/ChartPrefixSuffix.stories.tsx +29 -32
  8. package/src/_stories/_mock/pie_calculated_area.json +417 -0
  9. package/src/components/BarChart/components/BarChart.Horizontal.tsx +4 -13
  10. package/src/components/BarChart/components/BarChart.StackedVertical.tsx +3 -14
  11. package/src/components/BarChart/components/BarChart.Vertical.tsx +2 -8
  12. package/src/components/Brush/BrushChart.tsx +73 -0
  13. package/src/components/Brush/BrushController..tsx +39 -0
  14. package/src/components/DeviationBar.jsx +0 -1
  15. package/src/components/EditorPanel/EditorPanel.tsx +246 -156
  16. package/src/components/EditorPanel/components/Panels/Panel.General.tsx +2 -2
  17. package/src/components/EditorPanel/components/Panels/Panel.Series.tsx +3 -2
  18. package/src/components/EditorPanel/components/Panels/Panel.Visual.tsx +2 -1
  19. package/src/components/EditorPanel/components/Panels/panelVisual.styles.css +8 -0
  20. package/src/components/EditorPanel/useEditorPermissions.ts +7 -4
  21. package/src/components/HoverLine/HoverLine.tsx +74 -0
  22. package/src/components/Legend/Legend.Suppression.tsx +47 -3
  23. package/src/components/Legend/helpers/index.ts +1 -1
  24. package/src/components/LineChart/helpers.ts +7 -7
  25. package/src/components/LineChart/index.tsx +3 -6
  26. package/src/components/LinearChart.tsx +108 -72
  27. package/src/components/PieChart/PieChart.tsx +58 -13
  28. package/src/data/initial-state.js +8 -5
  29. package/src/helpers/countNumOfTicks.ts +4 -19
  30. package/src/helpers/getNewRuntime.ts +35 -0
  31. package/src/helpers/getPiePercent.ts +22 -0
  32. package/src/helpers/getTransformedData.ts +22 -0
  33. package/src/helpers/tests/getNewRuntime.test.ts +82 -0
  34. package/src/helpers/tests/getPiePercent.test.ts +38 -0
  35. package/src/hooks/useRightAxis.ts +1 -1
  36. package/src/hooks/useScales.ts +8 -3
  37. package/src/hooks/useTooltip.tsx +24 -10
  38. package/src/scss/main.scss +8 -4
  39. package/src/store/chart.actions.ts +2 -6
  40. package/src/store/chart.reducer.ts +23 -23
  41. package/src/types/ChartConfig.ts +7 -4
  42. package/src/types/ChartContext.ts +0 -2
  43. package/src/components/ZoomBrush.tsx +0 -251
@@ -11,8 +11,7 @@ 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,
@@ -238,7 +253,9 @@ const LinearChart = forwardRef<SVGAElement, LinearChartProps>(({ parentHeight, p
238
253
  const handleNumTicks = isForestPlot ? config.data.length : yTickCount
239
254
 
240
255
  // Tooltip Helpers
241
- const { tooltipData, showTooltip, hideTooltip, tooltipOpen, tooltipLeft, tooltipTop } = useTooltip()
256
+
257
+ const { tooltipData, showTooltip, hideTooltip, tooltipOpen, tooltipLeft, tooltipTop } =
258
+ useTooltip<UseTooltipReturn<TooltipData>>()
242
259
 
243
260
  // prettier-ignore
244
261
  const {
@@ -247,11 +264,11 @@ const LinearChart = forwardRef<SVGAElement, LinearChartProps>(({ parentHeight, p
247
264
  handleTooltipMouseOff,
248
265
  TooltipListItem,
249
266
  } = useCoveTooltip({
250
- xScale,
251
- yScale,
252
- seriesScale,
253
- showTooltip,
254
- hideTooltip
267
+ xScale,
268
+ yScale,
269
+ seriesScale,
270
+ showTooltip,
271
+ hideTooltip
255
272
  })
256
273
  // get the number of months between the first and last date
257
274
  const { dataKey } = runtime.xAxis
@@ -271,7 +288,7 @@ const LinearChart = forwardRef<SVGAElement, LinearChartProps>(({ parentHeight, p
271
288
  if (config.data && !config.data[index] && visualizationType === 'Forest Plot') return
272
289
  if (config.visualizationType === 'Forest Plot') return config.data[index][config.xAxis.dataKey]
273
290
  if (isDateScale(runtime.yAxis)) return formatDate(parseDate(tick))
274
- if (orientation === 'vertical' && max - min < 3)
291
+ if (orientation === 'vertical' && max - min < 3 && !config.dataFormat?.roundTo)
275
292
  return formatNumber(tick, 'left', shouldAbbreviate, false, false, '1', { index, length: ticks.length })
276
293
  if (orientation === 'vertical') {
277
294
  // TODO suggestion: pass all options as object key/values to allow for more flexibility
@@ -375,7 +392,7 @@ const LinearChart = forwardRef<SVGAElement, LinearChartProps>(({ parentHeight, p
375
392
  if (!suffixEl) return setSuffixWidth(0)
376
393
  const suffixElWidth = suffixEl.getBBox().width
377
394
  setSuffixWidth(suffixElWidth)
378
- }, [config.dataFormat.suffix, config.dataFormat.onlyShowTopPrefixSuffix])
395
+ }, [config.dataFormat.suffix, inlineLabel])
379
396
 
380
397
  // forest plot x-axis label positioning
381
398
  useEffect(() => {
@@ -401,11 +418,11 @@ const LinearChart = forwardRef<SVGAElement, LinearChartProps>(({ parentHeight, p
401
418
  const topLabelOnGridline = topYLabelRef.current && yAxis.labelsAboveGridlines
402
419
 
403
420
  // Heights to add
404
-
405
421
  const brushHeight = brush?.active ? brush?.height + brush?.height : 0
422
+ const brushHeightWithMargin = config.brush?.active ? brushHeight + brushHeight : 0
406
423
  const forestRowsHeight = isForestPlot ? config.data.length * forestPlot.rowHeight : 0
407
424
  const topLabelOnGridlineHeight = topLabelOnGridline ? topYLabelRef.current.getBBox().height : 0
408
- const additionalHeight = axisBottomHeight + brushHeight + forestRowsHeight + topLabelOnGridlineHeight
425
+ const additionalHeight = axisBottomHeight + brushHeightWithMargin + forestRowsHeight + topLabelOnGridlineHeight
409
426
  const newHeight = initialHeight + additionalHeight
410
427
  if (!parentRef.current) return
411
428
 
@@ -471,6 +488,23 @@ const LinearChart = forwardRef<SVGAElement, LinearChartProps>(({ parentHeight, p
471
488
  setYAxisAutoPadding(newPadding * 100 + 0.1)
472
489
  }, [maxValue, labelsOverflow, yScale, handleNumTicks])
473
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
+
474
508
  // Render Functions
475
509
  const generatePairedBarAxis = () => {
476
510
  const axisMaxHeight = bottomLabelStart + BOTTOM_LABEL_PADDING
@@ -635,6 +669,8 @@ const LinearChart = forwardRef<SVGAElement, LinearChartProps>(({ parentHeight, p
635
669
  role='img'
636
670
  aria-label={handleChartAriaLabels(config)}
637
671
  style={{ overflow: 'visible' }}
672
+ onMouseLeave={() => setShowHoverLine(false)}
673
+ onMouseEnter={() => setShowHoverLine(true)}
638
674
  >
639
675
  {!isDraggingAnnotation && <Bar width={parentWidth} height={initialHeight} fill={'transparent'}></Bar>}{' '}
640
676
  {/* GRID LINES */}
@@ -771,6 +807,27 @@ const LinearChart = forwardRef<SVGAElement, LinearChartProps>(({ parentHeight, p
771
807
  showTooltip={showTooltip}
772
808
  />
773
809
  )}
810
+ {/* Line chart */}
811
+ {/* TODO: Make this just line or combo? */}
812
+ {!['Paired Bar', 'Box Plot', 'Area Chart', 'Scatter Plot', 'Deviation Bar', 'Forecasting', 'Bar'].includes(
813
+ visualizationType
814
+ ) &&
815
+ !convertLineToBarGraph && (
816
+ <>
817
+ <LineChart
818
+ xScale={xScale}
819
+ yScale={yScale}
820
+ getXAxisData={getXAxisData}
821
+ getYAxisData={getYAxisData}
822
+ xMax={xMax}
823
+ yMax={yMax}
824
+ seriesStyle={config.runtime.series}
825
+ tooltipData={tooltipData}
826
+ handleTooltipMouseOver={handleTooltipMouseOver}
827
+ handleTooltipMouseOff={handleTooltipMouseOff}
828
+ />
829
+ </>
830
+ )}
774
831
  {(visualizationType === 'Forecasting' || visualizationType === 'Combo') && (
775
832
  <Forecasting
776
833
  showTooltip={showTooltip}
@@ -778,13 +835,11 @@ const LinearChart = forwardRef<SVGAElement, LinearChartProps>(({ parentHeight, p
778
835
  xScale={xScale}
779
836
  yScale={yScale}
780
837
  width={xMax}
781
- le
782
838
  height={yMax}
783
839
  xScaleNoPadding={xScaleNoPadding}
784
840
  chartRef={svgRef}
785
841
  handleTooltipMouseOver={handleTooltipMouseOver}
786
842
  handleTooltipMouseOff={handleTooltipMouseOff}
787
- isBrush={false}
788
843
  />
789
844
  )}
790
845
  {visualizationType === 'Forest Plot' && (
@@ -855,7 +910,7 @@ const LinearChart = forwardRef<SVGAElement, LinearChartProps>(({ parentHeight, p
855
910
  strokeDasharray={handleLineType(anchor.lineStyle)}
856
911
  stroke={anchor.color ? anchor.color : 'rgba(0,0,0,1)'}
857
912
  className='anchor-y'
858
- from={{ x: 0 + padding, y: position - middleOffset}}
913
+ from={{ x: 0 + padding, y: position - middleOffset }}
859
914
  to={{ x: width - config.yAxis.rightAxisSize, y: position - middleOffset }}
860
915
  />
861
916
  )
@@ -887,14 +942,14 @@ const LinearChart = forwardRef<SVGAElement, LinearChartProps>(({ parentHeight, p
887
942
  return (
888
943
  // prettier-ignore
889
944
  <Line
890
- key={`xAxis-${anchor.value}--${index}`}
891
- strokeDasharray={handleLineType(anchor.lineStyle)}
892
- stroke={anchor.color ? anchor.color : 'rgba(0,0,0,1)'}
893
- fill={anchor.color ? anchor.color : 'rgba(0,0,0,1)'}
894
- className='anchor-x'
895
- from={{ x: Number(anchorPosition) + Number(padding), y: 0 }}
896
- to={{ x: Number(anchorPosition) + Number(padding), y: yMax }}
897
- />
945
+ key={`xAxis-${anchor.value}--${index}`}
946
+ strokeDasharray={handleLineType(anchor.lineStyle)}
947
+ stroke={anchor.color ? anchor.color : 'rgba(0,0,0,1)'}
948
+ fill={anchor.color ? anchor.color : 'rgba(0,0,0,1)'}
949
+ className='anchor-x'
950
+ from={{ x: Number(anchorPosition) + Number(padding), y: 0 }}
951
+ to={{ x: Number(anchorPosition) + Number(padding), y: yMax }}
952
+ />
898
953
  )
899
954
  })}
900
955
  {/* we are handling regions in bar charts differently, so that we can calculate the bar group into the region space. */}
@@ -921,35 +976,11 @@ const LinearChart = forwardRef<SVGAElement, LinearChartProps>(({ parentHeight, p
921
976
  {config.chartMessage.noData}
922
977
  </Text>
923
978
  )}
924
- {config.visual.horizontalHoverLine && tooltipData && (
925
- <Group
926
- key={`tooltipLine-horizontal${point.y}${point.x}`}
927
- className='horizontal-tooltip-line'
928
- left={config.yAxis.size ? config.yAxis.size : 0}
929
- >
930
- <Line
931
- from={{ x: 0, y: point.y }}
932
- to={{ x: xMax, y: point.y }}
933
- stroke={'black'}
934
- strokeWidth={1}
935
- pointerEvents='none'
936
- strokeDasharray='5,5'
937
- className='horizontal-tooltip-line'
938
- />
939
- </Group>
940
- )}
941
- {config.visual.verticalHoverLine && tooltipData && (
942
- <Group key={`tooltipLine-vertical${point.y}${point.x}`} className='vertical-tooltip-line'>
943
- <Line
944
- from={{ x: point.x, y: 0 }}
945
- to={{ x: point.x, y: yMax }}
946
- stroke={'black'}
947
- strokeWidth={1}
948
- pointerEvents='none'
949
- strokeDasharray='5,5'
950
- className='vertical-tooltip-line'
951
- />
952
- </Group>
979
+ {showHoverLine && (
980
+ <>
981
+ <HoverLine xMax={xMax} yMax={yMax} point={point} tooltipData={tooltipData} orientation='horizontal' />
982
+ <HoverLine xMax={xMax} yMax={yMax} point={point} tooltipData={tooltipData} orientation='vertical' />
983
+ </>
953
984
  )}
954
985
  <Group left={Number(config.runtime.yAxis.size)}>
955
986
  <Annotation.Draggable
@@ -1021,15 +1052,18 @@ const LinearChart = forwardRef<SVGAElement, LinearChartProps>(({ parentHeight, p
1021
1052
 
1022
1053
  // Vertical value/suffix vars
1023
1054
  const lastTick = props.ticks.length - 1 === i
1024
- const hideTopTick = lastTick && onlyShowTopPrefixSuffix && suffix && !suffixHasNoSpace
1055
+ const useInlineLabel = lastTick && inlineLabel
1056
+ const hideTopTick = lastTick && inlineLabel && !inlineLabelHasNoSpace
1025
1057
  const valueOnLinePadding = hideAxis ? -8 : -12
1026
1058
  const labelXPadding = labelsAboveGridlines ? valueOnLinePadding : TICK_LABEL_MARGIN_RIGHT
1027
1059
  const labelYPadding = labelsAboveGridlines ? 4 : 0
1028
1060
  const labelX = tick.to.x - labelXPadding
1029
1061
  const labelY = tick.to.y - labelYPadding
1030
1062
  const labelVerticalAnchor = labelsAboveGridlines ? 'end' : 'middle'
1031
- const combineDomSuffixWithValue =
1032
- onlyShowTopPrefixSuffix && labelsAboveGridlines && suffix && lastTick
1063
+ const combineDomInlineLabelWithValue = inlineLabel && labelsAboveGridlines && lastTick
1064
+ const formattedValue = useInlineLabel
1065
+ ? tick.formattedValue.replace(config.dataFormat.suffix, '')
1066
+ : tick.formattedValue
1033
1067
 
1034
1068
  return (
1035
1069
  <Group key={`vx-tick-${tick.value}-${i}`} className={'vx-axis-tick'}>
@@ -1148,11 +1182,11 @@ const LinearChart = forwardRef<SVGAElement, LinearChartProps>(({ parentHeight, p
1148
1182
  visualizationType !== 'Bump Chart' &&
1149
1183
  !config.yAxis.hideLabel && (
1150
1184
  <>
1151
- {/* TOP ONLY SUFFIX: Dom suffix for 'show only top suffix' behavior */}
1152
- {/* top suffix is shown alone and is allowed to 'overflow' to the right */}
1153
- {/* SPECIAL ONE CHAR CASE: a one character top-only suffix does not overflow */}
1185
+ {/* INLINE LABEL BEHAVIOR: Dom suffix for 'inlineLabel' behavior */}
1186
+ {/* inline label is shown alone and is allowed to 'overflow' to the right */}
1187
+ {/* SPECIAL ONE CHAR CASE: a one character inlineLabel does not overflow */}
1154
1188
  {/* IF VALUES ON LINE: suffix is combined with value to avoid having to calculate varying (now left-aligned) value widths */}
1155
- {onlyShowTopPrefixSuffix && lastTick && !labelsAboveGridlines && (
1189
+ {inlineLabel && lastTick && !labelsAboveGridlines && (
1156
1190
  <BlurStrokeText
1157
1191
  innerRef={suffixRef}
1158
1192
  display={isLogarithmicAxis ? showTicks : 'block'}
@@ -1161,7 +1195,7 @@ const LinearChart = forwardRef<SVGAElement, LinearChartProps>(({ parentHeight, p
1161
1195
  y={labelY}
1162
1196
  angle={-Number(config.yAxis.tickRotation) || 0}
1163
1197
  verticalAnchor={labelVerticalAnchor}
1164
- textAnchor={suffixHasNoSpace ? 'end' : 'start'}
1198
+ textAnchor={inlineLabelHasNoSpace ? 'end' : 'start'}
1165
1199
  fill={config.yAxis.tickLabelColor}
1166
1200
  stroke={'#fff'}
1167
1201
  paintOrder={'stroke'} // keeps stroke under fill
@@ -1169,7 +1203,7 @@ const LinearChart = forwardRef<SVGAElement, LinearChartProps>(({ parentHeight, p
1169
1203
  style={{ whiteSpace: 'pre-wrap' }} // prevents leading spaces from being trimmed
1170
1204
  fontSize={tickLabelFontSize}
1171
1205
  >
1172
- {suffix}
1206
+ {inlineLabel}
1173
1207
  </BlurStrokeText>
1174
1208
  )}
1175
1209
 
@@ -1178,7 +1212,7 @@ const LinearChart = forwardRef<SVGAElement, LinearChartProps>(({ parentHeight, p
1178
1212
  innerRef={el => lastTick && (topYLabelRef.current = el)}
1179
1213
  display={isLogarithmicAxis ? showTicks : 'block'}
1180
1214
  dx={isLogarithmicAxis ? -6 : 0}
1181
- x={suffixHasNoSpace ? labelX - suffixWidth : labelX}
1215
+ x={inlineLabelHasNoSpace ? labelX - suffixWidth : labelX}
1182
1216
  y={labelY + (config.runtime.horizontal ? horizontalTickOffset : 0)}
1183
1217
  angle={-Number(config.yAxis.tickRotation) || 0}
1184
1218
  verticalAnchor={config.runtime.horizontal ? 'start' : labelVerticalAnchor}
@@ -1191,7 +1225,7 @@ const LinearChart = forwardRef<SVGAElement, LinearChartProps>(({ parentHeight, p
1191
1225
  style={{ whiteSpace: 'pre-wrap' }} // prevents leading spaces from being trimmed
1192
1226
  fontSize={tickLabelFontSize}
1193
1227
  >
1194
- {`${tick.formattedValue}${combineDomSuffixWithValue ? suffix : ''}`}
1228
+ {`${formattedValue}${combineDomInlineLabelWithValue ? inlineLabel : ''}`}
1195
1229
  </BlurStrokeText>
1196
1230
  </>
1197
1231
  )}
@@ -1495,13 +1529,15 @@ const LinearChart = forwardRef<SVGAElement, LinearChartProps>(({ parentHeight, p
1495
1529
  showTooltip &&
1496
1530
  !tooltipData?.data?.some(subArray => subArray.some(item => item === undefined)) &&
1497
1531
  tooltipData.dataYPosition &&
1498
- tooltipData.dataXPosition && (
1532
+ tooltipData.dataXPosition &&
1533
+ !config.tooltips.singleSeries && (
1499
1534
  <>
1500
1535
  <style>{`.tooltip {background-color: rgba(255,255,255, ${
1501
1536
  config.tooltips.opacity / 100
1502
1537
  }) !important;`}</style>
1503
1538
  <style>{`.tooltip {max-width:300px} !important; word-wrap: break-word; `}</style>
1504
1539
  <TooltipWithBounds
1540
+ ref={tooltipRef}
1505
1541
  key={Math.random()}
1506
1542
  className={'tooltip cdc-open-viz-module'}
1507
1543
  left={tooltipLeft}
@@ -1525,7 +1561,7 @@ const LinearChart = forwardRef<SVGAElement, LinearChartProps>(({ parentHeight, p
1525
1561
  style={{ background: `rgba(255,255,255, ${config.tooltips.opacity / 100})`, color: 'black' }}
1526
1562
  />
1527
1563
  )}
1528
- {visSupportsReactTooltip() && !isDraggingAnnotation && (
1564
+ {!isDraggingAnnotation && (
1529
1565
  <ReactTooltip
1530
1566
  id={`cdc-open-viz-tooltip-${runtime.uniqueId}`}
1531
1567
  variant='light'
@@ -46,28 +46,51 @@ const PieChart = props => {
46
46
  const pivotColumns = Object.values(config.columns).filter(column => column.showInViz)
47
47
  const dataNeedsPivot = pivotColumns.length > 0
48
48
  const pivotKey = dataNeedsPivot ? 'pivotColumn' : undefined
49
+ const showPercentage = config.dataFormat.showPiePercent
50
+ const labelForCalcArea = 'Calculated Area'
51
+
49
52
  const _data = useMemo(() => {
53
+ let baseData = []
54
+
50
55
  if (dataNeedsPivot) {
51
- let newData = []
52
56
  const primaryColumn = config.yAxis.dataKey
53
57
  const additionalColumns = pivotColumns.map(column => column.name)
54
58
  const allColumns = [primaryColumn, ...additionalColumns]
55
59
  const columnToUpdate = config.xAxis.dataKey
60
+
56
61
  data.forEach(d => {
57
62
  allColumns.forEach(col => {
58
- const data = d[col]
59
- if (data) {
60
- newData.push({
61
- [pivotKey]: data,
63
+ const val = d[col]
64
+ if (val) {
65
+ baseData.push({
66
+ [pivotKey]: val,
62
67
  [columnToUpdate]: `${d[columnToUpdate]} - ${col}`
63
68
  })
64
69
  }
65
70
  })
66
71
  })
67
- return newData
72
+ } else {
73
+ baseData = [...data]
68
74
  }
69
- return data
70
- }, [data, dataNeedsPivot])
75
+
76
+ // === ADD "OTHER" IF PERCENT MODE IS ENABLED ===
77
+ if (showPercentage) {
78
+ const total = baseData.reduce((sum, d) => {
79
+ const val = parseFloat(d[config.runtime.yAxis.dataKey])
80
+ return sum + (isNaN(val) ? 0 : val)
81
+ }, 0)
82
+
83
+ if (total < 100) {
84
+ const remaining = 100 - total
85
+ baseData.push({
86
+ [config.runtime.xAxis.dataKey]: labelForCalcArea,
87
+ [config.runtime.yAxis.dataKey]: remaining
88
+ })
89
+ }
90
+ }
91
+
92
+ return baseData
93
+ }, [data, dataNeedsPivot, showPercentage, config])
71
94
 
72
95
  const _colorScale = useMemo(() => {
73
96
  if (dataNeedsPivot) {
@@ -78,15 +101,31 @@ const PieChart = props => {
78
101
  const numberOfKeys = Object.entries(keys).length
79
102
  let palette = config.customColors || colorPalettes[config.palette]
80
103
  palette = palette.slice(0, numberOfKeys)
81
-
82
104
  return scaleOrdinal({
83
105
  domain: Object.keys(keys),
84
106
  range: palette,
85
107
  unknown: null
86
108
  })
87
109
  }
110
+
111
+ if (showPercentage) {
112
+ const keys = {}
113
+ _data.forEach(d => {
114
+ keys[d[config.xAxis.dataKey]] = true
115
+ })
116
+ // take out Calculated Area so it falls back to `unknown`
117
+ const domainKeys = Object.keys(keys).filter(k => k !== labelForCalcArea)
118
+
119
+ const basePalette = (config.customColors || colorPalettes[config.palette]).slice(0, domainKeys.length)
120
+ const COOL_GRAY_90 = getComputedStyle(document.documentElement).getPropertyValue('--cool-gray-10').trim()
121
+ return scaleOrdinal({
122
+ domain: domainKeys,
123
+ range: basePalette,
124
+ unknown: COOL_GRAY_90
125
+ })
126
+ }
88
127
  return colorScale
89
- }, [colorScale, dataNeedsPivot])
128
+ }, [_data, dataNeedsPivot, colorScale])
90
129
 
91
130
  const triggerRef = useRef()
92
131
  const dataRef = useIntersectionObserver(triggerRef, {
@@ -130,9 +169,15 @@ const PieChart = props => {
130
169
  const roundTo = Number(config.dataFormat.roundTo) || 0
131
170
  // Calculate the percentage of the full circle (360 degrees)
132
171
  const degrees = ((arc.endAngle - arc.startAngle) * 180) / Math.PI
172
+ const valueFromData = parseFloat(arc.data[config.runtime.yAxis.dataKey])
173
+ const percentageToDisplay = showPercentage ? valueFromData : (degrees / 360) * 100
174
+
175
+ let roundedPercentage = percentageToDisplay.toFixed(roundTo) + '%'
176
+ // add missing pie part
177
+ if (arc.data[config.xAxis.dataKey] === labelForCalcArea && config.dataFormat.showPiePercent) {
178
+ roundedPercentage = '**'
179
+ }
133
180
 
134
- const percentageOfCircle = (degrees / 360) * 100
135
- const roundedPercentage = percentageOfCircle.toFixed(roundTo) + '%'
136
181
  return (
137
182
  <Group key={key} className={`slice-${key}`}>
138
183
  {/* ── the slice */}
@@ -238,7 +283,7 @@ const PieChart = props => {
238
283
  <AnimatedPie
239
284
  {...pie}
240
285
  getKey={d => d.data[config.runtime.xAxis.dataKey]}
241
- colorScale={_colorScale}
286
+ colorScale={_colorScale}
242
287
  onHover={handleTooltipMouseOver}
243
288
  onLeave={handleTooltipMouseOff}
244
289
  />
@@ -89,6 +89,11 @@ export default {
89
89
  topAxis: {
90
90
  hasLine: false
91
91
  },
92
+ brush: {
93
+ isActive: false,
94
+ isBrushing: false,
95
+ data: []
96
+ },
92
97
  isLegendValue: false,
93
98
  barThickness: 0.35,
94
99
  barHeight: 25,
@@ -120,7 +125,8 @@ export default {
120
125
  maxTickRotation: 0,
121
126
  padding: 5,
122
127
  showYearsOnce: false,
123
- sortByRecentDate: false
128
+ sortByRecentDate: false,
129
+ brushActive: false
124
130
  },
125
131
  table: {
126
132
  label: 'Data Table',
@@ -176,10 +182,7 @@ export default {
176
182
  position: 'right',
177
183
  orderedValues: []
178
184
  },
179
- brush: {
180
- height: 45,
181
- active: false
182
- },
185
+
183
186
  exclusions: {
184
187
  active: false,
185
188
  keys: []
@@ -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,35 @@
1
+ import _ from 'lodash'
2
+
3
+ export const getNewRuntime = (visualizationConfig, newFilteredData) => {
4
+ const runtime = _.cloneDeep(visualizationConfig.runtime) || {}
5
+ runtime.series = []
6
+ runtime.seriesLabels = {}
7
+ runtime.seriesLabelsAll = []
8
+ const { filters, columns, dynamicSeriesType, dynamicSeriesLineType, xAxis } = visualizationConfig
9
+ if (newFilteredData?.length) {
10
+ const columnNames = Object.keys(newFilteredData[0])
11
+ columnNames.forEach(colName => {
12
+ const isNotXAxis = xAxis.dataKey !== colName
13
+ const isNotFiltered = !filters || !filters?.find(filter => filter.columnName === colName)
14
+ const noColConfiguration = !columns || Object.keys(columns).indexOf(colName) === -1
15
+ if (isNotXAxis && isNotFiltered && noColConfiguration) {
16
+ runtime.series.push({
17
+ dataKey: colName,
18
+ type: dynamicSeriesType,
19
+ lineType: dynamicSeriesLineType,
20
+ tooltip: true
21
+ })
22
+ }
23
+ })
24
+ }
25
+
26
+ runtime.seriesKeys = runtime.series
27
+ ? runtime.series.map(series => {
28
+ runtime.seriesLabels[series.dataKey] = series.name || series.label || series.dataKey
29
+ runtime.seriesLabelsAll.push(series.name || series.dataKey)
30
+ return series.dataKey
31
+ })
32
+ : []
33
+
34
+ return runtime
35
+ }
@@ -0,0 +1,22 @@
1
+ import _ from 'lodash'
2
+
3
+ export const getPiePercent = (data: Record<string, any>[], seriesKey: string): Record<string, any>[] => {
4
+ // getonly the numeric values for each seriesKey
5
+ const numericValues = data.map(row => _.toNumber(row[seriesKey])).filter(v => !Number.isNaN(v))
6
+
7
+ const total = numericValues.reduce((sum, v) => sum + v, 0)
8
+
9
+ return data.map(row => {
10
+ const raw = _.toNumber(row[seriesKey])
11
+ if (Number.isNaN(raw)) {
12
+ // skip non-numeric / undefined
13
+ return row
14
+ }
15
+
16
+ const pct = total === 0 ? 0 : (raw / total) * 100
17
+ return {
18
+ ...row,
19
+ [seriesKey]: pct
20
+ }
21
+ })
22
+ }
@@ -0,0 +1,22 @@
1
+ type DataRow = Record<string, any>
2
+
3
+ export const getTransformedData = ({
4
+ brushData,
5
+ filteredData,
6
+ excludedData,
7
+ clean
8
+ }: {
9
+ brushData: DataRow[]
10
+ filteredData: DataRow[]
11
+ excludedData: DataRow[]
12
+ clean: (data: DataRow[]) => DataRow[]
13
+ }): DataRow[] => {
14
+ const data =
15
+ Array.isArray(brushData) && brushData.length > 0
16
+ ? brushData
17
+ : Array.isArray(filteredData) && filteredData.length > 0
18
+ ? filteredData
19
+ : excludedData
20
+
21
+ return clean(data)
22
+ }