Skip to content

Commit

Permalink
feat: plotly-express Deephaven UI widget loading (#119)
Browse files Browse the repository at this point in the history
Updates plotly-express to use the latest web-client-ui packages which
allow for loading it properly via widget plugins after the ui.panel PR
changes
  • Loading branch information
mattrunyon authored Nov 21, 2023
1 parent 0f6bf62 commit 878aa91
Show file tree
Hide file tree
Showing 9 changed files with 1,063 additions and 1,290 deletions.
2,118 changes: 917 additions & 1,201 deletions package-lock.json

Large diffs are not rendered by default.

20 changes: 10 additions & 10 deletions plugins/plotly-express/src/js/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@
"build": "tsc && vite build"
},
"devDependencies": {
"@deephaven/jsapi-types": "0.53.0",
"@deephaven/jsapi-types": "0.55.0",
"@types/deep-equal": "^1.0.1",
"@types/plotly.js": "^2.12.18",
"@types/plotly.js-dist-min": "^2.3.1",
Expand All @@ -50,15 +50,15 @@
"react": "^17.0.2"
},
"dependencies": {
"@deephaven/chart": "0.53.0",
"@deephaven/components": "0.53.0",
"@deephaven/dashboard": "0.53.0",
"@deephaven/dashboard-core-plugins": "0.53.0",
"@deephaven/icons": "0.53.0",
"@deephaven/jsapi-bootstrap": "0.53.0",
"@deephaven/log": "0.53.0",
"@deephaven/plugin": "0.53.0",
"@deephaven/utils": "0.53.0",
"@deephaven/chart": "0.55.0",
"@deephaven/components": "0.55.0",
"@deephaven/dashboard": "0.55.0",
"@deephaven/dashboard-core-plugins": "0.55.0",
"@deephaven/icons": "0.55.0",
"@deephaven/jsapi-bootstrap": "0.55.0",
"@deephaven/log": "0.55.0",
"@deephaven/plugin": "0.55.0",
"@deephaven/utils": "0.55.0",
"deep-equal": "^2.2.1",
"plotly.js": "^2.23.0",
"plotly.js-dist-min": "^2.23.0",
Expand Down
46 changes: 46 additions & 0 deletions plugins/plotly-express/src/js/src/PlotlyExpressChart.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import React, { useEffect, useRef, useState } from 'react';
import Plotly from 'plotly.js-dist-min';
import { Chart } from '@deephaven/chart';
import { type WidgetComponentProps } from '@deephaven/plugin';
import { useApi } from '@deephaven/jsapi-bootstrap';
import PlotlyExpressChartModel from './PlotlyExpressChartModel.js';
import { useHandleSceneTicks } from './useHandleSceneTicks.js';

export function PlotlyExpressChart(
props: WidgetComponentProps
): JSX.Element | null {
const dh = useApi();
const { fetch } = props;
const containerRef = useRef<HTMLDivElement>(null);
const [model, setModel] = useState<PlotlyExpressChartModel>();

useEffect(() => {
let cancelled = false;
async function init() {
const widgetData = await fetch();
if (!cancelled) {
setModel(new PlotlyExpressChartModel(dh, widgetData, fetch));
}
}

init();

return () => {
cancelled = true;
};
}, [dh, fetch]);

useHandleSceneTicks(model, containerRef.current);

return model ? (
<Chart
// eslint-disable-next-line react/jsx-props-no-spreading, @typescript-eslint/ban-ts-comment
// @ts-ignore
containerRef={containerRef}
model={model}
Plotly={Plotly}
/>
) : null;
}

export default PlotlyExpressChart;
32 changes: 19 additions & 13 deletions plugins/plotly-express/src/js/src/PlotlyExpressChartModel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,17 @@ import type { Layout, Data } from 'plotly.js';
import type {
dh as DhType,
ChartData,
JsWidget,
Widget,
Table,
TableSubscription,
TableData,
} from '@deephaven/jsapi-types';
import { ChartModel, ChartUtils, ChartTheme } from '@deephaven/chart';
import {
ChartModel,
ChartUtils,
ChartTheme,
defaultChartTheme,
} from '@deephaven/chart';
import Log from '@deephaven/log';
import {
PlotlyChartWidgetData,
Expand All @@ -21,15 +26,16 @@ const log = Log.module('@deephaven/js-plugin-plotly-express.ChartModel');
export class PlotlyExpressChartModel extends ChartModel {
constructor(
dh: DhType,
widget: JsWidget,
refetch: () => Promise<JsWidget>,
theme: typeof ChartTheme = ChartTheme
widget: Widget,
refetch: () => Promise<Widget>,
theme: ChartTheme = defaultChartTheme()
) {
super(dh);

this.widget = widget;
this.refetch = refetch;
this.chartUtils = new ChartUtils(dh);
this.theme = theme;

this.handleFigureUpdated = this.handleFigureUpdated.bind(this);
this.handleWidgetUpdated = this.handleWidgetUpdated.bind(this);
Expand All @@ -39,17 +45,16 @@ export class PlotlyExpressChartModel extends ChartModel {
// before the widget is subscribed to.
this.handleWidgetUpdated(getWidgetData(widget), widget.exportedObjects);

this.theme = theme;
this.setTitle(this.getDefaultTitle());
}

isSubscribed = false;

chartUtils: ChartUtils;

refetch: () => Promise<JsWidget>;
refetch: () => Promise<Widget>;

widget?: JsWidget;
widget?: Widget;

widgetUnsubscribe?: () => void;

Expand Down Expand Up @@ -84,7 +89,7 @@ export class PlotlyExpressChartModel extends ChartModel {
*/
tableDataMap: Map<number, { [key: string]: unknown[] }> = new Map();

theme: typeof ChartTheme;
theme: ChartTheme;

plotlyData: Data[] = [];

Expand Down Expand Up @@ -154,7 +159,7 @@ export class PlotlyExpressChartModel extends ChartModel {
}

this.isSubscribed = true;
this.widgetUnsubscribe = this.widget.addEventListener(
this.widgetUnsubscribe = this.widget.addEventListener<Widget>(
this.dh.Widget.EVENT_MESSAGE,
({ detail }) => {
this.handleWidgetUpdated(
Expand Down Expand Up @@ -211,7 +216,7 @@ export class PlotlyExpressChartModel extends ChartModel {

handleWidgetUpdated(
data: PlotlyChartWidgetData,
references: JsWidget['exportedObjects']
references: Widget['exportedObjects']
): void {
const {
figure,
Expand Down Expand Up @@ -301,8 +306,9 @@ export class PlotlyExpressChartModel extends ChartModel {
this.tableSubscriptionMap.set(id, subscription);
this.subscriptionCleanupMap.set(
id,
subscription.addEventListener(this.dh.Table.EVENT_UPDATED, e =>
this.handleFigureUpdated(e, id)
subscription.addEventListener<TableData>(
this.dh.Table.EVENT_UPDATED,
e => this.handleFigureUpdated(e, id)
)
);
}
Expand Down
56 changes: 5 additions & 51 deletions plugins/plotly-express/src/js/src/PlotlyExpressChartPanel.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
import React, { useCallback, useEffect, useRef, useState } from 'react';
import React, { useCallback, useRef, useState } from 'react';
import Plotly from 'plotly.js-dist-min';
import { ChartPanel, ChartPanelProps } from '@deephaven/dashboard-core-plugins';
import { type WidgetComponentProps } from '@deephaven/plugin';
import { type WidgetPanelProps } from '@deephaven/plugin';
import { useApi } from '@deephaven/jsapi-bootstrap';
import PlotlyExpressChartModel from './PlotlyExpressChartModel.js';
import { useHandleSceneTicks } from './useHandleSceneTicks.js';

function PlotlyExpressChartPanel(props: WidgetComponentProps) {
export function PlotlyExpressChartPanel(props: WidgetPanelProps) {
const dh = useApi();
const { fetch, metadata = {}, ...rest } = props;
const containerRef = useRef<HTMLDivElement>(null);
Expand All @@ -18,52 +19,7 @@ function PlotlyExpressChartPanel(props: WidgetComponentProps) {
return m;
}, [dh, fetch]);

useEffect(
function handleSceneTicks() {
// Plotly scenes and geo views reset when our data ticks
// Pause rendering data updates when the user is manipulating a scene
if (
!model ||
!containerRef.current ||
!model.shouldPauseOnUserInteraction()
) {
return;
}

const container = containerRef.current;

function handleMouseDown() {
model?.pauseUpdates();
// The once option removes the listener after it is called
window.addEventListener('mouseup', handleMouseUp, { once: true });
}

function handleMouseUp() {
model?.resumeUpdates();
}

let wheelTimeout = 0;

function handleWheel() {
model?.pauseUpdates();
window.clearTimeout(wheelTimeout);
wheelTimeout = window.setTimeout(() => {
model?.resumeUpdates();
}, 300);
}

container.addEventListener('mousedown', handleMouseDown);
container.addEventListener('wheel', handleWheel);

return () => {
window.clearTimeout(wheelTimeout);
window.removeEventListener('mouseup', handleMouseUp);
container.removeEventListener('mousedown', handleMouseDown);
container.removeEventListener('wheel', handleWheel);
};
},
[model]
);
useHandleSceneTicks(model, containerRef.current);

return (
<ChartPanel
Expand All @@ -77,6 +33,4 @@ function PlotlyExpressChartPanel(props: WidgetComponentProps) {
);
}

PlotlyExpressChartPanel.displayName = 'PlotlyExpressChartPanel';

export default PlotlyExpressChartPanel;
4 changes: 2 additions & 2 deletions plugins/plotly-express/src/js/src/PlotlyExpressChartUtils.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import type { Data, PlotlyDataLayoutConfig } from 'plotly.js';
import type { JsWidget } from '@deephaven/jsapi-types';
import type { Widget } from '@deephaven/jsapi-types';
import Log from '@deephaven/log';

const log = Log.module('@deephaven/js-plugin-plotly-express.ChartUtils');
Expand All @@ -22,7 +22,7 @@ export interface PlotlyChartWidgetData {
removed_references: number[];
}

export function getWidgetData(widgetInfo: JsWidget): PlotlyChartWidgetData {
export function getWidgetData(widgetInfo: Widget): PlotlyChartWidgetData {
return JSON.parse(widgetInfo.getDataAsString());
}

Expand Down
15 changes: 15 additions & 0 deletions plugins/plotly-express/src/js/src/PlotlyExpressPlugin.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { type WidgetPlugin, PluginType } from '@deephaven/plugin';
import { vsGraph } from '@deephaven/icons';
import { PlotlyExpressChart } from './PlotlyExpressChart.js';
import { PlotlyExpressChartPanel } from './PlotlyExpressChartPanel.js';

export const PlotlyExpressPlugin: WidgetPlugin = {
name: '@deephaven/plotly-express',
type: PluginType.WIDGET_PLUGIN,
supportedTypes: 'deephaven.plot.express.DeephavenFigure',
component: PlotlyExpressChart,
panelComponent: PlotlyExpressChartPanel,
icon: vsGraph,
};

export default PlotlyExpressPlugin;
15 changes: 2 additions & 13 deletions plugins/plotly-express/src/js/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,6 @@
import { type WidgetPlugin, PluginType } from '@deephaven/plugin';
import { vsGraph } from '@deephaven/icons';
import PlotlyExpressChartPanel from './PlotlyExpressChartPanel.js';
import { PlotlyExpressPlugin } from './PlotlyExpressPlugin.js';

export * from './PlotlyExpressChartModel.js';
export * from './PlotlyExpressChartUtils.js';

const plugin: WidgetPlugin = {
name: '@deephaven/plotly-express',
type: PluginType.WIDGET_PLUGIN,
supportedTypes: 'deephaven.plot.express.DeephavenFigure',
component: PlotlyExpressChartPanel,
panelComponent: PlotlyExpressChartPanel,
icon: vsGraph,
};

export default plugin;
export default PlotlyExpressPlugin;
47 changes: 47 additions & 0 deletions plugins/plotly-express/src/js/src/useHandleSceneTicks.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import { useEffect } from 'react';
import PlotlyExpressChartModel from './PlotlyExpressChartModel.js';

export function useHandleSceneTicks(
model: PlotlyExpressChartModel | undefined,
container: HTMLDivElement | null
) {
useEffect(() => {
// Plotly scenes and geo views reset when our data ticks
// Pause rendering data updates when the user is manipulating a scene
if (!model || !container || !model.shouldPauseOnUserInteraction()) {
return;
}

function handleMouseDown() {
model?.pauseUpdates();
// The once option removes the listener after it is called
window.addEventListener('mouseup', handleMouseUp, { once: true });
}

function handleMouseUp() {
model?.resumeUpdates();
}

let wheelTimeout = 0;

function handleWheel() {
model?.pauseUpdates();
window.clearTimeout(wheelTimeout);
wheelTimeout = window.setTimeout(() => {
model?.resumeUpdates();
}, 300);
}

container.addEventListener('mousedown', handleMouseDown);
container.addEventListener('wheel', handleWheel);

return () => {
window.clearTimeout(wheelTimeout);
window.removeEventListener('mouseup', handleMouseUp);
container.removeEventListener('mousedown', handleMouseDown);
container.removeEventListener('wheel', handleWheel);
};
}, [model, container]);
}

export default useHandleSceneTicks;

0 comments on commit 878aa91

Please sign in to comment.