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
70 changes: 70 additions & 0 deletions docs/layers/tooltip.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
Tooltip
=====

Example
-------

.. jupyter-execute::

from ipyleaflet import Map, Tooltip, Marker, Polygon, Circle

m = Map(center=(51.505,-0.09), zoom=13)

standalone_tooltip = Tooltip(
location=[51.5, -0.09],
content="Hello world!<br />This is a nice tooltip.",
offset=[-30,50], # Offset in pixels
permanent=False, # The default is False, in which case you can remove the tooltip by clicking anywhere on the map.
direction='bottom', # Default is 'auto'
)

marker_tooltip = Tooltip(
content="I'm a marker tooltip! 👋<br>Appears on hover.",
)

marker = Marker(
location=[51.5, -0.09],
draggable=False,
tooltip=marker_tooltip,
)

polygon = Polygon(
locations= [
[51.509, -0.08],
[51.503, -0.06],
[51.51, -0.047]
])

polygon_tooltip = Tooltip(
content = "Polygon's Permanent Tooltip 🗺️",
permanent = True,
direction = 'center', # Centers the tooltip on the polygon
)

polygon.tooltip = polygon_tooltip

circle = Circle(
location = [51.515, -0.1],
radius = 500,
color = 'green',
fillColor = '#0f3',
fillOpacity = 0.5,
tooltip = Tooltip(
content = "Sticky Tooltip here! 📍<br>Stays with the mouse.",
sticky = True,
)
)

m.add(standalone_tooltip)
m.add(marker)
m.add(polygon)
m.add(circle)

m


Attributes and methods
----------------------

.. autoclass:: ipyleaflet.leaflet.Tooltip
:members:
43 changes: 43 additions & 0 deletions python/ipyleaflet/ipyleaflet/leaflet.py
Original file line number Diff line number Diff line change
Expand Up @@ -175,6 +175,8 @@ class Layer(Widget, InteractMixin):
Make Leaflet-Geoman ignore the layer, so it cannot modify it.
snap_ignore: boolean
Make Leaflet-Geoman snapping ignore the layer, so it is not used as a snap target when editing.
tooltip: Tooltip widget
Tooltip widget to bind to the layer.
"""

_view_name = Unicode("LeafletLayerView").tag(sync=True)
Expand All @@ -196,6 +198,10 @@ class Layer(Widget, InteractMixin):
popup_max_height = Int(default_value=None, allow_none=True).tag(sync=True)
pane = Unicode("").tag(sync=True)

tooltip = Instance(Widget, allow_none=True, default_value=None).tag(
sync=True, **widget_serialization
)

options = List(trait=Unicode()).tag(sync=True)
subitems = Tuple().tag(trait=Instance(Widget), sync=True, **widget_serialization)

Expand Down Expand Up @@ -640,6 +646,43 @@ def close_popup(self):
self.send({"msg": "close"})


class Tooltip(UILayer):
"""Tooltip class.

Used to display small texts on top of map layers.

Attributes
----------
location: tuple, default None
Optional tuple containing the latitude/longitude of the stand-alone tooltip.
content: str, default ""
The text to show inside the tooltip
offset: tuple, default (0, 0)
Optional offset of the tooltip position (in pixels).
direction: str, default 'auto'
Direction where to open the tooltip.
Possible values are: right, left, top, bottom, center, auto.
auto will dynamically switch between right and left according
to the tooltip position on the map.
permanent: bool, default False
Whether to open the tooltip permanently or only on mouseover.
sticky: bool, default False
If true, the tooltip will follow the mouse instead of being fixed at the feature center.
This option only applies when binding the tooltip to a Layer, not as stand-alone.
"""
_view_name = Unicode("LeafletTooltipView").tag(sync=True)
_model_name = Unicode("LeafletTooltipModel").tag(sync=True)

location = List(allow_none=True, default_value=None).tag(sync=True)

# Options
content = Unicode('').tag(sync=True, o=True)
offset = List(def_loc).tag(sync=True, o=True)
direction=Unicode('auto').tag(sync=True, o=True)
permanent = Bool(False).tag(sync=True, o=True)
sticky = Bool(False).tag(sync=True, o=True)


class RasterLayer(Layer):
"""Abstract RasterLayer class.

Expand Down
1 change: 1 addition & 0 deletions python/jupyter_leaflet/src/jupyter-leaflet.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ export * from './layers/Popup';
export * from './layers/RasterLayer';
export * from './layers/Rectangle';
export * from './layers/TileLayer';
export * from './layers/Tooltip';
export * from './layers/VectorLayer';
export * from './layers/VectorTileLayer';
export * from './layers/Velocity';
Expand Down
38 changes: 37 additions & 1 deletion python/jupyter_leaflet/src/layers/Layer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import {
} from '@jupyter-widgets/base';
import { IMessageHandler, MessageLoop } from '@lumino/messaging';
import { Widget } from '@lumino/widgets';
import { Control, Layer, LeafletMouseEvent, Popup } from 'leaflet';
import { Control, Layer, LeafletMouseEvent, Popup, Tooltip } from 'leaflet';
import { LeafletControlView, LeafletMapView } from '../jupyter-leaflet';
import L from '../leaflet';
import { LeafletWidgetView } from '../utils';
Expand All @@ -29,6 +29,7 @@ export interface ILeafletLayerModel {
popup_max_width: number;
popup_max_height: number | null;
pane: string;
tooltip: WidgetModel | null;
subitems: Layer[];
pm_ignore: boolean;
snap_ignore: boolean;
Expand All @@ -55,13 +56,15 @@ export class LeafletLayerModel extends WidgetModel {
subitems: [],
pm_ignore: true,
snap_ignore: false,
tooltip: null,
};
}
}

