From 6c0e1187d4ec7e3c1b70bff5df6409c192ae749d Mon Sep 17 00:00:00 2001 From: Benoit Bovy Date: Thu, 11 Jan 2018 07:37:14 +0100 Subject: [PATCH 01/42] add CSS style and internal functions for html repr --- xarray/core/formatting_html.py | 407 +++++++++++++++++++++++++++++++++ 1 file changed, 407 insertions(+) create mode 100644 xarray/core/formatting_html.py diff --git a/xarray/core/formatting_html.py b/xarray/core/formatting_html.py new file mode 100644 index 00000000000..6a8fcf97e35 --- /dev/null +++ b/xarray/core/formatting_html.py @@ -0,0 +1,407 @@ +# coding: utf-8 + +import uuid +from functools import partial + +from .formatting import format_array_flat + + +XR_REPR_STYLE = """ +.xr-wrap { + width: 540px; + font-size: 13px; + line-height: 1.5; + background-color: #fff; +} + +.xr-wrap ul { + padding: 0; +} + +.xr-header { + padding: 6px 0 6px 3px; + border-bottom-width: 1px; + border-bottom-style: solid; + border-bottom-color: #777; + color: #555;; +} + +ul.xr-sections { + list-style: none !important; + padding: 3px !important; + margin: 0 !important; +} + +input.xr-section-in { + display: none; +} + +input.xr-section-in + label { + display: inline-block; + width: 140px; + color: #555; + font-weight: 500; + padding: 4px 0 2px 0; +} + +input.xr-section-in:enabled + label { + cursor: pointer; +} + +input.xr-section-in + label:before { + display: inline-block; + content: '►'; + font-size: 11px; + width: 15px; + text-align: center; +} + +input.xr-section-in:checked + label:before { + content: '▼'; +} + +input.xr-section-in:disabled + label:before { + color: #777; +} + +input.xr-section-in + label > span { + display: inline-block; + margin-left: 4px; +} + +input.xr-section-in:checked + label > span { + display: none; +} + +input.xr-section-in ~ ul { + display: none; +} + +input.xr-section-in:checked ~ ul { + display: block; +} + +.xr-sections summary > div { + display: inline-block; + cursor: pointer; + width: 140px; + color: #555; + font-weight: 500; + padding: 4px 0 2px 0; +} + +.xr-dim-list { + display: inline-block !important; + list-style: none; + padding: 0 !important; +} + +.xr-dim-list li { + display: inline-block; + font-size: 13px !important; + padding: 0; + margin: 0; +} + +.xr-dim-list:before { + content: '('; +} + +.xr-dim-list:after { + content: ')'; +} + +.xr-dim-list li:not(:last-child):after { + content: ','; + padding-right: 5px; +} + +.xr-has-index { + text-decoration: underline; +} + +ul.xr-var-list { + list-style: none !important; + padding: 0 !important; + margin: 0 !important; +} + +.xr-var-list > li { + background-color: #fcfcfc; + overflow: hidden; +} + +.xr-var-list > li:nth-child(odd) { + background-color: #efefef; +} + +.xr-var-list li:hover { + background-color: rgba(3, 169, 244, .2); +} + +.xr-var-list li > span { + display: inline-block; +} + +.xr-var-list li input { + display: none; +} + +.xr-var-list li input:enabled + label { + cursor: pointer; +} + +input.xr-varname-in + label { + display: inline-block; + width: 140px; + padding-left: 0; +} + +input.xr-varname-in + label:before { + content: ' '; + display: inline-block; + font-size: 11px; + width: 15px; + padding-left: 20px; + padding-right: 5px; + text-align: center; + color: #aaa; + text-decoration: none !important; +} + +input.xr-varname-in ~ ul { + display: none; +} + +input.xr-varname-in:checked ~ ul { + display: block; +} + +input.xr-varname-in:enabled + label:before { + content: 'a'; +} + +input.xr-varname-in:enabled + label:hover:before { + color: #000; +} + +input.xr-varname-in:checked + label:before { + color: #ccc; +} + +.xr-dims { + width: 80px; +} + +.xr-dtype { + width: 96px; + padding-right: 4px; + text-align: right; + color: #555; +} + +.xr-values { + width: 200px; + text-align: left; + color: #888; + white-space: nowrap; + font-size: 12px; +} + +.xr-values > span:nth-child(odd) { + color: rgba(0, 0, 0, .65); +} + +input.xr-values-in + label:hover > span { + color: #000; +} + +input.xr-values-in:checked + label > span { + color: #ccc; +} + +input.xr-values-in ~ pre { + display: none; +} + +input.xr-values-in:checked ~ pre { + display: block; +} + +input.xr-values-in:checked + label > span { + color: #ccc; +} + +.xr-data-repr { + font-size: 11px !important; + background-color: #fff; + padding: 4px 0 6px 40px !important; + margin: 0 !important; +} + +.xr-attr-list { + list-style: none !important; + background-color: #fff; + padding: 0 0 6px 40px !important; + color: #555; +} + +.xr-attr-list li, +.xr-attr-list li:hover { + background-color: #fff; +} +""" + + +def format_dims(dims, coord_names): + dim_css_map = {k: " class='xr-has-index'" if k in coord_names else '' + for k, v in dims.items()} + + dims_li = "".join("
  • {name}: {size}
  • " + .format(cssclass=dim_css_map[k], name=k, size=v) + for k, v in dims.items()) + + return "".format(dims_li) + + +def format_values_preview(var): + pprint_str = format_array_flat(var, 35) + + return "".join("{} ".format(s) + for s in pprint_str.split()) + + +def summarize_attrs(attrs): + attrs_li = "".join("
  • {} : {}
  • ".format(k, v) + for k, v in attrs.items()) + + return "".format(attrs_li) + + +def summarize_variable(name, var): + d = {} + + d['dims_str'] = '(' + ', '.join(dim for dim in var.dims) + ')' + + d['name'] = name + d['cssclass_varname'] = 'xr-varname' + if name in var.dims: + d['cssclass_varname'] += ' xr-has-index' + + d['dtype'] = var.dtype + + # "unique" ids required to expand/collapse subsections + d['attrs_id'] = 'attrs-' + str(uuid.uuid4()) + d['values_id'] = 'values-' + str(uuid.uuid4()) + + if len(var.attrs): + d['disabled'] = '' + d['attrs'] = format_attrs(var.attrs) + else: + d['disabled'] = 'disabled' + d['attrs'] = '' + + d['values_preview'] = format_values_preview(var) + d['attrs_subsection'] = summarize_attrs(var.attrs) + d['data_repr_subsection'] = repr(var.data) + + return ( + "" + "" + "{dims_str}" + "{dtype}" + "" + "" + "{attrs_subsection}" + "
    {data_repr_subsection}
    " + .format(**d)) + + +def summarize_vars(variables): + vars_li = "".join("
  • {}
  • ".format(summarize_variable(k, v)) + for k, v in variables.items()) + + return "".format(vars_li) + + +def collapsible_section(name, body, n_items=None, + enabled=True, collapsed=False): + d = {} + + # "unique" id to expand/collapse the section + d['section_id'] = 'section-' + str(uuid.uuid4()) + + if n_items is not None: + n_items_span = " ({})".format(n_items) + else: + n_items_span = '' + + d['title'] = "{}:{}".format(name, n_items_span) + + d['body'] = body + + d['enabled'] = '' if enabled else 'disabled' + d['collapsed'] = '' if collapsed else 'checked' + + return ( + "" + "" + "{body}" + .format(**d)) + + +def _generic_section(mapping, name, body_func, + enabled=True, nmax_items_collapse=None): + n_items = len(mapping) + + if nmax_items_collapse is not None and n_items <= nmax_items_collapse: + collapsed = False + else: + collapsed = True + + return collapsible_section( + name, body_func(mapping), n_items=n_items, + enabled=enabled, collapsed=collapsed + ) + + +def dim_section(obj): + body = format_dims(obj.dims, list(obj.coords)) + + return collapsible_section('Dimensions', body, + enabled=False, collapsed=True) + + +coord_section = partial(_generic_section, + name='Coordinates', body_func=summarize_vars, + nmax_items_collapse=25) + + +datavar_section = partial(_generic_section, + name='Data variables', body_func=summarize_vars, + nmax_items_collapse=15) + + +attr_section = partial(_generic_section, + name='Attributes', body_func=summarize_attrs, + nmax_items_collapse=10) + + +def dataset_repr(ds): + d = {} + + d['header'] = "
    xarray.Dataset
    " + d['style'] = "".format(XR_REPR_STYLE) + + sections = [dim_section(ds), + coord_section(ds.coords), + datavar_section(ds.data_vars), + attr_section(ds.attrs)] + + d['sections'] = "".join("
  • {}
  • ".format(s) + for s in sections) + + return ("
    {style}
    " + "{header}
      {sections}
    " + "
    " + .format(**d)) From f87c372549e5262b5d6a157219ae117e943f340b Mon Sep 17 00:00:00 2001 From: Benoit Bovy Date: Thu, 11 Jan 2018 15:05:14 +0100 Subject: [PATCH 02/42] move CSS code to its own file in a new static directory --- setup.py | 2 +- xarray/core/formatting_html.py | 252 +------------------- xarray/static/css/style-jupyterlab.css | 316 +++++++++++++++++++++++++ 3 files changed, 322 insertions(+), 248 deletions(-) mode change 100644 => 100755 setup.py create mode 100644 xarray/static/css/style-jupyterlab.css diff --git a/setup.py b/setup.py old mode 100644 new mode 100755 index 08d4f54764f..079bc974f59 --- a/setup.py +++ b/setup.py @@ -104,5 +104,5 @@ tests_require=TESTS_REQUIRE, url=URL, packages=find_packages(), - package_data={"xarray": ["py.typed", "tests/data/*"]}, + package_data={"xarray": ["py.typed", "tests/data/*", "static/*"]}, ) diff --git a/xarray/core/formatting_html.py b/xarray/core/formatting_html.py index 6a8fcf97e35..95d5558ad67 100644 --- a/xarray/core/formatting_html.py +++ b/xarray/core/formatting_html.py @@ -2,255 +2,13 @@ import uuid from functools import partial +import pkg_resources from .formatting import format_array_flat -XR_REPR_STYLE = """ -.xr-wrap { - width: 540px; - font-size: 13px; - line-height: 1.5; - background-color: #fff; -} - -.xr-wrap ul { - padding: 0; -} - -.xr-header { - padding: 6px 0 6px 3px; - border-bottom-width: 1px; - border-bottom-style: solid; - border-bottom-color: #777; - color: #555;; -} - -ul.xr-sections { - list-style: none !important; - padding: 3px !important; - margin: 0 !important; -} - -input.xr-section-in { - display: none; -} - -input.xr-section-in + label { - display: inline-block; - width: 140px; - color: #555; - font-weight: 500; - padding: 4px 0 2px 0; -} - -input.xr-section-in:enabled + label { - cursor: pointer; -} - -input.xr-section-in + label:before { - display: inline-block; - content: '►'; - font-size: 11px; - width: 15px; - text-align: center; -} - -input.xr-section-in:checked + label:before { - content: '▼'; -} - -input.xr-section-in:disabled + label:before { - color: #777; -} - -input.xr-section-in + label > span { - display: inline-block; - margin-left: 4px; -} - -input.xr-section-in:checked + label > span { - display: none; -} - -input.xr-section-in ~ ul { - display: none; -} - -input.xr-section-in:checked ~ ul { - display: block; -} - -.xr-sections summary > div { - display: inline-block; - cursor: pointer; - width: 140px; - color: #555; - font-weight: 500; - padding: 4px 0 2px 0; -} - -.xr-dim-list { - display: inline-block !important; - list-style: none; - padding: 0 !important; -} - -.xr-dim-list li { - display: inline-block; - font-size: 13px !important; - padding: 0; - margin: 0; -} - -.xr-dim-list:before { - content: '('; -} - -.xr-dim-list:after { - content: ')'; -} - -.xr-dim-list li:not(:last-child):after { - content: ','; - padding-right: 5px; -} - -.xr-has-index { - text-decoration: underline; -} - -ul.xr-var-list { - list-style: none !important; - padding: 0 !important; - margin: 0 !important; -} - -.xr-var-list > li { - background-color: #fcfcfc; - overflow: hidden; -} - -.xr-var-list > li:nth-child(odd) { - background-color: #efefef; -} - -.xr-var-list li:hover { - background-color: rgba(3, 169, 244, .2); -} - -.xr-var-list li > span { - display: inline-block; -} - -.xr-var-list li input { - display: none; -} - -.xr-var-list li input:enabled + label { - cursor: pointer; -} - -input.xr-varname-in + label { - display: inline-block; - width: 140px; - padding-left: 0; -} - -input.xr-varname-in + label:before { - content: ' '; - display: inline-block; - font-size: 11px; - width: 15px; - padding-left: 20px; - padding-right: 5px; - text-align: center; - color: #aaa; - text-decoration: none !important; -} - -input.xr-varname-in ~ ul { - display: none; -} - -input.xr-varname-in:checked ~ ul { - display: block; -} - -input.xr-varname-in:enabled + label:before { - content: 'a'; -} - -input.xr-varname-in:enabled + label:hover:before { - color: #000; -} - -input.xr-varname-in:checked + label:before { - color: #ccc; -} - -.xr-dims { - width: 80px; -} - -.xr-dtype { - width: 96px; - padding-right: 4px; - text-align: right; - color: #555; -} - -.xr-values { - width: 200px; - text-align: left; - color: #888; - white-space: nowrap; - font-size: 12px; -} - -.xr-values > span:nth-child(odd) { - color: rgba(0, 0, 0, .65); -} - -input.xr-values-in + label:hover > span { - color: #000; -} - -input.xr-values-in:checked + label > span { - color: #ccc; -} - -input.xr-values-in ~ pre { - display: none; -} - -input.xr-values-in:checked ~ pre { - display: block; -} - -input.xr-values-in:checked + label > span { - color: #ccc; -} - -.xr-data-repr { - font-size: 11px !important; - background-color: #fff; - padding: 4px 0 6px 40px !important; - margin: 0 !important; -} - -.xr-attr-list { - list-style: none !important; - background-color: #fff; - padding: 0 0 6px 40px !important; - color: #555; -} - -.xr-attr-list li, -.xr-attr-list li:hover { - background-color: #fff; -} -""" +CSS_FILE_PATH = '/'.join(('static', 'css', 'style-jupyterlab.css')) +CSS_STYLE = pkg_resources.resource_string('xarray', CSS_FILE_PATH) def format_dims(dims, coord_names): @@ -296,7 +54,7 @@ def summarize_variable(name, var): if len(var.attrs): d['disabled'] = '' - d['attrs'] = format_attrs(var.attrs) + d['attrs'] = summarize_attrs(var.attrs) else: d['disabled'] = 'disabled' d['attrs'] = '' @@ -391,7 +149,7 @@ def dataset_repr(ds): d = {} d['header'] = "
    xarray.Dataset
    " - d['style'] = "".format(XR_REPR_STYLE) + d['style'] = "".format(CSS_STYLE) sections = [dim_section(ds), coord_section(ds.coords), diff --git a/xarray/static/css/style-jupyterlab.css b/xarray/static/css/style-jupyterlab.css new file mode 100644 index 00000000000..f032ed6e159 --- /dev/null +++ b/xarray/static/css/style-jupyterlab.css @@ -0,0 +1,316 @@ +/* CSS stylesheet for displaying xarray objects in jupyterlab. + * + */ + +.xr-wrap { + width: 540px; + font-size: 13px; + line-height: 1.5; + background-color: #fff; +} + +.xr-wrap ul { + padding: 0; +} + +.xr-header { + padding: 6px 0 6px 3px; + border-bottom-width: 1px; + border-bottom-style: solid; + border-bottom-color: #777; + color: #555;; +} + +.xr-header > div, +.xr-header > ul { + display: inline; + margin-top: 0; + margin-bottom: 0; +} + +.xr-dataarray-cls, +.xr-dataarray-name { + margin-left: 2px; + margin-right: 10px; +} + +.xr-dataarray-name { + color: #000; +} + +ul.xr-sections { + list-style: none !important; + padding: 3px !important; + margin: 0 !important; +} + +input.xr-section-in { + display: none; +} + +input.xr-section-in + label { + display: inline-block; + width: 140px; + color: #555; + font-weight: 500; + padding: 4px 0 2px 0; +} + +input.xr-section-in:enabled + label { + cursor: pointer; +} + +input.xr-section-in + label:before { + display: inline-block; + content: '►'; + font-size: 11px; + width: 15px; + text-align: center; +} + +input.xr-section-in:checked + label:before { + content: '▼'; +} + +input.xr-section-in:disabled + label:before { + color: #777; +} + +input.xr-section-in + label > span { + display: inline-block; + margin-left: 4px; +} + +input.xr-section-in:checked + label > span { + display: none; +} + +input.xr-section-in ~ ul { + display: none; +} + +input.xr-section-in:checked ~ ul { + display: block; +} + +.xr-sections summary > div { + display: inline-block; + cursor: pointer; + width: 140px; + color: #555; + font-weight: 500; + padding: 4px 0 2px 0; +} + +.xr-dim-list { + display: inline-block !important; + list-style: none; + padding: 0 !important; +} + +.xr-dim-list li { + display: inline-block; + font-size: 13px !important; + padding: 0; + margin: 0; +} + +.xr-dim-list:before { + content: '('; +} + +.xr-dim-list:after { + content: ')'; +} + +.xr-dim-list li:not(:last-child):after { + content: ','; + padding-right: 5px; +} + +.xr-has-index { + text-decoration: underline; +} + +input.xr-dataarray-in { + display: none; +} + +input.xr-dataarray-in + label { + display: inline-block; + width: 15px !important; + vertical-align: top; + padding: 4px 0 2px 0 !important; +} + +input.xr-dataarray-in + label:before { + content: '➕' !important; +} + +input.xr-dataarray-in:checked + label:before { + content: '➖' !important; +} + +input.xr-dataarray-in:enabled + label { + cursor: pointer; +} + +input.xr-dataarray-in ~ pre, +input.xr-dataarray-in ~ div { + font-size: 12px; + width: 500px; + padding: 5px 0 4px 8px !important; + margin: 0; +} + +input.xr-dataarray-in ~ div > span { + display: inline-block; + margin-right: 4px; +} + +input.xr-dataarray-in ~ pre { + display: none; +} + +input.xr-dataarray-in:checked ~ pre { + display: inline-block; +} + +input.xr-dataarray-in ~ div { + display: inline-block; +} + +input.xr-dataarray-in:checked ~ div { + display: none; +} + +ul.xr-var-list { + list-style: none !important; + padding: 0 !important; + margin: 0 !important; +} + +.xr-var-list > li { + background-color: #fcfcfc; + overflow: hidden; +} + +.xr-var-list > li:nth-child(odd) { + background-color: #efefef; +} + +.xr-var-list li:hover { + background-color: rgba(3, 169, 244, .2); +} + +.xr-var-list li > span { + display: inline-block; +} + +.xr-var-list li input { + display: none; +} + +.xr-var-list li input:enabled + label { + cursor: pointer; +} + +input.xr-varname-in + label { + display: inline-block; + width: 140px; + padding-left: 0; +} + +input.xr-varname-in + label:before { + content: ' '; + display: inline-block; + font-size: 11px; + width: 15px; + padding-left: 20px; + padding-right: 5px; + text-align: center; + color: #aaa; + text-decoration: none !important; +} + +input.xr-varname-in ~ ul { + display: none; +} + +input.xr-varname-in:checked ~ ul { + display: block; +} + +input.xr-varname-in:enabled + label:before { + content: 'a'; +} + +input.xr-varname-in:enabled + label:hover:before { + color: #000; +} + +input.xr-varname-in:checked + label:before { + color: #ccc; +} + +.xr-dims { + width: 80px; +} + +.xr-dtype { + width: 96px; + padding-right: 4px; + text-align: right; + color: #555; +} + +.xr-values { + width: 200px; + text-align: left; + color: #888; + white-space: nowrap; + font-size: 12px; +} + +.xr-values > span:nth-child(odd) { + color: rgba(0, 0, 0, .65); +} + +input.xr-values-in + label:hover > span { + color: #000; +} + +input.xr-values-in:checked + label > span { + color: #ccc; +} + +input.xr-values-in ~ pre { + display: none; +} + +input.xr-values-in:checked ~ pre { + display: block; +} + +input.xr-values-in:checked + label > span { + color: #ccc; +} + +.xr-data-repr { + font-size: 11px !important; + background-color: #fff; + padding: 4px 0 6px 40px !important; + margin: 0 !important; +} + +.xr-attr-list { + list-style: none !important; + background-color: #fff; + padding: 0 0 6px 40px !important; + color: #555; +} + +.xr-attr-list li, +.xr-attr-list li:hover { + background-color: #fff; +} From 11e919cb9548cfbc064d0263be036fe29915e881 Mon Sep 17 00:00:00 2001 From: Benoit Bovy Date: Thu, 11 Jan 2018 15:13:29 +0100 Subject: [PATCH 03/42] add repr of array objects + some refactoring and fixes --- xarray/core/formatting_html.py | 123 ++++++++++++++++++++++++++------- 1 file changed, 97 insertions(+), 26 deletions(-) diff --git a/xarray/core/formatting_html.py b/xarray/core/formatting_html.py index 95d5558ad67..f12c5c52050 100644 --- a/xarray/core/formatting_html.py +++ b/xarray/core/formatting_html.py @@ -1,8 +1,9 @@ # coding: utf-8 import uuid -from functools import partial import pkg_resources +from functools import partial +from collections import OrderedDict from .formatting import format_array_flat @@ -12,6 +13,9 @@ 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()} @@ -22,8 +26,8 @@ def format_dims(dims, coord_names): return "
      {}
    ".format(dims_li) -def format_values_preview(var): - pprint_str = format_array_flat(var, 35) +def format_values_preview(array, max_char=35): + pprint_str = format_array_flat(array, max_char) return "".join("{} ".format(s) for s in pprint_str.split()) @@ -59,12 +63,14 @@ def summarize_variable(name, var): d['disabled'] = 'disabled' d['attrs'] = '' + # TODO: no value preview if not in memory d['values_preview'] = format_values_preview(var) d['attrs_subsection'] = summarize_attrs(var.attrs) d['data_repr_subsection'] = repr(var.data) return ( - "" + "" "" "{dims_str}" "{dtype}" @@ -83,18 +89,28 @@ def summarize_vars(variables): def collapsible_section(name, body, n_items=None, - enabled=True, collapsed=False): + enabled=True, collapsed=False, + input_cssclass='xr-section-in'): d = {} + d['input_cssclass'] = input_cssclass + # "unique" id to expand/collapse the section d['section_id'] = 'section-' + str(uuid.uuid4()) - if n_items is not None: - n_items_span = " ({})".format(n_items) + if name is not None: + if n_items is not None: + n_items_span = " ({})".format(n_items) + else: + n_items_span = '' + + d['title'] = "{}:{}".format(name, n_items_span) + else: - n_items_span = '' + d['title'] = "" - d['title'] = "{}:{}".format(name, n_items_span) + if n_items is not None and not n_items: + collapsed = True d['body'] = body @@ -102,17 +118,18 @@ def collapsible_section(name, body, n_items=None, d['collapsed'] = '' if collapsed else 'checked' return ( - "" + "" "" "{body}" .format(**d)) -def _generic_section(mapping, name, body_func, - enabled=True, nmax_items_collapse=None): +def _mapping_section(mapping, name, body_func, + enabled=True, max_items_collapse=None): n_items = len(mapping) - if nmax_items_collapse is not None and n_items <= nmax_items_collapse: + if max_items_collapse is not None and n_items <= max_items_collapse: collapsed = False else: collapsed = True @@ -130,31 +147,41 @@ def dim_section(obj): enabled=False, collapsed=True) -coord_section = partial(_generic_section, +def array_section(obj): + # TODO: no value preview if not in memory + values_preview_div = "
    {}
    ".format( + format_values_preview(obj.values, max_char=70)) + + data_repr_pre = "
    {}
    ".format(repr(obj.data)) + + body = values_preview_div + data_repr_pre + + # TODO: maybe collapse section dep. on number of lines in
    +    return collapsible_section(None, body, input_cssclass='xr-dataarray-in')
    +
    +
    +coord_section = partial(_mapping_section,
                             name='Coordinates', body_func=summarize_vars,
    -                        nmax_items_collapse=25)
    +                        max_items_collapse=25)
     
     
    -datavar_section = partial(_generic_section,
    +datavar_section = partial(_mapping_section,
                               name='Data variables', body_func=summarize_vars,
    -                          nmax_items_collapse=15)
    +                          max_items_collapse=15)
     
     
    -attr_section = partial(_generic_section,
    +attr_section = partial(_mapping_section,
                            name='Attributes', body_func=summarize_attrs,
    -                       nmax_items_collapse=10)
    +                       max_items_collapse=10)
     
     
    -def dataset_repr(ds):
    +def _obj_repr(header_components, sections):
         d = {}
     
    -    d['header'] = "
    xarray.Dataset
    " - d['style'] = "".format(CSS_STYLE) + d['header'] = "
    {}
    ".format( + "".join(comp for comp in header_components)) - sections = [dim_section(ds), - coord_section(ds.coords), - datavar_section(ds.data_vars), - attr_section(ds.attrs)] + d['style'] = "".format(CSS_STYLE) d['sections'] = "".join("
  • {}
  • ".format(s) for s in sections) @@ -163,3 +190,47 @@ def dataset_repr(ds): "{header}
      {sections}
    " "" .format(**d)) + + +def array_repr(arr): + dims = OrderedDict((k, v) for k, v in zip(arr.dims, arr.shape)) + + arr_type = "xarray.{}".format(type(arr).__name__) + + if hasattr(arr, 'name') and arr.name is not None: + arr_name = "'{}'".format(arr.name) + else: + arr_name = "" + + if hasattr(arr, 'coords'): + coord_names = list(arr.coords) + else: + coord_names = [] + + header_components = [ + "
    {}
    ".format(arr_type), + "
    {}
    ".format(arr_name), + format_dims(dims, coord_names) + ] + + sections = [] + + sections.append(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): + header_components = ["xarray.{}".format(type(ds).__name__)] + + sections = [dim_section(ds), + coord_section(ds.coords), + datavar_section(ds.data_vars), + attr_section(ds.attrs)] + + return _obj_repr(header_components, sections) From 0c7e0e98f2614faf57034badc4eb655a944e5357 Mon Sep 17 00:00:00 2001 From: Benoit Bovy Date: Thu, 11 Jan 2018 15:40:19 +0100 Subject: [PATCH 04/42] add _repr_html_ methods to dataset, dataarray and variable --- xarray/core/common.py | 5 ++++- xarray/core/dataset.py | 4 ++++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/xarray/core/common.py b/xarray/core/common.py index 45d860a1797..fcdf8de29df 100644 --- a/xarray/core/common.py +++ b/xarray/core/common.py @@ -18,7 +18,7 @@ import numpy as np import pandas as pd -from . import dtypes, duck_array_ops, formatting, ops +from . import dtypes, duck_array_ops, formatting, formatting_html, ops from .arithmetic import SupportsArithmetic from .npcompat import DTypeLike from .options import _get_keep_attrs @@ -134,6 +134,9 @@ def __array__(self: Any, dtype: DTypeLike = None) -> np.ndarray: def __repr__(self) -> str: return formatting.array_repr(self) + def _repr_html_(self): + return formatting_html.array_repr(self) + def _iter(self: Any) -> Iterator[Any]: for n in range(len(self)): yield self[n] diff --git a/xarray/core/dataset.py b/xarray/core/dataset.py index 12d5cbdc9f3..efffd50f852 100644 --- a/xarray/core/dataset.py +++ b/xarray/core/dataset.py @@ -39,6 +39,7 @@ dtypes, duck_array_ops, formatting, + formatting_html, groupby, ops, resample, @@ -1619,6 +1620,9 @@ def to_zarr( def __repr__(self) -> str: return formatting.dataset_repr(self) + def _repr_html_(self): + return formatting_html.dataset_repr(self) + def info(self, buf=None) -> None: """ Concise summary of a Dataset variables and attributes. From 732eb3eb7d8250fa823f0749e8aaa3fc36b2f21d Mon Sep 17 00:00:00 2001 From: Benoit Bovy Date: Thu, 11 Jan 2018 15:41:09 +0100 Subject: [PATCH 05/42] fix encoding issue in read CSS --- xarray/core/formatting_html.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/xarray/core/formatting_html.py b/xarray/core/formatting_html.py index f12c5c52050..6693ea8cbe6 100644 --- a/xarray/core/formatting_html.py +++ b/xarray/core/formatting_html.py @@ -9,7 +9,9 @@ CSS_FILE_PATH = '/'.join(('static', 'css', 'style-jupyterlab.css')) -CSS_STYLE = pkg_resources.resource_string('xarray', CSS_FILE_PATH) +CSS_STYLE = (pkg_resources + .resource_string('xarray', CSS_FILE_PATH) + .decode('utf8')) def format_dims(dims, coord_names): From 0cb748cb881d543b7eb91cb89ea90001481c7783 Mon Sep 17 00:00:00 2001 From: Benoit Bovy Date: Thu, 11 Jan 2018 16:28:40 +0100 Subject: [PATCH 06/42] fix some CSS for compatibility with notebook (tested 5.2) --- xarray/static/css/style-jupyterlab.css | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/xarray/static/css/style-jupyterlab.css b/xarray/static/css/style-jupyterlab.css index f032ed6e159..738d7aceff7 100644 --- a/xarray/static/css/style-jupyterlab.css +++ b/xarray/static/css/style-jupyterlab.css @@ -13,6 +13,10 @@ padding: 0; } +.xr-wrap input + label { + margin-bottom: 0 !important; +} + .xr-header { padding: 6px 0 6px 3px; border-bottom-width: 1px; @@ -226,8 +230,8 @@ input.xr-varname-in + label:before { display: inline-block; font-size: 11px; width: 15px; - padding-left: 20px; - padding-right: 5px; + margin-left: 20px; + margin-right: 5px; text-align: center; color: #aaa; text-decoration: none !important; @@ -286,6 +290,7 @@ input.xr-values-in:checked + label > span { input.xr-values-in ~ pre { display: none; + background-color: white; } input.xr-values-in:checked ~ pre { From 877fb06503eaf5a732e33341da2ce455f77cb934 Mon Sep 17 00:00:00 2001 From: Benoit Bovy Date: Thu, 25 Jan 2018 20:03:15 +0100 Subject: [PATCH 07/42] use CSS grid + add icons to show/hide attrs and data repr --- xarray/core/formatting_html.py | 161 +++++++---- xarray/static/css/style-jupyterlab.css | 323 ++++++++++------------- xarray/static/html/icons-svg-inline.html | 17 ++ 3 files changed, 258 insertions(+), 243 deletions(-) create mode 100644 xarray/static/html/icons-svg-inline.html diff --git a/xarray/core/formatting_html.py b/xarray/core/formatting_html.py index 6693ea8cbe6..8b95e075d43 100644 --- a/xarray/core/formatting_html.py +++ b/xarray/core/formatting_html.py @@ -14,6 +14,12 @@ .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 format_dims(dims, coord_names): if not dims: return '' @@ -21,8 +27,8 @@ def format_dims(dims, coord_names): dim_css_map = {k: " class='xr-has-index'" if k in coord_names else '' for k, v in dims.items()} - dims_li = "".join("
  • {name}: {size}
  • " - .format(cssclass=dim_css_map[k], name=k, size=v) + dims_li = "".join("
  • {name}: {size}
  • " + .format(cssclass_idx=dim_css_map[k], name=k, size=v) for k, v in dims.items()) return "
      {}
    ".format(dims_li) @@ -39,7 +45,16 @@ def summarize_attrs(attrs): attrs_li = "".join("
  • {} : {}
  • ".format(k, v) for k, v in attrs.items()) - return "
      {}
    ".format(attrs_li) + return "
      {}
    ".format(attrs_li) + + +def _icon(icon_name): + # icon_name should be defined in xarray/static/html/icon-svg-inline.html + return ("" + "" + "" + "" + .format(icon_name)) def summarize_variable(name, var): @@ -48,15 +63,17 @@ def summarize_variable(name, var): d['dims_str'] = '(' + ', '.join(dim for dim in var.dims) + ')' d['name'] = name - d['cssclass_varname'] = 'xr-varname' + if name in var.dims: - d['cssclass_varname'] += ' xr-has-index' + d['cssclass_idx'] = " class='xr-has-index'" + else: + d['cssclass_idx'] = "" d['dtype'] = var.dtype # "unique" ids required to expand/collapse subsections d['attrs_id'] = 'attrs-' + str(uuid.uuid4()) - d['values_id'] = 'values-' + str(uuid.uuid4()) + d['data_id'] = 'data-' + str(uuid.uuid4()) if len(var.attrs): d['disabled'] = '' @@ -66,68 +83,76 @@ def summarize_variable(name, var): d['attrs'] = '' # TODO: no value preview if not in memory - d['values_preview'] = format_values_preview(var) - d['attrs_subsection'] = summarize_attrs(var.attrs) - d['data_repr_subsection'] = repr(var.data) + d['preview'] = format_values_preview(var) + d['attrs_ul'] = summarize_attrs(var.attrs) + d['data_repr'] = repr(var.data) + + d['attrs_icon'] = _icon('icon-file-text2') + d['data_icon'] = _icon('icon-database') return ( - "{name}" + "
    {dims_str}
    " + "
    {dtype}
    " + "
    {preview}
    " + "" - "" - "{dims_str}" - "{dtype}" - "" - "" - "{attrs_subsection}" - "
    {data_repr_subsection}
    " + "" + "" + "" + "
    {attrs_ul}
    " + "
    {data_repr}
    " .format(**d)) def summarize_vars(variables): - vars_li = "".join("
  • {}
  • ".format(summarize_variable(k, v)) + vars_li = "".join("
  • {}
  • " + .format(summarize_variable(k, v)) for k, v in variables.items()) return "
      {}
    ".format(vars_li) -def collapsible_section(name, body, n_items=None, - enabled=True, collapsed=False, - input_cssclass='xr-section-in'): +def collapsible_section(name, inline_details=None, details=None, + n_items=None, enabled=True, collapsed=False): d = {} - d['input_cssclass'] = input_cssclass - # "unique" id to expand/collapse the section - d['section_id'] = 'section-' + str(uuid.uuid4()) - - if name is not None: - if n_items is not None: - n_items_span = " ({})".format(n_items) - else: - n_items_span = '' - - d['title'] = "{}:{}".format(name, n_items_span) + d['id'] = 'section-' + str(uuid.uuid4()) + if n_items is not None: + n_items_span = " ({})".format(n_items) else: - d['title'] = "" + n_items_span = '' + + d['title'] = "{}:{}".format(name, n_items_span) if n_items is not None and not n_items: collapsed = True - d['body'] = body + d['inline_details'] = inline_details or '' + d['details'] = details or '' d['enabled'] = '' if enabled else 'disabled' d['collapsed'] = '' if collapsed else 'checked' + if enabled: + d['tip'] = " title='Expand/collapse section'" + else: + d['tip'] = "" + return ( - "" - "" - "{body}" + "" + "
    {inline_details}
    " + "
    {details}
    " .format(**d)) -def _mapping_section(mapping, name, body_func, +def _mapping_section(mapping, name, details_func, enabled=True, max_items_collapse=None): n_items = len(mapping) @@ -137,43 +162,56 @@ def _mapping_section(mapping, name, body_func, collapsed = True return collapsible_section( - name, body_func(mapping), n_items=n_items, + name, details=details_func(mapping), n_items=n_items, enabled=enabled, collapsed=collapsed ) def dim_section(obj): - body = format_dims(obj.dims, list(obj.coords)) + dim_list = format_dims(obj.dims, list(obj.coords)) - return collapsible_section('Dimensions', body, + return collapsible_section('Dimensions', inline_details=dim_list, enabled=False, collapsed=True) def array_section(obj): + d = {} + + # "unique" id to expand/collapse the section + d['id'] = 'section-' + str(uuid.uuid4()) + # TODO: no value preview if not in memory - values_preview_div = "
    {}
    ".format( - format_values_preview(obj.values, max_char=70)) + d['preview'] = format_values_preview(obj.values, max_char=70) - data_repr_pre = "
    {}
    ".format(repr(obj.data)) + d['data_repr'] = repr(obj.data) - body = values_preview_div + data_repr_pre + # TODO: maybe collapse section dep. on number of lines in data repr + d['collapsed'] = '' - # TODO: maybe collapse section dep. on number of lines in
    -    return collapsible_section(None, body, input_cssclass='xr-dataarray-in')
    +    d['tip'] = "Show/hide data repr"
    +
    +    return (
    +        "
    " + "" + "" + "
    {preview}
    " + "
    {data_repr}
    " + "
    " + .format(**d)) coord_section = partial(_mapping_section, - name='Coordinates', body_func=summarize_vars, + name='Coordinates', details_func=summarize_vars, max_items_collapse=25) datavar_section = partial(_mapping_section, - name='Data variables', body_func=summarize_vars, + name='Data variables', details_func=summarize_vars, max_items_collapse=15) attr_section = partial(_mapping_section, - name='Attributes', body_func=summarize_attrs, + name='Attributes', details_func=summarize_attrs, max_items_collapse=10) @@ -183,21 +221,26 @@ def _obj_repr(header_components, sections): d['header'] = "
    {}
    ".format( "".join(comp for comp in header_components)) + d['icons'] = ICONS_SVG d['style'] = "".format(CSS_STYLE) - d['sections'] = "".join("
  • {}
  • ".format(s) + d['sections'] = "".join("
  • {}
  • ".format(s) for s in sections) - return ("
    {style}
    " - "{header}
      {sections}
    " - "
    " + return ("
    " + "{icons}{style}" + "
    " + "{header}" + "
      {sections}
    " + "
    " + "
    " .format(**d)) def array_repr(arr): dims = OrderedDict((k, v) for k, v in zip(arr.dims, arr.shape)) - arr_type = "xarray.{}".format(type(arr).__name__) + obj_type = "xarray.{}".format(type(arr).__name__) if hasattr(arr, 'name') and arr.name is not None: arr_name = "'{}'".format(arr.name) @@ -210,8 +253,8 @@ def array_repr(arr): coord_names = [] header_components = [ - "
    {}
    ".format(arr_type), - "
    {}
    ".format(arr_name), + "
    {}
    ".format(obj_type), + "
    {}
    ".format(arr_name), format_dims(dims, coord_names) ] @@ -228,7 +271,9 @@ def array_repr(arr): def dataset_repr(ds): - header_components = ["xarray.{}".format(type(ds).__name__)] + obj_type = "xarray.{}".format(type(ds).__name__) + + header_components = ["
    {}
    ".format(obj_type)] sections = [dim_section(ds), coord_section(ds.coords), diff --git a/xarray/static/css/style-jupyterlab.css b/xarray/static/css/style-jupyterlab.css index 738d7aceff7..f6c5968a3a8 100644 --- a/xarray/static/css/style-jupyterlab.css +++ b/xarray/static/css/style-jupyterlab.css @@ -3,26 +3,15 @@ */ .xr-wrap { - width: 540px; - font-size: 13px; - line-height: 1.5; - background-color: #fff; -} - -.xr-wrap ul { - padding: 0; -} - -.xr-wrap input + label { - margin-bottom: 0 !important; + min-width: 500px; + max-width: 700px; } .xr-header { - padding: 6px 0 6px 3px; - border-bottom-width: 1px; - border-bottom-style: solid; - border-bottom-color: #777; - color: #555;; + padding-top: 6px; + padding-bottom: 6px; + margin-bottom: 4px; + border-bottom: solid 1px #ddd; } .xr-header > div, @@ -32,39 +21,51 @@ margin-bottom: 0; } -.xr-dataarray-cls, -.xr-dataarray-name { +.xr-obj-type, +.xr-array-name { margin-left: 2px; margin-right: 10px; } -.xr-dataarray-name { +.xr-obj-type { + color: #555; +} + +.xr-array-name { color: #000; } -ul.xr-sections { - list-style: none !important; - padding: 3px !important; +.xr-sections { margin: 0 !important; + padding: 3px !important; + display: grid; + grid-template-columns: minmax(150px, auto) 0.5fr auto 1fr 20px 20px; +} + +.xr-section-item { + display: contents; } -input.xr-section-in { +.xr-section-item input { display: none; } -input.xr-section-in + label { - display: inline-block; - width: 140px; +.xr-section-item input:enabled + label { + cursor: pointer; +} + +.xr-section-summary { + grid-column: 1; color: #555; font-weight: 500; - padding: 4px 0 2px 0; } -input.xr-section-in:enabled + label { - cursor: pointer; +.xr-section-summary > span { + display: inline-block; + padding-left: 0.5em; } -input.xr-section-in + label:before { +.xr-section-summary-in + label:before { display: inline-block; content: '►'; font-size: 11px; @@ -72,250 +73,202 @@ input.xr-section-in + label:before { text-align: center; } -input.xr-section-in:checked + label:before { - content: '▼'; -} - -input.xr-section-in:disabled + label:before { - color: #777; -} - -input.xr-section-in + label > span { - display: inline-block; - margin-left: 4px; +.xr-section-summary-in:disabled + label:before { + color: #ccc; } -input.xr-section-in:checked + label > span { - display: none; +.xr-section-summary-in:checked + label:before { + content: '▼'; } -input.xr-section-in ~ ul { +.xr-section-summary-in:checked + label > span { display: none; } -input.xr-section-in:checked ~ ul { - display: block; -} - -.xr-sections summary > div { - display: inline-block; - cursor: pointer; - width: 140px; - color: #555; - font-weight: 500; - padding: 4px 0 2px 0; -} - -.xr-dim-list { - display: inline-block !important; - list-style: none; - padding: 0 !important; +.xr-section-summary, +.xr-section-inline-details { + padding-top: 4px; + padding-bottom: 4px; } -.xr-dim-list li { - display: inline-block; - font-size: 13px !important; - padding: 0; - margin: 0; +.xr-section-inline-details { + grid-column: 2 / -1; } -.xr-dim-list:before { - content: '('; -} - -.xr-dim-list:after { - content: ')'; -} - -.xr-dim-list li:not(:last-child):after { - content: ','; - padding-right: 5px; +.xr-section-details { + display: none; + grid-column: 1 / -1; + margin-bottom: 5px; } -.xr-has-index { - text-decoration: underline; +.xr-section-summary-in:checked ~ .xr-section-details { + display: contents; } -input.xr-dataarray-in { - display: none; +.xr-array-wrap { + grid-column: 1 / -1; } -input.xr-dataarray-in + label { +.xr-array-icon { display: inline-block; width: 15px !important; vertical-align: top; padding: 4px 0 2px 0 !important; } -input.xr-dataarray-in + label:before { - content: '➕' !important; +.xr-array-in + label:before { + content: '➕'; } -input.xr-dataarray-in:checked + label:before { - content: '➖' !important; +.xr-array-in:checked + label:before { + content: '➖'; } -input.xr-dataarray-in:enabled + label { - cursor: pointer; -} - -input.xr-dataarray-in ~ pre, -input.xr-dataarray-in ~ div { - font-size: 12px; - width: 500px; +.xr-array-preview, +.xr-array-data { padding: 5px 0 4px 8px !important; margin: 0; } -input.xr-dataarray-in ~ div > span { - display: inline-block; - margin-right: 4px; -} - -input.xr-dataarray-in ~ pre { +.xr-array-data, +.xr-array-in:checked ~ .xr-array-preview { display: none; } -input.xr-dataarray-in:checked ~ pre { +.xr-array-in:checked ~ .xr-array-data, +.xr-array-preview { display: inline-block; } -input.xr-dataarray-in ~ div { - display: inline-block; -} - -input.xr-dataarray-in:checked ~ div { - display: none; +.xr-preview > span > span:nth-child(odd) { + color: rgba(0, 0, 0, .65); } -ul.xr-var-list { - list-style: none !important; +.xr-dim-list { + display: inline-block !important; + list-style: none; padding: 0 !important; - margin: 0 !important; } -.xr-var-list > li { - background-color: #fcfcfc; - overflow: hidden; -} - -.xr-var-list > li:nth-child(odd) { - background-color: #efefef; -} - -.xr-var-list li:hover { - background-color: rgba(3, 169, 244, .2); +.xr-dim-list li { + display: inline-block; + padding: 0; + margin: 0; } -.xr-var-list li > span { - display: inline-block; +.xr-dim-list:before { + content: '('; } -.xr-var-list li input { - display: none; +.xr-dim-list:after { + content: ')'; } -.xr-var-list li input:enabled + label { - cursor: pointer; +.xr-dim-list li:not(:last-child):after { + content: ','; + padding-right: 5px; } -input.xr-varname-in + label { - display: inline-block; - width: 140px; - padding-left: 0; +.xr-has-index { + text-decoration: underline; } -input.xr-varname-in + label:before { - content: ' '; - display: inline-block; - font-size: 11px; - width: 15px; - margin-left: 20px; - margin-right: 5px; - text-align: center; - color: #aaa; - text-decoration: none !important; +.xr-var-list { + display: contents; } -input.xr-varname-in ~ ul { - display: none; +.xr-var-item { + display: contents; } -input.xr-varname-in:checked ~ ul { - display: block; +.xr-var-item > div, +.xr-var-item > label { + background-color: #fcfcfc; } -input.xr-varname-in:enabled + label:before { - content: 'a'; +.xr-var-list > li:nth-child(odd) > div, +.xr-var-list > li:nth-child(odd) > label { + background-color: #efefef; } -input.xr-varname-in:enabled + label:hover:before { - color: #000; +.xr-var-name { + grid-column: 1; } -input.xr-varname-in:checked + label:before { - color: #ccc; +.xr-var-name > span { + padding-right: 10px; } -.xr-dims { - width: 80px; +.xr-var-dims { + grid-column: 2; } -.xr-dtype { - width: 96px; - padding-right: 4px; +.xr-var-dtype { + grid-column: 3; text-align: right; + padding-right: 10px; color: #555; } -.xr-values { - width: 200px; - text-align: left; +.xr-var-preview { + grid-column: 4; color: #888; +} + +.xr-var-name > span, +.xr-var-dims, +.xr-var-dtype, +.xr-var-preview > span { white-space: nowrap; - font-size: 12px; + overflow-x: hidden; } -.xr-values > span:nth-child(odd) { - color: rgba(0, 0, 0, .65); +.xr-var-attrs, +.xr-var-data { + display: none; + background-color: #fff !important; + padding-bottom: 5px !important; } -input.xr-values-in + label:hover > span { - color: #000; +.xr-var-attrs-in:checked ~ .xr-var-attrs, +.xr-var-data-in:checked ~ .xr-var-data { + display: block; } -input.xr-values-in:checked + label > span { +.xr-var-item input + label { color: #ccc; } -input.xr-values-in ~ pre { - display: none; - background-color: white; +.xr-var-item input:enabled + label { + color: #555; } -input.xr-values-in:checked ~ pre { - display: block; +.xr-var-item input:enabled + label:hover { + color: #000; } -input.xr-values-in:checked + label > span { - color: #ccc; +.xr-var-name span, +.xr-var-data, +.xr-attrs { + padding-left: 25px !important; } -.xr-data-repr { - font-size: 11px !important; - background-color: #fff; - padding: 4px 0 6px 40px !important; - margin: 0 !important; +.xr-attrs, +.xr-var-attrs, +.xr-var-data { + grid-column: 1 / -1; } -.xr-attr-list { +.xr-attrs { list-style: none !important; - background-color: #fff; - padding: 0 0 6px 40px !important; - color: #555; } -.xr-attr-list li, -.xr-attr-list li:hover { - background-color: #fff; +.icon { + display: inline-block; + vertical-align: middle; + width: 1em; + height: 1.5em !important; + stroke-width: 0; + stroke: currentColor; + fill: currentColor; } diff --git a/xarray/static/html/icons-svg-inline.html b/xarray/static/html/icons-svg-inline.html new file mode 100644 index 00000000000..aa20a4f88b4 --- /dev/null +++ b/xarray/static/html/icons-svg-inline.html @@ -0,0 +1,17 @@ + + + +database + + + + + +file-text2 + + + + + + + From 6f60af67adb1ad007c66be5f65e9349e1ff2a62c Mon Sep 17 00:00:00 2001 From: Julia Signell Date: Mon, 21 Oct 2019 15:45:59 -0400 Subject: [PATCH 08/42] Changing title of icons to make tooltips better --- xarray/static/html/icons-svg-inline.html | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/xarray/static/html/icons-svg-inline.html b/xarray/static/html/icons-svg-inline.html index aa20a4f88b4..c44f89c4304 100644 --- a/xarray/static/html/icons-svg-inline.html +++ b/xarray/static/html/icons-svg-inline.html @@ -1,13 +1,13 @@ -database +Show/Hide data repr -file-text2 +Show/Hide attributes From d3f2901724322f6c2235c71068f9f85dbe0db1f4 Mon Sep 17 00:00:00 2001 From: Julia Signell Date: Mon, 21 Oct 2019 17:03:32 -0400 Subject: [PATCH 09/42] Adding option to set repr back to classic --- xarray/core/common.py | 5 ++++- xarray/core/dataset.py | 3 +++ xarray/core/options.py | 7 +++++++ 3 files changed, 14 insertions(+), 1 deletion(-) diff --git a/xarray/core/common.py b/xarray/core/common.py index fcdf8de29df..553f07fcc37 100644 --- a/xarray/core/common.py +++ b/xarray/core/common.py @@ -21,7 +21,7 @@ from . import dtypes, duck_array_ops, formatting, formatting_html, ops from .arithmetic import SupportsArithmetic from .npcompat import DTypeLike -from .options import _get_keep_attrs +from .options import OPTIONS, _get_keep_attrs from .pycompat import dask_array_type from .rolling_exp import RollingExp from .utils import Frozen, ReprObject, either_dict_or_kwargs @@ -135,6 +135,9 @@ def __repr__(self) -> str: return formatting.array_repr(self) def _repr_html_(self): + if OPTIONS["display_style"] == "classic": + classic = repr(self).replace("<", "<").replace(">", ">") + return "
    {repr}
    ".format(repr=classic) return formatting_html.array_repr(self) def _iter(self: Any) -> Iterator[Any]: diff --git a/xarray/core/dataset.py b/xarray/core/dataset.py index efffd50f852..1ad3a48b83b 100644 --- a/xarray/core/dataset.py +++ b/xarray/core/dataset.py @@ -1621,6 +1621,9 @@ def __repr__(self) -> str: return formatting.dataset_repr(self) def _repr_html_(self): + if OPTIONS["display_style"] == "classic": + classic = repr(self).replace("<", "<").replace(">", ">") + return "
    {repr}
    ".format(repr=classic) return formatting_html.dataset_repr(self) def info(self, buf=None) -> None: diff --git a/xarray/core/options.py b/xarray/core/options.py index 2f464a33fb1..cdc2e393b03 100644 --- a/xarray/core/options.py +++ b/xarray/core/options.py @@ -8,6 +8,7 @@ CMAP_SEQUENTIAL = "cmap_sequential" CMAP_DIVERGENT = "cmap_divergent" KEEP_ATTRS = "keep_attrs" +DISPLAY_STYLE = "display_style" OPTIONS = { @@ -19,9 +20,11 @@ CMAP_SEQUENTIAL: "viridis", CMAP_DIVERGENT: "RdBu_r", KEEP_ATTRS: "default", + DISPLAY_STYLE: "classic", } _JOIN_OPTIONS = frozenset(["inner", "outer", "left", "right", "exact"]) +_DISPLAY_OPTIONS = frozenset(["classic", "html"]) def _positive_integer(value): @@ -35,6 +38,7 @@ def _positive_integer(value): FILE_CACHE_MAXSIZE: _positive_integer, WARN_FOR_UNCLOSED_FILES: lambda value: isinstance(value, bool), KEEP_ATTRS: lambda choice: choice in [True, False, "default"], + DISPLAY_STYLE: _DISPLAY_OPTIONS.__contains__, } @@ -98,6 +102,9 @@ class set_options: attrs, ``False`` to always discard them, or ``'default'`` to use original logic that attrs should only be kept in unambiguous circumstances. Default: ``'default'``. + - ``display_style``: display style to use in jupyter for xarray objects. + Default: ``classic``. Other options are ``html``. + You can use ``set_options`` either as a context manager: From 729160461f24728ddc2c896d7660ad38e89140b6 Mon Sep 17 00:00:00 2001 From: Julia Signell Date: Mon, 21 Oct 2019 18:12:05 -0400 Subject: [PATCH 10/42] Adding support for multiindexes --- xarray/core/formatting_html.py | 50 +++++++++++++++++++++++++++------- 1 file changed, 40 insertions(+), 10 deletions(-) diff --git a/xarray/core/formatting_html.py b/xarray/core/formatting_html.py index 8b95e075d43..ac92acb10c3 100644 --- a/xarray/core/formatting_html.py +++ b/xarray/core/formatting_html.py @@ -57,19 +57,49 @@ def _icon(icon_name): .format(icon_name)) -def summarize_variable(name, var): +def _summarize_coord_multiindex(name, coord, d): + d['dtype'] = 'MultiIndex' + d['preview'] = '(' + ', '.join(l for l in coord.level_names) + ')' + return summarize_variable(name, coord, d) + + +def summarize_coord(name, var): d = {} + is_index = name in var.dims + d['cssclass_idx'] = " class='xr-has-index'" if is_index else "" + if is_index: + coord = var.variable.to_index_variable() + if coord.level_names is not None: + coords = {} + coords[name] = _summarize_coord_multiindex(name, coord, d) - d['dims_str'] = '(' + ', '.join(dim for dim in var.dims) + ')' + for lname in coord.level_names: + var = coord.get_level_variable(lname) + coords[lname] = summarize_variable(lname, var) - d['name'] = name + return coords + + return {name: summarize_variable(name, var.variable, d)} - if name in var.dims: - d['cssclass_idx'] = " class='xr-has-index'" - else: - d['cssclass_idx'] = "" - d['dtype'] = var.dtype +def summarize_coords(variables): + coords = {} + for k, v in variables.items(): + coords.update(**summarize_coord(k, v)) + + vars_li = "".join("
  • {}
  • " + .format(v) for v in coords.values()) + + return "
      {}
    ".format(vars_li) + + +def summarize_variable(name, var, d=None): + if d is None: + d = {'cssclass_idx': ""} + + d['dims_str'] = '(' + ', '.join(dim for dim in var.dims) + ')' + d['name'] = name + d['dtype'] = d.get('dtype', var.dtype) # "unique" ids required to expand/collapse subsections d['attrs_id'] = 'attrs-' + str(uuid.uuid4()) @@ -83,7 +113,7 @@ def summarize_variable(name, var): d['attrs'] = '' # TODO: no value preview if not in memory - d['preview'] = format_values_preview(var) + d['preview'] = d.get('preview', format_values_preview(var)) d['attrs_ul'] = summarize_attrs(var.attrs) d['data_repr'] = repr(var.data) @@ -201,7 +231,7 @@ def array_section(obj): coord_section = partial(_mapping_section, - name='Coordinates', details_func=summarize_vars, + name='Coordinates', details_func=summarize_coords, max_items_collapse=25) From a5bbd862baa0aa08b631badf96f63ad1c3308586 Mon Sep 17 00:00:00 2001 From: Julia Signell Date: Tue, 22 Oct 2019 10:04:02 -0400 Subject: [PATCH 11/42] Getting rid of some spans and fixing alignment --- xarray/core/formatting_html.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/xarray/core/formatting_html.py b/xarray/core/formatting_html.py index ac92acb10c3..7c72a5d61f6 100644 --- a/xarray/core/formatting_html.py +++ b/xarray/core/formatting_html.py @@ -37,8 +37,7 @@ def format_dims(dims, coord_names): def format_values_preview(array, max_char=35): pprint_str = format_array_flat(array, max_char) - return "".join("{} ".format(s) - for s in pprint_str.split()) + return "".join("{} ".format(s) for s in pprint_str.split()) def summarize_attrs(attrs): @@ -50,7 +49,7 @@ def summarize_attrs(attrs): def _icon(icon_name): # icon_name should be defined in xarray/static/html/icon-svg-inline.html - return ("" + return ("" "" "" "" @@ -124,7 +123,7 @@ def summarize_variable(name, var, d=None): "
    {name}
    " "
    {dims_str}
    " "
    {dtype}
    " - "
    {preview}
    " + "
    {preview}
    " "" "