@amcharts/amcharts5
Version:
amCharts 5
171 lines • 7.04 kB
JavaScript
import { Hierarchy } from "../hierarchy/Hierarchy";
import { Template } from "../../core/util/Template";
import { ListTemplate } from "../../core/util/List";
import { Polygon } from "../../core/render/Polygon";
import { p50 } from "../../core/util/Percent";
import { voronoiTreemap } from 'd3-voronoi-treemap';
import seedrandom from "seedrandom";
import * as $utils from "../../core/util/Utils";
import * as $array from "../../core/util/Array";
;
/**
* A Weighted Voronoi Treemap series.
*
* NOTE: Try to avoid a big number of data items with very big value
* differences. Better group small items into "Other" item.
*
* @see {@link https://www.amcharts.com/docs/v5/charts/hierarchy/voronoi-treemap/} for more info
* @since 5.4.0
*/
export class VoronoiTreemap extends Hierarchy {
constructor() {
super(...arguments);
this._tag = "voronoitreemap";
/**
* A list of node graphics elements in a [[VoronoiTreemap]] chart.
*
* @default new ListTemplate<RoundedRectangle>
*/
this.polygons = this.addDisposer(new ListTemplate(Template.new({}), () => Polygon._new(this._root, {
themeTags: $utils.mergeTags(this.polygons.template.get("themeTags", []), [this._tag, "shape"])
}, [this.polygons.template])));
this.voronoi = voronoiTreemap();
}
_afterNew() {
this.nodesContainer.setAll({
x: p50,
y: p50,
centerX: p50,
centerY: p50
});
this.nodes.template.setPrivate("trustBounds", true);
super._afterNew();
}
_prepareChildren() {
super._prepareChildren();
if (this.isDirty("type") && this.get("type") !== undefined) {
this.set("shapeType", this.get("type"));
this.setRaw("type", undefined);
}
const width = this.innerWidth() / 2;
const height = this.innerHeight() / 2;
let node = this._rootNode;
const selectedDataItem = this.get("selectedDataItem");
if (selectedDataItem) {
node = selectedDataItem.get("d3HierarchyNode");
}
this.voronoi.convergenceRatio((this.get("convergenceRatio", 0.005)));
this.voronoi.maxIterationCount((this.get("maxIterationCount", 100)));
this.voronoi.minWeightRatio((this.get("minWeightRatio", 0.005)));
// Re-clip and re-tessellate on the relevant setting changes. Full
// re-tessellation is expensive, so avoid it where it isn't needed:
// - "rectangle": the clip depends on the actual size, so recompute on a
// shape change AND on resize.
// - "polygon": uses a size-independent unit clip (scaled per node in
// `_updateNode`), so it only needs a re-tessellation when the shape or
// `cornerCount` changes — NOT on resize. Resizing just rescales the
// existing cells (handled by the base `_updateVisuals` on `_sizeDirty`),
// which is cheap; re-tessellating there would spike CPU with lots of
// data (e.g. when a legend reflows the plot area).
if (node) {
if (this.get("shapeType") == "rectangle") {
if (this.isDirty("shapeType") || this._sizeDirty) {
this.voronoi.prng(seedrandom("X"));
this.voronoi.clip([[-width, -height], [-width, height], [width, height], [width, -height]])(node);
this._updateVisuals();
}
}
else {
if (this.isDirty("shapeType") || this.isDirty("cornerCount")) {
this.voronoi.prng(seedrandom("X"));
this.voronoi.clip(this.getCirclePolygon(1))(node);
this._updateVisuals();
}
}
}
if ((this._valuesDirty || this.isDirty("selectedDataItem")) && node) {
this.voronoi.prng(seedrandom("X"));
this.voronoi(node);
this._updateVisuals();
}
}
_updateNode(dataItem) {
const coords = dataItem.get("d3HierarchyNode").polygon;
const polygon = dataItem.get("polygon");
if (coords && polygon) {
let coordinates = [];
let d = 1;
if (this.get("shapeType") == "polygon") {
d = Math.min(this.innerWidth(), this.innerHeight()) / 2;
}
let minX = Infinity;
let maxX = -Infinity;
for (let i = 0, len = coords.length; i < len; i++) {
const point = coords[i];
let x = point[0] * d;
let y = point[1] * d;
coordinates.push([x, y]);
minX = Math.min(minX, x);
maxX = Math.max(maxX, x);
}
polygon.set("coordinates", coordinates);
const fill = dataItem.get("fill");
const fillPattern = dataItem.get("fillPattern");
polygon._setDefault("fill", fill);
polygon._setDefault("fillPattern", fillPattern);
const label = dataItem.get("label");
if (label) {
const site = coords.site;
if (site) {
label.setAll({
x: site.x * d,
y: site.y * d,
maxWidth: Math.abs(maxX - minX)
});
}
}
}
}
_handleSingle(dataItem) {
const parent = dataItem.get("parent");
if (parent) {
const children = parent.get("children");
if (children) {
$array.each(children, (child) => {
if (child != dataItem) {
this.disableDataItem(child);
child.get("node").hide();
}
});
}
this._handleSingle(parent);
}
}
/**
* @ignore
*/
makeNode(dataItem) {
const node = super.makeNode(dataItem);
this._makeNode(dataItem, node);
return node;
}
_makeNode(dataItem, node) {
const polygon = node.children.moveValue(this.polygons.make(), 0);
node.setPrivate("tooltipTarget", polygon);
dataItem.setRaw("polygon", polygon);
polygon._setDataItem(dataItem);
}
getCirclePolygon(radius) {
const points = this.get("cornerCount", 120);
const dAngle = Math.PI * 2 / points;
const polygon = [];
for (let i = 0; i < points; i++) {
let angle = i * dAngle;
polygon.push([radius * Math.cos(angle), radius * Math.sin(angle)]);
}
return polygon;
}
}
VoronoiTreemap.className = "VoronoiTreemap";
VoronoiTreemap.classNames = Hierarchy.classNames.concat([VoronoiTreemap.className]);
//# sourceMappingURL=VoronoiTreemap.js.map