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

[WIP] Imagemap #2739

Open
wants to merge 35 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
35 commits
Select commit Hold shift + click to select a range
7075e65
Passing click coordinates to the backend. Right now only for Buttons.
zerline Jan 15, 2020
403db7e
s/let/const
zerline Jan 15, 2020
bad5e4c
A MappedImage widget (WIP).
zerline Jan 15, 2020
e76f0e9
Update schema and s/let/const.
zerline Jan 15, 2020
72131e5
Widget serialization.
zerline Jan 16, 2020
2514659
Introducing an HTMLElement widget for more flexibility.
zerline Jan 16, 2020
271a91b
Passing click coordinates to the backend. Right now only for Buttons.
zerline Jan 15, 2020
6e944b6
A MappedImage widget (WIP).
zerline Jan 15, 2020
dbde615
Widget serialization.
zerline Jan 16, 2020
c0e93d0
Introducing an HTMLElement widget for more flexibility.
zerline Jan 16, 2020
7d1e915
Trying to update HTMLElement.
zerline Jan 17, 2020
9432791
No label for HTMLElement.
zerline Jan 17, 2020
f3984ae
A better, yet stille buggy, HTMLElement.
zerline Jan 17, 2020
c00045f
A working implementation of Image Map.
zerline Jan 17, 2020
1a3b646
Schema update.
zerline Jan 17, 2020
db8c9c2
Using HTML instead of HTMLElement. It works just the same ..
zerline Jan 17, 2020
c37af18
Image Map, with no new widget in widget_string ..
zerline Jan 17, 2020
d0eb77f
Schema update (with no HTMLElement).
zerline Jan 17, 2020
56e152d
Returning the clicks also from an Image.
zerline Jan 17, 2020
86f94f5
Passing click coordinates to the backend. Right now only for Buttons.
zerline Jan 15, 2020
b124d84
A MappedImage widget (WIP).
zerline Jan 15, 2020
647f58d
Widget serialization.
zerline Jan 16, 2020
13cc131
Introducing an HTMLElement widget for more flexibility.
zerline Jan 16, 2020
424ceb2
A MappedImage widget (WIP).
zerline Jan 15, 2020
cd7ea1f
Widget serialization.
zerline Jan 16, 2020
89e14a4
Introducing an HTMLElement widget for more flexibility.
zerline Jan 16, 2020
11fc5a8
Trying to update HTMLElement.
zerline Jan 17, 2020
9d8af14
No label for HTMLElement.
zerline Jan 17, 2020
1230eba
A better, yet stille buggy, HTMLElement.
zerline Jan 17, 2020
2a852ab
A working implementation of Image Map.
zerline Jan 17, 2020
4291c71
Schema update.
zerline Jan 17, 2020
f1fd625
Using HTML instead of HTMLElement. It works just the same ..
zerline Jan 17, 2020
e015512
Image Map, with no new widget in widget_string ..
zerline Jan 17, 2020
1ad0f71
Schema update.
zerline Feb 17, 2020
dbf95e4
Address lint error.
zerline Feb 17, 2020
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
2 changes: 1 addition & 1 deletion ipywidgets/widgets/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@
from .interaction import interact, interactive, fixed, interact_manual, interactive_output
from .widget_link import jslink, jsdlink
from .widget_layout import Layout
from .widget_media import Image, Video, Audio
from .widget_media import Image, MapArea, MappedImage, Video, Audio
from .widget_style import Style
from .widget_templates import TwoByTwoLayout, AppLayout, GridspecLayout
from .widget_upload import FileUpload
53 changes: 50 additions & 3 deletions ipywidgets/widgets/widget_media.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,12 @@
import mimetypes

from .widget_core import CoreWidget
from .widget_string import HTML
from .domwidget import DOMWidget
from .valuewidget import ValueWidget
from .widget import register
from traitlets import Unicode, CUnicode, Bool
from .trait_types import CByteMemoryView
from .widget import register, widget_serialization
from traitlets import Unicode, CUnicode, Bytes, Bool, Instance, observe
from .trait_types import CByteMemoryView, TypedTuple, bytes_serialization


