Skip to content

Instantly share code, notes, and snippets.

@kylebarron
Last active April 30, 2022 17:20
Show Gist options
  • Select an option

  • Save kylebarron/29e8bb17087031f61a492390f95e4b94 to your computer and use it in GitHub Desktop.

Select an option

Save kylebarron/29e8bb17087031f61a492390f95e4b94 to your computer and use it in GitHub Desktop.

Revisions

  1. kylebarron renamed this gist Apr 30, 2022. 1 changed file with 0 additions and 0 deletions.
    File renamed without changes.
  2. kylebarron created this gist Apr 30, 2022.
    225 changes: 225 additions & 0 deletions panel_deckgl.html
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,225 @@
    <!DOCTYPE html>
    <html lang="en">
    <head>
    <meta charset="utf-8">
    <link rel="icon" type="image/x-icon" href="./favicon.png">
    <meta name="apple-mobile-web-app-capable" content="yes">
    <meta name="apple-mobile-web-app-status-bar-style" content="default">
    <meta name="theme-color" content="#0072b5">
    <meta name="name" content="Pyscript/Panel DeckGL Demo">

    <title>Pyscript/Panel DeckGL Demo</title>
    <link rel="icon" type="image/x-icon" href="./favicon.png">

    <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.15.3/css/all.min.css" type="text/css" />
    <link rel="stylesheet" href="https://unpkg.com/@holoviz/panel@0.13.0/dist/css/widgets.css" type="text/css" />
    <link rel="stylesheet" href="https://unpkg.com/@holoviz/panel@0.13.0/dist/css/markdown.css" type="text/css" />

    <script type="text/javascript" src="https://unpkg.com/h3-js@3.7.2/dist/h3-js.umd.js"></script>
    <script type="text/javascript" src="https://cdn.jsdelivr.net/npm/deck.gl@8.6.7/dist.min.js"></script>
    <script type="text/javascript" src="https://cdn.jsdelivr.net/npm/@deck.gl/json@8.6.7/dist.min.js"></script>
    <script type="text/javascript" src="https://cdn.jsdelivr.net/npm/@loaders.gl/csv@3.1.7/dist/dist.min.js"></script>
    <script type="text/javascript" src="https://cdn.jsdelivr.net/npm/@loaders.gl/json@3.1.7/dist/dist.min.js"></script>
    <script type="text/javascript" src="https://cdn.jsdelivr.net/npm/@loaders.gl/3d-tiles@3.1.7/dist/dist.min.js"></script>
    <script type="text/javascript" src="https://api.mapbox.com/mapbox-gl-js/v2.6.1/mapbox-gl.js"></script>
    <script type="text/javascript" src="https://cdn.bokeh.org/bokeh/release/bokeh-2.4.2.js"></script>
    <script type="text/javascript" src="https://cdn.bokeh.org/bokeh/release/bokeh-widgets-2.4.2.min.js"></script>
    <script type="text/javascript" src="https://cdn.bokeh.org/bokeh/release/bokeh-tables-2.4.2.min.js"></script>
    <script type="text/javascript" src="https://unpkg.com/@holoviz/panel@0.13.0/dist/panel.js"></script>

    <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@4.6.1/dist/css/bootstrap.min.css">
    <link rel="stylesheet" href="https://unpkg.com/@holoviz/panel@0.13.0/dist/bundled/bootstraptemplate/bootstrap.css">
    <link rel="stylesheet" href="https://unpkg.com/@holoviz/panel@0.13.0/dist/bundled/defaulttheme/default.css">

    <script src="https://cdn.jsdelivr.net/npm/jquery@3.5.1/dist/jquery.slim.min.js"></script>
    <script src="https://cdn.jsdelivr.net/npm/bootstrap@4.6.1/dist/js/bootstrap.bundle.min.js"></script>
    <style>
    #sidebar {
    width: 400px;
    }
    </style>
    <link rel="stylesheet" href="https://pyscript.net/alpha/pyscript.css" />
    <script defer src="https://pyscript.net/alpha/pyscript.js"></script>
    </head>
    <body>
    <py-env>
    - bokeh
    - numpy
    - pandas
    - panel==0.13.1a2
    </py-env>

    <div class="container-fluid d-flex flex-column vh-100 overflow-hidden" id="container">
    <nav class="navbar navbar-expand-md navbar-dark sticky-top shadow" id="header" style="background-color: #000000;">
    <button type="button" class="navbar-toggle collapsed" id="sidebarCollapse">
    <span class="navbar-toggler-icon"></span>
    </button>
    <div class="app-header">
    <a class="navbar-brand app-logo" href="/">
    <img src="./logo.png" class="app-logo">
    </a>
    <a class="title" href="" style="color: #f0ab3c;">Panel DeckGL NYC Taxi Demo</a>
    </div>
    </nav>

    <div class="row overflow-hidden" id="content">
    <div class="sidenav" id="sidebar">
    <ul class="nav flex-column">
    <div class="bk-root" id="widgets"></div>
    <py-repl id="my-repl" auto-generate="true"> </py-repl>
    </ul>
    </div>
    <div class="col mh-100 float-left" style="padding: 0;">
    <div class="bk-root" id="plot"></div>
    </div>
    </div>
    </div>
    <py-script>
    import asyncio

    import panel as pn
    import param
    import pandas as pd

    from panel.io.pyodide import show
    from pyodide.http import open_url

    MAPBOX_KEY = "pk.eyJ1IjoicGFuZWxvcmciLCJhIjoiY2s1enA3ejhyMWhmZjNobjM1NXhtbWRrMyJ9.B_frQsAVepGIe-HiOJeqvQ"

    class App(pn.viewable.Viewer):

    data = param.DataFrame(precedence=-1)

    view = param.DataFrame(precedence=-1)

    arc_view = param.DataFrame(precedence=-1)

    radius = param.Integer(default=50, bounds=(20, 1000))

    elevation = param.Integer(default=10, bounds=(0, 50))

    hour = param.Integer(default=0, bounds=(0, 23))

    speed = param.Integer(default=1, bounds=(0, 10), precedence=-1)

    play = param.Event(label='▷')

    def __init__(self, **params):
    self.deck_gl = None
    super().__init__(**params)
    self.deck_gl = pn.pane.DeckGL(
    dict(self.spec),
    mapbox_api_key=MAPBOX_KEY,
    throttle={'click': 10},
    sizing_mode='stretch_both',
    margin=0
    )
    self.deck_gl.param.watch(self._update_arc_view, 'click_state')
    self._playing = False
    self._cb = pn.state.add_periodic_callback(
    self._update_hour, 1000//self.speed, start=False
    )

    def __panel__(self):
    return self.deck_gl

    @property
    def spec(self):
    return {
    "initialViewState": {
    "bearing": 0,
    "latitude": 40.7,
    "longitude": -73.9,
    "maxZoom": 15,
    "minZoom": 5,
    "pitch": 40.5,
    "zoom": 11
    },
    "layers": [self.hex_layer, self.arc_layer],
    "mapStyle": "mapbox://styles/mapbox/dark-v9",
    "views": [
    {"@@type": "MapView", "controller": True}
    ]
    }

    @property
    def hex_layer(self):
    return {
    "@@type": "HexagonLayer",
    "autoHighlight": True,
    "coverage": 1,
    "data": self.data if self.view is None else self.view,
    "elevationRange": [0, 100],
    "elevationScale": self.elevation,
    "radius": self.radius,
    "extruded": True,
    "getPosition": "@@=[pickup_x, pickup_y]",
    "id": "8a553b25-ef3a-489c-bbe2-e102d18a3211"
    }

    @property
    def arc_layer(self):
    return {
    "@@type": "ArcLayer",
    "id": 'arc-layer',
    "data": self.arc_view,
    "pickable": True,
    "getWidth": 1,
    "getSourcePosition": "@@=[pickup_x, pickup_y]",
    "getTargetPosition": "@@=[dropoff_x, dropoff_y]",
    "getSourceColor": [0, 255, 0, 180],
    "getTargetColor": [240, 100, 0, 180]
    }

    def _update_hour(self):
    self.hour = (self.hour+1) % 24

    @param.depends('view', watch=True)
    def _update_arc_view(self, event=None):
    data = self.data if self.view is None else self.view
    if not self.deck_gl or not self.deck_gl.click_state:
    self.arc_view = data.iloc[:0]
    else:
    lon, lat = self.deck_gl.click_state['coordinate']
    tol = 0.001
    self.arc_view = data[
    (df.pickup_x>=float(lon-tol)) &
    (df.pickup_x<=float(lon+tol)) &
    (df.pickup_y>=float(lat-tol)) &
    (df.pickup_y<=float(lat+tol))
    ]

    @param.depends('hour', watch=True, on_init=True)
    def _update_hourly_view(self):
    self.view = self.data[self.data.hour==self.hour]

    @param.depends('speed', watch=True)
    def _update_speed(self):
    self._cb.period = 1000//self.speed

    @param.depends('play', watch=True)
    def _play_pause(self):
    if self._playing:
    self._cb.stop()
    self.param.play.label = '▷'
    self.param.speed.precedence = -1
    else:
    self._cb.start()
    self.param.play.label = '❚❚'
    self.param.speed.precedence = 1
    self._playing = not self._playing

    @param.depends('view', 'radius', 'elevation', 'arc_view', watch=True)
    def update_spec(self):
    self.deck_gl.object = dict(self.spec)

    url = 'https://s3.eu-west-1.amazonaws.com/assets.holoviews.org/data/nyc_taxi_wide.csv'
    df = pd.read_csv(open_url(url))
    app = App(data=df)
    controls = pn.Param(app.param, sizing_mode='stretch_width', show_name=False)

    await show(controls, 'widgets')
    await show(app, 'plot')
    </py-script>
    </body>
    </html>