LeafletLayerModel.serializers = {
...WidgetModel.serializers,
popup: { deserialize: unpack_models },
tooltip: { deserialize: unpack_models },
subitems: { deserialize: unpack_models },
};

Expand All @@ -83,13 +86,16 @@ export class LeafletLayerView extends LeafletWidgetView {
obj: Layer;
subitems: WidgetModel[];
pWidget: IMessageHandler;
tooltip_content: LeafletLayerView;
tooltip_content_promise: Promise<void>;

create_obj(): void {}

initialize(parameters: WidgetView.IInitializeParameters<LeafletLayerModel>) {
super.initialize(parameters);
this.map_view = this.options.map_view;
this.popup_content_promise = Promise.resolve();
this.tooltip_content_promise = Promise.resolve();
}

remove_subitem_view(child_view: LeafletLayerView) {
Expand Down Expand Up @@ -128,6 +134,7 @@ export class LeafletLayerView extends LeafletWidgetView {
this.listenTo(this.model, 'change:popup', (model, value_2) => {
this.bind_popup(value_2);
});
this.bind_tooltip(this.model.get('tooltip'));
this.update_pane();
this.subitem_views = new ViewList(
this.add_subitem_model,
Expand Down Expand Up @@ -200,6 +207,9 @@ export class LeafletLayerView extends LeafletWidgetView {
this.listenTo(this.model, 'change:subitems', () => {
this.subitem_views.update(this.subitems);
});
this.listenTo(this.model, 'change:tooltip', () => {
this.bind_tooltip(this.model.get('tooltip'));
});
}

remove() {
Expand All @@ -210,6 +220,11 @@ export class LeafletLayerView extends LeafletWidgetView {
this.popup_content.remove();
}
});
this.tooltip_content_promise.then(() => {
if (this.tooltip_content) {
this.tooltip_content.remove();
}
});
}

bind_popup(value: WidgetModel) {
Expand Down Expand Up @@ -252,6 +267,27 @@ export class LeafletLayerView extends LeafletWidgetView {
this.obj.togglePopup();
this.obj.togglePopup();
}

bind_tooltip(value: WidgetModel) {
if (this.tooltip_content) {
this.obj.unbindTooltip();
this.tooltip_content.remove();
}
if (value) {
this.tooltip_content_promise = this.tooltip_content_promise.then(
async () => {
const view = await this.create_child_view<LeafletLayerView>(value, {
map_view: this.map_view,
});
if (view.obj instanceof Tooltip) {
this.obj.bindTooltip(view.obj);
}
this.tooltip_content = view;
}
);
}
return this.tooltip_content_promise;
}
}

export class LeafletUILayerView extends LeafletLayerView {}
66 changes: 66 additions & 0 deletions python/jupyter_leaflet/src/layers/Tooltip.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
// Copyright (c) Jupyter Development Team.
// Distributed under the terms of the Modified BSD License.

import { WidgetView } from '@jupyter-widgets/base';
import { Tooltip, TooltipOptions } from 'leaflet';
import L from '../leaflet';
import {
ILeafletLayerModel,
LeafletUILayerModel,
LeafletUILayerView,
} from './Layer';

interface ILeafletTooltipModel extends ILeafletLayerModel {
_view_name: string;
_model_name: string;
location: number[] | null;
}

export class LeafletTooltipModel extends LeafletUILayerModel {
defaults(): ILeafletTooltipModel {
return {
...super.defaults(),
_view_name: 'LeafletMarkerView',
_model_name: 'LeafletMarkerModel',
location: null,
};
}
}

export class LeafletTooltipView extends LeafletUILayerView {
obj: Tooltip;

initialize(
parameters: WidgetView.IInitializeParameters<LeafletTooltipModel>
) {
super.initialize(parameters);
}

create_obj() {
if (this.model.get('location')) {
// Stand-alone tooltip
this.obj = (L.tooltip as any)(
this.model.get('location'),
this.get_options() as TooltipOptions
);
} else {
this.obj = L.tooltip(this.get_options() as TooltipOptions);
}
}

model_events() {
super.model_events();
this.listenTo(this.model, 'change:location', () => {
if (this.model.get('location')) {
this.obj.setLatLng(this.model.get('location'));
this.send({
event: 'move',
location: this.model.get('location'),
});
}
});
this.listenTo(this.model, 'change:content', () => {
this.obj.setContent(this.model.get('content'));
});
}
}
Loading