xopr.xarray_accessor.formatting_html

  1# This is a very lightly modified version of:
  2# https://github.com/pydata/xarray/blob/8796d55a8506f2d3bd50a265ecbc72e3f2f76f18/xarray/core/formatting_html.py
  3# It is designed to implement a very simple nested dictionary _repr_html_
  4# Eventually, we should either figure out how to more cleanly integreate a custom _repr_html_
  5# or make it generic enough to get a PR into xarray.
  6
  7import uuid
  8from collections import OrderedDict
  9from collections.abc import Mapping
 10from functools import lru_cache, partial
 11from html import escape
 12from importlib.resources import files
 13from math import ceil
 14from typing import Literal
 15
 16from xarray.core.datatree import DataTree
 17from xarray.core.formatting import (
 18    inherited_vars,
 19    inline_index_repr,
 20    inline_variable_array_repr,
 21    short_data_repr,
 22)
 23from xarray.core.options import OPTIONS, _get_boolean_with_default
 24
 25STATIC_FILES = (
 26    ("xarray.static.html", "icons-svg-inline.html"),
 27    ("xopr.xarray_accessor", "style.css"),
 28)
 29
 30
 31@lru_cache(None)
 32def _load_static_files():
 33    """Lazily load the resource files into memory the first time they are needed"""
 34    return [
 35        files(package).joinpath(resource).read_text(encoding="utf-8")
 36        for package, resource in STATIC_FILES
 37    ]
 38
 39
 40def short_data_repr_html(array) -> str:
 41    """Format "data" for DataArray and Variable."""
 42    internal_data = getattr(array, "variable", array)._data
 43    if hasattr(internal_data, "_repr_html_"):
 44        return internal_data._repr_html_()
 45    text = escape(short_data_repr(array))
 46    return f"<pre>{text}</pre>"
 47
 48
 49def format_dims(dim_sizes, dims_with_index) -> str:
 50    if not dim_sizes:
 51        return ""
 52
 53    dim_css_map = {
 54        dim: " class='xr-has-index'" if dim in dims_with_index else ""
 55        for dim in dim_sizes
 56    }
 57
 58    dims_li = "".join(
 59        f"<li><span{dim_css_map[dim]}>{escape(str(dim))}</span>: {size}</li>"
 60        for dim, size in dim_sizes.items()
 61    )
 62
 63    return f"<ul class='xr-dim-list'>{dims_li}</ul>"
 64
 65
 66def summarize_attrs_inner(attrs) -> str:
 67    attrs_dl = ""
 68    for k, v in attrs.items():
 69        if isinstance(v, dict):
 70            attrs_dl += f"<dt><span>{escape(str(k))} :</span></dt><dd>"
 71            attrs_dl += summarize_attrs_inner(v)
 72            attrs_dl += "</dd>"
 73        else:
 74            attrs_dl += f"<dt><span>{escape(str(k))} :</span></dt><dd>{escape(str(v))}</dd>"
 75
 76    return attrs_dl
 77
 78
 79def summarize_attrs(attrs) -> str:
 80    # === Orig:
 81    # attrs_dl = "".join(
 82    #     f"<dt><span>{escape(str(k))} :</span></dt><dd>{escape(str(v))}</dd>"
 83    #     for k, v in attrs.items()
 84    # )
 85    # === V2:
 86    # attrs_dl = ""
 87    # for k, v in attrs.items():
 88    #     if isinstance(v, dict):
 89    #         attrs_dl += "<span class='xr-section-item'>"
 90    #         attrs_dl += collapsible_section(
 91    #             name=escape(str(k)),
 92    #             inline_details="",
 93    #             details=summarize_attrs(v),
 94    #             n_items=len(v),
 95    #             enabled=True,
 96    #             collapsed=True,
 97    #         )
 98    #         attrs_dl += "</span>"
 99    #     else:
