@cdc/chart 4.23.7 → 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 (38) hide show
  1. package/LICENSE +201 -0
  2. package/dist/cdcchart.js +27964 -26942
  3. package/examples/feature/__data__/area-chart-date-apple.json +5122 -0
  4. package/examples/feature/__data__/city-temperature.json +2198 -0
  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/forest-plot/broken.json +700 -0
  10. package/examples/feature/forest-plot/data.csv +24 -0
  11. package/examples/feature/forest-plot/forest-plot.json +717 -0
  12. package/examples/feature/pie/planet-pie-example-config.json +1 -1
  13. package/examples/private/confidence_interval_test.json +248 -0
  14. package/examples/private/tooltip-issue.json +45275 -0
  15. package/index.html +13 -11
  16. package/package.json +4 -3
  17. package/src/CdcChart.jsx +24 -14
  18. package/src/components/AreaChart.jsx +84 -59
  19. package/src/components/BarChart.Horizontal.jsx +251 -0
  20. package/src/components/BarChart.StackedHorizontal.jsx +118 -0
  21. package/src/components/BarChart.StackedVertical.jsx +93 -0
  22. package/src/components/BarChart.Vertical.jsx +204 -0
  23. package/src/components/BarChart.jsx +14 -674
  24. package/src/components/BarChartType.jsx +15 -0
  25. package/src/components/BrushHandle.jsx +17 -0
  26. package/src/components/DataTable.jsx +63 -21
  27. package/src/components/EditorPanel.jsx +351 -303
  28. package/src/components/ForestPlot.jsx +191 -0
  29. package/src/components/ForestPlotSettings.jsx +508 -0
  30. package/src/components/LineChart.jsx +2 -2
  31. package/src/components/LinearChart.jsx +115 -310
  32. package/src/data/initial-state.js +43 -0
  33. package/src/hooks/useBarChart.js +186 -0
  34. package/src/hooks/useEditorPermissions.js +218 -0
  35. package/src/hooks/useMinMax.js +15 -3
  36. package/src/hooks/useScales.js +45 -2
  37. package/src/hooks/useTooltip.jsx +407 -0
  38. package/src/scss/main.scss +7 -0
@@ -1,25 +1,24 @@
1
- import React, { forwardRef, useContext, useEffect, useRef, useState } from 'react'
1
+ import React, { useContext, useEffect, useRef, useState, useMemo } from 'react'
2
2
 
3
3
  // Libraries
4
4
  import { AxisLeft, AxisBottom, AxisRight, AxisTop } from '@visx/axis'
5
- import { bisector } from 'd3-array'
6
5
  import { Group } from '@visx/group'
7
6
  import { Line, Bar } from '@visx/shape'
8
- import { localPoint } from '@visx/event'
9
7
  import { Text } from '@visx/text'
10
8
  import { Tooltip as ReactTooltip } from 'react-tooltip'
11
- import { useTooltip, TooltipWithBounds, useTooltipInPortal } from '@visx/tooltip'
9
+ import { useTooltip, TooltipWithBounds } from '@visx/tooltip'
12
10
 
13
11
  // CDC Components
14
12
  import AreaChart from './AreaChart'
15
13
  import BarChart from './BarChart'
16
14
  import ConfigContext from '../ConfigContext'
17
15
  import CoveBoxPlot from './BoxPlot'
18
- import CoveScatterPlot from './ScatterPlot'
16
+ import ScatterPlot from './ScatterPlot'
19
17
  import DeviationBar from './DeviationBar'
20
18
  import ErrorBoundary from '@cdc/core/components/ErrorBoundary'
21
19
  import Forecasting from './Forecasting'
22
20
  import LineChart from './LineChart'
21
+ import ForestPlot from './ForestPlot'
23
22
  import PairedBarChart from './PairedBarChart'
24
23
  import useIntersectionObserver from './useIntersectionObserver'
25
24
 
@@ -29,44 +28,45 @@ import useReduceData from '../hooks/useReduceData'
29
28
  import useRightAxis from '../hooks/useRightAxis'
30
29
  import useScales from '../hooks/useScales'
31
30
  import useTopAxis from '../hooks/useTopAxis'
