Skip to content

Improve update of datasets to use correct dataset order #227

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Jan 12, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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';