highcharts
Version:
JavaScript charting framework
1,258 lines (1,250 loc) • 99.8 kB
JavaScript
/**
* @license Highcharts JS v9.0.1 (2021-02-16)
*
* (c) 2014-2021 Highsoft AS
* Authors: Jon Arild Nygard / Oystein Moseng
*
* License: www.highcharts.com/license
*/
'use strict';
(function (factory) {
if (typeof module === 'object' && module.exports) {
factory['default'] = factory;
module.exports = factory;
} else if (typeof define === 'function' && define.amd) {
define('highcharts/modules/treemap', ['highcharts'], function (Highcharts) {
factory(Highcharts);
factory.Highcharts = Highcharts;
return factory;
});
} else {
factory(typeof Highcharts !== 'undefined' ? Highcharts : undefined);
}
}(function (Highcharts) {
var _modules = Highcharts ? Highcharts._modules : {};
function _registerModule(obj, path, args, fn) {
if (!obj.hasOwnProperty(path)) {
obj[path] = fn.apply(null, args);
}
}
_registerModule(_modules, 'Mixins/ColorMapSeries.js', [_modules['Core/Globals.js'], _modules['Core/Series/Point.js'], _modules['Core/Utilities.js']], function (H, Point, U) {
/* *
*
* (c) 2010-2021 Torstein Honsi
*
* License: www.highcharts.com/license
*
* !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!!
*
* */
var defined = U.defined;
var noop = H.noop,
seriesTypes = H.seriesTypes;
/**
* Mixin for maps and heatmaps
*
* @private
* @mixin Highcharts.colorMapPointMixin
*/
var colorMapPointMixin = {
dataLabelOnNull: true,
/* eslint-disable valid-jsdoc */
/**
* Color points have a value option that determines whether or not it is
* a null point
* @private
*/
isValid: function () {
// undefined is allowed
return (this.value !== null &&
this.value !== Infinity &&
this.value !== -Infinity);
},
/**
* @private
*/
setState: function (state) {
Point.prototype.setState.call(this, state);
if (this.graphic) {
this.graphic.attr({
zIndex: state === 'hover' ? 1 : 0
});
}
}
/* eslint-enable valid-jsdoc */
};
/**
* @private
* @mixin Highcharts.colorMapSeriesMixin
*/
var colorMapSeriesMixin = {
pointArrayMap: ['value'],
axisTypes: ['xAxis', 'yAxis', 'colorAxis'],
trackerGroups: ['group', 'markerGroup', 'dataLabelsGroup'],
getSymbol: noop,
parallelArrays: ['x', 'y', 'value'],
colorKey: 'value',
pointAttribs: seriesTypes.column.prototype.pointAttribs,
/* eslint-disable valid-jsdoc */
/**
* Get the color attibutes to apply on the graphic
* @private
* @function Highcharts.colorMapSeriesMixin.colorAttribs
* @param {Highcharts.Point} point
* @return {Highcharts.SVGAttributes}
*/
colorAttribs: function (point) {
var ret = {};
if (defined(point.color)) {
ret[this.colorProp || 'fill'] = point.color;
}
return ret;
}
};
var exports = {
colorMapPointMixin: colorMapPointMixin,
colorMapSeriesMixin: colorMapSeriesMixin
};
return exports;
});
_registerModule(_modules, 'Series/Treemap/TreemapAlgorithmGroup.js', [], function () {
/* *
*
* (c) 2014-2021 Highsoft AS
*
* Authors: Jon Arild Nygard / Oystein Moseng
*
* License: www.highcharts.com/license
*
* !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!!
*
* */
/* *
*
* Class
*
* */
var TreemapAlgorithmGroup = /** @class */ (function () {
/* *
*
* Constructor
*
* */
function TreemapAlgorithmGroup(h, w, d, p) {
this.height = h;
this.width = w;
this.plot = p;
this.direction = d;
this.startDirection = d;
this.total = 0;
this.nW = 0;
this.lW = 0;
this.nH = 0;
this.lH = 0;
this.elArr = [];
this.lP = {
total: 0,
lH: 0,
nH: 0,
lW: 0,
nW: 0,
nR: 0,
lR: 0,
aspectRatio: function (w, h) {
return Math.max((w / h), (h / w));
}
};
}
/* *
*
* Functions
*
* */
/* eslint-disable valid-jsdoc */
TreemapAlgorithmGroup.prototype.addElement = function (el) {
this.lP.total = this.elArr[this.elArr.length - 1];
this.total = this.total + el;
if (this.direction === 0) {
// Calculate last point old aspect ratio
this.lW = this.nW;
this.lP.lH = this.lP.total / this.lW;
this.lP.lR = this.lP.aspectRatio(this.lW, this.lP.lH);
// Calculate last point new aspect ratio
this.nW = this.total / this.height;
this.lP.nH = this.lP.total / this.nW;
this.lP.nR = this.lP.aspectRatio(this.nW, this.lP.nH);
}
else {
// Calculate last point old aspect ratio
this.lH = this.nH;
this.lP.lW = this.lP.total / this.lH;
this.lP.lR = this.lP.aspectRatio(this.lP.lW, this.lH);
// Calculate last point new aspect ratio
this.nH = this.total / this.width;
this.lP.nW = this.lP.total / this.nH;
this.lP.nR = this.lP.aspectRatio(this.lP.nW, this.nH);
}
this.elArr.push(el);
};
TreemapAlgorithmGroup.prototype.reset = function () {
this.nW = 0;
this.lW = 0;
this.elArr = [];
this.total = 0;
};
return TreemapAlgorithmGroup;
}());
/* *
*
* Default Export
*
* */
return TreemapAlgorithmGroup;
});
_registerModule(_modules, 'Mixins/DrawPoint.js', [], function () {
/* *
*
* !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!!
*
* */
var isFn = function (x) {
return typeof x === 'function';
};
/* eslint-disable no-invalid-this, valid-jsdoc */
/**
* Handles the drawing of a component.
* Can be used for any type of component that reserves the graphic property, and
* provides a shouldDraw on its context.
*
* @private
* @function draw
* @param {DrawPointParams} params
* Parameters.
*
* @todo add type checking.
* @todo export this function to enable usage
*/
var draw = function draw(params) {
var _a;
var component = this,
graphic = component.graphic,
animatableAttribs = params.animatableAttribs,
onComplete = params.onComplete,
css = params.css,
renderer = params.renderer,
animation = (_a = component.series) === null || _a === void 0 ? void 0 : _a.options.animation;
if (component.shouldDraw()) {
if (!graphic) {
component.graphic = graphic =
renderer[params.shapeType](params.shapeArgs)
.add(params.group);
}
graphic
.css(css)
.attr(params.attribs)
.animate(animatableAttribs, params.isNew ? false : animation, onComplete);
}
else if (graphic) {
var destroy = function () {
component.graphic = graphic = graphic.destroy();
if (isFn(onComplete)) {
onComplete();
}
};
// animate only runs complete callback if something was animated.
if (Object.keys(animatableAttribs).length) {
graphic.animate(animatableAttribs, void 0, function () {
destroy();
});
}
else {
destroy();
}
}
};
/**
* An extended version of draw customized for points.
* It calls additional methods that is expected when rendering a point.
* @private
* @param {Highcharts.Dictionary<any>} params Parameters
*/
var drawPoint = function drawPoint(params) {
var point = this,
attribs = params.attribs = params.attribs || {};
// Assigning class in dot notation does go well in IE8
// eslint-disable-next-line dot-notation
attribs['class'] = point.getClassName();
// Call draw to render component
draw.call(point, params);
};
var drawPointModule = {
draw: draw,
drawPoint: drawPoint,
isFn: isFn
};
return drawPointModule;
});
_registerModule(_modules, 'Series/Treemap/TreemapPoint.js', [_modules['Mixins/DrawPoint.js'], _modules['Core/Series/SeriesRegistry.js'], _modules['Core/Utilities.js']], function (DrawPointMixin, SeriesRegistry, U) {
/* *
*
* (c) 2014-2021 Highsoft AS
*
* Authors: Jon Arild Nygard / Oystein Moseng
*
* License: www.highcharts.com/license
*
* !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!!
*
* */
var __extends = (this && this.__extends) || (function () {
var extendStatics = function (d,
b) {
extendStatics = Object.setPrototypeOf ||
({ __proto__: [] } instanceof Array && function (d,
b) { d.__proto__ = b; }) ||
function (d,
b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; };
return extendStatics(d, b);
};
return function (d, b) {
extendStatics(d, b);
function __() { this.constructor = d; }
d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
};
})();
var Point = SeriesRegistry.series.prototype.pointClass,
_a = SeriesRegistry.seriesTypes,
PiePoint = _a.pie.prototype.pointClass,
ScatterPoint = _a.scatter.prototype.pointClass;
var extend = U.extend,
isNumber = U.isNumber,
pick = U.pick;
/* *
*
* Class
*
* */
var TreemapPoint = /** @class */ (function (_super) {
__extends(TreemapPoint, _super);
function TreemapPoint() {
/* *
*
* Properties
*
* */
var _this = _super !== null && _super.apply(this,
arguments) || this;
_this.name = void 0;
_this.node = void 0;
_this.options = void 0;
_this.series = void 0;
_this.value = void 0;
return _this;
/* eslint-enable valid-jsdoc */
}
/* *
*
* Functions
*
* */
/* eslint-disable valid-jsdoc */
TreemapPoint.prototype.getClassName = function () {
var className = Point.prototype.getClassName.call(this),
series = this.series,
options = series.options;
// Above the current level
if (this.node.level <= series.nodeMap[series.rootNode].level) {
className += ' highcharts-above-level';
}
else if (!this.node.isLeaf &&
!pick(options.interactByLeaf, !options.allowTraversingTree)) {
className += ' highcharts-internal-node-interactive';
}
else if (!this.node.isLeaf) {
className += ' highcharts-internal-node';
}
return className;
};
/**
* A tree point is valid if it has han id too, assume it may be a parent
* item.
*
* @private
* @function Highcharts.Point#isValid
*/
TreemapPoint.prototype.isValid = function () {
return Boolean(this.id || isNumber(this.value));
};
TreemapPoint.prototype.setState = function (state) {
Point.prototype.setState.call(this, state);
// Graphic does not exist when point is not visible.
if (this.graphic) {
this.graphic.attr({
zIndex: state === 'hover' ? 1 : 0
});
}
};
TreemapPoint.prototype.shouldDraw = function () {
return isNumber(this.plotY) && this.y !== null;
};
return TreemapPoint;
}(ScatterPoint));
extend(TreemapPoint.prototype, {
draw: DrawPointMixin.drawPoint,
setVisible: PiePoint.prototype.setVisible
});
/* *
*
* Default Export
*
* */
return TreemapPoint;
});
_registerModule(_modules, 'Series/Treemap/TreemapUtilities.js', [_modules['Core/Utilities.js']], function (U) {
/* *
*
* (c) 2014-2021 Highsoft AS
*
* Authors: Jon Arild Nygard / Oystein Moseng
*
* License: www.highcharts.com/license
*
* !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!!
*
* */
/* *
*
* Imports
*
* */
var objectEach = U.objectEach;
/* *
*
* Namespace
*
* */
var TreemapUtilities;
(function (TreemapUtilities) {
TreemapUtilities.AXIS_MAX = 100;
/* eslint-disable no-invalid-this, valid-jsdoc */
/**
* @todo Similar to eachObject, this function is likely redundant
*/
function isBoolean(x) {
return typeof x === 'boolean';
}
TreemapUtilities.isBoolean = isBoolean;
/**
* @todo Similar to recursive, this function is likely redundant
*/
function eachObject(list, func, context) {
context = context || this;
objectEach(list, function (val, key) {
func.call(context, val, key, list);
});
}
TreemapUtilities.eachObject = eachObject;
/**
* @todo find correct name for this function.
* @todo Similar to reduce, this function is likely redundant
*/
function recursive(item, func, context) {
if (context === void 0) { context = this; }
var next;
next = func.call(context, item);
if (next !== false) {
recursive(next, func, context);
}
}
TreemapUtilities.recursive = recursive;
})(TreemapUtilities || (TreemapUtilities = {}));
/* *
*
* Default Export
*
* */
return TreemapUtilities;
});
_registerModule(_modules, 'Mixins/TreeSeries.js', [_modules['Core/Color/Color.js'], _modules['Core/Utilities.js']], function (Color, U) {
/* *
*
* !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!!
*
* */
var extend = U.extend,
isArray = U.isArray,
isNumber = U.isNumber,
isObject = U.isObject,
merge = U.merge,
pick = U.pick;
var isBoolean = function (x) {
return typeof x === 'boolean';
}, isFn = function (x) {
return typeof x === 'function';
};
/* eslint-disable valid-jsdoc */
/**
* @todo Combine buildTree and buildNode with setTreeValues
* @todo Remove logic from Treemap and make it utilize this mixin.
* @private
*/
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
tree.children.forEach(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;
};
/**
* @private
*/
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,
chartOptionsChart = series.chart.options.chart,
point,
level,
colorByPoint,
colorIndexByPoint,
color,
colorIndex;
/**
* @private
*/
function variation(color) {
var colorVariation = level && level.colorVariation;
if (colorVariation) {
if (colorVariation.key === 'brightness') {
return Color.parse(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 :
chartOptionsChart.colorCount);
colorByPoint = colors && colors[colorIndexByPoint];
}
// Select either point color, level color or inherited color.
if (!series.chart.styledMode) {
color = pick(point && point.options.color, level && level.color, colorByPoint, parentColor && variation(parentColor), series.color);
}
colorIndex = pick(point && point.options.colorIndex, level && level.colorIndex, colorIndexByPoint, parentColorIndex, options.colorIndex);
}
return {
color: color,
colorIndex: colorIndex
};
};
/**
* Creates a map from level number to its given options.
*
* @private
* @function getLevelOptions
* @param {object} params
* Object containing parameters.
* - `defaults` Object containing default options. The default options
* are merged with the userOptions to get the final options for a
* specific level.
* - `from` The lowest level number.
* - `levels` User options from series.levels.
* - `to` The highest level number.
* @return {Highcharts.Dictionary<object>|null}
* Returns a map from level number to its given options.
*/
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 = levels.reduce(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.
*
* @private
* @function updateRootId
*
* @param {object} series
* The series to operate on.
*
* @return {string}
* 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;
});
_registerModule(_modules, 'Series/Treemap/TreemapComposition.js', [_modules['Core/Series/SeriesRegistry.js'], _modules['Series/Treemap/TreemapUtilities.js'], _modules['Core/Utilities.js']], function (SeriesRegistry, TreemapUtilities, U) {
/* *
*
* (c) 2014-2021 Highsoft AS
*
* Authors: Jon Arild Nygard / Oystein Moseng
*
* License: www.highcharts.com/license
*
* !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!!
*
* */
/* *
*
* Imports
*
* */
var Series = SeriesRegistry.series;
var addEvent = U.addEvent,
extend = U.extend;
/* *
*
* Composition
*
* */
var treemapAxisDefaultValues = false;
addEvent(Series, 'afterBindAxes', function () {
// eslint-disable-next-line no-invalid-this
var series = this,
xAxis = series.xAxis,
yAxis = series.yAxis,
treeAxis;
if (xAxis && yAxis) {
if (series.is('treemap')) {
treeAxis = {
endOnTick: false,
gridLineWidth: 0,
lineWidth: 0,
min: 0,
dataMin: 0,
minPadding: 0,
max: TreemapUtilities.AXIS_MAX,
dataMax: TreemapUtilities.AXIS_MAX,
maxPadding: 0,
startOnTick: false,
title: null,
tickPositions: []
};
extend(yAxis.options, treeAxis);
extend(xAxis.options, treeAxis);
treemapAxisDefaultValues = true;
}
else if (treemapAxisDefaultValues) {
yAxis.setOptions(yAxis.userOptions);
xAxis.setOptions(xAxis.userOptions);
treemapAxisDefaultValues = false;
}
}
});
});
_registerModule(_modules, 'Series/Treemap/TreemapSeries.js', [_modules['Core/Color/Color.js'], _modules['Mixins/ColorMapSeries.js'], _modules['Core/Globals.js'], _modules['Mixins/LegendSymbol.js'], _modules['Core/Color/Palette.js'], _modules['Core/Series/SeriesRegistry.js'], _modules['Series/Treemap/TreemapAlgorithmGroup.js'], _modules['Series/Treemap/TreemapPoint.js'], _modules['Series/Treemap/TreemapUtilities.js'], _modules['Mixins/TreeSeries.js'], _modules['Core/Utilities.js']], function (Color, ColorMapMixin, H, LegendSymbolMixin, palette, SeriesRegistry, TreemapAlgorithmGroup, TreemapPoint, TreemapUtilities, TreeSeriesMixin, U) {
/* *
*
* (c) 2014-2021 Highsoft AS
*
* Authors: Jon Arild Nygard / Oystein Moseng
*
* License: www.highcharts.com/license
*
* !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!!
*
* */
var __extends = (this && this.__extends) || (function () {
var extendStatics = function (d,
b) {
extendStatics = Object.setPrototypeOf ||
({ __proto__: [] } instanceof Array && function (d,
b) { d.__proto__ = b; }) ||
function (d,
b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; };
return extendStatics(d, b);
};
return function (d, b) {
extendStatics(d, b);
function __() { this.constructor = d; }
d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
};
})();
var color = Color.parse;
var colorMapSeriesMixin = ColorMapMixin.colorMapSeriesMixin;
var noop = H.noop;
var Series = SeriesRegistry.series,
_a = SeriesRegistry.seriesTypes,
ColumnSeries = _a.column,
HeatmapSeries = _a.heatmap,
ScatterSeries = _a.scatter;
var getColor = TreeSeriesMixin.getColor,
getLevelOptions = TreeSeriesMixin.getLevelOptions,
updateRootId = TreeSeriesMixin.updateRootId;
var addEvent = U.addEvent,
correctFloat = U.correctFloat,
defined = U.defined,
error = U.error,
extend = U.extend,
fireEvent = U.fireEvent,
isArray = U.isArray,
isObject = U.isObject,
isString = U.isString,
merge = U.merge,
pick = U.pick,
stableSort = U.stableSort;
/* *
*
* Class
*
* */
/**
* @private
* @class
* @name Highcharts.seriesTypes.treemap
*
* @augments Highcharts.Series
*/
var TreemapSeries = /** @class */ (function (_super) {
__extends(TreemapSeries, _super);
function TreemapSeries() {
/* *
*
* Static Properties
*
* */
var _this = _super !== null && _super.apply(this,
arguments) || this;
/* *
*
* Properties
*
* */
_this.axisRatio = void 0;
_this.data = void 0;
_this.mapOptionsToLevel = void 0;
_this.nodeMap = void 0;
_this.options = void 0;
_this.points = void 0;
_this.rootNode = void 0;
_this.tree = void 0;
return _this;
/* eslint-enable valid-jsdoc */
}
/* *
*
* Function
*
* */
/* eslint-disable valid-jsdoc */
TreemapSeries.prototype.algorithmCalcPoints = function (directionChange, last, group, childrenArea) {
var pX,
pY,
pW,
pH,
gW = group.lW,
gH = group.lH,
plot = group.plot,
keep,
i = 0,
end = group.elArr.length - 1;
if (last) {
gW = group.nW;
gH = group.nH;
}
else {
keep = group.elArr[group.elArr.length - 1];
}
group.elArr.forEach(function (p) {
if (last || (i < end)) {
if (group.direction === 0) {
pX = plot.x;
pY = plot.y;
pW = gW;
pH = p / pW;
}
else {
pX = plot.x;
pY = plot.y;
pH = gH;
pW = p / pH;
}
childrenArea.push({
x: pX,
y: pY,
width: pW,
height: correctFloat(pH)
});
if (group.direction === 0) {
plot.y = plot.y + pH;
}
else {
plot.x = plot.x + pW;
}
}
i = i + 1;
});
// Reset variables
group.reset();
if (group.direction === 0) {
group.width = group.width - gW;
}
else {
group.height = group.height - gH;
}
plot.y = plot.parent.y + (plot.parent.height - group.height);
plot.x = plot.parent.x + (plot.parent.width - group.width);
if (directionChange) {
group.direction = 1 - group.direction;
}
// If not last, then add uncalculated element
if (!last) {
group.addElement(keep);
}
};
TreemapSeries.prototype.algorithmFill = function (directionChange, parent, children) {
var childrenArea = [],
pTot,
direction = parent.direction,
x = parent.x,
y = parent.y,
width = parent.width,
height = parent.height,
pX,
pY,
pW,
pH;
children.forEach(function (child) {
pTot =
(parent.width * parent.height) * (child.val / parent.val);
pX = x;
pY = y;
if (direction === 0) {
pH = height;
pW = pTot / pH;
width = width - pW;
x = x + pW;
}
else {
pW = width;
pH = pTot / pW;
height = height - pH;
y = y + pH;
}
childrenArea.push({
x: pX,
y: pY,
width: pW,
height: pH
});
if (directionChange) {
direction = 1 - direction;
}
});
return childrenArea;
};
TreemapSeries.prototype.algorithmLowAspectRatio = function (directionChange, parent, children) {
var childrenArea = [],
series = this,
pTot,
plot = {
x: parent.x,
y: parent.y,
parent: parent
},
direction = parent.direction,
i = 0,
end = children.length - 1,
group = new TreemapAlgorithmGroup(parent.height,
parent.width,
direction,
plot);
// Loop through and calculate all areas
children.forEach(function (child) {
pTot =
(parent.width * parent.height) * (child.val / parent.val);
group.addElement(pTot);
if (group.lP.nR > group.lP.lR) {
series.algorithmCalcPoints(directionChange, false, group, childrenArea, plot // @todo no supported
);
}
// If last child, then calculate all remaining areas
if (i === end) {
series.algorithmCalcPoints(directionChange, true, group, childrenArea, plot // @todo not supported
);
}
i = i + 1;
});
return childrenArea;
};
/**
* Over the alignment method by setting z index.
* @private
*/
TreemapSeries.prototype.alignDataLabel = function (point, dataLabel, labelOptions) {
var style = labelOptions.style;
// #8160: Prevent the label from exceeding the point's
// boundaries in treemaps by applying ellipsis overflow.
// The issue was happening when datalabel's text contained a
// long sequence of characters without a whitespace.
if (!defined(style.textOverflow) &&
dataLabel.text &&
dataLabel.getBBox().width > dataLabel.text.textWidth) {
dataLabel.css({
textOverflow: 'ellipsis',
// unit (px) is required when useHTML is true
width: style.width += 'px'
});
}
ColumnSeries.prototype.alignDataLabel.apply(this, arguments);
if (point.dataLabel) {
// point.node.zIndex could be undefined (#6956)
point.dataLabel.attr({ zIndex: (point.node.zIndex || 0) + 1 });
}
};
TreemapSeries.prototype.buildNode = function (id, i, level, list, parent) {
var series = this,
children = [],
point = series.points[i],
height = 0,
node,
child;
// Actions
((list[id] || [])).forEach(function (i) {
child = series.buildNode(series.points[i].id, i, (level + 1), list, id);
height = Math.max(child.height + 1, height);
children.push(child);
});
node = {
id: id,
i: i,
children: children,
height: height,
level: level,
parent: parent,
visible: false // @todo move this to better location
};
series.nodeMap[node.id] = node;
if (point) {
point.node = node;
}
return node;
};
/**
* Recursive function which calculates the area for all children of a
* node.
*
* @private
* @function Highcharts.Series#calculateChildrenAreas
*
* @param {object} node
* The node which is parent to the children.
*
* @param {object} area
* The rectangular area of the parent.
*/
TreemapSeries.prototype.calculateChildrenAreas = function (parent, area) {
var series = this,
options = series.options,
mapOptionsToLevel = series.mapOptionsToLevel,
level = mapOptionsToLevel[parent.level + 1],
algorithm = pick((series[(level && level.layoutAlgorithm)] &&
level.layoutAlgorithm),
options.layoutAlgorithm),
alternate = options.alternateStartingDirection,
childrenValues = [],
children;
// Collect all children which should be included
children = parent.children.filter(function (n) {
return !n.ignore;
});
if (level && level.layoutStartingDirection) {
area.direction = level.layoutStartingDirection === 'vertical' ?
0 :
1;
}
childrenValues = series[algorithm](area, children);
children.forEach(function (child, index) {
var values = childrenValues[index];
child.values = merge(values, {
val: child.childrenTotal,
direction: (alternate ? 1 - area.direction : area.direction)
});
child.pointValues = merge(values, {
x: (values.x / series.axisRatio),
// Flip y-values to avoid visual regression with csvCoord in
// Axis.translate at setPointValues. #12488
y: TreemapUtilities.AXIS_MAX - values.y - values.height,
width: (values.width / series.axisRatio)
});
// If node has children, then call method recursively
if (child.children.length) {
series.calculateChildrenAreas(child, child.values);
}
});
};
/**
* Extend drawDataLabels with logic to handle custom options related to
* the treemap series:
*
* - Points which is not a leaf node, has dataLabels disabled by
* default.
*
* - Options set on series.levels is merged in.
*
* - Width of the dataLabel is set to match the width of the point
* shape.
*
* @private
*/
TreemapSeries.prototype.drawDataLabels = function () {
var series = this,
mapOptionsToLevel = series.mapOptionsToLevel,
points = series.points.filter(function (n) {
return n.node.visible;
}), options, level;
points.forEach(function (point) {
level = mapOptionsToLevel[point.node.level];
// Set options to new object to avoid problems with scope
options = { style: {} };
// If not a leaf, then label should be disabled as default
if (!point.node.isLeaf) {
options.enabled = false;
}
// If options for level exists, include them as well
if (level && level.dataLabels) {
options = merge(options, level.dataLabels);
series._hasPointLabels = true;
}
// Set dataLabel width to the width of the point shape.
if (point.shapeArgs) {
options.style.width = point.shapeArgs.width;
if (point.dataLabel) {
point.dataLabel.css({
width: point.shapeArgs.width + 'px'
});
}
}
// Merge custom options with point options
point.dlOptions = merge(options, point.options.dataLabels);
});
Series.prototype.drawDataLabels.call(this);
};
/**
* Override drawPoints
* @private
*/
TreemapSeries.prototype.drawPoints = function () {
var series = this,
chart = series.chart,
renderer = chart.renderer,
points = series.points,
styledMode = chart.styledMode,
options = series.options,
shadow = styledMode ? {} : options.shadow,
borderRadius = options.borderRadius,
withinAnimationLimit = chart.pointCount < options.animationLimit,
allowTraversingTree = options.allowTraversingTree;
points.forEach(function (point) {
var levelDynamic = point.node.levelDynamic,
animatableAttribs = {},
attribs = {},
css = {},
groupKey = 'level-group-' + point.node.level,
hasGraphic = !!point.graphic,
shouldAnimate = withinAnimationLimit && hasGraphic,
shapeArgs = point.shapeArgs;
// Don't bother with calculate styling if the point is not drawn
if (point.shouldDraw()) {
if (borderRadius) {
attribs.r = borderRadius;
}
merge(true, // Extend object
// Which object to extend
shouldAnimate ? animatableAttribs : attribs,
// Add shapeArgs to animate/attr if graphic exists
hasGraphic ? shapeArgs : {},
// Add style attribs if !styleMode
styledMode ?
{} :
series.pointAttribs(point, point.selected ? 'select' : void 0));
// In styled mode apply point.color. Use CSS, otherwise the
// fill used in the style sheet will take precedence over
// the fill attribute.
if (series.colorAttribs && styledMode) {
// Heatmap is loaded
extend(css, series.colorAttribs(point));
}
if (!series[groupKey]) {
series[groupKey] = renderer.g(groupKey)
.attr({
// @todo Set the zIndex based upon the number of
// levels, instead of using 1000
zIndex: 1000 - (levelDynamic || 0)
})
.add(series.group);
series[groupKey].survive = true;
}
}
// Draw the point
point.draw({
animatableAttribs: animatableAttribs,
attribs: attribs,
css: css,
group: series[groupKey],
renderer: renderer,
shadow: shadow,
shapeArgs: shapeArgs,
shapeType: 'rect'
});
// If setRootNode is allowed, set a point cursor on clickables &
// add drillId to point
if (allowTraversingTree && point.graphic) {
point.drillId = options.interactByLeaf ?
series.drillToByLeaf(point) :
series.drillToByGroup(point);
}
});
};
/**
* Finds the drill id for a parent node. Returns false if point should
* not have a click event.
* @private
*/
TreemapSeries.prototype.drillToByGroup = function (point) {
var series = this,
drillId = false;
if ((point.node.level - series.nodeMap[series.rootNode].level) ===
1 &&
!point.node.isLeaf) {
drillId = point.id;
}
return drillId;
};
/**
* Finds the drill id for a leaf node. Returns false if point should not
* have a click event
* @private
*/
TreemapSeries.prototype.drillToByLeaf = function (point) {
var series = this,
drillId = false,
nodeParent;
if ((point.node.parent !== series.rootNode) &&
point.node.isLeaf) {
nodeParent = point.node;
while (!drillId) {
nodeParent = series.nodeMap[nodeParent.parent];
if (nodeParent.parent === series.rootNode) {
drillId = nodeParent.id;
}