Skip to content

Commit

Permalink
👌 IMPROVE: Add image options to glue:figure (#403)
Browse files Browse the repository at this point in the history
  • Loading branch information
chrisjsewell authored Apr 21, 2022
1 parent 2a48596 commit edb0daf
Show file tree
Hide file tree
Showing 5 changed files with 95 additions and 22 deletions.
4 changes: 2 additions & 2 deletions .github/workflows/tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -61,10 +61,10 @@ jobs:
# this is why we run `coverage xml` afterwards (required by codecov)

- name: Upload to Codecov
if: matrix.os == 'ubuntu-latest' && matrix.python-version == 3.7 && matrix.sphinx == '>=3,<4' && github.repository == 'executablebooks/MyST-NB'
if: github.repository == 'executablebooks/MyST-NB'
uses: codecov/codecov-action@v1
with:
name: myst-nb-pytests-py3.7
name: myst-nb-pytests
flags: pytests
file: ./coverage.xml
fail_ci_if_error: true
Expand Down
26 changes: 24 additions & 2 deletions docs/use/glue.md
Original file line number Diff line number Diff line change
Expand Up @@ -240,10 +240,24 @@ My rounded mean: {glue:text}`boot_mean:.2f` (95% CI: {glue:text}`boot_clo:.2f`/{
### The `glue:figure` directive

With `glue:figure` you can apply more formatting to figure like objects,
such as giving them a caption and referencable label:
such as giving them a caption and referenceable label:

:::{table} `glue:figure` directive options
| Option | Type | Description |
| ------ | ---- | ----------- |
| alt | text | Alternate text of an image |
| height | length | The desired height of an image |
| width | length or percentage | The width of an image |
| scale | percentage | The uniform scaling factor of an image |
| class | text | A space-separated list of class names for the image |
| figwidth | length or percentage | The width of the figure |
| figclass | text | A space-separated list of class names for the figure |
| name | text | referenceable label for the figure |
:::

````md
```{glue:figure} boot_fig
:alt: "Alternative title"
:figwidth: 300px
:name: "fig-boot"

Expand All @@ -252,6 +266,7 @@ This is a **caption**, with an embedded `{glue:text}` element: {glue:text}`boot_
````

```{glue:figure} boot_fig
:alt: "Alternative title"
:figwidth: 300px
:name: "fig-boot"
Expand Down Expand Up @@ -291,7 +306,14 @@ A caption for a pandas table.
The `glue:math` directive, is specific to latex math outputs
(glued variables that contain a `text/latex` mimetype),
and works similarly to the [sphinx math directive](https://www.sphinx-doc.org/en/1.8/usage/restructuredtext/directives.html#math).
For example:

:::{table} `glue:math` directive options
| Option | Type | Description |
| ------ | ---- | ----------- |
| nowrap | flag | Prevent any wrapping of the given math in a math environment |
| class | text | A space-separated list of class names |
| label or name | text | referenceable label for the figure |
:::

```{code-cell} ipython3
import sympy as sym
Expand Down
12 changes: 8 additions & 4 deletions myst_nb/core/render.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
from nbformat import NotebookNode
from typing_extensions import Protocol

from myst_nb.core.config import NbParserConfig
from myst_nb.core.loggers import DEFAULT_LOG_TYPE, LoggerType

if TYPE_CHECKING:
Expand Down Expand Up @@ -94,6 +95,11 @@ def renderer(self) -> DocutilsNbRenderer | SphinxNbRenderer:
"""The renderer this output renderer is associated with."""
return self._renderer

@property
def config(self) -> NbParserConfig:
"""The notebook parser config"""
return self._renderer.nb_config

@property
def logger(self) -> LoggerType:
"""The logger for this renderer.
Expand Down Expand Up @@ -126,7 +132,7 @@ def write_file(
:returns: URI to use for referencing the file
"""
output_folder = self.renderer.nb_config.output_folder
output_folder = self.config.output_folder
filepath = Path(output_folder).joinpath(*path)
if not output_folder:
pass # do not output anything if output_folder is not set (docutils only)
Expand Down Expand Up @@ -183,9 +189,7 @@ def render_nb_metadata(self, metadata: dict) -> dict:
"body": sanitize_script_content(json.dumps(ipywidgets_mime)),
},
)
for i, (path, kwargs) in enumerate(
self.renderer.nb_config.ipywidgets_js.items()
):
for i, (path, kwargs) in enumerate(self.config.ipywidgets_js.items()):
self.add_js_file(f"ipywidgets_{i}", path, kwargs)

return metadata
Expand Down
30 changes: 25 additions & 5 deletions myst_nb/glue/directives.py
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,7 @@ def run(self) -> List[nodes.Node]:
]

