@cdc/chart 4.23.5 → 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,58 +1,72 @@
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
 
3
+ // Libraries
4
+ import { AxisLeft, AxisBottom, AxisRight, AxisTop } from '@visx/axis'
5
+ import { bisector } from 'd3-array'
4
6
  import { Group } from '@visx/group'
5
- import { Line } from '@visx/shape'
7
+ import { Line, Bar } from '@visx/shape'
8
+ import { localPoint } from '@visx/event'
6
9
  import { Text } from '@visx/text'
7
- import { AxisLeft, AxisBottom, AxisRight, AxisTop } from '@visx/axis'
10
+ import { Tooltip as ReactTooltip } from 'react-tooltip'
11
+ import { useTooltip, TooltipWithBounds, useTooltipInPortal } from '@visx/tooltip'
8
12
 
13
+ // CDC Components
14
+ import AreaChart from './AreaChart'
9
15
  import BarChart from './BarChart'
10
16
  import ConfigContext from '../ConfigContext'
11
- import CoveAreaChart from './AreaChart'
12
17
  import CoveBoxPlot from './BoxPlot'
13
18
  import CoveScatterPlot from './ScatterPlot'
14
19
  import DeviationBar from './DeviationBar'
20
+ import ErrorBoundary from '@cdc/core/components/ErrorBoundary'
21
+ import Forecasting from './Forecasting'
15
22
  import LineChart from './LineChart'
16
23
  import PairedBarChart from './PairedBarChart'
17
24
  import useIntersectionObserver from './useIntersectionObserver'
18
25
 
19
- import ErrorBoundary from '@cdc/core/components/ErrorBoundary'
20
- import '../scss/LinearChart.scss'
21
- import useReduceData from '../hooks/useReduceData'
22
- import useScales from '../hooks/useScales'
26
+ // Hooks
23
27
  import useMinMax from '../hooks/useMinMax'
28
+ import useReduceData from '../hooks/useReduceData'
24
29
  import useRightAxis from '../hooks/useRightAxis'
30
+ import useScales from '../hooks/useScales'
25
31
  import useTopAxis from '../hooks/useTopAxis'
26
32
 
33
+ // styles
34
+ import { defaultStyles } from '@visx/tooltip'
35
+ import '../scss/LinearChart.scss'
36
+
27
37
  export default function LinearChart() {
28
- const { transformedData: data, dimensions, config, parseDate, formatDate, currentViewport, formatNumber, handleChartAriaLabels, updateConfig, handleLineType } = 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
29
42
 
30
43
  // getters & functions
31
- 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])
32
45
  const getYAxisData = (d, seriesKey) => d[seriesKey]
33
46
  const xAxisDataMapped = data.map(d => getXAxisData(d))
34
47
 
35
48
  // configure width
36
49
  let [width] = dimensions
50
+ let originalWidth = width
37
51
  if (config && config.legend && !config.legend.hide && config.legend.position !== 'bottom' && ['lg', 'md'].includes(currentViewport)) {
38
52
  width = width * 0.73
39
53
  }
40
54
  // configure height , yMax, xMAx
41
55
  const { horizontal: heightHorizontal } = config.heights
42
- const isHorizontal = config.orientation === 'horizontal'
56
+ const isHorizontal = orientation === 'horizontal'
43
57
  const shouldAbbreviate = true
44
- const height = config.aspectRatio ? width * config.aspectRatio : config.heights[config.orientation]
45
- const xMax = width - config.runtime.yAxis.size - (config.visualizationType === 'Combo' ? config.yAxis.rightAxisSize : 0)
46
- 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)
47
61
 
48
- // hooks % states
62
+ // hooks
49
63
  const { minValue, maxValue, existPositiveValue, isAllLine } = useReduceData(config, data)
50
64
  const { yScaleRight, hasRightAxis } = useRightAxis({ config, yMax, data, updateConfig })
51
65
  const { hasTopAxis } = useTopAxis(config)
52
66
  const [animatedChart, setAnimatedChart] = useState(false)
53
67
  const properties = { data, config, minValue, maxValue, isAllLine, existPositiveValue, xAxisDataMapped, xMax, yMax }
