%PDF- %PDF-
| Direktori : /home/tjamichg/cursos.tjamich.gob.mx/web/assets/chart.js/src/core/ |
| Current File : /home/tjamichg/cursos.tjamich.gob.mx/web/assets/chart.js/src/core/core.scale.js |
'use strict';
var defaults = require('./core.defaults');
var Element = require('./core.element');
var helpers = require('../helpers/index');
var Ticks = require('./core.ticks');
var isArray = helpers.isArray;
var isNullOrUndef = helpers.isNullOrUndef;
var valueOrDefault = helpers.valueOrDefault;
var valueAtIndexOrDefault = helpers.valueAtIndexOrDefault;
defaults._set('scale', {
display: true,
position: 'left',
offset: false,
// grid line settings
gridLines: {
display: true,
color: 'rgba(0,0,0,0.1)',
lineWidth: 1,
drawBorder: true,
drawOnChartArea: true,
drawTicks: true,
tickMarkLength: 10,
zeroLineWidth: 1,
zeroLineColor: 'rgba(0,0,0,0.25)',
zeroLineBorderDash: [],
zeroLineBorderDashOffset: 0.0,
offsetGridLines: false,
borderDash: [],
borderDashOffset: 0.0
},
// scale label
scaleLabel: {
// display property
display: false,
// actual label
labelString: '',
// top/bottom padding
padding: {
top: 4,
bottom: 4
}
},
// label settings
ticks: {
beginAtZero: false,
minRotation: 0,
maxRotation: 50,
mirror: false,
padding: 0,
reverse: false,
display: true,
autoSkip: true,
autoSkipPadding: 0,
labelOffset: 0,
// We pass through arrays to be rendered as multiline labels, we convert Others to strings here.
callback: Ticks.formatters.values,
minor: {},
major: {}
}
});
/** Returns a new array containing numItems from arr */
function sample(arr, numItems) {
var result = [];
var increment = arr.length / numItems;
var i = 0;
var len = arr.length;
for (; i < len; i += increment) {
result.push(arr[Math.floor(i)]);
}
return result;
}
function getPixelForGridLine(scale, index, offsetGridLines) {
var length = scale.getTicks().length;
var validIndex = Math.min(index, length - 1);
var lineValue = scale.getPixelForTick(validIndex);
var start = scale._startPixel;
var end = scale._endPixel;
var epsilon = 1e-6; // 1e-6 is margin in pixels for accumulated error.
var offset;
if (offsetGridLines) {
if (length === 1) {
offset = Math.max(lineValue - start, end - lineValue);
} else if (index === 0) {
offset = (scale.getPixelForTick(1) - lineValue) / 2;
} else {
offset = (lineValue - scale.getPixelForTick(validIndex - 1)) / 2;
}
lineValue += validIndex < index ? offset : -offset;
// Return undefined if the pixel is out of the range
if (lineValue < start - epsilon || lineValue > end + epsilon) {
return;
}
}
return lineValue;
}
function garbageCollect(caches, length) {
helpers.each(caches, function(cache) {
var gc = cache.gc;
var gcLen = gc.length / 2;
var i;
if (gcLen > length) {
for (i = 0; i < gcLen; ++i) {
delete cache.data[gc[i]];
}
gc.splice(0, gcLen);
}
});
}
/**
* Returns {width, height, offset} objects for the first, last, widest, highest tick
* labels where offset indicates the anchor point offset from the top in pixels.
*/
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 (!isNullOrUndef(label) && !isArray(label)) {
width = helpers.measureText(ctx, cache.data, cache.gc, width, label);
height = lineHeight;
} else if (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 (!isNullOrUndef(nestedLabel) && !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)
};
}
function getTickMarkLength(options) {
return options.drawTicks ? options.tickMarkLength : 0;
}
function getScaleLabelHeight(options) {
var font, padding;
if (!options.display) {
return 0;
}
font = helpers.options._parseFont(options);
padding = helpers.options.toPadding(options.padding);
return font.lineHeight + padding.height;
}
function parseFontOptions(options, nestedOpts) {
return helpers.extend(helpers.options._parseFont({
fontFamily: valueOrDefault(nestedOpts.fontFamily, options.fontFamily),
fontSize: valueOrDefault(nestedOpts.fontSize, options.fontSize),
fontStyle: valueOrDefault(nestedOpts.fontStyle, options.fontStyle),
lineHeight: valueOrDefault(nestedOpts.lineHeight, options.lineHeight)
}), {
color: helpers.options.resolve([nestedOpts.fontColor, options.fontColor, defaults.global.defaultFontColor])
});
}
function parseTickFontOptions(options) {
var minor = parseFontOptions(options, options.minor);
var major = options.major.enabled ? parseFontOptions(options, options.major) : minor;
return {minor: minor, major: major};
}
function nonSkipped(ticksToFilter) {
var filtered = [];
var item, index, len;
for (index = 0, len = ticksToFilter.length; index < len; ++index) {
item = ticksToFilter[index];
if (typeof item._index !== 'undefined') {
filtered.push(item);
}
}
return filtered;
}
function getEvenSpacing(arr) {
var len = arr.length;
var i, diff;
if (len < 2) {
return false;
}
for (diff = arr[0], i = 1; i < len; ++i) {
if (arr[i] - arr[i - 1] !== diff) {
return false;
}
}
return diff;
}
function calculateSpacing(majorIndices, ticks, axisLength, ticksLimit) {
var evenMajorSpacing = getEvenSpacing(majorIndices);
var spacing = (ticks.length - 1) / ticksLimit;
var factors, factor, i, ilen;
// If the major ticks are evenly spaced apart, place the minor ticks
// so that they divide the major ticks into even chunks
if (!evenMajorSpacing) {
return Math.max(spacing, 1);
}
factors = helpers.math._factorize(evenMajorSpacing);
for (i = 0, ilen = factors.length - 1; i < ilen; i++) {
factor = factors[i];
if (factor > spacing) {
return factor;
}
}
return Math.max(spacing, 1);
}
function getMajorIndices(ticks) {
var result = [];
var i, ilen;
for (i = 0, ilen = ticks.length; i < ilen; i++) {
if (ticks[i].major) {
result.push(i);
}
}
return result;
}
function skipMajors(ticks, majorIndices, spacing) {
var count = 0;
var next = majorIndices[0];
var i, tick;
spacing = Math.ceil(spacing);
for (i = 0; i < ticks.length; i++) {
tick = ticks[i];
if (i === next) {
tick._index = i;
count++;
next = majorIndices[count * spacing];
} else {
delete tick.label;
}
}
}
function skip(ticks, spacing, majorStart, majorEnd) {
var start = valueOrDefault(majorStart, 0);
var end = Math.min(valueOrDefault(majorEnd, ticks.length), ticks.length);
var count = 0;
var length, i, tick, next;
spacing = Math.ceil(spacing);
if (majorEnd) {
length = majorEnd - majorStart;
spacing = length / Math.floor(length / spacing);
}
next = start;
while (next < 0) {
count++;
next = Math.round(start + count * spacing);
}
for (i = Math.max(start, 0); i < end; i++) {
tick = ticks[i];
if (i === next) {
tick._index = i;
count++;
next = Math.round(start + count * spacing);
} else {
delete tick.label;
}
}
}
var Scale = Element.extend({
zeroLineIndex: 0,
/**
* Get the padding needed for the scale
* @method getPadding
* @private
* @returns {Padding} the necessary padding
*/
getPadding: function() {
var me = this;
return {
left: me.paddingLeft || 0,
top: me.paddingTop || 0,
right: me.paddingRight || 0,
bottom: me.paddingBottom || 0
};
},
/**
* Returns the scale tick objects ({label, major})
* @since 2.7
*/
getTicks: function() {
return this._ticks;
},
/**
* @private
*/
_getLabels: function() {
var data = this.chart.data;
return this.options.labels || (this.isHorizontal() ? data.xLabels : data.yLabels) || data.labels || [];
},
// These methods are ordered by lifecyle. Utilities then follow.
// Any function defined here is inherited by all scale types.
// Any function can be extended by the scale type
/**
* Provided for backward compatibility, not available anymore
* @function Chart.Scale.mergeTicksOptions
* @deprecated since version 2.8.0
* @todo remove at version 3
*/
mergeTicksOptions: function() {
// noop
},
beforeUpdate: function() {
helpers.callback(this.options.beforeUpdate, [this]);
},
/**
* @param {number} maxWidth - the max width in pixels
* @param {number} maxHeight - the max height in pixels
* @param {object} margins - the space between the edge of the other scales and edge of the chart
* This space comes from two sources:
* - padding - space that's required to show the labels at the edges of the scale
* - thickness of scales or legends in another orientation
*/
update: function(maxWidth, maxHeight, margins) {
var me = this;
var tickOpts = me.options.ticks;
var sampleSize = tickOpts.sampleSize;
var i, ilen, labels, ticks, samplingEnabled;
// Update Lifecycle - Probably don't want to ever extend or overwrite this function ;)
me.beforeUpdate();
// Absorb the master measurements
me.maxWidth = maxWidth;
me.maxHeight = maxHeight;
me.margins = helpers.extend({
left: 0,
right: 0,
top: 0,
bottom: 0
}, margins);
me._ticks = null;
me.ticks = null;
me._labelSizes = null;
me._maxLabelLines = 0;
me.longestLabelWidth = 0;
me.longestTextCache = me.longestTextCache || {};
me._gridLineItems = null;
me._labelItems = null;
// Dimensions
me.beforeSetDimensions();
me.setDimensions();
me.afterSetDimensions();
// Data min/max
me.beforeDataLimits();
me.determineDataLimits();
me.afterDataLimits();
// Ticks - `this.ticks` is now DEPRECATED!
// Internal ticks are now stored as objects in the PRIVATE `this._ticks` member
// and must not be accessed directly from outside this class. `this.ticks` being
// around for long time and not marked as private, we can't change its structure
// without unexpected breaking changes. If you need to access the scale ticks,
// use scale.getTicks() instead.
me.beforeBuildTicks();
// New implementations should return an array of objects but for BACKWARD COMPAT,
// we still support no return (`this.ticks` internally set by calling this method).
ticks = me.buildTicks() || [];
// Allow modification of ticks in callback.
ticks = me.afterBuildTicks(ticks) || ticks;
// Ensure ticks contains ticks in new tick format
if ((!ticks || !ticks.length) && me.ticks) {
ticks = [];
for (i = 0, ilen = me.ticks.length; i < ilen; ++i) {
ticks.push({
value: me.ticks[i],
major: false
});
}
}
me._ticks = ticks;
// Compute tick rotation and fit using a sampled subset of labels
// We generally don't need to compute the size of every single label for determining scale size
samplingEnabled = sampleSize < ticks.length;
labels = me._convertTicksToLabels(samplingEnabled ? sample(ticks, sampleSize) : ticks);
// _configure is called twice, once here, once from core.controller.updateLayout.
// Here we haven't been positioned yet, but dimensions are correct.
// Variables set in _configure are needed for calculateTickRotation, and
// it's ok that coordinates are not correct there, only dimensions matter.
me._configure();
// Tick Rotation
me.beforeCalculateTickRotation();
me.calculateTickRotation();
me.afterCalculateTickRotation();
me.beforeFit();
me.fit();
me.afterFit();
// Auto-skip
me._ticksToDraw = tickOpts.display && (tickOpts.autoSkip || tickOpts.source === 'auto') ? me._autoSkip(ticks) : ticks;
if (samplingEnabled) {
// Generate labels using all non-skipped ticks
labels = me._convertTicksToLabels(me._ticksToDraw);
}
me.ticks = labels; // BACKWARD COMPATIBILITY
// IMPORTANT: after this point, we consider that `this.ticks` will NEVER change!
me.afterUpdate();
// TODO(v3): remove minSize as a public property and return value from all layout boxes. It is unused
// make maxWidth and maxHeight private
return me.minSize;
},
/**
* @private
*/
_configure: function() {
var me = this;
var reversePixels = me.options.ticks.reverse;
var startPixel, endPixel;
if (me.isHorizontal()) {
startPixel = me.left;
endPixel = me.right;
} else {
startPixel = me.top;
endPixel = me.bottom;
// by default vertical scales are from bottom to top, so pixels are reversed
reversePixels = !reversePixels;
}
me._startPixel = startPixel;
me._endPixel = endPixel;
me._reversePixels = reversePixels;
me._length = endPixel - startPixel;
},
afterUpdate: function() {
helpers.callback(this.options.afterUpdate, [this]);
},
//
beforeSetDimensions: function() {
helpers.callback(this.options.beforeSetDimensions, [this]);
},
setDimensions: function() {
var me = this;
// Set the unconstrained dimension before label rotation
if (me.isHorizontal()) {
// Reset position before calculating rotation
me.width = me.maxWidth;
me.left = 0;
me.right = me.width;
} else {
me.height = me.maxHeight;
// Reset position before calculating rotation
me.top = 0;
me.bottom = me.height;
}
// Reset padding
me.paddingLeft = 0;
me.paddingTop = 0;
me.paddingRight = 0;
me.paddingBottom = 0;
},
afterSetDimensions: function() {
helpers.callback(this.options.afterSetDimensions, [this]);
},
// Data limits
beforeDataLimits: function() {
helpers.callback(this.options.beforeDataLimits, [this]);
},
determineDataLimits: helpers.noop,
afterDataLimits: function() {
helpers.callback(this.options.afterDataLimits, [this]);
},
//
beforeBuildTicks: function() {
helpers.callback(this.options.beforeBuildTicks, [this]);
},
buildTicks: helpers.noop,
afterBuildTicks: function(ticks) {
var me = this;
// ticks is empty for old axis implementations here
if (isArray(ticks) && ticks.length) {
return helpers.callback(me.options.afterBuildTicks, [me, ticks]);
}
// Support old implementations (that modified `this.ticks` directly in buildTicks)
me.ticks = helpers.callback(me.options.afterBuildTicks, [me, me.ticks]) || me.ticks;
return ticks;
},
beforeTickToLabelConversion: function() {
helpers.callback(this.options.beforeTickToLabelConversion, [this]);
},
convertTicksToLabels: function() {
var me = this;
// Convert ticks to strings
var tickOpts = me.options.ticks;
me.ticks = me.ticks.map(tickOpts.userCallback || tickOpts.callback, this);
},
afterTickToLabelConversion: function() {
helpers.callback(this.options.afterTickToLabelConversion, [this]);
},
//
beforeCalculateTickRotation: function() {
helpers.callback(this.options.beforeCalculateTickRotation, [this]);
},
calculateTickRotation: function() {
var me = this;
var options = me.options;
var tickOpts = options.ticks;
var numTicks = me.getTicks().length;
var minRotation = tickOpts.minRotation || 0;
var maxRotation = tickOpts.maxRotation;
var labelRotation = minRotation;
var labelSizes, maxLabelWidth, maxLabelHeight, maxWidth, tickWidth, maxHeight, maxLabelDiagonal;
if (!me._isVisible() || !tickOpts.display || minRotation >= maxRotation || numTicks <= 1 || !me.isHorizontal()) {
me.labelRotation = minRotation;
return;
}
labelSizes = me._getLabelSizes();
maxLabelWidth = labelSizes.widest.width;
maxLabelHeight = labelSizes.highest.height - labelSizes.highest.offset;
// Estimate the width of each grid based on the canvas width, the maximum
// label width and the number of tick intervals
maxWidth = Math.min(me.maxWidth, me.chart.width - maxLabelWidth);
tickWidth = options.offset ? me.maxWidth / numTicks : maxWidth / (numTicks - 1);
// Allow 3 pixels x2 padding either side for label readability
if (maxLabelWidth + 6 > tickWidth) {
tickWidth = maxWidth / (numTicks - (options.offset ? 0.5 : 1));
maxHeight = me.maxHeight - getTickMarkLength(options.gridLines)
- tickOpts.padding - getScaleLabelHeight(options.scaleLabel);
maxLabelDiagonal = Math.sqrt(maxLabelWidth * maxLabelWidth + maxLabelHeight * maxLabelHeight);
labelRotation = helpers.toDegrees(Math.min(
Math.asin(Math.min((labelSizes.highest.height + 6) / tickWidth, 1)),
Math.asin(Math.min(maxHeight / maxLabelDiagonal, 1)) - Math.asin(maxLabelHeight / maxLabelDiagonal)
));
labelRotation = Math.max(minRotation, Math.min(maxRotation, labelRotation));
}
me.labelRotation = labelRotation;
},
afterCalculateTickRotation: function() {
helpers.callback(this.options.afterCalculateTickRotation, [this]);
},
//
beforeFit: function() {
helpers.callback(this.options.beforeFit, [this]);
},
fit: function() {
var me = this;
// Reset
var minSize = me.minSize = {
width: 0,
height: 0
};
var chart = me.chart;
var opts = me.options;
var tickOpts = opts.ticks;
var scaleLabelOpts = opts.scaleLabel;
var gridLineOpts = opts.gridLines;
var display = me._isVisible();
var isBottom = opts.position === 'bottom';
var isHorizontal = me.isHorizontal();
// Width
if (isHorizontal) {
minSize.width = me.maxWidth;
} else if (display) {
minSize.width = getTickMarkLength(gridLineOpts) + getScaleLabelHeight(scaleLabelOpts);
}
// height
if (!isHorizontal) {
minSize.height = me.maxHeight; // fill all the height
} else if (display) {
minSize.height = getTickMarkLength(gridLineOpts) + getScaleLabelHeight(scaleLabelOpts);
}
// Don't bother fitting the ticks if we are not showing the labels
if (tickOpts.display && display) {
var tickFonts = parseTickFontOptions(tickOpts);
var labelSizes = me._getLabelSizes();
var firstLabelSize = labelSizes.first;
var lastLabelSize = labelSizes.last;
var widestLabelSize = labelSizes.widest;
var highestLabelSize = labelSizes.highest;
var lineSpace = tickFonts.minor.lineHeight * 0.4;
var tickPadding = tickOpts.padding;
if (isHorizontal) {
// A horizontal axis is more constrained by the height.
var isRotated = me.labelRotation !== 0;
var angleRadians = helpers.toRadians(me.labelRotation);
var cosRotation = Math.cos(angleRadians);
var sinRotation = Math.sin(angleRadians);
var labelHeight = sinRotation * widestLabelSize.width
+ cosRotation * (highestLabelSize.height - (isRotated ? highestLabelSize.offset : 0))
+ (isRotated ? 0 : lineSpace); // padding
minSize.height = Math.min(me.maxHeight, minSize.height + labelHeight + tickPadding);
var offsetLeft = me.getPixelForTick(0) - me.left;
var offsetRight = me.right - me.getPixelForTick(me.getTicks().length - 1);
var paddingLeft, paddingRight;
// Ensure that our ticks are always inside the canvas. When rotated, ticks are right aligned
// which means that the right padding is dominated by the font height
if (isRotated) {
paddingLeft = isBottom ?
cosRotation * firstLabelSize.width + sinRotation * firstLabelSize.offset :
sinRotation * (firstLabelSize.height - firstLabelSize.offset);
paddingRight = isBottom ?
sinRotation * (lastLabelSize.height - lastLabelSize.offset) :
cosRotation * lastLabelSize.width + sinRotation * lastLabelSize.offset;
} else {
paddingLeft = firstLabelSize.width / 2;
paddingRight = lastLabelSize.width / 2;
}
// Adjust padding taking into account changes in offsets
// and add 3 px to move away from canvas edges
me.paddingLeft = Math.max((paddingLeft - offsetLeft) * me.width / (me.width - offsetLeft), 0) + 3;
me.paddingRight = Math.max((paddingRight - offsetRight) * me.width / (me.width - offsetRight), 0) + 3;
} else {
// A vertical axis is more constrained by the width. Labels are the
// dominant factor here, so get that length first and account for padding
var labelWidth = tickOpts.mirror ? 0 :
// use lineSpace for consistency with horizontal axis
// tickPadding is not implemented for horizontal
widestLabelSize.width + tickPadding + lineSpace;
minSize.width = Math.min(me.maxWidth, minSize.width + labelWidth);
me.paddingTop = firstLabelSize.height / 2;
me.paddingBottom = lastLabelSize.height / 2;
}
}
me.handleMargins();
if (isHorizontal) {
me.width = me._length = chart.width - me.margins.left - me.margins.right;
me.height = minSize.height;
} else {
me.width = minSize.width;
me.height = me._length = chart.height - me.margins.top - me.margins.bottom;
}
},
/**
* Handle margins and padding interactions
* @private
*/
handleMargins: function() {
var me = this;
if (me.margins) {
me.margins.left = Math.max(me.paddingLeft, me.margins.left);
me.margins.top = Math.max(me.paddingTop, me.margins.top);
me.margins.right = Math.max(me.paddingRight, me.margins.right);
me.margins.bottom = Math.max(me.paddingBottom, me.margins.bottom);
}
},
afterFit: function() {
helpers.callback(this.options.afterFit, [this]);
},
// Shared Methods
isHorizontal: function() {
var pos = this.options.position;
return pos === 'top' || pos === 'bottom';
},
isFullWidth: function() {
return this.options.fullWidth;
},
// Get the correct value. NaN bad inputs, If the value type is object get the x or y based on whether we are horizontal or not
getRightValue: function(rawValue) {
// Null and undefined values first
if (isNullOrUndef(rawValue)) {
return NaN;
}
// isNaN(object) returns true, so make sure NaN is checking for a number; Discard Infinite values
if ((typeof rawValue === 'number' || rawValue instanceof Number) && !isFinite(rawValue)) {
return NaN;
}
// If it is in fact an object, dive in one more level
if (rawValue) {
if (this.isHorizontal()) {
if (rawValue.x !== undefined) {
return this.getRightValue(rawValue.x);
}
} else if (rawValue.y !== undefined) {
return this.getRightValue(rawValue.y);
}
}
// Value is good, return it
return rawValue;
},
_convertTicksToLabels: function(ticks) {
var me = this;
var labels, i, ilen;
me.ticks = ticks.map(function(tick) {
return tick.value;
});
me.beforeTickToLabelConversion();
// New implementations should return the formatted tick labels but for BACKWARD
// COMPAT, we still support no return (`this.ticks` internally changed by calling
// this method and supposed to contain only string values).
labels = me.convertTicksToLabels(ticks) || me.ticks;
me.afterTickToLabelConversion();
// BACKWARD COMPAT: synchronize `_ticks` with labels (so potentially `this.ticks`)
for (i = 0, ilen = ticks.length; i < ilen; ++i) {
ticks[i].label = labels[i];
}
return labels;
},
/**
* @private
*/
_getLabelSizes: function() {
var me = this;
var labelSizes = me._labelSizes;
if (!labelSizes) {
me._labelSizes = labelSizes = computeLabelSizes(me.ctx, parseTickFontOptions(me.options.ticks), me.getTicks(), me.longestTextCache);
me.longestLabelWidth = labelSizes.widest.width;
}
return labelSizes;
},
/**
* @private
*/
_parseValue: function(value) {
var start, end, min, max;
if (isArray(value)) {
start = +this.getRightValue(value[0]);
end = +this.getRightValue(value[1]);
min = Math.min(start, end);
max = Math.max(start, end);
} else {
value = +this.getRightValue(value);
start = undefined;
end = value;
min = value;
max = value;
}
return {
min: min,
max: max,
start: start,
end: end
};
},
/**
* @private
*/
_getScaleLabel: function(rawValue) {
var v = this._parseValue(rawValue);
if (v.start !== undefined) {
return '[' + v.start + ', ' + v.end + ']';
}
return +this.getRightValue(rawValue);
},
/**
* Used to get the value to display in the tooltip for the data at the given index
* @param index
* @param datasetIndex
*/
getLabelForIndex: helpers.noop,
/**
* Returns the location of the given data point. Value can either be an index or a numerical value
* The coordinate (0, 0) is at the upper-left corner of the canvas
* @param value
* @param index
* @param datasetIndex
*/
getPixelForValue: helpers.noop,
/**
* Used to get the data value from a given pixel. This is the inverse of getPixelForValue
* The coordinate (0, 0) is at the upper-left corner of the canvas
* @param pixel
*/
getValueForPixel: helpers.noop,
/**
* Returns the location of the tick at the given index
* The coordinate (0, 0) is at the upper-left corner of the canvas
*/
getPixelForTick: function(index) {
var me = this;
var offset = me.options.offset;
var numTicks = me._ticks.length;
var tickWidth = 1 / Math.max(numTicks - (offset ? 0 : 1), 1);
return index < 0 || index > numTicks - 1
? null
: me.getPixelForDecimal(index * tickWidth + (offset ? tickWidth / 2 : 0));
},
/**
* Utility for getting the pixel location of a percentage of scale
* The coordinate (0, 0) is at the upper-left corner of the canvas
*/
getPixelForDecimal: function(decimal) {
var me = this;
if (me._reversePixels) {
decimal = 1 - decimal;
}
return me._startPixel + decimal * me._length;
},
getDecimalForPixel: function(pixel) {
var decimal = (pixel - this._startPixel) / this._length;
return this._reversePixels ? 1 - decimal : decimal;
},
/**
* Returns the pixel for the minimum chart value
* The coordinate (0, 0) is at the upper-left corner of the canvas
*/
getBasePixel: function() {
return this.getPixelForValue(this.getBaseValue());
},
getBaseValue: function() {
var me = this;
var min = me.min;
var max = me.max;
return me.beginAtZero ? 0 :
min < 0 && max < 0 ? max :
min > 0 && max > 0 ? min :
0;
},
/**
* Returns a subset of ticks to be plotted to avoid overlapping labels.
* @private
*/
_autoSkip: function(ticks) {
var me = this;
var tickOpts = me.options.ticks;
var axisLength = me._length;
var ticksLimit = tickOpts.maxTicksLimit || axisLength / me._tickSize() + 1;
var majorIndices = tickOpts.major.enabled ? getMajorIndices(ticks) : [];
var numMajorIndices = majorIndices.length;
var first = majorIndices[0];
var last = majorIndices[numMajorIndices - 1];
var i, ilen, spacing, avgMajorSpacing;
// If there are too many major ticks to display them all
if (numMajorIndices > ticksLimit) {
skipMajors(ticks, majorIndices, numMajorIndices / ticksLimit);
return nonSkipped(ticks);
}
spacing = calculateSpacing(majorIndices, ticks, axisLength, ticksLimit);
if (numMajorIndices > 0) {
for (i = 0, ilen = numMajorIndices - 1; i < ilen; i++) {
skip(ticks, spacing, majorIndices[i], majorIndices[i + 1]);
}
avgMajorSpacing = numMajorIndices > 1 ? (last - first) / (numMajorIndices - 1) : null;
skip(ticks, spacing, helpers.isNullOrUndef(avgMajorSpacing) ? 0 : first - avgMajorSpacing, first);
skip(ticks, spacing, last, helpers.isNullOrUndef(avgMajorSpacing) ? ticks.length : last + avgMajorSpacing);
return nonSkipped(ticks);
}
skip(ticks, spacing);
return nonSkipped(ticks);
},
/**
* @private
*/
_tickSize: function() {
var me = this;
var optionTicks = me.options.ticks;
// Calculate space needed by label in axis direction.
var rot = helpers.toRadians(me.labelRotation);
var cos = Math.abs(Math.cos(rot));
var sin = Math.abs(Math.sin(rot));
var labelSizes = me._getLabelSizes();
var padding = optionTicks.autoSkipPadding || 0;
var w = labelSizes ? labelSizes.widest.width + padding : 0;
var h = labelSizes ? labelSizes.highest.height + padding : 0;
// Calculate space needed for 1 tick in axis direction.
return me.isHorizontal()
? h * cos > w * sin ? w / cos : h / sin
: h * sin < w * cos ? h / cos : w / sin;
},
/**
* @private
*/
_isVisible: function() {
var me = this;
var chart = me.chart;
var display = me.options.display;
var i, ilen, meta;
if (display !== 'auto') {
return !!display;
}
// When 'auto', the scale is visible if at least one associated dataset is visible.
for (i = 0, ilen = chart.data.datasets.length; i < ilen; ++i) {
if (chart.isDatasetVisible(i)) {
meta = chart.getDatasetMeta(i);
if (meta.xAxisID === me.id || meta.yAxisID === me.id) {
return true;
}
}
}
return false;
},
/**
* @private
*/
_computeGridLineItems: function(chartArea) {
var me = this;
var chart = me.chart;
var options = me.options;
var gridLines = options.gridLines;
var position = options.position;
var offsetGridLines = gridLines.offsetGridLines;
var isHorizontal = me.isHorizontal();
var ticks = me._ticksToDraw;
var ticksLength = ticks.length + (offsetGridLines ? 1 : 0);
var tl = getTickMarkLength(gridLines);
var items = [];
var axisWidth = gridLines.drawBorder ? valueAtIndexOrDefault(gridLines.lineWidth, 0, 0) : 0;
var axisHalfWidth = axisWidth / 2;
var alignPixel = helpers._alignPixel;
var alignBorderValue = function(pixel) {
return alignPixel(chart, pixel, axisWidth);
};
var borderValue, i, tick, lineValue, alignedLineValue;
var tx1, ty1, tx2, ty2, x1, y1, x2, y2, lineWidth, lineColor, borderDash, borderDashOffset;
if (position === 'top') {
borderValue = alignBorderValue(me.bottom);
ty1 = me.bottom - tl;
ty2 = borderValue - axisHalfWidth;
y1 = alignBorderValue(chartArea.top) + axisHalfWidth;
y2 = chartArea.bottom;
} else if (position === 'bottom') {
borderValue = alignBorderValue(me.top);
y1 = chartArea.top;
y2 = alignBorderValue(chartArea.bottom) - axisHalfWidth;
ty1 = borderValue + axisHalfWidth;
ty2 = me.top + tl;
} else if (position === 'left') {
borderValue = alignBorderValue(me.right);
tx1 = me.right - tl;
tx2 = borderValue - axisHalfWidth;
x1 = alignBorderValue(chartArea.left) + axisHalfWidth;
x2 = chartArea.right;
} else {
borderValue = alignBorderValue(me.left);
x1 = chartArea.left;
x2 = alignBorderValue(chartArea.right) - axisHalfWidth;
tx1 = borderValue + axisHalfWidth;
tx2 = me.left + tl;
}
for (i = 0; i < ticksLength; ++i) {
tick = ticks[i] || {};
// autoskipper skipped this tick (#4635)
if (isNullOrUndef(tick.label) && i < ticks.length) {
continue;
}
if (i === me.zeroLineIndex && options.offset === offsetGridLines) {
// Draw the first index specially
lineWidth = gridLines.zeroLineWidth;
lineColor = gridLines.zeroLineColor;
borderDash = gridLines.zeroLineBorderDash || [];
borderDashOffset = gridLines.zeroLineBorderDashOffset || 0.0;
} else {
lineWidth = valueAtIndexOrDefault(gridLines.lineWidth, i, 1);
lineColor = valueAtIndexOrDefault(gridLines.color, i, 'rgba(0,0,0,0.1)');
borderDash = gridLines.borderDash || [];
borderDashOffset = gridLines.borderDashOffset || 0.0;
}
lineValue = getPixelForGridLine(me, tick._index || i, offsetGridLines);
// Skip if the pixel is out of the range
if (lineValue === undefined) {
continue;
}
alignedLineValue = alignPixel(chart, lineValue, lineWidth);
if (isHorizontal) {
tx1 = tx2 = x1 = x2 = alignedLineValue;
} else {
ty1 = ty2 = y1 = y2 = alignedLineValue;
}
items.push({
tx1: tx1,
ty1: ty1,
tx2: tx2,
ty2: ty2,
x1: x1,
y1: y1,
x2: x2,
y2: y2,
width: lineWidth,
color: lineColor,
borderDash: borderDash,
borderDashOffset: borderDashOffset,
});
}
items.ticksLength = ticksLength;
items.borderValue = borderValue;
return items;
},
/**
* @private
*/
_computeLabelItems: function() {
var me = this;
var options = me.options;
var optionTicks = options.ticks;
var position = options.position;
var isMirrored = optionTicks.mirror;
var isHorizontal = me.isHorizontal();
var ticks = me._ticksToDraw;
var fonts = parseTickFontOptions(optionTicks);
var tickPadding = optionTicks.padding;
var tl = getTickMarkLength(options.gridLines);
var rotation = -helpers.toRadians(me.labelRotation);
var items = [];
var i, ilen, tick, label, x, y, textAlign, pixel, font, lineHeight, lineCount, textOffset;
if (position === 'top') {
y = me.bottom - tl - tickPadding;
textAlign = !rotation ? 'center' : 'left';
} else if (position === 'bottom') {
y = me.top + tl + tickPadding;
textAlign = !rotation ? 'center' : 'right';
} else if (position === 'left') {
x = me.right - (isMirrored ? 0 : tl) - tickPadding;
textAlign = isMirrored ? 'left' : 'right';
} else {
x = me.left + (isMirrored ? 0 : tl) + tickPadding;
textAlign = isMirrored ? 'right' : 'left';
}
for (i = 0, ilen = ticks.length; i < ilen; ++i) {
tick = ticks[i];
label = tick.label;
// autoskipper skipped this tick (#4635)
if (isNullOrUndef(label)) {
continue;
}
pixel = me.getPixelForTick(tick._index || i) + optionTicks.labelOffset;
font = tick.major ? fonts.major : fonts.minor;
lineHeight = font.lineHeight;
lineCount = isArray(label) ? label.length : 1;
if (isHorizontal) {
x = pixel;
textOffset = position === 'top'
? ((!rotation ? 0.5 : 1) - lineCount) * lineHeight
: (!rotation ? 0.5 : 0) * lineHeight;
} else {
y = pixel;
textOffset = (1 - lineCount) * lineHeight / 2;
}
items.push({
x: x,
y: y,
rotation: rotation,
label: label,
font: font,
textOffset: textOffset,
textAlign: textAlign
});
}
return items;
},
/**
* @private
*/
_drawGrid: function(chartArea) {
var me = this;
var gridLines = me.options.gridLines;
if (!gridLines.display) {
return;
}
var ctx = me.ctx;
var chart = me.chart;
var alignPixel = helpers._alignPixel;
var axisWidth = gridLines.drawBorder ? valueAtIndexOrDefault(gridLines.lineWidth, 0, 0) : 0;
var items = me._gridLineItems || (me._gridLineItems = me._computeGridLineItems(chartArea));
var width, color, i, ilen, item;
for (i = 0, ilen = items.length; i < ilen; ++i) {
item = items[i];
width = item.width;
color = item.color;
if (width && color) {
ctx.save();
ctx.lineWidth = width;
ctx.strokeStyle = color;
if (ctx.setLineDash) {
ctx.setLineDash(item.borderDash);
ctx.lineDashOffset = item.borderDashOffset;
}
ctx.beginPath();
if (gridLines.drawTicks) {
ctx.moveTo(item.tx1, item.ty1);
ctx.lineTo(item.tx2, item.ty2);
}
if (gridLines.drawOnChartArea) {
ctx.moveTo(item.x1, item.y1);
ctx.lineTo(item.x2, item.y2);
}
ctx.stroke();
ctx.restore();
}
}
if (axisWidth) {
// Draw the line at the edge of the axis
var firstLineWidth = axisWidth;
var lastLineWidth = valueAtIndexOrDefault(gridLines.lineWidth, items.ticksLength - 1, 1);
var borderValue = items.borderValue;
var x1, x2, y1, y2;
if (me.isHorizontal()) {
x1 = alignPixel(chart, me.left, firstLineWidth) - firstLineWidth / 2;
x2 = alignPixel(chart, me.right, lastLineWidth) + lastLineWidth / 2;
y1 = y2 = borderValue;
} else {
y1 = alignPixel(chart, me.top, firstLineWidth) - firstLineWidth / 2;
y2 = alignPixel(chart, me.bottom, lastLineWidth) + lastLineWidth / 2;
x1 = x2 = borderValue;
}
ctx.lineWidth = axisWidth;
ctx.strokeStyle = valueAtIndexOrDefault(gridLines.color, 0);
ctx.beginPath();
ctx.moveTo(x1, y1);
ctx.lineTo(x2, y2);
ctx.stroke();
}
},
/**
* @private
*/
_drawLabels: function() {
var me = this;
var optionTicks = me.options.ticks;
if (!optionTicks.display) {
return;
}
var ctx = me.ctx;
var items = me._labelItems || (me._labelItems = me._computeLabelItems());
var i, j, ilen, jlen, item, tickFont, label, y;
for (i = 0, ilen = items.length; i < ilen; ++i) {
item = items[i];
tickFont = item.font;
// Make sure we draw text in the correct color and font
ctx.save();
ctx.translate(item.x, item.y);
ctx.rotate(item.rotation);
ctx.font = tickFont.string;
ctx.fillStyle = tickFont.color;
ctx.textBaseline = 'middle';
ctx.textAlign = item.textAlign;
label = item.label;
y = item.textOffset;
if (isArray(label)) {
for (j = 0, jlen = label.length; j < jlen; ++j) {
// We just make sure the multiline element is a string here..
ctx.fillText('' + label[j], 0, y);
y += tickFont.lineHeight;
}
} else {
ctx.fillText(label, 0, y);
}
ctx.restore();
}
},
/**
* @private
*/
_drawTitle: function() {
var me = this;
var ctx = me.ctx;
var options = me.options;
var scaleLabel = options.scaleLabel;
if (!scaleLabel.display) {
return;
}
var scaleLabelFontColor = valueOrDefault(scaleLabel.fontColor, defaults.global.defaultFontColor);
var scaleLabelFont = helpers.options._parseFont(scaleLabel);
var scaleLabelPadding = helpers.options.toPadding(scaleLabel.padding);
var halfLineHeight = scaleLabelFont.lineHeight / 2;
var position = options.position;
var rotation = 0;
var scaleLabelX, scaleLabelY;
if (me.isHorizontal()) {
scaleLabelX = me.left + me.width / 2; // midpoint of the width
scaleLabelY = position === 'bottom'
? me.bottom - halfLineHeight - scaleLabelPadding.bottom
: me.top + halfLineHeight + scaleLabelPadding.top;
} else {
var isLeft = position === 'left';
scaleLabelX = isLeft
? me.left + halfLineHeight + scaleLabelPadding.top
: me.right - halfLineHeight - scaleLabelPadding.top;
scaleLabelY = me.top + me.height / 2;
rotation = isLeft ? -0.5 * Math.PI : 0.5 * Math.PI;
}
ctx.save();
ctx.translate(scaleLabelX, scaleLabelY);
ctx.rotate(rotation);
ctx.textAlign = 'center';
ctx.textBaseline = 'middle';
ctx.fillStyle = scaleLabelFontColor; // render in correct colour
ctx.font = scaleLabelFont.string;
ctx.fillText(scaleLabel.labelString, 0, 0);
ctx.restore();
},
draw: function(chartArea) {
var me = this;
if (!me._isVisible()) {
return;
}
me._drawGrid(chartArea);
me._drawTitle();
me._drawLabels();
},
/**
* @private
*/
_layers: function() {
var me = this;
var opts = me.options;
var tz = opts.ticks && opts.ticks.z || 0;
var gz = opts.gridLines && opts.gridLines.z || 0;
if (!me._isVisible() || tz === gz || me.draw !== me._draw) {
// backward compatibility: draw has been overridden by custom scale
return [{
z: tz,
draw: function() {
me.draw.apply(me, arguments);
}
}];
}
return [{
z: gz,
draw: function() {
me._drawGrid.apply(me, arguments);
me._drawTitle.apply(me, arguments);
}
}, {
z: tz,
draw: function() {
me._drawLabels.apply(me, arguments);
}
}];
},
/**
* @private
*/
_getMatchingVisibleMetas: function(type) {
var me = this;
var isHorizontal = me.isHorizontal();
return me.chart._getSortedVisibleDatasetMetas()
.filter(function(meta) {
return (!type || meta.type === type)
&& (isHorizontal ? meta.xAxisID === me.id : meta.yAxisID === me.id);
});
}
});
Scale.prototype._draw = Scale.prototype.draw;
module.exports = Scale;