diff --git a/app/packages/app/src/useEvents/useEvents.ts b/app/packages/app/src/useEvents/useEvents.ts index 04c2f7f345..182e36c67e 100644 --- a/app/packages/app/src/useEvents/useEvents.ts +++ b/app/packages/app/src/useEvents/useEvents.ts @@ -27,7 +27,7 @@ const useEvents = ( return { subscriptions, handler: useCallback((event: string, payload: string) => { - if (event === "ping") { + if (event === "ping" || event === "") { return; } diff --git a/app/packages/core/src/plugins/SchemaIO/components/PlotlyView.tsx b/app/packages/core/src/plugins/SchemaIO/components/PlotlyView.tsx index 7465cf4a9b..d7a18b33c8 100644 --- a/app/packages/core/src/plugins/SchemaIO/components/PlotlyView.tsx +++ b/app/packages/core/src/plugins/SchemaIO/components/PlotlyView.tsx @@ -9,6 +9,30 @@ import { HeaderView } from "."; import { getComponentProps } from "../utils"; import { ViewPropsType } from "../utils/types"; +type TraceWithIds = { + name?: string; + ids?: string[]; +}; + +function getIdForTrace( + point: Plotly.Point, + trace: TraceWithIds, + options: { is2DArray?: boolean } = {} +) { + const { is2DArray = false } = options; + const { data } = point; + const { x, y, z } = data; + if (trace?.ids) { + if (is2DArray) { + const [xIdx, yIdx] = point.pointIndex; + return trace.ids[yIdx][xIdx]; + } else { + return trace.ids[point.pointIndex]; + } + } + return null; +} + export default function PlotlyView(props: ViewPropsType) { const { data, schema, path, relativeLayout } = props; const { view = {} } = schema; @@ -23,10 +47,12 @@ export default function PlotlyView(props: ViewPropsType) { const data = EventDataMappers[event]?.(e) || {}; let xValue = null; let yValue = null; + let zValue = null; let value; let label; + let id = null; + if (event === "onClick") { - const values = e.points[0]; let selected = []; let xBinsSize = null; for (const p of e.points) { @@ -49,10 +75,12 @@ export default function PlotlyView(props: ViewPropsType) { } else if (type === "heatmap") { xValue = p.x; yValue = p.y; + zValue = p.z; } else if (type === "pie") { value = p.v; label = p.label; } + id = getIdForTrace(p, fullData, { is2DArray: type === "heatmap" }); } if (selected.length === 0) { selected = null; @@ -61,6 +89,7 @@ export default function PlotlyView(props: ViewPropsType) { const eventHandlerOperator = view[snakeCase(event)]; const defaultParams = { + id, path: props.path, relative_path: props.relativePath, schema: props.schema, @@ -68,16 +97,24 @@ export default function PlotlyView(props: ViewPropsType) { event, value, label, + shift_pressed: Boolean(e?.event?.shiftKey), }; if (eventHandlerOperator) { let params = {}; if (event === "onClick") { + const eventData = e as Plotly.PlotMouseEvent; params = { ...defaultParams, range, x: xValue, y: yValue, + z: zValue, + idx: e.points[0].pointIndex, + trace: eventData.points[0].data.name, + trace_idx: eventData.points[0].curveNumber, + value, + label, }; } else if (event === "onSelected") { params = { @@ -85,6 +122,11 @@ export default function PlotlyView(props: ViewPropsType) { data, path, }; + } else { + params = { + ...defaultParams, + data, + }; } triggerPanelEvent(panelId, { @@ -235,6 +277,7 @@ const EventDataMappers = { const result = { ...pointdata, data: metadata, + trace: fullData.name, }; return result; }, @@ -245,6 +288,8 @@ const EventDataMappers = { const { data, fullData, xaxis, yaxis, ...pointdata } = point; const { x, y, z, ids, selectedpoints, ...metadata } = data; selected.push({ + trace: fullData.name, + trace_idx: point.curveNumber, idx: point.pointIndex, id: Array.isArray(ids) ? ids[point.pointIndex] : null, x: Array.isArray(x) ? x[point.pointIndex] : null, diff --git a/fiftyone/operators/types.py b/fiftyone/operators/types.py index cc60ec2ee7..525d65ec1e 100644 --- a/fiftyone/operators/types.py +++ b/fiftyone/operators/types.py @@ -1483,10 +1483,51 @@ class PlotlyView(View): See https://github.com/plotly/react-plotly.js/#basic-props for documentation. + Examples:: + + def render(self, ctx): + panel.plot("my_plot", on_click=self.on_click, on_selected=self.on_selected) + + def print_params(self, ctx, params): + for key, value in params.items(): + ctx.print(f"{key}: {value}") + + def on_click(self, ctx): + # available params + self.print_prams(ctx, { + "id": "id", # the corresponding data.ids[idx] + "idx": 1, # the index of the clicked point + "label": "label", # label (eg. on pie charts) + "shift_pressed": false, # whether the shift key was pressed + "trace": "my_trace", # data[trace_idx].name + "trace_idx": 0, + "value": "my_value", # data[trace_idx].values[idx] (eg. on a pie chart) + "x": 2, # data[trace_idx].x[idx] (the x value on most plot types) + "y": 3, # data[trace_idx].y[idx] (the y value on most plot types) + "z": 4, # data[trace_idx].z[idx] (the z value on 3d plots eg. heatmap) + }) + + def on_selected(self, ctx): + prin(ctx.params['data']) + # [ + # { + # "trace": "trace 0", # data[trace_idx].name + # "trace_idx": 0, # the index of the trace + # "idx": 1, # the index of the selected point + # "id": "one", # the corresponding data.ids[idx] + # "x": 2, # the x value of the selected point + # "y": 15, # the y value of the selected point + # "z": 22 # the z value of the selected point + # } + # ] + Args: data (None): the chart data config (None): the chart config layout (None): the chart layout + on_click (None): event handler for click events + on_selected (None): event handler for selected events + on_double_click (None): event handler for double click events """ def __init__(self, **kwargs):