100    #         attrs_dl += f"<dt><span>{escape(str(k))} :</span></dt><dd>{escape(str(v))}</dd>"
101
102    attrs_dl = ""
103    for k, v in attrs.items():
104        if isinstance(v, dict):
105            attr_id = "attrs-" + str(uuid.uuid4())
106
107            attrs_dl += f"<div class='xr-attr-item'>"
108            attrs_dl += f"<input id='{attr_id}' class='xr-attr-in' type='checkbox'>"
109            attrs_dl += f"<label class='xr-attr-nested' for='{attr_id}'>{escape(str(k))} : "
110            attrs_dl += f"<span>({len(v)})</span></label>"
111            attrs_dl += "<span class='xr-attr-nested-inner'>"
112            attrs_dl += summarize_attrs(v)
113            attrs_dl += "</span></div>"
114        else:
115            attrs_dl += f"<dt><span>{escape(str(k))} :</span></dt><dd>{escape(str(v))}</dd>"
116
117
118    return f"<dl class='xr-attrs'>{attrs_dl}</dl>"
119
120
121def _icon(icon_name) -> str:
122    # icon_name should be defined in xarray/static/html/icon-svg-inline.html
123    return (
124        f"<svg class='icon xr-{icon_name}'><use xlink:href='#{icon_name}'></use></svg>"
125    )
126
127
128def summarize_variable(name, var, is_index=False, dtype=None) -> str:
129    variable = var.variable if hasattr(var, "variable") else var
130
131    cssclass_idx = " class='xr-has-index'" if is_index else ""
132    dims_str = f"({', '.join(escape(dim) for dim in var.dims)})"
133    name = escape(str(name))
134    dtype = dtype or escape(str(var.dtype))
135
136    # "unique" ids required to expand/collapse subsections
137    attrs_id = "attrs-" + str(uuid.uuid4())
138    data_id = "data-" + str(uuid.uuid4())
139    disabled = "" if len(var.attrs) else "disabled"
140
141    preview = escape(inline_variable_array_repr(variable, 35))
142    attrs_ul = summarize_attrs(var.attrs)
143    data_repr = short_data_repr_html(variable)
144
145    attrs_icon = _icon("icon-file-text2")
146    data_icon = _icon("icon-database")
147
148    return (
149        f"<div class='xr-var-name'><span{cssclass_idx}>{name}</span></div>"
150        f"<div class='xr-var-dims'>{dims_str}</div>"
151        f"<div class='xr-var-dtype'>{dtype}</div>"
152        f"<div class='xr-var-preview xr-preview'>{preview}</div>"
153        f"<input id='{attrs_id}' class='xr-var-attrs-in' "
154        f"type='checkbox' {disabled}>"
155        f"<label for='{attrs_id}' title='Show/Hide attributes'>"
156        f"{attrs_icon}</label>"
157        f"<input id='{data_id}' class='xr-var-data-in' type='checkbox'>"
158        f"<label for='{data_id}' title='Show/Hide data repr'>"
159        f"{data_icon}</label>"
160        f"<div class='xr-var-attrs'>{attrs_ul}</div>"
161        f"<div class='xr-var-data'>{data_repr}</div>"
162    )
163
164
165def summarize_coords(variables) -> str:
166    li_items = []
167    for k, v in variables.items():
168        li_content = summarize_variable(k, v, is_index=k in variables.xindexes)
169        li_items.append(f"<li class='xr-var-item'>{li_content}</li>")
170
171    vars_li = "".join(li_items)
172
173    return f"<ul class='xr-var-list'>{vars_li}</ul>"
174
175
176def summarize_vars(variables) -> str:
177    vars_li = "".join(
178        f"<li class='xr-var-item'>{summarize_variable(k, v)}</li>"
179        for k, v in variables.items()
180    )
181
182    return f"<ul class='xr-var-list'>{vars_li}</ul>"
183
184
185def short_index_repr_html(index) -> str:
186    if hasattr(index, "_repr_html_"):
187        return index._repr_html_()
188
189    return f"<pre>{escape(repr(index))}</pre>"
190
191
192def summarize_index(coord_names, index) -> str:
193    name = "<br>".join([escape(str(n)) for n in coord_names])
194
195    index_id = f"index-{uuid.uuid4()}"
196    preview = escape(inline_index_repr(index, max_width=70))
197    details = short_index_repr_html(index)
198
199    data_icon = _icon("icon-database")
200
201    return (
202        f"<div class='xr-index-name'><div>{name}</div></div>"
203        f"<div class='xr-index-preview'>{preview}</div>"
204        # need empty input + label here to conform to the fixed CSS grid layout
205        f"<input type='checkbox' disabled/>"
206        f"<label></label>"
207        f"<input id='{index_id}' class='xr-index-data-in' type='checkbox'/>"
208        f"<label for='{index_id}' title='Show/Hide index repr'>{data_icon}</label>"
209        f"<div class='xr-index-data'>{details}</div>"
210    )
211
212
213def summarize_indexes(indexes) -> str:
214    indexes_li = "".join(
215        f"<li class='xr-var-item'>{summarize_index(v, i)}</li>"
216        for v, i in indexes.items()
217    )
218    return f"<ul class='xr-var-list'>{indexes_li}</ul>"
219
220
221def collapsible_section(
222    name, inline_details="", details="", n_items=None, enabled=True, collapsed=False
223) -> str:
224    # "unique" id to expand/collapse the section
225    data_id = "section-" + str(uuid.uuid4())
226
227    has_items = n_items is not None and n_items
228    n_items_span = "" if n_items is None else f" <span>({n_items})</span>"
229    enabled = "" if enabled and has_items else "disabled"
230    collapsed = "" if collapsed or not has_items else "checked"
231    tip = " title='Expand/collapse section'" if enabled else ""
232
233    return (
234        f"<input id='{data_id}' class='xr-section-summary-in' "
235        f"type='checkbox' {enabled} {collapsed}>"
236        f"<label for='{data_id}' class='xr-section-summary' {tip}>"
237        f"{name}:{n_items_span}</label>"
238        f"<div class='xr-section-inline-details'>{inline_details}</div>"
239        f"<div class='xr-section-details'>{details}</div>"
240    )
241
242
243def _mapping_section(
244    mapping,
245    name,
246    details_func,
247    max_items_collapse,
248    expand_option_name,
249    enabled=True,
250    max_option_name: Literal["display_max_children"] | None = None,
251) -> str:
252    n_items = len(mapping)
253    expanded = _get_boolean_with_default(
254        expand_option_name, n_items < max_items_collapse
255    )
256    collapsed = not expanded
257
258    inline_details = ""
259    if max_option_name and max_option_name in OPTIONS:
260        max_items = int(OPTIONS[max_option_name])
261        if n_items > max_items:
262            inline_details = f"({max_items}/{n_items})"
263
264    return collapsible_section(
265        name,
266        inline_details=inline_details,
267        details=details_func(mapping),
268        n_items=n_items,
269        enabled=enabled,
270        collapsed=collapsed,
271    )
272
273
274def dim_section(obj) -> str:
275    dim_list = format_dims(obj.sizes, obj.xindexes.dims)
276
277    return collapsible_section(
278        "Dimensions", inline_details=dim_list, enabled=False, collapsed=True
279    )
280
281
282def array_section(obj) -> str:
283    # "unique" id to expand/collapse the section
284    data_id = "section-" + str(uuid.uuid4())
285    collapsed = (
286        "checked"
287        if _get_boolean_with_default("display_expand_data", default=True)
288        else ""
289    )
290    variable = getattr(obj, "variable", obj)
291    preview = escape(inline_variable_array_repr(variable, max_width=70))
292    data_repr = short_data_repr_html(obj)
293    data_icon = _icon("icon-database")
294
295    return (
296        "<div class='xr-array-wrap'>"
297        f"<input id='{data_id}' class='xr-array-in' type='checkbox' {collapsed}>"
298        f"<label for='{data_id}' title='Show/hide data repr'>{data_icon}</label>"
299        f"<div class='xr-array-preview xr-preview'><span>{preview}</span></div>"
300        f"<div class='xr-array-data'>{data_repr}</div>"
301        "</div>"
302    )
303
304
305coord_section = partial(
306    _mapping_section,
307    name="Coordinates",
308    details_func=summarize_coords,
309    max_items_collapse=25,
310    expand_option_name="display_expand_coords",
311)
312
313datavar_section = partial(
314    _mapping_section,
315    name="Data variables",
316    details_func=summarize_vars,
317    max_items_collapse=15,
318    expand_option_name="display_expand_data_vars",
319)
320
321index_section = partial(
322    _mapping_section,
323    name="Indexes",
324    details_func=summarize_indexes,
325    max_items_collapse=0,
326    expand_option_name="display_expand_indexes",
327)
328
329attr_section = partial(
330    _mapping_section,
331    name="Attributes",
332    details_func=summarize_attrs,
333    max_items_collapse=10,
334    expand_option_name="display_expand_attrs",
335)
336
337
338def _get_indexes_dict(indexes):
339    return {
340        tuple(index_vars.keys()): idx for idx, index_vars in indexes.group_by_index()
341    }
342
343
344def _obj_repr(obj, header_components, sections):
345    """Return HTML repr of an xarray object.
346
347    If CSS is not injected (untrusted notebook), fallback to the plain text repr.
348
349    """
350    header = f"<div class='xr-header'>{''.join(h for h in header_components)}</div>"
351    sections = "".join(f"<li class='xr-section-item'>{s}</li>" for s in sections)
352
353    icons_svg, css_style = _load_static_files()
354    return (
355        "<div>"
356        f"{icons_svg}<style>{css_style}</style>"
357        f"<pre class='xr-text-repr-fallback'>{escape(repr(obj))}</pre>"
358        "<div class='xr-wrap' style='display:none'>"
359        f"{header}"
360        f"<ul class='xr-sections'>{sections}</ul>"
361        "</div>"
362        "</div>"
363    )
364
365
366def array_repr(arr) -> str:
367    dims = OrderedDict((k, v) for k, v in zip(arr.dims, arr.shape, strict=True))
368    if hasattr(arr, "xindexes"):
369        indexed_dims = arr.xindexes.dims
370    else:
371        indexed_dims = {}
372
373    obj_type = f"xarray.{type(arr).__name__}"
374    arr_name = f"'{arr.name}'" if getattr(arr, "name", None) else ""
375
376    header_components = [
377        f"<div class='xr-obj-type'>{obj_type}</div>",
378        f"<div class='xr-array-name'>{arr_name}</div>",
379        format_dims(dims, indexed_dims),
380    ]
381
382    sections = [array_section(arr)]
383
384    if hasattr(arr, "coords"):
385        sections.append(coord_section(arr.coords))
386
387    if hasattr(arr, "xindexes"):
388        indexes = _get_indexes_dict(arr.xindexes)
389        sections.append(index_section(indexes))
390
391    sections.append(attr_section(arr.attrs))
392
393    return _obj_repr(arr, header_components, sections)
394
395
396def dataset_repr(ds) -> str:
397    obj_type = f"xarray.{type(ds).__name__}"
398
399    header_components = [f"<div class='xr-obj-type'>{escape(obj_type)}</div>"]
400
401    sections = [
402        dim_section(ds),
403        coord_section(ds.coords),
404        datavar_section(ds.data_vars),
405        index_section(_get_indexes_dict(ds.xindexes)),
406        attr_section(ds.attrs),
407    ]
408
409    return _obj_repr(ds, header_components, sections)
410
411
412def summarize_datatree_children(children: Mapping[str, DataTree]) -> str:
413    MAX_CHILDREN = OPTIONS["display_max_children"]
414    n_children = len(children)
415
416    children_html = []
417    for i, (n, c) in enumerate(children.items()):
418        if i < ceil(MAX_CHILDREN / 2) or i >= ceil(n_children - MAX_CHILDREN / 2):
419            is_last = i == (n_children - 1)
420            children_html.append(
421                _wrap_datatree_repr(datatree_node_repr(n, c), end=is_last)
422            )
423        elif n_children > MAX_CHILDREN and i == ceil(MAX_CHILDREN / 2):
424            children_html.append("<div>...</div>")
425
426    return "".join(
427        [
428            "<div style='display: inline-grid; grid-template-columns: 100%; grid-column: 1 / -1'>",
429            "".join(children_html),
430            "</div>",
431        ]
432    )
433
434
435children_section = partial(
436    _mapping_section,
437    name="Groups",
438    details_func=summarize_datatree_children,
439    max_items_collapse=1,
440    max_option_name="display_max_children",
441    expand_option_name="display_expand_groups",
442)
443
444inherited_coord_section = partial(
445    _mapping_section,
446    name="Inherited coordinates",
447    details_func=summarize_coords,
448    max_items_collapse=25,
449    expand_option_name="display_expand_coords",
450)
451
452
453def datatree_node_repr(group_title: str, node: DataTree, show_inherited=False) -> str:
454    from xarray.core.coordinates import Coordinates
455
456    header_components = [f"<div class='xr-obj-type'>{escape(group_title)}</div>"]
457
458    ds = node._to_dataset_view(rebuild_dims=False, inherit=True)
459    node_coords = node.to_dataset(inherit=False).coords
460
461    # use this class to get access to .xindexes property
462    inherited_coords = Coordinates(
463        coords=inherited_vars(node._coord_variables),
464        indexes=inherited_vars(node._indexes),
465    )
466
467    sections = [
468        children_section(node.children),
469        dim_section(ds),
470        coord_section(node_coords),
471    ]
472
473    # only show inherited coordinates on the root
474    if show_inherited:
475        sections.append(inherited_coord_section(inherited_coords))
476
477    sections += [
478        datavar_section(ds.data_vars),
479        attr_section(ds.attrs),
480    ]
481
482    return _obj_repr(ds, header_components, sections)
483
484
485def _wrap_datatree_repr(r: str, end: bool = False) -> str:
486    """
487    Wrap HTML representation with a tee to the left of it.
488
489    Enclosing HTML tag is a <div> with :code:`display: inline-grid` style.
490
491    Turns:
492    [    title    ]
493    |   details   |
494    |_____________|
495
496    into (A):
497    |─ [    title    ]
498    |  |   details   |
499    |  |_____________|
500
501    or (B):
502    └─ [    title    ]
503       |   details   |
504       |_____________|
505
506    Parameters
507    ----------
508    r: str
509        HTML representation to wrap.
510    end: bool
511        Specify if the line on the left should continue or end.
512
513        Default is True.
514
515    Returns
516    -------
517    str
518        Wrapped HTML representation.
519
520        Tee color is set to the variable :code:`--xr-border-color`.
521    """
522    # height of line
523    end = bool(end)
524    height = "100%" if end is False else "1.2em"
525    return "".join(
526        [
527            "<div style='display: inline-grid; grid-template-columns: 0px 20px auto; width: 100%;'>",
528            "<div style='",
529            "grid-column-start: 1;",
530            "border-right: 0.2em solid;",
531            "border-color: var(--xr-border-color);",
532            f"height: {height};",
533            "width: 0px;",
534            "'>",
535            "</div>",
536            "<div style='",
537            "grid-column-start: 2;",
538            "grid-row-start: 1;",
539            "height: 1em;",
540            "width: 20px;",
541            "border-bottom: 0.2em solid;",
542            "border-color: var(--xr-border-color);",
543            "'>",
544            "</div>",
545            "<div style='",
546            "grid-column-start: 3;",
547            "'>",
548            r,
549            "</div>",
550            "</div>",
551        ]
552    )
553
554
555def datatree_repr(dt: DataTree) -> str:
556    obj_type = f"xarray.{type(dt).__name__}"
557    return datatree_node_repr(obj_type, dt, show_inherited=True)
STATIC_FILES = (('xarray.static.html', 'icons-svg-inline.html'), ('xopr.xarray_accessor', 'style.css'))
def short_data_repr_html(array) -> str:
41def short_data_repr_html(array) -> str:
42    """Format "data" for DataArray and Variable."""
43    internal_data = getattr(array, "variable", array)._data
44    if hasattr(internal_data, "_repr_html_"):
45        return internal_data._repr_html_()
46    text = escape(short_data_repr(array))
47    return f"<pre>{text}</pre>"

