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
Feature Request: HoverTool tooltips stick to a point on click. #5724
Comments
+1 for this feature. Also, it would be great if the selected (either circle or point in a line) could take callbacks. |
@draperjames, sure. The idea to be able to interact with contents of a tooltip has been around for awhile, though in various forms. One thing to keep in mind is that whatever is implemented here, it should intersect smoothly with other bokeh's features. In particular, hover tool's functionality shouldn't interfere with tap tool. There may be some discussion needed to determine the best course of action. |
@mattpap +1 what would be nice is some kind of 'onclick_behavior' kwarg in HoverTool that could could be loaded with a couple options. But I have no idea what that would look like on the back end. What do you think? |
+1 |
+1 for this feature. Use case : click on some links in the HoverTool tooltip. An idea about the risk of interference between HoverTool and TapTool: It could be also a new model openToolTip, used with TapTool as openURL, that opens a tooltip created as the one for HoverTool (with a cross to close it). |
I've been thinking about improving inspections and tooltips, and have an idea for an implementation that would solve this issue. First, tooltips would owned by GlyphRenderers instead of the HoverTool. Currently, tooltips depend on the HoverTool, so they only appear when a hover tool is actively hovering. Moving the tooltips to the GlyphRenderers would make it possible for them to depend only the data source's |
copying comment from #8113:
A possible solution is to expose an |
In case its useful as workaround for now, a simplistic example was developed in the other issue
Initial: After some taps: this could be expanded to remove old labels on a second click, not change the selection highlights, etc. as desired. |
I'm sorry for this abomination, but it works quite well, at least with Bokeh 1.2.0.
from bokeh.models import HoverTool
class StickyHoverTool(HoverTool):
__implementation__ = "sticky_hover_tool.ts"
import {HoverTool, HoverToolView} from "models/tools/inspectors/hover_tool";
import {GlyphRenderer, GraphRenderer} from "models/renderers";
import {Tooltip} from "models/annotations";
import {isFunction, isString} from "core/util/types";
import {build_views} from "core/build_views";
import {values} from "core/util/object";
import {TooltipView} from "models/annotations/tooltip";
export class StickyTooltipView extends TooltipView {
protected _mouse_over = false;
protected _have_drawn = false;
protected _bound_mouse_moved: EventListener | null = null;
protected _cursor_margin = 10;
protected _createElement(): HTMLElement {
const el = super._createElement();
el.style.pointerEvents = "initial";
el.style.cursor = "initial";
return el;
}
protected _mouse_moved(event: MouseEvent): void {
const old_value = this._mouse_over;
if (this._have_drawn) {
const br = this.el.getBoundingClientRect();
const x = event.clientX;
const y = event.clientY;
this._mouse_over = (
x >= (br.left - this._cursor_margin)
&& x <= (br.right + this._cursor_margin)
&& y >= (br.top - this._cursor_margin)
&& y <= (br.bottom + this._cursor_margin)
);
} else {
this._mouse_over = false;
}
if (old_value && !this._mouse_over) {
this._draw_tips();
}
}
initialize(): void {
super.initialize();
// Using `document.addEventListener` instead of
// `this.el.addEventListener` since we also want to detect
// the mouse within some margin of the tooltip. It would
// be good to know if there are other solutions that
// don't require any cleanup.
this._bound_mouse_moved = this._mouse_moved.bind(this);
document.addEventListener("mousemove", this._bound_mouse_moved!);
}
remove(): void {
super.remove();
document.removeEventListener("mousemove", this._bound_mouse_moved!);
}
protected _redraw_if_not_interacting(): void {
if (!this._mouse_over) {
super._draw_tips();
this._have_drawn = this.el.childNodes.length > 0;
}
}
protected _draw_tips(): void {
if (this._have_drawn) {
setTimeout(this._redraw_if_not_interacting.bind(this), 500);
} else {
this._redraw_if_not_interacting();
}
}
}
export class StickyTooltip extends Tooltip {
static initClass(): void {
this.prototype.default_view = StickyTooltipView;
}
}
StickyTooltip.initClass();
export class StickyHoverToolView extends HoverToolView {
protected _compute_ttmodels(): { [key: string]: StickyTooltip } {
// The only thing this override of `HoverToolView._compute_ttmodels`
// does is replace the usages of `Tooltip` with `StickyTooltip`.
const ttmodels: { [key: string]: StickyTooltip } = {};
const tooltips = this.model.tooltips;
if (tooltips != null) {
for (const r of this.computed_renderers) {
if (r instanceof GlyphRenderer) {
ttmodels[r.id] = new StickyTooltip({
custom: isString(tooltips) || isFunction(tooltips),
attachment: this.model.attachment,
show_arrow: this.model.show_arrow,
});
} else if (r instanceof GraphRenderer) {
const tooltip = new StickyTooltip({
custom: isString(tooltips) || isFunction(tooltips),
attachment: this.model.attachment,
show_arrow: this.model.show_arrow,
});
ttmodels[r.node_renderer.id] = tooltip;
ttmodels[r.edge_renderer.id] = tooltip;
}
}
}
build_views(this.ttviews, values(ttmodels), {parent: this.plot_view});
return ttmodels
}
}
export class StickyHoverTool extends HoverTool {
static initClass(): void {
this.prototype.default_view = StickyHoverToolView;
}
}
StickyHoverTool.initClass(); Let me know how it works for you. |
Verified Eugene(p-himik)'s solution works |
This would be a really cool feature! |
@rsdenijs Have you tried my code in my previous message? |
@p-himik I tried but I need node.js v6.10.0 for building custom models and my environment currently does not allow adding dependencies. |
@rsdenijs I see. It should be possible to implement the same approach but avoid any custom model building. You'll have to rewrite the code to the target JS version, not add |
From my stack overflow question:
The text was updated successfully, but these errors were encountered: