Add cluster (dendogram) layout.

Based on the Protovis version.  The only difference is that I've dropped
the `orient`, `innerRadius` and `outerRadius` properties so that the D3
version is more flexible.
This commit is contained in:
Jason Davies 2011-04-11 10:50:34 +01:00
Родитель 9f71e4e211
Коммит 6ebe9e5779
12 изменённых файлов: 678 добавлений и 2 удалений

Просмотреть файл

@ -105,6 +105,7 @@ d3.layout.js: \
src/layout/pie.js \
src/layout/stack.js \
src/layout/hierarchy.js \
src/layout/cluster.js \
src/layout/tree.js \
src/layout/treemap.js \
src/end.js

Просмотреть файл

@ -739,6 +739,115 @@ function d3_layout_hierarchyValue(d) {
function d3_layout_hierarchySort(a, b) {
return b.value - a.value;
}
// Implements a hierarchical layout using the cluster (or dendogram) algorithm.
d3.layout.cluster = function() {
var hierarchy = d3.layout.hierarchy(),
group = 0;
function cluster(d, i) {
var nodes = hierarchy.call(this, d, i),
root = nodes[0],
leafCount = 0,
leafIndex = .5 - group / 2;
/* Count the leaf nodes and compute the depth of descendants. */
var p = undefined;
d3_layout_clusterVisitAfter(root, function(n) {
if (n.children) {
n.depth = 1 + d3.max(n.children, function(n) { return n.depth; });
} else {
if (group && (p != n.parent)) {
p = n.parent;
leafCount += group;
}
leafCount++;
n.depth = 0;
}
});
var breadth = 1 / leafCount;
var depth = 1 / root.depth;
/* Compute the unit breadth and depth of each node. */
var p = undefined;
d3_layout_clusterVisitAfter(root, function(n) {
if (n.children) {
n.breadth = d3_layout_clusterMean(n.children, function(n) { return n.breadth; });
} else {
if (group && (p != n.parent)) {
p = n.parent;
leafIndex += group;
}
n.breadth = breadth * leafIndex++;
}
n.depth = 1 - n.depth * depth;
});
/* Compute breadth and depth ranges for space-filling layouts. */
d3_layout_clusterVisitAfter(root, function(n) {
n.minBreadth = n.children
? n.children[0].minBreadth
: (n.breadth - breadth / 2);
n.maxBreadth = n.children
? n.children[n.children.length - 1].maxBreadth
: (n.breadth + breadth / 2);
});
d3_layout_clusterVisitBefore(root, function(n) {
n.minDepth = n.parent
? n.parent.maxDepth
: 0;
n.maxDepth = n.parent
? (n.depth + root.depth)
: (n.minDepth + 2 * root.depth);
});
root.minDepth = -depth;
return nodes;
}
cluster.sort = d3.rebind(cluster, hierarchy.sort);
cluster.children = d3.rebind(cluster, hierarchy.children);
cluster.value = d3.rebind(cluster, hierarchy.value);
cluster.group = function(x) {
if (!arguments.length) return group;
group = x;
return cluster;
};
return cluster;
};
d3_layout_clusterVisitAfter = d3_layout_treeVisitAfter;
function d3_layout_clusterVisitBefore(node, callback) {
function visit(node, previousSibling) {
callback(node, previousSibling);
var children = node.children;
if (children) {
var child,
previousChild = null,
i = -1,
n = children.length;
while (++i < n) {
child = children[i];
visit(child, previousChild);
previousChild = child;
}
}
}
visit(node, null);
}
function d3_layout_clusterSum(array, f) {
var o = {};
return array.reduce(f
? function(p, d, i) { o.index = i; return p + f.call(o, d); }
: function(p, d) { return p + d; }, 0);
}
function d3_layout_clusterMean(array, f) {
return d3_layout_clusterSum(array, f) / array.length;
}
// Node-link tree diagram using the Reingold-Tilford "tidy" algorithm
d3.layout.tree = function() {
var hierarchy = d3.layout.hierarchy(),

2
d3.layout.min.js поставляемый

Различия файлов скрыты, потому что одна или несколько строк слишком длинны

Просмотреть файл

@ -0,0 +1,14 @@
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html;charset=utf-8">
<title>Flare Dendogram</title>
<script type="text/javascript" src="../../d3.js"></script>
<script type="text/javascript" src="../../d3.layout.js"></script>
<link type="text/css" rel="stylesheet" href="cluster.css"/>
</head>
<body>
<div id="chart"></div>
<script type="text/javascript" src="cluster-radial.js"></script>
</body>
</html>

Просмотреть файл

@ -0,0 +1,65 @@
var w = 800,
h = 800,
m = 120;
var cluster = d3.layout.cluster()
.sort(null)
.group(true)
.children(function(d) { return isNaN(d.value) ? d3.entries(d.value) : null; });
var vis = d3.select("#chart").append("svg:svg")
.attr("width", w + 2 * m)
.attr("height", h + 2 * m)
.append("svg:g")
.attr("transform", "translate(" + (w / 2 + m) + "," + (h / 2 + m) +")");
d3.json("flare.json", function(json) {
var nodes = cluster(d3.entries(json)[0]);
var link = vis.selectAll("g.link")
.data(nodes)
.enter().append("svg:g")
.attr("class", "link")
.selectAll("line")
.data(children)
.enter();
link.append("svg:line")
.attr("x1", function(d) { return x(d.parent); })
.attr("y1", function(d) { return y(d.parent); })
.attr("x2", function(d) { return x(d.child); })
.attr("y2", function(d) { return y(d.child); });
var node = vis.selectAll("g.node")
.data(nodes)
.enter().append("svg:g")
.attr("class", "node")
.attr("transform", function(d) { return "translate(" + x(d) + "," + y(d) + ")rotate(" + (a(d)-90) + ")"; })
node.append("svg:circle")
.attr("r", 4.5);
node.append("svg:text")
.attr("dx", function(d) { return a(d) < 180 ? 8 : -8; })
.attr("dy", ".31em")
.attr("text-anchor", function(d) { return a(d) < 180 ? "start" : "end"; })
.attr("transform", function(d) { return a(d) < 180 ? null : "rotate(180)"; })
//.attr("transform", function(d) { return "rotate(" + (a(d.breadth) - 90) + ")"; })
.text(function(d) { return d.data.key; });
// Returns parent+child objects for any children of `d`.
function children(d) {
return (d.children || []).map(function(v) {
return {
parent: d,
child: v
};
});
}
// Radial scales for x and y.
function a(d) { return d.breadth * 360; }
function r(d) { return d.depth * w / 2; }
function x(d) { return r(d) * Math.cos((a(d) - 90) / 180 * Math.PI); }
function y(d) { return r(d) * Math.sin((a(d) - 90) / 180 * Math.PI); }
});

Просмотреть файл

@ -0,0 +1,14 @@
.node circle {
fill: #fff;
stroke: steelblue;
stroke-width: 1.5px;
}
.node {
font: 10px sans-serif;
}
.link {
stroke: #ccc;
stroke-width: 1.5px;
}

Просмотреть файл

@ -0,0 +1,14 @@
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html;charset=utf-8">
<title>Flare Dendogram</title>
<script type="text/javascript" src="../../d3.js"></script>
<script type="text/javascript" src="../../d3.layout.js"></script>
<link type="text/css" rel="stylesheet" href="cluster.css"/>
</head>
<body>
<div id="chart"></div>
<script type="text/javascript" src="cluster.js"></script>
</body>
</html>

Просмотреть файл

@ -0,0 +1,63 @@
var w = 200,
h = 2200,
x = d3.scale.linear().range([0, w]),
y = d3.scale.linear().range([0, h]);
var cluster = d3.layout.cluster()
.sort(null)
.group(true)
.children(function(d) { return isNaN(d.value) ? d3.entries(d.value) : null; });
var vis = d3.select("#chart").append("svg:svg")
.attr("width", w + 40 + 120)
.attr("height", h)
.append("svg:g")
.attr("transform", "translate(40, 0)");
d3.json("flare.json", function(json) {
var nodes = cluster(d3.entries(json)[0]);
var link = vis.selectAll("g.link")
.data(nodes)
.enter().append("svg:g")
.attr("class", "link")
.selectAll("line")
.data(children)
.enter();
link.append("svg:line")
.attr("x1", function(d) { return x(d.parent.depth); })
.attr("y1", function(d) { return y(d.parent.breadth); })
.attr("x2", function(d) { return x(d.parent.depth); })
.attr("y2", function(d) { return y(d.child.breadth); });
link.append("svg:line")
.attr("x1", function(d) { return x(d.parent.depth); })
.attr("y1", function(d) { return y(d.child.breadth); })
.attr("x2", function(d) { return x(d.child.depth); })
.attr("y2", function(d) { return y(d.child.breadth); });
var node = vis.selectAll("g.node")
.data(nodes)
.enter().append("svg:g")
.attr("class", "node")
.attr("transform", function(d) { return "translate(" + x(d.depth) + "," + y(d.breadth) + ")"; })
node.append("svg:circle")
.attr("r", 4.5);
node.append("svg:text")
.attr("dx", function(d) { return d.children ? -8 : 8; })
.attr("dy", 3)
.attr("text-anchor", function(d) { return d.children ? "end" : "start"; })
.text(function(d) { return d.data.key; });
// Returns parent+child objects for any children of `d`.
function children(d) {
return (d.children || []).map(function(v) {
return {
parent: d,
child: v
};
});
}
});

286
examples/cluster/flare.json Normal file
Просмотреть файл

@ -0,0 +1,286 @@
{
"flare": {
"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
}
}
}

Просмотреть файл

@ -16,6 +16,7 @@
<li><a href="chord/chord.html">chord</a></li>
<li><a href="choropleth/choropleth.html">choropleth</a></li>
<li><a href="contour/contour.html">contour</a></li>
<li><a href="cluster/cluster.html">cluster</a></li>
<li><a href="delaunay/delaunay.html">delaunay</a></li>
<li><a href="donut/donut.html">donut</a></li>
<li><a href="dot/dot.html">dot</a></li>

Просмотреть файл

@ -41,7 +41,7 @@ d3.json("flare.json", function(json) {
.attr("dx", function(d) { return d.x < 180 ? 8 : -8; })
.attr("dy", ".31em")
.attr("text-anchor", function(d) { return d.x < 180 ? "start" : "end"; })
.attr("transform", function(d) { return d.x < 180 ? null : "rotate(180)"; })
.attr("transform", function(d) { return (d.x < 180) ^ (d.children) ? null : "rotate(180)"; })
.text(function(d) { return d.data.key; });
// Returns parent+child objects for any children of `d`.

109
src/layout/cluster.js Normal file
Просмотреть файл

@ -0,0 +1,109 @@
// Implements a hierarchical layout using the cluster (or dendogram) algorithm.
d3.layout.cluster = function() {
var hierarchy = d3.layout.hierarchy(),
group = 0;
function cluster(d, i) {
var nodes = hierarchy.call(this, d, i),
root = nodes[0],
leafCount = 0,
leafIndex = .5 - group / 2;
/* Count the leaf nodes and compute the depth of descendants. */
var p = undefined;
d3_layout_clusterVisitAfter(root, function(n) {
if (n.children) {
n.depth = 1 + d3.max(n.children, function(n) { return n.depth; });
} else {
if (group && (p != n.parent)) {
p = n.parent;
leafCount += group;
}
leafCount++;
n.depth = 0;
}
});
var breadth = 1 / leafCount;
var depth = 1 / root.depth;
/* Compute the unit breadth and depth of each node. */
var p = undefined;
d3_layout_clusterVisitAfter(root, function(n) {
if (n.children) {
n.breadth = d3_layout_clusterMean(n.children, function(n) { return n.breadth; });
} else {
if (group && (p != n.parent)) {
p = n.parent;
leafIndex += group;
}
n.breadth = breadth * leafIndex++;
}
n.depth = 1 - n.depth * depth;
});
/* Compute breadth and depth ranges for space-filling layouts. */
d3_layout_clusterVisitAfter(root, function(n) {
n.minBreadth = n.children
? n.children[0].minBreadth
: (n.breadth - breadth / 2);
n.maxBreadth = n.children
? n.children[n.children.length - 1].maxBreadth
: (n.breadth + breadth / 2);
});
d3_layout_clusterVisitBefore(root, function(n) {
n.minDepth = n.parent
? n.parent.maxDepth
: 0;
n.maxDepth = n.parent
? (n.depth + root.depth)
: (n.minDepth + 2 * root.depth);
});
root.minDepth = -depth;
return nodes;
}
cluster.sort = d3.rebind(cluster, hierarchy.sort);
cluster.children = d3.rebind(cluster, hierarchy.children);
cluster.value = d3.rebind(cluster, hierarchy.value);
cluster.group = function(x) {
if (!arguments.length) return group;
group = x;
return cluster;
};
return cluster;
};
d3_layout_clusterVisitAfter = d3_layout_treeVisitAfter;
function d3_layout_clusterVisitBefore(node, callback) {
function visit(node, previousSibling) {
callback(node, previousSibling);
var children = node.children;
if (children) {
var child,
previousChild = null,
i = -1,
n = children.length;
while (++i < n) {
child = children[i];
visit(child, previousChild);
previousChild = child;
}
}
}
visit(node, null);
}
function d3_layout_clusterSum(array, f) {
var o = {};
return array.reduce(f
? function(p, d, i) { o.index = i; return p + f.call(o, d); }
: function(p, d) { return p + d; }, 0);
}
function d3_layout_clusterMean(array, f) {
return d3_layout_clusterSum(array, f) / array.length;
}