Skip to content

Commit

Permalink
Multi-range sliders for DataFilterExtension (#340)
Browse files Browse the repository at this point in the history
Closes #339 

### Change list

- Add `MultiRangeSlider`, a subclass of `ipywidgets.VBox` to connect
multiple float range sliders
- Add to API docs
  • Loading branch information
kylebarron committed Jan 30, 2024
1 parent 949b272 commit 2ba95c9
Show file tree
Hide file tree
Showing 6 changed files with 150 additions and 2 deletions.
3 changes: 3 additions & 0 deletions docs/api/controls.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# lonboard.controls

::: lonboard.controls.MultiRangeSlider
2 changes: 1 addition & 1 deletion lonboard/__init__.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
"""Python library for fast, interactive geospatial vector data visualization in Jupyter.
"""

from . import colormap, traits
from . import colormap, controls, traits
from ._layer import (
BaseArrowLayer,
BaseLayer,
Expand Down
81 changes: 81 additions & 0 deletions lonboard/controls.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
from functools import partial
from typing import Sequence

import traitlets
from ipywidgets import FloatRangeSlider
from ipywidgets.widgets.trait_types import TypedTuple

# Import from source to allow mkdocstrings to link to base class
from ipywidgets.widgets.widget_box import VBox


class MultiRangeSlider(VBox):
"""A widget for multiple ranged sliders.
This is designed to be used with the
[DataFilterExtension][lonboard.experimental.DataFilterExtension] when you want to
filter on 2 to 4 columns on the same time.
If you have only a single filter, use an ipywidgets
[FloatRangeSlider][ipywidgets.widgets.widget_float.FloatRangeSlider] directly.
# Example
```py
from ipywidgets import FloatRangeSlider
slider1 = FloatRangeSlider(
value=(2, 5),
min=0,
max=10,
step=0.1,
description="First slider: "
)
slider2 = FloatRangeSlider(
value=(30, 40),
min=0,
max=50,
step=1,
description="Second slider: "
)
multi_slider = MultiRangeSlider([slider1, slider2])
multi_slider
```
Then to propagate updates to a rendered layer, call
[jsdlink][ipywidgets.widgets.widget_link.jsdlink] to connect the two widgets.
```py
from ipywidgets import jsdlink
jsdlink(
(multi_slider, "value"),
(layer, "filter_range")
)
```
As you change the slider, the `filter_range` value on the layer class should be
updated.
"""

# We use a tuple to force reassignment to update the list
# This is because list mutations do not get propagated as events
# https://github.com/jupyter-widgets/ipywidgets/blob/b2531796d414b0970f18050d6819d932417b9953/python/ipywidgets/ipywidgets/widgets/widget_box.py#L52-L54
value = TypedTuple(trait=TypedTuple(trait=traitlets.Float())).tag(sync=True)

def __init__(self, children: Sequence[FloatRangeSlider], **kwargs):
# We manage a list of lists to match what deck.gl expects for the
# DataFilterExtension
def callback(change, *, i: int):
value = list(self.value)
value[i] = change["new"]
self.set_trait("value", value)
self.send_state("value")

initial_values = []
for i, child in enumerate(children):
func = partial(callback, i=i)
child.observe(func, "value")
initial_values.append(child.value)

super().__init__(children, value=initial_values, **kwargs)
62 changes: 62 additions & 0 deletions lonboard/experimental/layer_extension.py
Original file line number Diff line number Diff line change
Expand Up @@ -148,6 +148,68 @@ class DataFilterExtension(BaseExtension):
)
```
The `DataFilterExtension` allows filtering on 1 to 4 attributes at the same time. So
if you have four numeric columns of interest, you can filter on the intersection of
all of them.
For easy visualization, we suggest connecting the `DataFilterExtension` to an
interactive slider from `ipywidgets`.
```py
from ipywidgets import FloatRangeSlider
slider = FloatRangeSlider(
value=(2, 5),
min=0,
max=10,
step=0.1,
description="Slider: "
)
slider
jsdlink(
(slider, "value"),
(layer, "filter_range")
)
```
If you have 2 to 4 columns, use a
[`MultiRangeSlider`][lonboard.controls.MultiRangeSlider], which combines multiple
`FloatRangeSlider` objects in a form that the `DataFilterExtension` expects.
```py
from ipywidgets import FloatRangeSlider, jsdlink
slider1 = FloatRangeSlider(
value=(2, 5),
min=0,
max=10,
step=0.1,
description="First slider: "
)
slider2 = FloatRangeSlider(
value=(30, 40),
min=0,
max=50,
step=1,
description="Second slider: "
)
multi_slider = MultiRangeSlider([slider1, slider2])
multi_slider
jsdlink(
(multi_slider, "value"),
(layer, "filter_range")
)
```
# Important notes
- The DataFilterExtension only supports float32 data, so integer data will be casted
to float32.
- The DataFilterExtension copies all data referenced by `get_filter_value` to the
GPU, so it will increase memory pressure on the GPU.
# Layer Properties
## `filter_enabled`
Expand Down
2 changes: 2 additions & 0 deletions mkdocs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ nav:
- api/layers/base-layer.md
- api/basemap.md
- api/colormap.md
- api/controls.md
- api/traits.md
- Experimental:
- Layer Extensions:
Expand Down Expand Up @@ -144,6 +145,7 @@ plugins:
- https://geoarrow.github.io/geoarrow-rs/python/latest/objects.inv
- https://geopandas.org/en/stable/objects.inv
- https://geopy.readthedocs.io/en/stable/objects.inv
- https://ipywidgets.readthedocs.io/en/stable/objects.inv
- https://matplotlib.org/stable/objects.inv
- https://numpy.org/doc/stable/objects.inv
- https://pandas.pydata.org/pandas-docs/stable/objects.inv
Expand Down
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[tool.poetry]
name = "lonboard"
version = "0.5.0"
version = "0.6.0-beta.1"
description = "Python library for fast, interactive geospatial vector data visualization in Jupyter."
authors = ["Kyle Barron <kyle@developmentseed.org>"]
license = "MIT"
Expand Down

0 comments on commit 2ba95c9

Please sign in to comment.