@cdc/chart 4.23.10 → 4.24.1

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 (125) hide show
  1. package/dist/cdcchart.js +34606 -32218
  2. package/examples/feature/bar/additional-column-tooltip.json +446 -0
  3. package/examples/feature/bar/example-bar-chart.json +1 -46
  4. package/examples/feature/bar/lollipop.json +156 -0
  5. package/examples/feature/bar/tall-data.json +98 -0
  6. package/examples/feature/combo/planet-combo-example-config.json +99 -9
  7. package/examples/feature/dev-4261.json +399 -0
  8. package/examples/feature/forest-plot/forest-plot.json +63 -19
  9. package/examples/feature/forest-plot/{broken.json → linear.json} +77 -23
  10. package/examples/feature/forest-plot/log.json +26 -0
  11. package/examples/feature/forest-plot/logarithmic.json +271 -0
  12. package/examples/feature/line/line-chart-preliminary.json +346 -0
  13. package/examples/feature/line/line-points.json +340 -0
  14. package/examples/feature/regions/index.json +462 -0
  15. package/examples/feature/scatterplot/scatterplot.json +272 -33
  16. package/examples/gallery/bar-chart-vertical/combo-line-chart.json +181 -48
  17. package/examples/private/chart-t.json +3740 -0
  18. package/examples/private/combo.json +369 -0
  19. package/examples/private/epi-data.csv +13 -0
  20. package/examples/private/epi-data.json +62 -0
  21. package/examples/private/epi.json +403 -0
  22. package/examples/private/occupancy.json +109283 -0
  23. package/examples/private/prod-line-config.json +401 -0
  24. package/examples/private/region-data.json +822 -0
  25. package/examples/private/region-testing.json +312 -0
  26. package/examples/private/scaling.json +45325 -0
  27. package/examples/private/testing-data.json +1739 -0
  28. package/examples/private/testing.json +816 -0
  29. package/examples/sparkline-multilple.json +846 -0
  30. package/index.html +12 -8
  31. package/package.json +3 -3
  32. package/src/CdcChart.tsx +42 -211
  33. package/src/ConfigContext.tsx +6 -0
  34. package/src/_stories/Chart.stories.tsx +188 -0
  35. package/src/_stories/Chart.tooltip.stories.tsx +305 -0
  36. package/src/_stories/ChartBrush.stories.tsx +19 -0
  37. package/src/_stories/ChartEditor.stories.tsx +22 -0
  38. package/src/_stories/ChartLine.preliminary.tsx +19 -0
  39. package/src/_stories/ChartSuppress.stories.tsx +19 -0
  40. package/src/_stories/_mock/brush_mock.json +393 -0
  41. package/src/_stories/_mock/pie_config.json +191 -0
  42. package/src/_stories/_mock/pie_data.json +218 -0
  43. package/src/_stories/_mock/preliminary_mock.json +346 -0
  44. package/src/_stories/_mock/suppress_mock.json +911 -0
  45. package/src/components/{AreaChart.Stacked.jsx → AreaChart/components/AreaChart.Stacked.jsx} +6 -7
  46. package/src/components/{AreaChart.jsx → AreaChart/components/AreaChart.jsx} +7 -36
  47. package/src/components/AreaChart/index.tsx +4 -0
  48. package/src/components/{BarChart.Horizontal.jsx → BarChart/components/BarChart.Horizontal.tsx} +111 -34
  49. package/src/components/{BarChart.StackedHorizontal.jsx → BarChart/components/BarChart.StackedHorizontal.tsx} +55 -20
  50. package/src/components/BarChart/components/BarChart.StackedVertical.tsx +106 -0
  51. package/src/components/{BarChart.Vertical.jsx → BarChart/components/BarChart.Vertical.tsx} +162 -34
  52. package/src/components/BarChart/components/BarChart.jsx +39 -0
  53. package/src/components/{BarChartType.jsx → BarChart/components/BarChartType.jsx} +0 -2
  54. package/src/components/BarChart/components/context.tsx +13 -0
  55. package/src/components/BarChart/index.tsx +3 -0
  56. package/src/components/{BoxPlot.jsx → BoxPlot/BoxPlot.jsx} +1 -1
  57. package/src/components/BoxPlot/index.tsx +3 -0
  58. package/src/components/DeviationBar.jsx +4 -3
  59. package/src/components/{EditorPanel.jsx → EditorPanel/EditorPanel.tsx} +807 -865
  60. package/src/components/EditorPanel/components/Panel.DateHighlighting.tsx +109 -0
  61. package/src/components/{ForestPlotSettings.jsx → EditorPanel/components/Panel.ForestPlotSettings.tsx} +190 -220
  62. package/src/components/EditorPanel/components/Panel.Regions.tsx +168 -0
  63. package/src/components/{Series.jsx → EditorPanel/components/Panel.Series.tsx} +23 -4
  64. package/src/components/EditorPanel/components/PanelProps.ts +3 -0
  65. package/src/components/EditorPanel/components/Panels.tsx +13 -0
  66. package/src/components/EditorPanel/components/panels.scss +72 -0
  67. package/src/components/EditorPanel/editor-panel.scss +751 -0
  68. package/src/components/EditorPanel/index.tsx +3 -0
  69. package/src/{hooks → components/EditorPanel}/useEditorPermissions.js +50 -5
  70. package/src/components/{Forecasting.jsx → Forecasting/Forecasting.jsx} +1 -1
  71. package/src/components/Forecasting/index.tsx +3 -0
  72. package/src/components/ForestPlot/ForestPlot.tsx +254 -0
  73. package/src/components/ForestPlot/ForestPlotProps.ts +18 -0
  74. package/src/components/ForestPlot/index.scss +1 -0
  75. package/src/components/ForestPlot/index.tsx +3 -0
  76. package/src/components/Legend/Legend.tsx +347 -0
  77. package/src/components/Legend/index.tsx +3 -0
  78. package/src/components/LineChart/LineChartProps.ts +46 -0
  79. package/src/components/{LineChart.Circle.tsx → LineChart/components/LineChart.Circle.tsx} +36 -30
  80. package/src/components/LineChart/helpers.ts +45 -0
  81. package/src/components/LineChart/index.scss +1 -0
  82. package/src/components/{LineChart.tsx → LineChart/index.tsx} +83 -42
  83. package/src/components/LinearChart.jsx +125 -82
  84. package/src/components/PairedBarChart.jsx +2 -2
  85. package/src/components/{PieChart.jsx → PieChart/PieChart.tsx} +16 -7
  86. package/src/components/PieChart/index.tsx +3 -0
  87. package/src/components/Regions/components/Regions.tsx +135 -0
  88. package/src/components/Regions/index.tsx +3 -0
  89. package/src/components/{ScatterPlot.jsx → ScatterPlot/ScatterPlot.jsx} +3 -3
  90. package/src/components/ScatterPlot/index.tsx +3 -0
  91. package/src/components/{SparkLine.jsx → Sparkline/SparkLine.jsx} +2 -2
  92. package/src/components/Sparkline/index.tsx +3 -0
  93. package/src/components/ZoomBrush.tsx +168 -0
  94. package/src/data/initial-state.js +30 -16
  95. package/src/helpers/abbreviateNumber.ts +17 -0
  96. package/src/helpers/computeMarginBottom.ts +55 -0
  97. package/src/helpers/filterData.ts +18 -0
  98. package/src/helpers/generateColorsArray.ts +8 -0
  99. package/src/helpers/getQuartiles.ts +30 -0
  100. package/src/helpers/handleChartAriaLabels.ts +19 -0
  101. package/src/helpers/handleLineType.ts +18 -0
  102. package/src/helpers/lineOptions.ts +18 -0
  103. package/src/helpers/sort.ts +7 -0
  104. package/src/helpers/tests/computeMarginBottom.test.ts +20 -0
  105. package/src/hooks/useBarChart.js +72 -7
  106. package/src/hooks/useColorScale.ts +50 -0
  107. package/src/hooks/{useMinMax.js → useMinMax.ts} +75 -23
  108. package/src/hooks/{useRightAxis.js → useRightAxis.ts} +10 -2
  109. package/src/hooks/{useScales.js → useScales.ts} +64 -17
  110. package/src/hooks/{useTooltip.jsx → useTooltip.tsx} +84 -55
  111. package/src/scss/main.scss +70 -38
  112. package/src/types/ChartConfig.ts +178 -0
  113. package/src/types/ChartContext.ts +54 -0
  114. package/src/types/ForestPlot.ts +53 -0
  115. package/examples/feature/scatterplot/scatterplot-continuous.csv +0 -17
  116. package/src/ConfigContext.jsx +0 -5
  117. package/src/components/BarChart.StackedVertical.jsx +0 -95
  118. package/src/components/BarChart.jsx +0 -30
  119. package/src/components/ForestPlot.jsx +0 -191
  120. package/src/components/Legend.jsx +0 -277
  121. package/src/scss/LinearChart.scss +0 -0
  122. package/src/scss/editor-panel.scss +0 -745
  123. package/src/scss/legend.scss +0 -206
  124. package/src/scss/mixins.scss +0 -0
  125. package/src/scss/variables.scss +0 -1
