@cdc/chart 4.23.6 → 4.23.7

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.
@@ -1,54 +1,65 @@
1
- import React, { useContext, useEffect, useRef, useState } from 'react'
2
- import { Tooltip as ReactTooltip } from 'react-tooltip'
1
+ import React, { forwardRef, useContext, useEffect, useRef, useState } from 'react'
3
2
 
4
- import { Group } from '@visx/group'
5
- import { Line } from '@visx/shape'
6
- import { Text } from '@visx/text'
3
+ // Libraries
7
4
  import { AxisLeft, AxisBottom, AxisRight, AxisTop } from '@visx/axis'
5
+ import { bisector } from 'd3-array'
6
+ import { Group } from '@visx/group'
7
+ import { Line, Bar } from '@visx/shape'
8
8
  import { localPoint } from '@visx/event'
9
- import { useTooltip } from '@visx/tooltip'
9
+ import { Text } from '@visx/text'
10
+ import { Tooltip as ReactTooltip } from 'react-tooltip'
11
+ import { useTooltip, TooltipWithBounds, useTooltipInPortal } from '@visx/tooltip'
10
12
 
13
+ // CDC Components
14
+ import AreaChart from './AreaChart'
11
15
  import BarChart from './BarChart'
12
16
  import ConfigContext from '../ConfigContext'
13
- import CoveAreaChart from './AreaChart'
14
17
  import CoveBoxPlot from './BoxPlot'
15
18
  import CoveScatterPlot from './ScatterPlot'
16
19
  import DeviationBar from './DeviationBar'
20
+ import ErrorBoundary from '@cdc/core/components/ErrorBoundary'
21
+ import Forecasting from './Forecasting'
17
22
  import LineChart from './LineChart'
18
23
  import PairedBarChart from './PairedBarChart'
19
24
  import useIntersectionObserver from './useIntersectionObserver'
20
25
 
21
- import ErrorBoundary from '@cdc/core/components/ErrorBoundary'
22
- import '../scss/LinearChart.scss'
23
- import useReduceData from '../hooks/useReduceData'
24
- import useScales from '../hooks/useScales'
26
+ // Hooks
25
27
  import useMinMax from '../hooks/useMinMax'
28
+ import useReduceData from '../hooks/useReduceData'
26
29
  import useRightAxis from '../hooks/useRightAxis'
30
+ import useScales from '../hooks/useScales'
27
31
  import useTopAxis from '../hooks/useTopAxis'
28
- import Forecasting from './Forecasting'
32
+
33
+ // styles
34
+ import { defaultStyles } from '@visx/tooltip'
35
+ import '../scss/LinearChart.scss'
29
36
 
30
37
  export default function LinearChart() {
31
- const { transformedData: data, dimensions, config, parseDate, formatDate, currentViewport, formatNumber, handleChartAriaLabels, updateConfig, handleLineType, rawData } = useContext(ConfigContext)
38
+ const { isEditor, transformedData: data, dimensions, config, parseDate, formatDate, currentViewport, formatNumber, handleChartAriaLabels, updateConfig, handleLineType, rawData, capitalize, setSharedFilter, setSharedFilterValue, getTextWidth } = useContext(ConfigContext)
39
+
40
+ // todo: start destructuring this file for conciseness
41
+ const { visualizationType, visualizationSubType, orientation, xAxis, yAxis, runtime } = config
32
42
 
33
43
  // getters & functions
34
- const getXAxisData = d => (config.runtime.xAxis.type === 'date' ? parseDate(d[config.runtime.originalXAxis.dataKey]).getTime() : d[config.runtime.originalXAxis.dataKey])
44
+ const getXAxisData = d => (runtime.xAxis.type === 'date' ? parseDate(d[runtime.originalXAxis.dataKey]).getTime() : d[runtime.originalXAxis.dataKey])
35
45
  const getYAxisData = (d, seriesKey) => d[seriesKey]
36
46
  const xAxisDataMapped = data.map(d => getXAxisData(d))
37
47
 
38
48
  // configure width
39
49
  let [width] = dimensions
50
+ let originalWidth = width
40
51
  if (config && config.legend && !config.legend.hide && config.legend.position !== 'bottom' && ['lg', 'md'].includes(currentViewport)) {
41
52
  width = width * 0.73
42
53
  }
43
54
  // configure height , yMax, xMAx
44
55
  const { horizontal: heightHorizontal } = config.heights
45
- const isHorizontal = config.orientation === 'horizontal'
56
+ const isHorizontal = orientation === 'horizontal'
46
57
  const shouldAbbreviate = true
47
- const height = config.aspectRatio ? width * config.aspectRatio : config.heights[config.orientation]
48
- const xMax = width - config.runtime.yAxis.size - (config.visualizationType === 'Combo' ? config.yAxis.rightAxisSize : 0)
49
- const yMax = height - (config.orientation === 'horizontal' ? 0 : config.runtime.xAxis.size)
58
+ const height = config.aspectRatio ? width * config.aspectRatio : config.heights[orientation]
59
+ const xMax = width - runtime.yAxis.size - (visualizationType === 'Combo' ? config.yAxis.rightAxisSize : 0)
60
+ const yMax = height - (orientation === 'horizontal' ? 0 : runtime.xAxis.size)
50
61
 
51
- // hooks % states
62
+ // hooks
52
63
  const { minValue, maxValue, existPositiveValue, isAllLine } = useReduceData(config, data)
53
64
  const { yScaleRight, hasRightAxis } = useRightAxis({ config, yMax, data, updateConfig })
54
65
  const { hasTopAxis } = useTopAxis(config)
@@ -64,29 +75,58 @@ export default function LinearChart() {
64
75
  freezeOnceVisible: false
65
76
  })
66
77
 
78
+ // a unique id is needed for tooltips.
79
+ const tooltip_id = `cdc-open-viz-tooltip-${config.runtime.uniqueId}`
80
+
81
+ // sets the portal x/y for where tooltips should appear on the page.
82
+ const [chartPosition, setChartPosition] = useState(null)
83
+ useEffect(() => {
84
+ setChartPosition(svgRef?.current?.getBoundingClientRect())
85
+ }, [svgRef, config.legend])
86
+
87
+ // resolved an issue here with Object.entries, label no longer used.
88
+ const TooltipListItem = ({ item }) => {
89
+ const [label, value] = item
90
+
91
+ /**
92
+ * find the original series and use the name property if available
93
+ * otherwise default back to the original column name.
94
+ * @param {String} input - original columnName
95
+ * @returns user defined series name.
96
+ */
97
+ const getSeriesNameFromLabel = originalColumnName => {
98
+ let series = config.series.filter(s => s.dataKey === originalColumnName)
99
+ if (series[0]?.name) return series[0]?.name
100
+ return originalColumnName
101
+ }
102
+
103
+ if (value[0] === config.xAxis.dataKey) return <li className='tooltip-heading'>{`${capitalize(config.runtime.xAxis.label ? `${config.runtime.xAxis.label}: ` : '')} ${config.xAxis.type === 'date' ? formatDate(parseDate(value[1], false)) : value[1]}`}</li>
104
+ return <li className='tooltip-body'>{`${getSeriesNameFromLabel(value[0])}: ${formatNumber(value[1], 'left')}`}</li>
105
+ }
106
+
67
107
  const handleLeftTickFormatting = tick => {
68
108
  if (config.useLogScale && tick === 0.1) {
69
109
  //when logarithmic scale applied change value of first tick
70
110
  tick = 0
71
111
  }
72
- if (config.runtime.yAxis.type === 'date') return formatDate(parseDate(tick))
73
- if (config.orientation === 'vertical') return formatNumber(tick, 'left', shouldAbbreviate)
112
+ if (runtime.yAxis.type === 'date') return formatDate(parseDate(tick))
113
+ if (orientation === 'vertical') return formatNumber(tick, 'left', shouldAbbreviate)
74
114
  return tick
75
115
  }
