@cdc/chart 4.23.6 → 4.23.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (48) hide show
  1. package/dist/cdcchart.js +29981 -29995
  2. package/examples/feature/__data__/area-chart-date-apple.json +5122 -0
  3. package/examples/feature/__data__/city-temperature.json +2198 -0
  4. package/examples/feature/__data__/planet-example-data.json +1 -1
  5. package/examples/feature/area/area-chart-category.json +45 -45
  6. package/examples/feature/area/area-chart-date-apple.json +10376 -0
  7. package/examples/feature/area/area-chart-date-city-temperature.json +4528 -0
  8. package/examples/feature/area/area-chart-date.json +111 -3
  9. package/examples/feature/combo/right-issues.json +1 -1
  10. package/examples/feature/forecasting/combo-forecasting.json +72 -46
  11. package/examples/feature/forecasting/effective_reproduction.json +57 -8
  12. package/examples/feature/forecasting/forecasting.json +12 -3
  13. package/examples/feature/forest-plot/broken.json +700 -0
  14. package/examples/feature/forest-plot/data.csv +24 -0
  15. package/examples/feature/forest-plot/forest-plot.json +717 -0
  16. package/examples/feature/line/line-chart.json +11 -11
  17. package/examples/feature/pie/planet-pie-example-config.json +1 -1
  18. package/examples/gallery/bar-chart-vertical/vertical-bar-chart-categorical.json +167 -20
  19. package/examples/private/confidence_interval_test.json +248 -0
  20. package/examples/private/tooltip-issue.json +45275 -0
  21. package/index.html +13 -11
  22. package/package.json +4 -3
  23. package/src/CdcChart.jsx +78 -27
  24. package/src/components/AreaChart.jsx +65 -151
  25. package/src/components/BarChart.Horizontal.jsx +251 -0
  26. package/src/components/BarChart.StackedHorizontal.jsx +118 -0
  27. package/src/components/BarChart.StackedVertical.jsx +93 -0
  28. package/src/components/BarChart.Vertical.jsx +204 -0
  29. package/src/components/BarChart.jsx +17 -667
  30. package/src/components/BarChartType.jsx +15 -0
  31. package/src/components/BrushHandle.jsx +17 -0
  32. package/src/components/DataTable.jsx +67 -22
  33. package/src/components/EditorPanel.jsx +426 -358
  34. package/src/components/Forecasting.jsx +23 -86
  35. package/src/components/ForestPlot.jsx +191 -0
  36. package/src/components/ForestPlotSettings.jsx +508 -0
  37. package/src/components/Legend.jsx +10 -8
  38. package/src/components/LineChart.jsx +31 -6
  39. package/src/components/LinearChart.jsx +317 -230
  40. package/src/components/Series.jsx +40 -4
  41. package/src/data/initial-state.js +50 -3
  42. package/src/hooks/useBarChart.js +186 -0
  43. package/src/hooks/useEditorPermissions.js +218 -0
  44. package/src/hooks/useMinMax.js +18 -5
  45. package/src/hooks/useRightAxis.js +2 -1
  46. package/src/hooks/useScales.js +45 -2
  47. package/src/hooks/useTooltip.jsx +407 -0
  48. package/src/scss/main.scss +11 -17
@@ -1,61 +1,72 @@
1
- import React, { useContext, useEffect, useRef, useState } from 'react'
2
- import { Tooltip as ReactTooltip } from 'react-tooltip'
1
+ import React, { useContext, useEffect, useRef, useState, useMemo } from 'react'
3
2
 
3
+ // Libraries
4
+ import { AxisLeft, AxisBottom, AxisRight, AxisTop } from '@visx/axis'
4
5
  import { Group } from '@visx/group'
5
- import { Line } from '@visx/shape'
6
+ import { Line, Bar } from '@visx/shape'
6
7
  import { Text } from '@visx/text'
7
- import { AxisLeft, AxisBottom, AxisRight, AxisTop } from '@visx/axis'
8
- import { localPoint } from '@visx/event'
9
- import { useTooltip } from '@visx/tooltip'
8
+ import { Tooltip as ReactTooltip } from 'react-tooltip'
9
+ import { useTooltip, TooltipWithBounds } from '@visx/tooltip'
10
10
 
11
+ // CDC Components
12
+ import AreaChart from './AreaChart'
11
13
  import BarChart from './BarChart'
12
14
  import ConfigContext from '../ConfigContext'
13
- import CoveAreaChart from './AreaChart'
14
15
  import CoveBoxPlot from './BoxPlot'
15
- import CoveScatterPlot from './ScatterPlot'
16
+ import ScatterPlot from './ScatterPlot'
16
17
  import DeviationBar from './DeviationBar'
18
+ import ErrorBoundary from '@cdc/core/components/ErrorBoundary'
19
+ import Forecasting from './Forecasting'
17
20
  import LineChart from './LineChart'
21
+ import ForestPlot from './ForestPlot'
18
22
  import PairedBarChart from './PairedBarChart'
19
23
  import useIntersectionObserver from './useIntersectionObserver'
20
24
 
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'
25
+ // Hooks
25
26
  import useMinMax from '../hooks/useMinMax'
27
+ import useReduceData from '../hooks/useReduceData'
26
28
  import useRightAxis from '../hooks/useRightAxis'
29
+ import useScales from '../hooks/useScales'
27
30
  import useTopAxis from '../hooks/useTopAxis'
28
- import Forecasting from './Forecasting'
31
+ import { useTooltip as useCoveTooltip } from '../hooks/useTooltip'
32
+
33
+ // styles
34
+ import '../scss/LinearChart.scss'
29
35
 
