Skip to content
Open
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
51 changes: 41 additions & 10 deletions branca/element.py
Original file line number Diff line number Diff line change
Expand Up @@ -328,7 +328,8 @@ class Figure(Element):
A percentage defining the aspect ratio of the Figure.
It will be ignored if height is not None.
title : str, default None
Figure title.
Figure title. Also used as the ``title`` attribute (accessible
name) of the iframe when the Figure is displayed in a notebook.
figsize : tuple of two int, default None
If you're a matplotlib addict, you can overwrite width and
height. Values will be converted into pixels in using 60 dpi.
Expand Down Expand Up @@ -412,24 +413,37 @@ def render(self, **kwargs) -> str:
def _repr_html_(self, **kwargs) -> str:
"""Displays the Figure in a Jupyter notebook."""
html = escape(self.render(**kwargs))
# Give the iframe an accessible name when a title is set, so it is
# not flagged by "frames must have an accessible name" audits.
title_attr = f' title="{escape(self.title)}"' if self.title else ""
if self.height is None:
iframe = (
'<div style="width:{width};">'
'<div style="position:relative;width:100%;height:0;padding-bottom:{ratio};">' # noqa
'<span style="color:#565656">Make this Notebook Trusted to load map: File -> Trust Notebook</span>' # noqa
'<iframe srcdoc="{html}" style="position:absolute;width:100%;height:100%;left:0;top:0;' # noqa
'<iframe srcdoc="{html}"{title_attr} style="position:absolute;width:100%;height:100%;left:0;top:0;' # noqa
'border:none !important;" '
"allowfullscreen webkitallowfullscreen mozallowfullscreen>"
"</iframe>"
"</div></div>"
).format(html=html, width=self.width, ratio=self.ratio)
).format(
html=html,
width=self.width,
ratio=self.ratio,
title_attr=title_attr,
)
else:
iframe = (
'<iframe srcdoc="{html}" width="{width}" height="{height}"'
'<iframe srcdoc="{html}"{title_attr} width="{width}" height="{height}" '
'style="border:none !important;" '
'"allowfullscreen" "webkitallowfullscreen" "mozallowfullscreen">'
"allowfullscreen webkitallowfullscreen mozallowfullscreen>"
"</iframe>"
).format(html=html, width=self.width, height=self.height)
).format(
html=html,
width=self.width,
height=self.height,
title_attr=title_attr,
)
return iframe

def add_subplot(self, x: int, y: int, n: int, margin: float = 0.05) -> "Div":
Expand Down Expand Up @@ -640,6 +654,10 @@ class IFrame(Element):
height. Values will be converted into pixels in using 60 dpi.
For example figsize=(10, 5) will result in
width="600px", height="300px".
title : str, default None
Value for the iframe's ``title`` attribute, used as the frame's
accessible name. Set it to satisfy accessibility audits that
require every frame to have an accessible name.
"""

def __init__(
Expand All @@ -649,10 +667,12 @@ def __init__(
height: Optional[str] = None,
ratio: str = "60%",
figsize: Optional[Tuple[int, int]] = None,
title: Optional[str] = None,
):
super().__init__()
self._name = "IFrame"

self.title = title
self.width = width
self.height = height
self.ratio = ratio
Expand All @@ -671,21 +691,32 @@ def render(self, **kwargs) -> str:
html = "data:text/html;charset=utf-8;base64," + base64.b64encode(
html.encode("utf8"),
).decode("utf8")
title_attr = f' title="{escape(self.title)}"' if self.title else ""

if self.height is None:
iframe = (
'<div style="width:{width};">'
'<div style="position:relative;width:100%;height:0;padding-bottom:{ratio};">' # noqa
'<iframe src="{html}" style="position:absolute;width:100%;height:100%;left:0;top:0;' # noqa
'<iframe src="{html}"{title_attr} style="position:absolute;width:100%;height:100%;left:0;top:0;' # noqa
'border:none !important;">'
"</iframe>"
"</div></div>"
).format(html=html, width=self.width, ratio=self.ratio)
).format(
html=html,
width=self.width,
ratio=self.ratio,
title_attr=title_attr,
)
else:
iframe = (
'<iframe src="{html}" width="{width}" style="border:none !important;" '
'<iframe src="{html}"{title_attr} width="{width}" style="border:none !important;" '
'height="{height}"></iframe>'
).format(html=html, width=self.width, height=self.height)
).format(
html=html,
width=self.width,
height=self.height,
title_attr=title_attr,
)
return iframe


Expand Down
76 changes: 76 additions & 0 deletions tests/test_element.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
"""
Tests for branca.element
------------------------
"""

from html.parser import HTMLParser

import branca.element as elem


class _IframeAttrs(HTMLParser):
"""Collect the attributes of every <iframe> start tag."""

def __init__(self):
super().__init__()
self.iframes = []

def handle_starttag(self, tag, attrs):
if tag == "iframe":
self.iframes.append(dict(attrs))


def _iframe_attrs(html):
parser = _IframeAttrs()
parser.feed(html)
assert len(parser.iframes) == 1
return parser.iframes[0]


def test_figure_repr_html_fullscreen_attrs_without_height():
attrs = _iframe_attrs(elem.Figure()._repr_html_())
for name in ("allowfullscreen", "webkitallowfullscreen", "mozallowfullscreen"):
assert name in attrs


def test_figure_repr_html_fullscreen_attrs_with_height():
# The height branch used to quote the boolean attributes, producing
# <iframe ... "allowfullscreen" ...>, so the attribute names came out
# wrapped in literal quotes and the browser ignored them.
attrs = _iframe_attrs(elem.Figure(height="400px")._repr_html_())
for name in ("allowfullscreen", "webkitallowfullscreen", "mozallowfullscreen"):
assert name in attrs, f"{name!r} missing; got {sorted(attrs)}"
assert attrs["height"] == "400px"
assert attrs["width"] == "100%"


def test_figure_iframe_title_absent_by_default():
for height in (None, "400px"):
attrs = _iframe_attrs(elem.Figure(height=height)._repr_html_())
assert "title" not in attrs


def test_figure_iframe_title_set():
for height in (None, "400px"):
attrs = _iframe_attrs(elem.Figure(height=height, title="My Map")._repr_html_())
assert attrs["title"] == "My Map"


def test_figure_iframe_title_is_escaped():
# A title with a double quote must not break out of the attribute.
attrs = _iframe_attrs(elem.Figure(title='a "b" <c>')._repr_html_())
assert attrs["title"] == 'a "b" <c>'


def test_iframe_title_absent_by_default():
for height in (None, "300px"):
attrs = _iframe_attrs(elem.IFrame("<p>x</p>", height=height).render())
assert "title" not in attrs


def test_iframe_title_set():
for height in (None, "300px"):
attrs = _iframe_attrs(
elem.IFrame("<p>x</p>", height=height, title="Popup").render(),
)
assert attrs["title"] == "Popup"