Skip to content

Commit

Permalink
feat: improve perf and avoid updating data or view when not necessary (
Browse files Browse the repository at this point in the history
…#58)

* feat: optimize view recreation

* feat: improve perf and avoid updating data or view when not necessary

* feat: save little more computation
  • Loading branch information
kristw committed Sep 20, 2019
1 parent d15659a commit 20d99ca
Show file tree
Hide file tree
Showing 6 changed files with 79 additions and 20 deletions.
23 changes: 17 additions & 6 deletions packages/react-vega/src/Vega.tsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,16 @@
import React from 'react';
import { vega, Result } from 'vega-embed';
import { vega } from 'vega-embed';
import VegaEmbed, { VegaEmbedProps } from './VegaEmbed';
import isFunction from './utils/isFunction';
import { PlainObject, View } from './types';
import shallowEqual from './utils/shallowEqual';
import { NOOP } from './constants';

export type VegaProps = VegaEmbedProps & {
data: { [key: string]: any };
data: PlainObject;
};

function updateData(view: Result['view'], name: string, value: any) {
function updateData(view: View, name: string, value: any) {
if (value) {
if (isFunction(value)) {
value(view.data(name));
Expand All @@ -30,10 +33,18 @@ export default class Vega extends React.PureComponent<VegaProps> {
this.update();
}

componentDidUpdate() {
this.update();
componentDidUpdate(prevProps: VegaProps) {
if (!shallowEqual(this.props.data, prevProps.data)) {
this.update();
}
}

handleNewView: VegaProps['onNewView'] = (view: View) => {
this.update();
const { onNewView = NOOP } = this.props;
onNewView(view);
};

update() {
const { data, spec } = this.props;
if (this.vegaEmbed.current) {
Expand Down Expand Up @@ -62,6 +73,6 @@ export default class Vega extends React.PureComponent<VegaProps> {
render() {
const { data, ...restProps } = this.props;

return <VegaEmbed ref={this.vegaEmbed} {...restProps} />;
return <VegaEmbed ref={this.vegaEmbed} {...restProps} onNewView={this.handleNewView} />;
}
}
37 changes: 23 additions & 14 deletions packages/react-vega/src/VegaEmbed.tsx
Original file line number Diff line number Diff line change
@@ -1,33 +1,42 @@
import React, { CSSProperties } from 'react';
import vegaEmbed, { EmbedOptions, VisualizationSpec, Result } from 'vega-embed';
import vegaEmbed, { EmbedOptions, VisualizationSpec } from 'vega-embed';
import { ViewListener, View, SignalListeners } from './types';
import shallowEqual from './utils/shallowEqual';
import getUniqueFieldNames from './utils/getUniqueFieldNames';
import { NOOP } from './constants';

export type VegaEmbedProps = {
className?: string;
spec: VisualizationSpec;
signalListeners?: {
[key: string]: (name: string, value: any) => void;
};
signalListeners?: SignalListeners;
style?: CSSProperties;
onNewView?: (view: Result['view']) => {};
onNewView?: ViewListener;
onError?: (error: Error) => {};
} & EmbedOptions & {};

const NOOP = () => {};

type ViewModifier = (view: Result['view']) => void;

export default class VegaEmbed extends React.PureComponent<VegaEmbedProps> {
containerRef = React.createRef<HTMLDivElement>();

viewPromise?: Promise<Result['view'] | undefined>;
viewPromise?: Promise<View | undefined>;

componentDidMount() {
this.createView();
}

componentDidUpdate() {
this.clearView();
this.createView();
componentDidUpdate(prevProps: VegaEmbedProps) {
const fieldSet = getUniqueFieldNames([this.props, prevProps]) as Set<keyof VegaEmbedProps>;
fieldSet.delete('className');
fieldSet.delete('style');
fieldSet.delete('signalListeners');

// Only create a new view if necessary
if (
Array.from(fieldSet).some(f => this.props[f] !== prevProps[f]) ||
!shallowEqual(this.props.signalListeners, prevProps.signalListeners)
) {
this.clearView();
this.createView();
}
}

componentWillUnmount() {
Expand All @@ -42,7 +51,7 @@ export default class VegaEmbed extends React.PureComponent<VegaEmbedProps> {
return undefined;
};

modifyView = (action: ViewModifier) => {
modifyView = (action: ViewListener) => {
if (this.viewPromise) {
this.viewPromise
.then(view => {
Expand Down
2 changes: 2 additions & 0 deletions packages/react-vega/src/constants.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
// eslint-disable-next-line import/prefer-default-export
export const NOOP = () => {};
15 changes: 15 additions & 0 deletions packages/react-vega/src/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { Result } from 'vega-embed';

export type View = Result['view'];

export type PlainObject = {
[key: string]: any;
};

export type SignalListener = (name: string, value: any) => void;

export type SignalListeners = {
[key: string]: SignalListener;
};

export type ViewListener = (view: View) => void;
12 changes: 12 additions & 0 deletions packages/react-vega/src/utils/getUniqueFieldNames.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { PlainObject } from '../types';

export default function getUniqueFieldNames(objects: PlainObject[]) {
const fields = new Set();
objects.forEach(o => {
Object.keys(o).forEach(field => {
fields.add(field);
});
});

return fields;
}
10 changes: 10 additions & 0 deletions packages/react-vega/src/utils/shallowEqual.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import { PlainObject } from '../types';

const EMPTY = {};

export default function shallowEqual(a: PlainObject = EMPTY, b: PlainObject = EMPTY) {
const aKeys = Object.keys(a);
const bKeys = Object.keys(b);

return a === b || (aKeys.length === bKeys.length && aKeys.every(key => a[key] === b[key]));
}

0 comments on commit 20d99ca

Please sign in to comment.