Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
ce44314
draft changes to gallery and legacy recipes
bettina-gier May 22, 2025
dc9ce25
add legacy recipe page
bettina-gier May 23, 2025
fb0f11f
fav image in better comment style
bettina-gier May 23, 2025
720e999
docutils parser, html layout, multiple figures per recipe
lukruh May 27, 2025
ed6b995
skip recipes without label in first line
lukruh May 27, 2025
06ccafc
adapt psyplot to new legacy structure
bettina-gier May 28, 2025
d5a93b0
improve look and feel, extra css file
lukruh May 28, 2025
2652de0
Merge branch 'gallery-updates' of github.com:ESMValGroup/ESMValTool i…
lukruh May 28, 2025
946c676
readd psyplot legacy
bettina-gier May 28, 2025
6f59f58
Fixing figure captions
bettina-gier May 28, 2025
641134b
responsive layout
lukruh May 28, 2025
96f5aea
fix errors
bettina-gier May 28, 2025
6c88370
Merge branch 'gallery-updates' of github.com:ESMValGroup/ESMValTool i…
bettina-gier May 28, 2025
d5bd2a5
test 2 gallery plots from 1 recipe file
bettina-gier May 28, 2025
62453cf
hide docutils parsing errors, fine tune plot width
lukruh May 28, 2025
1a9945e
fix duplicate figure name
bettina-gier May 28, 2025
a9c40ee
hide caption numbers in gallery
lukruh Jun 2, 2025
d20a171
Merge branch 'main' into gallery-updates
bettina-gier Jun 2, 2025
953c1da
Merge branch 'main' into gallery-updates
bettina-gier Jun 5, 2025
49723cb
add gallery tags to documentation
bettina-gier Jun 5, 2025
46e5e8f
add gallery tags to documentation
bettina-gier Jun 5, 2025
73e0956
Merge branch 'main' into gallery-updates
bettina-gier Jun 17, 2025
8903f82
Update doc/sphinx/source/generate_gallery.py
lukruh Jun 18, 2025
5e50ca7
adjust mobile breakpoints without sidebar
lukruh Jun 19, 2025
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
46 changes: 46 additions & 0 deletions doc/sphinx/source/_static/custom.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
.gallery {
display: grid;
grid-template-columns: repeat(3, minmax(180px, 1fr));
gap: 1em;
}

@media (max-width: 620px) {
.gallery {
grid-template-columns: repeat(2, minmax(180px, 1fr));
}
}
@media (max-width: 420px) {
.gallery {
grid-template-columns: repeat(1, minmax(180px, 1fr));
}
}

.gallery figure:hover {
background-color: var(--pst-color-surface);
}

.gallery figure img {
max-height: 220px;
object-fit: cover;
overflow: hidden;
}

.gallery figure figcaption {
color: var(--pst-color-text-muted);
line-height: 1.2;
text-decoration: none;
}

.gallery figure figcaption a {
text-decoration: none;
color: var(--pst-color-text);
}

.gallery figure figcaption a:hover {
text-decoration: underline;
color: var(--pst-color-link-hover);
}

.gallery figure span.caption-number {
display: none;
}
20 changes: 20 additions & 0 deletions doc/sphinx/source/community/diagnostic.rst
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,26 @@ A resolution of 150 `dpi <https://en.wikipedia.org/wiki/Dots_per_inch>`_ is
recommended for these image files, as this is high enough for the images to look
good on the documentation webpage, but not so high that the files become large.

By default, the first example image will be used for the automatically
generated gallery. To select a specific image for the gallery, you can use the
following syntax for an arbitrary amount of images:

.. code-block:: rst

.. _fig_name:
..
gallery
.. figure:: /recipes/figure/recipe_name/figure-name.png

To not have any figure appear in the gallery, you can include a

.. code-block:: rst

..
no-gallery

anywhere in your recipe documentation file.

In the recipe
-------------
Fill in the ``documentation`` section of the recipe as described in
Expand Down
7 changes: 6 additions & 1 deletion doc/sphinx/source/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -203,7 +203,12 @@
# Add any paths that contain custom static files (such as style sheets) here,
# relative to this directory. They are copied after the builtin static files,
# so a file named "default.css" will overwrite the builtin "default.css".
html_static_path = ["figures/ESMValTool-logo-2-dark.png"]
html_static_path = [
"figures/ESMValTool-logo-2-dark.png",
"_static",
]

