highcharts-tree
Version:
Create tree type in Highcharts
590 lines • 26.7 kB
JavaScript
"use strict";
var __assign = (this && this.__assign) || function () {
__assign = Object.assign || function(t) {
for (var s, i = 1, n = arguments.length; i < n; i++) {
s = arguments[i];
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p))
t[p] = s[p];
}
return t;
};
return __assign.apply(this, arguments);
};
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
var Tree_1 = __importDefault(require("./Tree"));
var SHAPE_OFFSET = 3;
var NODE_WIDTH_OFFSET = SHAPE_OFFSET * 2;
// @types/highcharts not support some method
exports.default = (function (Highcharts) {
var highchartMajorVersion = Highcharts.version.split(".")[0];
var defaultConfig = {
horizontal: true,
disableToggle: false,
node: {
width: 200,
height: 0,
marginX: 20,
marginY: 20,
textColor: "#454d59",
backgroundColor: "#f2f2f2",
backgroundColorToggle: "#cccccc",
title: {
marginTop: 4,
marginDown: 4
},
padding: {
x: 1.5,
y: 1.5
},
content: {
align: "left"
},
hover: {
backgroundColor: "#d6d6d6",
backgroundColorToggle: "#cccccc"
},
border: {
width: 3,
color: "#919191"
},
dataFormatter: function (data) {
return data.toLocaleString();
}
},
row: {
height: 20,
line: 2
},
tooltip: {
enabled: false,
backgroundColor: "rgba(0,0,0,0.6)",
borderRadius: "3px",
textColor: "#fff",
width: 0,
tooltipFormatter: function (item) {
return ("" + item.content.title +
(item.content.data ? "<br>" + item.content.data.join("<br>") : ""));
}
},
connector: {
color: "#bcbcbc",
width: 4
},
legend: {
enabled: true,
nodeWidth: 20,
marginX: 10,
marginY: 25
}
};
var seriesType = Highcharts.seriesType, seriesTypes = Highcharts.seriesTypes, each = Highcharts.each, merge = Highcharts.merge;
seriesType("tree", "pie", {
config: defaultConfig
}, {
init: function () {
var _this = this;
Highcharts.addEvent(Highcharts.Chart, "redraw", function (e) {
var chart = e.target;
if (chart !== _this.chart)
return;
// ignore setSize
if (_this._skipTranslate) {
_this._skipTranslate = false;
return;
}
_this._tree.build();
_this.translate();
});
Highcharts.addEvent(Highcharts.Chart, "destroy", function (e) {
var chart = e.target;
if (chart !== _this.chart)
return;
Highcharts.removeEvent(chart, "redraw");
Highcharts.removeEvent(chart, "destroy");
});
seriesTypes.pie.prototype.init.apply(this, arguments);
var data = this.options.data;
if (data === null ||
typeof data === "undefined" ||
typeof data.tree === "undefined" ||
data.tree === null) {
return;
}
this._config = merge({}, this.options.config, this.chart.userOptions.chart.config);
this._tree = Tree_1.default.getTree({
tree: this._tree,
data: this.options.data.tree,
horizontal: this._config.horizontal
});
},
translate: function () {
var _this = this;
var data = this.options.data;
var ren = this.chart.renderer;
var config = this._config;
config.node.height = config.node.height || this._treeComputedNodeHeight || 0; // 0 is false, and _treeComputedNodeHeight is undefined initial
var colors = this.chart.options.colors;
var maxX = 0;
var maxY = 0;
var elements = this._elements;
var staticProps = ren.styledMode
? {
titleCss: {},
textCss: {},
tooltipStr: {
style: "",
class: "class=\"highcharts-tree-node-tooltip-inner\""
},
tooltipAttr: {
class: "highcharts-tree-node-tooltip"
},
boxAttr: { zIndex: 0 },
connectorAttr: {
class: "highcharts-tree-connector"
}
}
: {
titleCss: {
pointerEvents: "none",
fontSize: "14px",
color: config.node.textColor,
fontWeight: "bold",
textAlign: "center"
},
textCss: {
fontSize: "13px",
color: config.node.textColor,
textOverflow: "ellipsis",
pointerEvents: "none"
},
tooltipStr: {
style: "border-radius: " + config.tooltip.borderRadius + ";\nbackground-color: " + config.tooltip.backgroundColor + ";\ncolor: " + config.tooltip.textColor + ";\nfont-size: 12px;\nwhite-space: initial;\nword-break: break-word;",
class: ""
},
tooltipAttr: {
opacity: 0
},
boxAttr: {
zIndex: 0,
stroke: config.node.border.color,
"stroke-width": config.node.border.width // hyphenated
},
connectorAttr: {
"stroke-width": config.connector.width,
stroke: config.connector.color
}
};
if (!elements)
this._elements = elements = [];
var drawNode = function (node) {
var box = {
x: node.x * (config.node.width + config.node.marginX) +
config.node.border.width / 2,
y: node.y * (config.node.height + config.node.marginY) +
_this._titleOffsetY,
w: config.node.width,
h: config.node.height
};
// title, needs to be added to getBBox()
var titleElement = ren
.label(node.item.content.title, box.x + config.node.padding.x, box.y + config.node.padding.y + config.node.title.marginTop, "rect", undefined, undefined, undefined, undefined, "tree-node-title")
.attr({ zIndex: 1 })
.add();
/**
* in this line, highcharts will get the computed style
* so add it to RenderObject first
* @link
* https://github.com/highcharts/highcharts/blob/master/ts/parts/SvgRenderer.ts#L3716
*/
titleElement.css(__assign({}, staticProps.titleCss, { width: box.w - NODE_WIDTH_OFFSET - config.node.padding.x * 2, textOverflow: "ellipsis" }));
// - center it
titleElement.attr({ x: box.x + box.w / 2 - titleElement.width / 2 });
elements.push(titleElement);
// rows
var offsetTextByLegend = config.legend.enabled
? config.legend.nodeWidth
: 0;
var rowsY = titleElement.height +
config.node.title.marginTop +
config.node.title.marginDown;
if (node.item.content.data) {
for (var i = 0; i < node.item.content.data.length; i++) {
if (config.legend.enabled) {
// legend box
elements.push(ren
.rect(box.x + config.node.padding.x, box.y + rowsY + config.row.height * i, config.legend.nodeWidth, config.row.height)
.css({ pointerEvents: "none" })
.attr(ren.styledMode
? { class: "highcharts-tree-legend-" + i, zIndex: 1 }
: { fill: colors[i], zIndex: 1 }));
}
var text = config.node.dataFormatter(node.item.content.data[i], i);
var textAlign = config.node.content.align;
var textElementWidth = box.w -
offsetTextByLegend -
NODE_WIDTH_OFFSET -
config.node.padding.x * 2;
var computedWidth = textAlign === "center" ? undefined : textElementWidth;
var textElement = ren
.label(text, box.x + offsetTextByLegend + config.node.padding.x, box.y + rowsY + config.row.height * i, "rect", undefined, undefined, undefined, undefined, "tree-node-data")
.attr({
zIndex: 1,
width: computedWidth
})
.add();
textElement.css(__assign({}, staticProps.textCss, { width: computedWidth }));
// - allign right
// textElement.attr({x: box.x + box.w - textElement.width - config.row.marginX});
if (textAlign === "center") {
textElement.attr({
x: box.x + box.w / 2 - textElement.width / 2
});
}
elements.push(textElement);
}
}
// calculate node height(if not set) based on rendered content
var needRedraw = false;
if (typeof config.node.height === "undefined" ||
config.node.height === null ||
config.node.height < 1) {
config.node.height = box.h =
rowsY +
config.row.line * config.row.height +
config.node.padding.y;
_this._treeComputedNodeHeight = config.node.height;
needRedraw = true;
}
if (node === _this._tree.root) {
// region resize
var _a = _this.chart.userOptions.chart, width = _a.width, height = _a.height;
if (!width || !height) {
var changed = false;
var curWidth = _this._tree.root.width *
(config.node.width + config.node.marginX) +
config.node.width +
config.node.border.width;
var curHeight = _this._tree.root.height *
(config.node.height + config.node.marginY) +
config.node.height +
config.legend.marginY +
config.row.height +
_this._titleOffsetY;
if (!width && curWidth !== _this.chart.chartWidth) {
changed = true;
_this.chart.renderTo.style.width = curWidth + "px";
}
if (!height && curHeight !== _this.chart.chartHeight) {
changed = true;
_this.chart.renderTo.style.height = curHeight + "px";
}
if (changed) {
needRedraw = false;
_this._elements = elements;
_this._skipTranslate = true; // setSize will trigger redraw event
_this.chart.setSize(curWidth, curHeight, false);
return false;
}
}
// endregion
}
if (needRedraw) {
_this.translate();
return false;
}
// main box
// console.log(box.x);
var tooltipElement = null;
if (config.tooltip.enabled) {
var tooltipX = box.x + box.w;
var tooltipW = config.tooltip.width || box.w;
if (tooltipX + tooltipW > _this.chart.chartWidth) {
tooltipX = box.x - tooltipW;
}
var styleStr = "\nmin-height: " + box.h + "px;\nmargin-left: -" + SHAPE_OFFSET + "px;\nmargin-top: -" + SHAPE_OFFSET + "px;\nwidth: " + tooltipW + "px;" + staticProps.tooltipStr.style;
tooltipElement = ren
.label("<div\n" + staticProps.tooltipStr.class + "\nstyle=\"" + styleStr + "\">" + config.tooltip.tooltipFormatter(node.item) + "</div>", tooltipX, box.y, "rect", undefined, undefined, true)
.css({
pointerEvents: "none"
})
.attr(__assign({}, staticProps.tooltipAttr, { zIndex: 5 }))
.add();
elements.push(tooltipElement);
}
var boxElement = ren
.rect(box.x, box.y, box.w, box.h, 3)
.css({
cursor: node.children.length < 1 ? "default" : "pointer"
})
.attr(__assign({}, staticProps.boxAttr, { id: node.item.id }, (ren.styledMode
? {
class: "highcharts-tree-box" + (node.toggle ? "" : " fold")
}
: {
fill: node.toggle
? config.node.backgroundColor
: config.node.backgroundColorToggle
})))
.on("mouseover", function () {
if (ren.styledMode) {
if (tooltipElement) {
tooltipElement.attr({
class: "highcharts-tree-node-tooltip hover"
});
}
return;
}
if (highchartMajorVersion > 5) {
boxElement.animate({
fill: node.toggle
? config.node.hover.backgroundColor
: config.node.hover.backgroundColorToggle
}, { duration: 300 });
}
else {
boxElement.attr({
fill: node.toggle
? config.node.hover.backgroundColor
: config.node.hover.backgroundColorToggle
});
}
if (tooltipElement) {
tooltipElement.animate({ opacity: 1 }, { duration: 300 });
}
})
.on("mouseout", function () {
if (ren.styledMode) {
if (tooltipElement) {
tooltipElement.attr({
class: "highcharts-tree-node-tooltip"
});
}
return;
}
if (highchartMajorVersion > 5) {
boxElement.animate({
fill: node.toggle
? config.node.backgroundColor
: config.node.backgroundColorToggle
}, { duration: 300 });
}
else {
boxElement.attr({
fill: node.toggle
? config.node.backgroundColor
: config.node.backgroundColorToggle
});
}
if (tooltipElement) {
tooltipElement.animate({ opacity: 0 }, { duration: 300 });
}
});
elements.push(boxElement);
if (node.children.length > 0 && !config.disableToggle) {
boxElement.on("click", function () {
node.toggle = !node.toggle;
_this._tree.build();
_this.translate();
});
}
// region draw connector line
// draw line to parent
if (node.parent != null) {
if (config.horizontal) {
elements.push(ren
.path([
"M",
box.x + box.w / 2,
box.y,
"L",
box.x + box.w / 2,
box.y - config.node.marginY / 2 - config.connector.width / 2
])
.attr(staticProps.connectorAttr));
}
else {
elements.push(ren
.path([
"M",
box.x,
box.y + box.h / 2,
"L",
box.x -
config.node.marginX / 2 -
config.connector.width / 2,
box.y + box.h / 2
])
.attr(staticProps.connectorAttr));
}
}
if (node.toggle) {
// draw line to children
if (node.children.length > 0) {
if (config.horizontal) {
var nodeBottomMiddle = {
x: box.x + box.w / 2,
y: box.y + box.h
};
elements.push(ren
.path([
"M",
nodeBottomMiddle.x,
nodeBottomMiddle.y,
"L",
nodeBottomMiddle.x,
nodeBottomMiddle.y + config.node.marginY / 2
])
.attr(staticProps.connectorAttr));
// draw line over children
if (node.children.length > 1) {
var offsetX = config.node.width + config.node.marginX;
var linePositionY = nodeBottomMiddle.y + config.node.marginY / 2;
elements.push(ren
.path([
"M",
node.getRightMostChild().x * offsetX +
config.node.width / 2 -
config.connector.width / 2 +
config.node.border.width / 2,
linePositionY,
"L",
node.getLeftMostChild().x * offsetX +
config.node.width / 2 +
config.connector.width / 2 +
config.node.border.width / 2,
linePositionY
])
.attr(staticProps.connectorAttr));
}
}
else {
var nodeRightMiddle = {
x: box.x + box.w,
y: box.y + box.h / 2
};
elements.push(ren
.path([
"M",
nodeRightMiddle.x,
nodeRightMiddle.y,
"L",
nodeRightMiddle.x + config.node.marginX / 2,
nodeRightMiddle.y
])
.attr(staticProps.connectorAttr));
// draw line over children
if (node.children.length > 1) {
var offsetY = config.node.height + config.node.marginY;
var linePositionX = nodeRightMiddle.x + config.node.marginX / 2;
// bottom to top
elements.push(ren
.path([
"M",
linePositionX,
node.getRightMostChild().y * offsetY +
config.node.height / 2 -
config.connector.width / 2 +
config.node.border.width / 2 +
_this._titleOffsetY,
"L",
linePositionX,
node.getLeftMostChild().y * offsetY +
config.node.height / 2 -
config.connector.width / 2 +
config.node.border.width / 2 +
_this._titleOffsetY
])
.attr(staticProps.connectorAttr));
}
}
}
each(node.children, drawNode);
}
// endregion
maxX = Math.max(maxX, box.x + box.w);
maxY = Math.max(maxY, box.y + box.h);
return true;
};
// clear the previous
each(elements, function (element) {
element.destroy();
});
this._elements = elements = [];
if (data === null ||
typeof data === "undefined" ||
typeof data.tree === "undefined" ||
data.tree === null) {
// error
elements.push(ren.label("Invalid data.", 0, 0).css({
fontSize: "14px",
color: "#EE0000",
fontWeight: "bold"
}));
}
else {
this._titleOffsetY = 0;
if (this.chart.title) {
var titleBox = this.chart.title.getBBox();
this._titleOffsetY =
titleBox.y + titleBox.height + this.chart.options.title.margin;
}
// draw tree
if (!drawNode(this._tree.root)) {
return;
}
// draw legend
if (config.legend.enabled) {
var legends = data.legends || [];
var offsetX = 0;
for (var i = 0; i < Math.min(legends.length, config.row.line); i++) {
elements.push(ren
.rect(offsetX + config.legend.marginX, maxY + config.legend.marginY, config.legend.nodeWidth, config.row.height)
.attr(ren.styledMode
? { class: "highcharts-tree-legend-" + i }
: { fill: colors[i] }));
/*spacing between legend box and legend text*/
offsetX += config.legend.nodeWidth + 5;
var legendTextElement = ren
.label(legends[i].text, offsetX + config.legend.marginX, maxY + config.legend.marginY)
.css(ren.styledMode
? {}
: {
fontSize: "14px",
color: config.node.textColor,
fontWeight: "bold"
})
.attr(ren.styledMode
? {
class: "highcharts-tree-legend-text",
zIndex: 1
}
: { zIndex: 1 })
.add();
elements.push(legendTextElement);
/*spacing between legends */
offsetX += legendTextElement.width + 30;
}
}
}
each(elements, function (element) {
if (element.added)
return;
element.add();
});
this._elements = elements;
},
animate: function () {
return;
},
drawPoints: function () {
return;
},
drawDataLabels: function () {
return;
}
});
});
//# sourceMappingURL=index.js.map