31
+ import { useTooltip as useCoveTooltip } from '../hooks/useTooltip'
32
32
 
33
33
  // styles
34
- import { defaultStyles } from '@visx/tooltip'
35
34
  import '../scss/LinearChart.scss'
36
35
 
37
36
  export default function LinearChart() {
38
- const { isEditor, transformedData: data, dimensions, config, parseDate, formatDate, currentViewport, formatNumber, handleChartAriaLabels, updateConfig, handleLineType, rawData, capitalize, setSharedFilter, setSharedFilterValue, getTextWidth } = 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)
39
38
 
40
39
  // todo: start destructuring this file for conciseness
41
- const { visualizationType, visualizationSubType, orientation, xAxis, yAxis, runtime } = config
40
+ const { visualizationType, visualizationSubType, orientation, xAxis, yAxis, runtime, debugSvg } = config
42
41
 
43
- // getters & functions
44
- const getXAxisData = d => (runtime.xAxis.type === 'date' ? parseDate(d[runtime.originalXAxis.dataKey]).getTime() : d[runtime.originalXAxis.dataKey])
45
- const getYAxisData = (d, seriesKey) => d[seriesKey]
46
- const xAxisDataMapped = data.map(d => getXAxisData(d))
42
+ const getDate = d => new Date(d[config.xAxis.dataKey])
47
43
 
48
44
  // configure width
49
45
  let [width] = dimensions
50
- let originalWidth = width
51
46
  if (config && config.legend && !config.legend.hide && config.legend.position !== 'bottom' && ['lg', 'md'].includes(currentViewport)) {
52
47
  width = width * 0.73
53
48
  }
54
- // configure height , yMax, xMAx
49
+ // configure height , yMax, xMax
55
50
  const { horizontal: heightHorizontal } = config.heights
56
51
  const isHorizontal = orientation === 'horizontal'
57
52
  const shouldAbbreviate = true
58
- const height = config.aspectRatio ? width * config.aspectRatio : config.heights[orientation]
53
+ let height = config.aspectRatio ? width * config.aspectRatio : config.visualizationType === 'Forest Plot' ? config.heights['vertical'] : config.heights[orientation]
59
54
  const xMax = width - runtime.yAxis.size - (visualizationType === 'Combo' ? config.yAxis.rightAxisSize : 0)
60
- const yMax = height - (orientation === 'horizontal' ? 0 : runtime.xAxis.size)
55
+ let yMax = height - (orientation === 'horizontal' ? 0 : runtime.xAxis.size)
61
56
 
62
- // hooks
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
64
+
65
+ // hooks % states
63
66
  const { minValue, maxValue, existPositiveValue, isAllLine } = useReduceData(config, data)
64
67
  const { yScaleRight, hasRightAxis } = useRightAxis({ config, yMax, data, updateConfig })
65
68
  const { hasTopAxis } = useTopAxis(config)
66
69
  const [animatedChart, setAnimatedChart] = useState(false)
67
- const properties = { data, config, minValue, maxValue, isAllLine, existPositiveValue, xAxisDataMapped, xMax, yMax }
68
- const { min, max } = useMinMax(properties)
69
- const { xScale, yScale, seriesScale, g1xScale, g2xScale, xScaleNoPadding } = useScales({ ...properties, min, max })
70
70
 
71
71
  // refs
72
72
  const triggerRef = useRef()
@@ -75,8 +75,14 @@ export default function LinearChart() {
75
75
  freezeOnceVisible: false
76
76
  })
77
77
 
78
- // a unique id is needed for tooltips.
79
- const tooltip_id = `cdc-open-viz-tooltip-${config.runtime.uniqueId}`
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 })
80
86
 
81
87
  // sets the portal x/y for where tooltips should appear on the page.
82
88
  const [chartPosition, setChartPosition] = useState(null)
@@ -84,31 +90,14 @@ export default function LinearChart() {
84
90
  setChartPosition(svgRef?.current?.getBoundingClientRect())
85
91
  }, [svgRef, config.legend])
