diff --git a/README.md b/README.md index 4ea78c9..0231c47 100644 --- a/README.md +++ b/README.md @@ -5,7 +5,9 @@ Setvis is a python library for visualising set membership and patterns of missingness in data. -It can be used both programmatically and interactively in a Jupyter notebook (powered by [Bokeh](https://docs.bokeh.org/en/latest/index.html) widgets). It operates on data using a memory efficient architecture, and supports loading data from flat files, Pandas dataframes, and directly from a Postgres database. +The plotting and interactive workflow of Setvis is designed for use within a Jupyter notebook (although it is possible to run outside Jupyter). The other components of Setvis can be used interactively or programmatically. The interactive plots are powered by [Bokeh](https://docs.bokeh.org/en/latest/index.html) widgets. + +It operates on data using a memory efficient architecture, and supports loading data from flat files, Pandas dataframes, and directly from a Postgres database. ## Documentation diff --git a/docs/source/index.rst b/docs/source/index.rst index bda7aac..4a1b356 100644 --- a/docs/source/index.rst +++ b/docs/source/index.rst @@ -13,8 +13,10 @@ data `_ in a dataset). It can also be used to visualize set membership of which data missingness is a special case. -It is designed to work particularly well when used interactively from -a notebook, but can also be used non-interactively. +The plotting and interactive workflow of Setvis (see :ref:`plots`) is +designed for use within a Jupyter notebook (although it is possible +to run outside Jupyter, see :ref:`plot_outside_notebook`). The other +components of Setvis can be used interactively or programmatically. At the moment, setvis can load data from `pandas `_ dataframes, csv files, and also diff --git a/docs/source/plots.rst b/docs/source/plots.rst index af7b010..a90eda0 100644 --- a/docs/source/plots.rst +++ b/docs/source/plots.rst @@ -1,6 +1,68 @@ +.. _plots: + Plotting and interactivity ========================== +The `setvis.plots` module +------------------------- + .. automodule:: setvis.plots :members: + +.. _plot_outside_notebook: +Plotting outside of a notebook +------------------------------ + +.. note:: + + This is 'advanced' use of Setvis, and is not as well tested as the notebook + based workflow. + + +After creating a :class:`PlotSession` object, interactive plots can be +created with the :meth:`PlotSession.add_plot` method. The usual, +Jupyter-notebook-based workflow, creates these inline in the notebook. + +When calling :meth:`PlotSession.add_plot` with ``notebook=False``, a +Bokeh server is started and returned, enabling SetVis to be used outside of +a Jupyter notebook. + +Note that this usage is still *interactive*, but the plots are no longer +confined to a notebook. One use-case for this is to show the plots on a +large external display. + +The code below creates and starts the Bokeh server. + +.. code-block:: python + + import pandas as pd + from setvis.plots import PlotSession + + ## This csv file can be found in the Setvis git repository + df = pd.read_csv("examples/datasets/Synthetic_APC_DIAG_Fields.csv") + + session = PlotSession(df) + + ## Create a plot + bokeh_plot_server = session.add_plot(name="Plot 3", notebook=False, html_display_link=False) + + ## Display the URL of the plot that was just created + print(f"Connect to http://localhost:{bokeh_plot_server.port}/ to see the plot") + + ## Start and enter the event loop (this command blocks) + ## Not required if running inside Jupyter + bokeh_plot_server.run_until_shutdown() + + +The last command (``run_until_shutdown()``) is not required if +creating the plot from inside Jupyter (it will be attached to +Jupyter's event loop). See the Bokeh documentation for more +information on using a Bokeh server, including how it can be embedded +in other applications. + + +.. seealso:: + + - The example notebook in the Setvis repository `Example - plotting outside the notebook.ipynb `_ (GitHub link) + - The Bokeh documentation on `Bokeh server APIs `_ diff --git a/notebooks/Example - plotting outside the notebook.ipynb b/notebooks/Example - plotting outside the notebook.ipynb index 2210343..136eb60 100644 --- a/notebooks/Example - plotting outside the notebook.ipynb +++ b/notebooks/Example - plotting outside the notebook.ipynb @@ -5,7 +5,7 @@ "id": "1ed3b2ee", "metadata": {}, "source": [ - "# Example - plotting outside the notebook and additional options" + "# Example - additional plot options and plotting outside the notebook" ] }, { @@ -31,12 +31,22 @@ "metadata": {}, "outputs": [], "source": [ - "from setvis.plots import *\n", + "import pandas as pd\n", + "from setvis.plots import PlotSession\n", "\n", "df = pd.read_csv(\"../examples/datasets/Synthetic_APC_DIAG_Fields.csv\")\n", + "\n", "session = PlotSession(df)" ] }, + { + "cell_type": "markdown", + "id": "6c95e844-45f7-46ee-96b2-ee4170f19984", + "metadata": {}, + "source": [ + "## Passing additional arguments to configure the plot" + ] + }, { "cell_type": "markdown", "id": "eda7ddc0", @@ -51,29 +61,35 @@ "cell_type": "code", "execution_count": null, "id": "09c59044", - "metadata": { - "scrolled": false - }, + "metadata": {}, "outputs": [], "source": [ "session.add_plot(name=\"Plot 1\", height=480, output_backend=\"svg\")" ] }, + { + "cell_type": "markdown", + "id": "3a0803d3-4795-4f71-a411-1eefe0f361f1", + "metadata": {}, + "source": [ + "## Showing plots outside of the notebook (Bokeh server)" + ] + }, { "cell_type": "markdown", "id": "a7d5631c", "metadata": {}, "source": [ - "The cell below produces a new plot that does not show inline. Instead, it displays a link to a Bokeh server which can be opened in another tab or window. This can be done to make use of larger displays." + "The cell below produces a new plot that does not show inline. Instead, it displays a link to a Bokeh server which can be opened in another tab or window. This can be done to make use of larger displays, or to use SetVis outside of Jupyter.\n", + "\n", + "Notice how the `based_on` argument behaves in the same way as it does when the plot is rendered inline in the notebook, with the resulting plot (Plot 2) showing the data that was selected in Plot 1 above. Updates to the selection in Plot 1 aren't propagated until the cell below is run again, which will create a new Bokeh server." ] }, { "cell_type": "code", "execution_count": null, "id": "9732b0c4", - "metadata": { - "scrolled": false - }, + "metadata": {}, "outputs": [], "source": [ "session.add_plot(name=\"Plot 2\", based_on=\"Plot 1\", notebook=False)" @@ -81,10 +97,10 @@ }, { "cell_type": "markdown", - "id": "1991c84c", + "id": "51cdf2ca-90d3-46df-88ed-eb3d495ea4f4", "metadata": {}, "source": [ - "Making a selection with the plot shown in the newly-opened browser tab will affect the result of the following cell (which simply counts the number of records selected in the plot). Re-run the cell below after changing the selection." + "Making a selection with the plot shown in the newly-opened browser tab will affect the result of the following cell (which simply counts the number of records selected in the plot). Try re-running the cell below after changing the selection in Plot 2." ] }, { @@ -96,6 +112,38 @@ "source": [ "sum(session.selected_records(\"Plot 2\"))" ] + }, + { + "cell_type": "markdown", + "id": "05cfae41-93c2-4845-8edf-6044a967737f", + "metadata": {}, + "source": [ + "## Creating Setvis plots from outside of a Jupyter notebook (advanced)" + ] + }, + { + "cell_type": "markdown", + "id": "529c7ba9-f506-4b03-93c8-f0135ea75eda", + "metadata": {}, + "source": [ + "When run from a Jupyter notebook, as in the example above, the Bokeh server runs immediately in the background (it is attached to the asyncio event loop that Jupyter creates).\n", + "\n", + "When run from outside Jupyter, for example from a Python script or from the Python interactive shell, the event loop needs to be created. A simple way to do this is shown in the cell below (which is not run here). The Bokeh server object is returned from `add_plot`.\n", + "\n", + "Try running the contents of this cell from outside of a notebook." + ] + }, + { + "cell_type": "raw", + "id": "9f68ff5d-73dd-48ba-897c-2f577a71144d", + "metadata": {}, + "source": [ + "# Create a plot\n", + "bokeh_plot_server = session.add_plot(name=\"Plot 3\", notebook=False, html_display_link=False)\n", + "\n", + "# Start and enter the event loop (this command blocks)\n", + "bokeh_plot_server.run_until_shutdown()" + ] } ], "metadata": { @@ -114,7 +162,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.8.2" + "version": "3.12.1" } }, "nbformat": 4, diff --git a/setvis/plots.py b/setvis/plots.py index 43e8ebb..595e59c 100644 --- a/setvis/plots.py +++ b/setvis/plots.py @@ -12,7 +12,7 @@ from bokeh.models import TabPanel, Tabs from bokeh.events import SelectionGeometry import bokeh.io -import bokeh.server +import bokeh.server.server import pandas as pd import numpy as np import logging @@ -147,8 +147,6 @@ def __init__( self.tools = [ "xbox_select", "tap", - "box_zoom", - "pan", "reset", "save", HelpTool( @@ -613,7 +611,7 @@ def plot(self, **kwargs) -> bokeh.plotting.figure: source=self.source, line_color=self.linecolor, ) - p.xaxis.ticker = [x - 0.5 for x in range(self._bins)] + p.xaxis.ticker = [x for x in range(self._bins)] p.xaxis.major_label_overrides = self._get_xtick_labels() p.xaxis.axis_label = self.xlabel p.yaxis.axis_label = self.ylabel @@ -746,7 +744,7 @@ def plot(self, **kwargs) -> bokeh.plotting.figure: source=self.source, line_color=self.linecolor, ) - p.xaxis.ticker = [x - 0.5 for x in range(self._bins)] + p.xaxis.ticker = [x for x in range(self._bins)] p.xaxis.major_label_overrides = self._get_xtick_labels() p.xaxis.axis_label = self.xlabel p.yaxis.axis_label = self.ylabel @@ -1289,9 +1287,11 @@ def active_tab_callback(attr, old, new): else: server = bokeh.server.server.Server({"/": plot_app}, port=0) server.start() - from IPython.core.display import display, HTML + + logger.info(f"Bokeh server for plot {name} started at http://localhost:{server.port}/") if html_display_link: + from IPython.display import display, HTML display( HTML( f""