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.
--- /dev/null
+/* 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>';
+ }
+ });
+}());
--- /dev/null
+<!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>
--- /dev/null
+<!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>
--- /dev/null
+<!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>
--- /dev/null
+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;
+}
+/* global Chart */
+
+'use strict';
+
window.chartColors = {
red: 'rgb(255, 99, 132)',
orange: 'rgb(255, 159, 64)',
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);
-}
\ No newline at end of file
+};
+
+(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));
+
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;
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();
helpers.extend(meta.dataset, {
// Utility
_datasetIndex: me.index,
+ _scale: scale,
// Data
_children: points,
_loop: true,
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()
}
});
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;
};
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
ctx.moveTo(currentVM.x, currentVM.y);
} else {
// Line to next point
- lineToPoint(previous, current);
+ helpers.canvas.lineTo(ctx, previous._view, current._view);
}
lastDrawnIndex = index;
}
--- /dev/null
+'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);
+ }
+ }
+ };
+};
},
"line": {
"borderColor": "transparent",
- "fill": "top",
+ "fill": "end",
"tension": 0
}
}
},
"line": {
"borderColor": "transparent",
- "fill": "top",
+ "fill": "end",
"tension": 0
}
}
},
"line": {
"borderColor": "transparent",
- "fill": "zero",
+ "fill": "origin",
"tension": 0
}
}
"line": {
"cubicInterpolationMode": "monotone",
"borderColor": "transparent",
- "fill": "zero"
+ "fill": "origin"
}
}
}
"line": {
"cubicInterpolationMode": "monotone",
"borderColor": "transparent",
- "fill": "zero"
+ "fill": "origin"
}
}
}
"cubicInterpolationMode": "monotone",
"borderColor": "transparent",
"stepped": true,
- "fill": "zero"
+ "fill": "origin"
}
}
}
"cubicInterpolationMode": "monotone",
"borderColor": "transparent",
"stepped": true,
- "fill": "zero"
+ "fill": "origin"
}
}
}
},
"line": {
"borderColor": "transparent",
- "fill": "zero",
+ "fill": "origin",
"tension": 0
}
}
},
"line": {
"borderColor": "transparent",
- "fill": "bottom",
+ "fill": "start",
"tension": 0
}
}
},
"line": {
"borderColor": "transparent",
- "fill": "bottom",
+ "fill": "start",
"tension": 0
}
}
--- /dev/null
+{
+ "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
+ }
+ }
+}
--- /dev/null
+{
+ "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
+ }
+ }
+}
--- /dev/null
+{
+ "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
+ }
+ }
+}
--- /dev/null
+{
+ "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
+ }
+ }
+}
"line": {
"borderColor": "transparent",
"tension": 0.5,
- "fill": "zero"
+ "fill": "origin"
}
}
}
},
"line": {
"borderColor": "transparent",
- "fill": "zero"
+ "fill": "origin"
}
}
}
});
- 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',
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',
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',
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',
// Tests for the line element
describe('Chart.elements.Line', function() {
- describe('auto', jasmine.specsFromFixtures('element.line'));
-
it('should be constructed', function() {
var line = new Chart.elements.Line({
_datasetindex: 2,
// Need to provide some settings
_view: {
fill: false, // don't want to fill
- tension: 0.0, // no bezier curve for now
- scaleZero: 0
+ tension: 0, // no bezier curve for now
}
});
name: 'moveTo',
args: [0, 10]
}, {
- name: 'bezierCurveTo',
- args: [0, 10, 5, 0, 5, 0]
+ name: 'lineTo',
+ args: [5, 0]
}, {
- name: 'bezierCurveTo',
- args: [5, 0, 15, -10, 15, -10]
+ name: 'lineTo',
+ args: [15, -10]
}, {
- name: 'bezierCurveTo',
- args: [15, -10, 19, -5, 19, -5]
+ name: 'lineTo',
+ args: [19, -5]
}, {
name: 'stroke',
args: [],
// Need to provide some settings
_view: {
fill: false, // don't want to fill
- tension: 0.0, // no bezier curve for now
- scaleZero: 0
+ tension: 0, // no bezier curve for now
}
});
// Need to provide some settings
_view: {
fill: false, // don't want to fill
- tension: 0.0, // no bezier curve for now
- scaleZero: 0
+ tension: 0, // no bezier curve for now
}
});
// Need to provide some settings
_view: {
fill: true,
- scaleZero: 2, // for filling lines
- tension: 0.0, // no bezier curve for now
+ tension: 0, // no bezier curve for now
borderCapStyle: 'round',
borderColor: 'rgb(255, 255, 0)',
var expected = [{
name: 'save',
args: []
- }, {
- name: 'beginPath',
- args: []
- }, {
- name: 'moveTo',
- args: [0, 2]
- }, {
- name: 'lineTo',
- args: [0, 10]
- }, {
- name: 'bezierCurveTo',
- args: [0, 10, 5, 0, 5, 0]
- }, {
- name: 'bezierCurveTo',
- args: [5, 0, 15, -10, 15, -10]
- }, {
- name: 'bezierCurveTo',
- args: [15, -10, 19, -5, 19, -5]
- }, {
- name: 'lineTo',
- args: [19, 2]
- }, {
- name: 'setFillStyle',
- args: ['rgb(0, 0, 0)']
- }, {
- name: 'closePath',
- args: []
- }, {
- name: 'fill',
- args: []
}, {
name: 'setLineCap',
args: ['round']
}, {
name: 'moveTo',
args: [0, 10]
- }, {
- name: 'bezierCurveTo',
- args: [0, 10, 5, 0, 5, 0]
- }, {
- name: 'bezierCurveTo',
- args: [5, 0, 15, -10, 15, -10]
- }, {
- name: 'bezierCurveTo',
- args: [15, -10, 19, -5, 19, -5]
- }, {
- name: 'stroke',
- args: []
- }, {
- name: 'restore',
- args: []
- }];
- expect(mockContext.getCalls()).toEqual(expected);
- });
-
- it('should draw with fillMode top', function() {
- var mockContext = window.createMockContext();
-
- // Create our points
- var points = [];
- points.push(new Chart.elements.Point({
- _datasetindex: 2,
- _index: 0,
- _view: {
- x: 0,
- y: 10,
- controlPointNextX: 0,
- controlPointNextY: 10
- }
- }));
- points.push(new Chart.elements.Point({
- _datasetindex: 2,
- _index: 1,
- _view: {
- x: 5,
- y: 0,
- controlPointPreviousX: 5,
- controlPointPreviousY: 0,
- controlPointNextX: 5,
- controlPointNextY: 0
- }
- }));
- points.push(new Chart.elements.Point({
- _datasetindex: 2,
- _index: 2,
- _view: {
- x: 15,
- y: -10,
- controlPointPreviousX: 15,
- controlPointPreviousY: -10,
- controlPointNextX: 15,
- controlPointNextY: -10
- }
- }));
- points.push(new Chart.elements.Point({
- _datasetindex: 2,
- _index: 3,
- _view: {
- x: 19,
- y: -5,
- controlPointPreviousX: 19,
- controlPointPreviousY: -5,
- controlPointNextX: 19,
- controlPointNextY: -5
- }
- }));
-
- var line = new Chart.elements.Line({
- _datasetindex: 2,
- _chart: {
- ctx: mockContext,
- },
- _children: points,
- // Need to provide some settings
- _view: {
- fill: 'top',
- scaleZero: 2, // for filling lines
- scaleTop: -2,
- scaleBottom: 10,
- tension: 0.0, // no bezier curve for now
-
- borderCapStyle: 'round',
- borderColor: 'rgb(255, 255, 0)',
- borderDash: [2, 2],
- borderDashOffset: 1.5,
- borderJoinStyle: 'bevel',
- borderWidth: 4,
- backgroundColor: 'rgb(0, 0, 0)'
- }
- });
-
- line.draw();
-
- var expected = [{
- name: 'save',
- args: []
- }, {
- name: 'beginPath',
- args: []
- }, {
- name: 'moveTo',
- args: [0, -2]
- }, {
- name: 'lineTo',
- args: [0, 10]
- }, {
- name: 'bezierCurveTo',
- args: [0, 10, 5, 0, 5, 0]
- }, {
- name: 'bezierCurveTo',
- args: [5, 0, 15, -10, 15, -10]
- }, {
- name: 'bezierCurveTo',
- args: [15, -10, 19, -5, 19, -5]
}, {
name: 'lineTo',
- args: [19, -2]
- }, {
- name: 'setFillStyle',
- args: ['rgb(0, 0, 0)']
- }, {
- name: 'closePath',
- args: []
- }, {
- name: 'fill',
- args: []
- }, {
- name: 'setLineCap',
- args: ['round']
- }, {
- name: 'setLineDash',
- args: [
- [2, 2]
- ]
- }, {
- name: 'setLineDashOffset',
- args: [1.5]
- }, {
- name: 'setLineJoin',
- args: ['bevel']
- }, {
- name: 'setLineWidth',
- args: [4]
- }, {
- name: 'setStrokeStyle',
- args: ['rgb(255, 255, 0)']
- }, {
- name: 'beginPath',
- args: []
- }, {
- name: 'moveTo',
- args: [0, 10]
- }, {
- name: 'bezierCurveTo',
- args: [0, 10, 5, 0, 5, 0]
- }, {
- name: 'bezierCurveTo',
- args: [5, 0, 15, -10, 15, -10]
- }, {
- name: 'bezierCurveTo',
- args: [15, -10, 19, -5, 19, -5]
- }, {
- name: 'stroke',
- args: []
- }, {
- name: 'restore',
- args: []
- }];
- expect(mockContext.getCalls()).toEqual(expected);
- });
-
- it('should draw with fillMode bottom', function() {
- var mockContext = window.createMockContext();
-
- // Create our points
- var points = [];
- points.push(new Chart.elements.Point({
- _datasetindex: 2,
- _index: 0,
- _view: {
- x: 0,
- y: 10,
- controlPointNextX: 0,
- controlPointNextY: 10
- }
- }));
- points.push(new Chart.elements.Point({
- _datasetindex: 2,
- _index: 1,
- _view: {
- x: 5,
- y: 0,
- controlPointPreviousX: 5,
- controlPointPreviousY: 0,
- controlPointNextX: 5,
- controlPointNextY: 0
- }
- }));
- points.push(new Chart.elements.Point({
- _datasetindex: 2,
- _index: 2,
- _view: {
- x: 15,
- y: -10,
- controlPointPreviousX: 15,
- controlPointPreviousY: -10,
- controlPointNextX: 15,
- controlPointNextY: -10
- }
- }));
- points.push(new Chart.elements.Point({
- _datasetindex: 2,
- _index: 3,
- _view: {
- x: 19,
- y: -5,
- controlPointPreviousX: 19,
- controlPointPreviousY: -5,
- controlPointNextX: 19,
- controlPointNextY: -5
- }
- }));
-
- var line = new Chart.elements.Line({
- _datasetindex: 2,
- _chart: {
- ctx: mockContext,
- },
- _children: points,
- // Need to provide some settings
- _view: {
- fill: 'bottom',
- scaleZero: 2, // for filling lines
- scaleTop: -2,
- scaleBottom: 10,
- tension: 0.0, // no bezier curve for now
-
- borderCapStyle: 'round',
- borderColor: 'rgb(255, 255, 0)',
- borderDash: [2, 2],
- borderDashOffset: 1.5,
- borderJoinStyle: 'bevel',
- borderWidth: 4,
- backgroundColor: 'rgb(0, 0, 0)'
- }
- });
-
- line.draw();
-
- var expected = [{
- name: 'save',
- args: []
- }, {
- name: 'beginPath',
- args: []
- }, {
- name: 'moveTo',
- args: [0, 10]
+ args: [5, 0]
}, {
name: 'lineTo',
- args: [0, 10]
- }, {
- name: 'bezierCurveTo',
- args: [0, 10, 5, 0, 5, 0]
- }, {
- name: 'bezierCurveTo',
- args: [5, 0, 15, -10, 15, -10]
- }, {
- name: 'bezierCurveTo',
- args: [15, -10, 19, -5, 19, -5]
+ args: [15, -10]
}, {
name: 'lineTo',
- args: [19, 10]
- }, {
- name: 'setFillStyle',
- args: ['rgb(0, 0, 0)']
- }, {
- name: 'closePath',
- args: []
- }, {
- name: 'fill',
- args: []
- }, {
- name: 'setLineCap',
- args: ['round']
- }, {
- name: 'setLineDash',
- args: [
- [2, 2]
- ]
- }, {
- name: 'setLineDashOffset',
- args: [1.5]
- }, {
- name: 'setLineJoin',
- args: ['bevel']
- }, {
- name: 'setLineWidth',
- args: [4]
- }, {
- name: 'setStrokeStyle',
- args: ['rgb(255, 255, 0)']
- }, {
- name: 'beginPath',
- args: []
- }, {
- name: 'moveTo',
- args: [0, 10]
- }, {
- name: 'bezierCurveTo',
- args: [0, 10, 5, 0, 5, 0]
- }, {
- name: 'bezierCurveTo',
- args: [5, 0, 15, -10, 15, -10]
- }, {
- name: 'bezierCurveTo',
- args: [15, -10, 19, -5, 19, -5]
+ args: [19, -5]
}, {
name: 'stroke',
args: []
// Need to provide some settings
_view: {
fill: true,
- scaleZero: 2, // for filling lines
- tension: 0.0, // no bezier curve for now
+ tension: 0, // no bezier curve for now
}
- });
-
- line.draw();
-
- var expected = [{
- name: 'save',
- args: []
- }, {
- name: 'beginPath',
- args: []
- }, {
- name: 'moveTo',
- args: [0, 2]
- }, {
- name: 'lineTo',
- args: [0, 10]
- }, {
- name: 'bezierCurveTo',
- args: [0, 10, 5, 0, 5, 0]
- }, {
- name: 'lineTo',
- args: [5, 2]
- }, {
- name: 'lineTo',
- args: [19, 2]
- }, {
- name: 'lineTo',
- args: [19, -5]
- }, {
- name: 'lineTo',
- args: [19, 2]
- }, {
- name: 'setFillStyle',
- args: ['rgba(0,0,0,0.1)']
- }, {
- name: 'closePath',
- args: []
- }, {
- name: 'fill',
+ });
+
+ line.draw();
+
+ var expected = [{
+ name: 'save',
args: []
}, {
name: 'setLineCap',
name: 'moveTo',
args: [0, 10]
}, {
- name: 'bezierCurveTo',
- args: [0, 10, 5, 0, 5, 0]
+ name: 'lineTo',
+ args: [5, 0]
}, {
name: 'moveTo',
args: [19, -5]
// Need to provide some settings
_view: {
fill: true,
- scaleZero: 2, // for filling lines
- tension: 0.0, // no bezier curve for now
+ tension: 0, // no bezier curve for now
spanGaps: true
}
});
var expected = [{
name: 'save',
args: []
- }, {
- name: 'beginPath',
- args: []
- }, {
- name: 'moveTo',
- args: [0, 2]
- }, {
- name: 'lineTo',
- args: [0, 10]
- }, {
- name: 'bezierCurveTo',
- args: [0, 10, 5, 0, 5, 0]
- }, {
- name: 'bezierCurveTo',
- args: [5, 0, 19, -5, 19, -5]
- }, {
- name: 'lineTo',
- args: [19, 2]
- }, {
- name: 'setFillStyle',
- args: ['rgba(0,0,0,0.1)']
- }, {
- name: 'closePath',
- args: []
- }, {
- name: 'fill',
- args: []
}, {
name: 'setLineCap',
args: ['butt']
name: 'moveTo',
args: [0, 10]
}, {
- name: 'bezierCurveTo',
- args: [0, 10, 5, 0, 5, 0]
+ name: 'lineTo',
+ args: [5, 0]
}, {
- name: 'bezierCurveTo',
- args: [5, 0, 19, -5, 19, -5]
+ name: 'lineTo',
+ args: [19, -5]
}, {
name: 'stroke',
args: []
// Need to provide some settings
_view: {
fill: true,
- scaleZero: 2, // for filling lines
- tension: 0.0, // no bezier curve for now
+ tension: 0, // no bezier curve for now
spanGaps: true
}
});
var expected = [{
name: 'save',
args: []
- }, {
- name: 'beginPath',
- args: []
- }, {
- name: 'moveTo',
- args: [0, 2]
- }, {
- name: 'setFillStyle',
- args: ['rgba(0,0,0,0.1)']
- }, {
- name: 'closePath',
- args: []
- }, {
- name: 'fill',
- args: []
}, {
name: 'setLineCap',
args: ['butt']
// Need to provide some settings
_view: {
fill: true,
- scaleZero: 2, // for filling lines
- tension: 0.0, // no bezier curve for now
+ tension: 0, // no bezier curve for now
}
});
var expected = [{
name: 'save',
args: []
- }, {
- name: 'beginPath',
- args: []
- }, {
- name: 'moveTo',
- args: [0, 2]
- }, {
- name: 'lineTo',
- args: [5, 2]
- }, {
- name: 'lineTo',
- args: [5, 0]
- }, {
- name: 'bezierCurveTo',
- args: [5, 0, 15, -10, 15, -10]
- }, {
- name: 'bezierCurveTo',
- args: [15, -10, 19, -5, 19, -5]
- }, {
- name: 'lineTo',
- args: [19, 2]
- }, {
- name: 'setFillStyle',
- args: ['rgba(0,0,0,0.1)']
- }, {
- name: 'closePath',
- args: []
- }, {
- name: 'fill',
- args: []
}, {
name: 'setLineCap',
args: ['butt']
name: 'moveTo',
args: [5, 0]
}, {
- name: 'bezierCurveTo',
- args: [5, 0, 15, -10, 15, -10]
+ name: 'lineTo',
+ args: [15, -10]
}, {
- name: 'bezierCurveTo',
- args: [15, -10, 19, -5, 19, -5]
+ name: 'lineTo',
+ args: [19, -5]
}, {
name: 'stroke',
args: []
// Need to provide some settings
_view: {
fill: true,
- scaleZero: 2, // for filling lines
- tension: 0.0, // no bezier curve for now
+ tension: 0, // no bezier curve for now
spanGaps: true
}
});
var expected = [{
name: 'save',
args: []
- }, {
- name: 'beginPath',
- args: []
- }, {
- name: 'moveTo',
- args: [0, 2]
- }, {
- name: 'lineTo',
- args: [5, 2]
- }, {
- name: 'lineTo',
- args: [5, 0]
- }, {
- name: 'bezierCurveTo',
- args: [5, 0, 15, -10, 15, -10]
- }, {
- name: 'bezierCurveTo',
- args: [15, -10, 19, -5, 19, -5]
- }, {
- name: 'lineTo',
- args: [19, 2]
- }, {
- name: 'setFillStyle',
- args: ['rgba(0,0,0,0.1)']
- }, {
- name: 'closePath',
- args: []
- }, {
- name: 'fill',
- args: []
}, {
name: 'setLineCap',
args: ['butt']
name: 'moveTo',
args: [5, 0]
}, {
- name: 'bezierCurveTo',
- args: [5, 0, 15, -10, 15, -10]
+ name: 'lineTo',
+ args: [15, -10]
}, {
- name: 'bezierCurveTo',
- args: [15, -10, 19, -5, 19, -5]
+ name: 'lineTo',
+ args: [19, -5]
}, {
name: 'stroke',
args: []
// Need to provide some settings
_view: {
fill: true,
- scaleZero: 2, // for filling lines
- tension: 0.0, // no bezier curve for now
+ tension: 0, // no bezier curve for now
}
});
var expected = [{
name: 'save',
args: []
- }, {
- name: 'beginPath',
- args: []
- }, {
- name: 'moveTo',
- args: [0, 2]
- }, {
- name: 'lineTo',
- args: [0, 10]
- }, {
- name: 'bezierCurveTo',
- args: [0, 10, 5, 0, 5, 0]
- }, {
- name: 'bezierCurveTo',
- args: [5, 0, 15, -10, 15, -10]
- }, {
- name: 'lineTo',
- args: [15, 2]
- }, {
- name: 'lineTo',
- args: [15, 2]
- }, {
- name: 'setFillStyle',
- args: ['rgba(0,0,0,0.1)']
- }, {
- name: 'closePath',
- args: []
- }, {
- name: 'fill',
- args: []
}, {
name: 'setLineCap',
args: ['butt']
name: 'moveTo',
args: [0, 10]
}, {
- name: 'bezierCurveTo',
- args: [0, 10, 5, 0, 5, 0]
+ name: 'lineTo',
+ args: [5, 0]
}, {
- name: 'bezierCurveTo',
- args: [5, 0, 15, -10, 15, -10]
+ name: 'lineTo',
+ args: [15, -10]
}, {
name: 'stroke',
args: []
// Need to provide some settings
_view: {
fill: true,
- scaleZero: 2, // for filling lines
- tension: 0.0, // no bezier curve for now
+ tension: 0, // no bezier curve for now
spanGaps: true
}
});
var expected = [{
name: 'save',
args: []
- }, {
- name: 'beginPath',
- args: []
- }, {
- name: 'moveTo',
- args: [0, 2]
- }, {
- name: 'lineTo',
- args: [0, 10]
- }, {
- name: 'bezierCurveTo',
- args: [0, 10, 5, 0, 5, 0]
- }, {
- name: 'bezierCurveTo',
- args: [5, 0, 15, -10, 15, -10]
- }, {
- name: 'lineTo',
- args: [15, 2]
- }, {
- name: 'setFillStyle',
- args: ['rgba(0,0,0,0.1)']
- }, {
- name: 'closePath',
- args: []
- }, {
- name: 'fill',
- args: []
}, {
name: 'setLineCap',
args: ['butt']
name: 'moveTo',
args: [0, 10]
}, {
- name: 'bezierCurveTo',
- args: [0, 10, 5, 0, 5, 0]
+ name: 'lineTo',
+ args: [5, 0]
}, {
- name: 'bezierCurveTo',
- args: [5, 0, 15, -10, 15, -10]
+ name: 'lineTo',
+ args: [15, -10]
}, {
name: 'stroke',
args: []
// Need to provide some settings
_view: {
fill: true, // don't want to fill
- tension: 0.0, // no bezier curve for now
- scaleZero: {
- x: 3,
- y: 2
- },
+ tension: 0, // no bezier curve for now
}
});
expect(mockContext.getCalls()).toEqual([{
name: 'save',
args: [],
- }, {
- name: 'beginPath',
- args: []
- }, {
- name: 'moveTo',
- args: [3, 2]
- }, {
- name: 'lineTo',
- args: [0, 10]
- }, {
- name: 'bezierCurveTo',
- args: [0, 10, 5, 0, 5, 0]
- }, {
- name: 'bezierCurveTo',
- args: [5, 0, 15, -10, 15, -10]
- }, {
- name: 'bezierCurveTo',
- args: [15, -10, 19, -5, 19, -5]
- }, {
- name: 'bezierCurveTo',
- args: [19, -5, 0, 10, 0, 10]
- }, {
- name: 'setFillStyle',
- args: ['rgba(0,0,0,0.1)']
- }, {
- name: 'closePath',
- args: []
- }, {
- name: 'fill',
- args: []
}, {
name: 'setLineCap',
args: ['butt']
name: 'moveTo',
args: [0, 10]
}, {
- name: 'bezierCurveTo',
- args: [0, 10, 5, 0, 5, 0]
+ name: 'lineTo',
+ args: [5, 0]
}, {
- name: 'bezierCurveTo',
- args: [5, 0, 15, -10, 15, -10]
+ name: 'lineTo',
+ args: [15, -10]
}, {
- name: 'bezierCurveTo',
- args: [15, -10, 19, -5, 19, -5]
+ name: 'lineTo',
+ args: [19, -5]
}, {
- name: 'bezierCurveTo',
- args: [19, -5, 0, 10, 0, 10]
+ name: 'lineTo',
+ args: [0, 10]
}, {
name: 'stroke',
args: [],
// Need to provide some settings
_view: {
fill: true, // don't want to fill
- tension: 0.0, // no bezier curve for now
- scaleZero: {
- x: 3,
- y: 2
- },
+ tension: 0, // no bezier curve for now
}
});
expect(mockContext.getCalls()).toEqual([{
name: 'save',
args: [],
- }, {
- name: 'beginPath',
- args: []
- }, {
- name: 'moveTo',
- args: [3, 2]
- }, {
- name: 'lineTo',
- args: [0, 10]
- }, {
- name: 'lineTo',
- args: [3, 2]
- }, {
- name: 'lineTo',
- args: [15, -10]
- }, {
- name: 'bezierCurveTo',
- args: [15, -10, 19, -5, 19, -5]
- }, {
- name: 'bezierCurveTo',
- args: [19, -5, 0, 10, 0, 10]
- }, {
- name: 'setFillStyle',
- args: ['rgba(0,0,0,0.1)']
- }, {
- name: 'closePath',
- args: []
- }, {
- name: 'fill',
- args: []
}, {
name: 'setLineCap',
args: ['butt']
name: 'moveTo',
args: [15, -10]
}, {
- name: 'bezierCurveTo',
- args: [15, -10, 19, -5, 19, -5]
+ name: 'lineTo',
+ args: [19, -5]
}, {
- name: 'bezierCurveTo',
- args: [19, -5, 0, 10, 0, 10]
+ name: 'lineTo',
+ args: [0, 10]
}, {
name: 'stroke',
args: [],
// Need to provide some settings
_view: {
fill: true, // don't want to fill
- tension: 0.0, // no bezier curve for now
- scaleZero: {
- x: 3,
- y: 2
- },
+ tension: 0, // no bezier curve for now
spanGaps: true
}
});
expect(mockContext.getCalls()).toEqual([{
name: 'save',
args: [],
- }, {
- name: 'beginPath',
- args: []
- }, {
- name: 'moveTo',
- args: [3, 2]
- }, {
- name: 'lineTo',
- args: [0, 10]
- }, {
- name: 'bezierCurveTo',
- args: [0, 10, 15, -10, 15, -10]
- }, {
- name: 'bezierCurveTo',
- args: [15, -10, 19, -5, 19, -5]
- }, {
- name: 'bezierCurveTo',
- args: [19, -5, 0, 10, 0, 10]
- }, {
- name: 'setFillStyle',
- args: ['rgba(0,0,0,0.1)']
- }, {
- name: 'closePath',
- args: []
- }, {
- name: 'fill',
- args: []
}, {
name: 'setLineCap',
args: ['butt']
name: 'moveTo',
args: [0, 10]
}, {
- name: 'bezierCurveTo',
- args: [0, 10, 15, -10, 15, -10]
+ name: 'lineTo',
+ args: [15, -10]
}, {
- name: 'bezierCurveTo',
- args: [15, -10, 19, -5, 19, -5]
+ name: 'lineTo',
+ args: [19, -5]
}, {
- name: 'bezierCurveTo',
- args: [19, -5, 0, 10, 0, 10]
+ name: 'lineTo',
+ args: [0, 10]
}, {
name: 'stroke',
args: [],
// Need to provide some settings
_view: {
fill: true, // don't want to fill
- tension: 0.0, // no bezier curve for now
- scaleZero: {
- x: 3,
- y: 2
- },
+ tension: 0, // no bezier curve for now
}
});
expect(mockContext.getCalls()).toEqual([{
name: 'save',
args: [],
- }, {
- name: 'beginPath',
- args: []
- }, {
- name: 'moveTo',
- args: [3, 2]
- }, {
- name: 'lineTo',
- args: [5, 0]
- }, {
- name: 'bezierCurveTo',
- args: [5, 0, 15, -10, 15, -10]
- }, {
- name: 'bezierCurveTo',
- args: [15, -10, 19, -5, 19, -5]
- }, {
- name: 'lineTo',
- args: [3, 2]
- }, {
- name: 'setFillStyle',
- args: ['rgba(0,0,0,0.1)']
- }, {
- name: 'closePath',
- args: []
- }, {
- name: 'fill',
- args: []
}, {
name: 'setLineCap',
args: ['butt']
name: 'moveTo',
args: [5, 0]
}, {
- name: 'bezierCurveTo',
- args: [5, 0, 15, -10, 15, -10]
+ name: 'lineTo',
+ args: [15, -10]
}, {
- name: 'bezierCurveTo',
- args: [15, -10, 19, -5, 19, -5]
+ name: 'lineTo',
+ args: [19, -5]
}, {
name: 'stroke',
args: [],
// Need to provide some settings
_view: {
fill: true, // don't want to fill
- tension: 0.0, // no bezier curve for now
- scaleZero: {
- x: 3,
- y: 2
- },
+ tension: 0, // no bezier curve for now
}
});
expect(mockContext.getCalls()).toEqual([{
name: 'save',
args: [],
- }, {
- name: 'beginPath',
- args: []
- }, {
- name: 'moveTo',
- args: [3, 2]
- }, {
- name: 'lineTo',
- args: [0, 10]
- }, {
- name: 'bezierCurveTo',
- args: [0, 10, 5, 0, 5, 0]
- }, {
- name: 'bezierCurveTo',
- args: [5, 0, 15, -10, 15, -10]
- }, {
- name: 'lineTo',
- args: [3, 2]
- }, {
- name: 'lineTo',
- args: [0, 10]
- }, {
- name: 'setFillStyle',
- args: ['rgba(0,0,0,0.1)']
- }, {
- name: 'closePath',
- args: []
- }, {
- name: 'fill',
- args: []
}, {
name: 'setLineCap',
args: ['butt']
name: 'moveTo',
args: [0, 10]
}, {
- name: 'bezierCurveTo',
- args: [0, 10, 5, 0, 5, 0]
+ name: 'lineTo',
+ args: [5, 0]
}, {
- name: 'bezierCurveTo',
- args: [5, 0, 15, -10, 15, -10]
+ name: 'lineTo',
+ args: [15, -10]
}, {
name: 'moveTo',
args: [0, 10]
}, 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() {
--- /dev/null
+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)
+ ]);
+ });
+ });
+});