Format "data" for DataArray and Variable.

def format_dims(dim_sizes, dims_with_index) -> str:
50def format_dims(dim_sizes, dims_with_index) -> str:
51    if not dim_sizes:
52        return ""
53
54    dim_css_map = {
55        dim: " class='xr-has-index'" if dim in dims_with_index else ""
56        for dim in dim_sizes
57    }
58
59    dims_li = "".join(
60        f"<li><span{dim_css_map[dim]}>{escape(str(dim))}</span>: {size}</li>"
61        for dim, size in dim_sizes.items()
62    )
63
64    return f"<ul class='xr-dim-list'>{dims_li}</ul>"
def summarize_attrs_inner(attrs) -> str:
67def summarize_attrs_inner(attrs) -> str:
68    attrs_dl = ""
69    for k, v in attrs.items():
70        if isinstance(v, dict):
71            attrs_dl += f"<dt><span>{escape(str(k))} :</span></dt><dd>"
72            attrs_dl += summarize_attrs_inner(v)
73            attrs_dl += "</dd>"
74        else:
75            attrs_dl += f"<dt><span>{escape(str(k))} :</span></dt><dd>{escape(str(v))}</dd>"
76
77    return attrs_dl
def summarize_attrs(attrs) -> str:
 80def summarize_attrs(attrs) -> str:
 81    # === Orig:
 82    # attrs_dl = "".join(
 83    #     f"<dt><span>{escape(str(k))} :</span></dt><dd>{escape(str(v))}</dd>"
 84    #     for k, v in attrs.items()
 85    # )
 86    # === V2:
 87    # attrs_dl = ""
 88    # for k, v in attrs.items():
 89    #     if isinstance(v, dict):
 90    #         attrs_dl += "<span class='xr-section-item'>"
 91    #         attrs_dl += collapsible_section(
 92    #             name=escape(str(k)),
 93    #             inline_details="",
 94    #             details=summarize_attrs(v),
 95    #             n_items=len(v),
 96    #             enabled=True,
 97    #             collapsed=True,
 98    #         )
 99    #         attrs_dl += "</span>"
