@cdc/chart 4.23.5 → 4.23.7
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.
- package/dist/cdcchart.js +29320 -28775
- package/examples/feature/__data__/planet-example-data.json +15 -16
- package/examples/feature/bar/new.json +561 -0
- package/examples/feature/combo/right-issues.json +190 -0
- package/examples/feature/forecasting/combo-forecasting.json +271 -0
- package/examples/feature/forecasting/effective_reproduction.json +57 -8
- package/examples/feature/forecasting/forecasting.json +5334 -0
- package/examples/feature/forecasting/index.json +203 -0
- package/examples/feature/line/line-chart.json +12 -12
- package/examples/gallery/bar-chart-vertical/vertical-bar-chart-categorical.json +167 -20
- package/examples/gallery/line/line.json +173 -1
- package/index.html +14 -8
- package/package.json +2 -2
- package/src/CdcChart.jsx +104 -26
- package/src/components/AreaChart.jsx +23 -149
- package/src/components/BarChart.jsx +87 -15
- package/src/components/DataTable.jsx +35 -14
- package/src/components/EditorPanel.jsx +1829 -1954
- package/src/components/Forecasting.jsx +84 -0
- package/src/components/Legend.jsx +191 -275
- package/src/components/LineChart.jsx +34 -7
- package/src/components/LinearChart.jsx +510 -101
- package/src/components/Series.jsx +554 -0
- package/src/components/SparkLine.jsx +3 -3
- package/src/data/initial-state.js +13 -5
- package/src/hooks/useMinMax.js +37 -0
- package/src/hooks/useRightAxis.js +9 -2
- package/src/hooks/useScales.js +7 -13
- package/src/scss/main.scss +4 -17
- package/LICENSE +0 -201
|
@@ -1,58 +1,72 @@
|
|
|
1
|
-
import React, { useContext, useEffect, useRef, useState } from 'react'
|
|
2
|
-
import { Tooltip as ReactTooltip } from 'react-tooltip'
|
|
1
|
+
import React, { forwardRef, useContext, useEffect, useRef, useState } from 'react'
|
|
3
2
|
|
|
3
|
+
// Libraries
|
|
4
|
+
import { AxisLeft, AxisBottom, AxisRight, AxisTop } from '@visx/axis'
|
|
5
|
+
import { bisector } from 'd3-array'
|
|
4
6
|
import { Group } from '@visx/group'
|
|
5
|
-
import { Line } from '@visx/shape'
|
|
7
|
+
import { Line, Bar } from '@visx/shape'
|
|
8
|
+
import { localPoint } from '@visx/event'
|
|
6
9
|
import { Text } from '@visx/text'
|
|
7
|
-
import {
|
|
10
|
+
import { Tooltip as ReactTooltip } from 'react-tooltip'
|
|
11
|
+
import { useTooltip, TooltipWithBounds, useTooltipInPortal } from '@visx/tooltip'
|
|
8
12
|
|
|
13
|
+
// CDC Components
|
|
14
|
+
import AreaChart from './AreaChart'
|
|
9
15
|
import BarChart from './BarChart'
|
|
10
16
|
import ConfigContext from '../ConfigContext'
|
|
11
|
-
import CoveAreaChart from './AreaChart'
|
|
12
17
|
import CoveBoxPlot from './BoxPlot'
|
|
13
18
|
import CoveScatterPlot from './ScatterPlot'
|
|
14
19
|
import DeviationBar from './DeviationBar'
|
|
20
|
+
import ErrorBoundary from '@cdc/core/components/ErrorBoundary'
|
|
21
|
+
import Forecasting from './Forecasting'
|
|
15
22
|
import LineChart from './LineChart'
|
|
16
23
|
import PairedBarChart from './PairedBarChart'
|
|
17
24
|
import useIntersectionObserver from './useIntersectionObserver'
|
|
18
25
|
|
|
19
|
-
|
|
20
|
-
import '../scss/LinearChart.scss'
|
|
21
|
-
import useReduceData from '../hooks/useReduceData'
|
|
22
|
-
import useScales from '../hooks/useScales'
|
|
26
|
+
// Hooks
|
|
23
27
|
import useMinMax from '../hooks/useMinMax'
|
|
28
|
+
import useReduceData from '../hooks/useReduceData'
|
|
24
29
|
import useRightAxis from '../hooks/useRightAxis'
|
|
30
|
+
import useScales from '../hooks/useScales'
|
|
25
31
|
import useTopAxis from '../hooks/useTopAxis'
|
|
26
32
|
|
|
33
|
+
// styles
|
|
34
|
+
import { defaultStyles } from '@visx/tooltip'
|
|
35
|
+
import '../scss/LinearChart.scss'
|
|
36
|
+
|
|
27
37
|
export default function LinearChart() {
|
|
28
|
-
const { transformedData: data, dimensions, config, parseDate, formatDate, currentViewport, formatNumber, handleChartAriaLabels, updateConfig, handleLineType } = useContext(ConfigContext)
|
|
38
|
+
const { isEditor, transformedData: data, dimensions, config, parseDate, formatDate, currentViewport, formatNumber, handleChartAriaLabels, updateConfig, handleLineType, rawData, capitalize, setSharedFilter, setSharedFilterValue, getTextWidth } = useContext(ConfigContext)
|
|
39
|
+
|
|
40
|
+
// todo: start destructuring this file for conciseness
|
|
41
|
+
const { visualizationType, visualizationSubType, orientation, xAxis, yAxis, runtime } = config
|
|
29
42
|
|
|
30
43
|
// getters & functions
|
|
31
|
-
const getXAxisData = d => (
|
|
44
|
+
const getXAxisData = d => (runtime.xAxis.type === 'date' ? parseDate(d[runtime.originalXAxis.dataKey]).getTime() : d[runtime.originalXAxis.dataKey])
|
|
32
45
|
const getYAxisData = (d, seriesKey) => d[seriesKey]
|
|
33
46
|
const xAxisDataMapped = data.map(d => getXAxisData(d))
|
|
34
47
|
|
|
35
48
|
// configure width
|
|
36
49
|
let [width] = dimensions
|
|
50
|
+
let originalWidth = width
|
|
37
51
|
if (config && config.legend && !config.legend.hide && config.legend.position !== 'bottom' && ['lg', 'md'].includes(currentViewport)) {
|
|
38
52
|
width = width * 0.73
|
|
39
53
|
}
|
|
40
54
|
// configure height , yMax, xMAx
|
|
41
55
|
const { horizontal: heightHorizontal } = config.heights
|
|
42
|
-
const isHorizontal =
|
|
56
|
+
const isHorizontal = orientation === 'horizontal'
|
|
43
57
|
const shouldAbbreviate = true
|
|
44
|
-
const height = config.aspectRatio ? width * config.aspectRatio : config.heights[
|
|
45
|
-
const xMax = width -
|
|
46
|
-
const yMax = height - (
|
|
58
|
+
const height = config.aspectRatio ? width * config.aspectRatio : config.heights[orientation]
|
|
59
|
+
const xMax = width - runtime.yAxis.size - (visualizationType === 'Combo' ? config.yAxis.rightAxisSize : 0)
|
|
60
|
+
const yMax = height - (orientation === 'horizontal' ? 0 : runtime.xAxis.size)
|
|
47
61
|
|
|
48
|
-
// hooks
|
|
62
|
+
// hooks
|
|
49
63
|
const { minValue, maxValue, existPositiveValue, isAllLine } = useReduceData(config, data)
|
|
50
64
|
const { yScaleRight, hasRightAxis } = useRightAxis({ config, yMax, data, updateConfig })
|
|
51
65
|
const { hasTopAxis } = useTopAxis(config)
|
|
52
66
|
const [animatedChart, setAnimatedChart] = useState(false)
|
|
53
67
|
const properties = { data, config, minValue, maxValue, isAllLine, existPositiveValue, xAxisDataMapped, xMax, yMax }
|
|
54
68
|
const { min, max } = useMinMax(properties)
|
|
55
|
-
const { xScale, yScale, seriesScale, g1xScale, g2xScale } = useScales({ ...properties, min, max })
|
|
69
|
+
const { xScale, yScale, seriesScale, g1xScale, g2xScale, xScaleNoPadding } = useScales({ ...properties, min, max })
|
|
56
70
|
|
|
57
71
|
// refs
|
|
58
72
|
const triggerRef = useRef()
|
|
@@ -61,29 +75,58 @@ export default function LinearChart() {
|
|
|
61
75
|
freezeOnceVisible: false
|
|
62
76
|
})
|
|
63
77
|
|
|
78
|
+
// a unique id is needed for tooltips.
|
|
79
|
+
const tooltip_id = `cdc-open-viz-tooltip-${config.runtime.uniqueId}`
|
|
80
|
+
|
|
81
|
+
// sets the portal x/y for where tooltips should appear on the page.
|
|
82
|
+
const [chartPosition, setChartPosition] = useState(null)
|
|
83
|
+
useEffect(() => {
|
|
84
|
+
setChartPosition(svgRef?.current?.getBoundingClientRect())
|
|
85
|
+
}, [svgRef, config.legend])
|
|
86
|
+
|
|
87
|
+
// resolved an issue here with Object.entries, label no longer used.
|
|
88
|
+
const TooltipListItem = ({ item }) => {
|
|
89
|
+
const [label, value] = item
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* find the original series and use the name property if available
|
|
93
|
+
* otherwise default back to the original column name.
|
|
94
|
+
* @param {String} input - original columnName
|
|
95
|
+
* @returns user defined series name.
|
|
96
|
+
*/
|
|
97
|
+
const getSeriesNameFromLabel = originalColumnName => {
|
|
98
|
+
let series = config.series.filter(s => s.dataKey === originalColumnName)
|
|
99
|
+
if (series[0]?.name) return series[0]?.name
|
|
100
|
+
return originalColumnName
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
if (value[0] === config.xAxis.dataKey) return <li className='tooltip-heading'>{`${capitalize(config.runtime.xAxis.label ? `${config.runtime.xAxis.label}: ` : '')} ${config.xAxis.type === 'date' ? formatDate(parseDate(value[1], false)) : value[1]}`}</li>
|
|
104
|
+
return <li className='tooltip-body'>{`${getSeriesNameFromLabel(value[0])}: ${formatNumber(value[1], 'left')}`}</li>
|
|
105
|
+
}
|
|
106
|
+
|
|
64
107
|
const handleLeftTickFormatting = tick => {
|
|
65
108
|
if (config.useLogScale && tick === 0.1) {
|
|
66
|
-
//when
|
|
109
|
+
//when logarithmic scale applied change value of first tick
|
|
67
110
|
tick = 0
|
|
68
111
|
}
|
|
69
|
-
if (
|
|
70
|
-
if (
|
|
112
|
+
if (runtime.yAxis.type === 'date') return formatDate(parseDate(tick))
|
|
113
|
+
if (orientation === 'vertical') return formatNumber(tick, 'left', shouldAbbreviate)
|
|
71
114
|
return tick
|
|
72
115
|
}
|
|
73
116
|
|
|
74
117
|
const handleBottomTickFormatting = tick => {
|
|
75
118
|
if (config.useLogScale && tick === 0.1) {
|
|
76
|
-
// when
|
|
119
|
+
// when logarithmic scale applied change value FIRST of tick
|
|
77
120
|
tick = 0
|
|
78
121
|
}
|
|
79
|
-
if (
|
|
80
|
-
if (
|
|
122
|
+
if (runtime.xAxis.type === 'date') return formatDate(tick)
|
|
123
|
+
if (orientation === 'horizontal') return formatNumber(tick, 'left', shouldAbbreviate)
|
|
81
124
|
if (config.xAxis.type === 'continuous') return formatNumber(tick, 'bottom', shouldAbbreviate)
|
|
82
125
|
return tick
|
|
83
126
|
}
|
|
84
127
|
|
|
85
128
|
const countNumOfTicks = axis => {
|
|
86
|
-
const { numTicks } =
|
|
129
|
+
const { numTicks } = runtime[axis]
|
|
87
130
|
let tickCount = undefined
|
|
88
131
|
|
|
89
132
|
if (axis === 'yAxis') {
|
|
@@ -118,6 +161,187 @@ export default function LinearChart() {
|
|
|
118
161
|
return tickCount
|
|
119
162
|
}
|
|
120
163
|
|
|
164
|
+
// Tooltip helper for getting data to the closest date/category hovered.
|
|
165
|
+
const getXValueFromCoordinate = x => {
|
|
166
|
+
if (xScale.type === 'point') {
|
|
167
|
+
// Find the closest x value by calculating the minimum distance
|
|
168
|
+
let closestX = null
|
|
169
|
+
let minDistance = Number.MAX_VALUE
|
|
170
|
+
let offset = x - yAxis.size
|
|
171
|
+
|
|
172
|
+
data.forEach(d => {
|
|
173
|
+
const xPosition = xAxis.type === 'date' ? xScale(parseDate(d[xAxis.dataKey])) : xScale(d[xAxis.dataKey])
|
|
174
|
+
const distance = Math.abs(Number(xPosition - offset))
|
|
175
|
+
|
|
176
|
+
if (distance < minDistance) {
|
|
177
|
+
minDistance = distance
|
|
178
|
+
closestX = xAxis.type === 'date' ? parseDate(d[xAxis.dataKey]) : d[xAxis.dataKey]
|
|
179
|
+
}
|
|
180
|
+
})
|
|
181
|
+
return closestX
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
if (config.xAxis.type === 'categorical' || visualizationType === 'Combo') {
|
|
185
|
+
let eachBand = xScale.step()
|
|
186
|
+
let numerator = x
|
|
187
|
+
const index = Math.floor(Number(numerator) / eachBand)
|
|
188
|
+
return xScale.domain()[index - 1] // fixes off by 1 error
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
if (config.xAxis.type === 'date' && visualizationType !== 'Combo') {
|
|
192
|
+
const bisectDate = bisector(d => parseDate(d[config.xAxis.dataKey])).left
|
|
193
|
+
const x0 = xScale.invert(x)
|
|
194
|
+
const index = bisectDate(config.data, x0, 1)
|
|
195
|
+
const val = parseDate(config.data[index - 1][config.xAxis.dataKey])
|
|
196
|
+
return val
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
// visx tooltip hook
|
|
201
|
+
const { tooltipData, showTooltip, hideTooltip, tooltipOpen } = useTooltip()
|
|
202
|
+
|
|
203
|
+
/**
|
|
204
|
+
* handleTooltipClick - used on dashboard filters
|
|
205
|
+
* with visx tooltips, the handler is overwritten and we have to get the closest
|
|
206
|
+
* x axis value.
|
|
207
|
+
* TODO: move tooltip handlers to there own hook.
|
|
208
|
+
* @param {*} e
|
|
209
|
+
* @param {*} data
|
|
210
|
+
*/
|
|
211
|
+
const handleTooltipClick = (e, data) => {
|
|
212
|
+
try {
|
|
213
|
+
// Get the closest x axis value from the pointer.
|
|
214
|
+
// After getting the closest value, return the data entry with that x scale value.
|
|
215
|
+
// Pass the config.visual uid (not uuid) along with that data entry to setSharedFilters
|
|
216
|
+
const eventSvgCoords = localPoint(e)
|
|
217
|
+
const { x } = eventSvgCoords
|
|
218
|
+
if (!x) throw new Error('COVE: no x value in handleTooltipClick.')
|
|
219
|
+
let closestXScaleValue = getXValueFromCoordinate(x)
|
|
220
|
+
if (!closestXScaleValue) throw new Error('COVE: no closest x scale value in handleTooltipClick')
|
|
221
|
+
let datum = config.data.filter(item => item[config.xAxis.dataKey] === closestXScaleValue)
|
|
222
|
+
|
|
223
|
+
if (setSharedFilter) {
|
|
224
|
+
setSharedFilter(config.uid, datum[0])
|
|
225
|
+
}
|
|
226
|
+
} catch (e) {
|
|
227
|
+
// eslint-disable-next-line no-console
|
|
228
|
+
console.error(e.message)
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
// todo: combine mouseover functions
|
|
233
|
+
const handleTooltipMouseOver = (e, data) => {
|
|
234
|
+
// get the svg coordinates of the mouse
|
|
235
|
+
// and get the closest values
|
|
236
|
+
const eventSvgCoords = localPoint(e)
|
|
237
|
+
const { x, y } = eventSvgCoords
|
|
238
|
+
|
|
239
|
+
let yScaleValues
|
|
240
|
+
let closestXScaleValue = getXValueFromCoordinate(x)
|
|
241
|
+
let formattedDate = formatDate(closestXScaleValue)
|
|
242
|
+
|
|
243
|
+
// keep track of the series.tooltip values
|
|
244
|
+
// and remember to push the xaxis data key on top
|
|
245
|
+
let includedSeries = config.series.filter(series => series.tooltip === true).map(item => item.dataKey)
|
|
246
|
+
includedSeries.push(config.xAxis.dataKey)
|
|
247
|
+
|
|
248
|
+
if (xAxis.type === 'categorical') {
|
|
249
|
+
yScaleValues = data.filter(d => d[xAxis.dataKey] === closestXScaleValue)
|
|
250
|
+
yScaleValues = yScaleValues.map(object => {
|
|
251
|
+
return Object.fromEntries(Object.entries(object).filter(([key, value]) => includedSeries.includes(key)))
|
|
252
|
+
})
|
|
253
|
+
} else {
|
|
254
|
+
yScaleValues = rawData.filter(d => formatDate(parseDate(d[xAxis.dataKey])) === formattedDate)
|
|
255
|
+
yScaleValues = yScaleValues.map(object => {
|
|
256
|
+
return Object.fromEntries(Object.entries(object).filter(([key, value]) => includedSeries.includes(key)))
|
|
257
|
+
})
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
let seriesToInclude = []
|
|
261
|
+
let stageColumns = []
|
|
262
|
+
let ciItems = []
|
|
263
|
+
|
|
264
|
+
// loop through series for items to add to tooltip.
|
|
265
|
+
// there is probably a better way of doing this.
|
|
266
|
+
config.series?.forEach(s => {
|
|
267
|
+
if (s.type === 'Forecasting') {
|
|
268
|
+
stageColumns.push(s.stageColumn)
|
|
269
|
+
|
|
270
|
+
// greedy fn 😭
|
|
271
|
+
s?.confidenceIntervals.forEach(ci => {
|
|
272
|
+
if (ci.showInTooltip === true) {
|
|
273
|
+
ciItems.push(ci.low)
|
|
274
|
+
ciItems.push(ci.high)
|
|
275
|
+
}
|
|
276
|
+
})
|
|
277
|
+
}
|
|
278
|
+
})
|
|
279
|
+
|
|
280
|
+
let standardLoopItems = []
|
|
281
|
+
|
|
282
|
+
if (!config.dashboard) {
|
|
283
|
+
switch (visualizationType) {
|
|
284
|
+
case 'Combo':
|
|
285
|
+
standardLoopItems = [runtime.xAxis.dataKey, ...runtime?.barSeriesKeys, ...runtime?.lineSeriesKeys, ...stageColumns, ...ciItems]
|
|
286
|
+
break
|
|
287
|
+
case 'Forecasting':
|
|
288
|
+
standardLoopItems = [runtime.xAxis.dataKey, ...stageColumns, ...ciItems]
|
|
289
|
+
break
|
|
290
|
+
case 'Line':
|
|
291
|
+
standardLoopItems = [runtime.xAxis.dataKey, ...runtime?.seriesKeys]
|
|
292
|
+
break
|
|
293
|
+
case 'Bar':
|
|
294
|
+
standardLoopItems = [runtime.xAxis.dataKey, ...runtime?.seriesKeys]
|
|
295
|
+
break
|
|
296
|
+
default:
|
|
297
|
+
console.info('COVE: no visualization type found in handleMouseOver')
|
|
298
|
+
break
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
if (config.dashboard) {
|
|
303
|
+
standardLoopItems = [runtime.xAxis.dataKey, ...runtime?.barSeriesKeys, ...runtime?.lineSeriesKeys, ...stageColumns, ...ciItems]
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
standardLoopItems.map(seriesKey => {
|
|
307
|
+
if (!seriesKey) return false
|
|
308
|
+
if (!yScaleValues[0]) return false
|
|
309
|
+
for (const item of Object.entries(yScaleValues[0])) {
|
|
310
|
+
if (item[0] === seriesKey) {
|
|
311
|
+
seriesToInclude.push(item)
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
})
|
|
315
|
+
|
|
316
|
+
// filter out the series that aren't added to the map.
|
|
317
|
+
if (!seriesToInclude) return
|
|
318
|
+
let initialTooltipData = seriesToInclude ? seriesToInclude : {}
|
|
319
|
+
|
|
320
|
+
let tooltipData = {}
|
|
321
|
+
tooltipData.data = initialTooltipData
|
|
322
|
+
tooltipData.dataXPosition = isEditor ? x - 300 + 10 : x + 10
|
|
323
|
+
tooltipData.dataYPosition = y
|
|
324
|
+
|
|
325
|
+
let tooltipInformation = {
|
|
326
|
+
tooltipData: tooltipData,
|
|
327
|
+
tooltipTop: 0,
|
|
328
|
+
tooltipValues: yScaleValues,
|
|
329
|
+
tooltipLeft: x
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
showTooltip(tooltipInformation)
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
const handleTooltipMouseOff = () => {
|
|
336
|
+
if (config.visualizationType === 'Area Chart') {
|
|
337
|
+
setTimeout(() => {
|
|
338
|
+
hideTooltip()
|
|
339
|
+
}, 3000)
|
|
340
|
+
} else {
|
|
341
|
+
hideTooltip()
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
|
|
121
345
|
// Make sure the chart is visible if in the editor
|
|
122
346
|
/* eslint-disable react-hooks/exhaustive-deps */
|
|
123
347
|
useEffect(() => {
|
|
@@ -137,14 +361,86 @@ export default function LinearChart() {
|
|
|
137
361
|
}
|
|
138
362
|
}, [dataRef?.isIntersecting, config.animate])
|
|
139
363
|
|
|
140
|
-
const
|
|
364
|
+
const chartHasTooltipGuides = () => {
|
|
365
|
+
const { visualizationType } = config
|
|
366
|
+
if (visualizationType === 'Combo' && runtime.forecastingSeriesKeys > 0) return true
|
|
367
|
+
if (visualizationType === 'Area Chart') return true
|
|
368
|
+
if (visualizationType === 'Line') return true
|
|
369
|
+
if (visualizationType === 'Bar') return true
|
|
370
|
+
return false
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
// todo: combine mouse over functions
|
|
374
|
+
const handleAreaTooltipMouseOver = (e, data) => {
|
|
375
|
+
// get the svg coordinates of the mouse
|
|
376
|
+
// and get the closest values
|
|
377
|
+
const eventSvgCoords = localPoint(e)
|
|
378
|
+
const { x, y } = eventSvgCoords
|
|
379
|
+
let closestXScaleValue = getXValueFromCoordinate(x)
|
|
380
|
+
|
|
381
|
+
let formattedDate = formatDate(closestXScaleValue)
|
|
382
|
+
|
|
383
|
+
let yScaleValues
|
|
384
|
+
if (config.xAxis.type === 'categorical') {
|
|
385
|
+
yScaleValues = data.filter(d => d[config.xAxis.dataKey] === closestXScaleValue)
|
|
386
|
+
} else {
|
|
387
|
+
yScaleValues = data.filter(d => formatDate(parseDate(d[config.xAxis.dataKey])) === formattedDate)
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
let seriesToInclude = []
|
|
391
|
+
let yScaleMaxValues = []
|
|
392
|
+
let itemsToLoop = [runtime.xAxis.dataKey, ...runtime.seriesKeys]
|
|
393
|
+
|
|
394
|
+
itemsToLoop.map(seriesKey => {
|
|
395
|
+
if (!seriesKey) return false
|
|
396
|
+
if (!yScaleValues[0]) return false
|
|
397
|
+
for (const item of Object.entries(yScaleValues[0])) {
|
|
398
|
+
if (item[0] === seriesKey) {
|
|
399
|
+
seriesToInclude.push(item)
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
})
|
|
403
|
+
|
|
404
|
+
// filter out the series that aren't added to the map.
|
|
405
|
+
seriesToInclude.map(series => yScaleMaxValues.push(Number(yScaleValues[0][series])))
|
|
406
|
+
if (!seriesToInclude) return
|
|
407
|
+
let tooltipDataFromSeries = seriesToInclude ? seriesToInclude : {}
|
|
408
|
+
|
|
409
|
+
let tooltipData = {}
|
|
410
|
+
tooltipData.data = tooltipDataFromSeries
|
|
411
|
+
tooltipData.dataXPosition = x + 0
|
|
412
|
+
tooltipData.dataYPosition = y
|
|
413
|
+
|
|
414
|
+
let tooltipInformation = {
|
|
415
|
+
tooltipData: tooltipData,
|
|
416
|
+
tooltipTop: 0,
|
|
417
|
+
tooltipValues: yScaleValues,
|
|
418
|
+
tooltipLeft: x
|
|
419
|
+
}
|
|
420
|
+
showTooltip(tooltipInformation)
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
const tooltipStyles = tooltipData => {
|
|
424
|
+
const { dataXPosition, dataYPosition } = tooltipData
|
|
425
|
+
|
|
426
|
+
return {
|
|
427
|
+
opacity: config.tooltips.opacity ? config.tooltips.opacity / 100 : 1,
|
|
428
|
+
position: 'absolute',
|
|
429
|
+
backgroundColor: 'white',
|
|
430
|
+
borderRadius: '4px',
|
|
431
|
+
transform: `translate(${dataXPosition}px, ${Number(dataYPosition)}px)`
|
|
432
|
+
}
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
const random = Date.now()
|
|
141
436
|
|
|
142
437
|
return isNaN(width) ? (
|
|
143
|
-
|
|
438
|
+
<React.Fragment></React.Fragment>
|
|
144
439
|
) : (
|
|
145
440
|
<ErrorBoundary component='LinearChart'>
|
|
146
441
|
<svg width={width} height={height} className={`linear ${config.animate ? 'animated' : ''} ${animatedChart && config.animate ? 'animate' : ''}`} role='img' aria-label={handleChartAriaLabels(config)} tabIndex={0} ref={svgRef}>
|
|
147
|
-
{
|
|
442
|
+
<Bar width={width} height={height} fill={'transparent'}></Bar>
|
|
443
|
+
{/* Highlighted regions */}
|
|
148
444
|
{config.regions
|
|
149
445
|
? config.regions.map(region => {
|
|
150
446
|
if (!Object.keys(region).includes('from') || !Object.keys(region).includes('to')) return null
|
|
@@ -169,7 +465,7 @@ export default function LinearChart() {
|
|
|
169
465
|
if (!to) return null
|
|
170
466
|
|
|
171
467
|
return (
|
|
172
|
-
<Group className='regions' left={Number(
|
|
468
|
+
<Group className='regions' left={Number(runtime.yAxis.size)} key={region.label}>
|
|
173
469
|
<path
|
|
174
470
|
stroke='#333'
|
|
175
471
|
d={`M${from} -5
|
|
@@ -189,10 +485,10 @@ export default function LinearChart() {
|
|
|
189
485
|
: ''}
|
|
190
486
|
|
|
191
487
|
{/* Y axis */}
|
|
192
|
-
{
|
|
193
|
-
<AxisLeft scale={yScale} tickLength={config.useLogScale ? 6 : 8} left={Number(
|
|
488
|
+
{visualizationType !== 'Spark Line' && (
|
|
489
|
+
<AxisLeft scale={yScale} tickLength={config.useLogScale ? 6 : 8} left={Number(runtime.yAxis.size) - config.yAxis.axisPadding} label={runtime.yAxis.label} stroke='#333' tickFormat={tick => handleLeftTickFormatting(tick)} numTicks={countNumOfTicks('yAxis')}>
|
|
194
490
|
{props => {
|
|
195
|
-
const axisCenter =
|
|
491
|
+
const axisCenter = runtime.horizontal ? (props.axisToPoint.y - props.axisFromPoint.y) / 2 : (props.axisFromPoint.y - props.axisToPoint.y) / 2
|
|
196
492
|
const horizontalTickOffset = yMax / props.ticks.length / 2 - (yMax / props.ticks.length) * (1 - config.barThickness) + 5
|
|
197
493
|
return (
|
|
198
494
|
<Group className='left-axis'>
|
|
@@ -205,13 +501,13 @@ export default function LinearChart() {
|
|
|
205
501
|
|
|
206
502
|
return (
|
|
207
503
|
<Group key={`vx-tick-${tick.value}-${i}`} className={'vx-axis-tick'}>
|
|
208
|
-
{!
|
|
504
|
+
{!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'} />}
|
|
209
505
|
|
|
210
|
-
{
|
|
506
|
+
{runtime.yAxis.gridLines ? <Line key={`${tick.value}--hide-hideGridLines`} display={config.useLogScale && showTicks} from={{ x: tick.from.x + xMax, y: tick.from.y }} to={tick.from} stroke='rgba(0,0,0,0.3)' /> : ''}
|
|
211
507
|
|
|
212
|
-
{
|
|
508
|
+
{orientation === 'horizontal' && visualizationSubType !== 'stacked' && config.yAxis.labelPlacement === 'On Date/Category Axis' && !config.yAxis.hideLabel && (
|
|
213
509
|
<Text
|
|
214
|
-
transform={`translate(${tick.to.x - 5}, ${config.isLollipopChart ? tick.to.y - minY : tick.to.y - minY + (Number(config.barHeight * config.series.length) - barMinHeight) / 2}) rotate(-${config.runtime.horizontal ? config.runtime.yAxis.tickRotation : 0})`}
|
|
510
|
+
transform={`translate(${tick.to.x - 5}, ${config.isLollipopChart ? tick.to.y - minY : tick.to.y - minY + (Number(config.barHeight * config.series.length) - barMinHeight) / 2}) rotate(-${config.runtime.horizontal ? config.runtime.yAxis.tickRotation || 0 : 0})`}
|
|
215
511
|
verticalAnchor={'start'}
|
|
216
512
|
textAnchor={'end'}
|
|
217
513
|
>
|
|
@@ -219,29 +515,30 @@ export default function LinearChart() {
|
|
|
219
515
|
</Text>
|
|
220
516
|
)}
|
|
221
517
|
|
|
222
|
-
{
|
|
223
|
-
<Text transform={`translate(${tick.to.x - 5}, ${tick.to.y - minY + (Number(config.barHeight) - barMinHeight) / 2}) rotate(-${
|
|
518
|
+
{orientation === 'horizontal' && visualizationSubType === 'stacked' && config.yAxis.labelPlacement === 'On Date/Category Axis' && !config.yAxis.hideLabel && (
|
|
519
|
+
<Text transform={`translate(${tick.to.x - 5}, ${tick.to.y - minY + (Number(config.barHeight) - barMinHeight) / 2}) rotate(-${runtime.horizontal ? runtime.yAxis.tickRotation : 0})`} verticalAnchor={'start'} textAnchor={'end'}>
|
|
224
520
|
{tick.formattedValue}
|
|
225
521
|
</Text>
|
|
226
522
|
)}
|
|
227
523
|
|
|
228
|
-
{
|
|
229
|
-
<Text transform={`translate(${tick.to.x - 5}, ${tick.to.y - minY + Number(config.barHeight) / 2}) rotate(-${
|
|
524
|
+
{orientation === 'horizontal' && visualizationType === 'Paired Bar' && !config.yAxis.hideLabel && (
|
|
525
|
+
<Text transform={`translate(${tick.to.x - 5}, ${tick.to.y - minY + Number(config.barHeight) / 2}) rotate(-${runtime.horizontal ? runtime.yAxis.tickRotation : 0})`} textAnchor={'end'} verticalAnchor='middle'>
|
|
230
526
|
{tick.formattedValue}
|
|
231
527
|
</Text>
|
|
232
528
|
)}
|
|
233
|
-
{
|
|
234
|
-
<Text transform={`translate(${tick.to.x - 5}, ${config.isLollipopChart ? tick.to.y - minY + 2 : tick.to.y - minY + Number(config.barHeight) / 2}) rotate(-${
|
|
529
|
+
{orientation === 'horizontal' && visualizationType === 'Deviation Bar' && !config.yAxis.hideLabel && (
|
|
530
|
+
<Text transform={`translate(${tick.to.x - 5}, ${config.isLollipopChart ? tick.to.y - minY + 2 : tick.to.y - minY + Number(config.barHeight) / 2}) rotate(-${runtime.horizontal ? runtime.yAxis.tickRotation : 0})`} textAnchor={'end'} verticalAnchor='middle'>
|
|
235
531
|
{tick.formattedValue}
|
|
236
532
|
</Text>
|
|
237
533
|
)}
|
|
238
534
|
|
|
239
|
-
{
|
|
535
|
+
{orientation === 'vertical' && visualizationType !== 'Paired Bar' && !config.yAxis.hideLabel && (
|
|
240
536
|
<Text
|
|
241
537
|
display={config.useLogScale ? showTicks : 'block'}
|
|
242
538
|
dx={config.useLogScale ? -6 : 0}
|
|
243
539
|
x={config.runtime.horizontal ? tick.from.x + 2 : tick.to.x}
|
|
244
540
|
y={tick.to.y + (config.runtime.horizontal ? horizontalTickOffset : 0)}
|
|
541
|
+
angle={-Number(config.yAxis.tickRotation) || 0}
|
|
245
542
|
verticalAnchor={config.runtime.horizontal ? 'start' : 'middle'}
|
|
246
543
|
textAnchor={config.runtime.horizontal ? 'start' : 'end'}
|
|
247
544
|
fill={config.yAxis.tickLabelColor}
|
|
@@ -252,10 +549,10 @@ export default function LinearChart() {
|
|
|
252
549
|
</Group>
|
|
253
550
|
)
|
|
254
551
|
})}
|
|
255
|
-
{!config.yAxis.hideAxis && <Line from={props.axisFromPoint} to={
|
|
552
|
+
{!config.yAxis.hideAxis && <Line from={props.axisFromPoint} to={runtime.horizontal ? { x: 0, y: Number(heightHorizontal) } : props.axisToPoint} stroke='#000' />}
|
|
256
553
|
{yScale.domain()[0] < 0 && <Line from={{ x: props.axisFromPoint.x, y: yScale(0) }} to={{ x: xMax, y: yScale(0) }} stroke='#333' />}
|
|
257
|
-
{
|
|
258
|
-
<Text className='y-label' textAnchor='middle' verticalAnchor='start' transform={`translate(${-1 *
|
|
554
|
+
{visualizationType === 'Bar' && orientation === 'horizontal' && xScale.domain()[0] < 0 && <Line from={{ x: xScale(0), y: 0 }} to={{ x: xScale(0), y: yMax }} stroke='#333' strokeWidth={2} />}
|
|
555
|
+
<Text className='y-label' textAnchor='middle' verticalAnchor='start' transform={`translate(${-1 * runtime.yAxis.size}, ${axisCenter}) rotate(-90)`} fontWeight='bold' fill={config.yAxis.labelColor}>
|
|
259
556
|
{props.label}
|
|
260
557
|
</Text>
|
|
261
558
|
</Group>
|
|
@@ -266,21 +563,21 @@ export default function LinearChart() {
|
|
|
266
563
|
|
|
267
564
|
{/* Right Axis */}
|
|
268
565
|
{hasRightAxis && (
|
|
269
|
-
<AxisRight scale={yScaleRight} left={Number(width - config.yAxis.rightAxisSize)} label={config.yAxis.rightLabel} tickFormat={tick => formatNumber(tick, 'right')} numTicks={
|
|
566
|
+
<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}>
|
|
270
567
|
{props => {
|
|
271
|
-
const axisCenter =
|
|
568
|
+
const axisCenter = runtime.horizontal ? (props.axisToPoint.y - props.axisFromPoint.y) / 2 : (props.axisFromPoint.y - props.axisToPoint.y) / 2
|
|
272
569
|
const horizontalTickOffset = yMax / props.ticks.length / 2 - (yMax / props.ticks.length) * (1 - config.barThickness) + 5
|
|
273
570
|
return (
|
|
274
571
|
<Group className='right-axis'>
|
|
275
572
|
{props.ticks.map((tick, i) => {
|
|
276
573
|
return (
|
|
277
574
|
<Group key={`vx-tick-${tick.value}-${i}`} className='vx-axis-tick'>
|
|
278
|
-
{!
|
|
575
|
+
{!runtime.yAxis.rightHideTicks && <Line from={tick.from} to={tick.to} display={runtime.horizontal ? 'none' : 'block'} stroke={config.yAxis.rightAxisTickColor} />}
|
|
279
576
|
|
|
280
|
-
{
|
|
577
|
+
{runtime.yAxis.rightGridLines ? <Line from={{ x: tick.from.x + xMax, y: tick.from.y }} to={tick.from} stroke='rgba(0,0,0,0.3)' /> : ''}
|
|
281
578
|
|
|
282
579
|
{!config.yAxis.rightHideLabel && (
|
|
283
|
-
<Text x={tick.to.x} y={tick.to.y + (
|
|
580
|
+
<Text x={tick.to.x} y={tick.to.y + (runtime.horizontal ? horizontalTickOffset : 0)} verticalAnchor={runtime.horizontal ? 'start' : 'middle'} textAnchor={'start'} fill={config.yAxis.rightAxisTickLabelColor}>
|
|
284
581
|
{tick.formattedValue}
|
|
285
582
|
</Text>
|
|
286
583
|
)}
|
|
@@ -288,7 +585,7 @@ export default function LinearChart() {
|
|
|
288
585
|
)
|
|
289
586
|
})}
|
|
290
587
|
{!config.yAxis.rightHideAxis && <Line from={props.axisFromPoint} to={props.axisToPoint} stroke='#333' />}
|
|
291
|
-
<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}>
|
|
588
|
+
<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}>
|
|
292
589
|
{props.label}
|
|
293
590
|
</Text>
|
|
294
591
|
</Group>
|
|
@@ -300,7 +597,7 @@ export default function LinearChart() {
|
|
|
300
597
|
{hasTopAxis && config.topAxis.hasLine && (
|
|
301
598
|
<AxisTop
|
|
302
599
|
stroke='#333'
|
|
303
|
-
left={Number(
|
|
600
|
+
left={Number(runtime.yAxis.size)}
|
|
304
601
|
scale={xScale}
|
|
305
602
|
hideTicks
|
|
306
603
|
hideZero
|
|
@@ -311,39 +608,83 @@ export default function LinearChart() {
|
|
|
311
608
|
)}
|
|
312
609
|
|
|
313
610
|
{/* X axis */}
|
|
314
|
-
{
|
|
611
|
+
{visualizationType !== 'Paired Bar' && visualizationType !== 'Spark Line' && (
|
|
315
612
|
<AxisBottom
|
|
316
|
-
top={
|
|
317
|
-
left={Number(
|
|
318
|
-
label={
|
|
613
|
+
top={runtime.horizontal ? Number(heightHorizontal) + Number(config.xAxis.axisPadding) : yMax + Number(config.xAxis.axisPadding)}
|
|
614
|
+
left={Number(runtime.yAxis.size)}
|
|
615
|
+
label={runtime.xAxis.label}
|
|
319
616
|
tickFormat={handleBottomTickFormatting}
|
|
320
617
|
scale={xScale}
|
|
321
618
|
stroke='#333'
|
|
322
|
-
tickStroke='#333'
|
|
323
619
|
numTicks={countNumOfTicks('xAxis')}
|
|
620
|
+
tickStroke='#333'
|
|
324
621
|
>
|
|
325
622
|
{props => {
|
|
326
623
|
const axisCenter = (props.axisToPoint.x - props.axisFromPoint.x) / 2
|
|
624
|
+
const containsMultipleWords = inputString => /\s/.test(inputString)
|
|
625
|
+
const ismultiLabel = props.ticks.some(tick => containsMultipleWords(tick.value))
|
|
626
|
+
|
|
627
|
+
// Calculate sumOfTickWidth here, before map function
|
|
628
|
+
const fontSize = { small: 16, medium: 18, large: 20 }
|
|
629
|
+
const defaultTickLength = 8
|
|
630
|
+
const tickWidthMax = Math.max(...props.ticks.map(tick => getTextWidth(tick.formattedValue, `normal ${fontSize[config.fontSize]}px sans-serif`)))
|
|
631
|
+
const marginTop = 20
|
|
632
|
+
const accumulator = ismultiLabel ? 180 : 100
|
|
633
|
+
|
|
634
|
+
const textWidths = props.ticks.map(tick => getTextWidth(tick.formattedValue, `normal ${fontSize[config.fontSize]}px sans-serif`))
|
|
635
|
+
const sumOfTickWidth = textWidths.reduce((a, b) => a + b, accumulator)
|
|
636
|
+
const spaceBetweenEachTick = (xMax - sumOfTickWidth) / (props.ticks.length - 1)
|
|
637
|
+
|
|
638
|
+
// Check if ticks are overlapping
|
|
639
|
+
// Determine the position of each tick
|
|
640
|
+
let positions = [0] // The first tick is at position 0
|
|
641
|
+
for (let i = 1; i < textWidths.length; i++) {
|
|
642
|
+
// The position of each subsequent tick is the position of the previous tick
|
|
643
|
+
// plus the width of the previous tick and the space
|
|
644
|
+
positions[i] = positions[i - 1] + textWidths[i - 1] + spaceBetweenEachTick
|
|
645
|
+
}
|
|
646
|
+
|
|
647
|
+
// Check if ticks are overlapping
|
|
648
|
+
let areTicksTouching = false
|
|
649
|
+
textWidths.forEach((_, i) => {
|
|
650
|
+
if (positions[i] + textWidths[i] > positions[i + 1]) {
|
|
651
|
+
areTicksTouching = true
|
|
652
|
+
return
|
|
653
|
+
}
|
|
654
|
+
})
|
|
655
|
+
|
|
656
|
+
const dynamicMarginTop = areTicksTouching && config.isResponsiveTicks ? tickWidthMax + defaultTickLength + marginTop : 0
|
|
657
|
+
config.dynamicMarginTop = dynamicMarginTop
|
|
658
|
+
// config.xAxis.size = dynamicMarginTop
|
|
327
659
|
return (
|
|
328
660
|
<Group className='bottom-axis'>
|
|
329
|
-
{props.ticks.map((tick, i) => {
|
|
661
|
+
{props.ticks.map((tick, i, propsTicks) => {
|
|
330
662
|
// when using LogScale show major ticks values only
|
|
331
663
|
const showTick = String(tick.value).startsWith('1') || tick.value === 0.1 ? 'block' : 'none'
|
|
332
|
-
const
|
|
333
|
-
const tickLength = showTick === 'block' ? 16 : 8
|
|
664
|
+
const tickLength = showTick === 'block' ? 16 : defaultTickLength
|
|
334
665
|
const to = { x: tick.to.x, y: tickLength }
|
|
666
|
+
let textWidth = getTextWidth(tick.formattedValue, `normal ${fontSize[config.fontSize]}px sans-serif`)
|
|
667
|
+
let limitedWidth = 100 / propsTicks.length
|
|
668
|
+
//reset rotations by updating config
|
|
669
|
+
config.yAxis.tickRotation = config.isResponsiveTicks && config.orientation === 'horizontal' ? 0 : config.yAxis.tickRotation
|
|
670
|
+
config.xAxis.tickRotation = config.isResponsiveTicks && config.orientation === 'vertical' ? 0 : config.xAxis.tickRotation
|
|
671
|
+
//configure rotation
|
|
672
|
+
|
|
673
|
+
const tickRotation = config.isResponsiveTicks && areTicksTouching ? -Number(config.xAxis.maxTickRotation) || -90 : -Number(config.runtime.xAxis.tickRotation)
|
|
335
674
|
|
|
336
675
|
return (
|
|
337
676
|
<Group key={`vx-tick-${tick.value}-${i}`} className={'vx-axis-tick'}>
|
|
338
|
-
{!config.xAxis.hideTicks && <Line from={tick.from} to={
|
|
677
|
+
{!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} />}
|
|
339
678
|
{!config.xAxis.hideLabel && (
|
|
340
679
|
<Text
|
|
341
680
|
dy={config.orientation === 'horizontal' && config.useLogScale ? 8 : 0}
|
|
342
681
|
display={config.orientation === 'horizontal' && config.useLogScale ? showTick : 'block'}
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
682
|
+
x={tick.to.x}
|
|
683
|
+
y={tick.to.y}
|
|
684
|
+
angle={tickRotation}
|
|
685
|
+
verticalAnchor={tickRotation < -50 ? 'middle' : 'start'}
|
|
686
|
+
textAnchor={tickRotation ? 'end' : 'middle'}
|
|
687
|
+
width={areTicksTouching && !config.isResponsiveTicks && !config.xAxis.tickRotation ? limitedWidth : textWidth}
|
|
347
688
|
fill={config.xAxis.tickLabelColor}
|
|
348
689
|
>
|
|
349
690
|
{tick.formattedValue}
|
|
@@ -353,7 +694,7 @@ export default function LinearChart() {
|
|
|
353
694
|
)
|
|
354
695
|
})}
|
|
355
696
|
{!config.xAxis.hideAxis && <Line from={props.axisFromPoint} to={props.axisToPoint} stroke='#333' />}
|
|
356
|
-
<Text x={axisCenter} y={config.orientation === 'horizontal' ? config.xAxis.labelOffset : config.xAxis.size} textAnchor='middle' fontWeight='bold' fill={config.xAxis.labelColor}>
|
|
697
|
+
<Text x={axisCenter} y={config.orientation === 'horizontal' ? dynamicMarginTop || config.xAxis.labelOffset : dynamicMarginTop || config.xAxis.size} textAnchor='middle' fontWeight='bold' fill={config.xAxis.labelColor}>
|
|
357
698
|
{props.label}
|
|
358
699
|
</Text>
|
|
359
700
|
</Group>
|
|
@@ -362,9 +703,9 @@ export default function LinearChart() {
|
|
|
362
703
|
</AxisBottom>
|
|
363
704
|
)}
|
|
364
705
|
|
|
365
|
-
{
|
|
706
|
+
{visualizationType === 'Paired Bar' && (
|
|
366
707
|
<>
|
|
367
|
-
<AxisBottom top={yMax} left={Number(
|
|
708
|
+
<AxisBottom top={yMax} left={Number(runtime.yAxis.size)} label={runtime.xAxis.label} tickFormat={runtime.xAxis.type === 'date' ? formatDate : formatNumber} scale={g1xScale} stroke='#333' tickStroke='#333' numTicks={runtime.xAxis.numTicks || undefined}>
|
|
368
709
|
{props => {
|
|
369
710
|
return (
|
|
370
711
|
<Group className='bottom-axis'>
|
|
@@ -373,8 +714,8 @@ export default function LinearChart() {
|
|
|
373
714
|
const textAnchor = tick.index !== 0 && config.yAxis.tickRotation && config.yAxis.tickRotation > 0 ? 'end' : 'middle'
|
|
374
715
|
return (
|
|
375
716
|
<Group key={`vx-tick-${tick.value}-${i}`} className={'vx-axis-tick'}>
|
|
376
|
-
{!
|
|
377
|
-
{!
|
|
717
|
+
{!runtime.yAxis.hideTicks && <Line from={tick.from} to={tick.to} stroke='#333' />}
|
|
718
|
+
{!runtime.yAxis.hideLabel && (
|
|
378
719
|
<Text x={tick.to.x} y={tick.to.y} angle={-angle} verticalAnchor='start' textAnchor={textAnchor}>
|
|
379
720
|
{formatNumber(tick.value, 'left')}
|
|
380
721
|
</Text>
|
|
@@ -382,20 +723,20 @@ export default function LinearChart() {
|
|
|
382
723
|
</Group>
|
|
383
724
|
)
|
|
384
725
|
})}
|
|
385
|
-
{!
|
|
726
|
+
{!runtime.yAxis.hideAxis && <Line from={props.axisFromPoint} to={props.axisToPoint} stroke='#333' />}
|
|
386
727
|
</Group>
|
|
387
728
|
)
|
|
388
729
|
}}
|
|
389
730
|
</AxisBottom>
|
|
390
731
|
<AxisBottom
|
|
391
732
|
top={yMax}
|
|
392
|
-
left={Number(
|
|
393
|
-
label={
|
|
394
|
-
tickFormat={
|
|
733
|
+
left={Number(runtime.yAxis.size)}
|
|
734
|
+
label={runtime.xAxis.label}
|
|
735
|
+
tickFormat={runtime.xAxis.type === 'date' ? formatDate : runtime.xAxis.dataKey !== 'Year' ? formatNumber : tick => tick}
|
|
395
736
|
scale={g2xScale}
|
|
396
737
|
stroke='#333'
|
|
397
738
|
tickStroke='#333'
|
|
398
|
-
numTicks={
|
|
739
|
+
numTicks={runtime.xAxis.numTicks || undefined}
|
|
399
740
|
>
|
|
400
741
|
{props => {
|
|
401
742
|
return (
|
|
@@ -406,8 +747,8 @@ export default function LinearChart() {
|
|
|
406
747
|
const textAnchor = tick.index !== 0 && config.yAxis.tickRotation && config.yAxis.tickRotation > 0 ? 'end' : 'middle'
|
|
407
748
|
return (
|
|
408
749
|
<Group key={`vx-tick-${tick.value}-${i}`} className={'vx-axis-tick'}>
|
|
409
|
-
{!
|
|
410
|
-
{!
|
|
750
|
+
{!runtime.yAxis.hideTicks && <Line from={tick.from} to={tick.to} stroke='#333' />}
|
|
751
|
+
{!runtime.yAxis.hideLabel && (
|
|
411
752
|
<Text x={tick.to.x} y={tick.to.y} angle={-angle} verticalAnchor='start' textAnchor={textAnchor}>
|
|
412
753
|
{formatNumber(tick.value, 'left')}
|
|
413
754
|
</Text>
|
|
@@ -415,11 +756,11 @@ export default function LinearChart() {
|
|
|
415
756
|
</Group>
|
|
416
757
|
)
|
|
417
758
|
})}
|
|
418
|
-
{!
|
|
759
|
+
{!runtime.yAxis.hideAxis && <Line from={props.axisFromPoint} to={props.axisToPoint} stroke='#333' />}
|
|
419
760
|
</Group>
|
|
420
761
|
<Group>
|
|
421
762
|
<Text x={xMax / 2} y={config.xAxis.labelOffset} stroke='#333' textAnchor={'middle'} verticalAnchor='start'>
|
|
422
|
-
{
|
|
763
|
+
{runtime.xAxis.label}
|
|
423
764
|
</Text>
|
|
424
765
|
</Group>
|
|
425
766
|
</>
|
|
@@ -428,24 +769,68 @@ export default function LinearChart() {
|
|
|
428
769
|
</AxisBottom>
|
|
429
770
|
</>
|
|
430
771
|
)}
|
|
431
|
-
|
|
432
|
-
{
|
|
433
|
-
{
|
|
434
|
-
{
|
|
435
|
-
{
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
{
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
772
|
+
{visualizationType === 'Deviation Bar' && <DeviationBar xScale={xScale} yScale={yScale} width={xMax} height={yMax} />}
|
|
773
|
+
{visualizationType === 'Paired Bar' && <PairedBarChart originalWidth={width} width={xMax} height={yMax} />}
|
|
774
|
+
{visualizationType === 'Scatter Plot' && <CoveScatterPlot xScale={xScale} yScale={yScale} getXAxisData={getXAxisData} getYAxisData={getYAxisData} />}
|
|
775
|
+
{visualizationType === 'Box Plot' && <CoveBoxPlot xScale={xScale} yScale={yScale} />}
|
|
776
|
+
{(visualizationType === 'Area Chart' || visualizationType === 'Combo') && (
|
|
777
|
+
<AreaChart xScale={xScale} yScale={yScale} yMax={yMax} xMax={xMax} chartRef={svgRef} width={xMax} height={yMax} handleTooltipMouseOver={handleAreaTooltipMouseOver} handleTooltipMouseOff={handleTooltipMouseOff} showTooltip={showTooltip} tooltipData={tooltipData} />
|
|
778
|
+
)}
|
|
779
|
+
{(visualizationType === 'Bar' || visualizationType === 'Combo') && (
|
|
780
|
+
<BarChart
|
|
781
|
+
xScale={xScale}
|
|
782
|
+
yScale={yScale}
|
|
783
|
+
seriesScale={seriesScale}
|
|
784
|
+
xMax={xMax}
|
|
785
|
+
yMax={yMax}
|
|
786
|
+
getXAxisData={getXAxisData}
|
|
787
|
+
getYAxisData={getYAxisData}
|
|
788
|
+
animatedChart={animatedChart}
|
|
789
|
+
visible={animatedChart}
|
|
790
|
+
handleTooltipMouseOver={handleTooltipMouseOver}
|
|
791
|
+
handleTooltipMouseOff={handleTooltipMouseOff}
|
|
792
|
+
handleTooltipClick={handleTooltipClick}
|
|
793
|
+
tooltipData={tooltipData}
|
|
794
|
+
showTooltip={showTooltip}
|
|
795
|
+
chartRef={svgRef}
|
|
796
|
+
/>
|
|
797
|
+
)}
|
|
798
|
+
{(visualizationType === 'Line' || visualizationType === 'Combo') && (
|
|
799
|
+
<LineChart
|
|
800
|
+
xScale={xScale}
|
|
801
|
+
yScale={yScale}
|
|
802
|
+
getXAxisData={getXAxisData}
|
|
803
|
+
getYAxisData={getYAxisData}
|
|
804
|
+
xMax={xMax}
|
|
805
|
+
yMax={yMax}
|
|
806
|
+
seriesStyle={config.series}
|
|
807
|
+
handleTooltipMouseOver={handleTooltipMouseOver}
|
|
808
|
+
handleTooltipMouseOff={handleTooltipMouseOff}
|
|
809
|
+
handleTooltipClick={handleTooltipClick}
|
|
810
|
+
tooltipData={tooltipData}
|
|
811
|
+
showTooltip={showTooltip}
|
|
812
|
+
chartRef={svgRef}
|
|
813
|
+
/>
|
|
814
|
+
)}
|
|
815
|
+
{(visualizationType === 'Forecasting' || visualizationType === 'Combo') && (
|
|
816
|
+
<Forecasting
|
|
817
|
+
showTooltip={showTooltip}
|
|
818
|
+
tooltipData={tooltipData}
|
|
819
|
+
xScale={xScale}
|
|
820
|
+
yScale={yScale}
|
|
821
|
+
width={xMax}
|
|
822
|
+
height={yMax}
|
|
823
|
+
xScaleNoPadding={xScaleNoPadding}
|
|
824
|
+
chartRef={svgRef}
|
|
825
|
+
getXValueFromCoordinate={getXValueFromCoordinate}
|
|
826
|
+
handleTooltipMouseOver={handleTooltipMouseOver}
|
|
827
|
+
handleTooltipMouseOff={handleTooltipMouseOff}
|
|
828
|
+
/>
|
|
444
829
|
)}
|
|
445
830
|
|
|
446
831
|
{/* Line chart */}
|
|
447
832
|
{/* TODO: Make this just line or combo? */}
|
|
448
|
-
{
|
|
833
|
+
{visualizationType !== 'Bar' && visualizationType !== 'Paired Bar' && visualizationType !== 'Box Plot' && visualizationType !== 'Area Chart' && visualizationType !== 'Scatter Plot' && visualizationType !== 'Deviation Bar' && visualizationType !== 'Forecasting' && (
|
|
449
834
|
<>
|
|
450
835
|
<LineChart xScale={xScale} yScale={yScale} getXAxisData={getXAxisData} getYAxisData={getYAxisData} xMax={xMax} yMax={yMax} seriesStyle={config.series} />
|
|
451
836
|
</>
|
|
@@ -453,27 +838,30 @@ export default function LinearChart() {
|
|
|
453
838
|
|
|
454
839
|
{/* y anchors */}
|
|
455
840
|
{config.yAxis.anchors &&
|
|
456
|
-
config.yAxis.anchors.map(anchor => {
|
|
841
|
+
config.yAxis.anchors.map((anchor, index) => {
|
|
457
842
|
let anchorPosition = yScale(anchor.value)
|
|
458
|
-
|
|
459
|
-
const
|
|
843
|
+
if (!anchor.value) return
|
|
844
|
+
const padding = orientation === 'horizontal' ? Number(config.xAxis.size) : Number(config.yAxis.size)
|
|
845
|
+
const middleOffset = orientation === 'horizontal' && visualizationType === 'Bar' ? config.barHeight / 4 : 0
|
|
846
|
+
|
|
847
|
+
if (!anchorPosition) return
|
|
460
848
|
|
|
461
849
|
return (
|
|
462
850
|
// prettier-ignore
|
|
463
851
|
<Line
|
|
464
|
-
key={anchor.value}
|
|
852
|
+
key={`yAxis-${anchor.value}--${index}`}
|
|
465
853
|
strokeDasharray={handleLineType(anchor.lineStyle)}
|
|
466
854
|
stroke={anchor.color ? anchor.color : 'rgba(0,0,0,1)'}
|
|
467
855
|
className='anchor-y'
|
|
468
856
|
from={{ x: 0 + padding, y: anchorPosition - middleOffset}}
|
|
469
|
-
to={{ x: width, y: anchorPosition - middleOffset }}
|
|
857
|
+
to={{ x: width - config.yAxis.rightAxisSize, y: anchorPosition - middleOffset }}
|
|
470
858
|
/>
|
|
471
859
|
)
|
|
472
860
|
})}
|
|
473
861
|
|
|
474
862
|
{/* x anchors */}
|
|
475
863
|
{config.xAxis.anchors &&
|
|
476
|
-
config.xAxis.anchors.map(anchor => {
|
|
864
|
+
config.xAxis.anchors.map((anchor, index) => {
|
|
477
865
|
let newX = xAxis
|
|
478
866
|
if (orientation === 'horizontal') {
|
|
479
867
|
newX = yAxis
|
|
@@ -483,10 +871,12 @@ export default function LinearChart() {
|
|
|
483
871
|
|
|
484
872
|
const padding = orientation === 'horizontal' ? Number(config.xAxis.size) : Number(config.yAxis.size)
|
|
485
873
|
|
|
874
|
+
if (!anchorPosition) return
|
|
875
|
+
|
|
486
876
|
return (
|
|
487
877
|
// prettier-ignore
|
|
488
878
|
<Line
|
|
489
|
-
key={anchor.value}
|
|
879
|
+
key={`xAxis-${anchor.value}--${index}`}
|
|
490
880
|
strokeDasharray={handleLineType(anchor.lineStyle)}
|
|
491
881
|
stroke={anchor.color ? anchor.color : 'rgba(0,0,0,1)'}
|
|
492
882
|
fill={anchor.color ? anchor.color : 'rgba(0,0,0,1)'}
|
|
@@ -496,8 +886,27 @@ export default function LinearChart() {
|
|
|
496
886
|
/>
|
|
497
887
|
)
|
|
498
888
|
})}
|
|
889
|
+
|
|
890
|
+
{chartHasTooltipGuides && showTooltip && tooltipData && config.visual.verticalHoverLine && (
|
|
891
|
+
<Group key='tooltipLine-vertical' className='vertical-tooltip-line'>
|
|
892
|
+
<Line from={{ x: tooltipData.dataXPosition, y: 0 }} to={{ x: tooltipData.dataXPosition, y: yMax }} stroke={'black'} strokeWidth={1} pointerEvents='none' strokeDasharray='5,5' className='vertical-tooltip-line' />
|
|
893
|
+
</Group>
|
|
894
|
+
)}
|
|
895
|
+
|
|
896
|
+
{chartHasTooltipGuides && showTooltip && tooltipData && config.visual.horizontalHoverLine && (
|
|
897
|
+
<Group key='tooltipLine-horizontal' className='horizontal-tooltip-line' left={config.yAxis.size ? config.yAxis.size : 0}>
|
|
898
|
+
<Line from={{ x: 0, y: tooltipData.dataYPosition }} to={{ x: xMax, y: tooltipData.dataYPosition }} stroke={'black'} strokeWidth={1} pointerEvents='none' strokeDasharray='5,5' className='horizontal-tooltip-line' />
|
|
899
|
+
</Group>
|
|
900
|
+
)}
|
|
499
901
|
</svg>
|
|
500
|
-
|
|
902
|
+
{tooltipData && Object.entries(tooltipData.data).length > 0 && tooltipOpen && showTooltip && tooltipData.dataYPosition && tooltipData.dataXPosition && (
|
|
903
|
+
<TooltipWithBounds key={Math.random()} className={'tooltip cdc-open-viz-module'} style={tooltipStyles(tooltipData)} width={width}>
|
|
904
|
+
<ul>{typeof tooltipData === 'object' && Object.entries(tooltipData.data).map((item, index) => <TooltipListItem item={item} key={index} />)}</ul>
|
|
905
|
+
</TooltipWithBounds>
|
|
906
|
+
)}
|
|
907
|
+
{(config.orientation === 'horizontal' || config.visualizationType === 'Scatter Plot' || config.visualizationType === 'Box Plot') && (
|
|
908
|
+
<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' }} />
|
|
909
|
+
)}
|
|
501
910
|
<div className='animation-trigger' ref={triggerRef} />
|
|
502
911
|
</ErrorBoundary>
|
|
503
912
|
)
|