@cdc/chart 4.23.7 → 4.23.8
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/LICENSE +201 -0
- package/dist/cdcchart.js +27964 -26942
- package/examples/feature/__data__/area-chart-date-apple.json +5122 -0
- package/examples/feature/__data__/city-temperature.json +2198 -0
- package/examples/feature/area/area-chart-category.json +45 -45
- package/examples/feature/area/area-chart-date-apple.json +10376 -0
- package/examples/feature/area/area-chart-date-city-temperature.json +4528 -0
- package/examples/feature/area/area-chart-date.json +111 -3
- package/examples/feature/forest-plot/broken.json +700 -0
- package/examples/feature/forest-plot/data.csv +24 -0
- package/examples/feature/forest-plot/forest-plot.json +717 -0
- package/examples/feature/pie/planet-pie-example-config.json +1 -1
- package/examples/private/confidence_interval_test.json +248 -0
- package/examples/private/tooltip-issue.json +45275 -0
- package/index.html +13 -11
- package/package.json +4 -3
- package/src/CdcChart.jsx +24 -14
- package/src/components/AreaChart.jsx +84 -59
- package/src/components/BarChart.Horizontal.jsx +251 -0
- package/src/components/BarChart.StackedHorizontal.jsx +118 -0
- package/src/components/BarChart.StackedVertical.jsx +93 -0
- package/src/components/BarChart.Vertical.jsx +204 -0
- package/src/components/BarChart.jsx +14 -674
- package/src/components/BarChartType.jsx +15 -0
- package/src/components/BrushHandle.jsx +17 -0
- package/src/components/DataTable.jsx +63 -21
- package/src/components/EditorPanel.jsx +351 -303
- package/src/components/ForestPlot.jsx +191 -0
- package/src/components/ForestPlotSettings.jsx +508 -0
- package/src/components/LineChart.jsx +2 -2
- package/src/components/LinearChart.jsx +115 -310
- package/src/data/initial-state.js +43 -0
- package/src/hooks/useBarChart.js +186 -0
- package/src/hooks/useEditorPermissions.js +218 -0
- package/src/hooks/useMinMax.js +15 -3
- package/src/hooks/useScales.js +45 -2
- package/src/hooks/useTooltip.jsx +407 -0
- package/src/scss/main.scss +7 -0
|
@@ -1,25 +1,24 @@
|
|
|
1
|
-
import React, {
|
|
1
|
+
import React, { useContext, useEffect, useRef, useState, useMemo } from 'react'
|
|
2
2
|
|
|
3
3
|
// Libraries
|
|
4
4
|
import { AxisLeft, AxisBottom, AxisRight, AxisTop } from '@visx/axis'
|
|
5
|
-
import { bisector } from 'd3-array'
|
|
6
5
|
import { Group } from '@visx/group'
|
|
7
6
|
import { Line, Bar } from '@visx/shape'
|
|
8
|
-
import { localPoint } from '@visx/event'
|
|
9
7
|
import { Text } from '@visx/text'
|
|
10
8
|
import { Tooltip as ReactTooltip } from 'react-tooltip'
|
|
11
|
-
import { useTooltip, TooltipWithBounds
|
|
9
|
+
import { useTooltip, TooltipWithBounds } from '@visx/tooltip'
|
|
12
10
|
|
|
13
11
|
// CDC Components
|
|
14
12
|
import AreaChart from './AreaChart'
|
|
15
13
|
import BarChart from './BarChart'
|
|
16
14
|
import ConfigContext from '../ConfigContext'
|
|
17
15
|
import CoveBoxPlot from './BoxPlot'
|
|
18
|
-
import
|
|
16
|
+
import ScatterPlot from './ScatterPlot'
|
|
19
17
|
import DeviationBar from './DeviationBar'
|
|
20
18
|
import ErrorBoundary from '@cdc/core/components/ErrorBoundary'
|
|
21
19
|
import Forecasting from './Forecasting'
|
|
22
20
|
import LineChart from './LineChart'
|
|
21
|
+
import ForestPlot from './ForestPlot'
|
|
23
22
|
import PairedBarChart from './PairedBarChart'
|
|
24
23
|
import useIntersectionObserver from './useIntersectionObserver'
|
|
25
24
|
|
|
@@ -29,44 +28,45 @@ import useReduceData from '../hooks/useReduceData'
|
|
|
29
28
|
import useRightAxis from '../hooks/useRightAxis'
|
|
30
29
|
import useScales from '../hooks/useScales'
|
|
31
30
|
import useTopAxis from '../hooks/useTopAxis'
|
|
31
|
+
import { useTooltip as useCoveTooltip } from '../hooks/useTooltip'
|
|
32
32
|
|
|
33
33
|
// styles
|
|
34
|
-
import { defaultStyles } from '@visx/tooltip'
|
|
35
34
|
import '../scss/LinearChart.scss'
|
|
36
35
|
|
|
37
36
|
export default function LinearChart() {
|
|
38
|
-
const { isEditor, transformedData: data, dimensions, config, parseDate, formatDate, currentViewport, formatNumber, handleChartAriaLabels, updateConfig, handleLineType, rawData, capitalize, setSharedFilter, setSharedFilterValue, getTextWidth } = useContext(ConfigContext)
|
|
37
|
+
const { isEditor, isDashboard, transformedData: data, dimensions, config, parseDate, formatDate, currentViewport, formatNumber, handleChartAriaLabels, updateConfig, handleLineType, rawData, capitalize, setSharedFilter, setSharedFilterValue, getTextWidth, isDebug } = useContext(ConfigContext)
|
|
39
38
|
|
|
40
39
|
// todo: start destructuring this file for conciseness
|
|
41
|
-
const { visualizationType, visualizationSubType, orientation, xAxis, yAxis, runtime } = config
|
|
40
|
+
const { visualizationType, visualizationSubType, orientation, xAxis, yAxis, runtime, debugSvg } = config
|
|
42
41
|
|
|
43
|
-
|
|
44
|
-
const getXAxisData = d => (runtime.xAxis.type === 'date' ? parseDate(d[runtime.originalXAxis.dataKey]).getTime() : d[runtime.originalXAxis.dataKey])
|
|
45
|
-
const getYAxisData = (d, seriesKey) => d[seriesKey]
|
|
46
|
-
const xAxisDataMapped = data.map(d => getXAxisData(d))
|
|
42
|
+
const getDate = d => new Date(d[config.xAxis.dataKey])
|
|
47
43
|
|
|
48
44
|
// configure width
|
|
49
45
|
let [width] = dimensions
|
|
50
|
-
let originalWidth = width
|
|
51
46
|
if (config && config.legend && !config.legend.hide && config.legend.position !== 'bottom' && ['lg', 'md'].includes(currentViewport)) {
|
|
52
47
|
width = width * 0.73
|
|
53
48
|
}
|
|
54
|
-
// configure height , yMax,
|
|
49
|
+
// configure height , yMax, xMax
|
|
55
50
|
const { horizontal: heightHorizontal } = config.heights
|
|
56
51
|
const isHorizontal = orientation === 'horizontal'
|
|
57
52
|
const shouldAbbreviate = true
|
|
58
|
-
|
|
53
|
+
let height = config.aspectRatio ? width * config.aspectRatio : config.visualizationType === 'Forest Plot' ? config.heights['vertical'] : config.heights[orientation]
|
|
59
54
|
const xMax = width - runtime.yAxis.size - (visualizationType === 'Combo' ? config.yAxis.rightAxisSize : 0)
|
|
60
|
-
|
|
55
|
+
let yMax = height - (orientation === 'horizontal' ? 0 : runtime.xAxis.size)
|
|
61
56
|
|
|
62
|
-
|
|
57
|
+
if (config.visualizationType === 'Forest Plot') {
|
|
58
|
+
height = height + config.data.length * config.forestPlot.rowHeight
|
|
59
|
+
yMax = yMax + config.data.length * config.forestPlot.rowHeight
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
let dynamicMarginTop = 0 || config.dynamicMarginTop
|
|
63
|
+
const marginTop = 20
|
|
64
|
+
|
|
65
|
+
// hooks % states
|
|
63
66
|
const { minValue, maxValue, existPositiveValue, isAllLine } = useReduceData(config, data)
|
|
64
67
|
const { yScaleRight, hasRightAxis } = useRightAxis({ config, yMax, data, updateConfig })
|
|
65
68
|
const { hasTopAxis } = useTopAxis(config)
|
|
66
69
|
const [animatedChart, setAnimatedChart] = useState(false)
|
|
67
|
-
const properties = { data, config, minValue, maxValue, isAllLine, existPositiveValue, xAxisDataMapped, xMax, yMax }
|
|
68
|
-
const { min, max } = useMinMax(properties)
|
|
69
|
-
const { xScale, yScale, seriesScale, g1xScale, g2xScale, xScaleNoPadding } = useScales({ ...properties, min, max })
|
|
70
70
|
|
|
71
71
|
// refs
|
|
72
72
|
const triggerRef = useRef()
|
|
@@ -75,8 +75,14 @@ export default function LinearChart() {
|
|
|
75
75
|
freezeOnceVisible: false
|
|
76
76
|
})
|
|
77
77
|
|
|
78
|
-
//
|
|
79
|
-
const
|
|
78
|
+
// getters & functions
|
|
79
|
+
const getXAxisData = d => (config.runtime.xAxis.type === 'date' ? parseDate(d[config.runtime.originalXAxis.dataKey]).getTime() : d[config.runtime.originalXAxis.dataKey])
|
|
80
|
+
const getYAxisData = (d, seriesKey) => d[seriesKey]
|
|
81
|
+
const xAxisDataMapped = data.map(d => getXAxisData(d))
|
|
82
|
+
const section = config.orientation === 'horizontal' ? 'yAxis' : 'xAxis'
|
|
83
|
+
const properties = { data, config, minValue, maxValue, isAllLine, existPositiveValue, xAxisDataMapped, xMax, yMax }
|
|
84
|
+
const { min, max } = useMinMax(properties)
|
|
85
|
+
const { xScale, yScale, seriesScale, g1xScale, g2xScale, xScaleNoPadding } = useScales({ ...properties, min, max })
|
|
80
86
|
|
|
81
87
|
// sets the portal x/y for where tooltips should appear on the page.
|
|
82
88
|
const [chartPosition, setChartPosition] = useState(null)
|
|
@@ -84,31 +90,14 @@ export default function LinearChart() {
|
|
|
84
90
|
setChartPosition(svgRef?.current?.getBoundingClientRect())
|
|
85
91
|
}, [svgRef, config.legend])
|
|
86
92
|
|
|
87
|
-
|
|
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
|
-
|
|
107
|
-
const handleLeftTickFormatting = tick => {
|
|
93
|
+
const handleLeftTickFormatting = (tick, index) => {
|
|
108
94
|
if (config.useLogScale && tick === 0.1) {
|
|
109
95
|
//when logarithmic scale applied change value of first tick
|
|
110
96
|
tick = 0
|
|
111
97
|
}
|
|
98
|
+
|
|
99
|
+
if (config.data && !config.data[index] && visualizationType === 'Forest Plot') return
|
|
100
|
+
if (config.visualizationType === 'Forest Plot') return config.data[index][config.xAxis.dataKey]
|
|
112
101
|
if (runtime.yAxis.type === 'date') return formatDate(parseDate(tick))
|
|
113
102
|
if (orientation === 'vertical') return formatNumber(tick, 'left', shouldAbbreviate)
|
|
114
103
|
return tick
|
|
@@ -161,186 +150,24 @@ export default function LinearChart() {
|
|
|
161
150
|
return tickCount
|
|
162
151
|
}
|
|
163
152
|
|
|
164
|
-
// Tooltip
|
|
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
|
|
153
|
+
// Tooltip Helpers
|
|
201
154
|
const { tooltipData, showTooltip, hideTooltip, tooltipOpen } = useTooltip()
|
|
202
155
|
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
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
|
-
}
|
|
156
|
+
// prettier-ignore
|
|
157
|
+
const {
|
|
158
|
+
handleTooltipMouseOver,
|
|
159
|
+
handleTooltipClick,
|
|
160
|
+
handleTooltipMouseOff,
|
|
161
|
+
tooltipStyles,
|
|
162
|
+
TooltipListItem,
|
|
163
|
+
getXValueFromCoordinateDate,
|
|
164
|
+
getXValueFromCoordinate
|
|
165
|
+
} = useCoveTooltip({
|
|
166
|
+
xScale,
|
|
167
|
+
yScale,
|
|
168
|
+
showTooltip,
|
|
169
|
+
hideTooltip
|
|
170
|
+
})
|
|
344
171
|
|
|
345
172
|
// Make sure the chart is visible if in the editor
|
|
346
173
|
/* eslint-disable react-hooks/exhaustive-deps */
|
|
@@ -370,77 +197,20 @@ export default function LinearChart() {
|
|
|
370
197
|
return false
|
|
371
198
|
}
|
|
372
199
|
|
|
373
|
-
|
|
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
|
|
200
|
+
const padding = orientation === 'horizontal' ? Number(config.xAxis.size) : Number(config.yAxis.size)
|
|
425
201
|
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
borderRadius: '4px',
|
|
431
|
-
transform: `translate(${dataXPosition}px, ${Number(dataYPosition)}px)`
|
|
432
|
-
}
|
|
202
|
+
const handleNumTicks = () => {
|
|
203
|
+
// On forest plots we need to return every "study" or y axis value.
|
|
204
|
+
if (config.visualizationType === 'Forest Plot') return config.data.length
|
|
205
|
+
return countNumOfTicks('yAxis')
|
|
433
206
|
}
|
|
434
207
|
|
|
435
|
-
const random = Date.now()
|
|
436
|
-
|
|
437
208
|
return isNaN(width) ? (
|
|
438
209
|
<React.Fragment></React.Fragment>
|
|
439
210
|
) : (
|
|
440
211
|
<ErrorBoundary component='LinearChart'>
|
|
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>
|
|
443
|
-
{/* Highlighted regions */}
|
|
212
|
+
<svg width={width} height={height} className={`linear ${config.animate ? 'animated' : ''} ${animatedChart && config.animate ? 'animate' : ''} ${debugSvg && 'debug'}`} role='img' aria-label={handleChartAriaLabels(config)} tabIndex={0} ref={svgRef}>
|
|
213
|
+
<Bar width={width} height={height} fill={'transparent'}></Bar> {/* Highlighted regions */}
|
|
444
214
|
{config.regions
|
|
445
215
|
? config.regions.map(region => {
|
|
446
216
|
if (!Object.keys(region).includes('from') || !Object.keys(region).includes('to')) return null
|
|
@@ -483,10 +253,9 @@ export default function LinearChart() {
|
|
|
483
253
|
)
|
|
484
254
|
})
|
|
485
255
|
: ''}
|
|
486
|
-
|
|
487
256
|
{/* Y axis */}
|
|
488
|
-
{
|
|
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={
|
|
257
|
+
{!['Spark Line', 'Forest Plot'].includes(visualizationType) && (
|
|
258
|
+
<AxisLeft scale={yScale} tickLength={config.useLogScale ? 6 : 8} left={Number(runtime.yAxis.size) - config.yAxis.axisPadding} label={runtime.yAxis.label} stroke='#333' tickFormat={(tick, i) => handleLeftTickFormatting(tick, i)} numTicks={handleNumTicks()}>
|
|
490
259
|
{props => {
|
|
491
260
|
const axisCenter = runtime.horizontal ? (props.axisToPoint.y - props.axisFromPoint.y) / 2 : (props.axisFromPoint.y - props.axisToPoint.y) / 2
|
|
492
261
|
const horizontalTickOffset = yMax / props.ticks.length / 2 - (yMax / props.ticks.length) * (1 - config.barThickness) + 5
|
|
@@ -503,7 +272,7 @@ export default function LinearChart() {
|
|
|
503
272
|
<Group key={`vx-tick-${tick.value}-${i}`} className={'vx-axis-tick'}>
|
|
504
273
|
{!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'} />}
|
|
505
274
|
|
|
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)' /> : ''}
|
|
275
|
+
{runtime.yAxis.gridLines ? <Line key={`${tick.value}--hide-hideGridLines`} display={(config.useLogScale && showTicks).toString()} from={{ x: tick.from.x + xMax, y: tick.from.y }} to={tick.from} stroke='rgba(0,0,0,0.3)' /> : ''}
|
|
507
276
|
|
|
508
277
|
{orientation === 'horizontal' && visualizationSubType !== 'stacked' && config.yAxis.labelPlacement === 'On Date/Category Axis' && !config.yAxis.hideLabel && (
|
|
509
278
|
<Text
|
|
@@ -549,7 +318,7 @@ export default function LinearChart() {
|
|
|
549
318
|
</Group>
|
|
550
319
|
)
|
|
551
320
|
})}
|
|
552
|
-
{!config.yAxis.hideAxis && <Line from={props.axisFromPoint} to={runtime.horizontal ? { x: 0, y: Number(heightHorizontal) } : props.axisToPoint} stroke='#000' />}
|
|
321
|
+
{!config.yAxis.hideAxis && <Line from={props.axisFromPoint} to={runtime.horizontal ? { x: 0, y: config.visualizationType === 'Forest Plot' ? height : Number(heightHorizontal) } : props.axisToPoint} stroke='#000' />}
|
|
553
322
|
{yScale.domain()[0] < 0 && <Line from={{ x: props.axisFromPoint.x, y: yScale(0) }} to={{ x: xMax, y: yScale(0) }} stroke='#333' />}
|
|
554
323
|
{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
324
|
<Text className='y-label' textAnchor='middle' verticalAnchor='start' transform={`translate(${-1 * runtime.yAxis.size}, ${axisCenter}) rotate(-90)`} fontWeight='bold' fill={config.yAxis.labelColor}>
|
|
@@ -560,7 +329,6 @@ export default function LinearChart() {
|
|
|
560
329
|
}}
|
|
561
330
|
</AxisLeft>
|
|
562
331
|
)}
|
|
563
|
-
|
|
564
332
|
{/* Right Axis */}
|
|
565
333
|
{hasRightAxis && (
|
|
566
334
|
<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}>
|
|
@@ -593,7 +361,6 @@ export default function LinearChart() {
|
|
|
593
361
|
}}
|
|
594
362
|
</AxisRight>
|
|
595
363
|
)}
|
|
596
|
-
|
|
597
364
|
{hasTopAxis && config.topAxis.hasLine && (
|
|
598
365
|
<AxisTop
|
|
599
366
|
stroke='#333'
|
|
@@ -606,11 +373,10 @@ export default function LinearChart() {
|
|
|
606
373
|
})}
|
|
607
374
|
/>
|
|
608
375
|
)}
|
|
609
|
-
|
|
610
376
|
{/* X axis */}
|
|
611
377
|
{visualizationType !== 'Paired Bar' && visualizationType !== 'Spark Line' && (
|
|
612
378
|
<AxisBottom
|
|
613
|
-
top={runtime.horizontal ? Number(heightHorizontal) + Number(config.xAxis.axisPadding) : yMax + Number(config.xAxis.axisPadding)}
|
|
379
|
+
top={runtime.horizontal && config.visualizationType !== 'Forest Plot' ? Number(heightHorizontal) + Number(config.xAxis.axisPadding) : config.visualizationType === 'Forest Plot' ? yMax + Number(config.xAxis.axisPadding) : yMax + Number(config.xAxis.axisPadding)}
|
|
614
380
|
left={Number(runtime.yAxis.size)}
|
|
615
381
|
label={runtime.xAxis.label}
|
|
616
382
|
tickFormat={handleBottomTickFormatting}
|
|
@@ -620,7 +386,7 @@ export default function LinearChart() {
|
|
|
620
386
|
tickStroke='#333'
|
|
621
387
|
>
|
|
622
388
|
{props => {
|
|
623
|
-
const axisCenter = (props.axisToPoint.x - props.axisFromPoint.x) / 2
|
|
389
|
+
const axisCenter = config.visualizationType !== 'Forest Plot' ? (props.axisToPoint.x - props.axisFromPoint.x) / 2 : width / 2
|
|
624
390
|
const containsMultipleWords = inputString => /\s/.test(inputString)
|
|
625
391
|
const ismultiLabel = props.ticks.some(tick => containsMultipleWords(tick.value))
|
|
626
392
|
|
|
@@ -628,7 +394,7 @@ export default function LinearChart() {
|
|
|
628
394
|
const fontSize = { small: 16, medium: 18, large: 20 }
|
|
629
395
|
const defaultTickLength = 8
|
|
630
396
|
const tickWidthMax = Math.max(...props.ticks.map(tick => getTextWidth(tick.formattedValue, `normal ${fontSize[config.fontSize]}px sans-serif`)))
|
|
631
|
-
const marginTop = 20
|
|
397
|
+
// const marginTop = 20 // moved to top bc need for yMax calcs
|
|
632
398
|
const accumulator = ismultiLabel ? 180 : 100
|
|
633
399
|
|
|
634
400
|
const textWidths = props.ticks.map(tick => getTextWidth(tick.formattedValue, `normal ${fontSize[config.fontSize]}px sans-serif`))
|
|
@@ -653,9 +419,9 @@ export default function LinearChart() {
|
|
|
653
419
|
}
|
|
654
420
|
})
|
|
655
421
|
|
|
656
|
-
|
|
422
|
+
dynamicMarginTop = areTicksTouching && config.isResponsiveTicks ? tickWidthMax + defaultTickLength + marginTop : 0
|
|
657
423
|
config.dynamicMarginTop = dynamicMarginTop
|
|
658
|
-
|
|
424
|
+
|
|
659
425
|
return (
|
|
660
426
|
<Group className='bottom-axis'>
|
|
661
427
|
{props.ticks.map((tick, i, propsTicks) => {
|
|
@@ -684,7 +450,7 @@ export default function LinearChart() {
|
|
|
684
450
|
angle={tickRotation}
|
|
685
451
|
verticalAnchor={tickRotation < -50 ? 'middle' : 'start'}
|
|
686
452
|
textAnchor={tickRotation ? 'end' : 'middle'}
|
|
687
|
-
width={areTicksTouching && !config.isResponsiveTicks && !config.
|
|
453
|
+
width={areTicksTouching && !config.isResponsiveTicks && !Number(config[section].tickRotation) ? limitedWidth : textWidth}
|
|
688
454
|
fill={config.xAxis.tickLabelColor}
|
|
689
455
|
>
|
|
690
456
|
{tick.formattedValue}
|
|
@@ -702,7 +468,6 @@ export default function LinearChart() {
|
|
|
702
468
|
}}
|
|
703
469
|
</AxisBottom>
|
|
704
470
|
)}
|
|
705
|
-
|
|
706
471
|
{visualizationType === 'Paired Bar' && (
|
|
707
472
|
<>
|
|
708
473
|
<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}>
|
|
@@ -771,10 +536,24 @@ export default function LinearChart() {
|
|
|
771
536
|
)}
|
|
772
537
|
{visualizationType === 'Deviation Bar' && <DeviationBar xScale={xScale} yScale={yScale} width={xMax} height={yMax} />}
|
|
773
538
|
{visualizationType === 'Paired Bar' && <PairedBarChart originalWidth={width} width={xMax} height={yMax} />}
|
|
774
|
-
{visualizationType === 'Scatter Plot' &&
|
|
539
|
+
{visualizationType === 'Scatter Plot' && (
|
|
540
|
+
<ScatterPlot
|
|
541
|
+
xScale={xScale}
|
|
542
|
+
yScale={yScale}
|
|
543
|
+
getXAxisData={getXAxisData}
|
|
544
|
+
getYAxisData={getYAxisData}
|
|
545
|
+
xMax={xMax}
|
|
546
|
+
yMax={yMax}
|
|
547
|
+
handleTooltipMouseOver={handleTooltipMouseOver}
|
|
548
|
+
handleTooltipMouseOff={handleTooltipMouseOff}
|
|
549
|
+
handleTooltipClick={handleTooltipClick}
|
|
550
|
+
tooltipData={tooltipData}
|
|
551
|
+
showTooltip={showTooltip}
|
|
552
|
+
/>
|
|
553
|
+
)}
|
|
775
554
|
{visualizationType === 'Box Plot' && <CoveBoxPlot xScale={xScale} yScale={yScale} />}
|
|
776
555
|
{(visualizationType === 'Area Chart' || visualizationType === 'Combo') && (
|
|
777
|
-
<AreaChart xScale={xScale} yScale={yScale} yMax={yMax} xMax={xMax} chartRef={svgRef} width={xMax} height={yMax} handleTooltipMouseOver={
|
|
556
|
+
<AreaChart xScale={xScale} yScale={yScale} yMax={yMax} xMax={xMax} chartRef={svgRef} width={xMax} height={yMax} handleTooltipMouseOver={handleTooltipMouseOver} handleTooltipMouseOff={handleTooltipMouseOff} tooltipData={tooltipData} showTooltip={showTooltip} />
|
|
778
557
|
)}
|
|
779
558
|
{(visualizationType === 'Bar' || visualizationType === 'Combo') && (
|
|
780
559
|
<BarChart
|
|
@@ -819,15 +598,43 @@ export default function LinearChart() {
|
|
|
819
598
|
xScale={xScale}
|
|
820
599
|
yScale={yScale}
|
|
821
600
|
width={xMax}
|
|
601
|
+
le
|
|
822
602
|
height={yMax}
|
|
823
603
|
xScaleNoPadding={xScaleNoPadding}
|
|
824
604
|
chartRef={svgRef}
|
|
825
605
|
getXValueFromCoordinate={getXValueFromCoordinate}
|
|
826
606
|
handleTooltipMouseOver={handleTooltipMouseOver}
|
|
827
607
|
handleTooltipMouseOff={handleTooltipMouseOff}
|
|
608
|
+
isBrush={false}
|
|
609
|
+
/>
|
|
610
|
+
)}
|
|
611
|
+
{/* y anchors */}
|
|
612
|
+
{config.yAxis.anchors &&
|
|
613
|
+
config.yAxis.anchors.map(anchor => {
|
|
614
|
+
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={runtime.horizontal ? 'none' : 'block'} />
|
|
615
|
+
})}
|
|
616
|
+
{visualizationType === 'Forest Plot' && (
|
|
617
|
+
<ForestPlot
|
|
618
|
+
xScale={xScale}
|
|
619
|
+
yScale={yScale}
|
|
620
|
+
seriesScale={seriesScale}
|
|
621
|
+
width={xMax}
|
|
622
|
+
height={yMax}
|
|
623
|
+
maxWidth={width}
|
|
624
|
+
maxHeight={height}
|
|
625
|
+
getXAxisData={getXAxisData}
|
|
626
|
+
getYAxisData={getYAxisData}
|
|
627
|
+
animatedChart={animatedChart}
|
|
628
|
+
visible={animatedChart}
|
|
629
|
+
handleTooltipMouseOver={handleTooltipMouseOver}
|
|
630
|
+
handleTooltipMouseOff={handleTooltipMouseOff}
|
|
631
|
+
handleTooltipClick={handleTooltipClick}
|
|
632
|
+
tooltipData={tooltipData}
|
|
633
|
+
showTooltip={showTooltip}
|
|
634
|
+
chartRef={svgRef}
|
|
635
|
+
config={config}
|
|
828
636
|
/>
|
|
829
637
|
)}
|
|
830
|
-
|
|
831
638
|
{/* Line chart */}
|
|
832
639
|
{/* TODO: Make this just line or combo? */}
|
|
833
640
|
{visualizationType !== 'Bar' && visualizationType !== 'Paired Bar' && visualizationType !== 'Box Plot' && visualizationType !== 'Area Chart' && visualizationType !== 'Scatter Plot' && visualizationType !== 'Deviation Bar' && visualizationType !== 'Forecasting' && (
|
|
@@ -835,13 +642,13 @@ export default function LinearChart() {
|
|
|
835
642
|
<LineChart xScale={xScale} yScale={yScale} getXAxisData={getXAxisData} getYAxisData={getYAxisData} xMax={xMax} yMax={yMax} seriesStyle={config.series} />
|
|
836
643
|
</>
|
|
837
644
|
)}
|
|
838
|
-
|
|
839
645
|
{/* y anchors */}
|
|
840
646
|
{config.yAxis.anchors &&
|
|
841
647
|
config.yAxis.anchors.map((anchor, index) => {
|
|
842
648
|
let anchorPosition = yScale(anchor.value)
|
|
649
|
+
// have to move up
|
|
650
|
+
// const padding = orientation === 'horizontal' ? Number(config.xAxis.size) : Number(config.yAxis.size)
|
|
843
651
|
if (!anchor.value) return
|
|
844
|
-
const padding = orientation === 'horizontal' ? Number(config.xAxis.size) : Number(config.yAxis.size)
|
|
845
652
|
const middleOffset = orientation === 'horizontal' && visualizationType === 'Bar' ? config.barHeight / 4 : 0
|
|
846
653
|
|
|
847
654
|
if (!anchorPosition) return
|
|
@@ -858,7 +665,6 @@ export default function LinearChart() {
|
|
|
858
665
|
/>
|
|
859
666
|
)
|
|
860
667
|
})}
|
|
861
|
-
|
|
862
668
|
{/* x anchors */}
|
|
863
669
|
{config.xAxis.anchors &&
|
|
864
670
|
config.xAxis.anchors.map((anchor, index) => {
|
|
@@ -869,7 +675,8 @@ export default function LinearChart() {
|
|
|
869
675
|
|
|
870
676
|
let anchorPosition = newX.type === 'date' ? xScale(parseDate(anchor.value, false)) : xScale(anchor.value)
|
|
871
677
|
|
|
872
|
-
|
|
678
|
+
// have to move up
|
|
679
|
+
// const padding = orientation === 'horizontal' ? Number(config.xAxis.size) : Number(config.yAxis.size)
|
|
873
680
|
|
|
874
681
|
if (!anchorPosition) return
|
|
875
682
|
|
|
@@ -886,13 +693,11 @@ export default function LinearChart() {
|
|
|
886
693
|
/>
|
|
887
694
|
)
|
|
888
695
|
})}
|
|
889
|
-
|
|
890
696
|
{chartHasTooltipGuides && showTooltip && tooltipData && config.visual.verticalHoverLine && (
|
|
891
697
|
<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' />
|
|
698
|
+
<Line from={{ x: tooltipData.dataXPosition - 10, y: 0 }} to={{ x: tooltipData.dataXPosition - 10, y: yMax }} stroke={'black'} strokeWidth={1} pointerEvents='none' strokeDasharray='5,5' className='vertical-tooltip-line' />
|
|
893
699
|
</Group>
|
|
894
700
|
)}
|
|
895
|
-
|
|
896
701
|
{chartHasTooltipGuides && showTooltip && tooltipData && config.visual.horizontalHoverLine && (
|
|
897
702
|
<Group key='tooltipLine-horizontal' className='horizontal-tooltip-line' left={config.yAxis.size ? config.yAxis.size : 0}>
|
|
898
703
|
<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' />
|