-
-
Notifications
You must be signed in to change notification settings - Fork 1.1k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Html repr #3425
Merged
Merged
Html repr #3425
Changes from 39 commits
Commits
Show all changes
42 commits
Select commit
Hold shift + click to select a range
6c0e118
add CSS style and internal functions for html repr
benbovy f87c372
move CSS code to its own file in a new static directory
benbovy 11e919c
add repr of array objects + some refactoring and fixes
benbovy 0c7e0e9
add _repr_html_ methods to dataset, dataarray and variable
benbovy 732eb3e
fix encoding issue in read CSS
benbovy 0cb748c
fix some CSS for compatibility with notebook (tested 5.2)
benbovy 877fb06
use CSS grid + add icons to show/hide attrs and data repr
benbovy 6f60af6
Changing title of icons to make tooltips better
jsignell d3f2901
Adding option to set repr back to classic
jsignell 7291604
Adding support for multiindexes
jsignell a5bbd86
Getting rid of some spans and fixing alignment
jsignell 0206205
Forgot to check in css [skip ci]
jsignell 027347a
Overflow on hover
jsignell 24167ff
Cleaning up css
jsignell d6d31e8
Fixing indentation
jsignell 0f90852
Replacing + icon with db icon
jsignell 2cfd912
Unifying input css
jsignell e73984e
Renaming stylesheet [skip ci]
jsignell 52c3efd
Improving styling of attributes
jsignell 4b106ab
Using the repr functions
jsignell 6f91bb3
Using dask array _repr_html_
jsignell e872ace
Fixing alignment of Dimensions
jsignell 5768700
Make sure to include subdirs in package
jsignell fb0ef3b
Adding static to manifest
jsignell e3f1c93
Trying to include css files
jsignell 654a422
Fixing css discrepancies in colab
jsignell 22a47a0
Adding in lots of escapes and also f-strings
jsignell 962eca0
Adding some tests for formatting_html
jsignell 534f6be
linting
jsignell cbd365c
classic -> text
jsignell fa88966
linting more
jsignell 96587d8
Adding tests for new option
jsignell f6d4f2d
Trying to get better coverage
jsignell dc38a4e
reformatting
jsignell 64eaade
Fixing up test
jsignell f3fc38e
Last tests hopefully
jsignell e1b250f
Fixing dask test to work with lower version
jsignell 4989121
More black
jsignell 58754a0
Added what's new section
jsignell 542503f
classic -> text
jsignell 4e6f6ee
Fixing up dt/dl for jlab
jsignell 1d96093
Directly change dl objects for attrs section
jsignell File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,274 @@ | ||
import uuid | ||
import pkg_resources | ||
from collections import OrderedDict | ||
from functools import partial | ||
from html import escape | ||
|
||
from .formatting import inline_variable_array_repr, short_data_repr | ||
|
||
|
||
CSS_FILE_PATH = "/".join(("static", "css", "style.css")) | ||
CSS_STYLE = pkg_resources.resource_string("xarray", CSS_FILE_PATH).decode("utf8") | ||
|
||
|
||
ICONS_SVG_PATH = "/".join(("static", "html", "icons-svg-inline.html")) | ||
ICONS_SVG = pkg_resources.resource_string("xarray", ICONS_SVG_PATH).decode("utf8") | ||
|
||
|
||
def short_data_repr_html(array): | ||
"""Format "data" for DataArray and Variable.""" | ||
internal_data = getattr(array, "variable", array)._data | ||
if hasattr(internal_data, "_repr_html_"): | ||
return internal_data._repr_html_() | ||
return escape(short_data_repr(array)) | ||
|
||
|
||
def format_dims(dims, coord_names): | ||
if not dims: | ||
return "" | ||
|
||
dim_css_map = { | ||
k: " class='xr-has-index'" if k in coord_names else "" for k, v in dims.items() | ||
} | ||
|
||
dims_li = "".join( | ||
f"<li><span{dim_css_map[dim]}>" f"{escape(dim)}</span>: {size}</li>" | ||
for dim, size in dims.items() | ||
) | ||
|
||
return f"<ul class='xr-dim-list'>{dims_li}</ul>" | ||
|
||
|
||
def summarize_attrs(attrs): | ||
attrs_dl = "".join( | ||
f"<dt><span>{escape(k)} :</span></dt>" f"<dd>{escape(str(v))}</dd>" | ||
for k, v in attrs.items() | ||
) | ||
|
||
return f"<dl class='xr-attrs'>{attrs_dl}</dl>" | ||
|
||
|
||
def _icon(icon_name): | ||
# icon_name should be defined in xarray/static/html/icon-svg-inline.html | ||
return ( | ||
"<svg class='icon xr-{0}'>" | ||
"<use xlink:href='#{0}'>" | ||
"</use>" | ||
"</svg>".format(icon_name) | ||
) | ||
|
||
|
||
def _summarize_coord_multiindex(name, coord): | ||
preview = f"({', '.join(escape(l) for l in coord.level_names)})" | ||
return summarize_variable( | ||
name, coord, is_index=True, dtype="MultiIndex", preview=preview | ||
) | ||
|
||
|
||
def summarize_coord(name, var): | ||
is_index = name in var.dims | ||
if is_index: | ||
coord = var.variable.to_index_variable() | ||
if coord.level_names is not None: | ||
coords = {} | ||
coords[name] = _summarize_coord_multiindex(name, coord) | ||
for lname in coord.level_names: | ||
var = coord.get_level_variable(lname) | ||
coords[lname] = summarize_variable(lname, var) | ||
return coords | ||
|
||
return {name: summarize_variable(name, var, is_index)} | ||
|
||
|
||
def summarize_coords(variables): | ||
coords = {} | ||
for k, v in variables.items(): | ||
coords.update(**summarize_coord(k, v)) | ||
|
||
vars_li = "".join(f"<li class='xr-var-item'>{v}</li>" for v in coords.values()) | ||
|
||
return f"<ul class='xr-var-list'>{vars_li}</ul>" | ||
|
||
|
||
def summarize_variable(name, var, is_index=False, dtype=None, preview=None): | ||
variable = var.variable if hasattr(var, "variable") else var | ||
|
||
cssclass_idx = " class='xr-has-index'" if is_index else "" | ||
dims_str = f"({', '.join(escape(dim) for dim in var.dims)})" | ||
name = escape(name) | ||
dtype = dtype or var.dtype | ||
|
||
# "unique" ids required to expand/collapse subsections | ||
attrs_id = "attrs-" + str(uuid.uuid4()) | ||
data_id = "data-" + str(uuid.uuid4()) | ||
disabled = "" if len(var.attrs) else "disabled" | ||
|
||
preview = preview or escape(inline_variable_array_repr(variable, 35)) | ||
attrs_ul = summarize_attrs(var.attrs) | ||
data_repr = short_data_repr_html(variable) | ||
|
||
attrs_icon = _icon("icon-file-text2") | ||
data_icon = _icon("icon-database") | ||
|
||
return ( | ||
f"<div class='xr-var-name'><span{cssclass_idx}>{name}</span></div>" | ||
f"<div class='xr-var-dims'>{dims_str}</div>" | ||
f"<div class='xr-var-dtype'>{dtype}</div>" | ||
f"<div class='xr-var-preview xr-preview'>{preview}</div>" | ||
f"<input id='{attrs_id}' class='xr-var-attrs-in' " | ||
f"type='checkbox' {disabled}>" | ||
f"<label for='{attrs_id}' title='Show/Hide attributes'>" | ||
f"{attrs_icon}</label>" | ||
f"<input id='{data_id}' class='xr-var-data-in' type='checkbox'>" | ||
f"<label for='{data_id}' title='Show/Hide data repr'>" | ||
f"{data_icon}</label>" | ||
f"<div class='xr-var-attrs'>{attrs_ul}</div>" | ||
f"<pre class='xr-var-data'>{data_repr}</pre>" | ||
) | ||
|
||
|
||
def summarize_vars(variables): | ||
vars_li = "".join( | ||
f"<li class='xr-var-item'>{summarize_variable(k, v)}</li>" | ||
for k, v in variables.items() | ||
) | ||
|
||
return f"<ul class='xr-var-list'>{vars_li}</ul>" | ||
|
||
|
||
def collapsible_section( | ||
name, inline_details="", details="", n_items=None, enabled=True, collapsed=False | ||
): | ||
# "unique" id to expand/collapse the section | ||
data_id = "section-" + str(uuid.uuid4()) | ||
|
||
has_items = n_items is not None and n_items | ||
n_items_span = "" if n_items is None else f" <span>({n_items})</span>" | ||
enabled = "" if enabled and has_items else "disabled" | ||
collapsed = "" if collapsed or not has_items else "checked" | ||
tip = " title='Expand/collapse section'" if enabled else "" | ||
|
||
return ( | ||
f"<input id='{data_id}' class='xr-section-summary-in' " | ||
f"type='checkbox' {enabled} {collapsed}>" | ||
f"<label for='{data_id}' class='xr-section-summary' {tip}>" | ||
f"{name}:{n_items_span}</label>" | ||
f"<div class='xr-section-inline-details'>{inline_details}</div>" | ||
f"<div class='xr-section-details'>{details}</div>" | ||
) | ||
|
||
|
||
def _mapping_section(mapping, name, details_func, max_items_collapse, enabled=True): | ||
n_items = len(mapping) | ||
collapsed = n_items >= max_items_collapse | ||
|
||
return collapsible_section( | ||
name, | ||
details=details_func(mapping), | ||
n_items=n_items, | ||
enabled=enabled, | ||
collapsed=collapsed, | ||
) | ||
|
||
|
||
def dim_section(obj): | ||
dim_list = format_dims(obj.dims, list(obj.coords)) | ||
|
||
return collapsible_section( | ||
"Dimensions", inline_details=dim_list, enabled=False, collapsed=True | ||
) | ||
|
||
|
||
def array_section(obj): | ||
# "unique" id to expand/collapse the section | ||
data_id = "section-" + str(uuid.uuid4()) | ||
collapsed = "" | ||
preview = escape(inline_variable_array_repr(obj.variable, max_width=70)) | ||
data_repr = short_data_repr_html(obj) | ||
data_icon = _icon("icon-database") | ||
|
||
return ( | ||
"<div class='xr-array-wrap'>" | ||
f"<input id='{data_id}' class='xr-array-in' type='checkbox' {collapsed}>" | ||
f"<label for='{data_id}' title='Show/hide data repr'>{data_icon}</label>" | ||
f"<div class='xr-array-preview xr-preview'><span>{preview}</span></div>" | ||
f"<pre class='xr-array-data'>{data_repr}</pre>" | ||
"</div>" | ||
) | ||
|
||
|
||
coord_section = partial( | ||
_mapping_section, | ||
name="Coordinates", | ||
details_func=summarize_coords, | ||
max_items_collapse=25, | ||
) | ||
|
||
|
||
datavar_section = partial( | ||
_mapping_section, | ||
name="Data variables", | ||
details_func=summarize_vars, | ||
max_items_collapse=15, | ||
) | ||
|
||
|
||
attr_section = partial( | ||
_mapping_section, | ||
name="Attributes", | ||
details_func=summarize_attrs, | ||
max_items_collapse=10, | ||
) | ||
|
||
|
||
def _obj_repr(header_components, sections): | ||
header = f"<div class='xr-header'>{''.join(h for h in header_components)}</div>" | ||
sections = "".join(f"<li class='xr-section-item'>{s}</li>" for s in sections) | ||
|
||
return ( | ||
"<div>" | ||
f"{ICONS_SVG}<style>{CSS_STYLE}</style>" | ||
"<div class='xr-wrap'>" | ||
f"{header}" | ||
f"<ul class='xr-sections'>{sections}</ul>" | ||
"</div>" | ||
"</div>" | ||
) | ||
|
||
|
||
def array_repr(arr): | ||
dims = OrderedDict((k, v) for k, v in zip(arr.dims, arr.shape)) | ||
|
||
obj_type = "xarray.{}".format(type(arr).__name__) | ||
arr_name = "'{}'".format(arr.name) if getattr(arr, "name", None) else "" | ||
coord_names = list(arr.coords) if hasattr(arr, "coords") else [] | ||
|
||
header_components = [ | ||
"<div class='xr-obj-type'>{}</div>".format(obj_type), | ||
"<div class='xr-array-name'>{}</div>".format(arr_name), | ||
format_dims(dims, coord_names), | ||
] | ||
|
||
sections = [array_section(arr)] | ||
|
||
if hasattr(arr, "coords"): | ||
sections.append(coord_section(arr.coords)) | ||
|
||
sections.append(attr_section(arr.attrs)) | ||
|
||
return _obj_repr(header_components, sections) | ||
|
||
|
||
def dataset_repr(ds): | ||
obj_type = "xarray.{}".format(type(ds).__name__) | ||
|
||
header_components = [f"<div class='xr-obj-type'>{escape(obj_type)}</div>"] | ||
|
||
sections = [ | ||
dim_section(ds), | ||
coord_section(ds.coords), | ||
datavar_section(ds.data_vars), | ||
attr_section(ds.attrs), | ||
] | ||
|
||
return _obj_repr(header_components, sections) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
classic -> text