@cdc/chart 4.24.9 → 4.24.11
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 +45911 -41739
- package/examples/feature/boxplot/boxplot-data.json +88 -22
- package/examples/feature/boxplot/boxplot.json +540 -16
- package/examples/feature/boxplot/testing.csv +7 -7
- package/examples/feature/sankey/sankey-example-data.json +0 -1
- package/examples/private/test.json +20092 -0
- package/index.html +4 -4
- package/package.json +2 -2
- package/src/CdcChart.tsx +209 -188
- package/src/_stories/Chart.CustomColors.stories.tsx +19 -0
- package/src/_stories/Chart.DynamicSeries.stories.tsx +27 -0
- package/src/_stories/Chart.Legend.Gradient.stories.tsx +74 -0
- package/src/_stories/Chart.stories.tsx +30 -3
- package/src/_stories/ChartAxisLabels.stories.tsx +20 -0
- package/src/_stories/ChartAxisTitles.stories.tsx +53 -0
- package/src/_stories/ChartEditor.stories.tsx +27 -0
- package/src/_stories/ChartLine.Suppression.stories.tsx +25 -0
- package/src/_stories/ChartPrefixSuffix.stories.tsx +159 -0
- package/src/_stories/_mock/boxplot_multiseries.json +647 -0
- package/src/_stories/_mock/dynamic_series_bar_config.json +723 -0
- package/src/_stories/_mock/dynamic_series_config.json +979 -0
- package/src/_stories/_mock/horizontal_bar.json +257 -0
- package/src/_stories/_mock/large_x_axis_labels.json +261 -0
- package/src/_stories/_mock/paired-bar.json +262 -0
- package/src/_stories/_mock/pie_with_data.json +255 -0
- package/{examples/feature/scatterplot/scatterplot.json → src/_stories/_mock/scatterplot_mock.json} +62 -92
- package/src/_stories/_mock/simplified_line.json +1510 -0
- package/src/_stories/_mock/suppression_mock.json +1549 -0
- package/src/components/Annotations/components/AnnotationDraggable.tsx +0 -3
- package/src/components/Annotations/components/AnnotationDropdown.tsx +1 -1
- package/src/components/Axis/Categorical.Axis.tsx +22 -4
- package/src/components/BarChart/components/BarChart.Horizontal.tsx +95 -16
- package/src/components/BarChart/components/BarChart.StackedHorizontal.tsx +41 -17
- package/src/components/BarChart/components/BarChart.StackedVertical.tsx +43 -9
- package/src/components/BarChart/components/BarChart.Vertical.tsx +123 -47
- package/src/components/BarChart/helpers/index.ts +23 -5
- package/src/components/BoxPlot/BoxPlot.tsx +189 -0
- package/src/components/BrushChart.tsx +3 -2
- package/src/components/DeviationBar.jsx +58 -8
- package/src/components/EditorPanel/EditorPanel.tsx +127 -102
- package/src/components/EditorPanel/components/Panels/Panel.Annotate.tsx +11 -28
- package/src/components/EditorPanel/components/Panels/Panel.BoxPlot.tsx +51 -6
- package/src/components/EditorPanel/components/Panels/Panel.General.tsx +21 -4
- package/src/components/EditorPanel/components/Panels/Panel.Regions.tsx +40 -9
- package/src/components/EditorPanel/components/Panels/Panel.Sankey.tsx +3 -3
- package/src/components/EditorPanel/components/Panels/Panel.Series.tsx +121 -56
- package/src/components/EditorPanel/components/Panels/Panel.Visual.tsx +296 -35
- package/src/components/EditorPanel/components/panels.scss +4 -6
- package/src/components/EditorPanel/editor-panel.scss +0 -8
- package/src/components/EditorPanel/helpers/tests/updateFieldRankByValue.test.ts +38 -0
- package/src/components/EditorPanel/helpers/updateFieldRankByValue.ts +42 -0
- package/src/components/EditorPanel/useEditorPermissions.ts +16 -1
- package/src/components/ForestPlot/ForestPlot.tsx +2 -3
- package/src/components/ForestPlot/ForestPlotProps.ts +2 -0
- package/src/components/Legend/Legend.Component.tsx +23 -24
- package/src/components/Legend/Legend.Suppression.tsx +25 -20
- package/src/components/Legend/Legend.tsx +16 -18
- package/src/components/Legend/helpers/index.ts +16 -19
- package/src/components/LegendWrapper.tsx +3 -1
- package/src/components/LineChart/components/LineChart.Circle.tsx +10 -0
- package/src/components/LineChart/helpers.ts +48 -43
- package/src/components/LineChart/index.tsx +88 -82
- package/src/components/LinearChart.tsx +747 -562
- package/src/components/PairedBarChart.jsx +50 -10
- package/src/components/PieChart/PieChart.tsx +1 -6
- package/src/components/Regions/components/Regions.tsx +33 -19
- package/src/components/Sankey/index.tsx +50 -32
- package/src/components/Sankey/sankey.scss +6 -5
- package/src/components/Sankey/useSankeyAlert.tsx +60 -0
- package/src/components/ScatterPlot/ScatterPlot.jsx +20 -4
- package/src/components/ZoomBrush.tsx +25 -6
- package/src/coreStyles_chart.scss +3 -0
- package/src/data/initial-state.js +8 -10
- package/src/helpers/configHelpers.ts +28 -0
- package/src/helpers/handleRankByValue.ts +15 -0
- package/src/helpers/sizeHelpers.ts +25 -0
- package/src/helpers/tests/handleRankByValue.test.ts +37 -0
- package/src/helpers/tests/sizeHelpers.test.ts +80 -0
- package/src/hooks/useColorPalette.js +10 -2
- package/src/hooks/useLegendClasses.ts +13 -22
- package/src/hooks/useMinMax.ts +27 -13
- package/src/hooks/useReduceData.ts +43 -10
- package/src/hooks/useScales.ts +87 -38
- package/src/hooks/useTooltip.tsx +62 -53
- package/src/index.jsx +1 -0
- package/src/scss/DataTable.scss +5 -4
- package/src/scss/main.scss +57 -70
- package/src/types/ChartConfig.ts +43 -34
- package/src/types/ChartContext.ts +22 -15
- package/src/types/ForestPlot.ts +8 -0
- package/src/_stories/Chart.Legend.Gradient.tsx +0 -19
- package/src/_stories/ChartBrush.stories.tsx +0 -19
- package/src/components/BoxPlot/BoxPlot.jsx +0 -111
- package/src/components/LinearChart.jsx +0 -817
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import React, { useContext, useEffect, useRef, useState } from 'react'
|
|
1
|
+
import React, { forwardRef, useContext, useEffect, useMemo, useRef, useState } from 'react'
|
|
2
2
|
|
|
3
3
|
// Libraries
|
|
4
4
|
import { AxisLeft, AxisBottom, AxisRight, AxisTop } from '@visx/axis'
|
|
@@ -27,6 +27,9 @@ import CategoricalYAxis from './Axis/Categorical.Axis'
|
|
|
27
27
|
|
|
28
28
|
// Helpers
|
|
29
29
|
import { isConvertLineToBarGraph } from '../helpers/isConvertLineToBarGraph'
|
|
30
|
+
import { isLegendWrapViewport } from '@cdc/core/helpers/viewports'
|
|
31
|
+
import { getTextWidth } from '@cdc/core/helpers/getTextWidth'
|
|
32
|
+
import { calcInitialHeight } from '../helpers/sizeHelpers'
|
|
30
33
|
|
|
31
34
|
// Hooks
|
|
32
35
|
import useMinMax from '../hooks/useMinMax'
|
|
@@ -37,97 +40,140 @@ import useTopAxis from '../hooks/useTopAxis'
|
|
|
37
40
|
import { useTooltip as useCoveTooltip } from '../hooks/useTooltip'
|
|
38
41
|
import { useEditorPermissions } from './EditorPanel/useEditorPermissions'
|
|
39
42
|
import Annotation from './Annotations'
|
|
43
|
+
import { BlurStrokeText } from '@cdc/core/components/BlurStrokeText'
|
|
40
44
|
|
|
41
45
|
type LinearChartProps = {
|
|
42
46
|
parentWidth: number
|
|
43
47
|
parentHeight: number
|
|
44
48
|
}
|
|
45
49
|
|
|
46
|
-
const
|
|
50
|
+
const BOTTOM_LABEL_PADDING = 9
|
|
51
|
+
const X_TICK_LABEL_PADDING = 3
|
|
52
|
+
const DEFAULT_TICK_LENGTH = 8
|
|
53
|
+
|
|
54
|
+
const LinearChart = forwardRef<SVGAElement, LinearChartProps>(({ parentHeight, parentWidth }, svgRef) => {
|
|
47
55
|
// prettier-ignore
|
|
48
56
|
const {
|
|
49
57
|
brushConfig,
|
|
58
|
+
colorScale,
|
|
50
59
|
config,
|
|
51
60
|
currentViewport,
|
|
52
61
|
dimensions,
|
|
53
62
|
formatDate,
|
|
54
63
|
formatNumber,
|
|
55
|
-
getTextWidth,
|
|
56
64
|
handleChartAriaLabels,
|
|
57
65
|
handleLineType,
|
|
58
66
|
handleDragStateChange,
|
|
67
|
+
isDraggingAnnotation,
|
|
68
|
+
legendRef,
|
|
59
69
|
parseDate,
|
|
70
|
+
parentRef,
|
|
60
71
|
tableData,
|
|
61
72
|
transformedData: data,
|
|
62
73
|
updateConfig,
|
|
63
|
-
isDraggingAnnotation,
|
|
64
74
|
seriesHighlight,
|
|
65
|
-
colorScale
|
|
66
75
|
} = useContext(ConfigContext)
|
|
67
76
|
|
|
77
|
+
// CONFIG
|
|
68
78
|
// todo: start destructuring this file for conciseness
|
|
69
|
-
const {
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
//
|
|
87
|
-
const { horizontal: heightHorizontal, mobileVertical } = config.heights
|
|
88
|
-
const isHorizontal = orientation === 'horizontal' || config.visualizationType === 'Forest Plot'
|
|
89
|
-
const shouldAbbreviate = true
|
|
90
|
-
const isLogarithmicAxis = config.yAxis.type === 'logarithmic'
|
|
91
|
-
const xLabelOffset = isNaN(parseInt(runtime.xAxis.labelOffset)) ? 0 : parseInt(runtime.xAxis.labelOffset)
|
|
92
|
-
const yLabelOffset = isNaN(parseInt(runtime.yAxis.labelOffset)) ? 0 : parseInt(runtime.yAxis.labelOffset)
|
|
93
|
-
const xAxisSize = isNaN(parseInt(runtime.xAxis.size)) ? 0 : parseInt(runtime.xAxis.size)
|
|
94
|
-
const isForestPlot = visualizationType === 'Forest Plot'
|
|
95
|
-
const useVertical = orientation === 'vertical' || isForestPlot
|
|
96
|
-
const useMobileVertical = mobileVertical && ['xs', 'xxs'].includes(currentViewport)
|
|
97
|
-
const responsiveVertical = useMobileVertical ? 'mobileVertical' : 'vertical'
|
|
98
|
-
const renderedOrientation = useVertical ? responsiveVertical : 'horizontal'
|
|
99
|
-
let height = config.aspectRatio ? width * config.aspectRatio : config.heights[renderedOrientation]
|
|
100
|
-
height = Number(height)
|
|
101
|
-
const xMax = width - runtime.yAxis.size - (visualizationType === 'Combo' ? config.yAxis.rightAxisSize : 0)
|
|
102
|
-
let yMax = height - (orientation === 'horizontal' ? 0 : xAxisSize)
|
|
103
|
-
height += orientation === 'horizontal' ? xAxisSize : 0
|
|
104
|
-
|
|
105
|
-
if (config.visualizationType === 'Forest Plot') {
|
|
106
|
-
height = height + config.data.length * config.forestPlot.rowHeight
|
|
107
|
-
yMax = yMax + config.data.length * config.forestPlot.rowHeight
|
|
108
|
-
width = dimensions[0]
|
|
109
|
-
}
|
|
110
|
-
if (config.brush?.active) {
|
|
111
|
-
height = height + config.brush?.height
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
// hooks % states
|
|
79
|
+
const {
|
|
80
|
+
heights,
|
|
81
|
+
visualizationType,
|
|
82
|
+
visualizationSubType,
|
|
83
|
+
orientation,
|
|
84
|
+
xAxis,
|
|
85
|
+
yAxis,
|
|
86
|
+
runtime,
|
|
87
|
+
legend,
|
|
88
|
+
forestPlot,
|
|
89
|
+
brush,
|
|
90
|
+
dataFormat,
|
|
91
|
+
debugSvg
|
|
92
|
+
} = config
|
|
93
|
+
const { suffix, onlyShowTopPrefixSuffix } = dataFormat
|
|
94
|
+
const { labelsAboveGridlines, hideAxis } = config.yAxis
|
|
95
|
+
|
|
96
|
+
// HOOKS % STATES
|
|
115
97
|
const { minValue, maxValue, existPositiveValue, isAllLine } = useReduceData(config, data)
|
|
116
98
|
const { visSupportsReactTooltip } = useEditorPermissions()
|
|
117
99
|
const { hasTopAxis } = useTopAxis(config)
|
|
118
100
|
const [animatedChart, setAnimatedChart] = useState(false)
|
|
119
101
|
const [point, setPoint] = useState({ x: 0, y: 0 })
|
|
120
|
-
const
|
|
102
|
+
const [suffixWidth, setSuffixWidth] = useState(0)
|
|
121
103
|
|
|
122
|
-
//
|
|
123
|
-
const triggerRef = useRef()
|
|
104
|
+
// REFS
|
|
124
105
|
const axisBottomRef = useRef(null)
|
|
125
|
-
const
|
|
106
|
+
const forestPlotRightLabelRef = useRef(null)
|
|
107
|
+
const suffixRef = useRef(null)
|
|
108
|
+
const topYLabelRef = useRef(null)
|
|
109
|
+
const triggerRef = useRef()
|
|
110
|
+
const xAxisLabelRefs = useRef([])
|
|
111
|
+
const xAxisTitleRef = useRef(null)
|
|
112
|
+
|
|
126
113
|
const dataRef = useIntersectionObserver(triggerRef, {
|
|
127
114
|
freezeOnceVisible: false
|
|
128
115
|
})
|
|
129
116
|
|
|
130
|
-
//
|
|
117
|
+
// VARS/MEMOS
|
|
118
|
+
const shouldAbbreviate = true
|
|
119
|
+
const isHorizontal = orientation === 'horizontal' || config.visualizationType === 'Forest Plot'
|
|
120
|
+
const isLogarithmicAxis = config.yAxis.type === 'logarithmic'
|
|
121
|
+
const isForestPlot = visualizationType === 'Forest Plot'
|
|
122
|
+
const suffixHasNoSpace = !suffix.includes(' ')
|
|
123
|
+
|
|
124
|
+
const yLabelOffset = isNaN(parseInt(`${runtime.yAxis.labelOffset}`)) ? 0 : parseInt(`${runtime.yAxis.labelOffset}`)
|
|
125
|
+
|
|
126
|
+
// zero if not forest plot
|
|
127
|
+
const forestRowsHeight = isForestPlot ? config.data.length * config.forestPlot.rowHeight : 0
|
|
128
|
+
|
|
129
|
+
// height before bottom axis
|
|
130
|
+
const initialHeight = useMemo(
|
|
131
|
+
() => calcInitialHeight(config, currentViewport),
|
|
132
|
+
[config, currentViewport, parentHeight, config.heights?.vertical, config.heights?.horizontal]
|
|
133
|
+
)
|
|
134
|
+
const forestHeight = useMemo(() => initialHeight + forestRowsHeight, [initialHeight, forestRowsHeight])
|
|
135
|
+
|
|
136
|
+
// width
|
|
137
|
+
const width = useMemo(() => {
|
|
138
|
+
const initialWidth = dimensions[0]
|
|
139
|
+
const legendHidden = legend?.hide
|
|
140
|
+
const legendOnTopOrBottom = ['bottom', 'top'].includes(config.legend?.position)
|
|
141
|
+
const legendWrapped = isLegendWrapViewport(currentViewport)
|
|
142
|
+
|
|
143
|
+
const legendShowingLeftOrRight = !isForestPlot && !legendHidden && !legendOnTopOrBottom && !legendWrapped
|
|
144
|
+
|
|
145
|
+
if (!legendShowingLeftOrRight) return initialWidth
|
|
146
|
+
|
|
147
|
+
if (legendRef.current) {
|
|
148
|
+
const legendStyle = getComputedStyle(legendRef.current)
|
|
149
|
+
return (
|
|
150
|
+
initialWidth -
|
|
151
|
+
legendRef.current.getBoundingClientRect().width -
|
|
152
|
+
parseInt(legendStyle.marginLeft) -
|
|
153
|
+
parseInt(legendStyle.marginRight)
|
|
154
|
+
)
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
return initialWidth * 0.73
|
|
158
|
+
}, [dimensions[0], config.legend, currentViewport, legendRef.current])
|
|
159
|
+
|
|
160
|
+
// Used to calculate the y position of the x-axis title
|
|
161
|
+
const bottomLabelStart = useMemo(() => {
|
|
162
|
+
xAxisLabelRefs.current = xAxisLabelRefs.current?.filter(label => label)
|
|
163
|
+
if (!xAxisLabelRefs.current.length) return
|
|
164
|
+
const tallestLabel = Math.max(...xAxisLabelRefs.current.map(label => label.getBBox().height))
|
|
165
|
+
return tallestLabel + X_TICK_LABEL_PADDING + DEFAULT_TICK_LENGTH
|
|
166
|
+
}, [dimensions[0], config.xAxis, xAxisLabelRefs.current, config.xAxis.tickRotation])
|
|
167
|
+
|
|
168
|
+
// xMax and yMax
|
|
169
|
+
const xMax = width - runtime.yAxis.size - (visualizationType === 'Combo' ? config.yAxis.rightAxisSize : 0)
|
|
170
|
+
const yMax = initialHeight + forestRowsHeight
|
|
171
|
+
|
|
172
|
+
const checkLineToBarGraph = () => {
|
|
173
|
+
return isConvertLineToBarGraph(config.visualizationType, data, config.allowLineToBarGraph)
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
// GETTERS & FUNCTIONS
|
|
131
177
|
const getXAxisData = d =>
|
|
132
178
|
isDateScale(config.runtime.xAxis)
|
|
133
179
|
? parseDate(d[config.runtime.originalXAxis.dataKey]).getTime()
|
|
@@ -159,16 +205,10 @@ const LinearChart: React.FC<LinearChartProps> = props => {
|
|
|
159
205
|
leftMax,
|
|
160
206
|
rightMax,
|
|
161
207
|
dimensions,
|
|
162
|
-
xMax:
|
|
208
|
+
xMax: parentWidth - Number(config.orientation === 'horizontal' ? config.xAxis.size : config.yAxis.size)
|
|
163
209
|
})
|
|
164
210
|
|
|
165
|
-
|
|
166
|
-
const [chartPosition, setChartPosition] = useState(null)
|
|
167
|
-
useEffect(() => {
|
|
168
|
-
setChartPosition(svgRef?.current?.getBoundingClientRect())
|
|
169
|
-
}, [svgRef, config.legend])
|
|
170
|
-
|
|
171
|
-
const handleLeftTickFormatting = (tick, index) => {
|
|
211
|
+
const handleLeftTickFormatting = (tick, index, ticks) => {
|
|
172
212
|
if (isLogarithmicAxis && tick === 0.1) {
|
|
173
213
|
//when logarithmic scale applied change value of first tick
|
|
174
214
|
tick = 0
|
|
@@ -178,18 +218,23 @@ const LinearChart: React.FC<LinearChartProps> = props => {
|
|
|
178
218
|
if (config.visualizationType === 'Forest Plot') return config.data[index][config.xAxis.dataKey]
|
|
179
219
|
if (isDateScale(runtime.yAxis)) return formatDate(parseDate(tick))
|
|
180
220
|
if (orientation === 'vertical' && max - min < 3)
|
|
181
|
-
return formatNumber(tick, 'left', shouldAbbreviate, false, false, '1')
|
|
182
|
-
if (orientation === 'vertical')
|
|
221
|
+
return formatNumber(tick, 'left', shouldAbbreviate, false, false, '1', { index, length: ticks.length })
|
|
222
|
+
if (orientation === 'vertical') {
|
|
223
|
+
// TODO suggestion: pass all options as object key/values to allow for more flexibility
|
|
224
|
+
return formatNumber(tick, 'left', shouldAbbreviate, false, false, undefined, { index, length: ticks.length })
|
|
225
|
+
}
|
|
183
226
|
return tick
|
|
184
227
|
}
|
|
185
228
|
|
|
186
|
-
const handleBottomTickFormatting = tick => {
|
|
229
|
+
const handleBottomTickFormatting = (tick, i, ticks) => {
|
|
187
230
|
if (isLogarithmicAxis && tick === 0.1) {
|
|
188
231
|
// when logarithmic scale applied change value FIRST of tick
|
|
189
232
|
tick = 0
|
|
190
233
|
}
|
|
191
234
|
|
|
192
|
-
if (isDateScale(runtime.xAxis) && config.visualizationType !== 'Forest Plot')
|
|
235
|
+
if (isDateScale(runtime.xAxis) && config.visualizationType !== 'Forest Plot') {
|
|
236
|
+
return formatDate(tick, i, ticks)
|
|
237
|
+
}
|
|
193
238
|
if (orientation === 'horizontal' && config.visualizationType !== 'Forest Plot')
|
|
194
239
|
return formatNumber(tick, 'left', shouldAbbreviate)
|
|
195
240
|
if (config.xAxis.type === 'continuous' && config.visualizationType !== 'Forest Plot')
|
|
@@ -231,7 +276,7 @@ const LinearChart: React.FC<LinearChartProps> = props => {
|
|
|
231
276
|
tickCount = 4 // same default as standalone components
|
|
232
277
|
}
|
|
233
278
|
}
|
|
234
|
-
if (Number(tickCount) > Number(max)) {
|
|
279
|
+
if (Number(tickCount) > Number(max) && !isHorizontal) {
|
|
235
280
|
// cap it and round it so its an integer
|
|
236
281
|
tickCount = Number(min) < 0 ? Math.round(max) * 2 : Math.round(max)
|
|
237
282
|
}
|
|
@@ -272,9 +317,7 @@ const LinearChart: React.FC<LinearChartProps> = props => {
|
|
|
272
317
|
handleTooltipMouseOver,
|
|
273
318
|
handleTooltipClick,
|
|
274
319
|
handleTooltipMouseOff,
|
|
275
|
-
tooltipStyles,
|
|
276
320
|
TooltipListItem,
|
|
277
|
-
getXValueFromCoordinateDate,
|
|
278
321
|
getXValueFromCoordinate
|
|
279
322
|
} = useCoveTooltip({
|
|
280
323
|
xScale,
|
|
@@ -283,6 +326,8 @@ const LinearChart: React.FC<LinearChartProps> = props => {
|
|
|
283
326
|
hideTooltip
|
|
284
327
|
})
|
|
285
328
|
|
|
329
|
+
// EFFECTS
|
|
330
|
+
|
|
286
331
|
// Make sure the chart is visible if in the editor
|
|
287
332
|
/* eslint-disable react-hooks/exhaustive-deps */
|
|
288
333
|
useEffect(() => {
|
|
@@ -303,10 +348,66 @@ const LinearChart: React.FC<LinearChartProps> = props => {
|
|
|
303
348
|
}, [dataRef?.isIntersecting, config.animate])
|
|
304
349
|
|
|
305
350
|
useEffect(() => {
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
351
|
+
const suffixEl = suffixRef.current
|
|
352
|
+
if (!suffixEl && !suffixWidth) return
|
|
353
|
+
if (!suffixEl) return setSuffixWidth(0)
|
|
354
|
+
const suffixElWidth = suffixEl.getBBox().width
|
|
355
|
+
setSuffixWidth(suffixElWidth)
|
|
356
|
+
}, [config.dataFormat.suffix, config.dataFormat.onlyShowTopPrefixSuffix])
|
|
357
|
+
|
|
358
|
+
// forest plot x-axis label positioning
|
|
359
|
+
useEffect(() => {
|
|
360
|
+
if (!isForestPlot || xAxis.hideLabel) return
|
|
361
|
+
|
|
362
|
+
const rightLabel = forestPlotRightLabelRef.current
|
|
363
|
+
|
|
364
|
+
if (!rightLabel) return
|
|
365
|
+
|
|
366
|
+
const axisBottomY = yMax + Number(config.xAxis.axisPadding)
|
|
367
|
+
const labelRelativeY = rightLabel.getBBox().y - axisBottomY
|
|
368
|
+
const xLabelY = labelRelativeY + rightLabel.getBBox().height + BOTTOM_LABEL_PADDING
|
|
369
|
+
if (!xAxisTitleRef.current) return
|
|
370
|
+
xAxisTitleRef.current.setAttribute('y', xLabelY)
|
|
371
|
+
}, [config?.data?.length, forestRowsHeight])
|
|
372
|
+
|
|
373
|
+
// Parent height adjustments
|
|
374
|
+
useEffect(() => {
|
|
375
|
+
if (!axisBottomRef.current) return
|
|
376
|
+
const axisBottomHeight = axisBottomRef.current.getBBox().height
|
|
377
|
+
|
|
378
|
+
const isForestPlot = visualizationType === 'Forest Plot'
|
|
379
|
+
const topLabelOnGridline = topYLabelRef.current && yAxis.labelsAboveGridlines
|
|
380
|
+
|
|
381
|
+
// Heights to add
|
|
382
|
+
const brushHeight = brush?.active ? brush?.height : 0
|
|
383
|
+
const forestRowsHeight = isForestPlot ? config.data.length * forestPlot.rowHeight : 0
|
|
384
|
+
const topLabelOnGridlineHeight = topLabelOnGridline ? topYLabelRef.current.getBBox().height : 0
|
|
385
|
+
const additionalHeight = axisBottomHeight + brushHeight + forestRowsHeight + topLabelOnGridlineHeight
|
|
386
|
+
const newHeight = initialHeight + additionalHeight
|
|
387
|
+
if (!parentRef.current) return
|
|
388
|
+
|
|
389
|
+
parentRef.current.style.height = `${newHeight}px`
|
|
390
|
+
|
|
391
|
+
/* Adding text above the top gridline overflows the bounds of the svg.
|
|
392
|
+
To accommodate for this we need to...
|
|
393
|
+
1. Add the extra height to the svg (done above)
|
|
394
|
+
2. Adjust the viewBox to move the intended top height into focus
|
|
395
|
+
3. if the legend is on the left or right, translate it by
|
|
396
|
+
the label height so it is aligned with the top border */
|
|
397
|
+
if (!topLabelOnGridlineHeight) return
|
|
398
|
+
|
|
399
|
+
// Adjust the viewBox for the svg
|
|
400
|
+
const svg = svgRef.current
|
|
401
|
+
if (!svg) return
|
|
402
|
+
const parentWidthFromRef = parentRef.current.getBoundingClientRect().width
|
|
403
|
+
svg.setAttribute('viewBox', `0 ${-topLabelOnGridlineHeight} ${parentWidthFromRef} ${newHeight}`)
|
|
404
|
+
|
|
405
|
+
// translate legend match viewBox-adjusted height
|
|
406
|
+
if (!legendRef.current) return
|
|
407
|
+
const legendIsLeftOrRight =
|
|
408
|
+
legend?.position !== 'top' && legend?.position !== 'bottom' && !isLegendWrapViewport(currentViewport)
|
|
409
|
+
legendRef.current.style.transform = legendIsLeftOrRight ? `translateY(${topLabelOnGridlineHeight}px)` : 'none'
|
|
410
|
+
}, [axisBottomRef.current, config, bottomLabelStart, brush, currentViewport, topYLabelRef.current, initialHeight])
|
|
310
411
|
|
|
311
412
|
const chartHasTooltipGuides = () => {
|
|
312
413
|
const { visualizationType } = config
|
|
@@ -346,7 +447,7 @@ const LinearChart: React.FC<LinearChartProps> = props => {
|
|
|
346
447
|
}
|
|
347
448
|
|
|
348
449
|
const generatePairedBarAxis = () => {
|
|
349
|
-
|
|
450
|
+
const axisMaxHeight = bottomLabelStart + BOTTOM_LABEL_PADDING
|
|
350
451
|
|
|
351
452
|
const getTickPositions = (ticks, xScale) => {
|
|
352
453
|
if (!ticks.length) return false
|
|
@@ -401,16 +502,14 @@ const LinearChart: React.FC<LinearChartProps> = props => {
|
|
|
401
502
|
const isResponsiveTicks = config.isResponsiveTicks && isTicksOverlapping
|
|
402
503
|
const angle =
|
|
403
504
|
tick.index !== 0 && (isResponsiveTicks ? maxTickRotation : Number(config.yAxis.tickRotation))
|
|
404
|
-
const axisHeight = textWidth * Math.sin(angle * (Math.PI / 180)) + 25
|
|
405
505
|
const textAnchor = angle && tick.index !== 0 ? 'end' : 'middle'
|
|
406
506
|
|
|
407
|
-
if (axisHeight > axisMaxHeight) axisMaxHeight = axisHeight
|
|
408
|
-
|
|
409
507
|
return (
|
|
410
508
|
<Group key={`vx-tick-${tick.value}-${i}`} className={'vx-axis-tick'}>
|
|
411
509
|
{!runtime.yAxis.hideTicks && <Line from={tick.from} to={tick.to} stroke='#333' />}
|
|
412
510
|
{!runtime.yAxis.hideLabel && (
|
|
413
511
|
<Text // prettier-ignore
|
|
512
|
+
innerRef={el => (xAxisLabelRefs.current[i] = el)}
|
|
414
513
|
x={tick.to.x}
|
|
415
514
|
y={tick.to.y}
|
|
416
515
|
angle={-angle}
|
|
@@ -429,6 +528,7 @@ const LinearChart: React.FC<LinearChartProps> = props => {
|
|
|
429
528
|
}}
|
|
430
529
|
</AxisBottom>
|
|
431
530
|
<AxisBottom
|
|
531
|
+
innerRef={axisBottomRef}
|
|
432
532
|
top={yMax}
|
|
433
533
|
left={Number(runtime.yAxis.size)}
|
|
434
534
|
label={runtime.xAxis.label}
|
|
@@ -454,18 +554,15 @@ const LinearChart: React.FC<LinearChartProps> = props => {
|
|
|
454
554
|
const isResponsiveTicks = config.isResponsiveTicks && isTicksOverlapping
|
|
455
555
|
const angle =
|
|
456
556
|
tick.index !== 0 && (isResponsiveTicks ? maxTickRotation : Number(config.yAxis.tickRotation))
|
|
457
|
-
const axisHeight = textWidth * Math.sin(angle * (Math.PI / 180)) + 25
|
|
458
557
|
const textAnchor = angle && tick.index !== 0 ? 'end' : 'middle'
|
|
459
|
-
|
|
460
|
-
if (axisHeight > axisMaxHeight) axisMaxHeight = axisHeight
|
|
461
|
-
|
|
558
|
+
if (!i) return <></> // skip first tick to avoid overlapping 0's
|
|
462
559
|
return (
|
|
463
560
|
<Group key={`vx-tick-${tick.value}-${i}`} className={'vx-axis-tick'}>
|
|
464
561
|
{!runtime.yAxis.hideTicks && <Line from={tick.from} to={tick.to} stroke='#333' />}
|
|
465
562
|
{!runtime.yAxis.hideLabel && (
|
|
466
563
|
<Text // prettier-ignore
|
|
467
564
|
x={tick.to.x}
|
|
468
|
-
y={tick.to.y}
|
|
565
|
+
y={tick.to.y + X_TICK_LABEL_PADDING}
|
|
469
566
|
angle={-angle}
|
|
470
567
|
verticalAnchor={angle ? 'middle' : 'start'}
|
|
471
568
|
textAnchor={textAnchor}
|
|
@@ -480,8 +577,9 @@ const LinearChart: React.FC<LinearChartProps> = props => {
|
|
|
480
577
|
</Group>
|
|
481
578
|
<Group>
|
|
482
579
|
<Text
|
|
580
|
+
className='x-axis-title-label'
|
|
483
581
|
x={xMax / 2}
|
|
484
|
-
y={axisMaxHeight
|
|
582
|
+
y={axisMaxHeight}
|
|
485
583
|
stroke='#333'
|
|
486
584
|
textAnchor={'middle'}
|
|
487
585
|
verticalAnchor='start'
|
|
@@ -489,12 +587,6 @@ const LinearChart: React.FC<LinearChartProps> = props => {
|
|
|
489
587
|
{runtime.xAxis.label}
|
|
490
588
|
</Text>
|
|
491
589
|
</Group>
|
|
492
|
-
{svgRef.current
|
|
493
|
-
? svgRef.current.setAttribute(
|
|
494
|
-
'height',
|
|
495
|
-
Number(height) + Number(axisMaxHeight) + (runtime.xAxis.label ? 50 : 0) + 'px'
|
|
496
|
-
)
|
|
497
|
-
: ''}
|
|
498
590
|
</>
|
|
499
591
|
)
|
|
500
592
|
}}
|
|
@@ -508,32 +600,29 @@ const LinearChart: React.FC<LinearChartProps> = props => {
|
|
|
508
600
|
) : (
|
|
509
601
|
<ErrorBoundary component='LinearChart'>
|
|
510
602
|
{/* ! Notice - div needed for tooltip boundaries (flip/flop) */}
|
|
511
|
-
<div
|
|
603
|
+
<div
|
|
604
|
+
style={{ width: `${parentWidth}px`, overflow: 'visible', position: 'relative' }}
|
|
605
|
+
className='tooltip-boundary'
|
|
606
|
+
>
|
|
512
607
|
<svg
|
|
608
|
+
ref={svgRef}
|
|
513
609
|
onMouseMove={onMouseMove}
|
|
514
|
-
width={
|
|
515
|
-
height={
|
|
610
|
+
width={parentWidth}
|
|
611
|
+
height={parentHeight}
|
|
516
612
|
className={`linear ${config.animate ? 'animated' : ''} ${animatedChart && config.animate ? 'animate' : ''} ${
|
|
517
613
|
debugSvg && 'debug'
|
|
518
614
|
} ${isDraggingAnnotation && 'dragging-annotation'}`}
|
|
519
615
|
role='img'
|
|
520
616
|
aria-label={handleChartAriaLabels(config)}
|
|
521
|
-
ref={svgRef}
|
|
522
617
|
style={{ overflow: 'visible' }}
|
|
523
618
|
>
|
|
524
|
-
{!isDraggingAnnotation &&
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
{/* Highlighted regions */}
|
|
528
|
-
{/* Y axis */}
|
|
619
|
+
{!isDraggingAnnotation && <Bar width={parentWidth} height={initialHeight} fill={'transparent'}></Bar>}{' '}
|
|
620
|
+
{/* GRID LINES */}
|
|
621
|
+
{/* Actual AxisLeft is drawn after visualization */}
|
|
529
622
|
{!['Spark Line', 'Forest Plot'].includes(visualizationType) && config.yAxis.type !== 'categorical' && (
|
|
530
623
|
<AxisLeft
|
|
531
624
|
scale={yScale}
|
|
532
|
-
tickLength={isLogarithmicAxis ? 6 : 8}
|
|
533
625
|
left={Number(runtime.yAxis.size) - config.yAxis.axisPadding}
|
|
534
|
-
label={runtime.yAxis.label || runtime.yAxis.label}
|
|
535
|
-
stroke='#333'
|
|
536
|
-
tickFormat={(tick, i) => handleLeftTickFormatting(tick, i)}
|
|
537
626
|
numTicks={handleNumTicks()}
|
|
538
627
|
>
|
|
539
628
|
{props => {
|
|
@@ -541,185 +630,28 @@ const LinearChart: React.FC<LinearChartProps> = props => {
|
|
|
541
630
|
config.orientation === 'horizontal'
|
|
542
631
|
? (props.axisToPoint.y - props.axisFromPoint.y) / 2
|
|
543
632
|
: (props.axisFromPoint.y - props.axisToPoint.y) / 2
|
|
544
|
-
const horizontalTickOffset =
|
|
545
|
-
yMax / props.ticks.length / 2 - (yMax / props.ticks.length) * (1 - config.barThickness) + 5
|
|
546
633
|
return (
|
|
547
634
|
<Group className='left-axis'>
|
|
548
635
|
{props.ticks.map((tick, i) => {
|
|
549
|
-
const minY = props.ticks[0].to.y
|
|
550
|
-
const barMinHeight = 15 // 15 is the min height for bars by default
|
|
551
636
|
const showTicks = String(tick.value).startsWith('1') || tick.value === 0.1 ? 'block' : 'none'
|
|
552
|
-
const tickLength = showTicks === 'block' ? 7 : 0
|
|
553
|
-
const to = { x: tick.to.x - tickLength, y: tick.to.y }
|
|
554
637
|
const hideFirstGridLine = tick.index === 0 && tick.value === 0 && config.xAxis.hideAxis
|
|
555
638
|
|
|
556
639
|
return (
|
|
557
640
|
<Group key={`vx-tick-${tick.value}-${i}`} className={'vx-axis-tick'}>
|
|
558
|
-
{!runtime.yAxis.hideTicks && (
|
|
559
|
-
<Line
|
|
560
|
-
key={`${tick.value}--hide-hideTicks`}
|
|
561
|
-
from={tick.from}
|
|
562
|
-
to={isLogarithmicAxis ? to : tick.to}
|
|
563
|
-
stroke={config.yAxis.tickColor}
|
|
564
|
-
display={orientation === 'horizontal' ? 'none' : 'block'}
|
|
565
|
-
/>
|
|
566
|
-
)}
|
|
567
|
-
|
|
568
641
|
{runtime.yAxis.gridLines && !hideFirstGridLine ? (
|
|
569
642
|
<Line
|
|
570
643
|
key={`${tick.value}--hide-hideGridLines`}
|
|
571
644
|
display={(isLogarithmicAxis && showTicks).toString()}
|
|
572
645
|
from={{ x: tick.from.x + xMax, y: tick.from.y }}
|
|
573
646
|
to={tick.from}
|
|
574
|
-
stroke='
|
|
647
|
+
stroke='#d6d6d6'
|
|
575
648
|
/>
|
|
576
649
|
) : (
|
|
577
650
|
''
|
|
578
651
|
)}
|
|
579
|
-
|
|
580
|
-
{orientation === 'horizontal' &&
|
|
581
|
-
visualizationSubType !== 'stacked' &&
|
|
582
|
-
config.yAxis.labelPlacement === 'On Date/Category Axis' &&
|
|
583
|
-
!config.yAxis.hideLabel && (
|
|
584
|
-
<Text
|
|
585
|
-
transform={`translate(${tick.to.x - 5}, ${
|
|
586
|
-
config.isLollipopChart
|
|
587
|
-
? tick.to.y - minY
|
|
588
|
-
: tick.to.y -
|
|
589
|
-
minY +
|
|
590
|
-
(Number(config.barHeight * config.runtime.series.length) - barMinHeight) / 2
|
|
591
|
-
}) rotate(-${config.runtime.horizontal ? config.runtime.yAxis.tickRotation || 0 : 0})`}
|
|
592
|
-
verticalAnchor={'start'}
|
|
593
|
-
textAnchor={'end'}
|
|
594
|
-
>
|
|
595
|
-
{tick.formattedValue}
|
|
596
|
-
</Text>
|
|
597
|
-
)}
|
|
598
|
-
|
|
599
|
-
{orientation === 'horizontal' &&
|
|
600
|
-
visualizationSubType === 'stacked' &&
|
|
601
|
-
config.yAxis.labelPlacement === 'On Date/Category Axis' &&
|
|
602
|
-
!config.yAxis.hideLabel && (
|
|
603
|
-
<Text
|
|
604
|
-
transform={`translate(${tick.to.x - 5}, ${
|
|
605
|
-
tick.to.y - minY + (Number(config.barHeight) - barMinHeight) / 2
|
|
606
|
-
}) rotate(-${runtime.horizontal ? runtime.yAxis.tickRotation : 0})`}
|
|
607
|
-
verticalAnchor={'start'}
|
|
608
|
-
textAnchor={'end'}
|
|
609
|
-
>
|
|
610
|
-
{tick.formattedValue}
|
|
611
|
-
</Text>
|
|
612
|
-
)}
|
|
613
|
-
|
|
614
|
-
{orientation === 'horizontal' &&
|
|
615
|
-
visualizationType === 'Paired Bar' &&
|
|
616
|
-
!config.yAxis.hideLabel && (
|
|
617
|
-
<Text
|
|
618
|
-
transform={`translate(${tick.to.x - 5}, ${
|
|
619
|
-
tick.to.y - minY + Number(config.barHeight) / 2
|
|
620
|
-
}) rotate(-${runtime.horizontal ? runtime.yAxis.tickRotation : 0})`}
|
|
621
|
-
textAnchor={'end'}
|
|
622
|
-
verticalAnchor='middle'
|
|
623
|
-
>
|
|
624
|
-
{tick.formattedValue}
|
|
625
|
-
</Text>
|
|
626
|
-
)}
|
|
627
|
-
{orientation === 'horizontal' &&
|
|
628
|
-
visualizationType === 'Deviation Bar' &&
|
|
629
|
-
!config.yAxis.hideLabel && (
|
|
630
|
-
<Text
|
|
631
|
-
transform={`translate(${tick.to.x - 5}, ${
|
|
632
|
-
config.isLollipopChart
|
|
633
|
-
? tick.to.y - minY + 2
|
|
634
|
-
: tick.to.y - minY + Number(config.barHeight) / 2
|
|
635
|
-
}) rotate(-${runtime.horizontal ? runtime.yAxis.tickRotation : 0})`}
|
|
636
|
-
textAnchor={'end'}
|
|
637
|
-
verticalAnchor='middle'
|
|
638
|
-
>
|
|
639
|
-
{tick.formattedValue}
|
|
640
|
-
</Text>
|
|
641
|
-
)}
|
|
642
|
-
|
|
643
|
-
{orientation === 'vertical' &&
|
|
644
|
-
visualizationType === 'Bump Chart' &&
|
|
645
|
-
!config.yAxis.hideLabel && (
|
|
646
|
-
<>
|
|
647
|
-
<Text
|
|
648
|
-
display={config.useLogScale ? showTicks : 'block'}
|
|
649
|
-
dx={config.useLogScale ? -6 : 0}
|
|
650
|
-
x={config.runtime.horizontal ? tick.from.x + 2 : tick.to.x - 8.5}
|
|
651
|
-
y={tick.to.y - 13 + (config.runtime.horizontal ? horizontalTickOffset : 0)}
|
|
652
|
-
angle={-Number(config.yAxis.tickRotation) || 0}
|
|
653
|
-
verticalAnchor={config.runtime.horizontal ? 'start' : 'middle'}
|
|
654
|
-
textAnchor={config.runtime.horizontal ? 'start' : 'end'}
|
|
655
|
-
fill={config.yAxis.tickLabelColor}
|
|
656
|
-
>
|
|
657
|
-
{config.runtime.seriesLabelsAll[tick.formattedValue - 1]}
|
|
658
|
-
</Text>
|
|
659
|
-
|
|
660
|
-
{(seriesHighlight.length === 0 ||
|
|
661
|
-
seriesHighlight.includes(
|
|
662
|
-
config.runtime.seriesLabelsAll[tick.formattedValue - 1]
|
|
663
|
-
)) && (
|
|
664
|
-
<rect
|
|
665
|
-
x={0 - Number(config.yAxis.size)}
|
|
666
|
-
y={tick.to.y - 8 + (config.runtime.horizontal ? horizontalTickOffset : 7)}
|
|
667
|
-
width={Number(config.yAxis.size) + xScale(xScale.domain()[0])}
|
|
668
|
-
height='2'
|
|
669
|
-
fill={colorScale(config.runtime.seriesLabelsAll[tick.formattedValue - 1])}
|
|
670
|
-
/>
|
|
671
|
-
)}
|
|
672
|
-
</>
|
|
673
|
-
)}
|
|
674
|
-
{orientation === 'vertical' &&
|
|
675
|
-
visualizationType !== 'Paired Bar' &&
|
|
676
|
-
visualizationType !== 'Bump Chart' &&
|
|
677
|
-
!config.yAxis.hideLabel && (
|
|
678
|
-
<Text
|
|
679
|
-
display={isLogarithmicAxis ? showTicks : 'block'}
|
|
680
|
-
dx={isLogarithmicAxis ? -6 : 0}
|
|
681
|
-
x={config.runtime.horizontal ? tick.from.x + 2 : tick.to.x}
|
|
682
|
-
y={tick.to.y + (config.runtime.horizontal ? horizontalTickOffset : 0)}
|
|
683
|
-
angle={-Number(config.yAxis.tickRotation) || 0}
|
|
684
|
-
verticalAnchor={config.runtime.horizontal ? 'start' : 'middle'}
|
|
685
|
-
textAnchor={config.runtime.horizontal ? 'start' : 'end'}
|
|
686
|
-
fill={config.yAxis.tickLabelColor}
|
|
687
|
-
>
|
|
688
|
-
{tick.formattedValue}
|
|
689
|
-
</Text>
|
|
690
|
-
)}
|
|
691
652
|
</Group>
|
|
692
653
|
)
|
|
693
654
|
})}
|
|
694
|
-
{!config.yAxis.hideAxis && (
|
|
695
|
-
<Line
|
|
696
|
-
from={props.axisFromPoint}
|
|
697
|
-
to={
|
|
698
|
-
runtime.horizontal
|
|
699
|
-
? {
|
|
700
|
-
x: 0,
|
|
701
|
-
y: config.visualizationType === 'Forest Plot' ? height : Number(heightHorizontal)
|
|
702
|
-
}
|
|
703
|
-
: props.axisToPoint
|
|
704
|
-
}
|
|
705
|
-
stroke='#000'
|
|
706
|
-
/>
|
|
707
|
-
)}
|
|
708
|
-
{yScale.domain()[0] < 0 && (
|
|
709
|
-
<Line
|
|
710
|
-
from={{ x: props.axisFromPoint.x, y: yScale(0) }}
|
|
711
|
-
to={{ x: xMax, y: yScale(0) }}
|
|
712
|
-
stroke='#333'
|
|
713
|
-
/>
|
|
714
|
-
)}
|
|
715
|
-
{visualizationType === 'Bar' && orientation === 'horizontal' && xScale.domain()[0] < 0 && (
|
|
716
|
-
<Line
|
|
717
|
-
from={{ x: xScale(0), y: 0 }}
|
|
718
|
-
to={{ x: xScale(0), y: yMax }}
|
|
719
|
-
stroke='#333'
|
|
720
|
-
strokeWidth={2}
|
|
721
|
-
/>
|
|
722
|
-
)}
|
|
723
655
|
<Text
|
|
724
656
|
className='y-label'
|
|
725
657
|
textAnchor='middle'
|
|
@@ -735,290 +667,37 @@ const LinearChart: React.FC<LinearChartProps> = props => {
|
|
|
735
667
|
}}
|
|
736
668
|
</AxisLeft>
|
|
737
669
|
)}
|
|
738
|
-
{
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
670
|
+
{visualizationType === 'Paired Bar' && generatePairedBarAxis()}
|
|
671
|
+
{visualizationType === 'Deviation Bar' && config.runtime.series?.length === 1 && (
|
|
672
|
+
<DeviationBar animatedChart={animatedChart} xScale={xScale} yScale={yScale} width={xMax} height={yMax} />
|
|
673
|
+
)}
|
|
674
|
+
{visualizationType === 'Paired Bar' && <PairedBarChart originalWidth={width} width={xMax} height={yMax} />}
|
|
675
|
+
{visualizationType === 'Scatter Plot' && (
|
|
676
|
+
<ScatterPlot
|
|
677
|
+
xScale={xScale}
|
|
678
|
+
yScale={yScale}
|
|
679
|
+
getXAxisData={getXAxisData}
|
|
680
|
+
getYAxisData={getYAxisData}
|
|
743
681
|
xMax={xMax}
|
|
744
682
|
yMax={yMax}
|
|
745
|
-
|
|
683
|
+
handleTooltipMouseOver={handleTooltipMouseOver}
|
|
684
|
+
handleTooltipMouseOff={handleTooltipMouseOff}
|
|
685
|
+
handleTooltipClick={handleTooltipClick}
|
|
686
|
+
tooltipData={tooltipData}
|
|
687
|
+
showTooltip={showTooltip}
|
|
746
688
|
/>
|
|
747
689
|
)}
|
|
748
|
-
{
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
scale={yScaleRight}
|
|
752
|
-
left={Number(width - config.yAxis.rightAxisSize)}
|
|
753
|
-
label={config.yAxis.rightLabel}
|
|
754
|
-
tickFormat={tick => formatNumber(tick, 'right')}
|
|
755
|
-
numTicks={runtime.yAxis.rightNumTicks || undefined}
|
|
756
|
-
labelOffset={45}
|
|
757
|
-
>
|
|
758
|
-
{props => {
|
|
759
|
-
const axisCenter =
|
|
760
|
-
config.orientation === 'horizontal'
|
|
761
|
-
? (props.axisToPoint.y - props.axisFromPoint.y) / 2
|
|
762
|
-
: (props.axisFromPoint.y - props.axisToPoint.y) / 2
|
|
763
|
-
const horizontalTickOffset =
|
|
764
|
-
yMax / props.ticks.length / 2 - (yMax / props.ticks.length) * (1 - config.barThickness) + 5
|
|
765
|
-
return (
|
|
766
|
-
<Group className='right-axis'>
|
|
767
|
-
{props.ticks.map((tick, i) => {
|
|
768
|
-
return (
|
|
769
|
-
<Group key={`vx-tick-${tick.value}-${i}`} className='vx-axis-tick'>
|
|
770
|
-
{!runtime.yAxis.rightHideTicks && (
|
|
771
|
-
<Line
|
|
772
|
-
from={tick.from}
|
|
773
|
-
to={tick.to}
|
|
774
|
-
display={runtime.horizontal ? 'none' : 'block'}
|
|
775
|
-
stroke={config.yAxis.rightAxisTickColor}
|
|
776
|
-
/>
|
|
777
|
-
)}
|
|
778
|
-
|
|
779
|
-
{runtime.yAxis.rightGridLines ? (
|
|
780
|
-
<Line
|
|
781
|
-
from={{ x: tick.from.x + xMax, y: tick.from.y }}
|
|
782
|
-
to={tick.from}
|
|
783
|
-
stroke='rgba(0,0,0,0.3)'
|
|
784
|
-
/>
|
|
785
|
-
) : (
|
|
786
|
-
''
|
|
787
|
-
)}
|
|
788
|
-
|
|
789
|
-
{!config.yAxis.rightHideLabel && (
|
|
790
|
-
<Text
|
|
791
|
-
x={tick.to.x}
|
|
792
|
-
y={tick.to.y + (runtime.horizontal ? horizontalTickOffset : 0)}
|
|
793
|
-
verticalAnchor={runtime.horizontal ? 'start' : 'middle'}
|
|
794
|
-
textAnchor={'start'}
|
|
795
|
-
fill={config.yAxis.rightAxisTickLabelColor}
|
|
796
|
-
>
|
|
797
|
-
{tick.formattedValue}
|
|
798
|
-
</Text>
|
|
799
|
-
)}
|
|
800
|
-
</Group>
|
|
801
|
-
)
|
|
802
|
-
})}
|
|
803
|
-
{!config.yAxis.rightHideAxis && (
|
|
804
|
-
<Line from={props.axisFromPoint} to={props.axisToPoint} stroke='#333' />
|
|
805
|
-
)}
|
|
806
|
-
<Text
|
|
807
|
-
className='y-label'
|
|
808
|
-
textAnchor='middle'
|
|
809
|
-
verticalAnchor='start'
|
|
810
|
-
transform={`translate(${
|
|
811
|
-
config.yAxis.rightLabelOffsetSize ? config.yAxis.rightLabelOffsetSize : 0
|
|
812
|
-
}, ${axisCenter}) rotate(-90)`}
|
|
813
|
-
fontWeight='bold'
|
|
814
|
-
fill={config.yAxis.rightAxisLabelColor}
|
|
815
|
-
>
|
|
816
|
-
{props.label}
|
|
817
|
-
</Text>
|
|
818
|
-
</Group>
|
|
819
|
-
)
|
|
820
|
-
}}
|
|
821
|
-
</AxisRight>
|
|
822
|
-
)}
|
|
823
|
-
{hasTopAxis && config.topAxis.hasLine && (
|
|
824
|
-
<AxisTop
|
|
825
|
-
stroke='#333'
|
|
826
|
-
left={Number(runtime.yAxis.size)}
|
|
827
|
-
scale={xScale}
|
|
828
|
-
hideTicks
|
|
829
|
-
hideZero
|
|
830
|
-
tickLabelProps={() => ({
|
|
831
|
-
fill: 'transparent'
|
|
832
|
-
})}
|
|
833
|
-
/>
|
|
834
|
-
)}
|
|
835
|
-
{/* X axis */}
|
|
836
|
-
{visualizationType !== 'Paired Bar' && visualizationType !== 'Spark Line' && (
|
|
837
|
-
<AxisBottom
|
|
838
|
-
innerRef={axisBottomRef}
|
|
839
|
-
top={
|
|
840
|
-
runtime.horizontal && config.visualizationType !== 'Forest Plot'
|
|
841
|
-
? Number(heightHorizontal) + Number(config.xAxis.axisPadding)
|
|
842
|
-
: config.visualizationType === 'Forest Plot'
|
|
843
|
-
? yMax + Number(config.xAxis.axisPadding)
|
|
844
|
-
: yMax
|
|
845
|
-
}
|
|
846
|
-
left={config.visualizationType !== 'Forest Plot' ? Number(runtime.yAxis.size) : 0}
|
|
847
|
-
label={config[section].label}
|
|
848
|
-
tickFormat={handleBottomTickFormatting}
|
|
849
|
-
scale={xScale}
|
|
850
|
-
stroke='#333'
|
|
851
|
-
numTicks={countNumOfTicks('xAxis')}
|
|
852
|
-
tickStroke='#333'
|
|
853
|
-
tickValues={
|
|
854
|
-
config.xAxis.manual
|
|
855
|
-
? getTickValues(
|
|
856
|
-
xAxisDataMapped,
|
|
857
|
-
xScale,
|
|
858
|
-
config.xAxis.type === 'date-time' ? countNumOfTicks('xAxis') : getManualStep()
|
|
859
|
-
)
|
|
860
|
-
: undefined
|
|
861
|
-
}
|
|
862
|
-
>
|
|
863
|
-
{props => {
|
|
864
|
-
const axisCenter =
|
|
865
|
-
config.visualizationType !== 'Forest Plot'
|
|
866
|
-
? (props.axisToPoint.x - props.axisFromPoint.x) / 2
|
|
867
|
-
: dimensions[0] / 2
|
|
868
|
-
const containsMultipleWords = inputString => /\s/.test(inputString)
|
|
869
|
-
const ismultiLabel = props.ticks.some(tick => containsMultipleWords(tick.value))
|
|
870
|
-
|
|
871
|
-
// Calculate sumOfTickWidth here, before map function
|
|
872
|
-
const defaultTickLength = 8
|
|
873
|
-
const tickWidthMax = Math.max(
|
|
874
|
-
...props.ticks.map(tick =>
|
|
875
|
-
getTextWidth(tick.formattedValue, `normal ${fontSize[config.fontSize]}px sans-serif`)
|
|
876
|
-
)
|
|
877
|
-
)
|
|
878
|
-
// const marginTop = 20 // moved to top bc need for yMax calcs
|
|
879
|
-
const accumulator = ismultiLabel ? 180 : 100
|
|
880
|
-
|
|
881
|
-
const textWidths = props.ticks.map(tick =>
|
|
882
|
-
getTextWidth(tick.formattedValue, `normal ${fontSize[config.fontSize]}px sans-serif`)
|
|
883
|
-
)
|
|
884
|
-
const sumOfTickWidth = textWidths.reduce((a, b) => a + b, accumulator)
|
|
885
|
-
const spaceBetweenEachTick = (xMax - sumOfTickWidth) / (props.ticks.length - 1)
|
|
886
|
-
|
|
887
|
-
// Check if ticks are overlapping
|
|
888
|
-
// Determine the position of each tick
|
|
889
|
-
let positions = [0] // The first tick is at position 0
|
|
890
|
-
for (let i = 1; i < textWidths.length; i++) {
|
|
891
|
-
// The position of each subsequent tick is the position of the previous tick
|
|
892
|
-
// plus the width of the previous tick and the space
|
|
893
|
-
positions[i] = positions[i - 1] + textWidths[i - 1] + spaceBetweenEachTick
|
|
894
|
-
}
|
|
895
|
-
// calculate the end of x axis box
|
|
896
|
-
const axisBBox = axisBottomRef?.current?.getBBox().height
|
|
897
|
-
config.xAxis.axisBBox = axisBBox
|
|
898
|
-
|
|
899
|
-
// Check if ticks are overlapping
|
|
900
|
-
let areTicksTouching = false
|
|
901
|
-
textWidths.forEach((_, i) => {
|
|
902
|
-
if (positions[i] + textWidths[i] > positions[i + 1]) {
|
|
903
|
-
areTicksTouching = true
|
|
904
|
-
return
|
|
905
|
-
}
|
|
906
|
-
})
|
|
907
|
-
|
|
908
|
-
const dynamicMarginTop =
|
|
909
|
-
areTicksTouching && config.isResponsiveTicks ? tickWidthMax + defaultTickLength + 20 : 0
|
|
910
|
-
const rotation = Number(config.xAxis.tickRotation) > 0 ? Number(config.xAxis.tickRotation) : 0
|
|
911
|
-
|
|
912
|
-
config.dynamicMarginTop = dynamicMarginTop
|
|
913
|
-
config.xAxis.tickWidthMax = tickWidthMax
|
|
914
|
-
|
|
915
|
-
let axisMaxHeight = 40
|
|
916
|
-
|
|
917
|
-
const axisContents = (
|
|
918
|
-
<Group className='bottom-axis' width={dimensions[0]}>
|
|
919
|
-
{props.ticks.map((tick, i, propsTicks) => {
|
|
920
|
-
// when using LogScale show major ticks values only
|
|
921
|
-
const showTick = String(tick.value).startsWith('1') || tick.value === 0.1 ? 'block' : 'none'
|
|
922
|
-
const tickLength = showTick === 'block' ? 16 : defaultTickLength
|
|
923
|
-
const to = { x: tick.to.x, y: tickLength }
|
|
924
|
-
const textWidth = getTextWidth(
|
|
925
|
-
tick.formattedValue,
|
|
926
|
-
`normal ${fontSize[config.fontSize]}px sans-serif`
|
|
927
|
-
)
|
|
928
|
-
const limitedWidth = 100 / propsTicks.length
|
|
929
|
-
//reset rotations by updating config
|
|
930
|
-
config.yAxis.tickRotation =
|
|
931
|
-
config.isResponsiveTicks && config.orientation === 'horizontal' ? 0 : config.yAxis.tickRotation
|
|
932
|
-
config.xAxis.tickRotation =
|
|
933
|
-
config.isResponsiveTicks && config.orientation === 'vertical' ? 0 : config.xAxis.tickRotation
|
|
934
|
-
//configure rotation
|
|
935
|
-
|
|
936
|
-
const tickRotation =
|
|
937
|
-
config.isResponsiveTicks && areTicksTouching
|
|
938
|
-
? -Number(config.xAxis.maxTickRotation) || -90
|
|
939
|
-
: -Number(config.runtime.xAxis.tickRotation)
|
|
940
|
-
|
|
941
|
-
const axisHeight = textWidth * Math.sin(tickRotation * -1 * (Math.PI / 180)) + 25
|
|
942
|
-
|
|
943
|
-
if (axisHeight > axisMaxHeight) axisMaxHeight = axisHeight
|
|
944
|
-
|
|
945
|
-
return (
|
|
946
|
-
<Group key={`vx-tick-${tick.value}-${i}`} className={'vx-axis-tick'}>
|
|
947
|
-
{!config.xAxis.hideTicks && (
|
|
948
|
-
<Line
|
|
949
|
-
from={tick.from}
|
|
950
|
-
to={orientation === 'horizontal' && isLogarithmicAxis ? to : tick.to}
|
|
951
|
-
stroke={config.xAxis.tickColor}
|
|
952
|
-
strokeWidth={showTick === 'block' && isLogarithmicAxis ? 1.3 : 1}
|
|
953
|
-
/>
|
|
954
|
-
)}
|
|
955
|
-
{!config.xAxis.hideLabel && (
|
|
956
|
-
<Text
|
|
957
|
-
dy={config.orientation === 'horizontal' && isLogarithmicAxis ? 8 : 0}
|
|
958
|
-
display={config.orientation === 'horizontal' && isLogarithmicAxis ? showTick : 'block'}
|
|
959
|
-
x={tick.to.x}
|
|
960
|
-
y={tick.to.y}
|
|
961
|
-
angle={tickRotation}
|
|
962
|
-
verticalAnchor={tickRotation < -50 ? 'middle' : 'start'}
|
|
963
|
-
textAnchor={tickRotation ? 'end' : 'middle'}
|
|
964
|
-
width={
|
|
965
|
-
areTicksTouching && !config.isResponsiveTicks && !Number(config[section].tickRotation)
|
|
966
|
-
? limitedWidth
|
|
967
|
-
: undefined
|
|
968
|
-
}
|
|
969
|
-
fill={config.xAxis.tickLabelColor}
|
|
970
|
-
>
|
|
971
|
-
{tick.formattedValue}
|
|
972
|
-
</Text>
|
|
973
|
-
)}
|
|
974
|
-
</Group>
|
|
975
|
-
)
|
|
976
|
-
})}
|
|
977
|
-
{!config.xAxis.hideAxis && <Line from={props.axisFromPoint} to={props.axisToPoint} stroke='#333' />}
|
|
978
|
-
<Text
|
|
979
|
-
x={axisCenter}
|
|
980
|
-
y={axisMaxHeight + 20 + xLabelOffset}
|
|
981
|
-
textAnchor='middle'
|
|
982
|
-
verticalAnchor='start'
|
|
983
|
-
fontWeight='bold'
|
|
984
|
-
fill={config.xAxis.labelColor}
|
|
985
|
-
>
|
|
986
|
-
{props.label}
|
|
987
|
-
</Text>
|
|
988
|
-
</Group>
|
|
989
|
-
)
|
|
990
|
-
|
|
991
|
-
if (svgRef.current)
|
|
992
|
-
svgRef.current.setAttribute(
|
|
993
|
-
'height',
|
|
994
|
-
Number(height) + Number(axisMaxHeight) + (runtime.xAxis.label ? 50 : 0) + 'px'
|
|
995
|
-
)
|
|
996
|
-
|
|
997
|
-
return axisContents
|
|
998
|
-
}}
|
|
999
|
-
</AxisBottom>
|
|
1000
|
-
)}
|
|
1001
|
-
{visualizationType === 'Paired Bar' && generatePairedBarAxis()}
|
|
1002
|
-
{visualizationType === 'Deviation Bar' && config.runtime.series?.length === 1 && (
|
|
1003
|
-
<DeviationBar animatedChart={animatedChart} xScale={xScale} yScale={yScale} width={xMax} height={yMax} />
|
|
1004
|
-
)}
|
|
1005
|
-
{visualizationType === 'Paired Bar' && <PairedBarChart originalWidth={width} width={xMax} height={yMax} />}
|
|
1006
|
-
{visualizationType === 'Scatter Plot' && (
|
|
1007
|
-
<ScatterPlot
|
|
1008
|
-
xScale={xScale}
|
|
1009
|
-
yScale={yScale}
|
|
1010
|
-
getXAxisData={getXAxisData}
|
|
1011
|
-
getYAxisData={getYAxisData}
|
|
690
|
+
{visualizationType === 'Box Plot' && (
|
|
691
|
+
<BoxPlot
|
|
692
|
+
seriesScale={seriesScale}
|
|
1012
693
|
xMax={xMax}
|
|
1013
694
|
yMax={yMax}
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
showTooltip={showTooltip}
|
|
695
|
+
min={min}
|
|
696
|
+
max={max}
|
|
697
|
+
xScale={xScale}
|
|
698
|
+
yScale={yScale}
|
|
1019
699
|
/>
|
|
1020
700
|
)}
|
|
1021
|
-
{visualizationType === 'Box Plot' && <BoxPlot xScale={xScale} yScale={yScale} />}
|
|
1022
701
|
{((visualizationType === 'Area Chart' && config.visualizationSubType === 'regular') ||
|
|
1023
702
|
visualizationType === 'Combo') && (
|
|
1024
703
|
<AreaChart
|
|
@@ -1126,7 +805,7 @@ const LinearChart: React.FC<LinearChartProps> = props => {
|
|
|
1126
805
|
yScale={yScale}
|
|
1127
806
|
seriesScale={seriesScale}
|
|
1128
807
|
width={width}
|
|
1129
|
-
height={
|
|
808
|
+
height={forestHeight}
|
|
1130
809
|
getXAxisData={getXAxisData}
|
|
1131
810
|
getYAxisData={getYAxisData}
|
|
1132
811
|
animatedChart={animatedChart}
|
|
@@ -1138,11 +817,19 @@ const LinearChart: React.FC<LinearChartProps> = props => {
|
|
|
1138
817
|
showTooltip={showTooltip}
|
|
1139
818
|
chartRef={svgRef}
|
|
1140
819
|
config={config}
|
|
820
|
+
forestPlotRightLabelRef={forestPlotRightLabelRef}
|
|
1141
821
|
/>
|
|
1142
822
|
)}
|
|
1143
823
|
{/*Brush chart */}
|
|
1144
824
|
{config.brush.active && config.xAxis.type !== 'categorical' && (
|
|
1145
|
-
<BrushChart
|
|
825
|
+
<BrushChart
|
|
826
|
+
xScaleBrush={xScaleBrush}
|
|
827
|
+
yScale={yScale}
|
|
828
|
+
xMax={xMax}
|
|
829
|
+
yMax={yMax}
|
|
830
|
+
xScale={xScale}
|
|
831
|
+
seriesScale={seriesScale}
|
|
832
|
+
/>
|
|
1146
833
|
)}
|
|
1147
834
|
{/* Line chart */}
|
|
1148
835
|
{/* TODO: Make this just line or combo? */}
|
|
@@ -1262,7 +949,7 @@ const LinearChart: React.FC<LinearChartProps> = props => {
|
|
|
1262
949
|
{config.filters && config.filters.values.length === 0 && data.length === 0 && (
|
|
1263
950
|
<Text
|
|
1264
951
|
x={Number(config.yAxis.size) + Number(xMax / 2)}
|
|
1265
|
-
y={
|
|
952
|
+
y={initialHeight / 2 - (config.xAxis.padding || 0) / 2}
|
|
1266
953
|
textAnchor='middle'
|
|
1267
954
|
>
|
|
1268
955
|
{config.chartMessage.noData}
|
|
@@ -1312,26 +999,524 @@ const LinearChart: React.FC<LinearChartProps> = props => {
|
|
|
1312
999
|
onDragStateChange={handleDragStateChange}
|
|
1313
1000
|
/>
|
|
1314
1001
|
</Group>
|
|
1315
|
-
|
|
1316
|
-
|
|
1317
|
-
|
|
1318
|
-
|
|
1319
|
-
|
|
1320
|
-
|
|
1321
|
-
|
|
1322
|
-
|
|
1323
|
-
|
|
1324
|
-
|
|
1325
|
-
|
|
1326
|
-
|
|
1327
|
-
|
|
1328
|
-
|
|
1329
|
-
|
|
1330
|
-
|
|
1331
|
-
|
|
1332
|
-
|
|
1333
|
-
|
|
1334
|
-
|
|
1002
|
+
{/* Highlighted regions */}
|
|
1003
|
+
{/* Y axis */}
|
|
1004
|
+
{!['Spark Line', 'Forest Plot'].includes(visualizationType) && config.yAxis.type !== 'categorical' && (
|
|
1005
|
+
<AxisLeft
|
|
1006
|
+
scale={yScale}
|
|
1007
|
+
tickLength={isLogarithmicAxis ? 6 : 8}
|
|
1008
|
+
left={Number(runtime.yAxis.size) - config.yAxis.axisPadding}
|
|
1009
|
+
label={runtime.yAxis.label || runtime.yAxis.label}
|
|
1010
|
+
stroke='#333'
|
|
1011
|
+
tickFormat={handleLeftTickFormatting}
|
|
1012
|
+
numTicks={handleNumTicks()}
|
|
1013
|
+
>
|
|
1014
|
+
{props => {
|
|
1015
|
+
const axisCenter =
|
|
1016
|
+
config.orientation === 'horizontal'
|
|
1017
|
+
? (props.axisToPoint.y - props.axisFromPoint.y) / 2
|
|
1018
|
+
: (props.axisFromPoint.y - props.axisToPoint.y) / 2
|
|
1019
|
+
const horizontalTickOffset =
|
|
1020
|
+
yMax / props.ticks.length / 2 - (yMax / props.ticks.length) * (1 - config.barThickness) + 5
|
|
1021
|
+
return (
|
|
1022
|
+
<Group className='left-axis'>
|
|
1023
|
+
{!config.yAxis.hideAxis && (
|
|
1024
|
+
<Line
|
|
1025
|
+
from={props.axisFromPoint}
|
|
1026
|
+
to={
|
|
1027
|
+
runtime.horizontal
|
|
1028
|
+
? {
|
|
1029
|
+
x: 0,
|
|
1030
|
+
y:
|
|
1031
|
+
config.visualizationType === 'Forest Plot' ? parentHeight : Number(heights.horizontal)
|
|
1032
|
+
}
|
|
1033
|
+
: props.axisToPoint
|
|
1034
|
+
}
|
|
1035
|
+
stroke='#000'
|
|
1036
|
+
/>
|
|
1037
|
+
)}
|
|
1038
|
+
{yScale.domain()[0] < 0 && (
|
|
1039
|
+
<Line
|
|
1040
|
+
from={{ x: props.axisFromPoint.x, y: yScale(0) }}
|
|
1041
|
+
to={{ x: xMax, y: yScale(0) }}
|
|
1042
|
+
stroke='#333'
|
|
1043
|
+
/>
|
|
1044
|
+
)}
|
|
1045
|
+
{visualizationType === 'Bar' && orientation === 'horizontal' && xScale.domain()[0] < 0 && (
|
|
1046
|
+
<Line
|
|
1047
|
+
from={{ x: xScale(0), y: 0 }}
|
|
1048
|
+
to={{ x: xScale(0), y: yMax }}
|
|
1049
|
+
stroke='#333'
|
|
1050
|
+
strokeWidth={2}
|
|
1051
|
+
/>
|
|
1052
|
+
)}
|
|
1053
|
+
{props.ticks.map((tick, i) => {
|
|
1054
|
+
const minY = props.ticks[0].to.y
|
|
1055
|
+
const barMinHeight = 15 // 15 is the min height for bars by default
|
|
1056
|
+
const showTicks = String(tick.value).startsWith('1') || tick.value === 0.1 ? 'block' : 'none'
|
|
1057
|
+
const tickLength = showTicks === 'block' ? 7 : 0
|
|
1058
|
+
const to = { x: tick.to.x - tickLength, y: tick.to.y }
|
|
1059
|
+
|
|
1060
|
+
// Vertical value/suffix vars
|
|
1061
|
+
const lastTick = props.ticks.length - 1 === i
|
|
1062
|
+
const hideTopTick = lastTick && onlyShowTopPrefixSuffix && suffix && !suffixHasNoSpace
|
|
1063
|
+
const valueOnLinePadding = hideAxis ? -8 : -12
|
|
1064
|
+
const labelXPadding = labelsAboveGridlines ? valueOnLinePadding : 2
|
|
1065
|
+
const labelYPadding = labelsAboveGridlines ? 4 : 0
|
|
1066
|
+
const labelX = tick.to.x - labelXPadding
|
|
1067
|
+
const labelY = tick.to.y - labelYPadding
|
|
1068
|
+
const labelVerticalAnchor = labelsAboveGridlines ? 'end' : 'middle'
|
|
1069
|
+
const combineDomSuffixWithValue =
|
|
1070
|
+
onlyShowTopPrefixSuffix && labelsAboveGridlines && suffix && lastTick
|
|
1071
|
+
|
|
1072
|
+
return (
|
|
1073
|
+
<Group key={`vx-tick-${tick.value}-${i}`} className={'vx-axis-tick'}>
|
|
1074
|
+
{!runtime.yAxis.hideTicks && !labelsAboveGridlines && !hideTopTick && (
|
|
1075
|
+
<Line
|
|
1076
|
+
key={`${tick.value}--hide-hideTicks`}
|
|
1077
|
+
from={tick.from}
|
|
1078
|
+
to={isLogarithmicAxis ? to : tick.to}
|
|
1079
|
+
stroke={config.yAxis.tickColor}
|
|
1080
|
+
display={orientation === 'horizontal' ? 'none' : 'block'}
|
|
1081
|
+
/>
|
|
1082
|
+
)}
|
|
1083
|
+
|
|
1084
|
+
{orientation === 'horizontal' &&
|
|
1085
|
+
visualizationSubType !== 'stacked' &&
|
|
1086
|
+
config.yAxis.labelPlacement === 'On Date/Category Axis' &&
|
|
1087
|
+
!config.yAxis.hideLabel && (
|
|
1088
|
+
<Text
|
|
1089
|
+
transform={`translate(${tick.to.x - 5}, ${
|
|
1090
|
+
config.isLollipopChart
|
|
1091
|
+
? tick.to.y - minY
|
|
1092
|
+
: tick.to.y -
|
|
1093
|
+
minY +
|
|
1094
|
+
(Number(config.barHeight * config.runtime.series.length) - barMinHeight) / 2
|
|
1095
|
+
}) rotate(-${config.runtime.horizontal ? config.runtime.yAxis.tickRotation || 0 : 0})`}
|
|
1096
|
+
verticalAnchor={'start'}
|
|
1097
|
+
textAnchor={'end'}
|
|
1098
|
+
>
|
|
1099
|
+
{tick.formattedValue}
|
|
1100
|
+
</Text>
|
|
1101
|
+
)}
|
|
1102
|
+
|
|
1103
|
+
{orientation === 'horizontal' &&
|
|
1104
|
+
visualizationSubType === 'stacked' &&
|
|
1105
|
+
config.yAxis.labelPlacement === 'On Date/Category Axis' &&
|
|
1106
|
+
!config.yAxis.hideLabel && (
|
|
1107
|
+
<Text
|
|
1108
|
+
transform={`translate(${tick.to.x - 5}, ${
|
|
1109
|
+
tick.to.y - minY + (Number(config.barHeight) - barMinHeight) / 2
|
|
1110
|
+
}) rotate(-${runtime.horizontal ? runtime.yAxis.tickRotation : 0})`}
|
|
1111
|
+
verticalAnchor={'start'}
|
|
1112
|
+
textAnchor={'end'}
|
|
1113
|
+
>
|
|
1114
|
+
{tick.formattedValue}
|
|
1115
|
+
</Text>
|
|
1116
|
+
)}
|
|
1117
|
+
|
|
1118
|
+
{orientation === 'horizontal' &&
|
|
1119
|
+
visualizationType === 'Paired Bar' &&
|
|
1120
|
+
!config.yAxis.hideLabel && (
|
|
1121
|
+
<Text
|
|
1122
|
+
transform={`translate(${tick.to.x - 5}, ${
|
|
1123
|
+
tick.to.y - minY + Number(config.barHeight) / 2
|
|
1124
|
+
}) rotate(-${runtime.horizontal ? runtime.yAxis.tickRotation : 0})`}
|
|
1125
|
+
textAnchor={'end'}
|
|
1126
|
+
verticalAnchor='middle'
|
|
1127
|
+
>
|
|
1128
|
+
{tick.formattedValue}
|
|
1129
|
+
</Text>
|
|
1130
|
+
)}
|
|
1131
|
+
{orientation === 'horizontal' &&
|
|
1132
|
+
visualizationType === 'Deviation Bar' &&
|
|
1133
|
+
!config.yAxis.hideLabel && (
|
|
1134
|
+
<Text
|
|
1135
|
+
transform={`translate(${tick.to.x - 5}, ${
|
|
1136
|
+
config.isLollipopChart
|
|
1137
|
+
? tick.to.y - minY + 2
|
|
1138
|
+
: tick.to.y - minY + Number(config.barHeight) / 2
|
|
1139
|
+
}) rotate(-${runtime.horizontal ? runtime.yAxis.tickRotation : 0})`}
|
|
1140
|
+
textAnchor={'end'}
|
|
1141
|
+
verticalAnchor='middle'
|
|
1142
|
+
>
|
|
1143
|
+
{tick.formattedValue}
|
|
1144
|
+
</Text>
|
|
1145
|
+
)}
|
|
1146
|
+
|
|
1147
|
+
{orientation === 'vertical' &&
|
|
1148
|
+
visualizationType === 'Bump Chart' &&
|
|
1149
|
+
!config.yAxis.hideLabel && (
|
|
1150
|
+
<>
|
|
1151
|
+
<Text
|
|
1152
|
+
display={config.useLogScale ? showTicks : 'block'}
|
|
1153
|
+
dx={config.useLogScale ? -6 : 0}
|
|
1154
|
+
x={config.runtime.horizontal ? tick.from.x + 2 : tick.to.x - 8.5}
|
|
1155
|
+
y={tick.to.y - 13 + (config.runtime.horizontal ? horizontalTickOffset : 0)}
|
|
1156
|
+
angle={-Number(config.yAxis.tickRotation) || 0}
|
|
1157
|
+
verticalAnchor={config.runtime.horizontal ? 'start' : 'middle'}
|
|
1158
|
+
textAnchor={config.runtime.horizontal ? 'start' : 'end'}
|
|
1159
|
+
fill={config.yAxis.tickLabelColor}
|
|
1160
|
+
>
|
|
1161
|
+
{config.runtime.seriesLabelsAll[tick.formattedValue - 1]}
|
|
1162
|
+
</Text>
|
|
1163
|
+
|
|
1164
|
+
{(seriesHighlight.length === 0 ||
|
|
1165
|
+
seriesHighlight.includes(
|
|
1166
|
+
config.runtime.seriesLabelsAll[tick.formattedValue - 1]
|
|
1167
|
+
)) && (
|
|
1168
|
+
<rect
|
|
1169
|
+
x={0 - Number(config.yAxis.size)}
|
|
1170
|
+
y={tick.to.y - 8 + (config.runtime.horizontal ? horizontalTickOffset : 7)}
|
|
1171
|
+
width={Number(config.yAxis.size) + xScale(xScale.domain()[0])}
|
|
1172
|
+
height='2'
|
|
1173
|
+
fill={colorScale(config.runtime.seriesLabelsAll[tick.formattedValue - 1])}
|
|
1174
|
+
/>
|
|
1175
|
+
)}
|
|
1176
|
+
</>
|
|
1177
|
+
)}
|
|
1178
|
+
{orientation === 'vertical' &&
|
|
1179
|
+
visualizationType !== 'Paired Bar' &&
|
|
1180
|
+
visualizationType !== 'Bump Chart' &&
|
|
1181
|
+
!config.yAxis.hideLabel && (
|
|
1182
|
+
<>
|
|
1183
|
+
{/* TOP ONLY SUFFIX: Dom suffix for 'show only top suffix' behavior */}
|
|
1184
|
+
{/* top suffix is shown alone and is allowed to 'overflow' to the right */}
|
|
1185
|
+
{/* SPECIAL ONE CHAR CASE: a one character top-only suffix does not overflow */}
|
|
1186
|
+
{/* IF VALUES ON LINE: suffix is combined with value to avoid having to calculate varying (now left-aligned) value widths */}
|
|
1187
|
+
{onlyShowTopPrefixSuffix && lastTick && !labelsAboveGridlines && (
|
|
1188
|
+
<BlurStrokeText
|
|
1189
|
+
innerRef={suffixRef}
|
|
1190
|
+
display={isLogarithmicAxis ? showTicks : 'block'}
|
|
1191
|
+
dx={isLogarithmicAxis ? -6 : 0}
|
|
1192
|
+
x={labelX}
|
|
1193
|
+
y={labelY}
|
|
1194
|
+
angle={-Number(config.yAxis.tickRotation) || 0}
|
|
1195
|
+
verticalAnchor={labelVerticalAnchor}
|
|
1196
|
+
textAnchor={suffixHasNoSpace ? 'end' : 'start'}
|
|
1197
|
+
fill={config.yAxis.tickLabelColor}
|
|
1198
|
+
stroke={'#fff'}
|
|
1199
|
+
paintOrder={'stroke'} // keeps stroke under fill
|
|
1200
|
+
strokeLinejoin='round'
|
|
1201
|
+
style={{ whiteSpace: 'pre-wrap' }} // prevents leading spaces from being trimmed
|
|
1202
|
+
>
|
|
1203
|
+
{suffix}
|
|
1204
|
+
</BlurStrokeText>
|
|
1205
|
+
)}
|
|
1206
|
+
|
|
1207
|
+
{/* VALUE */}
|
|
1208
|
+
<BlurStrokeText
|
|
1209
|
+
innerRef={el => lastTick && (topYLabelRef.current = el)}
|
|
1210
|
+
display={isLogarithmicAxis ? showTicks : 'block'}
|
|
1211
|
+
dx={isLogarithmicAxis ? -6 : 0}
|
|
1212
|
+
x={suffixHasNoSpace ? labelX - suffixWidth : labelX}
|
|
1213
|
+
y={labelY + (config.runtime.horizontal ? horizontalTickOffset : 0)}
|
|
1214
|
+
angle={-Number(config.yAxis.tickRotation) || 0}
|
|
1215
|
+
verticalAnchor={config.runtime.horizontal ? 'start' : labelVerticalAnchor}
|
|
1216
|
+
textAnchor={config.runtime.horizontal || labelsAboveGridlines ? 'start' : 'end'}
|
|
1217
|
+
fill={config.yAxis.tickLabelColor}
|
|
1218
|
+
stroke={'#fff'}
|
|
1219
|
+
disableStroke={!labelsAboveGridlines}
|
|
1220
|
+
strokeLinejoin='round'
|
|
1221
|
+
paintOrder={'stroke'} // keeps stroke under fill
|
|
1222
|
+
style={{ whiteSpace: 'pre-wrap' }} // prevents leading spaces from being trimmed
|
|
1223
|
+
>
|
|
1224
|
+
{`${tick.formattedValue}${combineDomSuffixWithValue ? suffix : ''}`}
|
|
1225
|
+
</BlurStrokeText>
|
|
1226
|
+
</>
|
|
1227
|
+
)}
|
|
1228
|
+
</Group>
|
|
1229
|
+
)
|
|
1230
|
+
})}
|
|
1231
|
+
<Text
|
|
1232
|
+
className='y-label'
|
|
1233
|
+
textAnchor='middle'
|
|
1234
|
+
verticalAnchor='start'
|
|
1235
|
+
transform={`translate(${-1 * runtime.yAxis.size + yLabelOffset}, ${axisCenter}) rotate(-90)`}
|
|
1236
|
+
fontWeight='bold'
|
|
1237
|
+
fill={config.yAxis.labelColor}
|
|
1238
|
+
>
|
|
1239
|
+
{props.label}
|
|
1240
|
+
</Text>
|
|
1241
|
+
</Group>
|
|
1242
|
+
)
|
|
1243
|
+
}}
|
|
1244
|
+
</AxisLeft>
|
|
1245
|
+
)}
|
|
1246
|
+
{config.yAxis.type === 'categorical' && config.orientation === 'vertical' && (
|
|
1247
|
+
<CategoricalYAxis
|
|
1248
|
+
max={max}
|
|
1249
|
+
maxValue={maxValue}
|
|
1250
|
+
height={initialHeight}
|
|
1251
|
+
xMax={xMax}
|
|
1252
|
+
yMax={yMax}
|
|
1253
|
+
leftSize={Number(runtime.yAxis.size) - config.yAxis.axisPadding}
|
|
1254
|
+
/>
|
|
1255
|
+
)}
|
|
1256
|
+
{/* Right Axis */}
|
|
1257
|
+
{hasRightAxis && (
|
|
1258
|
+
<AxisRight
|
|
1259
|
+
scale={yScaleRight}
|
|
1260
|
+
left={Number(width - config.yAxis.rightAxisSize)}
|
|
1261
|
+
label={config.yAxis.rightLabel}
|
|
1262
|
+
tickFormat={tick => formatNumber(tick, 'right')}
|
|
1263
|
+
numTicks={runtime.yAxis.rightNumTicks || undefined}
|
|
1264
|
+
labelOffset={45}
|
|
1265
|
+
>
|
|
1266
|
+
{props => {
|
|
1267
|
+
const axisCenter =
|
|
1268
|
+
config.orientation === 'horizontal'
|
|
1269
|
+
? (props.axisToPoint.y - props.axisFromPoint.y) / 2
|
|
1270
|
+
: (props.axisFromPoint.y - props.axisToPoint.y) / 2
|
|
1271
|
+
const horizontalTickOffset =
|
|
1272
|
+
yMax / props.ticks.length / 2 - (yMax / props.ticks.length) * (1 - config.barThickness) + 5
|
|
1273
|
+
return (
|
|
1274
|
+
<Group className='right-axis'>
|
|
1275
|
+
{props.ticks.map((tick, i) => {
|
|
1276
|
+
return (
|
|
1277
|
+
<Group key={`vx-tick-${tick.value}-${i}`} className='vx-axis-tick'>
|
|
1278
|
+
{!runtime.yAxis.rightHideTicks && (
|
|
1279
|
+
<Line
|
|
1280
|
+
from={tick.from}
|
|
1281
|
+
to={tick.to}
|
|
1282
|
+
display={runtime.horizontal ? 'none' : 'block'}
|
|
1283
|
+
stroke={config.yAxis.rightAxisTickColor}
|
|
1284
|
+
/>
|
|
1285
|
+
)}
|
|
1286
|
+
|
|
1287
|
+
{runtime.yAxis.rightGridLines ? (
|
|
1288
|
+
<Line from={{ x: tick.from.x + xMax, y: tick.from.y }} to={tick.from} stroke='#d6d6d6' />
|
|
1289
|
+
) : (
|
|
1290
|
+
''
|
|
1291
|
+
)}
|
|
1292
|
+
|
|
1293
|
+
{!config.yAxis.rightHideLabel && (
|
|
1294
|
+
<Text
|
|
1295
|
+
x={tick.to.x}
|
|
1296
|
+
y={tick.to.y + (runtime.horizontal ? horizontalTickOffset : 0)}
|
|
1297
|
+
verticalAnchor={runtime.horizontal ? 'start' : 'middle'}
|
|
1298
|
+
textAnchor={'start'}
|
|
1299
|
+
fill={config.yAxis.rightAxisTickLabelColor}
|
|
1300
|
+
>
|
|
1301
|
+
{tick.formattedValue}
|
|
1302
|
+
</Text>
|
|
1303
|
+
)}
|
|
1304
|
+
</Group>
|
|
1305
|
+
)
|
|
1306
|
+
})}
|
|
1307
|
+
{!config.yAxis.rightHideAxis && (
|
|
1308
|
+
<Line from={props.axisFromPoint} to={props.axisToPoint} stroke='#333' />
|
|
1309
|
+
)}
|
|
1310
|
+
<Text
|
|
1311
|
+
className='y-label'
|
|
1312
|
+
textAnchor='middle'
|
|
1313
|
+
verticalAnchor='start'
|
|
1314
|
+
transform={`translate(${
|
|
1315
|
+
config.yAxis.rightLabelOffsetSize ? config.yAxis.rightLabelOffsetSize : 0
|
|
1316
|
+
}, ${axisCenter}) rotate(-90)`}
|
|
1317
|
+
fontWeight='bold'
|
|
1318
|
+
fill={config.yAxis.rightAxisLabelColor}
|
|
1319
|
+
>
|
|
1320
|
+
{props.label}
|
|
1321
|
+
</Text>
|
|
1322
|
+
</Group>
|
|
1323
|
+
)
|
|
1324
|
+
}}
|
|
1325
|
+
</AxisRight>
|
|
1326
|
+
)}
|
|
1327
|
+
{hasTopAxis && config.topAxis.hasLine && (
|
|
1328
|
+
<AxisTop
|
|
1329
|
+
stroke='#333'
|
|
1330
|
+
left={Number(runtime.yAxis.size)}
|
|
1331
|
+
scale={xScale}
|
|
1332
|
+
hideTicks
|
|
1333
|
+
hideZero
|
|
1334
|
+
tickLabelProps={() => ({
|
|
1335
|
+
fill: 'transparent'
|
|
1336
|
+
})}
|
|
1337
|
+
/>
|
|
1338
|
+
)}
|
|
1339
|
+
{/* X axis */}
|
|
1340
|
+
{visualizationType !== 'Paired Bar' && visualizationType !== 'Spark Line' && (
|
|
1341
|
+
<AxisBottom
|
|
1342
|
+
innerRef={axisBottomRef}
|
|
1343
|
+
top={
|
|
1344
|
+
runtime.horizontal && config.visualizationType !== 'Forest Plot'
|
|
1345
|
+
? Number(heights.horizontal) + Number(config.xAxis.axisPadding)
|
|
1346
|
+
: config.visualizationType === 'Forest Plot'
|
|
1347
|
+
? yMax + Number(config.xAxis.axisPadding)
|
|
1348
|
+
: yMax
|
|
1349
|
+
}
|
|
1350
|
+
left={config.visualizationType !== 'Forest Plot' ? Number(runtime.yAxis.size) : 0}
|
|
1351
|
+
label={config[section].label}
|
|
1352
|
+
tickFormat={handleBottomTickFormatting}
|
|
1353
|
+
scale={xScale}
|
|
1354
|
+
stroke='#333'
|
|
1355
|
+
numTicks={countNumOfTicks('xAxis')}
|
|
1356
|
+
tickStroke='#333'
|
|
1357
|
+
tickValues={
|
|
1358
|
+
config.xAxis.manual
|
|
1359
|
+
? getTickValues(
|
|
1360
|
+
xAxisDataMapped,
|
|
1361
|
+
xScale,
|
|
1362
|
+
config.xAxis.type === 'date-time' ? countNumOfTicks('xAxis') : getManualStep(),
|
|
1363
|
+
config
|
|
1364
|
+
)
|
|
1365
|
+
: undefined
|
|
1366
|
+
}
|
|
1367
|
+
>
|
|
1368
|
+
{props => {
|
|
1369
|
+
const axisMaxHeight = bottomLabelStart + BOTTOM_LABEL_PADDING
|
|
1370
|
+
|
|
1371
|
+
const axisCenter =
|
|
1372
|
+
config.visualizationType !== 'Forest Plot'
|
|
1373
|
+
? (props.axisToPoint.x - props.axisFromPoint.x) / 2
|
|
1374
|
+
: dimensions[0] / 2
|
|
1375
|
+
const containsMultipleWords = inputString => /\s/.test(inputString)
|
|
1376
|
+
const ismultiLabel = props.ticks.some(tick => containsMultipleWords(tick.value))
|
|
1377
|
+
|
|
1378
|
+
// Calculate sumOfTickWidth here, before map function
|
|
1379
|
+
const tickWidthMax = Math.max(
|
|
1380
|
+
...props.ticks.map(tick =>
|
|
1381
|
+
getTextWidth(tick.formattedValue, `normal ${fontSize[config.fontSize]}px sans-serif`)
|
|
1382
|
+
)
|
|
1383
|
+
)
|
|
1384
|
+
// const marginTop = 20 // moved to top bc need for yMax calcs
|
|
1385
|
+
const accumulator = ismultiLabel ? 180 : 100
|
|
1386
|
+
|
|
1387
|
+
const textWidths = props.ticks.map(tick =>
|
|
1388
|
+
getTextWidth(tick.formattedValue, `normal ${fontSize[config.fontSize]}px sans-serif`)
|
|
1389
|
+
)
|
|
1390
|
+
const sumOfTickWidth = textWidths.reduce((a, b) => a + b, accumulator)
|
|
1391
|
+
const spaceBetweenEachTick = (xMax - sumOfTickWidth) / (props.ticks.length - 1)
|
|
1392
|
+
|
|
1393
|
+
// Check if ticks are overlapping
|
|
1394
|
+
// Determine the position of each tick
|
|
1395
|
+
let positions = [0] // The first tick is at position 0
|
|
1396
|
+
for (let i = 1; i < textWidths.length; i++) {
|
|
1397
|
+
// The position of each subsequent tick is the position of the previous tick
|
|
1398
|
+
// plus the width of the previous tick and the space
|
|
1399
|
+
positions[i] = positions[i - 1] + textWidths[i - 1] + spaceBetweenEachTick
|
|
1400
|
+
}
|
|
1401
|
+
// calculate the end of x axis box
|
|
1402
|
+
const axisBBox = axisBottomRef?.current?.getBBox().height
|
|
1403
|
+
config.xAxis.axisBBox = axisBBox
|
|
1404
|
+
|
|
1405
|
+
// Check if ticks are overlapping
|
|
1406
|
+
let areTicksTouching = false
|
|
1407
|
+
textWidths.forEach((_, i) => {
|
|
1408
|
+
if (positions[i] + textWidths[i] > positions[i + 1]) {
|
|
1409
|
+
areTicksTouching = true
|
|
1410
|
+
return
|
|
1411
|
+
}
|
|
1412
|
+
})
|
|
1413
|
+
|
|
1414
|
+
// Force wrap when showing years once so it's easier to read
|
|
1415
|
+
if (config.xAxis.showYearsOnce) {
|
|
1416
|
+
areTicksTouching = true
|
|
1417
|
+
}
|
|
1418
|
+
|
|
1419
|
+
const dynamicMarginTop =
|
|
1420
|
+
areTicksTouching && config.isResponsiveTicks ? tickWidthMax + DEFAULT_TICK_LENGTH + 20 : 0
|
|
1421
|
+
|
|
1422
|
+
config.dynamicMarginTop = dynamicMarginTop
|
|
1423
|
+
config.xAxis.tickWidthMax = tickWidthMax
|
|
1424
|
+
|
|
1425
|
+
return (
|
|
1426
|
+
<Group className='bottom-axis' width={dimensions[0]}>
|
|
1427
|
+
{props.ticks.map((tick, i, propsTicks) => {
|
|
1428
|
+
// when using LogScale show major ticks values only
|
|
1429
|
+
const showTick = String(tick.value).startsWith('1') || tick.value === 0.1 ? 'block' : 'none'
|
|
1430
|
+
const tickLength = showTick === 'block' ? 16 : DEFAULT_TICK_LENGTH
|
|
1431
|
+
const to = { x: tick.to.x, y: tickLength }
|
|
1432
|
+
const textWidth = getTextWidth(
|
|
1433
|
+
tick.formattedValue,
|
|
1434
|
+
`normal ${fontSize[config.fontSize]}px sans-serif`
|
|
1435
|
+
)
|
|
1436
|
+
const limitedWidth = 100 / propsTicks.length
|
|
1437
|
+
//reset rotations by updating config
|
|
1438
|
+
config.yAxis.tickRotation =
|
|
1439
|
+
config.isResponsiveTicks && config.orientation === 'horizontal' ? 0 : config.yAxis.tickRotation
|
|
1440
|
+
config.xAxis.tickRotation =
|
|
1441
|
+
config.isResponsiveTicks && config.orientation === 'vertical' ? 0 : config.xAxis.tickRotation
|
|
1442
|
+
//configure rotation
|
|
1443
|
+
|
|
1444
|
+
const tickRotation =
|
|
1445
|
+
config.isResponsiveTicks && areTicksTouching
|
|
1446
|
+
? -Number(config.xAxis.maxTickRotation) || -90
|
|
1447
|
+
: -Number(config.runtime.xAxis.tickRotation)
|
|
1448
|
+
|
|
1449
|
+
return (
|
|
1450
|
+
<Group key={`vx-tick-${tick.value}-${i}`} className={'vx-axis-tick'}>
|
|
1451
|
+
{!config.xAxis.hideTicks && (
|
|
1452
|
+
<Line
|
|
1453
|
+
from={tick.from}
|
|
1454
|
+
to={orientation === 'horizontal' && isLogarithmicAxis ? to : tick.to}
|
|
1455
|
+
stroke={config.xAxis.tickColor}
|
|
1456
|
+
strokeWidth={showTick === 'block' && isLogarithmicAxis ? 1.3 : 1}
|
|
1457
|
+
/>
|
|
1458
|
+
)}
|
|
1459
|
+
{!config.xAxis.hideLabel && (
|
|
1460
|
+
<Text
|
|
1461
|
+
innerRef={el => (xAxisLabelRefs.current[i] = el)}
|
|
1462
|
+
dy={config.orientation === 'horizontal' && isLogarithmicAxis ? 8 : 0}
|
|
1463
|
+
display={config.orientation === 'horizontal' && isLogarithmicAxis ? showTick : 'block'}
|
|
1464
|
+
x={tick.to.x}
|
|
1465
|
+
y={tick.to.y + X_TICK_LABEL_PADDING}
|
|
1466
|
+
angle={tickRotation}
|
|
1467
|
+
verticalAnchor={tickRotation < -50 ? 'middle' : 'start'}
|
|
1468
|
+
textAnchor={tickRotation ? 'end' : 'middle'}
|
|
1469
|
+
width={
|
|
1470
|
+
areTicksTouching && !config.isResponsiveTicks && !Number(config[section].tickRotation)
|
|
1471
|
+
? limitedWidth
|
|
1472
|
+
: undefined
|
|
1473
|
+
}
|
|
1474
|
+
fill={config.xAxis.tickLabelColor}
|
|
1475
|
+
>
|
|
1476
|
+
{tick.formattedValue}
|
|
1477
|
+
</Text>
|
|
1478
|
+
)}
|
|
1479
|
+
</Group>
|
|
1480
|
+
)
|
|
1481
|
+
})}
|
|
1482
|
+
{!config.xAxis.hideAxis && <Line from={props.axisFromPoint} to={props.axisToPoint} stroke='#333' />}
|
|
1483
|
+
<Text
|
|
1484
|
+
innerRef={xAxisTitleRef}
|
|
1485
|
+
className='x-axis-title-label'
|
|
1486
|
+
x={xMax / 2}
|
|
1487
|
+
y={isForestPlot ? 0 /* set via ref */ : axisMaxHeight}
|
|
1488
|
+
textAnchor='middle'
|
|
1489
|
+
verticalAnchor='start'
|
|
1490
|
+
fontWeight='bold'
|
|
1491
|
+
fill={config.xAxis.labelColor}
|
|
1492
|
+
>
|
|
1493
|
+
{props.label}
|
|
1494
|
+
</Text>
|
|
1495
|
+
</Group>
|
|
1496
|
+
)
|
|
1497
|
+
}}
|
|
1498
|
+
</AxisBottom>
|
|
1499
|
+
)}
|
|
1500
|
+
</svg>
|
|
1501
|
+
{!isDraggingAnnotation &&
|
|
1502
|
+
tooltipData &&
|
|
1503
|
+
Object.entries(tooltipData.data).length > 0 &&
|
|
1504
|
+
tooltipOpen &&
|
|
1505
|
+
showTooltip &&
|
|
1506
|
+
tooltipData.dataYPosition &&
|
|
1507
|
+
tooltipData.dataXPosition && (
|
|
1508
|
+
<>
|
|
1509
|
+
<style>{`.tooltip {background-color: rgba(255,255,255, ${
|
|
1510
|
+
config.tooltips.opacity / 100
|
|
1511
|
+
}) !important;`}</style>
|
|
1512
|
+
<style>{`.tooltip {max-width:300px} !important; word-wrap: break-word; `}</style>
|
|
1513
|
+
<TooltipWithBounds
|
|
1514
|
+
key={Math.random()}
|
|
1515
|
+
className={'tooltip cdc-open-viz-module'}
|
|
1516
|
+
left={tooltipLeft}
|
|
1517
|
+
top={tooltipTop}
|
|
1518
|
+
>
|
|
1519
|
+
<ul>
|
|
1335
1520
|
{typeof tooltipData === 'object' &&
|
|
1336
1521
|
Object.entries(tooltipData.data).map((item, index) => <TooltipListItem item={item} key={index} />)}
|
|
1337
1522
|
</ul>
|
|
@@ -1361,6 +1546,6 @@ const LinearChart: React.FC<LinearChartProps> = props => {
|
|
|
1361
1546
|
</div>
|
|
1362
1547
|
</ErrorBoundary>
|
|
1363
1548
|
)
|
|
1364
|
-
}
|
|
1549
|
+
})
|
|
1365
1550
|
|
|
1366
1551
|
export default LinearChart
|