@cdc/chart 4.24.11 → 4.24.12

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 (64) hide show
  1. package/dist/cdcchart.js +31248 -31198
  2. package/examples/feature/sankey/sankey-example-data.json +126 -13
  3. package/examples/feature/tests-date-exclusions/date-exclusions-config.json +372 -12
  4. package/examples/private/DEV-8850-2.json +493 -0
  5. package/examples/private/DEV-9822.json +574 -0
  6. package/examples/private/DEV-9840.json +553 -0
  7. package/examples/private/DEV-9850-3.json +461 -0
  8. package/examples/private/chart.json +1084 -0
  9. package/examples/private/ci_formatted.json +202 -0
  10. package/examples/private/ci_issue.json +3016 -0
  11. package/examples/private/completed.json +634 -0
  12. package/examples/private/dem-data-long.csv +20 -0
  13. package/examples/private/dem-data-long.json +36 -0
  14. package/examples/private/demographic_data.csv +157 -0
  15. package/examples/private/demographic_data.json +2654 -0
  16. package/examples/private/demographic_dynamic.json +443 -0
  17. package/examples/private/demographic_standard.json +560 -0
  18. package/examples/private/test.json +448 -20047
  19. package/index.html +9 -6
  20. package/package.json +2 -2
  21. package/src/CdcChart.tsx +62 -82
  22. package/src/_stories/Chart.Anchors.stories.tsx +31 -0
  23. package/src/_stories/Chart.DynamicSeries.stories.tsx +8 -1
  24. package/src/_stories/Chart.stories.tsx +32 -0
  25. package/src/_stories/ChartAxisLabels.stories.tsx +4 -1
  26. package/{examples/feature/area/area-chart-date-city-temperature.json → src/_stories/_mock/area_chart_stacked.json} +125 -27
  27. package/src/_stories/_mock/line_chart_dynamic_ci.json +493 -0
  28. package/src/_stories/_mock/line_chart_non_dynamic_ci.json +522 -0
  29. package/src/_stories/_mock/short_dates.json +288 -0
  30. package/src/components/AreaChart/components/AreaChart.Stacked.jsx +15 -3
  31. package/src/components/Axis/Categorical.Axis.tsx +2 -2
  32. package/src/components/BarChart/components/BarChart.Horizontal.tsx +46 -37
  33. package/src/components/BarChart/components/BarChart.Vertical.tsx +28 -40
  34. package/src/components/BarChart/helpers/getBarData.ts +28 -0
  35. package/src/components/BarChart/helpers/tests/getBarData.test.ts +74 -0
  36. package/src/components/BoxPlot/BoxPlot.tsx +12 -70
  37. package/src/components/BoxPlot/helpers/index.ts +54 -0
  38. package/src/components/BrushChart.tsx +23 -26
  39. package/src/components/EditorPanel/EditorPanel.tsx +55 -79
  40. package/src/components/EditorPanel/components/Panels/Panel.Series.tsx +1 -0
  41. package/src/components/EditorPanel/useEditorPermissions.ts +5 -1
  42. package/src/components/Legend/Legend.Component.tsx +2 -2
  43. package/src/{hooks/useLegendClasses.ts → components/Legend/helpers/getLegendClasses.ts} +5 -5
  44. package/src/components/Legend/helpers/index.ts +2 -1
  45. package/src/components/Legend/tests/getLegendClasses.test.ts +115 -0
  46. package/src/components/LineChart/components/LineChart.Circle.tsx +1 -1
  47. package/src/components/LineChart/helpers.ts +1 -0
  48. package/src/components/LineChart/index.tsx +47 -1
  49. package/src/components/LinearChart.tsx +171 -172
  50. package/src/components/PieChart/PieChart.tsx +7 -1
  51. package/src/components/Sankey/components/ColumnList.tsx +19 -0
  52. package/src/components/Sankey/components/Sankey.tsx +479 -0
  53. package/src/components/Sankey/helpers/getSankeyTooltip.tsx +33 -0
  54. package/src/components/Sankey/index.tsx +1 -510
  55. package/src/components/Sankey/sankey.scss +16 -16
  56. package/src/components/Sankey/types/index.ts +1 -1
  57. package/src/data/initial-state.js +4 -3
  58. package/src/helpers/countNumOfTicks.ts +57 -0
  59. package/src/helpers/getQuartiles.ts +15 -18
  60. package/src/hooks/useMinMax.ts +18 -4
  61. package/src/hooks/useScales.ts +38 -4
  62. package/src/hooks/useTooltip.tsx +5 -1
  63. package/src/scss/DataTable.scss +5 -0
  64. package/src/scss/main.scss +6 -2