86
92
 
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
-
107
- const handleLeftTickFormatting = tick => {
93
+ const handleLeftTickFormatting = (tick, index) => {
108
94
  if (config.useLogScale && tick === 0.1) {
109
95
  //when logarithmic scale applied change value of first tick
110
96
  tick = 0
111
97
  }
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]
112
101
  if (runtime.yAxis.type === 'date') return formatDate(parseDate(tick))
113
102
  if (orientation === 'vertical') return formatNumber(tick, 'left', shouldAbbreviate)
114
103
  return tick
@@ -161,186 +150,24 @@ export default function LinearChart() {
161
150
  return tickCount
162
151
  }
163
152
 
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
153
+ // Tooltip Helpers
201
154
  const { tooltipData, showTooltip, hideTooltip, tooltipOpen } = useTooltip()
202
155
 
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
- }
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
+ })
344
171
 
345
172
  // Make sure the chart is visible if in the editor
346
173
  /* eslint-disable react-hooks/exhaustive-deps */
@@ -370,77 +197,20 @@ export default function LinearChart() {
370
197
  return false
371
198
  }
372
199
 
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
200
+ const padding = orientation === 'horizontal' ? Number(config.xAxis.size) : Number(config.yAxis.size)
425
201
 
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
- }
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')
433
206
  }
434
207
 
435
- const random = Date.now()
436
-
437
208
  return isNaN(width) ? (
438
209
  <React.Fragment></React.Fragment>
439
210
  ) : (
440
211
  <ErrorBoundary component='LinearChart'>
441
- <svg width={width} height={height} className={`linear ${config.animate ? 'animated' : ''} ${animatedChart && config.animate ? 'animate' : ''}`} role='img' aria-label={handleChartAriaLabels(config)} tabIndex={0} ref={svgRef}>
442
- <Bar width={width} height={height} fill={'transparent'}></Bar>
443
- {/* 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 */}
444
214
  {config.regions
445
215
  ? config.regions.map(region => {
446
216
  if (!Object.keys(region).includes('from') || !Object.keys(region).includes('to')) return null
@@ -483,10 +253,9 @@ export default function LinearChart() {
483
253
  )
484
254
  })
485
255
  : ''}
486
-
487
256
  {/* Y axis */}
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')}>
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()}>
490
259
  {props => {
491
260
  const axisCenter = runtime.horizontal ? (props.axisToPoint.y - props.axisFromPoint.y) / 2 : (props.axisFromPoint.y - props.axisToPoint.y) / 2
492
261
  const horizontalTickOffset = yMax / props.ticks.length / 2 - (yMax / props.ticks.length) * (1 - config.barThickness) + 5
@@ -503,7 +272,7 @@ export default function LinearChart() {
503
272
  <Group key={`vx-tick-${tick.value}-${i}`} className={'vx-axis-tick'}>
504
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'} />}
505
274
 
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)' /> : ''}
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)' /> : ''}
507
276
 
508
277
  {orientation === 'horizontal' && visualizationSubType !== 'stacked' && config.yAxis.labelPlacement === 'On Date/Category Axis' && !config.yAxis.hideLabel && (
509
278
  <Text
@@ -549,7 +318,7 @@ export default function LinearChart() {
549
318
  </Group>
550
319
  )
551
320
  })}
552
- {!config.yAxis.hideAxis && <Line from={props.axisFromPoint} to={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' />}
553
322
  {yScale.domain()[0] < 0 && <Line from={{ x: props.axisFromPoint.x, y: yScale(0) }} to={{ x: xMax, y: yScale(0) }} stroke='#333' />}
554
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} />}
555
324
  <Text className='y-label' textAnchor='middle' verticalAnchor='start' transform={`translate(${-1 * runtime.yAxis.size}, ${axisCenter}) rotate(-90)`} fontWeight='bold' fill={config.yAxis.labelColor}>
@@ -560,7 +329,6 @@ export default function LinearChart() {
560
329
  }}
561
330
  </AxisLeft>
562
331
  )}
563
-
564
332
  {/* Right Axis */}
565
333
  {hasRightAxis && (
566
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}>
@@ -593,7 +361,6 @@ export default function LinearChart() {
593
361
  }}
594
362
  </AxisRight>
