diff --git a/README.md b/README.md index 0393ed9..f1e8696 100644 --- a/README.md +++ b/README.md @@ -40,9 +40,9 @@ them. The release of JupyterLab 3 has made some version compatibility unpredictable. Below are some researched combinations that should work. -| `jupyterlab` | `ipycytoscape` | `pyviz_comms` | `pip install` | -| ------------ | ---------------- | ------------- | ------------------ | -| `>=3,<4` | `>=1.1.0` | `>=1.0.3` | `ipyradiant[lab3]` | +| `jupyterlab` | `ipycytoscape` | `pyviz_comms` | `pip install` | +| ------------ | -------------- | ------------- | ------------------ | +| `>=3,<4` | `>=1.1.0` | `>=1.0.3` | `ipyradiant[lab3]` | ## Install diff --git a/anaconda-project-lock.yml b/anaconda-project-lock.yml index 514ff4d..444358b 100644 --- a/anaconda-project-lock.yml +++ b/anaconda-project-lock.yml @@ -21,778 +21,782 @@ env_specs: locked: true env_spec_hash: 3a5692c6b256101c97521f67262da72ea264656c platforms: - - linux-64 - - osx-64 - - win-64 + - linux-64 + - osx-64 + - win-64 packages: all: - - appdirs=1.4.4=pyh9f0ad1d_0 - - async_generator=1.10=py_0 - - attrs=20.3.0=pyhd3deb0d_0 - - babel=2.9.0=pyhd3deb0d_0 - - backcall=0.2.0=pyh9f0ad1d_0 - - backports.functools_lru_cache=1.6.1=py_0 - - backports=1.0=py_2 - - bleach=3.3.0=pyh44b312d_0 - - cachetools=4.2.1=pyhd8ed1ab_0 - - click=7.1.2=pyh9f0ad1d_0 - - cloudpickle=1.6.0=py_0 - - colorcet=2.0.6=pyhd8ed1ab_0 - - cycler=0.10.0=py_2 - - dask-core=2021.2.0=pyhd8ed1ab_0 - - dask=2021.2.0=pyhd8ed1ab_0 - - dataclasses=0.7=pyhb2cacf7_7 - - datashader=0.12.0=pyhd3deb0d_0 - - datashape=0.5.4=py_1 - - decorator=4.4.2=py_0 - - defusedxml=0.6.0=py_0 - - entrypoints=0.3=pyhd8ed1ab_1003 - - frozendict=1.2=pyh9f0ad1d_3 - - fsspec=0.8.5=pyhd8ed1ab_0 - - heapdict=1.0.1=py_0 - - holoviews=1.14.1=pyhd3deb0d_0 - - html5lib=1.1=pyh9f0ad1d_0 - - hvplot=0.7.0=pyhd3deb0d_0 - - idna=2.10=pyh9f0ad1d_0 - - imageio=2.9.0=py_0 - - importlib_metadata=3.4.0=hd8ed1ab_0 - - importnb=0.7.0=pyhd8ed1ab_0 - - iniconfig=1.1.1=pyh9f0ad1d_0 - - ipycytoscape=1.2.0=pyhd8ed1ab_0 - - ipython_genutils=0.2.0=py_1 - - ipywidgets=7.6.3=pyhd3deb0d_0 - - isodate=0.6.0=py_1 - - jinja2=2.11.3=pyh44b312d_0 - - json5=0.9.5=pyh9f0ad1d_0 - - jsonschema=3.2.0=py_2 - - jupyter_client=6.1.11=pyhd8ed1ab_1 - - jupyterlab=3.0.7=pyhd8ed1ab_0 - - jupyterlab_pygments=0.1.2=pyh9f0ad1d_0 - - jupyterlab_server=2.2.0=pyhd8ed1ab_0 - - jupyterlab_widgets=1.0.0=pyhd8ed1ab_1 - - locket=0.2.0=py_2 - - markdown=3.3.3=pyh9f0ad1d_0 - - more-itertools=8.7.0=pyhd8ed1ab_0 - - multipledispatch=0.6.0=py_0 - - nbclassic=0.2.6=pyhd8ed1ab_0 - - nbclient=0.5.2=pyhd8ed1ab_0 - - nbformat=5.1.2=pyhd8ed1ab_1 - - nest-asyncio=1.4.3=pyhd8ed1ab_0 - - networkx=2.5=py_0 - - olefile=0.46=pyh9f0ad1d_1 - - owlrl=5.2.1=py_0 - - packaging=20.9=pyh44b312d_0 - - pandocfilters=1.4.2=py_1 - - panel=0.10.3=pyhd8ed1ab_0 - - param=1.10.1=pyhd3deb0d_0 - - parso=0.8.1=pyhd8ed1ab_0 - - partd=1.1.0=py_0 - - pickleshare=0.7.5=py_1003 - - pip=21.0.1=pyhd8ed1ab_0 - - pooch=1.3.0=pyhd8ed1ab_0 - - prometheus_client=0.9.0=pyhd3deb0d_0 - - prompt-toolkit=3.0.16=pyha770c72_0 - - py=1.10.0=pyhd3deb0d_0 - - pycparser=2.20=pyh9f0ad1d_2 - - pyct-core=0.4.6=py_0 - - pyct=0.4.6=py_0 - - pygments=2.7.4=pyhd8ed1ab_0 - - pyld=2.0.3=pyh9f0ad1d_0 - - pyopenssl=20.0.1=pyhd8ed1ab_0 - - pyparsing=2.4.7=pyh9f0ad1d_0 - - pyshacl=0.14.2=pyhd8ed1ab_0 - - pytest-cov=2.11.1=pyh44b312d_0 - - python-dateutil=2.8.1=py_0 - - python_abi=3.7=1_cp37m - - pytz=2021.1=pyhd8ed1ab_0 - - pyviz_comms=2.0.1=pyhd3deb0d_0 - - requests=2.25.1=pyhd3deb0d_0 - - requests_cache=0.4.13=py_0 - - send2trash=1.5.0=py_0 - - six=1.15.0=pyh9f0ad1d_0 - - sortedcontainers=2.3.0=pyhd8ed1ab_0 - - tblib=1.6.0=py_0 - - testpath=0.4.4=py_0 - - tifffile=2021.2.1=pyhd8ed1ab_0 - - toml=0.10.2=pyhd8ed1ab_0 - - toolz=0.11.1=py_0 - - tqdm=4.56.2=pyhd8ed1ab_0 - - traitlets=5.0.5=py_0 - - traittypes=0.2.1=pyh9f0ad1d_2 - - typing_extensions=3.7.4.3=py_0 - - urllib3=1.26.3=pyhd8ed1ab_0 - - wcwidth=0.2.5=pyh9f0ad1d_2 - - webencodings=0.5.1=py_1 - - wheel=0.36.2=pyhd3deb0d_0 - - xarray=0.16.2=pyhd8ed1ab_0 - - zict=2.0.0=py_0 - - zipp=3.4.0=py_0 + - appdirs=1.4.4=pyh9f0ad1d_0 + - async_generator=1.10=py_0 + - attrs=20.3.0=pyhd3deb0d_0 + - babel=2.9.0=pyhd3deb0d_0 + - backcall=0.2.0=pyh9f0ad1d_0 + - backports.functools_lru_cache=1.6.1=py_0 + - backports=1.0=py_2 + - bleach=3.3.0=pyh44b312d_0 + - cachetools=4.2.1=pyhd8ed1ab_0 + - click=7.1.2=pyh9f0ad1d_0 + - cloudpickle=1.6.0=py_0 + - colorcet=2.0.6=pyhd8ed1ab_0 + - cycler=0.10.0=py_2 + - dask-core=2021.2.0=pyhd8ed1ab_0 + - dask=2021.2.0=pyhd8ed1ab_0 + - dataclasses=0.7=pyhb2cacf7_7 + - datashader=0.12.0=pyhd3deb0d_0 + - datashape=0.5.4=py_1 + - decorator=4.4.2=py_0 + - defusedxml=0.6.0=py_0 + - entrypoints=0.3=pyhd8ed1ab_1003 + - frozendict=1.2=pyh9f0ad1d_3 + - fsspec=0.8.7=pyhd8ed1ab_0 + - heapdict=1.0.1=py_0 + - holoviews=1.14.1=pyhd3deb0d_0 + - html5lib=1.1=pyh9f0ad1d_0 + - hvplot=0.7.0=pyhd3deb0d_0 + - idna=2.10=pyh9f0ad1d_0 + - imageio=2.9.0=py_0 + - importlib_metadata=3.7.0=hd8ed1ab_0 + - importnb=0.7.0=pyhd8ed1ab_0 + - iniconfig=1.1.1=pyh9f0ad1d_0 + - ipycytoscape=1.2.0=pyhd8ed1ab_0 + - ipython_genutils=0.2.0=py_1 + - ipywidgets=7.6.3=pyhd3deb0d_0 + - isodate=0.6.0=py_1 + - jinja2=2.11.3=pyh44b312d_0 + - json5=0.9.5=pyh9f0ad1d_0 + - jsonschema=3.2.0=py_2 + - jupyter-packaging=0.7.12=pyhd8ed1ab_0 + - jupyter_client=6.1.11=pyhd8ed1ab_1 + - jupyterlab=3.0.9=pyhd8ed1ab_0 + - jupyterlab_pygments=0.1.2=pyh9f0ad1d_0 + - jupyterlab_server=2.3.0=pyhd8ed1ab_0 + - jupyterlab_widgets=1.0.0=pyhd8ed1ab_1 + - locket=0.2.0=py_2 + - markdown=3.3.4=pyhd8ed1ab_0 + - more-itertools=8.7.0=pyhd8ed1ab_0 + - multipledispatch=0.6.0=py_0 + - nbclassic=0.2.6=pyhd8ed1ab_0 + - nbclient=0.5.3=pyhd8ed1ab_0 + - nbformat=5.1.2=pyhd8ed1ab_1 + - nest-asyncio=1.4.3=pyhd8ed1ab_0 + - networkx=2.5=py_0 + - olefile=0.46=pyh9f0ad1d_1 + - owlrl=5.2.1=py_0 + - packaging=20.9=pyh44b312d_0 + - pandocfilters=1.4.2=py_1 + - panel=0.10.3=pyhd8ed1ab_0 + - param=1.10.1=pyhd3deb0d_0 + - parso=0.8.1=pyhd8ed1ab_0 + - partd=1.1.0=py_0 + - pickleshare=0.7.5=py_1003 + - pip=21.0.1=pyhd8ed1ab_0 + - pooch=1.3.0=pyhd8ed1ab_0 + - prometheus_client=0.9.0=pyhd3deb0d_0 + - prompt-toolkit=3.0.16=pyha770c72_0 + - py=1.10.0=pyhd3deb0d_0 + - pycparser=2.20=pyh9f0ad1d_2 + - pyct-core=0.4.6=py_0 + - pyct=0.4.6=py_0 + - pygments=2.8.0=pyhd8ed1ab_0 + - pyld=2.0.3=pyh9f0ad1d_0 + - pyopenssl=20.0.1=pyhd8ed1ab_0 + - pyparsing=2.4.7=pyh9f0ad1d_0 + - pyshacl=0.14.3=pyhd8ed1ab_0 + - pytest-cov=2.11.1=pyh44b312d_0 + - python-dateutil=2.8.1=py_0 + - python_abi=3.7=1_cp37m + - pytz=2021.1=pyhd8ed1ab_0 + - pyviz_comms=2.0.1=pyhd3deb0d_0 + - requests=2.25.1=pyhd3deb0d_0 + - requests_cache=0.4.13=py_0 + - send2trash=1.5.0=py_0 + - six=1.15.0=pyh9f0ad1d_0 + - sortedcontainers=2.3.0=pyhd8ed1ab_0 + - tblib=1.6.0=py_0 + - testpath=0.4.4=py_0 + - tifffile=2021.2.26=pyhd8ed1ab_0 + - toml=0.10.2=pyhd8ed1ab_0 + - toolz=0.11.1=py_0 + - tqdm=4.58.0=pyhd8ed1ab_0 + - traitlets=5.0.5=py_0 + - traittypes=0.2.1=pyh9f0ad1d_2 + - typing_extensions=3.7.4.3=py_0 + - urllib3=1.26.3=pyhd8ed1ab_0 + - wcwidth=0.2.5=pyh9f0ad1d_2 + - webencodings=0.5.1=py_1 + - wheel=0.36.2=pyhd3deb0d_0 + - xarray=0.17.0=pyhd8ed1ab_0 + - zict=2.0.0=py_0 + - zipp=3.4.0=py_0 unix: - - libblas=3.9.0=8_openblas - - libcblas=3.9.0=8_openblas - - liblapack=3.9.0=8_openblas - - pexpect=4.8.0=pyh9f0ad1d_2 - - ptyprocess=0.7.0=pyhd3deb0d_0 + - libblas=3.9.0=8_openblas + - libcblas=3.9.0=8_openblas + - liblapack=3.9.0=8_openblas + - pexpect=4.8.0=pyh9f0ad1d_2 + - ptyprocess=0.7.0=pyhd3deb0d_0 linux-64: - - _libgcc_mutex=0.1=conda_forge - - _openmp_mutex=4.5=1_gnu - - anyio=2.1.0=py37h89c1867_0 - - argon2-cffi=20.1.0=py37h5e8e339_2 - - blosc=1.21.0=h9c3ff4c_0 - - bokeh=2.2.3=py37h89c1867_0 - - brotli=1.0.9=h9c3ff4c_4 - - brotlipy=0.7.0=py37h5e8e339_1001 - - brunsli=0.1=h9c3ff4c_0 - - bzip2=1.0.8=h7f98852_4 - - ca-certificates=2020.12.5=ha878542_0 - - certifi=2020.12.5=py37h89c1867_1 - - cffi=1.14.4=py37hc58025e_1 - - chardet=4.0.0=py37h89c1867_1 - - charls=2.2.0=h9c3ff4c_0 - - coverage=5.4=py37h5e8e339_0 - - cryptography=3.4.4=py37hf1a17b8_0 - - cytoolz=0.11.0=py37h5e8e339_3 - - distributed=2021.2.0=py37h89c1867_0 - - freetype=2.10.4=h0708190_1 - - giflib=5.2.1=h36c2ea0_2 - - icu=68.1=h58526e2_0 - - imagecodecs=2021.1.11=py37h95c7a1b_1 - - importlib-metadata=3.4.0=py37h89c1867_0 - - importlib_resources=5.1.0=py37h89c1867_0 - - ipykernel=5.4.2=py37h888b3d9_0 - - ipython=7.20.0=py37h888b3d9_2 - - jedi=0.18.0=py37h89c1867_2 - - jpeg=9d=h36c2ea0_0 - - jupyter_core=4.7.1=py37h89c1867_0 - - jupyter_server=1.3.0=py37h89c1867_0 - - jxrlib=1.1=h7f98852_2 - - keepalive=0.5=py37h89c1867_5 - - kiwisolver=1.3.1=py37h2527ec5_1 - - lcms2=2.12=hddcbb42_0 - - ld_impl_linux-64=2.35.1=hea4e1c9_2 - - lerc=2.2.1=h9c3ff4c_0 - - libaec=1.0.4=h9c3ff4c_1 - - libdeflate=1.7=h7f98852_5 - - libffi=3.3=h58526e2_2 - - libgcc-ng=9.3.0=h2828fa1_18 - - libgfortran-ng=9.3.0=hff62375_18 - - libgfortran5=9.3.0=hff62375_18 - - libgomp=9.3.0=h2828fa1_18 - - libiconv=1.16=h516909a_0 - - libllvm10=10.0.1=he513fc3_3 - - libopenblas=0.3.12=pthreads_h4812303_1 - - libpng=1.6.37=h21135ba_2 - - libsodium=1.0.18=h36c2ea0_1 - - libstdcxx-ng=9.3.0=h6de172a_18 - - libtiff=4.2.0=hdc55705_0 - - libuv=1.40.0=h7f98852_0 - - libwebp-base=1.2.0=h7f98852_0 - - libxml2=2.9.10=h72842e0_3 - - libxslt=1.1.33=h15afd5d_2 - - libzopfli=1.0.3=h9c3ff4c_0 - - llvmlite=0.35.0=py37h9d7f4d0_1 - - lxml=4.6.2=py37h77fd288_1 - - lz4-c=1.9.3=h9c3ff4c_0 - - markupsafe=1.1.1=py37h5e8e339_3 - - matplotlib-base=3.3.4=py37h0c9df89_0 - - mistune=0.8.4=py37h5e8e339_1003 - - msgpack-python=1.0.2=py37h2527ec5_1 - - nbconvert=6.0.7=py37h89c1867_3 - - ncurses=6.2=h58526e2_4 - - nodejs=12.19.0=hfa01f41_2 - - notebook=6.2.0=py37h89c1867_0 - - numba=0.52.0=py37hdc94413_0 - - numpy=1.20.1=py37haa41c4c_0 - - openjpeg=2.4.0=hf7af979_0 - - openssl=1.1.1i=h7f98852_0 - - pandas=1.2.2=py37hdc94413_0 - - pandoc=2.11.4=h7f98852_0 - - pillow=8.1.0=py37h4600e1f_2 - - pluggy=0.13.1=py37h89c1867_4 - - psutil=5.8.0=py37h5e8e339_1 - - pyrsistent=0.17.3=py37h5e8e339_2 - - pysocks=1.7.1=py37h89c1867_3 - - pytest=6.2.2=py37h89c1867_0 - - python=3.7.9=hffdb5ce_0_cpython - - pywavelets=1.1.1=py37h902c9e0_3 - - pyyaml=5.4.1=py37h5e8e339_0 - - pyzmq=22.0.1=py37h499b945_0 - - rdflib-jsonld=0.5.0=py37h89c1867_2 - - rdflib=5.0.0=py37h89c1867_3 - - readline=8.0=he28a2e2_2 - - scikit-image=0.18.1=py37hdc94413_0 - - scipy=1.6.0=py37h14a347d_0 - - setuptools=49.6.0=py37h89c1867_3 - - snappy=1.1.8=he1b5a44_3 - - sniffio=1.2.0=py37h89c1867_1 - - sparqlwrapper=1.8.5=py37h89c1867_1005 - - spectate=1.0.0=py37h89c1867_1 - - sqlite=3.34.0=h74cdb3f_0 - - terminado=0.9.2=py37h89c1867_0 - - tk=8.6.10=h21135ba_1 - - tornado=6.1=py37h5e8e339_1 - - widgetsnbextension=3.5.1=py37h89c1867_4 - - xz=5.2.5=h516909a_1 - - yaml=0.2.5=h516909a_0 - - zeromq=4.3.4=h9c3ff4c_0 - - zfp=0.5.5=h9c3ff4c_4 - - zlib=1.2.11=h516909a_1010 - - zstd=1.4.8=ha95c52a_1 + - _libgcc_mutex=0.1=conda_forge + - _openmp_mutex=4.5=1_gnu + - anyio=2.2.0=py37h89c1867_0 + - argon2-cffi=20.1.0=py37h5e8e339_2 + - blosc=1.21.0=h9c3ff4c_0 + - bokeh=2.3.0=py37h89c1867_0 + - brotli=1.0.9=h9c3ff4c_4 + - brotlipy=0.7.0=py37h5e8e339_1001 + - brunsli=0.1=h9c3ff4c_0 + - bzip2=1.0.8=h7f98852_4 + - ca-certificates=2020.12.5=ha878542_0 + - certifi=2020.12.5=py37h89c1867_1 + - cffi=1.14.5=py37hc58025e_0 + - chardet=4.0.0=py37h89c1867_1 + - charls=2.2.0=h9c3ff4c_0 + - coverage=5.5=py37h5e8e339_0 + - cryptography=3.4.4=py37hf1a17b8_0 + - cytoolz=0.11.0=py37h5e8e339_3 + - distributed=2021.2.0=py37h89c1867_0 + - freetype=2.10.4=h0708190_1 + - giflib=5.2.1=h36c2ea0_2 + - icu=68.1=h58526e2_0 + - imagecodecs=2021.1.28=py37h1985380_0 + - importlib-metadata=3.7.0=py37h89c1867_0 + - importlib_resources=5.1.1=py37h89c1867_0 + - ipykernel=5.5.0=py37h888b3d9_1 + - ipython=7.21.0=py37h888b3d9_0 + - jedi=0.18.0=py37h89c1867_2 + - jpeg=9d=h36c2ea0_0 + - jupyter_core=4.7.1=py37h89c1867_0 + - jupyter_server=1.4.1=py37h89c1867_0 + - jxrlib=1.1=h7f98852_2 + - keepalive=0.5=py37h89c1867_5 + - kiwisolver=1.3.1=py37h2527ec5_1 + - lcms2=2.12=hddcbb42_0 + - ld_impl_linux-64=2.35.1=hea4e1c9_2 + - lerc=2.2.1=h9c3ff4c_0 + - libaec=1.0.4=h9c3ff4c_1 + - libdeflate=1.7=h7f98852_5 + - libffi=3.3=h58526e2_2 + - libgcc-ng=9.3.0=h2828fa1_18 + - libgfortran-ng=9.3.0=hff62375_18 + - libgfortran5=9.3.0=hff62375_18 + - libgomp=9.3.0=h2828fa1_18 + - libiconv=1.16=h516909a_0 + - libllvm10=10.0.1=he513fc3_3 + - libopenblas=0.3.12=pthreads_h4812303_1 + - libpng=1.6.37=h21135ba_2 + - libsodium=1.0.18=h36c2ea0_1 + - libstdcxx-ng=9.3.0=h6de172a_18 + - libtiff=4.2.0=hdc55705_0 + - libuv=1.41.0=h7f98852_0 + - libwebp-base=1.2.0=h7f98852_0 + - libxml2=2.9.10=h72842e0_3 + - libxslt=1.1.33=h15afd5d_2 + - libzopfli=1.0.3=h9c3ff4c_0 + - llvmlite=0.35.0=py37h9d7f4d0_1 + - lxml=4.6.2=py37h77fd288_1 + - lz4-c=1.9.3=h9c3ff4c_0 + - markupsafe=1.1.1=py37h5e8e339_3 + - matplotlib-base=3.3.4=py37h0c9df89_0 + - mistune=0.8.4=py37h5e8e339_1003 + - msgpack-python=1.0.2=py37h2527ec5_1 + - nbconvert=6.0.7=py37h89c1867_3 + - ncurses=6.2=h58526e2_4 + - nodejs=12.19.0=h8b53aa1_3 + - notebook=6.2.0=py37h89c1867_0 + - numba=0.52.0=py37hdc94413_0 + - numpy=1.20.1=py37haa41c4c_0 + - openjpeg=2.4.0=hf7af979_0 + - openssl=1.1.1j=h7f98852_0 + - pandas=1.2.2=py37hdc94413_0 + - pandoc=2.11.4=h7f98852_0 + - pillow=8.1.0=py37h4600e1f_2 + - pluggy=0.13.1=py37h89c1867_4 + - psutil=5.8.0=py37h5e8e339_1 + - pyrsistent=0.17.3=py37h5e8e339_2 + - pysocks=1.7.1=py37h89c1867_3 + - pytest=6.2.2=py37h89c1867_0 + - python=3.7.10=hffdb5ce_100_cpython + - pywavelets=1.1.1=py37h902c9e0_3 + - pyyaml=5.4.1=py37h5e8e339_0 + - pyzmq=22.0.3=py37h336d617_1 + - rdflib-jsonld=0.5.0=py37h89c1867_2 + - rdflib=5.0.0=py37h89c1867_3 + - readline=8.0=he28a2e2_2 + - scikit-image=0.18.1=py37hdc94413_0 + - scipy=1.6.0=py37h14a347d_0 + - setuptools=49.6.0=py37h89c1867_3 + - snappy=1.1.8=he1b5a44_3 + - sniffio=1.2.0=py37h89c1867_1 + - sparqlwrapper=1.8.5=py37h89c1867_1005 + - spectate=1.0.0=py37h89c1867_1 + - sqlite=3.34.0=h74cdb3f_0 + - terminado=0.9.2=py37h89c1867_0 + - tk=8.6.10=h21135ba_1 + - tornado=6.1=py37h5e8e339_1 + - widgetsnbextension=3.5.1=py37h89c1867_4 + - xz=5.2.5=h516909a_1 + - yaml=0.2.5=h516909a_0 + - zeromq=4.3.4=h9c3ff4c_0 + - zfp=0.5.5=h9c3ff4c_4 + - zlib=1.2.11=h516909a_1010 + - zstd=1.4.8=ha95c52a_1 osx-64: - - anyio=2.1.0=py37hf985489_0 - - appnope=0.1.2=py37hf985489_1 - - argon2-cffi=20.1.0=py37hf967b71_2 - - blosc=1.21.0=h046ec9c_0 - - bokeh=2.2.3=py37hf985489_0 - - brotli=1.0.9=h046ec9c_4 - - brotlipy=0.7.0=py37hf967b71_1001 - - brunsli=0.1=h046ec9c_0 - - bzip2=1.0.8=hc929b4f_4 - - ca-certificates=2020.12.5=h033912b_0 - - certifi=2020.12.5=py37hf985489_1 - - cffi=1.14.4=py37hc5b2277_1 - - chardet=4.0.0=py37hf985489_1 - - charls=2.2.0=h046ec9c_0 - - coverage=5.4=py37hf967b71_0 - - cryptography=3.4.4=py37ha1e1f9f_0 - - cytoolz=0.11.0=py37hf967b71_3 - - distributed=2021.2.0=py37hf985489_0 - - freetype=2.10.4=h4cff582_1 - - giflib=5.2.1=hbcb3906_2 - - icu=68.1=h74dc148_0 - - imagecodecs=2021.1.11=py37h37c44b6_1 - - importlib-metadata=3.4.0=py37hf985489_0 - - importlib_resources=5.1.0=py37hf985489_0 - - ipykernel=5.4.2=py37he01cfaa_0 - - ipython=7.20.0=py37he01cfaa_2 - - jedi=0.18.0=py37hf985489_2 - - jpeg=9d=hbcb3906_0 - - jupyter_core=4.7.1=py37hf985489_0 - - jupyter_server=1.3.0=py37hf985489_0 - - jxrlib=1.1=h35c211d_2 - - keepalive=0.5=py37hf985489_5 - - kiwisolver=1.3.1=py37h70f7d40_1 - - lcms2=2.12=h577c468_0 - - lerc=2.2.1=h046ec9c_0 - - libaec=1.0.4=h046ec9c_1 - - libcxx=11.0.1=habf9029_0 - - libdeflate=1.7=h35c211d_5 - - libffi=3.3=h046ec9c_2 - - libgfortran5=9.3.0=h6c81a4c_17 - - libgfortran=5.0.0=9_3_0_h6c81a4c_17 - - libiconv=1.16=haf1e3a3_0 - - libllvm10=10.0.1=h009f743_3 - - libopenblas=0.3.12=openmp_h54245bb_1 - - libpng=1.6.37=h7cec526_2 - - libsodium=1.0.18=hbcb3906_1 - - libtiff=4.2.0=h355d032_0 - - libuv=1.40.0=h35c211d_0 - - libwebp-base=1.2.0=hbcf498f_0 - - libxml2=2.9.10=h802ca4f_3 - - libxslt=1.1.33=h5739fc3_2 - - libzopfli=1.0.3=h046ec9c_0 - - llvm-openmp=11.0.1=h7c73e74_0 - - llvmlite=0.35.0=py37hd32b0aa_1 - - lxml=4.6.2=py37he94f95d_1 - - lz4-c=1.9.3=h046ec9c_0 - - markupsafe=1.1.1=py37hf967b71_3 - - matplotlib-base=3.3.4=py37haa7e288_0 - - mistune=0.8.4=py37hf967b71_1003 - - msgpack-python=1.0.2=py37h70f7d40_1 - - nbconvert=6.0.7=py37hf985489_3 - - ncurses=6.2=h2e338ed_4 - - nodejs=12.19.0=h49b9f75_2 - - notebook=6.2.0=py37hf985489_0 - - numba=0.52.0=py37h010c265_0 - - numpy=1.20.1=py37ha9839cc_0 - - openjpeg=2.4.0=h6cbf5cd_0 - - openssl=1.1.1i=h35c211d_0 - - pandas=1.2.2=py37h7d02bce_0 - - pandoc=2.11.4=h35c211d_0 - - pillow=8.1.0=py37hd722d6e_2 - - pluggy=0.13.1=py37hf985489_4 - - psutil=5.8.0=py37hf967b71_1 - - pyrsistent=0.17.3=py37hf967b71_2 - - pysocks=1.7.1=py37hf985489_3 - - pytest=6.2.2=py37hf985489_0 - - python=3.7.9=h6c3b2c9_0_cpython - - pywavelets=1.1.1=py37h37391d0_3 - - pyyaml=5.4.1=py37hf967b71_0 - - pyzmq=22.0.1=py37hf4687c3_0 - - rdflib-jsonld=0.5.0=py37hf985489_2 - - rdflib=5.0.0=py37hf985489_3 - - readline=8.0=h0678c8f_2 - - scikit-image=0.18.1=py37h010c265_0 - - scipy=1.6.0=py37hc22aa43_0 - - setuptools=49.6.0=py37hf985489_3 - - snappy=1.1.8=hb1e8313_3 - - sniffio=1.2.0=py37hf985489_1 - - sparqlwrapper=1.8.5=py37hf985489_1005 - - spectate=1.0.0=py37hf985489_1 - - sqlite=3.34.0=h17101e1_0 - - terminado=0.9.2=py37hf985489_0 - - tk=8.6.10=h0419947_1 - - tornado=6.1=py37hf967b71_1 - - widgetsnbextension=3.5.1=py37hf985489_4 - - xz=5.2.5=haf1e3a3_1 - - yaml=0.2.5=haf1e3a3_0 - - zeromq=4.3.4=h1c7c35f_0 - - zfp=0.5.5=h046ec9c_4 - - zlib=1.2.11=h7795811_1010 - - zstd=1.4.8=hf387650_1 + - anyio=2.2.0=py37hf985489_0 + - appnope=0.1.2=py37hf985489_1 + - argon2-cffi=20.1.0=py37hf967b71_2 + - blosc=1.21.0=h046ec9c_0 + - bokeh=2.3.0=py37hf985489_0 + - brotli=1.0.9=h046ec9c_4 + - brotlipy=0.7.0=py37hf967b71_1001 + - brunsli=0.1=h046ec9c_0 + - bzip2=1.0.8=hc929b4f_4 + - ca-certificates=2020.12.5=h033912b_0 + - certifi=2020.12.5=py37hf985489_1 + - cffi=1.14.5=py37h4d371b4_0 + - chardet=4.0.0=py37hf985489_1 + - charls=2.2.0=h046ec9c_0 + - coverage=5.5=py37h271585c_0 + - cryptography=3.4.4=py37ha1e1f9f_0 + - cytoolz=0.11.0=py37hf967b71_3 + - distributed=2021.2.0=py37hf985489_0 + - freetype=2.10.4=h4cff582_1 + - giflib=5.2.1=hbcb3906_2 + - icu=68.1=h74dc148_0 + - imagecodecs=2021.1.28=py37h9f08f5f_0 + - importlib-metadata=3.7.0=py37hf985489_0 + - importlib_resources=5.1.1=py37hf985489_0 + - ipykernel=5.5.0=py37he01cfaa_1 + - ipython=7.21.0=py37he01cfaa_0 + - jedi=0.18.0=py37hf985489_2 + - jpeg=9d=hbcb3906_0 + - jupyter_core=4.7.1=py37hf985489_0 + - jupyter_server=1.4.1=py37hf985489_0 + - jxrlib=1.1=h35c211d_2 + - keepalive=0.5=py37hf985489_5 + - kiwisolver=1.3.1=py37h70f7d40_1 + - lcms2=2.12=h577c468_0 + - lerc=2.2.1=h046ec9c_0 + - libaec=1.0.4=h046ec9c_1 + - libcxx=11.0.1=habf9029_0 + - libdeflate=1.7=h35c211d_5 + - libffi=3.3=h046ec9c_2 + - libgfortran5=9.3.0=h6c81a4c_19 + - libgfortran=5.0.0=9_3_0_h6c81a4c_19 + - libiconv=1.16=haf1e3a3_0 + - libllvm10=10.0.1=h009f743_3 + - libopenblas=0.3.12=openmp_h54245bb_1 + - libpng=1.6.37=h7cec526_2 + - libsodium=1.0.18=hbcb3906_1 + - libtiff=4.2.0=h355d032_0 + - libuv=1.41.0=hbcf498f_0 + - libwebp-base=1.2.0=hbcf498f_0 + - libxml2=2.9.10=h802ca4f_3 + - libxslt=1.1.33=h5739fc3_2 + - libzopfli=1.0.3=h046ec9c_0 + - llvm-openmp=11.0.1=h7c73e74_0 + - llvmlite=0.35.0=py37hd32b0aa_1 + - lxml=4.6.2=py37he94f95d_1 + - lz4-c=1.9.3=h046ec9c_0 + - markupsafe=1.1.1=py37hf967b71_3 + - matplotlib-base=3.3.4=py37haa7e288_0 + - mistune=0.8.4=py37hf967b71_1003 + - msgpack-python=1.0.2=py37h70f7d40_1 + - nbconvert=6.0.7=py37hf985489_3 + - ncurses=6.2=h2e338ed_4 + - nodejs=12.19.0=hf294227_3 + - notebook=6.2.0=py37hf985489_0 + - numba=0.52.0=py37h010c265_0 + - numpy=1.20.1=py37ha9839cc_0 + - openjpeg=2.4.0=h6cbf5cd_0 + - openssl=1.1.1j=hbcf498f_0 + - pandas=1.2.2=py37h7d02bce_0 + - pandoc=2.11.4=h35c211d_0 + - pillow=8.1.0=py37hd722d6e_2 + - pluggy=0.13.1=py37hf985489_4 + - psutil=5.8.0=py37hf967b71_1 + - pyrsistent=0.17.3=py37hf967b71_2 + - pysocks=1.7.1=py37hf985489_3 + - pytest=6.2.2=py37hf985489_0 + - python=3.7.10=h7728216_100_cpython + - pywavelets=1.1.1=py37h37391d0_3 + - pyyaml=5.4.1=py37hf967b71_0 + - pyzmq=22.0.3=py37h8f778e5_1 + - rdflib-jsonld=0.5.0=py37hf985489_2 + - rdflib=5.0.0=py37hf985489_3 + - readline=8.0=h0678c8f_2 + - scikit-image=0.18.1=py37h010c265_0 + - scipy=1.6.0=py37hc22aa43_0 + - setuptools=49.6.0=py37hf985489_3 + - snappy=1.1.8=hb1e8313_3 + - sniffio=1.2.0=py37hf985489_1 + - sparqlwrapper=1.8.5=py37hf985489_1005 + - spectate=1.0.0=py37hf985489_1 + - sqlite=3.34.0=h17101e1_0 + - terminado=0.9.2=py37hf985489_0 + - tk=8.6.10=h0419947_1 + - tornado=6.1=py37hf967b71_1 + - widgetsnbextension=3.5.1=py37hf985489_4 + - xz=5.2.5=haf1e3a3_1 + - yaml=0.2.5=haf1e3a3_0 + - zeromq=4.3.4=h1c7c35f_0 + - zfp=0.5.5=h046ec9c_4 + - zlib=1.2.11=h7795811_1010 + - zstd=1.4.8=hf387650_1 win-64: - - anyio=2.1.0=py37h03978a9_0 - - argon2-cffi=20.1.0=py37hcc03f2d_2 - - atomicwrites=1.4.0=pyh9f0ad1d_0 - - blosc=1.21.0=h0e60522_0 - - bokeh=2.2.3=py37h03978a9_0 - - brotli=1.0.9=h0e60522_4 - - brotlipy=0.7.0=py37hcc03f2d_1001 - - bzip2=1.0.8=h8ffe710_4 - - ca-certificates=2020.12.5=h5b45459_0 - - certifi=2020.12.5=py37h03978a9_1 - - cffi=1.14.4=py37hd8e9650_1 - - chardet=4.0.0=py37h03978a9_1 - - charls=2.2.0=h39d44d4_0 - - colorama=0.4.4=pyh9f0ad1d_0 - - coverage=5.4=py37hcc03f2d_0 - - cryptography=3.4.4=py37h65266a2_0 - - cytoolz=0.11.0=py37hcc03f2d_3 - - distributed=2021.2.0=py37h03978a9_0 - - freetype=2.10.4=h546665d_1 - - giflib=5.2.1=h8d14728_2 - - imagecodecs=2021.1.11=py37hfc4c502_1 - - importlib-metadata=3.4.0=py37h03978a9_0 - - importlib_resources=5.1.0=py37h03978a9_0 - - intel-openmp=2020.3=h57928b3_311 - - ipykernel=5.4.2=py37h7b7c402_0 - - ipython=7.20.0=py37heaed05f_2 - - jedi=0.18.0=py37h03978a9_2 - - jpeg=9d=h8ffe710_0 - - jupyter_core=4.7.1=py37h03978a9_0 - - jupyter_server=1.3.0=py37h03978a9_0 - - jxrlib=1.1=h8ffe710_2 - - keepalive=0.5=py37h03978a9_5 - - kiwisolver=1.3.1=py37h8c56517_1 - - lcms2=2.12=h2a16943_0 - - lerc=2.2.1=h0e60522_0 - - libaec=1.0.4=h39d44d4_1 - - libblas=3.9.0=8_mkl - - libcblas=3.9.0=8_mkl - - libdeflate=1.7=h8ffe710_5 - - libiconv=1.16=he774522_0 - - liblapack=3.9.0=8_mkl - - libpng=1.6.37=h1d00b33_2 - - libsodium=1.0.18=h8d14728_1 - - libtiff=4.2.0=hc10be44_0 - - libwebp-base=1.2.0=h8ffe710_0 - - libxml2=2.9.10=hf5bbc77_3 - - libxslt=1.1.33=h65864e5_2 - - libzopfli=1.0.3=h0e60522_0 - - llvmlite=0.35.0=py37habb0c8c_1 - - lxml=4.6.2=py37hd07aab1_1 - - lz4-c=1.9.3=h8ffe710_0 - - m2w64-gcc-libgfortran=5.3.0=6 - - m2w64-gcc-libs-core=5.3.0=7 - - m2w64-gcc-libs=5.3.0=7 - - m2w64-gmp=6.1.0=2 - - m2w64-libwinpthread-git=5.0.0.4634.697f757=2 - - markupsafe=1.1.1=py37hcc03f2d_3 - - matplotlib-base=3.3.4=py37h3379fd5_0 - - mistune=0.8.4=py37hcc03f2d_1003 - - mkl=2020.4=hb70f87d_311 - - msgpack-python=1.0.2=py37h8c56517_1 - - msys2-conda-epoch=20160418=1 - - nbconvert=6.0.7=py37h03978a9_3 - - nodejs=12.19.0=h57928b3_2 - - notebook=6.2.0=py37h03978a9_0 - - numba=0.52.0=py37h08fd248_0 - - numpy=1.20.1=py37hd20adf4_0 - - openjpeg=2.4.0=h48faf41_0 - - openssl=1.1.1i=h8ffe710_0 - - pandas=1.2.2=py37h08fd248_0 - - pandoc=2.11.4=h8ffe710_0 - - pillow=8.1.0=py37h96663a1_2 - - pluggy=0.13.1=py37h03978a9_4 - - psutil=5.8.0=py37hcc03f2d_1 - - pyrsistent=0.17.3=py37hcc03f2d_2 - - pysocks=1.7.1=py37h03978a9_3 - - pytest=6.2.2=py37h03978a9_0 - - python=3.7.9=h7840368_0_cpython - - pywavelets=1.1.1=py37hda49f71_3 - - pywin32=300=py37hcc03f2d_0 - - pywinpty=0.5.7=py37hc8dfbb8_1 - - pyyaml=5.4.1=py37hcc03f2d_0 - - pyzmq=22.0.1=py37h0d95fc2_0 - - rdflib-jsonld=0.5.0=py37h03978a9_2 - - rdflib=5.0.0=py37h03978a9_3 - - scikit-image=0.18.1=py37h08fd248_0 - - scipy=1.6.0=py37h6db1a17_0 - - setuptools=49.6.0=py37h03978a9_3 - - snappy=1.1.8=ha925a31_3 - - sniffio=1.2.0=py37h03978a9_1 - - sparqlwrapper=1.8.5=py37h03978a9_1005 - - spectate=0.4.1=pyh9f0ad1d_0 - - sqlite=3.34.0=h8ffe710_0 - - terminado=0.9.2=py37h03978a9_0 - - tk=8.6.10=h8ffe710_1 - - tornado=6.1=py37hcc03f2d_1 - - vc=14.2=hb210afc_3 - - vs2015_runtime=14.28.29325=h5e1d092_3 - - widgetsnbextension=3.5.1=py37h03978a9_4 - - win_inet_pton=1.1.0=py37h03978a9_2 - - wincertstore=0.2=py37h03978a9_1006 - - winpty=0.4.3=4 - - xz=5.2.5=h62dcd97_1 - - yaml=0.2.5=he774522_0 - - zeromq=4.3.3=h0e60522_3 - - zfp=0.5.5=h0e60522_4 - - zlib=1.2.11=h62dcd97_1010 - - zstd=1.4.8=h4e2f164_1 + - anyio=2.2.0=py37h03978a9_0 + - argon2-cffi=20.1.0=py37hcc03f2d_2 + - atomicwrites=1.4.0=pyh9f0ad1d_0 + - blosc=1.21.0=h0e60522_0 + - bokeh=2.3.0=py37h03978a9_0 + - brotli=1.0.9=h0e60522_4 + - brotlipy=0.7.0=py37hcc03f2d_1001 + - bzip2=1.0.8=h8ffe710_4 + - ca-certificates=2020.12.5=h5b45459_0 + - certifi=2020.12.5=py37h03978a9_1 + - cffi=1.14.5=py37hd8e9650_0 + - chardet=4.0.0=py37h03978a9_1 + - charls=2.2.0=h39d44d4_0 + - colorama=0.4.4=pyh9f0ad1d_0 + - coverage=5.5=py37hcc03f2d_0 + - cryptography=3.4.4=py37h65266a2_0 + - cytoolz=0.11.0=py37hcc03f2d_3 + - distributed=2021.2.0=py37h03978a9_0 + - freetype=2.10.4=h546665d_1 + - giflib=5.2.1=h8d14728_2 + - imagecodecs=2021.1.28=py37hb664a41_0 + - importlib-metadata=3.7.0=py37h03978a9_0 + - importlib_resources=5.1.1=py37h03978a9_0 + - intel-openmp=2020.3=h57928b3_311 + - ipykernel=5.5.0=py37heaed05f_1 + - ipython=7.21.0=py37heaed05f_0 + - jedi=0.18.0=py37h03978a9_2 + - jpeg=9d=h8ffe710_0 + - jupyter_core=4.7.1=py37h03978a9_0 + - jupyter_server=1.4.1=py37h03978a9_0 + - jxrlib=1.1=h8ffe710_2 + - keepalive=0.5=py37h03978a9_5 + - kiwisolver=1.3.1=py37h8c56517_1 + - lcms2=2.12=h2a16943_0 + - lerc=2.2.1=h0e60522_0 + - libaec=1.0.4=h39d44d4_1 + - libblas=3.9.0=8_mkl + - libcblas=3.9.0=8_mkl + - libdeflate=1.7=h8ffe710_5 + - libiconv=1.16=he774522_0 + - liblapack=3.9.0=8_mkl + - libpng=1.6.37=h1d00b33_2 + - libsodium=1.0.18=h8d14728_1 + - libtiff=4.2.0=hc10be44_0 + - libwebp-base=1.2.0=h8ffe710_0 + - libxml2=2.9.10=hf5bbc77_3 + - libxslt=1.1.33=h65864e5_2 + - libzopfli=1.0.3=h0e60522_0 + - llvmlite=0.35.0=py37habb0c8c_1 + - lxml=4.6.2=py37hd07aab1_1 + - lz4-c=1.9.3=h8ffe710_0 + - m2w64-gcc-libgfortran=5.3.0=6 + - m2w64-gcc-libs-core=5.3.0=7 + - m2w64-gcc-libs=5.3.0=7 + - m2w64-gmp=6.1.0=2 + - m2w64-libwinpthread-git=5.0.0.4634.697f757=2 + - markupsafe=1.1.1=py37hcc03f2d_3 + - matplotlib-base=3.3.4=py37h3379fd5_0 + - mistune=0.8.4=py37hcc03f2d_1003 + - mkl=2020.4=hb70f87d_311 + - msgpack-python=1.0.2=py37h8c56517_1 + - msys2-conda-epoch=20160418=1 + - nbconvert=6.0.7=py37h03978a9_3 + - nodejs=12.19.0=h57928b3_3 + - notebook=6.2.0=py37h03978a9_0 + - numba=0.52.0=py37h08fd248_0 + - numpy=1.20.1=py37hd20adf4_0 + - openjpeg=2.4.0=h48faf41_0 + - openssl=1.1.1j=h8ffe710_0 + - pandas=1.2.2=py37h08fd248_0 + - pandoc=2.11.4=h8ffe710_0 + - pillow=8.1.0=py37h96663a1_2 + - pluggy=0.13.1=py37h03978a9_4 + - psutil=5.8.0=py37hcc03f2d_1 + - pyrsistent=0.17.3=py37hcc03f2d_2 + - pysocks=1.7.1=py37h03978a9_3 + - pytest=6.2.2=py37h03978a9_0 + - python=3.7.10=h7840368_100_cpython + - pywavelets=1.1.1=py37hda49f71_3 + - pywin32=300=py37hcc03f2d_0 + - pywinpty=0.5.7=py37hc8dfbb8_1 + - pyyaml=5.4.1=py37hcc03f2d_0 + - pyzmq=22.0.3=py37hcce574b_1 + - rdflib-jsonld=0.5.0=py37h03978a9_2 + - rdflib=5.0.0=py37h03978a9_3 + - scikit-image=0.18.1=py37h08fd248_0 + - scipy=1.6.0=py37h6db1a17_0 + - setuptools=49.6.0=py37h03978a9_3 + - snappy=1.1.8=ha925a31_3 + - sniffio=1.2.0=py37h03978a9_1 + - sparqlwrapper=1.8.5=py37h03978a9_1005 + - spectate=0.4.1=pyh9f0ad1d_0 + - sqlite=3.34.0=h8ffe710_0 + - terminado=0.9.2=py37h03978a9_0 + - tk=8.6.10=h8ffe710_1 + - tornado=6.1=py37hcc03f2d_1 + - vc=14.2=hb210afc_3 + - vs2015_runtime=14.28.29325=h5e1d092_3 + - widgetsnbextension=3.5.1=py37h03978a9_4 + - win_inet_pton=1.1.0=py37h03978a9_2 + - wincertstore=0.2=py37h03978a9_1006 + - winpty=0.4.3=4 + - xz=5.2.5=h62dcd97_1 + - yaml=0.2.5=he774522_0 + - zeromq=4.3.4=h0e60522_0 + - zfp=0.5.5=h0e60522_4 + - zlib=1.2.11=h62dcd97_1010 + - zstd=1.4.8=h4e2f164_1 qa: locked: true env_spec_hash: 0fc8232ea04f9408db0d00c84810e209edbcfd4b platforms: - - linux-64 - - osx-64 - - win-64 + - linux-64 + - osx-64 + - win-64 packages: all: - - appdirs=1.4.4=pyh9f0ad1d_0 - - async_generator=1.10=py_0 - - attrs=20.3.0=pyhd3deb0d_0 - - babel=2.9.0=pyhd3deb0d_0 - - backcall=0.2.0=pyh9f0ad1d_0 - - backports.functools_lru_cache=1.6.1=py_0 - - backports=1.0=py_2 - - black=20.8b1=py_1 - - bleach=3.3.0=pyh44b312d_0 - - click=7.1.2=pyh9f0ad1d_0 - - dataclasses=0.7=pyhb2cacf7_7 - - decorator=4.4.2=py_0 - - defusedxml=0.6.0=py_0 - - entrypoints=0.3=pyhd8ed1ab_1003 - - flake8=3.8.4=py_0 - - idna=2.10=pyh9f0ad1d_0 - - importlib_metadata=3.4.0=hd8ed1ab_0 - - ipython_genutils=0.2.0=py_1 - - isort=5.7.0=pyhd8ed1ab_0 - - jinja2=2.11.3=pyh44b312d_0 - - json5=0.9.5=pyh9f0ad1d_0 - - jsonschema=3.2.0=py_2 - - jupyter_client=6.1.11=pyhd8ed1ab_1 - - jupyterlab=3.0.7=pyhd8ed1ab_0 - - jupyterlab_pygments=0.1.2=pyh9f0ad1d_0 - - jupyterlab_server=2.2.0=pyhd8ed1ab_0 - - mccabe=0.6.1=py_1 - - nbclassic=0.2.6=pyhd8ed1ab_0 - - nbclient=0.5.2=pyhd8ed1ab_0 - - nbformat=5.1.2=pyhd8ed1ab_1 - - nest-asyncio=1.4.3=pyhd8ed1ab_0 - - packaging=20.9=pyh44b312d_0 - - pandocfilters=1.4.2=py_1 - - parso=0.8.1=pyhd8ed1ab_0 - - pathspec=0.8.1=pyhd3deb0d_0 - - pickleshare=0.7.5=py_1003 - - prometheus_client=0.9.0=pyhd3deb0d_0 - - prompt-toolkit=3.0.16=pyha770c72_0 - - pycodestyle=2.6.0=pyh9f0ad1d_0 - - pycparser=2.20=pyh9f0ad1d_2 - - pyflakes=2.2.0=pyh9f0ad1d_0 - - pygments=2.7.4=pyhd8ed1ab_0 - - pyopenssl=20.0.1=pyhd8ed1ab_0 - - pyparsing=2.4.7=pyh9f0ad1d_0 - - python-dateutil=2.8.1=py_0 - - python_abi=3.7=1_cp37m - - pytz=2021.1=pyhd8ed1ab_0 - - requests=2.25.1=pyhd3deb0d_0 - - send2trash=1.5.0=py_0 - - six=1.15.0=pyh9f0ad1d_0 - - testpath=0.4.4=py_0 - - toml=0.10.2=pyhd8ed1ab_0 - - traitlets=5.0.5=py_0 - - typing_extensions=3.7.4.3=py_0 - - urllib3=1.26.3=pyhd8ed1ab_0 - - wcwidth=0.2.5=pyh9f0ad1d_2 - - webencodings=0.5.1=py_1 - - zipp=3.4.0=py_0 + - appdirs=1.4.4=pyh9f0ad1d_0 + - async_generator=1.10=py_0 + - attrs=20.3.0=pyhd3deb0d_0 + - babel=2.9.0=pyhd3deb0d_0 + - backcall=0.2.0=pyh9f0ad1d_0 + - backports.functools_lru_cache=1.6.1=py_0 + - backports=1.0=py_2 + - black=20.8b1=py_1 + - bleach=3.3.0=pyh44b312d_0 + - click=7.1.2=pyh9f0ad1d_0 + - dataclasses=0.7=pyhb2cacf7_7 + - decorator=4.4.2=py_0 + - defusedxml=0.6.0=py_0 + - entrypoints=0.3=pyhd8ed1ab_1003 + - flake8=3.8.4=py_0 + - idna=2.10=pyh9f0ad1d_0 + - importlib_metadata=3.7.0=hd8ed1ab_0 + - ipython_genutils=0.2.0=py_1 + - isort=5.7.0=pyhd8ed1ab_0 + - jinja2=2.11.3=pyh44b312d_0 + - json5=0.9.5=pyh9f0ad1d_0 + - jsonschema=3.2.0=py_2 + - jupyter-packaging=0.7.12=pyhd8ed1ab_0 + - jupyter_client=6.1.11=pyhd8ed1ab_1 + - jupyterlab=3.0.9=pyhd8ed1ab_0 + - jupyterlab_pygments=0.1.2=pyh9f0ad1d_0 + - jupyterlab_server=2.3.0=pyhd8ed1ab_0 + - mccabe=0.6.1=py_1 + - nbclassic=0.2.6=pyhd8ed1ab_0 + - nbclient=0.5.3=pyhd8ed1ab_0 + - nbformat=5.1.2=pyhd8ed1ab_1 + - nest-asyncio=1.4.3=pyhd8ed1ab_0 + - packaging=20.9=pyh44b312d_0 + - pandocfilters=1.4.2=py_1 + - parso=0.8.1=pyhd8ed1ab_0 + - pathspec=0.8.1=pyhd3deb0d_0 + - pickleshare=0.7.5=py_1003 + - pip=21.0.1=pyhd8ed1ab_0 + - prometheus_client=0.9.0=pyhd3deb0d_0 + - prompt-toolkit=3.0.16=pyha770c72_0 + - pycodestyle=2.6.0=pyh9f0ad1d_0 + - pycparser=2.20=pyh9f0ad1d_2 + - pyflakes=2.2.0=pyh9f0ad1d_0 + - pygments=2.8.0=pyhd8ed1ab_0 + - pyopenssl=20.0.1=pyhd8ed1ab_0 + - pyparsing=2.4.7=pyh9f0ad1d_0 + - python-dateutil=2.8.1=py_0 + - python_abi=3.7=1_cp37m + - pytz=2021.1=pyhd8ed1ab_0 + - requests=2.25.1=pyhd3deb0d_0 + - send2trash=1.5.0=py_0 + - six=1.15.0=pyh9f0ad1d_0 + - testpath=0.4.4=py_0 + - toml=0.10.2=pyhd8ed1ab_0 + - traitlets=5.0.5=py_0 + - typing_extensions=3.7.4.3=py_0 + - urllib3=1.26.3=pyhd8ed1ab_0 + - wcwidth=0.2.5=pyh9f0ad1d_2 + - webencodings=0.5.1=py_1 + - wheel=0.36.2=pyhd3deb0d_0 + - zipp=3.4.0=py_0 unix: - - pexpect=4.8.0=pyh9f0ad1d_2 - - ptyprocess=0.7.0=pyhd3deb0d_0 + - pexpect=4.8.0=pyh9f0ad1d_2 + - ptyprocess=0.7.0=pyhd3deb0d_0 linux-64: - - _libgcc_mutex=0.1=conda_forge - - _openmp_mutex=4.5=1_gnu - - anyio=2.1.0=py37h89c1867_0 - - argon2-cffi=20.1.0=py37h5e8e339_2 - - brotlipy=0.7.0=py37h5e8e339_1001 - - ca-certificates=2020.12.5=ha878542_0 - - certifi=2020.12.5=py37h89c1867_1 - - cffi=1.14.4=py37hc58025e_1 - - chardet=4.0.0=py37h89c1867_1 - - cryptography=3.4.4=py37hf1a17b8_0 - - icu=68.1=h58526e2_0 - - importlib-metadata=3.4.0=py37h89c1867_0 - - ipykernel=5.4.2=py37h888b3d9_0 - - ipython=7.20.0=py37h888b3d9_2 - - jedi=0.18.0=py37h89c1867_2 - - jupyter_core=4.7.1=py37h89c1867_0 - - jupyter_server=1.3.0=py37h89c1867_0 - - ld_impl_linux-64=2.35.1=hea4e1c9_2 - - libffi=3.3=h58526e2_2 - - libgcc-ng=9.3.0=h2828fa1_18 - - libgomp=9.3.0=h2828fa1_18 - - libsodium=1.0.18=h36c2ea0_1 - - libstdcxx-ng=9.3.0=h6de172a_18 - - libuv=1.40.0=h7f98852_0 - - markupsafe=1.1.1=py37h5e8e339_3 - - mistune=0.8.4=py37h5e8e339_1003 - - mypy_extensions=0.4.3=py37h89c1867_3 - - nbconvert=6.0.7=py37h89c1867_3 - - ncurses=6.2=h58526e2_4 - - nodejs=12.19.0=hfa01f41_2 - - notebook=6.2.0=py37h89c1867_0 - - openssl=1.1.1i=h7f98852_0 - - pandoc=2.11.4=h7f98852_0 - - pyrsistent=0.17.3=py37h5e8e339_2 - - pysocks=1.7.1=py37h89c1867_3 - - python=3.7.9=hffdb5ce_0_cpython - - pyzmq=22.0.1=py37h499b945_0 - - readline=8.0=he28a2e2_2 - - regex=2020.11.13=py37h5e8e339_1 - - setuptools=49.6.0=py37h89c1867_3 - - sniffio=1.2.0=py37h89c1867_1 - - sqlite=3.34.0=h74cdb3f_0 - - terminado=0.9.2=py37h89c1867_0 - - tk=8.6.10=h21135ba_1 - - tornado=6.1=py37h5e8e339_1 - - typed-ast=1.4.2=py37h5e8e339_0 - - xz=5.2.5=h516909a_1 - - zeromq=4.3.4=h9c3ff4c_0 - - zlib=1.2.11=h516909a_1010 + - _libgcc_mutex=0.1=conda_forge + - _openmp_mutex=4.5=1_gnu + - anyio=2.2.0=py37h89c1867_0 + - argon2-cffi=20.1.0=py37h5e8e339_2 + - brotlipy=0.7.0=py37h5e8e339_1001 + - ca-certificates=2020.12.5=ha878542_0 + - certifi=2020.12.5=py37h89c1867_1 + - cffi=1.14.5=py37hc58025e_0 + - chardet=4.0.0=py37h89c1867_1 + - cryptography=3.4.4=py37hf1a17b8_0 + - icu=68.1=h58526e2_0 + - importlib-metadata=3.7.0=py37h89c1867_0 + - ipykernel=5.5.0=py37h888b3d9_1 + - ipython=7.21.0=py37h888b3d9_0 + - jedi=0.18.0=py37h89c1867_2 + - jupyter_core=4.7.1=py37h89c1867_0 + - jupyter_server=1.4.1=py37h89c1867_0 + - ld_impl_linux-64=2.35.1=hea4e1c9_2 + - libffi=3.3=h58526e2_2 + - libgcc-ng=9.3.0=h2828fa1_18 + - libgomp=9.3.0=h2828fa1_18 + - libsodium=1.0.18=h36c2ea0_1 + - libstdcxx-ng=9.3.0=h6de172a_18 + - libuv=1.41.0=h7f98852_0 + - markupsafe=1.1.1=py37h5e8e339_3 + - mistune=0.8.4=py37h5e8e339_1003 + - mypy_extensions=0.4.3=py37h89c1867_3 + - nbconvert=6.0.7=py37h89c1867_3 + - ncurses=6.2=h58526e2_4 + - nodejs=12.19.0=h8b53aa1_3 + - notebook=6.2.0=py37h89c1867_0 + - openssl=1.1.1j=h7f98852_0 + - pandoc=2.11.4=h7f98852_0 + - pyrsistent=0.17.3=py37h5e8e339_2 + - pysocks=1.7.1=py37h89c1867_3 + - python=3.7.10=hffdb5ce_100_cpython + - pyzmq=22.0.3=py37h336d617_1 + - readline=8.0=he28a2e2_2 + - regex=2020.11.13=py37h5e8e339_1 + - setuptools=49.6.0=py37h89c1867_3 + - sniffio=1.2.0=py37h89c1867_1 + - sqlite=3.34.0=h74cdb3f_0 + - terminado=0.9.2=py37h89c1867_0 + - tk=8.6.10=h21135ba_1 + - tornado=6.1=py37h5e8e339_1 + - typed-ast=1.4.2=py37h5e8e339_0 + - xz=5.2.5=h516909a_1 + - zeromq=4.3.4=h9c3ff4c_0 + - zlib=1.2.11=h516909a_1010 osx-64: - - anyio=2.1.0=py37hf985489_0 - - appnope=0.1.2=py37hf985489_1 - - argon2-cffi=20.1.0=py37hf967b71_2 - - brotlipy=0.7.0=py37hf967b71_1001 - - ca-certificates=2020.12.5=h033912b_0 - - certifi=2020.12.5=py37hf985489_1 - - cffi=1.14.4=py37hc5b2277_1 - - chardet=4.0.0=py37hf985489_1 - - cryptography=3.4.4=py37ha1e1f9f_0 - - icu=68.1=h74dc148_0 - - importlib-metadata=3.4.0=py37hf985489_0 - - ipykernel=5.4.2=py37he01cfaa_0 - - ipython=7.20.0=py37he01cfaa_2 - - jedi=0.18.0=py37hf985489_2 - - jupyter_core=4.7.1=py37hf985489_0 - - jupyter_server=1.3.0=py37hf985489_0 - - libcxx=11.0.1=habf9029_0 - - libffi=3.3=h046ec9c_2 - - libsodium=1.0.18=hbcb3906_1 - - libuv=1.40.0=h35c211d_0 - - markupsafe=1.1.1=py37hf967b71_3 - - mistune=0.8.4=py37hf967b71_1003 - - mypy_extensions=0.4.3=py37hf985489_3 - - nbconvert=6.0.7=py37hf985489_3 - - ncurses=6.2=h2e338ed_4 - - nodejs=12.19.0=h49b9f75_2 - - notebook=6.2.0=py37hf985489_0 - - openssl=1.1.1i=h35c211d_0 - - pandoc=2.11.4=h35c211d_0 - - pyrsistent=0.17.3=py37hf967b71_2 - - pysocks=1.7.1=py37hf985489_3 - - python=3.7.9=h6c3b2c9_0_cpython - - pyzmq=22.0.1=py37hf4687c3_0 - - readline=8.0=h0678c8f_2 - - regex=2020.11.13=py37hf967b71_1 - - setuptools=49.6.0=py37hf985489_3 - - sniffio=1.2.0=py37hf985489_1 - - sqlite=3.34.0=h17101e1_0 - - terminado=0.9.2=py37hf985489_0 - - tk=8.6.10=h0419947_1 - - tornado=6.1=py37hf967b71_1 - - typed-ast=1.4.2=py37hf967b71_0 - - xz=5.2.5=haf1e3a3_1 - - zeromq=4.3.4=h1c7c35f_0 - - zlib=1.2.11=h7795811_1010 + - anyio=2.2.0=py37hf985489_0 + - appnope=0.1.2=py37hf985489_1 + - argon2-cffi=20.1.0=py37hf967b71_2 + - brotlipy=0.7.0=py37hf967b71_1001 + - ca-certificates=2020.12.5=h033912b_0 + - certifi=2020.12.5=py37hf985489_1 + - cffi=1.14.5=py37h4d371b4_0 + - chardet=4.0.0=py37hf985489_1 + - cryptography=3.4.4=py37ha1e1f9f_0 + - icu=68.1=h74dc148_0 + - importlib-metadata=3.7.0=py37hf985489_0 + - ipykernel=5.5.0=py37he01cfaa_1 + - ipython=7.21.0=py37he01cfaa_0 + - jedi=0.18.0=py37hf985489_2 + - jupyter_core=4.7.1=py37hf985489_0 + - jupyter_server=1.4.1=py37hf985489_0 + - libcxx=11.0.1=habf9029_0 + - libffi=3.3=h046ec9c_2 + - libsodium=1.0.18=hbcb3906_1 + - libuv=1.41.0=hbcf498f_0 + - markupsafe=1.1.1=py37hf967b71_3 + - mistune=0.8.4=py37hf967b71_1003 + - mypy_extensions=0.4.3=py37hf985489_3 + - nbconvert=6.0.7=py37hf985489_3 + - ncurses=6.2=h2e338ed_4 + - nodejs=12.19.0=hf294227_3 + - notebook=6.2.0=py37hf985489_0 + - openssl=1.1.1j=hbcf498f_0 + - pandoc=2.11.4=h35c211d_0 + - pyrsistent=0.17.3=py37hf967b71_2 + - pysocks=1.7.1=py37hf985489_3 + - python=3.7.10=h7728216_100_cpython + - pyzmq=22.0.3=py37h8f778e5_1 + - readline=8.0=h0678c8f_2 + - regex=2020.11.13=py37hf967b71_1 + - setuptools=49.6.0=py37hf985489_3 + - sniffio=1.2.0=py37hf985489_1 + - sqlite=3.34.0=h17101e1_0 + - terminado=0.9.2=py37hf985489_0 + - tk=8.6.10=h0419947_1 + - tornado=6.1=py37hf967b71_1 + - typed-ast=1.4.2=py37hf967b71_0 + - xz=5.2.5=haf1e3a3_1 + - zeromq=4.3.4=h1c7c35f_0 + - zlib=1.2.11=h7795811_1010 win-64: - - anyio=2.1.0=py37h03978a9_0 - - argon2-cffi=20.1.0=py37hcc03f2d_2 - - brotlipy=0.7.0=py37hcc03f2d_1001 - - ca-certificates=2020.12.5=h5b45459_0 - - certifi=2020.12.5=py37h03978a9_1 - - cffi=1.14.4=py37hd8e9650_1 - - chardet=4.0.0=py37h03978a9_1 - - colorama=0.4.4=pyh9f0ad1d_0 - - cryptography=3.4.4=py37h65266a2_0 - - importlib-metadata=3.4.0=py37h03978a9_0 - - ipykernel=5.4.2=py37h7b7c402_0 - - ipython=7.20.0=py37heaed05f_2 - - jedi=0.18.0=py37h03978a9_2 - - jupyter_core=4.7.1=py37h03978a9_0 - - jupyter_server=1.3.0=py37h03978a9_0 - - libsodium=1.0.18=h8d14728_1 - - m2w64-gcc-libgfortran=5.3.0=6 - - m2w64-gcc-libs-core=5.3.0=7 - - m2w64-gcc-libs=5.3.0=7 - - m2w64-gmp=6.1.0=2 - - m2w64-libwinpthread-git=5.0.0.4634.697f757=2 - - markupsafe=1.1.1=py37hcc03f2d_3 - - mistune=0.8.4=py37hcc03f2d_1003 - - msys2-conda-epoch=20160418=1 - - mypy_extensions=0.4.3=py37h03978a9_3 - - nbconvert=6.0.7=py37h03978a9_3 - - nodejs=12.19.0=h57928b3_2 - - notebook=6.2.0=py37h03978a9_0 - - openssl=1.1.1i=h8ffe710_0 - - pandoc=2.11.4=h8ffe710_0 - - pyrsistent=0.17.3=py37hcc03f2d_2 - - pysocks=1.7.1=py37h03978a9_3 - - python=3.7.9=h7840368_0_cpython - - pywin32=300=py37hcc03f2d_0 - - pywinpty=0.5.7=py37hc8dfbb8_1 - - pyzmq=22.0.1=py37h0d95fc2_0 - - regex=2020.11.13=py37hcc03f2d_1 - - setuptools=49.6.0=py37h03978a9_3 - - sniffio=1.2.0=py37h03978a9_1 - - sqlite=3.34.0=h8ffe710_0 - - terminado=0.9.2=py37h03978a9_0 - - tornado=6.1=py37hcc03f2d_1 - - typed-ast=1.4.2=py37hcc03f2d_0 - - vc=14.2=hb210afc_3 - - vs2015_runtime=14.28.29325=h5e1d092_3 - - win_inet_pton=1.1.0=py37h03978a9_2 - - wincertstore=0.2=py37h03978a9_1006 - - winpty=0.4.3=4 - - zeromq=4.3.3=h0e60522_3 + - anyio=2.2.0=py37h03978a9_0 + - argon2-cffi=20.1.0=py37hcc03f2d_2 + - brotlipy=0.7.0=py37hcc03f2d_1001 + - ca-certificates=2020.12.5=h5b45459_0 + - certifi=2020.12.5=py37h03978a9_1 + - cffi=1.14.5=py37hd8e9650_0 + - chardet=4.0.0=py37h03978a9_1 + - colorama=0.4.4=pyh9f0ad1d_0 + - cryptography=3.4.4=py37h65266a2_0 + - importlib-metadata=3.7.0=py37h03978a9_0 + - ipykernel=5.5.0=py37heaed05f_1 + - ipython=7.21.0=py37heaed05f_0 + - jedi=0.18.0=py37h03978a9_2 + - jupyter_core=4.7.1=py37h03978a9_0 + - jupyter_server=1.4.1=py37h03978a9_0 + - libsodium=1.0.18=h8d14728_1 + - m2w64-gcc-libgfortran=5.3.0=6 + - m2w64-gcc-libs-core=5.3.0=7 + - m2w64-gcc-libs=5.3.0=7 + - m2w64-gmp=6.1.0=2 + - m2w64-libwinpthread-git=5.0.0.4634.697f757=2 + - markupsafe=1.1.1=py37hcc03f2d_3 + - mistune=0.8.4=py37hcc03f2d_1003 + - msys2-conda-epoch=20160418=1 + - mypy_extensions=0.4.3=py37h03978a9_3 + - nbconvert=6.0.7=py37h03978a9_3 + - nodejs=12.19.0=h57928b3_3 + - notebook=6.2.0=py37h03978a9_0 + - openssl=1.1.1j=h8ffe710_0 + - pandoc=2.11.4=h8ffe710_0 + - pyrsistent=0.17.3=py37hcc03f2d_2 + - pysocks=1.7.1=py37h03978a9_3 + - python=3.7.10=h7840368_100_cpython + - pywin32=300=py37hcc03f2d_0 + - pywinpty=0.5.7=py37hc8dfbb8_1 + - pyzmq=22.0.3=py37hcce574b_1 + - regex=2020.11.13=py37hcc03f2d_1 + - setuptools=49.6.0=py37h03978a9_3 + - sniffio=1.2.0=py37h03978a9_1 + - sqlite=3.34.0=h8ffe710_0 + - terminado=0.9.2=py37h03978a9_0 + - tornado=6.1=py37hcc03f2d_1 + - typed-ast=1.4.2=py37hcc03f2d_0 + - vc=14.2=hb210afc_3 + - vs2015_runtime=14.28.29325=h5e1d092_3 + - win_inet_pton=1.1.0=py37h03978a9_2 + - wincertstore=0.2=py37h03978a9_1006 + - winpty=0.4.3=4 + - zeromq=4.3.4=h0e60522_0 build: locked: true env_spec_hash: bcd31b2b6ceae32cda53bdd935c86f9c8d4b5a4b platforms: - - linux-64 - - osx-64 - - win-64 + - linux-64 + - osx-64 + - win-64 packages: all: - - bleach=3.2.2=pyh44b312d_0 - - colorama=0.4.4=pyh9f0ad1d_0 - - conda=4.8.3=py37hc8dfbb8_1 - - idna=2.10=pyh9f0ad1d_0 - - importlib_metadata=3.4.0=hd8ed1ab_0 - - packaging=20.8=pyhd3deb0d_0 - - pip=20.3.3=pyhd8ed1ab_0 - - pkginfo=1.7.0=pyhd8ed1ab_0 - - pycparser=2.20=pyh9f0ad1d_2 - - pygments=2.7.4=pyhd8ed1ab_0 - - pyopenssl=20.0.1=pyhd8ed1ab_0 - - pyparsing=2.4.7=pyh9f0ad1d_0 - - python_abi=3.7=1_cp37m - - readme_renderer=27.0=pyh9f0ad1d_0 - - requests-toolbelt=0.9.1=py_0 - - requests=2.25.1=pyhd3deb0d_0 - - rfc3986=1.4.0=pyh9f0ad1d_0 - - six=1.15.0=pyh9f0ad1d_0 - - tqdm=4.56.0=pyhd8ed1ab_0 - - typing_extensions=3.7.4.3=py_0 - - urllib3=1.26.2=pyhd8ed1ab_0 - - webencodings=0.5.1=py_1 - - wheel=0.36.2=pyhd3deb0d_0 - - zipp=3.4.0=py_0 + - bleach=3.2.2=pyh44b312d_0 + - colorama=0.4.4=pyh9f0ad1d_0 + - conda=4.8.3=py37hc8dfbb8_1 + - idna=2.10=pyh9f0ad1d_0 + - importlib_metadata=3.4.0=hd8ed1ab_0 + - packaging=20.8=pyhd3deb0d_0 + - pip=20.3.3=pyhd8ed1ab_0 + - pkginfo=1.7.0=pyhd8ed1ab_0 + - pycparser=2.20=pyh9f0ad1d_2 + - pygments=2.7.4=pyhd8ed1ab_0 + - pyopenssl=20.0.1=pyhd8ed1ab_0 + - pyparsing=2.4.7=pyh9f0ad1d_0 + - python_abi=3.7=1_cp37m + - readme_renderer=27.0=pyh9f0ad1d_0 + - requests-toolbelt=0.9.1=py_0 + - requests=2.25.1=pyhd3deb0d_0 + - rfc3986=1.4.0=pyh9f0ad1d_0 + - six=1.15.0=pyh9f0ad1d_0 + - tqdm=4.56.0=pyhd8ed1ab_0 + - typing_extensions=3.7.4.3=py_0 + - urllib3=1.26.2=pyhd8ed1ab_0 + - webencodings=0.5.1=py_1 + - wheel=0.36.2=pyhd3deb0d_0 + - zipp=3.4.0=py_0 linux-64: - - _libgcc_mutex=0.1=conda_forge - - _openmp_mutex=4.5=1_gnu - - brotlipy=0.7.0=py37hb5d75c8_1001 - - ca-certificates=2020.12.5=ha878542_0 - - certifi=2020.12.5=py37h89c1867_1 - - cffi=1.14.4=py37hc58025e_1 - - chardet=4.0.0=py37h89c1867_1 - - cmarkgfm=0.4.2=py37h8f50634_3 - - conda-package-handling=1.7.2=py37hb5d75c8_0 - - cryptography=3.3.1=py37h7f0c10b_1 - - dbus=1.13.6=hfdff14a_1 - - docutils=0.16=py37h89c1867_3 - - expat=2.2.9=he1b5a44_2 - - gettext=0.19.8.1=h0b5b191_1005 - - glib=2.66.4=hcd2ae1e_1 - - importlib-metadata=3.4.0=py37h89c1867_0 - - jeepney=0.6.0=pyhd8ed1ab_0 - - keyring=21.8.0=py37h89c1867_0 - - ld_impl_linux-64=2.35.1=hed1e6ac_1 - - libffi=3.3=h58526e2_2 - - libgcc-ng=9.3.0=h2828fa1_18 - - libglib=2.66.4=h164308a_1 - - libgomp=9.3.0=h2828fa1_18 - - libiconv=1.16=h516909a_0 - - libstdcxx-ng=9.3.0=h6de172a_18 - - ncurses=6.2=h58526e2_4 - - openssl=1.1.1i=h7f98852_0 - - pcre=8.44=he1b5a44_0 - - pycosat=0.6.3=py37h5e8e339_1006 - - pysocks=1.7.1=py37h89c1867_3 - - python=3.7.9=hffdb5ce_0_cpython - - readline=8.0=he28a2e2_2 - - ruamel_yaml=0.15.80=py37hb5d75c8_1003 - - secretstorage=3.3.0=py37h89c1867_0 - - setuptools=49.6.0=py37h89c1867_3 - - sqlite=3.34.0=h74cdb3f_0 - - tk=8.6.10=hed695b0_1 - - twine=3.3.0=py37h89c1867_0 - - xz=5.2.5=h516909a_1 - - yaml=0.2.5=h516909a_0 - - zlib=1.2.11=h516909a_1010 + - _libgcc_mutex=0.1=conda_forge + - _openmp_mutex=4.5=1_gnu + - brotlipy=0.7.0=py37hb5d75c8_1001 + - ca-certificates=2020.12.5=ha878542_0 + - certifi=2020.12.5=py37h89c1867_1 + - cffi=1.14.4=py37hc58025e_1 + - chardet=4.0.0=py37h89c1867_1 + - cmarkgfm=0.4.2=py37h8f50634_3 + - conda-package-handling=1.7.2=py37hb5d75c8_0 + - cryptography=3.3.1=py37h7f0c10b_1 + - dbus=1.13.6=hfdff14a_1 + - docutils=0.16=py37h89c1867_3 + - expat=2.2.9=he1b5a44_2 + - gettext=0.19.8.1=h0b5b191_1005 + - glib=2.66.4=hcd2ae1e_1 + - importlib-metadata=3.4.0=py37h89c1867_0 + - jeepney=0.6.0=pyhd8ed1ab_0 + - keyring=21.8.0=py37h89c1867_0 + - ld_impl_linux-64=2.35.1=hed1e6ac_1 + - libffi=3.3=h58526e2_2 + - libgcc-ng=9.3.0=h2828fa1_18 + - libglib=2.66.4=h164308a_1 + - libgomp=9.3.0=h2828fa1_18 + - libiconv=1.16=h516909a_0 + - libstdcxx-ng=9.3.0=h6de172a_18 + - ncurses=6.2=h58526e2_4 + - openssl=1.1.1i=h7f98852_0 + - pcre=8.44=he1b5a44_0 + - pycosat=0.6.3=py37h5e8e339_1006 + - pysocks=1.7.1=py37h89c1867_3 + - python=3.7.9=hffdb5ce_0_cpython + - readline=8.0=he28a2e2_2 + - ruamel_yaml=0.15.80=py37hb5d75c8_1003 + - secretstorage=3.3.0=py37h89c1867_0 + - setuptools=49.6.0=py37h89c1867_3 + - sqlite=3.34.0=h74cdb3f_0 + - tk=8.6.10=hed695b0_1 + - twine=3.3.0=py37h89c1867_0 + - xz=5.2.5=h516909a_1 + - yaml=0.2.5=h516909a_0 + - zlib=1.2.11=h516909a_1010 osx-64: - - brotlipy=0.7.0=py37hf967b71_1001 - - ca-certificates=2020.12.5=h033912b_0 - - certifi=2020.12.5=py37hf985489_1 - - cffi=1.14.4=py37hc5b2277_1 - - chardet=4.0.0=py37hf985489_1 - - cmarkgfm=0.4.2=py37h50a09ed_3 - - conda-package-handling=1.7.2=py37h395d20d_0 - - cryptography=3.3.1=py37haf76d9e_1 - - docutils=0.16=py37hf985489_3 - - importlib-metadata=3.4.0=py37hf985489_0 - - keyring=21.8.0=py37hf985489_0 - - libcxx=11.0.1=habf9029_0 - - libffi=3.3=h046ec9c_2 - - ncurses=6.2=h2e338ed_4 - - openssl=1.1.1i=h35c211d_0 - - pycosat=0.6.3=py37hf967b71_1006 - - pysocks=1.7.1=py37hf985489_3 - - python=3.7.9=h6c3b2c9_0_cpython - - readline=8.0=h0678c8f_2 - - ruamel_yaml=0.15.80=py37hf967b71_1003 - - setuptools=49.6.0=py37hf985489_3 - - sqlite=3.34.0=h17101e1_0 - - tk=8.6.10=h0419947_1 - - twine=3.3.0=py37hf985489_0 - - xz=5.2.5=haf1e3a3_1 - - yaml=0.2.5=haf1e3a3_0 - - zlib=1.2.11=h7795811_1010 + - brotlipy=0.7.0=py37hf967b71_1001 + - ca-certificates=2020.12.5=h033912b_0 + - certifi=2020.12.5=py37hf985489_1 + - cffi=1.14.4=py37hc5b2277_1 + - chardet=4.0.0=py37hf985489_1 + - cmarkgfm=0.4.2=py37h50a09ed_3 + - conda-package-handling=1.7.2=py37h395d20d_0 + - cryptography=3.3.1=py37haf76d9e_1 + - docutils=0.16=py37hf985489_3 + - importlib-metadata=3.4.0=py37hf985489_0 + - keyring=21.8.0=py37hf985489_0 + - libcxx=11.0.1=habf9029_0 + - libffi=3.3=h046ec9c_2 + - ncurses=6.2=h2e338ed_4 + - openssl=1.1.1i=h35c211d_0 + - pycosat=0.6.3=py37hf967b71_1006 + - pysocks=1.7.1=py37hf985489_3 + - python=3.7.9=h6c3b2c9_0_cpython + - readline=8.0=h0678c8f_2 + - ruamel_yaml=0.15.80=py37hf967b71_1003 + - setuptools=49.6.0=py37hf985489_3 + - sqlite=3.34.0=h17101e1_0 + - tk=8.6.10=h0419947_1 + - twine=3.3.0=py37hf985489_0 + - xz=5.2.5=haf1e3a3_1 + - yaml=0.2.5=haf1e3a3_0 + - zlib=1.2.11=h7795811_1010 win-64: - - brotlipy=0.7.0=py37hcc03f2d_1001 - - ca-certificates=2020.12.5=h5b45459_0 - - certifi=2020.12.5=py37h03978a9_1 - - cffi=1.14.4=py37hd8e9650_1 - - chardet=4.0.0=py37h03978a9_1 - - cmarkgfm=0.4.2=py37hcc03f2d_3 - - conda-package-handling=1.7.2=py37h6dbccfb_0 - - cryptography=3.3.1=py37hd8e9650_1 - - docutils=0.16=py37h03978a9_3 - - importlib-metadata=3.4.0=py37h03978a9_0 - - keyring=21.8.0=py37h03978a9_0 - - menuinst=1.4.16=py37hc8dfbb8_1 - - openssl=1.1.1i=h8ffe710_0 - - pycosat=0.6.3=py37hcc03f2d_1006 - - pysocks=1.7.1=py37h03978a9_3 - - python=3.7.9=h7840368_0_cpython - - pywin32-ctypes=0.2.0=py37h03978a9_1003 - - pywin32=300=py37hcc03f2d_0 - - ruamel_yaml=0.15.80=py37hcc03f2d_1003 - - setuptools=49.6.0=py37h03978a9_3 - - sqlite=3.34.0=h8ffe710_0 - - twine=3.3.0=py37h03978a9_0 - - vc=14.2=hb210afc_2 - - vs2015_runtime=14.28.29325=h5e1d092_0 - - win_inet_pton=1.1.0=py37h03978a9_2 - - wincertstore=0.2=py37h03978a9_1006 - - yaml=0.2.5=he774522_0 + - brotlipy=0.7.0=py37hcc03f2d_1001 + - ca-certificates=2020.12.5=h5b45459_0 + - certifi=2020.12.5=py37h03978a9_1 + - cffi=1.14.4=py37hd8e9650_1 + - chardet=4.0.0=py37h03978a9_1 + - cmarkgfm=0.4.2=py37hcc03f2d_3 + - conda-package-handling=1.7.2=py37h6dbccfb_0 + - cryptography=3.3.1=py37hd8e9650_1 + - docutils=0.16=py37h03978a9_3 + - importlib-metadata=3.4.0=py37h03978a9_0 + - keyring=21.8.0=py37h03978a9_0 + - menuinst=1.4.16=py37hc8dfbb8_1 + - openssl=1.1.1i=h8ffe710_0 + - pycosat=0.6.3=py37hcc03f2d_1006 + - pysocks=1.7.1=py37h03978a9_3 + - python=3.7.9=h7840368_0_cpython + - pywin32-ctypes=0.2.0=py37h03978a9_1003 + - pywin32=300=py37hcc03f2d_0 + - ruamel_yaml=0.15.80=py37hcc03f2d_1003 + - setuptools=49.6.0=py37h03978a9_3 + - sqlite=3.34.0=h8ffe710_0 + - twine=3.3.0=py37h03978a9_0 + - vc=14.2=hb210afc_2 + - vs2015_runtime=14.28.29325=h5e1d092_0 + - win_inet_pton=1.1.0=py37h03978a9_2 + - wincertstore=0.2=py37h03978a9_1006 + - yaml=0.2.5=he774522_0 diff --git a/examples/Cytoscape_Example.ipynb b/examples/Cytoscape_Example.ipynb index 850bae4..4effddf 100644 --- a/examples/Cytoscape_Example.ipynb +++ b/examples/Cytoscape_Example.ipynb @@ -66,7 +66,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.7.8" + "version": "3.7.9" } }, "nbformat": 4, diff --git a/examples/Cytoscape_Viewer.ipynb b/examples/Cytoscape_Viewer.ipynb new file mode 100644 index 0000000..b8d9c5d --- /dev/null +++ b/examples/Cytoscape_Viewer.ipynb @@ -0,0 +1,145 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "equivalent-publisher", + "metadata": {}, + "source": [ + "# CytoscapeViewer Example\n", + "\n", + "In this notebook we show how to simply draw a cytoscape graph." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "grateful-dependence", + "metadata": {}, + "outputs": [], + "source": [ + "import rdflib\n", + "\n", + "from ipyradiant import FileManager, PathLoader\n", + "from ipyradiant.visualization import CytoscapeViewer" + ] + }, + { + "cell_type": "markdown", + "id": "protected-tomorrow", + "metadata": {}, + "source": [ + "Let's start by getting our graph loaded in." + ] + }, + { + "cell_type": "markdown", + "id": "about-smart", + "metadata": {}, + "source": [ + "Explain why we need a subgraph (too many nodes causes issues with cytoscape)." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "grand-lingerie", + "metadata": {}, + "outputs": [], + "source": [ + "lw = FileManager(loader=PathLoader(path=\"data\"))\n", + "# here we hard set what we want the file to be, but ideally a user can choose a file to work with.\n", + "lw.loader.file_picker.value = lw.loader.file_picker.options[\"starwars.ttl\"]\n", + "rdf_graph = lw.graph\n", + "qres = lw.graph.query(\n", + " \"\"\"\n", + " PREFIX planet: \n", + " PREFIX human: \n", + " PREFIX film: \n", + " \n", + " CONSTRUCT {\n", + " ?s ?p ?o .\n", + " }\n", + " WHERE {\n", + " ?s ?p ?o .\n", + " \n", + " VALUES (?s) {\n", + " (human:1)\n", + " (human:4)\n", + " (human:5)\n", + " (film:1)\n", + " (film:4)\n", + " (planet:1)\n", + " (planet:2)\n", + " }\n", + " }\n", + " \"\"\"\n", + ")\n", + "\n", + "simple_graph = rdflib.graph.Graph().parse(data=qres.serialize(format=\"xml\"))" + ] + }, + { + "cell_type": "markdown", + "id": "excessive-mixer", + "metadata": {}, + "source": [ + "## Now we can initiate the `CytoscapeViewer` object, which allows us to get a nice representation of an RDF or networkx graph" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "accompanied-plymouth", + "metadata": {}, + "outputs": [], + "source": [ + "# Try changing edge_labels=False for a cleaner (if less informative) visualization\n", + "cv = CytoscapeViewer(animate=True, node_labels=True, edge_labels=True)\n", + "cv.graph = simple_graph" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "infrared-renaissance", + "metadata": {}, + "outputs": [], + "source": [ + "cv" + ] + }, + { + "cell_type": "markdown", + "id": "hearing-coverage", + "metadata": {}, + "source": [ + "> The literal data from the RDF graph is collapsed onto the node based on the `RDF2NX`\n", + "> converter. For more information see [this example](RDF_to_NX.ipynb).\n", + "\n", + "> For a method to link the cytoscape graph to a JSON viewer in order to inspect node\n", + "> data, see [this example](JSON_Interactive_Example.ipynb)." + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.7.9" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/examples/Datashader_Example.ipynb b/examples/Datashader_Example.ipynb index d09a66e..4878de6 100644 --- a/examples/Datashader_Example.ipynb +++ b/examples/Datashader_Example.ipynb @@ -69,7 +69,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.7.8" + "version": "3.7.9" } }, "nbformat": 4, diff --git a/examples/Improved_Vis.ipynb b/examples/Improved_Vis.ipynb index e245068..f8e6c97 100644 --- a/examples/Improved_Vis.ipynb +++ b/examples/Improved_Vis.ipynb @@ -142,7 +142,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.7.8" + "version": "3.7.9" } }, "nbformat": 4, diff --git a/examples/Test_CytoscapeViewer.ipynb b/examples/Test_CytoscapeViewer.ipynb new file mode 100644 index 0000000..548c6dc --- /dev/null +++ b/examples/Test_CytoscapeViewer.ipynb @@ -0,0 +1,129 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# A Basic Test for the Cytoscape Viewer" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import json\n", + "import os\n", + "import time\n", + "from pathlib import Path\n", + "\n", + "import importnb\n", + "from rdflib import Graph as RDFGraph\n", + "from rdflib import Literal\n", + "from rdflib.namespace import RDF, RDFS, Namespace\n", + "from requests_cache import CachedSession\n", + "\n", + "from ipyradiant.visualization import CytoscapeViewer" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "This notebook is used as part of the `ipyradiant` test suite, where `IPYRADIANT_TESTING`\n", + "will be set, which will trigger the automted section below." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# test graph\n", + "EXPECTED_LEN_RDF = 0\n", + "EXPECTED_LEN_NX_NODES = 0\n", + "EXPECTED_LEN_NX_EDGES = 0\n", + "\n", + "EX = Namespace(\"https://www.example.com/\")\n", + "graph = RDFGraph()\n", + "graph.namespace_manager.bind(\"ex\", EX)\n", + "\n", + "s1 = EX.subject_1\n", + "graph.add((s1, RDF.type, EX.Thing))\n", + "graph.add((s1, RDFS.label, Literal(\"Thing 1\")))\n", + "EXPECTED_LEN_RDF += 2\n", + "EXPECTED_LEN_NX_NODES += 1\n", + "\n", + "s2 = EX.subject_2\n", + "graph.add((s2, RDF.type, EX.Thing))\n", + "graph.add((s2, RDFS.label, Literal(\"Thing 2\")))\n", + "EXPECTED_LEN_RDF += 2\n", + "EXPECTED_LEN_NX_NODES += 1\n", + "\n", + "graph.add((s1, EX.relatedTo, s2))\n", + "EXPECTED_LEN_RDF += 1\n", + "EXPECTED_LEN_NX_EDGES += 1" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "def timestamp(msg):\n", + " print(f\"[{time.time()}]\", msg)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "try:\n", + " timestamp(\"initiating...\")\n", + " cv = CytoscapeViewer()\n", + " timestamp(\"initiated...\")\n", + " assert cv.graph is None\n", + " timestamp(\"setting rdf graph...\")\n", + " assert len(graph) == EXPECTED_LEN_RDF\n", + " cv.graph = graph\n", + " assert len(cv.graph), \"Graph failed to populate.\"\n", + " timestamp(\"graph set successfully\")\n", + " assert len(cv.cytoscape_widget.graph.nodes) > 0, \"CytoscapeWidget failed to update.\"\n", + " assert len(cv.cytoscape_widget.graph.nodes) == EXPECTED_LEN_NX_NODES\n", + " assert len(cv.cytoscape_widget.graph.edges) == EXPECTED_LEN_NX_EDGES\n", + " timestamp(\"graph converted successfully...\")\n", + " timestamp(\"OK!\")\n", + "except Exception as err:\n", + " timestamp(\"ERROR\")\n", + " timestamp(err)\n", + " raise Exception(f\"{p} failed\") from err" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.7.9" + } + }, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/examples/Test_GraphExplorer.ipynb b/examples/Test_GraphExplorer.ipynb index 690ebb6..63e9566 100644 --- a/examples/Test_GraphExplorer.ipynb +++ b/examples/Test_GraphExplorer.ipynb @@ -195,7 +195,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.7.8" + "version": "3.7.9" } }, "nbformat": 4, diff --git a/index.ipynb b/index.ipynb index f0df007..e871dba 100644 --- a/index.ipynb +++ b/index.ipynb @@ -21,6 +21,7 @@ "\n", "### 3. Graph Visualization Widgets\n", "- Cytoscape for Small Interactive Graphs\n", + "- Cytoscape with RDF to LPG converter for Visualizing Small Graphs\n", "- Datashader for Large Graphs\n", "- Graph Explorer (`ipycytoscape`).\n", "\n", @@ -32,7 +33,7 @@ "- Remote Query Examples\n", "- Federated Query Examples\n", "\n", - "### 6. RDF to Networkx\n", + "### 6. RDF to `networkx`\n", "- Collapsing RDF to a Latent Property Graph (networkx)\n", "- URI Converters for RDF2NX process\n", "\n", @@ -70,7 +71,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.7.8" + "version": "3.7.9" } }, "nbformat": 4, diff --git a/src/ipyradiant/rdf2nx/converter.py b/src/ipyradiant/rdf2nx/converter.py index fe0709e..a3457d1 100644 --- a/src/ipyradiant/rdf2nx/converter.py +++ b/src/ipyradiant/rdf2nx/converter.py @@ -2,6 +2,7 @@ # Distributed under the terms of the Modified BSD License. import logging from typing import Callable, Dict, List, Union +from warnings import warn from networkx import MultiDiGraph from pandas import DataFrame @@ -221,9 +222,9 @@ def convert( if namespaces is None: namespaces = dict(rdf_graph.namespaces()) cls.initNs = dict(namespaces) - assert ( - "base" in cls.initNs - ), "For conversion, namespaces must include a base namespace." + if "base" not in cls.initNs: + warn("No base namespace specified. Defaulting to example namespace") + cls.initNs["base"] = URIRef("https://www.example.com/RDF2NX/") nx_graph = MultiDiGraph() diff --git a/src/ipyradiant/rdf2nx/literal_converter.py b/src/ipyradiant/rdf2nx/literal_converter.py index 8e09a2d..d8d02bf 100644 --- a/src/ipyradiant/rdf2nx/literal_converter.py +++ b/src/ipyradiant/rdf2nx/literal_converter.py @@ -52,4 +52,6 @@ def cast_literal(value: Literal, strict: bool = False) -> Any: ) return str(value) else: + if value.value is None: + return None return XSD2PY[value_datatype](value) diff --git a/src/ipyradiant/rdf2nx/nodes.py b/src/ipyradiant/rdf2nx/nodes.py index 6d74735..3553ec7 100644 --- a/src/ipyradiant/rdf2nx/nodes.py +++ b/src/ipyradiant/rdf2nx/nodes.py @@ -70,3 +70,31 @@ class NodeProperties(SPARQLQueryFramer): ?iri ?predicate ?value. } """ + + +class NodeAnnotationProperties(SPARQLQueryFramer): + """An example of how to return properties for a LPG node. + - ?iri: bound to a specific node IRI, to get the properties for that node. + - ?predicate: IRI for the property name (e.g. ex:hasThing) and is converted into a + shorter ID by means of a configured IRI->ID converter. + - ?value: a literal that may be converted using its lexical value. + + TODO experimental, taken from https://www.w3.org/TR/rdf-schema/#ch_properties + """ + + sparql = """ + SELECT DISTINCT ?iri ?predicate ?value + { + ?iri ?predicate ?value. + + VALUES (?predicate) { + (rdfs:range) + (rdfs:domain) + (rdf:type) + (rdfs:subClassOf) + (rdfs:subPropertyOf) # TODO remove this one? + (rdfs:label) + (rdfs:comment) + } + } + """ diff --git a/src/ipyradiant/visualization/__init__.py b/src/ipyradiant/visualization/__init__.py index 2ce7367..c357745 100644 --- a/src/ipyradiant/visualization/__init__.py +++ b/src/ipyradiant/visualization/__init__.py @@ -11,9 +11,11 @@ "NXBase", "InteractiveViewer", "GetOutgoingPredicateObjects", + "CytoscapeViewer", ] from .base import NXBase, VisualizerBase from .cytoscape import CytoscapeVisualizer from .datashader_vis import DatashaderVisualizer from .explore import GetOutgoingPredicateObjects, InteractiveViewer +from .improved_cytoscape import CytoscapeViewer from .tools import LayoutSelector diff --git a/src/ipyradiant/visualization/cytoscape/__init__.py b/src/ipyradiant/visualization/cytoscape/__init__.py new file mode 100644 index 0000000..62db742 --- /dev/null +++ b/src/ipyradiant/visualization/cytoscape/__init__.py @@ -0,0 +1,6 @@ +# Copyright (c) 2021 ipyradiant contributors. +# Distributed under the terms of the Modified BSD License. + +__all__ = ["CytoscapeVisualizer"] + +from .app import CytoscapeVisualizer diff --git a/src/ipyradiant/visualization/cytoscape.py b/src/ipyradiant/visualization/cytoscape/app.py similarity index 99% rename from src/ipyradiant/visualization/cytoscape.py rename to src/ipyradiant/visualization/cytoscape/app.py index 7bbb1c3..704fd7a 100644 --- a/src/ipyradiant/visualization/cytoscape.py +++ b/src/ipyradiant/visualization/cytoscape/app.py @@ -9,7 +9,7 @@ from rdflib import Graph, Literal, URIRef from rdflib.namespace import RDF -from .base import VisualizerBase +from ..base import VisualizerBase class CytoscapeVisualizer(VisualizerBase): diff --git a/src/ipyradiant/visualization/cytoscape/style.py b/src/ipyradiant/visualization/cytoscape/style.py new file mode 100644 index 0000000..6e6e232 --- /dev/null +++ b/src/ipyradiant/visualization/cytoscape/style.py @@ -0,0 +1,63 @@ +# Copyright (c) 2021 ipyradiant contributors. +# Distributed under the terms of the Modified BSD License. + +NODE = { + "selector": "node", + "style": { + "color": "black", + "background-color": "CadetBlue", + }, +} + +LABELLED_NODE = { + "selector": "node", + "style": { + "color": "black", + "background-color": "CadetBlue", + "label": "data(_label)", + }, +} + +EDGE = { + "selector": "edge", + "style": { + "line-color": "grey", + "line-opacity": "0.5", + }, +} + +LABELLED_EDGE = { + "selector": "edge", + "style": { + "font-size": "12", + "font-style": "italic", + "line-color": "grey", + "line-opacity": "0.5", + "label": "data(_label)", + }, +} + + +DIRECTED_EDGE = { + "selector": "edge.directed", + "style": {"curve-style": "bezier", "target-arrow-shape": "triangle"}, +} + +MULTIPLE_EDGES = { + "selector": "edge.multiple_edges", + "style": {"curve-style": "bezier"}, +} + +DIRECTED_GRAPH = [ + NODE, + EDGE, + DIRECTED_EDGE, + MULTIPLE_EDGES, +] + +LABELLED_DIRECTED_GRAPH = [ + LABELLED_NODE, + LABELLED_EDGE, + DIRECTED_EDGE, + MULTIPLE_EDGES, +] diff --git a/src/ipyradiant/visualization/improved_cytoscape.py b/src/ipyradiant/visualization/improved_cytoscape.py new file mode 100644 index 0000000..d5df452 --- /dev/null +++ b/src/ipyradiant/visualization/improved_cytoscape.py @@ -0,0 +1,190 @@ +# Copyright (c) 2021 ipyradiant contributors. +# Distributed under the terms of the Modified BSD License. +import ipycytoscape as cyto +import ipywidgets as W +import networkx as nx +import rdflib +import traitlets as T +from ipycytoscape.cytoscape import Graph as CytoscapeGraph + +from ipyradiant.rdf2nx import RDF2NX +from ipyradiant.visualization.cytoscape import style + +MAX_NODES = 300 +MAX_EDGES = MAX_NODES * 3 + + +class CytoscapeViewer(W.VBox): + """A simple Cytoscape graph visualizer that can render RDF and networkx graphs. + + :param animate: flag for turning cytoscape animations on/off + :param node_labels: flag for turning node labels on/off + :param edge_labels: flag for turning edge labels on/off + :param layouts: list of optional layouts for the ipycytoscape.CytoscapeWidget + :param layout_selector: ipywidgets Dropdown for storing cytoscape layouts + :param cytoscape_widget: ipycytoscape.CytoscapeWidget for rendering the graph + :param graph: a networkx or rdflib Graph to render + :param cyto_layout: the selected cytoscape graph layout + :param cyto_style: the style used when rendering the cytoscape graph + :param _render_large_graphs: flag to prevent long expensive layout computations for large graphs + :param _rdf_label: attribute to use when discovering labels for RDF nodes (post-LPG conversion) + :param _nx_label: attribute to use when discovering labels for networkx nodes + :param _rdf_converter: converter class that transforms the input RDF graph to networkx + """ + + animate = T.Bool(default_value=True) + node_labels = T.Bool(default_value=True) + edge_labels = T.Bool(default_value=True) + layouts = T.List() + layout_selector = T.Instance(W.Dropdown) + cytoscape_widget = T.Instance(cyto.CytoscapeWidget) + graph = T.Union( + ( + T.Instance(rdflib.Graph), + T.Instance(nx.Graph), + ), + allow_none=True, + default_value=None, + ) + cyto_layout = T.Unicode(default_value="random") + cyto_style = T.List() + _render_large_graphs = False + _rdf_label = "rdfs:label" + _nx_label = "label" + _rdf_converter: RDF2NX = RDF2NX + + def update_style(self): + """Update style based on class attributes.""" + style_list = [style.DIRECTED_EDGE, style.MULTIPLE_EDGES] + if self.node_labels and self.edge_labels: + style_list = style.LABELLED_DIRECTED_GRAPH + elif not self.node_labels and not self.edge_labels: + style_list = style.DIRECTED_GRAPH + else: + if self.node_labels: + style_list.append(style.LABELLED_NODE) + else: + style_list.append(style.NODE) + if self.edge_labels: + style_list.append(style.LABELLED_EDGE) + else: + style_list.append(style.EDGE) + + self.cyto_style = style_list + + def update_cytoscape_frontend(self): + """A temporary workaround to trigger a frontend refresh""" + + self.cytoscape_widget.graph.add_node(cyto.Node(data={"id": "random node"})) + self.cytoscape_widget.graph.remove_node_by_id("random node") + + @T.default("cyto_style") + def _make_cyto_style(self): + self.update_style() + return self.cyto_style + + @T.validate("graph") + def _valid_graph(self, proposal): + """Validate graph by throwing error when # nodes/edges is at the limits of ipycytoscape + + TODO is there a better way to determine the limits than arbitrary numbers? + """ + + graph = proposal["value"] + if ( + isinstance(graph, nx.Graph) + and not self._render_large_graphs + and (len(graph.nodes) > MAX_NODES or len(graph.edges) > MAX_EDGES) + ): + raise T.TraitError( + f"unable to render networkx graphs with more than {MAX_NODES} nodes or {MAX_EDGES} edges." + ) + elif ( + isinstance(graph, rdflib.Graph) + and not self._render_large_graphs + and (len(graph) > MAX_EDGES + MAX_NODES) + ): + raise T.TraitError( + f"unable to render RDF graphs with more than {MAX_EDGES+MAX_NODES} triples." + ) + return graph + + @T.observe("graph") + def _update_graph(self, change): + # Clear graph so that data isn't duplicated + self.cytoscape_widget.graph = CytoscapeGraph() + if isinstance(self.graph, nx.Graph): + self.cytoscape_widget.graph.add_graph_from_networkx(self.graph) + # TODO def add_label_from_nx + for node in self.cytoscape_widget.graph.nodes: + node.data["_label"] = node.data.get(self._nx_label, None) + elif isinstance(self.graph, rdflib.Graph): + # Note: rdflib_to_networkx_multidigraph does not store the predicate AT ALL, + # so it is basically unrecoverable (e.g. for labelling); using _rdf_converter + nx_graph = self._rdf_converter.convert(self.graph) + self.cytoscape_widget.graph.add_graph_from_networkx(nx_graph) + for node in self.cytoscape_widget.graph.nodes: + node.data["_label"] = node.data.get( + self._rdf_label, node.data.get("id", None) + ) + + @T.default("cytoscape_widget") + def _make_cytoscape_widget(self): + widget = cyto.CytoscapeWidget() + widget.set_layout( + animate=self.animate, + randomize=True, + # Fast animation time for initial graph + maxSimulationTime=1000, + ) + widget.set_style(self.cyto_style) + return widget + + @T.observe("node_labels", "edge_labels") + def _update_labels(self, change): + self.update_style() + + @T.default("layouts") + def _make_layouts(self): + # TODO increase spacing on the layout? + # https://stackoverflow.com/questions/54015729 + return ["circle", "cola", "concentric", "cose", "dagre", "grid", "random"] + + @T.default("layout_selector") + def _make_layout_selector(self): + widget = W.Dropdown(description="Layout:", options=self.layouts) + T.link((self, "cyto_layout"), (widget, "value")) + return widget + + @T.observe("layouts") + def _update_layout_selector(self, change): + self.layout_selector = self._make_layout_selector() + + @T.observe("cyto_layout") + def _update_layout(self, change): + # TODO move to .cytoscape.layout + # note: animate=False goes through the full simulation to compute layout, which is SLOW + self.cytoscape_widget.set_layout( + name=self.cyto_layout, + animate=self.animate, + randomize=True, + maxSimulationTime=2000, + ) + + @T.observe("cyto_style") + def _update_style(self, change): + self.cytoscape_widget.set_style(self.cyto_style) + + @T.validate("children") + def validate_children(self, proposal): + """ + Validate method for default children. + This is necessary because @trt.default does not work on children. + """ + children = proposal.value + if not children: + children = ( + self.layout_selector, + self.cytoscape_widget, + ) + return children