html_css_files = ["custom.css"]

# Add any extra paths that contain custom files (such as robots.txt or
# .htaccess) here, relative to this directory. These files are copied
Expand Down
208 changes: 100 additions & 108 deletions doc/sphinx/source/generate_gallery.py
Original file line number Diff line number Diff line change
@@ -1,137 +1,129 @@
#!/usr/bin/env python
"""Create gallery with all available recipes."""

import html
import os
from pathlib import Path

from docutils import nodes
from docutils.core import publish_doctree

RECIPE_DIR = "recipes"
OUT_PATH = os.path.abspath("gallery.rst")
HEADER = (
":html_theme.sidebar_secondary.remove:\n\n"
".. DO NOT MODIFY! THIS PAGE IS AUTOGENERATED!\n\n"
"#######\nGallery\n#######\n\n"
"This section shows example plots produced by ESMValTool. For more "
"information, click on the footnote below the image. "
"information, follow the links in the figure captions."
"A website displaying results produced with the latest release of "
"ESMValTool for all available recipes can be accessed `here "
"<https://esmvaltool.dkrz.de/shared/esmvaltool/stable_release/>`_."
"\n\n"
)
WIDTH = ":width: 90%"
FIGURE_STR = ".. figure::"
IMAGE_STR = " image:: "
TABLE_SEP = (
"+---------------------------------------------------"
"+---------------------------------------------------+\n"
)
EMPTY_TABLE = (
"| "
"| |\n"
)
CELL_WIDTH = 50
MAX_CAPTION_LENGTH = 100

START_GALLERY = '.. raw:: html\n\n <div class="gallery">\n\n'

def _get_figure_index(file_content):
"""Get index of figure in text."""
if FIGURE_STR in file_content:
return file_content.index(FIGURE_STR) + len(FIGURE_STR)
if IMAGE_STR in file_content:
return file_content.index(IMAGE_STR) + len(IMAGE_STR)
raise ValueError("File does not contain image")
END_GALLERY = "\n.. raw:: html\n\n </div>\n\n"

FIGURE_HTML = (
".. figure:: {uri}\n :width: 100%\n\n :ref:`{caption} <{link}>`\n\n"
)

