diff --git a/packages/react-vega-demo/stories/dimensions.stories.tsx b/packages/react-vega-demo/stories/dimensions.stories.tsx new file mode 100644 index 00000000..74c189ac --- /dev/null +++ b/packages/react-vega-demo/stories/dimensions.stories.tsx @@ -0,0 +1,38 @@ +import React from 'react'; +// eslint-disable-next-line import/no-extraneous-dependencies +import { storiesOf } from '@storybook/react'; +import { VegaLite } from '../../react-vega/src'; + +const DATA = { + myData: [ + { a: 'A', b: 20 }, + { a: 'B', b: 34 }, + { a: 'C', b: 55 }, + { a: 'D', b: 19 }, + { a: 'E', b: 40 }, + { a: 'F', b: 34 }, + { a: 'G', b: 91 }, + { a: 'H', b: 78 }, + { a: 'I', b: 25 }, + ], +}; + +const SPEC = { + description: 'A simple bar chart with embedded data.', + layer: [ + { + data: { name: 'myData' }, + encoding: { + x: { field: 'a', type: 'ordinal' }, + y: { field: 'b', type: 'quantitative' }, + }, + mark: 'bar', + }, + ], +}; + +storiesOf('react-vega', module).add('width=300', () => ( + +)).add('height=300', () => ( + +)); diff --git a/packages/react-vega/src/VegaEmbed.tsx b/packages/react-vega/src/VegaEmbed.tsx index cadf83c6..88cc8926 100644 --- a/packages/react-vega/src/VegaEmbed.tsx +++ b/packages/react-vega/src/VegaEmbed.tsx @@ -7,6 +7,7 @@ import { NOOP } from './constants'; import addSignalListenersToView from './utils/addSignalListenersToView'; import computeSpecChanges from './utils/computeSpecChanges'; import removeSignalListenersFromView from './utils/removeSignalListenersFromView'; +import combineSpecWithDimension from './utils/combineSpecWithDimension'; export type VegaEmbedProps = { className?: string; @@ -32,13 +33,18 @@ export default class VegaEmbed extends React.PureComponent { fieldSet.delete('signalListeners'); fieldSet.delete('spec'); fieldSet.delete('style'); + fieldSet.delete('width'); + fieldSet.delete('height'); // Only create a new view if necessary if (Array.from(fieldSet).some(f => this.props[f] !== prevProps[f])) { this.clearView(); this.createView(); } else { - const specChanges = computeSpecChanges(this.props.spec, prevProps.spec); + const specChanges = computeSpecChanges( + combineSpecWithDimension(this.props), + combineSpecWithDimension(prevProps), + ); const { signalListeners: newSignalListeners } = this.props; const { signalListeners: oldSignalListeners } = prevProps; @@ -68,18 +74,14 @@ export default class VegaEmbed extends React.PureComponent { view.run(); }); } - } else { - const areSignalListenersChanged = !shallowEqual(newSignalListeners, oldSignalListeners); + } else if (!shallowEqual(newSignalListeners, oldSignalListeners)) { this.modifyView(view => { - if (areSignalListenersChanged) { - if (oldSignalListeners) { - removeSignalListenersFromView(view, oldSignalListeners); - } - if (newSignalListeners) { - addSignalListenersToView(view, newSignalListeners); - } + if (oldSignalListeners) { + removeSignalListenersFromView(view, oldSignalListeners); + } + if (newSignalListeners) { + addSignalListenersToView(view, newSignalListeners); } - view.run(); }); } @@ -114,9 +116,10 @@ export default class VegaEmbed extends React.PureComponent { }; createView() { - const { spec, onNewView, signalListeners = {}, ...options } = this.props; + const { spec, onNewView, signalListeners = {}, width, height, ...options } = this.props; if (this.containerRef.current) { - this.viewPromise = vegaEmbed(this.containerRef.current, spec, options) + const finalSpec = combineSpecWithDimension(this.props); + this.viewPromise = vegaEmbed(this.containerRef.current, finalSpec, options) .then(({ view }) => { if (addSignalListenersToView(view, signalListeners)) { view.run(); @@ -143,9 +146,7 @@ export default class VegaEmbed extends React.PureComponent { render() { const { className, style } = this.props; - return ( - // Create the container Vega draws inside -
- ); + // Create the container Vega draws inside + return
; } } diff --git a/packages/react-vega/src/utils/combineSpecWithDimension.ts b/packages/react-vega/src/utils/combineSpecWithDimension.ts new file mode 100644 index 00000000..a07f92a9 --- /dev/null +++ b/packages/react-vega/src/utils/combineSpecWithDimension.ts @@ -0,0 +1,16 @@ +import { VisualizationSpec } from 'vega-embed'; +import { VegaEmbedProps } from '../VegaEmbed'; + +export default function combineSpecWithDimension(props: VegaEmbedProps): VisualizationSpec { + const { spec, width, height } = props; + if (typeof width !== 'undefined' && typeof height !== 'undefined') { + return { ...spec, width, height }; + } + if (typeof width !== 'undefined') { + return { ...spec, width }; + } + if (typeof height !== 'undefined') { + return { ...spec, height }; + } + return spec; +} diff --git a/packages/react-vega/src/utils/getUniqueFieldNames.ts b/packages/react-vega/src/utils/getUniqueFieldNames.ts index 2455b64c..cc9c6432 100644 --- a/packages/react-vega/src/utils/getUniqueFieldNames.ts +++ b/packages/react-vega/src/utils/getUniqueFieldNames.ts @@ -1,7 +1,4 @@ -import { VisualizationSpec } from 'vega-embed'; -import { PlainObject } from '../types'; - -export default function getUniqueFieldNames(objects: (PlainObject | VisualizationSpec)[]) { +export default function getUniqueFieldNames(objects: T[]) { const fields = new Set(); objects.forEach(o => { Object.keys(o).forEach(field => { diff --git a/packages/react-vega/test/utils/combineSpecWithDimension.test.ts b/packages/react-vega/test/utils/combineSpecWithDimension.test.ts new file mode 100644 index 00000000..6de11da2 --- /dev/null +++ b/packages/react-vega/test/utils/combineSpecWithDimension.test.ts @@ -0,0 +1,77 @@ +import combineSpecWithDimension from '../../src/utils/combineSpecWithDimension'; + +describe('combineSpecWithDimension(props)', () => { + const spec = { + $schema: 'https://vega.github.io/schema/vega-lite/v4.json', + data: { + values: [], + name: 'source', + }, + mark: 'bar', + encoding: { + y: { + field: 'b', + type: 'quantitative', + }, + }, + } as const; + + it('adds width and height', () => { + expect(combineSpecWithDimension({ spec, width: 100, height: 100 })).toEqual({ + $schema: 'https://vega.github.io/schema/vega-lite/v4.json', + width: 100, + height: 100, + data: { + values: [], + name: 'source', + }, + mark: 'bar', + encoding: { + y: { + field: 'b', + type: 'quantitative', + }, + }, + }); + }); + + it('adds width', () => { + expect(combineSpecWithDimension({ spec, width: 100 })).toEqual({ + $schema: 'https://vega.github.io/schema/vega-lite/v4.json', + width: 100, + data: { + values: [], + name: 'source', + }, + mark: 'bar', + encoding: { + y: { + field: 'b', + type: 'quantitative', + }, + }, + }); + }); + + it('adds height', () => { + expect(combineSpecWithDimension({ spec, height: 100 })).toEqual({ + $schema: 'https://vega.github.io/schema/vega-lite/v4.json', + height: 100, + data: { + values: [], + name: 'source', + }, + mark: 'bar', + encoding: { + y: { + field: 'b', + type: 'quantitative', + }, + }, + }); + }); + + it('returns original if no width or height are defined', () => { + expect(combineSpecWithDimension({ spec })).toBe(spec); + }); +});