@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
@@ -2,32 +2,53 @@ import React, { useContext } from 'react'
2
2
 
3
3
  import * as allCurves from '@visx/curve'
4
4
  import { Group } from '@visx/group'
5
- import { LinePath, Bar } from '@visx/shape'
5
+ import { LinePath, Bar, SplitLinePath } from '@visx/shape'
6
6
  import { Text } from '@visx/text'
7
7
 
8
8
  import ErrorBoundary from '@cdc/core/components/ErrorBoundary'
9
- import ConfigContext from '../ConfigContext'
10
- import useRightAxis from '../hooks/useRightAxis'
11
- import LineChartCircle from './LineChart.Circle'
12
-
13
- const LineChart = ({ xScale, yScale, getXAxisData, getYAxisData, xMax, yMax, handleTooltipMouseOver, handleTooltipMouseOff, showTooltip, seriesStyle = 'Line', svgRef, handleTooltipClick, tooltipData }) => {
14
- // Not sure why there's a redraw here.
15
-
16
- const { colorPalettes, transformedData: data, colorScale, seriesHighlight, config, formatNumber, formatDate, parseDate, isNumber, updateConfig, handleLineType, dashboardConfig, tableData } = useContext(ConfigContext)
9
+ import ConfigContext from '../../ConfigContext'
10
+ import useRightAxis from '../../hooks/useRightAxis'
11
+ import { filterCircles, createStyles } from './helpers'
12
+ import LineChartCircle from './components/LineChart.Circle'
13
+
14
+ // types
15
+ import { type ChartContext } from '../../types/ChartContext'
16
+ import { type LineChartProps } from './LineChartProps'
17
+
18
+ const LineChart = (props: LineChartProps) => {
19
+ // prettier-ignore
20
+ const {
21
+ getXAxisData,
22
+ getYAxisData,
23
+ handleTooltipClick,
24
+ handleTooltipMouseOff,
25
+ handleTooltipMouseOver,
26
+ tooltipData,
27
+ xMax,
28
+ xScale,
29
+ yMax,
30
+ yScale,
31
+ } = props
32
+
33
+ // prettier-ignore
34
+ const {
35
+ colorScale,
36
+ config,
37
+ formatNumber,
38
+ handleLineType,
39
+ isNumber,
40
+ parseDate,
41
+ seriesHighlight,
42
+ tableData,
43
+ transformedData: data,
44
+ updateConfig,
45
+ rawData
46
+ } = useContext<ChartContext>(ConfigContext)
17
47
  const { yScaleRight } = useRightAxis({ config, yMax, data, updateConfig })
18
-
19
48
  if (!handleTooltipMouseOver) return
20
- const handleAxisFormating = (axis = 'left', label, value) => {
21
- // if this is an x axis category/date value return without doing any formatting.
22
- // if (label === config.runtime.xAxis.label) return value
23
- axis = String(axis).toLocaleLowerCase()
24
- if (label) {
25
- return `${label}: ${formatNumber(value, axis)}`
26
- }
27
- return `${formatNumber(value, axis)}`
28
- }
29
49
 
30
50
  const DEBUG = false
51
+ const { lineDatapointStyle, showLineSeriesLabels, legend } = config
31
52
 
32
53
  return (
33
54
  <ErrorBoundary component='LineChart'>
@@ -38,22 +59,22 @@ const LineChart = ({ xScale, yScale, getXAxisData, getYAxisData, xMax, yMax, han
38
59
  let lineType = config.series.filter(item => item.dataKey === seriesKey)[0].type
39
60
  const seriesData = config.series.filter(item => item.dataKey === seriesKey)
40
61
  const seriesAxis = seriesData[0].axis ? seriesData[0].axis : 'left'
41
-
42
- let displayArea = config.legend.behavior === 'highlight' || seriesHighlight.length === 0 || seriesHighlight.indexOf(seriesKey) !== -1
62
+ let displayArea = legend.behavior === 'highlight' || seriesHighlight.length === 0 || seriesHighlight.indexOf(seriesKey) !== -1
63
+ const circleData = filterCircles(config.preliminaryData, rawData, seriesKey)
64
+ // styles for preliminary Data items
65
+ let styles = createStyles({ preliminaryData: config.preliminaryData, rawData, stroke: colorScale(config.runtime.seriesLabels[seriesKey]), handleLineType, lineType, seriesKey })
43
66
 
44
67
  return (
45
68
  <Group
46
69
  key={`series-${seriesKey}`}
47
- opacity={config.legend.behavior === 'highlight' && seriesHighlight.length > 0 && seriesHighlight.indexOf(seriesKey) === -1 ? 0.5 : 1}
48
- display={config.legend.behavior === 'highlight' || (seriesHighlight.length === 0 && !config.legend.dynamicLegend) || seriesHighlight.indexOf(seriesKey) !== -1 ? 'block' : 'none'}
70
+ opacity={legend.behavior === 'highlight' && seriesHighlight.length > 0 && seriesHighlight.indexOf(seriesKey) === -1 ? 0.5 : 1}
71
+ display={legend.behavior === 'highlight' || (seriesHighlight.length === 0 && !legend.dynamicLegend) || seriesHighlight.indexOf(seriesKey) !== -1 ? 'block' : 'none'}
49
72
  >
50
73
  {data.map((d, dataIndex) => {
51
74
  // Find the series object from the config.series array that has a dataKey matching the seriesKey variable.
52
75
  const series = config.series.find(({ dataKey }) => dataKey === seriesKey)
53
76
  const { axis } = series
54
77
 
55
- const xAxisValue = config.runtime.xAxis.type === 'date' ? formatDate(parseDate(d[config.runtime.xAxis.dataKey])) : d[config.runtime.xAxis.dataKey]
56
- const yAxisValue = getYAxisData(d, seriesKey)
57
78
  const hasMultipleSeries = Object.keys(config.runtime.seriesLabels).length > 1
58
79
  const labeltype = axis === 'Right' ? 'rightLabel' : 'label'
59
80
  let label = config.runtime.yAxis[labeltype]
@@ -74,54 +95,74 @@ const LineChart = ({ xScale, yScale, getXAxisData, getYAxisData, xMax, yMax, han
74
95
  {formatNumber(d[seriesKey], 'left')}
75
96
  </Text>
76
97
 
77
- {config.lineDatapointStyle === 'hidden' ||
78
- (config.lineDatapointStyle === 'always show' && <LineChartCircle d={d} config={config} seriesKey={seriesKey} displayArea={displayArea} tooltipData={tooltipData} xScale={xScale} yScale={yScale} colorScale={colorScale} parseDate={parseDate} yScaleRight={yScaleRight} />)}
98
+ {(lineDatapointStyle === 'hidden' || lineDatapointStyle === 'always show') && (
99
+ <LineChartCircle
100
+ circleData={circleData}
101
+ data={data}
102
+ d={d}
103
+ config={config}
104
+ seriesKey={seriesKey}
105
+ displayArea={displayArea}
106
+ tooltipData={tooltipData}
107
+ xScale={xScale}
108
+ yScale={yScale}
109
+ colorScale={colorScale}
110
+ parseDate={parseDate}
111
+ yScaleRight={yScaleRight}
112
+ seriesAxis={seriesAxis}
113
+ key={`line-circle--${dataIndex}`}
114
+ />
115
+ )}
79
116
  </Group>
80
117
  )
81
118
  )
82
119
  })}
83
-
84
- <>{config.lineDatapointStyle === 'hover' && <LineChartCircle config={config} seriesKey={seriesKey} displayArea={displayArea} tooltipData={tooltipData} xScale={xScale} yScale={yScale} colorScale={colorScale} parseDate={parseDate} yScaleRight={yScaleRight} seriesAxis={seriesAxis} />}</>
85
-
120
+ <>
121
+ {lineDatapointStyle === 'hover' && (
122
+ <LineChartCircle circleData={circleData} data={data} config={config} seriesKey={seriesKey} displayArea={displayArea} tooltipData={tooltipData} xScale={xScale} yScale={yScale} colorScale={colorScale} parseDate={parseDate} yScaleRight={yScaleRight} seriesAxis={seriesAxis} />
123
+ )}
124
+ </>
125
+ {/* STANDARD LINE */}
86
126
  <LinePath
87
127
  curve={allCurves[seriesData[0].lineType]}
88
128
  data={data}
89
129
  x={d => xScale(getXAxisData(d))}
90
- y={d => (seriesAxis === 'Right' ? yScaleRight(getYAxisData(d, seriesKey)) : yScale(getYAxisData(d, seriesKey)))}
91
- stroke={
92
- config.customColors
93
- ? config.customColors[index]
94
- : colorScale
95
- ? colorPalettes[config.palette][index]
96
- : // fallback
97
- '#000'
98
- }
130
+ y={d => (seriesAxis === 'Right' ? yScaleRight(getYAxisData(d, seriesKey)) : yScale(Number(getYAxisData(d, seriesKey))))}
131
+ stroke={colorScale(config.runtime.seriesLabels[seriesKey])}
99
132
  strokeWidth={2}
100
133
  strokeOpacity={1}
134
+ shapeRendering='geometricPrecision'
101
135
  strokeDasharray={lineType ? handleLineType(lineType) : 0}
102
136
  defined={(item, i) => {
103
137
  return item[seriesKey] !== '' && item[seriesKey] !== null && item[seriesKey] !== undefined
104
138
  }}
105
139
  />
140
+
141
+ {/* circles for preliminaryData data */}
142
+ {circleData.map((d, i) => {
143
+ return <circle key={i} cx={xScale(getXAxisData(d))} cy={yScale(Number(getYAxisData(d, seriesKey)))} r={6} strokeWidth={2} stroke={colorScale ? colorScale(config.runtime.seriesLabels[seriesKey]) : '#000'} fill='#fff' />
144
+ })}
145
+
146
+ {/* ANIMATED LINE */}
106
147
  {config.animate && (
107
148
  <LinePath
108
149
  className='animation'
109
150
  curve={seriesData.lineType}
110
151
  data={data}
111
152
  x={d => xScale(getXAxisData(d))}
112
- y={d => (seriesAxis === 'Right' ? yScaleRight(getYAxisData(d, seriesKey)) : yScale(getYAxisData(d, seriesKey)))}
153
+ y={d => (seriesAxis === 'Right' ? yScaleRight(getYAxisData(d, seriesKey)) : yScale(Number(getYAxisData(d, seriesKey))))}
113
154
  stroke='#fff'
114
155
  strokeWidth={3}
115
156
  strokeOpacity={1}
116
157
  shapeRendering='geometricPrecision'
117
158
  strokeDasharray={lineType ? handleLineType(lineType) : 0}
118
159
  defined={(item, i) => {
119
- return isNumber(item[config.runtime.seriesLabels[seriesKey]])
160
+ return item[seriesKey] !== '' && item[seriesKey] !== null && item[seriesKey] !== undefined
120
161
  }}
121
162
  />
122
163
  )}
123
164
  {/* Render series labels at end if each line if selected in the editor */}
124
- {config.showLineSeriesLabels &&
165
+ {showLineSeriesLabels &&
125
166
  (config.runtime.lineSeriesKeys || config.runtime.seriesKeys).map(seriesKey => {
126
167
  let lastDatum
127
168
  for (let i = data.length - 1; i >= 0; i--) {
@@ -1,4 +1,4 @@
1
- import React, { useContext, useEffect, useRef, useState, useMemo } from 'react'
1
+ import React, { useContext, useEffect, useRef, useState } from 'react'
2
2
 
3
3
  // Libraries
4
4
  import { AxisLeft, AxisBottom, AxisRight, AxisTop } from '@visx/axis'
@@ -6,14 +6,13 @@ 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, useTooltipInPortal } from '@visx/tooltip'
9
+ import { useTooltip, TooltipWithBounds } from '@visx/tooltip'
10
10
 
11
11
  // CDC Components
12
- import AreaChart from './AreaChart'
13
- import AreaChartStacked from './AreaChart.Stacked'
12
+ import { AreaChart, AreaChartStacked } from './AreaChart'
14
13
  import BarChart from './BarChart'
15
14
  import ConfigContext from '../ConfigContext'
16
- import CoveBoxPlot from './BoxPlot'
15
+ import BoxPlot from './BoxPlot'
17
16
  import ScatterPlot from './ScatterPlot'
18
17
  import DeviationBar from './DeviationBar'
19
18
  import ErrorBoundary from '@cdc/core/components/ErrorBoundary'
@@ -22,6 +21,7 @@ import LineChart from './LineChart'
22
21
  import ForestPlot from './ForestPlot'
23
22
  import PairedBarChart from './PairedBarChart'
24
23
  import useIntersectionObserver from './../hooks/useIntersectionObserver'
24
+ import Regions from './Regions'
25
25
 
26
26
  // Hooks
27
27
  import useMinMax from '../hooks/useMinMax'
@@ -30,13 +30,33 @@ import useRightAxis from '../hooks/useRightAxis'
30
30
  import useScales from '../hooks/useScales'
31
31
  import useTopAxis from '../hooks/useTopAxis'
32
32
  import { useTooltip as useCoveTooltip } from '../hooks/useTooltip'
33
+ import { useEditorPermissions } from './EditorPanel/useEditorPermissions'
33
34
 
34
35
  // styles
35
- import '../scss/LinearChart.scss'
36
+ import ZoomBrush from './ZoomBrush'
36
37
 
37
38
  const LinearChart = props => {
38
- const { isEditor, isDashboard, transformedData: data, dimensions, config, parseDate, formatDate, currentViewport, formatNumber, handleChartAriaLabels, updateConfig, handleLineType, rawData, capitalize, setSharedFilter, setSharedFilterValue, getTextWidth, isDebug } = useContext(ConfigContext)
39
-
39
+ const {
40
+ isEditor,
41
+ isDashboard,
42
+ computeMarginBottom,
43
+ transformedData: data,
44
+ dimensions,
45
+ config,
46
+ parseDate,
47
+ formatDate,
48
+ currentViewport,
49
+ formatNumber,
50
+ handleChartAriaLabels,
51
+ updateConfig,
52
+ handleLineType,
53
+ rawData,
54
+ capitalize,
55
+ setSharedFilter,
56
+ setSharedFilterValue,
57
+ getTextWidth,
58
+ isDebug
59
+ } = useContext(ConfigContext)
40
60
  // todo: start destructuring this file for conciseness
41
61
  const { visualizationType, visualizationSubType, orientation, xAxis, yAxis, runtime, debugSvg } = config
42
62
 
@@ -49,7 +69,7 @@ const LinearChart = props => {
49
69
  }
50
70
  // configure height , yMax, xMax
51
71
  const { horizontal: heightHorizontal } = config.heights
52
- const isHorizontal = orientation === 'horizontal'
72
+ const isHorizontal = orientation === 'horizontal' || config.visualizationType === 'Forest Plot'
53
73
  const shouldAbbreviate = true
54
74
  let height = config.aspectRatio ? width * config.aspectRatio : config.visualizationType === 'Forest Plot' ? config.heights['vertical'] : config.heights[orientation]
55
75
  const xMax = width - runtime.yAxis.size - (visualizationType === 'Combo' ? config.yAxis.rightAxisSize : 0)
@@ -58,16 +78,18 @@ const LinearChart = props => {
58
78
  if (config.visualizationType === 'Forest Plot') {
59
79
  height = height + config.data.length * config.forestPlot.rowHeight
60
80
  yMax = yMax + config.data.length * config.forestPlot.rowHeight
81
+ width = dimensions[0]
82
+ }
83
+ if (config.brush.active) {
84
+ height = height + config.brush.height
61
85
  }
62
-
63
- let dynamicMarginTop = 0 || config.dynamicMarginTop
64
- const marginTop = 20
65
86
 
66
87
  // hooks % states
67
88
  const { minValue, maxValue, existPositiveValue, isAllLine } = useReduceData(config, data)
68
- const { yScaleRight, hasRightAxis } = useRightAxis({ config, yMax, data, updateConfig })
89
+ const { visSupportsReactTooltip } = useEditorPermissions()
69
90
  const { hasTopAxis } = useTopAxis(config)
70
91
  const [animatedChart, setAnimatedChart] = useState(false)
92
+ const [point, setPoint] = useState({ x: 0, y: 0 })
71
93
 
72
94
  // refs
73
95
  const triggerRef = useRef()
@@ -79,11 +101,12 @@ const LinearChart = props => {
79
101
  // getters & functions
80
102
  const getXAxisData = d => (config.runtime.xAxis.type === 'date' ? parseDate(d[config.runtime.originalXAxis.dataKey]).getTime() : d[config.runtime.originalXAxis.dataKey])
81
103
  const getYAxisData = (d, seriesKey) => d[seriesKey]
82
- const xAxisDataMapped = data.map(d => getXAxisData(d))
83
- const section = config.orientation === 'horizontal' ? 'yAxis' : 'xAxis'
104
+ const xAxisDataMapped = config.brush.active && config.brush.data?.length ? config.brush.data.map(d => getXAxisData(d)) : data.map(d => getXAxisData(d))
105
+ const section = config.orientation === 'horizontal' || config.visualizationType === 'Forest Plot' ? 'yAxis' : 'xAxis'
84
106
  const properties = { data, config, minValue, maxValue, isAllLine, existPositiveValue, xAxisDataMapped, xMax, yMax }
85
- const { min, max } = useMinMax(properties)
86
- const { xScale, yScale, seriesScale, g1xScale, g2xScale, xScaleNoPadding } = useScales({ ...properties, min, max })
107
+ const { min, max, leftMax, rightMax } = useMinMax(properties)
108
+ const { yScaleRight, hasRightAxis } = useRightAxis({ config, yMax, data, updateConfig })
109
+ const { xScale, yScale, seriesScale, g1xScale, g2xScale, xScaleNoPadding, xScaleBrush } = useScales({ ...properties, min, max, leftMax, rightMax, dimensions })
87
110
 
88
111
  // sets the portal x/y for where tooltips should appear on the page.
89
112
  const [chartPosition, setChartPosition] = useState(null)
@@ -109,9 +132,11 @@ const LinearChart = props => {
109
132
  // when logarithmic scale applied change value FIRST of tick
110
133
  tick = 0
111
134
  }
112
- if (runtime.xAxis.type === 'date') return formatDate(tick)
113
- if (orientation === 'horizontal') return formatNumber(tick, 'left', shouldAbbreviate)
114
- if (config.xAxis.type === 'continuous') return formatNumber(tick, 'bottom', shouldAbbreviate)
135
+
136
+ if (runtime.xAxis.type === 'date' && config.visualizationType !== 'Forest Plot') return formatDate(tick)
137
+ if (orientation === 'horizontal' && config.visualizationType !== 'Forest Plot') return formatNumber(tick, 'left', shouldAbbreviate)
138
+ if (config.xAxis.type === 'continuous' && config.visualizationType !== 'Forest Plot') return formatNumber(tick, 'bottom', shouldAbbreviate)
139
+ if (config.visualizationType === 'Forest Plot') return formatNumber(tick, 'left', config.dataFormat.abbreviated, config.runtime.xAxis.prefix, config.runtime.xAxis.suffix, Number(config.dataFormat.roundTo))
115
140
  return tick
116
141
  }
117
142
 
@@ -147,7 +172,12 @@ const LinearChart = props => {
147
172
  tickCount = 4 // same default as standalone components
148
173
  }
149
174
  }
175
+
176
+ if (config.visualizationType === 'Forest Plot') {
177
+ tickCount = config.yAxis.numTicks !== '' ? config.yAxis.numTicks : 4
178
+ }
150
179
  }
180
+
151
181
  return tickCount
152
182
  }
153
183
 
@@ -206,61 +236,40 @@ const LinearChart = props => {
206
236
  return countNumOfTicks('yAxis')
207
237
  }
208
238
 
239
+ const onMouseMove = event => {
240
+ const svgRect = event.currentTarget.getBoundingClientRect()
241
+ const x = event.clientX - svgRect.left
242
+ const y = event.clientY - svgRect.top
243
+
244
+ setPoint({
245
+ x,
246
+ y
247
+ })
248
+ }
249
+
209
250
  return isNaN(width) ? (
210
251
  <React.Fragment></React.Fragment>
211
252
  ) : (
212
253
  <ErrorBoundary component='LinearChart'>
213
254
  {/* ! Notice - div needed for tooltip boundaries (flip/flop) */}
214
255
  <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' }}>
256
+ <svg
257
+ // onMouseLeave={() => setPoint(null)}
258
+ onMouseMove={onMouseMove}
259
+ width={'100%'}
260
+ height={'100%'}
261
+ className={`linear ${config.animate ? 'animated' : ''} ${animatedChart && config.animate ? 'animate' : ''} ${debugSvg && 'debug'}`}
262
+ role='img'
263
+ aria-label={handleChartAriaLabels(config)}
264
+ ref={svgRef}
265
+ style={{ overflow: 'visible' }}
266
+ >
216
267
  <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
- }
230
-
231
- if (config.xAxis.type === 'categorical') {
232
- from = xScale(region.from)
233
- to = xScale(region.to)
234
- width = to - from
235
- }
236
-
237
- if (!from) return null
238
- if (!to) return null
239
-
240
- return (
241
- <Group className='regions' left={Number(runtime.yAxis.size)} key={region.label}>
242
- <path
243
- stroke='#333'
244
- d={`M${from} -5
245
- L${from} 5
246
- M${from} 0
247
- L${to} 0
248
- M${to} -5
249
- L${to} 5`}
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
- )
257
- })
258
- : ''}
259
268
  {/* Y axis */}
260
269
  {!['Spark Line', 'Forest Plot'].includes(visualizationType) && (
261
270
  <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
271
  {props => {
263
- const axisCenter = runtime.horizontal ? (props.axisToPoint.y - props.axisFromPoint.y) / 2 : (props.axisFromPoint.y - props.axisToPoint.y) / 2
272
+ const axisCenter = config.orientation === 'horizontal' ? (props.axisToPoint.y - props.axisFromPoint.y) / 2 : (props.axisFromPoint.y - props.axisToPoint.y) / 2
264
273
  const horizontalTickOffset = yMax / props.ticks.length / 2 - (yMax / props.ticks.length) * (1 - config.barThickness) + 5
265
274
  return (
266
275
  <Group className='left-axis'>
@@ -273,7 +282,7 @@ const LinearChart = props => {
273
282
 
274
283
  return (
275
284
  <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'} />}
285
+ {!runtime.yAxis.hideTicks && <Line key={`${tick.value}--hide-hideTicks`} from={tick.from} to={config.useLogScale ? to : tick.to} stroke={config.yAxis.tickColor} display={orientation === 'horizontal' ? 'none' : 'block'} />}
277
286
 
278
287
  {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
288
 
@@ -336,7 +345,7 @@ const LinearChart = props => {
336
345
  {hasRightAxis && (
337
346
  <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}>
338
347
  {props => {
339
- const axisCenter = runtime.horizontal ? (props.axisToPoint.y - props.axisFromPoint.y) / 2 : (props.axisFromPoint.y - props.axisToPoint.y) / 2
348
+ const axisCenter = config.orientation === 'horizontal' ? (props.axisToPoint.y - props.axisFromPoint.y) / 2 : (props.axisFromPoint.y - props.axisToPoint.y) / 2
340
349
  const horizontalTickOffset = yMax / props.ticks.length / 2 - (yMax / props.ticks.length) * (1 - config.barThickness) + 5
341
350
  return (
342
351
  <Group className='right-axis'>
@@ -380,7 +389,7 @@ const LinearChart = props => {
380
389
  {visualizationType !== 'Paired Bar' && visualizationType !== 'Spark Line' && (
381
390
  <AxisBottom
382
391
  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)}
383
- left={Number(runtime.yAxis.size)}
392
+ left={config.visualizationType !== 'Forest Plot' ? Number(runtime.yAxis.size) : 0}
384
393
  label={runtime.xAxis.label}
385
394
  tickFormat={handleBottomTickFormatting}
386
395
  scale={xScale}
@@ -389,7 +398,7 @@ const LinearChart = props => {
389
398
  tickStroke='#333'
390
399
  >
391
400
  {props => {
392
- const axisCenter = config.visualizationType !== 'Forest Plot' ? (props.axisToPoint.x - props.axisFromPoint.x) / 2 : width / 2
401
+ const axisCenter = config.visualizationType !== 'Forest Plot' ? (props.axisToPoint.x - props.axisFromPoint.x) / 2 : dimensions[0] / 2
393
402
  const containsMultipleWords = inputString => /\s/.test(inputString)
394
403
  const ismultiLabel = props.ticks.some(tick => containsMultipleWords(tick.value))
395
404
 
@@ -422,11 +431,14 @@ const LinearChart = props => {
422
431
  }
423
432
  })
424
433
 
425
- dynamicMarginTop = areTicksTouching && config.isResponsiveTicks ? tickWidthMax + defaultTickLength + marginTop : 0
434
+ const dynamicMarginTop = areTicksTouching && config.isResponsiveTicks ? tickWidthMax + defaultTickLength + 20 : 0
435
+ const rotation = Number(config.xAxis.tickRotation) > 0 ? Number(config.xAxis.tickRotation) : 0
436
+
426
437
  config.dynamicMarginTop = dynamicMarginTop
438
+ config.xAxis.tickWidthMax = tickWidthMax
427
439
 
428
440
  return (
429
- <Group className='bottom-axis'>
441
+ <Group className='bottom-axis' width={dimensions[0]}>
430
442
  {props.ticks.map((tick, i, propsTicks) => {
431
443
  // when using LogScale show major ticks values only
432
444
  const showTick = String(tick.value).startsWith('1') || tick.value === 0.1 ? 'block' : 'none'
@@ -443,7 +455,7 @@ const LinearChart = props => {
443
455
 
444
456
  return (
445
457
  <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} />}
458
+ {!config.xAxis.hideTicks && <Line from={tick.from} to={orientation === 'horizontal' && config.useLogScale ? to : tick.to} stroke={config.xAxis.tickColor} strokeWidth={showTick === 'block' && config.useLogScale ? 1.3 : 1} />}
447
459
  {!config.xAxis.hideLabel && (
448
460
  <Text
449
461
  dy={config.orientation === 'horizontal' && config.useLogScale ? 8 : 0}
@@ -463,7 +475,24 @@ const LinearChart = props => {
463
475
  )
464
476
  })}
465
477
  {!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}>
478
+ <Text
479
+ x={axisCenter}
480
+ y={
481
+ config.visualizationType === 'Forest Plot'
482
+ ? config.xAxis.tickWidthMax + 40
483
+ : config.orientation === 'horizontal'
484
+ ? dynamicMarginTop || config.xAxis.labelOffset
485
+ : config.isResponsiveTicks && dynamicMarginTop && !isHorizontal
486
+ ? dynamicMarginTop
487
+ : Number(rotation) && !config.isResponsiveTicks && !isHorizontal
488
+ ? Number(rotation + tickWidthMax / 1.3)
489
+ : Number(config.xAxis.labelOffset)
490
+ }
491
+ textAnchor='middle'
492
+ verticalAnchor='start'
493
+ fontWeight='bold'
494
+ fill={config.xAxis.labelColor}
495
+ >
467
496
  {props.label}
468
497
  </Text>
469
498
  </Group>
@@ -537,7 +566,7 @@ const LinearChart = props => {
537
566
  </AxisBottom>
538
567
  </>
539
568
  )}
540
- {visualizationType === 'Deviation Bar' && <DeviationBar animatedChart={animatedChart} xScale={xScale} yScale={yScale} width={xMax} height={yMax} />}
569
+ {visualizationType === 'Deviation Bar' && config.series?.length === 1 && <DeviationBar animatedChart={animatedChart} xScale={xScale} yScale={yScale} width={xMax} height={yMax} />}
541
570
  {visualizationType === 'Paired Bar' && <PairedBarChart originalWidth={width} width={xMax} height={yMax} />}
542
571
  {visualizationType === 'Scatter Plot' && (
543
572
  <ScatterPlot
@@ -554,7 +583,7 @@ const LinearChart = props => {
554
583
  showTooltip={showTooltip}
555
584
  />
556
585
  )}
557
- {visualizationType === 'Box Plot' && <CoveBoxPlot xScale={xScale} yScale={yScale} />}
586
+ {visualizationType === 'Box Plot' && <BoxPlot xScale={xScale} yScale={yScale} />}
558
587
  {((visualizationType === 'Area Chart' && config.visualizationSubType === 'regular') || visualizationType === 'Combo') && (
559
588
  <AreaChart xScale={xScale} yScale={yScale} yMax={yMax} xMax={xMax} chartRef={svgRef} width={xMax} height={yMax} handleTooltipMouseOver={handleTooltipMouseOver} handleTooltipMouseOff={handleTooltipMouseOff} tooltipData={tooltipData} showTooltip={showTooltip} />
560
589
  )}
@@ -624,10 +653,8 @@ const LinearChart = props => {
624
653
  xScale={xScale}
625
654
  yScale={yScale}
626
655
  seriesScale={seriesScale}
627
- width={xMax}
628
- height={yMax}
629
- maxWidth={width}
630
- maxHeight={height}
656
+ width={width}
657
+ height={height}
631
658
  getXAxisData={getXAxisData}
632
659
  getYAxisData={getYAxisData}
633
660
  animatedChart={animatedChart}
@@ -641,6 +668,8 @@ const LinearChart = props => {
641
668
  config={config}
642
669
  />
643
670
  )}
671
+ {/*Zoom Brush */}
672
+ {['Line', 'Bar', 'Combo', 'Area Chart'].includes(config.visualizationType) && !isHorizontal && <ZoomBrush xScaleBrush={xScaleBrush} yScale={yScale} xMax={xMax} yMax={yMax} />}
644
673
  {/* Line chart */}
645
674
  {/* TODO: Make this just line or combo? */}
646
675
  {visualizationType !== 'Bar' && visualizationType !== 'Paired Bar' && visualizationType !== 'Box Plot' && visualizationType !== 'Area Chart' && visualizationType !== 'Scatter Plot' && visualizationType !== 'Deviation Bar' && visualizationType !== 'Forecasting' && (
@@ -699,6 +728,11 @@ const LinearChart = props => {
699
728
  />
700
729
  )
701
730
  })}
731
+ {/* we are handling regions in bar charts differently, so that we can calculate the bar group into the region space. */}
732
+ {/* prettier-ignore */}
733
+ {config.visualizationType !== 'Bar' && config.visualizationType !== 'Combo' && (
734
+ <Regions xScale={xScale} handleTooltipClick={handleTooltipClick} handleTooltipMouseOff={handleTooltipMouseOff} handleTooltipMouseOver={handleTooltipMouseOver} showTooltip={showTooltip} hideTooltip={hideTooltip} tooltipData={tooltipData} yMax={yMax} width={width} />
735
+ )}
702
736
  {chartHasTooltipGuides && showTooltip && tooltipData && config.visual.verticalHoverLine && (
703
737
  <Group key='tooltipLine-vertical' className='vertical-tooltip-line'>
704
738
  <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' />
@@ -714,8 +748,18 @@ const LinearChart = props => {
714
748
  {config.chartMessage.noData}
715
749
  </Text>
716
750
  )}
751
+ {config.visualizationType === 'Bar' && config.tooltips.singleSeries && config.visual.horizontalHoverLine && (
752
+ <Group key='tooltipLine-horizontal' className='horizontal-tooltip-line' left={config.yAxis.size ? config.yAxis.size : 0}>
753
+ <Line from={{ x: 0, y: point.y }} to={{ x: xMax, y: point.y }} stroke={'black'} strokeWidth={1} pointerEvents='none' strokeDasharray='5,5' className='horizontal-tooltip-line' />
754
+ </Group>
755
+ )}
756
+ {config.visualizationType === 'Bar' && config.tooltips.singleSeries && config.visual.verticalHoverLine && (
757
+ <Group key='tooltipLine-vertical' className='vertical-tooltip-line'>
758
+ <Line from={{ x: point.x, y: 0 }} to={{ x: point.x, y: yMax }} stroke={'black'} strokeWidth={1} pointerEvents='none' strokeDasharray='5,5' className='vertical-tooltip-line' />
759
+ </Group>
760
+ )}
717
761
  </svg>
718
- {tooltipData && Object.entries(tooltipData.data).length > 0 && tooltipOpen && showTooltip && tooltipData.dataYPosition && tooltipData.dataXPosition && (
762
+ {tooltipData && Object.entries(tooltipData.data).length > 0 && tooltipOpen && showTooltip && tooltipData.dataYPosition && tooltipData.dataXPosition && !config.tooltips.singleSeries && (
719
763
  <>
720
764
  <style>{`.tooltip {background-color: rgba(255,255,255, ${config.tooltips.opacity / 100}) !important`}</style>
721
765
  <TooltipWithBounds key={Math.random()} className={'tooltip cdc-open-viz-module'} left={tooltipLeft} top={tooltipTop}>
@@ -723,9 +767,8 @@ const LinearChart = props => {
723
767
  </TooltipWithBounds>
724
768
  </>
725
769
  )}
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' }} />
728
- )}
770
+
771
+ {visSupportsReactTooltip() && <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' }} />}
729
772
  <div className='animation-trigger' ref={triggerRef} />
730
773
  </div>
731
774
  </ErrorBoundary>
@@ -47,11 +47,11 @@ const PairedBarChart = ({ width, height, originalWidth }) => {
47
47
  // Set label color
48
48
  let labelColor = '#000000'
49
49
 
50
- if (chroma.contrast(labelColor, groupOne.color) < 4.9) {
50
+ if (groupOne.color && chroma.contrast(labelColor, groupOne.color) < 4.9) {
51
51
  groupOne.labelColor = '#FFFFFF'
52
52
  }
53
53
 
54
- if (chroma.contrast(labelColor, groupTwo.color) < 4.9) {
54
+ if (groupTwo.color && chroma.contrast(labelColor, groupTwo.color) < 4.9) {
55
55
  groupTwo.labelColor = '#FFFFFF'
56
56
  }
57
57