UNPKG

@amcharts/amcharts5

Version:
171 lines 7.04 kB
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