@@ -9,9 +9,10 @@ import { Text } from '@visx/text'
9
9
  import { useTooltip, TooltipWithBounds } from '@visx/tooltip'
10
10
 
11
11
  // cove
12
- import ConfigContext from '../ConfigContext'
13
- import { useTooltip as useCoveTooltip } from '../hooks/useTooltip'
14
- import useIntersectionObserver from '../hooks/useIntersectionObserver'
12
+ import ConfigContext from '../../ConfigContext'
13
+ import { useTooltip as useCoveTooltip } from '../../hooks/useTooltip'
14
+ import useIntersectionObserver from '../../hooks/useIntersectionObserver'
15
+ import { handleChartAriaLabels } from '../../helpers/handleChartAriaLabels'
15
16
  import ErrorBoundary from '@cdc/core/components/ErrorBoundary'
16
17
 
17
18
  const enterUpdateTransition = ({ startAngle, endAngle }) => ({
@@ -19,9 +20,17 @@ const enterUpdateTransition = ({ startAngle, endAngle }) => ({
19
20
  endAngle
20
21
  })
21
22
 
23
+ type TooltipData = {
24
+ data: {
25
+ [key: string]: string | number
26
+ }
27
+ dataXPosition: number
28
+ dataYPosition: number
29
+ }
30
+
22
31
  const PieChart = props => {
23
- const { transformedData: data, config, dimensions, seriesHighlight, colorScale, formatNumber, currentViewport, handleChartAriaLabels, isEditor } = useContext(ConfigContext)
24
- const { tooltipData, showTooltip, hideTooltip, tooltipOpen, tooltipLeft, tooltipTop } = useTooltip()
32
+ const { transformedData: data, config, dimensions, seriesHighlight, colorScale, currentViewport } = useContext(ConfigContext)
33
+ const { tooltipData, showTooltip, hideTooltip, tooltipOpen, tooltipLeft, tooltipTop } = useTooltip<TooltipData>()
25
34
  const { handleTooltipMouseOver, handleTooltipMouseOff, TooltipListItem } = useCoveTooltip({
26
35
  xScale: false,
27
36
  yScale: false,
@@ -94,7 +103,7 @@ const PieChart = props => {
94
103
  </Group>
95
104
  )
96
105
  })}
97
- {transitions.map(({ item: arc, key }) => {
106
+ {transitions.map(({ item: arc, key }, i) => {
98
107
  const [centroidX, centroidY] = path.centroid(arc)
99
108
  const hasSpaceForLabel = arc.endAngle - arc.startAngle >= 0.1
100
109
 
@@ -104,7 +113,7 @@ const PieChart = props => {
104
113
  }
105
114
 
106
115
  return (
107
- <animated.g key={key}>
116
+ <animated.g key={`${key}${i}`}>
108
117
  {hasSpaceForLabel && (
109
118
  <Text style={{ fill: textColor }} x={centroidX} y={centroidY} dy='.33em' textAnchor='middle' pointerEvents='none'>
110
119
  {Math.round((((arc.endAngle - arc.startAngle) * 180) / Math.PI / 360) * 100) + '%'}
@@ -0,0 +1,3 @@
1
+ import PieChart from './PieChart'
2
+
3
+ export default PieChart
@@ -0,0 +1,135 @@
1
+ import React, { useContext } from 'react'
2
+ import { ChartConfig } from '../../../types/ChartConfig'
3
+ import ConfigContext from '../../../ConfigContext'
4
+ import { ChartContext } from '../../../types/ChartContext'
5
+ import { Text } from '@visx/text'
6
+ import { Group } from '@visx/group'
7
+ import * as d3 from 'd3'
8
+
9
+ type RegionsProps = {
10
+ xScale: Function
11
+ barWidth: number
12
+ totalBarsInGroup: number
13
+ yMax: number
14
+ barWidth?: number
15
+ totalBarsInGroup?: number
16
+ }
17
+
18
+ const Regions = ({ xScale, barWidth = 0, totalBarsInGroup = 1, yMax, handleTooltipMouseOff, handleTooltipMouseOver, handleTooltipClick, tooltipData, showTooltip, hideTooltip }: RegionsProps) => {
19
+ const { parseDate, config } = useContext<ChartContext>(ConfigContext)
20
+
21
+ const { runtime, regions, visualizationType, orientation, xAxis } = config
22
+
23
+ let from
24
+ let to
25
+ let width
26
+
27
+ if (regions && orientation === 'vertical') {
28
+ return regions.map(region => {
29
+ if (xAxis.type === 'date' && region.fromType !== 'Previous Days') {
30
+ from = xScale(parseDate(region.from).getTime())
31
+ to = xScale(parseDate(region.to).getTime())
32
+ width = to - from
33
+ }
34
+
35
+ if (xAxis.type === 'categorical') {
36
+ from = xScale(region.from)
37
+ to = xScale(region.to)
38
+ width = to - from
39
+ }
40
+
41
+ if ((visualizationType === 'Bar' || config.visualizationType === 'Combo') && xAxis.type === 'date') {
42
+ from = region.fromType !== 'Previous Days' ? xScale(parseDate(region.from).getTime()) - (barWidth * totalBarsInGroup) / 2 : null
43
+ to = region.toType !== 'Last Date' ? xScale(parseDate(region.to).getTime()) + (barWidth * totalBarsInGroup) / 2 : null
44
+
45
+ width = to - from
46
+ }
47
+
48
+ if ((visualizationType === 'Bar' || config.visualizationType === 'Combo') && config.xAxis.type === 'categorical') {
49
+ from = xScale(region.from)
50
+ to = xScale(region.to)
51
+ width = to - from
52
+ }
53
+
54
+ if (region.fromType === 'Previous Days') {
55
+ to = region.toType !== 'Last Date' ? xScale(parseDate(region.to).getTime()) + (barWidth * totalBarsInGroup) / 2 : null
56
+
57
+ let domain = xScale.domain()
58
+ let bisectDate = d3.bisector(d => d).left
59
+ let closestValue
60
+
61
+ let previousDays = Number(region.from)
62
+ let lastDate = region.toType === 'Last Date' ? domain[domain.length - 1] : region.to
63
+ let fromDate = new Date(lastDate)
64
+
65
+ from = new Date(fromDate.setDate(fromDate.getDate() - previousDays)).getTime()
66
+ let targetValue = from
67
+
68
+ let index = bisectDate(domain, targetValue)
69
+ if (index === 0) {
70
+ closestValue = domain[0]
71
+ } else if (index === domain.length) {
72
+ closestValue = domain[domain.length - 1]
73
+ } else {
74
+ let d0 = domain[index - 1]
75
+ let d1 = domain[index]
76
+ closestValue = targetValue - d0 > d1 - targetValue ? d1 : d0
77
+ }
78
+
79
+ from = Number(xScale(closestValue) - (visualizationType === 'Bar' || visualizationType === 'Combo' ? (barWidth * totalBarsInGroup) / 2 : 0))
80
+
81
+ width = to - from
82
+ }
83
+
84
+ // set the region max to the charts max range.
85
+ if (region.toType === 'Last Date') {
86
+ let domainValues = xScale.domain()
87
+ let lastDate = domainValues[domainValues.length - 1]
88
+ to = Number(xScale(lastDate) + (visualizationType === 'Bar' || visualizationType === 'Combo' ? (barWidth * totalBarsInGroup) / 2 : 0))
89
+ width = to - from
90
+ }
91
+
92
+ if (!from) return null
93
+ if (!to) return null
94
+
95
+ const TopRegionBorderShape = () => {
96
+ return (
97
+ <path
98
+ stroke='#333'
99
+ d={`M${from} -5
100
+ L${from} 5
101
+ M${from} 0
102
+ L${to} 0
103
+ M${to} -5
104
+ L${to} 5`}
105
+ />
106
+ )
107
+ }
108
+
109
+ const HighlightedArea = () => {
110
+ return <rect x={from} y={0} width={width} height={yMax} fill={region.background} opacity={0.3} />
111
+ }
112
+
113
+ return (
114
+ <Group
115
+ className='regions regions-group--line'
116
+ left={config.visualizationType === 'Bar' || config.visualizationType === 'Combo' ? 0 : config?.visualizationType === 'Line' ? Number(runtime.yAxis.size) : 0}
117
+ key={region.label}
118
+ onMouseMove={handleTooltipMouseOver}
119
+ onMouseLeave={handleTooltipMouseOff}
120
+ handleTooltipClick={handleTooltipClick}
121
+ tooltipData={JSON.stringify(tooltipData)}
122
+ showTooltip={showTooltip}
123
+ >
124
+ <TopRegionBorderShape />
125
+ <HighlightedArea />
126
+ <Text x={from + width / 2} y={5} fill={region.color} verticalAnchor='start' textAnchor='middle'>
127
+ {region.label}
128
+ </Text>
129
+ </Group>
130
+ )
131
+ })
132
+ }
133
+ }
134
+
135
+ export default Regions
@@ -0,0 +1,3 @@
1
+ import Regions from './components/Regions'
2
+
3
+ export default Regions
@@ -1,8 +1,8 @@
1
1
  import React, { useContext } from 'react'
2
- import ConfigContext from '../ConfigContext'
2
+ import ConfigContext from '../../ConfigContext'
3
3
  import { Group } from '@visx/group'
4
4
 
5
- const CoveScatterPlot = ({ xScale, yScale, getXAxisData, getYAxisData }) => {
5
+ const ScatterPlot = ({ xScale, yScale, getXAxisData, getYAxisData }) => {
6
6
  const { colorScale, transformedData: data, config, formatNumber, seriesHighlight, colorPalettes } = useContext(ConfigContext)
7
7
 
8
8
  // TODO: copied from line chart should probably be a constant somewhere.
@@ -48,4 +48,4 @@ const CoveScatterPlot = ({ xScale, yScale, getXAxisData, getYAxisData }) => {
48
48
  </Group>
49
49
  )
50
50
  }
51
- export default CoveScatterPlot
51
+ export default ScatterPlot
@@ -0,0 +1,3 @@
1
+ import ScatterPlot from './ScatterPlot.jsx'
2
+
3
+ export default ScatterPlot
@@ -11,9 +11,9 @@ import { MarkerArrow } from '@visx/marker'
11
11
 
12
12
  import ErrorBoundary from '@cdc/core/components/ErrorBoundary'
13
13
 
14
- import useReduceData from '../hooks/useReduceData'
14
+ import useReduceData from '../../hooks/useReduceData'
15
15
 
16
- import ConfigContext from '../ConfigContext'
16
+ import ConfigContext from '../../ConfigContext'
17
17
 
18
18
  const SparkLine = props => {
19
19
  const { width: parentWidth, height: parentHeight } = props
@@ -0,0 +1,3 @@
1
+ import SparkLine from './SparkLine.jsx'
2
+
3
+ export default SparkLine
@@ -0,0 +1,168 @@
1
+ import { Brush } from '@visx/brush'
2
+ import { Group } from '@visx/group'
3
+ import { Text } from '@visx/text'
4
+ import { useBarChart } from '../hooks/useBarChart'
5
+ import { FC, useContext, useEffect, useRef, useState } from 'react'
6
+ import ConfigContext from '../ConfigContext'
7
+ import { ScaleLinear, ScaleBand } from 'd3-scale'
8
+
9
+ interface Props {
10
+ xScaleBrush: ScaleLinear<number, number>
11
+ yScale: ScaleBand<string>
12
+ xMax: number
13
+ yMax: number
14
+ }
15
+ const ZoomBrush: FC<Props> = props => {
16
+ const { transformedData: data, config, parseDate, formatDate, updateConfig } = useContext(ConfigContext)
17
+ const { fontSize } = useBarChart()
18
+
19
+ const [filteredData, setFilteredData] = useState([...data])
20
+ const brushRef = useRef(null)
21
+ const radius = 15
22
+
23
+ const [textProps, setTextProps] = useState({
24
+ startPosition: 0,
25
+ endPosition: 0,
26
+ startValue: '',
27
+ endValue: ''
28
+ })
29
+
30
+ const initialPosition = {
31
+ start: { x: 0 },
32
+ end: { x: props.xMax }
33
+ }
34
+
35
+ const style = {
36
+ fill: '#ddd',
37
+ stroke: 'blue',
38
+ fillOpacity: 0.8,
39
+ strokeOpacity: 0,
40
+ rx: radius
41
+ }
42
+
43
+ const onBrushChange = event => {
44
+ if (!event) return
45
+
46
+ const { xValues } = event
47
+
48
+ const dataKey = config.xAxis?.dataKey
49
+
50
+ const filteredData = data.filter(item => xValues.includes(item[dataKey]))
51
+
52
+ const endValue = xValues
53
+ .slice()
54
+ .reverse()
55
+ .find(item => item !== undefined)
56
+ const startValue = xValues.find(item => item !== undefined)
57
+
58
+ const formatIfDate = value => (config.runtime.xAxis.type === 'date' ? formatDate(parseDate(value)) : value)
59
+
60
+ setTextProps(prev => ({
61
+ ...prev,
62
+ startPosition: brushRef.current?.state.start.x,
63
+ endPosition: brushRef.current?.state.end.x,
64
+ endValue: formatIfDate(endValue),
65
+ startValue: formatIfDate(startValue)
66
+ }))
67
+
68
+ setFilteredData(filteredData)
69
+ }
70
+
71
+ useEffect(() => {
72
+ updateConfig({
73
+ ...config,
74
+ brush: {
75
+ ...config.brush,
76
+ data: filteredData
77
+ }
78
+ })
79
+ }, [filteredData])
80
+
81
+ //reset filters if brush is off
82
+ useEffect(() => {
83
+ if (!config.brush.active) {
84
+ setFilteredData(data)
85
+ }
86
+ }, [config.brush.active])
87
+
88
+ const calculateTop = (): number => {
89
+ const tickRotation = Number(config.xAxis.tickRotation) > 0 ? Number(config.xAxis.tickRotation) : 0
90
+ let top = 0
91
+ const offSet = 20
92
+ if (!config.xAxis.label) {
93
+ if (!config.isResponsiveTicks && tickRotation) {
94
+ top = Number(tickRotation + config.xAxis.tickWidthMax) / 1.6
95
+ }
96
+ if (!config.isResponsiveTicks && !tickRotation) {
97
+ top = Number(config.xAxis.labelOffset) - offSet
98
+ }
99
+ if (config.isResponsiveTicks && config.dynamicMarginTop) {
100
+ top = Number(config.xAxis.labelOffset + config.xAxis.tickWidthMax / 1.6)
101
+ }
102
+ if (config.isResponsiveTicks && !config.dynamicMarginTop) {
103
+ top = Number(config.xAxis.labelOffset - offSet)
104
+ }
105
+ }
106
+ if (config.xAxis.label) {
107
+ if (!config.isResponsiveTicks && tickRotation) {
108
+ top = Number(config.xAxis.tickWidthMax + tickRotation)
109
+ }
110
+
111
+ if (!config.isResponsiveTicks && !tickRotation) {
112
+ top = config.xAxis.labelOffset + offSet
113
+ }
114
+
115
+ if (config.isResponsiveTicks && !tickRotation) {
116
+ top = Number(config.dynamicMarginTop ? config.dynamicMarginTop : config.xAxis.labelOffset) + offSet
117
+ }
118
+ }
119
+
120
+ return top
121
+ }
122
+ if (!['Line', 'Bar', 'Area Chart', 'Combo'].includes(config.visualizationType)) {
123
+ return
124
+ }
125
+ return (
126
+ <Group display={config.brush.active ? 'block' : 'none'} top={Number(props.yMax) + calculateTop()} left={Number(config.runtime.yAxis.size)} pointerEvents='fill'>
127
+ <rect fill='#eee' width={props.xMax} height={config.brush.height} rx={radius} />
128
+ <Brush
129
+ renderBrushHandle={props => <BrushHandle textProps={textProps} fontSize={fontSize[config.fontSize]} {...props} isBrushing={brushRef.current?.state.isBrushing} />}
130
+ innerRef={brushRef}
131
+ useWindowMoveEvents={true}
132
+ selectedBoxStyle={style}
133
+ xScale={props.xScaleBrush}
134
+ yScale={props.yScale}
135
+ width={props.xMax}
136
+ resizeTriggerAreas={['left', 'right']}
137
+ height={config.brush.height}
138
+ handleSize={8}
139
+ brushDirection='horizontal'
140
+ initialBrushPosition={initialPosition}
141
+ onChange={onBrushChange}
142
+ />
143
+ </Group>
144
+ )
145
+ }
146
+
147
+ const BrushHandle = props => {
148
+ const { x, isBrushActive, isBrushing, className, textProps } = props
149
+ const pathWidth = 8
150
+ if (!isBrushActive) {
151
+ return null
152
+ }
153
+ // Flip the SVG path horizontally for the left handle
154
+ const isLeft = className.includes('left')
155
+ const transform = isLeft ? 'scale(-1, 1)' : 'translate(0,0)'
156
+ const textAnchor = isLeft ? 'end' : 'start'
157
+
158
+ return (
159
+ <Group left={x + pathWidth / 2} top={-2}>
160
+ <Text pointerEvents='visiblePainted' dominantBaseline='hanging' x={0} verticalAnchor='start' textAnchor={textAnchor} fontSize={props.fontSize / 1.4} dy={10} y={15}>
161
+ {isLeft ? textProps.startValue : textProps.endValue}
162
+ </Text>
163
+ <path cursor='ew-resize' d='M0.5,10A6,6 0 0 1 6.5,16V14A6,6 0 0 1 0.5,20ZM2.5,18V12M4.5,18V12' fill={!isBrushing ? '#666' : '#297EF1'} strokeWidth='1' transform={transform}></path>
164
+ </Group>
165
+ )
166
+ }
167
+
168
+ export default ZoomBrush
@@ -7,11 +7,11 @@ export default {
7
7
  title: '',
8
8
  showTitle: true,
9
9
  showDownloadMediaButton: false,
10
- showChartBrush: false,
11
10
  theme: 'theme-blue',
12
11
  animate: false,
13
12
  fontSize: 'medium',
14
13
  lineDatapointStyle: 'hover',
14
+ lineDatapointColor: 'Same as Line',
15
15
  barHasBorder: 'false',
16
16
  isLollipopChart: false,
17
17
  lollipopShape: 'circle',
@@ -28,6 +28,9 @@ export default {
28
28
  left: 5,
29
29
  right: 5
30
30
  },
31
+ suppressedData: [],
32
+ preliminaryData: [],
33
+
31
34
  yAxis: {
32
35
  hideAxis: false,
33
36
  displayNumbersOnBar: false,
@@ -134,8 +137,9 @@ export default {
134
137
  // start with a blank list
135
138
  },
136
139
  legend: {
140
+ hide: false,
137
141
  behavior: 'isolate',
138
- singleRow: false,
142
+ singleRow: true,
139
143
  colorCode: '',
140
144
  reverseLabelOrder: false,
141
145
  description: '',
@@ -145,7 +149,13 @@ export default {
145
149
  dynamicLegendItemLimitMessage: 'Dynamic Legend Item Limit Hit.',
146
150
  dynamicLegendChartMessage: 'Select Options from the Legend',
147
151
  lineMode: false,
148
- verticalSorted: false
152
+ verticalSorted: false,
153
+ highlightOnHover: false
154
+ },
155
+ brush: {
156
+ height: 25,
157
+ data: [],
158
+ active: false
149
159
  },
150
160
  exclusions: {
151
161
  active: false,
@@ -180,22 +190,27 @@ export default {
180
190
  highlightedBarValues: [],
181
191
  series: [],
182
192
  tooltips: {
183
- opacity: 90
193
+ opacity: 90,
194
+ singleSeries: false
184
195
  },
185
196
  forestPlot: {
186
197
  startAt: 0,
187
- width: 'auto',
188
198
  colors: {
189
199
  line: '',
190
200
  shape: ''
191
201
  },
202
+ lineOfNoEffect: {
203
+ show: true
204
+ },
205
+ type: '',
206
+ pooledResult: {
207
+ diamondHeight: 5,
208
+ column: ''
209
+ },
192
210
  estimateField: '',
193
211
  estimateRadius: '',
194
- lowerCiField: '',
195
- upperCiField: '',
196
- shape: '',
212
+ shape: 'square',
197
213
  rowHeight: 20,
198
- showZeroLine: false,
199
214
  description: {
200
215
  show: true,
201
216
  text: 'description',
@@ -207,8 +222,8 @@ export default {
207
222
  location: 100
208
223
  },
209
224
  radius: {
210
- min: 1,
211
- max: 8,
225
+ min: 2,
226
+ max: 10,
212
227
  scalingColumn: ''
213
228
  },
214
229
  regression: {
@@ -217,11 +232,10 @@ export default {
217
232
  estimateField: 0
218
233
  },
219
234
  leftWidthOffset: 0,
220
- rightWidthOffset: 0
221
- },
222
- brush: {
223
- pattern_id: 'brush_pattern',
224
- accent_color: '#ddd'
235
+ rightWidthOffset: 0,
236
+ showZeroLine: false,
237
+ leftLabel: '',
238
+ rightLabel: ''
225
239
  },
226
240
  area: {
227
241
  isStacked: false
@@ -0,0 +1,17 @@
1
+ export const abbreviateNumber = num => {
2
+ let unit = ''
3
+ let absNum = Math.abs(num)
4
+
5
+ if (absNum >= 1e9) {
6
+ unit = 'B'
7
+ num = num / 1e9
8
+ } else if (absNum >= 1e6) {
9
+ unit = 'M'
10
+ num = num / 1e6
11
+ } else if (absNum >= 1e3) {
12
+ unit = 'K'
13
+ num = num / 1e3
14
+ }
15
+
16
+ return num + unit
17
+ }
@@ -0,0 +1,55 @@
1
+ import { ChartConfig, Legend } from '../types/ChartConfig'
2
+
3
+ export const computeMarginBottom = (config: ChartConfig, legend: Legend, currentViewport: string): string | number => {
4
+ const isLegendBottom = legend.position === 'bottom' || ['sm', 'xs', 'xxs'].includes(currentViewport)
5
+ const isHorizontal = config.orientation === 'horizontal'
6
+ const tickRotation = Number(config.xAxis.tickRotation) > 0 ? Number(config.xAxis.tickRotation) : 0
7
+ const isBrush = config.brush.active
8
+ const offset = 20
9
+ const brushHeight = config.brush.height
10
+ let bottom = 0
11
+ if (!isLegendBottom && isHorizontal && !config.yAxis.label) {
12
+ bottom = Number(config.xAxis.labelOffset)
13
+ }
14
+ if (!isLegendBottom && isHorizontal && config.yAxis.label && !config.isResponsiveTicks) {
15
+ bottom = Number(config.runtime.xAxis.size) + Number(config.xAxis.labelOffset)
16
+ }
17
+ if (!isLegendBottom && isHorizontal && config.yAxis.label && config.isResponsiveTicks) {
18
+ bottom = config.dynamicMarginTop + offset
19
+ }
20
+ if (!isLegendBottom && isHorizontal && !config.yAxis.label && config.isResponsiveTicks) {
21
+ bottom = config.dynamicMarginTop ? config.dynamicMarginTop - offset : Number(config.xAxis.labelOffset) - offset
22
+ }
23
+ if (!isLegendBottom && isHorizontal && config.yAxis.label && config.isResponsiveTicks) {
24
+ bottom = config.dynamicMarginTop ? config.dynamicMarginTop + offset : Number(config.xAxis.labelOffset)
25
+ }
26
+
27
+ if (!isHorizontal && !isLegendBottom && config.xAxis.label && tickRotation && !config.isResponsiveTicks) {
28
+ bottom = isBrush ? brushHeight + config.xAxis.tickWidthMax + -config.xAxis.size + config.xAxis.labelOffset + offset : config.xAxis.tickWidthMax + offset + -config.xAxis.size + config.xAxis.labelOffset
29
+ }
30
+ if (!isHorizontal && !isLegendBottom && !config.xAxis.label && tickRotation && !config.isResponsiveTicks) {
31
+ }
32
+ if (!isHorizontal && !isLegendBottom && !config.xAxis.label && tickRotation && !config.dynamicMarginTop && !config.isResponsiveTicks) {
33
+ bottom = isBrush ? config.xAxis.tickWidthMax + brushHeight + offset + -config.xAxis.size : 0
34
+ }
35
+
36
+ if (!isHorizontal && !isLegendBottom && config.xAxis.label && !tickRotation && !config.isResponsiveTicks) {
37
+ bottom = isBrush ? brushHeight + -config.xAxis.size + config.xAxis.labelOffset + offset : -config.xAxis.size + config.xAxis.labelOffset + offset
38
+ }
39
+ if (!isHorizontal && !isLegendBottom && config.xAxis.label && config.dynamicMarginTop && config.isResponsiveTicks) {
40
+ bottom = isBrush ? brushHeight + config.xAxis.labelOffset + -config.xAxis.size + config.xAxis.tickWidthMax : config.dynamicMarginTop + -config.xAxis.size + offset
41
+ }
42
+ if (!isHorizontal && !isLegendBottom && !config.xAxis.label && config.dynamicMarginTop && config.isResponsiveTicks) {
43
+ bottom = isBrush ? brushHeight + config.xAxis.labelOffset + -config.xAxis.size + config.xAxis.tickWidthMax : config.dynamicMarginTop + -config.xAxis.size - offset
44
+ }
45
+ if (!isHorizontal && !isLegendBottom && config.xAxis.label && !config.dynamicMarginTop && config.isResponsiveTicks) {
46
+ bottom = isBrush ? brushHeight + config.xAxis.labelOffset + -config.xAxis.size + 25 : config.xAxis.labelOffset + -config.xAxis.size + offset
47
+ }
48
+ if (!isHorizontal && !isLegendBottom && !config.xAxis.label && !config.dynamicMarginTop && config.isResponsiveTicks) {
49
+ bottom = -config.xAxis.size + offset + config.xAxis.labelOffset
50
+ }
51
+ if (!isHorizontal && !isLegendBottom && !config.xAxis.label && !tickRotation && !config.dynamicMarginTop && !config.isResponsiveTicks) {
52
+ bottom = isBrush ? brushHeight + -config.xAxis.size + config.xAxis.labelOffset : 0
53
+ }
54
+ return `${bottom}px`
55
+ }
@@ -0,0 +1,18 @@
1
+ export const filterData = (filters, data) => {
2
+ let filteredData: any[] = []
3
+
4
+ data.forEach(row => {
5
+ let add = true
6
+ filters
7
+ .filter(filter => filter.type !== 'url')
8
+ .forEach(filter => {
9
+ if (row[filter.columnName] != filter.active) {
10
+ add = false
11
+ }
12
+ })
13
+
14
+ if (add) filteredData.push(row)
15
+ })
16
+
17
+ return filteredData
18
+ }
@@ -0,0 +1,8 @@
1
+ import chroma from 'chroma-js'
2
+
3
+ export const generateColorsArray = (color = '#000000', special = false) => {
4
+ let colorObj = chroma(color)
5
+ let hoverColor = special ? colorObj.brighten(0.5).hex() : colorObj.saturate(1.3).hex()
6
+
7
+ return [color, hoverColor, colorObj.darken(0.3).hex()]
8
+ }
@@ -0,0 +1,30 @@
1
+ /**
2
+ * Calculates the first quartile (q1) and third quartile (q3) from an array of integers or decimals.
3
+ *
4
+ * @param {Array} arr - The array of integers or decimals.
5
+ * @returns {Object} An object containing the q1 and q3 values.
6
+ */
7
+ export const getQuartiles = arr => {
8
+ arr.sort((a, b) => a - b)
9
+
10
+ // Calculate the index of the median value of the array
11
+ const medianIndex = Math.floor(arr.length / 2)
12
+
13
+ // Check if the length of the array is even or odd
14
+ const isEvenLength = arr.length % 2 === 0
15
+
16
+ // Split the array into two subarrays based on the median index
17
+ const q1Array = isEvenLength ? arr.slice(0, medianIndex) : arr.slice(0, medianIndex + 1)
18
+ const q3Array = isEvenLength ? arr.slice(medianIndex) : arr.slice(medianIndex + 1)
19
+
20
+ // Calculate the median of the first subarray to get the q1 value
21
+ const q1Index = Math.floor(q1Array.length / 2)
22
+ const q1 = isEvenLength ? (q1Array[q1Index - 1] + q1Array[q1Index]) / 2 : q1Array[q1Index]
23
+
24
+ // Calculate the median of the second subarray to get the q3 value
25
+ const q3Index = Math.floor(q3Array.length / 2)
26
+ const q3 = isEvenLength ? (q3Array[q3Index - 1] + q3Array[q3Index]) / 2 : q3Array[q3Index]
27
+
28
+ // Return an object containing the q1 and q3 values
29
+ return { q1, q3 }
30
+ }
@@ -0,0 +1,19 @@
1
+ export const handleChartAriaLabels = (state, testing = false) => {
2
+ if (testing) console.log(`handleChartAriaLabels Testing On:`, state) // eslint-disable-line
3
+ try {
4
+ if (!state.visualizationType) throw Error('handleChartAriaLabels: no visualization type found in state')
5
+ let ariaLabel = ''
6
+
7
+ if (state.visualizationType) {
8
+ ariaLabel += `${state.visualizationType} chart`
9
+ }
10
+
11
+ if (state.title && state.visualizationType) {
12
+ ariaLabel += ` with the title: ${state.title}`
13
+ }
14
+
15
+ return ariaLabel
16
+ } catch (e) {
17
+ console.error('COVE: ', e.message) // eslint-disable-line
18
+ }
19
+ }