diff --git a/src/index.js b/src/index.js index 2900ccf8e..65c2ae4dd 100644 --- a/src/index.js +++ b/src/index.js @@ -3,7 +3,7 @@ import PropTypes from 'prop-types'; import Chart from 'chart.js'; import isEqual from 'lodash/isEqual'; import find from 'lodash/find'; - +import keyBy from 'lodash/keyBy'; class ChartComponent extends React.Component { static getLabelAsKey = d => d.label; @@ -160,36 +160,35 @@ class ChartComponent extends React.Component { let currentDatasets = (this.chartInstance.config.data && this.chartInstance.config.data.datasets) || []; const nextDatasets = data.datasets || []; - // use the key provider to work out which series have been added/removed/changed - const currentDatasetKeys = currentDatasets.map(this.props.datasetKeyProvider); - const nextDatasetKeys = nextDatasets.map(this.props.datasetKeyProvider); - const newDatasets = nextDatasets.filter(d => currentDatasetKeys.indexOf(this.props.datasetKeyProvider(d)) === -1); - - // process the updates (via a reverse for loop so we can safely splice deleted datasets out of the array - for (let idx = currentDatasets.length - 1; idx >= 0; idx -= 1) { - const currentDatasetKey = this.props.datasetKeyProvider(currentDatasets[idx]); - if (nextDatasetKeys.indexOf(currentDatasetKey) === -1) { - // deleted series - currentDatasets.splice(idx, 1); + const currentDatasetsIndexed = keyBy( + currentDatasets, + this.props.datasetKeyProvider + ); + + // We can safely replace the dataset array, as long as we retain the _meta property + // on each dataset. + this.chartInstance.config.data.datasets = nextDatasets.map(next => { + const current = + currentDatasetsIndexed[this.props.datasetKeyProvider(next)]; + if (current && current.type === next.type) { + // The data array must be edited in place. As chart.js adds listeners to it. + current.data.splice(next.data.length); + next.data.forEach((point, pid) => { + current.data[pid] = next.data[pid]; + }); + const { data, ...otherProps } = next; + // Merge properties. Notice a weakness here. If a property is removed + // from next, it will be retained by current and never disappears. + // Workaround is to set value to null or undefined in next. + return { + ...current, + ...otherProps + }; } else { - const retainedDataset = find(nextDatasets, d => this.props.datasetKeyProvider(d) === currentDatasetKey); - if (retainedDataset) { - // update it in place if it is a retained dataset - currentDatasets[idx].data.splice(retainedDataset.data.length); - retainedDataset.data.forEach((point, pid) => { - currentDatasets[idx].data[pid] = retainedDataset.data[pid]; - }); - const {data, ...otherProps} = retainedDataset; - currentDatasets[idx] = { - data: currentDatasets[idx].data, - ...currentDatasets[idx], - ...otherProps - }; - } + return next; } - } - // finally add any new series - newDatasets.forEach(d => currentDatasets.push(d)); + }); + const { datasets, ...rest } = data; this.chartInstance.config.data = { diff --git a/stories/UpdatingChart.js b/stories/UpdatingChart.js new file mode 100644 index 000000000..7817a3b29 --- /dev/null +++ b/stories/UpdatingChart.js @@ -0,0 +1,167 @@ +import React from 'react'; +import { Bar } from 'react-chartjs-2'; +import { storiesOf } from '@kadira/storybook'; + +const srcData = [ + { + year: 2015, + data: [ + 536531, + 1017273, + 1496702, + 1882366, + 2228939, + 2515784, + 2753399, + 2966478, + 3236838, + 3613068, + 4047828, + 4547209 + ], + color: 'hsla(50,100%,59.21569%,1)' + }, + { + year: 2016, + data: [ + 551503, + 1057792, + 1521903, + 1908192, + 2191201, + 2412114, + 2634171, + 2900548, + 3159543, + 3552987, + 4052115, + 4553624 + ], + color: 'hsla(104,46.15384%,54.11765%,1)' + }, + { + year: 2017, + data: [ + 546988, + 1031054, + 1526958, + 1929360, + 2219497, + 2472468, + 2654013, + 2876660, + 3125501, + 3464636, + 3911575, + 3976944 + ], + color: 'hsla(191,100%,36.66667%,1)' + } +]; + +const options = { + responsive: true, + tooltips: { + mode: 'label' + }, + elements: { + line: { + fill: false, + lineTension: 0 + } + }, + scales: { + yAxes: [ + { + tics: { min: 0 } + } + ] + }, + legend: { + display: true, + position: 'bottom', + reverse: true, + onClick: null + } +}; + +storiesOf('Updating chart Example', module).add('Line & Bar', () => { + const labels = [ + 'January', + 'February', + 'March', + 'April', + 'May', + 'June', + 'July', + 'August', + 'September', + 'October', + 'November', + 'December' + ]; + + const Chart = ({ data }) => { + const config = { + labels: labels, + datasets: data.map((series, idx, arr) => { + let { year, data, color } = series; + return { + id: year, + type: idx < arr.length - 1 ? 'line' : 'bar', + label: year, + data: data, + backgroundColor: color, + borderColor: color + }; + }) + }; + return ; + }; + + class SelectAndChart extends React.Component { + constructor() { + super(); + this.state = { data: srcData.map(s => ({ ...s, selected: true })) }; + } + + toggleYear(year) { + this.setState(state => { + return { + data: state.data.map(s => ({ + ...s, + selected: year === s.year ? !s.selected : s.selected + })) + }; + }); + } + + render() { + return ( +
+ series.selected)} /> + + +
+ ))} + + ); + }; + + return ; +}); diff --git a/stories/index.js b/stories/index.js index 9eee53ae1..7a0ff4c76 100644 --- a/stories/index.js +++ b/stories/index.js @@ -4,4 +4,4 @@ import { storiesOf, action, linkTo } from '@kadira/storybook'; import './Welcome'; import './StockExamples'; import './MixLineBar'; - +import './UpdatingChart';