Skip to content

Commit f4dfc51

Browse files
sjurbajerairrest
authored andcommitted
Improve update of datasets to use correct dataset order (#227)
* Added storybook item for updating chart with example of faliure * Improve update of datasets to use correct dataset order
1 parent 1f86e35 commit f4dfc51

File tree

3 files changed

+196
-30
lines changed

3 files changed

+196
-30
lines changed

src/index.js

Lines changed: 28 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import PropTypes from 'prop-types';
33
import Chart from 'chart.js';
44
import isEqual from 'lodash/isEqual';
55
import find from 'lodash/find';
6-
6+
import keyBy from 'lodash/keyBy';
77

88
class ChartComponent extends React.Component {
99
static getLabelAsKey = d => d.label;
@@ -160,36 +160,35 @@ class ChartComponent extends React.Component {
160160
let currentDatasets = (this.chartInstance.config.data && this.chartInstance.config.data.datasets) || [];
161161
const nextDatasets = data.datasets || [];
162162

163-
// use the key provider to work out which series have been added/removed/changed
164-
const currentDatasetKeys = currentDatasets.map(this.props.datasetKeyProvider);
165-
const nextDatasetKeys = nextDatasets.map(this.props.datasetKeyProvider);
166-
const newDatasets = nextDatasets.filter(d => currentDatasetKeys.indexOf(this.props.datasetKeyProvider(d)) === -1);
167-
168-
// process the updates (via a reverse for loop so we can safely splice deleted datasets out of the array
169-
for (let idx = currentDatasets.length - 1; idx >= 0; idx -= 1) {
170-
const currentDatasetKey = this.props.datasetKeyProvider(currentDatasets[idx]);
171-
if (nextDatasetKeys.indexOf(currentDatasetKey) === -1) {
172-
// deleted series
173-
currentDatasets.splice(idx, 1);
163+
const currentDatasetsIndexed = keyBy(
164+
currentDatasets,
165+
this.props.datasetKeyProvider
166+
);
167+
168+
// We can safely replace the dataset array, as long as we retain the _meta property
169+
// on each dataset.
170+
this.chartInstance.config.data.datasets = nextDatasets.map(next => {
171+
const current =
172+
currentDatasetsIndexed[this.props.datasetKeyProvider(next)];
173+
if (current && current.type === next.type) {
174+
// The data array must be edited in place. As chart.js adds listeners to it.
175+
current.data.splice(next.data.length);
176+
next.data.forEach((point, pid) => {
177+
current.data[pid] = next.data[pid];
178+
});
179+
const { data, ...otherProps } = next;
180+
// Merge properties. Notice a weakness here. If a property is removed
181+
// from next, it will be retained by current and never disappears.
182+
// Workaround is to set value to null or undefined in next.
183+
return {
184+
...current,
185+
...otherProps
186+
};
174187
} else {
175-
const retainedDataset = find(nextDatasets, d => this.props.datasetKeyProvider(d) === currentDatasetKey);
176-
if (retainedDataset) {
177-
// update it in place if it is a retained dataset
178-
currentDatasets[idx].data.splice(retainedDataset.data.length);
179-
retainedDataset.data.forEach((point, pid) => {
180-
currentDatasets[idx].data[pid] = retainedDataset.data[pid];
181-
});
182-
const {data, ...otherProps} = retainedDataset;
183-
currentDatasets[idx] = {
184-
data: currentDatasets[idx].data,
185-
...currentDatasets[idx],
186-
...otherProps
187-
};
188-
}
188+
return next;
189189
}
190-
}
191-
// finally add any new series
192-
newDatasets.forEach(d => currentDatasets.push(d));
190+
});
191+
193192
const { datasets, ...rest } = data;
194193

195194
this.chartInstance.config.data = {

stories/UpdatingChart.js

Lines changed: 167 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,167 @@
1+
import React from 'react';
2+
import { Bar } from 'react-chartjs-2';
3+
import { storiesOf } from '@kadira/storybook';
4+
5+
const srcData = [
6+
{
7+
year: 2015,
8+
data: [
9+
536531,
10+
1017273,
11+
1496702,
12+
1882366,
13+
2228939,
14+
2515784,
15+
2753399,
16+
2966478,
17+
3236838,
18+
3613068,
19+
4047828,
20+
4547209
21+
],
22+
color: 'hsla(50,100%,59.21569%,1)'
23+
},
24+
{
25+
year: 2016,
26+
data: [
27+
551503,
28+
1057792,
29+
1521903,
30+
1908192,
31+
2191201,
32+
2412114,
33+
2634171,
34+
2900548,
35+
3159543,
36+
3552987,
37+
4052115,
38+
4553624
39+
],
40+
color: 'hsla(104,46.15384%,54.11765%,1)'
41+
},
42+
{
43+
year: 2017,
44+
data: [
45+
546988,
46+
1031054,
47+
1526958,
48+
1929360,
49+
2219497,
50+
2472468,
51+
2654013,
52+
2876660,
53+
3125501,
54+
3464636,
55+
3911575,
56+
3976944
57+
],
58+
color: 'hsla(191,100%,36.66667%,1)'
59+
}
60+
];
61+
62+
const options = {
63+
responsive: true,
64+
tooltips: {
65+
mode: 'label'
66+
},
67+
elements: {
68+
line: {
69+
fill: false,
70+
lineTension: 0
71+
}
72+
},
73+
scales: {
74+
yAxes: [
75+
{
76+
tics: { min: 0 }
77+
}
78+
]
79+
},
80+
legend: {
81+
display: true,
82+
position: 'bottom',
83+
reverse: true,
84+
onClick: null
85+
}
86+
};
87+
88+
storiesOf('Updating chart Example', module).add('Line & Bar', () => {
89+
const labels = [
90+
'January',
91+
'February',
92+
'March',
93+
'April',
94+
'May',
95+
'June',
96+
'July',
97+
'August',
98+
'September',
99+
'October',
100+
'November',
101+
'December'
102+
];
103+
104+
const Chart = ({ data }) => {
105+
const config = {
106+
labels: labels,
107+
datasets: data.map((series, idx, arr) => {
108+
let { year, data, color } = series;
109+
return {
110+
id: year,
111+
type: idx < arr.length - 1 ? 'line' : 'bar',
112+
label: year,
113+
data: data,
114+
backgroundColor: color,
115+
borderColor: color
116+
};
117+
})
118+
};
119+
return <Bar data={config} options={options} />;
120+
};
121+
122+
class SelectAndChart extends React.Component {
123+
constructor() {
124+
super();
125+
this.state = { data: srcData.map(s => ({ ...s, selected: true })) };
126+
}
127+
128+
toggleYear(year) {
129+
this.setState(state => {
130+
return {
131+
data: state.data.map(s => ({
132+
...s,
133+
selected: year === s.year ? !s.selected : s.selected
134+
}))
135+
};
136+
});
137+
}
138+
139+
render() {
140+
return (
141+
<div>
142+
<Chart data={this.state.data.filter(series => series.selected)} />
143+
<Select data={this.state.data} toggle={this.toggleYear.bind(this)} />
144+
</div>
145+
);
146+
}
147+
}
148+
149+
const Select = ({ data, toggle }) => {
150+
return (
151+
<div>
152+
{data.map(({ year, selected }) => (
153+
<div key={year}>
154+
<input
155+
type="checkbox"
156+
checked={selected}
157+
onChange={toggle.bind(null, year)}
158+
/>
159+
<label htmlFor={year}>{year}</label>
160+
</div>
161+
))}
162+
</div>
163+
);
164+
};
165+
166+
return <SelectAndChart />;
167+
});

stories/index.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,4 +4,4 @@ import { storiesOf, action, linkTo } from '@kadira/storybook';
44
import './Welcome';
55
import './StockExamples';
66
import './MixLineBar';
7-
7+
import './UpdatingChart';

0 commit comments

Comments
 (0)