@cdc/chart 4.23.4 → 4.23.6

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 (42) hide show
  1. package/dist/cdcchart.js +54845 -51755
  2. package/examples/feature/__data__/planet-example-data.json +14 -32
  3. package/examples/feature/__data__/planet-logaritmic-data.json +56 -0
  4. package/examples/feature/area/area-chart-category.json +240 -0
  5. package/examples/feature/bar/example-bar-chart.json +544 -22
  6. package/examples/feature/bar/new.json +561 -0
  7. package/examples/feature/bar/planet-chart-logaritmic-config.json +170 -0
  8. package/examples/feature/boxplot/valid-boxplot.csv +17 -0
  9. package/examples/feature/combo/right-issues.json +190 -0
  10. package/examples/feature/filters/filter-testing.json +37 -3
  11. package/examples/feature/forecasting/combo-forecasting.json +245 -0
  12. package/examples/feature/forecasting/forecasting.json +5325 -0
  13. package/examples/feature/forecasting/index.json +203 -0
  14. package/examples/feature/forecasting/random_data.csv +366 -0
  15. package/examples/feature/line/line-chart.json +3 -3
  16. package/examples/feature/test-highlight/test-highlight-2.json +789 -0
  17. package/examples/feature/test-highlight/test-highlight-vertical.json +561 -0
  18. package/examples/feature/test-highlight/test-highlight.json +100 -0
  19. package/examples/feature/tests-non-numerics/stacked-vertical-bar-example-nonnumerics.json +1 -2
  20. package/examples/gallery/bar-chart-horizontal/horizontal-highlight.json +345 -0
  21. package/examples/gallery/line/line.json +173 -1
  22. package/index.html +14 -8
  23. package/package.json +2 -2
  24. package/src/CdcChart.jsx +342 -25
  25. package/src/components/AreaChart.jsx +32 -40
  26. package/src/components/BarChart.jsx +147 -25
  27. package/src/components/DataTable.jsx +30 -12
  28. package/src/components/DeviationBar.jsx +32 -32
  29. package/src/components/EditorPanel.jsx +1902 -1126
  30. package/src/components/Forecasting.jsx +147 -0
  31. package/src/components/Legend.jsx +193 -243
  32. package/src/components/LineChart.jsx +4 -9
  33. package/src/components/LinearChart.jsx +263 -285
  34. package/src/components/Series.jsx +518 -0
  35. package/src/components/SparkLine.jsx +3 -3
  36. package/src/data/initial-state.js +24 -5
  37. package/src/hooks/useHighlightedBars.js +154 -0
  38. package/src/hooks/useMinMax.js +128 -0
  39. package/src/hooks/useReduceData.js +31 -57
  40. package/src/hooks/useRightAxis.js +8 -2
  41. package/src/hooks/useScales.js +196 -0
  42. /package/examples/feature/area/{area-chart.json → area-chart-date.json} +0 -0
@@ -4,302 +4,81 @@ import { Tooltip as ReactTooltip } from 'react-tooltip'
4
4
  import { Group } from '@visx/group'
5
5
  import { Line } from '@visx/shape'
6
6
  import { Text } from '@visx/text'
7
- import { scaleLinear, scalePoint, scaleBand, scaleTime } from '@visx/scale'
8
7
  import { AxisLeft, AxisBottom, AxisRight, AxisTop } from '@visx/axis'
8
+ import { localPoint } from '@visx/event'
9
+ import { useTooltip } from '@visx/tooltip'
9
10
 
10
- import CoveScatterPlot from './ScatterPlot'
11
11
  import BarChart from './BarChart'
12
- import LineChart from './LineChart'
13
12
  import ConfigContext from '../ConfigContext'
13
+ import CoveAreaChart from './AreaChart'
14
+ import CoveBoxPlot from './BoxPlot'
15
+ import CoveScatterPlot from './ScatterPlot'
16
+ import DeviationBar from './DeviationBar'
17
+ import LineChart from './LineChart'
14
18
  import PairedBarChart from './PairedBarChart'
15
19
  import useIntersectionObserver from './useIntersectionObserver'
16
- import CoveBoxPlot from './BoxPlot'
17
- import CoveAreaChart from './AreaChart'
18
20
 
19
21
  import ErrorBoundary from '@cdc/core/components/ErrorBoundary'
20
22
  import '../scss/LinearChart.scss'
21
23
  import useReduceData from '../hooks/useReduceData'
24
+ import useScales from '../hooks/useScales'
25
+ import useMinMax from '../hooks/useMinMax'
22
26
  import useRightAxis from '../hooks/useRightAxis'
23
27
  import useTopAxis from '../hooks/useTopAxis'
24
- import { DeviationBar } from './DeviationBar'
28
+ import Forecasting from './Forecasting'
25
29
 
