text: /**
   * 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
   */
  
  'use strict';
  
  var defaults = require('../core/core.defaults');
  var elements = require('../elements/index');
  var helpers = require('../helpers/index');
  
  defaults._set('global', {
  	plugins: {
  		filler: {
  			propagate: true
  		}
  	}
  });
  
  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) || [];
  		var length = points.length || 0;
  
  		return !length ? null : function(point, i) {
  			return (i < length && points[i]._view) || null;
  		};
  	},
  
  	boundary: function(source) {
  		var boundary = source.boundary;
  		var x = boundary ? boundary.x : null;
  		var y = boundary ? boundary.y : null;
  
  		if (helpers.isArray(boundary)) {
  			return function(point, i) {
  				return boundary[i];
  			};
  		}
  
  		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 computeLinearBoundary(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.getBasePixel) {
  		target = scale.getBasePixel();
  	}
  
  	if (target !== undefined && target !== null) {
  		if (target.x !== undefined && target.y !== undefined) {
  			return target;
  		}
  
  		if (helpers.isFinite(target)) {
  			horizontal = scale.isHorizontal();
  			return {
  				x: horizontal ? target : null,
  				y: horizontal ? null : target
  			};
  		}
  	}
  
  	return null;
  }
  
  function computeCircularBoundary(source) {
  	var scale = source.el._scale;
  	var options = scale.options;
  	var length = scale.chart.data.labels.length;
  	var fill = source.fill;
  	var target = [];
  	var start, end, center, i, point;
  
  	if (!length) {
  		return null;
  	}
  
  	start = options.ticks.reverse ? scale.max : scale.min;
  	end = options.ticks.reverse ? scale.min : scale.max;
  	center = scale.getPointPositionForValue(0, start);
  	for (i = 0; i < length; ++i) {
  		point = fill === 'start' || fill === 'end'
  			? scale.getPointPositionForValue(i, fill === 'start' ? start : end)
  			: scale.getBasePosition(i);
  		if (options.gridLines.circular) {
  			point.cx = center.x;
  			point.cy = center.y;
  			point.angle = scale.getIndexAngle(i) - Math.PI / 2;
  		}
  		target.push(point);
  	}
  	return target;
  }
  
  function computeBoundary(source) {
  	var scale = source.el._scale || {};
  
  	if (scale.getPointPositionForValue) {
  		return computeCircularBoundary(source);
  	}
  	return computeLinearBoundary(source);
  }
  
  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, cx, cy, r;
  
  	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]);
  	}
  
  	if (curve1[0].angle !== undefined) {
  		cx = curve1[0].cx;
  		cy = curve1[0].cy;
  		r = Math.sqrt(Math.pow(curve1[0].x - cx, 2) + Math.pow(curve1[0].y - cy, 2));
  		for (i = len1 - 1; i > 0; --i) {
  			ctx.arc(cx, cy, r, curve1[i].angle, curve1[i - 1].angle, true);
  		}
  		return;
  	}
  
  	// 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();
  }
  
  module.exports = {
  	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 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);
  		}
  	},
  
  	beforeDatasetsDraw: function(chart) {
  		var count = (chart.data.datasets || []).length - 1;
  		var ctx = chart.ctx;
  		var meta, i, el, view, points, mapper, color;
  
  		for (i = count; i >= 0; --i) {
  			meta = chart.getDatasetMeta(i).$filler;
  
  			if (!meta || !meta.visible) {
  				continue;
  			}
  
  			el = meta.el;
  			view = el._view;
  			points = el._children || [];
  			mapper = meta.mapper;
  			color = view.backgroundColor || defaults.global.defaultColor;
  
  			if (mapper && color && points.length) {
  				helpers.canvas.clipArea(ctx, chart.chartArea);
  				doFill(ctx, points, mapper, view, color, el._loop);
  				helpers.canvas.unclipArea(ctx);
  			}
  		}
  	}
  };
  