30
36
  export default function LinearChart() {
31
- const { transformedData: data, dimensions, config, parseDate, formatDate, currentViewport, formatNumber, handleChartAriaLabels, updateConfig, handleLineType, rawData } = useContext(ConfigContext)
37
+ const { isEditor, isDashboard, transformedData: data, dimensions, config, parseDate, formatDate, currentViewport, formatNumber, handleChartAriaLabels, updateConfig, handleLineType, rawData, capitalize, setSharedFilter, setSharedFilterValue, getTextWidth, isDebug } = useContext(ConfigContext)
32
38
 
33
- // getters & functions
34
- const getXAxisData = d => (config.runtime.xAxis.type === 'date' ? parseDate(d[config.runtime.originalXAxis.dataKey]).getTime() : d[config.runtime.originalXAxis.dataKey])
35
- const getYAxisData = (d, seriesKey) => d[seriesKey]
36
- const xAxisDataMapped = data.map(d => getXAxisData(d))
39
+ // todo: start destructuring this file for conciseness
40
+ const { visualizationType, visualizationSubType, orientation, xAxis, yAxis, runtime, debugSvg } = config
41
+
42
+ const getDate = d => new Date(d[config.xAxis.dataKey])
37
43
 
38
44
  // configure width
39
45
  let [width] = dimensions
40
46
  if (config && config.legend && !config.legend.hide && config.legend.position !== 'bottom' && ['lg', 'md'].includes(currentViewport)) {
41
47
  width = width * 0.73
42
48
  }
43
- // configure height , yMax, xMAx
49
+ // configure height , yMax, xMax
44
50
  const { horizontal: heightHorizontal } = config.heights
45
- const isHorizontal = config.orientation === 'horizontal'
51
+ const isHorizontal = orientation === 'horizontal'
46
52
  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)
53
+ let height = config.aspectRatio ? width * config.aspectRatio : config.visualizationType === 'Forest Plot' ? config.heights['vertical'] : config.heights[orientation]
54
+ const xMax = width - runtime.yAxis.size - (visualizationType === 'Combo' ? config.yAxis.rightAxisSize : 0)
55
+ let yMax = height - (orientation === 'horizontal' ? 0 : runtime.xAxis.size)
56
+
57
+ if (config.visualizationType === 'Forest Plot') {
58
+ height = height + config.data.length * config.forestPlot.rowHeight
59
+ yMax = yMax + config.data.length * config.forestPlot.rowHeight
60
+ }
61
+
62
+ let dynamicMarginTop = 0 || config.dynamicMarginTop
63
+ const marginTop = 20
50
64
 
51
65
  // hooks % states
52
66
  const { minValue, maxValue, existPositiveValue, isAllLine } = useReduceData(config, data)
53
67
  const { yScaleRight, hasRightAxis } = useRightAxis({ config, yMax, data, updateConfig })
54
68
  const { hasTopAxis } = useTopAxis(config)
55
69
  const [animatedChart, setAnimatedChart] = useState(false)
56
- const properties = { data, config, minValue, maxValue, isAllLine, existPositiveValue, xAxisDataMapped, xMax, yMax }
57
- const { min, max } = useMinMax(properties)
58
- const { xScale, yScale, seriesScale, g1xScale, g2xScale, xScaleNoPadding } = useScales({ ...properties, min, max })
59
70
 
60
71
  // refs
61
72
  const triggerRef = useRef()
@@ -64,29 +75,47 @@ export default function LinearChart() {
64
75
  freezeOnceVisible: false
65
76
  })
66
77
 
