{ "cells": [ { "cell_type": "code", "execution_count": null, "id": "fbba7ec1-5e4c-4d04-a21a-5b5d3fb67bf9", "metadata": {}, "outputs": [], "source": [ "import panel as pn\n", "import pandas as pd\n", "import param\n", "\n", "pn.extension('deckgl', design='bootstrap', theme='dark', template='bootstrap')\n", "\n", "pn.state.template.config.raw_css.append(\"\"\"\n", "#main {\n", " padding: 0;\n", "}\"\"\")" ] }, { "cell_type": "markdown", "id": "9925cce4-516f-4f44-8c5d-cd7cb72d3f16", "metadata": {}, "source": [ "## Define App" ] }, { "cell_type": "code", "execution_count": null, "id": "0574d7b1-ba4c-4c17-b35f-5819415a9aef", "metadata": {}, "outputs": [], "source": [ "class App(pn.viewable.Viewer):\n", "\n", " data = param.DataFrame(precedence=-1)\n", "\n", " view = param.DataFrame(precedence=-1)\n", "\n", " arc_view = param.DataFrame(precedence=-1)\n", "\n", " radius = param.Integer(default=50, bounds=(20, 1000))\n", "\n", " elevation = param.Integer(default=10, bounds=(0, 50))\n", "\n", " hour = param.Integer(default=0, bounds=(0, 23))\n", "\n", " speed = param.Integer(default=1, bounds=(0, 10), precedence=-1)\n", "\n", " play = param.Event(label='▷')\n", "\n", " def __init__(self, **params):\n", " self.deck_gl = None\n", " super().__init__(**params)\n", " self._update_arc_view()\n", " self.deck_gl = pn.pane.DeckGL(\n", " self.spec,\n", " throttle={'click': 10},\n", " sizing_mode='stretch_both',\n", " margin=0\n", " )\n", " self.deck_gl.param.watch(self._update_arc_view, 'click_state')\n", " self._playing = False\n", " self._cb = pn.state.add_periodic_callback(\n", " self._update_hour, 1000//self.speed, start=False\n", " )\n", "\n", " @param.depends('view', 'radius', 'elevation', 'arc_view')\n", " def spec(self):\n", " return {\n", " \"initialViewState\": {\n", " \"bearing\": 0,\n", " \"latitude\": 40.7,\n", " \"longitude\": -73.9,\n", " \"maxZoom\": 15,\n", " \"minZoom\": 5,\n", " \"pitch\": 40.5,\n", " \"zoom\": 11\n", " },\n", " \"mapStyle\": \"https://basemaps.cartocdn.com/gl/dark-matter-gl-style/style.json\",\n", " \"layers\": [self.hex_layer, self.arc_layer],\n", " \"views\": [\n", " {\"@@type\": \"MapView\", \"controller\": True}\n", " ]\n", " }\n", "\n", " @property\n", " def hex_layer(self):\n", " return {\n", " \"@@type\": \"HexagonLayer\",\n", " \"autoHighlight\": True,\n", " \"coverage\": 1,\n", " \"data\": self.data if self.view is None else self.view,\n", " \"elevationRange\": [0, 100],\n", " \"elevationScale\": self.elevation,\n", " \"radius\": self.radius,\n", " \"extruded\": True,\n", " \"getPosition\": \"@@=[pickup_x, pickup_y]\",\n", " \"id\": \"8a553b25-ef3a-489c-bbe2-e102d18a3211\"\n", " }\n", "\n", " @property\n", " def arc_layer(self):\n", " return {\n", " \"@@type\": \"ArcLayer\",\n", " \"id\": 'arc-layer',\n", " \"data\": self.arc_view,\n", " \"pickable\": True,\n", " \"getWidth\": 2,\n", " \"getSourcePosition\": \"@@=[pickup_x, pickup_y]\",\n", " \"getTargetPosition\": \"@@=[dropoff_x, dropoff_y]\",\n", " \"getSourceColor\": [0, 255, 0, 180],\n", " \"getTargetColor\": [240, 100, 0, 180]\n", " }\n", "\n", " def _update_hour(self):\n", " self.hour = (self.hour+1) % 24\n", "\n", " @param.depends('hour', watch=True, on_init=True)\n", " def _update_hourly_view(self):\n", " self.view = self.data[self.data.hour==self.hour]\n", "\n", " @param.depends('view', 'radius', watch=True)\n", " def _update_arc_view(self, event=None):\n", " data = self.data if self.view is None else self.view\n", " lon, lat, = (-73.9857, 40.7484)\n", " if self.deck_gl:\n", " lon, lat = self.deck_gl.click_state.get('coordinate', (lon, lat))\n", " tol = self.radius / 100000\n", " self.arc_view = data[\n", " (data.pickup_x>=float(lon-tol)) &\n", " (data.pickup_x<=float(lon+tol)) &\n", " (data.pickup_y>=float(lat-tol)) &\n", " (data.pickup_y<=float(lat+tol))\n", " ]\n", "\n", " @param.depends('speed', watch=True)\n", " def _update_speed(self):\n", " self._cb.period = 1000//self.speed\n", "\n", " @param.depends('play', watch=True)\n", " def _play_pause(self):\n", " if self._playing:\n", " self._cb.stop()\n", " self.param.play.label = '▷'\n", " self.param.speed.precedence = -1\n", " else:\n", " self._cb.start()\n", " self.param.play.label = '❚❚'\n", " self.param.speed.precedence = 1\n", " self._playing = not self._playing\n", "\n", " @property\n", " def controls(self):\n", " return pn.Param(app.param, show_name=False)\n", "\n", " def __panel__(self):\n", " return pn.Row(\n", " self.controls,\n", " self.deck_gl,\n", " min_height=800,\n", " sizing_mode='stretch_both',\n", " )" ] }, { "cell_type": "markdown", "id": "fbda0317-8c0c-47b1-8247-2cf223d8a6ad", "metadata": {}, "source": [ "## Display app" ] }, { "cell_type": "code", "execution_count": null, "id": "189e7992-6988-49bf-96d5-46d6ddbaa26e", "metadata": {}, "outputs": [], "source": [ "df = pd.read_parquet('https://datasets.holoviz.org/nyc_taxi_small/v1/nyc_taxi_small.parq')\n", "\n", "app = App(data=df)\n", "\n", "app.controls.servable(area='sidebar')\n", "app.deck_gl.servable(title='NYC Taxi Deck.GL Explorer')\n", "\n", "app" ] } ], "metadata": { "language_info": { "name": "python", "pygments_lexer": "ipython3" } }, "nbformat": 4, "nbformat_minor": 5 }