@register
Expand Down Expand Up @@ -164,6 +165,52 @@ def __repr__(self):
return self._get_repr(Image)


@register
class MapArea(HTML):
r"""
An <area> tag, part of an image map <map>.
"""
tagname = Unicode('area')
name = Unicode('')
shape = Unicode('')
coords = Unicode('')
href = Unicode(None, allow_none=True)

def __init__(self, name='noname', shape='rect', coords='', href=''):
"""
Parameters
----------
name: str
Area name. Will be used as 'alt' attribute.

shape: str
Area shape: rect/circle/poly/default.

coords: str
Area coordinates: tuple of integers, string represented.

href: str
URL to load when area is clicked.
"""
def __init__(self, *args, **kwargs):
super().__init__(*args, tagname='area', **kwargs)
self._compute_value(self.name, self.shape, self.coords, self.href)

def _compute_value(self, name, shape, coords, href):
self.value = '<area alt="{}" shape="{}" coords="{}" href="{}">' . format(name, shape, coords, href)

@observe('name', 'shape', 'coords', 'href')
def _attr_changed(self, change):
self._compute_value(self.name, self.shape, self.coords, self.href)


@register
class MappedImage(Image):
_view_name = Unicode('MappedImageView').tag(sync=True)
_model_name = Unicode('MappedImageModel').tag(sync=True)
areas = TypedTuple(trait=Instance(MapArea), help="List of mapped shapes").tag(sync=True, **widget_serialization)


