Skip to content
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

feat: anchor tooltip to mark #959

Merged
merged 19 commits into from
Nov 5, 2024

Conversation

marshallpete
Copy link
Contributor

@marshallpete marshallpete commented Nov 1, 2024

Add options and support for snapping tooltips to the associated mark and set the position.

Issue

fixes #200

API

Options {
...
anchor: 'cursor' | 'mark';
position: Postion | Position[];
}

type Position = 'top' | 'bottom' | 'left' | 'right' | 'top-left' | 'top-right' | 'bottom-left' | 'bottom-right';

Each of the position options match the available anchor options on the vega label transform.

Behavior

Setting the anchor = 'mark' will make it so that the tooltip snaps to the associated mark. For voronoi paths, it will use the point that the voronoi path is derived from.

The tooltip will be positioned based on the position option. If only one position is provided, it will attempt to place the tooltip in that location. If the tooltip would be partially out of the viewport at this position or if the cursor location would overlap the tooltip, then it defaults back to snapping to the cursor position.

If multiple positions are supplied in the position option, then the algo will try to place the tooltip in each position until it finds one that is in the viewport and does not overlap the cursor location. If none of the positions meet this criteria, it will default back to anchoring to the cursor position.

The offsetX and offsetY options are honored. If a tooltip is placed directly to the left or right, only the offsetX is applied. If the tooltip is placed directly above or below the mark, only the offsetY is applied. If placed on one of the corners, both offsets are applied, just like with the cursor position calculation.

Positioning Example

Screen.Recording.2024-11-01.at.12.03.34.PM.mov

@marshallpete marshallpete changed the title Issue200/anchor tooltip to mark feat: anchor tooltip to mark Nov 1, 2024
@marshallpete
Copy link
Contributor Author

This PR adds position. It would be pretty easy to also take advantage of the position option when positioning relative to the cursor like this issue calls out.
Maybe a follow up PR?

Copy link
Member

@domoritz domoritz left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is great. Thanks for the pull request. I have a few comments.

package.json Outdated
@@ -52,6 +52,7 @@
"start": "yarn build && concurrently --kill-others -n Server,Rollup 'yarn serve' 'rollup -c -w'",
"pretest": "yarn build:style",
"test": "jest",
"watch": "jest --watch",
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You can already run yarn test --watch so I don't think we need another command. The main reason I want to not add this is since we have all the other Vega packages and I want to move towards consistency with fewer commands and setup requirements.

src/Handler.ts Outdated
Comment on lines 87 to 96
if (this.options.anchor === 'mark') {
position = calculatePositionRelativeToMark(handler, event, item, this.el.getBoundingClientRect(), this.options);
} else {
position = calculatePositionRelativeToCursor(
event,
this.el.getBoundingClientRect(),
this.options.offsetX,
this.options.offsetY,
);
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

could this become

const position = (this.options.anchor === 'mark' ? calculatePositionRelativeToMark : calculatePositionRelativeToCursor)(event, this.el.getBoundingClientRect(), this.options)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The two methods can't have the exact same arguments since calculatePositionRelativeToMark needs the handler and the item.

Unless we want to pass the handler and item into calculatePositionRelativeToCursor but never use them.

};

export type Options = Partial<typeof DEFAULT_OPTIONS>;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What was wrong with this?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There wasn't anything wrong previously but when adding anchor and position, the method didn't work well.

Originally the types were inferred from the default object. Since the types are inferred, any string default will be inferred as type string. This worked great for all the previous properties of Options.

anchor and position are both string unions. So there is a set list of acceptable strings that can be provided. If we let the types be inferred by the default object, then the type for these would be inferred as string instead of cursor | mark etc.

The advantage of these two using a string union is intelliSense and general type checking. If a consumer is using TS, then they will get an error if they set anchor = 'mouse'. And all consumer (TS of JS) should get intelliSense suggestions as long intelliSense is enabled.

image

This is why I flipped the order so we define the Options type first and then define the DEFAULT_OPTIONS and set it's type to Options.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You can add as const or as 'cursor' | 'mark to the object, I think, to make the type stricter. But I don't feel strongly about flipping this so no need to change.

@@ -6,7 +11,7 @@
* @param offsetX Horizontal offset.
* @param offsetY Vertical offset.
*/
export function calculatePosition(
export function calculatePositionRelativeToCursor(
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can this method also use tooltipIsInViewport?

Copy link
Member

@domoritz domoritz left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks. Looks great. Just a few small comments.

src/Handler.ts Outdated
Comment on lines 91 to 92
this.options.offsetX,
this.options.offsetY,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's refactor this to just take this.options so it's the same as above and can be combined as I suggested earlier.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, the would mean passing maybe unused arguments but it simplifies the call here.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah I like this suggestion better as well.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry I missed that in your ternary suggestion.

src/defaults.ts Outdated
*
* Example: bottom left would offset the tooltip to the bottom left by the offset value in pixels.
*/
// offset?: number;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

leftover?

src/position.ts Outdated
}

// Checks if the mouse is within the tooltip area
function mouseIsOnTooltip(
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's write a unit test for this.

@domoritz
Copy link
Member

domoritz commented Nov 5, 2024

Oh, one last thing. Can you add a checkbox to the example pages to switch between cursor and mark anchoring? Would be nice to testing.

@marshallpete
Copy link
Contributor Author

Updates have been made and a radio toggle was added to change the anchor option.

Screen.Recording.2024-11-05.at.10.27.33.AM.mov

@domoritz
Copy link
Member

domoritz commented Nov 5, 2024

thank you @marshallpete!

@domoritz domoritz merged commit 5a95192 into vega:main Nov 5, 2024
4 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Support positioning relative to marks
3 participants