76
116
 
77
117
  const handleBottomTickFormatting = tick => {
78
118
  if (config.useLogScale && tick === 0.1) {
79
- // when logaritmic scale applyed change value FIRST of tick
119
+ // when logarithmic scale applied change value FIRST of tick
80
120
  tick = 0
81
121
  }
82
- if (config.runtime.xAxis.type === 'date') return formatDate(tick)
83
- if (config.orientation === 'horizontal') return formatNumber(tick, 'left', shouldAbbreviate)
122
+ if (runtime.xAxis.type === 'date') return formatDate(tick)
123
+ if (orientation === 'horizontal') return formatNumber(tick, 'left', shouldAbbreviate)
84
124
  if (config.xAxis.type === 'continuous') return formatNumber(tick, 'bottom', shouldAbbreviate)
85
125
  return tick
86
126
  }
87
127
 
88
128
  const countNumOfTicks = axis => {
89
- const { numTicks } = config.runtime[axis]
129
+ const { numTicks } = runtime[axis]
90
130
  let tickCount = undefined
91
131
 
92
132
  if (axis === 'yAxis') {
@@ -140,27 +180,81 @@ export default function LinearChart() {
140
180
  })
141
181
  return closestX
142
182
  }
183
+
184
+ if (config.xAxis.type === 'categorical' || visualizationType === 'Combo') {
185
+ let eachBand = xScale.step()
186
+ let numerator = x
187
+ const index = Math.floor(Number(numerator) / eachBand)
188
+ return xScale.domain()[index - 1] // fixes off by 1 error
189
+ }
190
+
191
+ if (config.xAxis.type === 'date' && visualizationType !== 'Combo') {
192
+ const bisectDate = bisector(d => parseDate(d[config.xAxis.dataKey])).left
193
+ const x0 = xScale.invert(x)
194
+ const index = bisectDate(config.data, x0, 1)
195
+ const val = parseDate(config.data[index - 1][config.xAxis.dataKey])
196
+ return val
197
+ }
143
198
  }
144
199
 
145
- // import tooltip helpers
146
- const { tooltipData, showTooltip, hideTooltip } = useTooltip()
200
+ // visx tooltip hook
201
+ const { tooltipData, showTooltip, hideTooltip, tooltipOpen } = useTooltip()
202
+
203
+ /**
204
+ * handleTooltipClick - used on dashboard filters
205
+ * with visx tooltips, the handler is overwritten and we have to get the closest
206
+ * x axis value.
207
+ * TODO: move tooltip handlers to there own hook.
208
+ * @param {*} e
209
+ * @param {*} data
210
+ */
211
+ const handleTooltipClick = (e, data) => {
212
+ try {
213
+ // Get the closest x axis value from the pointer.
214
+ // After getting the closest value, return the data entry with that x scale value.
215
+ // Pass the config.visual uid (not uuid) along with that data entry to setSharedFilters
216
+ const eventSvgCoords = localPoint(e)
217
+ const { x } = eventSvgCoords
218
+ if (!x) throw new Error('COVE: no x value in handleTooltipClick.')
219
+ let closestXScaleValue = getXValueFromCoordinate(x)
220
+ if (!closestXScaleValue) throw new Error('COVE: no closest x scale value in handleTooltipClick')
221
+ let datum = config.data.filter(item => item[config.xAxis.dataKey] === closestXScaleValue)
222
+
223
+ if (setSharedFilter) {
224
+ setSharedFilter(config.uid, datum[0])
225
+ }
226
+ } catch (e) {
227
+ // eslint-disable-next-line no-console
228
+ console.error(e.message)
229
+ }
230
+ }
147
231
 
