diff --git a/notebooks/07.More-libraries/07.05-ipycytoscape.ipynb b/notebooks/07.More-libraries/07.02-ipycytoscape.ipynb similarity index 100% rename from notebooks/07.More-libraries/07.05-ipycytoscape.ipynb rename to notebooks/07.More-libraries/07.02-ipycytoscape.ipynb diff --git a/notebooks/07.More-libraries/07.03-ipydatagrid.ipynb b/notebooks/07.More-libraries/07.03-ipydatagrid.ipynb new file mode 100644 index 00000000..871beca8 --- /dev/null +++ b/notebooks/07.More-libraries/07.03-ipydatagrid.ipynb @@ -0,0 +1,911 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "4d5ce026", + "metadata": {}, + "source": [ + "# `ipydatagrid`: fast, performant data grid\n", + "\n", + "## https://github.com/bloomberg/ipydatagrid\n", + "\n", + "### Full canvas rendering\n", + "\n", + "`ipydatagrid` is a complete data grid solution providing full integration with pandas DataFrames without compromising on performance:\n", + "\n", + "- built-in sorting and filtering\n", + "- full integration with the jupyter-widgets ecosystem\n", + "- highly customisable renderers\n", + "- complete two-way data binding between Python and the front-end\n", + "- supports Vega expressions for conditional formatting and styling\n", + "\n", + "**Installation**:\n", + "```bash\n", + "conda install -c conda-forge ipydatagrid\n", + "```" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "30f4cc0e", + "metadata": {}, + "outputs": [], + "source": [ + "from ipydatagrid import DataGrid, TextRenderer, BarRenderer, Expr, VegaExpr\n", + "import pandas as pd\n", + "import numpy as np\n", + "import requests" + ] + }, + { + "cell_type": "markdown", + "id": "7b847f68", + "metadata": {}, + "source": [ + "#### Use any dataset" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "b382fc75", + "metadata": {}, + "outputs": [], + "source": [ + "# Numpy/pandas\n", + "np.random.seed(0)\n", + "p_t, n = 100, 260\n", + "stock_df = pd.DataFrame(\n", + " {f'Stock {i}': p_t + np.round(np.random.standard_normal(n).cumsum(), 2) for i in range(10)}\n", + ")\n", + "\n", + "# Requests/JSON\n", + "req = requests.get(\"https://raw.githubusercontent.com/bloomberg/ipydatagrid/main/examples/cars.json\")\n", + "data = req.json()\n", + "cars_data = data['data']\n", + "\n", + "\n", + "# Random matrix\n", + "rand_df = pd.DataFrame(\n", + " {f'Column {col}': np.round(np.random.random(100), 2) for col in [chr(n) for n in range(65, 91)]}\n", + ")\n", + "\n", + "# Small grid\n", + "small_df = pd.DataFrame(\n", + " np.eye(10),\n", + " columns=[f'Col {i}' for i in range(10)],\n", + " index=[f'Row {i}' for i in range(10)]\n", + ")\n", + "\n", + "# Multi-index\n", + "top_level = ['Value Factors', 'Value Factors', 'Momentum Factors', 'Momentum Factors']\n", + "bottom_level = ['Factor A', 'Factor B', 'Factor C', 'Factor D']\n", + "\n", + "nested_df = pd.DataFrame(np.random.randn(4,4).round(2),\n", + " columns=pd.MultiIndex.from_arrays([top_level, bottom_level]),\n", + " index=pd.Index(['Security {}'.format(x) for x in ['A', 'B', 'C', 'D']], name='Ticker'))" + ] + }, + { + "cell_type": "markdown", + "id": "ab90aa2c", + "metadata": {}, + "source": [ + "#### Convert your pandas DataFrame to a datagrid - it 'just works'" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "94b69044", + "metadata": {}, + "outputs": [], + "source": [ + "df = pd.DataFrame(cars_data).set_index('index')\n", + "df.head()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "c2ae788a", + "metadata": {}, + "outputs": [], + "source": [ + "grid = DataGrid(df)\n", + "grid" + ] + }, + { + "cell_type": "markdown", + "id": "691cac62", + "metadata": {}, + "source": [ + "Additional constructor options" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "8b18b862", + "metadata": {}, + "outputs": [], + "source": [ + "grid = DataGrid(\n", + " dataframe=df,\n", + " base_row_size=30,\n", + " base_column_size=92,\n", + " base_row_header_size=128,\n", + " base_column_header_size=40\n", + ")\n", + "grid" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "2fd41d80", + "metadata": {}, + "outputs": [], + "source": [ + "grid.header_visibility = 'row'" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "80ad0ce1", + "metadata": {}, + "outputs": [], + "source": [ + "grid.header_visibility = 'column'" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "d02d97dd", + "metadata": {}, + "outputs": [], + "source": [ + "grid.header_visibility = 'none'" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "aab73a5d", + "metadata": {}, + "outputs": [], + "source": [ + "grid.header_visibility = 'all'" + ] + }, + { + "cell_type": "markdown", + "id": "63a965c8", + "metadata": {}, + "source": [ + "#### Style your data grid with custom renderers based on Vega expressions\n", + "\n", + "- Custom cell renderers can be defined for the entire grid or column-wise.\n", + "\n", + "- Two types of cell renderers are currently available: `TextRenderer` and `BarRenderer`.\n", + "\n", + "- Most of the TextRenderer/BarRenderer attributes (`background_color`, `text_color` etc.) can either be a `value`, a `bqplot` scale or a `VegaExpr` or `Expr` instance.\n", + "\n", + "The `VegaExpr` class allows you to define an attribute value as a result of a Vega-expression (see https://vega.github.io/vega/docs/expressions/):\n", + "```python\n", + "background_color = VegaExpr(\"value < 150 ? 'red' : 'green'\").\n", + "```\n", + "You can look at the vega-expression documentation for more information about available constants and functions. In the scope of the expression are also available: value: cell value, x and y: cell position in pixel, width and height of the cell, row and column: cell position." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "ccf880c4", + "metadata": {}, + "outputs": [], + "source": [ + "from bqplot import LinearScale, ColorScale, OrdinalColorScale, OrdinalScale\n", + "from py2vega.functions.color import rgb\n", + "\n", + "def horsepower_coloring(cell):\n", + " if cell.value < 100:\n", + " return \"red\"\n", + " elif cell.value < 150:\n", + " return \"orange\"\n", + " else:\n", + " return \"green\"\n", + "\n", + "\n", + "def weight_coloring(cell):\n", + " scaled_value = 1 if cell.value > 4500 else cell.value / 4500\n", + " color_value = scaled_value * 255\n", + "\n", + " return rgb(color_value, 0, 0)\n", + "\n", + "\n", + "renderers = {\n", + " \"Acceleration\": BarRenderer(\n", + " horizontal_alignment=\"center\",\n", + " bar_color=ColorScale(min=0, max=20, scheme=\"viridis\"),\n", + " bar_value=LinearScale(min=0, max=20),\n", + " ),\n", + " \"Cylinders\": TextRenderer(\n", + " background_color=Expr('\"grey\" if cell.row % 2 else default_value')\n", + " ),\n", + " \"Displacement\": TextRenderer(\n", + " text_color=ColorScale(min=97, max=455),\n", + " font=Expr(\n", + " \"'16px sans-serif' if cell.value > 400 else '12px sans-serif'\"\n", + " ),\n", + " ),\n", + " \"Horsepower\": TextRenderer(\n", + " text_color=\"black\", background_color=Expr(horsepower_coloring)\n", + " ),\n", + " \"Miles_per_Gallon\": TextRenderer(\n", + " background_color=Expr('\"grey\" if cell.value is None else default_value')\n", + " ),\n", + " \"Name\": TextRenderer(\n", + " background_color=Expr(\n", + " 'rgb(0, 100, 255) if \"chevrolet\" in cell.value or \"ford\" in cell.value else default_value'\n", + " )\n", + " ),\n", + " \"Origin\": TextRenderer(\n", + " text_color=\"black\",\n", + " background_color=OrdinalColorScale(domain=[\"USA\", \"Japan\", \"Europe\"]),\n", + " horizontal_alignment=Expr(\n", + " \"'right' if cell.value in ['USA', 'Japan'] else 'left'\"\n", + " ),\n", + " ),\n", + " \"Weight_in_lbs\": TextRenderer(\n", + " text_color=\"black\", background_color=Expr(weight_coloring)\n", + " ),\n", + " \"Year\": TextRenderer(text_color=\"black\", background_color=\"green\"),\n", + "}\n", + "\n", + "grid.renderers = renderers" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "526f0c77", + "metadata": {}, + "outputs": [], + "source": [ + "renderers[\"Name\"] \\\n", + " .background_color.value = '\"green\" if \"pontiac\" in cell.value or \"citroen\" in cell.value else default_value'" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "8bee2999", + "metadata": {}, + "outputs": [], + "source": [ + "renderers[\"Year\"].background_color = \"yellow\"" + ] + }, + { + "cell_type": "markdown", + "id": "688d5b2c", + "metadata": {}, + "source": [ + "#### Apply styling to column and row headers" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "f8c4b3ed", + "metadata": {}, + "outputs": [], + "source": [ + "grid.header_renderer = TextRenderer(\n", + " background_color=Expr('\"salmon\" if cell.value == \"Horsepower\" else \"skyblue\"'),\n", + " font='italic small-caps bold 12px/30px Georgia, serif'\n", + ")\n", + "\n", + "renderers['index'] = TextRenderer(background_color='slateblue')\n", + "grid.renderers = renderers" + ] + }, + { + "cell_type": "markdown", + "id": "e22665ef", + "metadata": {}, + "source": [ + "#### Built-in sorting and filtering functionality which you can trigger from both Python and directly via the GUI." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "6c96d8dc", + "metadata": {}, + "outputs": [], + "source": [ + "# Filtering based on car origin and sorting based on displacement\n", + "grid.transform(\n", + " [\n", + " {\n", + " \"type\": \"filter\",\n", + " \"operator\": \"=\",\n", + " \"columnIndex\": 2,\n", + " \"value\": \"Europe\",\n", + " },\n", + " {\"type\": \"sort\", \"columnIndex\": 9, \"desc\": True},\n", + " ]\n", + ")" + ] + }, + { + "cell_type": "markdown", + "id": "cd574769", + "metadata": {}, + "source": [ + "#### Conditional formatting based on another cell" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "e9e3b45a", + "metadata": {}, + "outputs": [], + "source": [ + "def format_based_on_other_column(cell):\n", + " return (\"green\" if cell.column == 2 and cell.metadata.data[\"Return\"] > 0 else \"red\")\n", + "\n", + "signal_column_formatting = TextRenderer(\n", + " text_color=\"white\",\n", + " background_color=Expr(format_based_on_other_column),\n", + ")\n", + "\n", + "renderers = {\n", + " \"Signal\": signal_column_formatting,\n", + " \"Return\": TextRenderer(background_color='seashell',\n", + " text_color=VegaExpr('cell.value > 0 ? \"green\" : \"firebrick\"')\n", + " )\n", + "}\n", + "\n", + "conditional_grid = DataGrid(\n", + " pd.DataFrame(\n", + " {\"Stock\": \"A B C D\".split(), \n", + " \"Return\": [0.11, -0.05, 0.08, -0.20], \n", + " \"Signal\": [\"Buy\", \"Sell\", \"Buy\", \"Sell\"]}\n", + " ),\n", + " column_widths={\"Stock\": 64, \"Return\": 64, \"Signal\": 300},\n", + " base_row_size=30,\n", + " renderers=renderers,\n", + " layout={\"height\": \"150px\"},\n", + ")\n", + "\n", + "conditional_grid" + ] + }, + { + "cell_type": "markdown", + "id": "9cb0121c", + "metadata": {}, + "source": [ + "#### Multi-index and nested columns DataFrames are also supported" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "761d5d7c", + "metadata": {}, + "outputs": [], + "source": [ + "nested_df" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "3e5a896c", + "metadata": {}, + "outputs": [], + "source": [ + "columns_renderer = TextRenderer(\n", + " background_color='dimgray',\n", + " horizontal_alignment='center')\n", + "\n", + "renderers= {\n", + " \"('Ticker', '')\": TextRenderer(background_color='dimgray')\n", + "}\n", + "\n", + "default_renderer = TextRenderer(\n", + " background_color=VegaExpr('cell.value > 0 ? \"steelblue\" : \"seagreen\"')\n", + ")\n", + "\n", + "nested_grid = DataGrid(nested_df,\n", + " base_column_size=90,\n", + " column_widths={\"('Ticker', '')\": 80},\n", + " layout={'height':'140px'},\n", + " renderers=renderers,\n", + " default_renderer=default_renderer,\n", + " header_renderer=columns_renderer)\n", + "\n", + "nested_grid" + ] + }, + { + "cell_type": "markdown", + "id": "80c520dd", + "metadata": {}, + "source": [ + "#### Two-way selections model at your disposal\n", + "\n", + "DataGrid cells can be selected using mouse by simply clicking and dragging over the cells. Pressing Cmd / Ctrl key during selection will add to existing selections. Pressing Shift key allows selection of regions between two clicks.\n", + "\n", + "DataGrid supports three modes of selection `cell`, `row`, `column`. In order to disable selections, selection mode can be set to `none` which is the default setting.\n", + "\n", + "Selection Modes:\n", + "\n", + "- `cell`: Clicking on grid will select only the cell under mouse cursor\n", + "- `row`: Clicking on grid will select all the cells on the row under mouse cursor\n", + "- `column`: Clicking on grid will select all the cells on the column under mouse cursor\n", + "\n", + "You can clear all selections by hitting the `esc` key." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "19daff23", + "metadata": {}, + "outputs": [], + "source": [ + "sel_grid = DataGrid(stock_df, selection_mode='cell')\n", + "sel_grid" + ] + }, + { + "cell_type": "markdown", + "id": "31bae137", + "metadata": {}, + "source": [ + "Select from the UI and retrieve on the Python side" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "24bb14f0", + "metadata": {}, + "outputs": [], + "source": [ + "sel_grid.selections" + ] + }, + { + "cell_type": "markdown", + "id": "f63bfcdd", + "metadata": {}, + "source": [ + "We can also access the selected cell values" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "f34ebe8c", + "metadata": {}, + "outputs": [], + "source": [ + "sel_grid.selected_cell_values" + ] + }, + { + "cell_type": "markdown", + "id": "5b79e117", + "metadata": {}, + "source": [ + "Select from the Python side and see selections highlighted on the UI.\n", + "\n", + "Parameters:\n", + "\n", + "- `row1`: start row (starts from 0).\n", + "- `row2`: end row (starts from 0).\n", + "- `column1`: start column.\n", + "- `column2`: end column.\n", + "\n", + "We can automatically clear any existing selections by passing a value for `clear_mode`:\n", + "\n", + "- `current`: clear last selection\n", + "- `all`: clear all existing selections\n", + "- `none`: do not clear selections (default)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "cb14e58b", + "metadata": {}, + "outputs": [], + "source": [ + "# Select top left corner of grid\n", + "sel_grid.select(row1=0, column1=0, row2=1, column2=1, clear_mode='current')" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "324850f9", + "metadata": {}, + "outputs": [], + "source": [ + "# Clear selection\n", + "sel_grid.clear_selection()" + ] + }, + { + "cell_type": "markdown", + "id": "1da0f04a", + "metadata": {}, + "source": [ + "We can select individual sells by omitting `row2` and `column2`" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "6852e273", + "metadata": {}, + "outputs": [], + "source": [ + "sel_grid.select(10, 5)" + ] + }, + { + "cell_type": "markdown", + "id": "702dc047", + "metadata": {}, + "source": [ + "When working with large grids, we can opt to use the `selected_cell_iterator`. It will yield values for each loop iteration, avoiding the need to store all selections in a list, in advance." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "fe0634e8", + "metadata": {}, + "outputs": [], + "source": [ + "for cell in sel_grid.selected_cell_iterator:\n", + " print(f'Cell value: {cell}')" + ] + }, + { + "cell_type": "markdown", + "id": "1f299815", + "metadata": {}, + "source": [ + "We can modify selections in place by passing a list of selections" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "88bccc93", + "metadata": {}, + "outputs": [], + "source": [ + "sel_grid.selections = [\n", + " {\"r1\": 22, \"r2\": 20, \"c1\": 0, \"c2\": 2},\n", + " {\"r1\": 6, \"r2\": 6, \"c1\": 2, \"c2\": 2},\n", + " {\"r1\": 10, \"r2\": 10, \"c1\": 3, \"c2\": 3},\n", + " {\"r1\": 13, \"r2\": 13, \"c1\": 2, \"c2\": 2},\n", + "]" + ] + }, + { + "cell_type": "markdown", + "id": "e8d948c4", + "metadata": {}, + "source": [ + "Row selection mode" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "b653da0e", + "metadata": {}, + "outputs": [], + "source": [ + "sel_grid.selection_mode='row'" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "71cdf563", + "metadata": {}, + "outputs": [], + "source": [ + "for i in range(260):\n", + " sel_grid.select(i, i) if i % 2 == 0 else None" + ] + }, + { + "cell_type": "markdown", + "id": "85045e17", + "metadata": {}, + "source": [ + "#### Two-way cell editing is possible\n", + "\n", + "Just pass `editable=True` to the grid's constructorand you're good to go (grids are not editable by default)." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "e118e5d5", + "metadata": {}, + "outputs": [], + "source": [ + "small_grid = DataGrid(small_df, \n", + " editable=True, \n", + " default_renderer=TextRenderer(\n", + " background_color=VegaExpr(\"cell.value === 1 ? 'limegreen' : 'hotpink'\")\n", + " ),\n", + " layout={'height': '250px'})\n", + "small_grid" + ] + }, + { + "cell_type": "markdown", + "id": "5e0ccd9a", + "metadata": {}, + "source": [ + "You can change values directly in the UI by double clicking a cell and changing the value. You can navigate the grid using the keyboard. You can use the arrow keys or the grid's __cursor__:\n", + "\n", + "- __Down__: Enter\n", + "- __Up__: Shift + Enter\n", + "- __Right__: Tab\n", + "- __Left__: Shift + Tab" + ] + }, + { + "cell_type": "markdown", + "id": "806468bd", + "metadata": {}, + "source": [ + "..or you can change it directly from the Python side" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "f0ac1126", + "metadata": {}, + "outputs": [], + "source": [ + "# Returns a boolean to indicate whether operations was successful\n", + "small_grid.set_cell_value('Col 0', 'Row 9', 1) # Sets value based on row name" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "af84ae1e", + "metadata": {}, + "outputs": [], + "source": [ + "small_grid.set_cell_value_by_index('Col 9', 0, 1) # Sets value based on row index" + ] + }, + { + "cell_type": "markdown", + "id": "9cb298fb", + "metadata": {}, + "source": [ + "#### Events and integration with `ipywidgets`" + ] + }, + { + "cell_type": "markdown", + "id": "2b00cab5", + "metadata": {}, + "source": [ + "Listen to cell change events" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "b214bb25", + "metadata": {}, + "outputs": [], + "source": [ + "def cell_changed(e):\n", + " row, column, col_index, value = e['row'], e['column'], e['column_index'], e['value']\n", + " print(f'The cell at row {row}, column \"{column}\" (index {col_index}), changed to {value}')\n", + " \n", + "small_grid.on_cell_change(cell_changed)\n", + "small_grid" + ] + }, + { + "cell_type": "markdown", + "id": "8c454fb8", + "metadata": {}, + "source": [ + "#### An example with the BarRenderer\n", + "\n", + "Renders cell values as horizontal bars based on a scale. `ipydatagrid` has two renderers - `TextRenderer`, which is the default one we've seen, and `BarRenderer`, which we will use now." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "d3dcd28e", + "metadata": {}, + "outputs": [], + "source": [ + "from bqplot import LinearScale, ColorScale\n", + "from ipydatagrid import DataGrid, BarRenderer\n", + "\n", + "linear_scale = LinearScale(min=0, max=1)\n", + "color_scale = ColorScale(min=0, max=1)\n", + "bar_renderer = BarRenderer(\n", + " bar_color=color_scale,\n", + " bar_value=linear_scale,\n", + " bar_horizontal_alignment=\"center\",\n", + ")\n", + "\n", + "\n", + "rand_grid = DataGrid(rand_df, default_renderer=bar_renderer, base_column_size=76)\n", + "rand_grid" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "8296850a", + "metadata": {}, + "outputs": [], + "source": [ + "bar_renderer.show_text = False" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "c920c322", + "metadata": {}, + "outputs": [], + "source": [ + "from ipywidgets import FloatSlider, link\n", + "\n", + "slider = FloatSlider(\n", + " description=\"Scale: \", value=linear_scale.max, min=0, max=0.99, step=0.01\n", + ")\n", + "link((color_scale, \"min\"), (slider, \"value\"))\n", + "link((linear_scale, \"min\"), (slider, \"value\"))\n", + "\n", + "slider" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "6ab2857d-60be-46e2-8402-6f6b8edb7c33", + "metadata": {}, + "outputs": [], + "source": [ + "color_scale.min" + ] + }, + { + "cell_type": "markdown", + "id": "b78005c8", + "metadata": {}, + "source": [ + "#### Integration with `bqplot`\n", + "\n", + "We have a DataGrid with time series of 10 different stock prices. Each time we click on a stock price column, we want to plot the time series of that stock price in a line chart." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "e3ecc75d", + "metadata": {}, + "outputs": [], + "source": [ + "stock_df.head()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "dee03573", + "metadata": {}, + "outputs": [], + "source": [ + "from bqplot import LinearScale, Axis, Figure, Lines, CATEGORY10\n", + "from ipywidgets import HBox, Layout\n", + " \n", + "# Setting up the data grid\n", + "stock_grid = DataGrid(stock_df, selection_mode='column')\n", + "\n", + "# Creating the bqplot chart objects\n", + "sc_x = LinearScale()\n", + "sc_y = LinearScale()\n", + "line = Lines(x=[], y=[], labels=['Fake stock price'], display_legend=True,\n", + " scales={'x': sc_x, 'y': sc_y})\n", + "ax_x = Axis(scale=sc_x, label='Index')\n", + "ax_y = Axis(scale=sc_y, orientation='vertical', label='y-value')\n", + "fig = Figure(marks=[line], axes=[ax_x, ax_y], title='Line Chart', layout=Layout(flex='1 1 auto', width='100%'))" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "6163f8dd", + "metadata": {}, + "outputs": [], + "source": [ + "def plot_stock(e=None):\n", + " line.y = stock_grid.selected_cell_values\n", + " line.x = range(len(line.y))\n", + " line.labels = [e['column']]\n", + " line.colors = [CATEGORY10[np.random.randint(0, len(CATEGORY10)) % len(CATEGORY10)]]\n", + " \n", + "# Event listener for cell click\n", + "stock_grid.on_cell_click(plot_stock)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "1413d459", + "metadata": {}, + "outputs": [], + "source": [ + "HBox(\n", + " [stock_grid, fig]\n", + ")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "100aec93-89b1-48d8-9077-0cc0d5fc48ac", + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "metadata": { + "kernelspec": { + "display_name": "widgets-tutorial", + "language": "python", + "name": "widgets-tutorial" + }, + "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.8.10" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/notebooks/07.More-libraries/07.06-ipygany.ipynb b/notebooks/07.More-libraries/07.04-ipygany.ipynb similarity index 100% rename from notebooks/07.More-libraries/07.06-ipygany.ipynb rename to notebooks/07.More-libraries/07.04-ipygany.ipynb diff --git a/notebooks/07.More-libraries/07.05-other-widget-libraries.ipynb b/notebooks/07.More-libraries/07.05-other-widget-libraries.ipynb new file mode 100644 index 00000000..d0ea6a8c --- /dev/null +++ b/notebooks/07.More-libraries/07.05-other-widget-libraries.ipynb @@ -0,0 +1,458 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "7e3a1d9e-3a48-41ff-a2b7-2dbcdb82c520", + "metadata": {}, + "source": [ + "# Other widget libraries\n", + "\n", + "We would have loved to show you everything the Jupyter Widgets ecosystem has to offer today, but we are blessed to have such an active community of widget creators and unfortunately can't fit all widgets in a single session, no matter how long. \n", + "\n", + "This notebook lists some of the widget libraries we wanted to demo but did not have enough time to include in the session. Enjoy!" + ] + }, + { + "cell_type": "markdown", + "id": "6bbc79d5-86b4-4b2b-b924-e458375e090a", + "metadata": {}, + "source": [ + "# ipyleaflet: Interactive maps\n", + "\n", + "## A Jupyter - LeafletJS bridge\n", + "\n", + "## https://github.com/jupyter-widgets/ipyleaflet\n", + "\n", + "\n", + "ipyleaflet is a jupyter interactive widget library which provides interactive maps to the Jupyter notebook.\n", + "\n", + "- MIT Licensed\n", + "\n", + "**Installation:**\n", + "\n", + "```bash\n", + "conda install -c conda-forge ipyleaflet\n", + "```" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "c77c85c9-6d99-475c-a9a2-e2c190ba4122", + "metadata": {}, + "outputs": [], + "source": [ + "from ipywidgets import Text, HTML, HBox\n", + "from ipyleaflet import GeoJSON, WidgetControl, Map \n", + "import json" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "54c3506c-2ac0-4319-bfcc-e017f0885295", + "metadata": {}, + "outputs": [], + "source": [ + "m = Map(center = (43,-100), zoom = 4)\n", + "\n", + "geo_json_data = json.load(open('us-states-density-colored.json'))\n", + "geojson = GeoJSON(data=geo_json_data, hover_style={'color': 'black', 'dashArray': '5, 5', 'weight': 2})\n", + "m.add_layer(geojson)\n", + "\n", + "html = HTML('''\n", + "

US population density

\n", + " Hover over a state\n", + "''')\n", + "html.layout.margin = '0px 20px 20px 20px'\n", + "control = WidgetControl(widget=html, position='topright')\n", + "m.add_control(control)\n", + "\n", + "def update_html(properties, **kwargs):\n", + " html.value = '''\n", + "

US population density

\n", + "

{}

\n", + " {} people / mi^2\n", + " '''.format(properties['name'], properties['density'])\n", + "\n", + "geojson.on_hover(update_html)\n", + "\n", + "m" + ] + }, + { + "cell_type": "markdown", + "id": "9bfcd179-0463-4b07-9bce-f7ba298ed3de", + "metadata": {}, + "source": [ + "# pythreejs: 3D rendering in the browser \n", + "\n", + "## A Jupyter - threejs bridge\n", + "\n", + "## https://github.com/jupyter-widgets/pythreejs\n", + "\n", + "\n", + "Pythreejs is a jupyter interactive widget bringing fast WebGL 3d visualization to the Jupyter notebook.\n", + "\n", + "- Originally authored by Jason Grout, currently maintained by Vidar Tonaas Fauske\n", + "- BSD Licensed\n", + "\n", + "Pythreejs is *not* a 3d plotting library, it only exposes the threejs scene objects to the Jupyter kernel.\n", + "\n", + "**Installation:**\n", + "\n", + "```bash\n", + "conda install -c conda-forge pythreejs\n", + "```" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "989097a4-302f-4092-a784-814282b5c366", + "metadata": {}, + "outputs": [], + "source": [ + "from pythreejs import *\n", + "import numpy as np\n", + "from IPython.display import display\n", + "from ipywidgets import HTML, Text, Output, VBox\n", + "from traitlets import link, dlink" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "07bb1212-d30f-4b22-a1e9-75ba63c449cc", + "metadata": {}, + "outputs": [], + "source": [ + "# Generate surface data:\n", + "view_width = 600\n", + "view_height = 400\n", + "nx, ny = (20, 20)\n", + "xmax=1\n", + "x = np.linspace(-xmax, xmax, nx)\n", + "y = np.linspace(-xmax, xmax, ny)\n", + "xx, yy = np.meshgrid(x, y)\n", + "z = xx ** 2 - yy ** 2\n", + "#z[6,1] = float('nan')\n", + "\n", + "\n", + "# Generate scene objects from data:\n", + "surf_g = SurfaceGeometry(z=list(z[::-1].flat), \n", + " width=2 * xmax,\n", + " height=2 * xmax,\n", + " width_segments=nx - 1,\n", + " height_segments=ny - 1)\n", + "\n", + "surf = Mesh(geometry=surf_g,\n", + " material=MeshLambertMaterial(map=height_texture(z[::-1], 'YlGnBu_r')))\n", + "\n", + "surfgrid = SurfaceGrid(geometry=surf_g, material=LineBasicMaterial(color='black'),\n", + " position=[0, 0, 1e-2]) # Avoid overlap by lifting grid slightly\n", + "\n", + "# Set up picking bojects:\n", + "hover_point = Mesh(geometry=SphereGeometry(radius=0.05),\n", + " material=MeshLambertMaterial(color='green'))\n", + "\n", + "click_picker = Picker(controlling=surf, event='dblclick')\n", + "hover_picker = Picker(controlling=surf, event='mousemove')\n", + "\n", + "# Set up scene:\n", + "key_light = DirectionalLight(color='white', position=[3, 5, 1], intensity=0.4)\n", + "c = PerspectiveCamera(position=[0, 3, 3], up=[0, 0, 1], aspect=view_width / view_height,\n", + " children=[key_light])\n", + "\n", + "scene = Scene(children=[surf, c, surfgrid, hover_point, AmbientLight(intensity=0.8)])\n", + "\n", + "renderer = Renderer(camera=c, scene=scene,\n", + " width=view_width, height=view_height,\n", + " controls=[OrbitControls(controlling=c), click_picker, hover_picker])\n", + "\n", + "\n", + "# Set up picking responses:\n", + "# Add a new marker when double-clicking:\n", + "out = Output()\n", + "def f(change):\n", + " value = change['new']\n", + " with out:\n", + " print('Clicked on %s' % (value,))\n", + " point = Mesh(geometry=SphereGeometry(radius=0.05), \n", + " material=MeshLambertMaterial(color='hotpink'),\n", + " position=value)\n", + " scene.add(point)\n", + "\n", + "click_picker.observe(f, names=['point'])\n", + "\n", + "# Have marker follow picker point:\n", + "link((hover_point, 'position'), (hover_picker, 'point'))\n", + "\n", + "# Show picker point coordinates as a label:\n", + "h = HTML()\n", + "def g(change):\n", + " h.value = 'Green point at (%.3f, %.3f, %.3f)' % tuple(change['new'])\n", + " h.value += ' Double-click to add marker'\n", + "g({'new': hover_point.position})\n", + "hover_picker.observe(g, names=['point'])\n", + "\n", + "display(VBox([h, renderer, out]))" + ] + }, + { + "cell_type": "markdown", + "id": "228aa568-3b3b-4901-9700-b6a865e474a1", + "metadata": {}, + "source": [ + "# bqplot: complex interactive visualizations\n", + "\n", + "## https://github.com/bloomberg/bqplot\n", + "\n", + "## A Jupyter - d3.js bridge\n", + "\n", + "bqplot is a jupyter interactive widget library bringing d3.js visualization to the Jupyter notebook.\n", + "\n", + "- Apache Licensed\n", + "\n", + "bqplot implements the abstractions of Wilkinson’s “The Grammar of Graphics” as interactive Jupyter widgets.\n", + "\n", + "bqplot provides both\n", + "-\thigh-level plotting procedures with relevant defaults for common chart types,\n", + "-\tlower-level descriptions of data visualizations meant for complex interactive visualization dashboards and applications involving mouse interactions and user-provided Python callbacks.\n", + "\n", + "**Installation:**\n", + "\n", + "```bash\n", + "conda install -c conda-forge bqplot\n", + "```" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "e406ea84-67fe-4137-9b26-cf4cbdb4e751", + "metadata": {}, + "outputs": [], + "source": [ + "import numpy as np\n", + "import bqplot as bq" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "625bd9df-8661-4267-aa34-3c758e04be21", + "metadata": {}, + "outputs": [], + "source": [ + "xs = bq.LinearScale()\n", + "ys = bq.LinearScale()\n", + "x = np.arange(100)\n", + "y = np.cumsum(np.random.randn(2, 100), axis=1) #two random walks\n", + "\n", + "line = bq.Lines(x=x, y=y, scales={'x': xs, 'y': ys}, colors=['red', 'green'])\n", + "xax = bq.Axis(scale=xs, label='x', grid_lines='solid')\n", + "yax = bq.Axis(scale=ys, orientation='vertical', tick_format='0.2f', label='y', grid_lines='solid')\n", + "\n", + "fig = bq.Figure(marks=[line], axes=[xax, yax], animation_duration=1000)\n", + "display(fig)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "94ac959d-76d1-4712-914c-bcb931d04e93", + "metadata": {}, + "outputs": [], + "source": [ + "# update data of the line mark\n", + "line.y = np.cumsum(np.random.randn(2, 100), axis=1)" + ] + }, + { + "cell_type": "markdown", + "id": "4ce97fcb-e621-4b0e-b826-d49c04146f51", + "metadata": {}, + "source": [ + "# ipympl: The Matplotlib Jupyter Widget Backend\n", + "\n", + "## https://github.com/matplotlib/ipympl\n", + "\n", + "\n", + "Enabling interaction with matplotlib charts in the Jupyter notebook and JupyterLab\n", + "\n", + "- BSD-3-Clause\n", + "\n", + "**Installation:**\n", + "\n", + "```bash\n", + "conda install -c conda-forge ipympl\n", + "```" + ] + }, + { + "cell_type": "markdown", + "id": "91a33e30-803d-4945-8129-2c37f30a1d09", + "metadata": {}, + "source": [ + "Enabling the `widget` backend. This requires ipympl. ipympl can be install via pip or conda." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "6b157ee7-4008-492d-8045-f93b747af703", + "metadata": {}, + "outputs": [], + "source": [ + "%matplotlib widget" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "152cba47-06f1-4896-b15a-b3ed960cafd3", + "metadata": {}, + "outputs": [], + "source": [ + "import numpy as np\n", + "import matplotlib.pyplot as plt\n", + "from ipywidgets import VBox, FloatSlider" + ] + }, + { + "cell_type": "markdown", + "id": "fc9dca28-883c-4286-af3d-a17081b089d6", + "metadata": {}, + "source": [ + "When using the `widget` backend from ipympl, fig.canvas is a proper Jupyter interactive widget, which can be embedded in Layout classes like HBox and Vbox.\n", + "\n", + "One can bound figure attributes to other widget values." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "9db5b3e0-317c-4b79-bb5c-36786d02895a", + "metadata": {}, + "outputs": [], + "source": [ + "plt.ioff()\n", + "plt.clf()\n", + "\n", + "slider = FloatSlider(\n", + " value=1.0,\n", + " min=0.02,\n", + " max=2.0\n", + ")\n", + "\n", + "fig1 = plt.figure(1)\n", + "\n", + "x1 = np.linspace(0, 20, 500)\n", + "\n", + "lines = plt.plot(x1, np.sin(slider.value * x1))\n", + "\n", + "def update_lines(change):\n", + " lines[0].set_data(x1, np.sin(change.new * x1))\n", + " fig1.canvas.draw()\n", + " fig1.canvas.flush_events()\n", + "\n", + "slider.observe(update_lines, names='value')\n", + "\n", + "VBox([slider, fig1.canvas])" + ] + }, + { + "cell_type": "markdown", + "id": "24389d91-db96-4591-a279-9dde81b9c8ef", + "metadata": {}, + "source": [ + "# ipytree: Interactive tree view based on ipywidgets\n", + "\n", + "## https://github.com/QuantStack/ipytree/\n", + "\n", + "\n", + "ipytree is a jupyter interactive widget library which provides a tree widget to the Jupyter notebook.\n", + "\n", + "- MIT Licensed\n", + "\n", + "**Installation:**\n", + "\n", + "```bash\n", + "conda install -c conda-forge ipytree\n", + "```" + ] + }, + { + "cell_type": "markdown", + "id": "fa20469b-b9b6-46bc-99f0-e85c313db971", + "metadata": {}, + "source": [ + "## Create a tree" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "c3b13d81-455d-438c-a843-f31421db0fc1", + "metadata": {}, + "outputs": [], + "source": [ + "from ipywidgets import Text, link\n", + "from ipytree import Tree, Node" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "5461f21f-e96e-4877-8f4c-bc1bc5f572e2", + "metadata": {}, + "outputs": [], + "source": [ + "tree = Tree()\n", + "tree.add_node(Node('node1'))\n", + "\n", + "node2 = Node('node2')\n", + "tree.add_node(node2)\n", + "\n", + "tree" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "6059b01a-8c08-492a-834b-450905e5c3dd", + "metadata": {}, + "outputs": [], + "source": [ + "node3 = Node('node3', disabled=True)\n", + "node4 = Node('node4')\n", + "node5 = Node('node5', [Node('1'), Node('2')])\n", + "node2.add_node(node3)\n", + "node2.add_node(node4)\n", + "node2.add_node(node5)" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "widgets-tutorial", + "language": "python", + "name": "widgets-tutorial" + }, + "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.8.10" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/notebooks/widgets_libraries/PyWWT.ipynb b/notebooks/extras/PyWWT.ipynb similarity index 100% rename from notebooks/widgets_libraries/PyWWT.ipynb rename to notebooks/extras/PyWWT.ipynb diff --git a/notebooks/07.More-libraries/07.03-bqplot.ipynb b/notebooks/extras/bqplot.ipynb similarity index 99% rename from notebooks/07.More-libraries/07.03-bqplot.ipynb rename to notebooks/extras/bqplot.ipynb index f17816a9..86f844e8 100644 --- a/notebooks/07.More-libraries/07.03-bqplot.ipynb +++ b/notebooks/extras/bqplot.ipynb @@ -449,7 +449,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.8.3" + "version": "3.8.10" }, "widgets": { "state": { diff --git a/notebooks/extra_examples/bqplot_A_Penalized_regression.ipynb b/notebooks/extras/extra_examples/bqplot_A_Penalized_regression.ipynb similarity index 100% rename from notebooks/extra_examples/bqplot_A_Penalized_regression.ipynb rename to notebooks/extras/extra_examples/bqplot_A_Penalized_regression.ipynb diff --git a/notebooks/extra_examples/bqplot_A_plot_as_a_control_in_a_widget.ipynb b/notebooks/extras/extra_examples/bqplot_A_plot_as_a_control_in_a_widget.ipynb similarity index 100% rename from notebooks/extra_examples/bqplot_A_plot_as_a_control_in_a_widget.ipynb rename to notebooks/extras/extra_examples/bqplot_A_plot_as_a_control_in_a_widget.ipynb diff --git a/notebooks/extra_examples/flight_sim.ipynb b/notebooks/extras/extra_examples/flight_sim.ipynb similarity index 100% rename from notebooks/extra_examples/flight_sim.ipynb rename to notebooks/extras/extra_examples/flight_sim.ipynb diff --git a/notebooks/extra_examples/vaex.ipynb b/notebooks/extras/extra_examples/vaex.ipynb similarity index 100% rename from notebooks/extra_examples/vaex.ipynb rename to notebooks/extras/extra_examples/vaex.ipynb diff --git a/notebooks/extras/ipyleaflet.ipynb b/notebooks/extras/ipyleaflet.ipynb new file mode 100644 index 00000000..35ac5e96 --- /dev/null +++ b/notebooks/extras/ipyleaflet.ipynb @@ -0,0 +1,657 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + "< [More widget libraries](07.00-More_widget_libraries.ipynb) | [Contents](00.00-index.ipynb) | [pythreejs: 3D rendering in the browser](07.02-pythreejs.ipynb) >" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# ipyleaflet: Interactive maps\n", + "\n", + "## A Jupyter - LeafletJS bridge\n", + "\n", + "## https://github.com/jupyter-widgets/ipyleaflet\n", + "\n", + "\n", + "ipyleaflet is a jupyter interactive widget library which provides interactive maps to the Jupyter notebook.\n", + "\n", + "- MIT Licensed\n", + "\n", + "**Installation:**\n", + "\n", + "```bash\n", + "conda install -c conda-forge ipyleaflet\n", + "```" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from __future__ import print_function\n", + "\n", + "from ipyleaflet import (\n", + " Map,\n", + " Marker, MarkerCluster, TileLayer, ImageOverlay, GeoJSON,\n", + " Polyline, Polygon, Rectangle, Circle, CircleMarker, Popup,\n", + " SplitMapControl, WidgetControl,\n", + " basemaps, basemap_to_tiles\n", + ")\n", + "\n", + "from ipywidgets import HTML" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "center = [34.6252978589571, -77.34580993652344]\n", + "zoom = 10" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Map Widget" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "m = Map(center=center, zoom=zoom)\n", + "m" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "m.layout.height = '700px'" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Every Layer in ipyleaflet is a Widget: TileLayers, Markers, Heatmaps, Videos..." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "You can dynamically change the layers you apply on top of the Map, for example you can dynamically change the base TileLayer and control it through a SelectionSlider widget:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from datetime import date, timedelta\n", + "from ipywidgets import SelectionSlider\n", + "\n", + "n_days = 30\n", + "days = []\n", + "\n", + "# Previous 30 days, starting from today\n", + "for n in range(n_days):\n", + " delta = (n_days - n) * timedelta(days=1)\n", + " days.append((date.today() - delta).isoformat())\n", + "\n", + "days_slider = SelectionSlider(options=days, value=days[-1], description='Day: ')\n", + "\n", + "base_layer = basemap_to_tiles(basemaps.NASAGIBS.ModisTerraTrueColorCR, days_slider.value)\n", + "\n", + "m.layers = [base_layer]\n", + "m.zoom = 6" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "def handle_day_change(change, **kwargs):\n", + " base_layer.url = basemaps.NASAGIBS.ModisTerraTrueColorCR.get('url') % change['new']\n", + "\n", + "days_slider.observe(handle_day_change, 'value')\n", + "\n", + "days_slider" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# jupyterlab-sidecar: A sidecar output widget for JupyterLab\n", + "\n", + "## https://github.com/jupyter-widgets/jupyterlab-sidecar" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from sidecar import Sidecar\n", + "\n", + "sc = Sidecar(title='Map Output')\n", + "with sc:\n", + " m.layout.height = ''\n", + " display(m)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Marker" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "mark = Marker(location=m.center)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "m += mark" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "mark.visible" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "mark.visible = False" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "mark.visible = True" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "mark.interact(opacity=(0.0, 1.0, 0.01))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Popup\n", + "\n", + "The popup is displayed when you click on the marker. You can add ANY widget as a popup for the marker, from simple HTMLWidget to plots using bqplot." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from ipywidgets import FloatSlider, link\n", + "\n", + "slider = FloatSlider(value=1.0, min=0.0, max=1.0)\n", + "link((mark, 'opacity'), (slider, 'value'))\n", + "\n", + "mark.popup = slider" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Marker Cluster\n", + "\n", + "Markers can be clustered depending on the zoom level." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "xscale = 5\n", + "yscale = 10\n", + "\n", + "x = [m.center[0] + i * xscale * .05 for i in (-1,0,1)]\n", + "y = [m.center[1] + i * yscale * .05 for i in (-1,0,1)]\n", + "\n", + "from itertools import product\n", + "locations = product(x, y)\n", + "markers = [Marker(location=loc) for loc in locations]" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The `MarkerCluster` will automatically handle clustering and the zoom level changes." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "marker_cluster = MarkerCluster(markers=markers)\n", + "m += marker_cluster" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Heatmap" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from ipywidgets import Button, IntSlider, link\n", + "from ipyleaflet import Heatmap\n", + "from random import gauss\n", + "import time" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "center = (37.09, -103.66)\n", + "zoom = 5" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "def create_random_data(length):\n", + " \"Return a list of some random lat/lon/value triples.\"\n", + " return [[gauss(center[0], 2), \n", + " gauss(center[1], 4),\n", + " gauss(700, 300)] for i in range(length)]" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "m.center = center\n", + "m.zoom = zoom" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "heat = Heatmap(locations=create_random_data(1000), radius=20, blur=10)\n", + "m.add_layer(heat)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "def generate(_):\n", + " heat.locations = create_random_data(1000)\n", + "\n", + "button = Button(description='Generate data', button_style='success')\n", + "button.on_click(generate)\n", + "button" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "slider = IntSlider(min=10, max=30, value=heat.radius)\n", + "link((slider, 'value'), (heat, 'radius'))\n", + "slider" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Controls" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# The following NASA images need a zoom level <= 9\n", + "if m.zoom > 9:\n", + " m.zoom = 9\n", + "\n", + "control = SplitMapControl(\n", + " left_layer=basemap_to_tiles(basemaps.NASAGIBS.ModisTerraTrueColorCR, \"2017-11-11\") , \n", + " right_layer=basemap_to_tiles(basemaps.NASAGIBS.ModisAquaBands721CR, \"2017-11-11\")\n", + ")\n", + "m.add_control(control)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "m.remove_control(control)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from ipywidgets import IntSlider, Button, link\n", + "\n", + "button = Button(description='Goto NYC')\n", + "\n", + "def goto_nyc(*args, **kwargs):\n", + " # NYC: 40.7128° N, 74.0060° W\n", + " m.center = (40.7128, -74.0060)\n", + " m.zoom = 9\n", + "\n", + "button.on_click(goto_nyc)\n", + "\n", + "wid_control = WidgetControl(widget=button, position='bottomright')\n", + "m.add_control(wid_control)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## JupyterLab Themes Support\n", + "\n", + "Controls in ipyleaflet have support for JupyterLab themes, try changing the current theme" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "m.remove_control(wid_control)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Advanced example 1: Velocity\n", + "\n", + "*It is ok to skip this for now -- running it requires downloading a ~1MB data file.*\n", + "\n", + "This example needs an extra dependency that is not in the installation instructions and a dataset.\n", + "\n", + "You can install it with this:\n", + "\n", + "```bash\n", + "pip install netcdf4\n", + "```\n", + "\n", + "To download the data, copy and paste the code below into a new cell and run it.\n", + "\n", + "```python\n", + "import requests\n", + "\n", + "wind_data = requests.get('https://github.com/benbovy/xvelmap/raw/master/notebooks/wind-global.nc') \n", + "\n", + "with open('wind-global.nc', 'wb') as f: \n", + " f.write(wind_data.content)\n", + "```\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from ipyleaflet.velocity import Velocity\n", + "import xarray as xr" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "center = (0, 0)\n", + "zoom = 4\n", + "\n", + "m2 = Map(center=center, zoom=zoom, interpolation='nearest', basemap=basemaps.CartoDB.DarkMatter)\n", + "\n", + "sc2 = Sidecar(title='Map Velocity')\n", + "\n", + "with sc2:\n", + " display(m2)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "ds = xr.open_dataset('wind-global.nc')\n", + "display_options = {\n", + " 'velocityType': 'Global Wind',\n", + " 'displayPosition': 'bottomleft',\n", + " 'displayEmptyString': 'No wind data'\n", + "}\n", + "wind = Velocity(data=ds,\n", + " zonal_speed='u_wind',\n", + " meridional_speed='v_wind',\n", + " latitude_dimension='lat',\n", + " longitude_dimension='lon',\n", + " velocity_scale=0.01,\n", + " max_velocity=20,\n", + " display_options=display_options)\n", + "m2.add_layer(wind)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Advanced example" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from ipywidgets import Text, HTML, HBox\n", + "from ipyleaflet import GeoJSON, WidgetControl, Map \n", + "import json" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "m = Map(center = (43,-100), zoom = 4)\n", + "\n", + "geo_json_data = json.load(open('us-states-density-colored.json'))\n", + "geojson = GeoJSON(data=geo_json_data, hover_style={'color': 'black', 'dashArray': '5, 5', 'weight': 2})\n", + "m.add_layer(geojson)\n", + "\n", + "html = HTML('''\n", + "

US population density

\n", + " Hover over a state\n", + "''')\n", + "html.layout.margin = '0px 20px 20px 20px'\n", + "control = WidgetControl(widget=html, position='topright')\n", + "m.add_control(control)\n", + "\n", + "def update_html(properties, **kwargs):\n", + " html.value = '''\n", + "

US population density

\n", + "

{}

\n", + " {} people / mi^2\n", + " '''.format(properties['name'], properties['density'])\n", + "\n", + "geojson.on_hover(update_html)\n", + "\n", + "m" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Exercise" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### 1) Create a Map" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### 2) Create a slider controlling the zoom level of the Map (manually, not using interact)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### 3) Embed the slider in a WidgetControl on the bottom left of the map" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + "< [More widget libraries](07.00-More_widget_libraries.ipynb) | [Contents](00.00-index.ipynb) | [pythreejs: 3D rendering in the browser](07.02-pythreejs.ipynb) >" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "widgets-tutorial", + "language": "python", + "name": "widgets-tutorial" + }, + "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.8.10" + } + }, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/notebooks/widgets_libraries/ipympl.ipynb b/notebooks/extras/ipympl.ipynb similarity index 100% rename from notebooks/widgets_libraries/ipympl.ipynb rename to notebooks/extras/ipympl.ipynb diff --git a/notebooks/widgets_libraries/ipytree.ipynb b/notebooks/extras/ipytree.ipynb similarity index 100% rename from notebooks/widgets_libraries/ipytree.ipynb rename to notebooks/extras/ipytree.ipynb diff --git a/notebooks/07.More-libraries/07.04-ipyvolume.ipynb b/notebooks/extras/ipyvolume.ipynb similarity index 99% rename from notebooks/07.More-libraries/07.04-ipyvolume.ipynb rename to notebooks/extras/ipyvolume.ipynb index 53d3e9ef..c3f5e8a6 100644 --- a/notebooks/07.More-libraries/07.04-ipyvolume.ipynb +++ b/notebooks/extras/ipyvolume.ipynb @@ -224,7 +224,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.8.3" + "version": "3.8.10" } }, "nbformat": 4, diff --git a/notebooks/widgets_libraries/ipyvuetify.ipynb b/notebooks/extras/ipyvuetify.ipynb similarity index 100% rename from notebooks/widgets_libraries/ipyvuetify.ipynb rename to notebooks/extras/ipyvuetify.ipynb diff --git a/notebooks/widgets_libraries/ipywebrtc.ipynb b/notebooks/extras/ipywebrtc.ipynb similarity index 100% rename from notebooks/widgets_libraries/ipywebrtc.ipynb rename to notebooks/extras/ipywebrtc.ipynb diff --git a/notebooks/07.More-libraries/07.02-pythreejs.ipynb b/notebooks/extras/pythreejs.ipynb similarity index 100% rename from notebooks/07.More-libraries/07.02-pythreejs.ipynb rename to notebooks/extras/pythreejs.ipynb