@cdc/chart 4.25.11 → 4.26.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cdcchart.js +38898 -40013
- package/examples/feature/pie/planet-pie-example-config.json +48 -2
- package/examples/private/DEV-12100.json +1303 -0
- package/examples/private/cat-y.json +1235 -0
- package/examples/private/data-points.json +228 -0
- package/examples/private/height.json +3915 -0
- package/examples/private/links.json +569 -0
- package/examples/private/quadrant.txt +30 -0
- package/examples/private/test-forecast.json +5510 -0
- package/examples/private/warming-stripe-test.json +2578 -0
- package/examples/private/warming-stripes.json +4763 -0
- package/examples/tech-adoption-with-links.json +560 -0
- package/index.html +15 -20
- package/package.json +5 -4
- package/preview.html +1616 -0
- package/src/CdcChartComponent.tsx +111 -75
- package/src/_stories/Chart.Regions.Categorical.stories.tsx +148 -0
- package/src/_stories/Chart.Regions.DateScale.stories.tsx +197 -0
- package/src/_stories/Chart.Regions.DateTimeScale.stories.tsx +297 -0
- package/src/_stories/Chart.stories.tsx +8 -0
- package/src/_stories/ChartBar.Editor.stories.tsx +11 -6
- package/src/_stories/ChartBrush.Editor.stories.tsx +295 -0
- package/src/_stories/ChartBrush.stories.tsx +50 -0
- package/src/_stories/ChartEditor.Editor.stories.tsx +3 -5
- package/src/_stories/TechAdoptionWithLinks.stories.tsx +27 -0
- package/src/_stories/_mock/brush_enabled.json +326 -0
- package/src/_stories/_mock/brush_mock.json +2 -69
- package/src/_stories/_mock/horizontal-bars-dynamic-y-axis.json +413 -0
- package/src/components/AreaChart/components/AreaChart.Stacked.jsx +1 -2
- package/src/components/Axis/Categorical.Axis.tsx +6 -7
- package/src/components/BarChart/components/BarChart.Horizontal.tsx +178 -24
- package/src/components/BarChart/components/BarChart.StackedHorizontal.tsx +3 -1
- package/src/components/BarChart/components/BarChart.StackedVertical.tsx +1 -0
- package/src/components/BarChart/components/BarChart.Vertical.tsx +6 -8
- package/src/components/BarChart/components/context.tsx +1 -0
- package/src/components/BarChart/helpers/useBarChart.ts +14 -2
- package/src/components/Brush/BrushSelector.tsx +1258 -0
- package/src/components/Brush/MiniChartPreview.tsx +283 -0
- package/src/components/DeviationBar.jsx +9 -7
- package/src/components/EditorPanel/EditorPanel.tsx +2711 -2586
- package/src/components/EditorPanel/components/Panels/Panel.ForestPlotSettings.tsx +56 -34
- package/src/components/EditorPanel/components/Panels/Panel.General.tsx +57 -30
- package/src/components/EditorPanel/components/Panels/Panel.PatternSettings.tsx +2 -0
- package/src/components/EditorPanel/components/Panels/Panel.SmallMultiples.tsx +30 -25
- package/src/components/EditorPanel/components/Panels/Panel.Visual.tsx +21 -27
- package/src/components/EditorPanel/useEditorPermissions.ts +31 -18
- package/src/components/Legend/Legend.tsx +3 -2
- package/src/components/Legend/helpers/createFormatLabels.tsx +151 -2
- package/src/components/Legend/helpers/index.ts +10 -6
- package/src/components/LinearChart.tsx +495 -430
- package/src/components/PairedBarChart.jsx +20 -3
- package/src/components/Regions/components/Regions.tsx +365 -122
- package/src/components/ScatterPlot/ScatterPlot.jsx +2 -2
- package/src/components/SmallMultiples/SmallMultipleTile.tsx +5 -1
- package/src/components/WarmingStripes/WarmingStripes.tsx +160 -0
- package/src/components/WarmingStripes/WarmingStripesGradientLegend.css +35 -0
- package/src/components/WarmingStripes/WarmingStripesGradientLegend.tsx +104 -0
- package/src/components/WarmingStripes/index.tsx +3 -0
- package/src/data/initial-state.js +3 -1
- package/src/helpers/calculateHorizontalBarCategoryLabelWidth.ts +57 -0
- package/src/helpers/getMinMax.ts +12 -7
- package/src/helpers/sizeHelpers.ts +0 -20
- package/src/helpers/smallMultiplesHelpers.ts +1 -1
- package/src/hooks/useChartHoverAnalytics.tsx +10 -9
- package/src/hooks/useScales.ts +11 -1
- package/src/hooks/useTooltip.tsx +31 -10
- package/src/scss/DataTable.scss +0 -4
- package/src/scss/main.scss +17 -3
- package/src/test/CdcChart.test.jsx +1 -1
- package/src/types/ChartConfig.ts +3 -0
- package/src/types/Label.ts +1 -0
- package/src/utils/analyticsTracking.ts +19 -0
- package/LICENSE +0 -201
- package/src/components/Brush/BrushChart.tsx +0 -128
- package/src/components/Brush/BrushController.tsx +0 -71
- package/src/components/Brush/types.tsx +0 -8
- package/src/components/BrushChart.tsx +0 -223
|
@@ -1,4 +1,13 @@
|
|
|
1
|
-
import React, {
|
|
1
|
+
import React, {
|
|
2
|
+
forwardRef,
|
|
3
|
+
useContext,
|
|
4
|
+
useEffect,
|
|
5
|
+
useImperativeHandle,
|
|
6
|
+
useMemo,
|
|
7
|
+
useRef,
|
|
8
|
+
useState,
|
|
9
|
+
useCallback
|
|
10
|
+
} from 'react'
|
|
2
11
|
|
|
3
12
|
// Libraries
|
|
4
13
|
import { AxisLeft, AxisBottom, AxisRight, AxisTop } from '@visx/axis'
|
|
@@ -27,13 +36,15 @@ import PairedBarChart from './PairedBarChart'
|
|
|
27
36
|
import useIntersectionObserver from '../hooks/useIntersectionObserver'
|
|
28
37
|
import Regions from './Regions'
|
|
29
38
|
import CategoricalYAxis from './Axis/Categorical.Axis'
|
|
30
|
-
import
|
|
39
|
+
import BrushSelector from './Brush/BrushSelector'
|
|
40
|
+
import WarmingStripes from './WarmingStripes'
|
|
31
41
|
|
|
32
42
|
// Helpers
|
|
33
43
|
import { isLegendWrapViewport, isMobileFontViewport } from '@cdc/core/helpers/viewports'
|
|
34
44
|
import { getTextWidth } from '@cdc/core/helpers/getTextWidth'
|
|
35
|
-
import { calcInitialHeight
|
|
45
|
+
import { calcInitialHeight } from '../helpers/sizeHelpers'
|
|
36
46
|
import { filterAndShiftLinearDateTicks } from '../helpers/filterAndShiftLinearDateTicks'
|
|
47
|
+
import { calculateHorizontalBarCategoryLabelWidth } from '../helpers/calculateHorizontalBarCategoryLabelWidth'
|
|
37
48
|
|
|
38
49
|
// Hooks
|
|
39
50
|
import useReduceData from '../hooks/useReduceData'
|
|
@@ -97,6 +108,7 @@ const LinearChart = forwardRef<SVGAElement, LinearChartProps>(({ parentHeight, p
|
|
|
97
108
|
handleDragStateChange,
|
|
98
109
|
interactionLabel,
|
|
99
110
|
isDraggingAnnotation,
|
|
111
|
+
isEditor,
|
|
100
112
|
legendRef,
|
|
101
113
|
parseDate,
|
|
102
114
|
parentRef,
|
|
@@ -125,13 +137,18 @@ const LinearChart = forwardRef<SVGAElement, LinearChartProps>(({ parentHeight, p
|
|
|
125
137
|
const { labelsAboveGridlines, hideAxis, inlineLabel } = config.yAxis
|
|
126
138
|
|
|
127
139
|
// HOOKS % STATES
|
|
128
|
-
|
|
129
|
-
|
|
140
|
+
// When brush is active, use tableData (full dataset) for min/max calculation
|
|
141
|
+
// so the y-axis shows the full range, but still use filtered data for rendering
|
|
142
|
+
const dataForMinMax = config.xAxis.brushActive && tableData && tableData.length > 0 ? tableData : data
|
|
143
|
+
const { minValue, maxValue, existPositiveValue, isAllLine } = useReduceData(config, dataForMinMax)
|
|
144
|
+
|
|
145
|
+
const { visSupportsSmallMultiples } = useEditorPermissions()
|
|
130
146
|
const { hasTopAxis } = getTopAxis(config)
|
|
131
147
|
const [animatedChart, setAnimatedChart] = useState(false)
|
|
132
148
|
const [showHoverLine, setShowHoverLine] = useState(false)
|
|
133
149
|
const [point, setPoint] = useState({ x: 0, y: 0 })
|
|
134
150
|
const [suffixWidth, setSuffixWidth] = useState(0)
|
|
151
|
+
const [calculatedSvgHeight, setCalculatedSvgHeight] = useState<number | null>(null)
|
|
135
152
|
|
|
136
153
|
// REFS
|
|
137
154
|
const axisBottomRef = useRef(null)
|
|
@@ -141,7 +158,6 @@ const LinearChart = forwardRef<SVGAElement, LinearChartProps>(({ parentHeight, p
|
|
|
141
158
|
const triggerRef = useRef()
|
|
142
159
|
const xAxisLabelRefs = useRef([])
|
|
143
160
|
const xAxisTitleRef = useRef(null)
|
|
144
|
-
const gridLineRefs = useRef([])
|
|
145
161
|
const tooltipRef = useRef(null)
|
|
146
162
|
|
|
147
163
|
const dataRef = useIntersectionObserver(triggerRef, {
|
|
@@ -167,45 +183,19 @@ const LinearChart = forwardRef<SVGAElement, LinearChartProps>(({ parentHeight, p
|
|
|
167
183
|
|
|
168
184
|
// height before bottom axis
|
|
169
185
|
const initialHeight = useMemo(
|
|
170
|
-
() => calcInitialHeight(config, currentViewport),
|
|
171
|
-
[config, currentViewport, parentHeight, config.heights?.vertical, config.heights?.horizontal]
|
|
186
|
+
() => (visualizationType === 'Warming Stripes' ? 78 : calcInitialHeight(config, currentViewport)),
|
|
187
|
+
[config, currentViewport, parentHeight, config.heights?.vertical, config.heights?.horizontal, visualizationType]
|
|
172
188
|
)
|
|
173
189
|
const forestHeight = useMemo(() => initialHeight + forestRowsHeight, [initialHeight, forestRowsHeight])
|
|
174
190
|
|
|
175
|
-
// width
|
|
176
|
-
const width = useMemo(() => {
|
|
177
|
-
const initialWidth = dimensions[0]
|
|
178
|
-
const legendHidden = legend?.hide
|
|
179
|
-
const legendOnTopOrBottom = ['bottom', 'top'].includes(config.legend?.position)
|
|
180
|
-
const legendWrapped = isLegendWrapViewport(currentViewport)
|
|
181
|
-
|
|
182
|
-
const legendShowingLeftOrRight = !isForestPlot && !legendHidden && !legendOnTopOrBottom && !legendWrapped
|
|
183
|
-
|
|
184
|
-
if (!legendShowingLeftOrRight) return initialWidth
|
|
185
|
-
|
|
186
|
-
if (legendRef.current) {
|
|
187
|
-
const legendStyle = getComputedStyle(legendRef.current)
|
|
188
|
-
return (
|
|
189
|
-
initialWidth -
|
|
190
|
-
legendRef.current.getBoundingClientRect().width -
|
|
191
|
-
parseInt(legendStyle.marginLeft) -
|
|
192
|
-
parseInt(legendStyle.marginRight)
|
|
193
|
-
)
|
|
194
|
-
}
|
|
195
|
-
|
|
196
|
-
return initialWidth * 0.73
|
|
197
|
-
}, [dimensions[0], config.legend, currentViewport, legendRef.current])
|
|
198
|
-
|
|
199
191
|
// Used to calculate the y position of the x-axis title
|
|
200
192
|
const bottomLabelStart = useMemo(() => {
|
|
201
193
|
xAxisLabelRefs.current = xAxisLabelRefs.current?.filter(label => label)
|
|
202
194
|
if (!xAxisLabelRefs.current.length) return
|
|
203
195
|
const tallestLabel = Math.max(...xAxisLabelRefs.current.map(label => label.getBBox().height))
|
|
204
196
|
return tallestLabel + X_TICK_LABEL_PADDING + DEFAULT_TICK_LENGTH
|
|
205
|
-
}, [
|
|
197
|
+
}, [parentWidth, config.xAxis, xAxisLabelRefs.current, config.xAxis.tickRotation])
|
|
206
198
|
|
|
207
|
-
// xMax and yMax
|
|
208
|
-
const xMax = width - runtime.yAxis.size - (visualizationType === 'Combo' ? config.yAxis.rightAxisSize : 0)
|
|
209
199
|
const yMax = initialHeight + forestRowsHeight
|
|
210
200
|
|
|
211
201
|
const isNoDataAvailable = config.filters?.length > 0 && data.length === 0
|
|
@@ -216,8 +206,25 @@ const LinearChart = forwardRef<SVGAElement, LinearChartProps>(({ parentHeight, p
|
|
|
216
206
|
: d[config.runtime.originalXAxis.dataKey]
|
|
217
207
|
const getYAxisData = (d, seriesKey) => d[seriesKey]
|
|
218
208
|
const xAxisDataMapped = data.map(d => getXAxisData(d))
|
|
209
|
+
|
|
210
|
+
// Get unique x-axis values (for cases where multiple series share the same x-axis value)
|
|
211
|
+
// This is important for brush filtering where we want to count unique dates, not total data points
|
|
212
|
+
const uniqueXAxisDataMapped = useMemo(() => {
|
|
213
|
+
const unique = new Set()
|
|
214
|
+
const result: any[] = []
|
|
215
|
+
for (const value of xAxisDataMapped) {
|
|
216
|
+
const key = value instanceof Date ? value.getTime() : typeof value === 'number' ? value : String(value)
|
|
217
|
+
if (!unique.has(key)) {
|
|
218
|
+
unique.add(key)
|
|
219
|
+
result.push(value)
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
return result
|
|
223
|
+
}, [xAxisDataMapped])
|
|
219
224
|
const { yScaleRight, hasRightAxis } = useRightAxis({ config, yMax, data })
|
|
220
225
|
|
|
226
|
+
const xMax = parentWidth - Number(runtime.yAxis.size) - (hasRightAxis ? config.yAxis.rightAxisSize : 0)
|
|
227
|
+
|
|
221
228
|
const {
|
|
222
229
|
xScale,
|
|
223
230
|
yScale,
|
|
@@ -240,15 +247,29 @@ const LinearChart = forwardRef<SVGAElement, LinearChartProps>(({ parentHeight, p
|
|
|
240
247
|
existPositiveValue,
|
|
241
248
|
xAxisDataMapped,
|
|
242
249
|
yMax,
|
|
243
|
-
|
|
244
|
-
xMax:
|
|
245
|
-
parentWidth -
|
|
246
|
-
Number(config.orientation === 'horizontal' ? config.xAxis.size : config.yAxis.size) -
|
|
247
|
-
(hasRightAxis ? config.yAxis.rightAxisSize : 0),
|
|
250
|
+
xMax,
|
|
248
251
|
needsYAxisAutoPadding,
|
|
249
252
|
currentViewport
|
|
250
253
|
})
|
|
251
254
|
|
|
255
|
+
// Calculate category label space for horizontal bar charts
|
|
256
|
+
const categoryLabelSpace = useMemo(() => {
|
|
257
|
+
return calculateHorizontalBarCategoryLabelWidth({
|
|
258
|
+
yScale,
|
|
259
|
+
chartWidth: parentWidth,
|
|
260
|
+
formatDate,
|
|
261
|
+
parseDate,
|
|
262
|
+
tickLabelFont: GET_TEXT_WIDTH_FONT,
|
|
263
|
+
xAxisType: config.runtime.xAxis?.type,
|
|
264
|
+
labelPlacement: config.yAxis.labelPlacement
|
|
265
|
+
})
|
|
266
|
+
}, [isHorizontal, config.visualizationType, config.yAxis.labelPlacement, yScale, parentWidth])
|
|
267
|
+
|
|
268
|
+
const horizontalYAxisLabelSpace = runtime.yAxis.label && !config.hideYAxisLabel ? 30 : 0
|
|
269
|
+
if (isHorizontal && config.visualizationType === 'Bar') {
|
|
270
|
+
runtime.yAxis.size = categoryLabelSpace + horizontalYAxisLabelSpace
|
|
271
|
+
}
|
|
272
|
+
|
|
252
273
|
const [yTickCount, xTickCount] = ['yAxis', 'xAxis'].map(axis =>
|
|
253
274
|
countNumOfTicks({ axis, max, runtime, currentViewport, isHorizontal, data, config, min })
|
|
254
275
|
)
|
|
@@ -381,23 +402,6 @@ const LinearChart = forwardRef<SVGAElement, LinearChartProps>(({ parentHeight, p
|
|
|
381
402
|
hideTooltip
|
|
382
403
|
})
|
|
383
404
|
|
|
384
|
-
// EFFECTS
|
|
385
|
-
// Adjust padding on the right side of the chart to accommodate for overflow
|
|
386
|
-
useEffect(() => {
|
|
387
|
-
if (!parentRef.current || !parentWidth || !gridLineRefs.current.length) return
|
|
388
|
-
|
|
389
|
-
const [updatePadding, paddingToAdd] = handleAutoPaddingRight(parentRef, xAxisLabelRefs, parentWidth)
|
|
390
|
-
|
|
391
|
-
if (!updatePadding) return
|
|
392
|
-
|
|
393
|
-
parentRef.current.style.paddingRight = `${paddingToAdd}px`
|
|
394
|
-
// subtract padding from grid line's x1 value
|
|
395
|
-
gridLineRefs.current.forEach(gridLine => {
|
|
396
|
-
if (!gridLine) return
|
|
397
|
-
gridLine.setAttribute('x1', xMax - paddingToAdd)
|
|
398
|
-
})
|
|
399
|
-
}, [parentWidth, parentHeight, data])
|
|
400
|
-
|
|
401
405
|
// Make sure the chart is visible if in the editor
|
|
402
406
|
/* eslint-disable react-hooks/exhaustive-deps */
|
|
403
407
|
useEffect(() => {
|
|
@@ -449,16 +453,24 @@ const LinearChart = forwardRef<SVGAElement, LinearChartProps>(({ parentHeight, p
|
|
|
449
453
|
const topLabelOnGridline = topYLabelRef.current && yAxis.labelsAboveGridlines
|
|
450
454
|
|
|
451
455
|
// Heights to add
|
|
452
|
-
|
|
453
|
-
const brushHeight = 25
|
|
454
|
-
const brushHeightWithMargin = config.xAxis.brushActive ? brushHeight + brushHeight : 0
|
|
455
456
|
const forestRowsHeight = isForestPlot ? config.data.length * forestPlot.rowHeight : 0
|
|
456
457
|
const topLabelOnGridlineHeight = topLabelOnGridline ? topYLabelRef.current.getBBox().height : 0
|
|
457
|
-
|
|
458
|
-
|
|
458
|
+
|
|
459
|
+
// SVG height (without brush)
|
|
460
|
+
const svgAdditionalHeight = axisBottomHeight + forestRowsHeight + topLabelOnGridlineHeight
|
|
461
|
+
const svgHeight = initialHeight + svgAdditionalHeight
|
|
462
|
+
|
|
463
|
+
// Parent container height (includes brush if active)
|
|
464
|
+
const brushHeight = 70
|
|
465
|
+
const brushMargin = 10
|
|
466
|
+
const brushHeightWithMargin = config.xAxis.brushActive ? brushHeight + brushMargin : 0
|
|
467
|
+
const parentHeight = svgHeight + brushHeightWithMargin
|
|
468
|
+
|
|
459
469
|
if (!parentRef.current) return
|
|
470
|
+
parentRef.current.style.height = `${parentHeight}px`
|
|
460
471
|
|
|
461
|
-
|
|
472
|
+
// Set the calculated SVG height via state to ensure it's used on render
|
|
473
|
+
setCalculatedSvgHeight(svgHeight)
|
|
462
474
|
|
|
463
475
|
/* Adding text above the top gridline overflows the bounds of the svg.
|
|
464
476
|
To accommodate for this we need to...
|
|
@@ -472,7 +484,7 @@ const LinearChart = forwardRef<SVGAElement, LinearChartProps>(({ parentHeight, p
|
|
|
472
484
|
const svg = internalSvgRef.current
|
|
473
485
|
if (!svg) return
|
|
474
486
|
const parentWidthFromRef = parentRef.current.getBoundingClientRect().width
|
|
475
|
-
svg.setAttribute('viewBox', `0 ${-topLabelOnGridlineHeight} ${parentWidthFromRef} ${
|
|
487
|
+
svg.setAttribute('viewBox', `0 ${-topLabelOnGridlineHeight} ${parentWidthFromRef} ${svgHeight}`)
|
|
476
488
|
|
|
477
489
|
// translate legend match viewBox-adjusted height
|
|
478
490
|
if (!legendRef.current) return
|
|
@@ -506,8 +518,8 @@ const LinearChart = forwardRef<SVGAElement, LinearChartProps>(({ parentHeight, p
|
|
|
506
518
|
tooltipRef.current.node.style.maxWidth = `${maxWidth}px`
|
|
507
519
|
}, [tooltipOpen, tooltipData])
|
|
508
520
|
|
|
509
|
-
// Check if small multiples are enabled - if so, render SmallMultiples instead
|
|
510
|
-
if (config.smallMultiples?.mode) {
|
|
521
|
+
// Check if small multiples are enabled and supported - if so, render SmallMultiples instead
|
|
522
|
+
if (config.smallMultiples?.mode && visSupportsSmallMultiples()) {
|
|
511
523
|
return (
|
|
512
524
|
<SmallMultiples
|
|
513
525
|
config={config}
|
|
@@ -663,7 +675,7 @@ const LinearChart = forwardRef<SVGAElement, LinearChartProps>(({ parentHeight, p
|
|
|
663
675
|
</>
|
|
664
676
|
)
|
|
665
677
|
}
|
|
666
|
-
return isNaN(
|
|
678
|
+
return isNaN(parentWidth) ? (
|
|
667
679
|
<React.Fragment></React.Fragment>
|
|
668
680
|
) : (
|
|
669
681
|
<ErrorBoundary component='LinearChart'>
|
|
@@ -676,7 +688,7 @@ const LinearChart = forwardRef<SVGAElement, LinearChartProps>(({ parentHeight, p
|
|
|
676
688
|
ref={internalSvgRef}
|
|
677
689
|
onMouseMove={onMouseMove}
|
|
678
690
|
width={parentWidth + config.yAxis.rightAxisSize}
|
|
679
|
-
height={isNoDataAvailable ? 1 : parentHeight}
|
|
691
|
+
height={isNoDataAvailable ? 1 : calculatedSvgHeight ?? parentHeight}
|
|
680
692
|
className={`linear ${config.animate ? 'animated' : ''} ${animatedChart && config.animate ? 'animate' : ''} ${
|
|
681
693
|
debugSvg && 'debug'
|
|
682
694
|
} ${isDraggingAnnotation && 'dragging-annotation'}`}
|
|
@@ -696,61 +708,79 @@ const LinearChart = forwardRef<SVGAElement, LinearChartProps>(({ parentHeight, p
|
|
|
696
708
|
{!isDraggingAnnotation && <Bar width={parentWidth} height={initialHeight} fill={'transparent'}></Bar>}{' '}
|
|
697
709
|
{/* GRID LINES */}
|
|
698
710
|
{/* Actual AxisLeft is drawn after visualization */}
|
|
699
|
-
{!['Spark Line', 'Forest Plot'].includes(visualizationType) &&
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
711
|
+
{!['Spark Line', 'Forest Plot', 'Warming Stripes'].includes(visualizationType) &&
|
|
712
|
+
config.yAxis.type !== 'categorical' && (
|
|
713
|
+
<AxisLeft
|
|
714
|
+
scale={yScale}
|
|
715
|
+
left={Number(runtime.yAxis.size) - config.yAxis.axisPadding}
|
|
716
|
+
numTicks={handleNumTicks}
|
|
717
|
+
>
|
|
718
|
+
{props => {
|
|
719
|
+
const axisCenter =
|
|
720
|
+
config.orientation === 'horizontal'
|
|
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
|
+
)}
|
|
761
|
+
{/* Horizontal chart grid lines */}
|
|
762
|
+
{runtime.xAxis.gridLines && orientation === 'horizontal' && (
|
|
763
|
+
<Group left={Number(runtime.yAxis.size)}>
|
|
764
|
+
{xScale.ticks(xTickCount).map((tickValue, i) => {
|
|
765
|
+
const tickPosition = xScale(tickValue)
|
|
710
766
|
return (
|
|
711
|
-
<
|
|
712
|
-
{
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
<Group key={`vx-tick-${tick.value}-${i}`} className={'vx-axis-tick'}>
|
|
718
|
-
{runtime.yAxis.gridLines && !hideFirstGridLine ? (
|
|
719
|
-
<Line
|
|
720
|
-
innerRef={el => (gridLineRefs.current[i] = el)}
|
|
721
|
-
key={`${tick.value}--hide-hideGridLines`}
|
|
722
|
-
display={(isLogarithmicAxis && showTicks).toString()}
|
|
723
|
-
from={{ x: tick.from.x + xMax, y: tick.from.y }}
|
|
724
|
-
to={tick.from}
|
|
725
|
-
stroke='#d6d6d6'
|
|
726
|
-
/>
|
|
727
|
-
) : (
|
|
728
|
-
''
|
|
729
|
-
)}
|
|
730
|
-
</Group>
|
|
731
|
-
)
|
|
732
|
-
})}
|
|
733
|
-
<Text
|
|
734
|
-
className='y-label'
|
|
735
|
-
textAnchor='middle'
|
|
736
|
-
verticalAnchor='start'
|
|
737
|
-
transform={`translate(${-1 * runtime.yAxis.size + yLabelOffset}, ${axisCenter}) rotate(-90)`}
|
|
738
|
-
fontWeight='bold'
|
|
739
|
-
fill={config.yAxis.labelColor}
|
|
740
|
-
fontSize={axisLabelFontSize}
|
|
741
|
-
>
|
|
742
|
-
{!config.hideYAxisLabel ? props.label : null}
|
|
743
|
-
</Text>
|
|
744
|
-
</Group>
|
|
767
|
+
<Line
|
|
768
|
+
key={`horizontal-gridline-${tickValue}-${i}`}
|
|
769
|
+
from={{ x: tickPosition, y: 0 }}
|
|
770
|
+
to={{ x: tickPosition, y: yMax }}
|
|
771
|
+
stroke='#d6d6d6'
|
|
772
|
+
/>
|
|
745
773
|
)
|
|
746
|
-
}}
|
|
747
|
-
</
|
|
774
|
+
})}
|
|
775
|
+
</Group>
|
|
748
776
|
)}
|
|
749
777
|
{visualizationType === 'Paired Bar' && generatePairedBarAxis()}
|
|
750
778
|
{visualizationType === 'Deviation Bar' && config.runtime.series?.length === 1 && (
|
|
751
779
|
<DeviationBar animatedChart={animatedChart} xScale={xScale} yScale={yScale} width={xMax} height={yMax} />
|
|
752
780
|
)}
|
|
753
|
-
{visualizationType === 'Paired Bar' &&
|
|
781
|
+
{visualizationType === 'Paired Bar' && (
|
|
782
|
+
<PairedBarChart originalWidth={parentWidth} width={xMax} height={yMax} />
|
|
783
|
+
)}
|
|
754
784
|
{visualizationType === 'Scatter Plot' && (
|
|
755
785
|
<ScatterPlot
|
|
756
786
|
xScale={xScale}
|
|
@@ -766,6 +796,9 @@ const LinearChart = forwardRef<SVGAElement, LinearChartProps>(({ parentHeight, p
|
|
|
766
796
|
showTooltip={showTooltip}
|
|
767
797
|
/>
|
|
768
798
|
)}
|
|
799
|
+
{visualizationType === 'Warming Stripes' && (
|
|
800
|
+
<WarmingStripes xScale={xScale} yScale={yScale} xMax={xMax} yMax={yMax} />
|
|
801
|
+
)}
|
|
769
802
|
{visualizationType === 'Box Plot' && config.orientation === 'vertical' && (
|
|
770
803
|
<BoxPlotVertical
|
|
771
804
|
seriesScale={seriesScale}
|
|
@@ -841,9 +874,16 @@ const LinearChart = forwardRef<SVGAElement, LinearChartProps>(({ parentHeight, p
|
|
|
841
874
|
)}
|
|
842
875
|
{/* Line chart */}
|
|
843
876
|
{/* TODO: Make this just line or combo? */}
|
|
844
|
-
{![
|
|
845
|
-
|
|
846
|
-
|
|
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) &&
|
|
847
887
|
!convertLineToBarGraph && (
|
|
848
888
|
<>
|
|
849
889
|
<LineChart
|
|
@@ -879,7 +919,7 @@ const LinearChart = forwardRef<SVGAElement, LinearChartProps>(({ parentHeight, p
|
|
|
879
919
|
xScale={xScale}
|
|
880
920
|
yScale={yScale}
|
|
881
921
|
seriesScale={seriesScale}
|
|
882
|
-
width={
|
|
922
|
+
width={parentWidth}
|
|
883
923
|
height={forestHeight}
|
|
884
924
|
getXAxisData={getXAxisData}
|
|
885
925
|
getYAxisData={getYAxisData}
|
|
@@ -895,29 +935,7 @@ const LinearChart = forwardRef<SVGAElement, LinearChartProps>(({ parentHeight, p
|
|
|
895
935
|
forestPlotRightLabelRef={forestPlotRightLabelRef}
|
|
896
936
|
/>
|
|
897
937
|
)}
|
|
898
|
-
{/*Brush
|
|
899
|
-
{config.xAxis.brushActive && config.xAxis.type !== 'categorical' && <BrushChart xMax={xMax} yMax={yMax} />}
|
|
900
|
-
{/* Line chart */}
|
|
901
|
-
{/* TODO: Make this just line or combo? */}
|
|
902
|
-
{!['Paired Bar', 'Box Plot', 'Area Chart', 'Scatter Plot', 'Deviation Bar', 'Forecasting', 'Bar'].includes(
|
|
903
|
-
visualizationType
|
|
904
|
-
) &&
|
|
905
|
-
!convertLineToBarGraph && (
|
|
906
|
-
<>
|
|
907
|
-
<LineChart
|
|
908
|
-
xScale={xScale}
|
|
909
|
-
yScale={yScale}
|
|
910
|
-
getXAxisData={getXAxisData}
|
|
911
|
-
getYAxisData={getYAxisData}
|
|
912
|
-
xMax={xMax}
|
|
913
|
-
yMax={yMax}
|
|
914
|
-
seriesStyle={config.runtime.series}
|
|
915
|
-
tooltipData={tooltipData}
|
|
916
|
-
handleTooltipMouseOver={handleTooltipMouseOver}
|
|
917
|
-
handleTooltipMouseOff={handleTooltipMouseOff}
|
|
918
|
-
/>
|
|
919
|
-
</>
|
|
920
|
-
)}
|
|
938
|
+
{/* Brush moved to separate overlay - no longer in main SVG */}
|
|
921
939
|
{/* y anchors */}
|
|
922
940
|
{config.yAxis.anchors &&
|
|
923
941
|
config.yAxis.anchors.map((anchor, index) => {
|
|
@@ -938,13 +956,13 @@ const LinearChart = forwardRef<SVGAElement, LinearChartProps>(({ parentHeight, p
|
|
|
938
956
|
return (
|
|
939
957
|
// prettier-ignore
|
|
940
958
|
<Line
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
959
|
+
key={`yAxis-${anchor.value}--${index}`}
|
|
960
|
+
strokeDasharray={handleLineType(anchor.lineStyle)}
|
|
961
|
+
stroke={anchor.color ? anchor.color : 'rgba(0,0,0,1)'}
|
|
962
|
+
className='anchor-y'
|
|
963
|
+
from={{ x: runtime.yAxis.size, y: position - middleOffset }}
|
|
964
|
+
to={{ x: runtime.yAxis.size + xMax, y: position - middleOffset }}
|
|
965
|
+
/>
|
|
948
966
|
)
|
|
949
967
|
})}
|
|
950
968
|
{/* x anchors */}
|
|
@@ -974,14 +992,14 @@ const LinearChart = forwardRef<SVGAElement, LinearChartProps>(({ parentHeight, p
|
|
|
974
992
|
return (
|
|
975
993
|
// prettier-ignore
|
|
976
994
|
<Line
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
995
|
+
key={`xAxis-${anchor.value}--${index}`}
|
|
996
|
+
strokeDasharray={handleLineType(anchor.lineStyle)}
|
|
997
|
+
stroke={anchor.color ? anchor.color : 'rgba(0,0,0,1)'}
|
|
998
|
+
fill={anchor.color ? anchor.color : 'rgba(0,0,0,1)'}
|
|
999
|
+
className='anchor-x'
|
|
1000
|
+
from={{ x: Number(anchorPosition) + Number(padding), y: 0 }}
|
|
1001
|
+
to={{ x: Number(anchorPosition) + Number(padding), y: yMax }}
|
|
1002
|
+
/>
|
|
985
1003
|
)
|
|
986
1004
|
})}
|
|
987
1005
|
{/* we are handling regions in bar charts differently, so that we can calculate the bar group into the region space. */}
|
|
@@ -996,12 +1014,12 @@ const LinearChart = forwardRef<SVGAElement, LinearChartProps>(({ parentHeight, p
|
|
|
996
1014
|
hideTooltip={hideTooltip}
|
|
997
1015
|
tooltipData={tooltipData}
|
|
998
1016
|
yMax={yMax}
|
|
999
|
-
|
|
1017
|
+
xMax={xMax}
|
|
1000
1018
|
/>
|
|
1001
1019
|
)}
|
|
1002
1020
|
{isNoDataAvailable && (
|
|
1003
1021
|
<Text
|
|
1004
|
-
x={Number(
|
|
1022
|
+
x={Number(runtime.yAxis.size) + Number(xMax / 2)}
|
|
1005
1023
|
y={initialHeight / 2 - (config.xAxis.padding || 0) / 2}
|
|
1006
1024
|
textAnchor='middle'
|
|
1007
1025
|
>
|
|
@@ -1026,293 +1044,311 @@ const LinearChart = forwardRef<SVGAElement, LinearChartProps>(({ parentHeight, p
|
|
|
1026
1044
|
</Group>
|
|
1027
1045
|
{/* Highlighted regions */}
|
|
1028
1046
|
{/* Y axis */}
|
|
1029
|
-
{!['Spark Line', 'Forest Plot'].includes(visualizationType) &&
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
{orientation === 'horizontal' &&
|
|
1123
|
-
visualizationType === 'Box Plot' &&
|
|
1124
|
-
config.yAxis.labelPlacement === 'On Date/Category Axis' &&
|
|
1125
|
-
!config.yAxis.hideLabel && (
|
|
1126
|
-
<Text
|
|
1127
|
-
x={tick.to.x}
|
|
1128
|
-
y={yScale(tick.value) + yScale.bandwidth() / 2}
|
|
1129
|
-
transform={`rotate(${
|
|
1130
|
-
config.orientation === 'horizontal' ? config.runtime.yAxis.tickRotation || 0 : 0
|
|
1131
|
-
}, ${tick.to.x}, ${tick.to.y})`}
|
|
1132
|
-
verticalAnchor={'middle'}
|
|
1133
|
-
textAnchor={'end'}
|
|
1134
|
-
fontSize={tickLabelFontSize}
|
|
1135
|
-
>
|
|
1136
|
-
{tick.formattedValue}
|
|
1137
|
-
</Text>
|
|
1138
|
-
)}
|
|
1139
|
-
|
|
1140
|
-
{orientation === 'horizontal' &&
|
|
1141
|
-
visualizationType !== 'Box Plot' &&
|
|
1142
|
-
visualizationSubType !== 'stacked' &&
|
|
1143
|
-
config.yAxis.labelPlacement === 'On Date/Category Axis' &&
|
|
1144
|
-
!config.yAxis.hideLabel && (
|
|
1145
|
-
<Text
|
|
1146
|
-
transform={`translate(${tick.to.x - 5}, ${
|
|
1147
|
-
config.isLollipopChart
|
|
1148
|
-
? tick.to.y - minY
|
|
1149
|
-
: tick.to.y -
|
|
1150
|
-
minY +
|
|
1151
|
-
(Number(config.barHeight * config.runtime.series.length) - barMinHeight) / 2
|
|
1152
|
-
}) rotate(-${config.runtime.horizontal ? config.runtime.yAxis.tickRotation || 0 : 0})`}
|
|
1153
|
-
verticalAnchor={'start'}
|
|
1154
|
-
textAnchor={'end'}
|
|
1155
|
-
fontSize={tickLabelFontSize}
|
|
1156
|
-
>
|
|
1157
|
-
{tick.formattedValue}
|
|
1158
|
-
</Text>
|
|
1159
|
-
)}
|
|
1160
|
-
|
|
1161
|
-
{orientation === 'horizontal' &&
|
|
1162
|
-
visualizationSubType === 'stacked' &&
|
|
1163
|
-
config.yAxis.labelPlacement === 'On Date/Category Axis' &&
|
|
1164
|
-
!config.yAxis.hideLabel && (
|
|
1165
|
-
<Text
|
|
1166
|
-
transform={`translate(${tick.to.x - 5}, ${
|
|
1167
|
-
tick.to.y - minY + (Number(config.barHeight) - barMinHeight) / 2
|
|
1168
|
-
}) rotate(-${runtime.horizontal ? runtime.yAxis.tickRotation : 0})`}
|
|
1169
|
-
verticalAnchor={'start'}
|
|
1170
|
-
textAnchor={'end'}
|
|
1171
|
-
fontSize={tickLabelFontSize}
|
|
1172
|
-
>
|
|
1173
|
-
{tick.formattedValue}
|
|
1174
|
-
</Text>
|
|
1175
|
-
)}
|
|
1176
|
-
|
|
1177
|
-
{orientation === 'horizontal' &&
|
|
1178
|
-
visualizationType === 'Paired Bar' &&
|
|
1179
|
-
!config.yAxis.hideLabel && (
|
|
1180
|
-
<Text
|
|
1181
|
-
transform={`translate(${tick.to.x - 5}, ${
|
|
1182
|
-
tick.to.y - minY + Number(config.barHeight) / 2
|
|
1183
|
-
}) rotate(-${runtime.horizontal ? runtime.yAxis.tickRotation : 0})`}
|
|
1184
|
-
textAnchor={'end'}
|
|
1185
|
-
verticalAnchor='middle'
|
|
1186
|
-
fontSize={tickLabelFontSize}
|
|
1187
|
-
>
|
|
1188
|
-
{tick.formattedValue}
|
|
1189
|
-
</Text>
|
|
1190
|
-
)}
|
|
1191
|
-
{orientation === 'horizontal' &&
|
|
1192
|
-
visualizationType === 'Deviation Bar' &&
|
|
1193
|
-
!config.yAxis.hideLabel && (
|
|
1194
|
-
<Text
|
|
1195
|
-
transform={`translate(${tick.to.x - 5}, ${
|
|
1196
|
-
config.isLollipopChart
|
|
1197
|
-
? tick.to.y - minY + 2
|
|
1198
|
-
: tick.to.y - minY + Number(config.barHeight) / 2
|
|
1199
|
-
}) rotate(-${runtime.horizontal ? runtime.yAxis.tickRotation : 0})`}
|
|
1200
|
-
textAnchor={'end'}
|
|
1201
|
-
verticalAnchor='middle'
|
|
1047
|
+
{!['Spark Line', 'Forest Plot', 'Warming Stripes'].includes(visualizationType) &&
|
|
1048
|
+
config.yAxis.type !== 'categorical' && (
|
|
1049
|
+
<AxisLeft
|
|
1050
|
+
scale={yScale}
|
|
1051
|
+
tickLength={isLogarithmicAxis ? 6 : 8}
|
|
1052
|
+
left={Number(runtime.yAxis.size) - config.yAxis.axisPadding}
|
|
1053
|
+
label={runtime.yAxis.label || runtime.yAxis.label}
|
|
1054
|
+
stroke='#333'
|
|
1055
|
+
tickFormat={handleLeftTickFormatting}
|
|
1056
|
+
numTicks={handleNumTicks}
|
|
1057
|
+
>
|
|
1058
|
+
{props => {
|
|
1059
|
+
const axisCenter =
|
|
1060
|
+
config.orientation === 'horizontal'
|
|
1061
|
+
? Math.abs(props.axisToPoint.y - props.axisFromPoint.y) / 2
|
|
1062
|
+
: (props.axisFromPoint.y - props.axisToPoint.y) / 2
|
|
1063
|
+
const horizontalTickOffset =
|
|
1064
|
+
yMax / props.ticks.length / 2 - (yMax / props.ticks.length) * (1 - config.barThickness) + 5
|
|
1065
|
+
return (
|
|
1066
|
+
<Group className='left-axis'>
|
|
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'}
|
|
1202
1139
|
fontSize={tickLabelFontSize}
|
|
1203
|
-
|
|
1204
|
-
{tick.formattedValue}
|
|
1205
|
-
</Text>
|
|
1140
|
+
/>
|
|
1206
1141
|
)}
|
|
1207
1142
|
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
|
|
1211
|
-
|
|
1143
|
+
{orientation === 'horizontal' &&
|
|
1144
|
+
visualizationType === 'Box Plot' &&
|
|
1145
|
+
config.yAxis.labelPlacement === 'On Date/Category Axis' &&
|
|
1146
|
+
!config.yAxis.hideLabel && (
|
|
1212
1147
|
<Text
|
|
1213
|
-
|
|
1214
|
-
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
|
|
1218
|
-
verticalAnchor={
|
|
1219
|
-
textAnchor={
|
|
1220
|
-
fill={config.yAxis.tickLabelColor}
|
|
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'}
|
|
1221
1155
|
fontSize={tickLabelFontSize}
|
|
1222
1156
|
>
|
|
1223
|
-
{
|
|
1157
|
+
{tick.formattedValue}
|
|
1224
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
|
+
)}
|
|
1225
1214
|
|
|
1226
|
-
|
|
1227
|
-
|
|
1228
|
-
|
|
1229
|
-
|
|
1230
|
-
|
|
1231
|
-
|
|
1232
|
-
|
|
1233
|
-
|
|
1234
|
-
|
|
1235
|
-
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
|
|
1241
|
-
|
|
1242
|
-
|
|
1243
|
-
|
|
1244
|
-
|
|
1245
|
-
|
|
1246
|
-
|
|
1247
|
-
|
|
1248
|
-
|
|
1249
|
-
|
|
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 */}
|
|
1250
1309
|
<BlurStrokeText
|
|
1251
|
-
innerRef={
|
|
1310
|
+
innerRef={el => lastTick && (topYLabelRef.current = el)}
|
|
1252
1311
|
display={isLogarithmicAxis ? showTicks : 'block'}
|
|
1253
1312
|
dx={isLogarithmicAxis ? -6 : 0}
|
|
1254
|
-
x={labelX}
|
|
1255
|
-
y={labelY}
|
|
1313
|
+
x={inlineLabelHasNoSpace ? labelX - suffixWidth : labelX}
|
|
1314
|
+
y={labelY + (config.runtime.horizontal ? horizontalTickOffset : 0)}
|
|
1256
1315
|
angle={-Number(config.yAxis.tickRotation) || 0}
|
|
1257
|
-
verticalAnchor={labelVerticalAnchor}
|
|
1258
|
-
textAnchor={
|
|
1316
|
+
verticalAnchor={config.runtime.horizontal ? 'start' : labelVerticalAnchor}
|
|
1317
|
+
textAnchor={config.runtime.horizontal || labelsAboveGridlines ? 'start' : 'end'}
|
|
1259
1318
|
fill={config.yAxis.tickLabelColor}
|
|
1260
1319
|
stroke={'#fff'}
|
|
1261
|
-
|
|
1320
|
+
disableStroke={!labelsAboveGridlines}
|
|
1262
1321
|
strokeLinejoin='round'
|
|
1322
|
+
paintOrder={'stroke'} // keeps stroke under fill
|
|
1263
1323
|
style={{ whiteSpace: 'pre-wrap' }} // prevents leading spaces from being trimmed
|
|
1264
1324
|
fontSize={tickLabelFontSize}
|
|
1265
1325
|
>
|
|
1266
|
-
{inlineLabel}
|
|
1326
|
+
{`${formattedValue}${combineDomInlineLabelWithValue ? inlineLabel : ''}`}
|
|
1267
1327
|
</BlurStrokeText>
|
|
1268
|
-
|
|
1269
|
-
|
|
1270
|
-
|
|
1271
|
-
|
|
1272
|
-
|
|
1273
|
-
|
|
1274
|
-
|
|
1275
|
-
|
|
1276
|
-
|
|
1277
|
-
|
|
1278
|
-
|
|
1279
|
-
|
|
1280
|
-
|
|
1281
|
-
|
|
1282
|
-
|
|
1283
|
-
|
|
1284
|
-
|
|
1285
|
-
|
|
1286
|
-
|
|
1287
|
-
|
|
1288
|
-
|
|
1289
|
-
</BlurStrokeText>
|
|
1290
|
-
</>
|
|
1291
|
-
)}
|
|
1292
|
-
</Group>
|
|
1293
|
-
)
|
|
1294
|
-
})}
|
|
1295
|
-
<Text
|
|
1296
|
-
className='y-label'
|
|
1297
|
-
textAnchor='middle'
|
|
1298
|
-
verticalAnchor='start'
|
|
1299
|
-
transform={`translate(${-1 * runtime.yAxis.size + yLabelOffset}, ${axisCenter}) rotate(-90)`}
|
|
1300
|
-
fontWeight='bold'
|
|
1301
|
-
fill={config.yAxis.labelColor}
|
|
1302
|
-
fontSize={axisLabelFontSize}
|
|
1303
|
-
>
|
|
1304
|
-
{!config.hideYAxisLabel ? props.label : null}
|
|
1305
|
-
</Text>
|
|
1306
|
-
</Group>
|
|
1307
|
-
)
|
|
1308
|
-
}}
|
|
1309
|
-
</AxisLeft>
|
|
1310
|
-
)}
|
|
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>
|
|
1348
|
+
)}
|
|
1311
1349
|
{config.yAxis.type === 'categorical' && config.orientation === 'vertical' && (
|
|
1312
1350
|
<CategoricalYAxis
|
|
1313
|
-
|
|
1314
|
-
maxValue={maxValue}
|
|
1315
|
-
height={initialHeight}
|
|
1351
|
+
yScale={yScale}
|
|
1316
1352
|
xMax={xMax}
|
|
1317
1353
|
yMax={yMax}
|
|
1318
1354
|
leftSize={Number(runtime.yAxis.size) - config.yAxis.axisPadding}
|
|
@@ -1322,7 +1358,7 @@ const LinearChart = forwardRef<SVGAElement, LinearChartProps>(({ parentHeight, p
|
|
|
1322
1358
|
{hasRightAxis && (
|
|
1323
1359
|
<AxisRight
|
|
1324
1360
|
scale={yScaleRight}
|
|
1325
|
-
left={Number(
|
|
1361
|
+
left={Number(runtime.yAxis.size + xMax)}
|
|
1326
1362
|
label={config.yAxis.rightLabel}
|
|
1327
1363
|
tickFormat={tick => formatNumber(tick, 'right')}
|
|
1328
1364
|
numTicks={runtime.yAxis.rightNumTicks || undefined}
|
|
@@ -1426,15 +1462,22 @@ const LinearChart = forwardRef<SVGAElement, LinearChartProps>(({ parentHeight, p
|
|
|
1426
1462
|
? getTickValues(xAxisDataMapped, xScale, isDateTime ? xTickCount : getManualStep(), config)
|
|
1427
1463
|
: config.runtime.xAxis.type === 'date'
|
|
1428
1464
|
? xAxisDataMapped
|
|
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
|
|
1429
1470
|
: undefined
|
|
1430
1471
|
}
|
|
1431
1472
|
>
|
|
1432
1473
|
{props => {
|
|
1433
1474
|
const hasDynamicCategory = config.series.some(s => s.dynamicCategory)
|
|
1475
|
+
|
|
1434
1476
|
// For these charts, we generated all ticks in tickValues above, and now need to filter/shift them
|
|
1435
1477
|
// so the last tick is always labeled
|
|
1478
|
+
// Use uniqueXAxisDataMapped for date filtering to match the tickValues we set
|
|
1436
1479
|
if (config.runtime.xAxis.type === 'date' && !config.runtime.xAxis.manual && !hasDynamicCategory) {
|
|
1437
|
-
props.ticks = filterAndShiftLinearDateTicks(config, props,
|
|
1480
|
+
props.ticks = filterAndShiftLinearDateTicks(config, props, uniqueXAxisDataMapped, formatDate)
|
|
1438
1481
|
}
|
|
1439
1482
|
|
|
1440
1483
|
const distanceBetweenTicks =
|
|
@@ -1478,7 +1521,8 @@ const LinearChart = forwardRef<SVGAElement, LinearChartProps>(({ parentHeight, p
|
|
|
1478
1521
|
const sumOfTickWidth = textWidths.reduce((a, b) => a + b, accumulator)
|
|
1479
1522
|
const spaceBetweenEachTick = (xMax - sumOfTickWidth) / (filteredTicks.length - 1)
|
|
1480
1523
|
const bufferBetweenTicks = 40
|
|
1481
|
-
const maxLengthOfTick =
|
|
1524
|
+
const maxLengthOfTick =
|
|
1525
|
+
parentWidth / filteredTicks.length - X_TICK_LABEL_PADDING * 2 - bufferBetweenTicks
|
|
1482
1526
|
|
|
1483
1527
|
// Determine the position of each tick
|
|
1484
1528
|
let positions = [0] // The first tick is at position 0
|
|
@@ -1509,7 +1553,7 @@ const LinearChart = forwardRef<SVGAElement, LinearChartProps>(({ parentHeight, p
|
|
|
1509
1553
|
config.xAxis.tickWidthMax = longestTickLength
|
|
1510
1554
|
|
|
1511
1555
|
return (
|
|
1512
|
-
<Group className='bottom-axis' width={
|
|
1556
|
+
<Group className='bottom-axis' width={parentWidth}>
|
|
1513
1557
|
{filteredTicks.map((tick, i, propsTicks) => {
|
|
1514
1558
|
// when using LogScale show major ticks values only
|
|
1515
1559
|
const showTick = String(tick.value).startsWith('1') || tick.value === 0.1 ? 'block' : 'none'
|
|
@@ -1631,6 +1675,27 @@ const LinearChart = forwardRef<SVGAElement, LinearChartProps>(({ parentHeight, p
|
|
|
1631
1675
|
/>
|
|
1632
1676
|
)}
|
|
1633
1677
|
<div className='animation-trigger' ref={triggerRef} />
|
|
1678
|
+
{/* SEPARATED BRUSH - Independent SVG overlay */}
|
|
1679
|
+
{config.xAxis.brushActive && config.xAxis.type !== 'categorical' && xMax > 0 && (
|
|
1680
|
+
<div
|
|
1681
|
+
style={{
|
|
1682
|
+
position: 'relative',
|
|
1683
|
+
marginTop: '10px',
|
|
1684
|
+
left: `${runtime.yAxis.size || 0}px`,
|
|
1685
|
+
width: `${Math.max(xMax, 100)}px`,
|
|
1686
|
+
height: '70px',
|
|
1687
|
+
pointerEvents: 'auto',
|
|
1688
|
+
zIndex: 15,
|
|
1689
|
+
touchAction: 'none', // Enable touch interactions for brush
|
|
1690
|
+
WebkitTouchCallout: 'none',
|
|
1691
|
+
WebkitUserSelect: 'none',
|
|
1692
|
+
userSelect: 'none'
|
|
1693
|
+
}}
|
|
1694
|
+
className='brush-overlay'
|
|
1695
|
+
>
|
|
1696
|
+
<BrushSelector xMax={Math.max(xMax, 100)} yMax={70} />
|
|
1697
|
+
</div>
|
|
1698
|
+
)}
|
|
1634
1699
|
</div>
|
|
1635
1700
|
</ErrorBoundary>
|
|
1636
1701
|
)
|