Open humanchimp opened 5 years ago
Hey if no one is working on this, should I submit a PR I need this fix for a project at work 😁
@plouc is the change by @humanchimp enough? I was thing to check the bounds of the tooltip with the container to determine if it fits and change the anchor if that's not the case.
Also what are your thoughts on letting the user opt into this behaviour via a flag in props? Because sometimes I'm ok when my tooltips get outside the container.
Agreed with @chirgjn about the flag prop option instead of making it dynamic in all cases.
i don't think nivo is deprecated? As the author of this issue, I was doubtful that my approach, which was just copypasta from elsewhere in the codebase, would be high quality enough to merge. I have been using a fork of just the @nivo/tooltip
package which applies my patch, and that works for my needs, and means that actually having my changes merged upstream is not a priority. If not having #631 merged in is causing you difficulty, I recommend doing likewise for now.
I'm using your patch and it works great for my use cases. Btw, I'm applying it via patch-package, so I don't need to fork it or change my npm deps.
Also I just want to say I appreciate your humility very much.
Has anyone managed to get this to work on both the X and Y axis? The code above works great for X but Y is a little trickier, since you need to compute the height of the tooltip to do it AFAIK.
The issue still exists. Is anybody going to work on it?
A workaround I used here was to use a css position: absolute on the tooltip. (Thx for your work on Nivo, this package is amazzzzzing!)
<ResponsiveLine tooltip={TooltipFormat}/>
const TooltipFormat = ({ point }, ...rest) => {
// example, app.betterself.io/demo
return <StyledDivWithAbsolutePosition/>
}
It would also be cool if we could choose which way we wanted our tooltips to anchor; it seems like the library is choosing right for tooltip "slices" and top for everything else.
This issue has been automatically marked as stale. If this issue is still affecting you, please leave any comment (for example, "bump"), and we'll keep it open. We are sorry that we haven't been able to prioritize it yet. If you have any new additional information, please include it with your comment!
bump
Bump. I solved mine with position absolute and dynamically setting left/right depending on the hover position. You can get the hover position from the node prop that is given by you for the custom tooltip. Thanks, @jeffshek.
Bump. I solved mine with position absolute and dynamically setting left/right depending on the hover position. You can get the hover position from the node prop that is given by you for the custom tooltip. Thanks, @jeffshek.
Hey @ozcanonur can you share how did you do that? I'm facing the same issue here. Thanks!
@celestemartins I did the following:
Send a prop to my Tooltip component indicating the number of elements in the x-axis
Check whether the given point is in the first or second half of the chart like so:
const isFirstHalf = point.index < numElementsX / 2;
Apply the appropriate css style, depending on what you want to achieve.
ex.
import React from 'react';
import { Point } from '@nivo/line';
const CustomTooltip = ({ point, numElementsX } : { point: Point, numElementsX: number }) => {
const isFirstHalf = point.index < numElementsX / 2;
return (
<div
style = {{
position: 'absolute',
left: isFirstHalf ? 30 : 0,
right: isFirstHalf ? 0 : 30,
}}
>
// your tooltip
</div>
);
};
Note: I actually used
clsx
to select the appropriate css class. But it is the same concept
update: the left / right styles and absolute positioning seem to be a reasonable temporary fix.
Love this project. Currently working on a ResponsiveSwarmPlot and struggling with this issue and coming from recharts where the tooltip finds a way to always stay in the viewport, i'd like to replicate that behavior. My company would be happy to fund someones work to get a thorough / high quality patch in to the project if that helps! Thanks again.
@celestemartins I did the following:
- Send a prop to my Tooltip component indicating the number of elements in the x-axis
- Check whether the given point is in the first or second half of the chart like so:
const isFirstHalf = point.index < numElementsX / 2;
- Apply the appropriate css style, depending on what you want to achieve.
ex.
import React from 'react'; import { Point } from '@nivo/line'; const CustomTooltip = ({ point, numElementsX } : { point: Point, numElementsX: number }) => { const isFirstHalf = point.index < numElementsX / 2; return ( <div style = {{ position: 'absolute', left: isFirstHalf ? 30 : 0, right: isFirstHalf ? 0 : 30, }} > // your tooltip </div> ); };
Note: I actually used
clsx
to select the appropriate css class. But it is the same concept
Thanks for your help!
This approach was really helpful for me, I wound up using the onMouseMove event and ResizeObserver to keep track of the width and height of the container, and the mouse position which made it pretty easy to figure out in which quadrant of the graph the tooltip would be rendered and adjust the top/left offsets accordingly!
you can workaround with breaking word:
word-wrap: break-word; /* All browsers since IE 5.5+ */
overflow-wrap: break-word; /* Renamed property in CSS3 draft spec */
max-width: 10rem;
This issue has been automatically marked as stale. If this issue is still affecting you, please leave any comment (for example, "bump"), and we'll keep it open. We are sorry that we haven't been able to prioritize it yet. If you have any new additional information, please include it with your comment!
bump :)
Bump
Bump
Bump
Bump
@celestemartins I did the following:
- Send a prop to my Tooltip component indicating the number of elements in the x-axis
- Check whether the given point is in the first or second half of the chart like so:
const isFirstHalf = point.index < numElementsX / 2;
- Apply the appropriate css style, depending on what you want to achieve.
ex.
import React from 'react'; import { Point } from '@nivo/line'; const CustomTooltip = ({ point, numElementsX } : { point: Point, numElementsX: number }) => { const isFirstHalf = point.index < numElementsX / 2; return ( <div style = {{ position: 'absolute', left: isFirstHalf ? 30 : 0, right: isFirstHalf ? 0 : 30, }} > // your tooltip </div> ); };
Note: I actually used
clsx
to select the appropriate css class. But it is the same concept
Thanks! This also helped me for a bar chart Tooltip, just using transform
instead of left/right
different.
const Tooltip = (props) => {
const { bar } = props;
const isFirstHalf = bar.index <= 6;
return (
<div
className="chart-tooltip"
style={{
position: "absolute",
transform: isFirstHalf ? "translate(0,0)" : "translate(-260px,0)",
}}
>
// ...content
</div>
);
};
First, thank you @plouc for the amazing Nivo library, and for others for posting their wrappers. Is there any plan to make these changes an official part of the library?
bump
Thanks for the examples i did the following using <ResponsiveLine/>
:
the <div>
also have an position:absolute
ex.
import React from 'react'; const basicTooltip = (props : any) => { const {point} = props const isFirstHalf = point.x < 941 / 2; return ( <div style={isFirstHalf ? { left: 0 } : { right: 0 }} //tooltip content </div> ); }; <ResponsiveLine tooltip={basicTooltip} />
bump
This issue has been automatically marked as stale. If this issue is still affecting you, please leave any comment (for example, "bump"), and we'll keep it open. We are sorry that we haven't been able to prioritize it yet. If you have any new additional information, please include it with your comment!
bump
Closing this issue after a prolonged period of inactivity. If this issue is still present in the latest release, please create a new issue with up-to-date information. Thank you!
I ended up creating a new custom layer and used our UI component library for the tooltip. This was fairly straight forward, worked better, and was more consistent with our overall UX.
I know the issue is closed but someone might reach this issue by googling the problem, same as I did.
The solutions above are either specific for bar charts, or they use hardcoded values like the 941
width in @Danamorah 's example.
Instead of determining wether one is on the left or right part of the chart, I propose determining wether the tooltip fits to the left and if so, show it there. I also address the some issue on the Y axis.
My code:
export interface NonOverflowTooltipWrapperProps {
point: { x: number; y: number };
innerComponent: React.ReactNode;
}
const NonOverflowTooltipWrapper = (props: NonOverflowTooltipWrapperProps) => {
const [tooltipSize, setTooltipSize] = React.useState<{
width: number;
height: number;
}>({ width: 0, height: 0 });
// dynamically get the size of the tooltip
React.useEffect(() => {
const tooltip = document.querySelector(".nivo_tooltip");
if (tooltip) {
const { width, height } = tooltip.getBoundingClientRect();
setTooltipSize({ width, height });
}
}, [setTooltipSize]);
// only show it to the right of the pointer when we are close to the left edge
const translateX = React.useMemo(
() =>
props.point.x < (tooltipSize.width * 1.3) / 2 ? 0 : -tooltipSize.width,
[tooltipSize, props.point.x]
);
// only show it below the pointer when we are close to the top edge
const translateY = React.useMemo(
() =>
props.point.y < (tooltipSize.height * 1.3) / 2 ? 0 : -tooltipSize.height,
[tooltipSize, props.point.y]
);
return (
<div
className={"nivo_tooltip"}
style={{
position: "absolute",
transform: `translate(${translateX}px, ${translateY}px)`,
background: "#ffffff",
padding: "12px 16px",
width: "fit-content",
}}
>
<div style={{ position: "relative" }}>{props.innerComponent}</div>
</div>
);
};
Using this component we don't need to know (or hardcode) the actual size of the chart.
Based on @damianr13's solution, I came up with this:
import { useState, useEffect, useMemo, useRef } from "react";
export interface NonOverflowTooltipProps {
point: { x: number; y: number };
container: React.RefObject<HTMLDivElement>;
children: React.ReactNode;
}
export function NonOverflowTooltip(props: NonOverflowTooltipProps) {
const ref = useRef<HTMLDivElement>(null);
const [containerSize, setContainerSize] = useState<{
width: number;
height: number;
}>({ width: 0, height: 0 });
const [tooltipSize, setTooltipSize] = useState<{
width: number;
height: number;
}>({ width: 0, height: 0 });
// dynamically get the size of the container
useEffect(() => {
const container = props.container.current;
if (container) {
const { width, height } = container.getBoundingClientRect();
setContainerSize({ width, height });
}
}, [setContainerSize, props.container]);
// dynamically get the size of the tooltip
useEffect(() => {
const tooltip = ref.current;
if (tooltip) {
const { width, height } = tooltip.getBoundingClientRect();
setTooltipSize({ width, height });
}
}, [setTooltipSize]);
const offsetHorizontal = useMemo(() => {
// only show it to the right of the pointer when we are close to the left edge
if (props.point.x < tooltipSize.width) {
return tooltipSize.width / 3;
}
// only show it to the left of the pointer when we are close to the right edge
const rightEdge = containerSize.width - props.point.x;
if (rightEdge < tooltipSize.width) {
return -(tooltipSize.width / 3);
}
return 0;
}, [tooltipSize.width, props.point.x, containerSize.width]);
const offsetVertical = useMemo(() => {
// only show it above the pointer when we are close to the bottom edge
if (props.point.y > containerSize.height - tooltipSize.height) {
return -tooltipSize.height;
}
const bottomEdge = containerSize.height - props.point.y;
if (bottomEdge < tooltipSize.height) {
return -tooltipSize.height;
}
return 0;
}, [tooltipSize.height, props.point.y, containerSize.height]);
return (
<div
ref={ref}
style={{
position: "relative",
left: offsetHorizontal,
right: 0,
top: offsetVertical,
bottom: 0,
}}
>
{props.children}
</div>
);
}
The benefit here is the layout and style is calculated by the children props, rather than the weird layout sizing I got using that solution + the tooltip will be joined with the crosshair.
Switched the query selector with react refs, so multiple graphs won't cause tooltip layout issues, however, a parent container ref needs to be passed in to get the surrounding bounds (For right side/bottom side positioning).
Version of nivo tested: 0.58.0
Is your feature request related to a problem? Please describe. Our concern/feature request is primarily related to the line chart, but this affects other chart types too.
After upgrading to a recent version of nivo, the tooltips stopped being positioned relatively such that they don't overflow the bounding box. In a previous version we were using (0.31.0, specifically), the tooltip would adjust by anchoring itself on the opposite side of the cursor when the pointer reached the midpoint. Now it seems the scheme has changed and an anchor attribute must be provided.
I don't think this option will let us dynamically position the tooltip to avoid colliding with the boundary.
Describe the solution you'd like It would be better for us if the tooltip would dynamically anchor itself like it used to. Alternatively, we'd like to be able to use a hook to determine the best anchor dynamically
Describe alternatives you've considered I considered sending a PR to modify the positioning logic, but I don't feel comfortable with this approach.
https://github.com/plouc/nivo/blob/master/packages/tooltip/src/hooks.js#L29-L41
Basically, I cannot imagine something like this being mergeable, although it does work well for my application.
I would like to solve this problem by potentially allowing an "auto" option for anchor, or something like that.
Additional context I sincerely hope this is a feature request, and not simply a demonstration of ignorance on my part. I tried to figure this out myself before resorting to this request