@cdc/chart 4.24.7 → 4.24.9

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 (51) hide show
  1. package/dist/cdcchart.js +40313 -37543
  2. package/examples/cases-year.json +13379 -0
  3. package/examples/gallery/bar-chart-vertical/combo-line-chart.json +76 -15
  4. package/examples/gallery/bar-chart-vertical/vertical-bar-chart-stacked.json +5 -5
  5. package/index.html +17 -8
  6. package/package.json +2 -2
  7. package/src/CdcChart.tsx +383 -133
  8. package/src/_stories/Chart.Legend.Gradient.tsx +19 -0
  9. package/src/_stories/_mock/legend.gradient_mock.json +236 -0
  10. package/src/components/Annotations/components/AnnotationDraggable.tsx +64 -11
  11. package/src/components/Axis/Categorical.Axis.tsx +145 -0
  12. package/src/components/BarChart/components/BarChart.Horizontal.tsx +4 -3
  13. package/src/components/BarChart/components/BarChart.StackedHorizontal.tsx +1 -1
  14. package/src/components/BarChart/components/BarChart.StackedVertical.tsx +2 -5
  15. package/src/components/BarChart/components/BarChart.Vertical.tsx +17 -8
  16. package/src/components/BarChart/helpers/index.ts +5 -16
  17. package/src/components/BrushChart.tsx +205 -0
  18. package/src/components/EditorPanel/EditorPanel.tsx +1766 -509
  19. package/src/components/EditorPanel/components/Panels/Panel.Annotate.tsx +19 -5
  20. package/src/components/EditorPanel/components/Panels/Panel.General.tsx +190 -37
  21. package/src/components/EditorPanel/components/Panels/Panel.Sankey.tsx +43 -7
  22. package/src/components/EditorPanel/components/Panels/Panel.Series.tsx +4 -4
  23. package/src/components/EditorPanel/components/Panels/Panel.Visual.tsx +1 -11
  24. package/src/components/EditorPanel/editor-panel.scss +16 -3
  25. package/src/components/EditorPanel/{useEditorPermissions.js → useEditorPermissions.ts} +90 -19
  26. package/src/components/Legend/Legend.Component.tsx +185 -193
  27. package/src/components/Legend/Legend.Suppression.tsx +146 -0
  28. package/src/components/Legend/Legend.tsx +21 -5
  29. package/src/components/Legend/helpers/index.ts +33 -3
  30. package/src/components/LegendWrapper.tsx +26 -0
  31. package/src/components/LineChart/LineChartProps.ts +1 -18
  32. package/src/components/LineChart/components/LineChart.BumpCircle.tsx +103 -0
  33. package/src/components/LineChart/components/LineChart.Circle.tsx +47 -8
  34. package/src/components/LineChart/helpers.ts +55 -11
  35. package/src/components/LineChart/index.tsx +113 -38
  36. package/src/components/LinearChart.tsx +1366 -0
  37. package/src/components/PieChart/PieChart.tsx +74 -17
  38. package/src/components/Sankey/index.tsx +22 -16
  39. package/src/components/Sparkline/components/SparkLine.tsx +2 -2
  40. package/src/data/initial-state.js +13 -3
  41. package/src/hooks/useLegendClasses.ts +52 -15
  42. package/src/hooks/useMinMax.ts +4 -4
  43. package/src/hooks/useScales.ts +34 -24
  44. package/src/hooks/useTooltip.tsx +85 -22
  45. package/src/scss/DataTable.scss +2 -1
  46. package/src/scss/main.scss +107 -14
  47. package/src/types/ChartConfig.ts +34 -8
  48. package/src/types/ChartContext.ts +5 -4
  49. package/examples/feature/line/line-chart.json +0 -449
  50. package/src/components/BrushHandle.jsx +0 -17
  51. package/src/components/LineChart/index.scss +0 -1
