@cdc/chart 4.23.9 → 4.23.10-alpha

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 (40) hide show
  1. package/LICENSE +201 -0
  2. package/dist/cdcchart.js +44124 -44458
  3. package/examples/feature/__data__/area-chart-date-apple.json +1 -5073
  4. package/examples/feature/area/area-chart-date-apple.json +73 -10316
  5. package/examples/feature/area/area-chart-date-city-temperature.json +204 -80
  6. package/examples/feature/area/area-chart-stacked.json +239 -0
  7. package/examples/feature/bar/lollipop.json +156 -0
  8. package/examples/feature/combo/planet-combo-example-config.json +99 -9
  9. package/examples/feature/filters/bar-filter.json +5027 -0
  10. package/examples/feature/legend-highlights/highlights.json +567 -0
  11. package/examples/private/TESTING.json +0 -0
  12. package/examples/private/forest-plot.json +356 -0
  13. package/examples/private/full.json +45324 -0
  14. package/examples/private/missing-color.json +333 -0
  15. package/index.html +11 -7
  16. package/package.json +3 -2
  17. package/src/{CdcChart.jsx → CdcChart.tsx} +81 -74
  18. package/src/_stories/Chart.stories.tsx +188 -0
  19. package/src/components/AreaChart.Stacked.jsx +73 -0
  20. package/src/components/AreaChart.jsx +24 -26
  21. package/src/components/DeviationBar.jsx +67 -13
  22. package/src/components/EditorPanel.jsx +483 -452
  23. package/src/components/Forecasting.jsx +5 -5
  24. package/src/components/ForestPlotSettings.jsx +5 -6
  25. package/src/components/Legend.jsx +7 -6
  26. package/src/components/LineChart.Circle.tsx +102 -0
  27. package/src/components/{LineChart.jsx → LineChart.tsx} +9 -48
  28. package/src/components/LinearChart.jsx +460 -443
  29. package/src/components/PieChart.jsx +54 -25
  30. package/src/components/Series.jsx +63 -17
  31. package/src/components/SparkLine.jsx +7 -19
  32. package/src/data/initial-state.js +6 -0
  33. package/src/hooks/useBarChart.js +1 -1
  34. package/src/hooks/useEditorPermissions.js +87 -24
  35. package/src/hooks/useReduceData.js +5 -0
  36. package/src/hooks/useScales.js +3 -3
  37. package/src/hooks/useTooltip.jsx +19 -6
  38. package/src/scss/main.scss +6 -12
  39. package/src/components/DataTable.jsx +0 -494
  40. /package/src/{components → hooks}/useIntersectionObserver.jsx +0 -0
@@ -6,10 +6,11 @@ import { Group } from '@visx/group'
6
6
  import { Line, Bar } from '@visx/shape'
7
7
  import { Text } from '@visx/text'
8
8
  import { Tooltip as ReactTooltip } from 'react-tooltip'
9
- import { useTooltip, TooltipWithBounds } from '@visx/tooltip'
9
+ import { useTooltip, TooltipWithBounds, useTooltipInPortal } from '@visx/tooltip'
10
10
 
11
11
  // CDC Components
12
12
  import AreaChart from './AreaChart'
13
+ import AreaChartStacked from './AreaChart.Stacked'
13
14
  import BarChart from './BarChart'
14
15
  import ConfigContext from '../ConfigContext'
15
16
  import CoveBoxPlot from './BoxPlot'
@@ -20,7 +21,7 @@ import Forecasting from './Forecasting'
20
21
  import LineChart from './LineChart'
21
22
  import ForestPlot from './ForestPlot'
22
23
  import PairedBarChart from './PairedBarChart'
23
- import useIntersectionObserver from './useIntersectionObserver'
24
+ import useIntersectionObserver from './../hooks/useIntersectionObserver'
24
25
 
25
26
  // Hooks
26
27
  import useMinMax from '../hooks/useMinMax'
@@ -33,7 +34,7 @@ import { useTooltip as useCoveTooltip } from '../hooks/useTooltip'
33
34
  // styles
34
35
  import '../scss/LinearChart.scss'
35
36
 
