ag-charts-community
Version:
Advanced Charting / Charts supporting Javascript / Typescript / React / Angular / Vue
410 lines • 16.7 kB
JavaScript
"use strict";
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
return c > 3 && r && Object.defineProperty(target, key, r), r;
};
Object.defineProperty(exports, "__esModule", { value: true });
const selection_1 = require("../../../scene/selection");
const hdpiCanvas_1 = require("../../../canvas/hdpiCanvas");
const observable_1 = require("../../../util/observable");
const label_1 = require("../../label");
const series_1 = require("../series");
const hierarchySeries_1 = require("./hierarchySeries");
const chart_1 = require("../../chart");
const group_1 = require("../../../scene/group");
const text_1 = require("../../../scene/shape/text");
const rect_1 = require("../../../scene/shape/rect");
const dropShadow_1 = require("../../../scene/dropShadow");
const linearScale_1 = require("../../../scale/linearScale");
const treemap_1 = require("../../../layout/treemap");
const hierarchy_1 = require("../../../layout/hierarchy");
const number_1 = require("../../../util/number");
class TreemapSeriesTooltip extends series_1.SeriesTooltip {
}
__decorate([
observable_1.reactive('change')
], TreemapSeriesTooltip.prototype, "renderer", void 0);
exports.TreemapSeriesTooltip = TreemapSeriesTooltip;
class TreemapSeriesLabel extends label_1.Label {
constructor() {
super(...arguments);
this.padding = 10;
}
}
__decorate([
observable_1.reactive('change')
], TreemapSeriesLabel.prototype, "padding", void 0);
exports.TreemapSeriesLabel = TreemapSeriesLabel;
var TextNodeTag;
(function (TextNodeTag) {
TextNodeTag[TextNodeTag["Name"] = 0] = "Name";
TextNodeTag[TextNodeTag["Value"] = 1] = "Value";
})(TextNodeTag || (TextNodeTag = {}));
class TreemapSeries extends hierarchySeries_1.HierarchySeries {
constructor() {
super();
this.groupSelection = selection_1.Selection.select(this.pickGroup).selectAll();
this.labelMap = new Map();
this.layout = new treemap_1.Treemap();
this.title = (() => {
const label = new TreemapSeriesLabel();
label.color = 'white';
label.fontWeight = 'bold';
label.fontSize = 12;
label.fontFamily = 'Verdana, sans-serif';
label.padding = 15;
return label;
})();
this.subtitle = (() => {
const label = new TreemapSeriesLabel();
label.color = 'white';
label.fontSize = 9;
label.fontFamily = 'Verdana, sans-serif';
label.padding = 13;
return label;
})();
this.labels = {
large: (() => {
const label = new label_1.Label();
label.color = 'white';
label.fontWeight = 'bold';
label.fontSize = 18;
return label;
})(),
medium: (() => {
const label = new label_1.Label();
label.color = 'white';
label.fontWeight = 'bold';
label.fontSize = 14;
return label;
})(),
small: (() => {
const label = new label_1.Label();
label.color = 'white';
label.fontWeight = 'bold';
label.fontSize = 10;
return label;
})(),
color: (() => {
const label = new label_1.Label();
label.color = 'white';
return label;
})()
};
this._nodePadding = 2;
this.labelKey = 'label';
this.sizeKey = 'size';
this.colorKey = 'color';
this.colorDomain = [-5, 5];
this.colorRange = ['#cb4b3f', '#6acb64'];
this.colorParents = false;
this.gradient = true;
this.colorName = 'Change';
this.rootName = 'Root';
this._shadow = (() => {
const shadow = new dropShadow_1.DropShadow();
shadow.color = 'rgba(0, 0, 0, 0.4)';
shadow.xOffset = 1.5;
shadow.yOffset = 1.5;
return shadow;
})();
this.tooltip = new TreemapSeriesTooltip();
this.shadow.addEventListener('change', this.scheduleUpdate, this);
this.title.addEventListener('change', this.scheduleUpdate, this);
this.subtitle.addEventListener('change', this.scheduleUpdate, this);
this.labels.small.addEventListener('change', this.scheduleUpdate, this);
this.labels.medium.addEventListener('change', this.scheduleUpdate, this);
this.labels.large.addEventListener('change', this.scheduleUpdate, this);
this.labels.color.addEventListener('change', this.scheduleUpdate, this);
}
set nodePadding(value) {
if (this._nodePadding !== value) {
this._nodePadding = value;
this.updateLayoutPadding();
this.scheduleUpdate();
}
}
get nodePadding() {
return this._nodePadding;
}
set shadow(value) {
if (this._shadow !== value) {
this._shadow = value;
this.scheduleUpdate();
}
}
get shadow() {
return this._shadow;
}
onHighlightChange() {
this.updateNodes();
}
updateLayoutPadding() {
const { title, subtitle, nodePadding, labelKey } = this;
this.layout.paddingRight = _ => nodePadding;
this.layout.paddingBottom = _ => nodePadding;
this.layout.paddingLeft = _ => nodePadding;
this.layout.paddingTop = (node) => {
let name = node.datum[labelKey] || '';
if (node.children) {
name = name.toUpperCase();
}
const font = node.depth > 1 ? subtitle : title;
const textSize = hdpiCanvas_1.HdpiCanvas.getTextSize(name, [font.fontWeight, font.fontSize + 'px', font.fontFamily].join(' ').trim());
const innerNodeWidth = node.x1 - node.x0 - nodePadding * 2;
const hasTitle = node.depth > 0 && node.children && textSize.width <= innerNodeWidth;
node.hasTitle = !!hasTitle;
return hasTitle ? textSize.height + nodePadding * 2 : nodePadding;
};
}
processData() {
if (!this.data) {
return false;
}
const { data, sizeKey, labelKey, colorKey, colorDomain, colorRange, colorParents } = this;
let dataRoot;
if (sizeKey) {
dataRoot = hierarchy_1.hierarchy(data).sum(datum => datum.children ? 1 : datum[sizeKey]);
}
else {
dataRoot = hierarchy_1.hierarchy(data).sum(datum => datum.children ? 0 : 1);
}
this.dataRoot = dataRoot;
const colorScale = new linearScale_1.LinearScale();
colorScale.domain = colorDomain;
colorScale.range = colorRange;
const series = this;
function traverse(root, depth = 0) {
const { children, datum } = root;
const label = datum[labelKey];
const colorValue = colorKey ? datum[colorKey] : depth;
root.series = series;
root.fill = !children || colorParents ? colorScale.convert(colorValue) : '#272931';
root.colorValue = colorValue;
if (label) {
root.label = children ? label.toUpperCase() : label;
}
else {
root.label = '';
}
if (children) {
children.forEach((child) => traverse(child, depth + 1));
}
}
traverse(this.dataRoot);
return true;
}
getLabelCenterX(datum) {
return (datum.x0 + datum.x1) / 2;
}
getLabelCenterY(datum) {
return (datum.y0 + datum.y1) / 2 + 2;
}
update() {
this.updatePending = false;
this.updateSelections();
this.updateNodes();
}
updateSelections() {
if (!this.nodeDataPending) {
return;
}
this.nodeDataPending = false;
const { chart, dataRoot } = this;
if (!chart || !dataRoot) {
return;
}
const seriesRect = chart.getSeriesRect();
if (!seriesRect) {
return;
}
this.layout.size = [seriesRect.width, seriesRect.height];
this.updateLayoutPadding();
const descendants = this.layout.processData(dataRoot).descendants();
const updateGroups = this.groupSelection.setData(descendants);
updateGroups.exit.remove();
const enterGroups = updateGroups.enter.append(group_1.Group);
enterGroups.append(rect_1.Rect);
enterGroups.append(text_1.Text).each((node) => node.tag = TextNodeTag.Name);
enterGroups.append(text_1.Text).each((node) => node.tag = TextNodeTag.Value);
this.groupSelection = updateGroups.merge(enterGroups);
}
updateNodes() {
if (!this.chart) {
return;
}
const { colorKey, labelMap, nodePadding, title, subtitle, labels, shadow, gradient, chart: { highlightedDatum }, highlightStyle: { fill: deprecatedFill, stroke: deprecatedStroke, strokeWidth: deprecatedStrokeWidth, item: { fill: highlightedFill = deprecatedFill, stroke: highlightedStroke = deprecatedStroke, strokeWidth: highlightedDatumStrokeWidth = deprecatedStrokeWidth } } } = this;
this.groupSelection.selectByClass(rect_1.Rect).each((rect, datum) => {
const isDatumHighlighted = datum === highlightedDatum;
const fill = isDatumHighlighted && highlightedFill !== undefined ? highlightedFill : datum.fill;
const stroke = isDatumHighlighted && highlightedStroke !== undefined ? highlightedStroke : datum.depth < 2 ? undefined : 'black';
const strokeWidth = isDatumHighlighted && highlightedDatumStrokeWidth !== undefined
? highlightedDatumStrokeWidth
: this.getStrokeWidth(1, datum);
rect.fill = fill;
rect.stroke = stroke;
rect.strokeWidth = strokeWidth;
rect.crisp = true;
rect.gradient = gradient;
rect.x = datum.x0;
rect.y = datum.y0;
rect.width = datum.x1 - datum.x0;
rect.height = datum.y1 - datum.y0;
});
this.groupSelection.selectByTag(TextNodeTag.Name).each((text, datum, index) => {
const isLeaf = !datum.children;
const innerNodeWidth = datum.x1 - datum.x0 - nodePadding * 2;
const innerNodeHeight = datum.y1 - datum.y0 - nodePadding * 2;
const hasTitle = datum.hasTitle;
const highlighted = datum === highlightedDatum;
let label;
if (isLeaf) {
if (innerNodeWidth > 40 && innerNodeHeight > 40) {
label = labels.large;
}
else if (innerNodeWidth > 20 && innerNodeHeight > 20) {
label = labels.medium;
}
else {
label = labels.small;
}
}
else {
if (datum.depth > 1) {
label = subtitle;
}
else {
label = title;
}
}
text.fontWeight = label.fontWeight;
text.fontSize = label.fontSize;
text.fontFamily = label.fontFamily;
text.textBaseline = isLeaf ? 'bottom' : (hasTitle ? 'top' : 'middle');
text.textAlign = hasTitle ? 'left' : 'center';
text.text = datum.label;
const textBBox = text.computeBBox();
const hasLabel = isLeaf && !!textBBox
&& textBBox.width <= innerNodeWidth
&& textBBox.height * 2 + 8 <= innerNodeHeight;
labelMap.set(index, text);
text.fill = highlighted ? 'black' : label.color;
text.fillShadow = hasLabel && !highlighted ? shadow : undefined;
text.visible = hasTitle || hasLabel;
if (hasTitle) {
text.x = datum.x0 + nodePadding;
text.y = datum.y0 + nodePadding;
}
else {
text.x = this.getLabelCenterX(datum);
text.y = this.getLabelCenterY(datum);
}
});
this.groupSelection.selectByTag(TextNodeTag.Value).each((text, datum, index) => {
const isLeaf = !datum.children;
const innerNodeWidth = datum.x1 - datum.x0 - nodePadding * 2;
const highlighted = datum === highlightedDatum;
const value = datum.colorValue;
const label = labels.color;
text.fontSize = label.fontSize;
text.fontFamily = label.fontFamily;
text.fontStyle = label.fontStyle;
text.fontWeight = label.fontWeight;
text.textBaseline = 'top';
text.textAlign = 'center';
text.text = typeof value === 'number' && isFinite(value)
? String(number_1.toFixed(datum.colorValue)) + '%'
: '';
const textBBox = text.computeBBox();
const nameNode = labelMap.get(index);
const hasLabel = !!nameNode && nameNode.visible;
const isVisible = isLeaf && !!colorKey && hasLabel && !!textBBox && textBBox.width < innerNodeWidth;
text.fill = highlighted ? 'black' : label.color;
text.fillShadow = highlighted ? undefined : shadow;
text.visible = isVisible;
if (isVisible) {
text.x = this.getLabelCenterX(datum);
text.y = this.getLabelCenterY(datum);
}
else {
if (nameNode && !(datum.children && datum.children.length)) {
nameNode.textBaseline = 'middle';
nameNode.y = this.getLabelCenterY(datum);
}
}
});
}
getDomain(direction) {
return [0, 1];
}
fireNodeClickEvent(event, datum) {
this.fireEvent({
type: 'nodeClick',
event,
series: this,
datum: datum.datum,
labelKey: this.labelKey,
sizeKey: this.sizeKey,
colorKey: this.colorKey
});
}
getTooltipHtml(nodeDatum) {
const { tooltip, sizeKey, labelKey, colorKey, colorName, rootName } = this;
const { datum } = nodeDatum;
const { renderer: tooltipRenderer } = tooltip;
const title = nodeDatum.depth ? datum[labelKey] : (rootName || datum[labelKey]);
let content = undefined;
const color = nodeDatum.fill || 'gray';
if (colorKey && colorName) {
const colorValue = datum[colorKey];
if (typeof colorValue === 'number' && isFinite(colorValue)) {
content = `<b>${colorName}</b>: ${number_1.toFixed(datum[colorKey])}`;
}
}
const defaults = {
title,
backgroundColor: color,
content
};
if (tooltipRenderer) {
return chart_1.toTooltipHtml(tooltipRenderer({
datum: nodeDatum,
sizeKey,
labelKey,
colorKey,
title,
color
}), defaults);
}
return chart_1.toTooltipHtml(defaults);
}
listSeriesItems(legendData) {
}
}
TreemapSeries.className = 'TreemapSeries';
TreemapSeries.type = 'treemap';
__decorate([
observable_1.reactive('dataChange')
], TreemapSeries.prototype, "labelKey", void 0);
__decorate([
observable_1.reactive('dataChange')
], TreemapSeries.prototype, "sizeKey", void 0);
__decorate([
observable_1.reactive('dataChange')
], TreemapSeries.prototype, "colorKey", void 0);
__decorate([
observable_1.reactive('dataChange')
], TreemapSeries.prototype, "colorDomain", void 0);
__decorate([
observable_1.reactive('dataChange')
], TreemapSeries.prototype, "colorRange", void 0);
__decorate([
observable_1.reactive('dataChange')
], TreemapSeries.prototype, "colorParents", void 0);
__decorate([
observable_1.reactive('update')
], TreemapSeries.prototype, "gradient", void 0);
exports.TreemapSeries = TreemapSeries;
//# sourceMappingURL=treemapSeries.js.map