Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Add ResizeObserver to Grid and Chart #1626

Merged
merged 3 commits into from
Nov 7, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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();
mofojed marked this conversation as resolved.
Show resolved Hide resolved
}

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();
}
mattrunyon marked this conversation as resolved.
Show resolved Hide resolved
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';
mattrunyon marked this conversation as resolved.
Show resolved Hide resolved
}

.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
Loading