-
Notifications
You must be signed in to change notification settings - Fork 45
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
feat: anchor tooltip to mark #959
Conversation
…shallpete/vega-tooltip into issue200/anchorTooltipToMark
This PR adds |
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.
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", |
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.
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
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, | ||
); | ||
} |
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.
could this become
const position = (this.options.anchor === 'mark' ? calculatePositionRelativeToMark : calculatePositionRelativeToCursor)(event, this.el.getBoundingClientRect(), this.options)
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.
Good catch
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.
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>; |
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.
What was wrong with this?
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.
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.
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
.
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.
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( |
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.
Can this method also use tooltipIsInViewport
?
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.
Thanks. Looks great. Just a few small comments.
src/Handler.ts
Outdated
this.options.offsetX, | ||
this.options.offsetY, |
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.
Let's refactor this to just take this.options
so it's the same as above and can be combined as I suggested earlier.
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.
Yes, the would mean passing maybe unused arguments but it simplifies the call here.
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.
Yeah I like this suggestion better as well.
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.
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; |
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.
leftover?
src/position.ts
Outdated
} | ||
|
||
// Checks if the mouse is within the tooltip area | ||
function mouseIsOnTooltip( |
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.
Let's write a unit test for this.
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. |
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 |
thank you @marshallpete! |
Add options and support for snapping tooltips to the associated mark and set the position.
Issue
fixes #200
API
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. Forvoronoi
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
andoffsetY
options are honored. If a tooltip is placed directly to the left or right, only theoffsetX
is applied. If the tooltip is placed directly above or below the mark, only theoffsetY
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