Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/tooltip-follow-axis.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@human-kit/charts': patch
---

A `Chart.Tooltip` that follows the pointer moves only along it, at the height of the point. The tooltip is centred with `translate` instead of `transform`, thus a CSS animation of `transform` no longer moves it to the side while it opens or closes.
2 changes: 1 addition & 1 deletion docs/src/content/tooltip/api.json
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@
"type": "boolean",
"required": false,
"default": "false",
"description": "Places the tooltip at the pointer and moves it with the pointer, over the point under it.\nFor a large mark, as a long bar, whose anchor can be far from the pointer. The point that\nhas the keyboard focus keeps its tooltip at the point. The default is `false`."
"description": "Moves the tooltip along with the pointer, at the height of the point under it. For a large\nmark, as a long bar, whose anchor can be far from the pointer. The point that has the\nkeyboard focus keeps its tooltip at the point. The default is `false`."
},
{
"name": "class",
Expand Down
4 changes: 2 additions & 2 deletions docs/src/content/tooltip/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ The tooltip is HTML, thus it goes in `Chart.Root` and not in `Chart.Plot`. It ma

`offset` is the distance between the point and the tooltip, in pixels. The tooltip is above the point, and its middle is at the x position of the point.

With `follow`, the tooltip goes to the pointer and moves with it, over the point under the pointer. Use it for a large mark, as a long bar, whose position can be far from the pointer. The point that has the keyboard focus keeps its tooltip at the point. A tooltip that follows the pointer stays in the box that would cut it (the nearest ancestor whose `overflow` is not `visible`, or the page) instead of the figure: on a figure narrower than the tooltip, it still goes where the pointer goes.
With `follow`, the tooltip moves along with the pointer, at the height of the point under the pointer. Use it for a large mark, as a long bar, whose position can be far from the pointer. The point that has the keyboard focus keeps its tooltip at the point. A tooltip that follows the pointer stays in the box that would cut it (the nearest ancestor whose `overflow` is not `visible`, or the page) instead of the figure: on a figure narrower than the tooltip, it still goes where the pointer goes.

## Content of your own

Expand Down Expand Up @@ -81,7 +81,7 @@ The last three attributes are on the default content only.

### Animation

The box has `data-entering` while it opens and `data-exiting` while it closes. Give them a CSS animation: the box goes after the animation of `data-exiting` ends. Without an animation, it goes at once.
The box has `data-entering` while it opens and `data-exiting` while it closes. It is centred with the `translate` property, thus an animation of `transform` does not move it away from its place. Give them a CSS animation: the box goes after the animation of `data-exiting` ends. Without an animation, it goes at once.

```css
@keyframes tooltip-in {
Expand Down
9 changes: 5 additions & 4 deletions packages/charts/src/lib/tooltip/chart-tooltip.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -29,9 +29,10 @@
const point = entry.points[index];
const anchor = ctx.anchor(entry.mark, entry.series, index);
if (!anchor) return null;
// A tooltip that follows the pointer goes with it over the point under it. The point that has
// the keyboard focus has no pointer, thus its tooltip stays at the point.
const [x, y] = follow && pointer && id === hoveredId ? pointer : anchor;
// A tooltip that follows the pointer goes along with it, at the height of the point under it.
// The point that has the keyboard focus has no pointer, thus its tooltip stays at the point.
const x = follow && pointer && id === hoveredId ? pointer[0] : anchor[0];
const y = anchor[1];
return {
point,
x,
Expand Down Expand Up @@ -179,7 +180,7 @@
bind:offsetWidth={width}
style:left="{left}px"
style:top="{placement.offset[1] + tip.y - offset}px"
style:transform="translate(-50%, -100%)"
style:translate="-50% -100%"
style:pointer-events="none"
{...restProps}
>
Expand Down
24 changes: 23 additions & 1 deletion packages/charts/src/lib/tooltip/chart-tooltip.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,8 @@ describe('Chart.Tooltip', () => {
const above =
document.querySelector('svg')!.getBoundingClientRect().top -
document.querySelector('figure')!.getBoundingClientRect().top;
expect(position()[1]).toBeCloseTo(above + cy + 10 - 8, 0);
// Only along: at the height of the point, not of the pointer.
expect(position()[1]).toBeCloseTo(above + cy - 8, 0);

await hoverAt(cx + 24, cy + 10);
expect(position()[0]).toBeCloseTo(cx + 24, 0);
Expand Down Expand Up @@ -71,6 +72,27 @@ describe('Chart.Tooltip', () => {
expect(position()[0]).toBeCloseTo(Number(point.getAttribute('cx')), 0);
});

it('stays in its place while an animation of transform closes it', async () => {
const style = document.createElement('style');
style.textContent = `
@keyframes out { to { transform: scale(0.9) } }
[data-tooltip][data-exiting] { animation: out 400ms }
`;
document.head.append(style);
try {
render(PartsTest);
points()[0].focus();
await userEvent.keyboard('{ArrowRight}');
const open = tooltip()!.getBoundingClientRect();
await userEvent.keyboard('{Escape}');
const closing = tooltip()!.getBoundingClientRect();
expect(closing.left + closing.width / 2).toBeCloseTo(open.left + open.width / 2, 0);
expect(closing.bottom).toBeLessThanOrEqual(open.bottom + 0.5);
} finally {
style.remove();
}
});

it('marks the opening for CSS, and closes after the animation of the closing', async () => {
const style = document.createElement('style');
style.textContent = `
Expand Down
6 changes: 3 additions & 3 deletions packages/charts/src/lib/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -296,9 +296,9 @@ export type ChartTooltipProps = Omit<HTMLAttributes<HTMLDivElement>, 'class' | '
/** The distance between the point and the tooltip, in pixels. The default is 8. */
offset?: number;
/**
* Places the tooltip at the pointer and moves it with the pointer, over the point under it.
* For a large mark, as a long bar, whose anchor can be far from the pointer. The point that
* has the keyboard focus keeps its tooltip at the point. The default is `false`.
* Moves the tooltip along with the pointer, at the height of the point under it. For a large
* mark, as a long bar, whose anchor can be far from the pointer. The point that has the
* keyboard focus keeps its tooltip at the point. The default is `false`.
*/
follow?: boolean;
/**
Expand Down
Loading