diff --git a/README.md b/README.md index b019851e..7777ee1c 100644 --- a/README.md +++ b/README.md @@ -24,17 +24,21 @@ Try it in your browser with Binder: ## Examples -### Widgets and Panels +### Add Jupyter Widgets to the JupyterLab interface -![widgets-panels](https://user-images.githubusercontent.com/591645/69000410-8f151f00-08cf-11ea-8491-7b8848497b62.gif) +![widgets-panels](https://user-images.githubusercontent.com/591645/80025074-59104280-84e0-11ea-9766-0cb49cba285a.gif) -### Command Registry +### Execute Commands -![command-registry](./docs/screencasts/commands.gif) +![command-registry](https://user-images.githubusercontent.com/591645/80026017-beb0fe80-84e1-11ea-842d-fa3bf5bc4a9b.gif) ### Custom Python Commands and Command Palette -![custom-commands](https://user-images.githubusercontent.com/591645/73125753-adbc2400-3faa-11ea-95f8-f7060e883ccd.gif) +![custom-commands](https://user-images.githubusercontent.com/591645/80026023-c1135880-84e1-11ea-9e83-fdb739659357.gif) + +### Building small UI applications + +![ipytree-example](https://user-images.githubusercontent.com/591645/80026006-b8bb1d80-84e1-11ea-87cc-86495186b938.gif) ## Installation @@ -56,6 +60,22 @@ To install the JupyterLab extension: jupyter labextension install @jupyter-widgets/jupyterlab-manager ipylab ``` +## Running the examples locally + +To try out the examples locally, the recommended way is to create a new environment with the dependencies: + +```bash +# create a new conda environment +conda create -n ipylab-examples -c conda-forge jupyterlab ipylab ipytree bqplot ipywidgets numpy +conda activate ipylab-examples + +# install the JupyterLab extensions +jupyter labextension install @jupyter-widgets/jupyterlab-manager ipylab bqplot ipytree + +# start JupyterLab +jupyter lab +``` + ## Under the hood `ipylab` can be seen as a proxy from Python to JupyterLab over Jupyter Widgets: diff --git a/environment.yml b/environment.yml index ac40d940..e89d7130 100644 --- a/environment.yml +++ b/environment.yml @@ -2,7 +2,9 @@ name: ipylab channels: - conda-forge dependencies: +- bqplot=0.12 - ipylab=0.2.1 - ipytree=0.1 - jupyterlab=2 - nodejs +- numpy diff --git a/examples/commands.ipynb b/examples/commands.ipynb index e383a185..41c7e650 100644 --- a/examples/commands.ipynb +++ b/examples/commands.ipynb @@ -162,7 +162,11 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "## Add your own command" + "## Add your own command\n", + "\n", + "Let's create a nice plot with `bqlot` and generate some random data.\n", + "\n", + "See https://github.com/bqplot/bqplot/blob/master/examples/Advanced%20Plotting/Animations.ipynb for more details." ] }, { @@ -171,11 +175,10 @@ "metadata": {}, "outputs": [], "source": [ - "from random import randint\n", - "from ipywidgets import IntSlider\n", + "import numpy as np\n", "\n", - "slider = IntSlider(min=0, max=100)\n", - "slider" + "from bqplot import LinearScale, Lines, Bars, Axis, Figure\n", + "from ipywidgets import IntSlider" ] }, { @@ -184,10 +187,65 @@ "metadata": {}, "outputs": [], "source": [ - "def my_command():\n", - " slider.value = randint(0, 100)\n", + "xs = LinearScale()\n", + "ys1 = LinearScale()\n", + "ys2 = LinearScale()\n", + "\n", + "x = np.arange(20)\n", + "y = np.cumsum(np.random.randn(20))\n", + "y1 = np.random.rand(20)\n", "\n", - "app.commands.add_command('random', execute=my_command, label=\"My Random Command\")" + "line = Lines(x=x, y=y, scales={'x': xs, 'y': ys1}, colors=['magenta'], marker='square')\n", + "bar = Bars(x=x, y=y1, scales={'x': xs, 'y': ys2}, colorpadding=0.2, colors=['steelblue'])\n", + "\n", + "xax = Axis(scale=xs, label='x', grid_lines='solid')\n", + "yax1 = Axis(scale=ys1, orientation='vertical', tick_format='0.1f', label='y', grid_lines='solid')\n", + "yax2 = Axis(scale=ys2, orientation='vertical', side='right', tick_format='0.0%', label='y1', grid_lines='none')\n", + "\n", + "Figure(marks=[bar, line], axes=[xax, yax1, yax2], animation_duration=1000)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "We now define a function to update the data." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "def update_data():\n", + " line.y = np.cumsum(np.random.randn(20))\n", + " bar.y = np.random.rand(20)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "update_data()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "This function will now be called when the JupyterLab command is executed." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "app.commands.add_command('update_data', execute=update_data, label=\"Update Data\")" ] }, { @@ -203,7 +261,7 @@ "metadata": {}, "outputs": [], "source": [ - "app.commands.execute('random')" + "app.commands.execute('update_data')" ] }, { @@ -262,7 +320,7 @@ "metadata": {}, "outputs": [], "source": [ - "palette.add_item('random', 'Python Commands')" + "palette.add_item('update_data', 'Python Commands')" ] }, { @@ -287,7 +345,7 @@ "metadata": {}, "outputs": [], "source": [ - "app.commands.remove_command('random')" + "app.commands.remove_command('update_data')" ] } ], @@ -307,7 +365,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.8.1" + "version": "3.8.2" }, "widgets": { "application/vnd.jupyter.widget-state+json": { diff --git a/examples/widgets.ipynb b/examples/widgets.ipynb index c384bf18..0d56f065 100644 --- a/examples/widgets.ipynb +++ b/examples/widgets.ipynb @@ -14,7 +14,7 @@ "outputs": [], "source": [ "from ipylab import JupyterFrontEnd, Panel, SplitPanel\n", - "from ipywidgets import IntSlider, Label, VBox\n", + "from ipywidgets import IntSlider, Layout\n", "\n", "app = JupyterFrontEnd()" ] @@ -23,7 +23,9 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "## Panel" + "## Panel\n", + "\n", + "A `Panel` widget is mostly the same as a `VBox`, but with a `Title`." ] }, { @@ -33,10 +35,27 @@ "outputs": [], "source": [ "panel = Panel()\n", - "panel.children = [IntSlider()]\n", + "slider = IntSlider()\n", + "panel.children = [slider]\n", "panel" ] }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "panel.title" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "To add the panel to the JupyterLab main area:" + ] + }, { "cell_type": "code", "execution_count": null, @@ -50,7 +69,18 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "## SplitPanel" + "In the case of sliders and other widgets that fit on a single line, they can even be added to the top area:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "inline_panel = Panel()\n", + "inline_panel.children = [slider]\n", + "inline_panel.layout = Layout(overflow='hidden')" ] }, { @@ -59,11 +89,62 @@ "metadata": {}, "outputs": [], "source": [ + "app.shell.add(inline_panel, 'top')" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "We can also remove it from the top area when we are done." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "inline_panel.close()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## SplitPanel\n", + "\n", + "Now let's create a `SplitPanel` with a few widgets inside." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from ipywidgets import HBox, IntProgress, jslink\n", + "\n", "split_panel = SplitPanel()\n", - "row_2 = VBox([Label(\"Second Split\"), IntSlider()])\n", + "progress = IntProgress(\n", + " value=7,\n", + " min=0,\n", + " max=100,\n", + " step=1,\n", + " description='Loading:',\n", + " bar_style='info',\n", + " orientation='horizontal',\n", + " layout=Layout(height='30px')\n", + ")\n", + "slider_ctrl = IntSlider(min=0, max=100, step=1, description='Slider Control:',)\n", + "\n", + "# link the slider to the progress bar\n", + "jslink((slider_ctrl, 'value'), (progress, 'value'))\n", + "\n", + "# add the widgets to the split panel\n", "split_panel.children = [\n", - " VBox([Label(\"First Split\"), IntSlider()]),\n", - " row_2\n", + " progress,\n", + " slider_ctrl\n", "]\n", "split_panel" ] @@ -88,6 +169,13 @@ "app.shell.add(split_panel, 'main', { 'mode': 'split-bottom' })" ] }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The orientation can be updated on the fly:" + ] + }, { "cell_type": "code", "execution_count": null, @@ -97,13 +185,61 @@ "split_panel.orientation = 'horizontal'" ] }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Let's put it back to `vertical`" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "split_panel.orientation = 'vertical'" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "We can add the progress bar one more time:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "split_panel.addWidget(progress)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Or add a new widget:" + ] + }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ - "split_panel.addWidget(row_2)" + "from ipywidgets import Play\n", + "\n", + "play = Play(\n", + " min=0,\n", + " max=100,\n", + " step=1,\n", + " description=\"Press play\"\n", + ")\n", + "jslink((play, 'value'), (slider_ctrl, 'value'))\n", + "split_panel.addWidget(play)" ] }, { @@ -112,7 +248,7 @@ "source": [ "## Left and Right Areas\n", "\n", - "Add the same `SplitPanel` widget to the left area:" + "The same `SplitPanel` widget can also be added to the left area:" ] }, { @@ -129,7 +265,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "And also to the right area:" + "As well as on the right area:" ] }, { @@ -138,7 +274,6 @@ "metadata": {}, "outputs": [], "source": [ - "split_panel.title.closable = False\n", "app.shell.add(split_panel, 'right', { 'rank': '1000' })\n", "app.shell.expand_right()" ] @@ -160,7 +295,14 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.8.0" + "version": "3.8.2" + }, + "widgets": { + "application/vnd.jupyter.widget-state+json": { + "state": {}, + "version_major": 2, + "version_minor": 0 + } } }, "nbformat": 4, diff --git a/postBuild b/postBuild index 14b3f319..2eecf092 100644 --- a/postBuild +++ b/postBuild @@ -1,3 +1,4 @@ jupyter labextension install @jupyter-widgets/jupyterlab-manager \ ipylab@0.2.1 \ + bqplot@0.5.6 \ ipytree@0.1