595
363
  )}
596
-
597
364
  {hasTopAxis && config.topAxis.hasLine && (
598
365
  <AxisTop
599
366
  stroke='#333'
@@ -606,11 +373,10 @@ export default function LinearChart() {
606
373
  })}
607
374
  />
608
375
  )}
609
-
610
376
  {/* X axis */}
611
377
  {visualizationType !== 'Paired Bar' && visualizationType !== 'Spark Line' && (
612
378
  <AxisBottom
613
- top={runtime.horizontal ? Number(heightHorizontal) + Number(config.xAxis.axisPadding) : yMax + Number(config.xAxis.axisPadding)}
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)}
614
380
  left={Number(runtime.yAxis.size)}
615
381
  label={runtime.xAxis.label}
616
382
  tickFormat={handleBottomTickFormatting}
@@ -620,7 +386,7 @@ export default function LinearChart() {
620
386
  tickStroke='#333'
621
387
  >
622
388
  {props => {
623
- 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
624
390
  const containsMultipleWords = inputString => /\s/.test(inputString)
625
391
  const ismultiLabel = props.ticks.some(tick => containsMultipleWords(tick.value))
626
392
 
@@ -628,7 +394,7 @@ export default function LinearChart() {
628
394
  const fontSize = { small: 16, medium: 18, large: 20 }
629
395
  const defaultTickLength = 8
630
396
  const tickWidthMax = Math.max(...props.ticks.map(tick => getTextWidth(tick.formattedValue, `normal ${fontSize[config.fontSize]}px sans-serif`)))
631
- const marginTop = 20
397
+ // const marginTop = 20 // moved to top bc need for yMax calcs
632
398
  const accumulator = ismultiLabel ? 180 : 100
633
399
 
634
400
  const textWidths = props.ticks.map(tick => getTextWidth(tick.formattedValue, `normal ${fontSize[config.fontSize]}px sans-serif`))
@@ -653,9 +419,9 @@ export default function LinearChart() {
653
419
  }
654
420
  })
655
421
 
656
- const dynamicMarginTop = areTicksTouching && config.isResponsiveTicks ? tickWidthMax + defaultTickLength + marginTop : 0
422
+ dynamicMarginTop = areTicksTouching && config.isResponsiveTicks ? tickWidthMax + defaultTickLength + marginTop : 0
657
423
  config.dynamicMarginTop = dynamicMarginTop
658
- // config.xAxis.size = dynamicMarginTop
424
+
659
425
  return (
660
426
  <Group className='bottom-axis'>
661
427
  {props.ticks.map((tick, i, propsTicks) => {
@@ -684,7 +450,7 @@ export default function LinearChart() {
684
450
  angle={tickRotation}
685
451
  verticalAnchor={tickRotation < -50 ? 'middle' : 'start'}
686
452
  textAnchor={tickRotation ? 'end' : 'middle'}
687
- width={areTicksTouching && !config.isResponsiveTicks && !config.xAxis.tickRotation ? limitedWidth : textWidth}
453
+ width={areTicksTouching && !config.isResponsiveTicks && !Number(config[section].tickRotation) ? limitedWidth : textWidth}
688
454
  fill={config.xAxis.tickLabelColor}
689
455
  >
690
456
  {tick.formattedValue}
@@ -702,7 +468,6 @@ export default function LinearChart() {
702
468
  }}
703
469
  </AxisBottom>
704
470
  )}
705
-
706
471
  {visualizationType === 'Paired Bar' && (
707
472
  <>
708
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}>
@@ -771,10 +536,24 @@ export default function LinearChart() {
771
536
  )}
772
537
  {visualizationType === 'Deviation Bar' && <DeviationBar xScale={xScale} yScale={yScale} width={xMax} height={yMax} />}
773
538
  {visualizationType === 'Paired Bar' && <PairedBarChart originalWidth={width} width={xMax} height={yMax} />}
774
- {visualizationType === 'Scatter Plot' && <CoveScatterPlot xScale={xScale} yScale={yScale} getXAxisData={getXAxisData} getYAxisData={getYAxisData} />}
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
+ )}
775
554
  {visualizationType === 'Box Plot' && <CoveBoxPlot xScale={xScale} yScale={yScale} />}
776
555
  {(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} />
556
+ <AreaChart xScale={xScale} yScale={yScale} yMax={yMax} xMax={xMax} chartRef={svgRef} width={xMax} height={yMax} handleTooltipMouseOver={handleTooltipMouseOver} handleTooltipMouseOff={handleTooltipMouseOff} tooltipData={tooltipData} showTooltip={showTooltip} />
778
557
  )}
779
558
  {(visualizationType === 'Bar' || visualizationType === 'Combo') && (
780
559
  <BarChart
@@ -819,15 +598,43 @@ export default function LinearChart() {
819
598
  xScale={xScale}
820
599
  yScale={yScale}
821
600
  width={xMax}
601
+ le
822
602
  height={yMax}
823
603
  xScaleNoPadding={xScaleNoPadding}
824
604
  chartRef={svgRef}
825
605
  getXValueFromCoordinate={getXValueFromCoordinate}
826
606
  handleTooltipMouseOver={handleTooltipMouseOver}
827
607
  handleTooltipMouseOff={handleTooltipMouseOff}
608
+ isBrush={false}
609
+ />
610
+ )}
611
+ {/* y anchors */}
612
+ {config.yAxis.anchors &&
613
+ config.yAxis.anchors.map(anchor => {
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'} />
615
+ })}
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}
828
636
  />
829
637
  )}