def _get_next_row(filenames, file_contents):
"""Get next row."""
figure_idx = [_get_figure_index(content) for content in file_contents]
figure_paths = [
file_contents[idx][fig_idx:].split("\n")[0].strip()
for (idx, fig_idx) in enumerate(figure_idx)

def _has_gallery_marker(node):
"""Wether the node is preceeded by a ``.. gallery`` comment."""
siblings = list(node.parent)
idx = siblings.index(node)
if idx <= 0:
return False
prev_node = siblings[idx - 1]
if not isinstance(prev_node, nodes.comment):
return False
return prev_node.astext().lower().strip().startswith("gallery")


def _is_excluded_from_gallery(node):
"""Wether the node has a no-gallery marker."""
comments = node.traverse(nodes.comment)
for comment in comments:
if comment.astext().lower().strip() == "no-gallery":
return True
return False


def _get_figures_from_file(fname):
"""Get marked, no or first figure from documentation page."""
with (Path(RECIPE_DIR) / fname).open() as f:
content = f.read()
tree = publish_doctree(content, settings_overrides={"report_level": 5})
try:
link = content.split("\n")[0].split(" ")[1][1:-1]
except IndexError:
print(f"No label found in first line of {fname}. Skipping")
return []
if _is_excluded_from_gallery(tree): # ignore files with no-gallery marker
return []
figures = tree.traverse(nodes.figure)
for fig in figures:
fig["link"] = link # add doc page link to figure
marked_figures = [f for f in figures if _has_gallery_marker(f)]
if len(marked_figures) > 0: # consider all figures with gallery marker
return marked_figures
if len(figures) > 0: # select first figure if nothing is marked
return [figures[0]]
return []


def _get_data_from_figure(figure):
image = figure.traverse(nodes.image)[0]
try:
caption = figure.traverse(nodes.caption)[0].astext().strip()
except IndexError:
caption = "No caption available"
if len(caption) > MAX_CAPTION_LENGTH:
caption = caption[:MAX_CAPTION_LENGTH] + "..."
return {
"uri": html.escape(image["uri"]),
"caption": html.escape(caption.replace("\n", " ")),
"link": html.escape(figure["link"]),
}


def _find_recipes(root_dir, exclude_dirs):
return [
path.relative_to(root_dir)
for path in Path(root_dir).rglob("recipe_*.rst")
if not any(
path.parents[0].name.startswith(exclude)
for exclude in exclude_dirs
)
]
subst = [f"|{os.path.splitext(filename)[0]}|" for filename in filenames]
link = [file_contents[0].split()[1][1:-1]]
if figure_paths[1] == "":
subst[1] = ""
link.append("")
else:
link.append(file_contents[1].split()[1][1:-1])

# Build table
row = ""
refs = ""
row += TABLE_SEP
row += f"| {subst[0].ljust(CELL_WIDTH)}| {subst[1].ljust(CELL_WIDTH)}|\n"
row += EMPTY_TABLE
left_col = "[#]_".ljust(CELL_WIDTH)
if figure_paths[1] == "":
right_col = "".ljust(CELL_WIDTH)
else:
right_col = "[#]_".ljust(CELL_WIDTH)
row += f"| {left_col}| {right_col}|\n"

# Build refs
for idx, path in enumerate(figure_paths):
if path == "":
continue
refs += f".. {subst[idx]} image:: {path}\n"
refs += f" {WIDTH}\n"
refs += "\n"
refs += f".. [#] :ref:`{link[idx]}`\n"
refs += "\n"

return (row, refs)


def _generate_rst_file(fname, data):
"""Generate rst file from data."""
output = ""
output += HEADER
output += START_GALLERY
for figure_data in data:
output += FIGURE_HTML.format(**figure_data)
output += END_GALLERY
with open(fname, "w") as f:
f.write(output)
print(f"Wrote {fname}")


def main():
"""Generate gallery for recipe plots."""
print(f"Generating gallery at {OUT_PATH}")
left_col = True
table = ""
refs = ""
filenames = []
file_contents = []
for filename in sorted(os.listdir(RECIPE_DIR)):
if not filename.startswith("recipe_"):
continue
if not filename.endswith(".rst"):
continue
with open(os.path.join(RECIPE_DIR, filename)) as in_file:
recipe_file = in_file.read()
if FIGURE_STR not in recipe_file and IMAGE_STR not in recipe_file:
print(f"INFO: {filename} does not contain an image, skipping")
continue
if not recipe_file.startswith(".."):
print(
f"INFO: {filename} does not contain reference at top, skipping"
)
continue

# Get next row
if left_col:
left_col = False
filenames = [filename]
file_contents = [recipe_file]
continue
else:
left_col = True
filenames.append(filename)
file_contents.append(recipe_file)
new_row = _get_next_row(filenames, file_contents)
table += new_row[0]
refs += new_row[1]

# Last row
if len(filenames) == 1:
filenames.append("")
file_contents.append(f"{FIGURE_STR}\n")
new_row = _get_next_row(filenames, file_contents)
table += new_row[0]
refs += new_row[1]
table += TABLE_SEP
table += "\n"

# Write file
whole_file = HEADER + table + refs
with open(OUT_PATH, "w") as out_file:
print(whole_file, file=out_file)
print(f"Wrote {OUT_PATH}")
figures = []
fnames = _find_recipes(RECIPE_DIR, ["legacy", "figures"])
for filename in sorted(fnames):
figures.extend(_get_figures_from_file(filename))
data = [_get_data_from_figure(f) for f in figures]
_generate_rst_file(OUT_PATH, data)


if __name__ == "__main__":
Expand Down
8 changes: 2 additions & 6 deletions doc/sphinx/source/recipes/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,6 @@ IPCC
recipe_ipccwg1ar6ch3
recipe_ipccwg1ar5ch9
recipe_collins13ipcc
recipe_examples

Land
^^^^
Expand Down Expand Up @@ -164,12 +163,9 @@ require a legacy version of ESMValTool to run.


.. toctree::
:maxdepth: 1
:maxdepth: 2

recipe_psyplot
recipe_rainfarm
recipe_schlund20jgr
recipe_spei
legacy_recipe_list


Broken recipe list
Expand Down
16 changes: 16 additions & 0 deletions doc/sphinx/source/recipes/legacy_recipe_list.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
.. _legacy_recipe_list:

Legacy Recipes
==============

Recipes that have been retired and are included for
documentation purposes only. Typically, these recipes
require a legacy version of ESMValTool to run.

.. toctree::
:maxdepth: 1

legacy/recipe_psyplot
legacy/recipe_rainfarm
legacy/recipe_schlund20jgr
legacy/recipe_spei
2 changes: 1 addition & 1 deletion doc/sphinx/source/recipes/recipe_capacity_factor.rst
Original file line number Diff line number Diff line change
Expand Up @@ -75,4 +75,4 @@ Example plots
:align: center
:width: 14cm

Wind capacity factor for five turbines: Enercon E70 (top-left), Gamesa G80 (middle-top), Gamesa G87 (top-right), Vestas V100 (bottom-left) and Vestas V110 (middle-bottom) using the IPSL-CM5A-MR simulations for the r1p1i1 ensemble for the rcp8.5 scenario during the period 2021-2050.
Wind capacity factor for five turbines: Enercon E70 (top-left), Gamesa G80 (middle-top), Gamesa G87 (top-right), Vestas V100 (bottom-left) and Vestas V110 (middle-bottom) using the IPSL-CM5A-MR simulations for the r1p1i1 ensemble for the rcp8.5 scenario during the period 2021-2050.
10 changes: 8 additions & 2 deletions doc/sphinx/source/recipes/recipe_climate_change_hotspot.rst
Original file line number Diff line number Diff line change
Expand Up @@ -190,11 +190,17 @@ Example plots
.. figure:: /recipes/figures/cos22esd/tas_45.png
:align: center

Mediterranean region temperature change differences against the mean global temperature
change. The changes for the periods 2041–2060 (first and third
row) and 2081–2100 (second and fourth row) are evaluated against 1986–2005 mean. The differences are shown for the CMIP5 (left)
and CMIP6 (right) DJF, JJA and annual mean projections (columns) under the high emission scenario RCP8.5 and SSP5-8.5 respectively. N
indicates the number of models included in the ensemble mean.

.. figure:: /recipes/figures/cos22esd/pr_45.png
:align: center

Mediterranean region temperature (upper rows) and precipitation (lower rows) change differences against the mean global temperature
change and the mean 30–45º  N latitudinal belt precipitation change respectively. The changes for the periods 2041–2060 (first and third
Mediterranean region precipitation change differences against the mean 30–45º  N latitudinal belt precipitation change respectively.
The changes for the periods 2041–2060 (first and third
row) and 2081–2100 (second and fourth row) are evaluated against 1986–2005 mean. The differences are shown for the CMIP5 (left)
and CMIP6 (right) DJF, JJA and annual mean projections (columns) under the high emission scenario RCP8.5 and SSP5-8.5 respectively. N
indicates the number of models included in the ensemble mean.
Expand Down
2 changes: 1 addition & 1 deletion doc/sphinx/source/recipes/recipe_combined_indices.rst
Original file line number Diff line number Diff line change
Expand Up @@ -77,4 +77,4 @@ Example plots
:align: center
:width: 14cm

Time series of the standardized sea surface temperature (tos) area averaged over the Nino 3.4 region during the boreal winter (December-January-February). The time series correspond to the MPI-ESM-MR (red) and BCC-CSM1-1 (blue) models and their mean (black) during the period 1950-2005 for the ensemble r1p1i1 of the historical simulations.
Time series of the standardized sea surface temperature (tos) area averaged over the Nino 3.4 region during the boreal winter (December-January-February). The time series correspond to the MPI-ESM-MR (red) and BCC-CSM1-1 (blue) models and their mean (black) during the period 1950-2005 for the ensemble r1p1i1 of the historical simulations.
Loading