UNPKG

recharts

Version:
187 lines (161 loc) 4.61 kB
import React, { PropTypes } from 'react'; import Surface from '../container/Surface'; import Layer from '../container/Layer'; import ReactUtils from '../util/ReactUtils'; import TreemapItem from './TreemapItem'; class TreemapChart extends React.Component { static displayName = 'TreemapChart'; static propTypes = { width: PropTypes.number, height: PropTypes.number, data: PropTypes.array, style: PropTypes.object, ratio: PropTypes.number, children: PropTypes.oneOfType([ PropTypes.arrayOf(PropTypes.node), PropTypes.node, ]), }; constructor(props) { super(props); } pad(node) { return { x: node.x, y: node.y, dx: node.dx, dy: node.dy }; } // Compute the area for each child based on value & scale. scale(children, k) { const n = children.length; let i = -1; let child; let area; while (++i < n) { area = (child = children[i]).value * (k < 0 ? 0 : k); child.area = isNaN(area) || area <= 0 ? 0 : area; } } // Recursively arranges the specified node's children into squarified rows. squarify(node) { const children = node.children; if (children && children.length) { const rect = this.pad(node); const row = []; const remaining = children.slice(); // copy-on-write let child; let best = Infinity; // the best row score so far let score; // the current row score let u = Math.min(rect.dx, rect.dy); // initial orientation let n = remaining.length; this.scale(remaining, rect.dx * rect.dy / node.value); row.area = 0; while (n > 0) { row.push(child = remaining[n - 1]); row.area += child.area; score = this.worst(row, u); if (score <= best) { // continue with this orientation remaining.pop(); best = score; } else { // abort, and try a different orientation row.area -= row.pop().area; this.position(row, u, rect, false); u = Math.min(rect.dx, rect.dy); row.length = row.area = 0; best = Infinity; } n = remaining.length; } if (row.length) { this.position(row, u, rect, true); row.length = row.area = 0; } children.forEach(this.squarify); } } // Computes the score for the specified row, as the worst aspect ratio. worst(row, u) { let newU = u; let s = row.area; let r; let rmax = 0; let rmin = Infinity; let i = -1; const n = row.length; const { ratio } = this.props; while (++i < n) { r = row[i].area; if (!r) continue; if (r < rmin) rmin = r; if (r > rmax) rmax = r; } s *= s; newU *= newU; return s ? Math.max((newU * rmax * ratio) / s, s / (newU * rmin * ratio)) : Infinity; } // Positions the specified row of nodes. Modifies `rect`. position(row, u, rect, flush) { let i = -1; const n = row.length; let x = rect.x; let y = rect.y; let v = u ? Math.round(row.area / u) : 0; let o; if (u === rect.dx) { // horizontal subdivision if (flush || v > rect.dy) v = rect.dy; // over+underflow while (++i < n) { o = row[i]; o.x = x; o.y = y; o.dy = v; x += o.dx = Math.min(rect.x + rect.dx - x, v ? Math.round(o.area / v) : 0); } o.z = true; o.dx += rect.x + rect.dx - x; // rounding error rect.y += v; rect.dy -= v; } else { // vertical subdivision if (flush || v > rect.dx) v = rect.dx; // over+underflow while (++i < n) { o = row[i]; o.x = x; o.y = y; o.dx = v; y += o.dy = Math.min(rect.y + rect.dy - y, v ? Math.round(o.area / v) : 0); } o.z = false; o.dy += rect.y + rect.dy - y; // rounding error rect.x += v; rect.dx -= v; } } renderAllNodes() { const { width, height, data, children } = this.props; const treemapItem = children.props.element; const nodes = { value: data.reduce((a, b) => (a + b.value), 0), children: data, x: 0, y: 0, dx: width, dy: height, }; this.squarify(nodes); return nodes.children.map( (v, i) => <Layer key={i}> { React.cloneElement(treemapItem, { node: v, index: i }) } </Layer> ); } render() { const { width, height } = this.props; return ( <Surface width={width} height={height}> {this.renderAllNodes()} </Surface> ); } } export default TreemapChart;