text: function createCanvas(w, h) {
  	var canvas = document.createElement('canvas');
  	canvas.width = w;
  	canvas.height = h;
  	return canvas;
  }
  
  function readImageData(url, callback) {
  	var image = new Image();
  
  	image.onload = function() {
  		var h = image.height;
  		var w = image.width;
  		var canvas = createCanvas(w, h);
  		var ctx = canvas.getContext('2d');
  		ctx.drawImage(image, 0, 0, w, h);
  		callback(ctx.getImageData(0, 0, w, h));
  	};
  
  	image.src = url;
  }
  
  /**
   * Injects a new canvas (and div wrapper) and creates teh associated Chart instance
   * using the given config. Additional options allow tweaking elements generation.
   * @param {object} config - Chart config.
   * @param {object} options - Chart acquisition options.
   * @param {object} options.canvas - Canvas attributes.
   * @param {object} options.wrapper - Canvas wrapper attributes.
   * @param {boolean} options.persistent - If true, the chart will not be released after the spec.
   */
  function acquireChart(config, options) {
  	var wrapper = document.createElement('div');
  	var canvas = document.createElement('canvas');
  	var chart, key;
  
  	config = config || {};
  	options = options || {};
  	options.canvas = options.canvas || {height: 512, width: 512};
  	options.wrapper = options.wrapper || {class: 'chartjs-wrapper'};
  
  	for (key in options.canvas) {
  		if (options.canvas.hasOwnProperty(key)) {
  			canvas.setAttribute(key, options.canvas[key]);
  		}
  	}
  
  	for (key in options.wrapper) {
  		if (options.wrapper.hasOwnProperty(key)) {
  			wrapper.setAttribute(key, options.wrapper[key]);
  		}
  	}
  
  	// by default, remove chart animation and auto resize
  	config.options = config.options || {};
  	config.options.animation = config.options.animation === undefined ? false : config.options.animation;
  	config.options.responsive = config.options.responsive === undefined ? false : config.options.responsive;
  	config.options.defaultFontFamily = config.options.defaultFontFamily || 'Arial';
  
  	wrapper.appendChild(canvas);
  	window.document.body.appendChild(wrapper);
  
  	try {
  		chart = new Chart(canvas.getContext('2d'), config);
  	} catch (e) {
  		window.document.body.removeChild(wrapper);
  		throw e;
  	}
  
  	chart.$test = {
  		persistent: options.persistent,
  		wrapper: wrapper
  	};
  
  	return chart;
  }
  
  function releaseChart(chart) {
  	chart.destroy();
  
  	var wrapper = (chart.$test || {}).wrapper;
  	if (wrapper && wrapper.parentNode) {
  		wrapper.parentNode.removeChild(wrapper);
  	}
  }
  
  function injectCSS(css) {
  	// https://stackoverflow.com/q/3922139
  	var head = document.getElementsByTagName('head')[0];
  	var style = document.createElement('style');
  	style.setAttribute('type', 'text/css');
  	if (style.styleSheet) { // IE
  		style.styleSheet.cssText = css;
  	} else {
  		style.appendChild(document.createTextNode(css));
  	}
  	head.appendChild(style);
  }
  
  function waitForResize(chart, callback) {
  	var override = chart.resize;
  	chart.resize = function() {
  		chart.resize = override;
  		override.apply(this, arguments);
  		callback();
  	};
  }
  
  function _resolveElementPoint(el) {
  	var point = {x: 0, y: 0};
  	if (el) {
  		if (typeof el.getCenterPoint === 'function') {
  			point = el.getCenterPoint();
  		} else if (el.x !== undefined && el.y !== undefined) {
  			point = el;
  		} else if (el._model && el._model.x !== undefined && el._model.y !== undefined) {
  			point = el._model;
  		}
  	}
  	return point;
  }
  
  function triggerMouseEvent(chart, type, el) {
  	var node = chart.canvas;
  	var rect = node.getBoundingClientRect();
  	var point = _resolveElementPoint(el);
  	var event = new MouseEvent(type, {
  		clientX: rect.left + point.x,
  		clientY: rect.top + point.y,
  		cancelable: true,
  		bubbles: true,
  		view: window
  	});
  
  	node.dispatchEvent(event);
  }
  
  module.exports = {
  	injectCSS: injectCSS,
  	createCanvas: createCanvas,
  	acquireChart: acquireChart,
  	releaseChart: releaseChart,
  	readImageData: readImageData,
  	triggerMouseEvent: triggerMouseEvent,
  	waitForResize: waitForResize
  };
  