├── .gitignore ├── js ├── types.ts ├── search.tsx ├── icons.tsx ├── common.tsx ├── widget.tsx └── styles.css ├── tsconfig.json ├── package.json ├── README.md ├── .github └── workflows │ └── pypipublish.yml ├── .pre-commit-config.yaml ├── src └── xarray_fancy_repr │ ├── __init__.py │ ├── wrap.py │ ├── utils.py │ └── widget.py ├── pyproject.toml └── LICENSE /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | .idea 3 | *.log 4 | tmp/ 5 | 6 | *.py[cod] 7 | *.egg 8 | build 9 | htmlcov 10 | 11 | node_modules 12 | dist 13 | 14 | __pycache__ 15 | .ipynb_checkpoints 16 | 17 | src/xarray_fancy_repr/static/ 18 | -------------------------------------------------------------------------------- /js/types.ts: -------------------------------------------------------------------------------- 1 | export type Dims = string[]; 2 | export type DimInfo = { 3 | [key: string]: { size: number; hasIndex: boolean }; 4 | }; 5 | 6 | export type Attrs = { 7 | [key: string]: string; 8 | }; 9 | 10 | export type Index = { 11 | coordNames: string[]; 12 | inlineRepr: string; 13 | repr: string; 14 | }; 15 | 16 | export type Variable = { 17 | name: string; 18 | hasIndex: boolean; 19 | dims: Dims; 20 | dtype: string; 21 | inlineRepr: string; 22 | attrs: Attrs; 23 | dataRepr: string; 24 | inMemory: boolean; 25 | }; 26 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "include": ["js"], 3 | "compilerOptions": { 4 | "target": "ES2020", 5 | "module": "ESNext", 6 | "lib": ["ES2020", "DOM", "DOM.Iterable"], 7 | "skipLibCheck": true, 8 | "moduleResolution": "bundler", 9 | "allowImportingTsExtensions": true, 10 | "resolveJsonModule": true, 11 | "isolatedModules": true, 12 | "noEmit": true, 13 | "jsx": "react", 14 | "strict": true, 15 | "noUnusedLocals": true, 16 | "noUnusedParameters": true, 17 | "noFallthroughCasesInSwitch": true 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "scripts": { 3 | "dev": "npm run build -- --sourcemap=inline --watch", 4 | "build": "esbuild --minify --format=esm --bundle --outdir=src/xarray_fancy_repr/static js/widget.tsx", 5 | "typecheck": "tsc --noEmit", 6 | "format": "prettier --ignore-path=.gitignore --write .", 7 | "lint": "prettier --ignore-path=.gitignore --check ." 8 | }, 9 | "dependencies": { 10 | "@anywidget/react": "^0.0.2", 11 | "react": "^18.2.0", 12 | "react-dom": "^18.2.0" 13 | }, 14 | "devDependencies": { 15 | "esbuild": "^0.19.2", 16 | "@types/react": "^18.2.21", 17 | "@types/react-dom": "^18.2.7", 18 | "prettier": "^3.0.3", 19 | "typescript": "^5.2.2" 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Xarray Fancy Repr 2 | 3 | This package provides enhanced representations of Xarray objects (Dataset, 4 | DataArray, Coordinates and Variable) for a better and more interactive user 5 | experience within Jupyter notebooks. 6 | 7 | ## Installation 8 | 9 | ```sh 10 | pip install xarray-fancy-repr 11 | ``` 12 | 13 | ## How Does it Work? 14 | 15 | After importing this package: 16 | 17 | ```python 18 | import xarray_fancy_repr 19 | ``` 20 | 21 | Xarray objects are patched so that they are displayed in the following fallback 22 | order: 23 | 24 | 1. Interactive widget: should work in most notebook environments (JupyterLab, 25 | Jupyter Notebook <7 and 7+, Google Colab, VSCode, etc.) based on 26 | [anywidget](https://anywidget.dev/) 27 | 2. Static HTML: should work in any web browser 28 | 3. Plain text 29 | -------------------------------------------------------------------------------- /.github/workflows/pypipublish.yml: -------------------------------------------------------------------------------- 1 | name: Upload Python Package on PyPI 2 | 3 | on: 4 | release: 5 | types: [published] 6 | 7 | jobs: 8 | deploy: 9 | runs-on: ubuntu-latest 10 | environment: 11 | name: pypi 12 | url: https://pypi.org/p/xarray-fancy-repr 13 | permissions: 14 | id-token: write 15 | steps: 16 | - name: Checkout 17 | uses: actions/checkout@v2 18 | - name: Set up Python 3.9 19 | uses: actions/setup-python@v4 20 | with: 21 | python-version: "3.9" 22 | - name: Install publish dependencies 23 | run: python -m pip install build hatch 24 | - name: Build package 25 | run: python -m build . -o py_dist 26 | - name: Publish package to PyPI 27 | uses: pypa/gh-action-pypi-publish@release/v1 28 | with: 29 | packages-dir: py_dist/ 30 | -------------------------------------------------------------------------------- /.pre-commit-config.yaml: -------------------------------------------------------------------------------- 1 | # run all checks with `pre-commit run --all-files` 2 | 3 | ci: 4 | autoupdate_schedule: monthly 5 | autofix_commit_msg: "style(pre-commit.ci): auto fixes [...]" 6 | autoupdate_commit_msg: "ci(pre-commit.ci): autoupdate" 7 | autofix_prs: false 8 | 9 | repos: 10 | - repo: https://github.com/pre-commit/pre-commit-hooks 11 | rev: v4.4.0 12 | hooks: 13 | - id: trailing-whitespace 14 | - id: end-of-file-fixer 15 | - id: check-yaml 16 | - id: mixed-line-ending 17 | 18 | - repo: https://github.com/astral-sh/ruff-pre-commit 19 | rev: v0.0.289 20 | hooks: 21 | - id: ruff 22 | args: [--fix] 23 | 24 | - repo: https://github.com/psf/black 25 | rev: 23.9.1 26 | hooks: 27 | - id: black 28 | 29 | - repo: https://github.com/pre-commit/mirrors-prettier 30 | rev: v3.0.3 31 | hooks: 32 | - id: prettier 33 | args: [--ignore-path .gitignore] 34 | types_or: [css, javascript, ts, tsx, json] 35 | -------------------------------------------------------------------------------- /src/xarray_fancy_repr/__init__.py: -------------------------------------------------------------------------------- 1 | import importlib.metadata 2 | 3 | import xarray as xr 4 | 5 | from xarray_fancy_repr.widget import XarrayWidget 6 | 7 | try: 8 | __version__ = importlib.metadata.version("xarray_fancy_repr") 9 | except importlib.metadata.PackageNotFoundError: 10 | __version__ = "unknown" 11 | 12 | __all__ = ["XarrayWidget"] 13 | 14 | 15 | # Monkey patch xarray objects 16 | def _patched_repr_mimebundle(self, **kwargs: dict) -> tuple[None | dict, dict]: 17 | repr_mime, repr_metadata = XarrayWidget(self)._repr_mimebundle_(**kwargs) 18 | if repr_mime is not None: 19 | repr_mime["text/plain"] = self.__repr__() 20 | # TODO: add built-in html repr for xarray.Coordinates 21 | if hasattr(self, "_repr_html_"): 22 | repr_mime["text/html"] = self._repr_html_() 23 | 24 | return repr_mime, repr_metadata 25 | 26 | 27 | xr.Dataset._repr_mimebundle_ = _patched_repr_mimebundle # type: ignore 28 | xr.DataArray._repr_mimebundle_ = _patched_repr_mimebundle # type: ignore 29 | xr.Coordinates._repr_mimebundle_ = _patched_repr_mimebundle # type: ignore 30 | xr.Variable._repr_mimebundle_ = _patched_repr_mimebundle # type: ignore 31 | -------------------------------------------------------------------------------- /src/xarray_fancy_repr/wrap.py: -------------------------------------------------------------------------------- 1 | from collections.abc import Hashable, Mapping 2 | 3 | import xarray as xr 4 | from xarray.core.indexes import Indexes 5 | 6 | XarrayObject = xr.Dataset | xr.DataArray | xr.Coordinates | xr.Variable 7 | 8 | 9 | class XarrayWrapper: 10 | """Lightweight wrapper around Xarray objects providing a common 11 | interface consumed by the widget representation. 12 | 13 | """ 14 | 15 | obj_type: str 16 | coords: Mapping[Hashable, xr.DataArray | xr.Variable] 17 | data_vars: Mapping[Hashable, xr.DataArray | xr.Variable] 18 | xindexes: Indexes 19 | sizes: Mapping[Hashable, int] 20 | attrs: Mapping 21 | 22 | def __init__(self, obj: XarrayObject): 23 | self._obj = obj 24 | 25 | if isinstance(obj, xr.Dataset): 26 | self.obj_type = "dataset" 27 | elif isinstance(obj, xr.DataArray): 28 | self.obj_type = "dataarray" 29 | elif isinstance(obj, xr.Coordinates): 30 | self.obj_type = "coordinates" 31 | elif isinstance(obj, xr.Variable): 32 | self.obj_type = "variable" 33 | else: 34 | raise TypeError( 35 | "XarrayWidget only accepts xarray.Dataset, xarray.DataArray, " 36 | "xarray.Coordinates or xarray.Variable" 37 | ) 38 | 39 | if isinstance(obj, xr.Coordinates): 40 | self.coords = obj 41 | else: 42 | self.coords = getattr(obj, "coords", {}) 43 | 44 | if isinstance(obj, xr.Dataset): 45 | self.data_vars = obj.data_vars 46 | elif isinstance(obj, xr.Coordinates): 47 | self.data_vars = {} 48 | elif isinstance(obj, xr.DataArray): 49 | self.data_vars = {obj.name or "": obj.variable} 50 | else: 51 | self.data_vars = {"": obj} 52 | 53 | self.xindexes = getattr(obj, "xindexes", Indexes()) 54 | self.sizes = obj.sizes 55 | self.attrs = getattr(obj, "attrs", {}) 56 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [build-system] 2 | requires = ["hatchling"] 3 | build-backend = "hatchling.build" 4 | 5 | [project] 6 | name = "xarray-fancy-repr" 7 | version = "0.0.2" 8 | description = "Fancy reprs for Xarray objects in Jupyter notebooks" 9 | keywords = ["xarray", "jupyter", "widget"] 10 | readme = "README.md" 11 | authors = [ 12 | {name = "Benoît Bovy"}, 13 | ] 14 | maintainers = [ 15 | {name = "xarray-fancy-repr contributors"}, 16 | ] 17 | license = {text = "Apache-2.0"} 18 | dependencies = ["anywidget", "xarray"] 19 | 20 | [project.optional-dependencies] 21 | dev = ["watchfiles", "jupyterlab", "pytest", "pre-commit"] 22 | 23 | [project.urls] 24 | Repository = "https://github.com/benbovy/xarray-fancy-repr" 25 | 26 | # automatically add the dev feature to the default env (e.g., hatch shell) 27 | [tool.hatch.envs.default] 28 | features = ["dev"] 29 | 30 | [tool.hatch.build] 31 | artifacts = ["src/xarray_fancy_repr/static/*"] 32 | 33 | [tool.hatch.build.hooks.jupyter-builder] 34 | build-function = "hatch_jupyter_builder.npm_builder" 35 | ensured-targets = ["src/xarray_fancy_repr/static/widget.js"] 36 | skip-if-exists = ["src/xarray_fancy_repr/static/widget.js"] 37 | dependencies = ["hatch-jupyter-builder>=0.5.0"] 38 | 39 | [tool.hatch.build.hooks.jupyter-builder.build-kwargs] 40 | npm = "npm" 41 | build_cmd = "build" 42 | path = "js" 43 | 44 | [tool.black] 45 | line-length = 100 46 | 47 | [tool.ruff] 48 | target-version = "py311" 49 | builtins = ["ellipsis"] 50 | exclude = [ 51 | ".git", 52 | ".eggs", 53 | "build", 54 | "dist", 55 | "__pycache__", 56 | ] 57 | # E402: module level import not at top of file 58 | # E501: line too long - let black worry about that 59 | # E731: do not assign a lambda expression, use a def 60 | ignore = [ 61 | "E402", 62 | "E501", 63 | "E731", 64 | ] 65 | select = [ 66 | # Pyflakes 67 | "F", 68 | # Pycodestyle 69 | "E", 70 | "W", 71 | # isort 72 | "I", 73 | # Pyupgrade 74 | "UP", 75 | ] 76 | 77 | [tool.ruff.isort] 78 | known-first-party = ["xarray_fancy_repr"] 79 | known-third-party=[ 80 | "anywidgets", 81 | "traitlets", 82 | ] 83 | -------------------------------------------------------------------------------- /src/xarray_fancy_repr/utils.py: -------------------------------------------------------------------------------- 1 | from collections.abc import Hashable, Mapping 2 | 3 | import xarray as xr 4 | from xarray.core.formatting import inline_index_repr, inline_variable_array_repr 5 | from xarray.core.formatting_html import short_data_repr_html, short_index_repr_html 6 | from xarray.core.indexes import Indexes 7 | from xarray.core.options import OPTIONS 8 | 9 | from xarray_fancy_repr.wrap import XarrayObject, XarrayWrapper 10 | 11 | 12 | def encode_attrs(attrs: Mapping) -> dict[str, str]: 13 | return {str(k): str(v) for k, v in attrs.items()} 14 | 15 | 16 | def _encode_variable( 17 | name: Hashable, var: xr.Variable | xr.DataArray, has_index: bool = False 18 | ) -> dict: 19 | if isinstance(var, xr.DataArray): 20 | var = var.variable 21 | 22 | name = str(name) 23 | dims = [str(dim) for dim in var.dims] 24 | dtype = str(var.dtype) 25 | inline_repr = inline_variable_array_repr(var, 35) 26 | attrs = encode_attrs(var.attrs) 27 | data_repr = short_data_repr_html(var) 28 | 29 | return { 30 | "name": name, 31 | "hasIndex": has_index, 32 | "dims": dims, 33 | "dtype": dtype, 34 | "inlineRepr": inline_repr, 35 | "attrs": attrs, 36 | "dataRepr": data_repr, 37 | "inMemory": var._in_memory, 38 | } 39 | 40 | 41 | def encode_variables( 42 | variables: Mapping[Hashable, xr.Variable | xr.DataArray], 43 | indexes: Mapping[Hashable, xr.Index] | None = None, 44 | ) -> list[dict]: 45 | if indexes is None: 46 | indexes = {} 47 | 48 | encoded = [ 49 | _encode_variable(k, v, has_index=k in indexes) for k, v in variables.items() 50 | ] 51 | 52 | return encoded 53 | 54 | 55 | def encode_indexes(indexes: Indexes) -> list[dict]: 56 | encoded = [] 57 | for idx, index_vars in indexes.group_by_index(): 58 | encoded_idx = { 59 | "coordNames": list(index_vars), 60 | "inlineRepr": inline_index_repr(idx, max_width=OPTIONS["display_width"]), 61 | "repr": short_index_repr_html(idx), 62 | } 63 | encoded.append(encoded_idx) 64 | return encoded 65 | 66 | 67 | def encode_dim_info(obj: XarrayObject | XarrayWrapper): 68 | encoded = {} 69 | 70 | if isinstance(obj, xr.Variable): 71 | indexed_dims = {} 72 | else: 73 | indexed_dims = obj.xindexes.dims 74 | 75 | for dim, size in obj.sizes.items(): 76 | encoded[dim] = {"size": size, "hasIndex": dim in indexed_dims} 77 | return encoded 78 | -------------------------------------------------------------------------------- /js/search.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | import { useModelState } from "@anywidget/react"; 3 | import { ClearIcon, FilterIcon } from "./icons"; 4 | 5 | export const Search = () => { 6 | const [optionsHidden, setOptionsHidden] = React.useState(true); 7 | const [filterBy, setFilterBy] = useModelState("_filter_by"); 8 | const [query, setQuery] = useModelState("_filter_search"); 9 | 10 | const inputHandler = (e: React.ChangeEvent) => { 11 | setQuery(e.target.value.toLowerCase()); 12 | }; 13 | 14 | const onChangeHandler = (key: string) => { 15 | const filterByCopy = Object.assign([], filterBy); 16 | const index = filterBy.indexOf(key, 0); 17 | if (index > -1) { 18 | filterByCopy.splice(index, 1); 19 | } else { 20 | filterByCopy.push(key); 21 | } 22 | setFilterBy(filterByCopy); 23 | }; 24 | 25 | const clearHandler = () => { 26 | setQuery(""); 27 | }; 28 | 29 | const isChecked = (key: string): boolean => { 30 | return filterBy.includes(key); 31 | }; 32 | 33 | return ( 34 |
35 |
36 | 42 | 48 |
49 | 55 |
56 |
    57 |
  • 58 | 66 |
  • 67 |
  • 68 | 76 |
  • 77 |
  • 78 | 86 |
  • 87 |
88 |
89 |
90 | ); 91 | }; 92 | -------------------------------------------------------------------------------- /js/icons.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | 3 | export const XarrayIcon = () => { 4 | return ( 5 | 13 | 17 | 21 | 22 | 26 | 30 | 34 | 38 | 42 | 43 | ); 44 | }; 45 | 46 | export const DataIcon = () => { 47 | return ( 48 | 53 | 54 | 55 | 56 | 57 | ); 58 | }; 59 | 60 | export const AttrsIcon = () => { 61 | return ( 62 | 67 | 68 | 69 | 70 | 71 | 72 | ); 73 | }; 74 | 75 | export const FilterIcon = () => { 76 | return ( 77 | 83 | 87 | 88 | ); 89 | }; 90 | 91 | export const ClearIcon = () => { 92 | return ( 93 | 101 | 102 | 103 | 104 | 105 | ); 106 | }; 107 | -------------------------------------------------------------------------------- /src/xarray_fancy_repr/widget.py: -------------------------------------------------------------------------------- 1 | import pathlib 2 | 3 | import anywidget 4 | import traitlets as tt 5 | 6 | from xarray_fancy_repr.utils import ( 7 | encode_attrs, 8 | encode_dim_info, 9 | encode_indexes, 10 | encode_variables, 11 | ) 12 | from xarray_fancy_repr.wrap import XarrayObject, XarrayWrapper 13 | 14 | DIMS = tt.List(tt.Unicode()) 15 | DIM_INFO = tt.Dict( 16 | value_trait=tt.Dict(per_key_traits={"size": tt.Int(), "hasIndex": tt.Bool()}), 17 | key_trait=tt.Unicode(), 18 | ) 19 | DIMS_HAS_INDEX = tt.Dict(value_trait=tt.Bool(), key_trait=tt.Unicode()) 20 | ATTRS = tt.Dict(value_trait=tt.Unicode(), key_trait=tt.Unicode()) 21 | VARIABLE = tt.Dict( 22 | per_key_traits={ 23 | "name": tt.Unicode(), 24 | "hasIndex": tt.Bool(), 25 | "dims": DIMS, 26 | "dtype": tt.Unicode(), 27 | "inlineRepr": tt.Unicode(), 28 | "attrs": ATTRS, 29 | "dataRepr": tt.Unicode(), 30 | "inMemory": tt.Bool(), 31 | }, 32 | ) 33 | INDEX = tt.Dict( 34 | per_key_traits={ 35 | "coordNames": tt.List(tt.Unicode()), 36 | "inlineRepr": tt.Unicode(), 37 | "repr": tt.Unicode(), 38 | } 39 | ) 40 | 41 | 42 | class XarrayWidget(anywidget.AnyWidget): 43 | _esm = pathlib.Path(__file__).parent / "static" / "widget.js" 44 | _css = pathlib.Path(__file__).parent / "static" / "widget.css" 45 | 46 | _obj_type = tt.Enum(("dataset", "dataarray", "coordinates", "variable")).tag(sync=True) 47 | _dim_info = DIM_INFO.tag(sync=True) 48 | _coords = tt.List(VARIABLE).tag(sync=True) 49 | _data_vars = tt.List(VARIABLE).tag(sync=True) 50 | _indexes = tt.List(INDEX).tag(sync=True) 51 | _attrs = ATTRS.tag(sync=True) 52 | _filter_search = tt.Unicode().tag(sync=True) 53 | _filter_by = tt.Tuple(("name",)).tag(sync=True) 54 | 55 | def __init__(self, obj: XarrayObject): 56 | self._obj = obj 57 | self._wrapped = XarrayWrapper(obj) 58 | 59 | super().__init__( 60 | _obj_type=self._wrapped.obj_type, 61 | _dim_info=encode_dim_info(self._wrapped), 62 | _coords=encode_variables(self._wrapped.coords, self._wrapped.xindexes), 63 | _data_vars=encode_variables(self._wrapped.data_vars), 64 | _indexes=encode_indexes(self._wrapped.xindexes), 65 | _attrs=encode_attrs(self._wrapped.attrs), 66 | ) 67 | 68 | def _filter_clear(self): 69 | with self.hold_sync(): 70 | self._coords = encode_variables(self._wrapped.coords, self._wrapped.xindexes) 71 | self._data_vars = encode_variables(self._wrapped.data_vars) 72 | self._attrs = encode_attrs(self._wrapped.attrs) 73 | 74 | def _filter_vars_and_attrs(self, query, by): 75 | if query == "" or not len(by): 76 | self._filter_clear() 77 | return 78 | 79 | def is_in(value): 80 | return query in str(value).lower() 81 | 82 | def in_dims(dims): 83 | return any(is_in(d) for d in dims) 84 | 85 | def in_attrs(attrs): 86 | return any(is_in(k) or is_in(v) for k, v in attrs.items()) 87 | 88 | coord_names = set() 89 | data_var_names = set() 90 | 91 | if "name" in by: 92 | coord_names.update({k for k in self._wrapped.coords if is_in(k)}) 93 | data_var_names.update({k for k in self._wrapped.data_vars if is_in(k)}) 94 | if "dim" in by: 95 | coord_names.update({k for k, v in self._wrapped.coords.items() if in_dims(v.dims)}) 96 | data_var_names.update( 97 | {k for k, v in self._wrapped.data_vars.items() if in_dims(v.dims)} 98 | ) 99 | if "attrs" in by: 100 | coord_names.update({k for k, v in self._wrapped.coords.items() if in_attrs(v.attrs)}) 101 | data_var_names.update( 102 | {k for k, v in self._wrapped.data_vars.items() if in_attrs(v.attrs)} 103 | ) 104 | 105 | new_coords = {k: v for k, v in self._wrapped.coords.items() if k in coord_names} 106 | # do not filter data vars for DataArray or Variable (used to store the unique array) 107 | if self._wrapped.obj_type != "dataset": 108 | new_data_vars = self._wrapped.data_vars 109 | else: 110 | new_data_vars = { 111 | k: v for k, v in self._wrapped.data_vars.items() if k in data_var_names 112 | } 113 | 114 | if "attrs" in by: 115 | new_attrs = {k: v for k, v in self._wrapped.attrs.items() if is_in(k) or is_in(v)} 116 | else: 117 | new_attrs = self._wrapped.attrs 118 | 119 | with self.hold_sync(): 120 | self._coords = encode_variables(new_coords, self._wrapped.xindexes) 121 | self._data_vars = encode_variables(new_data_vars) 122 | self._attrs = encode_attrs(new_attrs) 123 | 124 | @tt.observe("_filter_search") 125 | def _handle_filter_search(self, change): 126 | query = change["new"] 127 | self._filter_vars_and_attrs(query, self._filter_by) 128 | 129 | @tt.observe("_filter_by") 130 | def _handle_filter_by(self, change): 131 | by = change["new"] 132 | self._filter_vars_and_attrs(self._filter_search, by) 133 | -------------------------------------------------------------------------------- /js/common.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | import { useModelState } from "@anywidget/react"; 3 | import { AttrsIcon, DataIcon } from "./icons"; 4 | import { Attrs, DimInfo, Index, Variable } from "./types"; 5 | import "./styles.css"; 6 | 7 | export const AttrsSection = ({ attrs }: { attrs: Attrs }) => { 8 | const items = Object.entries(attrs).map(([key, value]) => ( 9 | <> 10 |
11 | {key} : 12 |
13 |
{value}
14 | 15 | )); 16 | return
{items}
; 17 | }; 18 | 19 | const VariableItem = ({ variable }: { variable: Variable }) => { 20 | const attrsDisabled = Object.keys(variable.attrs).length == 0; 21 | const [attrsCollapsed, setAttrsCollapsed] = React.useState(true); 22 | const [dataCollapsed, setDataCollapsed] = React.useState(true); 23 | 24 | return ( 25 | <> 26 |
27 | 28 | {variable.name} 29 | 30 |
31 |
({variable.dims.join(", ")})
32 |
{variable.dtype}
33 |
{variable.inlineRepr}
34 | 40 | 48 | 53 | 59 |
60 | 61 |
62 |
66 | 67 | ); 68 | }; 69 | 70 | export const VariableList = ({ variables }: { variables: Variable[] }) => { 71 | const items = variables.map((v) => ( 72 | <> 73 |
  • 74 | 75 |
  • 76 | 77 | )); 78 | 79 | return
      {items}
    ; 80 | }; 81 | 82 | const IndexItem = ({ index }: { index: Index }) => { 83 | const [dataCollapsed, setDataCollapsed] = React.useState(true); 84 | 85 | const indexName = index.coordNames.map((name) => ( 86 | <> 87 | {name} 88 |
    89 | 90 | )); 91 | 92 | return ( 93 | <> 94 |
    95 |
    {indexName}
    96 |
    97 |
    {index.inlineRepr}
    98 |
    99 | 104 | 110 |
    114 | 115 | ); 116 | }; 117 | 118 | export const IndexList = ({ indexes }: { indexes: Index[] }) => { 119 | const items = indexes.map((index) => ( 120 | <> 121 |
  • 122 | 123 |
  • 124 | 125 | )); 126 | 127 | return
      {items}
    ; 128 | }; 129 | 130 | interface MappingSectionProps { 131 | title: string; 132 | nitems: number; 133 | maxItemsCollapse: number; 134 | children?: React.ReactNode; 135 | } 136 | 137 | export const MappingSection = (props: MappingSectionProps) => { 138 | const initCollapsed = 139 | props.nitems == 0 || props.nitems > props.maxItemsCollapse; 140 | const [collapsed, setCollapsed] = React.useState(initCollapsed); 141 | 142 | return ( 143 | <> 144 | 149 | 156 |
    157 |
    {props.children}
    158 | 159 | ); 160 | }; 161 | 162 | export const DimsList = ({ dimInfo }: { dimInfo: DimInfo }) => { 163 | const [_1, setFilterBy] = useModelState("_filter_by"); 164 | const [_2, setQuery] = useModelState("_filter_search"); 165 | 166 | const clickHandler = (dim: string) => { 167 | setQuery(dim); 168 | setFilterBy(["dim"]); 169 | }; 170 | 171 | const items = Object.entries(dimInfo).map(([dim, value]) => ( 172 | <> 173 |
  • clickHandler(dim)}> 174 | {dim}:{" "} 175 | {value.size} 176 |
  • 177 | 178 | )); 179 | 180 | return ( 181 | <> 182 |
      {items}
    183 | 184 | ); 185 | }; 186 | 187 | export const DimsSection = ({ dimInfo }: { dimInfo: DimInfo }) => { 188 | return ( 189 | <> 190 | 196 | 197 |
    198 | 199 |
    200 |
    201 | 202 | ); 203 | }; 204 | 205 | export const ArraySection = ({ variable }: { variable: Variable }) => { 206 | const [collapsed, setCollapsed] = React.useState(false); 207 | 208 | return ( 209 |
    210 | 211 | 217 |
    218 | {variable.inlineRepr} 219 |
    220 |
    224 |
    225 | ); 226 | }; 227 | -------------------------------------------------------------------------------- /js/widget.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | import { createRender, useModelState } from "@anywidget/react"; 3 | import { 4 | ArraySection, 5 | AttrsSection, 6 | DimsList, 7 | DimsSection, 8 | IndexList, 9 | MappingSection, 10 | VariableList, 11 | } from "./common"; 12 | import { XarrayIcon } from "./icons"; 13 | import { Search } from "./search"; 14 | import { Attrs, DimInfo, Index, Variable } from "./types"; 15 | 16 | const DatasetWidget = () => { 17 | const [dimInfo, _0] = useModelState("_dim_info"); 18 | const [coords, _1] = useModelState("_coords"); 19 | const [dataVars, _2] = useModelState("_data_vars"); 20 | const [indexes, _3] = useModelState("_indexes"); 21 | const [attrs, _4] = useModelState("_attrs"); 22 | 23 | return ( 24 |
    25 |
    26 |
    27 |
    28 | 29 |
    30 |
    xarray.Dataset
    31 | 32 |
    33 |
      34 |
    • 35 | 36 |
    • 37 |
    • 38 | 43 | 44 | 45 |
    • 46 |
    • 47 | 52 | 53 | 54 |
    • 55 |
    • 56 | 61 | 62 | 63 |
    • 64 |
    • 65 | 70 | 71 | 72 |
    • 73 |
    74 |
    75 |
    76 | ); 77 | }; 78 | 79 | const DataArrayWidget = () => { 80 | const [dimInfo, _0] = useModelState("_dim_info"); 81 | const [coords, _1] = useModelState("_coords"); 82 | const [dataVars, _2] = useModelState("_data_vars"); 83 | const [indexes, _3] = useModelState("_indexes"); 84 | const [attrs, _4] = useModelState("_attrs"); 85 | 86 | const variable = Object(dataVars).values().next().value; 87 | const name = variable.name ? "'" + variable.name + "'" : ""; 88 | 89 | return ( 90 |
    91 |
    92 |
    93 |
    94 | 95 |
    96 |
    xarray.DataArray
    97 |
    {name}
    98 | 99 | 100 |
    101 |
      102 |
    • 103 | 104 |
    • 105 |
    • 106 | 111 | 112 | 113 |
    • 114 |
    • 115 | 120 | 121 | 122 |
    • 123 |
    • 124 | 129 | 130 | 131 |
    • 132 |
    133 |
    134 |
    135 | ); 136 | }; 137 | 138 | const CoordinatesWidget = () => { 139 | const [dimInfo, _0] = useModelState("_dim_info"); 140 | const [coords, _1] = useModelState("_coords"); 141 | const [indexes, _3] = useModelState("_indexes"); 142 | 143 | return ( 144 |
    145 |
    146 |
    147 |
    148 | 149 |
    150 |
    xarray.Coordinates
    151 | 152 |
    153 |
      154 |
    • 155 | 156 |
    • 157 |
    • 158 | 163 | 164 | 165 |
    • 166 |
    • 167 | 172 | 173 | 174 |
    • 175 |
    176 |
    177 |
    178 | ); 179 | }; 180 | 181 | const VariableWidget = () => { 182 | const [dimInfo, _0] = useModelState("_dim_info"); 183 | const [dataVars, _2] = useModelState("_data_vars"); 184 | const [attrs, _4] = useModelState("_attrs"); 185 | 186 | const variable = Object(dataVars).values().next().value; 187 | 188 | return ( 189 |
    190 |
    191 |
    192 |
    193 | 194 |
    195 |
    xarray.Variable
    196 |
    197 | 198 |
    199 |
      200 |
    • 201 | 202 |
    • 203 |
    • 204 | 209 | 210 | 211 |
    • 212 |
    213 |
    214 |
    215 | ); 216 | }; 217 | 218 | export const render = createRender(() => { 219 | const [objType, _1] = useModelState("_obj_type"); 220 | 221 | if (objType == "dataset") { 222 | return ; 223 | } else if (objType == "dataarray") { 224 | return ; 225 | } else if (objType == "coordinates") { 226 | return ; 227 | } else if (objType == "variable") { 228 | return ; 229 | } else { 230 | return
    ; 231 | } 232 | }); 233 | -------------------------------------------------------------------------------- /js/styles.css: -------------------------------------------------------------------------------- 1 | /* CSS stylesheet for displaying xarray objects in jupyterlab. 2 | * 3 | */ 4 | 5 | :root { 6 | --xr-font-family: var(--jp-content-font-family); 7 | --xr-font-size: var(--jp-content-font-size1); 8 | --xr-line-height: var(--jp-content-line-height); 9 | --xr-font-color0: var(--jp-content-font-color0, rgba(0, 0, 0, 1)); 10 | --xr-font-color2: var(--jp-content-font-color2, rgba(0, 0, 0, 0.54)); 11 | --xr-font-color3: var(--jp-content-font-color3, rgba(0, 0, 0, 0.38)); 12 | --xr-border-color: var(--jp-border-color1, #bdbdbd); 13 | --xr-border-color2: var(--jp-border-color2, #e0e0e0); 14 | --xr-border-radius: var(--jp-border-radius, 2px); 15 | --xr-disabled-color: var(--jp-layout-color3, #bdbdbd); 16 | --xr-background-color: var(--jp-layout-color0, white); 17 | --xr-background-color-row-even: var(--jp-layout-color1, white); 18 | --xr-background-color-row-odd: var(--jp-layout-color2, #eeeeee); 19 | --xr-warn-color: var(--jp-warn-color1, #ed7f10); 20 | } 21 | 22 | html[theme="dark"], 23 | body[data-theme="dark"], 24 | body.vscode-dark { 25 | --xr-font-color0: rgba(255, 255, 255, 1); 26 | --xr-font-color2: rgba(255, 255, 255, 0.54); 27 | --xr-font-color3: rgba(255, 255, 255, 0.38); 28 | --xr-border-color2: #1f1f1f; 29 | --xr-disabled-color: #515151; 30 | --xr-background-color: #111111; 31 | --xr-background-color-row-even: #111111; 32 | --xr-background-color-row-odd: #313131; 33 | } 34 | 35 | .xr-wrap { 36 | display: block !important; 37 | min-width: 300px; 38 | max-width: 800px; 39 | font-size: var(--xr-font-size); 40 | font-family: var(--xr-font-family); 41 | line-height: var(--xr-line-height); 42 | } 43 | 44 | .xr-text-repr-fallback { 45 | /* fallback to plain text repr when CSS is not injected (untrusted notebook) */ 46 | display: none; 47 | } 48 | 49 | .xr-header { 50 | display: flex; 51 | align-content: space-between; 52 | align-items: baseline; 53 | margin-top: 6px; 54 | padding-bottom: 6px; 55 | border-bottom: solid 1px var(--xr-border-color2); 56 | } 57 | 58 | .xr-obj-type, 59 | .xr-array-name { 60 | margin-left: 8px; 61 | margin-right: 10px; 62 | } 63 | 64 | .xr-obj-type { 65 | color: var(--xr-font-color2); 66 | } 67 | 68 | .xr-search { 69 | margin-left: auto; 70 | text-align: right; 71 | } 72 | 73 | .xr-search-text { 74 | display: inline-flex; 75 | flex-direction: row; 76 | border: solid 1px var(--xr-border-color); 77 | border-radius: var(--xr-border-radius); 78 | vertical-align: middle; 79 | padding: 1px; 80 | } 81 | 82 | .xr-search-text:focus-within { 83 | outline: var(--xr-border-color) solid 1px; 84 | } 85 | 86 | .xr-search-text.active { 87 | border-color: var(--xr-warn-color); 88 | } 89 | 90 | .xr-search-text.active:focus { 91 | outline: var(--xr-warn-color) solid 1px; 92 | } 93 | 94 | .xr-search-text > input { 95 | flex-grow: 2; 96 | background-color: var(--xr-background-color); 97 | color: var(--xr-font-color0); 98 | border: none; 99 | } 100 | 101 | .xr-search-text > input:focus { 102 | outline: none; 103 | } 104 | 105 | .xr-search > button, 106 | .xr-search-text > button { 107 | display: inline; 108 | color: var(--xr-font-color2); 109 | background-color: var(--xr-background-color); 110 | border: none; 111 | } 112 | 113 | .xr-search > button:hover, 114 | .xr-search-text > button:hover { 115 | color: var(--xr-font-color0); 116 | cursor: pointer; 117 | } 118 | 119 | .xr-search ul { 120 | list-style-type: none; 121 | padding: 0; 122 | margin: 0; 123 | } 124 | 125 | .xr-hidden { 126 | display: none; 127 | } 128 | 129 | .xr-hidden-alt { 130 | visibility: hidden; 131 | } 132 | 133 | .xr-sections { 134 | padding-left: 0 !important; 135 | display: grid; 136 | grid-template-columns: 150px auto auto 1fr 20px 20px; 137 | } 138 | 139 | .xr-section-item { 140 | display: contents; 141 | } 142 | 143 | .xr-section-item input { 144 | display: none; 145 | } 146 | 147 | .xr-section-item input + label { 148 | color: var(--xr-disabled-color); 149 | } 150 | 151 | .xr-section-item input:enabled + label { 152 | cursor: pointer; 153 | color: var(--xr-font-color2); 154 | } 155 | 156 | .xr-section-item input:enabled + label:hover { 157 | color: var(--xr-font-color0); 158 | } 159 | 160 | .xr-section-summary { 161 | grid-column: 1; 162 | color: var(--xr-font-color2); 163 | font-weight: 500; 164 | } 165 | 166 | .xr-section-summary > span { 167 | display: inline-block; 168 | padding-left: 0.5em; 169 | } 170 | 171 | .xr-section-summary-in:disabled + label { 172 | color: var(--xr-font-color2); 173 | } 174 | 175 | .xr-section-summary-in + label:before { 176 | display: inline-block; 177 | content: "►"; 178 | font-size: 11px; 179 | width: 15px; 180 | text-align: center; 181 | } 182 | 183 | .xr-section-summary-in:disabled + label:before { 184 | color: var(--xr-disabled-color); 185 | } 186 | 187 | .xr-section-summary-in:checked + label:before { 188 | content: "▼"; 189 | } 190 | 191 | .xr-section-summary-in:checked + label > span { 192 | display: none; 193 | } 194 | 195 | .xr-section-summary, 196 | .xr-section-inline-details { 197 | padding-top: 4px; 198 | padding-bottom: 4px; 199 | } 200 | 201 | .xr-section-inline-details { 202 | grid-column: 2 / -1; 203 | } 204 | 205 | .xr-section-details { 206 | display: none; 207 | grid-column: 1 / -1; 208 | margin-bottom: 5px; 209 | } 210 | 211 | .xr-section-summary-in:checked ~ .xr-section-details { 212 | display: contents; 213 | } 214 | 215 | .xr-array-wrap { 216 | grid-column: 1 / -1; 217 | display: grid; 218 | grid-template-columns: 20px auto; 219 | } 220 | 221 | .xr-array-wrap > label { 222 | grid-column: 1; 223 | vertical-align: top; 224 | } 225 | 226 | .xr-preview { 227 | color: var(--xr-font-color3); 228 | } 229 | 230 | .xr-array-preview, 231 | .xr-array-data { 232 | padding: 0 5px !important; 233 | grid-column: 2; 234 | } 235 | 236 | .xr-array-data, 237 | .xr-array-in:checked ~ .xr-array-preview { 238 | display: none; 239 | } 240 | 241 | .xr-array-in:checked ~ .xr-array-data, 242 | .xr-array-preview { 243 | display: inline-block; 244 | } 245 | 246 | .xr-dim-list { 247 | display: inline-block !important; 248 | list-style: none; 249 | padding: 0 !important; 250 | margin: 0; 251 | } 252 | 253 | .xr-dim-list li { 254 | display: inline-block; 255 | padding: 0; 256 | margin: 0; 257 | cursor: pointer; 258 | } 259 | 260 | .xr-dim-list li:hover { 261 | color: var(--xr-warn-color); 262 | } 263 | 264 | .xr-dim-list:before { 265 | content: "("; 266 | } 267 | 268 | .xr-dim-list:after { 269 | content: ")"; 270 | } 271 | 272 | .xr-dim-list li:not(:last-child):after { 273 | content: ","; 274 | padding-right: 5px; 275 | } 276 | 277 | .xr-has-index { 278 | font-weight: bold; 279 | } 280 | 281 | .xr-var-list, 282 | .xr-var-item { 283 | display: contents; 284 | } 285 | 286 | .xr-var-item > div, 287 | .xr-var-item label, 288 | .xr-var-item > .xr-var-name span { 289 | background-color: var(--xr-background-color-row-even); 290 | margin-bottom: 0; 291 | } 292 | 293 | .xr-var-item > .xr-var-name:hover span { 294 | padding-right: 5px; 295 | } 296 | 297 | .xr-var-list > li:nth-child(odd) > div, 298 | .xr-var-list > li:nth-child(odd) > label, 299 | .xr-var-list > li:nth-child(odd) > .xr-var-name span { 300 | background-color: var(--xr-background-color-row-odd); 301 | } 302 | 303 | .xr-var-list .selected, 304 | .xr-var-list .selected > span { 305 | background-color: #000 !important; 306 | } 307 | 308 | .xr-var-name { 309 | grid-column: 1; 310 | } 311 | 312 | .xr-var-dims { 313 | grid-column: 2; 314 | } 315 | 316 | .xr-var-dtype { 317 | grid-column: 3; 318 | text-align: right; 319 | color: var(--xr-font-color2); 320 | } 321 | 322 | .xr-var-preview { 323 | grid-column: 4; 324 | } 325 | 326 | .xr-index-preview { 327 | grid-column: 2 / 5; 328 | color: var(--xr-font-color2); 329 | } 330 | 331 | .xr-var-name, 332 | .xr-var-dims, 333 | .xr-var-dtype, 334 | .xr-preview, 335 | .xr-attrs dt { 336 | white-space: nowrap; 337 | overflow: hidden; 338 | text-overflow: ellipsis; 339 | padding-right: 10px; 340 | } 341 | 342 | .xr-var-name:hover, 343 | .xr-var-dims:hover, 344 | .xr-var-dtype:hover, 345 | .xr-attrs dt:hover { 346 | overflow: visible; 347 | width: auto; 348 | z-index: 1; 349 | } 350 | 351 | .xr-var-attrs, 352 | .xr-var-data, 353 | .xr-index-data { 354 | display: none; 355 | background-color: var(--xr-background-color) !important; 356 | padding-bottom: 5px !important; 357 | } 358 | 359 | .xr-var-attrs-in:checked ~ .xr-var-attrs, 360 | .xr-var-data-in:checked ~ .xr-var-data, 361 | .xr-index-data-in:checked ~ .xr-index-data { 362 | display: block; 363 | } 364 | 365 | .xr-var-data > table { 366 | float: right; 367 | } 368 | 369 | .xr-var-name span, 370 | .xr-var-data, 371 | .xr-index-name div, 372 | .xr-index-data, 373 | .xr-attrs { 374 | padding-left: 25px !important; 375 | } 376 | 377 | .xr-attrs, 378 | .xr-var-attrs, 379 | .xr-var-data, 380 | .xr-index-data { 381 | grid-column: 1 / -1; 382 | } 383 | 384 | dl.xr-attrs { 385 | padding: 0; 386 | margin: 0; 387 | display: grid; 388 | grid-template-columns: 125px auto; 389 | } 390 | 391 | .xr-attrs dt, 392 | .xr-attrs dd { 393 | padding: 0; 394 | margin: 0; 395 | float: left; 396 | padding-right: 10px; 397 | width: auto; 398 | } 399 | 400 | .xr-attrs dt { 401 | font-weight: normal; 402 | grid-column: 1; 403 | } 404 | 405 | .xr-attrs dt:hover span { 406 | display: inline-block; 407 | background: var(--xr-background-color); 408 | padding-right: 10px; 409 | } 410 | 411 | .xr-attrs dd { 412 | grid-column: 2; 413 | white-space: pre-wrap; 414 | word-break: break-all; 415 | } 416 | 417 | .xr-icon-xarray { 418 | vertical-align: bottom; 419 | } 420 | 421 | .xr-icon-data, 422 | .xr-icon-attrs, 423 | .xr-icon-filter, 424 | .xr-icon-clear, 425 | .xr-no-icon { 426 | display: inline-block; 427 | vertical-align: middle; 428 | width: 1em; 429 | height: 1.5em !important; 430 | stroke-width: 0; 431 | stroke: currentColor; 432 | fill: currentColor; 433 | } 434 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | --------------------------------------------------------------------------------