Skip to content
This repository was archived by the owner on Nov 25, 2021. It is now read-only.

Commit 6dc07b4

Browse files
committed
transition support for boxplots and violin plots
1 parent 2e085cd commit 6dc07b4

File tree

4 files changed

+166
-2
lines changed

4 files changed

+166
-2
lines changed

samples/animation.html

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
<!doctype html>
2+
<html>
3+
4+
<head>
5+
<title>Box Plot Chart</title>
6+
<script src="../node_modules/chart.js/dist/Chart.bundle.js"></script>
7+
<script src="../build/Chart.BoxPlot.js" type="text/javascript"></script>
8+
<style>
9+
canvas {
10+
-moz-user-select: none;
11+
-webkit-user-select: none;
12+
-ms-user-select: none;
13+
}
14+
</style>
15+
</head>
16+
17+
<body>
18+
<div id="container" style="width: 75%;">
19+
<canvas id="canvas"></canvas>
20+
</div>
21+
<script>
22+
function randomValues(count, min, max) {
23+
const delta = max - min;
24+
return Array.from({length: count}).map(() => Math.random() * delta + min);
25+
}
26+
const boxplotData = {
27+
labels: ['A', 'B'],
28+
datasets: [{
29+
label: 'Dataset 1',
30+
borderColor: 'red',
31+
borderWidth: 1,
32+
outlierRadius: 3,
33+
itemRadius: 3,
34+
outlierColor: '#999999',
35+
data: [
36+
randomValues(100, 1, 9).concat([14, 16, 0]),
37+
randomValues(100, 0, 10)
38+
],
39+
}]
40+
};
41+
42+
window.onload = function() {
43+
var ctx = document.getElementById("canvas").getContext("2d");
44+
window.myBar = new Chart(ctx, {
45+
type: 'boxplot',
46+
data: boxplotData,
47+
options: {
48+
responsive: true,
49+
legend: {
50+
position: 'top',
51+
},
52+
title: {
53+
display: true,
54+
text: 'Chart.js Box Plot Chart'
55+
},
56+
scales: {
57+
xAxes: [{
58+
// Specific to Bar Controller
59+
categoryPercentage: 0.9,
60+
barPercentage: 0.8
61+
}]
62+
}
63+
}
64+
});
65+
};
66+
67+
setTimeout(function() {
68+
const vs = boxplotData.datasets[0].data[0];
69+
boxplotData.datasets[0].data[0] = randomValues(100, 8, 19);
70+
window.myBar.update();
71+
}, 1000);
72+
</script>
73+
</body>
74+
75+
</html>

src/elements/base.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,8 +29,8 @@ const ArrayElementBase = Chart.Element.extend({
2929
ctx.strokeStle = vm.itemBorderColor;
3030
ctx.fillStyle = vm.itemBackgroundColor;
3131
// jitter based on random data
32-
// use the median to initialize the random number generator
33-
const random = rnd(container.median);
32+
// use the datesetindex and index to initialize the random number generator
33+
const random = rnd(this._datasetIndex * 1000 + this._index);
3434

3535
if (vert) {
3636
container.items.forEach((v) => {

src/elements/boxandwhiskers.js

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,51 @@ import ArrayElementBase, {defaults} from './base';
66

77
Chart.defaults.global.elements.boxandwhiskers = Object.assign({}, defaults);
88

9+
function transitionBoxPlot(start, view, model, ease) {
10+
const keys = Object.keys(model);
11+
for (const key of keys) {
12+
const target = model[key];
13+
const origin = start[key];
14+
if (origin === target) {
15+
continue;
16+
}
17+
if (typeof target === 'number') {
18+
view[key] = origin + (target - origin) * ease;
19+
continue;
20+
}
21+
if (Array.isArray(target)) {
22+
const v = view[key];
23+
const common = Math.min(target.length, origin.length);
24+
for (let i = 0; i < common; ++i) {
25+
v[i] = origin[i] + (target[i] - origin[i]) * ease;
26+
}
27+
}
28+
}
29+
}
30+
931
const BoxAndWiskers = Chart.elements.BoxAndWhiskers = ArrayElementBase.extend({
32+
transition(ease) {
33+
const r = Chart.Element.prototype.transition.call(this, ease);
34+
const model = this._model;
35+
const start = this._start;
36+
const view = this._view;
37+
38+
// No animation -> No Transition
39+
if (!model || ease === 1) {
40+
return r;
41+
}
42+
if (start.boxplot == null) {
43+
return r; // model === view -> not copied
44+
}
45+
46+
// create deep copy to avoid alternation
47+
if (model.boxplot === view.boxplot) {
48+
view.boxplot = Chart.helpers.clone(view.boxplot);
49+
}
50+
transitionBoxPlot(start.boxplot, view.boxplot, model.boxplot, ease);
51+
52+
return r;
53+
},
1054
draw() {
1155
const ctx = this._chart.ctx;
1256
const vm = this._view;

src/elements/violin.js

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,52 @@ Chart.defaults.global.elements.violin = Object.assign({
88
points: 100
99
}, defaults);
1010

11+
function transitionViolin(start, view, model, ease) {
12+
const keys = Object.keys(model);
13+
for (const key of keys) {
14+
const target = model[key];
15+
const origin = start[key];
16+
if (origin === target) {
17+
continue;
18+
}
19+
if (typeof target === 'number') {
20+
view[key] = origin + (target - origin) * ease;
21+
continue;
22+
}
23+
if (key === 'coords') {
24+
const v = view[key];
25+
const common = Math.min(target.length, origin.length);
26+
for (let i = 0; i < common; ++i) {
27+
v[i].v = origin[i].v + (target[i].v - origin[i].v) * ease;
28+
v[i].estimate = origin[i].estimate + (target[i].estimate - origin[i].estimate) * ease;
29+
}
30+
}
31+
}
32+
}
33+
1134
const Violin = Chart.elements.Violin = ArrayElementBase.extend({
35+
transition(ease) {
36+
const r = Chart.Element.prototype.transition.call(this, ease);
37+
const model = this._model;
38+
const start = this._start;
39+
const view = this._view;
40+
41+
// No animation -> No Transition
42+
if (!model || ease === 1) {
43+
return r;
44+
}
45+
if (start.violin == null) {
46+
return r; // model === view -> not copied
47+
}
48+
49+
// create deep copy to avoid alternation
50+
if (model.violin === view.violin) {
51+
view.violin = Chart.helpers.clone(view.violin);
52+
}
53+
transitionViolin(start.violin, view.violin, model.violin, ease);
54+
55+
return r;
56+
},
1257
draw() {
1358
const ctx = this._chart.ctx;
1459
const vm = this._view;

0 commit comments

Comments
 (0)