diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml
index 2f919520..808a8e92 100644
--- a/.github/workflows/test.yml
+++ b/.github/workflows/test.yml
@@ -2,7 +2,7 @@ name: Tests
on:
push:
- branches: [ master, dev, dev-2.0 ]
+ branches: [ master, dev, dev-1.0, dev-1.0-refactor ]
pull_request:
branches: [ master, dev ]
@@ -13,6 +13,10 @@ jobs:
fail-fast: false
matrix:
os: [ubuntu-latest, windows-latest, macos-latest]
+ # datawrangler 0.5.0+ supports pandas 3.0 (dw#30/#31) and py3.10-3.13. The pandas<3
+ # ceiling is lifted, so the resolver naturally picks pandas 3.x on py3.11-3.13 and
+ # pandas 2.x on py3.10 (pandas 3.0 requires py>=3.11) -- the matrix exercises both.
+ # A dedicated pinned-pandas-3 gate runs below on ubuntu/py3.12.
python-version: ['3.10', '3.11', '3.12', '3.13']
steps:
@@ -69,6 +73,16 @@ jobs:
run: |
pytest -v --tb=short
+ - name: Acceptance gate — pandas 3.0 (Ubuntu Python 3.12 only)
+ # Guarantees a pandas-3 run regardless of resolver choices (dw#30/#31 fix).
+ if: matrix.os == 'ubuntu-latest' && matrix.python-version == '3.12'
+ env:
+ MPLBACKEND: Agg
+ run: |
+ pip install "pandas>=3.0"
+ python -c "import pandas as pd; assert pd.__version__.split('.')[0] == '3', pd.__version__; print('pandas', pd.__version__)"
+ pytest -q
+
- name: Run pytest with coverage (Ubuntu Python 3.12 only)
if: matrix.os == 'ubuntu-latest' && matrix.python-version == '3.12'
env:
diff --git a/dev/animation_demo.gif b/dev/animation_demo.gif
index e7295143..ad894243 100644
Binary files a/dev/animation_demo.gif and b/dev/animation_demo.gif differ
diff --git a/dev/hypertools_2.0_dev.ipynb b/dev/hypertools_1.0_dev.ipynb
similarity index 95%
rename from dev/hypertools_2.0_dev.ipynb
rename to dev/hypertools_1.0_dev.ipynb
index d86326d4..a3f7f437 100644
--- a/dev/hypertools_2.0_dev.ipynb
+++ b/dev/hypertools_1.0_dev.ipynb
@@ -3,7 +3,7 @@
{
"cell_type": "markdown",
"metadata": {},
- "source": "# HyperTools 2.0 dev/testing notebook\n\nInteractive companion to the `dev-2.0` modernization branch (see `notes/hypertools_2.0_roadmap.md`).\n\n**Purpose:** exercise EVERY public function across the full use-case matrix, visually verify output, and compare backends. Each section corresponds to one public API function. Run top-to-bottom in Jupyter, Colab, or Kaggle — the three environments we must support.\n\n**Use-case matrix** (applied to each function where relevant):\n\n| dimension | cases |\n|-|-|\n| input type | single ndarray, list of ndarrays, DataFrame, list of DataFrames, nested lists (→ MultiIndex), text |\n| dimensionality | 2D, 3D, high-D (reduced), 1D edge case |\n| missing data | none, NaNs (PPCA fill) |\n| styling | fmt strings, hue/group, legend, labels, title, multilevel-index color/thickness/opacity |\n| coloring | categorical labels, continuous values, mixture proportions, user matrices, multicolored lines |\n| models | reduce (PCA/IncrementalPCA/TSNE/UMAP), cluster (KMeans/HDBSCAN), mixtures (GaussianMixture/BayesianGaussianMixture/LDA/NMF), align (hyper/SRM) |\n| animation | static, animate=True, spin, sliding window |\n| backend | matplotlib (default), plotly (interactive) — 2.0 feature |"
+ "source": "# HyperTools 1.0 dev/testing notebook\n\nInteractive companion to the `dev-1.0` modernization branch (see `notes/hypertools_1.0_roadmap.md`).\n\n**Purpose:** exercise EVERY public function across the full use-case matrix, visually verify output, and compare backends. Each section corresponds to one public API function. Run top-to-bottom in Jupyter, Colab, or Kaggle — the three environments we must support.\n\n**Use-case matrix** (applied to each function where relevant):\n\n| dimension | cases |\n|-|-|\n| input type | single ndarray, list of ndarrays, DataFrame, list of DataFrames, nested lists (→ MultiIndex), text |\n| dimensionality | 2D, 3D, high-D (reduced), 1D edge case |\n| missing data | none, NaNs (PPCA fill) |\n| styling | fmt strings, hue/group, legend, labels, title, multilevel-index color/thickness/opacity |\n| coloring | categorical labels, continuous values, mixture proportions, user matrices, multicolored lines |\n| models | reduce (PCA/IncrementalPCA/TSNE/UMAP), cluster (KMeans/HDBSCAN), mixtures (GaussianMixture/BayesianGaussianMixture/LDA/NMF), align (hyper/SRM) |\n| animation | static, animate=True, spin, sliding window |\n| backend | matplotlib (default), plotly (interactive) — 2.0 feature |"
},
{
"cell_type": "code",
@@ -19,7 +19,7 @@
"\n",
"t0 = time.time()\n",
"import hypertools as hyp\n",
- "print(f'hypertools {hyp.__version__} imported in {time.time()-t0:.2f}s') # 2.0 target: < 1s\n",
+ "print(f'hypertools {hyp.__version__} imported in {time.time()-t0:.2f}s') # 1.0 target: < 1s\n",
"\n",
"ENV = 'colab' if 'google.colab' in sys.modules else ('kaggle' if os.path.exists('/kaggle') else 'local')\n",
"print('environment:', ENV)"
@@ -78,7 +78,7 @@
"source": [
"## 3. `hyp.plot` — animation\n",
"\n",
- "Known-broken on master: numpy>=2 Jupyter animations (#265), Colab `animate=True` (#235), figures in loops (#264). These cells are the acceptance tests for the 2.0 fixes."
+ "Known-broken on master: numpy>=2 Jupyter animations (#265), Colab `animate=True` (#235), figures in loops (#264). These cells are the acceptance tests for the 1.0 fixes."
]
},
{
@@ -91,14 +91,14 @@
{
"cell_type": "markdown",
"metadata": {},
- "source": "## 4. `hyp.plot` — 2.0 features (IMPLEMENTED on dev-2.0)\n\nBackend switching, nested-list multilevel styling, mixture-model soft clustering, and matrix-valued hue. Each of these also has pytest coverage (`test_interactive.py`, `test_nested.py`, `test_colors.py`, `test_cluster.py`) and screenshot-harness cases."
+ "source": "## 4. `hyp.plot` — 1.0 features (IMPLEMENTED on dev-1.0)\n\nBackend switching, nested-list multilevel styling, mixture-model soft clustering, and matrix-valued hue. Each of these also has pytest coverage (`test_interactive.py`, `test_nested.py`, `test_colors.py`, `test_cluster.py`) and screenshot-harness cases."
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
- "source": "# --- backend switching (approved policy: plotly only on Colab/Kaggle) ---\nhyp.plot(walk, backend='matplotlib') # explicit classic renderer\nfig = hyp.plot(walk, backend='plotly') # interactive plotly renderer\nhyp.plot(walk, backend='auto') # plotly on Colab/Kaggle, else matplotlib\n\n# --- plotly ANIMATIONS: interactive frames in the notebook, exportable to\n# gif / animated png / mp4 exactly like the matplotlib backend ---\ngeo = hyp.plot(walk, animate=True, duration=3, backend='plotly') # play button\nhyp.plot(walk, animate='spin', duration=3, backend='plotly',\n save_path='plotly_spin_demo.gif', show=False)\nfrom IPython.display import Image as IPyImage\ndisplay(IPyImage('plotly_spin_demo.gif'))\n\n# --- nested lists -> multilevel styling (color by outer group; deeper\n# leaves render thinner + fainter) ---\nhyp.plot([[walk, walk2], [np.cumsum(np.random.default_rng(7).standard_normal((200, 10)), axis=0)]])\n\n# --- mixture models on OVERLAPPING clusters: multi-class membership shows\n# as blended (intermediate) colors between components ---\nprops = hyp.cluster(oclusters, cluster='GaussianMixture', n_clusters=3)\nsoft = np.mean(props.max(axis=1) < 0.9)\nprint(f'mixture proportions: {props.shape}; {soft:.0%} of points have genuinely mixed membership')\nhyp.plot(oclusters, 'o', cluster='GaussianMixture', n_clusters=3)\n\n# --- robust coloring: matrix-valued hue (proportions, weights, any matrix) ---\nhyp.plot(oclusters, 'o', hue=props)\nhyp.plot(walk, 'o', hue=np.arange(len(walk), dtype=float)) # continuous hue\n\n# --- MULTICOLORED LINES: continuous/matrix hue + line fmt colors each\n# trajectory continuously along its length (both backends) ---\nhyp.plot(walk, hue=np.arange(len(walk), dtype=float))\nhyp.plot(walk, hue=np.arange(len(walk), dtype=float), backend='plotly')\n\n# --- apply_model: stack -> fit once -> unstack core ---\nembedded = hyp.apply_model([walk, walk2], 'PCA', ndims=3)\nprint('shared embedding:', [e.shape for e in embedded])\n\n# --- retired in 2.0 (raise clear errors now): plot(group=...), plot(model=...),\n# reduce(model=/align=/normalize=), align(method=/align=True), cluster(ndims=) ---"
+ "source": "# --- backend switching (approved policy: plotly only on Colab/Kaggle) ---\nhyp.plot(walk, backend='matplotlib') # explicit classic renderer\nfig = hyp.plot(walk, backend='plotly') # interactive plotly renderer\nhyp.plot(walk, backend='auto') # plotly on Colab/Kaggle, else matplotlib\n\n# --- plotly ANIMATIONS: interactive frames in the notebook, exportable to\n# gif / animated png / mp4 exactly like the matplotlib backend ---\ngeo = hyp.plot(walk, animate=True, duration=3, backend='plotly') # play button\nhyp.plot(walk, animate='spin', duration=3, backend='plotly',\n save_path='plotly_spin_demo.gif', show=False)\nfrom IPython.display import Image as IPyImage\ndisplay(IPyImage('plotly_spin_demo.gif'))\n\n# --- nested lists -> multilevel styling (color by outer group; deeper\n# leaves render thinner + fainter) ---\nhyp.plot([[walk, walk2], [np.cumsum(np.random.default_rng(7).standard_normal((200, 10)), axis=0)]])\n\n# --- mixture models on OVERLAPPING clusters: multi-class membership shows\n# as blended (intermediate) colors between components ---\nprops = hyp.cluster(oclusters, cluster='GaussianMixture', n_clusters=3)\nsoft = np.mean(props.max(axis=1) < 0.9)\nprint(f'mixture proportions: {props.shape}; {soft:.0%} of points have genuinely mixed membership')\nhyp.plot(oclusters, 'o', cluster='GaussianMixture', n_clusters=3)\n\n# --- robust coloring: matrix-valued hue (proportions, weights, any matrix) ---\nhyp.plot(oclusters, 'o', hue=props)\nhyp.plot(walk, 'o', hue=np.arange(len(walk), dtype=float)) # continuous hue\n\n# --- MULTICOLORED LINES: continuous/matrix hue + line fmt colors each\n# trajectory continuously along its length (both backends) ---\nhyp.plot(walk, hue=np.arange(len(walk), dtype=float))\nhyp.plot(walk, hue=np.arange(len(walk), dtype=float), backend='plotly')\n\n# --- apply_model: stack -> fit once -> unstack core ---\nembedded = hyp.apply_model([walk, walk2], 'PCA', ndims=3)\nprint('shared embedding:', [e.shape for e in embedded])\n\n# --- retired in 1.0 (raise clear errors now): plot(group=...), plot(model=...),\n# reduce(model=/align=/normalize=), align(method=/align=True), cluster(ndims=) ---"
},
{
"cell_type": "markdown",
diff --git a/dev/hypertools_2.0_dev_executed.ipynb b/dev/hypertools_1.0_dev_executed.ipynb
similarity index 99%
rename from dev/hypertools_2.0_dev_executed.ipynb
rename to dev/hypertools_1.0_dev_executed.ipynb
index 6136da57..4b3b18cc 100644
--- a/dev/hypertools_2.0_dev_executed.ipynb
+++ b/dev/hypertools_1.0_dev_executed.ipynb
@@ -5,9 +5,9 @@
"id": "a788b512",
"metadata": {},
"source": [
- "# HyperTools 2.0 dev/testing notebook\n",
+ "# HyperTools 1.0 dev/testing notebook\n",
"\n",
- "Interactive companion to the `dev-2.0` modernization branch (see `notes/hypertools_2.0_roadmap.md`).\n",
+ "Interactive companion to the `dev-1.0` modernization branch (see `notes/hypertools_1.0_roadmap.md`).\n",
"\n",
"**Purpose:** exercise EVERY public function across the full use-case matrix, visually verify output, and compare backends. Each section corresponds to one public API function. Run top-to-bottom in Jupyter, Colab, or Kaggle — the three environments we must support.\n",
"\n",
@@ -42,7 +42,7 @@
"name": "stdout",
"output_type": "stream",
"text": [
- "hypertools 2.0.0.dev0 imported in 3.86s\n",
+ "hypertools 1.0.0.dev0 imported in 3.86s\n",
"environment: local\n"
]
}
@@ -56,7 +56,7 @@
"\n",
"t0 = time.time()\n",
"import hypertools as hyp\n",
- "print(f'hypertools {hyp.__version__} imported in {time.time()-t0:.2f}s') # 2.0 target: < 1s\n",
+ "print(f'hypertools {hyp.__version__} imported in {time.time()-t0:.2f}s') # 1.0 target: < 1s\n",
"\n",
"ENV = 'colab' if 'google.colab' in sys.modules else ('kaggle' if os.path.exists('/kaggle') else 'local')\n",
"print('environment:', ENV)"
@@ -163,7 +163,7 @@
"source": [
"## 3. `hyp.plot` — animation\n",
"\n",
- "Known-broken on master: numpy>=2 Jupyter animations (#265), Colab `animate=True` (#235), figures in loops (#264). These cells are the acceptance tests for the 2.0 fixes."
+ "Known-broken on master: numpy>=2 Jupyter animations (#265), Colab `animate=True` (#235), figures in loops (#264). These cells are the acceptance tests for the 1.0 fixes."
]
},
{
@@ -37291,7 +37291,7 @@
"id": "1ca254ea",
"metadata": {},
"source": [
- "## 4. `hyp.plot` — 2.0 features (IMPLEMENTED on dev-2.0)\n",
+ "## 4. `hyp.plot` — 1.0 features (IMPLEMENTED on dev-1.0)\n",
"\n",
"Backend switching, nested-list multilevel styling, mixture-model soft clustering, and matrix-valued hue. Each of these also has pytest coverage (`test_interactive.py`, `test_nested.py`, `test_colors.py`, `test_cluster.py`) and screenshot-harness cases."
]
@@ -44449,7 +44449,7 @@
"embedded = hyp.apply_model([walk, walk2], 'PCA', ndims=3)\n",
"print('shared embedding:', [e.shape for e in embedded])\n",
"\n",
- "# --- retired in 2.0 (raise clear errors now): plot(group=...), plot(model=...),\n",
+ "# --- retired in 1.0 (raise clear errors now): plot(group=...), plot(model=...),\n",
"# reduce(model=/align=/normalize=), align(method=/align=True), cluster(ndims=) ---"
]
},
diff --git a/dev/plotly_spin_demo.gif b/dev/plotly_spin_demo.gif
index f10acdc8..e1e0030f 100644
Binary files a/dev/plotly_spin_demo.gif and b/dev/plotly_spin_demo.gif differ
diff --git a/docs/api.rst b/docs/api.rst
index 540a2524..f2138c9c 100644
--- a/docs/api.rst
+++ b/docs/api.rst
@@ -7,16 +7,6 @@
API reference
=============
-.. _plot_api:
-
-DataGeometry class
-------------------
-
-.. autosummary::
- :toctree:
-
- hypertools.DataGeometry
-
Load
------------------
@@ -73,6 +63,22 @@ Cluster
hypertools.cluster
+Predict
+------------------
+
+.. autosummary::
+ :toctree:
+
+ hypertools.predict
+
+Impute
+------------------
+
+.. autosummary::
+ :toctree:
+
+ hypertools.impute
+
Apply model
------------------
@@ -99,7 +105,7 @@ Tools
.. autosummary::
:toctree:
- hypertools.tools.procrustes
+ hypertools.align.procrustes
.. autosummary::
:toctree:
diff --git a/docs/auto_examples/analyze.codeobj.json b/docs/auto_examples/analyze.codeobj.json
index 8a8b1813..6e82e8be 100644
--- a/docs/auto_examples/analyze.codeobj.json
+++ b/docs/auto_examples/analyze.codeobj.json
@@ -8,38 +8,6 @@
"name": "list"
}
],
- "geo": [
- {
- "is_class": false,
- "is_explicit": false,
- "module": "hypertools.datageometry",
- "module_short": "hypertools",
- "name": "DataGeometry"
- },
- {
- "is_class": false,
- "is_explicit": false,
- "module": "hypertools",
- "module_short": "hypertools",
- "name": "DataGeometry"
- }
- ],
- "geo.get_data": [
- {
- "is_class": false,
- "is_explicit": false,
- "module": "hypertools.datageometry",
- "module_short": "hypertools",
- "name": "DataGeometry.get_data"
- },
- {
- "is_class": false,
- "is_explicit": false,
- "module": "hypertools",
- "module_short": "hypertools",
- "name": "DataGeometry.get_data"
- }
- ],
"hyp.analyze": [
{
"is_class": false,
diff --git a/docs/auto_examples/analyze.ipynb b/docs/auto_examples/analyze.ipynb
index edbe1f22..322647a6 100644
--- a/docs/auto_examples/analyze.ipynb
+++ b/docs/auto_examples/analyze.ipynb
@@ -1,58 +1,54 @@
{
- "cells": [
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {
- "collapsed": false
- },
- "outputs": [],
- "source": [
- "# Install hypertools (dev-2.0 preview) -- run this first on Colab.\n",
- "# On release this becomes: %pip install hypertools\n",
- "%pip install -q \"hypertools[interactive] @ git+https://github.com/ContextLab/hypertools.git@dev-2.0\"\n",
- "\n",
- "%matplotlib inline"
- ]
+ "cells": [
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "collapsed": false
+ },
+ "outputs": [],
+ "source": [
+ "# Install hypertools (dev-1.0-refactor preview) -- run this first on Colab.\n# On release this becomes: %pip install hypertools\n%pip install -q \"hypertools[interactive] @ git+https://github.com/ContextLab/hypertools.git@dev-1.0-refactor\"\n\n%matplotlib inline"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "\n# Analyze data and then plot\n\nThis example demonstrates how to use the `analyze` function to process data\nprior to plotting. The data is a list of numpy arrays representing\nmulti-voxel activity patterns (columns) over time (rows). First, analyze function\nnormalizes the columns of each matrix (within each matrix). Then the data is\nreduced using PCA (10 dims) and finally it is aligned with hyperalignment. We can\nthen plot the data with hyp.plot, which further reduces it so that it can be\nvisualized.\n"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "collapsed": false
+ },
+ "outputs": [],
+ "source": [
+ "# sphinx_gallery_thumbnail_path = '_static/thumbnails/sphx_glr_analyze_thumb.png'\n\n# Code source: Andrew Heusser\n# License: MIT\n\n# load hypertools\nimport hypertools as hyp\n\n# load the data\ndata = hyp.load('weights')\n\n# process the data\ndata = hyp.analyze(data, normalize='within', reduce='PCA', ndims=10,\n align='hyper')\n\n# plot it\nhyp.plot(data)"
+ ]
+ }
+ ],
+ "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.12.10"
+ }
},
- {
- "cell_type": "markdown",
- "metadata": {},
- "source": [
- "\n# Analyze data and then plot\n\nThis example demonstrates how to use the `analyze` function to process data\nprior to plotting. The data is a list of numpy arrays representing\nmulti-voxel activity patterns (columns) over time (rows). First, analyze function\nnormalizes the columns of each matrix (within each matrix). Then the data is\nreduced using PCA (10 dims) and finally it is aligned with hyperalignment. We can\nthen plot the data with hyp.plot, which further reduces it so that it can be\nvisualized.\n"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {
- "collapsed": false
- },
- "outputs": [],
- "source": [
- "# sphinx_gallery_thumbnail_path = '_static/thumbnails/sphx_glr_analyze_thumb.png'\n\n# Code source: Andrew Heusser\n# License: MIT\n\n# load hypertools\nimport hypertools as hyp\n\n# load the data\ngeo = hyp.load('weights')\ndata = geo.get_data()\n\n# process the data\ndata = hyp.analyze(data, normalize='within', reduce='PCA', ndims=10,\n align='hyper')\n\n# plot it\nhyp.plot(data)"
- ]
- }
- ],
- "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.12.10"
- }
- },
- "nbformat": 4,
- "nbformat_minor": 0
-}
+ "nbformat": 4,
+ "nbformat_minor": 0
+}
\ No newline at end of file
diff --git a/docs/auto_examples/analyze.py b/docs/auto_examples/analyze.py
index 5035215e..16dce767 100644
--- a/docs/auto_examples/analyze.py
+++ b/docs/auto_examples/analyze.py
@@ -22,8 +22,7 @@
import hypertools as hyp
# load the data
-geo = hyp.load('weights')
-data = geo.get_data()
+data = hyp.load('weights')
# process the data
data = hyp.analyze(data, normalize='within', reduce='PCA', ndims=10,
diff --git a/docs/auto_examples/analyze.py.md5 b/docs/auto_examples/analyze.py.md5
index d597194d..28b0f1c9 100644
--- a/docs/auto_examples/analyze.py.md5
+++ b/docs/auto_examples/analyze.py.md5
@@ -1 +1 @@
-b188d6db61e339db0651634314ffdc5a
\ No newline at end of file
+68992cc2797469b2fefb43fcf35a16d6
\ No newline at end of file
diff --git a/docs/auto_examples/analyze.rst b/docs/auto_examples/analyze.rst
index b2d1de99..e410fb59 100644
--- a/docs/auto_examples/analyze.rst
+++ b/docs/auto_examples/analyze.rst
@@ -30,7 +30,7 @@ reduced using PCA (10 dims) and finally it is aligned with hyperalignment. We ca
then plot the data with hyp.plot, which further reduces it so that it can be
visualized.
-.. GENERATED FROM PYTHON SOURCE LINES 15-34
+.. GENERATED FROM PYTHON SOURCE LINES 15-33
@@ -55,8 +55,7 @@ visualized.
import hypertools as hyp
# load the data
- geo = hyp.load('weights')
- data = geo.get_data()
+ data = hyp.load('weights')
# process the data
data = hyp.analyze(data, normalize='within', reduce='PCA', ndims=10,
@@ -68,7 +67,7 @@ visualized.
.. rst-class:: sphx-glr-timing
- **Total running time of the script:** (0 minutes 0.389 seconds)
+ **Total running time of the script:** (0 minutes 0.374 seconds)
.. _sphx_glr_download_auto_examples_analyze.py:
diff --git a/docs/auto_examples/analyze.zip b/docs/auto_examples/analyze.zip
index 8170a90b..29d2ca32 100644
Binary files a/docs/auto_examples/analyze.zip and b/docs/auto_examples/analyze.zip differ
diff --git a/docs/auto_examples/animate.codeobj.json b/docs/auto_examples/animate.codeobj.json
index 1503c46f..dc560397 100644
--- a/docs/auto_examples/animate.codeobj.json
+++ b/docs/auto_examples/animate.codeobj.json
@@ -15,71 +15,48 @@
"name": "FuncAnimation"
}
],
- "ani_geo": [
+ "data": [
{
"is_class": false,
"is_explicit": false,
- "module": "hypertools.datageometry",
- "module_short": "hypertools",
- "name": "DataGeometry"
- },
- {
- "is_class": false,
- "is_explicit": false,
- "module": "hypertools",
- "module_short": "hypertools",
- "name": "DataGeometry"
+ "module": "builtins",
+ "module_short": "builtins",
+ "name": "list"
}
],
- "ani_geo.line_ani": [
+ "fig": [
{
"is_class": false,
"is_explicit": false,
- "module": "matplotlib.animation",
- "module_short": "matplotlib.animation",
- "name": "FuncAnimation"
+ "module": "matplotlib.figure",
+ "module_short": "matplotlib.figure",
+ "name": "Figure"
},
{
"is_class": false,
"is_explicit": false,
"module": "matplotlib",
"module_short": "matplotlib",
- "name": "FuncAnimation"
+ "name": "Figure"
}
],
- "geo": [
- {
- "is_class": false,
- "is_explicit": false,
- "module": "hypertools.datageometry",
- "module_short": "hypertools",
- "name": "DataGeometry"
- },
- {
- "is_class": false,
- "is_explicit": false,
- "module": "hypertools",
- "module_short": "hypertools",
- "name": "DataGeometry"
- }
- ],
- "geo.plot": [
+ "hyp.load": [
{
"is_class": false,
"is_explicit": false,
- "module": "hypertools.datageometry",
- "module_short": "hypertools",
- "name": "DataGeometry.plot"
+ "module": "builtins",
+ "module_short": "builtins",
+ "name": "function"
},
{
"is_class": false,
"is_explicit": false,
"module": "hypertools",
"module_short": "hypertools",
- "name": "DataGeometry.plot"
+ "name": "load"
}
],
- "hyp.load": [
+ "hyp.plot": [
{
"is_class": false,
"is_explicit": false,
@@ -92,7 +69,7 @@
"is_explicit": false,
"module": "hypertools",
"module_short": "hypertools",
- "name": "load"
+ "name": "plot"
}
]
}
\ No newline at end of file
diff --git a/docs/auto_examples/animate.ipynb b/docs/auto_examples/animate.ipynb
index dc801ce1..0a1c857e 100644
--- a/docs/auto_examples/animate.ipynb
+++ b/docs/auto_examples/animate.ipynb
@@ -1,58 +1,54 @@
{
- "cells": [
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {
- "collapsed": false
- },
- "outputs": [],
- "source": [
- "# Install hypertools (dev-2.0 preview) -- run this first on Colab.\n",
- "# On release this becomes: %pip install hypertools\n",
- "%pip install -q \"hypertools[interactive] @ git+https://github.com/ContextLab/hypertools.git@dev-2.0\"\n",
- "\n",
- "%matplotlib inline"
- ]
+ "cells": [
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "collapsed": false
+ },
+ "outputs": [],
+ "source": [
+ "# Install hypertools (dev-1.0-refactor preview) -- run this first on Colab.\n# On release this becomes: %pip install hypertools\n%pip install -q \"hypertools[interactive] @ git+https://github.com/ContextLab/hypertools.git@dev-1.0-refactor\"\n\n%matplotlib inline"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "\n# Animated plots\n\nTimeseries plots can be animated by simply passing `animate=True` when\ncalling hyp.plot.\n"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "collapsed": false
+ },
+ "outputs": [],
+ "source": [
+ "# sphinx_gallery_thumbnail_path = '_static/thumbnails/sphx_glr_animate_thumb.gif'\n\n# Code source: Andrew Heusser\n# License: MIT\n\n# import\nimport hypertools as hyp\n\n# load example data\ndata = hyp.load('weights_avg')\n\n# plot\nfig, ani = hyp.plot(data, animate=True, legend=['first', 'second'])"
+ ]
+ }
+ ],
+ "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.12.10"
+ }
},
- {
- "cell_type": "markdown",
- "metadata": {},
- "source": [
- "\n# Animated plots\n\nTimeseries plots can be animated by simply passing `animate=True` to the geo (\nor when calling hyp.plot).\n"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {
- "collapsed": false
- },
- "outputs": [],
- "source": [
- "# sphinx_gallery_thumbnail_path = '_static/thumbnails/sphx_glr_animate_thumb.gif'\n\n# Code source: Andrew Heusser\n# License: MIT\n\n# import\nimport hypertools as hyp\n\n# load example data\ngeo = hyp.load('weights_avg')\n\n# plot\nani_geo = geo.plot(animate=True, legend=['first', 'second'])\nani = ani_geo.line_ani # the underlying matplotlib FuncAnimation"
- ]
- }
- ],
- "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.12.10"
- }
- },
- "nbformat": 4,
- "nbformat_minor": 0
-}
+ "nbformat": 4,
+ "nbformat_minor": 0
+}
\ No newline at end of file
diff --git a/docs/auto_examples/animate.py b/docs/auto_examples/animate.py
index d96ee275..03f56aee 100644
--- a/docs/auto_examples/animate.py
+++ b/docs/auto_examples/animate.py
@@ -4,8 +4,8 @@
Animated plots
=============================
-Timeseries plots can be animated by simply passing `animate=True` to the geo (
-or when calling hyp.plot).
+Timeseries plots can be animated by simply passing `animate=True` when
+calling hyp.plot.
"""
# sphinx_gallery_thumbnail_path = '_static/thumbnails/sphx_glr_animate_thumb.gif'
@@ -17,8 +17,7 @@
import hypertools as hyp
# load example data
-geo = hyp.load('weights_avg')
+data = hyp.load('weights_avg')
# plot
-ani_geo = geo.plot(animate=True, legend=['first', 'second'])
-ani = ani_geo.line_ani # the underlying matplotlib FuncAnimation
+fig, ani = hyp.plot(data, animate=True, legend=['first', 'second'])
diff --git a/docs/auto_examples/animate.py.md5 b/docs/auto_examples/animate.py.md5
index 7aa85fb3..3430e3d5 100644
--- a/docs/auto_examples/animate.py.md5
+++ b/docs/auto_examples/animate.py.md5
@@ -1 +1 @@
-aa5bc3593032f96dd28767c310605c61
\ No newline at end of file
+99485eb15977b55d11efb75e13f17ec1
\ No newline at end of file
diff --git a/docs/auto_examples/animate.rst b/docs/auto_examples/animate.rst
index 287f87ed..4391ac66 100644
--- a/docs/auto_examples/animate.rst
+++ b/docs/auto_examples/animate.rst
@@ -22,17 +22,17 @@
Animated plots
=============================
-Timeseries plots can be animated by simply passing `animate=True` to the geo (
-or when calling hyp.plot).
+Timeseries plots can be animated by simply passing `animate=True` when
+calling hyp.plot.
-.. GENERATED FROM PYTHON SOURCE LINES 10-25
+.. GENERATED FROM PYTHON SOURCE LINES 10-24
.. video:: /auto_examples/images/sphx_glr_animate_001.mp4
:class: sphx-glr-single-img
:height: 480
- :width: 640
+ :width: 683
:autoplay:
@@ -52,16 +52,15 @@ or when calling hyp.plot).
import hypertools as hyp
# load example data
- geo = hyp.load('weights_avg')
+ data = hyp.load('weights_avg')
# plot
- ani_geo = geo.plot(animate=True, legend=['first', 'second'])
- ani = ani_geo.line_ani # the underlying matplotlib FuncAnimation
+ fig, ani = hyp.plot(data, animate=True, legend=['first', 'second'])
.. rst-class:: sphx-glr-timing
- **Total running time of the script:** (0 minutes 10.973 seconds)
+ **Total running time of the script:** (0 minutes 12.906 seconds)
.. _sphx_glr_download_auto_examples_animate.py:
diff --git a/docs/auto_examples/animate.zip b/docs/auto_examples/animate.zip
index 810084d2..ed312771 100644
Binary files a/docs/auto_examples/animate.zip and b/docs/auto_examples/animate.zip differ
diff --git a/docs/auto_examples/animate_MDS.codeobj.json b/docs/auto_examples/animate_MDS.codeobj.json
index 1503c46f..dc560397 100644
--- a/docs/auto_examples/animate_MDS.codeobj.json
+++ b/docs/auto_examples/animate_MDS.codeobj.json
@@ -15,71 +15,48 @@
"name": "FuncAnimation"
}
],
- "ani_geo": [
+ "data": [
{
"is_class": false,
"is_explicit": false,
- "module": "hypertools.datageometry",
- "module_short": "hypertools",
- "name": "DataGeometry"
- },
- {
- "is_class": false,
- "is_explicit": false,
- "module": "hypertools",
- "module_short": "hypertools",
- "name": "DataGeometry"
+ "module": "builtins",
+ "module_short": "builtins",
+ "name": "list"
}
],
- "ani_geo.line_ani": [
+ "fig": [
{
"is_class": false,
"is_explicit": false,
- "module": "matplotlib.animation",
- "module_short": "matplotlib.animation",
- "name": "FuncAnimation"
+ "module": "matplotlib.figure",
+ "module_short": "matplotlib.figure",
+ "name": "Figure"
},
{
"is_class": false,
"is_explicit": false,
"module": "matplotlib",
"module_short": "matplotlib",
- "name": "FuncAnimation"
+ "name": "Figure"
}
],
- "geo": [
- {
- "is_class": false,
- "is_explicit": false,
- "module": "hypertools.datageometry",
- "module_short": "hypertools",
- "name": "DataGeometry"
- },
- {
- "is_class": false,
- "is_explicit": false,
- "module": "hypertools",
- "module_short": "hypertools",
- "name": "DataGeometry"
- }
- ],
- "geo.plot": [
+ "hyp.load": [
{
"is_class": false,
"is_explicit": false,
- "module": "hypertools.datageometry",
- "module_short": "hypertools",
- "name": "DataGeometry.plot"
+ "module": "builtins",
+ "module_short": "builtins",
+ "name": "function"
},
{
"is_class": false,
"is_explicit": false,
"module": "hypertools",
"module_short": "hypertools",
- "name": "DataGeometry.plot"
+ "name": "load"
}
],
- "hyp.load": [
+ "hyp.plot": [
{
"is_class": false,
"is_explicit": false,
@@ -92,7 +69,7 @@
"is_explicit": false,
"module": "hypertools",
"module_short": "hypertools",
- "name": "load"
+ "name": "plot"
}
]
}
\ No newline at end of file
diff --git a/docs/auto_examples/animate_MDS.ipynb b/docs/auto_examples/animate_MDS.ipynb
index e248b6b3..a2f77c44 100644
--- a/docs/auto_examples/animate_MDS.ipynb
+++ b/docs/auto_examples/animate_MDS.ipynb
@@ -1,58 +1,54 @@
{
- "cells": [
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {
- "collapsed": false
- },
- "outputs": [],
- "source": [
- "# Install hypertools (dev-2.0 preview) -- run this first on Colab.\n",
- "# On release this becomes: %pip install hypertools\n",
- "%pip install -q \"hypertools[interactive] @ git+https://github.com/ContextLab/hypertools.git@dev-2.0\"\n",
- "\n",
- "%matplotlib inline"
- ]
+ "cells": [
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "collapsed": false
+ },
+ "outputs": [],
+ "source": [
+ "# Install hypertools (dev-1.0-refactor preview) -- run this first on Colab.\n# On release this becomes: %pip install hypertools\n%pip install -q \"hypertools[interactive] @ git+https://github.com/ContextLab/hypertools.git@dev-1.0-refactor\"\n\n%matplotlib inline"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "\n# Animated trajectory plotted with multidimensional scaling\n\nThis is a trajectory of brain data plotted in 3D with multidimensional scaling.\n"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "collapsed": false
+ },
+ "outputs": [],
+ "source": [
+ "# sphinx_gallery_thumbnail_path = '_static/thumbnails/sphx_glr_animate_MDS_thumb.gif'\n\n# Code source: Andrew Heusser\n# License: MIT\n\n# import hypertools\nimport hypertools as hyp\n\n# load the data\ndata = hyp.load('weights_avg')\n\n# plot\nfig, ani = hyp.plot(data, animate=True, reduce='MDS')"
+ ]
+ }
+ ],
+ "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.12.10"
+ }
},
- {
- "cell_type": "markdown",
- "metadata": {},
- "source": [
- "\n# Animated trajectory plotted with multidimensional scaling\n\nThis is a trajectory of brain data plotted in 3D with multidimensional scaling.\n"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {
- "collapsed": false
- },
- "outputs": [],
- "source": [
- "# sphinx_gallery_thumbnail_path = '_static/thumbnails/sphx_glr_animate_MDS_thumb.gif'\n\n# Code source: Andrew Heusser\n# License: MIT\n\n# import hypertools\nimport hypertools as hyp\n\n# load the geo\ngeo = hyp.load('weights_avg')\n\n# plot\nani_geo = geo.plot(animate=True, reduce='MDS')\nani = ani_geo.line_ani # the underlying matplotlib FuncAnimation"
- ]
- }
- ],
- "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.12.10"
- }
- },
- "nbformat": 4,
- "nbformat_minor": 0
-}
+ "nbformat": 4,
+ "nbformat_minor": 0
+}
\ No newline at end of file
diff --git a/docs/auto_examples/animate_MDS.py b/docs/auto_examples/animate_MDS.py
index 78023abd..a623219a 100644
--- a/docs/auto_examples/animate_MDS.py
+++ b/docs/auto_examples/animate_MDS.py
@@ -15,9 +15,8 @@
# import hypertools
import hypertools as hyp
-# load the geo
-geo = hyp.load('weights_avg')
+# load the data
+data = hyp.load('weights_avg')
# plot
-ani_geo = geo.plot(animate=True, reduce='MDS')
-ani = ani_geo.line_ani # the underlying matplotlib FuncAnimation
+fig, ani = hyp.plot(data, animate=True, reduce='MDS')
diff --git a/docs/auto_examples/animate_MDS.py.md5 b/docs/auto_examples/animate_MDS.py.md5
index 3db2cff8..912f8e51 100644
--- a/docs/auto_examples/animate_MDS.py.md5
+++ b/docs/auto_examples/animate_MDS.py.md5
@@ -1 +1 @@
-8a5b42387ccdae7c2ab5fbd5ac602a11
\ No newline at end of file
+e04bece23e3e8a3f7a5b77e9a9e18979
\ No newline at end of file
diff --git a/docs/auto_examples/animate_MDS.rst b/docs/auto_examples/animate_MDS.rst
index 4dad2555..c205aaec 100644
--- a/docs/auto_examples/animate_MDS.rst
+++ b/docs/auto_examples/animate_MDS.rst
@@ -24,7 +24,7 @@ Animated trajectory plotted with multidimensional scaling
This is a trajectory of brain data plotted in 3D with multidimensional scaling.
-.. GENERATED FROM PYTHON SOURCE LINES 9-24
+.. GENERATED FROM PYTHON SOURCE LINES 9-23
@@ -63,17 +63,16 @@ This is a trajectory of brain data plotted in 3D with multidimensional scaling.
# import hypertools
import hypertools as hyp
- # load the geo
- geo = hyp.load('weights_avg')
+ # load the data
+ data = hyp.load('weights_avg')
# plot
- ani_geo = geo.plot(animate=True, reduce='MDS')
- ani = ani_geo.line_ani # the underlying matplotlib FuncAnimation
+ fig, ani = hyp.plot(data, animate=True, reduce='MDS')
.. rst-class:: sphx-glr-timing
- **Total running time of the script:** (0 minutes 7.567 seconds)
+ **Total running time of the script:** (0 minutes 5.847 seconds)
.. _sphx_glr_download_auto_examples_animate_MDS.py:
diff --git a/docs/auto_examples/animate_MDS.zip b/docs/auto_examples/animate_MDS.zip
index 44f1a24b..cbe9daa8 100644
Binary files a/docs/auto_examples/animate_MDS.zip and b/docs/auto_examples/animate_MDS.zip differ
diff --git a/docs/auto_examples/animate_plotly.ipynb b/docs/auto_examples/animate_plotly.ipynb
index b722eac7..7c690288 100644
--- a/docs/auto_examples/animate_plotly.ipynb
+++ b/docs/auto_examples/animate_plotly.ipynb
@@ -1,58 +1,54 @@
{
- "cells": [
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {
- "collapsed": false
- },
- "outputs": [],
- "source": [
- "# Install hypertools (dev-2.0 preview) -- run this first on Colab.\n",
- "# On release this becomes: %pip install hypertools\n",
- "%pip install -q \"hypertools[interactive] @ git+https://github.com/ContextLab/hypertools.git@dev-2.0\"\n",
- "\n",
- "%matplotlib inline"
- ]
+ "cells": [
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "collapsed": false
+ },
+ "outputs": [],
+ "source": [
+ "# Install hypertools (dev-1.0-refactor preview) -- run this first on Colab.\n# On release this becomes: %pip install hypertools\n%pip install -q \"hypertools[interactive] @ git+https://github.com/ContextLab/hypertools.git@dev-1.0-refactor\"\n\n%matplotlib inline"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "\n# Animated interactive plots (plotly backend)\n\nAnimations work on the plotly backend too: `animate=True` reveals\ntrajectories through a sliding time window and `animate='spin'` rotates the\ncamera, each with interactive play/pause controls in notebooks. Animations\non either backend export to `.gif`, animated `.png`, or `.mp4` -- the file\nextension picks the format.\n"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "collapsed": false
+ },
+ "outputs": [],
+ "source": [
+ "# Code source: Contextual Dynamics Lab\n# License: MIT\n\nimport numpy as np\nimport hypertools as hyp\n\ndata = np.cumsum(np.random.default_rng(42).standard_normal((200, 8)),\n axis=0)\n\n# interactive animation (play/pause controls in notebooks)\nhyp.plot(data, animate=True, duration=30, backend='plotly')\n\n# export an animation: extension picks the format (.gif / .png / .mp4)\nhyp.plot(data, animate='spin', duration=30, backend='plotly',\n save_path='spin.gif', show=False)"
+ ]
+ }
+ ],
+ "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.12.10"
+ }
},
- {
- "cell_type": "markdown",
- "metadata": {},
- "source": [
- "\n# Animated interactive plots (plotly backend)\n\nAnimations work on the plotly backend too: `animate=True` reveals\ntrajectories through a sliding time window and `animate='spin'` rotates the\ncamera, each with interactive play/pause controls in notebooks. Animations\non either backend export to `.gif`, animated `.png`, or `.mp4` -- the file\nextension picks the format.\n"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {
- "collapsed": false
- },
- "outputs": [],
- "source": [
- "# Code source: Contextual Dynamics Lab\n# License: MIT\n\nimport numpy as np\nimport hypertools as hyp\n\ndata = np.cumsum(np.random.default_rng(42).standard_normal((200, 8)),\n axis=0)\n\n# interactive animation (play/pause controls in notebooks)\nhyp.plot(data, animate=True, duration=30, backend='plotly')\n\n# export an animation: extension picks the format (.gif / .png / .mp4)\nhyp.plot(data, animate='spin', duration=30, backend='plotly',\n save_path='spin.gif', show=False)"
- ]
- }
- ],
- "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.12.10"
- }
- },
- "nbformat": 4,
- "nbformat_minor": 0
-}
+ "nbformat": 4,
+ "nbformat_minor": 0
+}
\ No newline at end of file
diff --git a/docs/auto_examples/animate_plotly.rst b/docs/auto_examples/animate_plotly.rst
index 70a49751..07b05327 100644
--- a/docs/auto_examples/animate_plotly.rst
+++ b/docs/auto_examples/animate_plotly.rst
@@ -36,21 +36,19 @@ extension picks the format.
:file: images/sphx_glr_animate_plotly_001.html
-.. rst-class:: sphx-glr-script-out
-
- .. code-block:: none
-
- Resorting to unclean kill browser.
- Resorting to unclean kill browser.
- Resorting to unclean kill browser.
- Resorting to unclean kill browser.
-
-
-
-
+.. raw:: html
-|
+
+
+
.. code-block:: Python
@@ -74,7 +72,7 @@ extension picks the format.
.. rst-class:: sphx-glr-timing
- **Total running time of the script:** (47 minutes 29.943 seconds)
+ **Total running time of the script:** (46 minutes 56.795 seconds)
.. _sphx_glr_download_auto_examples_animate_plotly.py:
diff --git a/docs/auto_examples/animate_plotly.zip b/docs/auto_examples/animate_plotly.zip
index 3f446d4f..0d2468ac 100644
Binary files a/docs/auto_examples/animate_plotly.zip and b/docs/auto_examples/animate_plotly.zip differ
diff --git a/docs/auto_examples/animate_spin.codeobj.json b/docs/auto_examples/animate_spin.codeobj.json
index 1503c46f..dc560397 100644
--- a/docs/auto_examples/animate_spin.codeobj.json
+++ b/docs/auto_examples/animate_spin.codeobj.json
@@ -15,71 +15,48 @@
"name": "FuncAnimation"
}
],
- "ani_geo": [
+ "data": [
{
"is_class": false,
"is_explicit": false,
- "module": "hypertools.datageometry",
- "module_short": "hypertools",
- "name": "DataGeometry"
- },
- {
- "is_class": false,
- "is_explicit": false,
- "module": "hypertools",
- "module_short": "hypertools",
- "name": "DataGeometry"
+ "module": "builtins",
+ "module_short": "builtins",
+ "name": "list"
}
],
- "ani_geo.line_ani": [
+ "fig": [
{
"is_class": false,
"is_explicit": false,
- "module": "matplotlib.animation",
- "module_short": "matplotlib.animation",
- "name": "FuncAnimation"
+ "module": "matplotlib.figure",
+ "module_short": "matplotlib.figure",
+ "name": "Figure"
},
{
"is_class": false,
"is_explicit": false,
"module": "matplotlib",
"module_short": "matplotlib",
- "name": "FuncAnimation"
+ "name": "Figure"
}
],
- "geo": [
- {
- "is_class": false,
- "is_explicit": false,
- "module": "hypertools.datageometry",
- "module_short": "hypertools",
- "name": "DataGeometry"
- },
- {
- "is_class": false,
- "is_explicit": false,
- "module": "hypertools",
- "module_short": "hypertools",
- "name": "DataGeometry"
- }
- ],
- "geo.plot": [
+ "hyp.load": [
{
"is_class": false,
"is_explicit": false,
- "module": "hypertools.datageometry",
- "module_short": "hypertools",
- "name": "DataGeometry.plot"
+ "module": "builtins",
+ "module_short": "builtins",
+ "name": "function"
},
{
"is_class": false,
"is_explicit": false,
"module": "hypertools",
"module_short": "hypertools",
- "name": "DataGeometry.plot"
+ "name": "load"
}
],
- "hyp.load": [
+ "hyp.plot": [
{
"is_class": false,
"is_explicit": false,
@@ -92,7 +69,7 @@
"is_explicit": false,
"module": "hypertools",
"module_short": "hypertools",
- "name": "load"
+ "name": "plot"
}
]
}
\ No newline at end of file
diff --git a/docs/auto_examples/animate_spin.ipynb b/docs/auto_examples/animate_spin.ipynb
index f7714315..4b4749a7 100644
--- a/docs/auto_examples/animate_spin.ipynb
+++ b/docs/auto_examples/animate_spin.ipynb
@@ -1,58 +1,54 @@
{
- "cells": [
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {
- "collapsed": false
- },
- "outputs": [],
- "source": [
- "# Install hypertools (dev-2.0 preview) -- run this first on Colab.\n",
- "# On release this becomes: %pip install hypertools\n",
- "%pip install -q \"hypertools[interactive] @ git+https://github.com/ContextLab/hypertools.git@dev-2.0\"\n",
- "\n",
- "%matplotlib inline"
- ]
+ "cells": [
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "collapsed": false
+ },
+ "outputs": [],
+ "source": [
+ "# Install hypertools (dev-1.0-refactor preview) -- run this first on Colab.\n# On release this becomes: %pip install hypertools\n%pip install -q \"hypertools[interactive] @ git+https://github.com/ContextLab/hypertools.git@dev-1.0-refactor\"\n\n%matplotlib inline"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "\n# Create a rotating static plot\n\nIn addition to plotting dynamic timeseries data, the spin feature can be used to\nvisualize static data in an animated rotating plot.\n"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "collapsed": false
+ },
+ "outputs": [],
+ "source": [
+ "# sphinx_gallery_thumbnail_path = '_static/thumbnails/sphx_glr_animate_spin_thumb.gif'\n\n# Code source: Andrew Heusser\n# License: MIT\n\n# import hypertools\nimport hypertools as hyp\n\n# load the data\ndata = hyp.load('weights_sample')\n\n# plot\nfig, ani = hyp.plot(data, fmt='.', animate='spin')"
+ ]
+ }
+ ],
+ "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.12.10"
+ }
},
- {
- "cell_type": "markdown",
- "metadata": {},
- "source": [
- "\n# Create a rotating static plot\n\nIn addition to plotting dynamic timeseries data, the spin feature can be used to\nvisualize static data in an animated rotating plot.\n"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {
- "collapsed": false
- },
- "outputs": [],
- "source": [
- "# sphinx_gallery_thumbnail_path = '_static/thumbnails/sphx_glr_animate_spin_thumb.gif'\n\n# Code source: Andrew Heusser\n# License: MIT\n\n# import hypertools\nimport hypertools as hyp\n\n# load the geo\ngeo = hyp.load('weights_sample')\n\n# plot\nani_geo = geo.plot(fmt='.', animate='spin')\nani = ani_geo.line_ani # the underlying matplotlib FuncAnimation"
- ]
- }
- ],
- "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.12.10"
- }
- },
- "nbformat": 4,
- "nbformat_minor": 0
-}
+ "nbformat": 4,
+ "nbformat_minor": 0
+}
\ No newline at end of file
diff --git a/docs/auto_examples/animate_spin.py b/docs/auto_examples/animate_spin.py
index 0d59889d..9ce8b709 100644
--- a/docs/auto_examples/animate_spin.py
+++ b/docs/auto_examples/animate_spin.py
@@ -16,9 +16,8 @@
# import hypertools
import hypertools as hyp
-# load the geo
-geo = hyp.load('weights_sample')
+# load the data
+data = hyp.load('weights_sample')
# plot
-ani_geo = geo.plot(fmt='.', animate='spin')
-ani = ani_geo.line_ani # the underlying matplotlib FuncAnimation
+fig, ani = hyp.plot(data, fmt='.', animate='spin')
diff --git a/docs/auto_examples/animate_spin.py.md5 b/docs/auto_examples/animate_spin.py.md5
index 35783d81..790aa3e6 100644
--- a/docs/auto_examples/animate_spin.py.md5
+++ b/docs/auto_examples/animate_spin.py.md5
@@ -1 +1 @@
-d66b865e2f33dee0ce7852e3a978aa29
\ No newline at end of file
+60a5b9c1c59bdce7c1f4a9b15e877d76
\ No newline at end of file
diff --git a/docs/auto_examples/animate_spin.rst b/docs/auto_examples/animate_spin.rst
index 43264bf3..a678d246 100644
--- a/docs/auto_examples/animate_spin.rst
+++ b/docs/auto_examples/animate_spin.rst
@@ -25,14 +25,14 @@ Create a rotating static plot
In addition to plotting dynamic timeseries data, the spin feature can be used to
visualize static data in an animated rotating plot.
-.. GENERATED FROM PYTHON SOURCE LINES 10-25
+.. GENERATED FROM PYTHON SOURCE LINES 10-24
.. video:: /auto_examples/images/sphx_glr_animate_spin_001.mp4
:class: sphx-glr-single-img
- :height: 480
- :width: 640
+ :height: 960
+ :width: 1280
:autoplay:
@@ -51,17 +51,16 @@ visualize static data in an animated rotating plot.
# import hypertools
import hypertools as hyp
- # load the geo
- geo = hyp.load('weights_sample')
+ # load the data
+ data = hyp.load('weights_sample')
# plot
- ani_geo = geo.plot(fmt='.', animate='spin')
- ani = ani_geo.line_ani # the underlying matplotlib FuncAnimation
+ fig, ani = hyp.plot(data, fmt='.', animate='spin')
.. rst-class:: sphx-glr-timing
- **Total running time of the script:** (0 minutes 7.368 seconds)
+ **Total running time of the script:** (0 minutes 8.160 seconds)
.. _sphx_glr_download_auto_examples_animate_spin.py:
diff --git a/docs/auto_examples/animate_spin.zip b/docs/auto_examples/animate_spin.zip
index bcb8a946..d7302f57 100644
Binary files a/docs/auto_examples/animate_spin.zip and b/docs/auto_examples/animate_spin.zip differ
diff --git a/docs/auto_examples/animate_surface_morph.codeobj.json b/docs/auto_examples/animate_surface_morph.codeobj.json
new file mode 100644
index 00000000..228999c9
--- /dev/null
+++ b/docs/auto_examples/animate_surface_morph.codeobj.json
@@ -0,0 +1,327 @@
+{
+ "ani": [
+ {
+ "is_class": false,
+ "is_explicit": false,
+ "module": "matplotlib.animation",
+ "module_short": "matplotlib.animation",
+ "name": "FuncAnimation"
+ },
+ {
+ "is_class": false,
+ "is_explicit": false,
+ "module": "matplotlib",
+ "module_short": "matplotlib",
+ "name": "FuncAnimation"
+ }
+ ],
+ "clouds": [
+ {
+ "is_class": false,
+ "is_explicit": false,
+ "module": "builtins",
+ "module_short": "builtins",
+ "name": "list"
+ }
+ ],
+ "fig": [
+ {
+ "is_class": false,
+ "is_explicit": false,
+ "module": "matplotlib.figure",
+ "module_short": "matplotlib.figure",
+ "name": "Figure"
+ },
+ {
+ "is_class": false,
+ "is_explicit": false,
+ "module": "matplotlib",
+ "module_short": "matplotlib",
+ "name": "Figure"
+ }
+ ],
+ "fig.axes": [
+ {
+ "is_class": false,
+ "is_explicit": false,
+ "module": "matplotlib.figure",
+ "module_short": "matplotlib.figure",
+ "name": "Figure.axes"
+ },
+ {
+ "is_class": false,
+ "is_explicit": false,
+ "module": "matplotlib",
+ "module_short": "matplotlib",
+ "name": "Figure.axes"
+ },
+ {
+ "is_class": false,
+ "is_explicit": false,
+ "module": "matplotlib.figure",
+ "module_short": "matplotlib.figure",
+ "name": "FigureBase.axes"
+ },
+ {
+ "is_class": false,
+ "is_explicit": false,
+ "module": "matplotlib",
+ "module_short": "matplotlib",
+ "name": "FigureBase.axes"
+ },
+ {
+ "is_class": false,
+ "is_explicit": false,
+ "module": "matplotlib.artist",
+ "module_short": "matplotlib.artist",
+ "name": "Artist.axes"
+ },
+ {
+ "is_class": false,
+ "is_explicit": false,
+ "module": "matplotlib",
+ "module_short": "matplotlib",
+ "name": "Artist.axes"
+ }
+ ],
+ "hyp.load": [
+ {
+ "is_class": false,
+ "is_explicit": false,
+ "module": "builtins",
+ "module_short": "builtins",
+ "name": "function"
+ },
+ {
+ "is_class": false,
+ "is_explicit": false,
+ "module": "hypertools",
+ "module_short": "hypertools",
+ "name": "load"
+ }
+ ],
+ "hyp.plot": [
+ {
+ "is_class": false,
+ "is_explicit": false,
+ "module": "builtins",
+ "module_short": "builtins",
+ "name": "function"
+ },
+ {
+ "is_class": false,
+ "is_explicit": false,
+ "module": "hypertools",
+ "module_short": "hypertools",
+ "name": "plot"
+ }
+ ],
+ "hypertools.plot.meshutil.smooth_hull_3d": [
+ {
+ "is_class": false,
+ "is_explicit": true,
+ "module": "hypertools.plot.meshutil",
+ "module_short": "hypertools.plot.meshutil",
+ "name": "smooth_hull_3d"
+ }
+ ],
+ "load_shape": [
+ {
+ "is_class": false,
+ "is_explicit": false,
+ "module": "builtins",
+ "module_short": "builtins",
+ "name": "function"
+ }
+ ],
+ "n_points": [
+ {
+ "is_class": false,
+ "is_explicit": false,
+ "module": "builtins",
+ "module_short": "builtins",
+ "name": "int"
+ }
+ ],
+ "normalize_shape": [
+ {
+ "is_class": false,
+ "is_explicit": false,
+ "module": "builtins",
+ "module_short": "builtins",
+ "name": "function"
+ }
+ ],
+ "np.abs": [
+ {
+ "is_class": false,
+ "is_explicit": false,
+ "module": "numpy",
+ "module_short": "numpy",
+ "name": "ufunc"
+ },
+ {
+ "is_class": false,
+ "is_explicit": false,
+ "module": "numpy",
+ "module_short": "numpy",
+ "name": "abs"
+ }
+ ],
+ "np.asarray": [
+ {
+ "is_class": false,
+ "is_explicit": false,
+ "module": "builtins",
+ "module_short": "builtins",
+ "name": "builtin_function_or_method"
+ },
+ {
+ "is_class": false,
+ "is_explicit": false,
+ "module": "numpy",
+ "module_short": "numpy",
+ "name": "asarray"
+ }
+ ],
+ "np.float64": [
+ {
+ "is_class": true,
+ "is_explicit": false,
+ "module": "numpy",
+ "module_short": "numpy",
+ "name": "float64"
+ },
+ {
+ "is_class": true,
+ "is_explicit": false,
+ "module": "numpy",
+ "module_short": "numpy",
+ "name": "floating"
+ },
+ {
+ "is_class": true,
+ "is_explicit": false,
+ "module": "builtins",
+ "module_short": "builtins",
+ "name": "float"
+ },
+ {
+ "is_class": true,
+ "is_explicit": false,
+ "module": "numpy",
+ "module_short": "numpy",
+ "name": "inexact"
+ },
+ {
+ "is_class": true,
+ "is_explicit": false,
+ "module": "numpy",
+ "module_short": "numpy",
+ "name": "number"
+ },
+ {
+ "is_class": true,
+ "is_explicit": false,
+ "module": "numpy",
+ "module_short": "numpy",
+ "name": "generic"
+ },
+ {
+ "is_class": false,
+ "is_explicit": false,
+ "module": "numpy",
+ "module_short": "numpy",
+ "name": "float64"
+ }
+ ],
+ "np.random.default_rng": [
+ {
+ "is_class": false,
+ "is_explicit": false,
+ "module": "_cython_3_2_1",
+ "module_short": "_cython_3_2_1",
+ "name": "cython_function_or_method"
+ },
+ {
+ "is_class": false,
+ "is_explicit": false,
+ "module": "numpy.random",
+ "module_short": "numpy.random",
+ "name": "default_rng"
+ }
+ ],
+ "rng": [
+ {
+ "is_class": false,
+ "is_explicit": false,
+ "module": "numpy.random._generator",
+ "module_short": "numpy.random",
+ "name": "Generator"
+ },
+ {
+ "is_class": false,
+ "is_explicit": false,
+ "module": "numpy.random",
+ "module_short": "numpy.random",
+ "name": "Generator"
+ },
+ {
+ "is_class": false,
+ "is_explicit": false,
+ "module": "numpy",
+ "module_short": "numpy",
+ "name": "Generator"
+ }
+ ],
+ "rng.choice": [
+ {
+ "is_class": false,
+ "is_explicit": false,
+ "module": "numpy.random._generator",
+ "module_short": "numpy.random",
+ "name": "Generator.choice"
+ },
+ {
+ "is_class": false,
+ "is_explicit": false,
+ "module": "numpy.random",
+ "module_short": "numpy.random",
+ "name": "Generator.choice"
+ },
+ {
+ "is_class": false,
+ "is_explicit": false,
+ "module": "numpy",
+ "module_short": "numpy",
+ "name": "Generator.choice"
+ }
+ ],
+ "rotations": [
+ {
+ "is_class": false,
+ "is_explicit": false,
+ "module": "builtins",
+ "module_short": "builtins",
+ "name": "list"
+ }
+ ],
+ "shapes": [
+ {
+ "is_class": false,
+ "is_explicit": false,
+ "module": "builtins",
+ "module_short": "builtins",
+ "name": "list"
+ }
+ ],
+ "surface_spec": [
+ {
+ "is_class": false,
+ "is_explicit": false,
+ "module": "builtins",
+ "module_short": "builtins",
+ "name": "dict"
+ }
+ ]
+}
\ No newline at end of file
diff --git a/docs/auto_examples/animate_surface_morph.ipynb b/docs/auto_examples/animate_surface_morph.ipynb
new file mode 100644
index 00000000..71fb1c40
--- /dev/null
+++ b/docs/auto_examples/animate_surface_morph.ipynb
@@ -0,0 +1,54 @@
+{
+ "cells": [
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "collapsed": false
+ },
+ "outputs": [],
+ "source": [
+ "# Install hypertools (dev-1.0-refactor preview) -- run this first on Colab.\n# On release this becomes: %pip install hypertools\n%pip install -q \"hypertools[interactive] @ git+https://github.com/ContextLab/hypertools.git@dev-1.0-refactor\"\n\n%matplotlib inline"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "\n# Morphing hull surfaces through shapes\n\nBuilding on the *Morphing through the shapes zoo* example, HyperTools can\nalso render a smooth, lit convex-hull SURFACE around a moving point cloud\n(the ``surface=`` `hyp.plot` kwarg -- see :mod:`hypertools.plot.meshutil`\nand :mod:`hypertools.plot.surface`, GH #109) instead of just the raw\npoints. Combining `surface=` with `animate='morph'` (PR #272, maintainer\nrequest 2026-07-06) recomputes the moving cloud's smoothed hull mesh from\nscratch on every frame, shaded with a two-light Blinn-Phong model and\nbackface-culled for the current camera angle -- so the \"blob\" skin flows\ncontinuously as the underlying points rearrange themselves, all from one\n`hyp.plot` call. Camera rotation speed (degrees/frame) is always constant\nacross the whole animation, so a per-segment `rotations` list (below)\ncontrols how much SCREEN TIME each hold/transition gets, never how fast\nit spins. Since a convex hull cannot reproduce concave features,\nholds on concave shapes like the bunny necessarily render as a smooth,\nrounded blob; that loss of concavity is an expected trade-off of the\nhull-surface approach, not a bug. Hulls hug the data tightly BY\nCONSTRUCTION (each smoothing round pulls stray vertices back onto the\noriginal hull surface, see :func:`hypertools.plot.meshutil.smooth_hull_3d`)\nrather than via any fixed overshoot allowance, so the axes box never needs\na hand-computed fudge factor to contain the surface. A final, bounded,\ngrow-only rescale then guarantees at least 99% containment of the actual\npoints; for ordinary, reasonably-sampled clouds this rescale rarely does\nmore than nudge the mesh a few percent, and only grows large for very\nsparse clouds (rule of thumb: fewer than ~10 points), where a coarse,\nfew-vertex hull loses proportionally more to smoothing and needs more\ncorrection to recover that same 99% guarantee.\n\nTo keep the gallery build modest, only 5 of the 7 zoo shapes are used\n(dropping the very high point-count dragon and biplane meshes), and each\nshape is downsampled to 400 points before it's ever handed to `hyp.plot`\n(a hull's shape is set by its extreme points, so fewer interior points\nbarely change the rendered surface). This matters for more than just the\nper-frame ``ConvexHull`` cost: `hyp.plot` also builds one full hull per\nmorphing dataset up front (from whatever point cloud it's given) to size\nthe axes box safely for every frame, and a raw shapes-zoo cloud is dense\nenough that its hull can itself have tens of thousands of faces --\ndownsampling first keeps both that one-time sizing pass and every\nper-frame rebuild fast. The mesh smoothing is also capped at 2 rounds\nrather than the library default of 3 (roughly 20ms/frame instead of\n~100ms+, measured on this machine).\n"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "collapsed": false
+ },
+ "outputs": [],
+ "source": [
+ "# Code source: Contextual Dynamics Laboratory\n# License: MIT\n\nimport numpy as np\n\nimport hypertools as hyp\n\n# a subset of the shapes zoo -- smaller point-cloud shapes are used so the\n# per-frame convex-hull/smoothing pass stays fast (see module docstring)\nshapes = ['bunny', 'cube', 'sphere', 'teapot', 'vase']\n\n# a hull's shape is set by its extreme points, not its interior density, so\n# downsampling every shape to 400 points barely changes the rendered\n# surface while keeping both the one-time axes-sizing hull and every\n# per-frame rebuilt hull cheap (see module docstring)\nn_points = 400\nrng = np.random.default_rng(42)\n\n\ndef normalize_shape(points):\n \"\"\"Center and scale a point cloud into the hypertools [-1, 1] cube.\"\"\"\n points = np.asarray(points, dtype=np.float64)\n points = points - points.mean(axis=0)\n return points / np.abs(points).max()\n\n\ndef load_shape(name):\n \"\"\"Load and normalize one shapes-zoo cloud, downsampled to `n_points`\n (without replacement) so its hull stays cheap to compute.\"\"\"\n points = normalize_shape(hyp.load(name))\n idx = rng.choice(len(points), size=min(n_points, len(points)),\n replace=False)\n return points[idx]\n\n\nclouds = [load_shape(shape) for shape in shapes]\n\n# frame schedule: hold, morph, hold, morph, ..., hold -- 2 * n_shapes - 1 =\n# 9 segments in all. Holds get a slow, easy-to-watch full rotation (1)\n# while transitions get a brisk quarter-turn (0.25), so the camera visibly\n# steps forward every time one shape morphs into the next. Camera speed\n# stays CONSTANT (degrees/frame) across the whole animation, so each\n# segment's SCREEN TIME is proportional to its own rotation count instead\n# of split evenly: the 5 full-rotation holds get 60 frames each and the 4\n# quarter-turn transitions get 15 frames each (360 frames total @ 30 fps,\n# 12 sec, same total length as an equal-time split would have given).\nrotations = [1, 0.25] * (len(shapes) - 1) + [1]\n\n# surface look: a solid, lit blue-teal hull with 2 rounds of smoothing\n# (rather than the library default of 3) to keep per-frame cost low; the\n# points are kept only as a faint texture underneath the surface (set\n# below), which does the visual work\nsurface_spec = {\n 'alpha': 0.97,\n 'color': '#2E86AB',\n 'smoothing': 2,\n 'keep_points': True,\n}\n\n# morph_samples=n_points: every shape is ALREADY downsampled to exactly\n# n_points above, so under the new full-sample-with-duplication semantics\n# (PR #272 follow-up, 2026-07-06) this is a no-op safety cap -- no shape\n# ever exceeds n_points, so none is downsampled again and no duplication\n# is needed (all 5 clouds already share the same point count).\nfig, ani = hyp.plot(clouds, fmt='.', color='k', markersize=0.6,\n animate='morph', rotations=rotations,\n duration=12, frame_rate=30,\n morph_samples=n_points, surface=surface_spec)\n\n# fade the point layer to a subtle texture underneath the hull surface\nfig.axes[0].get_lines()[0].set_alpha(0.25)"
+ ]
+ }
+ ],
+ "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.12.10"
+ }
+ },
+ "nbformat": 4,
+ "nbformat_minor": 0
+}
\ No newline at end of file
diff --git a/docs/auto_examples/animate_surface_morph.py b/docs/auto_examples/animate_surface_morph.py
new file mode 100644
index 00000000..ef0df439
--- /dev/null
+++ b/docs/auto_examples/animate_surface_morph.py
@@ -0,0 +1,119 @@
+# -*- coding: utf-8 -*-
+"""
+=====================================
+Morphing hull surfaces through shapes
+=====================================
+
+Building on the *Morphing through the shapes zoo* example, HyperTools can
+also render a smooth, lit convex-hull SURFACE around a moving point cloud
+(the ``surface=`` `hyp.plot` kwarg -- see :mod:`hypertools.plot.meshutil`
+and :mod:`hypertools.plot.surface`, GH #109) instead of just the raw
+points. Combining `surface=` with `animate='morph'` (PR #272, maintainer
+request 2026-07-06) recomputes the moving cloud's smoothed hull mesh from
+scratch on every frame, shaded with a two-light Blinn-Phong model and
+backface-culled for the current camera angle -- so the "blob" skin flows
+continuously as the underlying points rearrange themselves, all from one
+`hyp.plot` call. Camera rotation speed (degrees/frame) is always constant
+across the whole animation, so a per-segment `rotations` list (below)
+controls how much SCREEN TIME each hold/transition gets, never how fast
+it spins. Since a convex hull cannot reproduce concave features,
+holds on concave shapes like the bunny necessarily render as a smooth,
+rounded blob; that loss of concavity is an expected trade-off of the
+hull-surface approach, not a bug. Hulls hug the data tightly BY
+CONSTRUCTION (each smoothing round pulls stray vertices back onto the
+original hull surface, see :func:`hypertools.plot.meshutil.smooth_hull_3d`)
+rather than via any fixed overshoot allowance, so the axes box never needs
+a hand-computed fudge factor to contain the surface. A final, bounded,
+grow-only rescale then guarantees at least 99% containment of the actual
+points; for ordinary, reasonably-sampled clouds this rescale rarely does
+more than nudge the mesh a few percent, and only grows large for very
+sparse clouds (rule of thumb: fewer than ~10 points), where a coarse,
+few-vertex hull loses proportionally more to smoothing and needs more
+correction to recover that same 99% guarantee.
+
+To keep the gallery build modest, only 5 of the 7 zoo shapes are used
+(dropping the very high point-count dragon and biplane meshes), and each
+shape is downsampled to 400 points before it's ever handed to `hyp.plot`
+(a hull's shape is set by its extreme points, so fewer interior points
+barely change the rendered surface). This matters for more than just the
+per-frame ``ConvexHull`` cost: `hyp.plot` also builds one full hull per
+morphing dataset up front (from whatever point cloud it's given) to size
+the axes box safely for every frame, and a raw shapes-zoo cloud is dense
+enough that its hull can itself have tens of thousands of faces --
+downsampling first keeps both that one-time sizing pass and every
+per-frame rebuild fast. The mesh smoothing is also capped at 2 rounds
+rather than the library default of 3 (roughly 20ms/frame instead of
+~100ms+, measured on this machine).
+"""
+
+# Code source: Contextual Dynamics Laboratory
+# License: MIT
+
+import numpy as np
+
+import hypertools as hyp
+
+# a subset of the shapes zoo -- smaller point-cloud shapes are used so the
+# per-frame convex-hull/smoothing pass stays fast (see module docstring)
+shapes = ['bunny', 'cube', 'sphere', 'teapot', 'vase']
+
+# a hull's shape is set by its extreme points, not its interior density, so
+# downsampling every shape to 400 points barely changes the rendered
+# surface while keeping both the one-time axes-sizing hull and every
+# per-frame rebuilt hull cheap (see module docstring)
+n_points = 400
+rng = np.random.default_rng(42)
+
+
+def normalize_shape(points):
+ """Center and scale a point cloud into the hypertools [-1, 1] cube."""
+ points = np.asarray(points, dtype=np.float64)
+ points = points - points.mean(axis=0)
+ return points / np.abs(points).max()
+
+
+def load_shape(name):
+ """Load and normalize one shapes-zoo cloud, downsampled to `n_points`
+ (without replacement) so its hull stays cheap to compute."""
+ points = normalize_shape(hyp.load(name))
+ idx = rng.choice(len(points), size=min(n_points, len(points)),
+ replace=False)
+ return points[idx]
+
+
+clouds = [load_shape(shape) for shape in shapes]
+
+# frame schedule: hold, morph, hold, morph, ..., hold -- 2 * n_shapes - 1 =
+# 9 segments in all. Holds get a slow, easy-to-watch full rotation (1)
+# while transitions get a brisk quarter-turn (0.25), so the camera visibly
+# steps forward every time one shape morphs into the next. Camera speed
+# stays CONSTANT (degrees/frame) across the whole animation, so each
+# segment's SCREEN TIME is proportional to its own rotation count instead
+# of split evenly: the 5 full-rotation holds get 60 frames each and the 4
+# quarter-turn transitions get 15 frames each (360 frames total @ 30 fps,
+# 12 sec, same total length as an equal-time split would have given).
+rotations = [1, 0.25] * (len(shapes) - 1) + [1]
+
+# surface look: a solid, lit blue-teal hull with 2 rounds of smoothing
+# (rather than the library default of 3) to keep per-frame cost low; the
+# points are kept only as a faint texture underneath the surface (set
+# below), which does the visual work
+surface_spec = {
+ 'alpha': 0.97,
+ 'color': '#2E86AB',
+ 'smoothing': 2,
+ 'keep_points': True,
+}
+
+# morph_samples=n_points: every shape is ALREADY downsampled to exactly
+# n_points above, so under the new full-sample-with-duplication semantics
+# (PR #272 follow-up, 2026-07-06) this is a no-op safety cap -- no shape
+# ever exceeds n_points, so none is downsampled again and no duplication
+# is needed (all 5 clouds already share the same point count).
+fig, ani = hyp.plot(clouds, fmt='.', color='k', markersize=0.6,
+ animate='morph', rotations=rotations,
+ duration=12, frame_rate=30,
+ morph_samples=n_points, surface=surface_spec)
+
+# fade the point layer to a subtle texture underneath the hull surface
+fig.axes[0].get_lines()[0].set_alpha(0.25)
diff --git a/docs/auto_examples/animate_surface_morph.py.md5 b/docs/auto_examples/animate_surface_morph.py.md5
new file mode 100644
index 00000000..12cd54de
--- /dev/null
+++ b/docs/auto_examples/animate_surface_morph.py.md5
@@ -0,0 +1 @@
+32455431c69646c09a2549683f1b6277
\ No newline at end of file
diff --git a/docs/auto_examples/animate_surface_morph.rst b/docs/auto_examples/animate_surface_morph.rst
new file mode 100644
index 00000000..448aacf9
--- /dev/null
+++ b/docs/auto_examples/animate_surface_morph.rst
@@ -0,0 +1,185 @@
+
+.. DO NOT EDIT.
+.. THIS FILE WAS AUTOMATICALLY GENERATED BY SPHINX-GALLERY.
+.. TO MAKE CHANGES, EDIT THE SOURCE PYTHON FILE:
+.. "auto_examples/animate_surface_morph.py"
+.. LINE NUMBERS ARE GIVEN BELOW.
+
+.. only:: html
+
+ .. note::
+ :class: sphx-glr-download-link-note
+
+ :ref:`Go to the end `
+ to download the full example code.
+
+.. rst-class:: sphx-glr-example-title
+
+.. _sphx_glr_auto_examples_animate_surface_morph.py:
+
+
+=====================================
+Morphing hull surfaces through shapes
+=====================================
+
+Building on the *Morphing through the shapes zoo* example, HyperTools can
+also render a smooth, lit convex-hull SURFACE around a moving point cloud
+(the ``surface=`` `hyp.plot` kwarg -- see :mod:`hypertools.plot.meshutil`
+and :mod:`hypertools.plot.surface`, GH #109) instead of just the raw
+points. Combining `surface=` with `animate='morph'` (PR #272, maintainer
+request 2026-07-06) recomputes the moving cloud's smoothed hull mesh from
+scratch on every frame, shaded with a two-light Blinn-Phong model and
+backface-culled for the current camera angle -- so the "blob" skin flows
+continuously as the underlying points rearrange themselves, all from one
+`hyp.plot` call. Camera rotation speed (degrees/frame) is always constant
+across the whole animation, so a per-segment `rotations` list (below)
+controls how much SCREEN TIME each hold/transition gets, never how fast
+it spins. Since a convex hull cannot reproduce concave features,
+holds on concave shapes like the bunny necessarily render as a smooth,
+rounded blob; that loss of concavity is an expected trade-off of the
+hull-surface approach, not a bug. Hulls hug the data tightly BY
+CONSTRUCTION (each smoothing round pulls stray vertices back onto the
+original hull surface, see :func:`hypertools.plot.meshutil.smooth_hull_3d`)
+rather than via any fixed overshoot allowance, so the axes box never needs
+a hand-computed fudge factor to contain the surface. A final, bounded,
+grow-only rescale then guarantees at least 99% containment of the actual
+points; for ordinary, reasonably-sampled clouds this rescale rarely does
+more than nudge the mesh a few percent, and only grows large for very
+sparse clouds (rule of thumb: fewer than ~10 points), where a coarse,
+few-vertex hull loses proportionally more to smoothing and needs more
+correction to recover that same 99% guarantee.
+
+To keep the gallery build modest, only 5 of the 7 zoo shapes are used
+(dropping the very high point-count dragon and biplane meshes), and each
+shape is downsampled to 400 points before it's ever handed to `hyp.plot`
+(a hull's shape is set by its extreme points, so fewer interior points
+barely change the rendered surface). This matters for more than just the
+per-frame ``ConvexHull`` cost: `hyp.plot` also builds one full hull per
+morphing dataset up front (from whatever point cloud it's given) to size
+the axes box safely for every frame, and a raw shapes-zoo cloud is dense
+enough that its hull can itself have tens of thousands of faces --
+downsampling first keeps both that one-time sizing pass and every
+per-frame rebuild fast. The mesh smoothing is also capped at 2 rounds
+rather than the library default of 3 (roughly 20ms/frame instead of
+~100ms+, measured on this machine).
+
+.. GENERATED FROM PYTHON SOURCE LINES 48-120
+
+
+
+.. video:: /auto_examples/images/sphx_glr_animate_surface_morph_001.mp4
+ :class: sphx-glr-single-img
+ :height: 480
+ :width: 640
+ :autoplay:
+
+
+
+
+
+
+.. code-block:: Python
+
+
+ # Code source: Contextual Dynamics Laboratory
+ # License: MIT
+
+ import numpy as np
+
+ import hypertools as hyp
+
+ # a subset of the shapes zoo -- smaller point-cloud shapes are used so the
+ # per-frame convex-hull/smoothing pass stays fast (see module docstring)
+ shapes = ['bunny', 'cube', 'sphere', 'teapot', 'vase']
+
+ # a hull's shape is set by its extreme points, not its interior density, so
+ # downsampling every shape to 400 points barely changes the rendered
+ # surface while keeping both the one-time axes-sizing hull and every
+ # per-frame rebuilt hull cheap (see module docstring)
+ n_points = 400
+ rng = np.random.default_rng(42)
+
+
+ def normalize_shape(points):
+ """Center and scale a point cloud into the hypertools [-1, 1] cube."""
+ points = np.asarray(points, dtype=np.float64)
+ points = points - points.mean(axis=0)
+ return points / np.abs(points).max()
+
+
+ def load_shape(name):
+ """Load and normalize one shapes-zoo cloud, downsampled to `n_points`
+ (without replacement) so its hull stays cheap to compute."""
+ points = normalize_shape(hyp.load(name))
+ idx = rng.choice(len(points), size=min(n_points, len(points)),
+ replace=False)
+ return points[idx]
+
+
+ clouds = [load_shape(shape) for shape in shapes]
+
+ # frame schedule: hold, morph, hold, morph, ..., hold -- 2 * n_shapes - 1 =
+ # 9 segments in all. Holds get a slow, easy-to-watch full rotation (1)
+ # while transitions get a brisk quarter-turn (0.25), so the camera visibly
+ # steps forward every time one shape morphs into the next. Camera speed
+ # stays CONSTANT (degrees/frame) across the whole animation, so each
+ # segment's SCREEN TIME is proportional to its own rotation count instead
+ # of split evenly: the 5 full-rotation holds get 60 frames each and the 4
+ # quarter-turn transitions get 15 frames each (360 frames total @ 30 fps,
+ # 12 sec, same total length as an equal-time split would have given).
+ rotations = [1, 0.25] * (len(shapes) - 1) + [1]
+
+ # surface look: a solid, lit blue-teal hull with 2 rounds of smoothing
+ # (rather than the library default of 3) to keep per-frame cost low; the
+ # points are kept only as a faint texture underneath the surface (set
+ # below), which does the visual work
+ surface_spec = {
+ 'alpha': 0.97,
+ 'color': '#2E86AB',
+ 'smoothing': 2,
+ 'keep_points': True,
+ }
+
+ # morph_samples=n_points: every shape is ALREADY downsampled to exactly
+ # n_points above, so under the new full-sample-with-duplication semantics
+ # (PR #272 follow-up, 2026-07-06) this is a no-op safety cap -- no shape
+ # ever exceeds n_points, so none is downsampled again and no duplication
+ # is needed (all 5 clouds already share the same point count).
+ fig, ani = hyp.plot(clouds, fmt='.', color='k', markersize=0.6,
+ animate='morph', rotations=rotations,
+ duration=12, frame_rate=30,
+ morph_samples=n_points, surface=surface_spec)
+
+ # fade the point layer to a subtle texture underneath the hull surface
+ fig.axes[0].get_lines()[0].set_alpha(0.25)
+
+
+.. rst-class:: sphx-glr-timing
+
+ **Total running time of the script:** (1 minutes 57.221 seconds)
+
+
+.. _sphx_glr_download_auto_examples_animate_surface_morph.py:
+
+.. only:: html
+
+ .. container:: sphx-glr-footer sphx-glr-footer-example
+
+ .. container:: sphx-glr-download sphx-glr-download-jupyter
+
+ :download:`Download Jupyter notebook: animate_surface_morph.ipynb `
+
+ .. container:: sphx-glr-download sphx-glr-download-python
+
+ :download:`Download Python source code: animate_surface_morph.py `
+
+ .. container:: sphx-glr-download sphx-glr-download-zip
+
+ :download:`Download zipped: animate_surface_morph.zip `
+
+
+.. only:: html
+
+ .. rst-class:: sphx-glr-signature
+
+ `Gallery generated by Sphinx-Gallery `_
diff --git a/docs/auto_examples/animate_surface_morph.zip b/docs/auto_examples/animate_surface_morph.zip
new file mode 100644
index 00000000..59df2fad
Binary files /dev/null and b/docs/auto_examples/animate_surface_morph.zip differ
diff --git a/docs/auto_examples/animate_trails_mix.codeobj.json b/docs/auto_examples/animate_trails_mix.codeobj.json
new file mode 100644
index 00000000..0bb5da5e
--- /dev/null
+++ b/docs/auto_examples/animate_trails_mix.codeobj.json
@@ -0,0 +1,214 @@
+{
+ "ani": [
+ {
+ "is_class": false,
+ "is_explicit": false,
+ "module": "matplotlib.animation",
+ "module_short": "matplotlib.animation",
+ "name": "FuncAnimation"
+ },
+ {
+ "is_class": false,
+ "is_explicit": false,
+ "module": "matplotlib",
+ "module_short": "matplotlib",
+ "name": "FuncAnimation"
+ }
+ ],
+ "data_a": [
+ {
+ "is_class": false,
+ "is_explicit": false,
+ "module": "numpy",
+ "module_short": "numpy",
+ "name": "ndarray"
+ }
+ ],
+ "data_b": [
+ {
+ "is_class": false,
+ "is_explicit": false,
+ "module": "numpy",
+ "module_short": "numpy",
+ "name": "ndarray"
+ }
+ ],
+ "data_c": [
+ {
+ "is_class": false,
+ "is_explicit": false,
+ "module": "numpy",
+ "module_short": "numpy",
+ "name": "ndarray"
+ }
+ ],
+ "fig": [
+ {
+ "is_class": false,
+ "is_explicit": false,
+ "module": "matplotlib.figure",
+ "module_short": "matplotlib.figure",
+ "name": "Figure"
+ },
+ {
+ "is_class": false,
+ "is_explicit": false,
+ "module": "matplotlib",
+ "module_short": "matplotlib",
+ "name": "Figure"
+ }
+ ],
+ "hyp.plot": [
+ {
+ "is_class": false,
+ "is_explicit": false,
+ "module": "builtins",
+ "module_short": "builtins",
+ "name": "function"
+ },
+ {
+ "is_class": false,
+ "is_explicit": false,
+ "module": "hypertools",
+ "module_short": "hypertools",
+ "name": "plot"
+ }
+ ],
+ "n_time": [
+ {
+ "is_class": false,
+ "is_explicit": false,
+ "module": "builtins",
+ "module_short": "builtins",
+ "name": "int"
+ }
+ ],
+ "np.column_stack": [
+ {
+ "is_class": false,
+ "is_explicit": false,
+ "module": "numpy",
+ "module_short": "numpy",
+ "name": "_ArrayFunctionDispatcher"
+ },
+ {
+ "is_class": false,
+ "is_explicit": false,
+ "module": "numpy",
+ "module_short": "numpy",
+ "name": "column_stack"
+ }
+ ],
+ "np.cos": [
+ {
+ "is_class": false,
+ "is_explicit": false,
+ "module": "numpy",
+ "module_short": "numpy",
+ "name": "ufunc"
+ },
+ {
+ "is_class": false,
+ "is_explicit": false,
+ "module": "numpy",
+ "module_short": "numpy",
+ "name": "cos"
+ }
+ ],
+ "np.linspace": [
+ {
+ "is_class": false,
+ "is_explicit": false,
+ "module": "numpy",
+ "module_short": "numpy",
+ "name": "_ArrayFunctionDispatcher"
+ },
+ {
+ "is_class": false,
+ "is_explicit": false,
+ "module": "numpy",
+ "module_short": "numpy",
+ "name": "linspace"
+ }
+ ],
+ "np.pi": [
+ {
+ "is_class": false,
+ "is_explicit": false,
+ "module": "builtins",
+ "module_short": "builtins",
+ "name": "float"
+ },
+ {
+ "is_class": false,
+ "is_explicit": false,
+ "module": "numpy",
+ "module_short": "numpy",
+ "name": "pi"
+ }
+ ],
+ "np.random.default_rng": [
+ {
+ "is_class": false,
+ "is_explicit": false,
+ "module": "_cython_3_2_1",
+ "module_short": "_cython_3_2_1",
+ "name": "cython_function_or_method"
+ },
+ {
+ "is_class": false,
+ "is_explicit": false,
+ "module": "numpy.random",
+ "module_short": "numpy.random",
+ "name": "default_rng"
+ }
+ ],
+ "np.sin": [
+ {
+ "is_class": false,
+ "is_explicit": false,
+ "module": "numpy",
+ "module_short": "numpy",
+ "name": "ufunc"
+ },
+ {
+ "is_class": false,
+ "is_explicit": false,
+ "module": "numpy",
+ "module_short": "numpy",
+ "name": "sin"
+ }
+ ],
+ "rng": [
+ {
+ "is_class": false,
+ "is_explicit": false,
+ "module": "numpy.random._generator",
+ "module_short": "numpy.random",
+ "name": "Generator"
+ },
+ {
+ "is_class": false,
+ "is_explicit": false,
+ "module": "numpy.random",
+ "module_short": "numpy.random",
+ "name": "Generator"
+ },
+ {
+ "is_class": false,
+ "is_explicit": false,
+ "module": "numpy",
+ "module_short": "numpy",
+ "name": "Generator"
+ }
+ ],
+ "t": [
+ {
+ "is_class": false,
+ "is_explicit": false,
+ "module": "numpy",
+ "module_short": "numpy",
+ "name": "ndarray"
+ }
+ ]
+}
\ No newline at end of file
diff --git a/docs/auto_examples/animate_trails_mix.ipynb b/docs/auto_examples/animate_trails_mix.ipynb
new file mode 100644
index 00000000..473eeace
--- /dev/null
+++ b/docs/auto_examples/animate_trails_mix.ipynb
@@ -0,0 +1,54 @@
+{
+ "cells": [
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "collapsed": false
+ },
+ "outputs": [],
+ "source": [
+ "# Install hypertools (dev-1.0-refactor preview) -- run this first on Colab.\n# On release this becomes: %pip install hypertools\n%pip install -q \"hypertools[interactive] @ git+https://github.com/ContextLab/hypertools.git@dev-1.0-refactor\"\n\n%matplotlib inline"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "\n# Mixing trail styles per dataset\n\n`chemtrails`, `precog`, and `bullettime` each accept a per-dataset list of\nbools instead of a single bool, so different datasets in the same animation\ncan show different trail styles (GH #127): a low-opacity trace of the past\n(chemtrails), of the future (precog), or of the entire timeseries at once\n(bullettime -- equivalent to chemtrails AND precog together).\n"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "collapsed": false
+ },
+ "outputs": [],
+ "source": [
+ "# Code source: Contextual Dynamics Laboratory\n# License: MIT\n\nimport numpy as np\n\nimport hypertools as hyp\n\nrng = np.random.default_rng(4)\nn_time = 40\nt = np.linspace(0, 4 * np.pi, n_time)\n\ndata_a = np.column_stack([np.cos(t), np.sin(t), t / 4])\ndata_b = np.column_stack([np.cos(t), -np.sin(t), t / 4]) + [3, 0, 0]\ndata_c = np.column_stack([np.sin(t), np.cos(t), t / 4]) + [-3, 0, 0]\n\n# dataset 0: chemtrails (past trail) only\n# dataset 1: precog (future trail) only\n# dataset 2: bullettime (full trail, past + future) only\nfig, ani = hyp.plot(\n [data_a, data_b, data_c],\n animate=True,\n chemtrails=[True, False, False],\n precog=[False, True, False],\n bullettime=[False, False, True],\n)"
+ ]
+ }
+ ],
+ "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.12.10"
+ }
+ },
+ "nbformat": 4,
+ "nbformat_minor": 0
+}
\ No newline at end of file
diff --git a/docs/auto_examples/animate_trails_mix.py b/docs/auto_examples/animate_trails_mix.py
new file mode 100644
index 00000000..00723413
--- /dev/null
+++ b/docs/auto_examples/animate_trails_mix.py
@@ -0,0 +1,38 @@
+# -*- coding: utf-8 -*-
+"""
+=============================
+Mixing trail styles per dataset
+=============================
+
+`chemtrails`, `precog`, and `bullettime` each accept a per-dataset list of
+bools instead of a single bool, so different datasets in the same animation
+can show different trail styles (GH #127): a low-opacity trace of the past
+(chemtrails), of the future (precog), or of the entire timeseries at once
+(bullettime -- equivalent to chemtrails AND precog together).
+"""
+
+# Code source: Contextual Dynamics Laboratory
+# License: MIT
+
+import numpy as np
+
+import hypertools as hyp
+
+rng = np.random.default_rng(4)
+n_time = 40
+t = np.linspace(0, 4 * np.pi, n_time)
+
+data_a = np.column_stack([np.cos(t), np.sin(t), t / 4])
+data_b = np.column_stack([np.cos(t), -np.sin(t), t / 4]) + [3, 0, 0]
+data_c = np.column_stack([np.sin(t), np.cos(t), t / 4]) + [-3, 0, 0]
+
+# dataset 0: chemtrails (past trail) only
+# dataset 1: precog (future trail) only
+# dataset 2: bullettime (full trail, past + future) only
+fig, ani = hyp.plot(
+ [data_a, data_b, data_c],
+ animate=True,
+ chemtrails=[True, False, False],
+ precog=[False, True, False],
+ bullettime=[False, False, True],
+)
diff --git a/docs/auto_examples/animate_trails_mix.py.md5 b/docs/auto_examples/animate_trails_mix.py.md5
new file mode 100644
index 00000000..603015cc
--- /dev/null
+++ b/docs/auto_examples/animate_trails_mix.py.md5
@@ -0,0 +1 @@
+faa3c47aa4604a05bf314e0e9932cdfb
\ No newline at end of file
diff --git a/docs/auto_examples/animate_trails_mix.rst b/docs/auto_examples/animate_trails_mix.rst
new file mode 100644
index 00000000..abc53adc
--- /dev/null
+++ b/docs/auto_examples/animate_trails_mix.rst
@@ -0,0 +1,104 @@
+
+.. DO NOT EDIT.
+.. THIS FILE WAS AUTOMATICALLY GENERATED BY SPHINX-GALLERY.
+.. TO MAKE CHANGES, EDIT THE SOURCE PYTHON FILE:
+.. "auto_examples/animate_trails_mix.py"
+.. LINE NUMBERS ARE GIVEN BELOW.
+
+.. only:: html
+
+ .. note::
+ :class: sphx-glr-download-link-note
+
+ :ref:`Go to the end `
+ to download the full example code.
+
+.. rst-class:: sphx-glr-example-title
+
+.. _sphx_glr_auto_examples_animate_trails_mix.py:
+
+
+=============================
+Mixing trail styles per dataset
+=============================
+
+`chemtrails`, `precog`, and `bullettime` each accept a per-dataset list of
+bools instead of a single bool, so different datasets in the same animation
+can show different trail styles (GH #127): a low-opacity trace of the past
+(chemtrails), of the future (precog), or of the entire timeseries at once
+(bullettime -- equivalent to chemtrails AND precog together).
+
+.. GENERATED FROM PYTHON SOURCE LINES 13-39
+
+
+
+.. video:: /auto_examples/images/sphx_glr_animate_trails_mix_001.mp4
+ :class: sphx-glr-single-img
+ :height: 480
+ :width: 640
+ :autoplay:
+
+
+
+
+
+
+.. code-block:: Python
+
+
+ # Code source: Contextual Dynamics Laboratory
+ # License: MIT
+
+ import numpy as np
+
+ import hypertools as hyp
+
+ rng = np.random.default_rng(4)
+ n_time = 40
+ t = np.linspace(0, 4 * np.pi, n_time)
+
+ data_a = np.column_stack([np.cos(t), np.sin(t), t / 4])
+ data_b = np.column_stack([np.cos(t), -np.sin(t), t / 4]) + [3, 0, 0]
+ data_c = np.column_stack([np.sin(t), np.cos(t), t / 4]) + [-3, 0, 0]
+
+ # dataset 0: chemtrails (past trail) only
+ # dataset 1: precog (future trail) only
+ # dataset 2: bullettime (full trail, past + future) only
+ fig, ani = hyp.plot(
+ [data_a, data_b, data_c],
+ animate=True,
+ chemtrails=[True, False, False],
+ precog=[False, True, False],
+ bullettime=[False, False, True],
+ )
+
+
+.. rst-class:: sphx-glr-timing
+
+ **Total running time of the script:** (0 minutes 6.351 seconds)
+
+
+.. _sphx_glr_download_auto_examples_animate_trails_mix.py:
+
+.. only:: html
+
+ .. container:: sphx-glr-footer sphx-glr-footer-example
+
+ .. container:: sphx-glr-download sphx-glr-download-jupyter
+
+ :download:`Download Jupyter notebook: animate_trails_mix.ipynb `
+
+ .. container:: sphx-glr-download sphx-glr-download-python
+
+ :download:`Download Python source code: animate_trails_mix.py `
+
+ .. container:: sphx-glr-download sphx-glr-download-zip
+
+ :download:`Download zipped: animate_trails_mix.zip `
+
+
+.. only:: html
+
+ .. rst-class:: sphx-glr-signature
+
+ `Gallery generated by Sphinx-Gallery `_
diff --git a/docs/auto_examples/animate_trails_mix.zip b/docs/auto_examples/animate_trails_mix.zip
new file mode 100644
index 00000000..51a80c45
Binary files /dev/null and b/docs/auto_examples/animate_trails_mix.zip differ
diff --git a/docs/auto_examples/auto_examples_jupyter.zip b/docs/auto_examples/auto_examples_jupyter.zip
index 7dec4750..435a7a5a 100644
Binary files a/docs/auto_examples/auto_examples_jupyter.zip and b/docs/auto_examples/auto_examples_jupyter.zip differ
diff --git a/docs/auto_examples/auto_examples_python.zip b/docs/auto_examples/auto_examples_python.zip
index 7f952d5a..10167e70 100644
Binary files a/docs/auto_examples/auto_examples_python.zip and b/docs/auto_examples/auto_examples_python.zip differ
diff --git a/docs/auto_examples/chemtrails.codeobj.json b/docs/auto_examples/chemtrails.codeobj.json
index 1503c46f..dc560397 100644
--- a/docs/auto_examples/chemtrails.codeobj.json
+++ b/docs/auto_examples/chemtrails.codeobj.json
@@ -15,71 +15,48 @@
"name": "FuncAnimation"
}
],
- "ani_geo": [
+ "data": [
{
"is_class": false,
"is_explicit": false,
- "module": "hypertools.datageometry",
- "module_short": "hypertools",
- "name": "DataGeometry"
- },
- {
- "is_class": false,
- "is_explicit": false,
- "module": "hypertools",
- "module_short": "hypertools",
- "name": "DataGeometry"
+ "module": "builtins",
+ "module_short": "builtins",
+ "name": "list"
}
],
- "ani_geo.line_ani": [
+ "fig": [
{
"is_class": false,
"is_explicit": false,
- "module": "matplotlib.animation",
- "module_short": "matplotlib.animation",
- "name": "FuncAnimation"
+ "module": "matplotlib.figure",
+ "module_short": "matplotlib.figure",
+ "name": "Figure"
},
{
"is_class": false,
"is_explicit": false,
"module": "matplotlib",
"module_short": "matplotlib",
- "name": "FuncAnimation"
+ "name": "Figure"
}
],
- "geo": [
- {
- "is_class": false,
- "is_explicit": false,
- "module": "hypertools.datageometry",
- "module_short": "hypertools",
- "name": "DataGeometry"
- },
- {
- "is_class": false,
- "is_explicit": false,
- "module": "hypertools",
- "module_short": "hypertools",
- "name": "DataGeometry"
- }
- ],
- "geo.plot": [
+ "hyp.load": [
{
"is_class": false,
"is_explicit": false,
- "module": "hypertools.datageometry",
- "module_short": "hypertools",
- "name": "DataGeometry.plot"
+ "module": "builtins",
+ "module_short": "builtins",
+ "name": "function"
},
{
"is_class": false,
"is_explicit": false,
"module": "hypertools",
"module_short": "hypertools",
- "name": "DataGeometry.plot"
+ "name": "load"
}
],
- "hyp.load": [
+ "hyp.plot": [
{
"is_class": false,
"is_explicit": false,
@@ -92,7 +69,7 @@
"is_explicit": false,
"module": "hypertools",
"module_short": "hypertools",
- "name": "load"
+ "name": "plot"
}
]
}
\ No newline at end of file
diff --git a/docs/auto_examples/chemtrails.ipynb b/docs/auto_examples/chemtrails.ipynb
index 6f5a30ac..a28c56ca 100644
--- a/docs/auto_examples/chemtrails.ipynb
+++ b/docs/auto_examples/chemtrails.ipynb
@@ -1,58 +1,54 @@
{
- "cells": [
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {
- "collapsed": false
- },
- "outputs": [],
- "source": [
- "# Install hypertools (dev-2.0 preview) -- run this first on Colab.\n",
- "# On release this becomes: %pip install hypertools\n",
- "%pip install -q \"hypertools[interactive] @ git+https://github.com/ContextLab/hypertools.git@dev-2.0\"\n",
- "\n",
- "%matplotlib inline"
- ]
+ "cells": [
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "collapsed": false
+ },
+ "outputs": [],
+ "source": [
+ "# Install hypertools (dev-1.0-refactor preview) -- run this first on Colab.\n# On release this becomes: %pip install hypertools\n%pip install -q \"hypertools[interactive] @ git+https://github.com/ContextLab/hypertools.git@dev-1.0-refactor\"\n\n%matplotlib inline"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "\n# Chemtrails\n\nThe past trajectory of an animated plot can be visualized with the chemtrails\nargument. This displays a low opacity version of the trace behind the\ncurrent points being plotted. This can be used in conjunction with the\nprecog argument to plot a low-opacity trace of the entire timeseries.\n"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "collapsed": false
+ },
+ "outputs": [],
+ "source": [
+ "# sphinx_gallery_thumbnail_path = '_static/thumbnails/sphx_glr_chemtrails_thumb.gif'\n\n# Code source: Andrew Heusser\n# License: MIT\n\n# import\nimport hypertools as hyp\n\n# load example data\ndata = hyp.load('weights_avg')\n\n# plot\nfig, ani = hyp.plot(data, animate=True, chemtrails=True)"
+ ]
+ }
+ ],
+ "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.12.10"
+ }
},
- {
- "cell_type": "markdown",
- "metadata": {},
- "source": [
- "\n# Chemtrails\n\nThe past trajectory of an animated plot can be visualized with the chemtrails\nargument. This displays a low opacity version of the trace behind the\ncurrent points being plotted. This can be used in conjunction with the\nprecog argument to plot a low-opacity trace of the entire timeseries.\n"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {
- "collapsed": false
- },
- "outputs": [],
- "source": [
- "# sphinx_gallery_thumbnail_path = '_static/thumbnails/sphx_glr_chemtrails_thumb.gif'\n\n# Code source: Andrew Heusser\n# License: MIT\n\n# import\nimport hypertools as hyp\n\n# load example data\ngeo = hyp.load('weights_avg')\n\n# plot\nani_geo = geo.plot(animate=True, chemtrails=True)\nani = ani_geo.line_ani # the underlying matplotlib FuncAnimation"
- ]
- }
- ],
- "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.12.10"
- }
- },
- "nbformat": 4,
- "nbformat_minor": 0
-}
+ "nbformat": 4,
+ "nbformat_minor": 0
+}
\ No newline at end of file
diff --git a/docs/auto_examples/chemtrails.py b/docs/auto_examples/chemtrails.py
index 6d7b3814..d421c08b 100644
--- a/docs/auto_examples/chemtrails.py
+++ b/docs/auto_examples/chemtrails.py
@@ -19,8 +19,7 @@
import hypertools as hyp
# load example data
-geo = hyp.load('weights_avg')
+data = hyp.load('weights_avg')
# plot
-ani_geo = geo.plot(animate=True, chemtrails=True)
-ani = ani_geo.line_ani # the underlying matplotlib FuncAnimation
+fig, ani = hyp.plot(data, animate=True, chemtrails=True)
diff --git a/docs/auto_examples/chemtrails.py.md5 b/docs/auto_examples/chemtrails.py.md5
index 13fddaba..b42d8daa 100644
--- a/docs/auto_examples/chemtrails.py.md5
+++ b/docs/auto_examples/chemtrails.py.md5
@@ -1 +1 @@
-9661f229ffed663458068c130b272445
\ No newline at end of file
+0642dc809cbac34d0382029d6964571d
\ No newline at end of file
diff --git a/docs/auto_examples/chemtrails.rst b/docs/auto_examples/chemtrails.rst
index 7782ea00..bb2fdcb2 100644
--- a/docs/auto_examples/chemtrails.rst
+++ b/docs/auto_examples/chemtrails.rst
@@ -27,7 +27,7 @@ argument. This displays a low opacity version of the trace behind the
current points being plotted. This can be used in conjunction with the
precog argument to plot a low-opacity trace of the entire timeseries.
-.. GENERATED FROM PYTHON SOURCE LINES 12-27
+.. GENERATED FROM PYTHON SOURCE LINES 12-26
@@ -54,16 +54,15 @@ precog argument to plot a low-opacity trace of the entire timeseries.
import hypertools as hyp
# load example data
- geo = hyp.load('weights_avg')
+ data = hyp.load('weights_avg')
# plot
- ani_geo = geo.plot(animate=True, chemtrails=True)
- ani = ani_geo.line_ani # the underlying matplotlib FuncAnimation
+ fig, ani = hyp.plot(data, animate=True, chemtrails=True)
.. rst-class:: sphx-glr-timing
- **Total running time of the script:** (0 minutes 7.915 seconds)
+ **Total running time of the script:** (0 minutes 6.295 seconds)
.. _sphx_glr_download_auto_examples_chemtrails.py:
diff --git a/docs/auto_examples/chemtrails.zip b/docs/auto_examples/chemtrails.zip
index 397fc738..fe1bd48f 100644
Binary files a/docs/auto_examples/chemtrails.zip and b/docs/auto_examples/chemtrails.zip differ
diff --git a/docs/auto_examples/explore.codeobj.json b/docs/auto_examples/explore.codeobj.json
index 11d4a30b..fae7d364 100644
--- a/docs/auto_examples/explore.codeobj.json
+++ b/docs/auto_examples/explore.codeobj.json
@@ -1,37 +1,30 @@
{
- "geo": [
+ "data": [
{
"is_class": false,
"is_explicit": false,
- "module": "hypertools.datageometry",
- "module_short": "hypertools",
- "name": "DataGeometry"
- },
- {
- "is_class": false,
- "is_explicit": false,
- "module": "hypertools",
- "module_short": "hypertools",
- "name": "DataGeometry"
+ "module": "builtins",
+ "module_short": "builtins",
+ "name": "list"
}
],
- "geo.plot": [
+ "hyp.load": [
{
"is_class": false,
"is_explicit": false,
- "module": "hypertools.datageometry",
- "module_short": "hypertools",
- "name": "DataGeometry.plot"
+ "module": "builtins",
+ "module_short": "builtins",
+ "name": "function"
},
{
"is_class": false,
"is_explicit": false,
"module": "hypertools",
"module_short": "hypertools",
- "name": "DataGeometry.plot"
+ "name": "load"
}
],
- "hyp.load": [
+ "hyp.plot": [
{
"is_class": false,
"is_explicit": false,
@@ -44,7 +37,7 @@
"is_explicit": false,
"module": "hypertools",
"module_short": "hypertools",
- "name": "load"
+ "name": "plot"
}
]
}
\ No newline at end of file
diff --git a/docs/auto_examples/explore.ipynb b/docs/auto_examples/explore.ipynb
index ba6773dc..18a1817d 100644
--- a/docs/auto_examples/explore.ipynb
+++ b/docs/auto_examples/explore.ipynb
@@ -1,58 +1,54 @@
{
- "cells": [
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {
- "collapsed": false
- },
- "outputs": [],
- "source": [
- "# Install hypertools (dev-2.0 preview) -- run this first on Colab.\n",
- "# On release this becomes: %pip install hypertools\n",
- "%pip install -q \"hypertools[interactive] @ git+https://github.com/ContextLab/hypertools.git@dev-2.0\"\n",
- "\n",
- "%matplotlib inline"
- ]
+ "cells": [
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "collapsed": false
+ },
+ "outputs": [],
+ "source": [
+ "# Install hypertools (dev-1.0-refactor preview) -- run this first on Colab.\n# On release this becomes: %pip install hypertools\n%pip install -q \"hypertools[interactive] @ git+https://github.com/ContextLab/hypertools.git@dev-1.0-refactor\"\n\n%matplotlib inline"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "\n# Explore mode!\n\nExplore mode is an experimental feature that allows you to (not surprisingly)\nexplore the points in your dataset. When you hover over the points, a label\nwill pop up that will help you identify the datapoint. You can customize the\nlabels by passing a list of labels to the `label(s)` kwarg. Alternatively, if\nyou don't pass a list of labels, the labels will be the index of the datapoint,\nalong with the PCA coordinate.\n"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "collapsed": false
+ },
+ "outputs": [],
+ "source": [
+ "# sphinx_gallery_thumbnail_path = '_static/thumbnails/sphx_glr_explore_thumb.png'\n\n# Code source: Andrew Heusser\n# License: MIT\n\n# import\nimport hypertools as hyp\n\n# load example data\ndata = hyp.load('weights_sample')\n\n# plot\nhyp.plot(data, fmt='.', explore=True)"
+ ]
+ }
+ ],
+ "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.12.10"
+ }
},
- {
- "cell_type": "markdown",
- "metadata": {},
- "source": [
- "\n# Explore mode!\n\nExplore mode is an experimental feature that allows you to (not surprisingly)\nexplore the points in your dataset. When you hover over the points, a label\nwill pop up that will help you identify the datapoint. You can customize the\nlabels by passing a list of labels to the `label(s)` kwarg. Alternatively, if\nyou don't pass a list of labels, the labels will be the index of the datapoint,\nalong with the PCA coordinate.\n"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {
- "collapsed": false
- },
- "outputs": [],
- "source": [
- "# sphinx_gallery_thumbnail_path = '_static/thumbnails/sphx_glr_explore_thumb.png'\n\n# Code source: Andrew Heusser\n# License: MIT\n\n# import\nimport hypertools as hyp\n\n# load example data\ngeo = hyp.load('weights_sample')\n\n# plot\ngeo.plot(fmt='.', explore=True)"
- ]
- }
- ],
- "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.12.10"
- }
- },
- "nbformat": 4,
- "nbformat_minor": 0
-}
+ "nbformat": 4,
+ "nbformat_minor": 0
+}
\ No newline at end of file
diff --git a/docs/auto_examples/explore.py b/docs/auto_examples/explore.py
index 7f397710..ee29adc5 100644
--- a/docs/auto_examples/explore.py
+++ b/docs/auto_examples/explore.py
@@ -21,7 +21,7 @@
import hypertools as hyp
# load example data
-geo = hyp.load('weights_sample')
+data = hyp.load('weights_sample')
# plot
-geo.plot(fmt='.', explore=True)
+hyp.plot(data, fmt='.', explore=True)
diff --git a/docs/auto_examples/explore.py.md5 b/docs/auto_examples/explore.py.md5
index 9de6b0c8..79a041bb 100644
--- a/docs/auto_examples/explore.py.md5
+++ b/docs/auto_examples/explore.py.md5
@@ -1 +1 @@
-45f4a8e6497d26fda8de224c7f4355d8
\ No newline at end of file
+96a34cde5b92640cfac770572d78635f
\ No newline at end of file
diff --git a/docs/auto_examples/explore.rst b/docs/auto_examples/explore.rst
index 3a48e468..4fa0db13 100644
--- a/docs/auto_examples/explore.rst
+++ b/docs/auto_examples/explore.rst
@@ -54,15 +54,15 @@ along with the PCA coordinate.
import hypertools as hyp
# load example data
- geo = hyp.load('weights_sample')
+ data = hyp.load('weights_sample')
# plot
- geo.plot(fmt='.', explore=True)
+ hyp.plot(data, fmt='.', explore=True)
.. rst-class:: sphx-glr-timing
- **Total running time of the script:** (0 minutes 0.104 seconds)
+ **Total running time of the script:** (0 minutes 0.024 seconds)
.. _sphx_glr_download_auto_examples_explore.py:
diff --git a/docs/auto_examples/explore.zip b/docs/auto_examples/explore.zip
index 10acc517..e7c5a7b5 100644
Binary files a/docs/auto_examples/explore.zip and b/docs/auto_examples/explore.zip differ
diff --git a/docs/auto_examples/images/sphx_glr_analyze_001.png b/docs/auto_examples/images/sphx_glr_analyze_001.png
index edd4e523..10721859 100644
Binary files a/docs/auto_examples/images/sphx_glr_analyze_001.png and b/docs/auto_examples/images/sphx_glr_analyze_001.png differ
diff --git a/docs/auto_examples/images/sphx_glr_animate_001.gif b/docs/auto_examples/images/sphx_glr_animate_001.gif
index ae2ac712..f8e6a56c 100644
Binary files a/docs/auto_examples/images/sphx_glr_animate_001.gif and b/docs/auto_examples/images/sphx_glr_animate_001.gif differ
diff --git a/docs/auto_examples/images/sphx_glr_animate_001.mp4 b/docs/auto_examples/images/sphx_glr_animate_001.mp4
index e7709f5c..43057341 100644
Binary files a/docs/auto_examples/images/sphx_glr_animate_001.mp4 and b/docs/auto_examples/images/sphx_glr_animate_001.mp4 differ
diff --git a/docs/auto_examples/images/sphx_glr_animate_MDS_001.gif b/docs/auto_examples/images/sphx_glr_animate_MDS_001.gif
index 3516503a..0778128e 100644
Binary files a/docs/auto_examples/images/sphx_glr_animate_MDS_001.gif and b/docs/auto_examples/images/sphx_glr_animate_MDS_001.gif differ
diff --git a/docs/auto_examples/images/sphx_glr_animate_MDS_001.mp4 b/docs/auto_examples/images/sphx_glr_animate_MDS_001.mp4
index 2db0fd71..e4fb2ebc 100644
Binary files a/docs/auto_examples/images/sphx_glr_animate_MDS_001.mp4 and b/docs/auto_examples/images/sphx_glr_animate_MDS_001.mp4 differ
diff --git a/docs/auto_examples/images/sphx_glr_animate_plotly_001.html b/docs/auto_examples/images/sphx_glr_animate_plotly_001.html
index 33801b9a..a5c4d286 100644
--- a/docs/auto_examples/images/sphx_glr_animate_plotly_001.html
+++ b/docs/auto_examples/images/sphx_glr_animate_plotly_001.html
@@ -2,8 +2,8 @@