From 35793c579c3b85577f3f1e7458c6d462041ceb91 Mon Sep 17 00:00:00 2001 From: Philipp Rudiger Date: Tue, 9 Jan 2024 17:33:15 +0100 Subject: [PATCH] Correctly reshape nd-arrays in Plotly pane (#6174) * Correctly reshape nd-arrays in Plotly pane * Add test --- panel/models/plotly.ts | 9 ++------ panel/models/util.ts | 25 ++++++++++++++++++++++ panel/tests/ui/pane/test_plotly.py | 34 ++++++++++++++++++++++++++++++ 3 files changed, 61 insertions(+), 7 deletions(-) diff --git a/panel/models/plotly.ts b/panel/models/plotly.ts index ec0fd2080f..0c59aa1377 100644 --- a/panel/models/plotly.ts +++ b/panel/models/plotly.ts @@ -5,7 +5,7 @@ import {is_equal} from "@bokehjs/core/util/eq" import {ColumnDataSource} from "@bokehjs/models/sources/column_data_source"; import {debounce} from "debounce" -import {deepCopy, isPlainObject, get, throttle} from "./util" +import {deepCopy, isPlainObject, get, reshape, throttle} from "./util" import {HTMLBox, HTMLBoxView, set_size} from "./layout" @@ -321,12 +321,7 @@ export class PlotlyPlotView extends HTMLBoxView { for (const column of cds.columns()) { let array = cds.get_array(column)[0]; if (array.shape != null && array.shape.length > 1) { - const arrays = []; - const shape = array.shape; - for (let s = 0; s < shape[0]; s++) { - arrays.push(array.slice(s*shape[1], (s+1)*shape[1])); - } - array = arrays; + array = reshape(array, array.shape); } let prop_path = column.split("."); let prop = prop_path[prop_path.length - 1]; diff --git a/panel/models/util.ts b/panel/models/util.ts index 59397cd8a6..e16bc40275 100644 --- a/panel/models/util.ts +++ b/panel/models/util.ts @@ -1,3 +1,6 @@ +import {concat} from "@bokehjs/core/util/array" + + export const get = (obj: any, path: string, defaultValue: any = undefined) => { const travel = (regexp: RegExp) => String.prototype.split @@ -51,3 +54,25 @@ export function deepCopy(obj: any): any { export function isPlainObject (obj: any) { return Object.prototype.toString.call(obj) === '[object Object]'; } + +export function reshape(arr: any[], dim: number[]) { + let elemIndex = 0; + + if (!dim || !arr) return []; + + function _nest(dimIndex: number): any[] { + let result = []; + + if (dimIndex === dim.length - 1) { + result = concat(arr.slice(elemIndex, elemIndex + dim[dimIndex])); + elemIndex += dim[dimIndex]; + } else { + for (let i = 0; i < dim[dimIndex]; i++) { + result.push(_nest(dimIndex + 1)); + } + } + + return result; + } + return _nest(0); +} diff --git a/panel/tests/ui/pane/test_plotly.py b/panel/tests/ui/pane/test_plotly.py index c99ba369b8..402e66e2b8 100644 --- a/panel/tests/ui/pane/test_plotly.py +++ b/panel/tests/ui/pane/test_plotly.py @@ -46,6 +46,22 @@ def plotly_3d_plot(): return plot_3d, title +@pytest.fixture +def plotly_img_plot(): + fig_dict = dict( + data={ + "z": np.random.randint(0, 255, size=(6, 30, 3)).astype(np.uint8), + "type": "image", + }, + layout={ + "width": 300, + "height": 60, + "margin": {"l": 0, "r": 0, "b": 0, "t": 0}, + }, + ) + return Plotly(fig_dict, width=300, height=60) + + def test_plotly_no_console_errors(page, plotly_2d_plot): msgs, _ = serve_component(page, plotly_2d_plot) @@ -54,6 +70,7 @@ def test_plotly_no_console_errors(page, plotly_2d_plot): assert [msg for msg in msgs if msg.type == 'error' and 'favicon' not in msg.location['url']] == [] + def test_plotly_2d_plot(page, plotly_2d_plot): serve_component(page, plotly_2d_plot) @@ -185,3 +202,20 @@ def test_plotly_select_data(page, plotly_2d_plot): assert 'range' in selected assert 'x' in selected['range'] assert 'y' in selected['range'] + + + +def test_plotly_img_plot(page, plotly_img_plot): + msgs, _ = serve_component(page, plotly_img_plot) + + # main pane + plotly_plot = page.locator('.js-plotly-plot .plot-container.plotly') + expect(plotly_plot).to_have_count(1) + + assert [msg for msg in msgs if msg.type == 'error' and 'favicon' not in msg.location['url']] == [] + + # Select and hover on first point + point = plotly_plot.locator('image') + point.hover(force=True) + + wait_until(lambda: plotly_img_plot.hover_data == {'points': [{'curveNumber': 0, 'x': 15, 'y': 3, 'colormodel': 'rgb'}]}, page)