From b0556cde5f78c3fe670bb20a07d5535661267c81 Mon Sep 17 00:00:00 2001 From: Justus Magin Date: Thu, 20 Oct 2022 11:55:15 +0200 Subject: [PATCH 01/10] use css classes instead of inline styles --- datatree/formatting_html.py | 105 +++++++++++++++++++++++------------- 1 file changed, 69 insertions(+), 36 deletions(-) diff --git a/datatree/formatting_html.py b/datatree/formatting_html.py index 4531f5ae..d0f56298 100644 --- a/datatree/formatting_html.py +++ b/datatree/formatting_html.py @@ -4,7 +4,7 @@ from xarray.core.formatting_html import ( _mapping_section, - _obj_repr, + _load_static_files, attr_section, coord_section, datavar_section, @@ -14,6 +14,42 @@ OPTIONS["display_expand_groups"] = "default" +additional_css_style = """ +.xr-tree { + display: inline-grid; + grid-template-columns: 100%; +} + +.xr-tree-item { + display: inline-grid; +} +.xr-tree-item-mid { + height: 100%; +} +.xr-tree-item-end { + height: 1.2em; +} + +.xr-tree-item-connection-vertical { + grid-column-start: 1; + border-right: 0.2em solid; + border-color: var(--xr-border-color); + width: 0px; +} +.xr-tree-item-connection-horizontal { + grid-column-start: 2; + grid-row-start: 1; + height: 1em; + width: 20px; + border-bottom: 0.2em solid; + border-color: var(--xr-border-color); +} + +.xr-tree-item-data { + grid-column-start: 3; +} +""" + def summarize_children(children: Mapping[str, Any]) -> str: N_CHILDREN = len(children) - 1 @@ -28,13 +64,7 @@ def summarize_children(children: Mapping[str, Any]) -> str: for i, (n, c) in enumerate(children.items()) ) - return "".join( - [ - "
", - children_html, - "
", - ] - ) + return f"
{children_html}
" children_section = partial( @@ -46,6 +76,30 @@ def summarize_children(children: Mapping[str, Any]) -> str: ) +def _obj_repr(obj, header_components, sections): + """Return HTML repr of a datatree object. + + If CSS is not injected (untrusted notebook), fallback to the plain text repr. + + """ + header = f"
{''.join(h for h in header_components)}
" + sections = "".join(f"
  • {s}
  • " for s in sections) + + icons_svg, css_style = _load_static_files() + return ( + "
    " + f"{icons_svg}" + f"" + f"" + f"
    {escape(repr(obj))}
    " + "" + "
    " + ) + + def node_repr(group_title: str, dt: Any) -> str: header_components = [f"
    {escape(group_title)}
    "] @@ -102,34 +156,13 @@ def _wrap_repr(r: str, end: bool = False) -> str: # height of line end = bool(end) height = "100%" if end is False else "1.2em" - return "".join( - [ - "
    ", - "
    ", - "
    ", - "
    ", - "
    ", - "
    ", - "
      ", - r, - "
    " "
    ", - "
    ", - ] + item_class = "xr-tree-item-mid" if not end else "xr-tree-item-end" + return ( + "
    " + f"
    " + "
    " + f"
      {r}
    " + "
    " ) From 2dccfdfaf9609012a455845b3f8e2a1767b71d41 Mon Sep 17 00:00:00 2001 From: Justus Magin Date: Thu, 20 Oct 2022 11:56:55 +0200 Subject: [PATCH 02/10] fall back to the text repr of the node --- datatree/formatting_html.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/datatree/formatting_html.py b/datatree/formatting_html.py index d0f56298..2c20a1e6 100644 --- a/datatree/formatting_html.py +++ b/datatree/formatting_html.py @@ -113,7 +113,7 @@ def node_repr(group_title: str, dt: Any) -> str: attr_section(ds.attrs), ] - return _obj_repr(ds, header_components, sections) + return _obj_repr(dt, header_components, sections) def _wrap_repr(r: str, end: bool = False) -> str: From a7915f60a141d1e29a880dc07b247295b52b819c Mon Sep 17 00:00:00 2001 From: Justus Magin Date: Thu, 20 Oct 2022 11:58:24 +0200 Subject: [PATCH 03/10] rely on built-in casting to bool --- datatree/formatting_html.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/datatree/formatting_html.py b/datatree/formatting_html.py index 2c20a1e6..f19a766c 100644 --- a/datatree/formatting_html.py +++ b/datatree/formatting_html.py @@ -153,9 +153,6 @@ def _wrap_repr(r: str, end: bool = False) -> str: Tee color is set to the variable :code:`--xr-border-color`. """ - # height of line - end = bool(end) - height = "100%" if end is False else "1.2em" item_class = "xr-tree-item-mid" if not end else "xr-tree-item-end" return ( "
    " From 3107a4f7a775e62aeb213115257b587f3f41c0cc Mon Sep 17 00:00:00 2001 From: Justus Magin Date: Thu, 20 Oct 2022 12:04:41 +0200 Subject: [PATCH 04/10] refactor the children formatter --- datatree/formatting_html.py | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/datatree/formatting_html.py b/datatree/formatting_html.py index f19a766c..c6c24d42 100644 --- a/datatree/formatting_html.py +++ b/datatree/formatting_html.py @@ -3,8 +3,8 @@ from typing import Any, Mapping from xarray.core.formatting_html import ( - _mapping_section, _load_static_files, + _mapping_section, attr_section, coord_section, datavar_section, @@ -52,16 +52,19 @@ def summarize_children(children: Mapping[str, Any]) -> str: - N_CHILDREN = len(children) - 1 + def is_last_item(index, n_total): + return index >= n_total - 1 + + def format_child(name, child, end): + """format node and wrap it into a tree""" + formatted = node_repr(name, child) + return _wrap_repr(formatted, end=end) - # Get result from node_repr and wrap it - lines_callback = lambda n, c, end: _wrap_repr(node_repr(n, c), end=end) + n_children = len(children) children_html = "".join( - lines_callback(n, c, end=False) # Long lines - if i < N_CHILDREN - else lines_callback(n, c, end=True) # Short lines - for i, (n, c) in enumerate(children.items()) + format_child(name, child, end=is_last_item(index, n_children)) + for index, (name, child) in enumerate(children.items()) ) return f"
    {children_html}
    " From a5fc694c7a01701626b8db512d8ac87434c9f9a1 Mon Sep 17 00:00:00 2001 From: Justus Magin Date: Thu, 20 Oct 2022 14:47:09 +0200 Subject: [PATCH 05/10] replace the obj_repr with a custom function for joining node reprs --- datatree/formatting_html.py | 38 ++++++++++++++++++------------------- 1 file changed, 19 insertions(+), 19 deletions(-) diff --git a/datatree/formatting_html.py b/datatree/formatting_html.py index c6c24d42..e6e2ecbe 100644 --- a/datatree/formatting_html.py +++ b/datatree/formatting_html.py @@ -79,26 +79,15 @@ def format_child(name, child, end): ) -def _obj_repr(obj, header_components, sections): - """Return HTML repr of a datatree object. - - If CSS is not injected (untrusted notebook), fallback to the plain text repr. - - """ - header = f"
    {''.join(h for h in header_components)}
    " - sections = "".join(f"
  • {s}
  • " for s in sections) - - icons_svg, css_style = _load_static_files() +def join_sections(sections, header_components): + combined_sections = "".join( + f"
  • {s}
  • " for s in sections + ) + header = f"
    {''.join(header_components)}
    " return ( - "
    " - f"{icons_svg}" - f"" - f"" - f"
    {escape(repr(obj))}
    " "" + f"
      {combined_sections}
    " "
    " ) @@ -116,7 +105,7 @@ def node_repr(group_title: str, dt: Any) -> str: attr_section(ds.attrs), ] - return _obj_repr(dt, header_components, sections) + return join_sections(sections, header_components) def _wrap_repr(r: str, end: bool = False) -> str: @@ -168,4 +157,15 @@ def _wrap_repr(r: str, end: bool = False) -> str: def datatree_repr(dt: Any) -> str: obj_type = f"datatree.{type(dt).__name__}" - return node_repr(obj_type, dt) + + icons_svg, css_style = _load_static_files() + + return ( + "
    " + f"{icons_svg}" + f"" + f"" + f"
    {escape(repr(dt))}
    " + f"{node_repr(obj_type, dt)}" + "
    " + ) From c610bd40d153799a9d361126b4f1d8ed4e732457 Mon Sep 17 00:00:00 2001 From: Justus Magin Date: Thu, 20 Oct 2022 14:48:42 +0200 Subject: [PATCH 06/10] allow collapsing the data --- datatree/formatting_html.py | 46 +++++++++++++++++++++++++++++-------- 1 file changed, 36 insertions(+), 10 deletions(-) diff --git a/datatree/formatting_html.py b/datatree/formatting_html.py index e6e2ecbe..d939a24c 100644 --- a/datatree/formatting_html.py +++ b/datatree/formatting_html.py @@ -6,13 +6,15 @@ _load_static_files, _mapping_section, attr_section, + collapsible_section, coord_section, datavar_section, dim_section, ) -from xarray.core.options import OPTIONS +from xarray.core.options import OPTIONS, _get_boolean_with_default OPTIONS["display_expand_groups"] = "default" +OPTIONS["display_expand_group_data"] = "default" additional_css_style = """ .xr-tree { @@ -79,6 +81,38 @@ def format_child(name, child, end): ) +def summarize_data(node): + ds = node.ds + sections = [ + dim_section(ds), + coord_section(ds.coords), + datavar_section(ds.data_vars), + attr_section(ds.attrs), + ] + return "".join(sections) + + +def data_section(node): + name = "Data" + + details = summarize_data(node) + + n_items = 5 + expanded = _get_boolean_with_default( + "display_expand_group_data", + True, + ) + collapsed = not expanded + + return collapsible_section( + name=name, + details=details, + n_items=n_items, + enabled=True, + collapsed=collapsed, + ) + + def join_sections(sections, header_components): combined_sections = "".join( f"
  • {s}
  • " for s in sections @@ -95,15 +129,7 @@ def join_sections(sections, header_components): def node_repr(group_title: str, dt: Any) -> str: header_components = [f"
    {escape(group_title)}
    "] - ds = dt.ds - - sections = [ - children_section(dt.children), - dim_section(ds), - coord_section(ds.coords), - datavar_section(ds.data_vars), - attr_section(ds.attrs), - ] + sections = [children_section(dt.children), data_section(dt)] return join_sections(sections, header_components) From e0b39e49faba714b713036a5f48fe9506fb553ca Mon Sep 17 00:00:00 2001 From: Justus Magin Date: Thu, 20 Oct 2022 15:03:54 +0200 Subject: [PATCH 07/10] indent the data sections a bit --- datatree/formatting_html.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/datatree/formatting_html.py b/datatree/formatting_html.py index d939a24c..af782abd 100644 --- a/datatree/formatting_html.py +++ b/datatree/formatting_html.py @@ -50,6 +50,9 @@ .xr-tree-item-data { grid-column-start: 3; } +.xr-tree-item-data-sections { + margin-left: 0.6em; +} """ @@ -89,7 +92,7 @@ def summarize_data(node): datavar_section(ds.data_vars), attr_section(ds.attrs), ] - return "".join(sections) + return f"
    {''.join(sections)}
    " def data_section(node): @@ -121,7 +124,7 @@ def join_sections(sections, header_components): return ( "" ) From afb0c29aec846eb7da04a1eb38a9501a73fee843 Mon Sep 17 00:00:00 2001 From: Justus Magin Date: Fri, 21 Oct 2022 14:16:28 +0200 Subject: [PATCH 08/10] add the header html when constructing the final str --- datatree/formatting_html.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/datatree/formatting_html.py b/datatree/formatting_html.py index af782abd..d1f9c9a7 100644 --- a/datatree/formatting_html.py +++ b/datatree/formatting_html.py @@ -120,10 +120,10 @@ def join_sections(sections, header_components): combined_sections = "".join( f"
  • {s}
  • " for s in sections ) - header = f"
    {''.join(header_components)}
    " + header = "".join(header_components) return ( "" ) From dc206ff39e6b707c59bec950f5e76c124077349f Mon Sep 17 00:00:00 2001 From: Justus Magin Date: Mon, 24 Oct 2022 16:21:44 +0200 Subject: [PATCH 09/10] solve the issue of expanding attrs depending on the previous section --- datatree/formatting_html.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/datatree/formatting_html.py b/datatree/formatting_html.py index d1f9c9a7..6da900c2 100644 --- a/datatree/formatting_html.py +++ b/datatree/formatting_html.py @@ -92,7 +92,11 @@ def summarize_data(node): datavar_section(ds.data_vars), attr_section(ds.attrs), ] - return f"
    {''.join(sections)}
    " + + sections_li = "".join( + f"
  • {section}
  • " for section in sections + ) + return f"
      {sections_li}
    " def data_section(node): @@ -122,7 +126,7 @@ def join_sections(sections, header_components): ) header = "".join(header_components) return ( - "