` element
182 |
183 | - **className**:String
184 | - **style**:Object
185 |
186 | #### Data
187 |
188 | - **data**:Object
189 |
190 | For `data`, this property takes an Object with keys being dataset names defined in the spec's data field, such as:
191 |
192 | ```js
193 | var barData = {
194 | table: [{"x": 1, "y": 28}, {"x": 2, "y": 55}, ...]
195 | };
196 | ```
197 |
198 | Each value can be an *array* or `function(dataset){...}`. If the value is a function, Vega's `vis.data(dataName)` will be passed as the argument `dataset`. If you are using `
` make sure to enable your tooltip in the the spec, as [described here](https://vega.github.io/vega-lite/docs/tooltip.html#using-tooltip-channel).
199 |
200 | ```js
201 | var barData = {
202 | table: function(dataset){...}
203 | };
204 | ```
205 |
206 | In the example above, `vis.data('table')` will be passed as `dataset`.
207 |
208 | - **signalListeners**:Object
209 |
210 | All signals defined in the spec can be listened to via `signalListeners`.
211 | For example, to listen to signal *hover*, attach a listener like this
212 |
213 | ```js
214 | // better declare outside of render function
215 | const signalListeners = { hover: handleHover };
216 |
217 |
218 | ```
219 |
220 | #### Event listeners
221 |
222 | - **onNewView**:Function Dispatched when new vega.View is constructed and pass the newly created view as argument.
223 | - **onParseError**:Function Dispatched when vega cannot parse the spec.
224 |
225 | ### Static function
226 |
227 | Any class created from `createClassFromSpec` will have this method.
228 |
229 | - Chart.**getSpec()** - return `spec`
230 |
231 | ## Frequently Asked Questions
232 |
233 | ### How to use Vega Tooltip?
234 |
235 | You can pass the [`vega-tooltip`](https://github.com/vega/vega-tooltip) handler instance to the `tooltip` property.
236 |
237 | ```javascript
238 | import { Handler } from 'vega-tooltip';
239 |
240 |
241 | ```
242 |
243 | [npm-image]: https://img.shields.io/npm/v/react-vega.svg?style=flat-square
244 | [npm-url]: https://npmjs.org/package/react-vega
245 |
--------------------------------------------------------------------------------
/packages/react-vega/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "react-vega",
3 | "version": "7.6.0",
4 | "description": "Convert Vega spec into React class conveniently",
5 | "author": "Krist Wongsuphasawat (http://kristw.yellowpigz.com)",
6 | "keywords": [],
7 | "repository": "git@github.com:vega/react-vega.git",
8 | "bugs": {
9 | "url": "https://github.com/vega/react-vega/issues"
10 | },
11 | "sideEffects": false,
12 | "main": "lib/index.js",
13 | "module": "esm/index.js",
14 | "types": "lib/index.d.ts",
15 | "files": [
16 | "src",
17 | "esm",
18 | "lib",
19 | "types"
20 | ],
21 | "dependencies": {
22 | "@types/react": "*",
23 | "fast-deep-equal": "^3.1.1",
24 | "prop-types": "^15.8.1",
25 | "vega-embed": "^6.5.1"
26 | },
27 | "peerDependencies": {
28 | "react": "^16 || ^17 || ^18",
29 | "vega": "*",
30 | "vega-lite": "*"
31 | },
32 | "license": "Apache-2.0",
33 | "publishConfig": {
34 | "access": "public"
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/packages/react-vega/src/Vega.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import shallowEqual from './utils/shallowEqual';
3 | import updateMultipleDatasetsInView from './utils/updateMultipleDatasetsInView';
4 | import VegaEmbed, { VegaEmbedProps } from './VegaEmbed';
5 | import { PlainObject, View, ViewListener } from './types';
6 | import { NOOP } from './constants';
7 |
8 | export type VegaProps = VegaEmbedProps & {
9 | data?: PlainObject;
10 | };
11 |
12 | const EMPTY = {};
13 |
14 | export default class Vega extends React.PureComponent {
15 | vegaEmbed = React.createRef();
16 |
17 | static defaultProps = {
18 | data: EMPTY,
19 | };
20 |
21 | componentDidMount() {
22 | this.update();
23 | }
24 |
25 | componentDidUpdate(prevProps: VegaProps) {
26 | if (!shallowEqual(this.props.data, prevProps.data)) {
27 | this.update();
28 | }
29 | }
30 |
31 | handleNewView: ViewListener = (view: View) => {
32 | this.update();
33 | const { onNewView = NOOP } = this.props;
34 | onNewView(view);
35 | };
36 |
37 | update() {
38 | const { data } = this.props;
39 |
40 | if (this.vegaEmbed.current && data && Object.keys(data).length > 0) {
41 | this.vegaEmbed.current.modifyView(view => {
42 | updateMultipleDatasetsInView(view, data);
43 | view.resize().run();
44 | });
45 | }
46 | }
47 |
48 | render() {
49 | const { data, ...restProps } = this.props;
50 |
51 | return ;
52 | }
53 | }
54 |
--------------------------------------------------------------------------------
/packages/react-vega/src/VegaEmbed.tsx:
--------------------------------------------------------------------------------
1 | import React, { CSSProperties } from 'react';
2 | import vegaEmbed, { EmbedOptions, VisualizationSpec } from 'vega-embed';
3 | import type { Result } from 'vega-embed';
4 | import { ViewListener, SignalListeners } from './types';
5 | import shallowEqual from './utils/shallowEqual';
6 | import getUniqueFieldNames from './utils/getUniqueFieldNames';
7 | import { NOOP } from './constants';
8 | import addSignalListenersToView from './utils/addSignalListenersToView';
9 | import computeSpecChanges from './utils/computeSpecChanges';
10 | import removeSignalListenersFromView from './utils/removeSignalListenersFromView';
11 | import combineSpecWithDimension from './utils/combineSpecWithDimension';
12 |
13 | export type VegaEmbedProps = {
14 | className?: string;
15 | spec: VisualizationSpec;
16 | signalListeners?: SignalListeners;
17 | style?: CSSProperties;
18 | onNewView?: ViewListener;
19 | onError?: (error: Error, containerRef: HTMLDivElement) => void;
20 | } & EmbedOptions;
21 |
22 | export default class VegaEmbed extends React.PureComponent {
23 | containerRef = React.createRef();
24 |
25 | resultPromise?: Promise;
26 |
27 | componentDidMount() {
28 | this.createView();
29 | }
30 |
31 | componentDidUpdate(prevProps: VegaEmbedProps) {
32 | const fieldSet = getUniqueFieldNames([this.props, prevProps]) as Set;
33 | fieldSet.delete('className');
34 | fieldSet.delete('signalListeners');
35 | fieldSet.delete('spec');
36 | fieldSet.delete('style');
37 | fieldSet.delete('width');
38 | fieldSet.delete('height');
39 |
40 | // Only create a new view if necessary
41 | if (Array.from(fieldSet).some(f => this.props[f] !== prevProps[f])) {
42 | this.clearView();
43 | this.createView();
44 | } else {
45 | const specChanges = computeSpecChanges(
46 | combineSpecWithDimension(this.props),
47 | combineSpecWithDimension(prevProps),
48 | );
49 |
50 | const { signalListeners: newSignalListeners } = this.props;
51 | const { signalListeners: oldSignalListeners } = prevProps;
52 |
53 | if (specChanges) {
54 | if (specChanges.isExpensive) {
55 | this.clearView();
56 | this.createView();
57 | } else {
58 | const areSignalListenersChanged = !shallowEqual(newSignalListeners, oldSignalListeners);
59 | this.modifyView(view => {
60 | if (specChanges.width !== false) {
61 | view.width(specChanges.width);
62 | }
63 | if (specChanges.height !== false) {
64 | view.height(specChanges.height);
65 | }
66 | if (areSignalListenersChanged) {
67 | if (oldSignalListeners) {
68 | removeSignalListenersFromView(view, oldSignalListeners);
69 | }
70 | if (newSignalListeners) {
71 | addSignalListenersToView(view, newSignalListeners);
72 | }
73 | }
74 |
75 | view.run();
76 | });
77 | }
78 | } else if (!shallowEqual(newSignalListeners, oldSignalListeners)) {
79 | this.modifyView(view => {
80 | if (oldSignalListeners) {
81 | removeSignalListenersFromView(view, oldSignalListeners);
82 | }
83 | if (newSignalListeners) {
84 | addSignalListenersToView(view, newSignalListeners);
85 | }
86 | view.run();
87 | });
88 | }
89 | }
90 | }
91 |
92 | componentWillUnmount() {
93 | this.clearView();
94 | }
95 |
96 | handleError = (error: Error): undefined => {
97 | const { onError = NOOP } = this.props;
98 | onError(error, this.containerRef.current as HTMLDivElement);
99 | // eslint-disable-next-line no-console
100 | console.warn(error);
101 |
102 | return undefined;
103 | };
104 |
105 | modifyView = (action: ViewListener) => {
106 | if (this.resultPromise) {
107 | this.resultPromise
108 | .then(result => {
109 | if (result) {
110 | action(result.view);
111 | }
112 |
113 | return true;
114 | })
115 | .catch(this.handleError);
116 | }
117 | };
118 |
119 | createView() {
120 | const { spec, onNewView, signalListeners = {}, width, height, ...options } = this.props;
121 | if (this.containerRef.current) {
122 | const finalSpec = combineSpecWithDimension(this.props);
123 | this.resultPromise = vegaEmbed(this.containerRef.current, finalSpec, options)
124 | .then(result => {
125 | if (result) {
126 | const { view } = result;
127 | if (addSignalListenersToView(view, signalListeners)) {
128 | view.run();
129 | }
130 | }
131 | return result;
132 | })
133 | .catch(this.handleError);
134 |
135 | if (onNewView) {
136 | this.modifyView(onNewView);
137 | }
138 | }
139 | }
140 |
141 | clearView() {
142 | if (this.resultPromise) {
143 | this.resultPromise
144 | .then(result => {
145 | if (result) {
146 | result.finalize();
147 | }
148 | })
149 | .catch(this.handleError);
150 | }
151 | this.resultPromise = undefined;
152 |
153 | return this;
154 | }
155 |
156 | render() {
157 | const { className, style } = this.props;
158 |
159 | // Create the container Vega draws inside
160 | return ;
161 | }
162 | }
163 |
--------------------------------------------------------------------------------
/packages/react-vega/src/VegaLite.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import Vega, { VegaProps } from './Vega';
3 |
4 | export type VegaLiteProps = Omit;
5 |
6 | /**
7 | * Syntactic sugar for using vega-lite with Vega
8 | * @param props
9 | */
10 | export default function VegaLite(props: VegaLiteProps) {
11 | return ;
12 | }
13 |
--------------------------------------------------------------------------------
/packages/react-vega/src/constants.ts:
--------------------------------------------------------------------------------
1 | // eslint-disable-next-line import/prefer-default-export
2 | export const NOOP = () => {};
3 |
--------------------------------------------------------------------------------
/packages/react-vega/src/createClassFromSpec.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { VisualizationSpec, Mode } from './types';
3 | import Vega, { VegaProps } from './Vega';
4 |
5 | interface Constructor {
6 | getSpec: () => VisualizationSpec;
7 | new (...args: unknown[]): T;
8 | }
9 |
10 | export type FixedVegaChartProps = Omit;
11 |
12 | export default function createClassFromSpec({
13 | mode,
14 | spec,
15 | }: {
16 | mode?: Mode;
17 | spec: VisualizationSpec;
18 | }) {
19 | class FixedVegaChart extends React.PureComponent {
20 | static getSpec = function getSpec() {
21 | return spec;
22 | };
23 |
24 | render() {
25 | return ;
26 | }
27 | }
28 |
29 | return FixedVegaChart as Constructor>;
30 | }
31 |
--------------------------------------------------------------------------------
/packages/react-vega/src/index.ts:
--------------------------------------------------------------------------------
1 | export { default as Vega } from './Vega';
2 | export { default as VegaLite } from './VegaLite';
3 | export { default as createClassFromSpec } from './createClassFromSpec';
4 |
5 | // Export types
6 | export * from './types';
7 |
--------------------------------------------------------------------------------
/packages/react-vega/src/types/index.ts:
--------------------------------------------------------------------------------
1 | import type { Result } from 'vega-embed';
2 |
3 | /** Vega View object */
4 | export type View = Result['view'];
5 |
6 | export type PlainObject = {
7 | [key: string]: unknown;
8 | };
9 |
10 | export type SignalListener = (name: string, value: unknown) => void;
11 |
12 | export type SignalListeners = {
13 | [key: string]: SignalListener;
14 | };
15 |
16 | /** Handler of view actions */
17 | export type ViewListener = (view: View) => void;
18 |
19 | export * from './reExport';
20 |
--------------------------------------------------------------------------------
/packages/react-vega/src/types/reExport.ts:
--------------------------------------------------------------------------------
1 | // Types to re-export from dependencies
2 |
3 | export type { VisualizationSpec, Mode } from 'vega-embed';
4 |
--------------------------------------------------------------------------------
/packages/react-vega/src/utils/addSignalListenersToView.ts:
--------------------------------------------------------------------------------
1 | import { View, SignalListeners } from '../types';
2 |
3 | export default function addSignalListenersToView(view: View, signalListeners: SignalListeners) {
4 | const signalNames = Object.keys(signalListeners);
5 | signalNames.forEach(signalName => {
6 | try {
7 | view.addSignalListener(signalName, signalListeners[signalName]);
8 | } catch (error) {
9 | // eslint-disable-next-line no-console
10 | console.warn('Cannot add invalid signal listener.', error);
11 | }
12 | });
13 |
14 | return signalNames.length > 0;
15 | }
16 |
--------------------------------------------------------------------------------
/packages/react-vega/src/utils/combineSpecWithDimension.ts:
--------------------------------------------------------------------------------
1 | import { VisualizationSpec } from '../types';
2 |
3 | export default function combineSpecWithDimension(props: {
4 | spec: VisualizationSpec;
5 | width?: number;
6 | height?: number;
7 | }): VisualizationSpec {
8 | const { spec, width, height } = props;
9 | if (typeof width !== 'undefined' && typeof height !== 'undefined') {
10 | return { ...spec, width, height };
11 | }
12 | if (typeof width !== 'undefined') {
13 | return { ...spec, width };
14 | }
15 | if (typeof height !== 'undefined') {
16 | return { ...spec, height };
17 | }
18 | return spec;
19 | }
20 |
--------------------------------------------------------------------------------
/packages/react-vega/src/utils/computeSpecChanges.ts:
--------------------------------------------------------------------------------
1 | import equal from 'fast-deep-equal';
2 | import getUniqueFieldNames from './getUniqueFieldNames';
3 | import { VisualizationSpec } from '../types';
4 |
5 | interface SpecChanges {
6 | width: false | number;
7 | height: false | number;
8 | isExpensive: boolean;
9 | }
10 |
11 | export default function computeSpecChanges(newSpec: VisualizationSpec, oldSpec: VisualizationSpec) {
12 | if (newSpec === oldSpec) return false;
13 |
14 | const changes: SpecChanges = {
15 | width: false,
16 | height: false,
17 | isExpensive: false,
18 | };
19 |
20 | const fieldNames = getUniqueFieldNames([newSpec, oldSpec]);
21 |
22 | if (
23 | fieldNames.has('width') &&
24 | (!('width' in newSpec) || !('width' in oldSpec) || newSpec.width !== oldSpec.width)
25 | ) {
26 | if ('width' in newSpec && typeof newSpec.width === 'number') {
27 | changes.width = newSpec.width;
28 | } else {
29 | changes.isExpensive = true;
30 | }
31 | }
32 |
33 | if (
34 | fieldNames.has('height') &&
35 | (!('height' in newSpec) || !('height' in oldSpec) || newSpec.height !== oldSpec.height)
36 | ) {
37 | if ('height' in newSpec && typeof newSpec.height === 'number') {
38 | changes.height = newSpec.height;
39 | } else {
40 | changes.isExpensive = true;
41 | }
42 | }
43 |
44 | // Delete cheap fields
45 | fieldNames.delete('width');
46 | fieldNames.delete('height');
47 |
48 | if (
49 | [...fieldNames].some(
50 | field =>
51 | !(field in newSpec) ||
52 | !(field in oldSpec) ||
53 | !equal(newSpec[field as keyof typeof newSpec], oldSpec[field as keyof typeof oldSpec]),
54 | )
55 | ) {
56 | changes.isExpensive = true;
57 | }
58 |
59 | return changes.width !== false || changes.height !== false || changes.isExpensive
60 | ? changes
61 | : false;
62 | }
63 |
--------------------------------------------------------------------------------
/packages/react-vega/src/utils/getDatasetNamesFromSpec.ts:
--------------------------------------------------------------------------------
1 | import { VisualizationSpec } from '../types';
2 |
3 | export default function getDatasetNamesFromSpec(spec: VisualizationSpec) {
4 | const { data } = spec;
5 | if (data) {
6 | if (Array.isArray(data)) {
7 | // Array of data
8 | return data.map(({ name }) => name);
9 | }
10 | if (typeof data.name === 'string') {
11 | // Single data
12 | return [data.name];
13 | }
14 | }
15 |
16 | return [];
17 | }
18 |
--------------------------------------------------------------------------------
/packages/react-vega/src/utils/getUniqueFieldNames.ts:
--------------------------------------------------------------------------------
1 | export default function getUniqueFieldNames(objects: T[]) {
2 | const fields = new Set();
3 | objects.forEach(o => {
4 | Object.keys(o).forEach(field => {
5 | fields.add(field);
6 | });
7 | });
8 |
9 | return fields;
10 | }
11 |
--------------------------------------------------------------------------------
/packages/react-vega/src/utils/isFunction.ts:
--------------------------------------------------------------------------------
1 | export default function isFunction(functionToCheck: unknown): functionToCheck is Function {
2 | const getType = {};
3 |
4 | return !!functionToCheck && getType.toString.call(functionToCheck) === '[object Function]';
5 | }
6 |
--------------------------------------------------------------------------------
/packages/react-vega/src/utils/removeSignalListenersFromView.ts:
--------------------------------------------------------------------------------
1 | import { View, SignalListeners } from '../types';
2 |
3 | export default function removeSignalListenersFromView(
4 | view: View,
5 | signalListeners: SignalListeners,
6 | ) {
7 | const signalNames = Object.keys(signalListeners);
8 | signalNames.forEach(signalName => {
9 | try {
10 | view.removeSignalListener(signalName, signalListeners[signalName]);
11 | } catch (error) {
12 | // eslint-disable-next-line no-console
13 | console.warn('Cannot remove invalid signal listener.', error);
14 | }
15 | });
16 |
17 | return signalNames.length > 0;
18 | }
19 |
--------------------------------------------------------------------------------
/packages/react-vega/src/utils/shallowEqual.ts:
--------------------------------------------------------------------------------
1 | import { PlainObject } from '../types';
2 |
3 | const EMPTY = {};
4 |
5 | export default function shallowEqual(a: PlainObject = EMPTY, b: PlainObject = EMPTY) {
6 | const aKeys = Object.keys(a);
7 | const bKeys = Object.keys(b);
8 |
9 | return a === b || (aKeys.length === bKeys.length && aKeys.every(key => a[key] === b[key]));
10 | }
11 |
--------------------------------------------------------------------------------
/packages/react-vega/src/utils/updateMultipleDatasetsInView.ts:
--------------------------------------------------------------------------------
1 | import updateSingleDatasetInView from './updateSingleDatasetInView';
2 | import { PlainObject, View } from '../types';
3 |
4 | export default function updateMultipleDatasetsInView(view: View, data: PlainObject) {
5 | Object.keys(data).forEach(name => {
6 | updateSingleDatasetInView(view, name, data[name]);
7 | });
8 | }
9 |
--------------------------------------------------------------------------------
/packages/react-vega/src/utils/updateSingleDatasetInView.ts:
--------------------------------------------------------------------------------
1 | import { vega } from 'vega-embed';
2 | import isFunction from './isFunction';
3 | import { View } from '../types';
4 |
5 | export default function updateSingleDatasetInView(view: View, name: string, value: unknown) {
6 | if (value) {
7 | if (isFunction(value)) {
8 | value(view.data(name));
9 | } else {
10 | view.change(
11 | name,
12 | vega
13 | .changeset()
14 | .remove(() => true)
15 | .insert(value),
16 | );
17 | }
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/packages/react-vega/test/Vega.test.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { render } from '@testing-library/react';
3 | import { Vega } from '../src';
4 | import spec from './mock/vegaLiteSpec';
5 |
6 | describe('', () => {
7 | const data1 = {
8 | source: [
9 | { a: 'B', b: 29 },
10 | { a: 'C', b: 20 },
11 | ],
12 | };
13 | const data2 = {
14 | source: [
15 | { a: 'B', b: 29 },
16 | { a: 'C', b: 20 },
17 | { a: 'D', b: 15 },
18 | ],
19 | };
20 |
21 | it('renders', async () => {
22 | const { container } = render();
23 |
24 | await new Promise(resolve => setTimeout(resolve, 0));
25 | expect(container.querySelector('svg')).toBeDefined();
26 | expect(container.querySelectorAll('g.mark-rect path')).toHaveLength(5);
27 | });
28 |
29 | it('renders with data', async () => {
30 | const { container } = render();
31 |
32 | await new Promise(resolve => setTimeout(resolve, 0));
33 | expect(container.querySelector('svg')).toBeDefined();
34 | expect(container.querySelectorAll('g.mark-rect path')).toHaveLength(2);
35 | });
36 |
37 | it('updates data if changed', async () => {
38 | const { container, rerender } = render(
39 | ,
40 | );
41 |
42 | await new Promise(resolve => setTimeout(resolve, 0));
43 | expect(container.querySelectorAll('g.mark-rect path')).toHaveLength(2);
44 |
45 | rerender();
46 |
47 | await new Promise(resolve => setTimeout(resolve, 10));
48 | expect(container.querySelectorAll('g.mark-rect path')).toHaveLength(3);
49 | });
50 |
51 | it('does not update data if does not changed', async () => {
52 | const { container, rerender } = render(
53 | ,
54 | );
55 | rerender();
56 |
57 | await new Promise(resolve => setTimeout(resolve, 0));
58 | expect(container.querySelectorAll('g.mark-rect path')).toHaveLength(2);
59 | });
60 | });
61 |
--------------------------------------------------------------------------------
/packages/react-vega/test/VegaLite.test.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { render } from '@testing-library/react';
3 | import { VegaLite } from '../src';
4 | import spec from './mock/vegaLiteSpec';
5 |
6 | describe('', () => {
7 | it('renders', async () => {
8 | const { container } = render();
9 |
10 | await new Promise(resolve => setTimeout(resolve, 0));
11 | expect(container.querySelector('svg')).toBeDefined();
12 | expect(container.querySelectorAll('g.mark-rect path')).toHaveLength(5);
13 | });
14 | });
15 |
--------------------------------------------------------------------------------
/packages/react-vega/test/createClassFromSpec.test.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { render } from '@testing-library/react';
3 | import { createClassFromSpec } from '../src';
4 | import spec from './mock/vegaLiteSpec';
5 |
6 | describe('createClassFromSpec', () => {
7 | const Component = createClassFromSpec({ mode: 'vega-lite', spec });
8 |
9 | describe('.getSpec()', () => {
10 | it('returns spec', () => {
11 | expect(Component.getSpec()).toBe(spec);
12 | });
13 | });
14 |
15 | it('renders', async () => {
16 | const { container } = render();
17 |
18 | await new Promise(resolve => setTimeout(resolve, 0));
19 | expect(container.querySelector('svg')).toBeDefined();
20 | expect(container.querySelectorAll('g.mark-rect path')).toHaveLength(5);
21 | });
22 | });
23 |
--------------------------------------------------------------------------------
/packages/react-vega/test/index.test.ts:
--------------------------------------------------------------------------------
1 | import { Vega, VegaLite, createClassFromSpec } from '../src';
2 |
3 | describe('index', () => {
4 | it('exports Vega', () => {
5 | expect(Vega).toBeDefined();
6 | });
7 | it('exports VegaLite', () => {
8 | expect(VegaLite).toBeDefined();
9 | });
10 | it('exports createClassFromSpec', () => {
11 | expect(createClassFromSpec).toBeDefined();
12 | });
13 | });
14 |
--------------------------------------------------------------------------------
/packages/react-vega/test/mock/vegaLiteSpec.ts:
--------------------------------------------------------------------------------
1 | export default {
2 | $schema: 'https://vega.github.io/schema/vega-lite/v4.json',
3 | width: 100,
4 | height: 100,
5 | data: {
6 | values: [
7 | {
8 | a: 'A',
9 | b: 28,
10 | },
11 | {
12 | a: 'B',
13 | b: 55,
14 | },
15 | {
16 | a: 'C',
17 | b: 43,
18 | },
19 | {
20 | a: 'D',
21 | b: 91,
22 | },
23 | {
24 | a: 'E',
25 | b: 81,
26 | },
27 | ],
28 | name: 'source',
29 | },
30 | mark: 'bar',
31 | encoding: {
32 | x: {
33 | field: 'a',
34 | type: 'ordinal',
35 | },
36 | y: {
37 | field: 'b',
38 | type: 'quantitative',
39 | },
40 | tooltip: {
41 | field: 'b',
42 | type: 'quantitative',
43 | },
44 | },
45 | } as const;
46 |
--------------------------------------------------------------------------------
/packages/react-vega/test/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "composite": false,
4 | "emitDeclarationOnly": false,
5 | "noEmit": true,
6 | "rootDir": "."
7 | },
8 | "extends": "../../../tsconfig.options.json",
9 | "include": [
10 | "**/*",
11 | "../types/**/*",
12 | "../../../types/**/*"
13 | ],
14 | "references": [
15 | {
16 | "path": ".."
17 | }
18 | ]
19 | }
20 |
--------------------------------------------------------------------------------
/packages/react-vega/test/utils/combineSpecWithDimension.test.ts:
--------------------------------------------------------------------------------
1 | import combineSpecWithDimension from '../../src/utils/combineSpecWithDimension';
2 |
3 | describe('combineSpecWithDimension(props)', () => {
4 | const spec = {
5 | $schema: 'https://vega.github.io/schema/vega-lite/v4.json',
6 | data: {
7 | values: [],
8 | name: 'source',
9 | },
10 | mark: 'bar',
11 | encoding: {
12 | y: {
13 | field: 'b',
14 | type: 'quantitative',
15 | },
16 | },
17 | } as const;
18 |
19 | it('adds width and height', () => {
20 | expect(combineSpecWithDimension({ spec, width: 100, height: 100 })).toEqual({
21 | $schema: 'https://vega.github.io/schema/vega-lite/v4.json',
22 | width: 100,
23 | height: 100,
24 | data: {
25 | values: [],
26 | name: 'source',
27 | },
28 | mark: 'bar',
29 | encoding: {
30 | y: {
31 | field: 'b',
32 | type: 'quantitative',
33 | },
34 | },
35 | });
36 | });
37 |
38 | it('adds width', () => {
39 | expect(combineSpecWithDimension({ spec, width: 100 })).toEqual({
40 | $schema: 'https://vega.github.io/schema/vega-lite/v4.json',
41 | width: 100,
42 | data: {
43 | values: [],
44 | name: 'source',
45 | },
46 | mark: 'bar',
47 | encoding: {
48 | y: {
49 | field: 'b',
50 | type: 'quantitative',
51 | },
52 | },
53 | });
54 | });
55 |
56 | it('adds height', () => {
57 | expect(combineSpecWithDimension({ spec, height: 100 })).toEqual({
58 | $schema: 'https://vega.github.io/schema/vega-lite/v4.json',
59 | height: 100,
60 | data: {
61 | values: [],
62 | name: 'source',
63 | },
64 | mark: 'bar',
65 | encoding: {
66 | y: {
67 | field: 'b',
68 | type: 'quantitative',
69 | },
70 | },
71 | });
72 | });
73 |
74 | it('returns original if no width or height are defined', () => {
75 | expect(combineSpecWithDimension({ spec })).toBe(spec);
76 | });
77 | });
78 |
--------------------------------------------------------------------------------
/packages/react-vega/test/utils/getDatasetNamesFromSpec.test.ts:
--------------------------------------------------------------------------------
1 | import getDatasetNamesFromSpec from '../../src/utils/getDatasetNamesFromSpec';
2 |
3 | describe('getDatasetNamesFromSpec()', () => {
4 | it('returns dataset names for array', () => {
5 | expect(
6 | getDatasetNamesFromSpec({
7 | data: [
8 | {
9 | name: 'set1',
10 | },
11 | {
12 | name: 'set2',
13 | },
14 | ],
15 | }),
16 | ).toEqual(['set1', 'set2']);
17 | });
18 | it('returns dataset name for single dataset', () => {
19 | expect(
20 | // @ts-ignore
21 | getDatasetNamesFromSpec({
22 | data: {
23 | name: 'set1',
24 | },
25 | }),
26 | ).toEqual(['set1']);
27 | });
28 | it('returns empty array if data is not specified', () => {
29 | expect(getDatasetNamesFromSpec({})).toEqual([]);
30 | });
31 | });
32 |
--------------------------------------------------------------------------------
/packages/react-vega/test/utils/getUniqueFieldNames.test.ts:
--------------------------------------------------------------------------------
1 | import getUniqueFieldNames from '../../src/utils/getUniqueFieldNames';
2 |
3 | describe('getUniqueFieldNames(objects)', () => {
4 | it('returns a set of field names', () => {
5 | const fields = getUniqueFieldNames([{ a: 1, b: 1 }, { b: 2 }, { c: 3 }]);
6 | expect(Array.from(fields)).toEqual(['a', 'b', 'c']);
7 | });
8 | });
9 |
--------------------------------------------------------------------------------
/packages/react-vega/test/utils/isFunction.test.ts:
--------------------------------------------------------------------------------
1 | import isFunction from '../../src/utils/isFunction';
2 |
3 | describe('isFunction(func)', () => {
4 | it('returns true for function', () => {
5 | expect(isFunction(() => {})).toBeTruthy();
6 | expect(isFunction(() => 1)).toBeTruthy();
7 | });
8 | it('returns false otherwise', () => {
9 | expect(isFunction(0)).toBeFalsy();
10 | expect(isFunction('abc')).toBeFalsy();
11 | expect(isFunction({})).toBeFalsy();
12 | expect(isFunction([])).toBeFalsy();
13 | });
14 | });
15 |
--------------------------------------------------------------------------------
/packages/react-vega/test/utils/shallowEqual.test.ts:
--------------------------------------------------------------------------------
1 | import shallowEqual from '../../src/utils/shallowEqual';
2 |
3 | describe('shallowEqual(a, b)', () => {
4 | it('returns true if all fields are the same', () => {
5 | expect(shallowEqual({ a: 1 }, { a: 1 })).toBeTruthy();
6 | const value = { b: 1 };
7 | expect(shallowEqual({ a: value }, { a: value })).toBeTruthy();
8 | });
9 | it('does not check recursively', () => {
10 | expect(shallowEqual({ a: { b: 1 } }, { a: { b: 1 } })).toBeFalsy();
11 | });
12 | it('uses empty object if receive undefined', () => {
13 | expect(shallowEqual(undefined, undefined)).toBeTruthy();
14 | expect(shallowEqual(undefined, {})).toBeTruthy();
15 | expect(shallowEqual(undefined, { a: 1 })).toBeFalsy();
16 | expect(shallowEqual({}, undefined)).toBeTruthy();
17 | expect(shallowEqual({ a: 1 }, undefined)).toBeFalsy();
18 | });
19 | it('returns false otherwise', () => {
20 | expect(shallowEqual({ a: 1 }, { a: 2 })).toBeFalsy();
21 | expect(shallowEqual({ b: 1 }, { a: 2 })).toBeFalsy();
22 | });
23 | });
24 |
--------------------------------------------------------------------------------
/packages/react-vega/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "declarationDir": "lib",
4 | "outDir": "lib",
5 | "rootDir": "src"
6 | },
7 | "exclude": [
8 | "lib",
9 | "test"
10 | ],
11 | "extends": "../../tsconfig.options.json",
12 | "include": [
13 | "src/**/*",
14 | "types/**/*",
15 | "../../types/**/*"
16 | ],
17 | "references": []
18 | }
19 |
--------------------------------------------------------------------------------
/prettier.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | arrowParens: 'avoid',
3 | bracketSpacing: true,
4 | printWidth: 100,
5 | proseWrap: 'always',
6 | semi: true,
7 | singleQuote: true,
8 | tabWidth: 2,
9 | trailingComma: 'all',
10 | useTabs: false,
11 | };
12 |
--------------------------------------------------------------------------------
/scripts/tsc.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | startTime=$(node -e 'console.log(Date.now())')
4 | tscExitCode=$(tsc "$@")
5 | duration=$(node -e "console.log('%ss', (Date.now() - $startTime) / 1000)")
6 |
7 | if [ ! "$tscExitCode" ]; then
8 | echo "compiled in ${duration}"
9 | else
10 | exit "$tscExitCode"
11 | fi
12 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "files": [],
3 | "references": [
4 | {
5 | "path": "packages/react-vega"
6 | },
7 | {
8 | "path": "packages/react-vega/test"
9 | }
10 | ],
11 | "extends": "./tsconfig.options.json"
12 | }
13 |
--------------------------------------------------------------------------------
/tsconfig.options.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "allowSyntheticDefaultImports": true,
4 | "declarationDir": "lib",
5 | "outDir": "lib",
6 | "rootDir": "src",
7 | "declaration": true,
8 | "esModuleInterop": true,
9 | "forceConsistentCasingInFileNames": true,
10 | "isolatedModules": false,
11 | "jsx": "react",
12 | "lib": [
13 | "dom",
14 | "esnext"
15 | ],
16 | "module": "esnext",
17 | "moduleResolution": "node",
18 | "noEmitOnError": true,
19 | "noImplicitReturns": true,
20 | "noUnusedLocals": true,
21 | "pretty": true,
22 | "removeComments": false,
23 | "strict": true,
24 | "target": "es2015",
25 | "composite": true,
26 | "declarationMap": true,
27 | "resolveJsonModule": true,
28 | "emitDeclarationOnly": true
29 | }
30 | }
31 |
--------------------------------------------------------------------------------