diff --git a/panel/models/data.ts b/panel/models/data.ts index 21b2f9d460..07dc1994d0 100644 --- a/panel/models/data.ts +++ b/panel/models/data.ts @@ -1,10 +1,11 @@ import {ColumnDataSource} from "@bokehjs/models/sources/column_data_source"; + export function transform_cds_to_records(cds: ColumnDataSource, addId: boolean = false, start: number = 0): any { const data: any = [] const columns = cds.columns() const cdsLength = cds.get_length() - if (columns.length === 0||cdsLength === null) + if (columns.length === 0 || cdsLength === null) return [] for (let i = start; i < cdsLength; i++) { @@ -14,7 +15,10 @@ export function transform_cds_to_records(cds: ColumnDataSource, addId: boolean = const shape = (array[0] == null || array[0].shape == null) ? null : array[0].shape; if ((shape != null) && (shape.length > 1) && (typeof shape[0] == "number")) item[column] = array.slice(i*shape[1], i*shape[1]+shape[1]) - else + else if (array.length != cdsLength && (array.length % cdsLength === 0)) { + const offset = array.length / cdsLength + item[column] = array.slice(i*offset, i*offset+offset) + } else item[column] = array[i] } if (addId) diff --git a/panel/pane/deckgl.py b/panel/pane/deckgl.py index a4ccb77646..fc8ff0c1b1 100644 --- a/panel/pane/deckgl.py +++ b/panel/pane/deckgl.py @@ -15,6 +15,7 @@ import numpy as np import param +from bokeh.core.serialization import Serializer from bokeh.models import ColumnDataSource from pyviz_comms import JupyterComm @@ -123,6 +124,8 @@ class DeckGL(ModelPane): 'view_state': 'viewState', 'tooltips': 'tooltip' } + _pydeck_encoders_are_added: ClassVar[bool] = False + _updates: ClassVar[bool] = True priority: ClassVar[float | bool | None] = None @@ -196,6 +199,36 @@ def _update_sources(cls, json_data, sources): sources.append(cds) layer['data'] = sources.index(cds) + @classmethod + def _add_pydeck_encoders(cls): + if cls._pydeck_encoders_are_added or 'pydeck' not in sys.modules: + return + + from pydeck.types import String + def pydeck_string_encoder(obj, serializer): + return obj.value + + Serializer._encoders[String] = pydeck_string_encoder + + def _transform_deck_object(self, obj): + data = dict(obj.__dict__) + mapbox_api_key = data.pop('mapbox_key', "") or self.mapbox_api_key + deck_widget = data.pop('deck_widget', None) + if isinstance(self.tooltips, dict) or deck_widget is None: + tooltip = self.tooltips + else: + tooltip = deck_widget.tooltip + data = {k: v for k, v in recurse_data(data).items() if v is not None} + + if "initialViewState" in data: + data["initialViewState"]={ + k:v for k, v in data["initialViewState"].items() if v is not None + } + + self._add_pydeck_encoders() + + return data, tooltip, mapbox_api_key + def _transform_object(self, obj) -> Dict[str, Any]: if self.object is None: data, mapbox_api_key, tooltip = {}, self.mapbox_api_key, self.tooltips @@ -208,14 +241,7 @@ def _transform_object(self, obj) -> Dict[str, Any]: mapbox_api_key = self.mapbox_api_key tooltip = self.tooltips else: - data = dict(self.object.__dict__) - mapbox_api_key = data.pop('mapbox_key', self.mapbox_api_key) - deck_widget = data.pop('deck_widget', None) - if isinstance(self.tooltips, dict) or deck_widget is None: - tooltip = self.tooltips - else: - tooltip = deck_widget.tooltip - data = {k: v for k, v in recurse_data(data).items() if v is not None} + data, tooltip, mapbox_api_key = self._transform_deck_object(self.object) # Delete undefined width and height for view in data.get('views', []): diff --git a/panel/tests/pane/test_deckgl.py b/panel/tests/pane/test_deckgl.py index 981270c941..3fccbd1b1a 100644 --- a/panel/tests/pane/test_deckgl.py +++ b/panel/tests/pane/test_deckgl.py @@ -8,6 +8,8 @@ pydeck_available = pytest.mark.skipif(pydeck is None, reason="requires pydeck") +from bokeh.core.serialization import Serializer + from panel.models.deckgl import DeckGLPlot from panel.pane import DeckGL, PaneBase, panel @@ -159,3 +161,56 @@ def test_deckgl_insert_layer(document, comm): assert cds1.data['b'] is b_vals assert np.array_equal(cds2.data['b'], np.array([3, 9])) assert np.array_equal(cds2.data['c'], np.array([1, 3])) + +@pydeck_available +def test_pydeck_mapbox_api_key_issue_5790(document, comm): + deck_wo_key = pydeck.Deck() + pane_w_key = DeckGL(deck_wo_key, mapbox_api_key="ABC") + + model = pane_w_key.get_root(document, comm=comm) + assert model.mapbox_api_key == "ABC" + +@pydeck_available +def test_pydeck_no_min_max_zoom_issue_5790(document, comm): + state_w_no_min_max_zoom = { + "latitude": 37.7749, + "longitude": -122.4194, + "zoom": 10, + "bearing": 0, + "pitch": 0, + } + view_state = pydeck.ViewState(**state_w_no_min_max_zoom) + deck = pydeck.Deck(initial_view_state=view_state) + pane = DeckGL(deck) + + model = pane.get_root(document, comm=comm) + assert model.initialViewState == state_w_no_min_max_zoom + +@pydeck_available +def test_pydeck_type_string_can_be_serialized_issue_5790(document, comm): + serializer = Serializer(references=document.models.synced_references) + data = [ + { + "name": "24th St. Mission (24TH)", + "code": "24", + "address": "2800 Mission Street, San Francisco CA 94110", + "entries": 12817, + "exits": 12529, + # "coordinates": [-122.418466, 37.752254] + } + ] + + + layer = pydeck.Layer( + "TextLayer", + data, + get_text_anchor=pydeck.types.String("middle"), + get_alignment_baseline=pydeck.types.String("center"), + size_units = pydeck.types.String("meters") # <--- The key addition to switch to meters as the units. + ) + deck = pydeck.Deck(layers=[layer]) + pane = DeckGL(deck) + + model = pane.get_root(document, comm=comm) + serializer.serialize(model) + assert Serializer._encoders.pop(pydeck.types.String)