26
- // TODO: Move scaling functions into hooks to manage complexity
27
30
  export default function LinearChart() {
28
- const { transformedData: data, dimensions, config, parseDate, formatDate, currentViewport, formatNumber, handleChartAriaLabels, updateConfig } = useContext(ConfigContext)
29
-
30
- let [width] = dimensions
31
- const { minValue, maxValue, existPositiveValue, isAllLine } = useReduceData(config, data)
32
- const [animatedChart, setAnimatedChart] = useState(false)
33
-
34
- const triggerRef = useRef()
35
- const dataRef = useIntersectionObserver(triggerRef, {
36
- freezeOnceVisible: false
37
- })
31
+ const { transformedData: data, dimensions, config, parseDate, formatDate, currentViewport, formatNumber, handleChartAriaLabels, updateConfig, handleLineType, rawData } = useContext(ConfigContext)
38
32
 
39
- // Make sure the chart is visible if in the editor
40
- /* eslint-disable react-hooks/exhaustive-deps */
41
- useEffect(() => {
42
- const element = document.querySelector('.isEditor')
43
- if (element) {
44
- // parent element is visible
45
- setAnimatedChart(prevState => true)
46
- }
47
- }) /* eslint-disable-line */
48
-
49
- // If the chart is in view, set to animate if it has not already played
50
- useEffect(() => {
51
- if (dataRef?.isIntersecting === true && config.animate) {
52
- setTimeout(() => {
53
- setAnimatedChart(prevState => true)
54
- }, 500)
55
- }
56
- }, [dataRef?.isIntersecting, config.animate])
33
+ // getters & functions
34
+ const getXAxisData = d => (config.runtime.xAxis.type === 'date' ? parseDate(d[config.runtime.originalXAxis.dataKey]).getTime() : d[config.runtime.originalXAxis.dataKey])
35
+ const getYAxisData = (d, seriesKey) => d[seriesKey]
36
+ const xAxisDataMapped = data.map(d => getXAxisData(d))
57
37
 
58
- if (config && config.legend && !config.legend.hide && config.legend.position !== 'bottom' && (currentViewport === 'lg' || currentViewport === 'md')) {
38
+ // configure width
39
+ let [width] = dimensions
40
+ if (config && config.legend && !config.legend.hide && config.legend.position !== 'bottom' && ['lg', 'md'].includes(currentViewport)) {
59
41
  width = width * 0.73
60
42
  }
43
+ // configure height , yMax, xMAx
61
44
  const { horizontal: heightHorizontal } = config.heights
45
+ const isHorizontal = config.orientation === 'horizontal'
46
+ const shouldAbbreviate = true
62
47
  const height = config.aspectRatio ? width * config.aspectRatio : config.heights[config.orientation]
63
48
  const xMax = width - config.runtime.yAxis.size - (config.visualizationType === 'Combo' ? config.yAxis.rightAxisSize : 0)
64
49
  const yMax = height - (config.orientation === 'horizontal' ? 0 : config.runtime.xAxis.size)
65
50
 
51
+ // hooks % states
52
+ const { minValue, maxValue, existPositiveValue, isAllLine } = useReduceData(config, data)
66
53
  const { yScaleRight, hasRightAxis } = useRightAxis({ config, yMax, data, updateConfig })
67
54
  const { hasTopAxis } = useTopAxis(config)
55
+ const [animatedChart, setAnimatedChart] = useState(false)
56
+ const properties = { data, config, minValue, maxValue, isAllLine, existPositiveValue, xAxisDataMapped, xMax, yMax }
57
+ const { min, max } = useMinMax(properties)
58
+ const { xScale, yScale, seriesScale, g1xScale, g2xScale, xScaleNoPadding } = useScales({ ...properties, min, max })
68
59
 
69
- const getXAxisData = d => (config.runtime.xAxis.type === 'date' ? parseDate(d[config.runtime.originalXAxis.dataKey]).getTime() : d[config.runtime.originalXAxis.dataKey])
70
- const getYAxisData = (d, seriesKey) => d[seriesKey]
71
-
72
- let xScale
73
- let yScale
74
- let seriesScale
75
-
76
- const { max: enteredMaxValue, min: enteredMinValue } = config.runtime.yAxis
77
- const isMaxValid = existPositiveValue ? enteredMaxValue >= maxValue : enteredMaxValue >= 0
78
- const isMinValid = (enteredMinValue <= 0 && minValue >= 0) || (enteredMinValue <= minValue && minValue < 0)
79
-
80
- let max = 0 // need outside the if statement
81
- let min = 0
82
- if (data) {
83
- min = enteredMinValue && isMinValid ? enteredMinValue : minValue
84
- max = enteredMaxValue && isMaxValid ? enteredMaxValue : Number.MIN_VALUE
85
-
86
- // If Confidence Intervals in data, then need to account for increased height in max for YScale
87
- if (config.visualizationType === 'Bar' || config.visualizationType === 'Combo' || config.visualizationType === 'Deviation Bar') {
88
- let ciYMax = 0
89
- if (config.hasOwnProperty('confidenceKeys')) {
90
- let upperCIValues = data.map(function (d) {
91
- return d[config.confidenceKeys.upper]
92
- })
93
- ciYMax = Math.max.apply(Math, upperCIValues)
94
- if (ciYMax > max) max = ciYMax // bump up the max
95
- }
96
- }
97
-
98
- if ((config.visualizationType === 'Bar' || (config.visualizationType === 'Combo' && !isAllLine)) && min > 0) {
99
- min = 0
100
- }
101
- if (config.visualizationType === 'Combo' && isAllLine) {
102
- if ((enteredMinValue === undefined || enteredMinValue === null || enteredMinValue === '') && min > 0) {
103
- min = 0
104
- }
105
- if (enteredMinValue) {
106
- const isMinValid = +enteredMinValue < minValue
107
- min = +enteredMinValue && isMinValid ? enteredMinValue : minValue
108
- }
109
- }
110
-
111
- if (config.visualizationType === 'Deviation Bar' && min > 0) {
112
- const isMinValid = Number(enteredMinValue) < Math.min(minValue, Number(config.xAxis.target))
113
- min = enteredMinValue && isMinValid ? enteredMinValue : 0
114
- }
115
-
116
- if (config.visualizationType === 'Line') {
117
- const isMinValid = enteredMinValue < minValue
118
- min = enteredMinValue && isMinValid ? enteredMinValue : minValue
119
- }
120
- //If data value max wasn't provided, calculate it
121
- if (max === Number.MIN_VALUE) {
122
- // if all values in data are negative set max = 0
123
- max = existPositiveValue ? maxValue : 0
124
- }
125
-
126
- //Adds Y Axis data padding if applicable
127
- if (config.runtime.yAxis.paddingPercent) {
128
- let paddingValue = (max - min) * config.runtime.yAxis.paddingPercent
129
- min -= paddingValue
130
- max += paddingValue
131
- }
132
-
133
- let xAxisDataMapped = data.map(d => getXAxisData(d))
134
-
135
- if (config.isLollipopChart && config.yAxis.displayNumbersOnBar) {
136
- const dataKey = data.map(item => item[config.series[0].dataKey])
137
- const maxDataVal = Math.max(...dataKey).toString().length
138
-
139
- switch (true) {
140
- case maxDataVal > 8 && maxDataVal <= 12:
141
- max = max * 1.3
142
- break
143
- case maxDataVal > 4 && maxDataVal <= 7:
144
- max = max * 1.1
145
- break
146
- default:
147
- break
148
- }
149
- }
150
-
151
- // DEV-3219 - bc some values are going above YScale - adding 10% or 20% factor onto Max
152
- // - put the statement up here and it works for both vert and horiz charts of all types
153
- if (config.yAxis.enablePadding) {
154
- if (min < 0) {
155
- // sets with negative data need more padding on the max
156
- max *= 1.2
157
- min *= 1.2
158
- } else {
159
- max *= 1.1
160
- }
161
- }
162
-
163
- if (config.runtime.horizontal) {
164
- xScale = scaleLinear({
165
- domain: [min * 1.03, max],
166
- range: [0, xMax]
167
- })
168
-
169
- yScale =
170
- config.runtime.xAxis.type === 'date'
171
- ? scaleLinear({
172
- domain: [Math.min(...xAxisDataMapped), Math.max(...xAxisDataMapped)]
173
- })
174
- : scalePoint({ domain: xAxisDataMapped, padding: 0.5 })
175
-
176
- seriesScale = scalePoint({
177
- domain: config.runtime.barSeriesKeys || config.runtime.seriesKeys,
178
- range: [0, yMax]
179
- })
180
-
181
- yScale.rangeRound([0, yMax])
182
- } else {
183
- min = min < 0 ? min * 1.11 : min
184
-
185
- yScale = scaleLinear({
186
- domain: [min, max],
187
- range: [yMax, 0]
188
- })
189
-
190
- xScale = scalePoint({
191
- domain: xAxisDataMapped,
192
- range: [0, xMax],
193
- padding: 0.5
194
- })
195
-
196
- seriesScale = scalePoint({
197
- domain: config.runtime.barSeriesKeys || config.runtime.seriesKeys,
198
- range: [0, xMax]
199
- })
200
- }
201
-
202
- if (config.visualizationType === 'Area Chart' && config.xAxis.type === 'date') {
203
- xScale = scaleTime({
204
- domain: [Math.min(...xAxisDataMapped), Math.max(...xAxisDataMapped)],
205
- range: [0, xMax]
206
- })
207
- }
208
-
209
- if (config.visualizationType === 'Paired Bar') {
210
- const offset = 1.02 // Offset of the ticks/values from the Axis
211
- let groupOneMax = Math.max.apply(
212
- Math,
213
- data.map(d => d[config.series[0].dataKey])
214
- )
215
- let groupTwoMax = Math.max.apply(
216
- Math,
217
- data.map(d => d[config.series[1].dataKey])
218
- )
219
-
220
- // group one
221
- var g1xScale = scaleLinear({
222
- domain: [0, Math.max(groupOneMax, groupTwoMax) * offset],
223
- range: [xMax / 2, 0]
224
- })
225
-
226
- // group 2
227
- var g2xScale = scaleLinear({
228
- domain: g1xScale.domain(),
229
- range: [xMax / 2, xMax],
230
- nice: true
231
- })
232
- }
233
-
234
- if (config.visualizationType === 'Scatter Plot') {
235
- if (config.xAxis.type === 'continuous') {
236
- xScale = scaleLinear({
237
- domain: [0, Math.max.apply(null, xScale.domain())],
238
- range: [0, xMax]
239
- })
240
- }
241
- }
242
-
243
- if (config.visualizationType === 'Deviation Bar') {
244
- const leftOffset = config.isLollipopChart ? 1.05 : 1.03
245
- yScale = scaleBand({
246
- domain: xAxisDataMapped,
247
- range: [0, yMax]
248
- })
249
- xScale = scaleLinear({
250
- domain: [min * leftOffset, Math.max(Number(config.xAxis.target), max)],
251
- range: [0, xMax],
252
- round: true,
253
- nice: true
254
- })
255
- }
256
- // Handle Box Plots
257
- if (config.visualizationType === 'Box Plot') {
258
- const allOutliers = []
259
- const hasOutliers = config.boxplot.plots.map(b => b.columnOutliers.map(outlier => allOutliers.push(outlier))) && !config.boxplot.hideOutliers
260
-
261
- // check if outliers are lower
262
- if (hasOutliers) {
263
- let outlierMin = Math.min(...allOutliers)
264
- let outlierMax = Math.max(...allOutliers)
265
-
266
- // check if outliers exceed standard bounds
267
- if (outlierMin < min) min = outlierMin
268
- if (outlierMax > max) max = outlierMax
269
- }
270
-
271
- // check fences for max/min
272
- let lowestFence = Math.min(...config.boxplot.plots.map(item => item.columnLowerBounds))
273
- let highestFence = Math.max(...config.boxplot.plots.map(item => item.columnUpperBounds))
274
-
275
- if (lowestFence < min) min = lowestFence
276
- if (highestFence > max) max = highestFence
277
-
278
- // Set Scales
279
- yScale = scaleLinear({
280
- range: [yMax, 0],
281
- round: true,
282
- domain: [min, max]
283
- })
284
-
285
- xScale = scaleBand({
286
- range: [0, xMax],
287
- round: true,
288
- domain: config.boxplot.categories,
289
- padding: 0.4
290
- })
291
- }
292
- }
293
-
294
- const shouldAbbreviate = true
60
+ // refs
61
+ const triggerRef = useRef()
62
+ const svgRef = useRef()
63
+ const dataRef = useIntersectionObserver(triggerRef, {
64
+ freezeOnceVisible: false
65
+ })
295
66
 
296
67
  const handleLeftTickFormatting = tick => {
68
+ if (config.useLogScale && tick === 0.1) {
69
+ //when logarithmic scale applied change value of first tick
70
+ tick = 0
71
+ }
297
72
  if (config.runtime.yAxis.type === 'date') return formatDate(parseDate(tick))
298
73
  if (config.orientation === 'vertical') return formatNumber(tick, 'left', shouldAbbreviate)
299
74
  return tick
300
75
  }
301
76
 
302
77
  const handleBottomTickFormatting = tick => {
78
+ if (config.useLogScale && tick === 0.1) {
79
+ // when logaritmic scale applyed change value FIRST of tick
80
+ tick = 0
81
+ }
303
82
  if (config.runtime.xAxis.type === 'date') return formatDate(tick)
304
83
  if (config.orientation === 'horizontal') return formatNumber(tick, 'left', shouldAbbreviate)
305
84
  if (config.xAxis.type === 'continuous') return formatNumber(tick, 'bottom', shouldAbbreviate)
@@ -307,8 +86,6 @@ export default function LinearChart() {
307
86
  }
308
87
 
309
88
  const countNumOfTicks = axis => {
310
- // function get number of ticks based on bar type & users value
311
- const isHorizontal = config.orientation === 'horizontal'
312
89
  const { numTicks } = config.runtime[axis]
313
90
  let tickCount = undefined
314
91
 
@@ -344,14 +121,136 @@ export default function LinearChart() {
344
121
  return tickCount
345
122
  }
346
123
 
347
- const svgRef = useRef()
124
+ // Tooltip helper for getting data to the closest date/category hovered.
125
+ const getXValueFromCoordinate = x => {
126
+ if (xScale.type === 'point') {
127
+ // Find the closest x value by calculating the minimum distance
128
+ let closestX = null
129
+ let minDistance = Number.MAX_VALUE
130
+ let offset = x - yAxis.size
131
+
132
+ data.forEach(d => {
133
+ const xPosition = xAxis.type === 'date' ? xScale(parseDate(d[xAxis.dataKey])) : xScale(d[xAxis.dataKey])
134
+ const distance = Math.abs(Number(xPosition - offset))
135
+
136
+ if (distance < minDistance) {
137
+ minDistance = distance
138
+ closestX = xAxis.type === 'date' ? parseDate(d[xAxis.dataKey]) : d[xAxis.dataKey]
139
+ }
140
+ })
141
+ return closestX
142
+ }
143
+ }
144
+
145
+ // import tooltip helpers
146
+ const { tooltipData, showTooltip, hideTooltip } = useTooltip()
147
+
148
+ const handleTooltipMouseOver = (e, data) => {
149
+ // get the svg coordinates of the mouse
150
+ // and get the closest values
151
+ const eventSvgCoords = localPoint(e)
152
+ const { x, y } = eventSvgCoords
153
+
154
+ const { runtime } = config
155
+
156
+ let closestXScaleValue = getXValueFromCoordinate(x)
157
+ let formattedDate = formatDate(closestXScaleValue)
158
+
159
+ let yScaleValues
160
+ if (xAxis.type === 'categorical') {
161
+ yScaleValues = data.filter(d => d[xAxis.dataKey] === closestXScaleValue)
162
+ } else {
163
+ yScaleValues = rawData.filter(d => formatDate(parseDate(d[xAxis.dataKey])) === formattedDate)
164
+ }
165
+
166
+ let seriesToInclude = []
167
+ let stageColumns = []
168
+ let ciItems = []
169
+
170
+ // loop through series for items to add to tooltip.
171
+ // there is probably a better way of doing this.
172
+ config.series?.map(s => {
173
+ if (s.type === 'Forecasting') {
174
+ stageColumns.push(s.stageColumn)
175
+
176
+ // greedy fn 😭
177
+ s?.confidenceIntervals.map(ci => {
178
+ if (ci.showInTooltip === true) {
179
+ ciItems.push(ci.low)
180
+ ciItems.push(ci.high)
181
+ }
182
+ })
183
+ }
184
+ })
185
+
186
+ let standardLoopItems = []
187
+
188
+ if (config.visualizationType === 'Combo') {
189
+ standardLoopItems = [runtime.xAxis.dataKey, ...runtime?.barSeriesKeys, ...stageColumns, ...ciItems]
190
+ } else {
191
+ standardLoopItems = [runtime.xAxis.dataKey, ...stageColumns, ...ciItems]
192
+ }
193
+
194
+ standardLoopItems.map(seriesKey => {
195
+ if (!seriesKey) return false
196
+ if (!yScaleValues[0]) return false
197
+ for (const item of Object.entries(yScaleValues[0])) {
198
+ if (item[0] === seriesKey) {
199
+ seriesToInclude.push(item)
200
+ }
201
+ }
202
+ })
203
+
204
+ // filter out the series that aren't added to the map.
205
+ if (!seriesToInclude) return
206
+ let initialTooltipData = Object.fromEntries(seriesToInclude) ? Object.fromEntries(seriesToInclude) : {}
207
+
208
+ let tooltipData = {}
209
+ tooltipData.data = initialTooltipData
210
+ tooltipData.dataXPosition = x + 10
211
+ tooltipData.dataYPosition = y
212
+
213
+ let tooltipInformation = {
214
+ tooltipData: tooltipData,
215
+ tooltipTop: 0,
216
+ tooltipValues: yScaleValues,
217
+ tooltipLeft: x
218
+ }
219
+
220
+ showTooltip(tooltipInformation)
221
+ }
222
+
223
+ const handleTooltipMouseOff = () => {
224
+ hideTooltip()
225
+ }
226
+
227
+ // Make sure the chart is visible if in the editor
228
+ /* eslint-disable react-hooks/exhaustive-deps */
229
+ useEffect(() => {
230
+ const element = document.querySelector('.isEditor')
231
+ if (element) {
232
+ // parent element is visible
233
+ setAnimatedChart(prevState => true)
234
+ }
235
+ }) /* eslint-disable-line */
236
+
237
+ // If the chart is in view, set to animate if it has not already played
238
+ useEffect(() => {
239
+ if (dataRef?.isIntersecting === true && config.animate) {
240
+ setTimeout(() => {
241
+ setAnimatedChart(prevState => true)
242
+ }, 500)
243
+ }
244
+ }, [dataRef?.isIntersecting, config.animate])
245
+
246
+ const { orientation, xAxis, yAxis } = config
348
247
 
349
248
  return isNaN(width) ? (
350
249
  <></>
351
250
  ) : (
352
251
  <ErrorBoundary component='LinearChart'>
353
252
  <svg width={width} height={height} className={`linear ${config.animate ? 'animated' : ''} ${animatedChart && config.animate ? 'animate' : ''}`} role='img' aria-label={handleChartAriaLabels(config)} tabIndex={0} ref={svgRef}>
354
- {/* Higlighted regions */}
253
+ {/* Highlighted regions */}
355
254
  {config.regions
356
255
  ? config.regions.map(region => {
357
256
  if (!Object.keys(region).includes('from') || !Object.keys(region).includes('to')) return null
@@ -397,7 +296,7 @@ export default function LinearChart() {
397
296
 
398
297
  {/* Y axis */}
399
298
  {config.visualizationType !== 'Spark Line' && (
400
- <AxisLeft scale={yScale} left={Number(config.runtime.yAxis.size) - config.yAxis.axisPadding} label={config.runtime.yAxis.label} stroke='#333' tickFormat={tick => handleLeftTickFormatting(tick)} numTicks={countNumOfTicks('yAxis')}>
299
+ <AxisLeft scale={yScale} tickLength={config.useLogScale ? 6 : 8} left={Number(config.runtime.yAxis.size) - config.yAxis.axisPadding} label={config.runtime.yAxis.label} stroke='#333' tickFormat={tick => handleLeftTickFormatting(tick)} numTicks={countNumOfTicks('yAxis')}>
401
300
  {props => {
402
301
  const axisCenter = config.runtime.horizontal ? (props.axisToPoint.y - props.axisFromPoint.y) / 2 : (props.axisFromPoint.y - props.axisToPoint.y) / 2
403
302
  const horizontalTickOffset = yMax / props.ticks.length / 2 - (yMax / props.ticks.length) * (1 - config.barThickness) + 5
@@ -406,12 +305,15 @@ export default function LinearChart() {
406
305
  {props.ticks.map((tick, i) => {
407
306
  const minY = props.ticks[0].to.y
408
307
  const barMinHeight = 15 // 15 is the min height for bars by default
308
+ const showTicks = String(tick.value).startsWith('1') || tick.value === 0.1 ? 'block' : 'none'
309
+ const tickLength = showTicks === 'block' ? 7 : 0
310
+ const to = { x: tick.to.x - tickLength, y: tick.to.y }
409
311
 
410
312
  return (
411
313
  <Group key={`vx-tick-${tick.value}-${i}`} className={'vx-axis-tick'}>
412
- {!config.runtime.yAxis.hideTicks && <Line from={tick.from} to={tick.to} stroke={config.yAxis.tickColor} display={config.runtime.horizontal ? 'none' : 'block'} />}
314
+ {!config.runtime.yAxis.hideTicks && <Line from={tick.from} to={config.useLogScale ? to : tick.to} stroke={config.yAxis.tickColor} display={config.runtime.horizontal ? 'none' : 'block'} />}
413
315
 
414
- {config.runtime.yAxis.gridLines ? <Line from={{ x: tick.from.x + xMax, y: tick.from.y }} to={tick.from} stroke='rgba(0,0,0,0.3)' /> : ''}
316
+ {config.runtime.yAxis.gridLines ? <Line display={config.useLogScale && showTicks} from={{ x: tick.from.x + xMax, y: tick.from.y }} to={tick.from} stroke='rgba(0,0,0,0.3)' /> : ''}
415
317
 
416
318
  {config.orientation === 'horizontal' && config.visualizationSubType !== 'stacked' && config.yAxis.labelPlacement === 'On Date/Category Axis' && !config.yAxis.hideLabel && (
417
319
  <Text
@@ -440,8 +342,10 @@ export default function LinearChart() {
440
342
  </Text>
441
343
  )}
442
344
 
443
- {config.orientation !== 'horizontal' && config.visualizationType !== 'Paired Bar' && !config.yAxis.hideLabel && (
345
+ {config.orientation === 'vertical' && config.visualizationType !== 'Paired Bar' && !config.yAxis.hideLabel && (
444
346
  <Text
347
+ display={config.useLogScale ? showTicks : 'block'}
348
+ dx={config.useLogScale ? -6 : 0}
445
349
  x={config.runtime.horizontal ? tick.from.x + 2 : tick.to.x}
446
350
  y={tick.to.y + (config.runtime.horizontal ? horizontalTickOffset : 0)}
447
351
  verticalAnchor={config.runtime.horizontal ? 'start' : 'middle'}
@@ -490,7 +394,7 @@ export default function LinearChart() {
490
394
  )
491
395
  })}
492
396
  {!config.yAxis.rightHideAxis && <Line from={props.axisFromPoint} to={props.axisToPoint} stroke='#333' />}
493
- <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}>
397
+ <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}>
494
398
  {props.label}
495
399
  </Text>
496
400
  </Group>
@@ -529,12 +433,19 @@ export default function LinearChart() {
529
433
  return (
530
434
  <Group className='bottom-axis'>
531
435
  {props.ticks.map((tick, i) => {
436
+ // when using LogScale show major ticks values only
437
+ const showTick = String(tick.value).startsWith('1') || tick.value === 0.1 ? 'block' : 'none'
532
438
  const tickWidth = xMax / props.ticks.length
439
+ const tickLength = showTick === 'block' ? 16 : 8
440
+ const to = { x: tick.to.x, y: tickLength }
441
+
533
442
  return (
534
443
  <Group key={`vx-tick-${tick.value}-${i}`} className={'vx-axis-tick'}>
535
- {!config.xAxis.hideTicks && <Line from={tick.from} to={tick.to} stroke={config.xAxis.tickColor} />}
444
+ {!config.xAxis.hideTicks && <Line from={tick.from} to={config.orientation === 'horizontal' && config.useLogScale ? to : tick.to} stroke={config.xAxis.tickColor} strokeWidth={showTick === 'block' ? 1.3 : 1} />}
536
445
  {!config.xAxis.hideLabel && (
537
446
  <Text
447
+ dy={config.orientation === 'horizontal' && config.useLogScale ? 8 : 0}
448
+ display={config.orientation === 'horizontal' && config.useLogScale ? showTick : 'block'}
538
449
  transform={`translate(${tick.to.x}, ${tick.to.y}) rotate(-${!config.runtime.horizontal ? config.runtime.xAxis.tickRotation : 0})`}
539
450
  verticalAnchor='start'
540
451
  textAnchor={config.runtime.xAxis.tickRotation && config.runtime.xAxis.tickRotation !== '0' ? 'end' : 'middle'}
@@ -623,28 +534,95 @@ export default function LinearChart() {
623
534
  </AxisBottom>
624
535
  </>
625
536
  )}
626
-
627
537
  {config.visualizationType === 'Deviation Bar' && <DeviationBar xScale={xScale} yScale={yScale} width={xMax} height={yMax} />}
628
538
  {config.visualizationType === 'Paired Bar' && <PairedBarChart originalWidth={width} width={xMax} height={yMax} />}
629
539
  {config.visualizationType === 'Scatter Plot' && <CoveScatterPlot xScale={xScale} yScale={yScale} getXAxisData={getXAxisData} getYAxisData={getYAxisData} />}
630
540
  {config.visualizationType === 'Box Plot' && <CoveBoxPlot xScale={xScale} yScale={yScale} />}
631
541
  {(config.visualizationType === 'Area Chart' || config.visualizationType === 'Combo') && <CoveAreaChart xScale={xScale} yScale={yScale} yMax={yMax} xMax={xMax} chartRef={svgRef} />}
632
-
633
- {/* Bar chart */}
634
- {/* TODO: Make this just bar or combo? */}
635
- {config.visualizationType !== 'Line' && config.visualizationType !== 'Paired Bar' && config.visualizationType !== 'Box Plot' && config.visualizationType !== 'Area Chart' && config.visualizationType !== 'Scatter Plot' && config.visualizationType !== 'Deviation Bar' && (
636
- <>
637
- <BarChart xScale={xScale} yScale={yScale} seriesScale={seriesScale} xMax={xMax} yMax={yMax} getXAxisData={getXAxisData} getYAxisData={getYAxisData} animatedChart={animatedChart} visible={animatedChart} />
638
- </>
542
+ {(config.visualizationType === 'Bar' || config.visualizationType === 'Combo') && <BarChart xScale={xScale} yScale={yScale} seriesScale={seriesScale} xMax={xMax} yMax={yMax} getXAxisData={getXAxisData} getYAxisData={getYAxisData} animatedChart={animatedChart} visible={animatedChart} />}
543
+ {(config.visualizationType === 'Line' || config.visualizationType === 'Combo') && <LineChart xScale={xScale} yScale={yScale} getXAxisData={getXAxisData} getYAxisData={getYAxisData} xMax={xMax} yMax={yMax} seriesStyle={config.series} />}
544
+ {(config.visualizationType === 'Forecasting' || config.visualizationType === 'Combo') && (
545
+ <Forecasting
546
+ hideTooltip={hideTooltip}
547
+ showTooltip={showTooltip}
548
+ tooltipData={tooltipData}
549
+ xScale={xScale}
550
+ yScale={yScale}
551
+ width={xMax}
552
+ height={yMax}
553
+ xScaleNoPadding={xScaleNoPadding}
554
+ chartRef={svgRef}
555
+ getXValueFromCoordinate={getXValueFromCoordinate}
556
+ handleTooltipMouseOver={handleTooltipMouseOver}
557
+ handleTooltipMouseOff={handleTooltipMouseOff}
558
+ />
639
559
  )}
640
560
 
561
+ {/* y anchors */}
562
+ {config.yAxis.anchors &&
563
+ config.yAxis.anchors.map(anchor => {
564
+ return <Line strokeDasharray={handleLineType(anchor.lineStyle)} stroke='rgba(0,0,0,1)' className='customAnchor' from={{ x: 0 + config.yAxis.size, y: yScale(anchor.value) }} to={{ x: xMax, y: yScale(anchor.value) }} display={config.runtime.horizontal ? 'none' : 'block'} />
565
+ })}
566
+
641
567
  {/* Line chart */}
642
568
  {/* TODO: Make this just line or combo? */}
643
- {config.visualizationType !== 'Bar' && config.visualizationType !== 'Paired Bar' && config.visualizationType !== 'Box Plot' && config.visualizationType !== 'Area Chart' && config.visualizationType !== 'Scatter Plot' && config.visualizationType !== 'Deviation Bar' && (
644
- <>
645
- <LineChart xScale={xScale} yScale={yScale} getXAxisData={getXAxisData} getYAxisData={getYAxisData} xMax={xMax} yMax={yMax} seriesStyle={config.series} />
646
- </>
647
- )}
569
+ {config.visualizationType !== 'Bar' &&
570
+ config.visualizationType !== 'Paired Bar' &&
571
+ config.visualizationType !== 'Box Plot' &&
572
+ config.visualizationType !== 'Area Chart' &&
573
+ config.visualizationType !== 'Scatter Plot' &&
574
+ config.visualizationType !== 'Deviation Bar' &&
575
+ config.visualizationType !== 'Forecasting' && (
576
+ <>
577
+ <LineChart xScale={xScale} yScale={yScale} getXAxisData={getXAxisData} getYAxisData={getYAxisData} xMax={xMax} yMax={yMax} seriesStyle={config.series} />
578
+ </>
579
+ )}
580
+
581
+ {/* y anchors */}
582
+ {config.yAxis.anchors &&
583
+ config.yAxis.anchors.map(anchor => {
584
+ let anchorPosition = yScale(anchor.value)
585
+ const padding = config.orientation === 'horizontal' ? Number(config.xAxis.size) : Number(config.yAxis.size)
586
+ const middleOffset = config.orientation === 'horizontal' && config.visualizationType === 'Bar' ? config.barHeight / 4 : 0
587
+
588
+ return (
589
+ // prettier-ignore
590
+ <Line
591
+ key={anchor.value}
592
+ strokeDasharray={handleLineType(anchor.lineStyle)}
593
+ stroke={anchor.color ? anchor.color : 'rgba(0,0,0,1)'}
594
+ className='anchor-y'
595
+ from={{ x: 0 + padding, y: anchorPosition - middleOffset}}
596
+ to={{ x: width, y: anchorPosition - middleOffset }}
597
+ />
598
+ )
599
+ })}
600
+
601
+ {/* x anchors */}
602
+ {config.xAxis.anchors &&
603
+ config.xAxis.anchors.map(anchor => {
604
+ let newX = xAxis
605
+ if (orientation === 'horizontal') {
606
+ newX = yAxis
607
+ }
608
+
609
+ let anchorPosition = newX.type === 'date' ? xScale(parseDate(anchor.value, false)) : xScale(anchor.value)
610
+
611
+ const padding = orientation === 'horizontal' ? Number(config.xAxis.size) : Number(config.yAxis.size)
612
+
613
+ return (
614
+ // prettier-ignore
615
+ <Line
616
+ key={anchor.value}
617
+ strokeDasharray={handleLineType(anchor.lineStyle)}
618
+ stroke={anchor.color ? anchor.color : 'rgba(0,0,0,1)'}
619
+ fill={anchor.color ? anchor.color : 'rgba(0,0,0,1)'}
620
+ className='anchor-x'
621
+ from={{ x: Number(anchorPosition) + Number(padding), y: 0 }}
622
+ to={{ x: Number(anchorPosition) + Number(padding), y: yMax }}
623
+ />
624
+ )
625
+ })}
648
626
  </svg>
649
627
  <ReactTooltip id={`cdc-open-viz-tooltip-${config.runtime.uniqueId}`} variant='light' arrowColor='rgba(0,0,0,0)' className='tooltip' />
650
628
  <div className='animation-trigger' ref={triggerRef} />