@cdc/chart 4.24.11 → 4.24.12-2

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 (65) hide show
  1. package/dist/cdcchart.js +32134 -32039
  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/ehdi.json +29939 -0
  19. package/examples/private/test.json +448 -20047
  20. package/index.html +9 -6
  21. package/package.json +2 -2
  22. package/src/CdcChart.tsx +62 -82
  23. package/src/_stories/Chart.Anchors.stories.tsx +31 -0
  24. package/src/_stories/Chart.DynamicSeries.stories.tsx +8 -1
  25. package/src/_stories/Chart.stories.tsx +32 -0
  26. package/src/_stories/ChartAxisLabels.stories.tsx +4 -1
  27. package/{examples/feature/area/area-chart-date-city-temperature.json → src/_stories/_mock/area_chart_stacked.json} +125 -27
  28. package/src/_stories/_mock/line_chart_dynamic_ci.json +493 -0
  29. package/src/_stories/_mock/line_chart_non_dynamic_ci.json +522 -0
  30. package/src/_stories/_mock/short_dates.json +288 -0
  31. package/src/components/AreaChart/components/AreaChart.Stacked.jsx +15 -3
  32. package/src/components/Axis/Categorical.Axis.tsx +2 -2
  33. package/src/components/BarChart/components/BarChart.Horizontal.tsx +46 -37
  34. package/src/components/BarChart/components/BarChart.Vertical.tsx +28 -40
  35. package/src/components/BarChart/helpers/getBarData.ts +28 -0
  36. package/src/components/BarChart/helpers/tests/getBarData.test.ts +74 -0
  37. package/src/components/BoxPlot/BoxPlot.tsx +12 -70
  38. package/src/components/BoxPlot/helpers/index.ts +54 -0
  39. package/src/components/BrushChart.tsx +23 -26
  40. package/src/components/EditorPanel/EditorPanel.tsx +55 -79
  41. package/src/components/EditorPanel/components/Panels/Panel.Series.tsx +1 -0
  42. package/src/components/EditorPanel/useEditorPermissions.ts +5 -1
  43. package/src/components/Legend/Legend.Component.tsx +2 -2
  44. package/src/{hooks/useLegendClasses.ts → components/Legend/helpers/getLegendClasses.ts} +5 -5
  45. package/src/components/Legend/helpers/index.ts +2 -1
  46. package/src/components/Legend/tests/getLegendClasses.test.ts +115 -0
  47. package/src/components/LineChart/components/LineChart.Circle.tsx +1 -1
  48. package/src/components/LineChart/helpers.ts +1 -0
  49. package/src/components/LineChart/index.tsx +47 -1
  50. package/src/components/LinearChart.tsx +180 -172
  51. package/src/components/PieChart/PieChart.tsx +7 -1
  52. package/src/components/Sankey/components/ColumnList.tsx +19 -0
  53. package/src/components/Sankey/components/Sankey.tsx +479 -0
  54. package/src/components/Sankey/helpers/getSankeyTooltip.tsx +33 -0
  55. package/src/components/Sankey/index.tsx +1 -510
  56. package/src/components/Sankey/sankey.scss +16 -16
  57. package/src/components/Sankey/types/index.ts +1 -1
  58. package/src/data/initial-state.js +4 -3
  59. package/src/helpers/countNumOfTicks.ts +57 -0
  60. package/src/helpers/getQuartiles.ts +15 -18
  61. package/src/hooks/useMinMax.ts +18 -4
  62. package/src/hooks/useScales.ts +38 -4
  63. package/src/hooks/useTooltip.tsx +5 -1
  64. package/src/scss/DataTable.scss +5 -0
  65. 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,54 +407,47 @@ 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
420
413
 
