@cdc/chart 4.23.6 → 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 +25700 -26736
- package/examples/feature/__data__/planet-example-data.json +1 -1
- package/examples/feature/combo/right-issues.json +1 -1
- package/examples/feature/forecasting/combo-forecasting.json +72 -46
- package/examples/feature/forecasting/effective_reproduction.json +57 -8
- package/examples/feature/forecasting/forecasting.json +12 -3
- package/examples/feature/line/line-chart.json +11 -11
- package/examples/gallery/bar-chart-vertical/vertical-bar-chart-categorical.json +167 -20
- package/index.html +6 -6
- package/package.json +2 -2
- package/src/CdcChart.jsx +56 -15
- package/src/components/AreaChart.jsx +22 -133
- package/src/components/BarChart.jsx +25 -15
- package/src/components/DataTable.jsx +5 -2
- package/src/components/EditorPanel.jsx +97 -77
- package/src/components/Forecasting.jsx +23 -86
- package/src/components/Legend.jsx +10 -8
- package/src/components/LineChart.jsx +31 -6
- package/src/components/LinearChart.jsx +408 -126
- package/src/components/Series.jsx +40 -4
- package/src/data/initial-state.js +7 -3
- package/src/hooks/useMinMax.js +3 -2
- package/src/hooks/useRightAxis.js +2 -1
- package/src/scss/main.scss +4 -17
- package/LICENSE +0 -201
|
@@ -1,54 +1,65 @@
|
|
|
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
|
|
|
4
|
-
|
|
5
|
-
import { Line } from '@visx/shape'
|
|
6
|
-
import { Text } from '@visx/text'
|
|
3
|
+
// Libraries
|
|
7
4
|
import { AxisLeft, AxisBottom, AxisRight, AxisTop } from '@visx/axis'
|
|
5
|
+
import { bisector } from 'd3-array'
|
|
6
|
+
import { Group } from '@visx/group'
|
|
7
|
+
import { Line, Bar } from '@visx/shape'
|
|
8
8
|
import { localPoint } from '@visx/event'
|
|
9
|
-
import {
|
|
9
|
+
import { Text } from '@visx/text'
|
|
10
|
+
import { Tooltip as ReactTooltip } from 'react-tooltip'
|
|
11
|
+
import { useTooltip, TooltipWithBounds, useTooltipInPortal } from '@visx/tooltip'
|
|
10
12
|
|
|
13
|
+
// CDC Components
|
|
14
|
+
import AreaChart from './AreaChart'
|
|
11
15
|
import BarChart from './BarChart'
|
|
12
16
|
import ConfigContext from '../ConfigContext'
|
|
13
|
-
import CoveAreaChart from './AreaChart'
|
|
14
17
|
import CoveBoxPlot from './BoxPlot'
|
|
15
18
|
import CoveScatterPlot from './ScatterPlot'
|
|
16
19
|
import DeviationBar from './DeviationBar'
|
|
20
|
+
import ErrorBoundary from '@cdc/core/components/ErrorBoundary'
|
|
21
|
+
import Forecasting from './Forecasting'
|
|
17
22
|
import LineChart from './LineChart'
|
|
18
23
|
import PairedBarChart from './PairedBarChart'
|
|
19
24
|
import useIntersectionObserver from './useIntersectionObserver'
|
|
20
25
|
|
|
21
|
-
|
|
22
|
-
import '../scss/LinearChart.scss'
|
|
23
|
-
import useReduceData from '../hooks/useReduceData'
|
|
24
|
-
import useScales from '../hooks/useScales'
|
|
26
|
+
// Hooks
|
|
25
27
|
import useMinMax from '../hooks/useMinMax'
|
|
28
|
+
import useReduceData from '../hooks/useReduceData'
|
|
26
29
|
import useRightAxis from '../hooks/useRightAxis'
|
|
30
|
+
import useScales from '../hooks/useScales'
|
|
27
31
|
import useTopAxis from '../hooks/useTopAxis'
|
|
28
|
-
|
|
32
|
+
|
|
33
|
+
// styles
|
|
34
|
+
import { defaultStyles } from '@visx/tooltip'
|
|
35
|
+
import '../scss/LinearChart.scss'
|
|
29
36
|
|
|
30
37
|
export default function LinearChart() {
|
|
31
|
-
const { transformedData: data, dimensions, config, parseDate, formatDate, currentViewport, formatNumber, handleChartAriaLabels, updateConfig, handleLineType, rawData } = 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
|
|
32
42
|
|
|
33
43
|
// getters & functions
|
|
34
|
-
const getXAxisData = d => (
|
|
44
|
+
const getXAxisData = d => (runtime.xAxis.type === 'date' ? parseDate(d[runtime.originalXAxis.dataKey]).getTime() : d[runtime.originalXAxis.dataKey])
|
|
35
45
|
const getYAxisData = (d, seriesKey) => d[seriesKey]
|
|
36
46
|
const xAxisDataMapped = data.map(d => getXAxisData(d))
|
|
37
47
|
|
|
38
48
|
// configure width
|
|
39
49
|
let [width] = dimensions
|
|
50
|
+
let originalWidth = width
|
|
40
51
|
if (config && config.legend && !config.legend.hide && config.legend.position !== 'bottom' && ['lg', 'md'].includes(currentViewport)) {
|
|
41
52
|
width = width * 0.73
|
|
42
53
|
}
|
|
43
54
|
// configure height , yMax, xMAx
|
|
44
55
|
const { horizontal: heightHorizontal } = config.heights
|
|
45
|
-
const isHorizontal =
|
|
56
|
+
const isHorizontal = orientation === 'horizontal'
|
|
46
57
|
const shouldAbbreviate = true
|
|
47
|
-
const height = config.aspectRatio ? width * config.aspectRatio : config.heights[
|
|
48
|
-
const xMax = width -
|
|
49
|
-
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)
|
|
50
61
|
|
|
51
|
-
// hooks
|
|
62
|
+
// hooks
|
|
52
63
|
const { minValue, maxValue, existPositiveValue, isAllLine } = useReduceData(config, data)
|
|
53
64
|
const { yScaleRight, hasRightAxis } = useRightAxis({ config, yMax, data, updateConfig })
|
|
54
65
|
const { hasTopAxis } = useTopAxis(config)
|
|
@@ -64,29 +75,58 @@ export default function LinearChart() {
|
|
|
64
75
|
freezeOnceVisible: false
|
|
65
76
|
})
|
|
66
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
|
+
|
|
67
107
|
const handleLeftTickFormatting = tick => {
|
|
68
108
|
if (config.useLogScale && tick === 0.1) {
|
|
69
109
|
//when logarithmic scale applied change value of first tick
|
|
70
110
|
tick = 0
|
|
71
111
|
}
|
|
72
|
-
if (
|
|
73
|
-
if (
|
|
112
|
+
if (runtime.yAxis.type === 'date') return formatDate(parseDate(tick))
|
|
113
|
+
if (orientation === 'vertical') return formatNumber(tick, 'left', shouldAbbreviate)
|
|
74
114
|
return tick
|
|
75
115
|
}
|
|
76
116
|
|
|
77
117
|
const handleBottomTickFormatting = tick => {
|
|
78
118
|
if (config.useLogScale && tick === 0.1) {
|
|
79
|
-
// when
|
|
119
|
+
// when logarithmic scale applied change value FIRST of tick
|
|
80
120
|
tick = 0
|
|
81
121
|
}
|
|
82
|
-
if (
|
|
83
|
-
if (
|
|
122
|
+
if (runtime.xAxis.type === 'date') return formatDate(tick)
|
|
123
|
+
if (orientation === 'horizontal') return formatNumber(tick, 'left', shouldAbbreviate)
|
|
84
124
|
if (config.xAxis.type === 'continuous') return formatNumber(tick, 'bottom', shouldAbbreviate)
|
|
85
125
|
return tick
|
|
86
126
|
}
|
|
87
127
|
|
|
88
128
|
const countNumOfTicks = axis => {
|
|
89
|
-
const { numTicks } =
|
|
129
|
+
const { numTicks } = runtime[axis]
|
|
90
130
|
let tickCount = undefined
|
|
91
131
|
|
|
92
132
|
if (axis === 'yAxis') {
|
|
@@ -140,27 +180,81 @@ export default function LinearChart() {
|
|
|
140
180
|
})
|
|
141
181
|
return closestX
|
|
142
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
|
+
}
|
|
143
198
|
}
|
|
144
199
|
|
|
145
|
-
//
|
|
146
|
-
const { tooltipData, showTooltip, hideTooltip } = useTooltip()
|
|
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
|
+
}
|
|
147
231
|
|
|
232
|
+
// todo: combine mouseover functions
|
|
148
233
|
const handleTooltipMouseOver = (e, data) => {
|
|
149
234
|
// get the svg coordinates of the mouse
|
|
150
235
|
// and get the closest values
|
|
151
236
|
const eventSvgCoords = localPoint(e)
|
|
152
237
|
const { x, y } = eventSvgCoords
|
|
153
238
|
|
|
154
|
-
|
|
155
|
-
|
|
239
|
+
let yScaleValues
|
|
156
240
|
let closestXScaleValue = getXValueFromCoordinate(x)
|
|
157
241
|
let formattedDate = formatDate(closestXScaleValue)
|
|
158
242
|
|
|
159
|
-
|
|
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
|
+
|
|
160
248
|
if (xAxis.type === 'categorical') {
|
|
161
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
|
+
})
|
|
162
253
|
} else {
|
|
163
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
|
+
})
|
|
164
258
|
}
|
|
165
259
|
|
|
166
260
|
let seriesToInclude = []
|
|
@@ -169,12 +263,12 @@ export default function LinearChart() {
|
|
|
169
263
|
|
|
170
264
|
// loop through series for items to add to tooltip.
|
|
171
265
|
// there is probably a better way of doing this.
|
|
172
|
-
config.series?.
|
|
266
|
+
config.series?.forEach(s => {
|
|
173
267
|
if (s.type === 'Forecasting') {
|
|
174
268
|
stageColumns.push(s.stageColumn)
|
|
175
269
|
|
|
176
270
|
// greedy fn 😭
|
|
177
|
-
s?.confidenceIntervals.
|
|
271
|
+
s?.confidenceIntervals.forEach(ci => {
|
|
178
272
|
if (ci.showInTooltip === true) {
|
|
179
273
|
ciItems.push(ci.low)
|
|
180
274
|
ciItems.push(ci.high)
|
|
@@ -185,10 +279,28 @@ export default function LinearChart() {
|
|
|
185
279
|
|
|
186
280
|
let standardLoopItems = []
|
|
187
281
|
|
|
188
|
-
if (config.
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
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]
|
|
192
304
|
}
|
|
193
305
|
|
|
194
306
|
standardLoopItems.map(seriesKey => {
|
|
@@ -203,11 +315,11 @@ export default function LinearChart() {
|
|
|
203
315
|
|
|
204
316
|
// filter out the series that aren't added to the map.
|
|
205
317
|
if (!seriesToInclude) return
|
|
206
|
-
let initialTooltipData =
|
|
318
|
+
let initialTooltipData = seriesToInclude ? seriesToInclude : {}
|
|
207
319
|
|
|
208
320
|
let tooltipData = {}
|
|
209
321
|
tooltipData.data = initialTooltipData
|
|
210
|
-
tooltipData.dataXPosition = x + 10
|
|
322
|
+
tooltipData.dataXPosition = isEditor ? x - 300 + 10 : x + 10
|
|
211
323
|
tooltipData.dataYPosition = y
|
|
212
324
|
|
|
213
325
|
let tooltipInformation = {
|
|
@@ -221,7 +333,13 @@ export default function LinearChart() {
|
|
|
221
333
|
}
|
|
222
334
|
|
|
223
335
|
const handleTooltipMouseOff = () => {
|
|
224
|
-
|
|
336
|
+
if (config.visualizationType === 'Area Chart') {
|
|
337
|
+
setTimeout(() => {
|
|
338
|
+
hideTooltip()
|
|
339
|
+
}, 3000)
|
|
340
|
+
} else {
|
|
341
|
+
hideTooltip()
|
|
342
|
+
}
|
|
225
343
|
}
|
|
226
344
|
|
|
227
345
|
// Make sure the chart is visible if in the editor
|
|
@@ -243,13 +361,85 @@ export default function LinearChart() {
|
|
|
243
361
|
}
|
|
244
362
|
}, [dataRef?.isIntersecting, config.animate])
|
|
245
363
|
|
|
246
|
-
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()
|
|
247
436
|
|
|
248
437
|
return isNaN(width) ? (
|
|
249
|
-
|
|
438
|
+
<React.Fragment></React.Fragment>
|
|
250
439
|
) : (
|
|
251
440
|
<ErrorBoundary component='LinearChart'>
|
|
252
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}>
|
|
442
|
+
<Bar width={width} height={height} fill={'transparent'}></Bar>
|
|
253
443
|
{/* Highlighted regions */}
|
|
254
444
|
{config.regions
|
|
255
445
|
? config.regions.map(region => {
|
|
@@ -275,7 +465,7 @@ export default function LinearChart() {
|
|
|
275
465
|
if (!to) return null
|
|
276
466
|
|
|
277
467
|
return (
|
|
278
|
-
<Group className='regions' left={Number(
|
|
468
|
+
<Group className='regions' left={Number(runtime.yAxis.size)} key={region.label}>
|
|
279
469
|
<path
|
|
280
470
|
stroke='#333'
|
|
281
471
|
d={`M${from} -5
|
|
@@ -295,10 +485,10 @@ export default function LinearChart() {
|
|
|
295
485
|
: ''}
|
|
296
486
|
|
|
297
487
|
{/* Y axis */}
|
|
298
|
-
{
|
|
299
|
-
<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')}>
|
|
300
490
|
{props => {
|
|
301
|
-
const axisCenter =
|
|
491
|
+
const axisCenter = runtime.horizontal ? (props.axisToPoint.y - props.axisFromPoint.y) / 2 : (props.axisFromPoint.y - props.axisToPoint.y) / 2
|
|
302
492
|
const horizontalTickOffset = yMax / props.ticks.length / 2 - (yMax / props.ticks.length) * (1 - config.barThickness) + 5
|
|
303
493
|
return (
|
|
304
494
|
<Group className='left-axis'>
|
|
@@ -311,13 +501,13 @@ export default function LinearChart() {
|
|
|
311
501
|
|
|
312
502
|
return (
|
|
313
503
|
<Group key={`vx-tick-${tick.value}-${i}`} className={'vx-axis-tick'}>
|
|
314
|
-
{!
|
|
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'} />}
|
|
315
505
|
|
|
316
|
-
{
|
|
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)' /> : ''}
|
|
317
507
|
|
|
318
|
-
{
|
|
508
|
+
{orientation === 'horizontal' && visualizationSubType !== 'stacked' && config.yAxis.labelPlacement === 'On Date/Category Axis' && !config.yAxis.hideLabel && (
|
|
319
509
|
<Text
|
|
320
|
-
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})`}
|
|
321
511
|
verticalAnchor={'start'}
|
|
322
512
|
textAnchor={'end'}
|
|
323
513
|
>
|
|
@@ -325,29 +515,30 @@ export default function LinearChart() {
|
|
|
325
515
|
</Text>
|
|
326
516
|
)}
|
|
327
517
|
|
|
328
|
-
{
|
|
329
|
-
<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'}>
|
|
330
520
|
{tick.formattedValue}
|
|
331
521
|
</Text>
|
|
332
522
|
)}
|
|
333
523
|
|
|
334
|
-
{
|
|
335
|
-
<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'>
|
|
336
526
|
{tick.formattedValue}
|
|
337
527
|
</Text>
|
|
338
528
|
)}
|
|
339
|
-
{
|
|
340
|
-
<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'>
|
|
341
531
|
{tick.formattedValue}
|
|
342
532
|
</Text>
|
|
343
533
|
)}
|
|
344
534
|
|
|
345
|
-
{
|
|
535
|
+
{orientation === 'vertical' && visualizationType !== 'Paired Bar' && !config.yAxis.hideLabel && (
|
|
346
536
|
<Text
|
|
347
537
|
display={config.useLogScale ? showTicks : 'block'}
|
|
348
538
|
dx={config.useLogScale ? -6 : 0}
|
|
349
539
|
x={config.runtime.horizontal ? tick.from.x + 2 : tick.to.x}
|
|
350
540
|
y={tick.to.y + (config.runtime.horizontal ? horizontalTickOffset : 0)}
|
|
541
|
+
angle={-Number(config.yAxis.tickRotation) || 0}
|
|
351
542
|
verticalAnchor={config.runtime.horizontal ? 'start' : 'middle'}
|
|
352
543
|
textAnchor={config.runtime.horizontal ? 'start' : 'end'}
|
|
353
544
|
fill={config.yAxis.tickLabelColor}
|
|
@@ -358,10 +549,10 @@ export default function LinearChart() {
|
|
|
358
549
|
</Group>
|
|
359
550
|
)
|
|
360
551
|
})}
|
|
361
|
-
{!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' />}
|
|
362
553
|
{yScale.domain()[0] < 0 && <Line from={{ x: props.axisFromPoint.x, y: yScale(0) }} to={{ x: xMax, y: yScale(0) }} stroke='#333' />}
|
|
363
|
-
{
|
|
364
|
-
<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}>
|
|
365
556
|
{props.label}
|
|
366
557
|
</Text>
|
|
367
558
|
</Group>
|
|
@@ -372,21 +563,21 @@ export default function LinearChart() {
|
|
|
372
563
|
|
|
373
564
|
{/* Right Axis */}
|
|
374
565
|
{hasRightAxis && (
|
|
375
|
-
<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}>
|
|
376
567
|
{props => {
|
|
377
|
-
const axisCenter =
|
|
568
|
+
const axisCenter = runtime.horizontal ? (props.axisToPoint.y - props.axisFromPoint.y) / 2 : (props.axisFromPoint.y - props.axisToPoint.y) / 2
|
|
378
569
|
const horizontalTickOffset = yMax / props.ticks.length / 2 - (yMax / props.ticks.length) * (1 - config.barThickness) + 5
|
|
379
570
|
return (
|
|
380
571
|
<Group className='right-axis'>
|
|
381
572
|
{props.ticks.map((tick, i) => {
|
|
382
573
|
return (
|
|
383
574
|
<Group key={`vx-tick-${tick.value}-${i}`} className='vx-axis-tick'>
|
|
384
|
-
{!
|
|
575
|
+
{!runtime.yAxis.rightHideTicks && <Line from={tick.from} to={tick.to} display={runtime.horizontal ? 'none' : 'block'} stroke={config.yAxis.rightAxisTickColor} />}
|
|
385
576
|
|
|
386
|
-
{
|
|
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)' /> : ''}
|
|
387
578
|
|
|
388
579
|
{!config.yAxis.rightHideLabel && (
|
|
389
|
-
<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}>
|
|
390
581
|
{tick.formattedValue}
|
|
391
582
|
</Text>
|
|
392
583
|
)}
|
|
@@ -406,7 +597,7 @@ export default function LinearChart() {
|
|
|
406
597
|
{hasTopAxis && config.topAxis.hasLine && (
|
|
407
598
|
<AxisTop
|
|
408
599
|
stroke='#333'
|
|
409
|
-
left={Number(
|
|
600
|
+
left={Number(runtime.yAxis.size)}
|
|
410
601
|
scale={xScale}
|
|
411
602
|
hideTicks
|
|
412
603
|
hideZero
|
|
@@ -417,39 +608,83 @@ export default function LinearChart() {
|
|
|
417
608
|
)}
|
|
418
609
|
|
|
419
610
|
{/* X axis */}
|
|
420
|
-
{
|
|
611
|
+
{visualizationType !== 'Paired Bar' && visualizationType !== 'Spark Line' && (
|
|
421
612
|
<AxisBottom
|
|
422
|
-
top={
|
|
423
|
-
left={Number(
|
|
424
|
-
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}
|
|
425
616
|
tickFormat={handleBottomTickFormatting}
|
|
426
617
|
scale={xScale}
|
|
427
618
|
stroke='#333'
|
|
428
|
-
tickStroke='#333'
|
|
429
619
|
numTicks={countNumOfTicks('xAxis')}
|
|
620
|
+
tickStroke='#333'
|
|
430
621
|
>
|
|
431
622
|
{props => {
|
|
432
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
|
|
433
659
|
return (
|
|
434
660
|
<Group className='bottom-axis'>
|
|
435
|
-
{props.ticks.map((tick, i) => {
|
|
661
|
+
{props.ticks.map((tick, i, propsTicks) => {
|
|
436
662
|
// when using LogScale show major ticks values only
|
|
437
663
|
const showTick = String(tick.value).startsWith('1') || tick.value === 0.1 ? 'block' : 'none'
|
|
438
|
-
const
|
|
439
|
-
const tickLength = showTick === 'block' ? 16 : 8
|
|
664
|
+
const tickLength = showTick === 'block' ? 16 : defaultTickLength
|
|
440
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)
|
|
441
674
|
|
|
442
675
|
return (
|
|
443
676
|
<Group key={`vx-tick-${tick.value}-${i}`} className={'vx-axis-tick'}>
|
|
444
|
-
{!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} />}
|
|
445
678
|
{!config.xAxis.hideLabel && (
|
|
446
679
|
<Text
|
|
447
680
|
dy={config.orientation === 'horizontal' && config.useLogScale ? 8 : 0}
|
|
448
681
|
display={config.orientation === 'horizontal' && config.useLogScale ? showTick : 'block'}
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
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}
|
|
453
688
|
fill={config.xAxis.tickLabelColor}
|
|
454
689
|
>
|
|
455
690
|
{tick.formattedValue}
|
|
@@ -459,7 +694,7 @@ export default function LinearChart() {
|
|
|
459
694
|
)
|
|
460
695
|
})}
|
|
461
696
|
{!config.xAxis.hideAxis && <Line from={props.axisFromPoint} to={props.axisToPoint} stroke='#333' />}
|
|
462
|
-
<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}>
|
|
463
698
|
{props.label}
|
|
464
699
|
</Text>
|
|
465
700
|
</Group>
|
|
@@ -468,9 +703,9 @@ export default function LinearChart() {
|
|
|
468
703
|
</AxisBottom>
|
|
469
704
|
)}
|
|
470
705
|
|
|
471
|
-
{
|
|
706
|
+
{visualizationType === 'Paired Bar' && (
|
|
472
707
|
<>
|
|
473
|
-
<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}>
|
|
474
709
|
{props => {
|
|
475
710
|
return (
|
|
476
711
|
<Group className='bottom-axis'>
|
|
@@ -479,8 +714,8 @@ export default function LinearChart() {
|
|
|
479
714
|
const textAnchor = tick.index !== 0 && config.yAxis.tickRotation && config.yAxis.tickRotation > 0 ? 'end' : 'middle'
|
|
480
715
|
return (
|
|
481
716
|
<Group key={`vx-tick-${tick.value}-${i}`} className={'vx-axis-tick'}>
|
|
482
|
-
{!
|
|
483
|
-
{!
|
|
717
|
+
{!runtime.yAxis.hideTicks && <Line from={tick.from} to={tick.to} stroke='#333' />}
|
|
718
|
+
{!runtime.yAxis.hideLabel && (
|
|
484
719
|
<Text x={tick.to.x} y={tick.to.y} angle={-angle} verticalAnchor='start' textAnchor={textAnchor}>
|
|
485
720
|
{formatNumber(tick.value, 'left')}
|
|
486
721
|
</Text>
|
|
@@ -488,20 +723,20 @@ export default function LinearChart() {
|
|
|
488
723
|
</Group>
|
|
489
724
|
)
|
|
490
725
|
})}
|
|
491
|
-
{!
|
|
726
|
+
{!runtime.yAxis.hideAxis && <Line from={props.axisFromPoint} to={props.axisToPoint} stroke='#333' />}
|
|
492
727
|
</Group>
|
|
493
728
|
)
|
|
494
729
|
}}
|
|
495
730
|
</AxisBottom>
|
|
496
731
|
<AxisBottom
|
|
497
732
|
top={yMax}
|
|
498
|
-
left={Number(
|
|
499
|
-
label={
|
|
500
|
-
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}
|
|
501
736
|
scale={g2xScale}
|
|
502
737
|
stroke='#333'
|
|
503
738
|
tickStroke='#333'
|
|
504
|
-
numTicks={
|
|
739
|
+
numTicks={runtime.xAxis.numTicks || undefined}
|
|
505
740
|
>
|
|
506
741
|
{props => {
|
|
507
742
|
return (
|
|
@@ -512,8 +747,8 @@ export default function LinearChart() {
|
|
|
512
747
|
const textAnchor = tick.index !== 0 && config.yAxis.tickRotation && config.yAxis.tickRotation > 0 ? 'end' : 'middle'
|
|
513
748
|
return (
|
|
514
749
|
<Group key={`vx-tick-${tick.value}-${i}`} className={'vx-axis-tick'}>
|
|
515
|
-
{!
|
|
516
|
-
{!
|
|
750
|
+
{!runtime.yAxis.hideTicks && <Line from={tick.from} to={tick.to} stroke='#333' />}
|
|
751
|
+
{!runtime.yAxis.hideLabel && (
|
|
517
752
|
<Text x={tick.to.x} y={tick.to.y} angle={-angle} verticalAnchor='start' textAnchor={textAnchor}>
|
|
518
753
|
{formatNumber(tick.value, 'left')}
|
|
519
754
|
</Text>
|
|
@@ -521,11 +756,11 @@ export default function LinearChart() {
|
|
|
521
756
|
</Group>
|
|
522
757
|
)
|
|
523
758
|
})}
|
|
524
|
-
{!
|
|
759
|
+
{!runtime.yAxis.hideAxis && <Line from={props.axisFromPoint} to={props.axisToPoint} stroke='#333' />}
|
|
525
760
|
</Group>
|
|
526
761
|
<Group>
|
|
527
762
|
<Text x={xMax / 2} y={config.xAxis.labelOffset} stroke='#333' textAnchor={'middle'} verticalAnchor='start'>
|
|
528
|
-
{
|
|
763
|
+
{runtime.xAxis.label}
|
|
529
764
|
</Text>
|
|
530
765
|
</Group>
|
|
531
766
|
</>
|
|
@@ -534,16 +769,51 @@ export default function LinearChart() {
|
|
|
534
769
|
</AxisBottom>
|
|
535
770
|
</>
|
|
536
771
|
)}
|
|
537
|
-
{
|
|
538
|
-
{
|
|
539
|
-
{
|
|
540
|
-
{
|
|
541
|
-
{(
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
{(
|
|
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') && (
|
|
545
816
|
<Forecasting
|
|
546
|
-
hideTooltip={hideTooltip}
|
|
547
817
|
showTooltip={showTooltip}
|
|
548
818
|
tooltipData={tooltipData}
|
|
549
819
|
xScale={xScale}
|
|
@@ -558,49 +828,40 @@ export default function LinearChart() {
|
|
|
558
828
|
/>
|
|
559
829
|
)}
|
|
560
830
|
|
|
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
|
-
|
|
567
831
|
{/* Line chart */}
|
|
568
832
|
{/* TODO: Make this just line or combo? */}
|
|
569
|
-
{
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
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
|
-
)}
|
|
833
|
+
{visualizationType !== 'Bar' && visualizationType !== 'Paired Bar' && visualizationType !== 'Box Plot' && visualizationType !== 'Area Chart' && visualizationType !== 'Scatter Plot' && visualizationType !== 'Deviation Bar' && visualizationType !== 'Forecasting' && (
|
|
834
|
+
<>
|
|
835
|
+
<LineChart xScale={xScale} yScale={yScale} getXAxisData={getXAxisData} getYAxisData={getYAxisData} xMax={xMax} yMax={yMax} seriesStyle={config.series} />
|
|
836
|
+
</>
|
|
837
|
+
)}
|
|
580
838
|
|
|
581
839
|
{/* y anchors */}
|
|
582
840
|
{config.yAxis.anchors &&
|
|
583
|
-
config.yAxis.anchors.map(anchor => {
|
|
841
|
+
config.yAxis.anchors.map((anchor, index) => {
|
|
584
842
|
let anchorPosition = yScale(anchor.value)
|
|
585
|
-
|
|
586
|
-
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
|
|
587
848
|
|
|
588
849
|
return (
|
|
589
850
|
// prettier-ignore
|
|
590
851
|
<Line
|
|
591
|
-
key={anchor.value}
|
|
852
|
+
key={`yAxis-${anchor.value}--${index}`}
|
|
592
853
|
strokeDasharray={handleLineType(anchor.lineStyle)}
|
|
593
854
|
stroke={anchor.color ? anchor.color : 'rgba(0,0,0,1)'}
|
|
594
855
|
className='anchor-y'
|
|
595
856
|
from={{ x: 0 + padding, y: anchorPosition - middleOffset}}
|
|
596
|
-
to={{ x: width, y: anchorPosition - middleOffset }}
|
|
857
|
+
to={{ x: width - config.yAxis.rightAxisSize, y: anchorPosition - middleOffset }}
|
|
597
858
|
/>
|
|
598
859
|
)
|
|
599
860
|
})}
|
|
600
861
|
|
|
601
862
|
{/* x anchors */}
|
|
602
863
|
{config.xAxis.anchors &&
|
|
603
|
-
config.xAxis.anchors.map(anchor => {
|
|
864
|
+
config.xAxis.anchors.map((anchor, index) => {
|
|
604
865
|
let newX = xAxis
|
|
605
866
|
if (orientation === 'horizontal') {
|
|
606
867
|
newX = yAxis
|
|
@@ -610,10 +871,12 @@ export default function LinearChart() {
|
|
|
610
871
|
|
|
611
872
|
const padding = orientation === 'horizontal' ? Number(config.xAxis.size) : Number(config.yAxis.size)
|
|
612
873
|
|
|
874
|
+
if (!anchorPosition) return
|
|
875
|
+
|
|
613
876
|
return (
|
|
614
877
|
// prettier-ignore
|
|
615
878
|
<Line
|
|
616
|
-
key={anchor.value}
|
|
879
|
+
key={`xAxis-${anchor.value}--${index}`}
|
|
617
880
|
strokeDasharray={handleLineType(anchor.lineStyle)}
|
|
618
881
|
stroke={anchor.color ? anchor.color : 'rgba(0,0,0,1)'}
|
|
619
882
|
fill={anchor.color ? anchor.color : 'rgba(0,0,0,1)'}
|
|
@@ -623,8 +886,27 @@ export default function LinearChart() {
|
|
|
623
886
|
/>
|
|
624
887
|
)
|
|
625
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
|
+
)}
|
|
626
901
|
</svg>
|
|
627
|
-
|
|
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
|
+
)}
|
|
628
910
|
<div className='animation-trigger' ref={triggerRef} />
|
|
629
911
|
</ErrorBoundary>
|
|
630
912
|
)
|