@register
class Video(_Media):
"""Displays a video as a widget.
Expand Down
1 change: 1 addition & 0 deletions ipywidgets/widgets/widget_string.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ class HTML(_String):
_view_name = Unicode('HTMLView').tag(sync=True)
_model_name = Unicode('HTMLModel').tag(sync=True)


@register
class HTMLMath(_String):
"""Renders the string `value` as HTML, and render mathematics."""
Expand Down
57 changes: 40 additions & 17 deletions packages/base/src/widget.ts
Original file line number Diff line number Diff line change
Expand Up @@ -948,15 +948,6 @@ export class DOMWidgetView extends WidgetView {
}
}

updateTooltip(): void {
const title = this.model.get('tooltip');
if (!title) {
this.el.removeAttribute('title');
} else if (this.model.get('description').length === 0) {
this.el.setAttribute('title', title);
}
}

/**
* Update the DOM classes applied to an element, default to this.el.
*/
Expand Down Expand Up @@ -1033,6 +1024,26 @@ export class DOMWidgetView extends WidgetView {
this.update_classes([], new_classes, el || this.el);
}

updateTabindex(): void {
const tabbable = this.model.get('tabbable');
if (tabbable === true) {
this.el.setAttribute('tabIndex', '0');
} else if (tabbable === false) {
this.el.setAttribute('tabIndex', '-1');
} else if (tabbable === null) {
this.el.removeAttribute('tabIndex');
}
}

updateTooltip(): void {
const title = this.model.get('tooltip');
if (!title) {
this.el.removeAttribute('title');
} else if (this.model.get('description').length === 0) {
this.el.setAttribute('title', title);
}
}

_setElement(el: HTMLElement): void {
if (this.pWidget) {
this.pWidget.dispose();
Expand Down Expand Up @@ -1069,16 +1080,28 @@ export class DOMWidgetView extends WidgetView {
}
}

updateTabindex(): void {
const tabbable = this.model.get('tabbable');
if (tabbable === true) {
this.el.setAttribute('tabIndex', '0');
} else if (tabbable === false) {
this.el.setAttribute('tabIndex', '-1');
} else if (tabbable === null) {
this.el.removeAttribute('tabIndex');
/**
* Return something like the event position relative to the widget view
* This is essentially what layerX and layerY are supposed to be (and are in chrome) but those event properties have
* red box warnings in the MDN documentation that they are not part of any
* standard and are not on any standards tracks, so get what we need here.
* (Code copied from `ipyevents` by M. Craig)
*
* Parameters
* ----------
* event: mouse event object.
*/

_get_relative_xy(event: MouseEvent): {[i: string]: number} {
const bounding_rect = this.el.getBoundingClientRect();
const y_offset = bounding_rect.top;
const x_offset = bounding_rect.left;
return {
'x': Math.round(event.clientX - x_offset),
'y': Math.round(event.clientY - y_offset)
}
}

el: HTMLElement; // Override typing
'$el': any;
pWidget: Widget;
Expand Down
3 changes: 2 additions & 1 deletion packages/controls/src/widget_button.ts
Original file line number Diff line number Diff line change
Expand Up @@ -118,7 +118,8 @@ export class ButtonView extends DOMWidgetView {
*/
_handle_click(event: MouseEvent): void {
event.preventDefault();
this.send({ event: 'click' });
const relative_xy = this._get_relative_xy(event);
this.send({event: 'click', click_pos: JSON.stringify(relative_xy)});
}

/**
Expand Down
78 changes: 77 additions & 1 deletion packages/controls/src/widget_image.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
// Copyright (c) Jupyter Development Team.
// Distributed under the terms of the Modified BSD License.

import { DOMWidgetView } from '@jupyter-widgets/base';
import { DOMWidgetView, unpack_models } from '@jupyter-widgets/base';

import { CoreDOMWidgetModel } from './widget_core';

import { uuid } from './utils';

export class ImageModel extends CoreDOMWidgetModel {
defaults(): Backbone.ObjectHash {
return {
Expand Down Expand Up @@ -88,6 +90,22 @@ export class ImageView extends DOMWidgetView {
super.remove();
}

/**
* Dictionary of events and handlers
*/
events(): { [e: string]: string } {
return { click: '_handle_click' };
}

/**
* Handles when the image is clicked.
*/
_handle_click(event: MouseEvent): void {
event.preventDefault();
const relative_xy = this._get_relative_xy(event);
this.send({ event: 'click', click_pos: JSON.stringify(relative_xy) });
}

/**
* The default tag name.
*
Expand All @@ -103,3 +121,61 @@ export class ImageView extends DOMWidgetView {

el: HTMLImageElement;
}

export class MappedImageModel extends ImageModel {
defaults(): Backbone.ObjectHash {
return {
...super.defaults(),
_model_name: 'MappedImageModel',
_view_name: 'MappedImageView',
areas: null
};
}

static serializers = {
...ImageModel.serializers,
areas: { deserialize: unpack_models }
};
}

export class MappedImageView extends ImageView {
render(): void {
/**
* Called when view is rendered.
*/
this.pWidget.addClass('jupyter-widgets');
this.pWidget.addClass('widget-mapped-image');
const map_name = uuid();
this.el.setAttribute('usemap', '#' + map_name);
this.map = document.createElement('map');
this.map.setAttribute('name', map_name);
this.el.appendChild(this.map);
this.update(); // Set defaults.
}

update(): void {
/**
* Update the contents of this view
*
* Called when the model is changed. The model may have been
* changed by another view or by a state update from the back-end.
*/
super.update(); // Render image
const areas = this.model.get('areas');
for (let i = 0; i < areas.length; i++) {
const area = document.createElement('area');
this.map.appendChild(area);
area.outerHTML = areas[i].get('value');
}
}

/**
* Handles when the image is clicked.
* Let the browser do it!
*/
_handle_click(event: MouseEvent): void {
return;
}

map: HTMLMapElement;
}
2 changes: 1 addition & 1 deletion packages/controls/src/widget_selection.ts
Original file line number Diff line number Diff line change
Expand Up @@ -385,7 +385,7 @@ export class RadioButtonsView extends DescriptionView {
const cStyles = window.getComputedStyle(e.container);
const containerMargin = parseInt(cStyles.marginBottom, 10);

// How far we are off from a multiple of single windget lines
// How far we are off from a multiple of single widget lines
const diff = (e.el.offsetHeight + margins - containerMargin) % lineHeight;

// Apply the new adjustment
Expand Down
7 changes: 0 additions & 7 deletions packages/controls/src/widget_string.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,13 +34,6 @@ export class StringView extends DescriptionView {
this.el.classList.add('widget-inline-hbox');
}

/**
* Update the contents of this view
*
* Called when the model is changed. The model may have been
* changed by another view or by a state update from the back-end.
*/

content: HTMLDivElement;
}

Expand Down