232
+ // todo: combine mouseover functions
148
233
  const handleTooltipMouseOver = (e, data) => {
149
234
  // get the svg coordinates of the mouse
150
235
  // and get the closest values
151
236
  const eventSvgCoords = localPoint(e)
152
237
  const { x, y } = eventSvgCoords
153
238
 
154
- const { runtime } = config
155
-
239
+ let yScaleValues
156
240
  let closestXScaleValue = getXValueFromCoordinate(x)
157
241
  let formattedDate = formatDate(closestXScaleValue)
158
242
 
159
- let yScaleValues
243
+ // keep track of the series.tooltip values
244
+ // and remember to push the xaxis data key on top
245
+ let includedSeries = config.series.filter(series => series.tooltip === true).map(item => item.dataKey)
246
+ includedSeries.push(config.xAxis.dataKey)
247
+
160
248
  if (xAxis.type === 'categorical') {
161
249
  yScaleValues = data.filter(d => d[xAxis.dataKey] === closestXScaleValue)
250
+ yScaleValues = yScaleValues.map(object => {
251
+ return Object.fromEntries(Object.entries(object).filter(([key, value]) => includedSeries.includes(key)))
252
+ })
162
253
  } else {
163
254
  yScaleValues = rawData.filter(d => formatDate(parseDate(d[xAxis.dataKey])) === formattedDate)
255
+ yScaleValues = yScaleValues.map(object => {
256
+ return Object.fromEntries(Object.entries(object).filter(([key, value]) => includedSeries.includes(key)))
257
+ })
164
258
  }
165
259
 
166
260
  let seriesToInclude = []
@@ -169,12 +263,12 @@ export default function LinearChart() {
169
263
 
170
264
  // loop through series for items to add to tooltip.
171
265
  // there is probably a better way of doing this.
172
- config.series?.map(s => {
266
+ config.series?.forEach(s => {
173
267
  if (s.type === 'Forecasting') {
174
268
  stageColumns.push(s.stageColumn)
175
269
 
176
270
  // greedy fn 😭
177
- s?.confidenceIntervals.map(ci => {
271
+ s?.confidenceIntervals.forEach(ci => {
178
272
  if (ci.showInTooltip === true) {
179
273
  ciItems.push(ci.low)
180
274
  ciItems.push(ci.high)
@@ -185,10 +279,28 @@ export default function LinearChart() {
185
279
 
186
280
  let standardLoopItems = []
187
281
 
188
- if (config.visualizationType === 'Combo') {
189
- standardLoopItems = [runtime.xAxis.dataKey, ...runtime?.barSeriesKeys, ...stageColumns, ...ciItems]
190
- } else {
191
- standardLoopItems = [runtime.xAxis.dataKey, ...stageColumns, ...ciItems]
282
+ if (!config.dashboard) {
283
+ switch (visualizationType) {
284
+ case 'Combo':
285
+ standardLoopItems = [runtime.xAxis.dataKey, ...runtime?.barSeriesKeys, ...runtime?.lineSeriesKeys, ...stageColumns, ...ciItems]
286
+ break
287
+ case 'Forecasting':
288
+ standardLoopItems = [runtime.xAxis.dataKey, ...stageColumns, ...ciItems]
289
+ break
290
+ case 'Line':
291
+ standardLoopItems = [runtime.xAxis.dataKey, ...runtime?.seriesKeys]
292
+ break
293
+ case 'Bar':
294
+ standardLoopItems = [runtime.xAxis.dataKey, ...runtime?.seriesKeys]
295
+ break
296
+ default:
297
+ console.info('COVE: no visualization type found in handleMouseOver')
298
+ break
299
+ }
300
+ }
301
+
302
+ if (config.dashboard) {
303
+ standardLoopItems = [runtime.xAxis.dataKey, ...runtime?.barSeriesKeys, ...runtime?.lineSeriesKeys, ...stageColumns, ...ciItems]
192
304
  }
193
305
 
194
306
  standardLoopItems.map(seriesKey => {
@@ -203,11 +315,11 @@ export default function LinearChart() {
203
315
 
204
316
  // filter out the series that aren't added to the map.
205
317
  if (!seriesToInclude) return
206
- let initialTooltipData = Object.fromEntries(seriesToInclude) ? Object.fromEntries(seriesToInclude) : {}
318
+ let initialTooltipData = seriesToInclude ? seriesToInclude : {}
207
319
 
208
320
  let tooltipData = {}
209
321
  tooltipData.data = initialTooltipData
210
- tooltipData.dataXPosition = x + 10
322
+ tooltipData.dataXPosition = isEditor ? x - 300 + 10 : x + 10
211
323
  tooltipData.dataYPosition = y
212
324
 
213
325
  let tooltipInformation = {
@@ -221,7 +333,13 @@ export default function LinearChart() {
221
333
  }
222
334
 
223
335
  const handleTooltipMouseOff = () => {
224
- hideTooltip()
336
+ if (config.visualizationType === 'Area Chart') {
337
+ setTimeout(() => {
338
+ hideTooltip()
339
+ }, 3000)
340
+ } else {
341
+ hideTooltip()
342
+ }
225
343
  }
226
344
 
227
345
  // Make sure the chart is visible if in the editor
@@ -243,13 +361,85 @@ export default function LinearChart() {
243
361
  }
244
362
  }, [dataRef?.isIntersecting, config.animate])
245
363
 
246
- const { orientation, xAxis, yAxis } = config
364
+ const chartHasTooltipGuides = () => {
365
+ const { visualizationType } = config
366
+ if (visualizationType === 'Combo' && runtime.forecastingSeriesKeys > 0) return true
367
+ if (visualizationType === 'Area Chart') return true
368
+ if (visualizationType === 'Line') return true
369
+ if (visualizationType === 'Bar') return true
370
+ return false
371
+ }
372
+
373
+ // todo: combine mouse over functions
374
+ const handleAreaTooltipMouseOver = (e, data) => {
375
+ // get the svg coordinates of the mouse
376
+ // and get the closest values
377
+ const eventSvgCoords = localPoint(e)
378
+ const { x, y } = eventSvgCoords
379
+ let closestXScaleValue = getXValueFromCoordinate(x)
380
+
381
+ let formattedDate = formatDate(closestXScaleValue)
382
+
383
+ let yScaleValues
384
+ if (config.xAxis.type === 'categorical') {
385
+ yScaleValues = data.filter(d => d[config.xAxis.dataKey] === closestXScaleValue)
386
+ } else {
387
+ yScaleValues = data.filter(d => formatDate(parseDate(d[config.xAxis.dataKey])) === formattedDate)
388
+ }
389
+
390
+ let seriesToInclude = []
391
+ let yScaleMaxValues = []
392
+ let itemsToLoop = [runtime.xAxis.dataKey, ...runtime.seriesKeys]
393
+
394
+ itemsToLoop.map(seriesKey => {
395
+ if (!seriesKey) return false
396
+ if (!yScaleValues[0]) return false
397
+ for (const item of Object.entries(yScaleValues[0])) {
398
+ if (item[0] === seriesKey) {
399
+ seriesToInclude.push(item)
400
+ }
401
+ }
402
+ })
403
+
404
+ // filter out the series that aren't added to the map.
405
+ seriesToInclude.map(series => yScaleMaxValues.push(Number(yScaleValues[0][series])))
406
+ if (!seriesToInclude) return
407
+ let tooltipDataFromSeries = seriesToInclude ? seriesToInclude : {}
408
+
409
+ let tooltipData = {}
410
+ tooltipData.data = tooltipDataFromSeries
411
+ tooltipData.dataXPosition = x + 0
412
+ tooltipData.dataYPosition = y
413
+
414
+ let tooltipInformation = {
415
+ tooltipData: tooltipData,
416
+ tooltipTop: 0,
417
+ tooltipValues: yScaleValues,
418
+ tooltipLeft: x
419
+ }
420
+ showTooltip(tooltipInformation)
421
+ }
422
+
423
+ const tooltipStyles = tooltipData => {
424
+ const { dataXPosition, dataYPosition } = tooltipData
425
+
426
+ return {
427
+ opacity: config.tooltips.opacity ? config.tooltips.opacity / 100 : 1,
428
+ position: 'absolute',
429
+ backgroundColor: 'white',
430
+ borderRadius: '4px',
431
+ transform: `translate(${dataXPosition}px, ${Number(dataYPosition)}px)`
432
+ }
433
+ }
434
+
435
+ const random = Date.now()
247
436
 
248
437
  return isNaN(width) ? (
249
- <></>
438
+ <React.Fragment></React.Fragment>
250
439
  ) : (
251
440
  <ErrorBoundary component='LinearChart'>
252
441
  <svg width={width} height={height} className={`linear ${config.animate ? 'animated' : ''} ${animatedChart && config.animate ? 'animate' : ''}`} role='img' aria-label={handleChartAriaLabels(config)} tabIndex={0} ref={svgRef}>
442
+ <Bar width={width} height={height} fill={'transparent'}></Bar>
253
443
  {/* Highlighted regions */}
254
444
  {config.regions
255
445
  ? config.regions.map(region => {
@@ -275,7 +465,7 @@ export default function LinearChart() {
275
465
  if (!to) return null
276
466
 
277
467
  return (
278
- <Group className='regions' left={Number(config.runtime.yAxis.size)} key={region.label}>
468
+ <Group className='regions' left={Number(runtime.yAxis.size)} key={region.label}>
279
469
  <path
280
470
  stroke='#333'
281
471
  d={`M${from} -5
@@ -295,10 +485,10 @@ export default function LinearChart() {
295
485
  : ''}
296
486
 
297
487
  {/* Y axis */}
298
- {config.visualizationType !== 'Spark Line' && (
299
- <AxisLeft scale={yScale} tickLength={config.useLogScale ? 6 : 8} left={Number(config.runtime.yAxis.size) - config.yAxis.axisPadding} label={config.runtime.yAxis.label} stroke='#333' tickFormat={tick => handleLeftTickFormatting(tick)} numTicks={countNumOfTicks('yAxis')}>
488
+ {visualizationType !== 'Spark Line' && (
489
+ <AxisLeft scale={yScale} tickLength={config.useLogScale ? 6 : 8} left={Number(runtime.yAxis.size) - config.yAxis.axisPadding} label={runtime.yAxis.label} stroke='#333' tickFormat={tick => handleLeftTickFormatting(tick)} numTicks={countNumOfTicks('yAxis')}>
300
490
  {props => {
301
- const axisCenter = config.runtime.horizontal ? (props.axisToPoint.y - props.axisFromPoint.y) / 2 : (props.axisFromPoint.y - props.axisToPoint.y) / 2
491
+ const axisCenter = runtime.horizontal ? (props.axisToPoint.y - props.axisFromPoint.y) / 2 : (props.axisFromPoint.y - props.axisToPoint.y) / 2
302
492
  const horizontalTickOffset = yMax / props.ticks.length / 2 - (yMax / props.ticks.length) * (1 - config.barThickness) + 5
303
493
  return (
304
494
  <Group className='left-axis'>
@@ -311,13 +501,13 @@ export default function LinearChart() {
311
501
 
312
502
  return (
313
503
  <Group key={`vx-tick-${tick.value}-${i}`} className={'vx-axis-tick'}>
314
- {!config.runtime.yAxis.hideTicks && <Line from={tick.from} to={config.useLogScale ? to : tick.to} stroke={config.yAxis.tickColor} display={config.runtime.horizontal ? 'none' : 'block'} />}
504
+ {!runtime.yAxis.hideTicks && <Line key={`${tick.value}--hide-hideTicks`} from={tick.from} to={config.useLogScale ? to : tick.to} stroke={config.yAxis.tickColor} display={runtime.horizontal ? 'none' : 'block'} />}
315
505
 
316
- {config.runtime.yAxis.gridLines ? <Line display={config.useLogScale && showTicks} from={{ x: tick.from.x + xMax, y: tick.from.y }} to={tick.from} stroke='rgba(0,0,0,0.3)' /> : ''}
506
+ {runtime.yAxis.gridLines ? <Line key={`${tick.value}--hide-hideGridLines`} display={config.useLogScale && showTicks} from={{ x: tick.from.x + xMax, y: tick.from.y }} to={tick.from} stroke='rgba(0,0,0,0.3)' /> : ''}
317
507
 
318
- {config.orientation === 'horizontal' && config.visualizationSubType !== 'stacked' && config.yAxis.labelPlacement === 'On Date/Category Axis' && !config.yAxis.hideLabel && (
508
+ {orientation === 'horizontal' && visualizationSubType !== 'stacked' && config.yAxis.labelPlacement === 'On Date/Category Axis' && !config.yAxis.hideLabel && (
319
509
  <Text
320
- transform={`translate(${tick.to.x - 5}, ${config.isLollipopChart ? tick.to.y - minY : tick.to.y - minY + (Number(config.barHeight * config.series.length) - barMinHeight) / 2}) rotate(-${config.runtime.horizontal ? config.runtime.yAxis.tickRotation : 0})`}
510
+ transform={`translate(${tick.to.x - 5}, ${config.isLollipopChart ? tick.to.y - minY : tick.to.y - minY + (Number(config.barHeight * config.series.length) - barMinHeight) / 2}) rotate(-${config.runtime.horizontal ? config.runtime.yAxis.tickRotation || 0 : 0})`}
321
511
  verticalAnchor={'start'}
322
512
  textAnchor={'end'}
323
513
  >
@@ -325,29 +515,30 @@ export default function LinearChart() {
325
515
  </Text>
326
516
  )}
327
517
 
328
- {config.orientation === 'horizontal' && config.visualizationSubType === 'stacked' && config.yAxis.labelPlacement === 'On Date/Category Axis' && !config.yAxis.hideLabel && (
329
- <Text transform={`translate(${tick.to.x - 5}, ${tick.to.y - minY + (Number(config.barHeight) - barMinHeight) / 2}) rotate(-${config.runtime.horizontal ? config.runtime.yAxis.tickRotation : 0})`} verticalAnchor={'start'} textAnchor={'end'}>
518
+ {orientation === 'horizontal' && visualizationSubType === 'stacked' && config.yAxis.labelPlacement === 'On Date/Category Axis' && !config.yAxis.hideLabel && (
519
+ <Text transform={`translate(${tick.to.x - 5}, ${tick.to.y - minY + (Number(config.barHeight) - barMinHeight) / 2}) rotate(-${runtime.horizontal ? runtime.yAxis.tickRotation : 0})`} verticalAnchor={'start'} textAnchor={'end'}>
330
520
  {tick.formattedValue}
331
521
  </Text>
332
522
  )}
333
523
 
334
- {config.orientation === 'horizontal' && config.visualizationType === 'Paired Bar' && !config.yAxis.hideLabel && (
335
- <Text transform={`translate(${tick.to.x - 5}, ${tick.to.y - minY + Number(config.barHeight) / 2}) rotate(-${config.runtime.horizontal ? config.runtime.yAxis.tickRotation : 0})`} textAnchor={'end'} verticalAnchor='middle'>
524
+ {orientation === 'horizontal' && visualizationType === 'Paired Bar' && !config.yAxis.hideLabel && (
525
+ <Text transform={`translate(${tick.to.x - 5}, ${tick.to.y - minY + Number(config.barHeight) / 2}) rotate(-${runtime.horizontal ? runtime.yAxis.tickRotation : 0})`} textAnchor={'end'} verticalAnchor='middle'>
336
526
  {tick.formattedValue}
337
527
  </Text>
338
528
  )}
339
- {config.orientation === 'horizontal' && config.visualizationType === 'Deviation Bar' && !config.yAxis.hideLabel && (
340
- <Text transform={`translate(${tick.to.x - 5}, ${config.isLollipopChart ? tick.to.y - minY + 2 : tick.to.y - minY + Number(config.barHeight) / 2}) rotate(-${config.runtime.horizontal ? config.runtime.yAxis.tickRotation : 0})`} textAnchor={'end'} verticalAnchor='middle'>
529
+ {orientation === 'horizontal' && visualizationType === 'Deviation Bar' && !config.yAxis.hideLabel && (
530
+ <Text transform={`translate(${tick.to.x - 5}, ${config.isLollipopChart ? tick.to.y - minY + 2 : tick.to.y - minY + Number(config.barHeight) / 2}) rotate(-${runtime.horizontal ? runtime.yAxis.tickRotation : 0})`} textAnchor={'end'} verticalAnchor='middle'>
341
531
  {tick.formattedValue}
342
532
  </Text>
343
533
  )}
344
534
 
345
- {config.orientation === 'vertical' && config.visualizationType !== 'Paired Bar' && !config.yAxis.hideLabel && (
535
+ {orientation === 'vertical' && visualizationType !== 'Paired Bar' && !config.yAxis.hideLabel && (
346
536
  <Text
347
537
  display={config.useLogScale ? showTicks : 'block'}
348
538
  dx={config.useLogScale ? -6 : 0}
349
539
  x={config.runtime.horizontal ? tick.from.x + 2 : tick.to.x}
350
540
  y={tick.to.y + (config.runtime.horizontal ? horizontalTickOffset : 0)}
541
+ angle={-Number(config.yAxis.tickRotation) || 0}
351
542
  verticalAnchor={config.runtime.horizontal ? 'start' : 'middle'}
352
543
  textAnchor={config.runtime.horizontal ? 'start' : 'end'}
353
544
  fill={config.yAxis.tickLabelColor}
@@ -358,10 +549,10 @@ export default function LinearChart() {
358
549
  </Group>
359
550
  )
360
551
  })}
361
- {!config.yAxis.hideAxis && <Line from={props.axisFromPoint} to={config.runtime.horizontal ? { x: 0, y: Number(heightHorizontal) } : props.axisToPoint} stroke='#000' />}
552
+ {!config.yAxis.hideAxis && <Line from={props.axisFromPoint} to={runtime.horizontal ? { x: 0, y: Number(heightHorizontal) } : props.axisToPoint} stroke='#000' />}
362
553
  {yScale.domain()[0] < 0 && <Line from={{ x: props.axisFromPoint.x, y: yScale(0) }} to={{ x: xMax, y: yScale(0) }} stroke='#333' />}
363
- {config.visualizationType === 'Bar' && config.orientation === 'horizontal' && xScale.domain()[0] < 0 && <Line from={{ x: xScale(0), y: 0 }} to={{ x: xScale(0), y: yMax }} stroke='#333' strokeWidth={2} />}
364
- <Text className='y-label' textAnchor='middle' verticalAnchor='start' transform={`translate(${-1 * config.runtime.yAxis.size}, ${axisCenter}) rotate(-90)`} fontWeight='bold' fill={config.yAxis.labelColor}>
554
+ {visualizationType === 'Bar' && orientation === 'horizontal' && xScale.domain()[0] < 0 && <Line from={{ x: xScale(0), y: 0 }} to={{ x: xScale(0), y: yMax }} stroke='#333' strokeWidth={2} />}
555
+ <Text className='y-label' textAnchor='middle' verticalAnchor='start' transform={`translate(${-1 * runtime.yAxis.size}, ${axisCenter}) rotate(-90)`} fontWeight='bold' fill={config.yAxis.labelColor}>
365
556
  {props.label}
366
557
  </Text>
367
558
  </Group>
@@ -372,21 +563,21 @@ export default function LinearChart() {
372
563
 
373
564
  {/* Right Axis */}
374
565
  {hasRightAxis && (
375
- <AxisRight scale={yScaleRight} left={Number(width - config.yAxis.rightAxisSize)} label={config.yAxis.rightLabel} tickFormat={tick => formatNumber(tick, 'right')} numTicks={config.runtime.yAxis.rightNumTicks || undefined} labelOffset={45}>
566
+ <AxisRight scale={yScaleRight} left={Number(width - config.yAxis.rightAxisSize)} label={config.yAxis.rightLabel} tickFormat={tick => formatNumber(tick, 'right')} numTicks={runtime.yAxis.rightNumTicks || undefined} labelOffset={45}>
376
567
  {props => {
377
- const axisCenter = config.runtime.horizontal ? (props.axisToPoint.y - props.axisFromPoint.y) / 2 : (props.axisFromPoint.y - props.axisToPoint.y) / 2
568
+ const axisCenter = runtime.horizontal ? (props.axisToPoint.y - props.axisFromPoint.y) / 2 : (props.axisFromPoint.y - props.axisToPoint.y) / 2
378
569
  const horizontalTickOffset = yMax / props.ticks.length / 2 - (yMax / props.ticks.length) * (1 - config.barThickness) + 5
379
570
  return (
380
571
  <Group className='right-axis'>
381
572
  {props.ticks.map((tick, i) => {
382
573
  return (
383
574
  <Group key={`vx-tick-${tick.value}-${i}`} className='vx-axis-tick'>
384
- {!config.runtime.yAxis.rightHideTicks && <Line from={tick.from} to={tick.to} display={config.runtime.horizontal ? 'none' : 'block'} stroke={config.yAxis.rightAxisTickColor} />}
575
+ {!runtime.yAxis.rightHideTicks && <Line from={tick.from} to={tick.to} display={runtime.horizontal ? 'none' : 'block'} stroke={config.yAxis.rightAxisTickColor} />}
385
576
 
386
- {config.runtime.yAxis.rightGridLines ? <Line from={{ x: tick.from.x + xMax, y: tick.from.y }} to={tick.from} stroke='rgba(0,0,0,0.3)' /> : ''}
577
+ {runtime.yAxis.rightGridLines ? <Line from={{ x: tick.from.x + xMax, y: tick.from.y }} to={tick.from} stroke='rgba(0,0,0,0.3)' /> : ''}
387
578
 
388
579
  {!config.yAxis.rightHideLabel && (
389
- <Text x={tick.to.x} y={tick.to.y + (config.runtime.horizontal ? horizontalTickOffset : 0)} verticalAnchor={config.runtime.horizontal ? 'start' : 'middle'} textAnchor={'start'} fill={config.yAxis.rightAxisTickLabelColor}>
580
+ <Text x={tick.to.x} y={tick.to.y + (runtime.horizontal ? horizontalTickOffset : 0)} verticalAnchor={runtime.horizontal ? 'start' : 'middle'} textAnchor={'start'} fill={config.yAxis.rightAxisTickLabelColor}>
390
581
  {tick.formattedValue}
391
582
  </Text>
392
583
  )}
@@ -406,7 +597,7 @@ export default function LinearChart() {
406
597
  {hasTopAxis && config.topAxis.hasLine && (
407
598
  <AxisTop
408
599
  stroke='#333'
409
- left={Number(config.runtime.yAxis.size)}
600
+ left={Number(runtime.yAxis.size)}
410
601
  scale={xScale}
411
602
  hideTicks
412
603
  hideZero
@@ -417,39 +608,83 @@ export default function LinearChart() {
417
608
  )}
418
609
 
419
610
  {/* X axis */}
420
- {config.visualizationType !== 'Paired Bar' && config.visualizationType !== 'Spark Line' && (
611
+ {visualizationType !== 'Paired Bar' && visualizationType !== 'Spark Line' && (
421
612
  <AxisBottom
422
- top={config.runtime.horizontal ? Number(heightHorizontal) + Number(config.xAxis.axisPadding) : yMax + Number(config.xAxis.axisPadding)}
423
- left={Number(config.runtime.yAxis.size)}
424
- label={config.runtime.xAxis.label}
613
+ top={runtime.horizontal ? Number(heightHorizontal) + Number(config.xAxis.axisPadding) : yMax + Number(config.xAxis.axisPadding)}
614
+ left={Number(runtime.yAxis.size)}
615
+ label={runtime.xAxis.label}
425
616
  tickFormat={handleBottomTickFormatting}
426
617
  scale={xScale}
427
618
  stroke='#333'
428
- tickStroke='#333'
429
619
  numTicks={countNumOfTicks('xAxis')}
620
+ tickStroke='#333'
430
621
  >
431
622
  {props => {
432
623
  const axisCenter = (props.axisToPoint.x - props.axisFromPoint.x) / 2
624
+ const containsMultipleWords = inputString => /\s/.test(inputString)
625
+ const ismultiLabel = props.ticks.some(tick => containsMultipleWords(tick.value))
626
+
627
+ // Calculate sumOfTickWidth here, before map function
628
+ const fontSize = { small: 16, medium: 18, large: 20 }
629
+ const defaultTickLength = 8
630
+ const tickWidthMax = Math.max(...props.ticks.map(tick => getTextWidth(tick.formattedValue, `normal ${fontSize[config.fontSize]}px sans-serif`)))
631
+ const marginTop = 20
632
+ const accumulator = ismultiLabel ? 180 : 100
633
+
634
+ const textWidths = props.ticks.map(tick => getTextWidth(tick.formattedValue, `normal ${fontSize[config.fontSize]}px sans-serif`))
635
+ const sumOfTickWidth = textWidths.reduce((a, b) => a + b, accumulator)
636
+ const spaceBetweenEachTick = (xMax - sumOfTickWidth) / (props.ticks.length - 1)
637
+
638
+ // Check if ticks are overlapping
639
+ // Determine the position of each tick
640
+ let positions = [0] // The first tick is at position 0
641
+ for (let i = 1; i < textWidths.length; i++) {
642
+ // The position of each subsequent tick is the position of the previous tick
643
+ // plus the width of the previous tick and the space
644
+ positions[i] = positions[i - 1] + textWidths[i - 1] + spaceBetweenEachTick
645
+ }
646
+
647
+ // Check if ticks are overlapping
648
+ let areTicksTouching = false
649
+ textWidths.forEach((_, i) => {
650
+ if (positions[i] + textWidths[i] > positions[i + 1]) {
651
+ areTicksTouching = true
652
+ return
653
+ }
654
+ })
655
+
656
+ const dynamicMarginTop = areTicksTouching && config.isResponsiveTicks ? tickWidthMax + defaultTickLength + marginTop : 0
657
+ config.dynamicMarginTop = dynamicMarginTop
658
+ // config.xAxis.size = dynamicMarginTop
433
659
  return (
434
660
  <Group className='bottom-axis'>
435
- {props.ticks.map((tick, i) => {
661
+ {props.ticks.map((tick, i, propsTicks) => {
436
662
  // when using LogScale show major ticks values only
437
663
  const showTick = String(tick.value).startsWith('1') || tick.value === 0.1 ? 'block' : 'none'
438
- const tickWidth = xMax / props.ticks.length
439
- const tickLength = showTick === 'block' ? 16 : 8
664
+ const tickLength = showTick === 'block' ? 16 : defaultTickLength
440
665
  const to = { x: tick.to.x, y: tickLength }
666
+ let textWidth = getTextWidth(tick.formattedValue, `normal ${fontSize[config.fontSize]}px sans-serif`)
667
+ let limitedWidth = 100 / propsTicks.length
668
+ //reset rotations by updating config
669
+ config.yAxis.tickRotation = config.isResponsiveTicks && config.orientation === 'horizontal' ? 0 : config.yAxis.tickRotation
670
+ config.xAxis.tickRotation = config.isResponsiveTicks && config.orientation === 'vertical' ? 0 : config.xAxis.tickRotation
671
+ //configure rotation
672
+
673
+ const tickRotation = config.isResponsiveTicks && areTicksTouching ? -Number(config.xAxis.maxTickRotation) || -90 : -Number(config.runtime.xAxis.tickRotation)
441
674
 
442
675
  return (
443
676
  <Group key={`vx-tick-${tick.value}-${i}`} className={'vx-axis-tick'}>
444
- {!config.xAxis.hideTicks && <Line from={tick.from} to={config.orientation === 'horizontal' && config.useLogScale ? to : tick.to} stroke={config.xAxis.tickColor} strokeWidth={showTick === 'block' ? 1.3 : 1} />}
677
+ {!config.xAxis.hideTicks && <Line from={tick.from} to={orientation === 'horizontal' && config.useLogScale ? to : tick.to} stroke={config.xAxis.tickColor} strokeWidth={showTick === 'block' ? 1.3 : 1} />}
445
678
  {!config.xAxis.hideLabel && (
446
679
  <Text
447
680
  dy={config.orientation === 'horizontal' && config.useLogScale ? 8 : 0}
448
681
  display={config.orientation === 'horizontal' && config.useLogScale ? showTick : 'block'}
449
- transform={`translate(${tick.to.x}, ${tick.to.y}) rotate(-${!config.runtime.horizontal ? config.runtime.xAxis.tickRotation : 0})`}
450
- verticalAnchor='start'
451
- textAnchor={config.runtime.xAxis.tickRotation && config.runtime.xAxis.tickRotation !== '0' ? 'end' : 'middle'}
452
- width={config.runtime.xAxis.tickRotation && config.runtime.xAxis.tickRotation !== '0' ? undefined : tickWidth}
682
+ x={tick.to.x}
683
+ y={tick.to.y}
684
+ angle={tickRotation}
685
+ verticalAnchor={tickRotation < -50 ? 'middle' : 'start'}
686
+ textAnchor={tickRotation ? 'end' : 'middle'}
687
+ width={areTicksTouching && !config.isResponsiveTicks && !config.xAxis.tickRotation ? limitedWidth : textWidth}
453
688
  fill={config.xAxis.tickLabelColor}
454
689
  >
455
690
  {tick.formattedValue}
@@ -459,7 +694,7 @@ export default function LinearChart() {
459
694
  )
460
695
  })}
461
696
  {!config.xAxis.hideAxis && <Line from={props.axisFromPoint} to={props.axisToPoint} stroke='#333' />}
462
- <Text x={axisCenter} y={config.orientation === 'horizontal' ? config.xAxis.labelOffset : config.xAxis.size} textAnchor='middle' fontWeight='bold' fill={config.xAxis.labelColor}>
697
+ <Text x={axisCenter} y={config.orientation === 'horizontal' ? dynamicMarginTop || config.xAxis.labelOffset : dynamicMarginTop || config.xAxis.size} textAnchor='middle' fontWeight='bold' fill={config.xAxis.labelColor}>
463
698
  {props.label}
464
699
  </Text>
465
700
  </Group>
@@ -468,9 +703,9 @@ export default function LinearChart() {
468
703
  </AxisBottom>
469
704
  )}
470
705
 
471
- {config.visualizationType === 'Paired Bar' && (
706
+ {visualizationType === 'Paired Bar' && (
472
707
  <>
473
- <AxisBottom top={yMax} left={Number(config.runtime.yAxis.size)} label={config.runtime.xAxis.label} tickFormat={config.runtime.xAxis.type === 'date' ? formatDate : formatNumber} scale={g1xScale} stroke='#333' tickStroke='#333' numTicks={config.runtime.xAxis.numTicks || undefined}>
708
+ <AxisBottom top={yMax} left={Number(runtime.yAxis.size)} label={runtime.xAxis.label} tickFormat={runtime.xAxis.type === 'date' ? formatDate : formatNumber} scale={g1xScale} stroke='#333' tickStroke='#333' numTicks={runtime.xAxis.numTicks || undefined}>
474
709
  {props => {
475
710
  return (
476
711
  <Group className='bottom-axis'>
@@ -479,8 +714,8 @@ export default function LinearChart() {
479
714
  const textAnchor = tick.index !== 0 && config.yAxis.tickRotation && config.yAxis.tickRotation > 0 ? 'end' : 'middle'
480
715
  return (
481
716
  <Group key={`vx-tick-${tick.value}-${i}`} className={'vx-axis-tick'}>
482
- {!config.runtime.yAxis.hideTicks && <Line from={tick.from} to={tick.to} stroke='#333' />}
483
- {!config.runtime.yAxis.hideLabel && (
717
+ {!runtime.yAxis.hideTicks && <Line from={tick.from} to={tick.to} stroke='#333' />}
718
+ {!runtime.yAxis.hideLabel && (
484
719
  <Text x={tick.to.x} y={tick.to.y} angle={-angle} verticalAnchor='start' textAnchor={textAnchor}>
485
720
  {formatNumber(tick.value, 'left')}
486
721
  </Text>
@@ -488,20 +723,20 @@ export default function LinearChart() {
488
723
  </Group>
489
724
  )
490
725
  })}
491
- {!config.runtime.yAxis.hideAxis && <Line from={props.axisFromPoint} to={props.axisToPoint} stroke='#333' />}
726
+ {!runtime.yAxis.hideAxis && <Line from={props.axisFromPoint} to={props.axisToPoint} stroke='#333' />}
492
727
  </Group>
493
728
  )
494
729
  }}
495
730
  </AxisBottom>
496
731
  <AxisBottom
497
732
  top={yMax}
498
- left={Number(config.runtime.yAxis.size)}
499
- label={config.runtime.xAxis.label}
500
- tickFormat={config.runtime.xAxis.type === 'date' ? formatDate : config.runtime.xAxis.dataKey !== 'Year' ? formatNumber : tick => tick}
733
+ left={Number(runtime.yAxis.size)}
734
+ label={runtime.xAxis.label}
735
+ tickFormat={runtime.xAxis.type === 'date' ? formatDate : runtime.xAxis.dataKey !== 'Year' ? formatNumber : tick => tick}
501
736
  scale={g2xScale}
502
737
  stroke='#333'
503
738
  tickStroke='#333'
504
- numTicks={config.runtime.xAxis.numTicks || undefined}
739
+ numTicks={runtime.xAxis.numTicks || undefined}
505
740
  >
506
741
  {props => {
507
742
  return (
@@ -512,8 +747,8 @@ export default function LinearChart() {
512
747
  const textAnchor = tick.index !== 0 && config.yAxis.tickRotation && config.yAxis.tickRotation > 0 ? 'end' : 'middle'
513
748
  return (
514
749
  <Group key={`vx-tick-${tick.value}-${i}`} className={'vx-axis-tick'}>
515
- {!config.runtime.yAxis.hideTicks && <Line from={tick.from} to={tick.to} stroke='#333' />}
516
- {!config.runtime.yAxis.hideLabel && (
750
+ {!runtime.yAxis.hideTicks && <Line from={tick.from} to={tick.to} stroke='#333' />}
751
+ {!runtime.yAxis.hideLabel && (
517
752
  <Text x={tick.to.x} y={tick.to.y} angle={-angle} verticalAnchor='start' textAnchor={textAnchor}>
518
753
  {formatNumber(tick.value, 'left')}
519
754
  </Text>
@@ -521,11 +756,11 @@ export default function LinearChart() {
521
756
  </Group>
522
757
  )
523
758
  })}
524
- {!config.runtime.yAxis.hideAxis && <Line from={props.axisFromPoint} to={props.axisToPoint} stroke='#333' />}
759
+ {!runtime.yAxis.hideAxis && <Line from={props.axisFromPoint} to={props.axisToPoint} stroke='#333' />}
525
760
  </Group>
526
761
  <Group>
527
762
  <Text x={xMax / 2} y={config.xAxis.labelOffset} stroke='#333' textAnchor={'middle'} verticalAnchor='start'>
528
- {config.runtime.xAxis.label}
763
+ {runtime.xAxis.label}
529
764
  </Text>
530
765
  </Group>
531
766
  </>
@@ -534,16 +769,51 @@ export default function LinearChart() {
534
769
  </AxisBottom>
535
770
  </>
536
771
  )}
537
- {config.visualizationType === 'Deviation Bar' && <DeviationBar xScale={xScale} yScale={yScale} width={xMax} height={yMax} />}
538
- {config.visualizationType === 'Paired Bar' && <PairedBarChart originalWidth={width} width={xMax} height={yMax} />}
539
- {config.visualizationType === 'Scatter Plot' && <CoveScatterPlot xScale={xScale} yScale={yScale} getXAxisData={getXAxisData} getYAxisData={getYAxisData} />}
540
- {config.visualizationType === 'Box Plot' && <CoveBoxPlot xScale={xScale} yScale={yScale} />}
541
- {(config.visualizationType === 'Area Chart' || config.visualizationType === 'Combo') && <CoveAreaChart xScale={xScale} yScale={yScale} yMax={yMax} xMax={xMax} chartRef={svgRef} />}
542
- {(config.visualizationType === 'Bar' || config.visualizationType === 'Combo') && <BarChart xScale={xScale} yScale={yScale} seriesScale={seriesScale} xMax={xMax} yMax={yMax} getXAxisData={getXAxisData} getYAxisData={getYAxisData} animatedChart={animatedChart} visible={animatedChart} />}
543
- {(config.visualizationType === 'Line' || config.visualizationType === 'Combo') && <LineChart xScale={xScale} yScale={yScale} getXAxisData={getXAxisData} getYAxisData={getYAxisData} xMax={xMax} yMax={yMax} seriesStyle={config.series} />}
544
- {(config.visualizationType === 'Forecasting' || config.visualizationType === 'Combo') && (
772
+ {visualizationType === 'Deviation Bar' && <DeviationBar xScale={xScale} yScale={yScale} width={xMax} height={yMax} />}
773
+ {visualizationType === 'Paired Bar' && <PairedBarChart originalWidth={width} width={xMax} height={yMax} />}
774
+ {visualizationType === 'Scatter Plot' && <CoveScatterPlot xScale={xScale} yScale={yScale} getXAxisData={getXAxisData} getYAxisData={getYAxisData} />}
775
+ {visualizationType === 'Box Plot' && <CoveBoxPlot xScale={xScale} yScale={yScale} />}
776
+ {(visualizationType === 'Area Chart' || visualizationType === 'Combo') && (
777
+ <AreaChart xScale={xScale} yScale={yScale} yMax={yMax} xMax={xMax} chartRef={svgRef} width={xMax} height={yMax} handleTooltipMouseOver={handleAreaTooltipMouseOver} handleTooltipMouseOff={handleTooltipMouseOff} showTooltip={showTooltip} tooltipData={tooltipData} />
778
+ )}
779
+ {(visualizationType === 'Bar' || visualizationType === 'Combo') && (
780
+ <BarChart
781
+ xScale={xScale}
782
+ yScale={yScale}
783
+ seriesScale={seriesScale}
784
+ xMax={xMax}
785
+ yMax={yMax}
786
+ getXAxisData={getXAxisData}
787
+ getYAxisData={getYAxisData}
788
+ animatedChart={animatedChart}
789
+ visible={animatedChart}
790
+ handleTooltipMouseOver={handleTooltipMouseOver}
791
+ handleTooltipMouseOff={handleTooltipMouseOff}
792
+ handleTooltipClick={handleTooltipClick}
793
+ tooltipData={tooltipData}
794
+ showTooltip={showTooltip}
795
+ chartRef={svgRef}
796
+ />
797
+ )}
798
+ {(visualizationType === 'Line' || visualizationType === 'Combo') && (
799
+ <LineChart
800
+ xScale={xScale}
801
+ yScale={yScale}
802
+ getXAxisData={getXAxisData}
803
+ getYAxisData={getYAxisData}
804
+ xMax={xMax}
805
+ yMax={yMax}
806
+ seriesStyle={config.series}
807
+ handleTooltipMouseOver={handleTooltipMouseOver}
808
+ handleTooltipMouseOff={handleTooltipMouseOff}
809
+ handleTooltipClick={handleTooltipClick}
810
+ tooltipData={tooltipData}
811
+ showTooltip={showTooltip}
812
+ chartRef={svgRef}
813
+ />
814
+ )}
815
+ {(visualizationType === 'Forecasting' || visualizationType === 'Combo') && (
545
816
  <Forecasting
546
- hideTooltip={hideTooltip}
547
817
  showTooltip={showTooltip}
548
818
  tooltipData={tooltipData}
549
819
  xScale={xScale}
@@ -558,49 +828,40 @@ export default function LinearChart() {
558
828
  />
559
829
  )}
560
830
 
561
- {/* y anchors */}
562
- {config.yAxis.anchors &&
563
- config.yAxis.anchors.map(anchor => {
564
- return <Line strokeDasharray={handleLineType(anchor.lineStyle)} stroke='rgba(0,0,0,1)' className='customAnchor' from={{ x: 0 + config.yAxis.size, y: yScale(anchor.value) }} to={{ x: xMax, y: yScale(anchor.value) }} display={config.runtime.horizontal ? 'none' : 'block'} />
565
- })}
566
-
567
831
  {/* Line chart */}
568
832
  {/* TODO: Make this just line or combo? */}
569
- {config.visualizationType !== 'Bar' &&
570
- config.visualizationType !== 'Paired Bar' &&
571
- config.visualizationType !== 'Box Plot' &&
572
- config.visualizationType !== 'Area Chart' &&
573
- config.visualizationType !== 'Scatter Plot' &&
574
- config.visualizationType !== 'Deviation Bar' &&
575
- config.visualizationType !== 'Forecasting' && (
576
- <>
577
- <LineChart xScale={xScale} yScale={yScale} getXAxisData={getXAxisData} getYAxisData={getYAxisData} xMax={xMax} yMax={yMax} seriesStyle={config.series} />
578
- </>
579
- )}
833
+ {visualizationType !== 'Bar' && visualizationType !== 'Paired Bar' && visualizationType !== 'Box Plot' && visualizationType !== 'Area Chart' && visualizationType !== 'Scatter Plot' && visualizationType !== 'Deviation Bar' && visualizationType !== 'Forecasting' && (
834
+ <>
835
+ <LineChart xScale={xScale} yScale={yScale} getXAxisData={getXAxisData} getYAxisData={getYAxisData} xMax={xMax} yMax={yMax} seriesStyle={config.series} />
836
+ </>
837
+ )}
580
838
 
581
839
  {/* y anchors */}
582
840
  {config.yAxis.anchors &&
583
- config.yAxis.anchors.map(anchor => {
841
+ config.yAxis.anchors.map((anchor, index) => {
584
842
  let anchorPosition = yScale(anchor.value)
585
- const padding = config.orientation === 'horizontal' ? Number(config.xAxis.size) : Number(config.yAxis.size)
586
- const middleOffset = config.orientation === 'horizontal' && config.visualizationType === 'Bar' ? config.barHeight / 4 : 0
843
+ if (!anchor.value) return
844
+ const padding = orientation === 'horizontal' ? Number(config.xAxis.size) : Number(config.yAxis.size)
845
+ const middleOffset = orientation === 'horizontal' && visualizationType === 'Bar' ? config.barHeight / 4 : 0
846
+
847
+ if (!anchorPosition) return
587
848
 
588
849
  return (
589
850
  // prettier-ignore
590
851
  <Line
591
- key={anchor.value}
852
+ key={`yAxis-${anchor.value}--${index}`}
592
853
  strokeDasharray={handleLineType(anchor.lineStyle)}
593
854
  stroke={anchor.color ? anchor.color : 'rgba(0,0,0,1)'}
594
855
  className='anchor-y'
595
856
  from={{ x: 0 + padding, y: anchorPosition - middleOffset}}
596
- to={{ x: width, y: anchorPosition - middleOffset }}
857
+ to={{ x: width - config.yAxis.rightAxisSize, y: anchorPosition - middleOffset }}
597
858
  />
598
859
  )
599
860
  })}
600
861
 
601
862
  {/* x anchors */}
602
863
  {config.xAxis.anchors &&
603
- config.xAxis.anchors.map(anchor => {
864
+ config.xAxis.anchors.map((anchor, index) => {
604
865
  let newX = xAxis
605
866
  if (orientation === 'horizontal') {
606
867
  newX = yAxis
@@ -610,10 +871,12 @@ export default function LinearChart() {
610
871
 
611
872
  const padding = orientation === 'horizontal' ? Number(config.xAxis.size) : Number(config.yAxis.size)
612
873
 
874
+ if (!anchorPosition) return
875
+
613
876
  return (
614
877
  // prettier-ignore
615
878
  <Line
616
- key={anchor.value}
879
+ key={`xAxis-${anchor.value}--${index}`}
617
880
  strokeDasharray={handleLineType(anchor.lineStyle)}
618
881
  stroke={anchor.color ? anchor.color : 'rgba(0,0,0,1)'}
619
882
  fill={anchor.color ? anchor.color : 'rgba(0,0,0,1)'}
@@ -623,8 +886,27 @@ export default function LinearChart() {
623
886
  />
624
887
  )
625
888
  })}
889
+
890
+ {chartHasTooltipGuides && showTooltip && tooltipData && config.visual.verticalHoverLine && (
891
+ <Group key='tooltipLine-vertical' className='vertical-tooltip-line'>
892
+ <Line from={{ x: tooltipData.dataXPosition, y: 0 }} to={{ x: tooltipData.dataXPosition, y: yMax }} stroke={'black'} strokeWidth={1} pointerEvents='none' strokeDasharray='5,5' className='vertical-tooltip-line' />
893
+ </Group>
894
+ )}
895
+
896
+ {chartHasTooltipGuides && showTooltip && tooltipData && config.visual.horizontalHoverLine && (
897
+ <Group key='tooltipLine-horizontal' className='horizontal-tooltip-line' left={config.yAxis.size ? config.yAxis.size : 0}>
898
+ <Line from={{ x: 0, y: tooltipData.dataYPosition }} to={{ x: xMax, y: tooltipData.dataYPosition }} stroke={'black'} strokeWidth={1} pointerEvents='none' strokeDasharray='5,5' className='horizontal-tooltip-line' />
899
+ </Group>
900
+ )}
626
901
  </svg>
627
- <ReactTooltip id={`cdc-open-viz-tooltip-${config.runtime.uniqueId}`} variant='light' arrowColor='rgba(0,0,0,0)' className='tooltip' />
902
+ {tooltipData && Object.entries(tooltipData.data).length > 0 && tooltipOpen && showTooltip && tooltipData.dataYPosition && tooltipData.dataXPosition && (
903
+ <TooltipWithBounds key={Math.random()} className={'tooltip cdc-open-viz-module'} style={tooltipStyles(tooltipData)} width={width}>
904
+ <ul>{typeof tooltipData === 'object' && Object.entries(tooltipData.data).map((item, index) => <TooltipListItem item={item} key={index} />)}</ul>
905
+ </TooltipWithBounds>
906
+ )}
907
+ {(config.orientation === 'horizontal' || config.visualizationType === 'Scatter Plot' || config.visualizationType === 'Box Plot') && (
908
+ <ReactTooltip id={`cdc-open-viz-tooltip-${runtime.uniqueId}`} variant='light' arrowColor='rgba(0,0,0,0)' className='tooltip' style={{ background: `rgba(255,255,255, ${config.tooltips.opacity / 100})`, color: 'black' }} />
909
+ )}
628
910
  <div className='animation-trigger' ref={triggerRef} />
629
911
  </ErrorBoundary>
630
912
  )