Skip to content

Commit

Permalink
Improve update of datasets to use correct dataset order (#227)
Browse files Browse the repository at this point in the history
* Added storybook item for updating chart with example of faliure

* Improve update of datasets to use correct dataset order
  • Loading branch information
sjurba authored and jerairrest committed Jan 12, 2018
1 parent 1f86e35 commit f4dfc51
Show file tree
Hide file tree
Showing 3 changed files with 196 additions and 30 deletions.
57 changes: 28 additions & 29 deletions src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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 = {
Expand Down
167 changes: 167 additions & 0 deletions stories/UpdatingChart.js
Original file line number Diff line number Diff line change
@@ -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 <Bar data={config} options={options} />;
};

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 (
<div>
<Chart data={this.state.data.filter(series => series.selected)} />
<Select data={this.state.data} toggle={this.toggleYear.bind(this)} />
</div>
);
}
}

const Select = ({ data, toggle }) => {
return (
<div>
{data.map(({ year, selected }) => (
<div key={year}>
<input
type="checkbox"
checked={selected}
onChange={toggle.bind(null, year)}
/>
<label htmlFor={year}>{year}</label>
</div>
))}
</div>
);
};

return <SelectAndChart />;
});
2 changes: 1 addition & 1 deletion stories/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,4 @@ import { storiesOf, action, linkTo } from '@kadira/storybook';
import './Welcome';
import './StockExamples';
import './MixLineBar';

import './UpdatingChart';

0 comments on commit f4dfc51

Please sign in to comment.