36
- export default function LinearChart() {
37
+ const LinearChart = props => {
37
38
  const { isEditor, isDashboard, transformedData: data, dimensions, config, parseDate, formatDate, currentViewport, formatNumber, handleChartAriaLabels, updateConfig, handleLineType, rawData, capitalize, setSharedFilter, setSharedFilterValue, getTextWidth, isDebug } = useContext(ConfigContext)
38
39
 
39
40
  // todo: start destructuring this file for conciseness
@@ -151,7 +152,7 @@ export default function LinearChart() {
151
152
  }
152
153
 
153
154
  // Tooltip Helpers
154
- const { tooltipData, showTooltip, hideTooltip, tooltipOpen } = useTooltip()
155
+ const { tooltipData, showTooltip, hideTooltip, tooltipOpen, tooltipLeft, tooltipTop } = useTooltip()
155
156
 
156
157
  // prettier-ignore
157
158
  const {
@@ -209,303 +210,272 @@ export default function LinearChart() {
209
210
  <React.Fragment></React.Fragment>
210
211
  ) : (
211
212
  <ErrorBoundary component='LinearChart'>
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 */}
214
- {config.regions
215
- ? config.regions.map(region => {
216
- if (!Object.keys(region).includes('from') || !Object.keys(region).includes('to')) return null
217
-
218
- let from
219
- let to
220
- let width
221
-
222
- if (config.xAxis.type === 'date') {
223
- from = xScale(parseDate(region.from).getTime())
224
- to = xScale(parseDate(region.to).getTime())
225
- width = to - from
226
- }
213
+ {/* ! Notice - div needed for tooltip boundaries (flip/flop) */}
214
+ <div style={{ width: `${width}px`, height: `${height}px`, overflow: 'visible' }} className='tooltip-boundary'>
215
+ <svg width={'100%'} height={'100%'} className={`linear ${config.animate ? 'animated' : ''} ${animatedChart && config.animate ? 'animate' : ''} ${debugSvg && 'debug'}`} role='img' aria-label={handleChartAriaLabels(config)} ref={svgRef} style={{ overflow: 'visible' }}>
216
+ <Bar width={width} height={height} fill={'transparent'}></Bar> {/* Highlighted regions */}
217
+ {config.regions
218
+ ? config.regions.map(region => {
219
+ if (!Object.keys(region).includes('from') || !Object.keys(region).includes('to')) return null
220
+
221
+ let from
222
+ let to
223
+ let width
224
+
225
+ if (config.xAxis.type === 'date') {
226
+ from = xScale(parseDate(region.from).getTime())
227
+ to = xScale(parseDate(region.to).getTime())
228
+ width = to - from
229
+ }
227
230
 
228
- if (config.xAxis.type === 'categorical') {
229
- from = xScale(region.from)
230
- to = xScale(region.to)
231
- width = to - from
232
- }
231
+ if (config.xAxis.type === 'categorical') {
232
+ from = xScale(region.from)
233
+ to = xScale(region.to)
234
+ width = to - from
235
+ }
233
236
 
234
- if (!from) return null
235
- if (!to) return null
237
+ if (!from) return null
238
+ if (!to) return null
236
239
 
237
- return (
238
- <Group className='regions' left={Number(runtime.yAxis.size)} key={region.label}>
239
- <path
240
- stroke='#333'
241
- d={`M${from} -5
240
+ return (
241
+ <Group className='regions' left={Number(runtime.yAxis.size)} key={region.label}>
242
+ <path
243
+ stroke='#333'
244
+ d={`M${from} -5
242
245
  L${from} 5
243
246
  M${from} 0
244
247
  L${to} 0
245
248
  M${to} -5
246
249
  L${to} 5`}
247
- />
248
- <rect x={from} y={0} width={width} height={yMax} fill={region.background} opacity={0.3} />
249
- <Text x={from + width / 2} y={5} fill={region.color} verticalAnchor='start' textAnchor='middle'>
250
- {region.label}
251
- </Text>
252
- </Group>
253
- )
254
- })
255
- : ''}
256
- {/* Y axis */}
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()}>
259
- {props => {
260
- const axisCenter = runtime.horizontal ? (props.axisToPoint.y - props.axisFromPoint.y) / 2 : (props.axisFromPoint.y - props.axisToPoint.y) / 2
261
- const horizontalTickOffset = yMax / props.ticks.length / 2 - (yMax / props.ticks.length) * (1 - config.barThickness) + 5
262
- return (
263
- <Group className='left-axis'>
264
- {props.ticks.map((tick, i) => {
265
- const minY = props.ticks[0].to.y
266
- const barMinHeight = 15 // 15 is the min height for bars by default
267
- const showTicks = String(tick.value).startsWith('1') || tick.value === 0.1 ? 'block' : 'none'
268
- const tickLength = showTicks === 'block' ? 7 : 0
269
- const to = { x: tick.to.x - tickLength, y: tick.to.y }
270
-
271
- return (
272
- <Group key={`vx-tick-${tick.value}-${i}`} className={'vx-axis-tick'}>
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'} />}
274
-
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)' /> : ''}
276
-
277
- {orientation === 'horizontal' && visualizationSubType !== 'stacked' && config.yAxis.labelPlacement === 'On Date/Category Axis' && !config.yAxis.hideLabel && (
278
- <Text
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})`}
280
- verticalAnchor={'start'}
281
- textAnchor={'end'}
282
- >
283
- {tick.formattedValue}
284
- </Text>
285
- )}
286
-
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'}>
289
- {tick.formattedValue}
290
- </Text>
291
- )}
292
-
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'>
295
- {tick.formattedValue}
296
- </Text>
297
- )}
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'>
300
- {tick.formattedValue}
301
- </Text>
302
- )}
303
-
304
- {orientation === 'vertical' && visualizationType !== 'Paired Bar' && !config.yAxis.hideLabel && (
305
- <Text
306
- display={config.useLogScale ? showTicks : 'block'}
307
- dx={config.useLogScale ? -6 : 0}
308
- x={config.runtime.horizontal ? tick.from.x + 2 : tick.to.x}
309
- y={tick.to.y + (config.runtime.horizontal ? horizontalTickOffset : 0)}
310
- angle={-Number(config.yAxis.tickRotation) || 0}
311
- verticalAnchor={config.runtime.horizontal ? 'start' : 'middle'}
312
- textAnchor={config.runtime.horizontal ? 'start' : 'end'}
313
- fill={config.yAxis.tickLabelColor}
314
- >
315
- {tick.formattedValue}
316
- </Text>
317
- )}
318
- </Group>
319
- )
320
- })}
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' />}
322
- {yScale.domain()[0] < 0 && <Line from={{ x: props.axisFromPoint.x, y: yScale(0) }} to={{ x: xMax, y: yScale(0) }} stroke='#333' />}
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}>
325
- {props.label}
326
- </Text>
327
- </Group>
328
- )
329
- }}
330
- </AxisLeft>
331
- )}
332
- {/* Right Axis */}
333
- {hasRightAxis && (
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}>
335
- {props => {
336
- const axisCenter = runtime.horizontal ? (props.axisToPoint.y - props.axisFromPoint.y) / 2 : (props.axisFromPoint.y - props.axisToPoint.y) / 2
337
- const horizontalTickOffset = yMax / props.ticks.length / 2 - (yMax / props.ticks.length) * (1 - config.barThickness) + 5
338
- return (
339
- <Group className='right-axis'>
340
- {props.ticks.map((tick, i) => {
341
- return (
342
- <Group key={`vx-tick-${tick.value}-${i}`} className='vx-axis-tick'>
343
- {!runtime.yAxis.rightHideTicks && <Line from={tick.from} to={tick.to} display={runtime.horizontal ? 'none' : 'block'} stroke={config.yAxis.rightAxisTickColor} />}
344
-
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)' /> : ''}
346
-
347
- {!config.yAxis.rightHideLabel && (
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}>
349
- {tick.formattedValue}
350
- </Text>
351
- )}
352
- </Group>
353
- )
354
- })}
355
- {!config.yAxis.rightHideAxis && <Line from={props.axisFromPoint} to={props.axisToPoint} stroke='#333' />}
356
- <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}>
357
- {props.label}
358
- </Text>
359
- </Group>
360
- )
361
- }}
362
- </AxisRight>
363
- )}
364
- {hasTopAxis && config.topAxis.hasLine && (
365
- <AxisTop
366
- stroke='#333'
367
- left={Number(runtime.yAxis.size)}
368
- scale={xScale}
369
- hideTicks
370
- hideZero
371
- tickLabelProps={() => ({
372
- fill: 'transparent'
373
- })}
374
- />
375
- )}
376
- {/* X axis */}
377
- {visualizationType !== 'Paired Bar' && visualizationType !== 'Spark Line' && (
378
- <AxisBottom
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}
382
- tickFormat={handleBottomTickFormatting}
383
- scale={xScale}
384
- stroke='#333'
385
- numTicks={countNumOfTicks('xAxis')}
386
- tickStroke='#333'
387
- >
388
- {props => {
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
- }
250
+ />
251
+ <rect x={from} y={0} width={width} height={yMax} fill={region.background} opacity={0.3} />
252
+ <Text x={from + width / 2} y={5} fill={region.color} verticalAnchor='start' textAnchor='middle'>
253
+ {region.label}
254
+ </Text>
255
+ </Group>
256
+ )
420
257
  })
