Skip to content

Commit

Permalink
feat: Add ResizeObserver to Grid and Chart (#1626)
Browse files Browse the repository at this point in the history
- ResizeObserver is now widely available in all browsers, so use it to
listen for resizing of our grid and plot elements
- Pull `grid-wrapper` from `IrisGrid` and put it directly in `Grid`
- Now `Grid` doesn't have to listen to the "parent" element, which was
kind of strange in the first place.
- Tested by opening up some tables and charts, resizing the panels and
ensuring they updated correctly.
  • Loading branch information
mofojed authored Nov 7, 2023
1 parent 80f29f5 commit 35311c8
Show file tree
Hide file tree
Showing 11 changed files with 130 additions and 69 deletions.
15 changes: 15 additions & 0 deletions packages/chart/src/Chart.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,7 @@ export class Chart extends Component<ChartProps, ChartState> {
this.handleModelEvent = this.handleModelEvent.bind(this);
this.handlePlotUpdate = this.handlePlotUpdate.bind(this);
this.handleRelayout = this.handleRelayout.bind(this);
this.handleResize = this.handleResize.bind(this);
this.handleRestyle = this.handleRestyle.bind(this);

this.PlotComponent = createPlotlyComponent(props.Plotly);
Expand All @@ -144,6 +145,7 @@ export class Chart extends Component<ChartProps, ChartState> {
this.isSubscribed = false;
this.isLoadedFired = false;
this.currentSeries = 0;
this.resizeObserver = new window.ResizeObserver(this.handleResize);

this.state = {
data: null,
Expand All @@ -170,6 +172,9 @@ export class Chart extends Component<ChartProps, ChartState> {
if (isActive) {
this.subscribe(model);
}
if (this.plotWrapper.current != null) {
this.resizeObserver.observe(this.plotWrapper.current);
}
}

componentDidUpdate(prevProps: ChartProps): void {
Expand All @@ -183,6 +188,7 @@ export class Chart extends Component<ChartProps, ChartState> {

if (isActive !== prevProps.isActive) {
if (isActive) {
this.updateDimensions();
this.subscribe(model);
} else {
this.unsubscribe(model);
Expand All @@ -193,6 +199,8 @@ export class Chart extends Component<ChartProps, ChartState> {
componentWillUnmount(): void {
const { model } = this.props;
this.unsubscribe(model);

this.resizeObserver.disconnect();
}

currentSeries: number;
Expand All @@ -219,6 +227,9 @@ export class Chart extends Component<ChartProps, ChartState> {

isLoadedFired: boolean;

// Listen for resizing of the element and update the canvas appropriately
resizeObserver: ResizeObserver;

getCachedConfig = memoize(
(
downsamplingError: unknown,
Expand Down Expand Up @@ -468,6 +479,10 @@ export class Chart extends Component<ChartProps, ChartState> {
this.updateModelDimensions();
}

handleResize(): void {
this.updateDimensions();
}

handleRestyle([changes, seriesIndexes]: readonly [
Record<string, unknown>,
number[],
Expand Down
17 changes: 0 additions & 17 deletions packages/dashboard-core-plugins/src/panels/ChartPanel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -218,7 +218,6 @@ export class ChartPanel extends Component<ChartPanelProps, ChartPanelState> {
this.handleError = this.handleError.bind(this);
this.handleLoadError = this.handleLoadError.bind(this);
this.handleLoadSuccess = this.handleLoadSuccess.bind(this);
this.handleResize = this.handleResize.bind(this);
this.handleSettingsChanged = this.handleSettingsChanged.bind(this);
this.handleOpenLinker = this.handleOpenLinker.bind(this);
this.handleShow = this.handleShow.bind(this);
Expand All @@ -235,7 +234,6 @@ export class ChartPanel extends Component<ChartPanelProps, ChartPanelState> {
this.handleClearAllFilters = this.handleClearAllFilters.bind(this);

this.panelContainer = props.containerRef ?? React.createRef();
this.chart = React.createRef();
this.pending = new Pending();

const { metadata, panelState } = props;
Expand Down Expand Up @@ -337,8 +335,6 @@ export class ChartPanel extends Component<ChartPanelProps, ChartPanelState> {

panelContainer: RefObject<HTMLDivElement>;

chart: RefObject<Chart>;

pending: Pending;

initModel(): void {
Expand Down Expand Up @@ -683,10 +679,6 @@ export class ChartPanel extends Component<ChartPanelProps, ChartPanelState> {
this.setState({ isLoading: false });
}

handleResize(): void {
this.updateChart();
}

handleSettingsChanged(update: Partial<Settings>): void {
this.setState(({ settings: prevSettings }) => {
const settings = {
Expand Down Expand Up @@ -752,7 +744,6 @@ export class ChartPanel extends Component<ChartPanelProps, ChartPanelState> {
this.setState({ isActive }, () => {
if (isActive) {
this.loadModelIfNecessary();
this.updateChart();
}
});
}
Expand Down Expand Up @@ -1026,12 +1017,6 @@ export class ChartPanel extends Component<ChartPanelProps, ChartPanelState> {
});
}

updateChart(): void {
if (this.chart.current) {
this.chart.current.updateDimensions();
}
}

render(): ReactElement {
const {
columnSelectionValidator,
Expand Down Expand Up @@ -1097,7 +1082,6 @@ export class ChartPanel extends Component<ChartPanelProps, ChartPanelState> {
glEventHub={glEventHub}
onHide={this.handleHide}
onClearAllFilters={this.handleClearAllFilters}
onResize={this.handleResize}
onShow={this.handleShow}
onTabBlur={this.handleTabBlur}
onTabFocus={this.handleTabFocus}
Expand All @@ -1118,7 +1102,6 @@ export class ChartPanel extends Component<ChartPanelProps, ChartPanelState> {
isActive={isActive}
model={model}
settings={settings}
ref={this.chart}
onDisconnect={this.handleDisconnect}
onReconnect={this.handleReconnect}
onUpdate={this.handleUpdate}
Expand Down
6 changes: 0 additions & 6 deletions packages/dashboard-core-plugins/src/panels/IrisGridPanel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -251,7 +251,6 @@ export class IrisGridPanel extends PureComponent<
this.handleGridStateChange = this.handleGridStateChange.bind(this);
this.handlePluginStateChange = this.handlePluginStateChange.bind(this);
this.handleCreateChart = this.handleCreateChart.bind(this);
this.handleResize = this.handleResize.bind(this);
this.handleShow = this.handleShow.bind(this);
this.handleTabClicked = this.handleTabClicked.bind(this);
this.handleDisconnect = this.handleDisconnect.bind(this);
Expand Down Expand Up @@ -762,10 +761,6 @@ export class IrisGridPanel extends PureComponent<
glEventHub.emit(IrisGridEvent.DATA_SELECTED, this, dataMap);
}

handleResize(): void {
this.updateGrid();
}

handleShow(): void {
this.updateGrid();
}
Expand Down Expand Up @@ -1277,7 +1272,6 @@ export class IrisGridPanel extends PureComponent<
glContainer={glContainer}
glEventHub={glEventHub}
onClearAllFilters={this.handleClearAllFilters}
onResize={this.handleResize}
onShow={this.handleShow}
onTabFocus={this.handleShow}
onTabClicked={this.handleTabClicked}
Expand Down
12 changes: 12 additions & 0 deletions packages/grid/src/Grid.scss
Original file line number Diff line number Diff line change
@@ -1,3 +1,15 @@
.grid-wrapper {
flex: 1 1 0;
max-width: 100%;
max-height: 100%;
// min-width/height used to make sure grid shrinks properly when notification bars are added/resized
min-width: 0;
min-height: 0;
position: relative;
font: sans-serif;
font-feature-settings: 'tnum';
}

.grid-canvas {
display: block;
}
Expand Down
11 changes: 11 additions & 0 deletions packages/grid/src/Grid.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -70,10 +70,21 @@ function makeMockCanvas() {
};
}

function makeMockWrapper() {
return {
focus: jest.fn(),
getBoundingClientRect: () => ({ width: VIEW_SIZE, height: VIEW_SIZE }),
};
}

function createNodeMock(element: ReactElement) {
if (element.type === 'canvas') {
return makeMockCanvas();
}
if (element?.props?.className?.includes('grid-wrapper') === true) {
return makeMockWrapper();
}

return null;
}

Expand Down
36 changes: 28 additions & 8 deletions packages/grid/src/Grid.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
/* eslint react/no-did-update-set-state: "off" */
import React, { CSSProperties, PureComponent, ReactNode } from 'react';
import React, {
CSSProperties,
PureComponent,
ReactNode,
RefObject,
} from 'react';
import classNames from 'classnames';
import memoize from 'memoize-one';
import clamp from 'lodash.clamp';
Expand Down Expand Up @@ -69,6 +74,9 @@ type LegacyCanvasRenderingContext2D = CanvasRenderingContext2D & {
};

export type GridProps = typeof Grid.defaultProps & {
// Children to render in the grid
children?: ReactNode;

// Options to set on the canvas
canvasOptions?: CanvasRenderingContext2DSettings;

Expand Down Expand Up @@ -295,6 +303,12 @@ class Grid extends PureComponent<GridProps, GridState> {

canvasContext: CanvasRenderingContext2D | null;

// The wrapper element for the canvas, used for sizing
canvasWrapper: RefObject<HTMLDivElement>;

// Listen for resizing of the element and update the canvas appropriately
resizeObserver: ResizeObserver;

// We draw the canvas on the next animation frame, keep track of the next one
animationFrame: number | null;

Expand Down Expand Up @@ -351,6 +365,8 @@ class Grid extends PureComponent<GridProps, GridState> {

this.canvas = null;
this.canvasContext = null;
this.canvasWrapper = React.createRef();
this.resizeObserver = new window.ResizeObserver(this.handleResize);
this.animationFrame = null;

this.prevMetrics = null;
Expand Down Expand Up @@ -457,7 +473,9 @@ class Grid extends PureComponent<GridProps, GridState> {
this.canvas?.addEventListener('wheel', this.handleWheel, {
passive: false,
});
window.addEventListener('resize', this.handleResize);
if (this.canvasWrapper.current != null) {
this.resizeObserver.observe(this.canvasWrapper.current);
}

this.updateCanvas();

Expand Down Expand Up @@ -561,7 +579,7 @@ class Grid extends PureComponent<GridProps, GridState> {
this.handleMouseUp as unknown as EventListenerOrEventListenerObject,
true
);
window.removeEventListener('resize', this.handleResize);
this.resizeObserver.disconnect();

this.stopDragTimer();
}
Expand Down Expand Up @@ -801,17 +819,17 @@ class Grid extends PureComponent<GridProps, GridState> {
}

private updateCanvasScale(): void {
const { canvas, canvasContext } = this;
const { canvas, canvasContext, canvasWrapper } = this;
if (!canvas) throw new Error('canvas not set');
if (!canvasContext) throw new Error('canvasContext not set');
if (!canvas.parentElement) throw new Error('Canvas has no parent element');
if (!canvasWrapper.current) throw new Error('canvasWrapper not set');

const scale = Grid.getScale(canvasContext);
// the parent wrapper has 100% width/height, and is used for determining size
// we don't want to stretch the canvas to 100%, to avoid fractional pixels.
// A wrapper element must be used for sizing, and canvas size must be
// set manually to a floored value in css and a scaled value in width/height
const rect = canvas.parentElement.getBoundingClientRect();
const rect = canvasWrapper.current.getBoundingClientRect();
const width = Math.floor(rect.width);
const height = Math.floor(rect.height);
canvas.style.width = `${width}px`;
Expand Down Expand Up @@ -2177,10 +2195,11 @@ class Grid extends PureComponent<GridProps, GridState> {
}

render(): ReactNode {
const { children } = this.props;
const { cursor } = this.state;

return (
<>
<div className="grid-wrapper" ref={this.canvasWrapper}>
<canvas
className={classNames('grid-canvas', Grid.getCursorClassName(cursor))}
ref={canvas => {
Expand All @@ -2198,7 +2217,8 @@ class Grid extends PureComponent<GridProps, GridState> {
Your browser does not support HTML canvas. Update your browser?
</canvas>
{this.renderInputField()}
</>
{children}
</div>
);
}
}
Expand Down
7 changes: 0 additions & 7 deletions packages/iris-grid/src/IrisGrid.scss
Original file line number Diff line number Diff line change
Expand Up @@ -79,13 +79,6 @@ $cell-invalid-box-shadow:
}

.grid-wrapper {
flex: 1 1 0;
max-width: 100%;
max-height: 100%;
// min-width/height used to make sure grid shrinks properly when notification bars are added/resized
min-width: 0;
min-height: 0;
position: relative;
font: $iris-grid-font;
font-feature-settings: $iris-grid-font-feature-settings;
transition: all $transition-mid;
Expand Down
13 changes: 11 additions & 2 deletions packages/iris-grid/src/IrisGrid.test.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React from 'react';
import React, { ReactElement } from 'react';
import TestRenderer from 'react-test-renderer';
import dh from '@deephaven/jsapi-shim';
import { DateUtils, Settings } from '@deephaven/jsapi-utils';
Expand Down Expand Up @@ -48,10 +48,19 @@ function makeMockCanvas() {
};
}

function createNodeMock(element) {
function makeMockWrapper() {
return {
getBoundingClientRect: () => ({ width: VIEW_SIZE, height: VIEW_SIZE }),
};
}

function createNodeMock(element: ReactElement) {
if (element.type === 'canvas') {
return makeMockCanvas();
}
if (element?.props?.className?.includes('grid-wrapper') === true) {
return makeMockWrapper();
}
return element;
}

Expand Down
Loading

0 comments on commit 35311c8

Please sign in to comment.