@@ -35,12 +35,14 @@ import { calcInitialHeight } from '../helpers/sizeHelpers'
35
35
  import useMinMax from '../hooks/useMinMax'
36
36
  import useReduceData from '../hooks/useReduceData'
37
37
  import useRightAxis from '../hooks/useRightAxis'
38
- import useScales, { getTickValues } from '../hooks/useScales'
38
+ import useScales, { getTickValues, filterAndShiftLinearDateTicks } from '../hooks/useScales'
39
39
  import useTopAxis from '../hooks/useTopAxis'
40
40
  import { useTooltip as useCoveTooltip } from '../hooks/useTooltip'
41
41
  import { useEditorPermissions } from './EditorPanel/useEditorPermissions'
42
42
  import Annotation from './Annotations'
43
43
  import { BlurStrokeText } from '@cdc/core/components/BlurStrokeText'
44
+ import { fontSizes } from '@cdc/core/helpers/cove/fontSettings'
45
+ import { countNumOfTicks } from '../helpers/countNumOfTicks'
44
46
 
45
47
  type LinearChartProps = {
46
48
  parentWidth: number
@@ -50,6 +52,7 @@ type LinearChartProps = {
50
52
  const BOTTOM_LABEL_PADDING = 9
51
53
  const X_TICK_LABEL_PADDING = 3
52
54
  const DEFAULT_TICK_LENGTH = 8
55
+ const MONTH_AS_MS = 1000 * 60 * 60 * 24 * 30
53
56
 
54
57
  const LinearChart = forwardRef<SVGAElement, LinearChartProps>(({ parentHeight, parentWidth }, svgRef) => {
55
58
  // prettier-ignore
@@ -100,6 +103,7 @@ const LinearChart = forwardRef<SVGAElement, LinearChartProps>(({ parentHeight, p
100
103
  const [animatedChart, setAnimatedChart] = useState(false)
101
104
  const [point, setPoint] = useState({ x: 0, y: 0 })
102
105
  const [suffixWidth, setSuffixWidth] = useState(0)
106
+ const [yAxisAutoPadding, setYAxisAutoPadding] = useState(0)
103
107
 
104
108
  // REFS
105
109
  const axisBottomRef = useRef(null)
@@ -109,6 +113,7 @@ const LinearChart = forwardRef<SVGAElement, LinearChartProps>(({ parentHeight, p
109
113
  const triggerRef = useRef()
110
114
  const xAxisLabelRefs = useRef([])
111
115
  const xAxisTitleRef = useRef(null)
116
+ const lastMaxValue = useRef(maxValue)
112
117
 
113
118
  const dataRef = useIntersectionObserver(triggerRef, {
114
119
  freezeOnceVisible: false
@@ -119,8 +124,10 @@ const LinearChart = forwardRef<SVGAElement, LinearChartProps>(({ parentHeight, p
119
124
  const isHorizontal = orientation === 'horizontal' || config.visualizationType === 'Forest Plot'
120
125
  const isLogarithmicAxis = config.yAxis.type === 'logarithmic'
121
126
  const isForestPlot = visualizationType === 'Forest Plot'
127
+ const isDateTime = config.xAxis.type === 'date-time'
122
128
  const suffixHasNoSpace = !suffix.includes(' ')
123
-
129
+ const labelsOverflow = onlyShowTopPrefixSuffix && !suffixHasNoSpace
130
+ const padding = orientation === 'horizontal' ? Number(config.xAxis.size) : Number(config.yAxis.size)
124
131
  const yLabelOffset = isNaN(parseInt(`${runtime.yAxis.labelOffset}`)) ? 0 : parseInt(`${runtime.yAxis.labelOffset}`)
125
132
 
126
133
  // zero if not forest plot
@@ -169,11 +176,8 @@ const LinearChart = forwardRef<SVGAElement, LinearChartProps>(({ parentHeight, p
169
176
  const xMax = width - runtime.yAxis.size - (visualizationType === 'Combo' ? config.yAxis.rightAxisSize : 0)
170
177
  const yMax = initialHeight + forestRowsHeight
171
178
 
172
- const checkLineToBarGraph = () => {
173
- return isConvertLineToBarGraph(config.visualizationType, data, config.allowLineToBarGraph)
174
- }
179
+ const isNoDataAvailable = config.filters && config.filters.values.length === 0 && data.length === 0
175
180
 
176
- // GETTERS & FUNCTIONS
177
181
  const getXAxisData = d =>
178
182
  isDateScale(config.runtime.xAxis)
179
183
  ? parseDate(d[config.runtime.originalXAxis.dataKey]).getTime()
@@ -187,7 +191,14 @@ const LinearChart = forwardRef<SVGAElement, LinearChartProps>(({ parentHeight, p
187
191
  const properties = {
188
192
  data,
189
193
  tableData,
190
- config,
194
+ config: {
195
+ ...config,
196
+ yAxis: {
197
+ ...config.yAxis,
198
+ scalePadding: labelsOverflow ? yAxisAutoPadding : config.yAxis.scalePadding,
199
+ enablePadding: labelsOverflow || config.yAxis.enablePadding
200
+ }
201
+ },
191
202
  minValue,
192
203
  maxValue,
193
204
  isAllLine,
@@ -208,6 +219,40 @@ const LinearChart = forwardRef<SVGAElement, LinearChartProps>(({ parentHeight, p
208
219
  xMax: parentWidth - Number(config.orientation === 'horizontal' ? config.xAxis.size : config.yAxis.size)
209
220
  })
210
221
 
222
+ const [yTickCount, xTickCount] = ['yAxis', 'xAxis'].map(axis =>
223
+ countNumOfTicks({ axis, max, runtime, currentViewport, isHorizontal, data, config, min })
224
+ )
225
+ const handleNumTicks = isForestPlot ? config.data.length : yTickCount
226
+
227
+ // Tooltip Helpers
228
+ const { tooltipData, showTooltip, hideTooltip, tooltipOpen, tooltipLeft, tooltipTop } = useTooltip()
229
+
230
+ // prettier-ignore
231
+ const {
232
+ handleTooltipMouseOver,
233
+ handleTooltipClick,
234
+ handleTooltipMouseOff,
235
+ TooltipListItem,
236
+ getXValueFromCoordinate
237
+ } = useCoveTooltip({
238
+ xScale,
239
+ yScale,
240
+ showTooltip,
241
+ hideTooltip
242
+ })
243
+ // get the number of months between the first and last date
244
+ const { dataKey } = runtime.xAxis
245
+ const dateSpanMonths =
246
+ data.length && isDateTime
247
+ ? [0, data.length - 1].map(i => parseDate(data[i][dataKey])).reduce((a, b) => Math.abs(a - b)) / MONTH_AS_MS
248
+ : 0
249
+ const useDateSpanMonths = isDateTime && dateSpanMonths > xTickCount
250
+
251
+ // GETTERS & FUNCTIONS
252
+ const checkLineToBarGraph = () => {
253
+ return isConvertLineToBarGraph(config.visualizationType, data, config.allowLineToBarGraph)
254
+ }
255
+
211
256
  const handleLeftTickFormatting = (tick, index, ticks) => {
212
257
  if (isLogarithmicAxis && tick === 0.1) {
213
258
  //when logarithmic scale applied change value of first tick
@@ -251,80 +296,33 @@ const LinearChart = forwardRef<SVGAElement, LinearChartProps>(({ parentHeight, p
251
296
  return tick
252
297
  }
253
298
 
254
- const countNumOfTicks = axis => {
255
- let { numTicks } = runtime[axis]
256
- if (runtime[axis].viewportNumTicks && runtime[axis].viewportNumTicks[currentViewport]) {
257
- numTicks = runtime[axis].viewportNumTicks[currentViewport]
258
- }
259
- let tickCount = undefined
260
-
261
- if (axis === 'yAxis') {
262
- tickCount =
263
- isHorizontal && !numTicks
264
- ? data.length
265
- : isHorizontal && numTicks
266
- ? numTicks
267
- : !isHorizontal && !numTicks
268
- ? undefined
269
- : !isHorizontal && numTicks && numTicks
270
- // to fix edge case of small numbers with decimals
271
- if (tickCount === undefined && !config.dataFormat.roundTo) {
272
- // then it is set to Auto
273
- if (Number(max) <= 3) {
274
- tickCount = 2
275
- } else {
276
- tickCount = 4 // same default as standalone components
277
- }
278
- }
279
- if (Number(tickCount) > Number(max) && !isHorizontal) {
280
- // cap it and round it so its an integer
281
- tickCount = Number(min) < 0 ? Math.round(max) * 2 : Math.round(max)
282
- }
283
- }
284
-
285
- if (axis === 'xAxis') {
286
- tickCount =
287
- isHorizontal && !numTicks
288
- ? undefined
289
- : isHorizontal && numTicks
290
- ? numTicks
291
- : !isHorizontal && !numTicks
292
- ? undefined
293
- : !isHorizontal && numTicks && numTicks
294
- if (isHorizontal && tickCount === undefined && !config.dataFormat.roundTo) {
295
- // then it is set to Auto
296
- // - check for small numbers situation
297
- if (max <= 3) {
298
- tickCount = 2
299
- } else {
300
- tickCount = 4 // same default as standalone components
301
- }
302
- }
299
+ const chartHasTooltipGuides = () => {
300
+ const { visualizationType } = config
301
+ if (visualizationType === 'Combo' && runtime.forecastingSeriesKeys > 0) return true
302
+ if (visualizationType === 'Area Chart') return true
303
+ if (visualizationType === 'Line') return true
304
+ if (visualizationType === 'Bar') return true
305
+ return false
306
+ }
303
307
 
304
- if (config.visualizationType === 'Forest Plot') {
305
- tickCount = config.yAxis.numTicks !== '' ? config.yAxis.numTicks : 4
306
- }
308
+ const getManualStep = () => {
309
+ let manualStep = config.xAxis.manualStep
310
+ if (config.xAxis.viewportStepCount && config.xAxis.viewportStepCount[currentViewport]) {
311
+ manualStep = config.xAxis.viewportStepCount[currentViewport]
307
312
  }
308
-
309
- return tickCount
313
+ return manualStep
310
314
  }
311
315
 
312
- // Tooltip Helpers
313
- const { tooltipData, showTooltip, hideTooltip, tooltipOpen, tooltipLeft, tooltipTop } = useTooltip()
316
+ const onMouseMove = event => {
317
+ const svgRect = event.currentTarget.getBoundingClientRect()
318
+ const x = event.clientX - svgRect.left
319
+ const y = event.clientY - svgRect.top
314
320
 
315
- // prettier-ignore
316
- const {
317
- handleTooltipMouseOver,
318
- handleTooltipClick,
319
- handleTooltipMouseOff,
320
- TooltipListItem,
321
- getXValueFromCoordinate
322
- } = useCoveTooltip({
323
- xScale,
324
- yScale,
325
- showTooltip,
326
- hideTooltip
327
- })
321
+ setPoint({
322
+ x,
323
+ y
324
+ })
325
+ }
328
326
 
329
327
  // EFFECTS
330
328
 
@@ -409,43 +407,27 @@ const LinearChart = forwardRef<SVGAElement, LinearChartProps>(({ parentHeight, p
409
407
  legendRef.current.style.transform = legendIsLeftOrRight ? `translateY(${topLabelOnGridlineHeight}px)` : 'none'
410
408
  }, [axisBottomRef.current, config, bottomLabelStart, brush, currentViewport, topYLabelRef.current, initialHeight])
411
409
 
412
- const chartHasTooltipGuides = () => {
413
- const { visualizationType } = config
414
- if (visualizationType === 'Combo' && runtime.forecastingSeriesKeys > 0) return true
415
- if (visualizationType === 'Area Chart') return true
416
- if (visualizationType === 'Line') return true
417
- if (visualizationType === 'Bar') return true
418
- return false
419
- }
410
+ useEffect(() => {
411
+ if (lastMaxValue.current === maxValue) return
412
+ lastMaxValue.current = maxValue
413
+ if (!yAxisAutoPadding) return
414
+ setYAxisAutoPadding(0)
415
+ }, [maxValue])
416
+ useEffect(() => {
417
+ if (orientation === 'horizontal') return
420
418
 
421
- const padding = orientation === 'horizontal' ? Number(config.xAxis.size) : Number(config.yAxis.size)
422
- const fontSize = { small: 16, medium: 18, large: 20 }
419
+ const maxValueIsGreaterThanTopGridLine = maxValue > Math.max(...yScale.ticks(handleNumTicks))
423
420
 
424
- const handleNumTicks = () => {
425
- // On forest plots we need to return every "study" or y axis value.
426
- if (config.visualizationType === 'Forest Plot') return config.data.length
427
- return countNumOfTicks('yAxis')
428
- }
421
+ if (!maxValueIsGreaterThanTopGridLine || !labelsOverflow) return
429
422
 
430
- const getManualStep = () => {
431
- let manualStep = config.xAxis.manualStep
432
- if (config.xAxis.viewportStepCount && config.xAxis.viewportStepCount[currentViewport]) {
433
- manualStep = config.xAxis.viewportStepCount[currentViewport]
434
- }
435
- return manualStep
436
- }
423
+ const tickGap = yScale.ticks(handleNumTicks)[1] - yScale.ticks(handleNumTicks)[0]
424
+ const nextTick = Math.max(...yScale.ticks(handleNumTicks)) + tickGap
425
+ const newPadding = minValue < 0 ? (nextTick - maxValue) / maxValue / 2 : (nextTick - maxValue) / maxValue
437
426
 
438
- const onMouseMove = event => {
439
- const svgRect = event.currentTarget.getBoundingClientRect()
440
- const x = event.clientX - svgRect.left
441
- const y = event.clientY - svgRect.top
442
-
443
- setPoint({
444
- x,
445
- y
446
- })
447
- }
427
+ setYAxisAutoPadding(newPadding * 100)
428
+ }, [maxValue, labelsOverflow, yScale, handleNumTicks])
448
429
 
430
+ // Render Functions
449
431
  const generatePairedBarAxis = () => {
450
432
  const axisMaxHeight = bottomLabelStart + BOTTOM_LABEL_PADDING
451
433
 
@@ -456,7 +438,7 @@ const LinearChart = forwardRef<SVGAElement, LinearChartProps>(({ parentHeight, p
456
438
  const numberOfTicks = filteredTicks?.length
457
439
  const xMaxHalf = xScale.range()[0] || xMax / 2
458
440
  const tickWidthAll = filteredTicks.map(tick =>
459
- getTextWidth(formatNumber(tick.value, 'left'), `normal ${fontSize[config.fontSize]}px sans-serif`)
441
+ getTextWidth(formatNumber(tick.value, 'left'), `normal ${fontSizes[config.fontSize]}px sans-serif`)
460
442
  )
461
443
  const accumulator = 100
462
444
  const sumOfTickWidth = tickWidthAll.reduce((a, b) => a + b, accumulator)
@@ -495,7 +477,7 @@ const LinearChart = forwardRef<SVGAElement, LinearChartProps>(({ parentHeight, p
495
477
  {props.ticks.map((tick, i) => {
496
478
  const textWidth = getTextWidth(
497
479
  formatNumber(tick.value, 'left'),
498
- `normal ${fontSize[config.fontSize]}px sans-serif`
480
+ `normal ${fontSizes[config.fontSize]}px sans-serif`
499
481
  )
500
482
  const isTicksOverlapping = getTickPositions(props.ticks, g1xScale)
501
483
  const maxTickRotation = Number(config.xAxis.maxTickRotation) || 90
@@ -547,7 +529,7 @@ const LinearChart = forwardRef<SVGAElement, LinearChartProps>(({ parentHeight, p
547
529
  {props.ticks.map((tick, i) => {
548
530
  const textWidth = getTextWidth(
549
531
  formatNumber(tick.value, 'left'),
550
- `normal ${fontSize[config.fontSize]}px sans-serif`
532
+ `normal ${fontSizes[config.fontSize]}px sans-serif`
551
533
  )
552
534
  const isTicksOverlapping = getTickPositions(props.ticks, g2xScale)
553
535
  const maxTickRotation = Number(config.xAxis.maxTickRotation) || 90
@@ -608,7 +590,7 @@ const LinearChart = forwardRef<SVGAElement, LinearChartProps>(({ parentHeight, p
608
590
  ref={svgRef}
609
591
  onMouseMove={onMouseMove}
610
592
  width={parentWidth}
611
- height={parentHeight}
593
+ height={isNoDataAvailable ? 1 : parentHeight}
612
594
  className={`linear ${config.animate ? 'animated' : ''} ${animatedChart && config.animate ? 'animate' : ''} ${
613
595
  debugSvg && 'debug'
614
596
  } ${isDraggingAnnotation && 'dragging-annotation'}`}
@@ -623,7 +605,7 @@ const LinearChart = forwardRef<SVGAElement, LinearChartProps>(({ parentHeight, p
623
605
  <AxisLeft
624
606
  scale={yScale}
625
607
  left={Number(runtime.yAxis.size) - config.yAxis.axisPadding}
626
- numTicks={handleNumTicks()}
608
+ numTicks={handleNumTicks}
627
609
  >
628
610
  {props => {
629
611
  const axisCenter =
@@ -785,20 +767,6 @@ const LinearChart = forwardRef<SVGAElement, LinearChartProps>(({ parentHeight, p
785
767
  isBrush={false}
786
768
  />
787
769
  )}
788
- {/* y anchors */}
789
- {config.yAxis.anchors &&
790
- config.yAxis.anchors.map(anchor => {
791
- return (
792
- <Line
793
- strokeDasharray={handleLineType(anchor.lineStyle)}
794
- stroke='rgba(0,0,0,1)'
795
- className='customAnchor'
796
- from={{ x: 0 + config.yAxis.size, y: yScale(anchor.value) }}
797
- to={{ x: xMax, y: yScale(anchor.value) }}
798
- display={runtime.horizontal ? 'none' : 'block'}
799
- />
800
- )
801
- })}
802
770
  {visualizationType === 'Forest Plot' && (
803
771
  <ForestPlot
804
772
  xScale={xScale}
@@ -821,16 +789,7 @@ const LinearChart = forwardRef<SVGAElement, LinearChartProps>(({ parentHeight, p
821
789
  />
822
790
  )}
823
791
  {/*Brush chart */}
824
- {config.brush.active && config.xAxis.type !== 'categorical' && (
825
- <BrushChart
826
- xScaleBrush={xScaleBrush}
827
- yScale={yScale}
828
- xMax={xMax}
829
- yMax={yMax}
830
- xScale={xScale}
831
- seriesScale={seriesScale}
832
- />
833
- )}
792
+ {config.brush.active && config.xAxis.type !== 'categorical' && <BrushChart xMax={xMax} yMax={yMax} />}
834
793
  {/* Line chart */}
835
794
  {/* TODO: Make this just line or combo? */}
836
795
  {!['Paired Bar', 'Box Plot', 'Area Chart', 'Scatter Plot', 'Deviation Bar', 'Forecasting', 'Bar'].includes(
@@ -852,14 +811,19 @@ const LinearChart = forwardRef<SVGAElement, LinearChartProps>(({ parentHeight, p
852
811
  {/* y anchors */}
853
812
  {config.yAxis.anchors &&
854
813
  config.yAxis.anchors.map((anchor, index) => {
855
- let anchorPosition = yScale(anchor.value)
856
- // have to move up
857
- // const padding = orientation === 'horizontal' ? Number(config.xAxis.size) : Number(config.yAxis.size)
814
+ let position = yScale(anchor.value)
815
+ let middleOffset = 0
816
+
858
817
  if (!anchor.value) return
859
- const middleOffset =
860
- orientation === 'horizontal' && visualizationType === 'Bar' ? config.barHeight / 4 : 0
818
+ if (config.yAxis.labelPlacement === 'Below Bar') {
819
+ const textOffset = -6.5
820
+ middleOffset = textOffset + Number(config.series.length * config.barHeight) / config.series.length
821
+ } else {
822
+ const paddingOffset = 8
823
+ middleOffset = paddingOffset
824
+ }
861
825
 
862
- if (!anchorPosition) return
826
+ if (!position) return
863
827
 
864
828
  return (
865
829
  // prettier-ignore
@@ -868,8 +832,8 @@ const LinearChart = forwardRef<SVGAElement, LinearChartProps>(({ parentHeight, p
868
832
  strokeDasharray={handleLineType(anchor.lineStyle)}
869
833
  stroke={anchor.color ? anchor.color : 'rgba(0,0,0,1)'}
870
834
  className='anchor-y'
871
- from={{ x: 0 + padding, y: anchorPosition - middleOffset}}
872
- to={{ x: width - config.yAxis.rightAxisSize, y: anchorPosition - middleOffset }}
835
+ from={{ x: 0 + padding, y: position - middleOffset}}
836
+ to={{ x: width - config.yAxis.rightAxisSize, y: position - middleOffset }}
873
837
  />
874
838
  )
875
839
  })}
@@ -881,10 +845,19 @@ const LinearChart = forwardRef<SVGAElement, LinearChartProps>(({ parentHeight, p
881
845
  newX = yAxis
882
846
  }
883
847
 
884
- let anchorPosition = isDateScale(newX) ? xScale(parseDate(anchor.value, false)) : xScale(anchor.value)
848
+ const getAnchorPosition = (): number | undefined => {
849
+ let position: number | undefined
885
850
 
886
- // have to move up
887
- // const padding = orientation === 'horizontal' ? Number(config.xAxis.size) : Number(config.yAxis.size)
851
+ position = isDateScale(newX) ? xScale(parseDate(anchor.value, false)) : xScale(anchor.value)
852
+ if (config.xAxis.type === 'categorical' || config.xAxis.type === 'date') {
853
+ position = position
854
+ ? position + (newX.type === 'categorical' || newX.type === 'date' ? xScale.bandwidth() : 0) / 2
855
+ : 0
856
+ }
857
+ return position
858
+ }
859
+
860
+ let anchorPosition = getAnchorPosition()
888
861
 
889
862
  if (!anchorPosition) return
890
863
 
@@ -946,7 +919,7 @@ const LinearChart = forwardRef<SVGAElement, LinearChartProps>(({ parentHeight, p
946
919
  />
947
920
  </Group>
948
921
  )}
949
- {config.filters && config.filters.values.length === 0 && data.length === 0 && (
922
+ {isNoDataAvailable && (
950
923
  <Text
951
924
  x={Number(config.yAxis.size) + Number(xMax / 2)}
952
925
  y={initialHeight / 2 - (config.xAxis.padding || 0) / 2}
@@ -1009,7 +982,7 @@ const LinearChart = forwardRef<SVGAElement, LinearChartProps>(({ parentHeight, p
1009
982
  label={runtime.yAxis.label || runtime.yAxis.label}
1010
983
  stroke='#333'
1011
984
  tickFormat={handleLeftTickFormatting}
1012
- numTicks={handleNumTicks()}
985
+ numTicks={handleNumTicks}
1013
986
  >
1014
987
  {props => {
1015
988
  const axisCenter =
@@ -1352,43 +1325,63 @@ const LinearChart = forwardRef<SVGAElement, LinearChartProps>(({ parentHeight, p
1352
1325
  tickFormat={handleBottomTickFormatting}
1353
1326
  scale={xScale}
1354
1327
  stroke='#333'
1355
- numTicks={countNumOfTicks('xAxis')}
1328
+ numTicks={useDateSpanMonths ? dateSpanMonths : xTickCount}
1356
1329
  tickStroke='#333'
1357
1330
  tickValues={
1358
1331
  config.xAxis.manual
1359
- ? getTickValues(
1360
- xAxisDataMapped,
1361
- xScale,
1362
- config.xAxis.type === 'date-time' ? countNumOfTicks('xAxis') : getManualStep(),
1363
- config
1364
- )
1332
+ ? getTickValues(xAxisDataMapped, xScale, isDateTime ? xTickCount : getManualStep(), config)
1333
+ : config.xAxis.type === 'date'
1334
+ ? xAxisDataMapped
1365
1335
  : undefined
1366
1336
  }
1367
1337
  >
1368
1338
  {props => {
1339
+ // For these charts, we generated all ticks in tickValues above, and now need to filter/shift them
1340
+ // so the last tick is always labeled
1341
+ if (config.xAxis.type === 'date' && !config.xAxis.manual) {
1342
+ props.ticks = filterAndShiftLinearDateTicks(config, props, xAxisDataMapped, formatDate)
1343
+ }
1344
+
1345
+ const distanceBetweenTicks =
1346
+ useDateSpanMonths &&
1347
+ xScale
1348
+ .ticks(xTickCount)
1349
+ .map(t => props.ticks.findIndex(tick => tick.value.getTime() === t.getTime()))
1350
+ .slice(0, 2)
1351
+ .reduce((acc, curr) => curr - acc)
1352
+
1353
+ // filter out every [distanceBetweenTicks] tick starting from the end, so the last tick is always labeled
1354
+ const filteredTicks = useDateSpanMonths
1355
+ ? [...props.ticks]
1356
+ .reverse()
1357
+ .filter((_, i) => i % distanceBetweenTicks === 0)
1358
+ .reverse()
1359
+ .map((tick, i, arr) => ({
1360
+ ...tick,
1361
+ // reformat in case showYearsOnce, since first month of year may have changed
1362
+ formattedValue: handleBottomTickFormatting(tick.value, i, arr)
1363
+ }))
1364
+ : props.ticks
1365
+
1369
1366
  const axisMaxHeight = bottomLabelStart + BOTTOM_LABEL_PADDING
1370
1367
 
1371
- const axisCenter =
1372
- config.visualizationType !== 'Forest Plot'
1373
- ? (props.axisToPoint.x - props.axisFromPoint.x) / 2
1374
- : dimensions[0] / 2
1375
1368
  const containsMultipleWords = inputString => /\s/.test(inputString)
1376
- const ismultiLabel = props.ticks.some(tick => containsMultipleWords(tick.value))
1369
+ const ismultiLabel = filteredTicks.some(tick => containsMultipleWords(tick.value))
1377
1370
 
1378
1371
  // Calculate sumOfTickWidth here, before map function
1379
1372
  const tickWidthMax = Math.max(
1380
- ...props.ticks.map(tick =>
1381
- getTextWidth(tick.formattedValue, `normal ${fontSize[config.fontSize]}px sans-serif`)
1373
+ ...filteredTicks.map(tick =>
1374
+ getTextWidth(tick.formattedValue, `normal ${fontSizes[config.fontSize]}px sans-serif`)
1382
1375
  )
1383
1376
  )
1384
1377
  // const marginTop = 20 // moved to top bc need for yMax calcs
1385
1378
  const accumulator = ismultiLabel ? 180 : 100
1386
1379
 
1387
- const textWidths = props.ticks.map(tick =>
1388
- getTextWidth(tick.formattedValue, `normal ${fontSize[config.fontSize]}px sans-serif`)
1380
+ const textWidths = filteredTicks.map(tick =>
1381
+ getTextWidth(tick.formattedValue, `normal ${fontSizes[config.fontSize]}px sans-serif`)
1389
1382
  )
1390
1383
  const sumOfTickWidth = textWidths.reduce((a, b) => a + b, accumulator)
1391
- const spaceBetweenEachTick = (xMax - sumOfTickWidth) / (props.ticks.length - 1)
1384
+ const spaceBetweenEachTick = (xMax - sumOfTickWidth) / (filteredTicks.length - 1)
1392
1385
 
1393
1386
  // Check if ticks are overlapping
1394
1387
  // Determine the position of each tick
@@ -1416,6 +1409,16 @@ const LinearChart = forwardRef<SVGAElement, LinearChartProps>(({ parentHeight, p
1416
1409
  areTicksTouching = true
1417
1410
  }
1418
1411
 
1412
+ // force wrap it last tick is close to the end of the axis
1413
+ const lastTickWidth = textWidths[textWidths.length - 1]
1414
+ const lastTickPosition = positions[positions.length - 1] + lastTickWidth
1415
+ const lastTickEnd = lastTickPosition + lastTickWidth / 2
1416
+ const lastTickEndThreshold = xMax - lastTickWidth
1417
+
1418
+ if (lastTickEnd > lastTickEndThreshold) {
1419
+ areTicksTouching = true
1420
+ }
1421
+
1419
1422
  const dynamicMarginTop =
1420
1423
  areTicksTouching && config.isResponsiveTicks ? tickWidthMax + DEFAULT_TICK_LENGTH + 20 : 0
1421
1424
 
@@ -1424,15 +1427,11 @@ const LinearChart = forwardRef<SVGAElement, LinearChartProps>(({ parentHeight, p
1424
1427
 
1425
1428
  return (
1426
1429
  <Group className='bottom-axis' width={dimensions[0]}>
1427
- {props.ticks.map((tick, i, propsTicks) => {
1430
+ {filteredTicks.map((tick, i, propsTicks) => {
1428
1431
  // when using LogScale show major ticks values only
1429
1432
  const showTick = String(tick.value).startsWith('1') || tick.value === 0.1 ? 'block' : 'none'
1430
1433
  const tickLength = showTick === 'block' ? 16 : DEFAULT_TICK_LENGTH
1431
1434
  const to = { x: tick.to.x, y: tickLength }
1432
- const textWidth = getTextWidth(
1433
- tick.formattedValue,
1434
- `normal ${fontSize[config.fontSize]}px sans-serif`
1435
- )
1436
1435
  const limitedWidth = 100 / propsTicks.length
1437
1436
  //reset rotations by updating config
1438
1437
  config.yAxis.tickRotation =
@@ -170,6 +170,7 @@ const PieChart = props => {
170
170
  )
171
171
  })}
172
172
  {transitions.map(({ item: arc, key }, i) => {
173
+ const roundTo = Number(config.dataFormat.roundTo) || 0
173
174
  const [centroidX, centroidY] = path.centroid(arc)
174
175
  const hasSpaceForLabel = arc.endAngle - arc.startAngle >= 0.1
175
176
 
@@ -177,6 +178,11 @@ const PieChart = props => {
177
178
  if (_colorScale(arc.data[config.runtime.xAxis.dataKey])) {
178
179
  textColor = getContrastColor(textColor, _colorScale(arc.data[config.runtime.xAxis.dataKey]))
179
180
  }
181
+ const degrees = ((arc.endAngle - arc.startAngle) * 180) / Math.PI
182
+
183
+ // Calculate the percentage of the full circle (360 degrees)
184
+ const percentageOfCircle = (degrees / 360) * 100
185
+ const roundedPercentage = percentageOfCircle.toFixed(roundTo)
180
186
 
181
187
  return (
182
188
  <animated.g key={`${key}${i}`}>
@@ -189,7 +195,7 @@ const PieChart = props => {
189
195
  textAnchor='middle'
190
196
  pointerEvents='none'
191
197
  >
192
- {Math.round((((arc.endAngle - arc.startAngle) * 180) / Math.PI / 360) * 100) + '%'}
198
+ {roundedPercentage + '%'}
193
199
  </Text>
194
200
  )}
195
201
  </animated.g>
@@ -0,0 +1,19 @@
1
+ interface ColumnData {
2
+ label: string
3
+ value: string
4
+ additional_info: string
5
+ }
6
+
7
+ const ColumnList = ({ columnData }) => {
8
+ return (
9
+ <ul>
10
+ {columnData?.map((entry, index) => (
11
+ <li key={index}>
12
+ {entry.label}: {entry.value} ({entry.additional_info}%)
13
+ </li>
14
+ ))}
15
+ </ul>
16
+ )
17
+ }
18
+
19
+ export default ColumnList