258
+ : ''}
259
+ {/* Y axis */}
260
+ {!['Spark Line', 'Forest Plot'].includes(visualizationType) && (
261
+ <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()}>
262
+ {props => {
263
+ const axisCenter = runtime.horizontal ? (props.axisToPoint.y - props.axisFromPoint.y) / 2 : (props.axisFromPoint.y - props.axisToPoint.y) / 2
264
+ const horizontalTickOffset = yMax / props.ticks.length / 2 - (yMax / props.ticks.length) * (1 - config.barThickness) + 5
265
+ return (
266
+ <Group className='left-axis'>
267
+ {props.ticks.map((tick, i) => {
268
+ const minY = props.ticks[0].to.y
269
+ const barMinHeight = 15 // 15 is the min height for bars by default
270
+ const showTicks = String(tick.value).startsWith('1') || tick.value === 0.1 ? 'block' : 'none'
271
+ const tickLength = showTicks === 'block' ? 7 : 0
272
+ const to = { x: tick.to.x - tickLength, y: tick.to.y }
421
273
 
422
- dynamicMarginTop = areTicksTouching && config.isResponsiveTicks ? tickWidthMax + defaultTickLength + marginTop : 0
423
- config.dynamicMarginTop = dynamicMarginTop
274
+ return (
275
+ <Group key={`vx-tick-${tick.value}-${i}`} className={'vx-axis-tick'}>
276
+ {!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'} />}
424
277
 
425
- return (
426
- <Group className='bottom-axis'>
427
- {props.ticks.map((tick, i, propsTicks) => {
428
- // when using LogScale show major ticks values only
429
- const showTick = String(tick.value).startsWith('1') || tick.value === 0.1 ? 'block' : 'none'
430
- const tickLength = showTick === 'block' ? 16 : defaultTickLength
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)
440
-
441
- return (
442
- <Group key={`vx-tick-${tick.value}-${i}`} className={'vx-axis-tick'}>
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} />}
444
- {!config.xAxis.hideLabel && (
445
- <Text
446
- dy={config.orientation === 'horizontal' && config.useLogScale ? 8 : 0}
447
- display={config.orientation === 'horizontal' && config.useLogScale ? showTick : 'block'}
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}
454
- fill={config.xAxis.tickLabelColor}
455
- >
456
- {tick.formattedValue}
457
- </Text>
458
- )}
459
- </Group>
460
- )
461
- })}
462
- {!config.xAxis.hideAxis && <Line from={props.axisFromPoint} to={props.axisToPoint} stroke='#333' />}
463
- <Text x={axisCenter} y={config.orientation === 'horizontal' ? dynamicMarginTop || config.xAxis.labelOffset : dynamicMarginTop || config.xAxis.size} textAnchor='middle' fontWeight='bold' fill={config.xAxis.labelColor}>
464
- {props.label}
465
- </Text>
466
- </Group>
467
- )
468
- }}
469
- </AxisBottom>
470
- )}
471
- {visualizationType === 'Paired Bar' && (
472
- <>
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}>
278
+ {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)' /> : ''}
279
+
280
+ {orientation === 'horizontal' && visualizationSubType !== 'stacked' && config.yAxis.labelPlacement === 'On Date/Category Axis' && !config.yAxis.hideLabel && (
281
+ <Text
282
+ 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})`}
283
+ verticalAnchor={'start'}
284
+ textAnchor={'end'}
285
+ >
286
+ {tick.formattedValue}
287
+ </Text>
288
+ )}
289
+
290
+ {orientation === 'horizontal' && visualizationSubType === 'stacked' && config.yAxis.labelPlacement === 'On Date/Category Axis' && !config.yAxis.hideLabel && (
291
+ <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'}>
292
+ {tick.formattedValue}
293
+ </Text>
294
+ )}
295
+
296
+ {orientation === 'horizontal' && visualizationType === 'Paired Bar' && !config.yAxis.hideLabel && (
297
+ <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'>
298
+ {tick.formattedValue}
299
+ </Text>
300
+ )}
301
+ {orientation === 'horizontal' && visualizationType === 'Deviation Bar' && !config.yAxis.hideLabel && (
302
+ <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'>
303
+ {tick.formattedValue}
304
+ </Text>
305
+ )}
306
+
307
+ {orientation === 'vertical' && visualizationType !== 'Paired Bar' && !config.yAxis.hideLabel && (
308
+ <Text
309
+ display={config.useLogScale ? showTicks : 'block'}
310
+ dx={config.useLogScale ? -6 : 0}
311
+ x={config.runtime.horizontal ? tick.from.x + 2 : tick.to.x}
312
+ y={tick.to.y + (config.runtime.horizontal ? horizontalTickOffset : 0)}
313
+ angle={-Number(config.yAxis.tickRotation) || 0}
314
+ verticalAnchor={config.runtime.horizontal ? 'start' : 'middle'}
315
+ textAnchor={config.runtime.horizontal ? 'start' : 'end'}
316
+ fill={config.yAxis.tickLabelColor}
317
+ >
318
+ {tick.formattedValue}
319
+ </Text>
320
+ )}
321
+ </Group>
322
+ )
323
+ })}
324
+ {!config.yAxis.hideAxis && <Line from={props.axisFromPoint} to={runtime.horizontal ? { x: 0, y: config.visualizationType === 'Forest Plot' ? height : Number(heightHorizontal) } : props.axisToPoint} stroke='#000' />}
325
+ {yScale.domain()[0] < 0 && <Line from={{ x: props.axisFromPoint.x, y: yScale(0) }} to={{ x: xMax, y: yScale(0) }} stroke='#333' />}
326
+ {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} />}
327
+ <Text className='y-label' textAnchor='middle' verticalAnchor='start' transform={`translate(${-1 * runtime.yAxis.size}, ${axisCenter}) rotate(-90)`} fontWeight='bold' fill={config.yAxis.labelColor}>
328
+ {props.label}
329
+ </Text>
330
+ </Group>
331
+ )
332
+ }}
333
+ </AxisLeft>
334
+ )}
335
+ {/* Right Axis */}
336
+ {hasRightAxis && (
337
+ <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}>
474
338
  {props => {
339
+ const axisCenter = runtime.horizontal ? (props.axisToPoint.y - props.axisFromPoint.y) / 2 : (props.axisFromPoint.y - props.axisToPoint.y) / 2
340
+ const horizontalTickOffset = yMax / props.ticks.length / 2 - (yMax / props.ticks.length) * (1 - config.barThickness) + 5
475
341
  return (
476
- <Group className='bottom-axis'>
342
+ <Group className='right-axis'>
477
343
  {props.ticks.map((tick, i) => {
478
- const angle = tick.index !== 0 ? config.yAxis.tickRotation : 0
479
- const textAnchor = tick.index !== 0 && config.yAxis.tickRotation && config.yAxis.tickRotation > 0 ? 'end' : 'middle'
480
344
  return (
481
- <Group key={`vx-tick-${tick.value}-${i}`} className={'vx-axis-tick'}>
482
- {!runtime.yAxis.hideTicks && <Line from={tick.from} to={tick.to} stroke='#333' />}
483
- {!runtime.yAxis.hideLabel && (
484
- <Text x={tick.to.x} y={tick.to.y} angle={-angle} verticalAnchor='start' textAnchor={textAnchor}>
485
- {formatNumber(tick.value, 'left')}
345
+ <Group key={`vx-tick-${tick.value}-${i}`} className='vx-axis-tick'>
346
+ {!runtime.yAxis.rightHideTicks && <Line from={tick.from} to={tick.to} display={runtime.horizontal ? 'none' : 'block'} stroke={config.yAxis.rightAxisTickColor} />}
347
+
348
+ {runtime.yAxis.rightGridLines ? <Line from={{ x: tick.from.x + xMax, y: tick.from.y }} to={tick.from} stroke='rgba(0,0,0,0.3)' /> : ''}
349
+
350
+ {!config.yAxis.rightHideLabel && (
351
+ <Text x={tick.to.x} y={tick.to.y + (runtime.horizontal ? horizontalTickOffset : 0)} verticalAnchor={runtime.horizontal ? 'start' : 'middle'} textAnchor={'start'} fill={config.yAxis.rightAxisTickLabelColor}>
352
+ {tick.formattedValue}
486
353
  </Text>
487
354
  )}
488
355
  </Group>
489
356
  )
490
357
  })}
491
- {!runtime.yAxis.hideAxis && <Line from={props.axisFromPoint} to={props.axisToPoint} stroke='#333' />}
358
+ {!config.yAxis.rightHideAxis && <Line from={props.axisFromPoint} to={props.axisToPoint} stroke='#333' />}
359
+ <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}>
360
+ {props.label}
361
+ </Text>
492
362
  </Group>
493
363
  )
494
364
  }}
495
- </AxisBottom>
365
+ </AxisRight>
366
+ )}
367
+ {hasTopAxis && config.topAxis.hasLine && (
368
+ <AxisTop
369
+ stroke='#333'
370
+ left={Number(runtime.yAxis.size)}
371
+ scale={xScale}
372
+ hideTicks
373
+ hideZero
374
+ tickLabelProps={() => ({
375
+ fill: 'transparent'
376
+ })}
377
+ />
378
+ )}
379
+ {/* X axis */}
380
+ {visualizationType !== 'Paired Bar' && visualizationType !== 'Spark Line' && (
496
381
  <AxisBottom
497
- top={yMax}
382
+ 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)}
498
383
  left={Number(runtime.yAxis.size)}
499
384
  label={runtime.xAxis.label}
500
- tickFormat={runtime.xAxis.type === 'date' ? formatDate : runtime.xAxis.dataKey !== 'Year' ? formatNumber : tick => tick}
501
- scale={g2xScale}
385
+ tickFormat={handleBottomTickFormatting}
386
+ scale={xScale}
502
387
  stroke='#333'
388
+ numTicks={countNumOfTicks('xAxis')}
503
389
  tickStroke='#333'
504
- numTicks={runtime.xAxis.numTicks || undefined}
505
390
  >
506
391
  {props => {
392
+ const axisCenter = config.visualizationType !== 'Forest Plot' ? (props.axisToPoint.x - props.axisFromPoint.x) / 2 : width / 2
393
+ const containsMultipleWords = inputString => /\s/.test(inputString)
394
+ const ismultiLabel = props.ticks.some(tick => containsMultipleWords(tick.value))
395
+
396
+ // Calculate sumOfTickWidth here, before map function
397
+ const fontSize = { small: 16, medium: 18, large: 20 }
398
+ const defaultTickLength = 8
399
+ const tickWidthMax = Math.max(...props.ticks.map(tick => getTextWidth(tick.formattedValue, `normal ${fontSize[config.fontSize]}px sans-serif`)))
400
+ // const marginTop = 20 // moved to top bc need for yMax calcs
401
+ const accumulator = ismultiLabel ? 180 : 100
402
+
403
+ const textWidths = props.ticks.map(tick => getTextWidth(tick.formattedValue, `normal ${fontSize[config.fontSize]}px sans-serif`))
404
+ const sumOfTickWidth = textWidths.reduce((a, b) => a + b, accumulator)
405
+ const spaceBetweenEachTick = (xMax - sumOfTickWidth) / (props.ticks.length - 1)
406
+
407
+ // Check if ticks are overlapping
408
+ // Determine the position of each tick
409
+ let positions = [0] // The first tick is at position 0
410
+ for (let i = 1; i < textWidths.length; i++) {
411
+ // The position of each subsequent tick is the position of the previous tick
412
+ // plus the width of the previous tick and the space
413
+ positions[i] = positions[i - 1] + textWidths[i - 1] + spaceBetweenEachTick
414
+ }
415
+
416
+ // Check if ticks are overlapping
417
+ let areTicksTouching = false
418
+ textWidths.forEach((_, i) => {
419
+ if (positions[i] + textWidths[i] > positions[i + 1]) {
420
+ areTicksTouching = true
421
+ return
422
+ }
423
+ })
424
+
425
+ dynamicMarginTop = areTicksTouching && config.isResponsiveTicks ? tickWidthMax + defaultTickLength + marginTop : 0
426
+ config.dynamicMarginTop = dynamicMarginTop
427
+
507
428
  return (
508
- <>
429
+ <Group className='bottom-axis'>
430
+ {props.ticks.map((tick, i, propsTicks) => {
431
+ // when using LogScale show major ticks values only
432
+ const showTick = String(tick.value).startsWith('1') || tick.value === 0.1 ? 'block' : 'none'
433
+ const tickLength = showTick === 'block' ? 16 : defaultTickLength
434
+ const to = { x: tick.to.x, y: tickLength }
435
+ let textWidth = getTextWidth(tick.formattedValue, `normal ${fontSize[config.fontSize]}px sans-serif`)
436
+ let limitedWidth = 100 / propsTicks.length
437
+ //reset rotations by updating config
438
+ config.yAxis.tickRotation = config.isResponsiveTicks && config.orientation === 'horizontal' ? 0 : config.yAxis.tickRotation
439
+ config.xAxis.tickRotation = config.isResponsiveTicks && config.orientation === 'vertical' ? 0 : config.xAxis.tickRotation
440
+ //configure rotation
441
+
442
+ const tickRotation = config.isResponsiveTicks && areTicksTouching ? -Number(config.xAxis.maxTickRotation) || -90 : -Number(config.runtime.xAxis.tickRotation)
443
+
444
+ return (
445
+ <Group key={`vx-tick-${tick.value}-${i}`} className={'vx-axis-tick'}>
446
+ {!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} />}
447
+ {!config.xAxis.hideLabel && (
448
+ <Text
449
+ dy={config.orientation === 'horizontal' && config.useLogScale ? 8 : 0}
450
+ display={config.orientation === 'horizontal' && config.useLogScale ? showTick : 'block'}
451
+ x={tick.to.x}
452
+ y={tick.to.y}
453
+ angle={tickRotation}
454
+ verticalAnchor={tickRotation < -50 ? 'middle' : 'start'}
455
+ textAnchor={tickRotation ? 'end' : 'middle'}
456
+ width={areTicksTouching && !config.isResponsiveTicks && !Number(config[section].tickRotation) ? limitedWidth : textWidth}
457
+ fill={config.xAxis.tickLabelColor}
458
+ >
459
+ {tick.formattedValue}
460
+ </Text>
461
+ )}
462
+ </Group>
463
+ )
464
+ })}
465
+ {!config.xAxis.hideAxis && <Line from={props.axisFromPoint} to={props.axisToPoint} stroke='#333' />}
466
+ <Text x={axisCenter} y={config.orientation === 'horizontal' ? dynamicMarginTop || config.xAxis.labelOffset : dynamicMarginTop || config.xAxis.size} textAnchor='middle' fontWeight='bold' fill={config.xAxis.labelColor}>
467
+ {props.label}
468
+ </Text>
469
+ </Group>
470
+ )
471
+ }}
472
+ </AxisBottom>
473
+ )}
474
+ {visualizationType === 'Paired Bar' && (
475
+ <>
476
+ <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}>
477
+ {props => {
478
+ return (
509
479
  <Group className='bottom-axis'>
510
480
  {props.ticks.map((tick, i) => {
511
481
  const angle = tick.index !== 0 ? config.yAxis.tickRotation : 0
@@ -523,139 +493,175 @@ export default function LinearChart() {
523
493
  })}
524
494
  {!runtime.yAxis.hideAxis && <Line from={props.axisFromPoint} to={props.axisToPoint} stroke='#333' />}
525
495
  </Group>
526
- <Group>
527
- <Text x={xMax / 2} y={config.xAxis.labelOffset} stroke='#333' textAnchor={'middle'} verticalAnchor='start'>
528
- {runtime.xAxis.label}
529
- </Text>
530
- </Group>
531
- </>
532
- )
533
- }}
534
- </AxisBottom>
535
- </>
536
- )}
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') && (
595
- <Forecasting
596
- showTooltip={showTooltip}
597
- tooltipData={tooltipData}
598
- xScale={xScale}
599
- yScale={yScale}
600
- width={xMax}
601
- le
602
- height={yMax}
603
- xScaleNoPadding={xScaleNoPadding}
604
- chartRef={svgRef}
605
- getXValueFromCoordinate={getXValueFromCoordinate}
606
- handleTooltipMouseOver={handleTooltipMouseOver}
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}
636
- />
637
- )}
638
- {/* Line chart */}
639
- {/* TODO: Make this just line or combo? */}
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
- )}
645
- {/* y anchors */}
646
- {config.yAxis.anchors &&
647
- config.yAxis.anchors.map((anchor, index) => {
648
- let anchorPosition = yScale(anchor.value)
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
655
-
656
- return (
657
- // prettier-ignore
658
- <Line
496
+ )
497
+ }}
498
+ </AxisBottom>
499
+ <AxisBottom
500
+ top={yMax}
501
+ left={Number(runtime.yAxis.size)}
502
+ label={runtime.xAxis.label}
503
+ tickFormat={runtime.xAxis.type === 'date' ? formatDate : runtime.xAxis.dataKey !== 'Year' ? formatNumber : tick => tick}
504
+ scale={g2xScale}
505
+ stroke='#333'
506
+ tickStroke='#333'
507
+ numTicks={runtime.xAxis.numTicks || undefined}
508
+ >
509
+ {props => {
510
+ return (
511
+ <>
512
+ <Group className='bottom-axis'>
513
+ {props.ticks.map((tick, i) => {
514
+ const angle = tick.index !== 0 ? config.yAxis.tickRotation : 0
515
+ const textAnchor = tick.index !== 0 && config.yAxis.tickRotation && config.yAxis.tickRotation > 0 ? 'end' : 'middle'
516
+ return (
517
+ <Group key={`vx-tick-${tick.value}-${i}`} className={'vx-axis-tick'}>
518
+ {!runtime.yAxis.hideTicks && <Line from={tick.from} to={tick.to} stroke='#333' />}
519
+ {!runtime.yAxis.hideLabel && (
520
+ <Text x={tick.to.x} y={tick.to.y} angle={-angle} verticalAnchor='start' textAnchor={textAnchor}>
521
+ {formatNumber(tick.value, 'left')}
522
+ </Text>
523
+ )}
524
+ </Group>
525
+ )
526
+ })}
527
+ {!runtime.yAxis.hideAxis && <Line from={props.axisFromPoint} to={props.axisToPoint} stroke='#333' />}
528
+ </Group>
529
+ <Group>
530
+ <Text x={xMax / 2} y={config.xAxis.labelOffset} stroke='#333' textAnchor={'middle'} verticalAnchor='start'>
531
+ {runtime.xAxis.label}
532
+ </Text>
533
+ </Group>
534
+ </>
535
+ )
536
+ }}
537
+ </AxisBottom>
538
+ </>
539
+ )}
540
+ {visualizationType === 'Deviation Bar' && <DeviationBar animatedChart={animatedChart} xScale={xScale} yScale={yScale} width={xMax} height={yMax} />}
541
+ {visualizationType === 'Paired Bar' && <PairedBarChart originalWidth={width} width={xMax} height={yMax} />}
542
+ {visualizationType === 'Scatter Plot' && (
543
+ <ScatterPlot
544
+ xScale={xScale}
545
+ yScale={yScale}
546
+ getXAxisData={getXAxisData}
547
+ getYAxisData={getYAxisData}
548
+ xMax={xMax}
549
+ yMax={yMax}
550
+ handleTooltipMouseOver={handleTooltipMouseOver}
551
+ handleTooltipMouseOff={handleTooltipMouseOff}
552
+ handleTooltipClick={handleTooltipClick}
553
+ tooltipData={tooltipData}
554
+ showTooltip={showTooltip}
555
+ />
556
+ )}
557
+ {visualizationType === 'Box Plot' && <CoveBoxPlot xScale={xScale} yScale={yScale} />}
558
+ {((visualizationType === 'Area Chart' && config.visualizationSubType === 'regular') || visualizationType === 'Combo') && (
559
+ <AreaChart xScale={xScale} yScale={yScale} yMax={yMax} xMax={xMax} chartRef={svgRef} width={xMax} height={yMax} handleTooltipMouseOver={handleTooltipMouseOver} handleTooltipMouseOff={handleTooltipMouseOff} tooltipData={tooltipData} showTooltip={showTooltip} />
560
+ )}
561
+ {((visualizationType === 'Area Chart' && config.visualizationSubType === 'stacked') || visualizationType === 'Combo') && (
562
+ <AreaChartStacked xScale={xScale} yScale={yScale} yMax={yMax} xMax={xMax} chartRef={svgRef} width={xMax} height={yMax} handleTooltipMouseOver={handleTooltipMouseOver} handleTooltipMouseOff={handleTooltipMouseOff} tooltipData={tooltipData} showTooltip={showTooltip} />
563
+ )}
564
+ {(visualizationType === 'Bar' || visualizationType === 'Combo') && (
565
+ <BarChart
566
+ xScale={xScale}
567
+ yScale={yScale}
568
+ seriesScale={seriesScale}
569
+ xMax={xMax}
570
+ yMax={yMax}
571
+ getXAxisData={getXAxisData}
572
+ getYAxisData={getYAxisData}
573
+ animatedChart={animatedChart}
574
+ visible={animatedChart}
575
+ handleTooltipMouseOver={handleTooltipMouseOver}
576
+ handleTooltipMouseOff={handleTooltipMouseOff}
577
+ handleTooltipClick={handleTooltipClick}
578
+ tooltipData={tooltipData}
579
+ showTooltip={showTooltip}
580
+ chartRef={svgRef}
581
+ />
582
+ )}
583
+ {(visualizationType === 'Line' || visualizationType === 'Combo') && (
584
+ <LineChart
585
+ xScale={xScale}
586
+ yScale={yScale}
587
+ getXAxisData={getXAxisData}
588
+ getYAxisData={getYAxisData}
589
+ xMax={xMax}
590
+ yMax={yMax}
591
+ seriesStyle={config.series}
592
+ handleTooltipMouseOver={handleTooltipMouseOver}
593
+ handleTooltipMouseOff={handleTooltipMouseOff}
594
+ handleTooltipClick={handleTooltipClick}
595
+ tooltipData={tooltipData}
596
+ showTooltip={showTooltip}
597
+ chartRef={svgRef}
598
+ />
599
+ )}
600
+ {(visualizationType === 'Forecasting' || visualizationType === 'Combo') && (
601
+ <Forecasting
602
+ showTooltip={showTooltip}
603
+ tooltipData={tooltipData}
604
+ xScale={xScale}
605
+ yScale={yScale}
606
+ width={xMax}
607
+ le
608
+ height={yMax}
609
+ xScaleNoPadding={xScaleNoPadding}
610
+ chartRef={svgRef}
611
+ getXValueFromCoordinate={getXValueFromCoordinate}
612
+ handleTooltipMouseOver={handleTooltipMouseOver}
613
+ handleTooltipMouseOff={handleTooltipMouseOff}
614
+ isBrush={false}
615
+ />
616
+ )}
617
+ {/* y anchors */}
618
+ {config.yAxis.anchors &&
619
+ config.yAxis.anchors.map(anchor => {
620
+ 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'} />
621
+ })}
622
+ {visualizationType === 'Forest Plot' && (
623
+ <ForestPlot
624
+ xScale={xScale}
625
+ yScale={yScale}
626
+ seriesScale={seriesScale}
627
+ width={xMax}
628
+ height={yMax}
629
+ maxWidth={width}
630
+ maxHeight={height}
631
+ getXAxisData={getXAxisData}
632
+ getYAxisData={getYAxisData}
633
+ animatedChart={animatedChart}
634
+ visible={animatedChart}
635
+ handleTooltipMouseOver={handleTooltipMouseOver}
636
+ handleTooltipMouseOff={handleTooltipMouseOff}
637
+ handleTooltipClick={handleTooltipClick}
638
+ tooltipData={tooltipData}
639
+ showTooltip={showTooltip}
640
+ chartRef={svgRef}
641
+ config={config}
642
+ />
643
+ )}
644
+ {/* Line chart */}
645
+ {/* TODO: Make this just line or combo? */}
646
+ {visualizationType !== 'Bar' && visualizationType !== 'Paired Bar' && visualizationType !== 'Box Plot' && visualizationType !== 'Area Chart' && visualizationType !== 'Scatter Plot' && visualizationType !== 'Deviation Bar' && visualizationType !== 'Forecasting' && (
647
+ <>
648
+ <LineChart xScale={xScale} yScale={yScale} getXAxisData={getXAxisData} getYAxisData={getYAxisData} xMax={xMax} yMax={yMax} seriesStyle={config.series} />
649
+ </>
650
+ )}
651
+ {/* y anchors */}
652
+ {config.yAxis.anchors &&
653
+ config.yAxis.anchors.map((anchor, index) => {
654
+ let anchorPosition = yScale(anchor.value)
655
+ // have to move up
656
+ // const padding = orientation === 'horizontal' ? Number(config.xAxis.size) : Number(config.yAxis.size)
657
+ if (!anchor.value) return
658
+ const middleOffset = orientation === 'horizontal' && visualizationType === 'Bar' ? config.barHeight / 4 : 0
659
+
660
+ if (!anchorPosition) return
661
+
662
+ return (
663
+ // prettier-ignore
664
+ <Line
659
665
  key={`yAxis-${anchor.value}--${index}`}
660
666
  strokeDasharray={handleLineType(anchor.lineStyle)}
661
667
  stroke={anchor.color ? anchor.color : 'rgba(0,0,0,1)'}
@@ -663,26 +669,26 @@ export default function LinearChart() {
663
669
  from={{ x: 0 + padding, y: anchorPosition - middleOffset}}
664
670
  to={{ x: width - config.yAxis.rightAxisSize, y: anchorPosition - middleOffset }}
665
671
  />
666
- )
667
- })}
668
- {/* x anchors */}
669
- {config.xAxis.anchors &&
670
- config.xAxis.anchors.map((anchor, index) => {
671
- let newX = xAxis
672
- if (orientation === 'horizontal') {
673
- newX = yAxis
674
- }
675
-
676
- let anchorPosition = newX.type === 'date' ? xScale(parseDate(anchor.value, false)) : xScale(anchor.value)
677
-
678
- // have to move up
679
- // const padding = orientation === 'horizontal' ? Number(config.xAxis.size) : Number(config.yAxis.size)
680
-
681
- if (!anchorPosition) return
682
-
683
- return (
684
- // prettier-ignore
685
- <Line
672
+ )
673
+ })}
674
+ {/* x anchors */}
675
+ {config.xAxis.anchors &&
676
+ config.xAxis.anchors.map((anchor, index) => {
677
+ let newX = xAxis
678
+ if (orientation === 'horizontal') {
679
+ newX = yAxis
680
+ }
681
+
682
+ let anchorPosition = newX.type === 'date' ? xScale(parseDate(anchor.value, false)) : xScale(anchor.value)
683
+
684
+ // have to move up
685
+ // const padding = orientation === 'horizontal' ? Number(config.xAxis.size) : Number(config.yAxis.size)
686
+
687
+ if (!anchorPosition) return
688
+
689
+ return (
690
+ // prettier-ignore
691
+ <Line
686
692
  key={`xAxis-${anchor.value}--${index}`}
687
693
  strokeDasharray={handleLineType(anchor.lineStyle)}
688
694
  stroke={anchor.color ? anchor.color : 'rgba(0,0,0,1)'}
@@ -691,28 +697,39 @@ export default function LinearChart() {
691
697
  from={{ x: Number(anchorPosition) + Number(padding), y: 0 }}
692
698
  to={{ x: Number(anchorPosition) + Number(padding), y: yMax }}
693
699
  />
694
- )
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
+ })}
702
+ {chartHasTooltipGuides && showTooltip && tooltipData && config.visual.verticalHoverLine && (
703
+ <Group key='tooltipLine-vertical' className='vertical-tooltip-line'>
704
+ <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' />
705
+ </Group>
706
+ )}
707
+ {chartHasTooltipGuides && showTooltip && tooltipData && config.visual.horizontalHoverLine && (
708
+ <Group key='tooltipLine-horizontal' className='horizontal-tooltip-line' left={config.yAxis.size ? config.yAxis.size : 0}>
709
+ <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' />
710
+ </Group>
711
+ )}
712
+ {config.filters && config.filters.values.length === 0 && data.length === 0 && (
713
+ <Text x={Number(config.yAxis.size) + Number(xMax / 2)} y={height / 2 - config.xAxis.size / 2} textAnchor='middle'>
714
+ {config.chartMessage.noData}
715
+ </Text>
716
+ )}
717
+ </svg>
718
+ {tooltipData && Object.entries(tooltipData.data).length > 0 && tooltipOpen && showTooltip && tooltipData.dataYPosition && tooltipData.dataXPosition && (
719
+ <>
720
+ <style>{`.tooltip {background-color: rgba(255,255,255, ${config.tooltips.opacity / 100}) !important`}</style>
721
+ <TooltipWithBounds key={Math.random()} className={'tooltip cdc-open-viz-module'} left={tooltipLeft} top={tooltipTop}>
722
+ <ul>{typeof tooltipData === 'object' && Object.entries(tooltipData.data).map((item, index) => <TooltipListItem item={item} key={index} />)}</ul>
723
+ </TooltipWithBounds>
724
+ </>
700
725
  )}
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>
726
+ {(config.orientation === 'horizontal' || config.visualizationType === 'Scatter Plot' || config.visualizationType === 'Box Plot') && (
727
+ <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' }} />
705
728
  )}
706
- </svg>
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
- )}
715
- <div className='animation-trigger' ref={triggerRef} />
729
+ <div className='animation-trigger' ref={triggerRef} />
730
+ </div>
716
731
  </ErrorBoundary>
717
732
  )
718
733
  }
734
+
735
+ export default LinearChart