@@ -0,0 +1,1366 @@
1
+ import React, { useContext, useEffect, useRef, useState } from 'react'
2
+
3
+ // Libraries
4
+ import { AxisLeft, AxisBottom, AxisRight, AxisTop } from '@visx/axis'
5
+ import { Group } from '@visx/group'
6
+ import { Line, Bar } from '@visx/shape'
7
+ import { Text } from '@visx/text'
8
+ import { Tooltip as ReactTooltip } from 'react-tooltip'
9
+ import { useTooltip, TooltipWithBounds } from '@visx/tooltip'
10
+ import { isDateScale } from '@cdc/core/helpers/cove/date'
11
+ import BrushChart from './BrushChart'
12
+ // CDC Components
13
+ import { AreaChart, AreaChartStacked } from './AreaChart'
14
+ import BarChart from './BarChart'
15
+ import ConfigContext from '../ConfigContext'
16
+ import BoxPlot from './BoxPlot'
17
+ import ScatterPlot from './ScatterPlot'
18
+ import DeviationBar from './DeviationBar'
19
+ import ErrorBoundary from '@cdc/core/components/ErrorBoundary'
20
+ import Forecasting from './Forecasting'
21
+ import LineChart from './LineChart'
22
+ import ForestPlot from './ForestPlot'
23
+ import PairedBarChart from './PairedBarChart'
24
+ import useIntersectionObserver from '../hooks/useIntersectionObserver'
25
+ import Regions from './Regions'
26
+ import CategoricalYAxis from './Axis/Categorical.Axis'
27
+
28
+ // Helpers
29
+ import { isConvertLineToBarGraph } from '../helpers/isConvertLineToBarGraph'
30
+
31
+ // Hooks
32
+ import useMinMax from '../hooks/useMinMax'
33
+ import useReduceData from '../hooks/useReduceData'
34
+ import useRightAxis from '../hooks/useRightAxis'
35
+ import useScales, { getTickValues } from '../hooks/useScales'
36
+ import useTopAxis from '../hooks/useTopAxis'
37
+ import { useTooltip as useCoveTooltip } from '../hooks/useTooltip'
38
+ import { useEditorPermissions } from './EditorPanel/useEditorPermissions'
39
+ import Annotation from './Annotations'
40
+
41
+ type LinearChartProps = {
42
+ parentWidth: number
43
+ parentHeight: number
44
+ }
45
+
46
+ const LinearChart: React.FC<LinearChartProps> = props => {
47
+ // prettier-ignore
48
+ const {
49
+ brushConfig,
50
+ config,
51
+ currentViewport,
52
+ dimensions,
53
+ formatDate,
54
+ formatNumber,
55
+ getTextWidth,
56
+ handleChartAriaLabels,
57
+ handleLineType,
58
+ handleDragStateChange,
59
+ parseDate,
60
+ tableData,
61
+ transformedData: data,
62
+ updateConfig,
63
+ isDraggingAnnotation,
64
+ seriesHighlight,
65
+ colorScale
66
+ } = useContext(ConfigContext)
67
+
68
+ // todo: start destructuring this file for conciseness
69
+ const { visualizationType, visualizationSubType, orientation, xAxis, yAxis, runtime, debugSvg } = config
70
+
71
+ const checkLineToBarGraph = () => {
72
+ return isConvertLineToBarGraph(config.visualizationType, data, config.allowLineToBarGraph)
73
+ }
74
+
75
+ // configure width
76
+ let [width] = dimensions
77
+ if (
78
+ config &&
79
+ config.legend &&
80
+ !config.legend.hide &&
81
+ !['bottom', 'top'].includes(config.legend?.position) &&
82
+ ['lg', 'md'].includes(currentViewport)
83
+ ) {
84
+ width = width * 0.73
85
+ }
86
+ // configure height , yMax, xMax
87
+ const { horizontal: heightHorizontal, mobileVertical } = config.heights
88
+ const isHorizontal = orientation === 'horizontal' || config.visualizationType === 'Forest Plot'
89
+ const shouldAbbreviate = true
90
+ const isLogarithmicAxis = config.yAxis.type === 'logarithmic'
91
+ const xLabelOffset = isNaN(parseInt(runtime.xAxis.labelOffset)) ? 0 : parseInt(runtime.xAxis.labelOffset)
92
+ const yLabelOffset = isNaN(parseInt(runtime.yAxis.labelOffset)) ? 0 : parseInt(runtime.yAxis.labelOffset)
93
+ const xAxisSize = isNaN(parseInt(runtime.xAxis.size)) ? 0 : parseInt(runtime.xAxis.size)
94
+ const isForestPlot = visualizationType === 'Forest Plot'
95
+ const useVertical = orientation === 'vertical' || isForestPlot
96
+ const useMobileVertical = mobileVertical && ['xs', 'xxs'].includes(currentViewport)
97
+ const responsiveVertical = useMobileVertical ? 'mobileVertical' : 'vertical'
98
+ const renderedOrientation = useVertical ? responsiveVertical : 'horizontal'
99
+ let height = config.aspectRatio ? width * config.aspectRatio : config.heights[renderedOrientation]
100
+ height = Number(height)
101
+ const xMax = width - runtime.yAxis.size - (visualizationType === 'Combo' ? config.yAxis.rightAxisSize : 0)
102
+ let yMax = height - (orientation === 'horizontal' ? 0 : xAxisSize)
103
+ height += orientation === 'horizontal' ? xAxisSize : 0
104
+
105
+ if (config.visualizationType === 'Forest Plot') {
106
+ height = height + config.data.length * config.forestPlot.rowHeight
107
+ yMax = yMax + config.data.length * config.forestPlot.rowHeight
108
+ width = dimensions[0]
109
+ }
110
+ if (config.brush?.active) {
111
+ height = height + config.brush?.height
112
+ }
113
+
114
+ // hooks % states
115
+ const { minValue, maxValue, existPositiveValue, isAllLine } = useReduceData(config, data)
116
+ const { visSupportsReactTooltip } = useEditorPermissions()
117
+ const { hasTopAxis } = useTopAxis(config)
118
+ const [animatedChart, setAnimatedChart] = useState(false)
119
+ const [point, setPoint] = useState({ x: 0, y: 0 })
120
+ const annotationRefs = useRef(null)
121
+
122
+ // refs
123
+ const triggerRef = useRef()
124
+ const axisBottomRef = useRef(null)
125
+ const svgRef = useRef()
126
+ const dataRef = useIntersectionObserver(triggerRef, {
127
+ freezeOnceVisible: false
128
+ })
129
+
130
+ // getters & functions
131
+ const getXAxisData = d =>
132
+ isDateScale(config.runtime.xAxis)
133
+ ? parseDate(d[config.runtime.originalXAxis.dataKey]).getTime()
134
+ : d[config.runtime.originalXAxis.dataKey]
135
+ const getYAxisData = (d, seriesKey) => d[seriesKey]
136
+ const xAxisDataMapped =
137
+ config.brush.active && brushConfig.data?.length
138
+ ? brushConfig.data.map(d => getXAxisData(d))
139
+ : data.map(d => getXAxisData(d))
140
+ const section = config.orientation === 'horizontal' || config.visualizationType === 'Forest Plot' ? 'yAxis' : 'xAxis'
141
+ const properties = {
142
+ data,
143
+ tableData,
144
+ config,
145
+ minValue,
146
+ maxValue,
147
+ isAllLine,
148
+ existPositiveValue,
149
+ xAxisDataMapped,
150
+ xMax,
151
+ yMax
152
+ }
153
+ const { min, max, leftMax, rightMax } = useMinMax(properties)
154
+ const { yScaleRight, hasRightAxis } = useRightAxis({ config, yMax, data, updateConfig })
155
+ const { xScale, yScale, seriesScale, g1xScale, g2xScale, xScaleNoPadding, xScaleAnnotation } = useScales({
156
+ ...properties,
157
+ min,
158
+ max,
159
+ leftMax,
160
+ rightMax,
161
+ dimensions,
162
+ xMax: props.parentWidth - Number(config.yAxis.size)
163
+ })
164
+
165
+ // sets the portal x/y for where tooltips should appear on the page.
166
+ const [chartPosition, setChartPosition] = useState(null)
167
+ useEffect(() => {
168
+ setChartPosition(svgRef?.current?.getBoundingClientRect())
169
+ }, [svgRef, config.legend])
170
+
171
+ const handleLeftTickFormatting = (tick, index) => {
172
+ if (isLogarithmicAxis && tick === 0.1) {
173
+ //when logarithmic scale applied change value of first tick
174
+ tick = 0
175
+ }
176
+
177
+ if (config.data && !config.data[index] && visualizationType === 'Forest Plot') return
178
+ if (config.visualizationType === 'Forest Plot') return config.data[index][config.xAxis.dataKey]
179
+ if (isDateScale(runtime.yAxis)) return formatDate(parseDate(tick))
180
+ if (orientation === 'vertical' && max - min < 3)
181
+ return formatNumber(tick, 'left', shouldAbbreviate, false, false, '1')
182
+ if (orientation === 'vertical') return formatNumber(tick, 'left', shouldAbbreviate)
183
+ return tick
184
+ }
185
+
186
+ const handleBottomTickFormatting = tick => {
187
+ if (isLogarithmicAxis && tick === 0.1) {
188
+ // when logarithmic scale applied change value FIRST of tick
189
+ tick = 0
190
+ }
191
+
192
+ if (isDateScale(runtime.xAxis) && config.visualizationType !== 'Forest Plot') return formatDate(tick)
193
+ if (orientation === 'horizontal' && config.visualizationType !== 'Forest Plot')
194
+ return formatNumber(tick, 'left', shouldAbbreviate)
195
+ if (config.xAxis.type === 'continuous' && config.visualizationType !== 'Forest Plot')
196
+ return formatNumber(tick, 'bottom', shouldAbbreviate)
197
+ if (config.visualizationType === 'Forest Plot')
198
+ return formatNumber(
199
+ tick,
200
+ 'left',
201
+ config.dataFormat.abbreviated,
202
+ config.runtime.xAxis.prefix,
203
+ config.runtime.xAxis.suffix,
204
+ Number(config.dataFormat.roundTo)
205
+ )
206
+ return tick
207
+ }
208
+
209
+ const countNumOfTicks = axis => {
210
+ let { numTicks } = runtime[axis]
211
+ if (runtime[axis].viewportNumTicks && runtime[axis].viewportNumTicks[currentViewport]) {
212
+ numTicks = runtime[axis].viewportNumTicks[currentViewport]
213
+ }
214
+ let tickCount = undefined
215
+
216
+ if (axis === 'yAxis') {
217
+ tickCount =
218
+ isHorizontal && !numTicks
219
+ ? data.length
220
+ : isHorizontal && numTicks
221
+ ? numTicks
222
+ : !isHorizontal && !numTicks
223
+ ? undefined
224
+ : !isHorizontal && numTicks && numTicks
225
+ // to fix edge case of small numbers with decimals
226
+ if (tickCount === undefined && !config.dataFormat.roundTo) {
227
+ // then it is set to Auto
228
+ if (Number(max) <= 3) {
229
+ tickCount = 2
230
+ } else {
231
+ tickCount = 4 // same default as standalone components
232
+ }
233
+ }
234
+ if (Number(tickCount) > Number(max)) {
235
+ // cap it and round it so its an integer
236
+ tickCount = Number(min) < 0 ? Math.round(max) * 2 : Math.round(max)
237
+ }
238
+ }
239
+
240
+ if (axis === 'xAxis') {
241
+ tickCount =
242
+ isHorizontal && !numTicks
243
+ ? undefined
244
+ : isHorizontal && numTicks
245
+ ? numTicks
246
+ : !isHorizontal && !numTicks
247
+ ? undefined
248
+ : !isHorizontal && numTicks && numTicks
249
+ if (isHorizontal && tickCount === undefined && !config.dataFormat.roundTo) {
250
+ // then it is set to Auto
251
+ // - check for small numbers situation
252
+ if (max <= 3) {
253
+ tickCount = 2
254
+ } else {
255
+ tickCount = 4 // same default as standalone components
256
+ }
257
+ }
258
+
259
+ if (config.visualizationType === 'Forest Plot') {
260
+ tickCount = config.yAxis.numTicks !== '' ? config.yAxis.numTicks : 4
261
+ }
262
+ }
263
+
264
+ return tickCount
265
+ }
266
+
267
+ // Tooltip Helpers
268
+ const { tooltipData, showTooltip, hideTooltip, tooltipOpen, tooltipLeft, tooltipTop } = useTooltip()
269
+
270
+ // prettier-ignore
271
+ const {
272
+ handleTooltipMouseOver,
273
+ handleTooltipClick,
274
+ handleTooltipMouseOff,
275
+ tooltipStyles,
276
+ TooltipListItem,
277
+ getXValueFromCoordinateDate,
278
+ getXValueFromCoordinate
279
+ } = useCoveTooltip({
280
+ xScale,
281
+ yScale,
282
+ showTooltip,
283
+ hideTooltip
284
+ })
285
+
286
+ // Make sure the chart is visible if in the editor
287
+ /* eslint-disable react-hooks/exhaustive-deps */
288
+ useEffect(() => {
289
+ const element = document.querySelector('.isEditor')
290
+ if (element) {
291
+ // parent element is visible
292
+ setAnimatedChart(prevState => true)
293
+ }
294
+ }) /* eslint-disable-line */
295
+
296
+ // If the chart is in view, set to animate if it has not already played
297
+ useEffect(() => {
298
+ if (dataRef?.isIntersecting === true && config.animate) {
299
+ setTimeout(() => {
300
+ setAnimatedChart(prevState => true)
301
+ }, 500)
302
+ }
303
+ }, [dataRef?.isIntersecting, config.animate])
304
+
305
+ useEffect(() => {
306
+ if (max) {
307
+ updateConfig({ ...config, yAxis: { ...config.yAxis, maxValue: max } })
308
+ }
309
+ }, [max])
310
+
311
+ const chartHasTooltipGuides = () => {
312
+ const { visualizationType } = config
313
+ if (visualizationType === 'Combo' && runtime.forecastingSeriesKeys > 0) return true
314
+ if (visualizationType === 'Area Chart') return true
315
+ if (visualizationType === 'Line') return true
316
+ if (visualizationType === 'Bar') return true
317
+ return false
318
+ }
319
+
320
+ const padding = orientation === 'horizontal' ? Number(config.xAxis.size) : Number(config.yAxis.size)
321
+ const fontSize = { small: 16, medium: 18, large: 20 }
322
+
323
+ const handleNumTicks = () => {
324
+ // On forest plots we need to return every "study" or y axis value.
325
+ if (config.visualizationType === 'Forest Plot') return config.data.length
326
+ return countNumOfTicks('yAxis')
327
+ }
328
+
329
+ const getManualStep = () => {
330
+ let manualStep = config.xAxis.manualStep
331
+ if (config.xAxis.viewportStepCount && config.xAxis.viewportStepCount[currentViewport]) {
332
+ manualStep = config.xAxis.viewportStepCount[currentViewport]
333
+ }
334
+ return manualStep
335
+ }
336
+
337
+ const onMouseMove = event => {
338
+ const svgRect = event.currentTarget.getBoundingClientRect()
339
+ const x = event.clientX - svgRect.left
340
+ const y = event.clientY - svgRect.top
341
+
342
+ setPoint({
343
+ x,
344
+ y
345
+ })
346
+ }
347
+
348
+ const generatePairedBarAxis = () => {
349
+ let axisMaxHeight = 40
350
+
351
+ const getTickPositions = (ticks, xScale) => {
352
+ if (!ticks.length) return false
353
+ // filterout first index
354
+ const filteredTicks = ticks.filter(tick => tick.index !== 0)
355
+ const numberOfTicks = filteredTicks?.length
356
+ const xMaxHalf = xScale.range()[0] || xMax / 2
357
+ const tickWidthAll = filteredTicks.map(tick =>
358
+ getTextWidth(formatNumber(tick.value, 'left'), `normal ${fontSize[config.fontSize]}px sans-serif`)
359
+ )
360
+ const accumulator = 100
361
+ const sumOfTickWidth = tickWidthAll.reduce((a, b) => a + b, accumulator)
362
+ const spaceBetweenEachTick = (xMaxHalf - sumOfTickWidth) / numberOfTicks
363
+ // Determine the position of each tick
364
+ let positions = [0]
365
+ for (let i = 1; i < tickWidthAll.length; i++) {
366
+ positions[i] = positions[i - 1] + tickWidthAll[i - 1] + spaceBetweenEachTick
367
+ }
368
+
369
+ // Check if ticks are overlapping
370
+ let isTicksOverlapping = false
371
+ tickWidthAll.forEach((_, i) => {
372
+ if (positions[i] + tickWidthAll[i] > positions[i + 1]) {
373
+ isTicksOverlapping = true
374
+ return
375
+ }
376
+ })
377
+ return isTicksOverlapping
378
+ }
379
+ return (
380
+ <>
381
+ <AxisBottom
382
+ top={yMax}
383
+ left={Number(runtime.yAxis.size)}
384
+ label={runtime.xAxis.label}
385
+ tickFormat={isDateScale(runtime.xAxis) ? formatDate : formatNumber}
386
+ scale={g1xScale}
387
+ stroke='#333'
388
+ tickStroke='#333'
389
+ numTicks={runtime.xAxis.numTicks || undefined}
390
+ >
391
+ {props => {
392
+ return (
393
+ <Group className='bottom-axis'>
394
+ {props.ticks.map((tick, i) => {
395
+ const textWidth = getTextWidth(
396
+ formatNumber(tick.value, 'left'),
397
+ `normal ${fontSize[config.fontSize]}px sans-serif`
398
+ )
399
+ const isTicksOverlapping = getTickPositions(props.ticks, g1xScale)
400
+ const maxTickRotation = Number(config.xAxis.maxTickRotation) || 90
401
+ const isResponsiveTicks = config.isResponsiveTicks && isTicksOverlapping
402
+ const angle =
403
+ tick.index !== 0 && (isResponsiveTicks ? maxTickRotation : Number(config.yAxis.tickRotation))
404
+ const axisHeight = textWidth * Math.sin(angle * (Math.PI / 180)) + 25
405
+ const textAnchor = angle && tick.index !== 0 ? 'end' : 'middle'
406
+
407
+ if (axisHeight > axisMaxHeight) axisMaxHeight = axisHeight
408
+
409
+ return (
410
+ <Group key={`vx-tick-${tick.value}-${i}`} className={'vx-axis-tick'}>
411
+ {!runtime.yAxis.hideTicks && <Line from={tick.from} to={tick.to} stroke='#333' />}
412
+ {!runtime.yAxis.hideLabel && (
413
+ <Text // prettier-ignore
414
+ x={tick.to.x}
415
+ y={tick.to.y}
416
+ angle={-angle}
417
+ verticalAnchor={angle ? 'middle' : 'start'}
418
+ textAnchor={textAnchor}
419
+ >
420
+ {formatNumber(tick.value, 'left')}
421
+ </Text>
422
+ )}
423
+ </Group>
424
+ )
425
+ })}
426
+ {!runtime.yAxis.hideAxis && <Line from={props.axisFromPoint} to={props.axisToPoint} stroke='#333' />}
427
+ </Group>
428
+ )
429
+ }}
430
+ </AxisBottom>
431
+ <AxisBottom
432
+ top={yMax}
433
+ left={Number(runtime.yAxis.size)}
434
+ label={runtime.xAxis.label}
435
+ tickFormat={
436
+ isDateScale(runtime.xAxis) ? formatDate : runtime.xAxis.dataKey !== 'Year' ? formatNumber : tick => tick
437
+ }
438
+ scale={g2xScale}
439
+ stroke='#333'
440
+ tickStroke='#333'
441
+ numTicks={runtime.xAxis.numTicks || undefined}
442
+ >
443
+ {props => {
444
+ return (
445
+ <>
446
+ <Group className='bottom-axis'>
447
+ {props.ticks.map((tick, i) => {
448
+ const textWidth = getTextWidth(
449
+ formatNumber(tick.value, 'left'),
450
+ `normal ${fontSize[config.fontSize]}px sans-serif`
451
+ )
452
+ const isTicksOverlapping = getTickPositions(props.ticks, g2xScale)
453
+ const maxTickRotation = Number(config.xAxis.maxTickRotation) || 90
454
+ const isResponsiveTicks = config.isResponsiveTicks && isTicksOverlapping
455
+ const angle =
456
+ tick.index !== 0 && (isResponsiveTicks ? maxTickRotation : Number(config.yAxis.tickRotation))
457
+ const axisHeight = textWidth * Math.sin(angle * (Math.PI / 180)) + 25
458
+ const textAnchor = angle && tick.index !== 0 ? 'end' : 'middle'
459
+
460
+ if (axisHeight > axisMaxHeight) axisMaxHeight = axisHeight
461
+
462
+ return (
463
+ <Group key={`vx-tick-${tick.value}-${i}`} className={'vx-axis-tick'}>
464
+ {!runtime.yAxis.hideTicks && <Line from={tick.from} to={tick.to} stroke='#333' />}
465
+ {!runtime.yAxis.hideLabel && (
466
+ <Text // prettier-ignore
467
+ x={tick.to.x}
468
+ y={tick.to.y}
469
+ angle={-angle}
470
+ verticalAnchor={angle ? 'middle' : 'start'}
471
+ textAnchor={textAnchor}
472
+ >
473
+ {formatNumber(tick.value, 'left')}
474
+ </Text>
475
+ )}
476
+ </Group>
477
+ )
478
+ })}
479
+ {!runtime.yAxis.hideAxis && <Line from={props.axisFromPoint} to={props.axisToPoint} stroke='#333' />}
480
+ </Group>
481
+ <Group>
482
+ <Text
483
+ x={xMax / 2}
484
+ y={axisMaxHeight + 20 + xLabelOffset}
485
+ stroke='#333'
486
+ textAnchor={'middle'}
487
+ verticalAnchor='start'
488
+ >
489
+ {runtime.xAxis.label}
490
+ </Text>
491
+ </Group>
492
+ {svgRef.current
493
+ ? svgRef.current.setAttribute(
494
+ 'height',
495
+ Number(height) + Number(axisMaxHeight) + (runtime.xAxis.label ? 50 : 0) + 'px'
496
+ )
497
+ : ''}
498
+ </>
499
+ )
500
+ }}
501
+ </AxisBottom>
502
+ </>
503
+ )
504
+ }
505
+
506
+ return isNaN(width) ? (
507
+ <React.Fragment></React.Fragment>
508
+ ) : (
509
+ <ErrorBoundary component='LinearChart'>
510
+ {/* ! Notice - div needed for tooltip boundaries (flip/flop) */}
511
+ <div style={{ width: `${props.parentWidth}px`, overflow: 'visible' }} className='tooltip-boundary'>
512
+ <svg
513
+ onMouseMove={onMouseMove}
514
+ width={props.parentWidth}
515
+ height={props.parentHeight}
516
+ className={`linear ${config.animate ? 'animated' : ''} ${animatedChart && config.animate ? 'animate' : ''} ${
517
+ debugSvg && 'debug'
518
+ } ${isDraggingAnnotation && 'dragging-annotation'}`}
519
+ role='img'
520
+ aria-label={handleChartAriaLabels(config)}
521
+ ref={svgRef}
522
+ style={{ overflow: 'visible' }}
523
+ >
524
+ {!isDraggingAnnotation && (
525
+ <Bar width={props.parentWidth} height={props.parentHeight} fill={'transparent'}></Bar>
526
+ )}{' '}
527
+ {/* Highlighted regions */}
528
+ {/* Y axis */}
529
+ {!['Spark Line', 'Forest Plot'].includes(visualizationType) && config.yAxis.type !== 'categorical' && (
530
+ <AxisLeft
531
+ scale={yScale}
532
+ tickLength={isLogarithmicAxis ? 6 : 8}
533
+ left={Number(runtime.yAxis.size) - config.yAxis.axisPadding}
534
+ label={runtime.yAxis.label || runtime.yAxis.label}
535
+ stroke='#333'
536
+ tickFormat={(tick, i) => handleLeftTickFormatting(tick, i)}
537
+ numTicks={handleNumTicks()}
538
+ >
539
+ {props => {
540
+ const axisCenter =
541
+ config.orientation === 'horizontal'
542
+ ? (props.axisToPoint.y - props.axisFromPoint.y) / 2
543
+ : (props.axisFromPoint.y - props.axisToPoint.y) / 2
544
+ const horizontalTickOffset =
545
+ yMax / props.ticks.length / 2 - (yMax / props.ticks.length) * (1 - config.barThickness) + 5
546
+ return (
547
+ <Group className='left-axis'>
548
+ {props.ticks.map((tick, i) => {
549
+ const minY = props.ticks[0].to.y
550
+ const barMinHeight = 15 // 15 is the min height for bars by default
551
+ const showTicks = String(tick.value).startsWith('1') || tick.value === 0.1 ? 'block' : 'none'
552
+ const tickLength = showTicks === 'block' ? 7 : 0
553
+ const to = { x: tick.to.x - tickLength, y: tick.to.y }
554
+ const hideFirstGridLine = tick.index === 0 && tick.value === 0 && config.xAxis.hideAxis
555
+
556
+ return (
557
+ <Group key={`vx-tick-${tick.value}-${i}`} className={'vx-axis-tick'}>
558
+ {!runtime.yAxis.hideTicks && (
559
+ <Line
560
+ key={`${tick.value}--hide-hideTicks`}
561
+ from={tick.from}
562
+ to={isLogarithmicAxis ? to : tick.to}
563
+ stroke={config.yAxis.tickColor}
564
+ display={orientation === 'horizontal' ? 'none' : 'block'}
565
+ />
566
+ )}
567
+
568
+ {runtime.yAxis.gridLines && !hideFirstGridLine ? (
569
+ <Line
570
+ key={`${tick.value}--hide-hideGridLines`}
571
+ display={(isLogarithmicAxis && showTicks).toString()}
572
+ from={{ x: tick.from.x + xMax, y: tick.from.y }}
573
+ to={tick.from}
574
+ stroke='rgba(0,0,0,0.3)'
575
+ />
576
+ ) : (
577
+ ''
578
+ )}
579
+
580
+ {orientation === 'horizontal' &&
581
+ visualizationSubType !== 'stacked' &&
582
+ config.yAxis.labelPlacement === 'On Date/Category Axis' &&
583
+ !config.yAxis.hideLabel && (
584
+ <Text
585
+ transform={`translate(${tick.to.x - 5}, ${
586
+ config.isLollipopChart
587
+ ? tick.to.y - minY
588
+ : tick.to.y -
589
+ minY +
590
+ (Number(config.barHeight * config.runtime.series.length) - barMinHeight) / 2
591
+ }) rotate(-${config.runtime.horizontal ? config.runtime.yAxis.tickRotation || 0 : 0})`}
592
+ verticalAnchor={'start'}
593
+ textAnchor={'end'}
594
+ >
595
+ {tick.formattedValue}
596
+ </Text>
597
+ )}
598
+
599
+ {orientation === 'horizontal' &&
600
+ visualizationSubType === 'stacked' &&
601
+ config.yAxis.labelPlacement === 'On Date/Category Axis' &&
602
+ !config.yAxis.hideLabel && (
603
+ <Text
604
+ transform={`translate(${tick.to.x - 5}, ${
605
+ tick.to.y - minY + (Number(config.barHeight) - barMinHeight) / 2
606
+ }) rotate(-${runtime.horizontal ? runtime.yAxis.tickRotation : 0})`}
607
+ verticalAnchor={'start'}
608
+ textAnchor={'end'}
609
+ >
610
+ {tick.formattedValue}
611
+ </Text>
612
+ )}
613
+
614
+ {orientation === 'horizontal' &&
615
+ visualizationType === 'Paired Bar' &&
616
+ !config.yAxis.hideLabel && (
617
+ <Text
618
+ transform={`translate(${tick.to.x - 5}, ${
619
+ tick.to.y - minY + Number(config.barHeight) / 2
620
+ }) rotate(-${runtime.horizontal ? runtime.yAxis.tickRotation : 0})`}
621
+ textAnchor={'end'}
622
+ verticalAnchor='middle'
623
+ >
624
+ {tick.formattedValue}
625
+ </Text>
626
+ )}
627
+ {orientation === 'horizontal' &&
628
+ visualizationType === 'Deviation Bar' &&
629
+ !config.yAxis.hideLabel && (
630
+ <Text
631
+ transform={`translate(${tick.to.x - 5}, ${
632
+ config.isLollipopChart
633
+ ? tick.to.y - minY + 2
634
+ : tick.to.y - minY + Number(config.barHeight) / 2
635
+ }) rotate(-${runtime.horizontal ? runtime.yAxis.tickRotation : 0})`}
636
+ textAnchor={'end'}
637
+ verticalAnchor='middle'
638
+ >
639
+ {tick.formattedValue}
640
+ </Text>
641
+ )}
642
+
643
+ {orientation === 'vertical' &&
644
+ visualizationType === 'Bump Chart' &&
645
+ !config.yAxis.hideLabel && (
646
+ <>
647
+ <Text
648
+ display={config.useLogScale ? showTicks : 'block'}
649
+ dx={config.useLogScale ? -6 : 0}
650
+ x={config.runtime.horizontal ? tick.from.x + 2 : tick.to.x - 8.5}
651
+ y={tick.to.y - 13 + (config.runtime.horizontal ? horizontalTickOffset : 0)}
652
+ angle={-Number(config.yAxis.tickRotation) || 0}
653
+ verticalAnchor={config.runtime.horizontal ? 'start' : 'middle'}
654
+ textAnchor={config.runtime.horizontal ? 'start' : 'end'}
655
+ fill={config.yAxis.tickLabelColor}
656
+ >
657
+ {config.runtime.seriesLabelsAll[tick.formattedValue - 1]}
658
+ </Text>
659
+
660
+ {(seriesHighlight.length === 0 ||
661
+ seriesHighlight.includes(
662
+ config.runtime.seriesLabelsAll[tick.formattedValue - 1]
663
+ )) && (
664
+ <rect
665
+ x={0 - Number(config.yAxis.size)}
666
+ y={tick.to.y - 8 + (config.runtime.horizontal ? horizontalTickOffset : 7)}
667
+ width={Number(config.yAxis.size) + xScale(xScale.domain()[0])}
668
+ height='2'
669
+ fill={colorScale(config.runtime.seriesLabelsAll[tick.formattedValue - 1])}
670
+ />
671
+ )}
672
+ </>
673
+ )}
674
+ {orientation === 'vertical' &&
675
+ visualizationType !== 'Paired Bar' &&
676
+ visualizationType !== 'Bump Chart' &&
677
+ !config.yAxis.hideLabel && (
678
+ <Text
679
+ display={isLogarithmicAxis ? showTicks : 'block'}
680
+ dx={isLogarithmicAxis ? -6 : 0}
681
+ x={config.runtime.horizontal ? tick.from.x + 2 : tick.to.x}
682
+ y={tick.to.y + (config.runtime.horizontal ? horizontalTickOffset : 0)}
683
+ angle={-Number(config.yAxis.tickRotation) || 0}
684
+ verticalAnchor={config.runtime.horizontal ? 'start' : 'middle'}
685
+ textAnchor={config.runtime.horizontal ? 'start' : 'end'}
686
+ fill={config.yAxis.tickLabelColor}
687
+ >
688
+ {tick.formattedValue}
689
+ </Text>
690
+ )}
691
+ </Group>
692
+ )
693
+ })}
694
+ {!config.yAxis.hideAxis && (
695
+ <Line
696
+ from={props.axisFromPoint}
697
+ to={
698
+ runtime.horizontal
699
+ ? {
700
+ x: 0,
701
+ y: config.visualizationType === 'Forest Plot' ? height : Number(heightHorizontal)
702
+ }
703
+ : props.axisToPoint
704
+ }
705
+ stroke='#000'
706
+ />
707
+ )}
708
+ {yScale.domain()[0] < 0 && (
709
+ <Line
710
+ from={{ x: props.axisFromPoint.x, y: yScale(0) }}
711
+ to={{ x: xMax, y: yScale(0) }}
712
+ stroke='#333'
713
+ />
714
+ )}
715
+ {visualizationType === 'Bar' && orientation === 'horizontal' && xScale.domain()[0] < 0 && (
716
+ <Line
717
+ from={{ x: xScale(0), y: 0 }}
718
+ to={{ x: xScale(0), y: yMax }}
719
+ stroke='#333'
720
+ strokeWidth={2}
721
+ />
722
+ )}
723
+ <Text
724
+ className='y-label'
725
+ textAnchor='middle'
726
+ verticalAnchor='start'
727
+ transform={`translate(${-1 * runtime.yAxis.size + yLabelOffset}, ${axisCenter}) rotate(-90)`}
728
+ fontWeight='bold'
729
+ fill={config.yAxis.labelColor}
730
+ >
731
+ {props.label}
732
+ </Text>
733
+ </Group>
734
+ )
735
+ }}
736
+ </AxisLeft>
737
+ )}
738
+ {config.yAxis.type === 'categorical' && config.orientation === 'vertical' && (
739
+ <CategoricalYAxis
740
+ max={max}
741
+ maxValue={maxValue}
742
+ height={height}
743
+ xMax={xMax}
744
+ yMax={yMax}
745
+ leftSize={Number(runtime.yAxis.size) - config.yAxis.axisPadding}
746
+ />
747
+ )}
748
+ {/* Right Axis */}
749
+ {hasRightAxis && (
750
+ <AxisRight
751
+ scale={yScaleRight}
752
+ left={Number(width - config.yAxis.rightAxisSize)}
753
+ label={config.yAxis.rightLabel}
754
+ tickFormat={tick => formatNumber(tick, 'right')}
755
+ numTicks={runtime.yAxis.rightNumTicks || undefined}
756
+ labelOffset={45}
757
+ >
758
+ {props => {
759
+ const axisCenter =
760
+ config.orientation === 'horizontal'
761
+ ? (props.axisToPoint.y - props.axisFromPoint.y) / 2
762
+ : (props.axisFromPoint.y - props.axisToPoint.y) / 2
763
+ const horizontalTickOffset =
764
+ yMax / props.ticks.length / 2 - (yMax / props.ticks.length) * (1 - config.barThickness) + 5
765
+ return (
766
+ <Group className='right-axis'>
767
+ {props.ticks.map((tick, i) => {
768
+ return (
769
+ <Group key={`vx-tick-${tick.value}-${i}`} className='vx-axis-tick'>
770
+ {!runtime.yAxis.rightHideTicks && (
771
+ <Line
772
+ from={tick.from}
773
+ to={tick.to}
774
+ display={runtime.horizontal ? 'none' : 'block'}
775
+ stroke={config.yAxis.rightAxisTickColor}
776
+ />
777
+ )}
778
+
779
+ {runtime.yAxis.rightGridLines ? (
780
+ <Line
781
+ from={{ x: tick.from.x + xMax, y: tick.from.y }}
782
+ to={tick.from}
783
+ stroke='rgba(0,0,0,0.3)'
784
+ />
785
+ ) : (
786
+ ''
787
+ )}
788
+
789
+ {!config.yAxis.rightHideLabel && (
790
+ <Text
791
+ x={tick.to.x}
792
+ y={tick.to.y + (runtime.horizontal ? horizontalTickOffset : 0)}
793
+ verticalAnchor={runtime.horizontal ? 'start' : 'middle'}
794
+ textAnchor={'start'}
795
+ fill={config.yAxis.rightAxisTickLabelColor}
796
+ >
797
+ {tick.formattedValue}
798
+ </Text>
799
+ )}
800
+ </Group>
801
+ )
802
+ })}
803
+ {!config.yAxis.rightHideAxis && (
804
+ <Line from={props.axisFromPoint} to={props.axisToPoint} stroke='#333' />
805
+ )}
806
+ <Text
807
+ className='y-label'
808
+ textAnchor='middle'
809
+ verticalAnchor='start'
810
+ transform={`translate(${
811
+ config.yAxis.rightLabelOffsetSize ? config.yAxis.rightLabelOffsetSize : 0
812
+ }, ${axisCenter}) rotate(-90)`}
813
+ fontWeight='bold'
814
+ fill={config.yAxis.rightAxisLabelColor}
815
+ >
816
+ {props.label}
817
+ </Text>
818
+ </Group>
819
+ )
820
+ }}
821
+ </AxisRight>
822
+ )}
823
+ {hasTopAxis && config.topAxis.hasLine && (
824
+ <AxisTop
825
+ stroke='#333'
826
+ left={Number(runtime.yAxis.size)}
827
+ scale={xScale}
828
+ hideTicks
829
+ hideZero
830
+ tickLabelProps={() => ({
831
+ fill: 'transparent'
832
+ })}
833
+ />
834
+ )}
835
+ {/* X axis */}
836
+ {visualizationType !== 'Paired Bar' && visualizationType !== 'Spark Line' && (
837
+ <AxisBottom
838
+ innerRef={axisBottomRef}
839
+ top={
840
+ runtime.horizontal && config.visualizationType !== 'Forest Plot'
841
+ ? Number(heightHorizontal) + Number(config.xAxis.axisPadding)
842
+ : config.visualizationType === 'Forest Plot'
843
+ ? yMax + Number(config.xAxis.axisPadding)
844
+ : yMax
845
+ }
846
+ left={config.visualizationType !== 'Forest Plot' ? Number(runtime.yAxis.size) : 0}
847
+ label={config[section].label}
848
+ tickFormat={handleBottomTickFormatting}
849
+ scale={xScale}
850
+ stroke='#333'
851
+ numTicks={countNumOfTicks('xAxis')}
852
+ tickStroke='#333'
853
+ tickValues={
854
+ config.xAxis.manual
855
+ ? getTickValues(
856
+ xAxisDataMapped,
857
+ xScale,
858
+ config.xAxis.type === 'date-time' ? countNumOfTicks('xAxis') : getManualStep()
859
+ )
860
+ : undefined
861
+ }
862
+ >
863
+ {props => {
864
+ const axisCenter =
865
+ config.visualizationType !== 'Forest Plot'
866
+ ? (props.axisToPoint.x - props.axisFromPoint.x) / 2
867
+ : dimensions[0] / 2
868
+ const containsMultipleWords = inputString => /\s/.test(inputString)
869
+ const ismultiLabel = props.ticks.some(tick => containsMultipleWords(tick.value))
870
+
871
+ // Calculate sumOfTickWidth here, before map function
872
+ const defaultTickLength = 8
873
+ const tickWidthMax = Math.max(
874
+ ...props.ticks.map(tick =>
875
+ getTextWidth(tick.formattedValue, `normal ${fontSize[config.fontSize]}px sans-serif`)
876
+ )
877
+ )
878
+ // const marginTop = 20 // moved to top bc need for yMax calcs
879
+ const accumulator = ismultiLabel ? 180 : 100
880
+
881
+ const textWidths = props.ticks.map(tick =>
882
+ getTextWidth(tick.formattedValue, `normal ${fontSize[config.fontSize]}px sans-serif`)
883
+ )
884
+ const sumOfTickWidth = textWidths.reduce((a, b) => a + b, accumulator)
885
+ const spaceBetweenEachTick = (xMax - sumOfTickWidth) / (props.ticks.length - 1)
886
+
887
+ // Check if ticks are overlapping
888
+ // Determine the position of each tick
889
+ let positions = [0] // The first tick is at position 0
890
+ for (let i = 1; i < textWidths.length; i++) {
891
+ // The position of each subsequent tick is the position of the previous tick
892
+ // plus the width of the previous tick and the space
893
+ positions[i] = positions[i - 1] + textWidths[i - 1] + spaceBetweenEachTick
894
+ }
895
+ // calculate the end of x axis box
896
+ const axisBBox = axisBottomRef?.current?.getBBox().height
897
+ config.xAxis.axisBBox = axisBBox
898
+
899
+ // Check if ticks are overlapping
900
+ let areTicksTouching = false
901
+ textWidths.forEach((_, i) => {
902
+ if (positions[i] + textWidths[i] > positions[i + 1]) {
903
+ areTicksTouching = true
904
+ return
905
+ }
906
+ })
907
+
908
+ const dynamicMarginTop =
909
+ areTicksTouching && config.isResponsiveTicks ? tickWidthMax + defaultTickLength + 20 : 0
910
+ const rotation = Number(config.xAxis.tickRotation) > 0 ? Number(config.xAxis.tickRotation) : 0
911
+
912
+ config.dynamicMarginTop = dynamicMarginTop
913
+ config.xAxis.tickWidthMax = tickWidthMax
914
+
915
+ let axisMaxHeight = 40
916
+
917
+ const axisContents = (
918
+ <Group className='bottom-axis' width={dimensions[0]}>
919
+ {props.ticks.map((tick, i, propsTicks) => {
920
+ // when using LogScale show major ticks values only
921
+ const showTick = String(tick.value).startsWith('1') || tick.value === 0.1 ? 'block' : 'none'
922
+ const tickLength = showTick === 'block' ? 16 : defaultTickLength
923
+ const to = { x: tick.to.x, y: tickLength }
924
+ const textWidth = getTextWidth(
925
+ tick.formattedValue,
926
+ `normal ${fontSize[config.fontSize]}px sans-serif`
927
+ )
928
+ const limitedWidth = 100 / propsTicks.length
929
+ //reset rotations by updating config
930
+ config.yAxis.tickRotation =
931
+ config.isResponsiveTicks && config.orientation === 'horizontal' ? 0 : config.yAxis.tickRotation
932
+ config.xAxis.tickRotation =
933
+ config.isResponsiveTicks && config.orientation === 'vertical' ? 0 : config.xAxis.tickRotation
934
+ //configure rotation
935
+
936
+ const tickRotation =
937
+ config.isResponsiveTicks && areTicksTouching
938
+ ? -Number(config.xAxis.maxTickRotation) || -90
939
+ : -Number(config.runtime.xAxis.tickRotation)
940
+
941
+ const axisHeight = textWidth * Math.sin(tickRotation * -1 * (Math.PI / 180)) + 25
942
+
943
+ if (axisHeight > axisMaxHeight) axisMaxHeight = axisHeight
944
+
945
+ return (
946
+ <Group key={`vx-tick-${tick.value}-${i}`} className={'vx-axis-tick'}>
947
+ {!config.xAxis.hideTicks && (
948
+ <Line
949
+ from={tick.from}
950
+ to={orientation === 'horizontal' && isLogarithmicAxis ? to : tick.to}
951
+ stroke={config.xAxis.tickColor}
952
+ strokeWidth={showTick === 'block' && isLogarithmicAxis ? 1.3 : 1}
953
+ />
954
+ )}
955
+ {!config.xAxis.hideLabel && (
956
+ <Text
957
+ dy={config.orientation === 'horizontal' && isLogarithmicAxis ? 8 : 0}
958
+ display={config.orientation === 'horizontal' && isLogarithmicAxis ? showTick : 'block'}
959
+ x={tick.to.x}
960
+ y={tick.to.y}
961
+ angle={tickRotation}
962
+ verticalAnchor={tickRotation < -50 ? 'middle' : 'start'}
963
+ textAnchor={tickRotation ? 'end' : 'middle'}
964
+ width={
965
+ areTicksTouching && !config.isResponsiveTicks && !Number(config[section].tickRotation)
966
+ ? limitedWidth
967
+ : undefined
968
+ }
969
+ fill={config.xAxis.tickLabelColor}
970
+ >
971
+ {tick.formattedValue}
972
+ </Text>
973
+ )}
974
+ </Group>
975
+ )
976
+ })}
977
+ {!config.xAxis.hideAxis && <Line from={props.axisFromPoint} to={props.axisToPoint} stroke='#333' />}
978
+ <Text
979
+ x={axisCenter}
980
+ y={axisMaxHeight + 20 + xLabelOffset}
981
+ textAnchor='middle'
982
+ verticalAnchor='start'
983
+ fontWeight='bold'
984
+ fill={config.xAxis.labelColor}
985
+ >
986
+ {props.label}
987
+ </Text>
988
+ </Group>
989
+ )
990
+
991
+ if (svgRef.current)
992
+ svgRef.current.setAttribute(
993
+ 'height',
994
+ Number(height) + Number(axisMaxHeight) + (runtime.xAxis.label ? 50 : 0) + 'px'
995
+ )
996
+
997
+ return axisContents
998
+ }}
999
+ </AxisBottom>
1000
+ )}
1001
+ {visualizationType === 'Paired Bar' && generatePairedBarAxis()}
1002
+ {visualizationType === 'Deviation Bar' && config.runtime.series?.length === 1 && (
1003
+ <DeviationBar animatedChart={animatedChart} xScale={xScale} yScale={yScale} width={xMax} height={yMax} />
1004
+ )}
1005
+ {visualizationType === 'Paired Bar' && <PairedBarChart originalWidth={width} width={xMax} height={yMax} />}
1006
+ {visualizationType === 'Scatter Plot' && (
1007
+ <ScatterPlot
1008
+ xScale={xScale}
1009
+ yScale={yScale}
1010
+ getXAxisData={getXAxisData}
1011
+ getYAxisData={getYAxisData}
1012
+ xMax={xMax}
1013
+ yMax={yMax}
1014
+ handleTooltipMouseOver={handleTooltipMouseOver}
1015
+ handleTooltipMouseOff={handleTooltipMouseOff}
1016
+ handleTooltipClick={handleTooltipClick}
1017
+ tooltipData={tooltipData}
1018
+ showTooltip={showTooltip}
1019
+ />
1020
+ )}
1021
+ {visualizationType === 'Box Plot' && <BoxPlot xScale={xScale} yScale={yScale} />}
1022
+ {((visualizationType === 'Area Chart' && config.visualizationSubType === 'regular') ||
1023
+ visualizationType === 'Combo') && (
1024
+ <AreaChart
1025
+ xScale={xScale}
1026
+ yScale={yScale}
1027
+ yMax={yMax}
1028
+ xMax={xMax}
1029
+ chartRef={svgRef}
1030
+ width={xMax}
1031
+ height={yMax}
1032
+ handleTooltipMouseOver={handleTooltipMouseOver}
1033
+ handleTooltipMouseOff={handleTooltipMouseOff}
1034
+ tooltipData={tooltipData}
1035
+ showTooltip={showTooltip}
1036
+ />
1037
+ )}
1038
+ {((visualizationType === 'Area Chart' && config.visualizationSubType === 'stacked') ||
1039
+ visualizationType === 'Combo') && (
1040
+ <AreaChartStacked
1041
+ xScale={xScale}
1042
+ yScale={yScale}
1043
+ yMax={yMax}
1044
+ xMax={xMax}
1045
+ chartRef={svgRef}
1046
+ width={xMax}
1047
+ height={yMax}
1048
+ handleTooltipMouseOver={handleTooltipMouseOver}
1049
+ handleTooltipMouseOff={handleTooltipMouseOff}
1050
+ tooltipData={tooltipData}
1051
+ showTooltip={showTooltip}
1052
+ />
1053
+ )}
1054
+ {(visualizationType === 'Bar' || visualizationType === 'Combo' || checkLineToBarGraph()) && (
1055
+ <BarChart
1056
+ xScale={xScale}
1057
+ yScale={yScale}
1058
+ seriesScale={seriesScale}
1059
+ xMax={xMax}
1060
+ yMax={yMax}
1061
+ getXAxisData={getXAxisData}
1062
+ getYAxisData={getYAxisData}
1063
+ animatedChart={animatedChart}
1064
+ visible={animatedChart}
1065
+ handleTooltipMouseOver={handleTooltipMouseOver}
1066
+ handleTooltipMouseOff={handleTooltipMouseOff}
1067
+ handleTooltipClick={handleTooltipClick}
1068
+ tooltipData={tooltipData}
1069
+ showTooltip={showTooltip}
1070
+ chartRef={svgRef}
1071
+ />
1072
+ )}
1073
+ {((visualizationType === 'Line' && !checkLineToBarGraph()) ||
1074
+ visualizationType === 'Combo' ||
1075
+ visualizationType === 'Bump Chart') && (
1076
+ <LineChart
1077
+ xScale={xScale}
1078
+ yScale={yScale}
1079
+ getXAxisData={getXAxisData}
1080
+ getYAxisData={getYAxisData}
1081
+ xMax={xMax}
1082
+ yMax={yMax}
1083
+ seriesStyle={config.runtime.series}
1084
+ handleTooltipMouseOver={handleTooltipMouseOver}
1085
+ handleTooltipMouseOff={handleTooltipMouseOff}
1086
+ handleTooltipClick={handleTooltipClick}
1087
+ tooltipData={tooltipData}
1088
+ showTooltip={showTooltip}
1089
+ chartRef={svgRef}
1090
+ />
1091
+ )}
1092
+ {(visualizationType === 'Forecasting' || visualizationType === 'Combo') && (
1093
+ <Forecasting
1094
+ showTooltip={showTooltip}
1095
+ tooltipData={tooltipData}
1096
+ xScale={xScale}
1097
+ yScale={yScale}
1098
+ width={xMax}
1099
+ le
1100
+ height={yMax}
1101
+ xScaleNoPadding={xScaleNoPadding}
1102
+ chartRef={svgRef}
1103
+ getXValueFromCoordinate={getXValueFromCoordinate}
1104
+ handleTooltipMouseOver={handleTooltipMouseOver}
1105
+ handleTooltipMouseOff={handleTooltipMouseOff}
1106
+ isBrush={false}
1107
+ />
1108
+ )}
1109
+ {/* y anchors */}
1110
+ {config.yAxis.anchors &&
1111
+ config.yAxis.anchors.map(anchor => {
1112
+ return (
1113
+ <Line
1114
+ strokeDasharray={handleLineType(anchor.lineStyle)}
1115
+ stroke='rgba(0,0,0,1)'
1116
+ className='customAnchor'
1117
+ from={{ x: 0 + config.yAxis.size, y: yScale(anchor.value) }}
1118
+ to={{ x: xMax, y: yScale(anchor.value) }}
1119
+ display={runtime.horizontal ? 'none' : 'block'}
1120
+ />
1121
+ )
1122
+ })}
1123
+ {visualizationType === 'Forest Plot' && (
1124
+ <ForestPlot
1125
+ xScale={xScale}
1126
+ yScale={yScale}
1127
+ seriesScale={seriesScale}
1128
+ width={width}
1129
+ height={height}
1130
+ getXAxisData={getXAxisData}
1131
+ getYAxisData={getYAxisData}
1132
+ animatedChart={animatedChart}
1133
+ visible={animatedChart}
1134
+ handleTooltipMouseOver={handleTooltipMouseOver}
1135
+ handleTooltipMouseOff={handleTooltipMouseOff}
1136
+ handleTooltipClick={handleTooltipClick}
1137
+ tooltipData={tooltipData}
1138
+ showTooltip={showTooltip}
1139
+ chartRef={svgRef}
1140
+ config={config}
1141
+ />
1142
+ )}
1143
+ {/*Brush chart */}
1144
+ {config.brush.active && config.xAxis.type !== 'categorical' && (
1145
+ <BrushChart yScale={yScale} xMax={xMax} yMax={yMax} xScale={xScale} seriesScale={seriesScale} />
1146
+ )}
1147
+ {/* Line chart */}
1148
+ {/* TODO: Make this just line or combo? */}
1149
+ {!['Paired Bar', 'Box Plot', 'Area Chart', 'Scatter Plot', 'Deviation Bar', 'Forecasting', 'Bar'].includes(
1150
+ visualizationType
1151
+ ) &&
1152
+ !checkLineToBarGraph() && (
1153
+ <>
1154
+ <LineChart
1155
+ xScale={xScale}
1156
+ yScale={yScale}
1157
+ getXAxisData={getXAxisData}
1158
+ getYAxisData={getYAxisData}
1159
+ xMax={xMax}
1160
+ yMax={yMax}
1161
+ seriesStyle={config.runtime.series}
1162
+ />
1163
+ </>
1164
+ )}
1165
+ {/* y anchors */}
1166
+ {config.yAxis.anchors &&
1167
+ config.yAxis.anchors.map((anchor, index) => {
1168
+ let anchorPosition = yScale(anchor.value)
1169
+ // have to move up
1170
+ // const padding = orientation === 'horizontal' ? Number(config.xAxis.size) : Number(config.yAxis.size)
1171
+ if (!anchor.value) return
1172
+ const middleOffset =
1173
+ orientation === 'horizontal' && visualizationType === 'Bar' ? config.barHeight / 4 : 0
1174
+
1175
+ if (!anchorPosition) return
1176
+
1177
+ return (
1178
+ // prettier-ignore
1179
+ <Line
1180
+ key={`yAxis-${anchor.value}--${index}`}
1181
+ strokeDasharray={handleLineType(anchor.lineStyle)}
1182
+ stroke={anchor.color ? anchor.color : 'rgba(0,0,0,1)'}
1183
+ className='anchor-y'
1184
+ from={{ x: 0 + padding, y: anchorPosition - middleOffset}}
1185
+ to={{ x: width - config.yAxis.rightAxisSize, y: anchorPosition - middleOffset }}
1186
+ />
1187
+ )
1188
+ })}
1189
+ {/* x anchors */}
1190
+ {config.xAxis.anchors &&
1191
+ config.xAxis.anchors.map((anchor, index) => {
1192
+ let newX = xAxis
1193
+ if (orientation === 'horizontal') {
1194
+ newX = yAxis
1195
+ }
1196
+
1197
+ let anchorPosition = isDateScale(newX) ? xScale(parseDate(anchor.value, false)) : xScale(anchor.value)
1198
+
1199
+ // have to move up
1200
+ // const padding = orientation === 'horizontal' ? Number(config.xAxis.size) : Number(config.yAxis.size)
1201
+
1202
+ if (!anchorPosition) return
1203
+
1204
+ return (
1205
+ // prettier-ignore
1206
+ <Line
1207
+ key={`xAxis-${anchor.value}--${index}`}
1208
+ strokeDasharray={handleLineType(anchor.lineStyle)}
1209
+ stroke={anchor.color ? anchor.color : 'rgba(0,0,0,1)'}
1210
+ fill={anchor.color ? anchor.color : 'rgba(0,0,0,1)'}
1211
+ className='anchor-x'
1212
+ from={{ x: Number(anchorPosition) + Number(padding), y: 0 }}
1213
+ to={{ x: Number(anchorPosition) + Number(padding), y: yMax }}
1214
+ />
1215
+ )
1216
+ })}
1217
+ {/* we are handling regions in bar charts differently, so that we can calculate the bar group into the region space. */}
1218
+ {/* prettier-ignore */}
1219
+ {config.visualizationType !== 'Bar' && config.visualizationType !== 'Combo' && (
1220
+ <Regions
1221
+ xScale={xScale}
1222
+ handleTooltipClick={handleTooltipClick}
1223
+ handleTooltipMouseOff={handleTooltipMouseOff}
1224
+ handleTooltipMouseOver={handleTooltipMouseOver}
1225
+ showTooltip={showTooltip}
1226
+ hideTooltip={hideTooltip}
1227
+ tooltipData={tooltipData}
1228
+ yMax={yMax}
1229
+ width={width}
1230
+ />
1231
+ )}
1232
+ {chartHasTooltipGuides && showTooltip && tooltipData && config.visual.verticalHoverLine && (
1233
+ <Group key='tooltipLine-vertical' className='vertical-tooltip-line'>
1234
+ <Line
1235
+ from={{ x: tooltipData.dataXPosition - 10, y: 0 }}
1236
+ to={{ x: tooltipData.dataXPosition - 10, y: yMax }}
1237
+ stroke={'black'}
1238
+ strokeWidth={1}
1239
+ pointerEvents='none'
1240
+ strokeDasharray='5,5'
1241
+ className='vertical-tooltip-line'
1242
+ />
1243
+ </Group>
1244
+ )}
1245
+ {chartHasTooltipGuides && showTooltip && tooltipData && config.visual.horizontalHoverLine && (
1246
+ <Group
1247
+ key='tooltipLine-horizontal'
1248
+ className='horizontal-tooltip-line'
1249
+ left={config.yAxis.size ? config.yAxis.size : 0}
1250
+ >
1251
+ <Line
1252
+ from={{ x: 0, y: tooltipData.dataYPosition }}
1253
+ to={{ x: xMax, y: tooltipData.dataYPosition }}
1254
+ stroke={'black'}
1255
+ strokeWidth={1}
1256
+ pointerEvents='none'
1257
+ strokeDasharray='5,5'
1258
+ className='horizontal-tooltip-line'
1259
+ />
1260
+ </Group>
1261
+ )}
1262
+ {config.filters && config.filters.values.length === 0 && data.length === 0 && (
1263
+ <Text
1264
+ x={Number(config.yAxis.size) + Number(xMax / 2)}
1265
+ y={height / 2 - config.xAxis.padding / 2}
1266
+ textAnchor='middle'
1267
+ >
1268
+ {config.chartMessage.noData}
1269
+ </Text>
1270
+ )}
1271
+ {(config.visualizationType === 'Bar' || checkLineToBarGraph()) &&
1272
+ config.tooltips.singleSeries &&
1273
+ config.visual.horizontalHoverLine && (
1274
+ <Group
1275
+ key='tooltipLine-horizontal'
1276
+ className='horizontal-tooltip-line'
1277
+ left={config.yAxis.size ? config.yAxis.size : 0}
1278
+ >
1279
+ <Line
1280
+ from={{ x: 0, y: point.y }}
1281
+ to={{ x: xMax, y: point.y }}
1282
+ stroke={'black'}
1283
+ strokeWidth={1}
1284
+ pointerEvents='none'
1285
+ strokeDasharray='5,5'
1286
+ className='horizontal-tooltip-line'
1287
+ />
1288
+ </Group>
1289
+ )}
1290
+ {(config.visualizationType === 'Bar' || checkLineToBarGraph()) &&
1291
+ config.tooltips.singleSeries &&
1292
+ config.visual.verticalHoverLine && (
1293
+ <Group key='tooltipLine-vertical' className='vertical-tooltip-line'>
1294
+ <Line
1295
+ from={{ x: point.x, y: 0 }}
1296
+ to={{ x: point.x, y: yMax }}
1297
+ stroke={'black'}
1298
+ strokeWidth={1}
1299
+ pointerEvents='none'
1300
+ strokeDasharray='5,5'
1301
+ className='vertical-tooltip-line'
1302
+ />
1303
+ </Group>
1304
+ )}
1305
+ <Group left={Number(config.runtime.yAxis.size)}>
1306
+ <Annotation.Draggable
1307
+ xScale={xScale}
1308
+ yScale={yScale}
1309
+ xScaleAnnotation={xScaleAnnotation}
1310
+ xMax={xMax}
1311
+ svgRef={svgRef}
1312
+ onDragStateChange={handleDragStateChange}
1313
+ />
1314
+ </Group>
1315
+ </svg>
1316
+ {!isDraggingAnnotation &&
1317
+ tooltipData &&
1318
+ Object.entries(tooltipData.data).length > 0 &&
1319
+ tooltipOpen &&
1320
+ showTooltip &&
1321
+ tooltipData.dataYPosition &&
1322
+ tooltipData.dataXPosition && (
1323
+ <>
1324
+ <style>{`.tooltip {background-color: rgba(255,255,255, ${
1325
+ config.tooltips.opacity / 100
1326
+ }) !important;`}</style>
1327
+ <style>{`.tooltip {max-width:300px} !important; word-wrap: break-word; `}</style>
1328
+ <TooltipWithBounds
1329
+ key={Math.random()}
1330
+ className={'tooltip cdc-open-viz-module'}
1331
+ left={tooltipLeft}
1332
+ top={tooltipTop}
1333
+ >
1334
+ <ul>
1335
+ {typeof tooltipData === 'object' &&
1336
+ Object.entries(tooltipData.data).map((item, index) => <TooltipListItem item={item} key={index} />)}
1337
+ </ul>
1338
+ </TooltipWithBounds>
1339
+ </>
1340
+ )}
1341
+
1342
+ {config.visualizationType === 'Bump Chart' && (
1343
+ <ReactTooltip
1344
+ id={`bump-chart`}
1345
+ variant='light'
1346
+ arrowColor='rgba(0,0,0,0)'
1347
+ className='tooltip'
1348
+ style={{ background: `rgba(255,255,255, ${config.tooltips.opacity / 100})`, color: 'black' }}
1349
+ />
1350
+ )}
1351
+ {visSupportsReactTooltip() && !isDraggingAnnotation && (
1352
+ <ReactTooltip
1353
+ id={`cdc-open-viz-tooltip-${runtime.uniqueId}`}
1354
+ variant='light'
1355
+ arrowColor='rgba(0,0,0,0)'
1356
+ className='tooltip'
1357
+ style={{ background: `rgba(255,255,255, ${config.tooltips.opacity / 100})`, color: 'black' }}
1358
+ />
1359
+ )}
1360
+ <div className='animation-trigger' ref={triggerRef} />
1361
+ </div>
1362
+ </ErrorBoundary>
1363
+ )
1364
+ }
1365
+
1366
+ export default LinearChart