Skip to content

Commit

Permalink
new(xychart): refine TooltipContext + Tooltip functionality (#846)
Browse files Browse the repository at this point in the history
* new(tooltip/useTooltipInPortal): allow detectBounds overrid in component

* new(xychart/Tooltip): add working crosshairs + point

* internal(demo/xychart/ChartBackground): use innerWidth/Height

* new(xychart): working multi-datum toolips

* new(xychart/Tooltip): add snapTooltipToDatumX/Y

* new(xychart): add showMultipleCircles support, debounce hideTooltip

* new(demo/xychart): add tooltip controls

* internal(xychart): cleanup

* internal(xychart): cleanup

* internal(xychart): memoize coordinate getter

* type(xychart/TooltipContext): remove svgPoint

* fix(xychart/TooltipContext): infer Datum type

* new(xychart): circle => glyph

* test(xychart): add refined Tooltip, TooltipProvider tests (#852)

* test(xychart): add more Tooltip tests

* test(xychart): add more TooltipProvider tests
  • Loading branch information
williaster authored Oct 8, 2020
1 parent c0eb0b2 commit 5cd287f
Show file tree
Hide file tree
Showing 16 changed files with 677 additions and 107 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { DataContext } from '@visx/xychart';
const patternId = 'xy-chart-pattern';

export default function CustomChartBackground() {
const { theme, margin, width, height } = useContext(DataContext);
const { theme, margin, width, height, innerWidth, innerHeight } = useContext(DataContext);

// early return values not available in context
if (width == null || height == null || margin == null || theme == null) return null;
Expand All @@ -24,8 +24,8 @@ export default function CustomChartBackground() {
<rect
x={margin.left}
y={margin.top}
width={width - margin.left - margin.right}
height={height - margin.top - margin.bottom}
width={innerWidth}
height={innerHeight}
fill={`url(#${patternId})`}
fillOpacity={0.3}
/>
Expand Down
54 changes: 48 additions & 6 deletions packages/visx-demo/src/sandboxes/visx-xychart/Example.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ const xScaleConfig = { type: 'band', paddingInner: 0.3 } as const;
const yScaleConfig = { type: 'linear' } as const;
const numTicks = 4;
const data = cityTemperature.slice(150, 225);
const getDate = (d: CityTemperature) => d.date; // new Date(d.date);
const getDate = (d: CityTemperature) => d.date;
const getSfTemperature = (d: CityTemperature) => Number(d['San Francisco']);
const getNyTemperature = (d: CityTemperature) => Number(d['New York']);

Expand All @@ -33,8 +33,14 @@ export default function Example({ height }: Props) {
renderBarSeries,
renderHorizontally,
renderLineSeries,
sharedTooltip,
showGridColumns,
showGridRows,
showHorizontalCrosshair,
showTooltip,
showVerticalCrosshair,
snapTooltipToDatumX,
snapTooltipToDatumY,
theme,
xAxisOrientation,
yAxisOrientation,
Expand All @@ -55,7 +61,7 @@ export default function Example({ height }: Props) {
/>
{renderBarSeries && (
<BarSeries
dataKey="ny"
dataKey="New York"
data={data}
xAccessor={renderHorizontally ? getNyTemperature : getDate}
yAccessor={renderHorizontally ? getDate : getNyTemperature}
Expand All @@ -64,10 +70,11 @@ export default function Example({ height }: Props) {
)}
{renderLineSeries && (
<LineSeries
dataKey="sf"
dataKey="San Francisco"
data={data}
xAccessor={renderHorizontally ? getSfTemperature : getDate}
yAccessor={renderHorizontally ? getDate : getSfTemperature}
horizontal={!renderHorizontally}
/>
)}
<AnimatedAxis
Expand All @@ -83,9 +90,44 @@ export default function Example({ height }: Props) {
numTicks={numTicks}
animationTrajectory={animationTrajectory}
/>
<Tooltip
renderTooltip={({ tooltipData }) => <pre>{JSON.stringify(tooltipData, null, 2)}</pre>}
/>
{showTooltip && (
<Tooltip<CityTemperature>
showHorizontalCrosshair={showHorizontalCrosshair}
showVerticalCrosshair={showVerticalCrosshair}
snapTooltipToDatumX={snapTooltipToDatumX}
snapTooltipToDatumY={snapTooltipToDatumY}
showDatumGlyph={snapTooltipToDatumX || snapTooltipToDatumY}
showSeriesGlyphs={sharedTooltip}
renderTooltip={({ tooltipData, colorScale }) => (
<>
{/** date */}
{tooltipData?.nearestDatum?.datum
? getDate(tooltipData?.nearestDatum?.datum)
: 'No date'}
<br />
<br />
{/** temperatures */}
{((sharedTooltip
? Object.keys(tooltipData?.datumByKey ?? {})
: [tooltipData?.nearestDatum?.key]
).filter(key => key) as string[]).map(key => (
<div key={key}>
<em
style={{
color: colorScale?.(key),
textDecoration:
tooltipData?.nearestDatum?.key === key ? 'underline' : undefined,
}}
>
{key}
</em>{' '}
{tooltipData?.datumByKey[key].datum[key as keyof CityTemperature]}° F
</div>
))}
</>
)}
/>
)}
</XYChart>
</DataProvider>
)}
Expand Down
75 changes: 75 additions & 0 deletions packages/visx-demo/src/sandboxes/visx-xychart/ExampleControls.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,14 @@ type ProvidedProps = {
renderHorizontally: boolean;
renderBarSeries: boolean;
renderLineSeries: boolean;
sharedTooltip: boolean;
showGridColumns: boolean;
showGridRows: boolean;
showHorizontalCrosshair: boolean;
showTooltip: boolean;
showVerticalCrosshair: boolean;
snapTooltipToDatumX: boolean;
snapTooltipToDatumY: boolean;
theme: XYChartTheme;
xAxisOrientation: 'top' | 'bottom';
yAxisOrientation: 'left' | 'right';
Expand All @@ -28,6 +34,12 @@ export default function ExampleControls({ children }: ControlsProps) {
const [xAxisOrientation, setXAxisOrientation] = useState<'top' | 'bottom'>('bottom');
const [yAxisOrientation, setYAxisOrientation] = useState<'left' | 'right'>('right');
const [renderHorizontally, setRenderHorizontally] = useState(false);
const [showTooltip, setShowTooltip] = useState(true);
const [showVerticalCrosshair, setShowVerticalCrosshair] = useState(true);
const [showHorizontalCrosshair, setShowHorizontalCrosshair] = useState(false);
const [snapTooltipToDatumX, setSnapTooltipToDatumX] = useState(true);
const [snapTooltipToDatumY, setSnapTooltipToDatumY] = useState(true);
const [sharedTooltip, setSharedTooltip] = useState(true);
const [renderBarSeries, setRenderBarSeries] = useState(true);
const [renderLineSeries, setRenderLineSeries] = useState(true);

Expand All @@ -38,8 +50,14 @@ export default function ExampleControls({ children }: ControlsProps) {
renderBarSeries,
renderHorizontally,
renderLineSeries,
sharedTooltip,
showGridColumns,
showGridRows,
showHorizontalCrosshair,
showTooltip,
showVerticalCrosshair,
snapTooltipToDatumX,
snapTooltipToDatumY,
theme,
xAxisOrientation,
yAxisOrientation,
Expand Down Expand Up @@ -205,6 +223,63 @@ export default function ExampleControls({ children }: ControlsProps) {
from max
</label>
</div>
{/** tooltip */}
<div>
<strong>tooltip</strong>
<label>
<input
type="checkbox"
onChange={() => setShowTooltip(!showTooltip)}
checked={showTooltip}
/>{' '}
show tooltip
</label>
<label>
<input
type="checkbox"
disabled={!showTooltip}
onChange={() => setSnapTooltipToDatumX(!snapTooltipToDatumX)}
checked={showTooltip && snapTooltipToDatumX}
/>{' '}
snap tooltip to datum x
</label>
<label>
<input
type="checkbox"
disabled={!showTooltip}
onChange={() => setSnapTooltipToDatumY(!snapTooltipToDatumY)}
checked={showTooltip && snapTooltipToDatumY}
/>{' '}
snap tooltip to datum y
</label>
<label>
<input
type="checkbox"
disabled={!showTooltip}
onChange={() => setShowVerticalCrosshair(!showVerticalCrosshair)}
checked={showTooltip && showVerticalCrosshair}
/>{' '}
vertical crosshair
</label>
<label>
<input
type="checkbox"
disabled={!showTooltip}
onChange={() => setShowHorizontalCrosshair(!showHorizontalCrosshair)}
checked={showTooltip && showHorizontalCrosshair}
/>{' '}
horizontal crosshair
</label>
<label>
<input
type="checkbox"
disabled={!showTooltip}
onChange={() => setSharedTooltip(!sharedTooltip)}
checked={showTooltip && sharedTooltip}
/>{' '}
shared tooltip
</label>
</div>
{/** series */}
<div>
<strong>series</strong>
Expand Down
44 changes: 19 additions & 25 deletions packages/visx-tooltip/src/hooks/useTooltip.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,57 +13,51 @@ type UseTooltipState<TooltipData> = Pick<
UseTooltipParams<TooltipData>,
'tooltipOpen' | 'tooltipLeft' | 'tooltipTop' | 'tooltipData'
>;
type ShowTooltipArgs<TooltipData> = Omit<UseTooltipState<TooltipData>, 'tooltipOpen'>;
type UpdateTooltipArgs<TooltipData> = UseTooltipState<TooltipData>;
type ValueOrFunc<T> = T | ((t: T) => T);
type ShowTooltipArgs<TooltipData> = ValueOrFunc<Omit<UseTooltipState<TooltipData>, 'tooltipOpen'>>;
type UpdateTooltipArgs<TooltipData> = ValueOrFunc<UseTooltipState<TooltipData>>;

export default function useTooltip<TooltipData = {}>(
/** Optional initial TooltipState. */
initialTooltipState?: Partial<UseTooltipParams<TooltipData>>,
): UseTooltipParams<TooltipData> {
const [tooltipState, setTooltipState] = useState<UseTooltipState<TooltipData>>({
tooltipOpen: false,
...initialTooltipState,
});

const updateTooltip = useCallback(
({ tooltipOpen, tooltipLeft, tooltipTop, tooltipData }: UpdateTooltipArgs<TooltipData>) =>
setTooltipState(prevState => ({
...prevState,
tooltipOpen,
tooltipLeft,
tooltipTop,
tooltipData,
})),
[],
);

const showTooltip = useCallback(
({ tooltipLeft, tooltipTop, tooltipData }: ShowTooltipArgs<TooltipData>) =>
updateTooltip({
tooltipOpen: true,
tooltipLeft,
tooltipTop,
tooltipData,
}),
[updateTooltip],
(showArgs: ShowTooltipArgs<TooltipData>) =>
setTooltipState(
typeof showArgs === 'function'
? ({ tooltipOpen, ...show }) => ({ ...showArgs(show), tooltipOpen: true })
: {
tooltipOpen: true,
tooltipLeft: showArgs.tooltipLeft,
tooltipTop: showArgs.tooltipTop,
tooltipData: showArgs.tooltipData,
},
),
[setTooltipState],
);

const hideTooltip = useCallback(
() =>
updateTooltip({
setTooltipState({
tooltipOpen: false,
tooltipLeft: undefined,
tooltipTop: undefined,
tooltipData: undefined,
}),
[updateTooltip],
[setTooltipState],
);

return {
tooltipOpen: tooltipState.tooltipOpen,
tooltipLeft: tooltipState.tooltipLeft,
tooltipTop: tooltipState.tooltipTop,
tooltipData: tooltipState.tooltipData,
updateTooltip,
updateTooltip: setTooltipState,
showTooltip,
hideTooltip,
};
Expand Down
16 changes: 12 additions & 4 deletions packages/visx-tooltip/src/hooks/useTooltipInPortal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,12 @@ import Portal from '../Portal';
import Tooltip, { TooltipProps } from '../tooltips/Tooltip';
import TooltipWithBounds from '../tooltips/TooltipWithBounds';

export type TooltipInPortalProps = TooltipProps & Pick<UseTooltipPortalOptions, 'detectBounds'>;

export type UseTooltipInPortal = {
containerRef: (element: HTMLElement | SVGElement | null) => void;
containerBounds: RectReadOnly;
TooltipInPortal: React.FC<TooltipProps>;
TooltipInPortal: React.FC<TooltipInPortalProps>;
};

export type UseTooltipPortalOptions = {
Expand All @@ -27,13 +29,19 @@ export type UseTooltipPortalOptions = {
* Handles conversion of container coordinates to page coordinates using the container bounds.
*/
export default function useTooltipInPortal({
detectBounds = true,
detectBounds: detectBoundsOption = true,
...useMeasureOptions
}: UseTooltipPortalOptions | undefined = {}): UseTooltipInPortal {
const [containerRef, containerBounds] = useMeasure(useMeasureOptions);

const TooltipInPortal = useMemo(
() => ({ left: containerLeft = 0, top: containerTop = 0, ...tooltipProps }: TooltipProps) => {
() => ({
left: containerLeft = 0,
top: containerTop = 0,
detectBounds: detectBoundsProp, // allow override at component-level
...tooltipProps
}: TooltipInPortalProps) => {
const detectBounds = detectBoundsProp == null ? detectBoundsOption : detectBoundsProp;
const TooltipComponent = detectBounds ? TooltipWithBounds : Tooltip;
// convert container coordinates to page coordinates
const portalLeft = containerLeft + (containerBounds.left || 0) + window.scrollX;
Expand All @@ -45,7 +53,7 @@ export default function useTooltipInPortal({
</Portal>
);
},
[detectBounds, containerBounds.left, containerBounds.top],
[detectBoundsOption, containerBounds.left, containerBounds.top],
);

return {
Expand Down
2 changes: 2 additions & 0 deletions packages/visx-xychart/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,9 @@
},
"dependencies": {
"@types/classnames": "^2.2.9",
"@types/lodash": "^4.14.146",
"@types/react": "*",
"lodash": "^4.17.10",
"@visx/axis": "1.0.0",
"@visx/event": "1.0.0",
"@visx/grid": "1.0.0",
Expand Down
Loading

0 comments on commit 5cd287f

Please sign in to comment.