Add third-party library 'webtreemap' for use in tools.

BUG=

Review URL: https://codereview.chromium.org/131463006

git-svn-id: svn://svn.chromium.org/chrome/trunk/src@245353 0039d316-1c4b-4281-b951-d872f2087c98
parent 6dc38f78
torne@chromium.org
bulach@chromium.org
Name: webtreemap
Short Name: webtreemap
URL: https://github.com/martine/webtreemap
Version: 0
Date: Tue Apr 23 08:57:05 2013 -0700
Revision: a2fa353833eca1a4821ba4c93fb6a7d546aed02a
License: Apache 2.0
License File: src/COPYING
Security Critical: No
License Android Compatible: Yes
Description:
A standalone javascript visualization library that generates DOM-based treemaps
for arbitrary data.
IMPORTANT:
This library is currently used only for internal tools, and has not been
security reviewed for use in any shipping or web-facing code.
Local Modifications:
Deleted demo directory
This diff is collapsed.
# webtreemap
A simple treemap implementation using web technologies (DOM nodes, CSS
styling and transitions) rather than a big canvas/svg/plugin.
Play with a [demo][].
[demo]: http://martine.github.com/webtreemap/demo/demo.html
## Creating your own
1. Create a page with a DOM node (i.e. a `<div>`) that will contain
your treemap.
2. Add the treemap to the node via something like
appendTreemap(document.getElementById('mynode'), mydata);
3. Style the treemap using CSS.
### Input format
The input data (`mydata` in the overview snippet) is a tree of nodes,
likely imported via a separate JSON file. Each node (including the
root) should contain data in the following format.
{
name: (HTML that is displayed via .innerHTML on the caption),
data: {
"$area": (a number, in arbitrary units)
},
children: (list of child tree nodes)
}
(This strange format for data comes from the the [JavaScript InfoVis
Toolkit][thejit]. I might change it in the future.)
The `$area` of a node should be the sum of the `$area` of all of its
`children`.
(At runtime, tree nodes will dynamically will gain two extra
attributes, `parent` and `dom`; this is only worth pointing out so
that you don't accidentally conflict with them.)
### CSS styling
The treemap is constructed with one `div` per region with a separate
`div` for the caption. Each div is styleable via the
`webtreemap-node` CSS class. The captions are stylable as
`webtreemap-caption`.
Each level of the tree also gets a per-level CSS class,
`webtreemap-level0` through `webtreemap-level4`. These can be
adjusted to e.g. made different levels different colors. To control
the caption on a per-level basis, use a CSS selector like
`.webtreemap-level2 > .webtreemap-caption`.
Your best bet is to modify the included `webtreemap.css`, which
contains comments about required and optional CSS attributes.
## Related projects
* [JavaScript InfoVis Toolkit][thejit]
[thejit]: http://thejit.org/
.webtreemap-node {
/* Required attributes. */
position: absolute;
overflow: hidden; /* To hide overlong captions. */
background: white; /* Nodes must be opaque for zIndex layering. */
border: solid 1px black; /* Calculations assume 1px border. */
/* Optional: CSS animation. */
-webkit-transition: top 0.3s,
left 0.3s,
width 0.3s,
height 0.3s;
}
/* Optional: highlight nodes on mouseover. */
.webtreemap-node:hover {
background: #eee;
}
/* Optional: Different background colors depending on symbol. */
.webtreemap-symbol-bss {
background: #66C2A5;
}
.webtreemap-symbol-data {
background: #FC8D62;
}
.webtreemap-symbol-read-only_data {
background: #8DA0CB;
}
.webtreemap-symbol-code {
background: #E78AC3;
}
.webtreemap-symbol-weak_symbol {
background: #A6D854;
}
.webtreemap-symbol-bss.webtreemap-aggregate {
background: #B3E2CD;
}
.webtreemap-symbol-data.webtreemap-aggregate {
background: #FDCDAC;
}
.webtreemap-symbol-read-only_data.webtreemap-aggregate {
background: #CBD5E8;
}
.webtreemap-symbol-code.webtreemap-aggregate {
background: #F4CAE4;
}
.webtreemap-symbol-weak_symbol.webtreemap-aggregate {
background: #E6F5C9;
}
#legend > * {
border: solid 1px #444;
}
/* Optional: Different borders depending on level. */
.webtreemap-level0 {
border: solid 1px #444;
}
.webtreemap-level1 {
border: solid 1px #666;
}
.webtreemap-level2 {
border: solid 1px #888;
}
.webtreemap-level3 {
border: solid 1px #aaa;
}
.webtreemap-level4 {
border: solid 1px #ccc;
}
/* Optional: styling on node captions. */
.webtreemap-caption {
font-family: sans-serif;
font-size: 11px;
padding: 2px;
text-align: center;
}
/* Optional: styling on captions on mouse hover. */
/*.webtreemap-node:hover > .webtreemap-caption {
text-decoration: underline;
}*/
// Copyright 2013 Google Inc. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// Size of border around nodes.
// We could support arbitrary borders using getComputedStyle(), but I am
// skeptical the extra complexity (and performance hit) is worth it.
var kBorderWidth = 1;
// Padding around contents.
// TODO: do this with a nested div to allow it to be CSS-styleable.
var kPadding = 4;
var focused = null;
function focus(tree) {
focused = tree;
// Hide all visible siblings of all our ancestors by lowering them.
var level = 0;
var root = tree;
while (root.parent) {
root = root.parent;
level += 1;
for (var i = 0, sibling; sibling = root.children[i]; ++i) {
if (sibling.dom)
sibling.dom.style.zIndex = 0;
}
}
var width = root.dom.offsetWidth;
var height = root.dom.offsetHeight;
// Unhide (raise) and maximize us and our ancestors.
for (var t = tree; t.parent; t = t.parent) {
// Shift off by border so we don't get nested borders.
// TODO: actually make nested borders work (need to adjust width/height).
position(t.dom, -kBorderWidth, -kBorderWidth, width, height);
t.dom.style.zIndex = 1;
}
// And layout into the topmost box.
layout(tree, level, width, height);
}
function makeDom(tree, level) {
var dom = document.createElement('div');
dom.style.zIndex = 1;
dom.className = 'webtreemap-node webtreemap-level' + Math.min(level, 4);
if (tree.data['$symbol']) {
dom.className += (' webtreemap-symbol-' +
tree.data['$symbol'].replace(' ', '_'));
}
if (tree.data['$dominant_symbol']) {
dom.className += (' webtreemap-symbol-' +
tree.data['$dominant_symbol'].replace(' ', '_'));
dom.className += (' webtreemap-aggregate');
}
dom.onmousedown = function(e) {
if (e.button == 0) {
if (focused && tree == focused && focused.parent) {
focus(focused.parent);
} else {
focus(tree);
}
}
e.stopPropagation();
return true;
};
var caption = document.createElement('div');
caption.className = 'webtreemap-caption';
caption.innerHTML = tree.name;
dom.appendChild(caption);
tree.dom = dom;
return dom;
}
function position(dom, x, y, width, height) {
// CSS width/height does not include border.
width -= kBorderWidth*2;
height -= kBorderWidth*2;
dom.style.left = x + 'px';
dom.style.top = y + 'px';
dom.style.width = Math.max(width, 0) + 'px';
dom.style.height = Math.max(height, 0) + 'px';
}
// Given a list of rectangles |nodes|, the 1-d space available
// |space|, and a starting rectangle index |start|, compute an span of
// rectangles that optimizes a pleasant aspect ratio.
//
// Returns [end, sum], where end is one past the last rectangle and sum is the
// 2-d sum of the rectangles' areas.
function selectSpan(nodes, space, start) {
// Add rectangle one by one, stopping when aspect ratios begin to go
// bad. Result is [start,end) covering the best run for this span.
// http://scholar.google.com/scholar?cluster=5972512107845615474
var node = nodes[start];
var rmin = node.data['$area']; // Smallest seen child so far.
var rmax = rmin; // Largest child.
var rsum = 0; // Sum of children in this span.
var last_score = 0; // Best score yet found.
for (var end = start; node = nodes[end]; ++end) {
var size = node.data['$area'];
if (size < rmin)
rmin = size;
if (size > rmax)
rmax = size;
rsum += size;
// This formula is from the paper, but you can easily prove to
// yourself it's taking the larger of the x/y aspect ratio or the
// y/x aspect ratio. The additional magic fudge constant of 5
// makes us prefer wider rectangles to taller ones.
var score = Math.max(5*space*space*rmax / (rsum*rsum),
1*rsum*rsum / (space*space*rmin));
if (last_score && score > last_score) {
rsum -= size; // Undo size addition from just above.
break;
}
last_score = score;
}
return [end, rsum];
}
function layout(tree, level, width, height) {
if (!('children' in tree))
return;
var total = tree.data['$area'];
// XXX why do I need an extra -1/-2 here for width/height to look right?
var x1 = 0, y1 = 0, x2 = width - 1, y2 = height - 2;
x1 += kPadding; y1 += kPadding;
x2 -= kPadding; y2 -= kPadding;
y1 += 14; // XXX get first child height for caption spacing
var pixels_to_units = Math.sqrt(total / ((x2 - x1) * (y2 - y1)));
for (var start = 0, child; child = tree.children[start]; ++start) {
if (x2 - x1 < 60 || y2 - y1 < 40) {
if (child.dom) {
child.dom.style.zIndex = 0;
position(child.dom, -2, -2, 0, 0);
}
continue;
}
// In theory we can dynamically decide whether to split in x or y based
// on aspect ratio. In practice, changing split direction with this
// layout doesn't look very good.
// var ysplit = (y2 - y1) > (x2 - x1);
var ysplit = true;
var space; // Space available along layout axis.
if (ysplit)
space = (y2 - y1) * pixels_to_units;
else
space = (x2 - x1) * pixels_to_units;
var span = selectSpan(tree.children, space, start);
var end = span[0], rsum = span[1];
// Now that we've selected a span, lay out rectangles [start,end) in our
// available space.
var x = x1, y = y1;
for (var i = start; i < end; ++i) {
child = tree.children[i];
if (!child.dom) {
child.parent = tree;
child.dom = makeDom(child, level + 1);
tree.dom.appendChild(child.dom);
} else {
child.dom.style.zIndex = 1;
}
var size = child.data['$area'];
var frac = size / rsum;
if (ysplit) {
width = rsum / space;
height = size / width;
} else {
height = rsum / space;
width = size / height;
}
width /= pixels_to_units;
height /= pixels_to_units;
width = Math.round(width);
height = Math.round(height);
position(child.dom, x, y, width, height);
if ('children' in child) {
layout(child, level + 1, width, height);
}
if (ysplit)
y += height;
else
x += width;
}
// Shrink our available space based on the amount we used.
if (ysplit)
x1 += Math.round((rsum / space) / pixels_to_units);
else
y1 += Math.round((rsum / space) / pixels_to_units);
// end points one past where we ended, which is where we want to
// begin the next iteration, but subtract one to balance the ++ in
// the loop.
start = end - 1;
}
}
function appendTreemap(dom, data) {
var style = getComputedStyle(dom, null);
var width = parseInt(style.width);
var height = parseInt(style.height);
if (!data.dom)
makeDom(data, 0);
dom.appendChild(data.dom);
position(data.dom, 0, 0, width, height);
layout(data, 0, width, height);
}
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment