diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 782cc34..65afead 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -2,24 +2,24 @@ ci: autoupdate_schedule: "quarterly" repos: - repo: https://github.com/pre-commit/pre-commit-hooks - rev: v4.3.0 + rev: v4.4.0 hooks: - id: check-docstring-first - id: end-of-file-fixer - id: trailing-whitespace - repo: https://github.com/psf/black - rev: 22.8.0 + rev: 23.3.0 hooks: - id: black - repo: https://github.com/pycqa/isort - rev: 5.10.1 + rev: 5.12.0 hooks: - id: isort - repo: https://github.com/nbQA-dev/nbQA - rev: 1.5.2 + rev: 1.7.0 hooks: - id: nbqa-black - id: nbqa-isort @@ -30,25 +30,12 @@ repos: - id: nbstripout - repo: https://github.com/pre-commit/mirrors-prettier - rev: v3.0.0-alpha.0 + rev: v3.0.0-alpha.6 hooks: - id: prettier - - repo: https://github.com/PyCQA/flake8 - rev: 5.0.4 + - repo: https://github.com/charliermarsh/ruff-pre-commit + rev: v0.0.256 hooks: - - id: flake8 - additional_dependencies: [flake8-typing-imports==1.7.0] - - - repo: https://github.com/PyCQA/autoflake - rev: v1.6.1 - hooks: - - id: autoflake - args: - [ - "--exclude=mpl_interactions/ipyplot.py", - "--in-place", - "--remove-all-unused-imports", - "--ignore-init-module-imports", - "--remove-unused-variables", - ] + - id: ruff + args: [--fix] diff --git a/readthedocs.yml b/.readthedocs.yml similarity index 78% rename from readthedocs.yml rename to .readthedocs.yml index 984ae72..5b04c65 100644 --- a/readthedocs.yml +++ b/.readthedocs.yml @@ -1,13 +1,15 @@ version: 2 +build: + os: ubuntu-22.04 + tools: + python: "3.10" python: - version: 3.7 install: - method: pip path: . extra_requirements: - doc - jupyter - # - requirements: docs/requirements.txt # Build documentation in the docs/ directory with Sphinx sphinx: diff --git a/docs/conf.py b/docs/conf.py index b24d47e..ed0f0b7 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -198,7 +198,7 @@ }, "path_to_docs": "docs", "repository_branch": "master", - "repository_url": "https://github.com/ianhi/mpl-interactions", + "repository_url": "https://github.com/mpl-extensions/mpl-interactions", "use_download_button": True, "use_edit_page_button": True, "use_issues_button": True, @@ -254,4 +254,4 @@ def linkcode_resolve(domain, info): fn = os.path.relpath(fn, start=os.path.dirname(mpl_inter.__file__)) - return f"https://github.com/ianhi/mpl-interactions/blob/master/mpl_interactions/{fn}{linespec}" + return f"https://github.com/mpl-extensions/mpl-interactions/blob/main/mpl_interactions/{fn}{linespec}" diff --git a/docs/contributing.md b/docs/contributing.md index 3b22b01..ee2b324 100644 --- a/docs/contributing.md +++ b/docs/contributing.md @@ -1,10 +1,10 @@ # Contributing -Thanks for thinking of a way to help improve this library! Remember that contributions come in all shapes and sizes beyond writing bug fixes. Contributing to [documentation](#documentation), opening new [issues](https://github.com/ianhi/mpl-interactions/issues) for bugs, asking for clarification on things you find unclear, and requesting new features, are all super valuable contributions. +Thanks for thinking of a way to help improve this library! Remember that contributions come in all shapes and sizes beyond writing bug fixes. Contributing to [documentation](#documentation), opening new [issues](https://github.com/mpl-extensions/mpl-interactions/issues) for bugs, asking for clarification on things you find unclear, and requesting new features, are all super valuable contributions. ## Code Improvements -All development for this library happens on GitHub at [mpl_interactions](https://github.com/ianhi/mpl-interactions). We recommend you work with a [Conda](https://www.anaconda.com/products/individual) environment (or an alternative virtual environment like [`venv`](https://docs.python.org/3/library/venv.html)). +All development for this library happens on GitHub at [mpl_interactions](https://github.com/mpl-extensions/mpl-interactions). We recommend you work with a [Conda](https://www.anaconda.com/products/individual) environment (or an alternative virtual environment like [`venv`](https://docs.python.org/3/library/venv.html)). ```bash git clone @@ -40,7 +40,7 @@ If you are working in a Jupyter Notebook, then in order to see your code changes Using Git/GitHub can confusing (), so if you're new to Git, you may find it helpful to use a program like [GitHub Desktop](https://desktop.github.com) and to follow a [guide](https://github.com/firstcontributions/first-contributions#first-contributions). -Also feel free to ask for help/advice on the relevant GitHub [issue](https://github.com/ianhi/mpl-interactions/issues). +Also feel free to ask for help/advice on the relevant GitHub [issue](https://github.com/extensions/mpl-interactions/issues). ## Documentation @@ -72,4 +72,4 @@ To accomplish, these GIFs are best stored in `docs/_static/images`. Then, below ## Thank you to our current team! -This project follows the [all-contributors](https://github.com/all-contributors/all-contributors) specification. Contributors members can be found on `mpl_interactions`' [README](https://github.com/ianhi/mpl-interactions#contributors-) page. +This project follows the [all-contributors](https://github.com/all-contributors/all-contributors) specification. Contributors members can be found on `mpl_interactions`' [README](https://github.com/extensions/mpl-interactions#contributors-) page. diff --git a/docs/examples/custom-callbacks.ipynb b/docs/examples/custom-callbacks.ipynb index d85f20f..30bf86e 100644 --- a/docs/examples/custom-callbacks.ipynb +++ b/docs/examples/custom-callbacks.ipynb @@ -158,6 +158,8 @@ "# attach a custom callback\n", "\n", "# if running from a script you can just delete the widgets.Output and associated code\n", + "\n", + "\n", "def my_callback(tau, beta):\n", " if tau < 7.5:\n", " ax.tick_params(axis=\"x\", colors=\"red\")\n", diff --git a/docs/examples/devlop/devlop-controller.ipynb b/docs/examples/devlop/devlop-controller.ipynb index f593b32..379c310 100644 --- a/docs/examples/devlop/devlop-controller.ipynb +++ b/docs/examples/devlop/devlop-controller.ipynb @@ -310,7 +310,6 @@ " )\n", "\n", " def update(params, indices):\n", - "\n", " # update plot\n", " for i, f in enumerate(funcs):\n", " if x is not None and not indexed_x:\n", @@ -379,7 +378,6 @@ "\n", " lines = []\n", " for i, f in enumerate(funcs):\n", - "\n", " if x is not None and not indexed_x:\n", " lines.append(ax.plot(x, f(x, **params), **plot_kwargs[i])[0])\n", " elif indexed_x:\n", diff --git a/docs/examples/embed_heatmap_slicer_tk.py b/docs/examples/embed_heatmap_slicer_tk.py index b6e0fdd..4f9b805 100644 --- a/docs/examples/embed_heatmap_slicer_tk.py +++ b/docs/examples/embed_heatmap_slicer_tk.py @@ -1,7 +1,7 @@ """ based on https://matplotlib.org/3.1.0/gallery/user_interfaces/embedding_in_tk_sgskip.html special thanks to @tacaswell for helping on the matplotlib discourse: -https://discourse.matplotlib.org/t/mpl-iteractions-with-gui-outside-of-jupyter-notebooks/21523/6 +https://discourse.matplotlib.org/t/mpl-iteractions-with-gui-outside-of-jupyter-notebooks/21523/6. """ import tkinter diff --git a/docs/examples/gallery/README.md b/docs/examples/gallery/README.md index df74ae8..884f198 100644 --- a/docs/examples/gallery/README.md +++ b/docs/examples/gallery/README.md @@ -1,3 +1,3 @@ # Gallery -The gallery is currently woefully incomplete as it is serving as a test case for developing , which is a very new project. You should follow the tutorials available in the table of contents on the left of the screen in order to learn how to use this package. +The gallery is currently woefully incomplete as it is serving as a test case for developing , which is a very new project. You should follow the tutorials available in the table of contents on the left of the screen in order to learn how to use this package. diff --git a/docs/examples/gallery/heatmap_slicer.py b/docs/examples/gallery/heatmap_slicer.py index ce0f544..ae0fd82 100644 --- a/docs/examples/gallery/heatmap_slicer.py +++ b/docs/examples/gallery/heatmap_slicer.py @@ -1,7 +1,7 @@ """ ============= Heamap Slicer -============= +=============. """ import matplotlib.pyplot as plt diff --git a/docs/examples/gallery/mpl-sliders-same-figure.py b/docs/examples/gallery/mpl-sliders-same-figure.py index 47e0c2f..3fcd9a4 100644 --- a/docs/examples/gallery/mpl-sliders-same-figure.py +++ b/docs/examples/gallery/mpl-sliders-same-figure.py @@ -1,7 +1,7 @@ """ ===================================================== Matplotlib Sliders without a separate Controls Figure -===================================================== +=====================================================. Demonstration of how to provide a matplotlib slider to prevent the creation of a separate controls figure. diff --git a/docs/examples/hyperslicer.ipynb b/docs/examples/hyperslicer.ipynb index 766ff86..374d572 100644 --- a/docs/examples/hyperslicer.ipynb +++ b/docs/examples/hyperslicer.ipynb @@ -230,7 +230,7 @@ "source": [ "### Other ways of specifying axes\n", "\n", - "All of the below are valid calls to hyperslicer, which generally supports omitting any of the labels or names while falling back to integer valued sliders. If you come across bugs relating to omitting values or passing {obj}`None`, please feel free to [open an issue](https://github.com/ianhi/mpl-interactions/issues)." + "All of the below are valid calls to hyperslicer, which generally supports omitting any of the labels or names while falling back to integer valued sliders. If you come across bugs relating to omitting values or passing {obj}`None`, please feel free to [open an issue](https://github.com/mpl-extensions/mpl-interactions/issues)." ] }, { @@ -341,7 +341,7 @@ ], "metadata": { "kernelspec": { - "display_name": "Python 3", + "display_name": "Python 3 (ipykernel)", "language": "python", "name": "python3" }, @@ -355,7 +355,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.8.6" + "version": "3.11.0" } }, "nbformat": 4, diff --git a/docs/examples/rossler-attractor.ipynb b/docs/examples/rossler-attractor.ipynb index 732f624..8aecb48 100644 --- a/docs/examples/rossler-attractor.ipynb +++ b/docs/examples/rossler-attractor.ipynb @@ -40,7 +40,7 @@ "\n", "The Rossler attractor is a 3 dimensional system, but as 3D plots are not yet supported by `mpl_interactions` we will only visualize the `x` and `y` components.\n", "\n", - "**Note:** Matplotlib supports 3D plots, but `mpl_interactions` does not yet support them. That makes this a great place to contribute to `mpl_interactions` if you're interested in doing so. If you want to have a crack at it feel free to comment on [ianhi/mpl-interactions#89](https://github.com/ianhi/mpl-interactions/issues/89) and [`@ianhi`](https://github.com/ianhi) will be happy to help you through the process.\n", + "**Note:** Matplotlib supports 3D plots, but `mpl_interactions` does not yet support them. That makes this a great place to contribute to `mpl_interactions` if you're interested in doing so. If you want to have a crack at it feel free to comment on [mpl-extensions/mpl-interactions#89](https://github.com/mpl-extensions/mpl-interactions/issues/89) and [`@ianhi`](https://github.com/ianhi) will be happy to help you through the process.\n", "\n", "\n", "### Caching\n", @@ -174,7 +174,7 @@ ], "metadata": { "kernelspec": { - "display_name": "Python 3", + "display_name": "Python 3 (ipykernel)", "language": "python", "name": "python3" }, @@ -188,7 +188,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.9.0" + "version": "3.11.0" } }, "nbformat": 4, diff --git a/docs/examples/scatter-selector.ipynb b/docs/examples/scatter-selector.ipynb index 18cdc6e..6b55a9e 100644 --- a/docs/examples/scatter-selector.ipynb +++ b/docs/examples/scatter-selector.ipynb @@ -49,7 +49,7 @@ "\n", "For this example we have pre-cleaned data that we will just load. If you are curious on how the data was originally processed you see the full code at the bottom of this notebook.\n", "\n", - "The datafiles that we load for this example are available for download at " + "The datafiles that we load for this example are available for download at " ] }, { @@ -226,7 +226,7 @@ ], "metadata": { "kernelspec": { - "display_name": "Python 3", + "display_name": "Python 3 (ipykernel)", "language": "python", "name": "python3" }, @@ -240,7 +240,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.7.8" + "version": "3.11.0" } }, "nbformat": 4, diff --git a/docs/examples/text-annotations.ipynb b/docs/examples/text-annotations.ipynb index be18341..dbfa6ce 100644 --- a/docs/examples/text-annotations.ipynb +++ b/docs/examples/text-annotations.ipynb @@ -9,7 +9,7 @@ "\n", "\n", "```{note}\n", - "Support for modifying text is not complete as none of the function implemented support updating `fontdict` or other text properties like size and color. However, the core functionality is there to place text, change it's position, or change what it reads. see https://github.com/ianhi/mpl-interactions/issues/247 for updates.\n", + "Support for modifying text is not complete as none of the function implemented support updating `fontdict` or other text properties like size and color. However, the core functionality is there to place text, change it's position, or change what it reads. see https://github.com/mpl-extensions/mpl-interactions/issues/247 for updates.\n", "```" ] }, @@ -164,7 +164,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.9.9" + "version": "3.11.0" } }, "nbformat": 4, diff --git a/docs/examples/usage.ipynb b/docs/examples/usage.ipynb index a76f59f..e3979a9 100644 --- a/docs/examples/usage.ipynb +++ b/docs/examples/usage.ipynb @@ -453,6 +453,8 @@ "\n", "iplt.title(\"the value of tau is: {tau:.2f}\", controls=controls[\"tau\"])\n", "# you can still use plt commands if this is the active figure\n", + "\n", + "\n", "def ylabel(tau):\n", " return f\"tau/2 is {np.round(tau/2,3)}\"\n", "\n", diff --git a/docs/install.md b/docs/install.md index 79bcb6f..a139c30 100644 --- a/docs/install.md +++ b/docs/install.md @@ -40,7 +40,7 @@ jupyter labextension install @jupyter-widgets/jupyterlab-manager jupyter-matplot ## Development installation -First create your own fork of . +First create your own fork of . ```bash git clone diff --git a/mpl_interactions/__init__.py b/mpl_interactions/__init__.py index 3109beb..9dd1bf5 100644 --- a/mpl_interactions/__init__.py +++ b/mpl_interactions/__init__.py @@ -2,7 +2,6 @@ from ._version import __version__ except ImportError: __version__ = "unkown" -from .deprecations import mpl_interactions_DeprecationWarning from .generic import * from .helpers import * from .pyplot import * diff --git a/mpl_interactions/controller.py b/mpl_interactions/controller.py index 3362860..3763550 100644 --- a/mpl_interactions/controller.py +++ b/mpl_interactions/controller.py @@ -67,7 +67,7 @@ def __init__( def add_kwargs(self, kwargs, slider_formats=None, play_buttons=None, allow_duplicates=False): """ If you pass a redundant kwarg it will just be overwritten - maybe should only raise a warning rather than an error? + maybe should only raise a warning rather than an error?. need to implement matplotlib widgets also a big question is how to dynamically update the display of matplotlib widgets. @@ -109,7 +109,7 @@ def add_kwargs(self, kwargs, slider_formats=None, play_buttons=None, allow_dupli ) if control: self.controls[k] = control - self.vbox.children = list(self.vbox.children) + [control] + self.vbox.children = [*list(self.vbox.children), control] if k == "vmin_vmax": self.params["vmin"] = self.params["vmin_vmax"][0] self.params["vmax"] = self.params["vmin_vmax"][1] @@ -140,19 +140,17 @@ def add_kwargs(self, kwargs, slider_formats=None, play_buttons=None, allow_dupli self.params["vmax"] = self.params["vmin_vmax"][1] def _slider_updated(self, change, key, values): - """ - gotta also give the indices in order to support hyperslicer without horrifying contortions - """ + """gotta also give the indices in order to support hyperslicer without horrifying contortions.""" if values is None: self.params[key] = change["new"] else: c = change["new"] # int casting due to a bug in numpy < 1.19 - # see https://github.com/ianhi/mpl-interactions/pull/155 + # see https://github.com/mpl-extensions/mpl-interactions/pull/155 if isinstance(c, Iterable): # range sliders return two indices # check for iterable as mpl and ipywidgets sliders don't both use - # tuples - https://github.com/ianhi/mpl-interactions/issues/195 + # tuples - https://github.com/mpl-extensions/mpl-interactions/issues/195 self.params[key] = values[[int(c) for c in change["new"]]] if key == "vmin_vmax": self.params["vmin"] = self.params[key][0] @@ -183,7 +181,7 @@ def slider_updated(self, change, key, values): """ thin wrapper to enable splitting of special cased range sliders. e.g. of ``vmin_vmax`` -> ``vmin`` and ``vmax``. In the future maybe - generalize this to any range slider with an underscore in the name? + generalize this to any range slider with an underscore in the name?. """ self._slider_updated(change, key, values) if key == "vmin_vmax": @@ -215,9 +213,7 @@ def register_callback(self, callback, params=None, eager=False): self._register_function(callback, fig=None, params=params) def _register_function(self, f, fig=None, params=None): - """ - if params is None use the entire current set of params - """ + """if params is None use the entire current set of params.""" if params is None: params = self.params.keys() # listify to ensure it's not a reference to dicts keys @@ -306,9 +302,7 @@ def f(i): return anim def display(self): - """ - Display the display the ipywidgets controls or show the control figures - """ + """Display the display the ipywidgets controls or show the control figures.""" if self.use_ipywidgets: ipy_display(self.vbox) else: @@ -317,9 +311,7 @@ def display(self): fig.show() def show(self): - """ - Show the control figures or display the ipywidgets controls - """ + """Show the control figures or display the ipywidgets controls.""" self.display() def _ipython_display_(self): @@ -330,7 +322,7 @@ def __getitem__(self, key): hack to allow calls like interactive_plot(...beta=(0,1), controls = controls["tau"]) also allows [None] to grab None of the current params - to imply that we only want tau from the existing set of commands + to imply that we only want tau from the existing set of commands. I think ideally this would give another controls object with just the given params that has this one as a parent - I think that that is most consistent with @@ -338,7 +330,6 @@ def __getitem__(self, key): But it's not clear how to implement that with all the sliders and such that get created. So for now do a sort of half-measure by returing the controls_proxy object. """ - # make sure keys is a list # bc in gogogo_controls it may get added to another list keys = key @@ -451,7 +442,7 @@ def f(*args, **kwargs): def _gen_param_excluder(added_kwargs): """ Pass through all the original keys, but exclude any kwargs that we added - manually through prep_scalar + manually through prep_scalar. Parameters ---------- diff --git a/mpl_interactions/generic.py b/mpl_interactions/generic.py index ccf6c21..035fc78 100644 --- a/mpl_interactions/generic.py +++ b/mpl_interactions/generic.py @@ -44,7 +44,6 @@ def heatmap_slicer( figsize=(18, 9), **pcolormesh_kwargs, ): - """ Compare horizontal and/or vertical slices across multiple arrays. @@ -333,9 +332,7 @@ def __init__(self, fig, button=3): @property def enabled(self) -> bool: - """ - Status of the panhandler, whether it's enabled or disabled. - """ + """Status of the panhandler, whether it's enabled or disabled.""" return self._id_press is not None and self._id_release is not None def enable(self): @@ -418,9 +415,7 @@ def _mouse_move(self, event): class image_segmenter: - """ - Manually segment an image with the lasso selector. - """ + """Manually segment an image with the lasso selector.""" def __init__( self, @@ -564,7 +559,7 @@ def _onselect(self, verts): self.fig.canvas.draw_idle() def _ipython_display_(self): - display(self.fig.canvas) # noqa: F405, F821 + display(self.fig.canvas) # noqa: F821 def hyperslicer( @@ -594,7 +589,6 @@ def hyperslicer( display_controls=True, **kwargs, ): - """ View slices from a hyperstack of images selected by sliders. Also accepts Xarray.DataArrays in which case the axes names and coordinates will be inferred from the xarray dims and coords. @@ -656,7 +650,6 @@ def hyperslicer( ------- controls """ - arr = np.squeeze(arr) arr_type = "numpy" @@ -697,7 +690,6 @@ def hyperslicer( # Just pass in an array - no kwargs for i in range(arr.ndim - im_dims): - start, stop = None, None name = f"axis{i}" if name in kwargs: diff --git a/mpl_interactions/helpers.py b/mpl_interactions/helpers.py index 2812f71..a22aa71 100644 --- a/mpl_interactions/helpers.py +++ b/mpl_interactions/helpers.py @@ -45,9 +45,7 @@ def sca(ax): - """ - sca that won't fail if figure not managed by pyplot - """ + """sca that won't fail if figure not managed by pyplot.""" try: mpl_sca(ax) except ValueError as e: @@ -88,7 +86,7 @@ def update_datalim_from_xy(ax, x, y, stretch_x=True, stretch_y=True): x : array the new x datavalues to include y : array - the new y datavalues to include + the new y datavalues to include. """ # this part bc scatter not affect by relim # so need this to keep stretchign working for scatter @@ -103,7 +101,7 @@ def is_jagged(seq): """ checks for jaggedness up to two dimensions don't need more because more doesn't make any sense for this library - need this bc numpy is unhappy about being passed jagged arrays now :( + need this bc numpy is unhappy about being passed jagged arrays now :(. """ lens = [] if is_sequence(seq): @@ -140,7 +138,7 @@ def broadcast_to(arr, to_shape, names): import sys xs = np.arange(5) print(sys.getsizeof(xs.nbytes)) - print(sys.getsizeof(np.broadcast_to(xs, (19000, xs.shape[0])))) + print(sys.getsizeof(np.broadcast_to(xs, (19000, xs.shape[0])))). gives 28 and 112. Note 112/28 != 19000 """ @@ -161,9 +159,8 @@ def broadcast_to(arr, to_shape, names): def broadcast_arrays(*args): """ This is a modified version the numpy `broadcast_arrays` function - that uses a version of _broadcast_to that only considers the first axis + that uses a version of _broadcast_to that only considers the first axis. """ - shapes = [array.shape[0] for (array, name) in args] idx = np.argmax(shapes) if all([shapes[0] == s for s in shapes]): @@ -175,15 +172,13 @@ def broadcast_arrays(*args): def broadcast_many(*args): """ helper to call prep_broadcast followed by broadcast arrays - keep as a separate function to keep the idea of broadcast_arrays the same + keep as a separate function to keep the idea of broadcast_arrays the same. """ return broadcast_arrays(*[(prep_broadcast(arg[0]), arg[1]) for arg in args]) def notebook_backend(): - """ - returns True if the backend is ipympl or nbagg, otherwise False - """ + """returns True if the backend is ipympl or nbagg, otherwise False.""" backend = get_backend().lower() if "ipympl" in backend: return True @@ -193,9 +188,7 @@ def notebook_backend(): def callable_else_value(arg, params, cache=None): - """ - returns as a numpy array - """ + """returns as a numpy array.""" if isinstance(arg, Callable): if cache: if arg not in cache: @@ -209,7 +202,7 @@ def callable_else_value(arg, params, cache=None): def callable_else_value_no_cast(arg, params, cache=None): """ doesn't cast to numpy. Useful when working with parametric functions that might - return (x, y) where it's handy to check if the return is a tuple + return (x, y) where it's handy to check if the return is a tuple. """ if isinstance(arg, Callable): if cache: @@ -302,7 +295,6 @@ def kwarg_to_ipywidget(key, val, update, slider_format_string, play_button=None) widget (e.g. HBox) depending on what widget was generated. If a fixed value is returned then control will be *None* """ - control = None if isinstance(val, set): if len(val) == 1: @@ -401,9 +393,7 @@ def kwarg_to_ipywidget(key, val, update, slider_format_string, play_button=None) def extract_num_options(val): - """ - convert a categorical to a number of options - """ + """convert a categorical to a number of options.""" if len(val) == 1: for v in val: if isinstance(v, tuple): @@ -422,7 +412,7 @@ def extract_num_options(val): def changeify(val, update): """ make matplotlib update functions return a dict with key 'new'. - Do this for compatibility with ipywidgets + Do this for compatibility with ipywidgets. """ update({"new": val}) @@ -431,7 +421,7 @@ def changeify_radio(val, labels, update): r""" matplolib radio buttons don't keep track what index is selected. So this figures out what the index is - made a whole function bc its easier to use with partial then + made a whole function bc its easier to use with partial then. There doesn't seem to be a good way to determine which one was clicked if the radio button has multiple identical values but that's wildly niche @@ -465,7 +455,7 @@ def create_mpl_controls_fig(kwargs): n_opts = 0 n_radio = 0 n_sliders = 0 - for key, val in kwargs.items(): + for _key, val in kwargs.items(): if isinstance(val, set): new_opts = extract_num_options(val) if new_opts > 0: @@ -492,7 +482,7 @@ def create_mpl_controls_fig(kwargs): slider_height = 0 radio_height = 0 gap_height = 0 - if not all(map(lambda x: isinstance(x, mwidgets.AxesWidget), kwargs.values())): + if not all(isinstance(x, mwidgets.AxesWidget) for x in kwargs.values()): # if the only kwargs are existing matplotlib widgets don't make a new figure with ioff(): fig = figure() @@ -510,9 +500,7 @@ def create_mpl_controls_fig(kwargs): def create_mpl_selection_slider(ax, label, values, slider_format_string): - """ - creates a slider that behaves similarly to the ipywidgets selection slider - """ + """creates a slider that behaves similarly to the ipywidgets selection slider.""" slider = mwidgets.Slider(ax, label, 0, len(values) - 1, valinit=0, valstep=1) def update_text(val): @@ -525,9 +513,7 @@ def update_text(val): def create_mpl_range_selection_slider(ax, label, values, slider_format_string): - """ - creates a slider that behaves similarly to the ipywidgets selection slider - """ + """creates a slider that behaves similarly to the ipywidgets selection slider.""" slider = RangeSlider(ax, label, 0, len(values) - 1, valinit=(0, len(values) - 1), valstep=1) def update_text(val): @@ -547,7 +533,7 @@ def process_mpl_widget(val, update): """ handle the case of a kwarg being an existing matplotlib widget. This needs to be separate so that the controller can call it when mixing ipywidets and - a widget like scatter_selector without having to create a control figure + a widget like scatter_selector without having to create a control figure. """ if isinstance(val, mwidgets.RadioButtons): # gotta set it to the zeroth index bc there's no reasonable way to determine the current value @@ -589,7 +575,7 @@ def kwarg_to_mpl_widget( cb the callback id new_y - The widget_y to use for the next pass + The widget_y to use for the next pass. """ slider_height, radio_height, gap_height = heights @@ -608,7 +594,7 @@ def kwarg_to_mpl_widget( val = list(val) n = len(val) - longest_len = max(list(map(lambda x: len(list(x)), map(str, val)))) + longest_len = max([len(list(x)) for x in map(str, val)]) # should probably use something based on fontsize rather that .015 width = max(0.15, 0.015 * longest_len) radio_ax = fig.add_axes([0.2, 0.9 - widget_y - radio_height * n, width, radio_height * n]) @@ -689,9 +675,7 @@ def f(): def gogogo_figure(ipympl, ax=None): - """ - gogogo the greatest function name of all - """ + """gogogo the greatest function name of all.""" if ax is None: if ipympl: with ioff(): diff --git a/mpl_interactions/ipyplot.py b/mpl_interactions/ipyplot.py index 17102a1..e69de29 100644 --- a/mpl_interactions/ipyplot.py +++ b/mpl_interactions/ipyplot.py @@ -1,10 +0,0 @@ -from .pyplot import interactive_axhline as axhline -from .pyplot import interactive_axvline as axvline -from .pyplot import interactive_hist as hist -from .pyplot import interactive_imshow as imshow -from .pyplot import interactive_plot as plot -from .pyplot import interactive_scatter as scatter -from .pyplot import interactive_text as text -from .pyplot import interactive_title as title -from .pyplot import interactive_xlabel as xlabel -from .pyplot import interactive_ylabel as ylabel diff --git a/mpl_interactions/pyplot.py b/mpl_interactions/pyplot.py index 2ed55c0..878e55d 100644 --- a/mpl_interactions/pyplot.py +++ b/mpl_interactions/pyplot.py @@ -1,7 +1,8 @@ """Control the output of standard plotting functions such as :func:`~matplotlib.pyplot.plot` and :func:`~matplotlib.pyplot.hist` using sliders and other widgets. When using the ``ipympl`` backend these functions will leverage ipywidgets for the controls, otherwise they will use the built-in -Matplotlib widgets.""" +Matplotlib widgets. +""" from collections.abc import Callable @@ -60,7 +61,7 @@ def interactive_plot( **kwargs, ): """ - Control a plot using widgets + Control a plot using widgets. interactive_plot([x], y, [fmt]) @@ -316,9 +317,10 @@ def interactive_hist( """ Control the contents of a histogram using widgets. - See https://github.com/ianhi/mpl-interactions/pull/73#issue-470638134 for a discussion - of the limitations of this function. These limitations will be improved once - https://github.com/matplotlib/matplotlib/pull/18275 has been merged. + See https://github.com/mpl-extensions/mpl-interactions/pull/73#issue-470638134 + for a discussion of the limitations of this function. These limitations will + can improved once https://github.com/matplotlib/matplotlib/pull/18275 has + been merged. Parameters ---------- @@ -375,7 +377,6 @@ def f(loc, scale): return np.random.randn(1000)*scale + loc interactive_hist(f, loc=(-5, 5, 500), scale=(1, 10, 100)) """ - ipympl = notebook_backend() fig, ax = gogogo_figure(ipympl, ax=ax) ipympl or force_ipywidgets @@ -491,7 +492,6 @@ def interactive_scatter( ------- controls """ - if isinstance(xlim, str): stretch_x = xlim == "stretch" else: @@ -1270,7 +1270,7 @@ def interactive_text( .. note:: - fontdict properties are currently static - see https://github.com/ianhi/mpl-interactions/issues/247 + fontdict properties are currently static - see https://github.com/mpl-extensions/mpl-interactions/issues/247 Parameters @@ -1282,7 +1282,7 @@ def interactive_text( can include {} style formatting. e.g. 'The voltage is {volts:.2f}' fontdict : dict[str] Passed through to the Text object. Currently not dynamically updateable. See - https://github.com/ianhi/mpl-interactions/issues/247 + https://github.com/mpl-extensions/mpl-interactions/issues/247 controls : mpl_interactions.controller.Controls An existing controls object if you want to tie multiple plot elements to the same set of controls diff --git a/mpl_interactions/utils.py b/mpl_interactions/utils.py index 5dcde3a..6324916 100644 --- a/mpl_interactions/utils.py +++ b/mpl_interactions/utils.py @@ -6,7 +6,11 @@ import numpy as np from matplotlib import interactive, is_interactive from matplotlib.pyplot import figure as mpl_figure -from matplotlib.pyplot import install_repl_displayhook, rcParams, uninstall_repl_displayhook +from matplotlib.pyplot import ( + install_repl_displayhook, + rcParams, + uninstall_repl_displayhook, +) from numpy import abs, argmin, asarray from .deprecations import mpl_interactions_DeprecationWarning @@ -23,7 +27,7 @@ class _ioff_class: """ A context manager for turning interactive mode off. Now that https://github.com/matplotlib/matplotlib/pull/17371 has been merged this will - be available via ``plt.ioff`` starting in Matplotlib 3.4 + be available via ``plt.ioff`` starting in Matplotlib 3.4. """ def __call__(self): @@ -92,7 +96,7 @@ def nearest_idx(array, value, axis=None): def indexer(arr, index_name=None, axis=0): """ Utility function for when you want to index an array as part of an interactive function. - For example: ``iplt.plot(indexor(arr), idx = np.arange(5))`` + For example: ``iplt.plot(indexor(arr), idx = np.arange(5))``. Parameters ---------- @@ -109,7 +113,6 @@ def indexer(arr, index_name=None, axis=0): f : function Function to be passed as an argument to an interactive plotting function """ - if index_name is None: idxs = ["idx", "index", "indx", "ind"] else: diff --git a/mpl_interactions/widgets.py b/mpl_interactions/widgets.py index 34b4669..970acc7 100644 --- a/mpl_interactions/widgets.py +++ b/mpl_interactions/widgets.py @@ -7,9 +7,7 @@ class scatter_selector(AxesWidget): - """ - A widget for selecting a point in a scatter plot. callback will receive (index, (x, y)) - """ + """A widget for selecting a point in a scatter plot. callback will receive (index, (x, y)).""" def __init__(self, ax, x, y, pickradius=5, which_button=1, **kwargs): """ @@ -57,7 +55,7 @@ def _process(self, idx, val): def on_changed(self, func): """ - When a point is clicked calll *func* with the newly selected point + When a point is clicked calll *func* with the newly selected point. Parameters ---------- @@ -185,7 +183,7 @@ def _stepped_value(self, val): def disconnect(self, cid): """ - Remove the observer with connection id *cid* + Remove the observer with connection id *cid*. Parameters ---------- @@ -195,7 +193,7 @@ def disconnect(self, cid): self._observers.disconnect(cid) def reset(self): - """Reset the slider to the initial value""" + """Reset the slider to the initial value.""" if self.val != self.valinit: self.set_val(self.valinit) @@ -320,9 +318,7 @@ def __init__( self.set_val(valinit) def _min_in_bounds(self, min): - """ - Ensure the new min value is between valmin and self.val[1] - """ + """Ensure the new min value is between valmin and self.val[1].""" if min <= self.valmin: if not self.closedmin: return self.val[0] @@ -333,9 +329,7 @@ def _min_in_bounds(self, min): return self._stepped_value(min) def _max_in_bounds(self, max): - """ - Ensure the new max value is between valmax and self.val[0] - """ + """Ensure the new max value is between valmax and self.val[0].""" if max >= self.valmax: if not self.closedmax: return self.val[1] @@ -349,9 +343,7 @@ def _value_in_bounds(self, val): return (self._min_in_bounds(val[0]), self._max_in_bounds(val[1])) def _update_val_from_pos(self, pos): - """ - Given a position update the *val* - """ + """Given a position update the *val*.""" idx = np.argmin(np.abs(self.val - pos)) if idx == 0: val = self._min_in_bounds(pos) @@ -398,7 +390,7 @@ def _format(self, val): def set_min(self, min): """ - Set the lower value of the slider to *min* + Set the lower value of the slider to *min*. Parameters ---------- @@ -408,7 +400,7 @@ def set_min(self, min): def set_max(self, max): """ - Set the lower value of the slider to *max* + Set the lower value of the slider to *max*. Parameters ---------- @@ -418,7 +410,7 @@ def set_max(self, max): def set_val(self, val): """ - Set slider value to *val* + Set slider value to *val*. Parameters ---------- @@ -453,7 +445,7 @@ def set_val(self, val): def on_changed(self, func): """ When the slider value is changed call *func* with the new - slider value + slider value. Parameters ---------- diff --git a/mpl_interactions/xarray_helpers.py b/mpl_interactions/xarray_helpers.py index ea93245..1d438fb 100644 --- a/mpl_interactions/xarray_helpers.py +++ b/mpl_interactions/xarray_helpers.py @@ -20,7 +20,6 @@ def choose_datetime_nonsense(arr, timeunit="m"): Array modified to format decently in a slider. """ - if np.issubdtype(arr.dtype, "datetime64"): out = arr.astype(f"datetime64[{timeunit}]") elif np.issubdtype(arr.dtype, "timedelta64"): @@ -77,7 +76,6 @@ def get_hs_extent(xarr, is_color_image=False): Extent argument for imshow. [d0_min, d0_max, d1_min, d1_max] """ - if not is_color_image: dims = xarr.dims[-2:] else: @@ -122,7 +120,7 @@ def get_hs_fmts(xarr, units=None, is_color_image=False): fmt_strs[d] = choose_fmt_str(xarr[d].dtype) if units is not None and units[i] is not None: try: - fmt_strs[d] += " {}".format(units[i]) + fmt_strs[d] += f" {units[i]}" except KeyError: continue return fmt_strs diff --git a/pyproject.toml b/pyproject.toml index 5f0b1ac..13e419d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -28,3 +28,38 @@ addopts = [ "--mpl", "--nbval", ] + + +# https://github.com/charliermarsh/ruff +[tool.ruff] +line-length = 88 +target-version = "py38" +extend-select = [ + "E", # style errors + "F", # flakes + "D", # pydocstyle + "I001", # isort + "U", # pyupgrade + # "N", # pep8-naming + # "S", # bandit + "C", # flake8-comprehensions + "B", # flake8-bugbear + "A001", # flake8-builtins + "RUF", # ruff-specific rules + "M001", # Unused noqa directive +] +extend-ignore = [ + "D100", # Missing docstring in public module + "D107", # Missing docstring in __init__ + "D203", # 1 blank line required before class docstring + "D212", # Multi-line docstring summary should start at the first line + "D213", # Multi-line docstring summary should start at the second line + "D413", # Missing blank line after last section + "D416", # Section name should end with a colon +] + + +[tool.ruff.per-file-ignores] +"tests/*.py" = ["D"] +"__init__.py" = ["E402"] +"docs/conf.py" = ["A001", "C901", "D200", "D400", "D415"] diff --git a/setup.cfg b/setup.cfg index a013e51..e9694a7 100644 --- a/setup.cfg +++ b/setup.cfg @@ -7,10 +7,10 @@ description = Matplotlib aware interact functions long_description = file: README.md long_description_content_type = text/markdown project_urls = - Tracker = https://github.com/ianhi/mpl-interactions/issues - Changelog = https://github.com/ianhi/mpl-interactions/releases + Tracker = https://github.com/mpl-extensions/mpl-interactions/issues + Changelog = https://github.com/mpl-extensions/mpl-interactions/releases Documentation = https://mpl-interactions.rtfd.io - Source = https://github.com/ianhi/mpl-interactions + Source = https://github.com/mpl-extensions/mpl-interactions classifiers = Framework :: Jupyter Framework :: Matplotlib diff --git a/tests/test_helpers.py b/tests/test_helpers.py index 655b71c..f86d5f2 100644 --- a/tests/test_helpers.py +++ b/tests/test_helpers.py @@ -2,7 +2,11 @@ import numpy as np from matplotlib.figure import Figure -from mpl_interactions.helpers import sca, update_datalim_from_bbox, update_datalim_from_xy +from mpl_interactions.helpers import ( + sca, + update_datalim_from_bbox, + update_datalim_from_xy, +) def test_bbox_update():