Add support to fill between datasets (#4008)
The `fill` option now accepts the index of the target dataset (number) or a string starting by "+" or "-" followed by a number representing the dataset index relative to the current one (e.g. `fill: "-2"` on dataset at index 3 will fill to dataset at index 1). It's also possible to "propagate" the filling to the target of an hidden dataset (`options.plugins.filler.propagate`). Fill boundaries `zero`, `top` and `bottom` have been deprecated and replaced by `origin`, `start` and `end`. Implementation has been moved out of the line element into a new plugin (`src/plugins/plugin.filler.js`) and does not rely anymore on the deprecated model `scaleTop`, `scaleBottom` and `scaleZero` values. Drawing Bézier splines has been refactored in the canvas helpers (note that `Chart.helpers.canvas` is now an alias of `Chart.canvasHelpers`). Add 3 new examples and extend utils with a pseudo-random number generator that can be initialized with `srand`. That makes possible to design examples starting always with the same initial data.
66
samples/area/analyser.js
Normal file
@ -0,0 +1,66 @@
|
||||
/* global Chart */
|
||||
|
||||
'use strict';
|
||||
|
||||
(function() {
|
||||
Chart.plugins.register({
|
||||
id: 'samples_filler_analyser',
|
||||
|
||||
beforeInit: function(chart, options) {
|
||||
this.element = document.getElementById(options.target);
|
||||
},
|
||||
|
||||
afterUpdate: function(chart) {
|
||||
var datasets = chart.data.datasets;
|
||||
var element = this.element;
|
||||
var stats = [];
|
||||
var meta, i, ilen, dataset;
|
||||
|
||||
if (!element) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (i=0, ilen=datasets.length; i<ilen; ++i) {
|
||||
meta = chart.getDatasetMeta(i).$filler;
|
||||
if (meta) {
|
||||
dataset = datasets[i];
|
||||
stats.push({
|
||||
fill: dataset.fill,
|
||||
target: meta.fill,
|
||||
visible: meta.visible,
|
||||
index: i
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
this.element.innerHTML = '<table>' +
|
||||
'<tr>' +
|
||||
'<th>Dataset</th>' +
|
||||
'<th>Fill</th>' +
|
||||
'<th>Target (visibility)</th>' +
|
||||
'</tr>' +
|
||||
stats.map(function(stat) {
|
||||
var target = stat.target;
|
||||
var row =
|
||||
'<td><b>' + stat.index + '</b></td>' +
|
||||
'<td>' + JSON.stringify(stat.fill) + '</td>';
|
||||
|
||||
if (target === false) {
|
||||
target = 'none';
|
||||
} else if (isFinite(target)) {
|
||||
target = 'dataset ' + target;
|
||||
} else {
|
||||
target = 'boundary "' + target + '"';
|
||||
}
|
||||
|
||||
if (stat.visible) {
|
||||
row += '<td>' + target + '</td>';
|
||||
} else {
|
||||
row += '<td>(hidden)</td>';
|
||||
}
|
||||
|
||||
return '<tr>' + row + '</tr>';
|
||||
}).join('') + '</table>';
|
||||
}
|
||||
});
|
||||
}());
|
||||
120
samples/area/line-boundaries.html
Normal file
@ -0,0 +1,120 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en-US">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=Edge">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>area > boundaries | Chart.js sample</title>
|
||||
<link rel="stylesheet" type="text/css" href="../style.css">
|
||||
<script src="../../dist/Chart.bundle.js"></script>
|
||||
<script src="../utils.js"></script>
|
||||
<script src="analyser.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<div class="content">
|
||||
<div class="wrapper col-2"><canvas id="chart-0"></canvas></div>
|
||||
<div class="wrapper col-2"><canvas id="chart-1"></canvas></div>
|
||||
<div class="wrapper col-2"><canvas id="chart-2"></canvas></div>
|
||||
<div class="wrapper col-2"><canvas id="chart-3"></canvas></div>
|
||||
|
||||
<div class="toolbar">
|
||||
<button onclick="toggleSmooth(this)">Smooth</button>
|
||||
<button onclick="randomize(this)">Randomize</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
var presets = window.chartColors;
|
||||
var utils = Samples.utils;
|
||||
var inputs = {
|
||||
min: -100,
|
||||
max: 100,
|
||||
count: 8,
|
||||
decimals: 2,
|
||||
continuity: 1
|
||||
};
|
||||
|
||||
function generateData(config) {
|
||||
return utils.numbers(utils.merge(inputs, config || {}));
|
||||
}
|
||||
|
||||
function generateLabels(config) {
|
||||
return utils.months(utils.merge({
|
||||
count: inputs.count,
|
||||
section: 3
|
||||
}, config || {}));
|
||||
}
|
||||
|
||||
var options = {
|
||||
maintainAspectRatio: false,
|
||||
spanGaps: false,
|
||||
elements: {
|
||||
line: {
|
||||
tension: 0.000001
|
||||
}
|
||||
},
|
||||
plugins: {
|
||||
filler: {
|
||||
propagate: false
|
||||
}
|
||||
},
|
||||
scales: {
|
||||
xAxes: [{
|
||||
ticks: {
|
||||
autoSkip: false,
|
||||
maxRotation: 0
|
||||
}
|
||||
}]
|
||||
}
|
||||
};
|
||||
|
||||
[false, 'origin', 'start', 'end'].forEach(function(boundary, index) {
|
||||
|
||||
// reset the random seed to generate the same data for all charts
|
||||
utils.srand(8);
|
||||
|
||||
new Chart('chart-' + index, {
|
||||
type: 'line',
|
||||
data: {
|
||||
labels: generateLabels(),
|
||||
datasets: [{
|
||||
backgroundColor: utils.transparentize(presets.red),
|
||||
borderColor: presets.red,
|
||||
data: generateData(),
|
||||
label: 'Dataset',
|
||||
fill: boundary
|
||||
}]
|
||||
},
|
||||
options: utils.merge(options, {
|
||||
title: {
|
||||
text: 'fill: ' + boundary,
|
||||
display: true
|
||||
}
|
||||
})
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
function toggleSmooth(btn) {
|
||||
var value = btn.classList.toggle('btn-on');
|
||||
Chart.helpers.each(Chart.instances, function(chart) {
|
||||
chart.options.elements.line.tension = value? 0.4 : 0.000001;
|
||||
chart.update();
|
||||
});
|
||||
}
|
||||
|
||||
function randomize() {
|
||||
var seed = utils.rand();
|
||||
Chart.helpers.each(Chart.instances, function(chart) {
|
||||
utils.srand(seed);
|
||||
|
||||
chart.data.datasets.forEach(function(dataset) {
|
||||
dataset.data = generateData();
|
||||
});
|
||||
|
||||
chart.update();
|
||||
});
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
157
samples/area/line-datasets.html
Normal file
@ -0,0 +1,157 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en-US">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=Edge">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>area > datasets | Chart.js sample</title>
|
||||
<link rel="stylesheet" type="text/css" href="../style.css">
|
||||
<script src="../../dist/Chart.bundle.js"></script>
|
||||
<script src="../utils.js"></script>
|
||||
<script src="analyser.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<div class="content">
|
||||
<div class="wrapper">
|
||||
<canvas id="chart-0"></canvas>
|
||||
</div>
|
||||
<div class="toolbar">
|
||||
<button onclick="togglePropagate(this)">Propagate</button>
|
||||
<button onclick="toggleSmooth(this)">Smooth</button>
|
||||
<button onclick="randomize(this)">Randomize</button>
|
||||
</div>
|
||||
<div id="chart-analyser" class="analyser"></div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
var presets = window.chartColors;
|
||||
var utils = Samples.utils;
|
||||
var inputs = {
|
||||
min: 20,
|
||||
max: 80,
|
||||
count: 8,
|
||||
decimals: 2,
|
||||
continuity: 1
|
||||
};
|
||||
|
||||
function generateData() {
|
||||
return utils.numbers(inputs);
|
||||
}
|
||||
|
||||
function generateLabels(config) {
|
||||
return utils.months({count: inputs.count});
|
||||
}
|
||||
|
||||
utils.srand(42);
|
||||
|
||||
var data = {
|
||||
labels: generateLabels(),
|
||||
datasets: [{
|
||||
backgroundColor: utils.transparentize(presets.red),
|
||||
borderColor: presets.red,
|
||||
data: generateData(),
|
||||
hidden: true,
|
||||
label: 'D0'
|
||||
}, {
|
||||
backgroundColor: utils.transparentize(presets.orange),
|
||||
borderColor: presets.orange,
|
||||
data: generateData(),
|
||||
label: 'D1',
|
||||
fill: '-1'
|
||||
}, {
|
||||
backgroundColor: utils.transparentize(presets.yellow),
|
||||
borderColor: presets.yellow,
|
||||
data: generateData(),
|
||||
hidden: true,
|
||||
label: 'D2',
|
||||
fill: 1
|
||||
}, {
|
||||
backgroundColor: utils.transparentize(presets.green),
|
||||
borderColor: presets.green,
|
||||
data: generateData(),
|
||||
label: 'D3',
|
||||
fill: '-1'
|
||||
}, {
|
||||
backgroundColor: utils.transparentize(presets.blue),
|
||||
borderColor: presets.blue,
|
||||
data: generateData(),
|
||||
label: 'D4',
|
||||
fill: '-1'
|
||||
}, {
|
||||
backgroundColor: utils.transparentize(presets.grey),
|
||||
borderColor: presets.grey,
|
||||
data: generateData(),
|
||||
label: 'D5',
|
||||
fill: '+2'
|
||||
}, {
|
||||
backgroundColor: utils.transparentize(presets.purple),
|
||||
borderColor: presets.purple,
|
||||
data: generateData(),
|
||||
label: 'D6',
|
||||
fill: false
|
||||
}, {
|
||||
backgroundColor: utils.transparentize(presets.red),
|
||||
borderColor: presets.red,
|
||||
data: generateData(),
|
||||
label: 'D7',
|
||||
fill: 8
|
||||
}, {
|
||||
backgroundColor: utils.transparentize(presets.orange),
|
||||
borderColor: presets.orange,
|
||||
data: generateData(),
|
||||
hidden: true,
|
||||
label: 'D8',
|
||||
fill: 'end'
|
||||
}]
|
||||
};
|
||||
|
||||
var options = {
|
||||
maintainAspectRatio: false,
|
||||
spanGaps: false,
|
||||
elements: {
|
||||
line: {
|
||||
tension: 0.000001
|
||||
}
|
||||
},
|
||||
scales: {
|
||||
yAxes: [{
|
||||
stacked: true
|
||||
}]
|
||||
},
|
||||
plugins: {
|
||||
filler: {
|
||||
propagate: false
|
||||
},
|
||||
samples_filler_analyser: {
|
||||
target: 'chart-analyser'
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
var chart = new Chart('chart-0', {
|
||||
type: 'line',
|
||||
data: data,
|
||||
options: options
|
||||
});
|
||||
|
||||
function togglePropagate(btn) {
|
||||
var value = btn.classList.toggle('btn-on');
|
||||
chart.options.plugins.filler.propagate = value;
|
||||
chart.update();
|
||||
}
|
||||
|
||||
function toggleSmooth(btn) {
|
||||
var value = btn.classList.toggle('btn-on');
|
||||
chart.options.elements.line.tension = value? 0.4 : 0.000001;
|
||||
chart.update();
|
||||
}
|
||||
|
||||
function randomize() {
|
||||
chart.data.datasets.forEach(function(dataset) {
|
||||
dataset.data = generateData();
|
||||
});
|
||||
chart.update();
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
136
samples/area/radar.html
Normal file
@ -0,0 +1,136 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en-US">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=Edge">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>area > radar | Chart.js sample</title>
|
||||
<link rel="stylesheet" type="text/css" href="../style.css">
|
||||
<script src="../../dist/Chart.bundle.js"></script>
|
||||
<script src="../utils.js"></script>
|
||||
<script src="analyser.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<div class="content">
|
||||
<div class="wrapper" style="max-width: 512px; margin: auto">
|
||||
<canvas id="chart-0"></canvas>
|
||||
</div>
|
||||
<div class="toolbar">
|
||||
<button onclick="togglePropagate(this)">Propagate</button>
|
||||
<button onclick="toggleSmooth(this)">Smooth</button>
|
||||
<button onclick="randomize(this)">Randomize</button>
|
||||
</div>
|
||||
<div id="chart-analyser" class="analyser"></div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
var presets = window.chartColors;
|
||||
var utils = Samples.utils;
|
||||
var inputs = {
|
||||
min: 8,
|
||||
max: 16,
|
||||
count: 8,
|
||||
decimals: 2,
|
||||
continuity: 1
|
||||
};
|
||||
|
||||
function generateData() {
|
||||
// radar chart doesn't support stacked values, let's do it manually
|
||||
var values = utils.numbers(inputs);
|
||||
inputs.from = values;
|
||||
return values;
|
||||
}
|
||||
|
||||
function generateLabels(config) {
|
||||
return utils.months({count: inputs.count});
|
||||
}
|
||||
|
||||
utils.srand(42);
|
||||
|
||||
var data = {
|
||||
labels: generateLabels(),
|
||||
datasets: [{
|
||||
backgroundColor: utils.transparentize(presets.red),
|
||||
borderColor: presets.red,
|
||||
data: generateData(),
|
||||
label: 'D0'
|
||||
}, {
|
||||
backgroundColor: utils.transparentize(presets.orange),
|
||||
borderColor: presets.orange,
|
||||
data: generateData(),
|
||||
hidden: true,
|
||||
label: 'D1',
|
||||
fill: '-1'
|
||||
}, {
|
||||
backgroundColor: utils.transparentize(presets.yellow),
|
||||
borderColor: presets.yellow,
|
||||
data: generateData(),
|
||||
label: 'D2',
|
||||
fill: 1
|
||||
}, {
|
||||
backgroundColor: utils.transparentize(presets.green),
|
||||
borderColor: presets.green,
|
||||
data: generateData(),
|
||||
label: 'D3',
|
||||
fill: false
|
||||
}, {
|
||||
backgroundColor: utils.transparentize(presets.blue),
|
||||
borderColor: presets.blue,
|
||||
data: generateData(),
|
||||
label: 'D4',
|
||||
fill: '-1'
|
||||
}, {
|
||||
backgroundColor: utils.transparentize(presets.purple),
|
||||
borderColor: presets.purple,
|
||||
data: generateData(),
|
||||
label: 'D5',
|
||||
fill: '-1'
|
||||
}]
|
||||
};
|
||||
|
||||
var options = {
|
||||
maintainAspectRatio: true,
|
||||
spanGaps: false,
|
||||
elements: {
|
||||
line: {
|
||||
tension: 0.000001
|
||||
}
|
||||
},
|
||||
plugins: {
|
||||
filler: {
|
||||
propagate: false
|
||||
},
|
||||
samples_filler_analyser: {
|
||||
target: 'chart-analyser'
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
var chart = new Chart('chart-0', {
|
||||
type: 'radar',
|
||||
data: data,
|
||||
options: options
|
||||
});
|
||||
|
||||
function togglePropagate(btn) {
|
||||
var value = btn.classList.toggle('btn-on');
|
||||
chart.options.plugins.filler.propagate = value;
|
||||
chart.update();
|
||||
}
|
||||
|
||||
function toggleSmooth(btn) {
|
||||
var value = btn.classList.toggle('btn-on');
|
||||
chart.options.elements.line.tension = value? 0.4 : 0.000001;
|
||||
chart.update();
|
||||
}
|
||||
|
||||
function randomize() {
|
||||
inputs.from = [];
|
||||
chart.data.datasets.forEach(function(dataset) {
|
||||
dataset.data = generateData();
|
||||
});
|
||||
chart.update();
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
64
samples/style.css
Normal file
@ -0,0 +1,64 @@
|
||||
body, html {
|
||||
font-family: sans-serif;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.content {
|
||||
max-width: 800px;
|
||||
margin: auto;
|
||||
padding: 16px;
|
||||
}
|
||||
|
||||
.wrapper {
|
||||
min-height: 400px;
|
||||
padding: 16px 0;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.wrapper.col-2 {
|
||||
display: inline-block;
|
||||
min-height: 256px;
|
||||
width: 49%;
|
||||
}
|
||||
|
||||
@media (max-width: 400px) {
|
||||
.wrapper.col-2 {
|
||||
width: 100%
|
||||
}
|
||||
}
|
||||
|
||||
.wrapper canvas {
|
||||
-moz-user-select: none;
|
||||
-webkit-user-select: none;
|
||||
-ms-user-select: none;
|
||||
}
|
||||
|
||||
.toolbar {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.toolbar > * {
|
||||
margin: 0 8px 0 0;
|
||||
}
|
||||
|
||||
.btn-on {
|
||||
border-style: inset;
|
||||
}
|
||||
|
||||
.analyser table {
|
||||
color: #333;
|
||||
font-size: 0.9rem;
|
||||
margin: 8px 0;
|
||||
width: 100%
|
||||
}
|
||||
|
||||
.analyser th {
|
||||
background-color: #f0f0f0;
|
||||
padding: 2px;
|
||||
}
|
||||
|
||||
.analyser td {
|
||||
padding: 2px;
|
||||
text-align: center;
|
||||
}
|
||||
110
samples/utils.js
@ -1,3 +1,7 @@
|
||||
/* global Chart */
|
||||
|
||||
'use strict';
|
||||
|
||||
window.chartColors = {
|
||||
red: 'rgb(255, 99, 132)',
|
||||
orange: 'rgb(255, 159, 64)',
|
||||
@ -5,9 +9,111 @@ window.chartColors = {
|
||||
green: 'rgb(75, 192, 192)',
|
||||
blue: 'rgb(54, 162, 235)',
|
||||
purple: 'rgb(153, 102, 255)',
|
||||
grey: 'rgb(231,233,237)'
|
||||
grey: 'rgb(201, 203, 207)'
|
||||
};
|
||||
|
||||
window.randomScalingFactor = function() {
|
||||
return (Math.random() > 0.5 ? 1.0 : -1.0) * Math.round(Math.random() * 100);
|
||||
}
|
||||
};
|
||||
|
||||
(function(global) {
|
||||
var Months = [
|
||||
'January',
|
||||
'February',
|
||||
'March',
|
||||
'April',
|
||||
'May',
|
||||
'June',
|
||||
'July',
|
||||
'August',
|
||||
'September',
|
||||
'October',
|
||||
'November',
|
||||
'December'
|
||||
];
|
||||
|
||||
var Samples = global.Samples || (global.Samples = {});
|
||||
Samples.utils = {
|
||||
// Adapted from http://indiegamr.com/generate-repeatable-random-numbers-in-js/
|
||||
srand: function(seed) {
|
||||
this._seed = seed;
|
||||
},
|
||||
|
||||
rand: function(min, max) {
|
||||
var seed = this._seed;
|
||||
min = min === undefined? 0 : min;
|
||||
max = max === undefined? 1 : max;
|
||||
this._seed = (seed * 9301 + 49297) % 233280;
|
||||
return min + (this._seed / 233280) * (max - min);
|
||||
},
|
||||
|
||||
numbers: function(config) {
|
||||
var cfg = config || {};
|
||||
var min = cfg.min || 0;
|
||||
var max = cfg.max || 1;
|
||||
var from = cfg.from || [];
|
||||
var count = cfg.count || 8;
|
||||
var decimals = cfg.decimals || 8;
|
||||
var continuity = cfg.continuity || 1;
|
||||
var dfactor = Math.pow(10, decimals) || 0;
|
||||
var data = [];
|
||||
var i, value;
|
||||
|
||||
for (i=0; i<count; ++i) {
|
||||
value = (from[i] || 0) + this.rand(min, max);
|
||||
if (this.rand() <= continuity) {
|
||||
data.push(Math.round(dfactor * value) / dfactor);
|
||||
} else {
|
||||
data.push(null);
|
||||
}
|
||||
}
|
||||
|
||||
return data;
|
||||
},
|
||||
|
||||
labels: function(config) {
|
||||
var cfg = config || {};
|
||||
var min = cfg.min || 0;
|
||||
var max = cfg.max || 100;
|
||||
var count = cfg.count || 8;
|
||||
var step = (max-min) / count;
|
||||
var decimals = cfg.decimals || 8;
|
||||
var dfactor = Math.pow(10, decimals) || 0;
|
||||
var prefix = cfg.prefix || '';
|
||||
var values = [];
|
||||
var i;
|
||||
|
||||
for (i=min; i<max; i+=step) {
|
||||
values.push(prefix + Math.round(dfactor * i) / dfactor);
|
||||
}
|
||||
|
||||
return values;
|
||||
},
|
||||
|
||||
months: function(config) {
|
||||
var cfg = config || {};
|
||||
var count = cfg.count || 12;
|
||||
var section = cfg.section;
|
||||
var values = [];
|
||||
var i, value;
|
||||
|
||||
for (i=0; i<count; ++i) {
|
||||
value = Months[Math.ceil(i)%12];
|
||||
values.push(value.substring(0, section));
|
||||
}
|
||||
|
||||
return values;
|
||||
},
|
||||
|
||||
transparentize: function(color, opacity) {
|
||||
var alpha = opacity === undefined? 0.5 : 1 - opacity;
|
||||
return Chart.helpers.color(color).alpha(alpha).rgbString();
|
||||
},
|
||||
|
||||
merge: Chart.helpers.configMerge
|
||||
};
|
||||
|
||||
Samples.utils.srand(Date.now());
|
||||
|
||||
}(this));
|
||||
|
||||
|
||||
@ -49,4 +49,11 @@ require('./charts/Chart.PolarArea')(Chart);
|
||||
require('./charts/Chart.Radar')(Chart);
|
||||
require('./charts/Chart.Scatter')(Chart);
|
||||
|
||||
// Loading built-it plugins
|
||||
var plugins = [];
|
||||
|
||||
plugins.push(require('./plugins/plugin.filler.js')(Chart));
|
||||
|
||||
Chart.plugins.register(plugins);
|
||||
|
||||
window.Chart = module.exports = Chart;
|
||||
|
||||
@ -78,10 +78,6 @@ module.exports = function(Chart) {
|
||||
fill: custom.fill ? custom.fill : (dataset.fill !== undefined ? dataset.fill : lineElementOptions.fill),
|
||||
steppedLine: custom.steppedLine ? custom.steppedLine : helpers.getValueOrDefault(dataset.steppedLine, lineElementOptions.stepped),
|
||||
cubicInterpolationMode: custom.cubicInterpolationMode ? custom.cubicInterpolationMode : helpers.getValueOrDefault(dataset.cubicInterpolationMode, lineElementOptions.cubicInterpolationMode),
|
||||
// Scale
|
||||
scaleTop: scale.top,
|
||||
scaleBottom: scale.bottom,
|
||||
scaleZero: scale.getBasePixel()
|
||||
};
|
||||
|
||||
line.pivot();
|
||||
|
||||
@ -42,6 +42,7 @@ module.exports = function(Chart) {
|
||||
helpers.extend(meta.dataset, {
|
||||
// Utility
|
||||
_datasetIndex: me.index,
|
||||
_scale: scale,
|
||||
// Data
|
||||
_children: points,
|
||||
_loop: true,
|
||||
@ -57,11 +58,6 @@ module.exports = function(Chart) {
|
||||
borderDash: custom.borderDash ? custom.borderDash : (dataset.borderDash || lineElementOptions.borderDash),
|
||||
borderDashOffset: custom.borderDashOffset ? custom.borderDashOffset : (dataset.borderDashOffset || lineElementOptions.borderDashOffset),
|
||||
borderJoinStyle: custom.borderJoinStyle ? custom.borderJoinStyle : (dataset.borderJoinStyle || lineElementOptions.borderJoinStyle),
|
||||
|
||||
// Scale
|
||||
scaleTop: scale.top,
|
||||
scaleBottom: scale.bottom,
|
||||
scaleZero: scale.getBasePosition()
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
@ -121,4 +121,26 @@ module.exports = function(Chart) {
|
||||
ctx.restore();
|
||||
};
|
||||
|
||||
helpers.lineTo = function(ctx, previous, target, flip) {
|
||||
if (target.steppedLine) {
|
||||
ctx.lineTo(target.x, previous.y);
|
||||
ctx.lineTo(target.x, target.y);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!target.tension) {
|
||||
ctx.lineTo(target.x, target.y);
|
||||
return;
|
||||
}
|
||||
|
||||
ctx.bezierCurveTo(
|
||||
flip? previous.controlPointPreviousX : previous.controlPointNextX,
|
||||
flip? previous.controlPointPreviousY : previous.controlPointNextY,
|
||||
flip? target.controlPointNextX : target.controlPointPreviousX,
|
||||
flip? target.controlPointNextY : target.controlPointPreviousY,
|
||||
target.x,
|
||||
target.y);
|
||||
};
|
||||
|
||||
Chart.helpers.canvas = helpers;
|
||||
};
|
||||
|
||||
@ -22,118 +22,21 @@ module.exports = function(Chart) {
|
||||
draw: function() {
|
||||
var me = this;
|
||||
var vm = me._view;
|
||||
var spanGaps = vm.spanGaps;
|
||||
var fillPoint = vm.scaleZero;
|
||||
var loop = me._loop;
|
||||
|
||||
// Handle different fill modes for cartesian lines
|
||||
if (!loop) {
|
||||
if (vm.fill === 'top') {
|
||||
fillPoint = vm.scaleTop;
|
||||
} else if (vm.fill === 'bottom') {
|
||||
fillPoint = vm.scaleBottom;
|
||||
}
|
||||
}
|
||||
|
||||
var ctx = me._chart.ctx;
|
||||
ctx.save();
|
||||
|
||||
// Helper function to draw a line to a point
|
||||
function lineToPoint(previousPoint, point) {
|
||||
var pointVM = point._view;
|
||||
if (point._view.steppedLine === true) {
|
||||
ctx.lineTo(pointVM.x, previousPoint._view.y);
|
||||
ctx.lineTo(pointVM.x, pointVM.y);
|
||||
} else if (point._view.tension === 0) {
|
||||
ctx.lineTo(pointVM.x, pointVM.y);
|
||||
} else {
|
||||
ctx.bezierCurveTo(
|
||||
previousPoint._view.controlPointNextX,
|
||||
previousPoint._view.controlPointNextY,
|
||||
pointVM.controlPointPreviousX,
|
||||
pointVM.controlPointPreviousY,
|
||||
pointVM.x,
|
||||
pointVM.y
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
var spanGaps = vm.spanGaps;
|
||||
var points = me._children.slice(); // clone array
|
||||
var globalOptionLineElements = globalDefaults.elements.line;
|
||||
var lastDrawnIndex = -1;
|
||||
var index, current, previous, currentVM;
|
||||
|
||||
// If we are looping, adding the first point again
|
||||
if (loop && points.length) {
|
||||
if (me._loop && points.length) {
|
||||
points.push(points[0]);
|
||||
}
|
||||
|
||||
var index, current, previous, currentVM;
|
||||
|
||||
// Fill Line
|
||||
if (points.length && vm.fill) {
|
||||
ctx.beginPath();
|
||||
|
||||
for (index = 0; index < points.length; ++index) {
|
||||
current = points[index];
|
||||
previous = helpers.previousItem(points, index);
|
||||
currentVM = current._view;
|
||||
|
||||
// First point moves to it's starting position no matter what
|
||||
if (index === 0) {
|
||||
if (loop) {
|
||||
ctx.moveTo(fillPoint.x, fillPoint.y);
|
||||
} else {
|
||||
ctx.moveTo(currentVM.x, fillPoint);
|
||||
}
|
||||
|
||||
if (!currentVM.skip) {
|
||||
lastDrawnIndex = index;
|
||||
ctx.lineTo(currentVM.x, currentVM.y);
|
||||
}
|
||||
} else {
|
||||
previous = lastDrawnIndex === -1 ? previous : points[lastDrawnIndex];
|
||||
|
||||
if (currentVM.skip) {
|
||||
// Only do this if this is the first point that is skipped
|
||||
if (!spanGaps && lastDrawnIndex === (index - 1)) {
|
||||
if (loop) {
|
||||
ctx.lineTo(fillPoint.x, fillPoint.y);
|
||||
} else {
|
||||
ctx.lineTo(previous._view.x, fillPoint);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (lastDrawnIndex !== (index - 1)) {
|
||||
// There was a gap and this is the first point after the gap. If we've never drawn a point, this is a special case.
|
||||
// If the first data point is NaN, then there is no real gap to skip
|
||||
if (spanGaps && lastDrawnIndex !== -1) {
|
||||
// We are spanning the gap, so simple draw a line to this point
|
||||
lineToPoint(previous, current);
|
||||
} else if (loop) {
|
||||
ctx.lineTo(currentVM.x, currentVM.y);
|
||||
} else {
|
||||
ctx.lineTo(currentVM.x, fillPoint);
|
||||
ctx.lineTo(currentVM.x, currentVM.y);
|
||||
}
|
||||
} else {
|
||||
// Line to next point
|
||||
lineToPoint(previous, current);
|
||||
}
|
||||
lastDrawnIndex = index;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!loop && lastDrawnIndex !== -1) {
|
||||
ctx.lineTo(points[lastDrawnIndex]._view.x, fillPoint);
|
||||
}
|
||||
|
||||
ctx.fillStyle = vm.backgroundColor || globalDefaults.defaultColor;
|
||||
ctx.closePath();
|
||||
ctx.fill();
|
||||
}
|
||||
ctx.save();
|
||||
|
||||
// Stroke Line Options
|
||||
var globalOptionLineElements = globalDefaults.elements.line;
|
||||
ctx.lineCap = vm.borderCapStyle || globalOptionLineElements.borderCapStyle;
|
||||
|
||||
// IE 9 and 10 do not support line dash
|
||||
@ -170,7 +73,7 @@ module.exports = function(Chart) {
|
||||
ctx.moveTo(currentVM.x, currentVM.y);
|
||||
} else {
|
||||
// Line to next point
|
||||
lineToPoint(previous, current);
|
||||
helpers.canvas.lineTo(ctx, previous._view, current._view);
|
||||
}
|
||||
lastDrawnIndex = index;
|
||||
}
|
||||
|
||||
309
src/plugins/plugin.filler.js
Normal file
@ -0,0 +1,309 @@
|
||||
'use strict';
|
||||
|
||||
module.exports = function(Chart) {
|
||||
/**
|
||||
* Plugin based on discussion from the following Chart.js issues:
|
||||
* @see https://github.com/chartjs/Chart.js/issues/2380#issuecomment-279961569
|
||||
* @see https://github.com/chartjs/Chart.js/issues/2440#issuecomment-256461897
|
||||
*/
|
||||
Chart.defaults.global.plugins.filler = {
|
||||
propagate: true
|
||||
};
|
||||
|
||||
var defaults = Chart.defaults;
|
||||
var helpers = Chart.helpers;
|
||||
var mappers = {
|
||||
dataset: function(source) {
|
||||
var index = source.fill;
|
||||
var chart = source.chart;
|
||||
var meta = chart.getDatasetMeta(index);
|
||||
var visible = meta && chart.isDatasetVisible(index);
|
||||
var points = (visible && meta.dataset._children) || [];
|
||||
|
||||
return !points.length? null : function(point, i) {
|
||||
return points[i]._view || null;
|
||||
};
|
||||
},
|
||||
|
||||
boundary: function(source) {
|
||||
var boundary = source.boundary;
|
||||
var x = boundary? boundary.x : null;
|
||||
var y = boundary? boundary.y : null;
|
||||
|
||||
return function(point) {
|
||||
return {
|
||||
x: x === null? point.x : x,
|
||||
y: y === null? point.y : y,
|
||||
};
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
// @todo if (fill[0] === '#')
|
||||
function decodeFill(el, index, count) {
|
||||
var model = el._model || {};
|
||||
var fill = model.fill;
|
||||
var target;
|
||||
|
||||
if (fill === undefined) {
|
||||
fill = !!model.backgroundColor;
|
||||
}
|
||||
|
||||
if (fill === false || fill === null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (fill === true) {
|
||||
return 'origin';
|
||||
}
|
||||
|
||||
target = parseFloat(fill, 10);
|
||||
if (isFinite(target) && Math.floor(target) === target) {
|
||||
if (fill[0] === '-' || fill[0] === '+') {
|
||||
target = index + target;
|
||||
}
|
||||
|
||||
if (target === index || target < 0 || target >= count) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return target;
|
||||
}
|
||||
|
||||
switch (fill) {
|
||||
// compatibility
|
||||
case 'bottom':
|
||||
return 'start';
|
||||
case 'top':
|
||||
return 'end';
|
||||
case 'zero':
|
||||
return 'origin';
|
||||
// supported boundaries
|
||||
case 'origin':
|
||||
case 'start':
|
||||
case 'end':
|
||||
return fill;
|
||||
// invalid fill values
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
function computeBoundary(source) {
|
||||
var model = source.el._model || {};
|
||||
var scale = source.el._scale || {};
|
||||
var fill = source.fill;
|
||||
var target = null;
|
||||
var horizontal;
|
||||
|
||||
if (isFinite(fill)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Backward compatibility: until v3, we still need to support boundary values set on
|
||||
// the model (scaleTop, scaleBottom and scaleZero) because some external plugins and
|
||||
// controllers might still use it (e.g. the Smith chart).
|
||||
|
||||
if (fill === 'start') {
|
||||
target = model.scaleBottom === undefined? scale.bottom : model.scaleBottom;
|
||||
} else if (fill === 'end') {
|
||||
target = model.scaleTop === undefined? scale.top : model.scaleTop;
|
||||
} else if (model.scaleZero !== undefined) {
|
||||
target = model.scaleZero;
|
||||
} else if (scale.getBasePosition) {
|
||||
target = scale.getBasePosition();
|
||||
} else if (scale.getBasePixel) {
|
||||
target = scale.getBasePixel();
|
||||
}
|
||||
|
||||
if (target !== undefined && target !== null) {
|
||||
if (target.x !== undefined && target.y !== undefined) {
|
||||
return target;
|
||||
}
|
||||
|
||||
if (typeof target === 'number' && isFinite(target)) {
|
||||
horizontal = scale.isHorizontal();
|
||||
return {
|
||||
x: horizontal? target : null,
|
||||
y: horizontal? null : target
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
function resolveTarget(sources, index, propagate) {
|
||||
var source = sources[index];
|
||||
var fill = source.fill;
|
||||
var visited = [index];
|
||||
var target;
|
||||
|
||||
if (!propagate) {
|
||||
return fill;
|
||||
}
|
||||
|
||||
while (fill !== false && visited.indexOf(fill) === -1) {
|
||||
if (!isFinite(fill)) {
|
||||
return fill;
|
||||
}
|
||||
|
||||
target = sources[fill];
|
||||
if (!target) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (target.visible) {
|
||||
return fill;
|
||||
}
|
||||
|
||||
visited.push(fill);
|
||||
fill = target.fill;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
function createMapper(source) {
|
||||
var fill = source.fill;
|
||||
var type = 'dataset';
|
||||
|
||||
if (fill === false) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!isFinite(fill)) {
|
||||
type = 'boundary';
|
||||
}
|
||||
|
||||
return mappers[type](source);
|
||||
}
|
||||
|
||||
function isDrawable(point) {
|
||||
return point && !point.skip;
|
||||
}
|
||||
|
||||
function drawArea(ctx, curve0, curve1, len0, len1) {
|
||||
var i;
|
||||
|
||||
if (!len0 || !len1) {
|
||||
return;
|
||||
}
|
||||
|
||||
// building first area curve (normal)
|
||||
ctx.moveTo(curve0[0].x, curve0[0].y);
|
||||
for (i=1; i<len0; ++i) {
|
||||
helpers.canvas.lineTo(ctx, curve0[i-1], curve0[i]);
|
||||
}
|
||||
|
||||
// joining the two area curves
|
||||
ctx.lineTo(curve1[len1-1].x, curve1[len1-1].y);
|
||||
|
||||
// building opposite area curve (reverse)
|
||||
for (i=len1-1; i>0; --i) {
|
||||
helpers.canvas.lineTo(ctx, curve1[i], curve1[i-1], true);
|
||||
}
|
||||
}
|
||||
|
||||
function doFill(ctx, points, mapper, view, color, loop) {
|
||||
var count = points.length;
|
||||
var span = view.spanGaps;
|
||||
var curve0 = [];
|
||||
var curve1 = [];
|
||||
var len0 = 0;
|
||||
var len1 = 0;
|
||||
var i, ilen, index, p0, p1, d0, d1;
|
||||
|
||||
ctx.beginPath();
|
||||
|
||||
for (i = 0, ilen = (count + !!loop); i < ilen; ++i) {
|
||||
index = i%count;
|
||||
p0 = points[index]._view;
|
||||
p1 = mapper(p0, index, view);
|
||||
d0 = isDrawable(p0);
|
||||
d1 = isDrawable(p1);
|
||||
|
||||
if (d0 && d1) {
|
||||
len0 = curve0.push(p0);
|
||||
len1 = curve1.push(p1);
|
||||
} else if (len0 && len1) {
|
||||
if (!span) {
|
||||
drawArea(ctx, curve0, curve1, len0, len1);
|
||||
len0 = len1 = 0;
|
||||
curve0 = [];
|
||||
curve1 = [];
|
||||
} else {
|
||||
if (d0) {
|
||||
curve0.push(p0);
|
||||
}
|
||||
if (d1) {
|
||||
curve1.push(p1);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
drawArea(ctx, curve0, curve1, len0, len1);
|
||||
|
||||
ctx.closePath();
|
||||
ctx.fillStyle = color;
|
||||
ctx.fill();
|
||||
}
|
||||
|
||||
return {
|
||||
id: 'filler',
|
||||
|
||||
afterDatasetsUpdate: function(chart, options) {
|
||||
var count = (chart.data.datasets || []).length;
|
||||
var propagate = options.propagate;
|
||||
var sources = [];
|
||||
var meta, i, el, source;
|
||||
|
||||
for (i = 0; i < count; ++i) {
|
||||
meta = chart.getDatasetMeta(i);
|
||||
el = meta.dataset;
|
||||
source = null;
|
||||
|
||||
if (el && el._model && el instanceof Chart.elements.Line) {
|
||||
source = {
|
||||
visible: chart.isDatasetVisible(i),
|
||||
fill: decodeFill(el, i, count),
|
||||
chart: chart,
|
||||
el: el
|
||||
};
|
||||
}
|
||||
|
||||
meta.$filler = source;
|
||||
sources.push(source);
|
||||
}
|
||||
|
||||
for (i=0; i<count; ++i) {
|
||||
source = sources[i];
|
||||
if (!source) {
|
||||
continue;
|
||||
}
|
||||
|
||||
source.fill = resolveTarget(sources, i, propagate);
|
||||
source.boundary = computeBoundary(source);
|
||||
source.mapper = createMapper(source);
|
||||
}
|
||||
},
|
||||
|
||||
beforeDatasetDraw: function(chart, args) {
|
||||
var meta = args.meta.$filler;
|
||||
if (!meta) {
|
||||
return;
|
||||
}
|
||||
|
||||
var el = meta.el;
|
||||
var view = el._view;
|
||||
var points = el._children || [];
|
||||
var mapper = meta.mapper;
|
||||
var color = view.backgroundColor || defaults.global.defaultColor;
|
||||
|
||||
if (mapper && color && points.length) {
|
||||
doFill(chart.ctx, points, mapper, view, color, el._loop);
|
||||
}
|
||||
}
|
||||
};
|
||||
};
|
||||
@ -40,7 +40,7 @@
|
||||
},
|
||||
"line": {
|
||||
"borderColor": "transparent",
|
||||
"fill": "top",
|
||||
"fill": "end",
|
||||
"tension": 0
|
||||
}
|
||||
}
|
||||
|
Before Width: | Height: | Size: 16 KiB After Width: | Height: | Size: 16 KiB |
@ -40,7 +40,7 @@
|
||||
},
|
||||
"line": {
|
||||
"borderColor": "transparent",
|
||||
"fill": "top",
|
||||
"fill": "end",
|
||||
"tension": 0
|
||||
}
|
||||
}
|
||||
|
Before Width: | Height: | Size: 15 KiB After Width: | Height: | Size: 15 KiB |
@ -40,7 +40,7 @@
|
||||
},
|
||||
"line": {
|
||||
"borderColor": "transparent",
|
||||
"fill": "zero",
|
||||
"fill": "origin",
|
||||
"tension": 0
|
||||
}
|
||||
}
|
||||
|
Before Width: | Height: | Size: 17 KiB After Width: | Height: | Size: 17 KiB |
@ -41,7 +41,7 @@
|
||||
"line": {
|
||||
"cubicInterpolationMode": "monotone",
|
||||
"borderColor": "transparent",
|
||||
"fill": "zero"
|
||||
"fill": "origin"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Before Width: | Height: | Size: 20 KiB After Width: | Height: | Size: 20 KiB |
@ -41,7 +41,7 @@
|
||||
"line": {
|
||||
"cubicInterpolationMode": "monotone",
|
||||
"borderColor": "transparent",
|
||||
"fill": "zero"
|
||||
"fill": "origin"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Before Width: | Height: | Size: 19 KiB After Width: | Height: | Size: 19 KiB |
@ -42,7 +42,7 @@
|
||||
"cubicInterpolationMode": "monotone",
|
||||
"borderColor": "transparent",
|
||||
"stepped": true,
|
||||
"fill": "zero"
|
||||
"fill": "origin"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Before Width: | Height: | Size: 6.8 KiB After Width: | Height: | Size: 6.8 KiB |
@ -42,7 +42,7 @@
|
||||
"cubicInterpolationMode": "monotone",
|
||||
"borderColor": "transparent",
|
||||
"stepped": true,
|
||||
"fill": "zero"
|
||||
"fill": "origin"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Before Width: | Height: | Size: 6.7 KiB After Width: | Height: | Size: 6.7 KiB |
@ -40,7 +40,7 @@
|
||||
},
|
||||
"line": {
|
||||
"borderColor": "transparent",
|
||||
"fill": "zero",
|
||||
"fill": "origin",
|
||||
"tension": 0
|
||||
}
|
||||
}
|
||||
|
Before Width: | Height: | Size: 16 KiB After Width: | Height: | Size: 16 KiB |
@ -40,7 +40,7 @@
|
||||
},
|
||||
"line": {
|
||||
"borderColor": "transparent",
|
||||
"fill": "bottom",
|
||||
"fill": "start",
|
||||
"tension": 0
|
||||
}
|
||||
}
|
||||
|
Before Width: | Height: | Size: 17 KiB After Width: | Height: | Size: 17 KiB |
@ -40,7 +40,7 @@
|
||||
},
|
||||
"line": {
|
||||
"borderColor": "transparent",
|
||||
"fill": "bottom",
|
||||
"fill": "start",
|
||||
"tension": 0
|
||||
}
|
||||
}
|
||||
|
Before Width: | Height: | Size: 15 KiB After Width: | Height: | Size: 15 KiB |
62
test/fixtures/plugin.filler/fill-line-dataset-span.json
vendored
Normal file
@ -0,0 +1,62 @@
|
||||
{
|
||||
"config": {
|
||||
"type": "line",
|
||||
"data": {
|
||||
"labels": ["0", "1", "2", "3", "4", "5", "6", "7", "8"],
|
||||
"datasets": [{
|
||||
"backgroundColor": "rgba(255, 0, 0, 0.25)",
|
||||
"data": [null, null, 0, -1, 0, 1, 0, -1, 0],
|
||||
"fill": 1
|
||||
}, {
|
||||
"backgroundColor": "rgba(0, 255, 0, 0.25)",
|
||||
"data": [1, 0, null, 1, 0, null, -1, 0, 1],
|
||||
"fill": "+1"
|
||||
}, {
|
||||
"backgroundColor": "rgba(0, 0, 255, 0.25)",
|
||||
"data": [0, 2, 0, -2, 0, 2, 0, null, null],
|
||||
"fill": 3
|
||||
}, {
|
||||
"backgroundColor": "rgba(255, 0, 255, 0.25)",
|
||||
"data": [2, 0, -2, 0, 2, 0, -2, 0, 2],
|
||||
"fill": "-2"
|
||||
}, {
|
||||
"backgroundColor": "rgba(255, 255, 0, 0.25)",
|
||||
"data": [3, 1, -1, -3, -1, 1, 3, 1, -1],
|
||||
"fill": "-1"
|
||||
}]
|
||||
},
|
||||
"options": {
|
||||
"responsive": false,
|
||||
"spanGaps": true,
|
||||
"legend": false,
|
||||
"title": false,
|
||||
"scales": {
|
||||
"xAxes": [{
|
||||
"ticks": {
|
||||
"display": false
|
||||
}
|
||||
}],
|
||||
"yAxes": [{
|
||||
"ticks": {
|
||||
"display": false
|
||||
}
|
||||
}]
|
||||
},
|
||||
"elements": {
|
||||
"point": {
|
||||
"radius": 0
|
||||
},
|
||||
"line": {
|
||||
"borderColor": "transparent",
|
||||
"tension": 0
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"options": {
|
||||
"canvas": {
|
||||
"height": 256,
|
||||
"width": 512
|
||||
}
|
||||
}
|
||||
}
|
||||
BIN
test/fixtures/plugin.filler/fill-line-dataset-span.png
vendored
Normal file
|
After Width: | Height: | Size: 24 KiB |
62
test/fixtures/plugin.filler/fill-line-dataset-spline-span.json
vendored
Normal file
@ -0,0 +1,62 @@
|
||||
{
|
||||
"config": {
|
||||
"type": "line",
|
||||
"data": {
|
||||
"labels": ["0", "1", "2", "3", "4", "5", "6", "7", "8"],
|
||||
"datasets": [{
|
||||
"backgroundColor": "rgba(255, 0, 0, 0.25)",
|
||||
"data": [null, null, 0, -1, 0, 1, 0, -1, 0],
|
||||
"fill": 1
|
||||
}, {
|
||||
"backgroundColor": "rgba(0, 255, 0, 0.25)",
|
||||
"data": [1, 0, null, 1, 0, null, -1, 0, 1],
|
||||
"fill": "+1"
|
||||
}, {
|
||||
"backgroundColor": "rgba(0, 0, 255, 0.25)",
|
||||
"data": [0, 2, 0, -2, 0, 2, 0, null, null],
|
||||
"fill": 3
|
||||
}, {
|
||||
"backgroundColor": "rgba(255, 0, 255, 0.25)",
|
||||
"data": [2, 0, -2, 0, 2, 0, -2, 0, 2],
|
||||
"fill": "-2"
|
||||
}, {
|
||||
"backgroundColor": "rgba(255, 255, 0, 0.25)",
|
||||
"data": [3, 1, -1, -3, -1, 1, 3, 1, -1],
|
||||
"fill": "-1"
|
||||
}]
|
||||
},
|
||||
"options": {
|
||||
"responsive": false,
|
||||
"spanGaps": true,
|
||||
"legend": false,
|
||||
"title": false,
|
||||
"scales": {
|
||||
"xAxes": [{
|
||||
"ticks": {
|
||||
"display": false
|
||||
}
|
||||
}],
|
||||
"yAxes": [{
|
||||
"ticks": {
|
||||
"display": false
|
||||
}
|
||||
}]
|
||||
},
|
||||
"elements": {
|
||||
"point": {
|
||||
"radius": 0
|
||||
},
|
||||
"line": {
|
||||
"cubicInterpolationMode": "monotone",
|
||||
"borderColor": "transparent"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"options": {
|
||||
"canvas": {
|
||||
"height": 256,
|
||||
"width": 512
|
||||
}
|
||||
}
|
||||
}
|
||||
BIN
test/fixtures/plugin.filler/fill-line-dataset-spline-span.png
vendored
Normal file
|
After Width: | Height: | Size: 25 KiB |
62
test/fixtures/plugin.filler/fill-line-dataset-spline.json
vendored
Normal file
@ -0,0 +1,62 @@
|
||||
{
|
||||
"config": {
|
||||
"type": "line",
|
||||
"data": {
|
||||
"labels": ["0", "1", "2", "3", "4", "5", "6", "7", "8"],
|
||||
"datasets": [{
|
||||
"backgroundColor": "rgba(255, 0, 0, 0.25)",
|
||||
"data": [null, null, 0, -1, 0, 1, 0, -1, 0],
|
||||
"fill": 1
|
||||
}, {
|
||||
"backgroundColor": "rgba(0, 255, 0, 0.25)",
|
||||
"data": [1, 0, null, 1, 0, null, -1, 0, 1],
|
||||
"fill": "+1"
|
||||
}, {
|
||||
"backgroundColor": "rgba(0, 0, 255, 0.25)",
|
||||
"data": [0, 2, 0, -2, 0, 2, 0, null, null],
|
||||
"fill": 3
|
||||
}, {
|
||||
"backgroundColor": "rgba(255, 0, 255, 0.25)",
|
||||
"data": [2, 0, -2, 0, 2, 0, -2, 0, 2],
|
||||
"fill": "-2"
|
||||
}, {
|
||||
"backgroundColor": "rgba(255, 255, 0, 0.25)",
|
||||
"data": [3, 1, -1, -3, -1, 1, 3, 1, -1],
|
||||
"fill": "-1"
|
||||
}]
|
||||
},
|
||||
"options": {
|
||||
"responsive": false,
|
||||
"spanGaps": false,
|
||||
"legend": false,
|
||||
"title": false,
|
||||
"scales": {
|
||||
"xAxes": [{
|
||||
"ticks": {
|
||||
"display": false
|
||||
}
|
||||
}],
|
||||
"yAxes": [{
|
||||
"ticks": {
|
||||
"display": false
|
||||
}
|
||||
}]
|
||||
},
|
||||
"elements": {
|
||||
"point": {
|
||||
"radius": 0
|
||||
},
|
||||
"line": {
|
||||
"cubicInterpolationMode": "monotone",
|
||||
"borderColor": "transparent"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"options": {
|
||||
"canvas": {
|
||||
"height": 256,
|
||||
"width": 512
|
||||
}
|
||||
}
|
||||
}
|
||||
BIN
test/fixtures/plugin.filler/fill-line-dataset-spline.png
vendored
Normal file
|
After Width: | Height: | Size: 23 KiB |
62
test/fixtures/plugin.filler/fill-line-dataset.json
vendored
Normal file
@ -0,0 +1,62 @@
|
||||
{
|
||||
"config": {
|
||||
"type": "line",
|
||||
"data": {
|
||||
"labels": ["0", "1", "2", "3", "4", "5", "6", "7", "8"],
|
||||
"datasets": [{
|
||||
"backgroundColor": "rgba(255, 0, 0, 0.25)",
|
||||
"data": [null, null, 0, -1, 0, 1, 0, -1, 0],
|
||||
"fill": 1
|
||||
}, {
|
||||
"backgroundColor": "rgba(0, 255, 0, 0.25)",
|
||||
"data": [1, 0, null, 1, 0, null, -1, 0, 1],
|
||||
"fill": "+1"
|
||||
}, {
|
||||
"backgroundColor": "rgba(0, 0, 255, 0.25)",
|
||||
"data": [0, 2, 0, -2, 0, 2, 0, null, null],
|
||||
"fill": 3
|
||||
}, {
|
||||
"backgroundColor": "rgba(255, 0, 255, 0.25)",
|
||||
"data": [2, 0, -2, 0, 2, 0, -2, 0, 2],
|
||||
"fill": "-2"
|
||||
}, {
|
||||
"backgroundColor": "rgba(255, 255, 0, 0.25)",
|
||||
"data": [3, 1, -1, -3, -1, 1, 3, 1, -1],
|
||||
"fill": "-1"
|
||||
}]
|
||||
},
|
||||
"options": {
|
||||
"responsive": false,
|
||||
"spanGaps": false,
|
||||
"legend": false,
|
||||
"title": false,
|
||||
"scales": {
|
||||
"xAxes": [{
|
||||
"ticks": {
|
||||
"display": false
|
||||
}
|
||||
}],
|
||||
"yAxes": [{
|
||||
"ticks": {
|
||||
"display": false
|
||||
}
|
||||
}]
|
||||
},
|
||||
"elements": {
|
||||
"point": {
|
||||
"radius": 0
|
||||
},
|
||||
"line": {
|
||||
"borderColor": "transparent",
|
||||
"tension": 0
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"options": {
|
||||
"canvas": {
|
||||
"height": 256,
|
||||
"width": 512
|
||||
}
|
||||
}
|
||||
}
|
||||
BIN
test/fixtures/plugin.filler/fill-line-dataset.png
vendored
Normal file
|
After Width: | Height: | Size: 22 KiB |
@ -36,7 +36,7 @@
|
||||
"line": {
|
||||
"borderColor": "transparent",
|
||||
"tension": 0.5,
|
||||
"fill": "zero"
|
||||
"fill": "origin"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Before Width: | Height: | Size: 25 KiB After Width: | Height: | Size: 25 KiB |
@ -35,7 +35,7 @@
|
||||
},
|
||||
"line": {
|
||||
"borderColor": "transparent",
|
||||
"fill": "zero"
|
||||
"fill": "origin"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Before Width: | Height: | Size: 22 KiB After Width: | Height: | Size: 22 KiB |
@ -509,48 +509,6 @@ describe('Line controller tests', function() {
|
||||
|
||||
});
|
||||
|
||||
it('should find the correct scale zero when the data is all positive', function() {
|
||||
var chart = window.acquireChart({
|
||||
type: 'line',
|
||||
data: {
|
||||
datasets: [{
|
||||
data: [10, 15, 20, 20],
|
||||
label: 'dataset1',
|
||||
}],
|
||||
labels: ['label1', 'label2', 'label3', 'label4']
|
||||
},
|
||||
});
|
||||
|
||||
var meta = chart.getDatasetMeta(0);
|
||||
|
||||
expect(meta.dataset._model).toEqual(jasmine.objectContaining({
|
||||
scaleTop: 32,
|
||||
scaleBottom: 484,
|
||||
scaleZero: 484,
|
||||
}));
|
||||
});
|
||||
|
||||
it('should find the correct scale zero when the data is all negative', function() {
|
||||
var chart = window.acquireChart({
|
||||
type: 'line',
|
||||
data: {
|
||||
datasets: [{
|
||||
data: [-10, -15, -20, -20],
|
||||
label: 'dataset1',
|
||||
}],
|
||||
labels: ['label1', 'label2', 'label3', 'label4']
|
||||
},
|
||||
});
|
||||
|
||||
var meta = chart.getDatasetMeta(0);
|
||||
|
||||
expect(meta.dataset._model).toEqual(jasmine.objectContaining({
|
||||
scaleTop: 32,
|
||||
scaleBottom: 484,
|
||||
scaleZero: 32,
|
||||
}));
|
||||
});
|
||||
|
||||
it('should fall back to the line styles for points', function() {
|
||||
var chart = window.acquireChart({
|
||||
type: 'line',
|
||||
|
||||
@ -111,10 +111,6 @@ describe('Radar controller tests', function() {
|
||||
meta.controller.reset(); // reset first
|
||||
|
||||
// Line element
|
||||
expect(meta.dataset._model.scaleTop).toBeCloseToPixel(32);
|
||||
expect(meta.dataset._model.scaleBottom).toBeCloseToPixel(512);
|
||||
expect(meta.dataset._model.scaleZero.x).toBeCloseToPixel(256);
|
||||
expect(meta.dataset._model.scaleZero.y).toBeCloseToPixel(272);
|
||||
expect(meta.dataset._model).toEqual(jasmine.objectContaining({
|
||||
backgroundColor: 'rgb(255, 0, 0)',
|
||||
borderCapStyle: 'round',
|
||||
@ -198,10 +194,6 @@ describe('Radar controller tests', function() {
|
||||
|
||||
meta.controller.update();
|
||||
|
||||
expect(meta.dataset._model.scaleTop).toBeCloseToPixel(32);
|
||||
expect(meta.dataset._model.scaleBottom).toBeCloseToPixel(512);
|
||||
expect(meta.dataset._model.scaleZero.x).toBeCloseToPixel(256);
|
||||
expect(meta.dataset._model.scaleZero.y).toBeCloseToPixel(272);
|
||||
expect(meta.dataset._model).toEqual(jasmine.objectContaining({
|
||||
backgroundColor: 'rgb(98, 98, 98)',
|
||||
borderCapStyle: 'butt',
|
||||
@ -262,10 +254,6 @@ describe('Radar controller tests', function() {
|
||||
|
||||
meta.controller.update();
|
||||
|
||||
expect(meta.dataset._model.scaleTop).toBeCloseToPixel(32);
|
||||
expect(meta.dataset._model.scaleBottom).toBeCloseToPixel(512);
|
||||
expect(meta.dataset._model.scaleZero.x).toBeCloseToPixel(256);
|
||||
expect(meta.dataset._model.scaleZero.y).toBeCloseToPixel(272);
|
||||
expect(meta.dataset._model).toEqual(jasmine.objectContaining({
|
||||
backgroundColor: 'rgb(55, 55, 54)',
|
||||
borderCapStyle: 'square',
|
||||
|
||||
@ -86,6 +86,27 @@ describe('Deprecations', function() {
|
||||
}, 200);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Chart.elements.Line: fill option', function() {
|
||||
it('should decode "zero", "top" and "bottom" as "origin", "start" and "end"', function() {
|
||||
var chart = window.acquireChart({
|
||||
type: 'line',
|
||||
data: {
|
||||
datasets: [
|
||||
{fill: 'zero'},
|
||||
{fill: 'bottom'},
|
||||
{fill: 'top'},
|
||||
]
|
||||
}
|
||||
});
|
||||
|
||||
['origin', 'start', 'end'].forEach(function(expected, index) {
|
||||
var meta = chart.getDatasetMeta(index);
|
||||
expect(meta.$filler).toBeDefined();
|
||||
expect(meta.$filler.fill).toBe(expected);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('Version 2.5.0', function() {
|
||||
|
||||
265
test/specs/plugin.filler.tests.js
Normal file
@ -0,0 +1,265 @@
|
||||
describe('Plugin.filler', function() {
|
||||
function decodedFillValues(chart) {
|
||||
return chart.data.datasets.map(function(dataset, index) {
|
||||
var meta = chart.getDatasetMeta(index) || {};
|
||||
expect(meta.$filler).toBeDefined();
|
||||
return meta.$filler.fill;
|
||||
});
|
||||
}
|
||||
|
||||
describe('auto', jasmine.specsFromFixtures('plugin.filler'));
|
||||
|
||||
describe('dataset.fill', function() {
|
||||
it('should support boundaries', function() {
|
||||
var chart = window.acquireChart({
|
||||
type: 'line',
|
||||
data: {
|
||||
datasets: [
|
||||
{fill: 'origin'},
|
||||
{fill: 'start'},
|
||||
{fill: 'end'},
|
||||
]
|
||||
}
|
||||
});
|
||||
|
||||
expect(decodedFillValues(chart)).toEqual(['origin', 'start', 'end']);
|
||||
});
|
||||
|
||||
it('should support absolute dataset index', function() {
|
||||
var chart = window.acquireChart({
|
||||
type: 'line',
|
||||
data: {
|
||||
datasets: [
|
||||
{fill: 1},
|
||||
{fill: 3},
|
||||
{fill: 0},
|
||||
{fill: 2},
|
||||
]
|
||||
}
|
||||
});
|
||||
|
||||
expect(decodedFillValues(chart)).toEqual([1, 3, 0, 2]);
|
||||
});
|
||||
|
||||
it('should support relative dataset index', function() {
|
||||
var chart = window.acquireChart({
|
||||
type: 'line',
|
||||
data: {
|
||||
datasets: [
|
||||
{fill: '+3'},
|
||||
{fill: '-1'},
|
||||
{fill: '+1'},
|
||||
{fill: '-2'},
|
||||
]
|
||||
}
|
||||
});
|
||||
|
||||
expect(decodedFillValues(chart)).toEqual([
|
||||
3, // 0 + 3
|
||||
0, // 1 - 1
|
||||
3, // 2 + 1
|
||||
1, // 3 - 2
|
||||
]);
|
||||
});
|
||||
|
||||
it('should handle default fill when true (origin)', function() {
|
||||
var chart = window.acquireChart({
|
||||
type: 'line',
|
||||
data: {
|
||||
datasets: [
|
||||
{fill: true},
|
||||
{fill: false},
|
||||
]
|
||||
}
|
||||
});
|
||||
|
||||
expect(decodedFillValues(chart)).toEqual(['origin', false]);
|
||||
});
|
||||
|
||||
it('should ignore self dataset index', function() {
|
||||
var chart = window.acquireChart({
|
||||
type: 'line',
|
||||
data: {
|
||||
datasets: [
|
||||
{fill: 0},
|
||||
{fill: '-0'},
|
||||
{fill: '+0'},
|
||||
{fill: 3},
|
||||
]
|
||||
}
|
||||
});
|
||||
|
||||
expect(decodedFillValues(chart)).toEqual([
|
||||
false, // 0 === 0
|
||||
false, // 1 === 1 - 0
|
||||
false, // 2 === 2 + 0
|
||||
false, // 3 === 3
|
||||
]);
|
||||
});
|
||||
|
||||
it('should ignore out of bounds dataset index', function() {
|
||||
var chart = window.acquireChart({
|
||||
type: 'line',
|
||||
data: {
|
||||
datasets: [
|
||||
{fill: -2},
|
||||
{fill: 4},
|
||||
{fill: '-3'},
|
||||
{fill: '+1'},
|
||||
]
|
||||
}
|
||||
});
|
||||
|
||||
expect(decodedFillValues(chart)).toEqual([
|
||||
false, // 0 - 2 < 0
|
||||
false, // 1 + 4 > 3
|
||||
false, // 2 - 3 < 0
|
||||
false, // 3 + 1 > 3
|
||||
]);
|
||||
});
|
||||
|
||||
it('should ignore invalid values', function() {
|
||||
var chart = window.acquireChart({
|
||||
type: 'line',
|
||||
data: {
|
||||
datasets: [
|
||||
{fill: 'foo'},
|
||||
{fill: '+foo'},
|
||||
{fill: '-foo'},
|
||||
{fill: '+1.1'},
|
||||
{fill: '-2.2'},
|
||||
{fill: 3.3},
|
||||
{fill: -4.4},
|
||||
{fill: NaN},
|
||||
{fill: Infinity},
|
||||
{fill: ''},
|
||||
{fill: null},
|
||||
{fill: []},
|
||||
{fill: {}},
|
||||
{fill: function() {}}
|
||||
]
|
||||
}
|
||||
});
|
||||
|
||||
expect(decodedFillValues(chart)).toEqual([
|
||||
false, // NaN (string)
|
||||
false, // NaN (string)
|
||||
false, // NaN (string)
|
||||
false, // float (string)
|
||||
false, // float (string)
|
||||
false, // float (number)
|
||||
false, // float (number)
|
||||
false, // NaN
|
||||
false, // !isFinite
|
||||
false, // empty string
|
||||
false, // null
|
||||
false, // array
|
||||
false, // object
|
||||
false, // function
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('options.plugins.filler.propagate', function() {
|
||||
it('should compute propagated fill targets if true', function() {
|
||||
var chart = window.acquireChart({
|
||||
type: 'line',
|
||||
data: {
|
||||
datasets: [
|
||||
{fill: 'start', hidden: true},
|
||||
{fill: '-1', hidden: true},
|
||||
{fill: 1, hidden: true},
|
||||
{fill: '-2', hidden: true},
|
||||
{fill: '+1'},
|
||||
{fill: '+2'},
|
||||
{fill: '-1'},
|
||||
{fill: 'end', hidden: true},
|
||||
]
|
||||
},
|
||||
options: {
|
||||
plugins: {
|
||||
filler: {
|
||||
propagate: true
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
expect(decodedFillValues(chart)).toEqual([
|
||||
'start', // 'start'
|
||||
'start', // 1 - 1 -> 0 (hidden) -> 'start'
|
||||
'start', // 1 (hidden) -> 0 (hidden) -> 'start'
|
||||
'start', // 3 - 2 -> 1 (hidden) -> 0 (hidden) -> 'start'
|
||||
5, // 4 + 1
|
||||
'end', // 5 + 2 -> 7 (hidden) -> 'end'
|
||||
5, // 6 - 1 -> 5
|
||||
'end', // 'end'
|
||||
]);
|
||||
});
|
||||
|
||||
it('should preserve initial fill targets if false', function() {
|
||||
var chart = window.acquireChart({
|
||||
type: 'line',
|
||||
data: {
|
||||
datasets: [
|
||||
{fill: 'start', hidden: true},
|
||||
{fill: '-1', hidden: true},
|
||||
{fill: 1, hidden: true},
|
||||
{fill: '-2', hidden: true},
|
||||
{fill: '+1'},
|
||||
{fill: '+2'},
|
||||
{fill: '-1'},
|
||||
{fill: 'end', hidden: true},
|
||||
]
|
||||
},
|
||||
options: {
|
||||
plugins: {
|
||||
filler: {
|
||||
propagate: false
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
expect(decodedFillValues(chart)).toEqual([
|
||||
'start', // 'origin'
|
||||
0, // 1 - 1
|
||||
1, // 1
|
||||
1, // 3 - 2
|
||||
5, // 4 + 1
|
||||
7, // 5 + 2
|
||||
5, // 6 - 1
|
||||
'end', // 'end'
|
||||
]);
|
||||
});
|
||||
|
||||
it('should prevent recursive propagation', function() {
|
||||
var chart = window.acquireChart({
|
||||
type: 'line',
|
||||
data: {
|
||||
datasets: [
|
||||
{fill: '+2', hidden: true},
|
||||
{fill: '-1', hidden: true},
|
||||
{fill: '-1', hidden: true},
|
||||
{fill: '-2'}
|
||||
]
|
||||
},
|
||||
options: {
|
||||
plugins: {
|
||||
filler: {
|
||||
propagate: true
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
expect(decodedFillValues(chart)).toEqual([
|
||||
false, // 0 + 2 -> 2 (hidden) -> 1 (hidden) -> 0 (loop)
|
||||
false, // 1 - 1 -> 0 (hidden) -> 2 (hidden) -> 1 (loop)
|
||||
false, // 2 - 1 -> 1 (hidden) -> 0 (hidden) -> 2 (loop)
|
||||
false, // 3 - 2 -> 1 (hidden) -> 0 (hidden) -> 2 (hidden) -> 1 (loop)
|
||||
]);
|
||||
});
|
||||
});
|
||||
});
|
||||