Skip to content

Commit

Permalink
fix: improve Chart component
Browse files Browse the repository at this point in the history
fix: #2053
  • Loading branch information
HellWolf93 committed Jan 29, 2021
1 parent a47ea12 commit f939cd5
Show file tree
Hide file tree
Showing 6 changed files with 172 additions and 34 deletions.
19 changes: 7 additions & 12 deletions src/components/Chart/__test__/chart.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,10 @@ jest.mock('../helpers/unregisterGlobalPlugins', () => jest.fn());

jest.mock('../chart', () =>
jest.fn(() => ({
data: {
labels: [],
datasets: [],
},
update: jest.fn(),
config: {},
})),
Expand All @@ -25,18 +29,13 @@ describe('<Chart />', () => {
type: 'line',
data: {
labels: ['A', 'B', 'C', 'D'],
datasets: [expect.any(Object)],
},
plugins: null,
options: expect.any(Object),
});
});
it('should call ChartJS update method to update the chart when the component changes', () => {
const component = mount(
<Chart labels={['A', 'B', 'C', 'D']} type="line">
<Dataset values={[34, 345, 234, 234]} title="Dataset 1" />
</Chart>,
);
const component = mount(<Chart labels={['A', 'B', 'C', 'D']} type="line" />);
const { chartInstance } = component.instance();
component.setProps({
labels: ['A', 'B', 'C', 'D', 'E'],
Expand All @@ -46,16 +45,12 @@ describe('<Chart />', () => {
});

it('should call ChartJS update method to update the chart when the children changes', () => {
const component = mount(
<Chart labels={['A', 'B', 'C', 'D']} type="line">
<Dataset values={[34, 345, 234, 234]} title="Dataset 1" />
</Chart>,
);
const component = mount(<Chart labels={['A', 'B', 'C', 'D']} type="line" />);
const { chartInstance } = component.instance();
component.setProps({
children: [<Dataset values={[34, 345, 234, 234, 90]} title="Dataset 1" />],
});
expect(chartInstance.update).toHaveBeenCalledTimes(1);
expect(chartInstance.update).toHaveBeenCalledTimes(2);
});

it('should update the chart type when changed dynamically', () => {
Expand Down
4 changes: 4 additions & 0 deletions src/components/Chart/context.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
import React from 'react';

const ChartContext = React.createContext();
export default ChartContext;
58 changes: 39 additions & 19 deletions src/components/Chart/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,10 @@ import PropTypes from 'prop-types';
import { withTheme } from 'styled-components';
import ChartJS from './chart';
import resolveOptions from './resolveOptions';
import resolveDatasets from './resolveDatasets';
import StyledContainer from './styled/container';
import DatasetContainer from './styled/datasetContainer';
import unregisterGlobalPlugins from './helpers/unregisterGlobalPlugins';
import ChartContext from './context';

/**
* A chart is a graphical representation of data. Charts allow users to better understand
Expand All @@ -18,36 +19,48 @@ export class Chart extends Component {
constructor(props) {
super(props);
this.chartRef = React.createRef();
this.datasets = [];
this.datasets = {};
this.registerDataset = this.registerDataset.bind(this);
this.unregisterDataset = this.unregisterDataset.bind(this);
this.updateDataset = this.updateDataset.bind(this);
}

componentDidMount() {
const { children } = this.props;
this.datasets = resolveDatasets(children);
this.renderChart();
}

componentDidUpdate() {
const { children } = this.props;
this.datasets = resolveDatasets(children);
this.updateChart();
}

updateChart() {
const { labels, type, ...conditions } = this.props;
this.chartInstance.data = {
labels,
datasets: this.datasets,
};
this.chartInstance.config.type = type;
this.chartInstance.data.labels = labels;
this.chartInstance.data.datasets = Object.values(this.datasets);
this.chartInstance.options = resolveOptions({ type, ...conditions });
this.chartInstance.update();
}

if (type && this.chartInstance.config && type !== this.chartInstance.config.type) {
this.chartInstance.config.type = type;
}
registerDataset(id, dataset) {
this.datasets[id] = dataset;
this.updateChart();
}

this.chartInstance.options = resolveOptions({ type, ...conditions });
unregisterDataset(id) {
const { [id]: remove, ...rest } = this.datasets;
this.datasets = rest;
this.chartInstance.update();
}

updateDataset(id, dataset) {
const keys = Object.keys(dataset);
keys.forEach(key => {
this.datasets[id][key] = dataset[key];
});
this.updateChart();
}

renderChart() {
unregisterGlobalPlugins(ChartJS);
const { type, labels, plugins, ...conditions } = this.props;
Expand All @@ -57,20 +70,27 @@ export class Chart extends Component {
type,
data: {
labels,
datasets: this.datasets,
},
plugins: plugins || null,
options: resolveOptions({ type, plugins, ...conditions }),
});
}

render() {
const { style, className } = this.props;
const { style, className, children } = this.props;
const context = {
registerDataset: this.registerDataset,
unregisterDataset: this.unregisterDataset,
updateDataset: this.updateDataset,
};

return (
<StyledContainer className={className} style={style}>
<canvas ref={this.chartRef} />
</StyledContainer>
<ChartContext.Provider value={context}>
<StyledContainer className={className} style={style}>
<canvas ref={this.chartRef} />
<DatasetContainer>{children}</DatasetContainer>
</StyledContainer>
</ChartContext.Provider>
);
}
}
Expand Down
7 changes: 7 additions & 0 deletions src/components/Chart/styled/datasetContainer.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import styled from 'styled-components';

const StyledDatasetContainer = styled.div`
display: none;
`;

export default StyledDatasetContainer;
78 changes: 78 additions & 0 deletions src/components/Dataset/__test__/dataset.spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
import React from 'react';
import { mount } from 'enzyme';
import Dataset from '..';

const context = {
registerDataset: jest.fn(),
unregisterDataset: jest.fn(),
updateDataset: jest.fn(),
};

describe('<Dataset />', () => {
beforeEach(() => {
jest.resetAllMocks();
React.useContext = jest.fn().mockReturnValue(context);
});

it('should call registerDataset with dataset props', () => {
mount(
<Dataset
title="Dataset 1"
values={[1, 2, 3]}
backgroundColor="#ccc"
borderColor="#000"
stack="stack-1"
type="line"
/>,
);
expect(context.registerDataset).toHaveBeenCalledWith(expect.any(String), {
label: 'Dataset 1',
data: [1, 2, 3],
backgroundColor: '#ccc',
borderColor: '#000',
stack: 'stack-1',
type: 'line',
fill: false,
});
});

it('should call unregisterDataset when unmounted', () => {
const wrapper = mount(
<Dataset
title="Dataset 1"
values={[1, 2, 3]}
backgroundColor="#ccc"
borderColor="#000"
stack="stack-1"
type="line"
/>,
);
wrapper.unmount();
expect(context.unregisterDataset).toHaveBeenCalledTimes(1);
});

it('should call updateDataset when props changes', () => {
const wrapper = mount(
<Dataset
title="Dataset 1"
values={[1, 2, 3]}
backgroundColor="#ccc"
borderColor="#000"
stack="stack-1"
type="line"
/>,
);
wrapper.setProps({
values: [4, 5, 6],
});
expect(context.updateDataset).toHaveBeenCalledWith(expect.any(String), {
label: 'Dataset 1',
data: [4, 5, 6],
backgroundColor: '#ccc',
borderColor: '#000',
stack: 'stack-1',
type: 'line',
fill: false,
});
});
});
40 changes: 37 additions & 3 deletions src/components/Dataset/index.js
Original file line number Diff line number Diff line change
@@ -1,10 +1,44 @@
/* eslint-disable react/no-unused-prop-types */
import React from 'react';
import React, { useEffect, useRef } from 'react';
import PropTypes from 'prop-types';
import ChartContext from '../Chart/context';
import { useUniqueIdentifier } from '../../libs/hooks';

/** @category DataView */
export default function Dataset() {
return <div />;
export default function Dataset(props) {
const datasetId = useUniqueIdentifier('dataset');
const { title: label, values: data, ...rest } = props;
const { registerDataset, unregisterDataset, updateDataset } = React.useContext(ChartContext);
const isRegistered = useRef();

useEffect(() => {
if (isRegistered.current) {
updateDataset(datasetId, {
label,
data,
...rest,
});
}
});

useEffect(() => {
registerDataset(datasetId, {
label,
data,
...rest,
});
isRegistered.current = true;

return () => {
if (isRegistered.current) {
unregisterDataset(datasetId);
isRegistered.current = false;
}
};
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);

return <></>;
}

Dataset.propTypes = {
Expand Down

0 comments on commit f939cd5

Please sign in to comment.