421
- const padding = orientation === 'horizontal' ? Number(config.xAxis.size) : Number(config.yAxis.size)
422
- const fontSize = { small: 16, medium: 18, large: 20 }
414
+ if (!yAxisAutoPadding) return
415
+ setYAxisAutoPadding(0)
416
+ }, [maxValue])
417
+ useEffect(() => {
418
+ if (orientation === 'horizontal') return
423
419
 
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
- }
420
+ const maxValueIsGreaterThanTopGridLine = maxValue > Math.max(...yScale.ticks(handleNumTicks))
429
421
 
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
- }
422
+ if (!maxValueIsGreaterThanTopGridLine || !labelsOverflow) return
423
+ const ticks = yScale.ticks(handleNumTicks)
424
+ const tickGap = ticks.length === 1 ? ticks[0] : ticks[1] - ticks[0]
425
+ const nextTick = Math.max(...yScale.ticks(handleNumTicks)) + tickGap
426
+ const divideBy = minValue < 0 ? maxValue / 2 : maxValue
427
+ const calculatedPadding = (nextTick - maxValue) / divideBy
437
428
 
438
- const onMouseMove = event => {
439
- const svgRect = event.currentTarget.getBoundingClientRect()
440
- const x = event.clientX - svgRect.left
441
- const y = event.clientY - svgRect.top
429
+ // if auto padding is too close to next tick, add one more ticks worth of padding
430
+ const PADDING_THRESHOLD = 0.025
431
+ const newPadding =
432
+ calculatedPadding > PADDING_THRESHOLD ? calculatedPadding : calculatedPadding + tickGap / divideBy
442
433
 
443
- setPoint({
444
- x,
445
- y
446
- })
447
- }
434
+ /* sometimes even though the padding is getting to the next tick exactly,
435
+ d3 still doesn't show the tick. we add 0.1 to ensure to tip it over the edge */
436
+ setYAxisAutoPadding(newPadding * 100 + 0.1)
437
+ }, [maxValue, labelsOverflow, yScale, handleNumTicks])
448
438
 
