-
Notifications
You must be signed in to change notification settings - Fork 727
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
new(xychart): add (EventEmitter, Tooltip)Context + basic Tooltips #825
Merged
Merged
Changes from all commits
Commits
Show all changes
10 commits
Select commit
Hold shift + click to select a range
e8da33d
deps(xychart): add mitt
williaster 42ec7a9
new(xychart): add EventEmitter context, provider, types, and hook
williaster 393de2f
new(xychart): add findNearestDatum utils
williaster b6823a8
new(xychart): add TooltipContext type, context, and provider
williaster 60bf423
new(xychart): add Tooltip, update Example
williaster 044c712
new(xychart): add tooltip functionality to BarSeries
williaster 9cd4d78
fix(xychart): clean up
williaster 3a6b82f
fix(xychart): fix test, improve tooltip prop annotations and containe…
williaster 2b22494
internal(xychart): svgCoords => svgPoint + point, svgCoord => scaledV…
williaster 1dafd66
test(xychart): add Event + Tooltip tests (#835)
williaster File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,95 @@ | ||
import React, { useCallback, useContext } from 'react'; | ||
import { useTooltipInPortal, defaultStyles } from '@visx/tooltip'; | ||
import { TooltipProps as BaseTooltipProps } from '@visx/tooltip/lib/tooltips/Tooltip'; | ||
import { PickD3Scale } from '@visx/scale'; | ||
import { UseTooltipPortalOptions } from '@visx/tooltip/lib/hooks/useTooltipInPortal'; | ||
|
||
import TooltipContext from '../context/TooltipContext'; | ||
import DataContext from '../context/DataContext'; | ||
import { TooltipContextType } from '../types'; | ||
|
||
export type RenderTooltipParams = TooltipContextType & { | ||
colorScale?: PickD3Scale<'ordinal', string, string>; | ||
}; | ||
|
||
export type TooltipProps = { | ||
/** | ||
* When TooltipContext.tooltipOpen=true, this function is invoked and if the | ||
* return value is non-null, its content is rendered inside the tooltip container. | ||
* Content will be rendered in an HTML parent. | ||
*/ | ||
renderTooltip: (params: RenderTooltipParams) => React.ReactNode; | ||
/** | ||
* Tooltip depends on ResizeObserver, which may be pollyfilled globally | ||
* or injected into this component. | ||
*/ | ||
resizeObserverPolyfill?: UseTooltipPortalOptions['polyfill']; | ||
} & Omit<BaseTooltipProps, 'left' | 'top' | 'children'> & | ||
Pick<UseTooltipPortalOptions, 'debounce' | 'detectBounds' | 'scroll'>; | ||
|
||
const INVISIBLE_STYLES: React.CSSProperties = { | ||
position: 'absolute', | ||
left: 0, | ||
top: 0, | ||
opacity: 0, | ||
width: 0, | ||
height: 0, | ||
pointerEvents: 'none', | ||
}; | ||
|
||
export default function Tooltip({ | ||
renderTooltip, | ||
debounce, | ||
detectBounds, | ||
resizeObserverPolyfill, | ||
scroll, | ||
...tooltipProps | ||
}: TooltipProps) { | ||
const { colorScale, theme } = useContext(DataContext) || {}; | ||
const tooltipContext = useContext(TooltipContext); | ||
const { containerRef, TooltipInPortal } = useTooltipInPortal({ | ||
debounce, | ||
detectBounds, | ||
polyfill: resizeObserverPolyfill, | ||
scroll, | ||
}); | ||
|
||
// To correctly position itself in a Portal, the tooltip must know its container bounds | ||
// this is done by rendering an invisible node which can be used to find its parents element | ||
const setContainerRef = useCallback( | ||
(ownRef: HTMLElement | SVGElement | null) => { | ||
containerRef(ownRef?.parentElement ?? null); | ||
}, | ||
[containerRef], | ||
); | ||
|
||
const tooltipContent = tooltipContext?.tooltipOpen | ||
? renderTooltip({ ...tooltipContext, colorScale }) | ||
: null; | ||
|
||
return ( | ||
// Tooltip can be rendered as a child of SVG or HTML since its output is rendered in a Portal. | ||
// So use svg element to find container ref because it's a valid child of SVG and HTML parents. | ||
<> | ||
<svg ref={setContainerRef} style={INVISIBLE_STYLES} /> | ||
{tooltipContext?.tooltipOpen && tooltipContent != null && ( | ||
<TooltipInPortal | ||
left={tooltipContext?.tooltipLeft} | ||
top={tooltipContext?.tooltipTop} | ||
style={{ | ||
...defaultStyles, | ||
background: theme?.backgroundColor ?? 'white', | ||
boxShadow: `0 1px 2px ${ | ||
theme?.htmlLabelStyles?.color ? `${theme?.htmlLabelStyles?.color}55` : '#22222255' | ||
}`, | ||
|
||
...theme?.htmlLabelStyles, | ||
}} | ||
{...tooltipProps} | ||
> | ||
{tooltipContent} | ||
</TooltipInPortal> | ||
)} | ||
</> | ||
); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
import { createContext } from 'react'; | ||
import { EventEmitterContextType } from '../types'; | ||
|
||
const EventEmitterContext = createContext<EventEmitterContextType | null>(null); | ||
|
||
export default EventEmitterContext; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
import { createContext } from 'react'; | ||
import { TooltipContextType } from '../types'; | ||
|
||
const TooltipContext = createContext<TooltipContextType | null>(null); | ||
|
||
export default TooltipContext; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,35 @@ | ||
import { useCallback, useContext, useEffect } from 'react'; | ||
import { localPoint } from '@visx/event'; | ||
import EventEmitterContext from '../context/EventEmitterContext'; | ||
|
||
export type EventType = 'mousemove' | 'mouseout' | 'touchmove' | 'touchend' | 'click'; | ||
export type HandlerParams = { | ||
event: React.MouseEvent | React.TouchEvent; | ||
svgPoint: ReturnType<typeof localPoint>; | ||
}; | ||
export type Handler = (params?: HandlerParams) => void; | ||
|
||
/** | ||
* Hook for optionally subscribing to a specified EventType, | ||
* and returns emitter for emitting events. | ||
*/ | ||
export default function useEventEmitter(eventType?: EventType, handler?: Handler) { | ||
const emitter = useContext(EventEmitterContext); | ||
|
||
/** wrap emitter.emit so we can enforce stricter type signature */ | ||
const emit = useCallback( | ||
(type: EventType, event: HandlerParams['event']) => | ||
emitter?.emit<HandlerParams>(type, { event, svgPoint: localPoint(event) }), | ||
[emitter], | ||
); | ||
|
||
useEffect(() => { | ||
if (emitter && eventType && handler) { | ||
emitter.on<HandlerParams>(eventType, handler); | ||
return () => emitter?.off<HandlerParams>(eventType, handler); | ||
} | ||
return undefined; | ||
}, [emitter, eventType, handler]); | ||
|
||
return emitter ? emit : null; | ||
} |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
not necessary to return
undefined
?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I get lint errors if I return nothing (
not all paths return a value
) andundefined
was the only non-function return value allowed byTS
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
oh right. ignore my comment then