docs: function computeLabelSizes(ctx, tickFonts, ticks, caches) {
  	var length = ticks.length;
  	var widths = [];
  	var heights = [];
  	var offsets = [];
  	var i, j, jlen, label, tickFont, fontString, cache, lineHeight, width, height, nestedLabel, widest, highest;
  
  	for (i = 0; i < length; ++i) {
  		label = ticks[i].label;
  		tickFont = ticks[i].major ? tickFonts.major : tickFonts.minor;
  		ctx.font = fontString = tickFont.string;
  		cache = caches[fontString] = caches[fontString] || {data: {}, gc: []};
  		lineHeight = tickFont.lineHeight;
  		width = height = 0;
  		// Undefined labels and arrays should not be measured
  		if (!helpers.isNullOrUndef(label) && !helpers.isArray(label)) {
  			width = helpers.measureText(ctx, cache.data, cache.gc, width, label);
  			height = lineHeight;
  		} else if (helpers.isArray(label)) {
  			// if it is an array let's measure each element
  			for (j = 0, jlen = label.length; j < jlen; ++j) {
  				nestedLabel = label[j];
  				// Undefined labels and arrays should not be measured
  				if (!helpers.isNullOrUndef(nestedLabel) && !helpers.isArray(nestedLabel)) {
  					width = helpers.measureText(ctx, cache.data, cache.gc, width, nestedLabel);
  					height += lineHeight;
  				}
  			}
  		}
  		widths.push(width);
  		heights.push(height);
  		offsets.push(lineHeight / 2);
  	}
  	garbageCollect(caches, length);
  
  	widest = widths.indexOf(Math.max.apply(null, widths));
  	highest = heights.indexOf(Math.max.apply(null, heights));
  
  	function valueAt(idx) {
  		return {
  			width: widths[idx] || 0,
  			height: heights[idx] || 0,
  			offset: offsets[idx] || 0
  		};
  	}
  
  	return {
  		first: valueAt(0),
  		last: valueAt(length - 1),
  		widest: valueAt(widest),
  		highest: valueAt(highest)
  	};
  }