highcharts
Version:
JavaScript charting framework
1,533 lines (1,379 loc) • 92.6 kB
JavaScript
/**
* @license Highcharts JS v6.2.0 (2018-10-17)
* Tree Grid
*
* (c) 2016 Jon Arild Nygard
*
* --- WORK IN PROGRESS ---
*
* License: www.highcharts.com/license
*/
'use strict';
(function (factory) {
if (typeof module === 'object' && module.exports) {
module.exports = factory;
} else if (typeof define === 'function' && define.amd) {
define(function () {
return factory;
});
} else {
factory(Highcharts);
}
}(function (Highcharts) {
(function (H) {
/**
* (c) 2016 Highsoft AS
* Authors: Lars A. V. Cabrera
*
* License: www.highcharts.com/license
*/
var argsToArray = function (args) {
return Array.prototype.slice.call(args, 1);
},
dateFormat = H.dateFormat,
defined = H.defined,
each = H.each,
isArray = H.isArray,
isNumber = H.isNumber,
isObject = function (x) {
// Always use strict mode
return H.isObject(x, true);
},
merge = H.merge,
pick = H.pick,
wrap = H.wrap,
Axis = H.Axis,
Tick = H.Tick;
/**
* Set grid options for the axis labels. Requires Highcharts Gantt.
*
* @type {object}
* @since 6.2.0
* @product gantt
* @apioption xAxis.grid
*/
/**
* Enable grid on the axis labels. Defaults to true for Gantt charts.
*
* @type {boolean}
* @since 6.2.0
* @default true
* @product gantt
* @apioption xAxis.grid.enabled
*/
/**
* Set specific options for each column (or row for horizontal axes) in the
* grid. Each extra column/row is its own axis, and the axis options can be set
* here.
*
* @type {Array<object>}
* @sample gantt/demo/left-axis-table
* Left axis as a table
* @apioption xAxis.grid.columns
*/
/**
* Set border color for the label grid lines.
*
* @type {Highcharts.ColorString}
* @apioption xAxis.grid.borderColor
*/
/**
* Set border width of the label grid lines.
*
* @type {number}
* @default 1
* @apioption xAxis.grid.borderWidth
*/
/**
* Set cell height for grid axis labels. By default this is calculated from font
* size.
*
* @type {number}
* @default null
* @apioption xAxis.grid.cellHeight
*/
// Enum for which side the axis is on.
// Maps to axis.side
var axisSide = {
top: 0,
right: 1,
bottom: 2,
left: 3,
0: 'top',
1: 'right',
2: 'bottom',
3: 'left'
};
/**
* Checks if an axis is a navigator axis.
* @return {Boolean} true if axis is found in axis.chart.navigator
*/
Axis.prototype.isNavigatorAxis = function () {
return /highcharts-navigator-[xy]axis/.test(this.options.className);
};
/**
* Checks if an axis is the outer axis in its dimension. Since
* axes are placed outwards in order, the axis with the highest
* index is the outermost axis.
*
* Example: If there are multiple x-axes at the top of the chart,
* this function returns true if the axis supplied is the last
* of the x-axes.
*
* @return true if the axis is the outermost axis in its dimension;
* false if not
*/
Axis.prototype.isOuterAxis = function () {
var axis = this,
chart = axis.chart,
thisIndex = -1,
isOuter = true;
each(chart.axes, function (otherAxis, index) {
if (otherAxis.side === axis.side && !otherAxis.isNavigatorAxis()) {
if (otherAxis === axis) {
// Get the index of the axis in question
thisIndex = index;
// Check thisIndex >= 0 in case thisIndex has
// not been found yet
} else if (thisIndex >= 0 && index > thisIndex) {
// There was an axis on the same side with a
// higher index.
isOuter = false;
}
}
});
// There were either no other axes on the same side,
// or the other axes were not farther from the chart
return isOuter;
};
/**
* Get the largest label width and height.
* @param {object} ticks All the ticks on one axis.
* @param {array} tickPositions All the tick positions on one axis.
* @return {object} object containing the properties height and width.
*/
Axis.prototype.getMaxLabelDimensions = function (ticks, tickPositions) {
var dimensions = {
width: 0,
height: 0
};
each(tickPositions, function (pos) {
var tick = ticks[pos],
tickHeight = 0,
tickWidth = 0,
label;
if (isObject(tick)) {
label = isObject(tick.label) ? tick.label : {};
// Find width and height of tick
tickHeight = label.getBBox ? label.getBBox().height : 0;
tickWidth = isNumber(label.textPxLength) ? label.textPxLength : 0;
// Update the result if width and/or height are larger
dimensions.height = Math.max(tickHeight, dimensions.height);
dimensions.width = Math.max(tickWidth, dimensions.width);
}
});
return dimensions;
};
/**
* Add custom date formats
*/
H.dateFormats = {
// Week number
W: function (timestamp) {
var d = new Date(timestamp),
yearStart,
weekNo;
d.setHours(0, 0, 0, 0);
d.setDate(d.getDate() - (d.getDay() || 7));
yearStart = new Date(d.getFullYear(), 0, 1);
weekNo = Math.ceil((((d - yearStart) / 86400000) + 1) / 7);
return weekNo;
},
// First letter of the day of the week, e.g. 'M' for 'Monday'.
E: function (timestamp) {
return dateFormat('%a', timestamp, true).charAt(0);
}
};
/**
* If chart is stockChart, always return 'left' to avoid labels being placed
* inside chart. Stock charts place yAxis labels inside by default.
* @param {function} proceed - the original function
* @return {string} 'left' if stockChart, or auto calculated alignment
*/
wrap(Axis.prototype, 'autoLabelAlign', function (proceed) {
var axis = this,
retVal;
if (axis.chart.isStock) {
retVal = 'left';
} else {
retVal = proceed.apply(axis, argsToArray(arguments));
}
return retVal;
});
/**
* Center tick labels in cells.
*
* @param {function} proceed - the original function
*
* @return {object} object - an object containing x and y positions
* for the tick
*/
wrap(Tick.prototype, 'getLabelPosition', function (proceed, x, y, label, horiz,
labelOpts, tickmarkOffset, index) {
var tick = this,
axis = tick.axis,
reversed = axis.reversed,
chart = axis.chart,
options = axis.options,
gridOptions = (options && isObject(options.grid)) ? options.grid : {},
align = labelOpts.align,
// verticalAlign is currently not supported for axis.labels.
verticalAlign = 'middle', // labelOpts.verticalAlign,
side = axisSide[axis.side],
tickPositions = axis.tickPositions,
tickPos = tick.pos - tickmarkOffset,
nextTickPos = (
isNumber(tickPositions[index + 1]) ?
tickPositions[index + 1] - tickmarkOffset :
axis.max + tickmarkOffset
),
tickSize = axis.tickSize('tick', true),
tickWidth = isArray(tickSize) ? tickSize[0] : 0,
crispCorr = tickSize && tickSize[1] / 2,
labelHeight,
lblMetrics,
lines,
result,
bottom,
top,
left,
right;
// Only center tick labels in grid axes
if (gridOptions.enabled === true) {
/**
* Calculate top and bottom positions of the cell.
*/
if (side === 'top') {
bottom = axis.top + axis.offset;
top = bottom - tickWidth;
} else if (side === 'bottom') {
top = chart.chartHeight - axis.bottom + axis.offset;
bottom = top + tickWidth;
} else {
bottom = axis.top + axis.len - axis.translate(
reversed ? nextTickPos : tickPos
);
top = axis.top + axis.len - axis.translate(
reversed ? tickPos : nextTickPos
);
}
/**
* Calculate left and right positions of the cell.
*/
if (side === 'right') {
left = chart.chartWidth - axis.right + axis.offset;
right = left + tickWidth;
} else if (side === 'left') {
right = axis.left + axis.offset;
left = right - tickWidth;
} else {
left = Math.round(axis.left + axis.translate(
reversed ? nextTickPos : tickPos
)) - crispCorr;
right = Math.round(axis.left + axis.translate(
reversed ? tickPos : nextTickPos
)) - crispCorr;
}
tick.slotWidth = right - left;
/**
* Calculate the positioning of the label based on alignment.
*/
result = {
x: (
align === 'left' ?
left :
align === 'right' ?
right :
left + ((right - left) / 2) // default to center
),
y: (
verticalAlign === 'top' ?
top :
verticalAlign === 'bottom' ?
bottom :
top + ((bottom - top) / 2) // default to middle
)
};
lblMetrics = chart.renderer.fontMetrics(
labelOpts.style.fontSize,
label.element
);
labelHeight = label.getBBox().height;
// Adjustment to y position to align the label correctly.
// Would be better to have a setter or similar for this.
if (!labelOpts.useHTML) {
lines = Math.round(labelHeight / lblMetrics.h);
result.y += (
// Center the label
// TODO: why does this actually center the label?
((lblMetrics.b - (lblMetrics.h - lblMetrics.f)) / 2) +
// Adjust for height of additional lines.
-(((lines - 1) * lblMetrics.h) / 2)
);
} else {
result.y += (
// Readjust yCorr in htmlUpdateTransform
lblMetrics.b +
// Adjust for height of html label
-(labelHeight / 2)
);
}
result.x += (axis.horiz && labelOpts.x || 0);
} else {
result = proceed.apply(tick, argsToArray(arguments));
}
return result;
});
/**
* Draw vertical axis ticks extra long to create cell floors and roofs.
* Overrides the tickLength for vertical axes.
*
* @param {function} proceed - the original function
* @returns {array} retVal -
*/
wrap(Axis.prototype, 'tickSize', function (proceed) {
var axis = this,
dimensions = axis.maxLabelDimensions,
options = axis.options,
gridOptions = (options && isObject(options.grid)) ? options.grid : {},
retVal = proceed.apply(axis, argsToArray(arguments)),
labelPadding,
distance;
if (gridOptions.enabled === true) {
labelPadding = (Math.abs(axis.defaultLeftAxisOptions.labels.x) * 2);
distance = labelPadding +
(axis.horiz ? dimensions.height : dimensions.width);
if (isArray(retVal)) {
retVal[0] = distance;
} else {
retVal = [distance];
}
}
return retVal;
});
wrap(Axis.prototype, 'getTitlePosition', function (proceed) {
var axis = this,
options = axis.options,
gridOptions = (options && isObject(options.grid)) ? options.grid : {};
if (gridOptions.enabled === true) {
// compute anchor points for each of the title align options
var title = axis.axisTitle,
titleWidth = title && title.getBBox().width,
horiz = axis.horiz,
axisLeft = axis.left,
axisTop = axis.top,
axisWidth = axis.width,
axisHeight = axis.height,
axisTitleOptions = options.title,
opposite = axis.opposite,
offset = axis.offset,
tickSize = axis.tickSize() || [0],
xOption = axisTitleOptions.x || 0,
yOption = axisTitleOptions.y || 0,
titleMargin = pick(axisTitleOptions.margin, horiz ? 5 : 10),
titleFontSize = axis.chart.renderer.fontMetrics(
axisTitleOptions.style && axisTitleOptions.style.fontSize,
title
).f,
// TODO account for alignment
// the position in the perpendicular direction of the axis
offAxis = (horiz ? axisTop + axisHeight : axisLeft) +
(horiz ? 1 : -1) * // horizontal axis reverses the margin
(opposite ? -1 : 1) * // so does opposite axes
(tickSize[0] / 2) +
(axis.side === axisSide.bottom ? titleFontSize : 0);
return {
x: horiz ?
axisLeft - titleWidth / 2 - titleMargin + xOption :
offAxis + (opposite ? axisWidth : 0) + offset + xOption,
y: horiz ?
(
offAxis -
(opposite ? axisHeight : 0) +
(opposite ? titleFontSize : -titleFontSize) / 2 +
offset +
yOption
) :
axisTop - titleMargin + yOption
};
}
return proceed.apply(this, argsToArray(arguments));
});
/**
* Avoid altering tickInterval when reserving space.
*/
wrap(Axis.prototype, 'unsquish', function (proceed) {
var axis = this,
options = axis.options,
gridOptions = (options && isObject(options.grid)) ? options.grid : {};
if (gridOptions.enabled === true && this.categories) {
return this.tickInterval;
}
return proceed.apply(this, argsToArray(arguments));
});
/**
* Creates a left and right wall on horizontal axes:
* - Places leftmost tick at the start of the axis, to create a left wall
* - Ensures that the rightmost tick is at the end of the axis, to create a
* right wall.
*
* @param {function} proceed - the original function
* @param {object} options - the pure axis options as input by the user
*/
H.addEvent(Axis, 'afterSetOptions', function (e) {
var options = this.options,
userOptions = e.userOptions,
gridAxisOptions,
gridOptions = (options && isObject(options.grid)) ? options.grid : {};
if (gridOptions.enabled === true) {
// Merge the user options into default grid axis options so that when a
// user option is set, it takes presedence.
gridAxisOptions = merge(true, {
className: 'highcharts-grid-axis ' + (userOptions.className || ''),
dateTimeLabelFormats: {
hour: {
list: ['%H:%M', '%H']
},
day: {
list: ['%A, %e. %B', '%a, %e. %b', '%E']
},
week: {
list: ['Week %W', 'W%W']
},
month: {
list: ['%B', '%b', '%o']
}
},
grid: {
borderWidth: 1
},
labels: {
padding: 2,
style: {
fontSize: '13px'
}
},
title: {
text: null,
reserveSpace: false,
rotation: 0
},
// In a grid axis, only allow one unit of certain types, for example
// we shouln't have one grid cell spanning two days.
units: [[
'millisecond', // unit name
[1, 10, 100]
], [
'second',
[1, 10]
], [
'minute',
[1, 5, 15]
], [
'hour',
[1, 6]
], [
'day',
[1]
], [
'week',
[1]
], [
'month',
[1]
], [
'year',
null
]]
}, userOptions);
// X-axis specific options
if (this.coll === 'xAxis') {
// For linked axes, tickPixelInterval is used only if the
// tickPositioner below doesn't run or returns undefined (like
// multiple years)
if (
defined(userOptions.linkedTo) &&
!defined(userOptions.tickPixelInterval)
) {
gridAxisOptions.tickPixelInterval = 350;
}
// For the secondary grid axis, use the primary axis' tick intervals
// and return ticks one level higher.
if (
// Check for tick pixel interval in options
!defined(userOptions.tickPixelInterval) &&
// Only for linked axes
defined(userOptions.linkedTo) &&
!defined(userOptions.tickPositioner) &&
!defined(userOptions.tickInterval)
) {
gridAxisOptions.tickPositioner = function (min, max) {
var parentInfo = (
this.linkedParent &&
this.linkedParent.tickPositions &&
this.linkedParent.tickPositions.info
);
if (parentInfo) {
var unitIdx,
count,
unitName,
i,
units = gridAxisOptions.units,
unitRange;
for (i = 0; i < units.length; i++) {
if (units[i][0] === parentInfo.unitName) {
unitIdx = i;
break;
}
}
// Spanning multiple years, go default
if (!units[unitIdx][1]) {
return;
}
// Get the first allowed count on the next unit.
if (units[unitIdx + 1]) {
unitName = units[unitIdx + 1][0];
count = (units[unitIdx + 1][1] || [1])[0];
}
unitRange = H.timeUnits[unitName];
this.tickInterval = unitRange * count;
return this.getTimeTicks(
{
unitRange: unitRange,
count: count,
unitName: unitName
},
min,
max,
this.options.startOfWeek
);
}
};
}
}
// Now merge the combined options into the axis options
merge(true, this.options, gridAxisOptions);
if (this.horiz) {
/** _________________________
* Make this: ___|_____|_____|_____|__|
* ^ ^
* _________________________
* Into this: |_____|_____|_____|_____|
* ^ ^
*/
options.minPadding = pick(userOptions.minPadding, 0);
options.maxPadding = pick(userOptions.maxPadding, 0);
}
// If borderWidth is set, then use its value for tick and line width.
if (isNumber(options.grid.borderWidth)) {
options.tickWidth = options.lineWidth = gridOptions.borderWidth;
}
}
});
/**
* Ensures a left wall on horizontal axes with series inheriting from column.
* ColumnSeries normally sets pointRange to null, resulting in Axis to select
* other values for point ranges. This enforces the above Axis.setOptions()
* override.
* _________________________
* Enforce this: ___|_____|_____|_____|__|
* ^
* _________________________
* To be this: |_____|_____|_____|_____|
* ^
*
* @param {function} proceed - the original function
* @param {object} options - the pure axis options as input by the user
*/
wrap(Axis.prototype, 'setAxisTranslation', function (proceed) {
var axis = this,
options = axis.options,
gridOptions = (options && isObject(options.grid)) ? options.grid : {},
tickInfo = this.tickPositions && this.tickPositions.info,
userLabels = this.userOptions.labels || {};
if (this.horiz) {
if (gridOptions.enabled === true) {
each(axis.series, function (series) {
series.options.pointRange = 0;
});
}
// Lower level time ticks, like hours or minutes, represent points in
// time and not ranges. These should be aligned left in the grid cell
// by default. The same applies to years of higher order.
if (
tickInfo &&
(
options.dateTimeLabelFormats[tickInfo.unitName]
.range === false ||
tickInfo.count > 1 // years
) &&
!defined(userLabels.align)
) {
options.labels.align = 'left';
if (!defined(userLabels.x)) {
options.labels.x = 3;
}
}
}
proceed.apply(axis, argsToArray(arguments));
});
// TODO: Does this function do what the drawing says? Seems to affect ticks and
// not the labels directly?
/**
* Makes tick labels which are usually ignored in a linked axis displayed if
* they are within range of linkedParent.min.
* _____________________________
* | | | | |
* Make this: | | 2 | 3 | 4 |
* |___|_______|_______|_______|
* ^
* _____________________________
* | | | | |
* Into this: | 1 | 2 | 3 | 4 |
* |___|_______|_______|_______|
* ^
* @param {function} proceed - the original function
*/
wrap(Axis.prototype, 'trimTicks', function (proceed) {
var axis = this,
options = axis.options,
gridOptions = (options && isObject(options.grid)) ? options.grid : {},
categoryAxis = axis.categories,
tickPositions = axis.tickPositions,
firstPos = tickPositions[0],
lastPos = tickPositions[tickPositions.length - 1],
linkedMin = axis.linkedParent && axis.linkedParent.min,
linkedMax = axis.linkedParent && axis.linkedParent.max,
min = linkedMin || axis.min,
max = linkedMax || axis.max,
tickInterval = axis.tickInterval,
moreThanMin = firstPos > min,
lessThanMax = lastPos < max,
endMoreThanMin = firstPos < min && firstPos + tickInterval > min,
startLessThanMax = lastPos > max && lastPos - tickInterval < max;
if (
gridOptions.enabled === true &&
!categoryAxis &&
(axis.horiz || axis.isLinked)
) {
if ((moreThanMin || endMoreThanMin) && !options.startOnTick) {
tickPositions[0] = min;
}
if ((lessThanMax || startLessThanMax) && !options.endOnTick) {
tickPositions[tickPositions.length - 1] = max;
}
}
proceed.apply(axis, argsToArray(arguments));
});
/**
* Draw an extra line on the far side of the outermost axis,
* creating floor/roof/wall of a grid. And some padding.
*
* Make this:
* (axis.min) __________________________ (axis.max)
* | | | | |
* Into this:
* (axis.min) __________________________ (axis.max)
* ___|____|____|____|____|__
*
* @param {function} proceed - the original function
*/
wrap(Axis.prototype, 'render', function (proceed) {
var axis = this,
options = axis.options,
gridOptions = (options && isObject(options.grid)) ? options.grid : {},
labelPadding,
distance,
lineWidth,
linePath,
yStartIndex,
yEndIndex,
xStartIndex,
xEndIndex,
renderer = axis.chart.renderer,
horiz = axis.horiz,
axisGroupBox;
if (gridOptions.enabled === true) {
// TODO acutual label padding (top, bottom, left, right)
// Label padding is needed to figure out where to draw the outer line.
labelPadding = (Math.abs(axis.defaultLeftAxisOptions.labels.x) * 2);
axis.maxLabelDimensions = axis.getMaxLabelDimensions(
axis.ticks,
axis.tickPositions
);
distance = axis.maxLabelDimensions.width + labelPadding;
lineWidth = options.lineWidth;
// Remove right wall before rendering if updating
if (axis.rightWall) {
axis.rightWall.destroy();
}
// Call original Axis.render() to obtain axis.axisLine and
// axis.axisGroup
proceed.apply(axis);
axisGroupBox = axis.axisGroup.getBBox();
/*
* Draw an extra axis line on outer axes
* >
* Make this: |______|______|______|___
*
* > _________________________
* Into this: |______|______|______|__|
*
*/
if (axis.isOuterAxis() && axis.axisLine) {
if (horiz) {
// -1 to avoid adding distance each time the chart updates
distance = axisGroupBox.height - 1;
}
if (lineWidth) {
linePath = axis.getLinePath(lineWidth);
xStartIndex = linePath.indexOf('M') + 1;
xEndIndex = linePath.indexOf('L') + 1;
yStartIndex = linePath.indexOf('M') + 2;
yEndIndex = linePath.indexOf('L') + 2;
// Negate distance if top or left axis
if (axis.side === axisSide.top || axis.side === axisSide.left) {
distance = -distance;
}
// If axis is horizontal, reposition line path vertically
if (horiz) {
linePath[yStartIndex] = linePath[yStartIndex] + distance;
linePath[yEndIndex] = linePath[yEndIndex] + distance;
} else {
// If axis is vertical, reposition line path horizontally
linePath[xStartIndex] = linePath[xStartIndex] + distance;
linePath[xEndIndex] = linePath[xEndIndex] + distance;
}
if (!axis.axisLineExtra) {
axis.axisLineExtra = renderer.path(linePath)
.attr({
zIndex: 7
})
.addClass('highcharts-axis-line')
.add(axis.axisGroup);
} else {
axis.axisLineExtra.animate({
d: linePath
});
}
// show or hide the line depending on options.showEmpty
axis.axisLine[axis.showAxis ? 'show' : 'hide'](true);
}
}
} else {
proceed.apply(axis);
}
});
/**
* Wraps axis init to draw cell walls on vertical axes.
*
* @param {function} proceed - the original function
*/
wrap(Axis.prototype, 'init', function (proceed, chart, userOptions) {
var axis = this,
gridOptions = (
(userOptions && isObject(userOptions.grid)) ?
userOptions.grid :
{}
),
columnOptions,
column,
columnIndex,
i;
function applyGridOptions(axis) {
var options = axis.options,
// TODO: Consider using cell margins defined in % of font size?
// 25 is optimal height for default fontSize (11px)
// 25 / 11 ≈ 2.28
fontSizeToCellHeightRatio = 25 / 11,
fontSize = options.labels.style.fontSize,
fontMetrics = axis.chart.renderer.fontMetrics(fontSize);
// Center-align by default
if (!options.labels) {
options.labels = {};
}
options.labels.align = pick(options.labels.align, 'center');
// TODO: Check against tickLabelPlacement between/on etc
/**
* Prevents adding the last tick label if the axis is not a category
* axis.
*
* Since numeric labels are normally placed at starts and ends of a
* range of value, and this module makes the label point at the value,
* an "extra" label would appear.
*/
if (!axis.categories) {
options.showLastLabel = false;
}
/**
* Make tick marks taller, creating cell walls of a grid.
* Use cellHeight axis option if set
*/
if (axis.horiz) {
options.tickLength = gridOptions.cellHeight ||
fontMetrics.h * fontSizeToCellHeightRatio;
}
/**
* Prevents rotation of labels when squished, as rotating them would not
* help.
*/
axis.labelRotation = 0;
options.labels.rotation = 0;
}
if (gridOptions.enabled) {
if (defined(gridOptions.borderColor)) {
userOptions.tickColor =
userOptions.lineColor = gridOptions.borderColor;
}
// Handle columns, each column is a grid axis
if (isArray(gridOptions.columns)) {
columnIndex = 0;
i = gridOptions.columns.length;
while (i--) {
columnOptions = merge(
userOptions,
gridOptions.columns[i],
{
// Force to behave like category axis
type: 'category'
}
);
delete columnOptions.grid.columns; // Prevent recursion
column = new Axis(chart, columnOptions);
column.isColumn = true;
column.columnIndex = columnIndex;
wrap(column, 'labelFormatter', function (proceed) {
var axis = this.axis,
tickPos = axis.tickPositions,
value = this.value,
series = axis.series[0],
isFirst = value === tickPos[0],
isLast = value === tickPos[tickPos.length - 1],
point = H.find(series.options.data, function (p) {
return p[axis.isXAxis ? 'x' : 'y'] === value;
});
// Make additional properties available for the formatter
this.isFirst = isFirst;
this.isLast = isLast;
this.point = point;
// Call original labelFormatter
return proceed.call(this);
});
columnIndex++;
}
} else {
// Call original Axis.init()
proceed.apply(axis, argsToArray(arguments));
applyGridOptions(axis);
}
} else {
// Call original Axis.init()
proceed.apply(axis, argsToArray(arguments));
}
});
}(Highcharts));
var Tree = (function (H) {
/**
* (c) 2016 Highsoft AS
* Authors: Jon Arild Nygard
*
* License: www.highcharts.com/license
*/
/* eslint no-console: 0 */
var each = H.each,
extend = H.extend,
isNumber = H.isNumber,
keys = H.keys,
map = H.map,
pick = H.pick,
reduce = H.reduce,
isFunction = function (x) {
return typeof x === 'function';
};
/**
* Creates an object map from parent id to childrens index.
* @param {Array} data List of points set in options.
* @param {string} data[].parent Parent id of point.
* @param {Array} ids List of all point ids.
* @returns {Object} Map from parent id to children index in data
*/
var getListOfParents = function (data, ids) {
var listOfParents = reduce(data, function (prev, curr) {
var parent = pick(curr.parent, '');
if (prev[parent] === undefined) {
prev[parent] = [];
}
prev[parent].push(curr);
return prev;
}, {}),
parents = keys(listOfParents);
// If parent does not exist, hoist parent to root of tree.
each(parents, function (parent, list) {
var children = listOfParents[parent];
if ((parent !== '') && (H.inArray(parent, ids) === -1)) {
each(children, function (child) {
list[''].push(child);
});
delete list[parent];
}
});
return listOfParents;
};
var getNode = function (id, parent, level, data, mapOfIdToChildren, options) {
var descendants = 0,
height = 0,
after = options && options.after,
before = options && options.before,
node = {
data: data,
depth: level - 1,
id: id,
level: level,
parent: parent
},
start,
end,
children;
// Allow custom logic before the children has been created.
if (isFunction(before)) {
before(node, options);
}
/**
* Call getNode recursively on the children. Calulate the height of the
* node, and the number of descendants.
*/
children = map((mapOfIdToChildren[id] || []), function (child) {
var node = getNode(
child.id,
id,
(level + 1),
child,
mapOfIdToChildren,
options
),
childStart = child.start,
childEnd = (
child.milestone === true ?
childStart :
child.end
);
// Start should be the lowest child.start.
start = (
(!isNumber(start) || childStart < start) ?
childStart :
start
);
// End should be the largest child.end.
// If child is milestone, then use start as end.
end = (
(!isNumber(end) || childEnd > end) ?
childEnd :
end
);
descendants = descendants + 1 + node.descendants;
height = Math.max(node.height + 1, height);
return node;
});
// Calculate start and end for point if it is not already explicitly set.
if (data) {
data.start = pick(data.start, start);
data.end = pick(data.end, end);
}
extend(node, {
children: children,
descendants: descendants,
height: height
});
// Allow custom logic after the children has been created.
if (isFunction(after)) {
after(node, options);
}
return node;
};
var getTree = function (data, options) {
var ids = map(data, function (d) {
return d.id;
}),
mapOfIdToChildren = getListOfParents(data, ids);
return getNode('', null, 1, null, mapOfIdToChildren, options);
};
var Tree = {
getListOfParents: getListOfParents,
getNode: getNode,
getTree: getTree
};
return Tree;
}(Highcharts));
var result = (function (H) {
var each = H.each,
extend = H.extend,
isArray = H.isArray,
isBoolean = function (x) {
return typeof x === 'boolean';
},
isFn = function (x) {
return typeof x === 'function';
},
isObject = H.isObject,
isNumber = H.isNumber,
merge = H.merge,
pick = H.pick,
reduce = H.reduce;
// TODO Combine buildTree and buildNode with setTreeValues
// TODO Remove logic from Treemap and make it utilize this mixin.
var setTreeValues = function setTreeValues(tree, options) {
var before = options.before,
idRoot = options.idRoot,
mapIdToNode = options.mapIdToNode,
nodeRoot = mapIdToNode[idRoot],
levelIsConstant = (
isBoolean(options.levelIsConstant) ?
options.levelIsConstant :
true
),
points = options.points,
point = points[tree.i],
optionsPoint = point && point.options || {},
childrenTotal = 0,
children = [],
value;
extend(tree, {
levelDynamic: tree.level - (levelIsConstant ? 0 : nodeRoot.level),
name: pick(point && point.name, ''),
visible: (
idRoot === tree.id ||
(isBoolean(options.visible) ? options.visible : false)
)
});
if (isFn(before)) {
tree = before(tree, options);
}
// First give the children some values
each(tree.children, function (child, i) {
var newOptions = extend({}, options);
extend(newOptions, {
index: i,
siblings: tree.children.length,
visible: tree.visible
});
child = setTreeValues(child, newOptions);
children.push(child);
if (child.visible) {
childrenTotal += child.val;
}
});
tree.visible = childrenTotal > 0 || tree.visible;
// Set the values
value = pick(optionsPoint.value, childrenTotal);
extend(tree, {
children: children,
childrenTotal: childrenTotal,
isLeaf: tree.visible && !childrenTotal,
val: value
});
return tree;
};
var getColor = function getColor(node, options) {
var index = options.index,
mapOptionsToLevel = options.mapOptionsToLevel,
parentColor = options.parentColor,
parentColorIndex = options.parentColorIndex,
series = options.series,
colors = options.colors,
siblings = options.siblings,
points = series.points,
getColorByPoint,
point,
level,
colorByPoint,
colorIndexByPoint,
color,
colorIndex;
function variation(color) {
var colorVariation = level && level.colorVariation;
if (colorVariation) {
if (colorVariation.key === 'brightness') {
return H.color(color).brighten(
colorVariation.to * (index / siblings)
).get();
}
}
return color;
}
if (node) {
point = points[node.i];
level = mapOptionsToLevel[node.level] || {};
getColorByPoint = point && level.colorByPoint;
if (getColorByPoint) {
colorIndexByPoint = point.index % (colors ?
colors.length :
series.chart.options.chart.colorCount
);
colorByPoint = colors && colors[colorIndexByPoint];
}
colorIndex = pick(
point && point.options.colorIndex,
level && level.colorIndex,
colorIndexByPoint,
parentColorIndex,
options.colorIndex
);
}
return {
color: color,
colorIndex: colorIndex
};
};
/**
* getLevelOptions - Creates a map from level number to its given options.
* @param {Object} params Object containing parameters.
* @param {Object} params.defaults Object containing default options. The
* default options are merged with the userOptions to get the final options for
* a specific level.
* @param {Number} params.from The lowest level number.
* @param {Array} params.levels User options from series.levels.
* @param {Number} params.to The highest level number.
* @return {null|Object} Returns a map from level number to its given options.
* Returns null if invalid input parameters.
*/
var getLevelOptions = function getLevelOptions(params) {
var result = null,
defaults,
converted,
i,
from,
to,
levels;
if (isObject(params)) {
result = {};
from = isNumber(params.from) ? params.from : 1;
levels = params.levels;
converted = {};
defaults = isObject(params.defaults) ? params.defaults : {};
if (isArray(levels)) {
converted = reduce(levels, function (obj, item) {
var level,
levelIsConstant,
options;
if (isObject(item) && isNumber(item.level)) {
options = merge({}, item);
levelIsConstant = (
isBoolean(options.levelIsConstant) ?
options.levelIsConstant :
defaults.levelIsConstant
);
// Delete redundant properties.
delete options.levelIsConstant;
delete options.level;
// Calculate which level these options apply to.
level = item.level + (levelIsConstant ? 0 : from - 1);
if (isObject(obj[level])) {
extend(obj[level], options);
} else {
obj[level] = options;
}
}
return obj;
}, {});
}
to = isNumber(params.to) ? params.to : 1;
for (i = 0; i <= to; i++) {
result[i] = merge(
{},
defaults,
isObject(converted[i]) ? converted[i] : {}
);
}
}
return result;
};
/**
* Update the rootId property on the series. Also makes sure that it is
* accessible to exporting.
* @param {object} series The series to operate on.
* @returns Returns the resulting rootId after update.
*/
var updateRootId = function (series) {
var rootId,
options;
if (isObject(series)) {
// Get the series options.
options = isObject(series.options) ? series.options : {};
// Calculate the rootId.
rootId = pick(series.rootNode, options.rootId, '');
// Set rootId on series.userOptions to pick it up in exporting.
if (isObject(series.userOptions)) {
series.userOptions.rootId = rootId;
}
// Set rootId on series to pick it up on next update.
series.rootNode = rootId;
}
return rootId;
};
var result = {
getColor: getColor,
getLevelOptions: getLevelOptions,
setTreeValues: setTreeValues,
updateRootId: updateRootId
};
return result;
}(Highcharts));
(function (H) {
/**
* (c) 2009-2018 Torstein Honsi
*
* License: www.highcharts.com/license
*/
var addEvent = H.addEvent,
pick = H.pick,
wrap = H.wrap,
each = H.each,
extend = H.extend,
isArray = H.isArray,
fireEvent = H.fireEvent,
Axis = H.Axis,
Series = H.Series;
function stripArguments() {
return Array.prototype.slice.call(arguments, 1);
}
extend(Axis.prototype, {
isInBreak: function (brk, val) {
var ret,
repeat = brk.repeat || Infinity,
from = brk.from,
length = brk.to - brk.from,
test = (
val >= from ?
(val - from) % repeat :
repeat - ((from - val) % repeat)
);
if (!brk.inclusive) {
ret = test < length && test !== 0;
} else {
ret = test <= length;
}
return ret;
},
isInAnyBreak: function (val, testKeep) {
var breaks = this.options.breaks,
i = breaks && breaks.length,
inbrk,
keep,
ret;
if (i) {
while (i--) {
if (this.isInBreak(breaks[i], val)) {
inbrk = true;
if (!keep) {
keep = pick(
breaks[i].showPoints,
this.isXAxis ? false : true
);
}
}
}
if (inbrk && testKeep) {
ret = inbrk && !keep;
} else {
ret = inbrk;
}
}
return ret;
}
});
addEvent(Axis, 'afterInit', function () {
if (typeof this.setBreaks === 'function') {
this.setBreaks(this.options.breaks, false);
}
});
addEvent(Axis, 'afterSetTickPositions', function () {
if (this.isBroken) {
var axis = this,
tickPositions = this.tickPositions,
info = this.tickPositions.info,
newPositions = [],
i;
for (i = 0; i < tickPositions.length; i++) {
if (!axis.isInAnyBreak(tickPositions[i])) {
newPositions.push(tickPositions[i]);
}
}
this.tickPositions = newPositions;
this.tickPositions.info = info;
}
});
// Force Axis to be not-ordinal when breaks are defined
addEvent(Axis, 'afterSetOptions', function () {
if (this.isBroken) {
this.options.ordinal = false;
}
});
/**
* Dynamically set or unset breaks in an axis. This function in lighter than
* usin Axis.update, and it also preserves animation.
* @param {Array} [breaks]
* The breaks to add. When `undefined` it removes existing breaks.
* @param {Boolean} [redraw=true]
* Whether to redraw the chart immediately.
*/
Axis.prototype.setBreaks = function (breaks, redraw) {
var axis = this,
isBroken = (isArray(breaks) && !!breaks.length);
function breakVal2Lin(val) {
var nval = val,
brk,
i;
for (i = 0; i < axis.breakArray.length; i++) {
brk = axis.breakArray[i];
if (brk.to <= val) {
nval -= brk.len;
} else if (brk.from >= val) {
break;
} else if (axis.isInBreak(brk, val)) {
nval -= (val - brk.from);
break;
}
}
return nval;
}
function breakLin2Val(val) {
var nval = val,
brk,
i;
for (i = 0; i < axis.breakArray.length; i++) {
brk = axis.breakArray[i];
if (brk.from >= nval) {
break;
} else if (brk.to < nval) {
nval += brk.len;
} else if (axis.isInBreak(brk, nval)) {
nval += brk.len;
}
}
return nval;
}
axis.isDirty = axis.isBroken !== isBroken;
axis.isBroken = isBroken;
axis.options.breaks = axis.userOptions.breaks = breaks;
axis.forceRedraw = true; // Force recalculation in setScale
if (!isBroken && axis.val2lin === breakVal2Lin) {
// Revert to prototype functions
delete axis.val2lin;
delete axis.lin2val;
}
if (isBroken) {
axis.userOptions.ordinal = false;
axis.val2lin = breakVal2Lin;
axis.lin2val = breakLin2Val;
axis.setExtremes = function (
newMin,