830
-
831
638
  {/* Line chart */}
832
639
  {/* TODO: Make this just line or combo? */}
833
640
  {visualizationType !== 'Bar' && visualizationType !== 'Paired Bar' && visualizationType !== 'Box Plot' && visualizationType !== 'Area Chart' && visualizationType !== 'Scatter Plot' && visualizationType !== 'Deviation Bar' && visualizationType !== 'Forecasting' && (
@@ -835,13 +642,13 @@ export default function LinearChart() {
835
642
  <LineChart xScale={xScale} yScale={yScale} getXAxisData={getXAxisData} getYAxisData={getYAxisData} xMax={xMax} yMax={yMax} seriesStyle={config.series} />
836
643
  </>
837
644
  )}
838
-
839
645
  {/* y anchors */}
840
646
  {config.yAxis.anchors &&
841
647
  config.yAxis.anchors.map((anchor, index) => {
842
648
  let anchorPosition = yScale(anchor.value)
649
+ // have to move up
650
+ // const padding = orientation === 'horizontal' ? Number(config.xAxis.size) : Number(config.yAxis.size)
843
651
  if (!anchor.value) return
844
- const padding = orientation === 'horizontal' ? Number(config.xAxis.size) : Number(config.yAxis.size)
845
652
  const middleOffset = orientation === 'horizontal' && visualizationType === 'Bar' ? config.barHeight / 4 : 0
846
653
 
847
654
  if (!anchorPosition) return
@@ -858,7 +665,6 @@ export default function LinearChart() {
858
665
  />
859
666
  )
860
667
  })}
861
-
862
668
  {/* x anchors */}
863
669
  {config.xAxis.anchors &&
864
670
  config.xAxis.anchors.map((anchor, index) => {
@@ -869,7 +675,8 @@ export default function LinearChart() {
869
675
 
870
676
  let anchorPosition = newX.type === 'date' ? xScale(parseDate(anchor.value, false)) : xScale(anchor.value)
871
677
 
872
- 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)
873
680
 
874
681
  if (!anchorPosition) return
875
682
 
@@ -886,13 +693,11 @@ export default function LinearChart() {
886
693
  />
887
694
  )
888
695
  })}
889
-
890
696
  {chartHasTooltipGuides && showTooltip && tooltipData && config.visual.verticalHoverLine && (
891
697
  <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' />
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' />
893
699
  </Group>
894
700
  )}
895
-
896
701
  {chartHasTooltipGuides && showTooltip && tooltipData && config.visual.horizontalHoverLine && (
897
702
  <Group key='tooltipLine-horizontal' className='horizontal-tooltip-line' left={config.yAxis.size ? config.yAxis.size : 0}>
898
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' />