@cdc/chart 4.26.1 → 4.26.3
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/LICENSE +201 -0
- package/dist/{cdcchart-dgT_1dIT.es.js → cdcchart-DQ00cQCm.es.js} +1 -20
- package/dist/cdcchart.js +54742 -49796
- package/examples/data/data-with-metadata.json +10 -0
- 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/feature/pie/planet-pie-example-config.json +2 -1
- package/examples/line-chart-states.json +1085 -0
- package/examples/metadata-variables.json +58 -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/CdcChart.tsx +8 -4
- package/src/CdcChartComponent.tsx +398 -284
- 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 +78 -0
- package/src/_stories/Chart.Defaults.stories.tsx +95 -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.SmallestLeftAxisMax.stories.tsx +64 -0
- package/src/_stories/Chart.stories.tsx +72 -1
- 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/ChartBar.Editor.stories.tsx +97 -38
- package/src/_stories/ChartBrush.Editor.stories.tsx +11 -25
- 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.Editor.stories.tsx +1 -1
- 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/_stories/_mock/paired-bar-abbr.json +421 -0
- package/src/_stories/_mock/pie_custom_colors.json +268 -0
- package/src/_stories/_mock/smallest_left_axis_max.json +104 -0
- package/src/components/Annotations/components/AnnotationDraggable.styles.css +14 -20
- 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 +12 -18
- 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 +277 -0
- package/src/components/Axis/LeftAxis.tsx +404 -0
- package/src/components/Axis/LeftAxisGridlines.tsx +77 -0
- package/src/components/Axis/PairedBarAxis.tsx +192 -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.Horizontal.tsx +12 -28
- package/src/components/BarChart/components/BarChart.StackedHorizontal.tsx +12 -30
- package/src/components/BarChart/components/BarChart.StackedVertical.tsx +12 -31
- package/src/components/BarChart/components/BarChart.Vertical.tsx +12 -28
- package/src/components/BarChart/components/BarChart.tsx +7 -1
- package/src/components/BarChart/helpers/getPatternUrl.ts +94 -0
- package/src/components/BarChart/helpers/tests/getPatternUrl.test.ts +134 -0
- package/src/components/BarChart/helpers/useBarChart.ts +3 -0
- package/src/components/Brush/BrushSelector.tsx +155 -22
- package/src/components/Brush/MiniChartPreview.tsx +133 -21
- package/src/components/EditorPanel/EditorPanel.tsx +81 -54
- package/src/components/EditorPanel/components/Panels/Panel.Annotate.tsx +67 -29
- package/src/components/EditorPanel/components/Panels/Panel.ForestPlotSettings.tsx +0 -78
- package/src/components/EditorPanel/components/Panels/Panel.General.tsx +120 -2
- package/src/components/EditorPanel/components/Panels/Panel.PatternSettings.tsx +25 -43
- package/src/components/EditorPanel/components/Panels/Panel.Radar.tsx +353 -0
- package/src/components/EditorPanel/components/Panels/Panel.Series.tsx +83 -3
- package/src/components/EditorPanel/components/Panels/Panel.Visual.tsx +66 -43
- package/src/components/EditorPanel/components/Panels/index.tsx +2 -0
- package/src/components/EditorPanel/editor-panel.scss +1 -1
- package/src/components/EditorPanel/useEditorPermissions.ts +55 -26
- package/src/components/ForestPlot/ForestPlot.tsx +26 -22
- 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/LegendGroup/LegendGroup.styles.css +4 -4
- package/src/components/Legend/LegendValueRange.tsx +77 -0
- package/src/components/Legend/helpers/createFormatLabels.tsx +16 -2
- 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 +278 -0
- package/src/components/LinearChart/tests/mockConfigContext.ts +131 -0
- package/src/components/LinearChart/utils/tickFormatting.ts +146 -0
- package/src/components/LinearChart.tsx +268 -1057
- package/src/components/PieChart/PieChart.tsx +20 -5
- 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/Regions/components/Regions.tsx +6 -6
- package/src/components/Sankey/components/Sankey.tsx +3 -3
- package/src/components/Sankey/sankey.scss +1 -1
- package/src/components/SmallMultiples/SmallMultiples.css +5 -5
- package/src/components/Sparkline/index.scss +4 -2
- package/src/components/WarmingStripes/WarmingStripes.tsx +95 -25
- package/src/components/WarmingStripes/WarmingStripesGradientLegend.css +8 -8
- package/src/data/initial-state.js +37 -15
- package/src/data/legacy-defaults.ts +18 -0
- package/src/helpers/abbreviateNumber.ts +24 -17
- package/src/helpers/getChartPatternId.ts +17 -0
- package/src/helpers/getExcludedData.ts +4 -0
- package/src/helpers/getMinMax.ts +16 -2
- package/src/helpers/handleChartAriaLabels.ts +19 -19
- package/src/helpers/handleLineType.ts +22 -18
- package/src/helpers/seriesColumnSettings.ts +114 -0
- package/src/helpers/tests/countNumOfTicks.test.ts +77 -0
- package/src/helpers/tests/seriesColumnSettings.test.ts +84 -0
- package/src/hooks/useProgrammaticTooltip.ts +23 -2
- package/src/hooks/useRightAxis.ts +14 -0
- package/src/hooks/useScales.ts +99 -56
- package/src/hooks/useTooltip.tsx +23 -3
- package/src/scss/main.scss +157 -79
- package/src/selectors/README.md +68 -0
- package/src/store/chart.reducer.ts +2 -0
- package/src/test/CdcChart.test.jsx +2 -2
- package/src/types/ChartConfig.ts +22 -0
- package/src/types/ChartContext.ts +1 -0
- package/src/types/Horizon.ts +64 -0
- package/tests/fixtures/chart-config-with-metadata.json +29 -0
- package/tests/fixtures/data-with-metadata.json +10 -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,46 +11,35 @@ 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'
|
|
17
18
|
import 'react-tooltip/dist/react-tooltip.css'
|
|
18
19
|
import { Text } from '@visx/text'
|
|
19
20
|
import { useTooltip, TooltipWithBounds } from '@visx/tooltip'
|
|
20
|
-
import
|
|
21
|
-
|
|
21
|
+
import ResizeObserver from 'resize-observer-polyfill'
|
|
22
22
|
// CDC Components
|
|
23
23
|
import { isDateScale } from '@cdc/core/helpers/cove/date'
|
|
24
|
-
import { AreaChartStacked } from './AreaChart'
|
|
25
|
-
import BarChart from './BarChart'
|
|
26
24
|
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
25
|
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
26
|
import useIntersectionObserver from '../hooks/useIntersectionObserver'
|
|
37
27
|
import Regions from './Regions'
|
|
38
|
-
import CategoricalYAxis from './Axis
|
|
28
|
+
import { CategoricalYAxis, LeftAxis, LeftAxisGridlines, BottomAxis, PairedBarAxis, RightAxis } from './Axis'
|
|
39
29
|
import BrushSelector from './Brush/BrushSelector'
|
|
40
|
-
import
|
|
30
|
+
import VisualizationRenderer from './LinearChart/VisualizationRenderer'
|
|
31
|
+
import { TYPES_WITHOUT_GRID, TYPES_WITH_TOOLTIP_GUIDES } from './LinearChart/linearChart.constants'
|
|
32
|
+
import { useTickFormatters } from './LinearChart/utils/tickFormatting'
|
|
41
33
|
|
|
42
34
|
// Helpers
|
|
43
35
|
import { isLegendWrapViewport, isMobileFontViewport } from '@cdc/core/helpers/viewports'
|
|
44
|
-
import { getTextWidth } from '@cdc/core/helpers/getTextWidth'
|
|
45
36
|
import { calcInitialHeight } from '../helpers/sizeHelpers'
|
|
46
|
-
import { filterAndShiftLinearDateTicks } from '../helpers/filterAndShiftLinearDateTicks'
|
|
47
37
|
import { calculateHorizontalBarCategoryLabelWidth } from '../helpers/calculateHorizontalBarCategoryLabelWidth'
|
|
48
38
|
|
|
49
39
|
// Hooks
|
|
50
40
|
import useReduceData from '../hooks/useReduceData'
|
|
51
41
|
import useRightAxis from '../hooks/useRightAxis'
|
|
52
|
-
import useScales
|
|
42
|
+
import useScales from '../hooks/useScales'
|
|
53
43
|
import { useProgrammaticTooltip } from '../hooks/useProgrammaticTooltip'
|
|
54
44
|
import { useSmallMultipleSynchronization } from '../hooks/useSmallMultipleSynchronization'
|
|
55
45
|
|
|
@@ -58,7 +48,6 @@ import { useTooltip as useCoveTooltip } from '../hooks/useTooltip'
|
|
|
58
48
|
import { useChartHoverAnalytics } from '../hooks/useChartHoverAnalytics'
|
|
59
49
|
import { useEditorPermissions } from './EditorPanel/useEditorPermissions'
|
|
60
50
|
import Annotation from './Annotations'
|
|
61
|
-
import { BlurStrokeText } from '@cdc/core/components/BlurStrokeText'
|
|
62
51
|
import { countNumOfTicks } from '../helpers/countNumOfTicks'
|
|
63
52
|
import HoverLine from './HoverLine/HoverLine'
|
|
64
53
|
import { SmallMultiples } from './SmallMultiples'
|
|
@@ -68,15 +57,36 @@ type LinearChartProps = {
|
|
|
68
57
|
parentHeight: number
|
|
69
58
|
}
|
|
70
59
|
|
|
60
|
+
// Axis and tick constants
|
|
71
61
|
const BOTTOM_LABEL_PADDING = 9
|
|
72
62
|
const X_TICK_LABEL_PADDING = 4.5
|
|
73
63
|
const DEFAULT_TICK_LENGTH = 8
|
|
74
|
-
const
|
|
64
|
+
const DEFAULT_MAX_TICK_ROTATION = 90
|
|
65
|
+
|
|
66
|
+
// Font sizes
|
|
75
67
|
const TICK_LABEL_FONT_SIZE = 16
|
|
76
68
|
const TICK_LABEL_FONT_SIZE_SMALL = 13
|
|
77
69
|
const AXIS_LABEL_FONT_SIZE = 18
|
|
78
70
|
const AXIS_LABEL_FONT_SIZE_SMALL = 14
|
|
79
|
-
|
|
71
|
+
|
|
72
|
+
// Label positioning constants
|
|
73
|
+
const BELOW_BAR_TEXT_OFFSET = -6.5
|
|
74
|
+
const LABEL_PADDING_OFFSET = 8
|
|
75
|
+
|
|
76
|
+
// Brush constants
|
|
77
|
+
const BRUSH_HEIGHT = 70
|
|
78
|
+
const BRUSH_MARGIN = 10
|
|
79
|
+
const BRUSH_MIN_WIDTH = 100
|
|
80
|
+
|
|
81
|
+
// Tooltip constants
|
|
82
|
+
const TOOLTIP_EDGE_BUFFER = 10
|
|
83
|
+
const TOOLTIP_OFFSET = 6
|
|
84
|
+
|
|
85
|
+
// Chart-specific constants
|
|
86
|
+
const WARMING_STRIPES_HEIGHT = 78
|
|
87
|
+
|
|
88
|
+
// Time constants
|
|
89
|
+
const MONTH_AS_MS = 1000 * 60 * 60 * 24 * 30
|
|
80
90
|
|
|
81
91
|
type TooltipData = {
|
|
82
92
|
dataXPosition?: number
|
|
@@ -95,12 +105,9 @@ type UseTooltipReturn<T = TooltipData> = {
|
|
|
95
105
|
const LinearChart = forwardRef<SVGAElement, LinearChartProps>(({ parentHeight, parentWidth }, svgRef) => {
|
|
96
106
|
// prettier-ignore
|
|
97
107
|
const {
|
|
98
|
-
colorScale,
|
|
99
108
|
config,
|
|
100
|
-
convertLineToBarGraph,
|
|
101
109
|
currentViewport,
|
|
102
110
|
vizViewport,
|
|
103
|
-
dimensions,
|
|
104
111
|
formatDate,
|
|
105
112
|
formatNumber,
|
|
106
113
|
handleChartAriaLabels,
|
|
@@ -108,33 +115,18 @@ const LinearChart = forwardRef<SVGAElement, LinearChartProps>(({ parentHeight, p
|
|
|
108
115
|
handleDragStateChange,
|
|
109
116
|
interactionLabel,
|
|
110
117
|
isDraggingAnnotation,
|
|
111
|
-
isEditor,
|
|
112
118
|
legendRef,
|
|
113
119
|
parseDate,
|
|
114
120
|
parentRef,
|
|
115
121
|
tableData,
|
|
116
122
|
transformedData: data,
|
|
117
|
-
seriesHighlight
|
|
118
123
|
} = useContext(ConfigContext)
|
|
119
124
|
|
|
120
125
|
// CONFIG
|
|
121
126
|
// 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
|
|
127
|
+
const { visualizationType, orientation, xAxis, yAxis, runtime, legend, forestPlot, debugSvg } = config
|
|
128
|
+
|
|
129
|
+
const { inlineLabel } = config.yAxis
|
|
138
130
|
|
|
139
131
|
// HOOKS % STATES
|
|
140
132
|
// When brush is active, use tableData (full dataset) for min/max calculation
|
|
@@ -144,11 +136,23 @@ const LinearChart = forwardRef<SVGAElement, LinearChartProps>(({ parentHeight, p
|
|
|
144
136
|
|
|
145
137
|
const { visSupportsSmallMultiples } = useEditorPermissions()
|
|
146
138
|
const { hasTopAxis } = getTopAxis(config)
|
|
139
|
+
|
|
140
|
+
// Increment on tableData change to force BrushSelector remount when filters change
|
|
141
|
+
const brushKeyRef = useRef(0)
|
|
142
|
+
const prevTableDataRef = useRef(tableData)
|
|
143
|
+
if (prevTableDataRef.current !== tableData) {
|
|
144
|
+
prevTableDataRef.current = tableData
|
|
145
|
+
brushKeyRef.current += 1
|
|
146
|
+
}
|
|
147
|
+
|
|
147
148
|
const [animatedChart, setAnimatedChart] = useState(false)
|
|
148
149
|
const [showHoverLine, setShowHoverLine] = useState(false)
|
|
149
150
|
const [point, setPoint] = useState({ x: 0, y: 0 })
|
|
150
151
|
const [suffixWidth, setSuffixWidth] = useState(0)
|
|
151
152
|
const [calculatedSvgHeight, setCalculatedSvgHeight] = useState<number | null>(null)
|
|
153
|
+
const [axisUpdateKey, setAxisUpdateKey] = useState(0)
|
|
154
|
+
const [axisBottomSizeKey, setAxisBottomSizeKey] = 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,33 @@ 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
|
+
|
|
248
|
+
// Recompute heights when bottom axis size changes (e.g., font load or wrap).
|
|
249
|
+
useEffect(() => {
|
|
250
|
+
const axisBottomEl = axisBottomRef.current
|
|
251
|
+
if (!axisBottomEl) return
|
|
252
|
+
const observer = new ResizeObserver(() => {
|
|
253
|
+
setAxisBottomSizeKey(prev => prev + 1)
|
|
254
|
+
})
|
|
255
|
+
observer.observe(axisBottomEl)
|
|
256
|
+
return () => observer.disconnect()
|
|
257
|
+
}, [axisBottomRef.current])
|
|
258
|
+
|
|
224
259
|
const { yScaleRight, hasRightAxis } = useRightAxis({ config, yMax, data })
|
|
225
260
|
|
|
226
|
-
|
|
261
|
+
// State for computed y-axis width - allows re-render when horizontal bar label space is calculated
|
|
262
|
+
const [computedYAxisWidth, setComputedYAxisWidth] = useState<number | null>(null)
|
|
263
|
+
|
|
264
|
+
// Use computed width if available, otherwise fall back to config value
|
|
265
|
+
const yAxisWidth = computedYAxisWidth ?? Number(runtime.yAxis.size)
|
|
266
|
+
|
|
267
|
+
// Chart width calculation using the current y-axis width
|
|
268
|
+
const xMax = parentWidth - yAxisWidth - (hasRightAxis ? config.yAxis.rightAxisSize : 0)
|
|
227
269
|
|
|
228
270
|
const {
|
|
229
271
|
xScale,
|
|
@@ -233,10 +275,9 @@ const LinearChart = forwardRef<SVGAElement, LinearChartProps>(({ parentHeight, p
|
|
|
233
275
|
g2xScale,
|
|
234
276
|
xScaleNoPadding,
|
|
235
277
|
xScaleAnnotation,
|
|
278
|
+
yScaleAnnotation,
|
|
236
279
|
min,
|
|
237
|
-
max
|
|
238
|
-
leftMax,
|
|
239
|
-
rightMax
|
|
280
|
+
max
|
|
240
281
|
} = useScales({
|
|
241
282
|
data,
|
|
242
283
|
tableData,
|
|
@@ -252,6 +293,16 @@ const LinearChart = forwardRef<SVGAElement, LinearChartProps>(({ parentHeight, p
|
|
|
252
293
|
currentViewport
|
|
253
294
|
})
|
|
254
295
|
|
|
296
|
+
// Consolidated tick formatters
|
|
297
|
+
const { handleLeftTickFormatting, handleBottomTickFormatting } = useTickFormatters({
|
|
298
|
+
isLogarithmicAxis,
|
|
299
|
+
orientation,
|
|
300
|
+
visualizationType,
|
|
301
|
+
min,
|
|
302
|
+
max,
|
|
303
|
+
shouldAbbreviate
|
|
304
|
+
})
|
|
305
|
+
|
|
255
306
|
// Calculate category label space for horizontal bar charts
|
|
256
307
|
const categoryLabelSpace = useMemo(() => {
|
|
257
308
|
return calculateHorizontalBarCategoryLabelWidth({
|
|
@@ -266,9 +317,19 @@ const LinearChart = forwardRef<SVGAElement, LinearChartProps>(({ parentHeight, p
|
|
|
266
317
|
}, [isHorizontal, config.visualizationType, config.yAxis.labelPlacement, yScale, parentWidth])
|
|
267
318
|
|
|
268
319
|
const horizontalYAxisLabelSpace = runtime.yAxis.label && !config.hideYAxisLabel ? 30 : 0
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
320
|
+
|
|
321
|
+
// Update y-axis width state when computed value changes (for horizontal bar charts)
|
|
322
|
+
useEffect(() => {
|
|
323
|
+
if (isHorizontal && config.visualizationType === 'Bar') {
|
|
324
|
+
const newWidth = categoryLabelSpace + horizontalYAxisLabelSpace
|
|
325
|
+
if (newWidth !== computedYAxisWidth) {
|
|
326
|
+
setComputedYAxisWidth(newWidth)
|
|
327
|
+
}
|
|
328
|
+
} else if (computedYAxisWidth !== null) {
|
|
329
|
+
// Reset to null for non-horizontal bar charts so we use config value
|
|
330
|
+
setComputedYAxisWidth(null)
|
|
331
|
+
}
|
|
332
|
+
}, [isHorizontal, config.visualizationType, categoryLabelSpace, horizontalYAxisLabelSpace, computedYAxisWidth])
|
|
272
333
|
|
|
273
334
|
const [yTickCount, xTickCount] = ['yAxis', 'xAxis'].map(axis =>
|
|
274
335
|
countNumOfTicks({ axis, max, runtime, currentViewport, isHorizontal, data, config, min })
|
|
@@ -312,65 +373,19 @@ const LinearChart = forwardRef<SVGAElement, LinearChartProps>(({ parentHeight, p
|
|
|
312
373
|
const useDateSpanMonths = isDateTime && dateSpanMonths > xTickCount && !config.runtime.xAxis.manual
|
|
313
374
|
|
|
314
375
|
// 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
376
|
const chartHasTooltipGuides = () => {
|
|
359
377
|
const { visualizationType } = config
|
|
360
378
|
if (visualizationType === 'Combo' && runtime.forecastingSeriesKeys > 0) return true
|
|
361
|
-
|
|
362
|
-
if (visualizationType === 'Line') return true
|
|
363
|
-
if (visualizationType === 'Bar') return true
|
|
364
|
-
return false
|
|
379
|
+
return TYPES_WITH_TOOLTIP_GUIDES.includes(visualizationType as any)
|
|
365
380
|
}
|
|
366
381
|
|
|
367
|
-
const getManualStep = () => {
|
|
382
|
+
const getManualStep = useCallback(() => {
|
|
368
383
|
let manualStep = config.xAxis.manualStep
|
|
369
384
|
if (config.xAxis.viewportStepCount && config.xAxis.viewportStepCount[currentViewport]) {
|
|
370
385
|
manualStep = config.xAxis.viewportStepCount[currentViewport]
|
|
371
386
|
}
|
|
372
387
|
return manualStep
|
|
373
|
-
}
|
|
388
|
+
}, [config.xAxis.manualStep, config.xAxis.viewportStepCount, currentViewport])
|
|
374
389
|
|
|
375
390
|
const smallMultiplesSync = useSmallMultipleSynchronization(xMax, yMax, getXValueFromCoordinate)
|
|
376
391
|
|
|
@@ -384,11 +399,17 @@ const LinearChart = forwardRef<SVGAElement, LinearChartProps>(({ parentHeight, p
|
|
|
384
399
|
y
|
|
385
400
|
})
|
|
386
401
|
|
|
387
|
-
|
|
402
|
+
// Warming Stripes handles its own sync at the rect level since
|
|
403
|
+
// getXValueFromCoordinate won't map correctly due to data sampling
|
|
404
|
+
if (visualizationType !== 'Warming Stripes') {
|
|
405
|
+
smallMultiplesSync.onMouseMove?.(event)
|
|
406
|
+
}
|
|
388
407
|
}
|
|
389
408
|
|
|
390
409
|
const onMouseLeave = () => {
|
|
391
|
-
|
|
410
|
+
if (visualizationType !== 'Warming Stripes') {
|
|
411
|
+
smallMultiplesSync.onMouseLeave?.()
|
|
412
|
+
}
|
|
392
413
|
}
|
|
393
414
|
|
|
394
415
|
// Use custom hook to provide programmatic tooltip control for small multiples
|
|
@@ -399,24 +420,23 @@ const LinearChart = forwardRef<SVGAElement, LinearChartProps>(({ parentHeight, p
|
|
|
399
420
|
setPoint,
|
|
400
421
|
setShowHoverLine,
|
|
401
422
|
handleTooltipMouseOver,
|
|
402
|
-
hideTooltip
|
|
423
|
+
hideTooltip,
|
|
424
|
+
setSynchronizedXValue
|
|
403
425
|
})
|
|
404
426
|
|
|
405
427
|
// Make sure the chart is visible if in the editor
|
|
406
|
-
/* eslint-disable react-hooks/exhaustive-deps */
|
|
407
428
|
useEffect(() => {
|
|
408
|
-
const element = document.querySelector('.
|
|
429
|
+
const element = document.querySelector('.is-editor')
|
|
409
430
|
if (element) {
|
|
410
|
-
|
|
411
|
-
setAnimatedChart(prevState => true)
|
|
431
|
+
setAnimatedChart(true)
|
|
412
432
|
}
|
|
413
|
-
})
|
|
433
|
+
}, [])
|
|
414
434
|
|
|
415
435
|
// If the chart is in view, set to animate if it has not already played
|
|
416
436
|
useEffect(() => {
|
|
417
437
|
if (dataRef?.isIntersecting === true && config.animate) {
|
|
418
438
|
setTimeout(() => {
|
|
419
|
-
setAnimatedChart(
|
|
439
|
+
setAnimatedChart(true)
|
|
420
440
|
}, 500)
|
|
421
441
|
}
|
|
422
442
|
}, [dataRef?.isIntersecting, config.animate])
|
|
@@ -461,9 +481,7 @@ const LinearChart = forwardRef<SVGAElement, LinearChartProps>(({ parentHeight, p
|
|
|
461
481
|
const svgHeight = initialHeight + svgAdditionalHeight
|
|
462
482
|
|
|
463
483
|
// Parent container height (includes brush if active)
|
|
464
|
-
const
|
|
465
|
-
const brushMargin = 10
|
|
466
|
-
const brushHeightWithMargin = config.xAxis.brushActive ? brushHeight + brushMargin : 0
|
|
484
|
+
const brushHeightWithMargin = config.xAxis.brushActive ? BRUSH_HEIGHT + BRUSH_MARGIN : 0
|
|
467
485
|
const parentHeight = svgHeight + brushHeightWithMargin
|
|
468
486
|
|
|
469
487
|
if (!parentRef.current) return
|
|
@@ -494,11 +512,12 @@ const LinearChart = forwardRef<SVGAElement, LinearChartProps>(({ parentHeight, p
|
|
|
494
512
|
}, [
|
|
495
513
|
axisBottomRef.current,
|
|
496
514
|
config,
|
|
497
|
-
bottomLabelStart,
|
|
498
515
|
config.xAxis.brushActive,
|
|
499
516
|
currentViewport,
|
|
500
517
|
topYLabelRef.current,
|
|
501
|
-
initialHeight
|
|
518
|
+
initialHeight,
|
|
519
|
+
parentWidth,
|
|
520
|
+
axisBottomSizeKey
|
|
502
521
|
])
|
|
503
522
|
|
|
504
523
|
useEffect(() => {
|
|
@@ -513,8 +532,8 @@ const LinearChart = forwardRef<SVGAElement, LinearChartProps>(({ parentHeight, p
|
|
|
513
532
|
|
|
514
533
|
const rightSideRemainingSpace = parentWidth - dataXPosition
|
|
515
534
|
|
|
516
|
-
const rightSide = rightSideRemainingSpace <= tooltipWidth && dataXPosition > parentWidth / 2 -
|
|
517
|
-
const maxWidth = rightSide ? dataXPosition -
|
|
535
|
+
const rightSide = rightSideRemainingSpace <= tooltipWidth && dataXPosition > parentWidth / 2 - TOOLTIP_EDGE_BUFFER
|
|
536
|
+
const maxWidth = rightSide ? dataXPosition - TOOLTIP_EDGE_BUFFER : parentWidth - (dataXPosition + TOOLTIP_OFFSET)
|
|
518
537
|
tooltipRef.current.node.style.maxWidth = `${maxWidth}px`
|
|
519
538
|
}, [tooltipOpen, tooltipData])
|
|
520
539
|
|
|
@@ -531,150 +550,6 @@ const LinearChart = forwardRef<SVGAElement, LinearChartProps>(({ parentHeight, p
|
|
|
531
550
|
)
|
|
532
551
|
}
|
|
533
552
|
|
|
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
553
|
return isNaN(parentWidth) ? (
|
|
679
554
|
<React.Fragment></React.Fragment>
|
|
680
555
|
) : (
|
|
@@ -687,7 +562,7 @@ const LinearChart = forwardRef<SVGAElement, LinearChartProps>(({ parentHeight, p
|
|
|
687
562
|
<svg
|
|
688
563
|
ref={internalSvgRef}
|
|
689
564
|
onMouseMove={onMouseMove}
|
|
690
|
-
width={parentWidth
|
|
565
|
+
width={parentWidth}
|
|
691
566
|
height={isNoDataAvailable ? 1 : calculatedSvgHeight ?? parentHeight}
|
|
692
567
|
className={`linear ${config.animate ? 'animated' : ''} ${animatedChart && config.animate ? 'animate' : ''} ${
|
|
693
568
|
debugSvg && 'debug'
|
|
@@ -707,60 +582,20 @@ const LinearChart = forwardRef<SVGAElement, LinearChartProps>(({ parentHeight, p
|
|
|
707
582
|
>
|
|
708
583
|
{!isDraggingAnnotation && <Bar width={parentWidth} height={initialHeight} fill={'transparent'}></Bar>}{' '}
|
|
709
584
|
{/* 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
|
-
)}
|
|
585
|
+
{/* Actual LeftAxis is drawn after visualization */}
|
|
586
|
+
{!TYPES_WITHOUT_GRID.includes(visualizationType as any) && config.yAxis.type !== 'categorical' && (
|
|
587
|
+
<LeftAxisGridlines
|
|
588
|
+
yScale={yScale}
|
|
589
|
+
xMax={xMax}
|
|
590
|
+
yAxisWidth={yAxisWidth}
|
|
591
|
+
numTicks={handleNumTicks}
|
|
592
|
+
yLabelOffset={yLabelOffset}
|
|
593
|
+
axisLabelFontSize={axisLabelFontSize}
|
|
594
|
+
/>
|
|
595
|
+
)}
|
|
761
596
|
{/* Horizontal chart grid lines */}
|
|
762
597
|
{runtime.xAxis.gridLines && orientation === 'horizontal' && (
|
|
763
|
-
<Group left={
|
|
598
|
+
<Group left={yAxisWidth}>
|
|
764
599
|
{xScale.ticks(xTickCount).map((tickValue, i) => {
|
|
765
600
|
const tickPosition = xScale(tickValue)
|
|
766
601
|
return (
|
|
@@ -774,167 +609,46 @@ const LinearChart = forwardRef<SVGAElement, LinearChartProps>(({ parentHeight, p
|
|
|
774
609
|
})}
|
|
775
610
|
</Group>
|
|
776
611
|
)}
|
|
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
612
|
{visualizationType === 'Paired Bar' && (
|
|
782
|
-
<
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
<ScatterPlot
|
|
786
|
-
xScale={xScale}
|
|
787
|
-
yScale={yScale}
|
|
788
|
-
getXAxisData={getXAxisData}
|
|
789
|
-
getYAxisData={getYAxisData}
|
|
790
|
-
xMax={xMax}
|
|
613
|
+
<PairedBarAxis
|
|
614
|
+
g1xScale={g1xScale}
|
|
615
|
+
g2xScale={g2xScale}
|
|
791
616
|
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
617
|
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}
|
|
618
|
+
yAxisWidth={yAxisWidth}
|
|
619
|
+
bottomLabelStart={bottomLabelStart}
|
|
620
|
+
tickLabelFontSize={tickLabelFontSize}
|
|
621
|
+
axisLabelFontSize={axisLabelFontSize}
|
|
622
|
+
axisBottomRef={axisBottomRef}
|
|
623
|
+
xAxisLabelRefs={xAxisLabelRefs}
|
|
624
|
+
tickLabelFont={GET_TEXT_WIDTH_FONT}
|
|
936
625
|
/>
|
|
937
626
|
)}
|
|
627
|
+
{/* Visualization Renderer - handles all chart type rendering */}
|
|
628
|
+
<VisualizationRenderer
|
|
629
|
+
xScale={xScale}
|
|
630
|
+
yScale={yScale}
|
|
631
|
+
xMax={xMax}
|
|
632
|
+
yMax={yMax}
|
|
633
|
+
seriesScale={seriesScale}
|
|
634
|
+
xScaleNoPadding={xScaleNoPadding}
|
|
635
|
+
min={min}
|
|
636
|
+
max={max}
|
|
637
|
+
parentWidth={parentWidth}
|
|
638
|
+
yAxisWidth={yAxisWidth}
|
|
639
|
+
forestHeight={forestHeight}
|
|
640
|
+
animatedChart={animatedChart}
|
|
641
|
+
tooltipData={tooltipData}
|
|
642
|
+
showTooltip={showTooltip}
|
|
643
|
+
handleTooltipMouseOver={handleTooltipMouseOver}
|
|
644
|
+
handleTooltipMouseOff={handleTooltipMouseOff}
|
|
645
|
+
handleTooltipClick={handleTooltipClick}
|
|
646
|
+
getXAxisData={getXAxisData}
|
|
647
|
+
getYAxisData={getYAxisData}
|
|
648
|
+
svgRef={svgRef}
|
|
649
|
+
forestPlotRightLabelRef={forestPlotRightLabelRef}
|
|
650
|
+
synchronizedXValue={synchronizedXValue}
|
|
651
|
+
/>
|
|
938
652
|
{/* Brush moved to separate overlay - no longer in main SVG */}
|
|
939
653
|
{/* y anchors */}
|
|
940
654
|
{config.yAxis.anchors &&
|
|
@@ -944,11 +658,10 @@ const LinearChart = forwardRef<SVGAElement, LinearChartProps>(({ parentHeight, p
|
|
|
944
658
|
|
|
945
659
|
if (!anchor.value) return
|
|
946
660
|
if (config.yAxis.labelPlacement === 'Below Bar') {
|
|
947
|
-
|
|
948
|
-
|
|
661
|
+
middleOffset =
|
|
662
|
+
BELOW_BAR_TEXT_OFFSET + Number(config.series.length * config.barHeight) / config.series.length
|
|
949
663
|
} else {
|
|
950
|
-
|
|
951
|
-
middleOffset = paddingOffset
|
|
664
|
+
middleOffset = LABEL_PADDING_OFFSET
|
|
952
665
|
}
|
|
953
666
|
|
|
954
667
|
if (!position) return
|
|
@@ -956,13 +669,13 @@ const LinearChart = forwardRef<SVGAElement, LinearChartProps>(({ parentHeight, p
|
|
|
956
669
|
return (
|
|
957
670
|
// prettier-ignore
|
|
958
671
|
<Line
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
672
|
+
key={`yAxis-${anchor.value}--${index}`}
|
|
673
|
+
strokeDasharray={handleLineType(anchor.lineStyle)}
|
|
674
|
+
stroke={anchor.color ? anchor.color : 'rgba(0,0,0,1)'}
|
|
675
|
+
className='anchor-y'
|
|
676
|
+
from={{ x: Number(runtime.yAxis.size), y: position - middleOffset }}
|
|
677
|
+
to={{ x: Number(runtime.yAxis.size) + Number(xMax), y: position - middleOffset }}
|
|
678
|
+
/>
|
|
966
679
|
)
|
|
967
680
|
})}
|
|
968
681
|
{/* x anchors */}
|
|
@@ -992,14 +705,14 @@ const LinearChart = forwardRef<SVGAElement, LinearChartProps>(({ parentHeight, p
|
|
|
992
705
|
return (
|
|
993
706
|
// prettier-ignore
|
|
994
707
|
<Line
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
708
|
+
key={`xAxis-${anchor.value}--${index}`}
|
|
709
|
+
strokeDasharray={handleLineType(anchor.lineStyle)}
|
|
710
|
+
stroke={anchor.color ? anchor.color : 'rgba(0,0,0,1)'}
|
|
711
|
+
fill={anchor.color ? anchor.color : 'rgba(0,0,0,1)'}
|
|
712
|
+
className='anchor-x'
|
|
713
|
+
from={{ x: Number(anchorPosition) + Number(padding), y: 0 }}
|
|
714
|
+
to={{ x: Number(anchorPosition) + Number(padding), y: yMax }}
|
|
715
|
+
/>
|
|
1003
716
|
)
|
|
1004
717
|
})}
|
|
1005
718
|
{/* we are handling regions in bar charts differently, so that we can calculate the bar group into the region space. */}
|
|
@@ -1019,7 +732,7 @@ const LinearChart = forwardRef<SVGAElement, LinearChartProps>(({ parentHeight, p
|
|
|
1019
732
|
)}
|
|
1020
733
|
{isNoDataAvailable && (
|
|
1021
734
|
<Text
|
|
1022
|
-
x={
|
|
735
|
+
x={yAxisWidth + Number(xMax / 2)}
|
|
1023
736
|
y={initialHeight / 2 - (config.xAxis.padding || 0) / 2}
|
|
1024
737
|
textAnchor='middle'
|
|
1025
738
|
>
|
|
@@ -1032,405 +745,65 @@ const LinearChart = forwardRef<SVGAElement, LinearChartProps>(({ parentHeight, p
|
|
|
1032
745
|
<HoverLine xMax={xMax} yMax={yMax} point={point} tooltipData={tooltipData} orientation='vertical' />
|
|
1033
746
|
</>
|
|
1034
747
|
)}
|
|
1035
|
-
<Group left={
|
|
748
|
+
<Group left={yAxisWidth}>
|
|
1036
749
|
<Annotation.Draggable
|
|
1037
750
|
xScale={xScale}
|
|
1038
751
|
yScale={yScale}
|
|
1039
752
|
xScaleAnnotation={xScaleAnnotation}
|
|
753
|
+
yScaleAnnotation={yScaleAnnotation}
|
|
1040
754
|
xMax={xMax}
|
|
755
|
+
yMax={yMax}
|
|
756
|
+
seriesScale={seriesScale}
|
|
1041
757
|
svgRef={svgRef}
|
|
1042
758
|
onDragStateChange={handleDragStateChange}
|
|
1043
759
|
/>
|
|
1044
760
|
</Group>
|
|
1045
761
|
{/* Highlighted regions */}
|
|
1046
762
|
{/* Y axis */}
|
|
1047
|
-
{
|
|
763
|
+
{/* Horizon charts don't have a grid but should be rendered with a left axis */}
|
|
764
|
+
{(!TYPES_WITHOUT_GRID.includes(visualizationType as any) || visualizationType === 'Horizon Chart') &&
|
|
1048
765
|
config.yAxis.type !== 'categorical' && (
|
|
1049
|
-
<
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
tickFormat={handleLeftTickFormatting}
|
|
766
|
+
<LeftAxis
|
|
767
|
+
yScale={yScale}
|
|
768
|
+
xScale={xScale}
|
|
769
|
+
yMax={yMax}
|
|
770
|
+
xMax={xMax}
|
|
771
|
+
yAxisWidth={yAxisWidth}
|
|
1056
772
|
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>
|
|
773
|
+
tickLabelFontSize={tickLabelFontSize}
|
|
774
|
+
axisLabelFontSize={axisLabelFontSize}
|
|
775
|
+
handleLeftTickFormatting={handleLeftTickFormatting}
|
|
776
|
+
topYLabelRef={topYLabelRef}
|
|
777
|
+
suffixRef={suffixRef}
|
|
778
|
+
suffixWidth={suffixWidth}
|
|
779
|
+
horizontalYAxisLabelSpace={horizontalYAxisLabelSpace}
|
|
780
|
+
categoryLabelSpace={categoryLabelSpace}
|
|
781
|
+
yLabelOffset={yLabelOffset}
|
|
782
|
+
/>
|
|
1348
783
|
)}
|
|
1349
784
|
{config.yAxis.type === 'categorical' && config.orientation === 'vertical' && (
|
|
1350
785
|
<CategoricalYAxis
|
|
1351
786
|
yScale={yScale}
|
|
1352
787
|
xMax={xMax}
|
|
1353
788
|
yMax={yMax}
|
|
1354
|
-
leftSize={
|
|
789
|
+
leftSize={yAxisWidth - config.yAxis.axisPadding}
|
|
1355
790
|
/>
|
|
1356
791
|
)}
|
|
1357
792
|
{/* Right Axis */}
|
|
1358
793
|
{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>
|
|
794
|
+
<RightAxis
|
|
795
|
+
yScaleRight={yScaleRight}
|
|
796
|
+
yMax={yMax}
|
|
797
|
+
xMax={xMax}
|
|
798
|
+
yAxisWidth={yAxisWidth}
|
|
799
|
+
tickLabelFontSize={tickLabelFontSize}
|
|
800
|
+
axisLabelFontSize={axisLabelFontSize}
|
|
801
|
+
/>
|
|
1429
802
|
)}
|
|
1430
803
|
{hasTopAxis && config.topAxis.hasLine && (
|
|
1431
804
|
<AxisTop
|
|
1432
805
|
stroke='#333'
|
|
1433
|
-
left={
|
|
806
|
+
left={yAxisWidth}
|
|
1434
807
|
scale={xScale}
|
|
1435
808
|
hideTicks
|
|
1436
809
|
hideZero
|
|
@@ -1441,189 +814,27 @@ const LinearChart = forwardRef<SVGAElement, LinearChartProps>(({ parentHeight, p
|
|
|
1441
814
|
)}
|
|
1442
815
|
{/* X axis */}
|
|
1443
816
|
{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>
|
|
817
|
+
<BottomAxis
|
|
818
|
+
axisBottomRef={axisBottomRef}
|
|
819
|
+
xScale={xScale}
|
|
820
|
+
yMax={yMax}
|
|
821
|
+
xMax={xMax}
|
|
822
|
+
yAxisWidth={yAxisWidth}
|
|
823
|
+
xTickCount={xTickCount}
|
|
824
|
+
tickLabelFontSize={tickLabelFontSize}
|
|
825
|
+
axisLabelFontSize={axisLabelFontSize}
|
|
826
|
+
handleBottomTickFormatting={handleBottomTickFormatting}
|
|
827
|
+
useDateSpanMonths={useDateSpanMonths}
|
|
828
|
+
dateSpanMonths={dateSpanMonths}
|
|
829
|
+
xAxisDataMapped={xAxisDataMapped}
|
|
830
|
+
uniqueXAxisDataMapped={uniqueXAxisDataMapped}
|
|
831
|
+
isDateTime={isDateTime}
|
|
832
|
+
bottomLabelStart={bottomLabelStart}
|
|
833
|
+
parentWidth={parentWidth}
|
|
834
|
+
xAxisLabelRefs={xAxisLabelRefs}
|
|
835
|
+
xAxisTitleRef={xAxisTitleRef}
|
|
836
|
+
getManualStep={getManualStep}
|
|
837
|
+
/>
|
|
1627
838
|
)}
|
|
1628
839
|
</svg>
|
|
1629
840
|
{!isDraggingAnnotation &&
|
|
@@ -1643,7 +854,7 @@ const LinearChart = forwardRef<SVGAElement, LinearChartProps>(({ parentHeight, p
|
|
|
1643
854
|
<TooltipWithBounds
|
|
1644
855
|
ref={tooltipRef}
|
|
1645
856
|
key={Math.random()}
|
|
1646
|
-
className={'tooltip
|
|
857
|
+
className={'tooltip cove-visualization'}
|
|
1647
858
|
left={tooltipLeft}
|
|
1648
859
|
top={tooltipTop}
|
|
1649
860
|
>
|
|
@@ -1680,10 +891,10 @@ const LinearChart = forwardRef<SVGAElement, LinearChartProps>(({ parentHeight, p
|
|
|
1680
891
|
<div
|
|
1681
892
|
style={{
|
|
1682
893
|
position: 'relative',
|
|
1683
|
-
marginTop:
|
|
1684
|
-
left: `${
|
|
1685
|
-
width: `${Math.max(xMax,
|
|
1686
|
-
height:
|
|
894
|
+
marginTop: `${BRUSH_MARGIN}px`,
|
|
895
|
+
left: `${yAxisWidth}px`,
|
|
896
|
+
width: `${Math.max(xMax, BRUSH_MIN_WIDTH)}px`,
|
|
897
|
+
height: `${BRUSH_HEIGHT}px`,
|
|
1687
898
|
pointerEvents: 'auto',
|
|
1688
899
|
zIndex: 15,
|
|
1689
900
|
touchAction: 'none', // Enable touch interactions for brush
|
|
@@ -1693,7 +904,7 @@ const LinearChart = forwardRef<SVGAElement, LinearChartProps>(({ parentHeight, p
|
|
|
1693
904
|
}}
|
|
1694
905
|
className='brush-overlay'
|
|
1695
906
|
>
|
|
1696
|
-
<BrushSelector xMax={Math.max(xMax,
|
|
907
|
+
<BrushSelector key={brushKeyRef.current} xMax={Math.max(xMax, BRUSH_MIN_WIDTH)} yMax={BRUSH_HEIGHT} />
|
|
1697
908
|
</div>
|
|
1698
909
|
)}
|
|
1699
910
|
</div>
|