Skip to content

Commit 9cdcf35

Browse files
committed
feat: axis formatters
1 parent 3749f32 commit 9cdcf35

11 files changed

+336
-173
lines changed

src/components/AxisLinear.tsx

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { ScaleLinear, ScaleTime } from 'd3-scale'
22
import React from 'react'
33

4-
import { Axis } from '../types'
4+
import { Axis, AxisTime } from '../types'
55
import { getTickPx, translate } from '../utils/Utils'
66
import useChartContext from '../utils/chartContext'
77
//
@@ -187,7 +187,7 @@ export default function AxisLinearComp<TDatum>(axis: Axis<TDatum>) {
187187
isRotated ? (axis.position === 'top' ? 60 : -60) : 0
188188
})`}
189189
>
190-
{axis.format(tick as any)}
190+
{(axis as AxisTime<any>).formatters.scale(tick as Date)}
191191
</text>
192192
</g>
193193
)

src/components/AxisLinear.useMeasure.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -230,9 +230,9 @@ export default function useMeasure<TDatum>({
230230
// Measure after if needed
231231
useIsomorphicLayoutEffect(() => {
232232
measureRotation()
233-
})
233+
}, [measureRotation])
234234

235235
useIsomorphicLayoutEffect(() => {
236236
measureDimensions()
237-
})
237+
}, [measureRotation])
238238
}

src/components/Chart.tsx

+13-1
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,19 @@ export function Chart<TDatum>({
8787
containerElement,
8888
setContainerElement,
8989
] = React.useState<HTMLDivElement | null>(null)
90-
const { width, height } = useRect(containerElement?.parentElement, options)
90+
const parentElement = containerElement?.parentElement
91+
92+
const { width, height } = useRect(parentElement, options)
93+
94+
useIsomorphicLayoutEffect(() => {
95+
if (parentElement) {
96+
const computed = window.getComputedStyle(parentElement)
97+
98+
if (!['relative', 'absolute', 'fixed'].includes(computed.display)) {
99+
parentElement.style.position = 'relative'
100+
}
101+
}
102+
}, [parentElement])
91103

92104
return (
93105
<div

src/components/Cursors.tsx

+66-50
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,13 @@
11
import React from 'react'
2+
import ReactDOM from 'react-dom'
23
import * as TSTB from 'ts-toolbelt'
34

45
import { animated, config, useSpring } from '@react-spring/web'
56

7+
// import useIsScrolling from '../hooks/useIsScrolling'
68
import useLatestWhen from '../hooks/useLatestWhen'
7-
import { CursorOptions, Datum } from '../types'
9+
import usePortalElement from '../hooks/usePortalElement'
10+
import { AxisTime, CursorOptions, Datum } from '../types'
811
import { translate } from '../utils/Utils'
912
//
1013
import useChartContext from '../utils/chartContext'
@@ -75,6 +78,7 @@ function Cursor<TDatum>(props: {
7578
}) {
7679
const {
7780
getOptions,
81+
svgRect,
7882
gridDimensions,
7983
useFocusedDatumAtom,
8084
primaryAxis,
@@ -192,72 +196,84 @@ function Cursor<TDatum>(props: {
192196
}
193197
}
194198

195-
const formattedValue = axis.format(latestValue)
199+
const formattedValue = (axis as AxisTime<any>).formatters.cursor(latestValue)
200+
201+
// const isScrolling = useIsScrolling(200)
196202

197203
const lineSpring = useSpring({
198204
transform: translate(lineStartX, lineStartY),
199205
width: `${lineWidth}px`,
200206
height: `${lineHeight}px`,
201207
config: config.stiff,
208+
// immediate: isScrolling,
202209
})
203210

204211
const bubbleSpring = useSpring({
205212
transform: translate(bubbleX, bubbleY),
206213
config: config.stiff,
214+
// immediate: isScrolling,
207215
})
208216

209-
return (
210-
<div
211-
style={{
212-
pointerEvents: 'none',
213-
position: 'absolute',
214-
top: 0,
215-
left: 0,
216-
transform: translate(gridDimensions.gridX, gridDimensions.gridY),
217-
opacity: show ? 1 : 0,
218-
transition: 'all .3s ease',
219-
}}
220-
className="Cursor"
221-
>
222-
{/* Render the cursor line */}
223-
{props.options.showLine ? (
224-
<animated.div
225-
style={{
226-
...lineSpring,
227-
position: 'absolute',
228-
top: 0,
229-
left: 0,
230-
background: getLineBackgroundColor(getOptions().dark),
231-
}}
232-
/>
233-
) : null}
234-
{/* Render the cursor bubble */}
235-
{props.options.showLabel ? (
236-
<animated.div
217+
const portalEl = usePortalElement()
218+
219+
return portalEl
220+
? ReactDOM.createPortal(
221+
<div
237222
style={{
238-
...bubbleSpring,
223+
pointerEvents: 'none',
239224
position: 'absolute',
240225
top: 0,
241226
left: 0,
227+
transform: translate(
228+
svgRect.left + gridDimensions.gridX,
229+
svgRect.top + gridDimensions.gridY
230+
),
231+
opacity: show ? 1 : 0,
232+
transition: 'all .3s ease',
242233
}}
234+
className="Cursor"
243235
>
244-
{/* Render the cursor label */}
245-
<div
246-
style={{
247-
padding: '5px',
248-
fontSize: '10px',
249-
background: getBackgroundColor(getOptions().dark),
250-
color: getBackgroundColor(!getOptions().dark),
251-
borderRadius: '3px',
252-
position: 'relative',
253-
transform: `translate3d(${alignPctX}%, ${alignPctY}%, 0)`,
254-
whiteSpace: 'nowrap',
255-
}}
256-
>
257-
{formattedValue}
258-
</div>
259-
</animated.div>
260-
) : null}
261-
</div>
262-
)
236+
{/* Render the cursor line */}
237+
{props.options.showLine ? (
238+
<animated.div
239+
style={{
240+
...lineSpring,
241+
position: 'absolute',
242+
top: 0,
243+
left: 0,
244+
background: getLineBackgroundColor(getOptions().dark),
245+
}}
246+
/>
247+
) : null}
248+
{/* Render the cursor bubble */}
249+
{props.options.showLabel ? (
250+
<animated.div
251+
style={{
252+
...bubbleSpring,
253+
position: 'absolute',
254+
top: 0,
255+
left: 0,
256+
}}
257+
>
258+
{/* Render the cursor label */}
259+
<div
260+
style={{
261+
padding: '5px',
262+
fontSize: '10px',
263+
background: getBackgroundColor(getOptions().dark),
264+
color: getBackgroundColor(!getOptions().dark),
265+
borderRadius: '3px',
266+
position: 'relative',
267+
transform: `translate3d(${alignPctX}%, ${alignPctY}%, 0)`,
268+
whiteSpace: 'nowrap',
269+
}}
270+
>
271+
{formattedValue}
272+
</div>
273+
</animated.div>
274+
) : null}
275+
</div>,
276+
portalEl
277+
)
278+
: null
263279
}

src/components/Tooltip.tsx

+7-30
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,9 @@ import ReactDOM from 'react-dom'
44
import { useSpring, animated } from '@react-spring/web'
55

66
import { useAnchor } from '../hooks/useAnchor'
7-
import useIsomorphicLayoutEffect from '../hooks/useIsomorphicLayoutEffect'
7+
// import useIsScrolling from '../hooks/useIsScrolling'
88
import useLatestWhen from '../hooks/useLatestWhen'
9+
import usePortalElement from '../hooks/usePortalElement'
910
import usePrevious from '../hooks/usePrevious'
1011
import { Datum, ResolvedTooltipOptions, TooltipOptions } from '../types'
1112
//
@@ -70,36 +71,9 @@ export default function Tooltip<TDatum>(): React.ReactPortal | null {
7071
anchorRect = latestFocusedDatum.element?.getBoundingClientRect() ?? null
7172
}
7273

73-
const [portalEl, setPortalEl] = React.useState<HTMLDivElement | null>()
74-
const [tooltipEl, setTooltipEl] = React.useState<HTMLDivElement | null>()
75-
76-
useIsomorphicLayoutEffect(() => {
77-
if (!portalEl) {
78-
let element = document.getElementById(
79-
'react-charts-portal'
80-
) as HTMLDivElement
81-
82-
if (!element) {
83-
element = document.createElement('div')
84-
85-
element.setAttribute('id', 'react-charts-portal')
86-
87-
Object.assign(element.style, {
88-
pointerEvents: 'none',
89-
position: 'fixed',
90-
left: 0,
91-
right: 0,
92-
top: 0,
93-
bottom: 0,
94-
'z-index': 99999999999,
95-
})
74+
const portalEl = usePortalElement()
9675

97-
document.body.append(element)
98-
}
99-
100-
setPortalEl(element)
101-
}
102-
})
76+
const [tooltipEl, setTooltipEl] = React.useState<HTMLDivElement | null>()
10377

10478
const translateX = anchorRect?.left ?? 0
10579
const translateY = anchorRect?.top ?? 0
@@ -133,6 +107,8 @@ export default function Tooltip<TDatum>(): React.ReactPortal | null {
133107
[boundingBox]
134108
)
135109

110+
// const isScrolling = useIsScrolling(200)
111+
136112
const anchorFit = useAnchor({
137113
show: !!focusedDatum,
138114
portalEl,
@@ -154,6 +130,7 @@ export default function Tooltip<TDatum>(): React.ReactPortal | null {
154130
config: { mass: 1, tension: 210, friction: 30 },
155131
immediate: key => {
156132
return (
133+
// isScrolling ||
157134
wasZero ||
158135
(['left', 'top'].includes(key) &&
159136
!previousFocusedDatum &&

src/components/TooltipRenderer.tsx

+7-7
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import { sum } from 'd3-array'
22
import React, { CSSProperties } from 'react'
33

44
import { useAnchor } from '../hooks/useAnchor'
5-
import { Axis, Datum, RequiredChartOptions } from '../types'
5+
import { Axis, AxisTime, Datum, RequiredChartOptions } from '../types'
66

77
//
88
//
@@ -193,13 +193,13 @@ export default function TooltipRenderer<TDatum>(
193193
<strong>{focusedDatum.seriesLabel}</strong>
194194
) : groupingMode === 'secondary' ? (
195195
<strong>
196-
{secondaryAxis.format(
196+
{(secondaryAxis as AxisTime<any>).formatters.tooltip(
197197
secondaryAxis.getValue(focusedDatum.originalDatum)
198198
)}
199199
</strong>
200200
) : (
201201
<strong>
202-
{primaryAxis.format(
202+
{(primaryAxis as AxisTime<any>).formatters.tooltip(
203203
primaryAxis.getValue(focusedDatum.originalDatum)
204204
)}
205205
</strong>
@@ -256,7 +256,7 @@ export default function TooltipRenderer<TDatum>(
256256
{groupingMode === 'series' ? (
257257
<React.Fragment>
258258
<td>
259-
{primaryAxis.format(
259+
{(primaryAxis as AxisTime<any>).formatters.tooltip(
260260
primaryAxis.getValue(sortedDatum.originalDatum)
261261
)}
262262
: &nbsp;
@@ -266,7 +266,7 @@ export default function TooltipRenderer<TDatum>(
266266
textAlign: 'right',
267267
}}
268268
>
269-
{secondaryAxis.format(
269+
{(secondaryAxis as AxisTime<any>).formatters.tooltip(
270270
secondaryAxis.getValue(sortedDatum.originalDatum)
271271
)}
272272
</td>
@@ -279,7 +279,7 @@ export default function TooltipRenderer<TDatum>(
279279
textAlign: 'right',
280280
}}
281281
>
282-
{primaryAxis.format(
282+
{(primaryAxis as AxisTime<any>).formatters.tooltip(
283283
primaryAxis.getValue(sortedDatum.originalDatum)
284284
)}
285285
</td>
@@ -292,7 +292,7 @@ export default function TooltipRenderer<TDatum>(
292292
textAlign: 'right',
293293
}}
294294
>
295-
{secondaryAxis.format(
295+
{(secondaryAxis as AxisTime<any>).formatters.tooltip(
296296
secondaryAxis.getValue(sortedDatum.originalDatum)
297297
)}
298298
</td>

src/hooks/useIsScrolling.ts

+32
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
import * as React from 'react'
2+
3+
export default function useIsScrolling(debounce: number) {
4+
const [scrolling, setScrolling] = React.useState(false)
5+
6+
const ref = React.useRef(scrolling)
7+
ref.current = scrolling
8+
9+
React.useEffect(() => {
10+
let timeout: ReturnType<typeof setTimeout>
11+
12+
const cb = () => {
13+
clearTimeout(timeout)
14+
15+
if (!ref.current) {
16+
setScrolling(true)
17+
timeout = setTimeout(() => {
18+
setScrolling(false)
19+
}, debounce)
20+
}
21+
}
22+
23+
document.addEventListener('scroll', cb, true)
24+
25+
return () => {
26+
clearTimeout(timeout)
27+
document.removeEventListener('scroll', cb)
28+
}
29+
}, [debounce])
30+
31+
return scrolling
32+
}

0 commit comments

Comments
 (0)