# TODO this "override" feels a bit hacky
cell_key = result.nb_renderer.renderer.nb_config.cell_render_key
cell_key = result.nb_renderer.config.cell_render_key
mime = MimeData(
"text/markdown",
result.data["text/markdown"],
Expand All @@ -134,7 +134,11 @@ def run(self) -> List[nodes.Node]:


class PasteFigureDirective(_PasteDirectiveBase):
"""A directive for pasting code outputs from notebooks, wrapped in a figure."""
"""A directive for pasting code outputs from notebooks, wrapped in a figure.
Mirrors:
https://github.com/docutils-mirror/docutils/blob/9649abee47b4ce4db51be1d90fcb1fb500fa78b3/docutils/parsers/rst/directives/images.py#95
"""

def align(argument):
return directives.choice(argument, ("left", "center", "right"))
Expand All @@ -143,6 +147,13 @@ def figwidth_value(argument):
return directives.length_or_percentage_or_unitless(argument, "px")

option_spec = {
# note we don't add converters for image options,
# since this is handled in `NbElementRenderer.render_image`
"alt": directives.unchanged,
"height": directives.unchanged,
"width": directives.unchanged,
"scale": directives.unchanged,
"class": directives.unchanged,
"figwidth": figwidth_value,
"figclass": directives.class_option,
"align": align,
Expand All @@ -155,7 +166,15 @@ def run(self):
data = retrieve_glue_data(self.document, self.arguments[0])
except RetrievalError as exc:
return [glue_warning(str(exc), self.document, self.line)]
paste_nodes = render_glue_output(data, self.document, self.line, self.source)
render: Dict[str, Any] = {}
for key in ("alt", "height", "width", "scale", "class"):
if key in self.options:
render.setdefault("image", {})[
key.replace("classes", "class")
] = self.options[key]
paste_nodes = render_glue_output(
data, self.document, self.line, self.source, render=render
)

# note: most of this is copied directly from sphinx.Figure

Expand Down Expand Up @@ -204,10 +223,11 @@ class PasteMathDirective(_PasteDirectiveBase):
"""A directive for pasting latex outputs from notebooks as math."""

option_spec = {
"label": directives.unchanged,
"name": directives.unchanged,
"class": directives.class_option,
"nowrap": directives.flag,
# these are equivalent
"label": directives.unchanged,
"name": directives.unchanged,
}

def run(self) -> List[nodes.Node]:
Expand Down
45 changes: 36 additions & 9 deletions myst_nb/glue/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,9 @@ def render_glue_output(
document: nodes.document,
line: int,
source: str,
inline=False,
*,
inline: bool = False,
render: Optional[Dict[str, Any]] = None,
) -> List[nodes.Node]:
"""Retrive the notebook output data for this glue key,
then return the docutils/sphinx nodes relevant to this data.
Expand All @@ -96,16 +98,32 @@ def render_glue_output(
:param line: The current source line number of the directive or role.
:param source: The current source path or description.
:param inline: Whether to render the output as inline (or block).
:param render: Cell-level render metadata
:returns: A tuple of (was the key found, the docutils/sphinx nodes).
"""
cell_metadata = {}
if render:
cell_metadata[data.nb_renderer.config.cell_render_key] = render
if is_sphinx(document):
_nodes = _render_output_sphinx(
data.nb_renderer, data.data, data.metadata, source, line, inline
data.nb_renderer,
data.data,
cell_metadata,
data.metadata,
source,
line,
inline,
)
else:
_nodes = _render_output_docutils(
data.nb_renderer, data.data, data.metadata, document, line, inline
data.nb_renderer,
data.data,
cell_metadata,
data.metadata,
document,
line,
inline,
)
# TODO rendering should perhaps return if it succeeded explicitly,
# and whether system_messages or not (required for roles)
Expand All @@ -115,15 +133,16 @@ def render_glue_output(
def _render_output_docutils(
nb_renderer: NbElementRenderer,
data: Dict[str, Any],
metadata: Dict[str, Any],
cell_metadata: Dict[str, Any],
output_metadata: Dict[str, Any],
document: nodes.document,
line: int,
inline=False,
) -> List[nodes.Node]:
"""Render the output in docutils (select mime priority directly)."""
mime_priority = get_mime_priority(
nb_renderer.renderer.nb_config.builder_name,
nb_renderer.renderer.nb_config.mime_priority_overrides,
nb_renderer.config.builder_name,
nb_renderer.config.mime_priority_overrides,
)
try:
mime_type = next(x for x in mime_priority if x in data)
Expand All @@ -139,7 +158,8 @@ def _render_output_docutils(
mime_data = MimeData(
mime_type,
data[mime_type],
output_metadata=metadata,
cell_metadata=cell_metadata,
output_metadata=output_metadata,
line=line,
)
if inline:
Expand All @@ -150,7 +170,8 @@ def _render_output_docutils(
def _render_output_sphinx(
nb_renderer: NbElementRenderer,
data: Dict[str, Any],
metadata: Dict[str, Any],
cell_metadata: Dict[str, Any],
output_metadata: Dict[str, Any],
source: str,
line: int,
inline=False,
Expand All @@ -161,7 +182,13 @@ def _render_output_sphinx(
for mime_type, content in data.items():
mime_container = nodes.container(mime_type=mime_type)
set_source_info(mime_container, source, line)
mime_data = MimeData(mime_type, content, output_metadata=metadata, line=line)
mime_data = MimeData(
mime_type,
content,
cell_metadata=cell_metadata,
output_metadata=output_metadata,
line=line,
)
if inline:
_nodes = nb_renderer.render_mime_type_inline(mime_data)
else:
Expand Down

0 comments on commit edb0daf

Please sign in to comment.