67
- const handleLeftTickFormatting = tick => {
78
+ // getters & functions
79
+ const getXAxisData = d => (config.runtime.xAxis.type === 'date' ? parseDate(d[config.runtime.originalXAxis.dataKey]).getTime() : d[config.runtime.originalXAxis.dataKey])
80
+ const getYAxisData = (d, seriesKey) => d[seriesKey]
81
+ const xAxisDataMapped = data.map(d => getXAxisData(d))
82
+ const section = config.orientation === 'horizontal' ? 'yAxis' : 'xAxis'
83
+ const properties = { data, config, minValue, maxValue, isAllLine, existPositiveValue, xAxisDataMapped, xMax, yMax }
84
+ const { min, max } = useMinMax(properties)
85
+ const { xScale, yScale, seriesScale, g1xScale, g2xScale, xScaleNoPadding } = useScales({ ...properties, min, max })
86
+
87
+ // sets the portal x/y for where tooltips should appear on the page.
88
+ const [chartPosition, setChartPosition] = useState(null)
89
+ useEffect(() => {
90
+ setChartPosition(svgRef?.current?.getBoundingClientRect())
91
+ }, [svgRef, config.legend])
92
+
93
+ const handleLeftTickFormatting = (tick, index) => {
68
94
  if (config.useLogScale && tick === 0.1) {
69
95
  //when logarithmic scale applied change value of first tick
70
96
  tick = 0
71
97
  }
72
- if (config.runtime.yAxis.type === 'date') return formatDate(parseDate(tick))
73
- if (config.orientation === 'vertical') return formatNumber(tick, 'left', shouldAbbreviate)
98
+
99
+ if (config.data && !config.data[index] && visualizationType === 'Forest Plot') return
100
+ if (config.visualizationType === 'Forest Plot') return config.data[index][config.xAxis.dataKey]
101
+ if (runtime.yAxis.type === 'date') return formatDate(parseDate(tick))
102
+ if (orientation === 'vertical') return formatNumber(tick, 'left', shouldAbbreviate)
74
103
  return tick
75
104
  }
76
105
 
77
106
  const handleBottomTickFormatting = tick => {
78
107
  if (config.useLogScale && tick === 0.1) {
79
- // when logaritmic scale applyed change value FIRST of tick
108
+ // when logarithmic scale applied change value FIRST of tick
80
109
  tick = 0
81
110
  }
82
- if (config.runtime.xAxis.type === 'date') return formatDate(tick)
83
- if (config.orientation === 'horizontal') return formatNumber(tick, 'left', shouldAbbreviate)
111
+ if (runtime.xAxis.type === 'date') return formatDate(tick)
112
+ if (orientation === 'horizontal') return formatNumber(tick, 'left', shouldAbbreviate)
84
113
  if (config.xAxis.type === 'continuous') return formatNumber(tick, 'bottom', shouldAbbreviate)
85
114
  return tick
86
115
  }
87
116
 
88
117
  const countNumOfTicks = axis => {
89
- const { numTicks } = config.runtime[axis]
118
+ const { numTicks } = runtime[axis]
90
119
  let tickCount = undefined
91
120
 
92
121
  if (axis === 'yAxis') {
@@ -121,108 +150,24 @@ export default function LinearChart() {
121
150
  return tickCount
122
151
  }
123
152
 
124
- // Tooltip helper for getting data to the closest date/category hovered.
125
- const getXValueFromCoordinate = x => {
126
- if (xScale.type === 'point') {
127
- // Find the closest x value by calculating the minimum distance
128
- let closestX = null
129
- let minDistance = Number.MAX_VALUE
130
- let offset = x - yAxis.size
131
-
132
- data.forEach(d => {
133
- const xPosition = xAxis.type === 'date' ? xScale(parseDate(d[xAxis.dataKey])) : xScale(d[xAxis.dataKey])
134
- const distance = Math.abs(Number(xPosition - offset))
135
-
136
- if (distance < minDistance) {
137
- minDistance = distance
138
- closestX = xAxis.type === 'date' ? parseDate(d[xAxis.dataKey]) : d[xAxis.dataKey]
139
- }
140
- })
141
- return closestX
142
- }
143
- }
144
-
145
- // import tooltip helpers
146
- const { tooltipData, showTooltip, hideTooltip } = useTooltip()
147
-
148
- const handleTooltipMouseOver = (e, data) => {
149
- // get the svg coordinates of the mouse
150
- // and get the closest values
151
- const eventSvgCoords = localPoint(e)
152
- const { x, y } = eventSvgCoords
153
-
154
- const { runtime } = config
155
-
156
- let closestXScaleValue = getXValueFromCoordinate(x)
157
- let formattedDate = formatDate(closestXScaleValue)
158
-
159
- let yScaleValues
160
- if (xAxis.type === 'categorical') {
161
- yScaleValues = data.filter(d => d[xAxis.dataKey] === closestXScaleValue)
162
- } else {
163
- yScaleValues = rawData.filter(d => formatDate(parseDate(d[xAxis.dataKey])) === formattedDate)
164
- }
165
-
166
- let seriesToInclude = []
167
- let stageColumns = []
168
- let ciItems = []
169
-
170
- // loop through series for items to add to tooltip.
171
- // there is probably a better way of doing this.
172
- config.series?.map(s => {
173
- if (s.type === 'Forecasting') {
174
- stageColumns.push(s.stageColumn)
175
-
176
- // greedy fn 😭
177
- s?.confidenceIntervals.map(ci => {
178
- if (ci.showInTooltip === true) {
179
- ciItems.push(ci.low)
180
- ciItems.push(ci.high)
181
- }
182
- })
183
- }
184
- })
185
-
186
- let standardLoopItems = []
187
-
188
- if (config.visualizationType === 'Combo') {
189
- standardLoopItems = [runtime.xAxis.dataKey, ...runtime?.barSeriesKeys, ...stageColumns, ...ciItems]
190
- } else {
191
- standardLoopItems = [runtime.xAxis.dataKey, ...stageColumns, ...ciItems]
192
- }
193
-
194
- standardLoopItems.map(seriesKey => {
195
- if (!seriesKey) return false
196
- if (!yScaleValues[0]) return false
197
- for (const item of Object.entries(yScaleValues[0])) {
198
- if (item[0] === seriesKey) {
199
- seriesToInclude.push(item)
200
- }
201
- }
202
- })
203
-
204
- // filter out the series that aren't added to the map.
205
- if (!seriesToInclude) return
206
- let initialTooltipData = Object.fromEntries(seriesToInclude) ? Object.fromEntries(seriesToInclude) : {}
207
-
208
- let tooltipData = {}
209
- tooltipData.data = initialTooltipData
210
- tooltipData.dataXPosition = x + 10
211
- tooltipData.dataYPosition = y
212
-
213
- let tooltipInformation = {
214
- tooltipData: tooltipData,
215
- tooltipTop: 0,
216
- tooltipValues: yScaleValues,
217
- tooltipLeft: x
218
- }
219
-
220
- showTooltip(tooltipInformation)
221
- }
222
-
223
- const handleTooltipMouseOff = () => {
224
- hideTooltip()
225
- }
153
+ // Tooltip Helpers
154
+ const { tooltipData, showTooltip, hideTooltip, tooltipOpen } = useTooltip()
155
+
156
+ // prettier-ignore
157
+ const {
158
+ handleTooltipMouseOver,
159
+ handleTooltipClick,
160
+ handleTooltipMouseOff,
161
+ tooltipStyles,
162
+ TooltipListItem,
163
+ getXValueFromCoordinateDate,
164
+ getXValueFromCoordinate
165
+ } = useCoveTooltip({
166
+ xScale,
167
+ yScale,
168
+ showTooltip,
169
+ hideTooltip
170
+ })
226
171
 
227
172
  // Make sure the chart is visible if in the editor
228
173
  /* eslint-disable react-hooks/exhaustive-deps */
@@ -243,14 +188,29 @@ export default function LinearChart() {
243
188
  }
244
189
  }, [dataRef?.isIntersecting, config.animate])
245
190
 
246
- const { orientation, xAxis, yAxis } = config
191
+ const chartHasTooltipGuides = () => {
192
+ const { visualizationType } = config
193
+ if (visualizationType === 'Combo' && runtime.forecastingSeriesKeys > 0) return true
194
+ if (visualizationType === 'Area Chart') return true
195
+ if (visualizationType === 'Line') return true
196
+ if (visualizationType === 'Bar') return true
197
+ return false
198
+ }
199
+
200
+ const padding = orientation === 'horizontal' ? Number(config.xAxis.size) : Number(config.yAxis.size)
201
+
202
+ const handleNumTicks = () => {
203
+ // On forest plots we need to return every "study" or y axis value.
204
+ if (config.visualizationType === 'Forest Plot') return config.data.length
205
+ return countNumOfTicks('yAxis')
206
+ }
247
207
 
248
208
  return isNaN(width) ? (
249
- <></>
209
+ <React.Fragment></React.Fragment>
250
210
  ) : (
251
211
  <ErrorBoundary component='LinearChart'>
252
- <svg width={width} height={height} className={`linear ${config.animate ? 'animated' : ''} ${animatedChart && config.animate ? 'animate' : ''}`} role='img' aria-label={handleChartAriaLabels(config)} tabIndex={0} ref={svgRef}>
253
- {/* Highlighted regions */}
212
+ <svg width={width} height={height} className={`linear ${config.animate ? 'animated' : ''} ${animatedChart && config.animate ? 'animate' : ''} ${debugSvg && 'debug'}`} role='img' aria-label={handleChartAriaLabels(config)} tabIndex={0} ref={svgRef}>
213
+ <Bar width={width} height={height} fill={'transparent'}></Bar> {/* Highlighted regions */}
254
214
  {config.regions
255
215
  ? config.regions.map(region => {
256
216
  if (!Object.keys(region).includes('from') || !Object.keys(region).includes('to')) return null
@@ -275,7 +235,7 @@ export default function LinearChart() {
275
235
  if (!to) return null
276
236
 
277
237
  return (
278
- <Group className='regions' left={Number(config.runtime.yAxis.size)} key={region.label}>
238
+ <Group className='regions' left={Number(runtime.yAxis.size)} key={region.label}>
279
239
  <path
280
240
  stroke='#333'
281
241
  d={`M${from} -5
@@ -293,12 +253,11 @@ export default function LinearChart() {
293
253
  )
294
254
  })
295
255
  : ''}
296
-
297
256
  {/* 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')}>
257
+ {!['Spark Line', 'Forest Plot'].includes(visualizationType) && (
258
+ <AxisLeft scale={yScale} tickLength={config.useLogScale ? 6 : 8} left={Number(runtime.yAxis.size) - config.yAxis.axisPadding} label={runtime.yAxis.label} stroke='#333' tickFormat={(tick, i) => handleLeftTickFormatting(tick, i)} numTicks={handleNumTicks()}>
300
259
  {props => {
301
- const axisCenter = config.runtime.horizontal ? (props.axisToPoint.y - props.axisFromPoint.y) / 2 : (props.axisFromPoint.y - props.axisToPoint.y) / 2
260
+ const axisCenter = runtime.horizontal ? (props.axisToPoint.y - props.axisFromPoint.y) / 2 : (props.axisFromPoint.y - props.axisToPoint.y) / 2
302
261
  const horizontalTickOffset = yMax / props.ticks.length / 2 - (yMax / props.ticks.length) * (1 - config.barThickness) + 5
303
262
  return (
304
263
  <Group className='left-axis'>
@@ -311,13 +270,13 @@ export default function LinearChart() {
311
270
 
312
271
  return (
313
272
  <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'} />}
273
+ {!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
274
 
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)' /> : ''}
275
+ {runtime.yAxis.gridLines ? <Line key={`${tick.value}--hide-hideGridLines`} display={(config.useLogScale && showTicks).toString()} from={{ x: tick.from.x + xMax, y: tick.from.y }} to={tick.from} stroke='rgba(0,0,0,0.3)' /> : ''}
317
276
 
318
- {config.orientation === 'horizontal' && config.visualizationSubType !== 'stacked' && config.yAxis.labelPlacement === 'On Date/Category Axis' && !config.yAxis.hideLabel && (
277
+ {orientation === 'horizontal' && visualizationSubType !== 'stacked' && config.yAxis.labelPlacement === 'On Date/Category Axis' && !config.yAxis.hideLabel && (
319
278
  <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})`}
279
+ 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
280
  verticalAnchor={'start'}
322
281
  textAnchor={'end'}
323
282
  >
@@ -325,29 +284,30 @@ export default function LinearChart() {
325
284
  </Text>
326
285
  )}
327
286
 
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'}>
287
+ {orientation === 'horizontal' && visualizationSubType === 'stacked' && config.yAxis.labelPlacement === 'On Date/Category Axis' && !config.yAxis.hideLabel && (
288
+ <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
289
  {tick.formattedValue}
331
290
  </Text>
332
291
  )}
333
292
 
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'>
293
+ {orientation === 'horizontal' && visualizationType === 'Paired Bar' && !config.yAxis.hideLabel && (
294
+ <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
295
  {tick.formattedValue}
337
296
  </Text>
338
297
  )}
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'>
298
+ {orientation === 'horizontal' && visualizationType === 'Deviation Bar' && !config.yAxis.hideLabel && (
299
+ <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
300
  {tick.formattedValue}
342
301
  </Text>
343
302
  )}
344
303
 
345
- {config.orientation === 'vertical' && config.visualizationType !== 'Paired Bar' && !config.yAxis.hideLabel && (
304
+ {orientation === 'vertical' && visualizationType !== 'Paired Bar' && !config.yAxis.hideLabel && (
346
305
  <Text
347
306
  display={config.useLogScale ? showTicks : 'block'}
348
307
  dx={config.useLogScale ? -6 : 0}
349
308
  x={config.runtime.horizontal ? tick.from.x + 2 : tick.to.x}
350
309
  y={tick.to.y + (config.runtime.horizontal ? horizontalTickOffset : 0)}
310
+ angle={-Number(config.yAxis.tickRotation) || 0}
351
311
  verticalAnchor={config.runtime.horizontal ? 'start' : 'middle'}
352
312
  textAnchor={config.runtime.horizontal ? 'start' : 'end'}
353
313
  fill={config.yAxis.tickLabelColor}
@@ -358,10 +318,10 @@ export default function LinearChart() {
358
318
  </Group>
359
319
  )
360
320
  })}
361
- {!config.yAxis.hideAxis && <Line from={props.axisFromPoint} to={config.runtime.horizontal ? { x: 0, y: Number(heightHorizontal) } : props.axisToPoint} stroke='#000' />}
321
+ {!config.yAxis.hideAxis && <Line from={props.axisFromPoint} to={runtime.horizontal ? { x: 0, y: config.visualizationType === 'Forest Plot' ? height : Number(heightHorizontal) } : props.axisToPoint} stroke='#000' />}
362
322
  {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}>
323
+ {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} />}
324
+ <Text className='y-label' textAnchor='middle' verticalAnchor='start' transform={`translate(${-1 * runtime.yAxis.size}, ${axisCenter}) rotate(-90)`} fontWeight='bold' fill={config.yAxis.labelColor}>
365
325
  {props.label}
366
326
  </Text>
367
327
  </Group>
@@ -369,24 +329,23 @@ export default function LinearChart() {
369
329
  }}
370
330
  </AxisLeft>
371
331
  )}
372
-
373
332
  {/* Right Axis */}
374
333
  {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}>
334
+ <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
335
  {props => {
377
- const axisCenter = config.runtime.horizontal ? (props.axisToPoint.y - props.axisFromPoint.y) / 2 : (props.axisFromPoint.y - props.axisToPoint.y) / 2
336
+ const axisCenter = runtime.horizontal ? (props.axisToPoint.y - props.axisFromPoint.y) / 2 : (props.axisFromPoint.y - props.axisToPoint.y) / 2
378
337
  const horizontalTickOffset = yMax / props.ticks.length / 2 - (yMax / props.ticks.length) * (1 - config.barThickness) + 5
379
338
  return (
380
339
  <Group className='right-axis'>
381
340
  {props.ticks.map((tick, i) => {
382
341
  return (
383
342
  <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} />}
343
+ {!runtime.yAxis.rightHideTicks && <Line from={tick.from} to={tick.to} display={runtime.horizontal ? 'none' : 'block'} stroke={config.yAxis.rightAxisTickColor} />}
385
344
 
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)' /> : ''}
345
+ {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
346
 
388
347
  {!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}>
348
+ <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
349
  {tick.formattedValue}
391
350
  </Text>
392
351
  )}
@@ -402,11 +361,10 @@ export default function LinearChart() {
402
361
  }}
403
362
  </AxisRight>
404
363
  )}
405
-
406
364
  {hasTopAxis && config.topAxis.hasLine && (
407
365
  <AxisTop
408
366
  stroke='#333'
409
- left={Number(config.runtime.yAxis.size)}
367
+ left={Number(runtime.yAxis.size)}
410
368
  scale={xScale}
411
369
  hideTicks
412
370
  hideZero
@@ -415,41 +373,84 @@ export default function LinearChart() {
415
373
  })}
416
374
  />
417
375
  )}
418
-
419
376
  {/* X axis */}
420
- {config.visualizationType !== 'Paired Bar' && config.visualizationType !== 'Spark Line' && (
377
+ {visualizationType !== 'Paired Bar' && visualizationType !== 'Spark Line' && (
421
378
  <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}
379
+ top={runtime.horizontal && config.visualizationType !== 'Forest Plot' ? Number(heightHorizontal) + Number(config.xAxis.axisPadding) : config.visualizationType === 'Forest Plot' ? yMax + Number(config.xAxis.axisPadding) : yMax + Number(config.xAxis.axisPadding)}
380
+ left={Number(runtime.yAxis.size)}
381
+ label={runtime.xAxis.label}
425
382
  tickFormat={handleBottomTickFormatting}
426
383
  scale={xScale}
427
384
  stroke='#333'
428
- tickStroke='#333'
429
385
  numTicks={countNumOfTicks('xAxis')}
386
+ tickStroke='#333'
430
387
  >
431
388
  {props => {
432
- const axisCenter = (props.axisToPoint.x - props.axisFromPoint.x) / 2
389
+ const axisCenter = config.visualizationType !== 'Forest Plot' ? (props.axisToPoint.x - props.axisFromPoint.x) / 2 : width / 2
390
+ const containsMultipleWords = inputString => /\s/.test(inputString)
391
+ const ismultiLabel = props.ticks.some(tick => containsMultipleWords(tick.value))
392
+
393
+ // Calculate sumOfTickWidth here, before map function
394
+ const fontSize = { small: 16, medium: 18, large: 20 }
395
+ const defaultTickLength = 8
396
+ const tickWidthMax = Math.max(...props.ticks.map(tick => getTextWidth(tick.formattedValue, `normal ${fontSize[config.fontSize]}px sans-serif`)))
397
+ // const marginTop = 20 // moved to top bc need for yMax calcs
398
+ const accumulator = ismultiLabel ? 180 : 100
399
+
400
+ const textWidths = props.ticks.map(tick => getTextWidth(tick.formattedValue, `normal ${fontSize[config.fontSize]}px sans-serif`))
401
+ const sumOfTickWidth = textWidths.reduce((a, b) => a + b, accumulator)
402
+ const spaceBetweenEachTick = (xMax - sumOfTickWidth) / (props.ticks.length - 1)
403
+
404
+ // Check if ticks are overlapping
405
+ // Determine the position of each tick
406
+ let positions = [0] // The first tick is at position 0
407
+ for (let i = 1; i < textWidths.length; i++) {
408
+ // The position of each subsequent tick is the position of the previous tick
409
+ // plus the width of the previous tick and the space
410
+ positions[i] = positions[i - 1] + textWidths[i - 1] + spaceBetweenEachTick
411
+ }
412
+
413
+ // Check if ticks are overlapping
414
+ let areTicksTouching = false
415
+ textWidths.forEach((_, i) => {
416
+ if (positions[i] + textWidths[i] > positions[i + 1]) {
417
+ areTicksTouching = true
418
+ return
419
+ }
420
+ })
421
+
422
+ dynamicMarginTop = areTicksTouching && config.isResponsiveTicks ? tickWidthMax + defaultTickLength + marginTop : 0
423
+ config.dynamicMarginTop = dynamicMarginTop
424
+
433
425
  return (
434
426
  <Group className='bottom-axis'>
435
- {props.ticks.map((tick, i) => {
427
+ {props.ticks.map((tick, i, propsTicks) => {
436
428
  // when using LogScale show major ticks values only
437
429
  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
430
+ const tickLength = showTick === 'block' ? 16 : defaultTickLength
440
431
  const to = { x: tick.to.x, y: tickLength }
432
+ let textWidth = getTextWidth(tick.formattedValue, `normal ${fontSize[config.fontSize]}px sans-serif`)
433
+ let limitedWidth = 100 / propsTicks.length
434
+ //reset rotations by updating config
435
+ config.yAxis.tickRotation = config.isResponsiveTicks && config.orientation === 'horizontal' ? 0 : config.yAxis.tickRotation
436
+ config.xAxis.tickRotation = config.isResponsiveTicks && config.orientation === 'vertical' ? 0 : config.xAxis.tickRotation
437
+ //configure rotation
438
+
439
+ const tickRotation = config.isResponsiveTicks && areTicksTouching ? -Number(config.xAxis.maxTickRotation) || -90 : -Number(config.runtime.xAxis.tickRotation)
441
440
 
442
441
  return (
443
442
  <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} />}
443
+ {!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
444
  {!config.xAxis.hideLabel && (
446
445
  <Text
447
446
  dy={config.orientation === 'horizontal' && config.useLogScale ? 8 : 0}
448
447
  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}
448
+ x={tick.to.x}
449
+ y={tick.to.y}
450
+ angle={tickRotation}
451
+ verticalAnchor={tickRotation < -50 ? 'middle' : 'start'}
452
+ textAnchor={tickRotation ? 'end' : 'middle'}
453
+ width={areTicksTouching && !config.isResponsiveTicks && !Number(config[section].tickRotation) ? limitedWidth : textWidth}
453
454
  fill={config.xAxis.tickLabelColor}
454
455
  >
455
456
  {tick.formattedValue}
@@ -459,7 +460,7 @@ export default function LinearChart() {
459
460
  )
460
461
  })}
461
462
  {!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}>
463
+ <Text x={axisCenter} y={config.orientation === 'horizontal' ? dynamicMarginTop || config.xAxis.labelOffset : dynamicMarginTop || config.xAxis.size} textAnchor='middle' fontWeight='bold' fill={config.xAxis.labelColor}>
463
464
  {props.label}
464
465
  </Text>
465
466
  </Group>
@@ -467,10 +468,9 @@ export default function LinearChart() {
467
468
  }}
468
469
  </AxisBottom>
469
470
  )}
470
-
471
- {config.visualizationType === 'Paired Bar' && (
471
+ {visualizationType === 'Paired Bar' && (
472
472
  <>
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}>
473
+ <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
474
  {props => {
475
475
  return (
476
476
  <Group className='bottom-axis'>
@@ -479,8 +479,8 @@ export default function LinearChart() {
479
479
  const textAnchor = tick.index !== 0 && config.yAxis.tickRotation && config.yAxis.tickRotation > 0 ? 'end' : 'middle'
480
480
  return (
481
481
  <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 && (
482
+ {!runtime.yAxis.hideTicks && <Line from={tick.from} to={tick.to} stroke='#333' />}
483
+ {!runtime.yAxis.hideLabel && (
484
484
  <Text x={tick.to.x} y={tick.to.y} angle={-angle} verticalAnchor='start' textAnchor={textAnchor}>
485
485
  {formatNumber(tick.value, 'left')}
486
486
  </Text>
@@ -488,20 +488,20 @@ export default function LinearChart() {
488
488
  </Group>
489
489
  )
490
490
  })}
491
- {!config.runtime.yAxis.hideAxis && <Line from={props.axisFromPoint} to={props.axisToPoint} stroke='#333' />}
491
+ {!runtime.yAxis.hideAxis && <Line from={props.axisFromPoint} to={props.axisToPoint} stroke='#333' />}
492
492
  </Group>
493
493
  )
494
494
  }}
495
495
  </AxisBottom>
496
496
  <AxisBottom
497
497
  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}
498
+ left={Number(runtime.yAxis.size)}
499
+ label={runtime.xAxis.label}
500
+ tickFormat={runtime.xAxis.type === 'date' ? formatDate : runtime.xAxis.dataKey !== 'Year' ? formatNumber : tick => tick}
501
501
  scale={g2xScale}
502
502
  stroke='#333'
503
503
  tickStroke='#333'
504
- numTicks={config.runtime.xAxis.numTicks || undefined}
504
+ numTicks={runtime.xAxis.numTicks || undefined}
505
505
  >
506
506
  {props => {
507
507
  return (
@@ -512,8 +512,8 @@ export default function LinearChart() {
512
512
  const textAnchor = tick.index !== 0 && config.yAxis.tickRotation && config.yAxis.tickRotation > 0 ? 'end' : 'middle'
513
513
  return (
514
514
  <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 && (
515
+ {!runtime.yAxis.hideTicks && <Line from={tick.from} to={tick.to} stroke='#333' />}
516
+ {!runtime.yAxis.hideLabel && (
517
517
  <Text x={tick.to.x} y={tick.to.y} angle={-angle} verticalAnchor='start' textAnchor={textAnchor}>
518
518
  {formatNumber(tick.value, 'left')}
519
519
  </Text>
@@ -521,11 +521,11 @@ export default function LinearChart() {
521
521
  </Group>
522
522
  )
523
523
  })}
524
- {!config.runtime.yAxis.hideAxis && <Line from={props.axisFromPoint} to={props.axisToPoint} stroke='#333' />}
524
+ {!runtime.yAxis.hideAxis && <Line from={props.axisFromPoint} to={props.axisToPoint} stroke='#333' />}
525
525
  </Group>
526
526
  <Group>
527
527
  <Text x={xMax / 2} y={config.xAxis.labelOffset} stroke='#333' textAnchor={'middle'} verticalAnchor='start'>
528
- {config.runtime.xAxis.label}
528
+ {runtime.xAxis.label}
529
529
  </Text>
530
530
  </Group>
531
531
  </>
@@ -534,73 +534,140 @@ export default function LinearChart() {
534
534
  </AxisBottom>
535
535
  </>
536
536
  )}
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') && (
537
+ {visualizationType === 'Deviation Bar' && <DeviationBar xScale={xScale} yScale={yScale} width={xMax} height={yMax} />}
538
+ {visualizationType === 'Paired Bar' && <PairedBarChart originalWidth={width} width={xMax} height={yMax} />}
539
+ {visualizationType === 'Scatter Plot' && (
540
+ <ScatterPlot
541
+ xScale={xScale}
542
+ yScale={yScale}
543
+ getXAxisData={getXAxisData}
544
+ getYAxisData={getYAxisData}
545
+ xMax={xMax}
546
+ yMax={yMax}
547
+ handleTooltipMouseOver={handleTooltipMouseOver}
548
+ handleTooltipMouseOff={handleTooltipMouseOff}
549
+ handleTooltipClick={handleTooltipClick}
550
+ tooltipData={tooltipData}
551
+ showTooltip={showTooltip}
552
+ />
553
+ )}
554
+ {visualizationType === 'Box Plot' && <CoveBoxPlot xScale={xScale} yScale={yScale} />}
555
+ {(visualizationType === 'Area Chart' || visualizationType === 'Combo') && (
556
+ <AreaChart xScale={xScale} yScale={yScale} yMax={yMax} xMax={xMax} chartRef={svgRef} width={xMax} height={yMax} handleTooltipMouseOver={handleTooltipMouseOver} handleTooltipMouseOff={handleTooltipMouseOff} tooltipData={tooltipData} showTooltip={showTooltip} />
557
+ )}
558
+ {(visualizationType === 'Bar' || visualizationType === 'Combo') && (
559
+ <BarChart
560
+ xScale={xScale}
561
+ yScale={yScale}
562
+ seriesScale={seriesScale}
563
+ xMax={xMax}
564
+ yMax={yMax}
565
+ getXAxisData={getXAxisData}
566
+ getYAxisData={getYAxisData}
567
+ animatedChart={animatedChart}
568
+ visible={animatedChart}
569
+ handleTooltipMouseOver={handleTooltipMouseOver}
570
+ handleTooltipMouseOff={handleTooltipMouseOff}
571
+ handleTooltipClick={handleTooltipClick}
572
+ tooltipData={tooltipData}
573
+ showTooltip={showTooltip}
574
+ chartRef={svgRef}
575
+ />
576
+ )}
577
+ {(visualizationType === 'Line' || visualizationType === 'Combo') && (
578
+ <LineChart
579
+ xScale={xScale}
580
+ yScale={yScale}
581
+ getXAxisData={getXAxisData}
582
+ getYAxisData={getYAxisData}
583
+ xMax={xMax}
584
+ yMax={yMax}
585
+ seriesStyle={config.series}
586
+ handleTooltipMouseOver={handleTooltipMouseOver}
587
+ handleTooltipMouseOff={handleTooltipMouseOff}
588
+ handleTooltipClick={handleTooltipClick}
589
+ tooltipData={tooltipData}
590
+ showTooltip={showTooltip}
591
+ chartRef={svgRef}
592
+ />
593
+ )}
594
+ {(visualizationType === 'Forecasting' || visualizationType === 'Combo') && (
545
595
  <Forecasting
546
- hideTooltip={hideTooltip}
547
596
  showTooltip={showTooltip}
548
597
  tooltipData={tooltipData}
549
598
  xScale={xScale}
550
599
  yScale={yScale}
551
600
  width={xMax}
601
+ le
552
602
  height={yMax}
553
603
  xScaleNoPadding={xScaleNoPadding}
554
604
  chartRef={svgRef}
555
605
  getXValueFromCoordinate={getXValueFromCoordinate}
556
606
  handleTooltipMouseOver={handleTooltipMouseOver}
557
607
  handleTooltipMouseOff={handleTooltipMouseOff}
608
+ isBrush={false}
558
609
  />
559
610
  )}
560
-
561
611
  {/* y anchors */}
562
612
  {config.yAxis.anchors &&
563
613
  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'} />
614
+ 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={runtime.horizontal ? 'none' : 'block'} />
565
615
  })}
566
-
616
+ {visualizationType === 'Forest Plot' && (
617
+ <ForestPlot
618
+ xScale={xScale}
619
+ yScale={yScale}
620
+ seriesScale={seriesScale}
621
+ width={xMax}
622
+ height={yMax}
623
+ maxWidth={width}
624
+ maxHeight={height}
625
+ getXAxisData={getXAxisData}
626
+ getYAxisData={getYAxisData}
627
+ animatedChart={animatedChart}
628
+ visible={animatedChart}
629
+ handleTooltipMouseOver={handleTooltipMouseOver}
630
+ handleTooltipMouseOff={handleTooltipMouseOff}
631
+ handleTooltipClick={handleTooltipClick}
632
+ tooltipData={tooltipData}
633
+ showTooltip={showTooltip}
634
+ chartRef={svgRef}
635
+ config={config}
636
+ />
637
+ )}
567
638
  {/* Line chart */}
568
639
  {/* 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
- )}
580
-
640
+ {visualizationType !== 'Bar' && visualizationType !== 'Paired Bar' && visualizationType !== 'Box Plot' && visualizationType !== 'Area Chart' && visualizationType !== 'Scatter Plot' && visualizationType !== 'Deviation Bar' && visualizationType !== 'Forecasting' && (
641
+ <>
642
+ <LineChart xScale={xScale} yScale={yScale} getXAxisData={getXAxisData} getYAxisData={getYAxisData} xMax={xMax} yMax={yMax} seriesStyle={config.series} />
643
+ </>
644
+ )}
581
645
  {/* y anchors */}
582
646
  {config.yAxis.anchors &&
583
- config.yAxis.anchors.map(anchor => {
647
+ config.yAxis.anchors.map((anchor, index) => {
584
648
  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
649
+ // have to move up
650
+ // const padding = orientation === 'horizontal' ? Number(config.xAxis.size) : Number(config.yAxis.size)
651
+ if (!anchor.value) return
652
+ const middleOffset = orientation === 'horizontal' && visualizationType === 'Bar' ? config.barHeight / 4 : 0
653
+
654
+ if (!anchorPosition) return
587
655
 
588
656
  return (
589
657
  // prettier-ignore
590
658
  <Line
591
- key={anchor.value}
659
+ key={`yAxis-${anchor.value}--${index}`}
592
660
  strokeDasharray={handleLineType(anchor.lineStyle)}
593
661
  stroke={anchor.color ? anchor.color : 'rgba(0,0,0,1)'}
594
662
  className='anchor-y'
595
663
  from={{ x: 0 + padding, y: anchorPosition - middleOffset}}
596
- to={{ x: width, y: anchorPosition - middleOffset }}
664
+ to={{ x: width - config.yAxis.rightAxisSize, y: anchorPosition - middleOffset }}
597
665
  />
598
666
  )
599
667
  })}
600
-
601
668
  {/* x anchors */}
602
669
  {config.xAxis.anchors &&
603
- config.xAxis.anchors.map(anchor => {
670
+ config.xAxis.anchors.map((anchor, index) => {
604
671
  let newX = xAxis
605
672
  if (orientation === 'horizontal') {
606
673
  newX = yAxis
@@ -608,12 +675,15 @@ export default function LinearChart() {
608
675
 
609
676
  let anchorPosition = newX.type === 'date' ? xScale(parseDate(anchor.value, false)) : xScale(anchor.value)
610
677
 
611
- const padding = orientation === 'horizontal' ? Number(config.xAxis.size) : Number(config.yAxis.size)
678
+ // have to move up
679
+ // const padding = orientation === 'horizontal' ? Number(config.xAxis.size) : Number(config.yAxis.size)
680
+
681
+ if (!anchorPosition) return
612
682
 
613
683
  return (
614
684
  // prettier-ignore
615
685
  <Line
616
- key={anchor.value}
686
+ key={`xAxis-${anchor.value}--${index}`}
617
687
  strokeDasharray={handleLineType(anchor.lineStyle)}
618
688
  stroke={anchor.color ? anchor.color : 'rgba(0,0,0,1)'}
619
689
  fill={anchor.color ? anchor.color : 'rgba(0,0,0,1)'}
@@ -623,8 +693,25 @@ export default function LinearChart() {
623
693
  />
624
694
  )
625
695
  })}
696
+ {chartHasTooltipGuides && showTooltip && tooltipData && config.visual.verticalHoverLine && (
697
+ <Group key='tooltipLine-vertical' className='vertical-tooltip-line'>
698
+ <Line from={{ x: tooltipData.dataXPosition - 10, y: 0 }} to={{ x: tooltipData.dataXPosition - 10, y: yMax }} stroke={'black'} strokeWidth={1} pointerEvents='none' strokeDasharray='5,5' className='vertical-tooltip-line' />
699
+ </Group>
700
+ )}
701
+ {chartHasTooltipGuides && showTooltip && tooltipData && config.visual.horizontalHoverLine && (
702
+ <Group key='tooltipLine-horizontal' className='horizontal-tooltip-line' left={config.yAxis.size ? config.yAxis.size : 0}>
703
+ <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' />
704
+ </Group>
705
+ )}
626
706
  </svg>
627
- <ReactTooltip id={`cdc-open-viz-tooltip-${config.runtime.uniqueId}`} variant='light' arrowColor='rgba(0,0,0,0)' className='tooltip' />
707
+ {tooltipData && Object.entries(tooltipData.data).length > 0 && tooltipOpen && showTooltip && tooltipData.dataYPosition && tooltipData.dataXPosition && (
708
+ <TooltipWithBounds key={Math.random()} className={'tooltip cdc-open-viz-module'} style={tooltipStyles(tooltipData)} width={width}>
709
+ <ul>{typeof tooltipData === 'object' && Object.entries(tooltipData.data).map((item, index) => <TooltipListItem item={item} key={index} />)}</ul>
710
+ </TooltipWithBounds>
711
+ )}
712
+ {(config.orientation === 'horizontal' || config.visualizationType === 'Scatter Plot' || config.visualizationType === 'Box Plot') && (
713
+ <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' }} />
714
+ )}
628
715
  <div className='animation-trigger' ref={triggerRef} />
629
716
  </ErrorBoundary>
630
717
  )