54
68
  const { min, max } = useMinMax(properties)
55
- const { xScale, yScale, seriesScale, g1xScale, g2xScale } = useScales({ ...properties, min, max })
69
+ const { xScale, yScale, seriesScale, g1xScale, g2xScale, xScaleNoPadding } = useScales({ ...properties, min, max })
56
70
 
57
71
  // refs
58
72
  const triggerRef = useRef()
@@ -61,29 +75,58 @@ export default function LinearChart() {
61
75
  freezeOnceVisible: false
62
76
  })
63
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
+
64
107
  const handleLeftTickFormatting = tick => {
65
108
  if (config.useLogScale && tick === 0.1) {
66
- //when logaritmic scale applyed change value of FIRST tick
109
+ //when logarithmic scale applied change value of first tick
67
110
  tick = 0
68
111
  }
69
- if (config.runtime.yAxis.type === 'date') return formatDate(parseDate(tick))
70
- 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)
71
114
  return tick
72
115
  }
73
116
 
74
117
  const handleBottomTickFormatting = tick => {
75
118
  if (config.useLogScale && tick === 0.1) {
76
- // when logaritmic scale applyed change value FIRST of tick
119
+ // when logarithmic scale applied change value FIRST of tick
77
120
  tick = 0
78
121
  }
79
- if (config.runtime.xAxis.type === 'date') return formatDate(tick)
80
- 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)
81
124
  if (config.xAxis.type === 'continuous') return formatNumber(tick, 'bottom', shouldAbbreviate)
82
125
  return tick
83
126
  }
84
127
 
85
128
  const countNumOfTicks = axis => {
86
- const { numTicks } = config.runtime[axis]
129
+ const { numTicks } = runtime[axis]
87
130
  let tickCount = undefined
88
131
 
89
132
  if (axis === 'yAxis') {
@@ -118,6 +161,187 @@ export default function LinearChart() {
118
161
  return tickCount
119
162
  }
120
163
 
164
+ // Tooltip helper for getting data to the closest date/category hovered.
165
+ const getXValueFromCoordinate = x => {
166
+ if (xScale.type === 'point') {
167
+ // Find the closest x value by calculating the minimum distance
168
+ let closestX = null
169
+ let minDistance = Number.MAX_VALUE
170
+ let offset = x - yAxis.size
171
+
172
+ data.forEach(d => {
173
+ const xPosition = xAxis.type === 'date' ? xScale(parseDate(d[xAxis.dataKey])) : xScale(d[xAxis.dataKey])
174
+ const distance = Math.abs(Number(xPosition - offset))
175
+
176
+ if (distance < minDistance) {
177
+ minDistance = distance
178
+ closestX = xAxis.type === 'date' ? parseDate(d[xAxis.dataKey]) : d[xAxis.dataKey]
179
+ }
180
+ })
181
+ return closestX
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
+ }
198
+ }
199
+
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
+ }
231
+
232
+ // todo: combine mouseover functions
233
+ const handleTooltipMouseOver = (e, data) => {
234
+ // get the svg coordinates of the mouse
235
+ // and get the closest values
236
+ const eventSvgCoords = localPoint(e)
237
+ const { x, y } = eventSvgCoords
238
+
239
+ let yScaleValues
240
+ let closestXScaleValue = getXValueFromCoordinate(x)
241
+ let formattedDate = formatDate(closestXScaleValue)
242
+
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
+
248
+ if (xAxis.type === 'categorical') {
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
+ })
253
+ } else {
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
+ })
258
+ }
259
+
260
+ let seriesToInclude = []
261
+ let stageColumns = []
262
+ let ciItems = []
263
+
264
+ // loop through series for items to add to tooltip.
265
+ // there is probably a better way of doing this.
266
+ config.series?.forEach(s => {
267
+ if (s.type === 'Forecasting') {
268
+ stageColumns.push(s.stageColumn)
269
+
270
+ // greedy fn 😭
271
+ s?.confidenceIntervals.forEach(ci => {
272
+ if (ci.showInTooltip === true) {
273
+ ciItems.push(ci.low)
274
+ ciItems.push(ci.high)
275
+ }
276
+ })
277
+ }
278
+ })
279
+
280
+ let standardLoopItems = []
281
+
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]
304
+ }
305
+
306
+ standardLoopItems.map(seriesKey => {
307
+ if (!seriesKey) return false
308
+ if (!yScaleValues[0]) return false
309
+ for (const item of Object.entries(yScaleValues[0])) {
310
+ if (item[0] === seriesKey) {
311
+ seriesToInclude.push(item)
312
+ }
313
+ }
314
+ })
315
+
316
+ // filter out the series that aren't added to the map.
317
+ if (!seriesToInclude) return
318
+ let initialTooltipData = seriesToInclude ? seriesToInclude : {}
319
+
320
+ let tooltipData = {}
321
+ tooltipData.data = initialTooltipData
322
+ tooltipData.dataXPosition = isEditor ? x - 300 + 10 : x + 10
323
+ tooltipData.dataYPosition = y
324
+
325
+ let tooltipInformation = {
326
+ tooltipData: tooltipData,
327
+ tooltipTop: 0,
328
+ tooltipValues: yScaleValues,
329
+ tooltipLeft: x
330
+ }
331
+
332
+ showTooltip(tooltipInformation)
333
+ }
334
+
335
+ const handleTooltipMouseOff = () => {
336
+ if (config.visualizationType === 'Area Chart') {
337
+ setTimeout(() => {
338
+ hideTooltip()
339
+ }, 3000)
340
+ } else {
341
+ hideTooltip()
342
+ }
343
+ }
344
+
121
345
  // Make sure the chart is visible if in the editor
