@cdc/chart 4.26.1 → 4.26.2
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/CLAUDE.local.md +79 -0
- package/dist/{cdcchart-dgT_1dIT.es.js → cdcchart-DQ00cQCm.es.js} +1 -20
- package/dist/cdcchart.js +45357 -43655
- package/examples/default.json +378 -0
- package/examples/feature/__data__/horizon-chart-data.json +373 -0
- package/examples/feature/annotations/index.json +3 -6
- package/examples/feature/horizon/horizon-chart.json +395 -0
- package/examples/line-chart-states.json +1085 -0
- package/examples/private/123.json +694 -0
- package/examples/private/anchor-issue.json +4094 -0
- package/examples/private/backwards-slider.json +10430 -0
- package/examples/private/georgia.csv +160 -0
- package/examples/private/timeline-data.json +1 -0
- package/examples/private/timeline.json +389 -0
- package/examples/radar-chart-simple.json +133 -0
- package/examples/radar-chart.json +148 -0
- package/index.html +1 -31
- package/package.json +57 -59
- package/src/CdcChartComponent.tsx +99 -18
- package/src/_stories/Chart.Anchors.stories.tsx +10 -0
- package/src/_stories/Chart.BoxPlot.stories.tsx +7 -0
- package/src/_stories/Chart.CI.stories.tsx +13 -0
- package/src/_stories/Chart.Combo.stories.tsx +17 -0
- package/src/_stories/Chart.CustomColors.stories.tsx +4 -0
- package/src/_stories/Chart.DynamicSeries.stories.tsx +19 -0
- package/src/_stories/Chart.Filters.stories.tsx +4 -0
- package/src/_stories/Chart.Forecast.stories.tsx +4 -0
- package/src/_stories/Chart.HTMLInDataTable.stories.tsx +22 -0
- package/src/_stories/Chart.Legend.Gradient.stories.tsx +28 -0
- package/src/_stories/Chart.Patterns.stories.tsx +4 -0
- package/src/_stories/Chart.PreserveDecimals.stories.tsx +25 -0
- package/src/_stories/Chart.Regions.Categorical.stories.tsx +13 -0
- package/src/_stories/Chart.Regions.DateScale.stories.tsx +19 -0
- package/src/_stories/Chart.Regions.DateTimeScale.stories.tsx +25 -10
- package/src/_stories/Chart.ScatterPlot.stories.tsx +4 -0
- package/src/_stories/Chart.SmallMultiples.stories.tsx +16 -0
- package/src/_stories/Chart.stories.tsx +37 -0
- package/src/_stories/Chart.tooltip.stories.tsx +7 -0
- package/src/_stories/ChartAnnotation.stories.tsx +10 -0
- package/src/_stories/ChartAxisLabels.stories.tsx +4 -0
- package/src/_stories/ChartAxisTitles.stories.tsx +10 -0
- package/src/_stories/ChartBrush.Matrix.Continuous.stories.tsx +41 -0
- package/src/_stories/ChartBrush.Matrix.Date.stories.tsx +114 -0
- package/src/_stories/ChartBrush.Matrix.DateTime.stories.tsx +78 -0
- package/src/_stories/ChartBrush.stories.tsx +7 -0
- package/src/_stories/ChartEditor.stories.tsx +7 -0
- package/src/_stories/ChartLine.QuadrantAngles.stories.tsx +89 -0
- package/src/_stories/ChartLine.Suppression.stories.tsx +7 -0
- package/src/_stories/ChartLine.Symbols.stories.tsx +4 -0
- package/src/_stories/ChartPrefixSuffix.stories.tsx +46 -1
- package/src/_stories/TechAdoptionWithLinks.stories.tsx +7 -0
- package/src/_stories/_mock/brush_continuous.json +86 -0
- package/src/_stories/_mock/brush_date_large.json +176 -0
- package/src/_stories/_mock/line_chart_angle_near_zero_fall.json +195 -0
- package/src/_stories/_mock/line_chart_angle_near_zero_rise.json +195 -0
- package/src/_stories/_mock/line_chart_angle_q1_steep_upward.json +195 -0
- package/src/_stories/_mock/line_chart_angle_q2_gentle_downward.json +195 -0
- package/src/_stories/_mock/line_chart_angle_q3_steep_downward.json +195 -0
- package/src/_stories/_mock/line_chart_angle_q4_gentle_upward.json +195 -0
- package/src/_stories/_mock/line_chart_quadrant_angles.json +264 -0
- package/src/components/Annotations/components/AnnotationDraggable.styles.css +11 -17
- package/src/components/Annotations/components/AnnotationDraggable.tsx +240 -116
- package/src/components/Annotations/components/AnnotationDropdown.styles.css +1 -2
- package/src/components/Annotations/components/AnnotationDropdown.tsx +8 -12
- package/src/components/Annotations/components/AnnotationList.styles.css +4 -10
- package/src/components/Annotations/components/AnnotationList.tsx +5 -4
- package/src/components/Annotations/components/findNearestDatum.ts +75 -85
- package/src/components/Annotations/helpers/getVisibleAnnotations.ts +38 -0
- package/src/components/Axis/BottomAxis.tsx +270 -0
- package/src/components/Axis/LeftAxis.tsx +404 -0
- package/src/components/Axis/LeftAxisGridlines.tsx +77 -0
- package/src/components/Axis/PairedBarAxis.tsx +186 -0
- package/src/components/Axis/README.md +94 -0
- package/src/components/Axis/RightAxis.tsx +108 -0
- package/src/components/Axis/axis.constants.ts +21 -0
- package/src/components/Axis/index.ts +7 -0
- package/src/components/BarChart/components/BarChart.tsx +7 -1
- package/src/components/Brush/BrushSelector.tsx +154 -22
- package/src/components/Brush/MiniChartPreview.tsx +138 -21
- package/src/components/EditorPanel/EditorPanel.tsx +25 -11
- package/src/components/EditorPanel/components/Panels/Panel.Annotate.tsx +60 -22
- package/src/components/EditorPanel/components/Panels/Panel.General.tsx +81 -1
- package/src/components/EditorPanel/components/Panels/Panel.PatternSettings.tsx +1 -1
- package/src/components/EditorPanel/components/Panels/Panel.Radar.tsx +353 -0
- package/src/components/EditorPanel/components/Panels/Panel.Series.tsx +0 -1
- package/src/components/EditorPanel/components/Panels/Panel.Visual.tsx +21 -1
- package/src/components/EditorPanel/components/Panels/index.tsx +2 -0
- package/src/components/EditorPanel/useEditorPermissions.ts +55 -26
- package/src/components/HorizonChart/HorizonChart.tsx +131 -0
- package/src/components/HorizonChart/components/HorizonBand.tsx +160 -0
- package/src/components/HorizonChart/helpers/calculateHorizonBands.ts +27 -0
- package/src/components/HorizonChart/helpers/getHorizonLayerColors.ts +40 -0
- package/src/components/HorizonChart/index.tsx +3 -0
- package/src/components/Legend/Legend.Component.tsx +52 -4
- package/src/components/Legend/Legend.tsx +1 -1
- package/src/components/Legend/LegendValueRange.tsx +77 -0
- package/src/components/Legend/helpers/createFormatLabels.tsx +13 -0
- package/src/components/Legend/helpers/generateValueRanges.ts +92 -0
- package/src/components/LineChart/helpers/README.md +292 -0
- package/src/components/LineChart/helpers/labelPositioning.test.ts +245 -0
- package/src/components/LineChart/helpers/labelPositioning.ts +304 -0
- package/src/components/LineChart/index.tsx +44 -8
- package/src/components/LinearChart/README.md +109 -0
- package/src/components/LinearChart/VisualizationRenderer.tsx +267 -0
- package/src/components/LinearChart/linearChart.constants.ts +84 -0
- package/src/components/LinearChart/tests/LinearChart.test.tsx +201 -0
- package/src/components/LinearChart/tests/mockConfigContext.ts +129 -0
- package/src/components/LinearChart/utils/tickFormatting.ts +146 -0
- package/src/components/LinearChart.tsx +250 -1059
- package/src/components/PieChart/PieChart.tsx +1 -1
- package/src/components/RadarChart/RadarAxis.tsx +78 -0
- package/src/components/RadarChart/RadarChart.tsx +298 -0
- package/src/components/RadarChart/RadarGrid.tsx +64 -0
- package/src/components/RadarChart/RadarPolygon.tsx +91 -0
- package/src/components/RadarChart/helpers.ts +83 -0
- package/src/components/RadarChart/index.tsx +3 -0
- package/src/components/WarmingStripes/WarmingStripes.tsx +95 -25
- package/src/data/initial-state.js +14 -1
- package/src/helpers/getExcludedData.ts +4 -0
- package/src/helpers/handleChartAriaLabels.ts +19 -19
- package/src/helpers/handleLineType.ts +22 -18
- package/src/hooks/useProgrammaticTooltip.ts +23 -2
- package/src/hooks/useScales.ts +7 -0
- package/src/hooks/useTooltip.tsx +3 -0
- package/src/scss/main.scss +5 -0
- package/src/selectors/README.md +68 -0
- package/src/store/chart.reducer.ts +2 -0
- package/src/types/ChartConfig.ts +18 -0
- package/src/types/ChartContext.ts +1 -0
- package/src/types/Horizon.ts +64 -0
- package/preview.html +0 -1616
- package/src/components/Annotations/components/helpers/index.tsx +0 -46
|
@@ -2,6 +2,7 @@ import React, {
|
|
|
2
2
|
forwardRef,
|
|
3
3
|
useContext,
|
|
4
4
|
useEffect,
|
|
5
|
+
useLayoutEffect,
|
|
5
6
|
useImperativeHandle,
|
|
6
7
|
useMemo,
|
|
7
8
|
useRef,
|
|
@@ -10,7 +11,7 @@ import React, {
|
|
|
10
11
|
} from 'react'
|
|
11
12
|
|
|
12
13
|
// Libraries
|
|
13
|
-
import {
|
|
14
|
+
import { AxisTop } from '@visx/axis'
|
|
14
15
|
import { Group } from '@visx/group'
|
|
15
16
|
import { Line, Bar } from '@visx/shape'
|
|
16
17
|
import { Tooltip as ReactTooltip } from 'react-tooltip'
|
|
@@ -21,35 +22,25 @@ import _ from 'lodash'
|
|
|
21
22
|
|
|
22
23
|
// CDC Components
|
|
23
24
|
import { isDateScale } from '@cdc/core/helpers/cove/date'
|
|
24
|
-
import { AreaChartStacked } from './AreaChart'
|
|
25
|
-
import BarChart from './BarChart'
|
|
26
25
|
import ConfigContext from '../ConfigContext'
|
|
27
|
-
import BoxPlotVertical from './BoxPlot/BoxPlot.Vertical'
|
|
28
|
-
import BoxPlotHorizontal from './BoxPlot/BoxPlot.Horizontal'
|
|
29
|
-
import ScatterPlot from './ScatterPlot'
|
|
30
|
-
import DeviationBar from './DeviationBar'
|
|
31
26
|
import ErrorBoundary from '@cdc/core/components/ErrorBoundary'
|
|
32
|
-
import Forecasting from './Forecasting'
|
|
33
|
-
import LineChart from './LineChart'
|
|
34
|
-
import ForestPlot from './ForestPlot'
|
|
35
|
-
import PairedBarChart from './PairedBarChart'
|
|
36
27
|
import useIntersectionObserver from '../hooks/useIntersectionObserver'
|
|
37
28
|
import Regions from './Regions'
|
|
38
|
-
import CategoricalYAxis from './Axis
|
|
29
|
+
import { CategoricalYAxis, LeftAxis, LeftAxisGridlines, BottomAxis, PairedBarAxis, RightAxis } from './Axis'
|
|
39
30
|
import BrushSelector from './Brush/BrushSelector'
|
|
40
|
-
import
|
|
31
|
+
import VisualizationRenderer from './LinearChart/VisualizationRenderer'
|
|
32
|
+
import { TYPES_WITHOUT_GRID, TYPES_WITH_TOOLTIP_GUIDES } from './LinearChart/linearChart.constants'
|
|
33
|
+
import { useTickFormatters } from './LinearChart/utils/tickFormatting'
|
|
41
34
|
|
|
42
35
|
// Helpers
|
|
43
36
|
import { isLegendWrapViewport, isMobileFontViewport } from '@cdc/core/helpers/viewports'
|
|
44
|
-
import { getTextWidth } from '@cdc/core/helpers/getTextWidth'
|
|
45
37
|
import { calcInitialHeight } from '../helpers/sizeHelpers'
|
|
46
|
-
import { filterAndShiftLinearDateTicks } from '../helpers/filterAndShiftLinearDateTicks'
|
|
47
38
|
import { calculateHorizontalBarCategoryLabelWidth } from '../helpers/calculateHorizontalBarCategoryLabelWidth'
|
|
48
39
|
|
|
49
40
|
// Hooks
|
|
50
41
|
import useReduceData from '../hooks/useReduceData'
|
|
51
42
|
import useRightAxis from '../hooks/useRightAxis'
|
|
52
|
-
import useScales
|
|
43
|
+
import useScales from '../hooks/useScales'
|
|
53
44
|
import { useProgrammaticTooltip } from '../hooks/useProgrammaticTooltip'
|
|
54
45
|
import { useSmallMultipleSynchronization } from '../hooks/useSmallMultipleSynchronization'
|
|
55
46
|
|
|
@@ -58,7 +49,6 @@ import { useTooltip as useCoveTooltip } from '../hooks/useTooltip'
|
|
|
58
49
|
import { useChartHoverAnalytics } from '../hooks/useChartHoverAnalytics'
|
|
59
50
|
import { useEditorPermissions } from './EditorPanel/useEditorPermissions'
|
|
60
51
|
import Annotation from './Annotations'
|
|
61
|
-
import { BlurStrokeText } from '@cdc/core/components/BlurStrokeText'
|
|
62
52
|
import { countNumOfTicks } from '../helpers/countNumOfTicks'
|
|
63
53
|
import HoverLine from './HoverLine/HoverLine'
|
|
64
54
|
import { SmallMultiples } from './SmallMultiples'
|
|
@@ -68,15 +58,36 @@ type LinearChartProps = {
|
|
|
68
58
|
parentHeight: number
|
|
69
59
|
}
|
|
70
60
|
|
|
61
|
+
// Axis and tick constants
|
|
71
62
|
const BOTTOM_LABEL_PADDING = 9
|
|
72
63
|
const X_TICK_LABEL_PADDING = 4.5
|
|
73
64
|
const DEFAULT_TICK_LENGTH = 8
|
|
74
|
-
const
|
|
65
|
+
const DEFAULT_MAX_TICK_ROTATION = 90
|
|
66
|
+
|
|
67
|
+
// Font sizes
|
|
75
68
|
const TICK_LABEL_FONT_SIZE = 16
|
|
76
69
|
const TICK_LABEL_FONT_SIZE_SMALL = 13
|
|
77
70
|
const AXIS_LABEL_FONT_SIZE = 18
|
|
78
71
|
const AXIS_LABEL_FONT_SIZE_SMALL = 14
|
|
79
|
-
|
|
72
|
+
|
|
73
|
+
// Label positioning constants
|
|
74
|
+
const BELOW_BAR_TEXT_OFFSET = -6.5
|
|
75
|
+
const LABEL_PADDING_OFFSET = 8
|
|
76
|
+
|
|
77
|
+
// Brush constants
|
|
78
|
+
const BRUSH_HEIGHT = 70
|
|
79
|
+
const BRUSH_MARGIN = 10
|
|
80
|
+
const BRUSH_MIN_WIDTH = 100
|
|
81
|
+
|
|
82
|
+
// Tooltip constants
|
|
83
|
+
const TOOLTIP_EDGE_BUFFER = 10
|
|
84
|
+
const TOOLTIP_OFFSET = 6
|
|
85
|
+
|
|
86
|
+
// Chart-specific constants
|
|
87
|
+
const WARMING_STRIPES_HEIGHT = 78
|
|
88
|
+
|
|
89
|
+
// Time constants
|
|
90
|
+
const MONTH_AS_MS = 1000 * 60 * 60 * 24 * 30
|
|
80
91
|
|
|
81
92
|
type TooltipData = {
|
|
82
93
|
dataXPosition?: number
|
|
@@ -95,12 +106,9 @@ type UseTooltipReturn<T = TooltipData> = {
|
|
|
95
106
|
const LinearChart = forwardRef<SVGAElement, LinearChartProps>(({ parentHeight, parentWidth }, svgRef) => {
|
|
96
107
|
// prettier-ignore
|
|
97
108
|
const {
|
|
98
|
-
colorScale,
|
|
99
109
|
config,
|
|
100
|
-
convertLineToBarGraph,
|
|
101
110
|
currentViewport,
|
|
102
111
|
vizViewport,
|
|
103
|
-
dimensions,
|
|
104
112
|
formatDate,
|
|
105
113
|
formatNumber,
|
|
106
114
|
handleChartAriaLabels,
|
|
@@ -108,33 +116,18 @@ const LinearChart = forwardRef<SVGAElement, LinearChartProps>(({ parentHeight, p
|
|
|
108
116
|
handleDragStateChange,
|
|
109
117
|
interactionLabel,
|
|
110
118
|
isDraggingAnnotation,
|
|
111
|
-
isEditor,
|
|
112
119
|
legendRef,
|
|
113
120
|
parseDate,
|
|
114
121
|
parentRef,
|
|
115
122
|
tableData,
|
|
116
123
|
transformedData: data,
|
|
117
|
-
seriesHighlight
|
|
118
124
|
} = useContext(ConfigContext)
|
|
119
125
|
|
|
120
126
|
// CONFIG
|
|
121
127
|
// todo: start destructuring this file for conciseness
|
|
122
|
-
const {
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
visualizationSubType,
|
|
126
|
-
orientation,
|
|
127
|
-
xAxis,
|
|
128
|
-
yAxis,
|
|
129
|
-
runtime,
|
|
130
|
-
legend,
|
|
131
|
-
forestPlot,
|
|
132
|
-
brush,
|
|
133
|
-
dataFormat,
|
|
134
|
-
debugSvg
|
|
135
|
-
} = config
|
|
136
|
-
|
|
137
|
-
const { labelsAboveGridlines, hideAxis, inlineLabel } = config.yAxis
|
|
128
|
+
const { visualizationType, orientation, xAxis, yAxis, runtime, legend, forestPlot, debugSvg } = config
|
|
129
|
+
|
|
130
|
+
const { inlineLabel } = config.yAxis
|
|
138
131
|
|
|
139
132
|
// HOOKS % STATES
|
|
140
133
|
// When brush is active, use tableData (full dataset) for min/max calculation
|
|
@@ -144,11 +137,22 @@ const LinearChart = forwardRef<SVGAElement, LinearChartProps>(({ parentHeight, p
|
|
|
144
137
|
|
|
145
138
|
const { visSupportsSmallMultiples } = useEditorPermissions()
|
|
146
139
|
const { hasTopAxis } = getTopAxis(config)
|
|
140
|
+
|
|
141
|
+
// Increment on tableData change to force BrushSelector remount when filters change
|
|
142
|
+
const brushKeyRef = useRef(0)
|
|
143
|
+
const prevTableDataRef = useRef(tableData)
|
|
144
|
+
if (prevTableDataRef.current !== tableData) {
|
|
145
|
+
prevTableDataRef.current = tableData
|
|
146
|
+
brushKeyRef.current += 1
|
|
147
|
+
}
|
|
148
|
+
|
|
147
149
|
const [animatedChart, setAnimatedChart] = useState(false)
|
|
148
150
|
const [showHoverLine, setShowHoverLine] = useState(false)
|
|
149
151
|
const [point, setPoint] = useState({ x: 0, y: 0 })
|
|
150
152
|
const [suffixWidth, setSuffixWidth] = useState(0)
|
|
151
153
|
const [calculatedSvgHeight, setCalculatedSvgHeight] = useState<number | null>(null)
|
|
154
|
+
const [axisUpdateKey, setAxisUpdateKey] = useState(0)
|
|
155
|
+
const [synchronizedXValue, setSynchronizedXValue] = useState<any>(null)
|
|
152
156
|
|
|
153
157
|
// REFS
|
|
154
158
|
const axisBottomRef = useRef(null)
|
|
@@ -183,29 +187,43 @@ const LinearChart = forwardRef<SVGAElement, LinearChartProps>(({ parentHeight, p
|
|
|
183
187
|
|
|
184
188
|
// height before bottom axis
|
|
185
189
|
const initialHeight = useMemo(
|
|
186
|
-
() =>
|
|
187
|
-
|
|
190
|
+
() =>
|
|
191
|
+
visualizationType === 'Warming Stripes' ? WARMING_STRIPES_HEIGHT : calcInitialHeight(config, currentViewport),
|
|
192
|
+
[
|
|
193
|
+
visualizationType,
|
|
194
|
+
currentViewport,
|
|
195
|
+
config.heights?.vertical,
|
|
196
|
+
config.heights?.horizontal,
|
|
197
|
+
config.heights?.mobileVertical,
|
|
198
|
+
config.orientation
|
|
199
|
+
]
|
|
188
200
|
)
|
|
189
201
|
const forestHeight = useMemo(() => initialHeight + forestRowsHeight, [initialHeight, forestRowsHeight])
|
|
190
202
|
|
|
191
203
|
// Used to calculate the y position of the x-axis title
|
|
204
|
+
// Dependencies trigger recalc when axis config changes (affects label rendering/size)
|
|
192
205
|
const bottomLabelStart = useMemo(() => {
|
|
193
206
|
xAxisLabelRefs.current = xAxisLabelRefs.current?.filter(label => label)
|
|
194
207
|
if (!xAxisLabelRefs.current.length) return
|
|
195
208
|
const tallestLabel = Math.max(...xAxisLabelRefs.current.map(label => label.getBBox().height))
|
|
196
209
|
return tallestLabel + X_TICK_LABEL_PADDING + DEFAULT_TICK_LENGTH
|
|
197
|
-
}, [parentWidth, config.xAxis, xAxisLabelRefs.current,
|
|
210
|
+
}, [parentWidth, config.xAxis.tickRotation, config.xAxis.hideLabel, xAxisLabelRefs.current, axisUpdateKey])
|
|
198
211
|
|
|
199
212
|
const yMax = initialHeight + forestRowsHeight
|
|
200
213
|
|
|
201
214
|
const isNoDataAvailable = config.filters?.length > 0 && data.length === 0
|
|
202
215
|
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
216
|
+
// Memoized data accessors to prevent unnecessary re-renders
|
|
217
|
+
const getXAxisData = useCallback(
|
|
218
|
+
d =>
|
|
219
|
+
isDateScale(config.runtime.xAxis)
|
|
220
|
+
? parseDate(d[config.runtime.originalXAxis.dataKey]).getTime()
|
|
221
|
+
: d[config.runtime.originalXAxis.dataKey],
|
|
222
|
+
[config.runtime.xAxis, config.runtime.originalXAxis.dataKey, parseDate]
|
|
223
|
+
)
|
|
224
|
+
const getYAxisData = useCallback((d, seriesKey) => d[seriesKey], [])
|
|
225
|
+
|
|
226
|
+
const xAxisDataMapped = useMemo(() => data.map(d => getXAxisData(d)), [data, getXAxisData])
|
|
209
227
|
|
|
210
228
|
// Get unique x-axis values (for cases where multiple series share the same x-axis value)
|
|
211
229
|
// This is important for brush filtering where we want to count unique dates, not total data points
|
|
@@ -221,9 +239,22 @@ const LinearChart = forwardRef<SVGAElement, LinearChartProps>(({ parentHeight, p
|
|
|
221
239
|
}
|
|
222
240
|
return result
|
|
223
241
|
}, [xAxisDataMapped])
|
|
242
|
+
|
|
243
|
+
// Force update x axis ticks when filtering
|
|
244
|
+
useLayoutEffect(() => {
|
|
245
|
+
setAxisUpdateKey(prev => prev + 1)
|
|
246
|
+
}, [data.length, xAxisDataMapped?.[0], xAxisDataMapped?.[xAxisDataMapped.length - 1]])
|
|
247
|
+
|
|
224
248
|
const { yScaleRight, hasRightAxis } = useRightAxis({ config, yMax, data })
|
|
225
249
|
|
|
226
|
-
|
|
250
|
+
// State for computed y-axis width - allows re-render when horizontal bar label space is calculated
|
|
251
|
+
const [computedYAxisWidth, setComputedYAxisWidth] = useState<number | null>(null)
|
|
252
|
+
|
|
253
|
+
// Use computed width if available, otherwise fall back to config value
|
|
254
|
+
const yAxisWidth = computedYAxisWidth ?? Number(runtime.yAxis.size)
|
|
255
|
+
|
|
256
|
+
// Chart width calculation using the current y-axis width
|
|
257
|
+
const xMax = parentWidth - yAxisWidth - (hasRightAxis ? config.yAxis.rightAxisSize : 0)
|
|
227
258
|
|
|
228
259
|
const {
|
|
229
260
|
xScale,
|
|
@@ -233,10 +264,9 @@ const LinearChart = forwardRef<SVGAElement, LinearChartProps>(({ parentHeight, p
|
|
|
233
264
|
g2xScale,
|
|
234
265
|
xScaleNoPadding,
|
|
235
266
|
xScaleAnnotation,
|
|
267
|
+
yScaleAnnotation,
|
|
236
268
|
min,
|
|
237
|
-
max
|
|
238
|
-
leftMax,
|
|
239
|
-
rightMax
|
|
269
|
+
max
|
|
240
270
|
} = useScales({
|
|
241
271
|
data,
|
|
242
272
|
tableData,
|
|
@@ -252,6 +282,16 @@ const LinearChart = forwardRef<SVGAElement, LinearChartProps>(({ parentHeight, p
|
|
|
252
282
|
currentViewport
|
|
253
283
|
})
|
|
254
284
|
|
|
285
|
+
// Consolidated tick formatters
|
|
286
|
+
const { handleLeftTickFormatting, handleBottomTickFormatting } = useTickFormatters({
|
|
287
|
+
isLogarithmicAxis,
|
|
288
|
+
orientation,
|
|
289
|
+
visualizationType,
|
|
290
|
+
min,
|
|
291
|
+
max,
|
|
292
|
+
shouldAbbreviate
|
|
293
|
+
})
|
|
294
|
+
|
|
255
295
|
// Calculate category label space for horizontal bar charts
|
|
256
296
|
const categoryLabelSpace = useMemo(() => {
|
|
257
297
|
return calculateHorizontalBarCategoryLabelWidth({
|
|
@@ -266,9 +306,19 @@ const LinearChart = forwardRef<SVGAElement, LinearChartProps>(({ parentHeight, p
|
|
|
266
306
|
}, [isHorizontal, config.visualizationType, config.yAxis.labelPlacement, yScale, parentWidth])
|
|
267
307
|
|
|
268
308
|
const horizontalYAxisLabelSpace = runtime.yAxis.label && !config.hideYAxisLabel ? 30 : 0
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
309
|
+
|
|
310
|
+
// Update y-axis width state when computed value changes (for horizontal bar charts)
|
|
311
|
+
useEffect(() => {
|
|
312
|
+
if (isHorizontal && config.visualizationType === 'Bar') {
|
|
313
|
+
const newWidth = categoryLabelSpace + horizontalYAxisLabelSpace
|
|
314
|
+
if (newWidth !== computedYAxisWidth) {
|
|
315
|
+
setComputedYAxisWidth(newWidth)
|
|
316
|
+
}
|
|
317
|
+
} else if (computedYAxisWidth !== null) {
|
|
318
|
+
// Reset to null for non-horizontal bar charts so we use config value
|
|
319
|
+
setComputedYAxisWidth(null)
|
|
320
|
+
}
|
|
321
|
+
}, [isHorizontal, config.visualizationType, categoryLabelSpace, horizontalYAxisLabelSpace, computedYAxisWidth])
|
|
272
322
|
|
|
273
323
|
const [yTickCount, xTickCount] = ['yAxis', 'xAxis'].map(axis =>
|
|
274
324
|
countNumOfTicks({ axis, max, runtime, currentViewport, isHorizontal, data, config, min })
|
|
@@ -312,65 +362,19 @@ const LinearChart = forwardRef<SVGAElement, LinearChartProps>(({ parentHeight, p
|
|
|
312
362
|
const useDateSpanMonths = isDateTime && dateSpanMonths > xTickCount && !config.runtime.xAxis.manual
|
|
313
363
|
|
|
314
364
|
// GETTERS & FUNCTIONS
|
|
315
|
-
const handleLeftTickFormatting = (tick, index, ticks) => {
|
|
316
|
-
if (isLogarithmicAxis && tick === 0.1) {
|
|
317
|
-
//when logarithmic scale applied change value of first tick
|
|
318
|
-
tick = 0
|
|
319
|
-
}
|
|
320
|
-
|
|
321
|
-
if (config.data && !config.data[index] && visualizationType === 'Forest Plot') return
|
|
322
|
-
if (config.visualizationType === 'Forest Plot') return config.data[index][config.xAxis.dataKey]
|
|
323
|
-
if (isDateScale(runtime.yAxis)) return formatDate(parseDate(tick))
|
|
324
|
-
if (orientation === 'vertical' && max - min < 3 && !config.dataFormat?.roundTo)
|
|
325
|
-
return formatNumber(tick, 'left', shouldAbbreviate, false, false, '1', { index, length: ticks.length })
|
|
326
|
-
if (orientation === 'vertical') {
|
|
327
|
-
// TODO suggestion: pass all options as object key/values to allow for more flexibility
|
|
328
|
-
return formatNumber(tick, 'left', shouldAbbreviate, false, false, undefined, { index, length: ticks.length })
|
|
329
|
-
}
|
|
330
|
-
return tick
|
|
331
|
-
}
|
|
332
|
-
|
|
333
|
-
const handleBottomTickFormatting = (tick, i, ticks) => {
|
|
334
|
-
if (isLogarithmicAxis && tick === 0.1) {
|
|
335
|
-
// when logarithmic scale applied change value FIRST of tick
|
|
336
|
-
tick = 0
|
|
337
|
-
}
|
|
338
|
-
|
|
339
|
-
if (isDateScale(runtime.xAxis) && config.visualizationType !== 'Forest Plot') {
|
|
340
|
-
return formatDate(tick, i, ticks)
|
|
341
|
-
}
|
|
342
|
-
if (orientation === 'horizontal' && config.visualizationType !== 'Forest Plot')
|
|
343
|
-
return formatNumber(tick, 'left', shouldAbbreviate)
|
|
344
|
-
if (config.xAxis.type === 'continuous' && config.visualizationType !== 'Forest Plot')
|
|
345
|
-
return formatNumber(tick, 'bottom', shouldAbbreviate)
|
|
346
|
-
if (config.visualizationType === 'Forest Plot')
|
|
347
|
-
return formatNumber(
|
|
348
|
-
tick,
|
|
349
|
-
'left',
|
|
350
|
-
config.dataFormat.abbreviated,
|
|
351
|
-
config.runtime.xAxis.prefix,
|
|
352
|
-
config.runtime.xAxis.suffix,
|
|
353
|
-
Number(config.dataFormat.roundTo)
|
|
354
|
-
)
|
|
355
|
-
return tick
|
|
356
|
-
}
|
|
357
|
-
|
|
358
365
|
const chartHasTooltipGuides = () => {
|
|
359
366
|
const { visualizationType } = config
|
|
360
367
|
if (visualizationType === 'Combo' && runtime.forecastingSeriesKeys > 0) return true
|
|
361
|
-
|
|
362
|
-
if (visualizationType === 'Line') return true
|
|
363
|
-
if (visualizationType === 'Bar') return true
|
|
364
|
-
return false
|
|
368
|
+
return TYPES_WITH_TOOLTIP_GUIDES.includes(visualizationType as any)
|
|
365
369
|
}
|
|
366
370
|
|
|
367
|
-
const getManualStep = () => {
|
|
371
|
+
const getManualStep = useCallback(() => {
|
|
368
372
|
let manualStep = config.xAxis.manualStep
|
|
369
373
|
if (config.xAxis.viewportStepCount && config.xAxis.viewportStepCount[currentViewport]) {
|
|
370
374
|
manualStep = config.xAxis.viewportStepCount[currentViewport]
|
|
371
375
|
}
|
|
372
376
|
return manualStep
|
|
373
|
-
}
|
|
377
|
+
}, [config.xAxis.manualStep, config.xAxis.viewportStepCount, currentViewport])
|
|
374
378
|
|
|
375
379
|
const smallMultiplesSync = useSmallMultipleSynchronization(xMax, yMax, getXValueFromCoordinate)
|
|
376
380
|
|
|
@@ -384,11 +388,17 @@ const LinearChart = forwardRef<SVGAElement, LinearChartProps>(({ parentHeight, p
|
|
|
384
388
|
y
|
|
385
389
|
})
|
|
386
390
|
|
|
387
|
-
|
|
391
|
+
// Warming Stripes handles its own sync at the rect level since
|
|
392
|
+
// getXValueFromCoordinate won't map correctly due to data sampling
|
|
393
|
+
if (visualizationType !== 'Warming Stripes') {
|
|
394
|
+
smallMultiplesSync.onMouseMove?.(event)
|
|
395
|
+
}
|
|
388
396
|
}
|
|
389
397
|
|
|
390
398
|
const onMouseLeave = () => {
|
|
391
|
-
|
|
399
|
+
if (visualizationType !== 'Warming Stripes') {
|
|
400
|
+
smallMultiplesSync.onMouseLeave?.()
|
|
401
|
+
}
|
|
392
402
|
}
|
|
393
403
|
|
|
394
404
|
// Use custom hook to provide programmatic tooltip control for small multiples
|
|
@@ -399,24 +409,23 @@ const LinearChart = forwardRef<SVGAElement, LinearChartProps>(({ parentHeight, p
|
|
|
399
409
|
setPoint,
|
|
400
410
|
setShowHoverLine,
|
|
401
411
|
handleTooltipMouseOver,
|
|
402
|
-
hideTooltip
|
|
412
|
+
hideTooltip,
|
|
413
|
+
setSynchronizedXValue
|
|
403
414
|
})
|
|
404
415
|
|
|
405
416
|
// Make sure the chart is visible if in the editor
|
|
406
|
-
/* eslint-disable react-hooks/exhaustive-deps */
|
|
407
417
|
useEffect(() => {
|
|
408
418
|
const element = document.querySelector('.isEditor')
|
|
409
419
|
if (element) {
|
|
410
|
-
|
|
411
|
-
setAnimatedChart(prevState => true)
|
|
420
|
+
setAnimatedChart(true)
|
|
412
421
|
}
|
|
413
|
-
})
|
|
422
|
+
}, [])
|
|
414
423
|
|
|
415
424
|
// If the chart is in view, set to animate if it has not already played
|
|
416
425
|
useEffect(() => {
|
|
417
426
|
if (dataRef?.isIntersecting === true && config.animate) {
|
|
418
427
|
setTimeout(() => {
|
|
419
|
-
setAnimatedChart(
|
|
428
|
+
setAnimatedChart(true)
|
|
420
429
|
}, 500)
|
|
421
430
|
}
|
|
422
431
|
}, [dataRef?.isIntersecting, config.animate])
|
|
@@ -461,9 +470,7 @@ const LinearChart = forwardRef<SVGAElement, LinearChartProps>(({ parentHeight, p
|
|
|
461
470
|
const svgHeight = initialHeight + svgAdditionalHeight
|
|
462
471
|
|
|
463
472
|
// Parent container height (includes brush if active)
|
|
464
|
-
const
|
|
465
|
-
const brushMargin = 10
|
|
466
|
-
const brushHeightWithMargin = config.xAxis.brushActive ? brushHeight + brushMargin : 0
|
|
473
|
+
const brushHeightWithMargin = config.xAxis.brushActive ? BRUSH_HEIGHT + BRUSH_MARGIN : 0
|
|
467
474
|
const parentHeight = svgHeight + brushHeightWithMargin
|
|
468
475
|
|
|
469
476
|
if (!parentRef.current) return
|
|
@@ -491,15 +498,7 @@ const LinearChart = forwardRef<SVGAElement, LinearChartProps>(({ parentHeight, p
|
|
|
491
498
|
const legendIsLeftOrRight =
|
|
492
499
|
legend?.position !== 'top' && legend?.position !== 'bottom' && !isLegendWrapViewport(currentViewport)
|
|
493
500
|
legendRef.current.style.transform = legendIsLeftOrRight ? `translateY(${topLabelOnGridlineHeight}px)` : 'none'
|
|
494
|
-
}, [
|
|
495
|
-
axisBottomRef.current,
|
|
496
|
-
config,
|
|
497
|
-
bottomLabelStart,
|
|
498
|
-
config.xAxis.brushActive,
|
|
499
|
-
currentViewport,
|
|
500
|
-
topYLabelRef.current,
|
|
501
|
-
initialHeight
|
|
502
|
-
])
|
|
501
|
+
}, [axisBottomRef.current, config, config.xAxis.brushActive, currentViewport, topYLabelRef.current, initialHeight])
|
|
503
502
|
|
|
504
503
|
useEffect(() => {
|
|
505
504
|
if (!tooltipOpen) return
|
|
@@ -513,8 +512,8 @@ const LinearChart = forwardRef<SVGAElement, LinearChartProps>(({ parentHeight, p
|
|
|
513
512
|
|
|
514
513
|
const rightSideRemainingSpace = parentWidth - dataXPosition
|
|
515
514
|
|
|
516
|
-
const rightSide = rightSideRemainingSpace <= tooltipWidth && dataXPosition > parentWidth / 2 -
|
|
517
|
-
const maxWidth = rightSide ? dataXPosition -
|
|
515
|
+
const rightSide = rightSideRemainingSpace <= tooltipWidth && dataXPosition > parentWidth / 2 - TOOLTIP_EDGE_BUFFER
|
|
516
|
+
const maxWidth = rightSide ? dataXPosition - TOOLTIP_EDGE_BUFFER : parentWidth - (dataXPosition + TOOLTIP_OFFSET)
|
|
518
517
|
tooltipRef.current.node.style.maxWidth = `${maxWidth}px`
|
|
519
518
|
}, [tooltipOpen, tooltipData])
|
|
520
519
|
|
|
@@ -531,150 +530,6 @@ const LinearChart = forwardRef<SVGAElement, LinearChartProps>(({ parentHeight, p
|
|
|
531
530
|
)
|
|
532
531
|
}
|
|
533
532
|
|
|
534
|
-
// Render Functions
|
|
535
|
-
const generatePairedBarAxis = () => {
|
|
536
|
-
const axisMaxHeight = bottomLabelStart + BOTTOM_LABEL_PADDING
|
|
537
|
-
|
|
538
|
-
const getTickPositions = (ticks, xScale) => {
|
|
539
|
-
if (!ticks.length) return false
|
|
540
|
-
// filter out first index
|
|
541
|
-
const filteredTicks = ticks.filter(tick => tick.index !== 0)
|
|
542
|
-
const numberOfTicks = filteredTicks?.length
|
|
543
|
-
const xMaxHalf = xScale.range()[0] || xMax / 2
|
|
544
|
-
const tickWidthAll = filteredTicks.map(tick =>
|
|
545
|
-
getTextWidth(formatNumber(tick.value, 'left'), GET_TEXT_WIDTH_FONT)
|
|
546
|
-
)
|
|
547
|
-
const accumulator = 100
|
|
548
|
-
const sumOfTickWidth = tickWidthAll.reduce((a, b) => a + b, accumulator)
|
|
549
|
-
const spaceBetweenEachTick = (xMaxHalf - sumOfTickWidth) / numberOfTicks
|
|
550
|
-
// Determine the position of each tick
|
|
551
|
-
let positions = [0]
|
|
552
|
-
for (let i = 1; i < tickWidthAll.length; i++) {
|
|
553
|
-
positions[i] = positions[i - 1] + tickWidthAll[i - 1] + spaceBetweenEachTick
|
|
554
|
-
}
|
|
555
|
-
|
|
556
|
-
// Check if ticks are overlapping
|
|
557
|
-
let isTicksOverlapping = false
|
|
558
|
-
tickWidthAll.forEach((_, i) => {
|
|
559
|
-
if (positions[i] + tickWidthAll[i] > positions[i + 1]) {
|
|
560
|
-
isTicksOverlapping = true
|
|
561
|
-
return
|
|
562
|
-
}
|
|
563
|
-
})
|
|
564
|
-
return isTicksOverlapping
|
|
565
|
-
}
|
|
566
|
-
return (
|
|
567
|
-
<>
|
|
568
|
-
<AxisBottom
|
|
569
|
-
top={yMax}
|
|
570
|
-
left={Number(runtime.yAxis.size)}
|
|
571
|
-
label={runtime.xAxis.label}
|
|
572
|
-
tickFormat={isDateScale(runtime.xAxis) ? formatDate : formatNumber}
|
|
573
|
-
scale={g1xScale}
|
|
574
|
-
stroke='#333'
|
|
575
|
-
tickStroke='#333'
|
|
576
|
-
numTicks={runtime.xAxis.numTicks || undefined}
|
|
577
|
-
>
|
|
578
|
-
{props => {
|
|
579
|
-
return (
|
|
580
|
-
<Group className='bottom-axis'>
|
|
581
|
-
{props.ticks.map((tick, i) => {
|
|
582
|
-
const isTicksOverlapping = getTickPositions(props.ticks, g1xScale)
|
|
583
|
-
const maxTickRotation = Number(config.xAxis.maxTickRotation) || 90
|
|
584
|
-
const isResponsiveTicks = config.isResponsiveTicks && isTicksOverlapping
|
|
585
|
-
const angle =
|
|
586
|
-
tick.index !== 0 && (isResponsiveTicks ? maxTickRotation : Number(config.yAxis.tickRotation))
|
|
587
|
-
const textAnchor = angle && tick.index !== 0 ? 'end' : 'middle'
|
|
588
|
-
|
|
589
|
-
return (
|
|
590
|
-
<Group key={`vx-tick-${tick.value}-${i}`} className={'vx-axis-tick'}>
|
|
591
|
-
{!runtime.yAxis.hideTicks && <Line from={tick.from} to={tick.to} stroke='#333' />}
|
|
592
|
-
{!runtime.yAxis.hideLabel && (
|
|
593
|
-
<Text // prettier-ignore
|
|
594
|
-
innerRef={el => (xAxisLabelRefs.current[i] = el)}
|
|
595
|
-
x={tick.to.x}
|
|
596
|
-
y={tick.to.y}
|
|
597
|
-
angle={-angle}
|
|
598
|
-
verticalAnchor={angle ? 'middle' : 'start'}
|
|
599
|
-
textAnchor={textAnchor}
|
|
600
|
-
fontSize={tickLabelFontSize}
|
|
601
|
-
>
|
|
602
|
-
{formatNumber(tick.value, 'left')}
|
|
603
|
-
</Text>
|
|
604
|
-
)}
|
|
605
|
-
</Group>
|
|
606
|
-
)
|
|
607
|
-
})}
|
|
608
|
-
{!runtime.yAxis.hideAxis && <Line from={props.axisFromPoint} to={props.axisToPoint} stroke='#333' />}
|
|
609
|
-
</Group>
|
|
610
|
-
)
|
|
611
|
-
}}
|
|
612
|
-
</AxisBottom>
|
|
613
|
-
<AxisBottom
|
|
614
|
-
innerRef={axisBottomRef}
|
|
615
|
-
top={yMax}
|
|
616
|
-
left={Number(runtime.yAxis.size)}
|
|
617
|
-
label={runtime.xAxis.label}
|
|
618
|
-
tickFormat={
|
|
619
|
-
isDateScale(runtime.xAxis) ? formatDate : runtime.xAxis.dataKey !== 'Year' ? formatNumber : tick => tick
|
|
620
|
-
}
|
|
621
|
-
scale={g2xScale}
|
|
622
|
-
stroke='#333'
|
|
623
|
-
tickStroke='#333'
|
|
624
|
-
numTicks={runtime.xAxis.numTicks || undefined}
|
|
625
|
-
>
|
|
626
|
-
{props => {
|
|
627
|
-
return (
|
|
628
|
-
<>
|
|
629
|
-
<Group className='bottom-axis'>
|
|
630
|
-
{props.ticks.map((tick, i) => {
|
|
631
|
-
const isTicksOverlapping = getTickPositions(props.ticks, g2xScale)
|
|
632
|
-
const maxTickRotation = Number(config.xAxis.maxTickRotation) || 90
|
|
633
|
-
const isResponsiveTicks = config.isResponsiveTicks && isTicksOverlapping
|
|
634
|
-
const angle =
|
|
635
|
-
tick.index !== 0 && (isResponsiveTicks ? maxTickRotation : Number(config.yAxis.tickRotation))
|
|
636
|
-
const textAnchor = angle && tick.index !== 0 ? 'end' : 'middle'
|
|
637
|
-
if (!i) return <></> // skip first tick to avoid overlapping 0's
|
|
638
|
-
return (
|
|
639
|
-
<Group key={`vx-tick-${tick.value}-${i}`} className={'vx-axis-tick'}>
|
|
640
|
-
{!runtime.yAxis.hideTicks && <Line from={tick.from} to={tick.to} stroke='#333' />}
|
|
641
|
-
{!runtime.yAxis.hideLabel && (
|
|
642
|
-
<Text // prettier-ignore
|
|
643
|
-
x={tick.to.x}
|
|
644
|
-
y={tick.to.y + X_TICK_LABEL_PADDING}
|
|
645
|
-
angle={-angle}
|
|
646
|
-
verticalAnchor={angle ? 'middle' : 'start'}
|
|
647
|
-
textAnchor={textAnchor}
|
|
648
|
-
fontSize={tickLabelFontSize}
|
|
649
|
-
>
|
|
650
|
-
{formatNumber(tick.value, 'left')}
|
|
651
|
-
</Text>
|
|
652
|
-
)}
|
|
653
|
-
</Group>
|
|
654
|
-
)
|
|
655
|
-
})}
|
|
656
|
-
{!runtime.yAxis.hideAxis && <Line from={props.axisFromPoint} to={props.axisToPoint} stroke='#333' />}
|
|
657
|
-
</Group>
|
|
658
|
-
<Group>
|
|
659
|
-
<Text
|
|
660
|
-
className='x-axis-title-label'
|
|
661
|
-
x={xMax / 2}
|
|
662
|
-
y={axisMaxHeight}
|
|
663
|
-
stroke='#333'
|
|
664
|
-
textAnchor={'middle'}
|
|
665
|
-
verticalAnchor='start'
|
|
666
|
-
fontSize={axisLabelFontSize}
|
|
667
|
-
>
|
|
668
|
-
{!config.hideXAxisLabel ? runtime.xAxis.label : null}
|
|
669
|
-
</Text>
|
|
670
|
-
</Group>
|
|
671
|
-
</>
|
|
672
|
-
)
|
|
673
|
-
}}
|
|
674
|
-
</AxisBottom>
|
|
675
|
-
</>
|
|
676
|
-
)
|
|
677
|
-
}
|
|
678
533
|
return isNaN(parentWidth) ? (
|
|
679
534
|
<React.Fragment></React.Fragment>
|
|
680
535
|
) : (
|
|
@@ -707,60 +562,20 @@ const LinearChart = forwardRef<SVGAElement, LinearChartProps>(({ parentHeight, p
|
|
|
707
562
|
>
|
|
708
563
|
{!isDraggingAnnotation && <Bar width={parentWidth} height={initialHeight} fill={'transparent'}></Bar>}{' '}
|
|
709
564
|
{/* GRID LINES */}
|
|
710
|
-
{/* Actual
|
|
711
|
-
{!
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
? Math.abs(props.axisToPoint.y - props.axisFromPoint.y) / 2
|
|
722
|
-
: (props.axisFromPoint.y - props.axisToPoint.y) / 2
|
|
723
|
-
return (
|
|
724
|
-
<Group className='left-axis'>
|
|
725
|
-
{props.ticks.map((tick, i) => {
|
|
726
|
-
const showTicks = String(tick.value).startsWith('1') || tick.value === 0.1 ? 'block' : 'none'
|
|
727
|
-
const hideFirstGridLine = tick.index === 0 && tick.value === 0 && config.xAxis.hideAxis
|
|
728
|
-
|
|
729
|
-
return (
|
|
730
|
-
<Group key={`vx-tick-${tick.value}-${i}`} className={'vx-axis-tick'}>
|
|
731
|
-
{runtime.yAxis.gridLines && !hideFirstGridLine ? (
|
|
732
|
-
<Line
|
|
733
|
-
key={`${tick.value}--hide-hideGridLines`}
|
|
734
|
-
display={(isLogarithmicAxis && showTicks).toString()}
|
|
735
|
-
from={{ x: tick.from.x + xMax, y: tick.from.y }}
|
|
736
|
-
to={tick.from}
|
|
737
|
-
stroke='#d6d6d6'
|
|
738
|
-
/>
|
|
739
|
-
) : (
|
|
740
|
-
''
|
|
741
|
-
)}
|
|
742
|
-
</Group>
|
|
743
|
-
)
|
|
744
|
-
})}
|
|
745
|
-
<Text
|
|
746
|
-
className='y-label'
|
|
747
|
-
textAnchor='middle'
|
|
748
|
-
verticalAnchor='start'
|
|
749
|
-
transform={`translate(${-1 * runtime.yAxis.size + yLabelOffset}, ${axisCenter}) rotate(-90)`}
|
|
750
|
-
fontWeight='bold'
|
|
751
|
-
fill={config.yAxis.labelColor}
|
|
752
|
-
fontSize={axisLabelFontSize}
|
|
753
|
-
>
|
|
754
|
-
{!config.hideYAxisLabel ? props.label : null}
|
|
755
|
-
</Text>
|
|
756
|
-
</Group>
|
|
757
|
-
)
|
|
758
|
-
}}
|
|
759
|
-
</AxisLeft>
|
|
760
|
-
)}
|
|
565
|
+
{/* Actual LeftAxis is drawn after visualization */}
|
|
566
|
+
{!TYPES_WITHOUT_GRID.includes(visualizationType as any) && config.yAxis.type !== 'categorical' && (
|
|
567
|
+
<LeftAxisGridlines
|
|
568
|
+
yScale={yScale}
|
|
569
|
+
xMax={xMax}
|
|
570
|
+
yAxisWidth={yAxisWidth}
|
|
571
|
+
numTicks={handleNumTicks}
|
|
572
|
+
yLabelOffset={yLabelOffset}
|
|
573
|
+
axisLabelFontSize={axisLabelFontSize}
|
|
574
|
+
/>
|
|
575
|
+
)}
|
|
761
576
|
{/* Horizontal chart grid lines */}
|
|
762
577
|
{runtime.xAxis.gridLines && orientation === 'horizontal' && (
|
|
763
|
-
<Group left={
|
|
578
|
+
<Group left={yAxisWidth}>
|
|
764
579
|
{xScale.ticks(xTickCount).map((tickValue, i) => {
|
|
765
580
|
const tickPosition = xScale(tickValue)
|
|
766
581
|
return (
|
|
@@ -774,167 +589,46 @@ const LinearChart = forwardRef<SVGAElement, LinearChartProps>(({ parentHeight, p
|
|
|
774
589
|
})}
|
|
775
590
|
</Group>
|
|
776
591
|
)}
|
|
777
|
-
{visualizationType === 'Paired Bar' && generatePairedBarAxis()}
|
|
778
|
-
{visualizationType === 'Deviation Bar' && config.runtime.series?.length === 1 && (
|
|
779
|
-
<DeviationBar animatedChart={animatedChart} xScale={xScale} yScale={yScale} width={xMax} height={yMax} />
|
|
780
|
-
)}
|
|
781
592
|
{visualizationType === 'Paired Bar' && (
|
|
782
|
-
<
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
<ScatterPlot
|
|
786
|
-
xScale={xScale}
|
|
787
|
-
yScale={yScale}
|
|
788
|
-
getXAxisData={getXAxisData}
|
|
789
|
-
getYAxisData={getYAxisData}
|
|
790
|
-
xMax={xMax}
|
|
593
|
+
<PairedBarAxis
|
|
594
|
+
g1xScale={g1xScale}
|
|
595
|
+
g2xScale={g2xScale}
|
|
791
596
|
yMax={yMax}
|
|
792
|
-
handleTooltipMouseOver={handleTooltipMouseOver}
|
|
793
|
-
handleTooltipMouseOff={handleTooltipMouseOff}
|
|
794
|
-
handleTooltipClick={handleTooltipClick}
|
|
795
|
-
tooltipData={tooltipData}
|
|
796
|
-
showTooltip={showTooltip}
|
|
797
|
-
/>
|
|
798
|
-
)}
|
|
799
|
-
{visualizationType === 'Warming Stripes' && (
|
|
800
|
-
<WarmingStripes xScale={xScale} yScale={yScale} xMax={xMax} yMax={yMax} />
|
|
801
|
-
)}
|
|
802
|
-
{visualizationType === 'Box Plot' && config.orientation === 'vertical' && (
|
|
803
|
-
<BoxPlotVertical
|
|
804
|
-
seriesScale={seriesScale}
|
|
805
597
|
xMax={xMax}
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
{visualizationType === 'Box Plot' && config.orientation === 'horizontal' && (
|
|
814
|
-
<BoxPlotHorizontal
|
|
815
|
-
seriesScale={seriesScale}
|
|
816
|
-
xMax={xMax}
|
|
817
|
-
yMax={yMax}
|
|
818
|
-
min={min}
|
|
819
|
-
max={max}
|
|
820
|
-
xScale={xScale}
|
|
821
|
-
yScale={yScale}
|
|
822
|
-
/>
|
|
823
|
-
)}
|
|
824
|
-
{((visualizationType === 'Area Chart' && config.visualizationSubType === 'stacked') ||
|
|
825
|
-
visualizationType === 'Combo') && (
|
|
826
|
-
<AreaChartStacked
|
|
827
|
-
xScale={xScale}
|
|
828
|
-
yScale={yScale}
|
|
829
|
-
yMax={yMax}
|
|
830
|
-
xMax={xMax}
|
|
831
|
-
chartRef={svgRef}
|
|
832
|
-
width={xMax}
|
|
833
|
-
height={yMax}
|
|
834
|
-
handleTooltipMouseOver={handleTooltipMouseOver}
|
|
835
|
-
handleTooltipMouseOff={handleTooltipMouseOff}
|
|
836
|
-
tooltipData={tooltipData}
|
|
837
|
-
showTooltip={showTooltip}
|
|
838
|
-
/>
|
|
839
|
-
)}
|
|
840
|
-
{(visualizationType === 'Bar' || visualizationType === 'Combo' || convertLineToBarGraph) && (
|
|
841
|
-
<BarChart
|
|
842
|
-
xScale={xScale}
|
|
843
|
-
yScale={yScale}
|
|
844
|
-
seriesScale={seriesScale}
|
|
845
|
-
xMax={xMax}
|
|
846
|
-
yMax={yMax}
|
|
847
|
-
getXAxisData={getXAxisData}
|
|
848
|
-
getYAxisData={getYAxisData}
|
|
849
|
-
animatedChart={animatedChart}
|
|
850
|
-
visible={animatedChart}
|
|
851
|
-
handleTooltipMouseOver={handleTooltipMouseOver}
|
|
852
|
-
handleTooltipMouseOff={handleTooltipMouseOff}
|
|
853
|
-
handleTooltipClick={handleTooltipClick}
|
|
854
|
-
tooltipData={tooltipData}
|
|
855
|
-
showTooltip={showTooltip}
|
|
856
|
-
chartRef={svgRef}
|
|
857
|
-
/>
|
|
858
|
-
)}
|
|
859
|
-
{(visualizationType === 'Combo' || visualizationType === 'Bump Chart') && (
|
|
860
|
-
<LineChart
|
|
861
|
-
xScale={xScale}
|
|
862
|
-
yScale={yScale}
|
|
863
|
-
getXAxisData={getXAxisData}
|
|
864
|
-
getYAxisData={getYAxisData}
|
|
865
|
-
xMax={xMax}
|
|
866
|
-
yMax={yMax}
|
|
867
|
-
seriesStyle={config.runtime.series}
|
|
868
|
-
handleTooltipMouseOver={handleTooltipMouseOver}
|
|
869
|
-
handleTooltipMouseOff={handleTooltipMouseOff}
|
|
870
|
-
handleTooltipClick={handleTooltipClick}
|
|
871
|
-
tooltipData={tooltipData}
|
|
872
|
-
showTooltip={showTooltip}
|
|
873
|
-
/>
|
|
874
|
-
)}
|
|
875
|
-
{/* Line chart */}
|
|
876
|
-
{/* TODO: Make this just line or combo? */}
|
|
877
|
-
{![
|
|
878
|
-
'Paired Bar',
|
|
879
|
-
'Box Plot',
|
|
880
|
-
'Area Chart',
|
|
881
|
-
'Scatter Plot',
|
|
882
|
-
'Deviation Bar',
|
|
883
|
-
'Forecasting',
|
|
884
|
-
'Bar',
|
|
885
|
-
'Warming Stripes'
|
|
886
|
-
].includes(visualizationType) &&
|
|
887
|
-
!convertLineToBarGraph && (
|
|
888
|
-
<>
|
|
889
|
-
<LineChart
|
|
890
|
-
xScale={xScale}
|
|
891
|
-
yScale={yScale}
|
|
892
|
-
getXAxisData={getXAxisData}
|
|
893
|
-
getYAxisData={getYAxisData}
|
|
894
|
-
xMax={xMax}
|
|
895
|
-
yMax={yMax}
|
|
896
|
-
seriesStyle={config.runtime.series}
|
|
897
|
-
tooltipData={tooltipData}
|
|
898
|
-
handleTooltipMouseOver={handleTooltipMouseOver}
|
|
899
|
-
handleTooltipMouseOff={handleTooltipMouseOff}
|
|
900
|
-
/>
|
|
901
|
-
</>
|
|
902
|
-
)}
|
|
903
|
-
{(visualizationType === 'Forecasting' || visualizationType === 'Combo') && (
|
|
904
|
-
<Forecasting
|
|
905
|
-
showTooltip={showTooltip}
|
|
906
|
-
tooltipData={tooltipData}
|
|
907
|
-
xScale={xScale}
|
|
908
|
-
yScale={yScale}
|
|
909
|
-
width={xMax}
|
|
910
|
-
height={yMax}
|
|
911
|
-
xScaleNoPadding={xScaleNoPadding}
|
|
912
|
-
chartRef={svgRef}
|
|
913
|
-
handleTooltipMouseOver={handleTooltipMouseOver}
|
|
914
|
-
handleTooltipMouseOff={handleTooltipMouseOff}
|
|
915
|
-
/>
|
|
916
|
-
)}
|
|
917
|
-
{visualizationType === 'Forest Plot' && (
|
|
918
|
-
<ForestPlot
|
|
919
|
-
xScale={xScale}
|
|
920
|
-
yScale={yScale}
|
|
921
|
-
seriesScale={seriesScale}
|
|
922
|
-
width={parentWidth}
|
|
923
|
-
height={forestHeight}
|
|
924
|
-
getXAxisData={getXAxisData}
|
|
925
|
-
getYAxisData={getYAxisData}
|
|
926
|
-
animatedChart={animatedChart}
|
|
927
|
-
visible={animatedChart}
|
|
928
|
-
handleTooltipMouseOver={handleTooltipMouseOver}
|
|
929
|
-
handleTooltipMouseOff={handleTooltipMouseOff}
|
|
930
|
-
handleTooltipClick={handleTooltipClick}
|
|
931
|
-
tooltipData={tooltipData}
|
|
932
|
-
showTooltip={showTooltip}
|
|
933
|
-
chartRef={svgRef}
|
|
934
|
-
config={config}
|
|
935
|
-
forestPlotRightLabelRef={forestPlotRightLabelRef}
|
|
598
|
+
yAxisWidth={yAxisWidth}
|
|
599
|
+
bottomLabelStart={bottomLabelStart}
|
|
600
|
+
tickLabelFontSize={tickLabelFontSize}
|
|
601
|
+
axisLabelFontSize={axisLabelFontSize}
|
|
602
|
+
axisBottomRef={axisBottomRef}
|
|
603
|
+
xAxisLabelRefs={xAxisLabelRefs}
|
|
604
|
+
tickLabelFont={GET_TEXT_WIDTH_FONT}
|
|
936
605
|
/>
|
|
937
606
|
)}
|
|
607
|
+
{/* Visualization Renderer - handles all chart type rendering */}
|
|
608
|
+
<VisualizationRenderer
|
|
609
|
+
xScale={xScale}
|
|
610
|
+
yScale={yScale}
|
|
611
|
+
xMax={xMax}
|
|
612
|
+
yMax={yMax}
|
|
613
|
+
seriesScale={seriesScale}
|
|
614
|
+
xScaleNoPadding={xScaleNoPadding}
|
|
615
|
+
min={min}
|
|
616
|
+
max={max}
|
|
617
|
+
parentWidth={parentWidth}
|
|
618
|
+
yAxisWidth={yAxisWidth}
|
|
619
|
+
forestHeight={forestHeight}
|
|
620
|
+
animatedChart={animatedChart}
|
|
621
|
+
tooltipData={tooltipData}
|
|
622
|
+
showTooltip={showTooltip}
|
|
623
|
+
handleTooltipMouseOver={handleTooltipMouseOver}
|
|
624
|
+
handleTooltipMouseOff={handleTooltipMouseOff}
|
|
625
|
+
handleTooltipClick={handleTooltipClick}
|
|
626
|
+
getXAxisData={getXAxisData}
|
|
627
|
+
getYAxisData={getYAxisData}
|
|
628
|
+
svgRef={svgRef}
|
|
629
|
+
forestPlotRightLabelRef={forestPlotRightLabelRef}
|
|
630
|
+
synchronizedXValue={synchronizedXValue}
|
|
631
|
+
/>
|
|
938
632
|
{/* Brush moved to separate overlay - no longer in main SVG */}
|
|
939
633
|
{/* y anchors */}
|
|
940
634
|
{config.yAxis.anchors &&
|
|
@@ -944,11 +638,10 @@ const LinearChart = forwardRef<SVGAElement, LinearChartProps>(({ parentHeight, p
|
|
|
944
638
|
|
|
945
639
|
if (!anchor.value) return
|
|
946
640
|
if (config.yAxis.labelPlacement === 'Below Bar') {
|
|
947
|
-
|
|
948
|
-
|
|
641
|
+
middleOffset =
|
|
642
|
+
BELOW_BAR_TEXT_OFFSET + Number(config.series.length * config.barHeight) / config.series.length
|
|
949
643
|
} else {
|
|
950
|
-
|
|
951
|
-
middleOffset = paddingOffset
|
|
644
|
+
middleOffset = LABEL_PADDING_OFFSET
|
|
952
645
|
}
|
|
953
646
|
|
|
954
647
|
if (!position) return
|
|
@@ -956,13 +649,13 @@ const LinearChart = forwardRef<SVGAElement, LinearChartProps>(({ parentHeight, p
|
|
|
956
649
|
return (
|
|
957
650
|
// prettier-ignore
|
|
958
651
|
<Line
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
652
|
+
key={`yAxis-${anchor.value}--${index}`}
|
|
653
|
+
strokeDasharray={handleLineType(anchor.lineStyle)}
|
|
654
|
+
stroke={anchor.color ? anchor.color : 'rgba(0,0,0,1)'}
|
|
655
|
+
className='anchor-y'
|
|
656
|
+
from={{ x: Number(runtime.yAxis.size), y: position - middleOffset }}
|
|
657
|
+
to={{ x: Number(runtime.yAxis.size) + Number(xMax), y: position - middleOffset }}
|
|
658
|
+
/>
|
|
966
659
|
)
|
|
967
660
|
})}
|
|
968
661
|
{/* x anchors */}
|
|
@@ -992,14 +685,14 @@ const LinearChart = forwardRef<SVGAElement, LinearChartProps>(({ parentHeight, p
|
|
|
992
685
|
return (
|
|
993
686
|
// prettier-ignore
|
|
994
687
|
<Line
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
688
|
+
key={`xAxis-${anchor.value}--${index}`}
|
|
689
|
+
strokeDasharray={handleLineType(anchor.lineStyle)}
|
|
690
|
+
stroke={anchor.color ? anchor.color : 'rgba(0,0,0,1)'}
|
|
691
|
+
fill={anchor.color ? anchor.color : 'rgba(0,0,0,1)'}
|
|
692
|
+
className='anchor-x'
|
|
693
|
+
from={{ x: Number(anchorPosition) + Number(padding), y: 0 }}
|
|
694
|
+
to={{ x: Number(anchorPosition) + Number(padding), y: yMax }}
|
|
695
|
+
/>
|
|
1003
696
|
)
|
|
1004
697
|
})}
|
|
1005
698
|
{/* we are handling regions in bar charts differently, so that we can calculate the bar group into the region space. */}
|
|
@@ -1019,7 +712,7 @@ const LinearChart = forwardRef<SVGAElement, LinearChartProps>(({ parentHeight, p
|
|
|
1019
712
|
)}
|
|
1020
713
|
{isNoDataAvailable && (
|
|
1021
714
|
<Text
|
|
1022
|
-
x={
|
|
715
|
+
x={yAxisWidth + Number(xMax / 2)}
|
|
1023
716
|
y={initialHeight / 2 - (config.xAxis.padding || 0) / 2}
|
|
1024
717
|
textAnchor='middle'
|
|
1025
718
|
>
|
|
@@ -1032,405 +725,65 @@ const LinearChart = forwardRef<SVGAElement, LinearChartProps>(({ parentHeight, p
|
|
|
1032
725
|
<HoverLine xMax={xMax} yMax={yMax} point={point} tooltipData={tooltipData} orientation='vertical' />
|
|
1033
726
|
</>
|
|
1034
727
|
)}
|
|
1035
|
-
<Group left={
|
|
728
|
+
<Group left={yAxisWidth}>
|
|
1036
729
|
<Annotation.Draggable
|
|
1037
730
|
xScale={xScale}
|
|
1038
731
|
yScale={yScale}
|
|
1039
732
|
xScaleAnnotation={xScaleAnnotation}
|
|
733
|
+
yScaleAnnotation={yScaleAnnotation}
|
|
1040
734
|
xMax={xMax}
|
|
735
|
+
yMax={yMax}
|
|
736
|
+
seriesScale={seriesScale}
|
|
1041
737
|
svgRef={svgRef}
|
|
1042
738
|
onDragStateChange={handleDragStateChange}
|
|
1043
739
|
/>
|
|
1044
740
|
</Group>
|
|
1045
741
|
{/* Highlighted regions */}
|
|
1046
742
|
{/* Y axis */}
|
|
1047
|
-
{
|
|
743
|
+
{/* Horizon charts don't have a grid but should be rendered with a left axis */}
|
|
744
|
+
{(!TYPES_WITHOUT_GRID.includes(visualizationType as any) || visualizationType === 'Horizon Chart') &&
|
|
1048
745
|
config.yAxis.type !== 'categorical' && (
|
|
1049
|
-
<
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
tickFormat={handleLeftTickFormatting}
|
|
746
|
+
<LeftAxis
|
|
747
|
+
yScale={yScale}
|
|
748
|
+
xScale={xScale}
|
|
749
|
+
yMax={yMax}
|
|
750
|
+
xMax={xMax}
|
|
751
|
+
yAxisWidth={yAxisWidth}
|
|
1056
752
|
numTicks={handleNumTicks}
|
|
1057
|
-
|
|
1058
|
-
{
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
{!config.yAxis.hideAxis && (
|
|
1068
|
-
<Line
|
|
1069
|
-
from={props.axisFromPoint}
|
|
1070
|
-
to={
|
|
1071
|
-
runtime.horizontal
|
|
1072
|
-
? {
|
|
1073
|
-
x: 0,
|
|
1074
|
-
y:
|
|
1075
|
-
config.visualizationType === 'Forest Plot'
|
|
1076
|
-
? parentHeight
|
|
1077
|
-
: Number(heights.horizontal)
|
|
1078
|
-
}
|
|
1079
|
-
: props.axisToPoint
|
|
1080
|
-
}
|
|
1081
|
-
stroke='#000'
|
|
1082
|
-
/>
|
|
1083
|
-
)}
|
|
1084
|
-
{orientation === 'vertical' && yScale.domain()[0] < 0 && (
|
|
1085
|
-
// draw from the Left of the chart …
|
|
1086
|
-
<Line
|
|
1087
|
-
from={{ x: props.axisFromPoint.x, y: yScale(0) }}
|
|
1088
|
-
to={{ x: xMax, y: yScale(0) }}
|
|
1089
|
-
stroke='#333'
|
|
1090
|
-
/>
|
|
1091
|
-
)}
|
|
1092
|
-
{orientation === 'horizontal' && xScale.domain()[0] < 0 && (
|
|
1093
|
-
<Line
|
|
1094
|
-
// draw from the top of the char
|
|
1095
|
-
from={{ x: xScale(0), y: 0 }}
|
|
1096
|
-
to={{ x: xScale(0), y: yMax }}
|
|
1097
|
-
stroke='#333'
|
|
1098
|
-
/>
|
|
1099
|
-
)}
|
|
1100
|
-
{visualizationType === 'Bar' && orientation === 'horizontal' && xScale.domain()[0] < 0 && (
|
|
1101
|
-
<Line
|
|
1102
|
-
from={{ x: xScale(0), y: 0 }}
|
|
1103
|
-
to={{ x: xScale(0), y: yMax }}
|
|
1104
|
-
stroke='#333'
|
|
1105
|
-
strokeWidth={2}
|
|
1106
|
-
/>
|
|
1107
|
-
)}
|
|
1108
|
-
{props.ticks.map((tick, i) => {
|
|
1109
|
-
const minY = props.ticks[0].to.y
|
|
1110
|
-
const barMinHeight = 15 // 15 is the min height for bars by default
|
|
1111
|
-
const showTicks = String(tick.value).startsWith('1') || tick.value === 0.1 ? 'block' : 'none'
|
|
1112
|
-
const tickLength = showTicks === 'block' ? 7 : 0
|
|
1113
|
-
const to = { x: tick.to.x - tickLength, y: tick.to.y }
|
|
1114
|
-
|
|
1115
|
-
// Vertical value/suffix vars
|
|
1116
|
-
const lastTick = props.ticks.length - 1 === i
|
|
1117
|
-
const useInlineLabel = lastTick && inlineLabel
|
|
1118
|
-
const hideTopTick = lastTick && inlineLabel && !inlineLabelHasNoSpace
|
|
1119
|
-
const valueOnLinePadding = hideAxis ? -8 : -12
|
|
1120
|
-
const labelXPadding = labelsAboveGridlines ? valueOnLinePadding : TICK_LABEL_MARGIN_RIGHT
|
|
1121
|
-
const labelYPadding = labelsAboveGridlines ? 4 : 0
|
|
1122
|
-
const labelX = tick.to.x - labelXPadding
|
|
1123
|
-
const labelY = tick.to.y - labelYPadding
|
|
1124
|
-
const labelVerticalAnchor = labelsAboveGridlines ? 'end' : 'middle'
|
|
1125
|
-
const combineDomInlineLabelWithValue = inlineLabel && labelsAboveGridlines && lastTick
|
|
1126
|
-
const formattedValue = useInlineLabel
|
|
1127
|
-
? String(tick?.formattedValue || '').replace(config.dataFormat.suffix, '')
|
|
1128
|
-
: tick?.formattedValue
|
|
1129
|
-
|
|
1130
|
-
return (
|
|
1131
|
-
<Group key={`vx-tick-${tick.value}-${i}`} className={'vx-axis-tick'}>
|
|
1132
|
-
{!runtime.yAxis.hideTicks && !labelsAboveGridlines && !hideTopTick && (
|
|
1133
|
-
<Line
|
|
1134
|
-
key={`${tick.value}--hide-hideTicks`}
|
|
1135
|
-
from={tick.from}
|
|
1136
|
-
to={isLogarithmicAxis ? to : tick.to}
|
|
1137
|
-
stroke={config.yAxis.tickColor}
|
|
1138
|
-
display={orientation === 'horizontal' ? 'none' : 'block'}
|
|
1139
|
-
fontSize={tickLabelFontSize}
|
|
1140
|
-
/>
|
|
1141
|
-
)}
|
|
1142
|
-
|
|
1143
|
-
{orientation === 'horizontal' &&
|
|
1144
|
-
visualizationType === 'Box Plot' &&
|
|
1145
|
-
config.yAxis.labelPlacement === 'On Date/Category Axis' &&
|
|
1146
|
-
!config.yAxis.hideLabel && (
|
|
1147
|
-
<Text
|
|
1148
|
-
x={tick.to.x}
|
|
1149
|
-
y={yScale(tick.value) + yScale.bandwidth() / 2}
|
|
1150
|
-
transform={`rotate(${
|
|
1151
|
-
config.orientation === 'horizontal' ? config.runtime.yAxis.tickRotation || 0 : 0
|
|
1152
|
-
}, ${tick.to.x}, ${tick.to.y})`}
|
|
1153
|
-
verticalAnchor={'middle'}
|
|
1154
|
-
textAnchor={'end'}
|
|
1155
|
-
fontSize={tickLabelFontSize}
|
|
1156
|
-
>
|
|
1157
|
-
{tick.formattedValue}
|
|
1158
|
-
</Text>
|
|
1159
|
-
)}
|
|
1160
|
-
|
|
1161
|
-
{orientation === 'horizontal' &&
|
|
1162
|
-
visualizationType === 'Bar' &&
|
|
1163
|
-
config.yAxis.labelPlacement === 'On Date/Category Axis' &&
|
|
1164
|
-
!config.yAxis.hideLabel &&
|
|
1165
|
-
(() => {
|
|
1166
|
-
const barGroupCount =
|
|
1167
|
-
config.visualizationSubType === 'stacked' ? 1 : config.runtime.seriesKeys.length
|
|
1168
|
-
|
|
1169
|
-
// Calculate barHeight based on chart type (regular bar vs lollipop)
|
|
1170
|
-
let barHeight
|
|
1171
|
-
if (config.isLollipopChart) {
|
|
1172
|
-
const lollipopSizes = { large: 7, medium: 6, small: 5 }
|
|
1173
|
-
const lollipopBarWidth = lollipopSizes[config.lollipopSize] || 5
|
|
1174
|
-
barHeight = lollipopBarWidth * barGroupCount
|
|
1175
|
-
} else {
|
|
1176
|
-
barHeight = Number(config.barHeight) * barGroupCount
|
|
1177
|
-
}
|
|
1178
|
-
|
|
1179
|
-
const totalBarHeight = barHeight + Number(config.barSpace)
|
|
1180
|
-
const barGroupY = i === 0 ? 0 : totalBarHeight * i
|
|
1181
|
-
const labelCenterY = barGroupY + barHeight / 2
|
|
1182
|
-
|
|
1183
|
-
return (
|
|
1184
|
-
<Text
|
|
1185
|
-
x={tick.from.x - Number(runtime.yAxis.size) + horizontalYAxisLabelSpace}
|
|
1186
|
-
y={labelCenterY}
|
|
1187
|
-
verticalAnchor={'middle'}
|
|
1188
|
-
textAnchor={'start'}
|
|
1189
|
-
fontSize={tickLabelFontSize}
|
|
1190
|
-
width={categoryLabelSpace}
|
|
1191
|
-
lineHeight={'1.2em'}
|
|
1192
|
-
>
|
|
1193
|
-
{tick.formattedValue}
|
|
1194
|
-
</Text>
|
|
1195
|
-
)
|
|
1196
|
-
})()}
|
|
1197
|
-
|
|
1198
|
-
{orientation === 'horizontal' &&
|
|
1199
|
-
visualizationType !== 'Bar' &&
|
|
1200
|
-
visualizationSubType === 'stacked' &&
|
|
1201
|
-
config.yAxis.labelPlacement === 'On Date/Category Axis' &&
|
|
1202
|
-
!config.yAxis.hideLabel && (
|
|
1203
|
-
<Text
|
|
1204
|
-
transform={`translate(${tick.to.x - 5}, ${
|
|
1205
|
-
tick.to.y - minY + (Number(config.barHeight) - barMinHeight) / 2
|
|
1206
|
-
}) rotate(-${runtime.horizontal ? runtime.yAxis.tickRotation : 0})`}
|
|
1207
|
-
verticalAnchor={'start'}
|
|
1208
|
-
textAnchor={'end'}
|
|
1209
|
-
fontSize={tickLabelFontSize}
|
|
1210
|
-
>
|
|
1211
|
-
{tick.formattedValue}
|
|
1212
|
-
</Text>
|
|
1213
|
-
)}
|
|
1214
|
-
|
|
1215
|
-
{orientation === 'horizontal' &&
|
|
1216
|
-
visualizationType === 'Paired Bar' &&
|
|
1217
|
-
!config.yAxis.hideLabel && (
|
|
1218
|
-
<Text
|
|
1219
|
-
transform={`translate(${tick.to.x - 5}, ${
|
|
1220
|
-
tick.to.y - minY + Number(config.barHeight) / 2
|
|
1221
|
-
}) rotate(-${runtime.horizontal ? runtime.yAxis.tickRotation : 0})`}
|
|
1222
|
-
textAnchor={'end'}
|
|
1223
|
-
verticalAnchor='middle'
|
|
1224
|
-
fontSize={tickLabelFontSize}
|
|
1225
|
-
>
|
|
1226
|
-
{tick.formattedValue}
|
|
1227
|
-
</Text>
|
|
1228
|
-
)}
|
|
1229
|
-
{orientation === 'horizontal' &&
|
|
1230
|
-
visualizationType === 'Deviation Bar' &&
|
|
1231
|
-
!config.yAxis.hideLabel && (
|
|
1232
|
-
<Text
|
|
1233
|
-
transform={`translate(${tick.to.x - 5}, ${
|
|
1234
|
-
config.isLollipopChart
|
|
1235
|
-
? tick.to.y - minY + 2
|
|
1236
|
-
: tick.to.y - minY + Number(config.barHeight) / 2
|
|
1237
|
-
}) rotate(-${runtime.horizontal ? runtime.yAxis.tickRotation : 0})`}
|
|
1238
|
-
textAnchor={'end'}
|
|
1239
|
-
verticalAnchor='middle'
|
|
1240
|
-
fontSize={tickLabelFontSize}
|
|
1241
|
-
>
|
|
1242
|
-
{tick.formattedValue}
|
|
1243
|
-
</Text>
|
|
1244
|
-
)}
|
|
1245
|
-
|
|
1246
|
-
{orientation === 'vertical' &&
|
|
1247
|
-
visualizationType === 'Bump Chart' &&
|
|
1248
|
-
!config.yAxis.hideLabel && (
|
|
1249
|
-
<>
|
|
1250
|
-
<Text
|
|
1251
|
-
display={config.useLogScale ? showTicks : 'block'}
|
|
1252
|
-
dx={config.useLogScale ? -6 : 0}
|
|
1253
|
-
x={config.runtime.horizontal ? tick.from.x + 2 : tick.to.x - 8.5}
|
|
1254
|
-
y={tick.to.y - 13 + (config.runtime.horizontal ? horizontalTickOffset : 0)}
|
|
1255
|
-
angle={-Number(config.yAxis.tickRotation) || 0}
|
|
1256
|
-
verticalAnchor={config.runtime.horizontal ? 'start' : 'middle'}
|
|
1257
|
-
textAnchor={config.runtime.horizontal ? 'start' : 'end'}
|
|
1258
|
-
fill={config.yAxis.tickLabelColor}
|
|
1259
|
-
fontSize={tickLabelFontSize}
|
|
1260
|
-
>
|
|
1261
|
-
{config.runtime.seriesLabelsAll[tick.formattedValue - 1]}
|
|
1262
|
-
</Text>
|
|
1263
|
-
|
|
1264
|
-
{(seriesHighlight.length === 0 ||
|
|
1265
|
-
seriesHighlight.includes(
|
|
1266
|
-
config.runtime.seriesLabelsAll[tick.formattedValue - 1]
|
|
1267
|
-
)) && (
|
|
1268
|
-
<rect
|
|
1269
|
-
x={0 - Number(runtime.yAxis.size)}
|
|
1270
|
-
y={tick.to.y - 8 + (config.runtime.horizontal ? horizontalTickOffset : 7)}
|
|
1271
|
-
width={Number(runtime.yAxis.size) + xScale(xScale.domain()[0])}
|
|
1272
|
-
height='2'
|
|
1273
|
-
fill={colorScale(config.runtime.seriesLabelsAll[tick.formattedValue - 1])}
|
|
1274
|
-
/>
|
|
1275
|
-
)}
|
|
1276
|
-
</>
|
|
1277
|
-
)}
|
|
1278
|
-
{orientation === 'vertical' &&
|
|
1279
|
-
visualizationType !== 'Paired Bar' &&
|
|
1280
|
-
visualizationType !== 'Bump Chart' &&
|
|
1281
|
-
!config.yAxis.hideLabel && (
|
|
1282
|
-
<>
|
|
1283
|
-
{/* INLINE LABEL BEHAVIOR: Dom suffix for 'inlineLabel' behavior */}
|
|
1284
|
-
{/* inline label is shown alone and is allowed to 'overflow' to the right */}
|
|
1285
|
-
{/* SPECIAL ONE CHAR CASE: a one character inlineLabel does not overflow */}
|
|
1286
|
-
{/* IF VALUES ON LINE: suffix is combined with value to avoid having to calculate varying (now left-aligned) value widths */}
|
|
1287
|
-
{inlineLabel && lastTick && !labelsAboveGridlines && (
|
|
1288
|
-
<BlurStrokeText
|
|
1289
|
-
innerRef={suffixRef}
|
|
1290
|
-
display={isLogarithmicAxis ? showTicks : 'block'}
|
|
1291
|
-
dx={isLogarithmicAxis ? -6 : 0}
|
|
1292
|
-
x={labelX}
|
|
1293
|
-
y={labelY}
|
|
1294
|
-
angle={-Number(config.yAxis.tickRotation) || 0}
|
|
1295
|
-
verticalAnchor={labelVerticalAnchor}
|
|
1296
|
-
textAnchor={inlineLabelHasNoSpace ? 'end' : 'start'}
|
|
1297
|
-
fill={config.yAxis.tickLabelColor}
|
|
1298
|
-
stroke={'#fff'}
|
|
1299
|
-
paintOrder={'stroke'} // keeps stroke under fill
|
|
1300
|
-
strokeLinejoin='round'
|
|
1301
|
-
style={{ whiteSpace: 'pre-wrap' }} // prevents leading spaces from being trimmed
|
|
1302
|
-
fontSize={tickLabelFontSize}
|
|
1303
|
-
>
|
|
1304
|
-
{inlineLabel}
|
|
1305
|
-
</BlurStrokeText>
|
|
1306
|
-
)}
|
|
1307
|
-
|
|
1308
|
-
{/* VALUE */}
|
|
1309
|
-
<BlurStrokeText
|
|
1310
|
-
innerRef={el => lastTick && (topYLabelRef.current = el)}
|
|
1311
|
-
display={isLogarithmicAxis ? showTicks : 'block'}
|
|
1312
|
-
dx={isLogarithmicAxis ? -6 : 0}
|
|
1313
|
-
x={inlineLabelHasNoSpace ? labelX - suffixWidth : labelX}
|
|
1314
|
-
y={labelY + (config.runtime.horizontal ? horizontalTickOffset : 0)}
|
|
1315
|
-
angle={-Number(config.yAxis.tickRotation) || 0}
|
|
1316
|
-
verticalAnchor={config.runtime.horizontal ? 'start' : labelVerticalAnchor}
|
|
1317
|
-
textAnchor={config.runtime.horizontal || labelsAboveGridlines ? 'start' : 'end'}
|
|
1318
|
-
fill={config.yAxis.tickLabelColor}
|
|
1319
|
-
stroke={'#fff'}
|
|
1320
|
-
disableStroke={!labelsAboveGridlines}
|
|
1321
|
-
strokeLinejoin='round'
|
|
1322
|
-
paintOrder={'stroke'} // keeps stroke under fill
|
|
1323
|
-
style={{ whiteSpace: 'pre-wrap' }} // prevents leading spaces from being trimmed
|
|
1324
|
-
fontSize={tickLabelFontSize}
|
|
1325
|
-
>
|
|
1326
|
-
{`${formattedValue}${combineDomInlineLabelWithValue ? inlineLabel : ''}`}
|
|
1327
|
-
</BlurStrokeText>
|
|
1328
|
-
</>
|
|
1329
|
-
)}
|
|
1330
|
-
</Group>
|
|
1331
|
-
)
|
|
1332
|
-
})}
|
|
1333
|
-
<Text
|
|
1334
|
-
className='y-label'
|
|
1335
|
-
textAnchor='middle'
|
|
1336
|
-
verticalAnchor='start'
|
|
1337
|
-
transform={`translate(${-1 * runtime.yAxis.size + yLabelOffset}, ${axisCenter}) rotate(-90)`}
|
|
1338
|
-
fontWeight='bold'
|
|
1339
|
-
fill={config.yAxis.labelColor}
|
|
1340
|
-
fontSize={axisLabelFontSize}
|
|
1341
|
-
>
|
|
1342
|
-
{!config.hideYAxisLabel ? props.label : null}
|
|
1343
|
-
</Text>
|
|
1344
|
-
</Group>
|
|
1345
|
-
)
|
|
1346
|
-
}}
|
|
1347
|
-
</AxisLeft>
|
|
753
|
+
tickLabelFontSize={tickLabelFontSize}
|
|
754
|
+
axisLabelFontSize={axisLabelFontSize}
|
|
755
|
+
handleLeftTickFormatting={handleLeftTickFormatting}
|
|
756
|
+
topYLabelRef={topYLabelRef}
|
|
757
|
+
suffixRef={suffixRef}
|
|
758
|
+
suffixWidth={suffixWidth}
|
|
759
|
+
horizontalYAxisLabelSpace={horizontalYAxisLabelSpace}
|
|
760
|
+
categoryLabelSpace={categoryLabelSpace}
|
|
761
|
+
yLabelOffset={yLabelOffset}
|
|
762
|
+
/>
|
|
1348
763
|
)}
|
|
1349
764
|
{config.yAxis.type === 'categorical' && config.orientation === 'vertical' && (
|
|
1350
765
|
<CategoricalYAxis
|
|
1351
766
|
yScale={yScale}
|
|
1352
767
|
xMax={xMax}
|
|
1353
768
|
yMax={yMax}
|
|
1354
|
-
leftSize={
|
|
769
|
+
leftSize={yAxisWidth - config.yAxis.axisPadding}
|
|
1355
770
|
/>
|
|
1356
771
|
)}
|
|
1357
772
|
{/* Right Axis */}
|
|
1358
773
|
{hasRightAxis && (
|
|
1359
|
-
<
|
|
1360
|
-
|
|
1361
|
-
|
|
1362
|
-
|
|
1363
|
-
|
|
1364
|
-
|
|
1365
|
-
|
|
1366
|
-
|
|
1367
|
-
{props => {
|
|
1368
|
-
const axisCenter =
|
|
1369
|
-
config.orientation === 'horizontal'
|
|
1370
|
-
? (props.axisToPoint.y - props.axisFromPoint.y) / 2
|
|
1371
|
-
: (props.axisFromPoint.y - props.axisToPoint.y) / 2
|
|
1372
|
-
const horizontalTickOffset =
|
|
1373
|
-
yMax / props.ticks.length / 2 - (yMax / props.ticks.length) * (1 - config.barThickness) + 5
|
|
1374
|
-
return (
|
|
1375
|
-
<Group className='right-axis'>
|
|
1376
|
-
{props.ticks.map((tick, i) => {
|
|
1377
|
-
return (
|
|
1378
|
-
<Group key={`vx-tick-${tick.value}-${i}`} className='vx-axis-tick'>
|
|
1379
|
-
{!runtime.yAxis.rightHideTicks && (
|
|
1380
|
-
<Line
|
|
1381
|
-
from={tick.from}
|
|
1382
|
-
to={tick.to}
|
|
1383
|
-
display={runtime.horizontal ? 'none' : 'block'}
|
|
1384
|
-
stroke={config.yAxis.rightAxisTickColor}
|
|
1385
|
-
/>
|
|
1386
|
-
)}
|
|
1387
|
-
|
|
1388
|
-
{runtime.yAxis.rightGridLines ? (
|
|
1389
|
-
<Line from={{ x: tick.from.x + xMax, y: tick.from.y }} to={tick.from} stroke='#d6d6d6' />
|
|
1390
|
-
) : (
|
|
1391
|
-
''
|
|
1392
|
-
)}
|
|
1393
|
-
|
|
1394
|
-
{!config.yAxis.rightHideLabel && (
|
|
1395
|
-
<Text
|
|
1396
|
-
x={tick.to.x}
|
|
1397
|
-
y={tick.to.y + (runtime.horizontal ? horizontalTickOffset : 0)}
|
|
1398
|
-
verticalAnchor={runtime.horizontal ? 'start' : 'middle'}
|
|
1399
|
-
textAnchor={'start'}
|
|
1400
|
-
fill={config.yAxis.rightAxisTickLabelColor}
|
|
1401
|
-
fontSize={tickLabelFontSize}
|
|
1402
|
-
>
|
|
1403
|
-
{tick.formattedValue}
|
|
1404
|
-
</Text>
|
|
1405
|
-
)}
|
|
1406
|
-
</Group>
|
|
1407
|
-
)
|
|
1408
|
-
})}
|
|
1409
|
-
{!config.yAxis.rightHideAxis && (
|
|
1410
|
-
<Line from={props.axisFromPoint} to={props.axisToPoint} stroke='#333' />
|
|
1411
|
-
)}
|
|
1412
|
-
<Text
|
|
1413
|
-
className='y-label'
|
|
1414
|
-
textAnchor='middle'
|
|
1415
|
-
verticalAnchor='start'
|
|
1416
|
-
transform={`translate(${
|
|
1417
|
-
config.yAxis.rightLabelOffsetSize ? config.yAxis.rightLabelOffsetSize : 0
|
|
1418
|
-
}, ${axisCenter}) rotate(-90)`}
|
|
1419
|
-
fontWeight='bold'
|
|
1420
|
-
fill={config.yAxis.rightAxisLabelColor}
|
|
1421
|
-
fontSize={axisLabelFontSize}
|
|
1422
|
-
>
|
|
1423
|
-
{props.label}
|
|
1424
|
-
</Text>
|
|
1425
|
-
</Group>
|
|
1426
|
-
)
|
|
1427
|
-
}}
|
|
1428
|
-
</AxisRight>
|
|
774
|
+
<RightAxis
|
|
775
|
+
yScaleRight={yScaleRight}
|
|
776
|
+
yMax={yMax}
|
|
777
|
+
xMax={xMax}
|
|
778
|
+
yAxisWidth={yAxisWidth}
|
|
779
|
+
tickLabelFontSize={tickLabelFontSize}
|
|
780
|
+
axisLabelFontSize={axisLabelFontSize}
|
|
781
|
+
/>
|
|
1429
782
|
)}
|
|
1430
783
|
{hasTopAxis && config.topAxis.hasLine && (
|
|
1431
784
|
<AxisTop
|
|
1432
785
|
stroke='#333'
|
|
1433
|
-
left={
|
|
786
|
+
left={yAxisWidth}
|
|
1434
787
|
scale={xScale}
|
|
1435
788
|
hideTicks
|
|
1436
789
|
hideZero
|
|
@@ -1441,189 +794,27 @@ const LinearChart = forwardRef<SVGAElement, LinearChartProps>(({ parentHeight, p
|
|
|
1441
794
|
)}
|
|
1442
795
|
{/* X axis */}
|
|
1443
796
|
{visualizationType !== 'Paired Bar' && visualizationType !== 'Spark Line' && (
|
|
1444
|
-
<
|
|
1445
|
-
|
|
1446
|
-
|
|
1447
|
-
|
|
1448
|
-
|
|
1449
|
-
|
|
1450
|
-
|
|
1451
|
-
|
|
1452
|
-
}
|
|
1453
|
-
|
|
1454
|
-
|
|
1455
|
-
|
|
1456
|
-
|
|
1457
|
-
|
|
1458
|
-
|
|
1459
|
-
|
|
1460
|
-
|
|
1461
|
-
|
|
1462
|
-
|
|
1463
|
-
|
|
1464
|
-
|
|
1465
|
-
: // For date-time with small datasets (e.g., brush-filtered), use explicit tick values
|
|
1466
|
-
// to ensure each data point can have a tick. Otherwise, visx may generate too few.
|
|
1467
|
-
// Use uniqueXAxisDataMapped to handle cases where multiple series share x-axis values
|
|
1468
|
-
isDateTime && uniqueXAxisDataMapped.length > 0 && uniqueXAxisDataMapped.length <= (xTickCount || 15)
|
|
1469
|
-
? uniqueXAxisDataMapped
|
|
1470
|
-
: undefined
|
|
1471
|
-
}
|
|
1472
|
-
>
|
|
1473
|
-
{props => {
|
|
1474
|
-
const hasDynamicCategory = config.series.some(s => s.dynamicCategory)
|
|
1475
|
-
|
|
1476
|
-
// For these charts, we generated all ticks in tickValues above, and now need to filter/shift them
|
|
1477
|
-
// so the last tick is always labeled
|
|
1478
|
-
// Use uniqueXAxisDataMapped for date filtering to match the tickValues we set
|
|
1479
|
-
if (config.runtime.xAxis.type === 'date' && !config.runtime.xAxis.manual && !hasDynamicCategory) {
|
|
1480
|
-
props.ticks = filterAndShiftLinearDateTicks(config, props, uniqueXAxisDataMapped, formatDate)
|
|
1481
|
-
}
|
|
1482
|
-
|
|
1483
|
-
const distanceBetweenTicks =
|
|
1484
|
-
useDateSpanMonths &&
|
|
1485
|
-
xScale
|
|
1486
|
-
.ticks(xTickCount)
|
|
1487
|
-
.map(t =>
|
|
1488
|
-
props.ticks.findIndex(
|
|
1489
|
-
tick => (typeof tick.value === 'number' ? tick.value : tick.value.getTime()) === t.getTime()
|
|
1490
|
-
)
|
|
1491
|
-
)
|
|
1492
|
-
.slice(0, 2)
|
|
1493
|
-
.reduce((acc, curr) => curr - acc)
|
|
1494
|
-
|
|
1495
|
-
// filter out every [distanceBetweenTicks] tick starting from the end, so the last tick is always labeled
|
|
1496
|
-
const filteredTicks = useDateSpanMonths
|
|
1497
|
-
? [...props.ticks]
|
|
1498
|
-
.reverse()
|
|
1499
|
-
.filter((_, i) => i % distanceBetweenTicks === 0)
|
|
1500
|
-
.reverse()
|
|
1501
|
-
.map((tick, i, arr) => ({
|
|
1502
|
-
...tick,
|
|
1503
|
-
// reformat in case showYearsOnce, since first month of year may have changed
|
|
1504
|
-
formattedValue: handleBottomTickFormatting(tick.value, i, arr)
|
|
1505
|
-
}))
|
|
1506
|
-
: props.ticks
|
|
1507
|
-
|
|
1508
|
-
const axisMaxHeight = bottomLabelStart + BOTTOM_LABEL_PADDING
|
|
1509
|
-
|
|
1510
|
-
const containsMultipleWords = inputString => /\s/.test(inputString)
|
|
1511
|
-
const isMultiLabel = filteredTicks.some(tick => containsMultipleWords(tick.value))
|
|
1512
|
-
|
|
1513
|
-
// Calculate sumOfTickWidth here, before map function
|
|
1514
|
-
const longestTickLength = Math.max(
|
|
1515
|
-
...filteredTicks.map(tick => getTextWidth(tick.formattedValue, GET_TEXT_WIDTH_FONT))
|
|
1516
|
-
)
|
|
1517
|
-
// const marginTop = 20 // moved to top bc need for yMax calcs
|
|
1518
|
-
const accumulator = isMultiLabel ? 180 : 100
|
|
1519
|
-
|
|
1520
|
-
const textWidths = filteredTicks.map(tick => getTextWidth(tick.formattedValue, GET_TEXT_WIDTH_FONT))
|
|
1521
|
-
const sumOfTickWidth = textWidths.reduce((a, b) => a + b, accumulator)
|
|
1522
|
-
const spaceBetweenEachTick = (xMax - sumOfTickWidth) / (filteredTicks.length - 1)
|
|
1523
|
-
const bufferBetweenTicks = 40
|
|
1524
|
-
const maxLengthOfTick =
|
|
1525
|
-
parentWidth / filteredTicks.length - X_TICK_LABEL_PADDING * 2 - bufferBetweenTicks
|
|
1526
|
-
|
|
1527
|
-
// Determine the position of each tick
|
|
1528
|
-
let positions = [0] // The first tick is at position 0
|
|
1529
|
-
for (let i = 1; i < textWidths.length; i++) {
|
|
1530
|
-
// The position of each subsequent tick is the position of the previous tick
|
|
1531
|
-
// plus the width of the previous tick and the space
|
|
1532
|
-
positions[i] = positions[i - 1] + textWidths[i - 1] + spaceBetweenEachTick
|
|
1533
|
-
}
|
|
1534
|
-
// calculate the end of x axis box
|
|
1535
|
-
const axisBBox = axisBottomRef?.current?.getBBox().height
|
|
1536
|
-
config.xAxis.axisBBox = axisBBox
|
|
1537
|
-
|
|
1538
|
-
// force wrap it last tick is close to the end of the axis
|
|
1539
|
-
const lastTickWidth = textWidths[textWidths.length - 1]
|
|
1540
|
-
const lastTickPosition = positions[positions.length - 1] + lastTickWidth
|
|
1541
|
-
const lastTickEnd = lastTickPosition + lastTickWidth / 2
|
|
1542
|
-
const lastTickEndThreshold = xMax - lastTickWidth
|
|
1543
|
-
|
|
1544
|
-
const areTicksTouching =
|
|
1545
|
-
textWidths.some(textWidth => textWidth > maxLengthOfTick) || // Force wrap if any tick is too long
|
|
1546
|
-
config.xAxis.showYearsOnce || // Force wrap when showing years once so it's easier to read
|
|
1547
|
-
lastTickEnd > lastTickEndThreshold // Force wrap it last tick is close to the end of the axis
|
|
1548
|
-
|
|
1549
|
-
const dynamicMarginTop =
|
|
1550
|
-
areTicksTouching && config.isResponsiveTicks ? longestTickLength + DEFAULT_TICK_LENGTH + 20 : 0
|
|
1551
|
-
|
|
1552
|
-
config.dynamicMarginTop = dynamicMarginTop
|
|
1553
|
-
config.xAxis.tickWidthMax = longestTickLength
|
|
1554
|
-
|
|
1555
|
-
return (
|
|
1556
|
-
<Group className='bottom-axis' width={parentWidth}>
|
|
1557
|
-
{filteredTicks.map((tick, i, propsTicks) => {
|
|
1558
|
-
// when using LogScale show major ticks values only
|
|
1559
|
-
const showTick = String(tick.value).startsWith('1') || tick.value === 0.1 ? 'block' : 'none'
|
|
1560
|
-
const tickLength = showTick === 'block' ? 16 : DEFAULT_TICK_LENGTH
|
|
1561
|
-
const to = { x: tick.to.x, y: tickLength }
|
|
1562
|
-
const limitedWidth = 100 / propsTicks.length
|
|
1563
|
-
//reset rotations by updating config
|
|
1564
|
-
config.yAxis.tickRotation =
|
|
1565
|
-
config.isResponsiveTicks && config.orientation === 'horizontal' ? 0 : config.yAxis.tickRotation
|
|
1566
|
-
config.xAxis.tickRotation =
|
|
1567
|
-
config.isResponsiveTicks && config.orientation === 'vertical' ? 0 : config.xAxis.tickRotation
|
|
1568
|
-
//configure rotation
|
|
1569
|
-
|
|
1570
|
-
const tickRotation =
|
|
1571
|
-
config.isResponsiveTicks && areTicksTouching
|
|
1572
|
-
? -Number(config.xAxis.maxTickRotation) || -90
|
|
1573
|
-
: -Number(config.runtime.xAxis.tickRotation)
|
|
1574
|
-
|
|
1575
|
-
return (
|
|
1576
|
-
<Group key={`vx-tick-${tick.value}-${i}`} className={'vx-axis-tick'}>
|
|
1577
|
-
{!config.xAxis.hideTicks && (
|
|
1578
|
-
<Line
|
|
1579
|
-
from={tick.from}
|
|
1580
|
-
to={orientation === 'horizontal' && isLogarithmicAxis ? to : tick.to}
|
|
1581
|
-
stroke={config.xAxis.tickColor}
|
|
1582
|
-
strokeWidth={showTick === 'block' && isLogarithmicAxis ? 1.3 : 1}
|
|
1583
|
-
/>
|
|
1584
|
-
)}
|
|
1585
|
-
{!config.xAxis.hideLabel && (
|
|
1586
|
-
<Text
|
|
1587
|
-
innerRef={el => (xAxisLabelRefs.current[i] = el)}
|
|
1588
|
-
dy={config.orientation === 'horizontal' && isLogarithmicAxis ? 8 : 0}
|
|
1589
|
-
display={config.orientation === 'horizontal' && isLogarithmicAxis ? showTick : 'block'}
|
|
1590
|
-
x={tick.to.x}
|
|
1591
|
-
y={tick.to.y + X_TICK_LABEL_PADDING}
|
|
1592
|
-
angle={tickRotation}
|
|
1593
|
-
verticalAnchor={tickRotation < -50 ? 'middle' : 'start'}
|
|
1594
|
-
textAnchor={tickRotation ? 'end' : 'middle'}
|
|
1595
|
-
width={
|
|
1596
|
-
areTicksTouching && !config.isResponsiveTicks && !Number(config.xAxis.tickRotation)
|
|
1597
|
-
? limitedWidth
|
|
1598
|
-
: undefined
|
|
1599
|
-
}
|
|
1600
|
-
fill={config.xAxis.tickLabelColor}
|
|
1601
|
-
fontSize={tickLabelFontSize}
|
|
1602
|
-
>
|
|
1603
|
-
{tick.formattedValue}
|
|
1604
|
-
</Text>
|
|
1605
|
-
)}
|
|
1606
|
-
</Group>
|
|
1607
|
-
)
|
|
1608
|
-
})}
|
|
1609
|
-
{!config.xAxis.hideAxis && <Line from={props.axisFromPoint} to={props.axisToPoint} stroke='#333' />}
|
|
1610
|
-
<Text
|
|
1611
|
-
innerRef={xAxisTitleRef}
|
|
1612
|
-
className='x-axis-title-label'
|
|
1613
|
-
x={xMax / 2}
|
|
1614
|
-
y={isForestPlot ? 0 /* set via ref */ : axisMaxHeight}
|
|
1615
|
-
textAnchor='middle'
|
|
1616
|
-
verticalAnchor='start'
|
|
1617
|
-
fontWeight='bold'
|
|
1618
|
-
fill={config.xAxis.labelColor}
|
|
1619
|
-
fontSize={axisLabelFontSize}
|
|
1620
|
-
>
|
|
1621
|
-
{!config.hideXAxisLabel ? props.label : null}
|
|
1622
|
-
</Text>
|
|
1623
|
-
</Group>
|
|
1624
|
-
)
|
|
1625
|
-
}}
|
|
1626
|
-
</AxisBottom>
|
|
797
|
+
<BottomAxis
|
|
798
|
+
axisBottomRef={axisBottomRef}
|
|
799
|
+
xScale={xScale}
|
|
800
|
+
yMax={yMax}
|
|
801
|
+
xMax={xMax}
|
|
802
|
+
yAxisWidth={yAxisWidth}
|
|
803
|
+
xTickCount={xTickCount}
|
|
804
|
+
tickLabelFontSize={tickLabelFontSize}
|
|
805
|
+
axisLabelFontSize={axisLabelFontSize}
|
|
806
|
+
handleBottomTickFormatting={handleBottomTickFormatting}
|
|
807
|
+
useDateSpanMonths={useDateSpanMonths}
|
|
808
|
+
dateSpanMonths={dateSpanMonths}
|
|
809
|
+
xAxisDataMapped={xAxisDataMapped}
|
|
810
|
+
uniqueXAxisDataMapped={uniqueXAxisDataMapped}
|
|
811
|
+
isDateTime={isDateTime}
|
|
812
|
+
bottomLabelStart={bottomLabelStart}
|
|
813
|
+
parentWidth={parentWidth}
|
|
814
|
+
xAxisLabelRefs={xAxisLabelRefs}
|
|
815
|
+
xAxisTitleRef={xAxisTitleRef}
|
|
816
|
+
getManualStep={getManualStep}
|
|
817
|
+
/>
|
|
1627
818
|
)}
|
|
1628
819
|
</svg>
|
|
1629
820
|
{!isDraggingAnnotation &&
|
|
@@ -1680,10 +871,10 @@ const LinearChart = forwardRef<SVGAElement, LinearChartProps>(({ parentHeight, p
|
|
|
1680
871
|
<div
|
|
1681
872
|
style={{
|
|
1682
873
|
position: 'relative',
|
|
1683
|
-
marginTop:
|
|
1684
|
-
left: `${
|
|
1685
|
-
width: `${Math.max(xMax,
|
|
1686
|
-
height:
|
|
874
|
+
marginTop: `${BRUSH_MARGIN}px`,
|
|
875
|
+
left: `${yAxisWidth}px`,
|
|
876
|
+
width: `${Math.max(xMax, BRUSH_MIN_WIDTH)}px`,
|
|
877
|
+
height: `${BRUSH_HEIGHT}px`,
|
|
1687
878
|
pointerEvents: 'auto',
|
|
1688
879
|
zIndex: 15,
|
|
1689
880
|
touchAction: 'none', // Enable touch interactions for brush
|
|
@@ -1693,7 +884,7 @@ const LinearChart = forwardRef<SVGAElement, LinearChartProps>(({ parentHeight, p
|
|
|
1693
884
|
}}
|
|
1694
885
|
className='brush-overlay'
|
|
1695
886
|
>
|
|
1696
|
-
<BrushSelector xMax={Math.max(xMax,
|
|
887
|
+
<BrushSelector key={brushKeyRef.current} xMax={Math.max(xMax, BRUSH_MIN_WIDTH)} yMax={BRUSH_HEIGHT} />
|
|
1697
888
|
</div>
|
|
1698
889
|
)}
|
|
1699
890
|
</div>
|