439
+ // Render Functions
449
440
  const generatePairedBarAxis = () => {
450
441
  const axisMaxHeight = bottomLabelStart + BOTTOM_LABEL_PADDING
451
442
 
452
443
  const getTickPositions = (ticks, xScale) => {
453
444
  if (!ticks.length) return false
454
- // filterout first index
445
+ // filter out first index
455
446
  const filteredTicks = ticks.filter(tick => tick.index !== 0)
456
447
  const numberOfTicks = filteredTicks?.length
457
448
  const xMaxHalf = xScale.range()[0] || xMax / 2
458
449
  const tickWidthAll = filteredTicks.map(tick =>
459
- getTextWidth(formatNumber(tick.value, 'left'), `normal ${fontSize[config.fontSize]}px sans-serif`)
450
+ getTextWidth(formatNumber(tick.value, 'left'), `normal ${fontSizes[config.fontSize]}px sans-serif`)
460
451
  )
461
452
  const accumulator = 100
462
453
  const sumOfTickWidth = tickWidthAll.reduce((a, b) => a + b, accumulator)
@@ -495,7 +486,7 @@ const LinearChart = forwardRef<SVGAElement, LinearChartProps>(({ parentHeight, p
495
486
  {props.ticks.map((tick, i) => {
496
487
  const textWidth = getTextWidth(
497
488
  formatNumber(tick.value, 'left'),
498
- `normal ${fontSize[config.fontSize]}px sans-serif`
489
+ `normal ${fontSizes[config.fontSize]}px sans-serif`
499
490
  )
500
491
  const isTicksOverlapping = getTickPositions(props.ticks, g1xScale)
501
492
  const maxTickRotation = Number(config.xAxis.maxTickRotation) || 90
@@ -547,7 +538,7 @@ const LinearChart = forwardRef<SVGAElement, LinearChartProps>(({ parentHeight, p
547
538
  {props.ticks.map((tick, i) => {
548
539
  const textWidth = getTextWidth(
549
540
  formatNumber(tick.value, 'left'),
550
- `normal ${fontSize[config.fontSize]}px sans-serif`
541
+ `normal ${fontSizes[config.fontSize]}px sans-serif`
551
542
  )
552
543
  const isTicksOverlapping = getTickPositions(props.ticks, g2xScale)
553
544
  const maxTickRotation = Number(config.xAxis.maxTickRotation) || 90
@@ -608,7 +599,7 @@ const LinearChart = forwardRef<SVGAElement, LinearChartProps>(({ parentHeight, p
608
599
  ref={svgRef}
609
600
  onMouseMove={onMouseMove}
610
601
  width={parentWidth}
611
- height={parentHeight}
602
+ height={isNoDataAvailable ? 1 : parentHeight}
612
603
  className={`linear ${config.animate ? 'animated' : ''} ${animatedChart && config.animate ? 'animate' : ''} ${
613
604
  debugSvg && 'debug'
614
605
  } ${isDraggingAnnotation && 'dragging-annotation'}`}
@@ -623,7 +614,7 @@ const LinearChart = forwardRef<SVGAElement, LinearChartProps>(({ parentHeight, p
623
614
  <AxisLeft
624
615
  scale={yScale}
625
616
  left={Number(runtime.yAxis.size) - config.yAxis.axisPadding}
626
- numTicks={handleNumTicks()}
617
+ numTicks={handleNumTicks}
627
618
  >
628
619
  {props => {
629
620
  const axisCenter =
@@ -785,20 +776,6 @@ const LinearChart = forwardRef<SVGAElement, LinearChartProps>(({ parentHeight, p
785
776
  isBrush={false}
786
777
  />
787
778
  )}
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
779
  {visualizationType === 'Forest Plot' && (
803
780
  <ForestPlot
804
781
  xScale={xScale}
@@ -821,16 +798,7 @@ const LinearChart = forwardRef<SVGAElement, LinearChartProps>(({ parentHeight, p
821
798
  />
822
799
  )}
823
800
  {/*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
- )}
801
+ {config.brush.active && config.xAxis.type !== 'categorical' && <BrushChart xMax={xMax} yMax={yMax} />}
834
802
  {/* Line chart */}
835
803
  {/* TODO: Make this just line or combo? */}
836
804
  {!['Paired Bar', 'Box Plot', 'Area Chart', 'Scatter Plot', 'Deviation Bar', 'Forecasting', 'Bar'].includes(
@@ -852,14 +820,19 @@ const LinearChart = forwardRef<SVGAElement, LinearChartProps>(({ parentHeight, p
852
820
  {/* y anchors */}
853
821
  {config.yAxis.anchors &&
854
822
  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)
823
+ let position = yScale(anchor.value)
824
+ let middleOffset = 0
825
+
858
826
  if (!anchor.value) return
859
- const middleOffset =
860
- orientation === 'horizontal' && visualizationType === 'Bar' ? config.barHeight / 4 : 0
827
+ if (config.yAxis.labelPlacement === 'Below Bar') {
828
+ const textOffset = -6.5
829
+ middleOffset = textOffset + Number(config.series.length * config.barHeight) / config.series.length
830
+ } else {
831
+ const paddingOffset = 8
832
+ middleOffset = paddingOffset
833
+ }
861
834
 
862
- if (!anchorPosition) return
835
+ if (!position) return
863
836
 
864
837
  return (
865
838
  // prettier-ignore
@@ -868,8 +841,8 @@ const LinearChart = forwardRef<SVGAElement, LinearChartProps>(({ parentHeight, p
868
841
  strokeDasharray={handleLineType(anchor.lineStyle)}
869
842
  stroke={anchor.color ? anchor.color : 'rgba(0,0,0,1)'}
870
843
  className='anchor-y'
871
- from={{ x: 0 + padding, y: anchorPosition - middleOffset}}
872
- to={{ x: width - config.yAxis.rightAxisSize, y: anchorPosition - middleOffset }}
844
+ from={{ x: 0 + padding, y: position - middleOffset}}
845
+ to={{ x: width - config.yAxis.rightAxisSize, y: position - middleOffset }}
873
846
  />
874
847
  )
875
848
  })}
@@ -881,10 +854,19 @@ const LinearChart = forwardRef<SVGAElement, LinearChartProps>(({ parentHeight, p
881
854
  newX = yAxis
882
855
  }
883
856
 
884
- let anchorPosition = isDateScale(newX) ? xScale(parseDate(anchor.value, false)) : xScale(anchor.value)
857
+ const getAnchorPosition = (): number | undefined => {
858
+ let position: number | undefined
885
859
 
886
- // have to move up
887
- // const padding = orientation === 'horizontal' ? Number(config.xAxis.size) : Number(config.yAxis.size)
860
+ position = isDateScale(newX) ? xScale(parseDate(anchor.value, false)) : xScale(anchor.value)
861
+ if (config.xAxis.type === 'categorical' || config.xAxis.type === 'date') {
862
+ position = position
863
+ ? position + (newX.type === 'categorical' || newX.type === 'date' ? xScale.bandwidth() : 0) / 2
864
+ : 0
865
+ }
866
+ return position
867
+ }
868
+
869
+ let anchorPosition = getAnchorPosition()
888
870
 
889
871
  if (!anchorPosition) return
890
872
 
@@ -946,7 +928,7 @@ const LinearChart = forwardRef<SVGAElement, LinearChartProps>(({ parentHeight, p
946
928
  />
947
929
  </Group>
948
930
  )}
949
- {config.filters && config.filters.values.length === 0 && data.length === 0 && (
931
+ {isNoDataAvailable && (
950
932
  <Text
951
933
  x={Number(config.yAxis.size) + Number(xMax / 2)}
952
934
  y={initialHeight / 2 - (config.xAxis.padding || 0) / 2}
@@ -1009,7 +991,7 @@ const LinearChart = forwardRef<SVGAElement, LinearChartProps>(({ parentHeight, p
1009
991
  label={runtime.yAxis.label || runtime.yAxis.label}
1010
992
  stroke='#333'
1011
993
  tickFormat={handleLeftTickFormatting}
1012
- numTicks={handleNumTicks()}
994
+ numTicks={handleNumTicks}
1013
995
  >
1014
996
  {props => {
1015
997
  const axisCenter =
@@ -1352,43 +1334,63 @@ const LinearChart = forwardRef<SVGAElement, LinearChartProps>(({ parentHeight, p
1352
1334
  tickFormat={handleBottomTickFormatting}
1353
1335
  scale={xScale}
1354
1336
  stroke='#333'
1355
- numTicks={countNumOfTicks('xAxis')}
1337
+ numTicks={useDateSpanMonths ? dateSpanMonths : xTickCount}
1356
1338
  tickStroke='#333'
1357
1339
  tickValues={
1358
1340
  config.xAxis.manual
1359
- ? getTickValues(
1360
- xAxisDataMapped,
1361
- xScale,
1362
- config.xAxis.type === 'date-time' ? countNumOfTicks('xAxis') : getManualStep(),
1363
- config
1364
- )
1341
+ ? getTickValues(xAxisDataMapped, xScale, isDateTime ? xTickCount : getManualStep(), config)
1342
+ : config.xAxis.type === 'date'
1343
+ ? xAxisDataMapped
1365
1344
  : undefined
1366
1345
  }
1367
1346
  >
1368
1347
  {props => {
1348
+ // For these charts, we generated all ticks in tickValues above, and now need to filter/shift them
1349
+ // so the last tick is always labeled
1350
+ if (config.xAxis.type === 'date' && !config.xAxis.manual) {
1351
+ props.ticks = filterAndShiftLinearDateTicks(config, props, xAxisDataMapped, formatDate)
1352
+ }
1353
+
1354
+ const distanceBetweenTicks =
1355
+ useDateSpanMonths &&
1356
+ xScale
1357
+ .ticks(xTickCount)
1358
+ .map(t => props.ticks.findIndex(tick => tick.value.getTime() === t.getTime()))
1359
+ .slice(0, 2)
1360
+ .reduce((acc, curr) => curr - acc)
1361
+
1362
+ // filter out every [distanceBetweenTicks] tick starting from the end, so the last tick is always labeled
1363
+ const filteredTicks = useDateSpanMonths
1364
+ ? [...props.ticks]
1365
+ .reverse()
1366
+ .filter((_, i) => i % distanceBetweenTicks === 0)
1367
+ .reverse()
1368
+ .map((tick, i, arr) => ({
1369
+ ...tick,
1370
+ // reformat in case showYearsOnce, since first month of year may have changed
1371
+ formattedValue: handleBottomTickFormatting(tick.value, i, arr)
1372
+ }))
1373
+ : props.ticks
1374
+
1369
1375
  const axisMaxHeight = bottomLabelStart + BOTTOM_LABEL_PADDING
1370
1376
 
1371
- const axisCenter =
1372
- config.visualizationType !== 'Forest Plot'
1373
- ? (props.axisToPoint.x - props.axisFromPoint.x) / 2
1374
- : dimensions[0] / 2
1375
1377
  const containsMultipleWords = inputString => /\s/.test(inputString)
1376
- const ismultiLabel = props.ticks.some(tick => containsMultipleWords(tick.value))
1378
+ const ismultiLabel = filteredTicks.some(tick => containsMultipleWords(tick.value))
1377
1379
 
1378
1380
  // Calculate sumOfTickWidth here, before map function
1379
1381
  const tickWidthMax = Math.max(
1380
- ...props.ticks.map(tick =>
1381
- getTextWidth(tick.formattedValue, `normal ${fontSize[config.fontSize]}px sans-serif`)
1382
+ ...filteredTicks.map(tick =>
1383
+ getTextWidth(tick.formattedValue, `normal ${fontSizes[config.fontSize]}px sans-serif`)
1382
1384
  )
1383
1385
  )
1384
1386
  // const marginTop = 20 // moved to top bc need for yMax calcs
1385
1387
  const accumulator = ismultiLabel ? 180 : 100
1386
1388
 
1387
- const textWidths = props.ticks.map(tick =>
1388
- getTextWidth(tick.formattedValue, `normal ${fontSize[config.fontSize]}px sans-serif`)
1389
+ const textWidths = filteredTicks.map(tick =>
1390
+ getTextWidth(tick.formattedValue, `normal ${fontSizes[config.fontSize]}px sans-serif`)
1389
1391
  )
1390
1392
  const sumOfTickWidth = textWidths.reduce((a, b) => a + b, accumulator)
1391
- const spaceBetweenEachTick = (xMax - sumOfTickWidth) / (props.ticks.length - 1)
1393
+ const spaceBetweenEachTick = (xMax - sumOfTickWidth) / (filteredTicks.length - 1)
1392
1394
 
1393
1395
  // Check if ticks are overlapping
1394
1396
  // Determine the position of each tick
@@ -1416,6 +1418,16 @@ const LinearChart = forwardRef<SVGAElement, LinearChartProps>(({ parentHeight, p
1416
1418
  areTicksTouching = true
1417
1419
  }
1418
1420
 
1421
+ // force wrap it last tick is close to the end of the axis
1422
+ const lastTickWidth = textWidths[textWidths.length - 1]
1423
+ const lastTickPosition = positions[positions.length - 1] + lastTickWidth
1424
+ const lastTickEnd = lastTickPosition + lastTickWidth / 2
1425
+ const lastTickEndThreshold = xMax - lastTickWidth
1426
+
1427
+ if (lastTickEnd > lastTickEndThreshold) {
1428
+ areTicksTouching = true
1429
+ }
1430
+
1419
1431
  const dynamicMarginTop =
1420
1432
  areTicksTouching && config.isResponsiveTicks ? tickWidthMax + DEFAULT_TICK_LENGTH + 20 : 0
1421
1433
 
@@ -1424,15 +1436,11 @@ const LinearChart = forwardRef<SVGAElement, LinearChartProps>(({ parentHeight, p
1424
1436
 
1425
1437
  return (
1426
1438
  <Group className='bottom-axis' width={dimensions[0]}>
1427
- {props.ticks.map((tick, i, propsTicks) => {
1439
+ {filteredTicks.map((tick, i, propsTicks) => {
1428
1440
  // when using LogScale show major ticks values only
1429
1441
  const showTick = String(tick.value).startsWith('1') || tick.value === 0.1 ? 'block' : 'none'
1430
1442
  const tickLength = showTick === 'block' ? 16 : DEFAULT_TICK_LENGTH
1431
1443
  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
1444
  const limitedWidth = 100 / propsTicks.length
1437
1445
  //reset rotations by updating config
1438
1446
  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