diff --git a/setup.py b/setup.py
old mode 100644
new mode 100755
index ccffc6369e8..9c0f50f406d
--- a/setup.py
+++ b/setup.py
@@ -147,4 +147,6 @@ def write_version_py(filename=None):
tests_require=TESTS_REQUIRE,
url=URL,
packages=find_packages(),
- package_data={'xarray': ['tests/data/*', 'plot/default_colormap.csv']})
+ package_data={'xarray': ['static/*',
+ 'tests/data/*',
+ 'plot/default_colormap.csv']})
diff --git a/xarray/core/common.py b/xarray/core/common.py
index 3bfcd484474..386f61f79a0 100644
--- a/xarray/core/common.py
+++ b/xarray/core/common.py
@@ -8,6 +8,7 @@
from .pycompat import basestring, suppress, dask_array_type, OrderedDict
from . import dtypes
from . import formatting
+from . import formatting_html
from . import ops
from .utils import SortedKeysDict, not_implemented, Frozen
@@ -99,6 +100,9 @@ def __array__(self, dtype=None):
def __repr__(self):
return formatting.array_repr(self)
+ def _repr_html_(self):
+ return formatting_html.array_repr(self)
+
def _iter(self):
for n in range(len(self)):
yield self[n]
diff --git a/xarray/core/dataset.py b/xarray/core/dataset.py
index 58847bb0086..3e2e9e5e792 100644
--- a/xarray/core/dataset.py
+++ b/xarray/core/dataset.py
@@ -20,6 +20,7 @@
from . import indexing
from . import alignment
from . import formatting
+from . import formatting_html
from . import duck_array_ops
from .. import conventions
from .alignment import align
@@ -1169,6 +1170,9 @@ def to_zarr(self, store=None, mode='w-', synchronizer=None, group=None,
def __unicode__(self):
return formatting.dataset_repr(self)
+ def _repr_html_(self):
+ return formatting_html.dataset_repr(self)
+
def info(self, buf=None):
"""
Concise summary of a Dataset variables and attributes.
diff --git a/xarray/core/formatting_html.py b/xarray/core/formatting_html.py
new file mode 100644
index 00000000000..8b95e075d43
--- /dev/null
+++ b/xarray/core/formatting_html.py
@@ -0,0 +1,283 @@
+# coding: utf-8
+
+import uuid
+import pkg_resources
+from functools import partial
+from collections import OrderedDict
+
+from .formatting import format_array_flat
+
+
+CSS_FILE_PATH = '/'.join(('static', 'css', 'style-jupyterlab.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 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("
{name}: {size}"
+ .format(cssclass_idx=dim_css_map[k], name=k, size=v)
+ for k, v in dims.items())
+
+ return "".format(dims_li)
+
+
+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())
+
+
+def summarize_attrs(attrs):
+ attrs_li = "".join("{} : {}".format(k, v)
+ for k, v in attrs.items())
+
+ 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):
+ d = {}
+
+ d['dims_str'] = '(' + ', '.join(dim for dim in var.dims) + ')'
+
+ d['name'] = name
+
+ if name in var.dims:
+ 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['data_id'] = 'data-' + str(uuid.uuid4())
+
+ if len(var.attrs):
+ d['disabled'] = ''
+ d['attrs'] = summarize_attrs(var.attrs)
+ else:
+ d['disabled'] = 'disabled'
+ d['attrs'] = ''
+
+ # TODO: no value preview if not in memory
+ 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}
"
+ ""
+ ""
+ ""
+ ""
+ "{attrs_ul}
"
+ "{data_repr}
"
+ .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, inline_details=None, details=None,
+ n_items=None, enabled=True, collapsed=False):
+ d = {}
+
+ # "unique" id to expand/collapse the section
+ d['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)
+
+ if n_items is not None and not n_items:
+ collapsed = True
+
+ 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 (
+ ""
+ ""
+ "{inline_details}
"
+ "{details}
"
+ .format(**d))
+
+
+def _mapping_section(mapping, name, details_func,
+ enabled=True, max_items_collapse=None):
+ n_items = len(mapping)
+
+ if max_items_collapse is not None and n_items <= max_items_collapse:
+ collapsed = False
+ else:
+ collapsed = True
+
+ 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):
+ d = {}
+
+ # "unique" id to expand/collapse the section
+ d['id'] = 'section-' + str(uuid.uuid4())
+
+ # TODO: no value preview if not in memory
+ d['preview'] = format_values_preview(obj.values, max_char=70)
+
+ d['data_repr'] = repr(obj.data)
+
+ # TODO: maybe collapse section dep. on number of lines in data repr
+ d['collapsed'] = ''
+
+ d['tip'] = "Show/hide data repr"
+
+ return (
+ ""
+ "
"
+ "
"
+ "
{preview}
"
+ "
{data_repr}
"
+ "
"
+ .format(**d))
+
+
+coord_section = partial(_mapping_section,
+ name='Coordinates', details_func=summarize_vars,
+ 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):
+ d = {}
+
+ d['header'] = "".format(
+ "".join(comp for comp in header_components))
+
+ d['icons'] = ICONS_SVG
+ d['style'] = "".format(CSS_STYLE)
+
+ d['sections'] = "".join("{}".format(s)
+ for s in sections)
+
+ return (""
+ "{icons}{style}"
+ "
"
+ "
"
+ .format(**d))
+
+
+def array_repr(arr):
+ dims = OrderedDict((k, v) for k, v in zip(arr.dims, arr.shape))
+
+ obj_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(obj_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):
+ obj_type = "xarray.{}".format(type(ds).__name__)
+
+ header_components = ["{}
".format(obj_type)]
+
+ sections = [dim_section(ds),
+ coord_section(ds.coords),
+ datavar_section(ds.data_vars),
+ attr_section(ds.attrs)]
+
+ return _obj_repr(header_components, sections)
diff --git a/xarray/static/css/style-jupyterlab.css b/xarray/static/css/style-jupyterlab.css
new file mode 100644
index 00000000000..f6c5968a3a8
--- /dev/null
+++ b/xarray/static/css/style-jupyterlab.css
@@ -0,0 +1,274 @@
+/* CSS stylesheet for displaying xarray objects in jupyterlab.
+ *
+ */
+
+.xr-wrap {
+ min-width: 500px;
+ max-width: 700px;
+}
+
+.xr-header {
+ padding-top: 6px;
+ padding-bottom: 6px;
+ margin-bottom: 4px;
+ border-bottom: solid 1px #ddd;
+}
+
+.xr-header > div,
+.xr-header > ul {
+ display: inline;
+ margin-top: 0;
+ margin-bottom: 0;
+}
+
+.xr-obj-type,
+.xr-array-name {
+ margin-left: 2px;
+ margin-right: 10px;
+}
+
+.xr-obj-type {
+ color: #555;
+}
+
+.xr-array-name {
+ color: #000;
+}
+
+.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;
+}
+
+.xr-section-item input {
+ display: none;
+}
+
+.xr-section-item input:enabled + label {
+ cursor: pointer;
+}
+
+.xr-section-summary {
+ grid-column: 1;
+ color: #555;
+ font-weight: 500;
+}
+
+.xr-section-summary > span {
+ display: inline-block;
+ padding-left: 0.5em;
+}
+
+.xr-section-summary-in + label:before {
+ display: inline-block;
+ content: '►';
+ font-size: 11px;
+ width: 15px;
+ text-align: center;
+}
+
+.xr-section-summary-in:disabled + label:before {
+ color: #ccc;
+}
+
+.xr-section-summary-in:checked + label:before {
+ content: '▼';
+}
+
+.xr-section-summary-in:checked + label > span {
+ display: none;
+}
+
+.xr-section-summary,
+.xr-section-inline-details {
+ padding-top: 4px;
+ padding-bottom: 4px;
+}
+
+.xr-section-inline-details {
+ grid-column: 2 / -1;
+}
+
+.xr-section-details {
+ display: none;
+ grid-column: 1 / -1;
+ margin-bottom: 5px;
+}
+
+.xr-section-summary-in:checked ~ .xr-section-details {
+ display: contents;
+}
+
+.xr-array-wrap {
+ grid-column: 1 / -1;
+}
+
+.xr-array-icon {
+ display: inline-block;
+ width: 15px !important;
+ vertical-align: top;
+ padding: 4px 0 2px 0 !important;
+}
+
+.xr-array-in + label:before {
+ content: '➕';
+}
+
+.xr-array-in:checked + label:before {
+ content: '➖';
+}
+
+.xr-array-preview,
+.xr-array-data {
+ padding: 5px 0 4px 8px !important;
+ margin: 0;
+}
+
+.xr-array-data,
+.xr-array-in:checked ~ .xr-array-preview {
+ display: none;
+}
+
+.xr-array-in:checked ~ .xr-array-data,
+.xr-array-preview {
+ display: inline-block;
+}
+
+.xr-preview > span > span:nth-child(odd) {
+ color: rgba(0, 0, 0, .65);
+}
+
+.xr-dim-list {
+ display: inline-block !important;
+ list-style: none;
+ padding: 0 !important;
+}
+
+.xr-dim-list li {
+ display: inline-block;
+ 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;
+}
+
+.xr-var-list {
+ display: contents;
+}
+
+.xr-var-item {
+ display: contents;
+}
+
+.xr-var-item > div,
+.xr-var-item > label {
+ background-color: #fcfcfc;
+}
+
+.xr-var-list > li:nth-child(odd) > div,
+.xr-var-list > li:nth-child(odd) > label {
+ background-color: #efefef;
+}
+
+.xr-var-name {
+ grid-column: 1;
+}
+
+.xr-var-name > span {
+ padding-right: 10px;
+}
+
+.xr-var-dims {
+ grid-column: 2;
+}
+
+.xr-var-dtype {
+ grid-column: 3;
+ text-align: right;
+ padding-right: 10px;
+ color: #555;
+}
+
+.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;
+ overflow-x: hidden;
+}
+
+.xr-var-attrs,
+.xr-var-data {
+ display: none;
+ background-color: #fff !important;
+ padding-bottom: 5px !important;
+}
+
+.xr-var-attrs-in:checked ~ .xr-var-attrs,
+.xr-var-data-in:checked ~ .xr-var-data {
+ display: block;
+}
+
+.xr-var-item input + label {
+ color: #ccc;
+}
+
+.xr-var-item input:enabled + label {
+ color: #555;
+}
+
+.xr-var-item input:enabled + label:hover {
+ color: #000;
+}
+
+.xr-var-name span,
+.xr-var-data,
+.xr-attrs {
+ padding-left: 25px !important;
+}
+
+.xr-attrs,
+.xr-var-attrs,
+.xr-var-data {
+ grid-column: 1 / -1;
+}
+
+.xr-attrs {
+ list-style: none !important;
+}
+
+.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 @@
+