From 6b8839eab69f1eae4b59cab8925d4ab4dfb90f50 Mon Sep 17 00:00:00 2001 From: Michael Bostock Date: Mon, 28 Feb 2011 15:04:20 -0800 Subject: [PATCH] Add treemap example. Still a work in progress. Need to decide on the appropriate API for representing tree structures; not sure I want to go the heavyweight pv.dom route. May also need rounding to exact pixel values. --- examples/treemap/flare.json | 284 ++++++++++++++++++++++++++++++++++ examples/treemap/layout.js | 174 +++++++++++++++++++++ examples/treemap/treemap.html | 52 +++++++ 3 files changed, 510 insertions(+) create mode 100644 examples/treemap/flare.json create mode 100644 examples/treemap/layout.js create mode 100644 examples/treemap/treemap.html diff --git a/examples/treemap/flare.json b/examples/treemap/flare.json new file mode 100644 index 00000000..2efb409a --- /dev/null +++ b/examples/treemap/flare.json @@ -0,0 +1,284 @@ +{ + "analytics": { + "cluster": { + "AgglomerativeCluster": 3938, + "CommunityStructure": 3812, + "HierarchicalCluster": 6714, + "MergeEdge": 743 + }, + "graph": { + "BetweennessCentrality": 3534, + "LinkDistance": 5731, + "MaxFlowMinCut": 7840, + "ShortestPaths": 5914, + "SpanningTree": 3416 + }, + "optimization": { + "AspectRatioBanker": 7074 + } + }, + "animate": { + "Easing": 17010, + "FunctionSequence": 5842, + "interpolate": { + "ArrayInterpolator": 1983, + "ColorInterpolator": 2047, + "DateInterpolator": 1375, + "Interpolator": 8746, + "MatrixInterpolator": 2202, + "NumberInterpolator": 1382, + "ObjectInterpolator": 1629, + "PointInterpolator": 1675, + "RectangleInterpolator": 2042 + }, + "ISchedulable": 1041, + "Parallel": 5176, + "Pause": 449, + "Scheduler": 5593, + "Sequence": 5534, + "Transition": 9201, + "Transitioner": 19975, + "TransitionEvent": 1116, + "Tween": 6006 + }, + "data": { + "converters": { + "Converters": 721, + "DelimitedTextConverter": 4294, + "GraphMLConverter": 9800, + "IDataConverter": 1314, + "JSONConverter": 2220 + }, + "DataField": 1759, + "DataSchema": 2165, + "DataSet": 586, + "DataSource": 3331, + "DataTable": 772, + "DataUtil": 3322 + }, + "display": { + "DirtySprite": 8833, + "LineSprite": 1732, + "RectSprite": 3623, + "TextSprite": 10066 + }, + "flex": { + "FlareVis": 4116 + }, + "physics": { + "DragForce": 1082, + "GravityForce": 1336, + "IForce": 319, + "NBodyForce": 10498, + "Particle": 2822, + "Simulation": 9983, + "Spring": 2213, + "SpringForce": 1681 + }, + "query": { + "AggregateExpression": 1616, + "And": 1027, + "Arithmetic": 3891, + "Average": 891, + "BinaryExpression": 2893, + "Comparison": 5103, + "CompositeExpression": 3677, + "Count": 781, + "DateUtil": 4141, + "Distinct": 933, + "Expression": 5130, + "ExpressionIterator": 3617, + "Fn": 3240, + "If": 2732, + "IsA": 2039, + "Literal": 1214, + "Match": 3748, + "Maximum": 843, + "methods": { + "add": 593, + "and": 330, + "average": 287, + "count": 277, + "distinct": 292, + "div": 595, + "eq": 594, + "fn": 460, + "gt": 603, + "gte": 625, + "iff": 748, + "isa": 461, + "lt": 597, + "lte": 619, + "max": 283, + "min": 283, + "mod": 591, + "mul": 603, + "neq": 599, + "not": 386, + "or": 323, + "orderby": 307, + "range": 772, + "select": 296, + "stddev": 363, + "sub": 600, + "sum": 280, + "update": 307, + "variance": 335, + "where": 299, + "xor": 354, + "_": 264 + }, + "Minimum": 843, + "Not": 1554, + "Or": 970, + "Query": 13896, + "Range": 1594, + "StringUtil": 4130, + "Sum": 791, + "Variable": 1124, + "Variance": 1876, + "Xor": 1101 + }, + "scale": { + "IScaleMap": 2105, + "LinearScale": 1316, + "LogScale": 3151, + "OrdinalScale": 3770, + "QuantileScale": 2435, + "QuantitativeScale": 4839, + "RootScale": 1756, + "Scale": 4268, + "ScaleType": 1821, + "TimeScale": 5833 + }, + "util": { + "Arrays": 8258, + "Colors": 10001, + "Dates": 8217, + "Displays": 12555, + "Filter": 2324, + "Geometry": 10993, + "heap": { + "FibonacciHeap": 9354, + "HeapNode": 1233 + }, + "IEvaluable": 335, + "IPredicate": 383, + "IValueProxy": 874, + "math": { + "DenseMatrix": 3165, + "IMatrix": 2815, + "SparseMatrix": 3366 + }, + "Maths": 17705, + "Orientation": 1486, + "palette": { + "ColorPalette": 6367, + "Palette": 1229, + "ShapePalette": 2059, + "SizePalette": 2291 + }, + "Property": 5559, + "Shapes": 19118, + "Sort": 6887, + "Stats": 6557, + "Strings": 22026 + }, + "vis": { + "axis": { + "Axes": 1302, + "Axis": 24593, + "AxisGridLine": 652, + "AxisLabel": 636, + "CartesianAxes": 6703 + }, + "controls": { + "AnchorControl": 2138, + "ClickControl": 3824, + "Control": 1353, + "ControlList": 4665, + "DragControl": 2649, + "ExpandControl": 2832, + "HoverControl": 4896, + "IControl": 763, + "PanZoomControl": 5222, + "SelectionControl": 7862, + "TooltipControl": 8435 + }, + "data": { + "Data": 20544, + "DataList": 19788, + "DataSprite": 10349, + "EdgeSprite": 3301, + "NodeSprite": 19382, + "render": { + "ArrowType": 698, + "EdgeRenderer": 5569, + "IRenderer": 353, + "ShapeRenderer": 2247 + }, + "ScaleBinding": 11275, + "Tree": 7147, + "TreeBuilder": 9930 + }, + "events": { + "DataEvent": 2313, + "SelectionEvent": 1880, + "TooltipEvent": 1701, + "VisualizationEvent": 1117 + }, + "legend": { + "Legend": 20859, + "LegendItem": 4614, + "LegendRange": 10530 + }, + "operator": { + "distortion": { + "BifocalDistortion": 4461, + "Distortion": 6314, + "FisheyeDistortion": 3444 + }, + "encoder": { + "ColorEncoder": 3179, + "Encoder": 4060, + "PropertyEncoder": 4138, + "ShapeEncoder": 1690, + "SizeEncoder": 1830 + }, + "filter": { + "FisheyeTreeFilter": 5219, + "GraphDistanceFilter": 3165, + "VisibilityFilter": 3509 + }, + "IOperator": 1286, + "label": { + "Labeler": 9956, + "RadialLabeler": 3899, + "StackedAreaLabeler": 3202 + }, + "layout": { + "AxisLayout": 6725, + "BundledEdgeRouter": 3727, + "CircleLayout": 9317, + "CirclePackingLayout": 12003, + "DendrogramLayout": 4853, + "ForceDirectedLayout": 8411, + "IcicleTreeLayout": 4864, + "IndentedTreeLayout": 3174, + "Layout": 7881, + "NodeLinkTreeLayout": 12870, + "PieLayout": 2728, + "RadialTreeLayout": 12348, + "RandomLayout": 870, + "StackedAreaLayout": 9121, + "TreeMapLayout": 9191 + }, + "Operator": 2490, + "OperatorList": 5248, + "OperatorSequence": 4190, + "OperatorSwitch": 2581, + "SortOperator": 2023 + }, + "Visualization": 16540 + } +} diff --git a/examples/treemap/layout.js b/examples/treemap/layout.js new file mode 100644 index 00000000..fe251db7 --- /dev/null +++ b/examples/treemap/layout.js @@ -0,0 +1,174 @@ +// Squarified Treemaps by Mark Bruls, Kees Huizing, and Jarke J. van Wijk +function layout_treemap() { + var children = layout_treemapChildren, + value = layout_treemapValue, + size = [1, 1]; // width, height + + // Recursively compute the node depth and value. + // Also converts the data representation into a standard tree structure. + // Also sorts child nodes by descending value to optimize squarification. + function sum(data, depth, nodes) { + var datas = children.call(treemap, data, depth), + node = {depth: depth, data: data}; + nodes.push(node); + if (datas) { + var i = -1, + n = datas.length, + c = node.children = [], + v = 0, + j = depth + 1; + while (++i < n) { + d = sum(datas[i], j, nodes); + if (d.value > 0) { // ignore NaN, negative, etc. + c.push(d); + v += d.value; + } + } + node.value = v; + } else { + node.value = value.call(treemap, data, depth); + } + if (!depth) scale(node, size[0] * size[1] / node.value); // root + return node; + } + + // Recursively compute the node area based on value & scale. + function scale(node, k) { + var children = node.children; + node.area = node.value * k; + if (children) { + var i = -1, + n = children.length; + while (++i < n) scale(children[i], k); + } + } + + // Arranges the specified children into squarified rows. + function squarify(children, node) { + var rect = {x: node.x, y: node.y, dx: node.dx, dy: node.dy}, + row = [], + child, + best = Infinity, // the best row score so far + score, // the current row score + u = Math.min(rect.dx, rect.dy), // initial orientation + n; + children = children.slice(); // copy-on-write + children.sort(function(a, b) { return b.area - a.area; }); + row.area = 0; + while ((n = children.length) > 0) { + row.push(child = children[n - 1]); + row.area += child.area; + if ((score = worst(row, u)) <= best) { // continue with this orientation + children.pop(); + best = score; + } else { // abort, and try a different orientation + row.area -= row.pop().area; + position(row, u, rect); + u = Math.min(rect.dx, rect.dy); + row.length = row.area = 0; + best = Infinity; + } + } + if (row.length) { + position(row, u, rect); + row.length = row.area = 0; + } + } + + // Computes the score for the specified row, as the worst aspect ratio. + function worst(row, u) { + var s = row.area, + r, + rmax = 0, + rmin = Infinity, + i = -1, + n = row.length; + while (++i < n) { + r = row[i].area; + if (r < rmin) rmin = r; + if (r > rmax) rmax = r; + } + s *= s; + u *= u; + return Math.max((u * rmax) / s, s / (u * rmin)); + } + + // Positions the specified row of nodes. Modifies `rect`. + function position(row, u, rect) { + var i = -1, + n = row.length, + x = rect.x, + y = rect.y, + v = u ? row.area / u : 0, + o; + if (u == rect.dx) { // horizontal subdivision + while (++i < n) { + o = row[i]; + o.x = x; + o.y = y; + o.dy = v; + x += o.dx = o.area / v; + } + rect.y += v; + rect.dy -= v; + } else { // vertical subdivision + while (++i < n) { + o = row[i]; + o.x = x; + o.y = y; + o.dx = v; + y += o.dy = o.area / v; + } + rect.x += v; + rect.dx -= v; + } + } + + // Recursively computes the treemap layout for the node and its children. + function layout(node) { + var children = node.children; + if (children) { + squarify(children, node); + children.forEach(layout); + } + } + + function treemap(d) { + var nodes = [], + root = sum(d, 0, nodes); + root.x = 0; + root.y = 0; + root.dx = size[0]; + root.dy = size[1]; + layout(root); + return nodes; + } + + treemap.children = function(x) { + if (!arguments.length) return children; + children = x; + return treemap; + }; + + treemap.value = function(x) { + if (!arguments.length) return value; + value = x; + return treemap; + }; + + treemap.size = function(x) { + if (!arguments.length) return size; + size = x; + return treemap; + }; + + return treemap; +} + +function layout_treemapChildren(d) { + return d.children; +} + +function layout_treemapValue(d) { + return d.value; +} diff --git a/examples/treemap/treemap.html b/examples/treemap/treemap.html new file mode 100644 index 00000000..9afae314 --- /dev/null +++ b/examples/treemap/treemap.html @@ -0,0 +1,52 @@ + + + + + Treemap + + + + + + + +