100    #     else:
101    #         attrs_dl += f"<dt><span>{escape(str(k))} :</span></dt><dd>{escape(str(v))}</dd>"
102
103    attrs_dl = ""
104    for k, v in attrs.items():
105        if isinstance(v, dict):
106            attr_id = "attrs-" + str(uuid.uuid4())
107
108            attrs_dl += f"<div class='xr-attr-item'>"
109            attrs_dl += f"<input id='{attr_id}' class='xr-attr-in' type='checkbox'>"
110            attrs_dl += f"<label class='xr-attr-nested' for='{attr_id}'>{escape(str(k))} : "
111            attrs_dl += f"<span>({len(v)})</span></label>"
112            attrs_dl += "<span class='xr-attr-nested-inner'>"
113            attrs_dl += summarize_attrs(v)
114            attrs_dl += "</span></div>"
115        else:
116            attrs_dl += f"<dt><span>{escape(str(k))} :</span></dt><dd>{escape(str(v))}</dd>"
117
118
119    return f"<dl class='xr-attrs'>{attrs_dl}</dl>"
def summarize_variable(name, var, is_index=False, dtype=None) -> str:
129def summarize_variable(name, var, is_index=False, dtype=None) -> str:
130    variable = var.variable if hasattr(var, "variable") else var
131
132    cssclass_idx = " class='xr-has-index'" if is_index else ""
133    dims_str = f"({', '.join(escape(dim) for dim in var.dims)})"
134    name = escape(str(name))
135    dtype = dtype or escape(str(var.dtype))
136
137    # "unique" ids required to expand/collapse subsections
138    attrs_id = "attrs-" + str(uuid.uuid4())
139    data_id = "data-" + str(uuid.uuid4())
140    disabled = "" if len(var.attrs) else "disabled"
141
142    preview = escape(inline_variable_array_repr(variable, 35))
143    attrs_ul = summarize_attrs(var.attrs)
144    data_repr = short_data_repr_html(variable)
145
146    attrs_icon = _icon("icon-file-text2")
147    data_icon = _icon("icon-database")
148
149    return (
150        f"<div class='xr-var-name'><span{cssclass_idx}>{name}</span></div>"
151        f"<div class='xr-var-dims'>{dims_str}</div>"
152        f"<div class='xr-var-dtype'>{dtype}</div>"
153        f"<div class='xr-var-preview xr-preview'>{preview}</div>"
154        f"<input id='{attrs_id}' class='xr-var-attrs-in' "
155        f"type='checkbox' {disabled}>"
156        f"<label for='{attrs_id}' title='Show/Hide attributes'>"
157        f"{attrs_icon}</label>"
158        f"<input id='{data_id}' class='xr-var-data-in' type='checkbox'>"
159        f"<label for='{data_id}' title='Show/Hide data repr'>"
160        f"{data_icon}</label>"
161        f"<div class='xr-var-attrs'>{attrs_ul}</div>"
162        f"<div class='xr-var-data'>{data_repr}</div>"
163    )
def summarize_coords(variables) -> str:
166def summarize_coords(variables) -> str:
167    li_items = []
168    for k, v in variables.items():
169        li_content = summarize_variable(k, v, is_index=k in variables.xindexes)
170        li_items.append(f"<li class='xr-var-item'>{li_content}</li>")
171
172    vars_li = "".join(li_items)
173
174    return f"<ul class='xr-var-list'>{vars_li}</ul>"
def summarize_vars(variables) -> str:
177def summarize_vars(variables) -> str:
178    vars_li = "".join(
179        f"<li class='xr-var-item'>{summarize_variable(k, v)}</li>"
180        for k, v in variables.items()
181    )
182
183    return f"<ul class='xr-var-list'>{vars_li}</ul>"
def short_index_repr_html(index) -> str:
186def short_index_repr_html(index) -> str:
187    if hasattr(index, "_repr_html_"):
188        return index._repr_html_()
189
190    return f"<pre>{escape(repr(index))}</pre>"
def summarize_index(coord_names, index) -> str:
193def summarize_index(coord_names, index) -> str:
194    name = "<br>".join([escape(str(n)) for n in coord_names])
195
196    index_id = f"index-{uuid.uuid4()}"
197    preview = escape(inline_index_repr(index, max_width=70))
198    details = short_index_repr_html(index)
199
200    data_icon = _icon("icon-database")
201
202    return (
203        f"<div class='xr-index-name'><div>{name}</div></div>"
204        f"<div class='xr-index-preview'>{preview}</div>"
205        # need empty input + label here to conform to the fixed CSS grid layout
206        f"<input type='checkbox' disabled/>"
207        f"<label></label>"
208        f"<input id='{index_id}' class='xr-index-data-in' type='checkbox'/>"
209        f"<label for='{index_id}' title='Show/Hide index repr'>{data_icon}</label>"
210        f"<div class='xr-index-data'>{details}</div>"
211    )
def summarize_indexes(indexes) -> str:
214def summarize_indexes(indexes) -> str:
215    indexes_li = "".join(
216        f"<li class='xr-var-item'>{summarize_index(v, i)}</li>"
217        for v, i in indexes.items()
218    )
219    return f"<ul class='xr-var-list'>{indexes_li}</ul>"
def collapsible_section( name, inline_details='', details='', n_items=None, enabled=True, collapsed=False) -> str:
222def collapsible_section(
223    name, inline_details="", details="", n_items=None, enabled=True, collapsed=False
224) -> str:
225    # "unique" id to expand/collapse the section
226    data_id = "section-" + str(uuid.uuid4())
227
228    has_items = n_items is not None and n_items
229    n_items_span = "" if n_items is None else f" <span>({n_items})</span>"
230    enabled = "" if enabled and has_items else "disabled"
231    collapsed = "" if collapsed or not has_items else "checked"
232    tip = " title='Expand/collapse section'" if enabled else ""
233
234    return (
235        f"<input id='{data_id}' class='xr-section-summary-in' "
236        f"type='checkbox' {enabled} {collapsed}>"
237        f"<label for='{data_id}' class='xr-section-summary' {tip}>"
238        f"{name}:{n_items_span}</label>"
239        f"<div class='xr-section-inline-details'>{inline_details}</div>"
240        f"<div class='xr-section-details'>{details}</div>"
241    )
def dim_section(obj) -> str:
275def dim_section(obj) -> str:
276    dim_list = format_dims(obj.sizes, obj.xindexes.dims)
277
278    return collapsible_section(
279        "Dimensions", inline_details=dim_list, enabled=False, collapsed=True
280    )
def array_section(obj) -> str:
283def array_section(obj) -> str:
284    # "unique" id to expand/collapse the section
285    data_id = "section-" + str(uuid.uuid4())
286    collapsed = (
287        "checked"
288        if _get_boolean_with_default("display_expand_data", default=True)
289        else ""
290    )
291    variable = getattr(obj, "variable", obj)
292    preview = escape(inline_variable_array_repr(variable, max_width=70))
293    data_repr = short_data_repr_html(obj)
294    data_icon = _icon("icon-database")
295
296    return (
297        "<div class='xr-array-wrap'>"
298        f"<input id='{data_id}' class='xr-array-in' type='checkbox' {collapsed}>"
299        f"<label for='{data_id}' title='Show/hide data repr'>{data_icon}</label>"
300        f"<div class='xr-array-preview xr-preview'><span>{preview}</span></div>"
301        f"<div class='xr-array-data'>{data_repr}</div>"
302        "</div>"
303    )
coord_section = functools.partial(<function _mapping_section>, name='Coordinates', details_func=<function summarize_coords>, max_items_collapse=25, expand_option_name='display_expand_coords')
datavar_section = functools.partial(<function _mapping_section>, name='Data variables', details_func=<function summarize_vars>, max_items_collapse=15, expand_option_name='display_expand_data_vars')
index_section = functools.partial(<function _mapping_section>, name='Indexes', details_func=<function summarize_indexes>, max_items_collapse=0, expand_option_name='display_expand_indexes')
attr_section = functools.partial(<function _mapping_section>, name='Attributes', details_func=<function summarize_attrs>, max_items_collapse=10, expand_option_name='display_expand_attrs')
def array_repr(arr) -> str:
367def array_repr(arr) -> str:
368    dims = OrderedDict((k, v) for k, v in zip(arr.dims, arr.shape, strict=True))
369    if hasattr(arr, "xindexes"):
370        indexed_dims = arr.xindexes.dims
371    else:
372        indexed_dims = {}
373
374    obj_type = f"xarray.{type(arr).__name__}"
375    arr_name = f"'{arr.name}'" if getattr(arr, "name", None) else ""
376
377    header_components = [
378        f"<div class='xr-obj-type'>{obj_type}</div>",
379        f"<div class='xr-array-name'>{arr_name}</div>",
380        format_dims(dims, indexed_dims),
381    ]
382
383    sections = [array_section(arr)]
384
385    if hasattr(arr, "coords"):
386        sections.append(coord_section(arr.coords))
387
388    if hasattr(arr, "xindexes"):
389        indexes = _get_indexes_dict(arr.xindexes)
390        sections.append(index_section(indexes))
391
392    sections.append(attr_section(arr.attrs))
393
394    return _obj_repr(arr, header_components, sections)
def dataset_repr(ds) -> str:
397def dataset_repr(ds) -> str:
398    obj_type = f"xarray.{type(ds).__name__}"
399
400    header_components = [f"<div class='xr-obj-type'>{escape(obj_type)}</div>"]
401
402    sections = [
403        dim_section(ds),
404        coord_section(ds.coords),
405        datavar_section(ds.data_vars),
406        index_section(_get_indexes_dict(ds.xindexes)),
407        attr_section(ds.attrs),
408    ]
409
410    return _obj_repr(ds, header_components, sections)
def summarize_datatree_children(children: Mapping[str, xarray.core.datatree.DataTree]) -> str:
413def summarize_datatree_children(children: Mapping[str, DataTree]) -> str:
414    MAX_CHILDREN = OPTIONS["display_max_children"]
415    n_children = len(children)
416
417    children_html = []
418    for i, (n, c) in enumerate(children.items()):
419        if i < ceil(MAX_CHILDREN / 2) or i >= ceil(n_children - MAX_CHILDREN / 2):
420            is_last = i == (n_children - 1)
421            children_html.append(
422                _wrap_datatree_repr(datatree_node_repr(n, c), end=is_last)
423            )
424        elif n_children > MAX_CHILDREN and i == ceil(MAX_CHILDREN / 2):
425            children_html.append("<div>...</div>")
426
427    return "".join(
428        [
429            "<div style='display: inline-grid; grid-template-columns: 100%; grid-column: 1 / -1'>",
430            "".join(children_html),
431            "</div>",
432        ]
433    )
children_section = functools.partial(<function _mapping_section>, name='Groups', details_func=<function summarize_datatree_children>, max_items_collapse=1, max_option_name='display_max_children', expand_option_name='display_expand_groups')
inherited_coord_section = functools.partial(<function _mapping_section>, name='Inherited coordinates', details_func=<function summarize_coords>, max_items_collapse=25, expand_option_name='display_expand_coords')
def datatree_node_repr( group_title: str, node: xarray.core.datatree.DataTree, show_inherited=False) -> str:
454def datatree_node_repr(group_title: str, node: DataTree, show_inherited=False) -> str:
455    from xarray.core.coordinates import Coordinates
456
457    header_components = [f"<div class='xr-obj-type'>{escape(group_title)}</div>"]
458
459    ds = node._to_dataset_view(rebuild_dims=False, inherit=True)
460    node_coords = node.to_dataset(inherit=False).coords
461
462    # use this class to get access to .xindexes property
463    inherited_coords = Coordinates(
464        coords=inherited_vars(node._coord_variables),
465        indexes=inherited_vars(node._indexes),
466    )
467
468    sections = [
469        children_section(node.children),
470        dim_section(ds),
471        coord_section(node_coords),
472    ]
473
474    # only show inherited coordinates on the root
475    if show_inherited:
476        sections.append(inherited_coord_section(inherited_coords))
477
478    sections += [
479        datavar_section(ds.data_vars),
480        attr_section(ds.attrs),
481    ]
482
483    return _obj_repr(ds, header_components, sections)
def datatree_repr(dt: xarray.core.datatree.DataTree) -> str:
556def datatree_repr(dt: DataTree) -> str:
557    obj_type = f"xarray.{type(dt).__name__}"
558    return datatree_node_repr(obj_type, dt, show_inherited=True)