122
346
  /* eslint-disable react-hooks/exhaustive-deps */
123
347
  useEffect(() => {
@@ -137,14 +361,86 @@ export default function LinearChart() {
137
361
  }
138
362
  }, [dataRef?.isIntersecting, config.animate])
139
363
 
140
- 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()
141
436
 
142
437
  return isNaN(width) ? (
143
- <></>
438
+ <React.Fragment></React.Fragment>
144
439
  ) : (
145
440
  <ErrorBoundary component='LinearChart'>
146
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}>
147
- {/* Higlighted regions */}
442
+ <Bar width={width} height={height} fill={'transparent'}></Bar>
443
+ {/* Highlighted regions */}
148
444
  {config.regions
149
445
  ? config.regions.map(region => {
150
446
  if (!Object.keys(region).includes('from') || !Object.keys(region).includes('to')) return null
@@ -169,7 +465,7 @@ export default function LinearChart() {
169
465
  if (!to) return null
170
466
 
171
467
  return (
172
- <Group className='regions' left={Number(config.runtime.yAxis.size)} key={region.label}>
468
+ <Group className='regions' left={Number(runtime.yAxis.size)} key={region.label}>
173
469
  <path
174
470
  stroke='#333'
175
471
  d={`M${from} -5
@@ -189,10 +485,10 @@ export default function LinearChart() {
189
485
  : ''}
190
486
 
191
487
  {/* Y axis */}
192
- {config.visualizationType !== 'Spark Line' && (
193
- <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')}>
194
490
  {props => {
195
- 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
196
492
  const horizontalTickOffset = yMax / props.ticks.length / 2 - (yMax / props.ticks.length) * (1 - config.barThickness) + 5
197
493
  return (
198
494
  <Group className='left-axis'>
@@ -205,13 +501,13 @@ export default function LinearChart() {
205
501
 
206
502
  return (
207
503
  <Group key={`vx-tick-${tick.value}-${i}`} className={'vx-axis-tick'}>
208
- {!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'} />}
209
505
 
210
- {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)' /> : ''}
211
507
 
212
- {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 && (
213
509
  <Text
214
- 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})`}
215
511
  verticalAnchor={'start'}
216
512
  textAnchor={'end'}
217
513
  >
@@ -219,29 +515,30 @@ export default function LinearChart() {
219
515
  </Text>
220
516
  )}
221
517
 
222
- {config.orientation === 'horizontal' && config.visualizationSubType === 'stacked' && config.yAxis.labelPlacement === 'On Date/Category Axis' && !config.yAxis.hideLabel && (
223
- <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'}>
224
520
  {tick.formattedValue}
225
521
  </Text>
226
522
  )}
227
523
 
228
- {config.orientation === 'horizontal' && config.visualizationType === 'Paired Bar' && !config.yAxis.hideLabel && (
229
- <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'>
230
526
  {tick.formattedValue}
231
527
  </Text>
232
528
  )}
233
- {config.orientation === 'horizontal' && config.visualizationType === 'Deviation Bar' && !config.yAxis.hideLabel && (
234
- <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'>
235
531
  {tick.formattedValue}
236
532
  </Text>
237
533
  )}
238
534
 
239
- {config.orientation === 'vertical' && config.visualizationType !== 'Paired Bar' && !config.yAxis.hideLabel && (
535
+ {orientation === 'vertical' && visualizationType !== 'Paired Bar' && !config.yAxis.hideLabel && (
240
536
  <Text
241
537
  display={config.useLogScale ? showTicks : 'block'}
242
538
  dx={config.useLogScale ? -6 : 0}
243
539
  x={config.runtime.horizontal ? tick.from.x + 2 : tick.to.x}
244
540
  y={tick.to.y + (config.runtime.horizontal ? horizontalTickOffset : 0)}
541
+ angle={-Number(config.yAxis.tickRotation) || 0}
245
542
  verticalAnchor={config.runtime.horizontal ? 'start' : 'middle'}
246
543
  textAnchor={config.runtime.horizontal ? 'start' : 'end'}
247
544
  fill={config.yAxis.tickLabelColor}
@@ -252,10 +549,10 @@ export default function LinearChart() {
252
549
  </Group>
253
550
  )
254
551
  })}
255
- {!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' />}
256
553
  {yScale.domain()[0] < 0 && <Line from={{ x: props.axisFromPoint.x, y: yScale(0) }} to={{ x: xMax, y: yScale(0) }} stroke='#333' />}
257
- {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} />}
258
- <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}>
259
556
  {props.label}
260
557
  </Text>
261
558
  </Group>
@@ -266,21 +563,21 @@ export default function LinearChart() {
266
563
 
267
564
  {/* Right Axis */}
268
565
  {hasRightAxis && (
269
- <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}>
270
567
  {props => {
271
- 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
272
569
  const horizontalTickOffset = yMax / props.ticks.length / 2 - (yMax / props.ticks.length) * (1 - config.barThickness) + 5
273
570
  return (
274
571
  <Group className='right-axis'>
275
572
  {props.ticks.map((tick, i) => {
276
573
  return (
277
574
  <Group key={`vx-tick-${tick.value}-${i}`} className='vx-axis-tick'>
278
- {!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} />}
279
576
 
280
- {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)' /> : ''}
281
578
 
282
579
  {!config.yAxis.rightHideLabel && (
283
- <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}>
284
581
  {tick.formattedValue}
285
582
  </Text>
286
583
  )}
@@ -288,7 +585,7 @@ export default function LinearChart() {
288
585
  )
289
586
  })}
290
587
  {!config.yAxis.rightHideAxis && <Line from={props.axisFromPoint} to={props.axisToPoint} stroke='#333' />}
291
- <Text className='y-label' textAnchor='middle' verticalAnchor='start' transform={`translate(${config.yAxis.rightLabelOffsetSize ? config.yAxis.rightLabelOffsetSize : 0}, ${axisCenter}) rotate(90)`} fontWeight='bold' fill={config.yAxis.rightAxisLabelColor}>
588
+ <Text className='y-label' textAnchor='middle' verticalAnchor='start' transform={`translate(${config.yAxis.rightLabelOffsetSize ? config.yAxis.rightLabelOffsetSize : 0}, ${axisCenter}) rotate(-90)`} fontWeight='bold' fill={config.yAxis.rightAxisLabelColor}>
292
589
  {props.label}
293
590
  </Text>
294
591
  </Group>
@@ -300,7 +597,7 @@ export default function LinearChart() {
300
597
  {hasTopAxis && config.topAxis.hasLine && (
301
598
  <AxisTop
302
599
  stroke='#333'
303
- left={Number(config.runtime.yAxis.size)}
600
+ left={Number(runtime.yAxis.size)}
304
601
  scale={xScale}
305
602
  hideTicks
306
603
  hideZero
@@ -311,39 +608,83 @@ export default function LinearChart() {
311
608
  )}
312
609
 
313
610
  {/* X axis */}
314
- {config.visualizationType !== 'Paired Bar' && config.visualizationType !== 'Spark Line' && (
611
+ {visualizationType !== 'Paired Bar' && visualizationType !== 'Spark Line' && (
315
612
  <AxisBottom
316
- top={config.runtime.horizontal ? Number(heightHorizontal) + Number(config.xAxis.axisPadding) : yMax + Number(config.xAxis.axisPadding)}
317
- left={Number(config.runtime.yAxis.size)}
318
- 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}
319
616
  tickFormat={handleBottomTickFormatting}
320
617
  scale={xScale}
321
618
  stroke='#333'
322
- tickStroke='#333'
323
619
  numTicks={countNumOfTicks('xAxis')}
620
+ tickStroke='#333'
324
621
  >
325
622
  {props => {
326
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
327
659
  return (
328
660
  <Group className='bottom-axis'>
329
- {props.ticks.map((tick, i) => {
661
+ {props.ticks.map((tick, i, propsTicks) => {
330
662
  // when using LogScale show major ticks values only
331
663
  const showTick = String(tick.value).startsWith('1') || tick.value === 0.1 ? 'block' : 'none'
332
- const tickWidth = xMax / props.ticks.length
333
- const tickLength = showTick === 'block' ? 16 : 8
664
+ const tickLength = showTick === 'block' ? 16 : defaultTickLength
334
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)
335
674
 
336
675
  return (
337
676
  <Group key={`vx-tick-${tick.value}-${i}`} className={'vx-axis-tick'}>
338
- {!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} />}
339
678
  {!config.xAxis.hideLabel && (
340
679
  <Text
341
680
  dy={config.orientation === 'horizontal' && config.useLogScale ? 8 : 0}
342
681
  display={config.orientation === 'horizontal' && config.useLogScale ? showTick : 'block'}
343
- transform={`translate(${tick.to.x}, ${tick.to.y}) rotate(-${!config.runtime.horizontal ? config.runtime.xAxis.tickRotation : 0})`}
344
- verticalAnchor='start'
345
- textAnchor={config.runtime.xAxis.tickRotation && config.runtime.xAxis.tickRotation !== '0' ? 'end' : 'middle'}
346
- 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}
347
688
  fill={config.xAxis.tickLabelColor}
348
689
  >
349
690
  {tick.formattedValue}
@@ -353,7 +694,7 @@ export default function LinearChart() {
353
694
  )
354
695
  })}
355
696
  {!config.xAxis.hideAxis && <Line from={props.axisFromPoint} to={props.axisToPoint} stroke='#333' />}
356
- <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}>
357
698
  {props.label}
358
699
  </Text>
359
700
  </Group>
@@ -362,9 +703,9 @@ export default function LinearChart() {
362
703
  </AxisBottom>
363
704
  )}
364
705
 
365
- {config.visualizationType === 'Paired Bar' && (
706
+ {visualizationType === 'Paired Bar' && (
366
707
  <>
367
- <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}>
368
709
  {props => {
369
710
  return (
370
711
  <Group className='bottom-axis'>
@@ -373,8 +714,8 @@ export default function LinearChart() {
373
714
  const textAnchor = tick.index !== 0 && config.yAxis.tickRotation && config.yAxis.tickRotation > 0 ? 'end' : 'middle'
374
715
  return (
375
716
  <Group key={`vx-tick-${tick.value}-${i}`} className={'vx-axis-tick'}>
376
- {!config.runtime.yAxis.hideTicks && <Line from={tick.from} to={tick.to} stroke='#333' />}
377
- {!config.runtime.yAxis.hideLabel && (
717
+ {!runtime.yAxis.hideTicks && <Line from={tick.from} to={tick.to} stroke='#333' />}
718
+ {!runtime.yAxis.hideLabel && (
378
719
  <Text x={tick.to.x} y={tick.to.y} angle={-angle} verticalAnchor='start' textAnchor={textAnchor}>
379
720
  {formatNumber(tick.value, 'left')}
380
721
  </Text>
@@ -382,20 +723,20 @@ export default function LinearChart() {
382
723
  </Group>
383
724
  )
384
725
  })}
385
- {!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' />}
386
727
  </Group>
387
728
  )
388
729
  }}
389
730
  </AxisBottom>
390
731
  <AxisBottom
391
732
  top={yMax}
392
- left={Number(config.runtime.yAxis.size)}
393
- label={config.runtime.xAxis.label}
394
- 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}
395
736
  scale={g2xScale}
396
737
  stroke='#333'
397
738
  tickStroke='#333'
398
- numTicks={config.runtime.xAxis.numTicks || undefined}
739
+ numTicks={runtime.xAxis.numTicks || undefined}
399
740
  >
400
741
  {props => {
401
742
  return (
@@ -406,8 +747,8 @@ export default function LinearChart() {
406
747
  const textAnchor = tick.index !== 0 && config.yAxis.tickRotation && config.yAxis.tickRotation > 0 ? 'end' : 'middle'
407
748
  return (
408
749
  <Group key={`vx-tick-${tick.value}-${i}`} className={'vx-axis-tick'}>
409
- {!config.runtime.yAxis.hideTicks && <Line from={tick.from} to={tick.to} stroke='#333' />}
410
- {!config.runtime.yAxis.hideLabel && (
750
+ {!runtime.yAxis.hideTicks && <Line from={tick.from} to={tick.to} stroke='#333' />}
751
+ {!runtime.yAxis.hideLabel && (
411
752
  <Text x={tick.to.x} y={tick.to.y} angle={-angle} verticalAnchor='start' textAnchor={textAnchor}>
412
753
  {formatNumber(tick.value, 'left')}
413
754
  </Text>
@@ -415,11 +756,11 @@ export default function LinearChart() {
415
756
  </Group>
416
757
  )
417
758
  })}
418
- {!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' />}
419
760
  </Group>
420
761
  <Group>
421
762
  <Text x={xMax / 2} y={config.xAxis.labelOffset} stroke='#333' textAnchor={'middle'} verticalAnchor='start'>
422
- {config.runtime.xAxis.label}
763
+ {runtime.xAxis.label}
423
764
  </Text>
424
765
  </Group>
425
766
  </>
@@ -428,24 +769,68 @@ export default function LinearChart() {
428
769
  </AxisBottom>
429
770
  </>
430
771
  )}
431
-
432
- {config.visualizationType === 'Deviation Bar' && <DeviationBar xScale={xScale} yScale={yScale} width={xMax} height={yMax} />}
433
- {config.visualizationType === 'Paired Bar' && <PairedBarChart originalWidth={width} width={xMax} height={yMax} />}
434
- {config.visualizationType === 'Scatter Plot' && <CoveScatterPlot xScale={xScale} yScale={yScale} getXAxisData={getXAxisData} getYAxisData={getYAxisData} />}
435
- {config.visualizationType === 'Box Plot' && <CoveBoxPlot xScale={xScale} yScale={yScale} />}
436
- {(config.visualizationType === 'Area Chart' || config.visualizationType === 'Combo') && <CoveAreaChart xScale={xScale} yScale={yScale} yMax={yMax} xMax={xMax} chartRef={svgRef} />}
437
-
438
- {/* Bar chart */}
439
- {/* TODO: Make this just bar or combo? */}
440
- {config.visualizationType !== 'Line' && config.visualizationType !== 'Paired Bar' && config.visualizationType !== 'Box Plot' && config.visualizationType !== 'Area Chart' && config.visualizationType !== 'Scatter Plot' && config.visualizationType !== 'Deviation Bar' && (
441
- <>
442
- <BarChart xScale={xScale} yScale={yScale} seriesScale={seriesScale} xMax={xMax} yMax={yMax} getXAxisData={getXAxisData} getYAxisData={getYAxisData} animatedChart={animatedChart} visible={animatedChart} />
443
- </>
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') && (
816
+ <Forecasting
817
+ showTooltip={showTooltip}
818
+ tooltipData={tooltipData}
819
+ xScale={xScale}
820
+ yScale={yScale}
821
+ width={xMax}
822
+ height={yMax}
823
+ xScaleNoPadding={xScaleNoPadding}
824
+ chartRef={svgRef}
825
+ getXValueFromCoordinate={getXValueFromCoordinate}
826
+ handleTooltipMouseOver={handleTooltipMouseOver}
827
+ handleTooltipMouseOff={handleTooltipMouseOff}
828
+ />
444
829
  )}
445
830
 
446
831
  {/* Line chart */}
447
832
  {/* TODO: Make this just line or combo? */}
448
- {config.visualizationType !== 'Bar' && config.visualizationType !== 'Paired Bar' && config.visualizationType !== 'Box Plot' && config.visualizationType !== 'Area Chart' && config.visualizationType !== 'Scatter Plot' && config.visualizationType !== 'Deviation Bar' && (
833
+ {visualizationType !== 'Bar' && visualizationType !== 'Paired Bar' && visualizationType !== 'Box Plot' && visualizationType !== 'Area Chart' && visualizationType !== 'Scatter Plot' && visualizationType !== 'Deviation Bar' && visualizationType !== 'Forecasting' && (
449
834
  <>
450
835
  <LineChart xScale={xScale} yScale={yScale} getXAxisData={getXAxisData} getYAxisData={getYAxisData} xMax={xMax} yMax={yMax} seriesStyle={config.series} />
451
836
  </>
@@ -453,27 +838,30 @@ export default function LinearChart() {
453
838
 
454
839
  {/* y anchors */}
455
840
  {config.yAxis.anchors &&
456
- config.yAxis.anchors.map(anchor => {
841
+ config.yAxis.anchors.map((anchor, index) => {
457
842
  let anchorPosition = yScale(anchor.value)
458
- const padding = config.orientation === 'horizontal' ? Number(config.xAxis.size) : Number(config.yAxis.size)
459
- 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
460
848
 
461
849
  return (
462
850
  // prettier-ignore
463
851
  <Line
464
- key={anchor.value}
852
+ key={`yAxis-${anchor.value}--${index}`}
465
853
  strokeDasharray={handleLineType(anchor.lineStyle)}
466
854
  stroke={anchor.color ? anchor.color : 'rgba(0,0,0,1)'}
467
855
  className='anchor-y'
468
856
  from={{ x: 0 + padding, y: anchorPosition - middleOffset}}
469
- to={{ x: width, y: anchorPosition - middleOffset }}
857
+ to={{ x: width - config.yAxis.rightAxisSize, y: anchorPosition - middleOffset }}
470
858
  />
471
859
  )
472
860
  })}
473
861
 
474
862
  {/* x anchors */}
475
863
  {config.xAxis.anchors &&
476
- config.xAxis.anchors.map(anchor => {
864
+ config.xAxis.anchors.map((anchor, index) => {
477
865
  let newX = xAxis
478
866
  if (orientation === 'horizontal') {
479
867
  newX = yAxis
@@ -483,10 +871,12 @@ export default function LinearChart() {
483
871
 
484
872
  const padding = orientation === 'horizontal' ? Number(config.xAxis.size) : Number(config.yAxis.size)
485
873
 
874
+ if (!anchorPosition) return
875
+
486
876
  return (
487
877
  // prettier-ignore
488
878
  <Line
489
- key={anchor.value}
879
+ key={`xAxis-${anchor.value}--${index}`}
490
880
  strokeDasharray={handleLineType(anchor.lineStyle)}
491
881
  stroke={anchor.color ? anchor.color : 'rgba(0,0,0,1)'}
492
882
  fill={anchor.color ? anchor.color : 'rgba(0,0,0,1)'}
@@ -496,8 +886,27 @@ export default function LinearChart() {
496
886
  />
497
887
  )
498
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
+ )}
499
901
  </svg>
500
- <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
+ )}
501
910
  <div className='animation-trigger' ref={triggerRef} />
502
911
  </ErrorBoundary>
503
912
  )