Skip to content

Instantly share code, notes, and snippets.

@nitaku
Last active May 23, 2023 06:44
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 2 You must be signed in to fork a gist
  • Save nitaku/833632f23c308ae2d58b to your computer and use it in GitHub Desktop.
Save nitaku/833632f23c308ae2d58b to your computer and use it in GitHub Desktop.
Isometric treemap (flare)

A first attempt at making a real isometric treemap. Unlike previous attempts, this experiment uses the treemap layout to actually represent a tree (in this example, the flare package hierarchy).

Treemaps are very good to show hierarchies in a compact way, and excel in the case of attributed hierarchies with a quantitative attribute (a classical example would be a file system hierarchy, in which size is given by the number of bytes of each file). However, they are not very good in representing internal nodes, parent-child relations and depth.

Nested treemaps introduce some padding between a parent and its children to better represent this aspect, losing accuracy in area encoding. Despite this improvement, hierarchy depth is still not represented.

An isometric treemap loses some clarity and compactness to introduce an intuitive representation of depth as 2.5D elevation. By controlling both the padding used by the nesting algorithm and the height of the solids, occlusion (one of the worst problems of 3D and 2.5D solutions) can be fairly limited.

{
"name": "flare",
"children": [
{
"name": "analytics",
"children": [
{
"name": "cluster",
"children": [
{
"name": "AgglomerativeCluster",
"size": 3938
},
{
"name": "CommunityStructure",
"size": 3812
},
{
"name": "HierarchicalCluster",
"size": 6714
},
{
"name": "MergeEdge",
"size": 743
}
]
},
{
"name": "graph",
"children": [
{
"name": "BetweennessCentrality",
"size": 3534
},
{
"name": "LinkDistance",
"size": 5731
},
{
"name": "MaxFlowMinCut",
"size": 7840
},
{
"name": "ShortestPaths",
"size": 5914
},
{
"name": "SpanningTree",
"size": 3416
}
]
},
{
"name": "optimization",
"children": [
{
"name": "AspectRatioBanker",
"size": 7074
}
]
}
]
},
{
"name": "animate",
"children": [
{
"name": "Easing",
"size": 17010
},
{
"name": "FunctionSequence",
"size": 5842
},
{
"name": "interpolate",
"children": [
{
"name": "ArrayInterpolator",
"size": 1983
},
{
"name": "ColorInterpolator",
"size": 2047
},
{
"name": "DateInterpolator",
"size": 1375
},
{
"name": "Interpolator",
"size": 8746
},
{
"name": "MatrixInterpolator",
"size": 2202
},
{
"name": "NumberInterpolator",
"size": 1382
},
{
"name": "ObjectInterpolator",
"size": 1629
},
{
"name": "PointInterpolator",
"size": 1675
},
{
"name": "RectangleInterpolator",
"size": 2042
}
]
},
{
"name": "ISchedulable",
"size": 1041
},
{
"name": "Parallel",
"size": 5176
},
{
"name": "Pause",
"size": 449
},
{
"name": "Scheduler",
"size": 5593
},
{
"name": "Sequence",
"size": 5534
},
{
"name": "Transition",
"size": 9201
},
{
"name": "Transitioner",
"size": 19975
},
{
"name": "TransitionEvent",
"size": 1116
},
{
"name": "Tween",
"size": 6006
}
]
},
{
"name": "data",
"children": [
{
"name": "converters",
"children": [
{
"name": "Converters",
"size": 721
},
{
"name": "DelimitedTextConverter",
"size": 4294
},
{
"name": "GraphMLConverter",
"size": 9800
},
{
"name": "IDataConverter",
"size": 1314
},
{
"name": "JSONConverter",
"size": 2220
}
]
},
{
"name": "DataField",
"size": 1759
},
{
"name": "DataSchema",
"size": 2165
},
{
"name": "DataSet",
"size": 586
},
{
"name": "DataSource",
"size": 3331
},
{
"name": "DataTable",
"size": 772
},
{
"name": "DataUtil",
"size": 3322
}
]
},
{
"name": "display",
"children": [
{
"name": "DirtySprite",
"size": 8833
},
{
"name": "LineSprite",
"size": 1732
},
{
"name": "RectSprite",
"size": 3623
},
{
"name": "TextSprite",
"size": 10066
}
]
},
{
"name": "flex",
"children": [
{
"name": "FlareVis",
"size": 4116
}
]
},
{
"name": "physics",
"children": [
{
"name": "DragForce",
"size": 1082
},
{
"name": "GravityForce",
"size": 1336
},
{
"name": "IForce",
"size": 319
},
{
"name": "NBodyForce",
"size": 10498
},
{
"name": "Particle",
"size": 2822
},
{
"name": "Simulation",
"size": 9983
},
{
"name": "Spring",
"size": 2213
},
{
"name": "SpringForce",
"size": 1681
}
]
},
{
"name": "query",
"children": [
{
"name": "AggregateExpression",
"size": 1616
},
{
"name": "And",
"size": 1027
},
{
"name": "Arithmetic",
"size": 3891
},
{
"name": "Average",
"size": 891
},
{
"name": "BinaryExpression",
"size": 2893
},
{
"name": "Comparison",
"size": 5103
},
{
"name": "CompositeExpression",
"size": 3677
},
{
"name": "Count",
"size": 781
},
{
"name": "DateUtil",
"size": 4141
},
{
"name": "Distinct",
"size": 933
},
{
"name": "Expression",
"size": 5130
},
{
"name": "ExpressionIterator",
"size": 3617
},
{
"name": "Fn",
"size": 3240
},
{
"name": "If",
"size": 2732
},
{
"name": "IsA",
"size": 2039
},
{
"name": "Literal",
"size": 1214
},
{
"name": "Match",
"size": 3748
},
{
"name": "Maximum",
"size": 843
},
{
"name": "methods",
"children": [
{
"name": "add",
"size": 593
},
{
"name": "and",
"size": 330
},
{
"name": "average",
"size": 287
},
{
"name": "count",
"size": 277
},
{
"name": "distinct",
"size": 292
},
{
"name": "div",
"size": 595
},
{
"name": "eq",
"size": 594
},
{
"name": "fn",
"size": 460
},
{
"name": "gt",
"size": 603
},
{
"name": "gte",
"size": 625
},
{
"name": "iff",
"size": 748
},
{
"name": "isa",
"size": 461
},
{
"name": "lt",
"size": 597
},
{
"name": "lte",
"size": 619
},
{
"name": "max",
"size": 283
},
{
"name": "min",
"size": 283
},
{
"name": "mod",
"size": 591
},
{
"name": "mul",
"size": 603
},
{
"name": "neq",
"size": 599
},
{
"name": "not",
"size": 386
},
{
"name": "or",
"size": 323
},
{
"name": "orderby",
"size": 307
},
{
"name": "range",
"size": 772
},
{
"name": "select",
"size": 296
},
{
"name": "stddev",
"size": 363
},
{
"name": "sub",
"size": 600
},
{
"name": "sum",
"size": 280
},
{
"name": "update",
"size": 307
},
{
"name": "variance",
"size": 335
},
{
"name": "where",
"size": 299
},
{
"name": "xor",
"size": 354
},
{
"name": "_",
"size": 264
}
]
},
{
"name": "Minimum",
"size": 843
},
{
"name": "Not",
"size": 1554
},
{
"name": "Or",
"size": 970
},
{
"name": "Query",
"size": 13896
},
{
"name": "Range",
"size": 1594
},
{
"name": "StringUtil",
"size": 4130
},
{
"name": "Sum",
"size": 791
},
{
"name": "Variable",
"size": 1124
},
{
"name": "Variance",
"size": 1876
},
{
"name": "Xor",
"size": 1101
}
]
},
{
"name": "scale",
"children": [
{
"name": "IScaleMap",
"size": 2105
},
{
"name": "LinearScale",
"size": 1316
},
{
"name": "LogScale",
"size": 3151
},
{
"name": "OrdinalScale",
"size": 3770
},
{
"name": "QuantileScale",
"size": 2435
},
{
"name": "QuantitativeScale",
"size": 4839
},
{
"name": "RootScale",
"size": 1756
},
{
"name": "Scale",
"size": 4268
},
{
"name": "ScaleType",
"size": 1821
},
{
"name": "TimeScale",
"size": 5833
}
]
},
{
"name": "util",
"children": [
{
"name": "Arrays",
"size": 8258
},
{
"name": "Colors",
"size": 10001
},
{
"name": "Dates",
"size": 8217
},
{
"name": "Displays",
"size": 12555
},
{
"name": "Filter",
"size": 2324
},
{
"name": "Geometry",
"size": 10993
},
{
"name": "heap",
"children": [
{
"name": "FibonacciHeap",
"size": 9354
},
{
"name": "HeapNode",
"size": 1233
}
]
},
{
"name": "IEvaluable",
"size": 335
},
{
"name": "IPredicate",
"size": 383
},
{
"name": "IValueProxy",
"size": 874
},
{
"name": "math",
"children": [
{
"name": "DenseMatrix",
"size": 3165
},
{
"name": "IMatrix",
"size": 2815
},
{
"name": "SparseMatrix",
"size": 3366
}
]
},
{
"name": "Maths",
"size": 17705
},
{
"name": "Orientation",
"size": 1486
},
{
"name": "palette",
"children": [
{
"name": "ColorPalette",
"size": 6367
},
{
"name": "Palette",
"size": 1229
},
{
"name": "ShapePalette",
"size": 2059
},
{
"name": "SizePalette",
"size": 2291
}
]
},
{
"name": "Property",
"size": 5559
},
{
"name": "Shapes",
"size": 19118
},
{
"name": "Sort",
"size": 6887
},
{
"name": "Stats",
"size": 6557
},
{
"name": "Strings",
"size": 22026
}
]
},
{
"name": "vis",
"children": [
{
"name": "axis",
"children": [
{
"name": "Axes",
"size": 1302
},
{
"name": "Axis",
"size": 24593
},
{
"name": "AxisGridLine",
"size": 652
},
{
"name": "AxisLabel",
"size": 636
},
{
"name": "CartesianAxes",
"size": 6703
}
]
},
{
"name": "controls",
"children": [
{
"name": "AnchorControl",
"size": 2138
},
{
"name": "ClickControl",
"size": 3824
},
{
"name": "Control",
"size": 1353
},
{
"name": "ControlList",
"size": 4665
},
{
"name": "DragControl",
"size": 2649
},
{
"name": "ExpandControl",
"size": 2832
},
{
"name": "HoverControl",
"size": 4896
},
{
"name": "IControl",
"size": 763
},
{
"name": "PanZoomControl",
"size": 5222
},
{
"name": "SelectionControl",
"size": 7862
},
{
"name": "TooltipControl",
"size": 8435
}
]
},
{
"name": "data",
"children": [
{
"name": "Data",
"size": 20544
},
{
"name": "DataList",
"size": 19788
},
{
"name": "DataSprite",
"size": 10349
},
{
"name": "EdgeSprite",
"size": 3301
},
{
"name": "NodeSprite",
"size": 19382
},
{
"name": "render",
"children": [
{
"name": "ArrowType",
"size": 698
},
{
"name": "EdgeRenderer",
"size": 5569
},
{
"name": "IRenderer",
"size": 353
},
{
"name": "ShapeRenderer",
"size": 2247
}
]
},
{
"name": "ScaleBinding",
"size": 11275
},
{
"name": "Tree",
"size": 7147
},
{
"name": "TreeBuilder",
"size": 9930
}
]
},
{
"name": "events",
"children": [
{
"name": "DataEvent",
"size": 2313
},
{
"name": "SelectionEvent",
"size": 1880
},
{
"name": "TooltipEvent",
"size": 1701
},
{
"name": "VisualizationEvent",
"size": 1117
}
]
},
{
"name": "legend",
"children": [
{
"name": "Legend",
"size": 20859
},
{
"name": "LegendItem",
"size": 4614
},
{
"name": "LegendRange",
"size": 10530
}
]
},
{
"name": "operator",
"children": [
{
"name": "distortion",
"children": [
{
"name": "BifocalDistortion",
"size": 4461
},
{
"name": "Distortion",
"size": 6314
},
{
"name": "FisheyeDistortion",
"size": 3444
}
]
},
{
"name": "encoder",
"children": [
{
"name": "ColorEncoder",
"size": 3179
},
{
"name": "Encoder",
"size": 4060
},
{
"name": "PropertyEncoder",
"size": 4138
},
{
"name": "ShapeEncoder",
"size": 1690
},
{
"name": "SizeEncoder",
"size": 1830
}
]
},
{
"name": "filter",
"children": [
{
"name": "FisheyeTreeFilter",
"size": 5219
},
{
"name": "GraphDistanceFilter",
"size": 3165
},
{
"name": "VisibilityFilter",
"size": 3509
}
]
},
{
"name": "IOperator",
"size": 1286
},
{
"name": "label",
"children": [
{
"name": "Labeler",
"size": 9956
},
{
"name": "RadialLabeler",
"size": 3899
},
{
"name": "StackedAreaLabeler",
"size": 3202
}
]
},
{
"name": "layout",
"children": [
{
"name": "AxisLayout",
"size": 6725
},
{
"name": "BundledEdgeRouter",
"size": 3727
},
{
"name": "CircleLayout",
"size": 9317
},
{
"name": "CirclePackingLayout",
"size": 12003
},
{
"name": "DendrogramLayout",
"size": 4853
},
{
"name": "ForceDirectedLayout",
"size": 8411
},
{
"name": "IcicleTreeLayout",
"size": 4864
},
{
"name": "IndentedTreeLayout",
"size": 3174
},
{
"name": "Layout",
"size": 7881
},
{
"name": "NodeLinkTreeLayout",
"size": 12870
},
{
"name": "PieLayout",
"size": 2728
},
{
"name": "RadialTreeLayout",
"size": 12348
},
{
"name": "RandomLayout",
"size": 870
},
{
"name": "StackedAreaLayout",
"size": 9121
},
{
"name": "TreeMapLayout",
"size": 9191
}
]
},
{
"name": "Operator",
"size": 2490
},
{
"name": "OperatorList",
"size": 5248
},
{
"name": "OperatorSequence",
"size": 4190
},
{
"name": "OperatorSwitch",
"size": 2581
},
{
"name": "SortOperator",
"size": 2023
}
]
},
{
"name": "Visualization",
"size": 16540
}
]
}
]
}
svg = d3.select('svg')
width = svg.node().getBoundingClientRect().width
height = svg.node().getBoundingClientRect().height
# append a group for zoomable content
zoomable_layer = svg.append('g')
# define a zoom behavior
zoom = d3.behavior.zoom()
.scaleExtent([1,100]) # min-max zoom
.on 'zoom', () ->
# GEOMETRIC ZOOM
zoomable_layer
.attr
transform: "translate(#{zoom.translate()})scale(#{zoom.scale()})"
# bind the zoom behavior to the main SVG
svg.call(zoom)
vis = zoomable_layer.append('g')
.attr
class: 'vis'
transform: "translate(#{width/2},#{height/3-112})"
# [x, y, h] -> [-Math.sqrt(3)/2*x+Math.sqrt(3)/2*y, 0.5*x+0.5*y-h]
isometric = (_3d_p) -> [-Math.sqrt(3)/2*_3d_p[0]+Math.sqrt(3)/2*_3d_p[1], +0.5*_3d_p[0]+0.5*_3d_p[1]-_3d_p[2]]
parallelepipedon = (d) ->
d.x = 0 if not d.x?
d.y = 0 if not d.y?
d.h = 0 if not d.h?
d.dx = 10 if not d.dx?
d.dy = 10 if not d.dy?
d.dh = 10 if not d.dh?
fb = isometric [d.x, d.y, d.h],
mlb = isometric [d.x+d.dx, d.y, d.h],
nb = isometric [d.x+d.dx, d.y+d.dy, d.h],
mrb = isometric [d.x, d.y+d.dy, d.h],
ft = isometric [d.x, d.y, d.h+d.dh],
mlt = isometric [d.x+d.dx, d.y, d.h+d.dh],
nt = isometric [d.x+d.dx, d.y+d.dy, d.h+d.dh],
mrt = isometric [d.x, d.y+d.dy, d.h+d.dh]
d.iso = {
face_bottom: [fb, mrb, nb, mlb],
face_left: [mlb, mlt, nt, nb],
face_right: [nt, mrt, mrb, nb],
face_top: [ft, mrt, nt, mlt],
outline: [ft, mrt, mrb, nb, mlb, mlt],
fb: fb,
mlb: mlb,
nb: nb,
mrb: mrb,
ft: ft,
mlt: mlt,
nt: nt,
mrt: mrt
}
return d
ordering = (a,b) -> b.i - a.i
iso_layout = (data, shape, scale) ->
scale = 1 if not scale?
data.forEach (d) ->
shape(d, scale)
# this uses the treemap ordering in some way... (!!!)
# also, use the index to obtain a total ordering
data.sort ordering
path_generator = (d) -> 'M' + d.map((p)->p.join(' ')).join('L') + 'z'
DH = 5
PAD = 4
treemap = d3.layout.treemap()
.size([400, 400])
.value((d) -> d.size)
.sort((a,b) -> ordering(b,a)) # same as before, but inverted
.padding(PAD)
.round(false) # bugfix: d3 wrong ordering
color = d3.scale.category20c()
correct_x = d3.scale.linear()
.domain([0, width])
.range([0, width*1.05])
correct_y = d3.scale.linear()
.domain([0, height])
.range([0, height*3/4])
d3.json 'flare.json', (tree) ->
walk = (n, depth) ->
n.depth = depth
n.dh = DH
n.h = DH*depth
if n.children?
for child in n.children
walk(child, depth+1)
n.children.sort (a,b) -> a.size - b.size
n.size = d3.sum n.children, (d) -> d.size
walk(tree, 0)
# depth-first enumeration
i = 0
walk_i = (n) ->
if n.children?
for child in n.children
walk_i(child)
n.i = i
i += 1
walk_i(tree)
data = treemap.nodes(tree)
iso_layout(data, parallelepipedon)
data.forEach (d, i) ->
# save the template color
d.template_color = d3.hcl(color(d.i))
pipedons = vis.selectAll('.pipedon')
.data(data)
enter_pipedons = pipedons.enter().append('g')
.attr
class: 'pipedon'
enter_pipedons.append('path')
.attr
class: 'iso face bottom'
d: (d) -> path_generator(d.iso.face_bottom)
enter_pipedons.append('path')
.attr
class: 'iso face left'
d: (d) -> path_generator(d.iso.face_left)
fill: (d) -> d.template_color
enter_pipedons.append('path')
.attr
class: 'iso face right'
d: (d) -> path_generator(d.iso.face_right)
fill: (d) -> d3.hcl(d.template_color.h, d.template_color.c, d.template_color.l-12)
enter_pipedons.append('path')
.attr
class: 'iso face top'
d: (d) -> path_generator(d.iso.face_top)
fill: (d) -> d3.hcl(d.template_color.h, d.template_color.c, d.template_color.l+12)
enter_labels_g = enter_pipedons.append('g')
.classed('hidden', (d) -> d.children?)
enter_labels = enter_labels_g.append('svg')
.attr
class: 'label'
enter_labels.append('text')
.text((d) -> d.name.toUpperCase())
.attr
dy: '.35em'
.each (node) ->
bbox = this.getBBox()
bbox_aspect = bbox.width / bbox.height
node_bbox = {width: node.dx, height: node.dy}
node_bbox_aspect = node_bbox.width / node_bbox.height
rotate = bbox_aspect >= 1 and node_bbox_aspect < 1 or bbox_aspect < 1 and node_bbox_aspect >= 1
node.label_bbox = {
x: bbox.x+(bbox.width-correct_x(bbox.width))/2,
y: bbox.y+(bbox.height-correct_y(bbox.height))/2,
width: correct_x(bbox.width),
height: correct_y(bbox.height)
}
if rotate
node.label_bbox = {
x: node.label_bbox.y,
y: node.label_bbox.x,
width: node.label_bbox.height,
height: node.label_bbox.width
}
d3.select(this).attr('transform', 'rotate(90) translate(0,1)')
enter_labels
.each (d) ->
d.iso_x = isometric([d.x+d.dx/2, d.y+d.dy/2, d.h+d.dh])[0]-d.dx/2
d.iso_y = isometric([d.x+d.dx/2, d.y+d.dy/2, d.h+d.dh])[1]-d.dy/2
enter_labels
.attr
x: (d) -> d.iso_x
y: (d) -> d.iso_y
width: (node) -> node.dx
height: (node) -> node.dy
viewBox: (node) -> "#{node.label_bbox.x} #{node.label_bbox.y} #{node.label_bbox.width} #{node.label_bbox.height}"
preserveAspectRatio: 'none'
fill: (d) -> d3.hcl(d.template_color.h, d.template_color.c, d.template_color.l-12)
enter_labels_g
.attr
transform: (d) -> "translate(#{d.iso_x+d.dx/2},#{d.iso_y+d.dy/2}) scale(1, #{1/Math.sqrt(3)}) rotate(-45) translate(#{-(d.iso_x+d.dx/2)},#{-(d.iso_y+d.dy/2)})"
enter_pipedons.append('path')
.attr
class: 'iso outline'
d: (d) -> path_generator(d.iso.outline)
enter_pipedons.append('title')
.text((d) -> d.name)
.iso.outline {
stroke: #333;
fill: none;
vector-effect: non-scaling-stroke;
}
.label {
pointer-events: none;
text-anchor: middle;
font-family: Impact;
}
.pipedon:hover .label {
fill: black;
}
.pipedon:hover .face {
fill: yellow;
}
.hidden {
display: none;
}
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Isometric treemap</title>
<script src="http://d3js.org/d3.v3.min.js"></script>
<link rel="stylesheet" href="index.css">
</head>
<body>
<svg width="960px" height="500px"></svg>
<script src="index.js"></script>
</body>
</html>
// Generated by CoffeeScript 1.4.0
(function() {
var DH, PAD, color, correct_x, correct_y, height, iso_layout, isometric, ordering, parallelepipedon, path_generator, svg, treemap, vis, width, zoom, zoomable_layer;
svg = d3.select('svg');
width = svg.node().getBoundingClientRect().width;
height = svg.node().getBoundingClientRect().height;
zoomable_layer = svg.append('g');
zoom = d3.behavior.zoom().scaleExtent([1, 100]).on('zoom', function() {
return zoomable_layer.attr({
transform: "translate(" + (zoom.translate()) + ")scale(" + (zoom.scale()) + ")"
});
});
svg.call(zoom);
vis = zoomable_layer.append('g').attr({
"class": 'vis',
transform: "translate(" + (width / 2) + "," + (height / 3 - 112) + ")"
});
isometric = function(_3d_p) {
return [-Math.sqrt(3) / 2 * _3d_p[0] + Math.sqrt(3) / 2 * _3d_p[1], +0.5 * _3d_p[0] + 0.5 * _3d_p[1] - _3d_p[2]];
};
parallelepipedon = function(d) {
var fb, ft, mlb, mlt, mrb, mrt, nb, nt;
if (!(d.x != null)) {
d.x = 0;
}
if (!(d.y != null)) {
d.y = 0;
}
if (!(d.h != null)) {
d.h = 0;
}
if (!(d.dx != null)) {
d.dx = 10;
}
if (!(d.dy != null)) {
d.dy = 10;
}
if (!(d.dh != null)) {
d.dh = 10;
}
fb = isometric([d.x, d.y, d.h], mlb = isometric([d.x + d.dx, d.y, d.h], nb = isometric([d.x + d.dx, d.y + d.dy, d.h], mrb = isometric([d.x, d.y + d.dy, d.h], ft = isometric([d.x, d.y, d.h + d.dh], mlt = isometric([d.x + d.dx, d.y, d.h + d.dh], nt = isometric([d.x + d.dx, d.y + d.dy, d.h + d.dh], mrt = isometric([d.x, d.y + d.dy, d.h + d.dh]))))))));
d.iso = {
face_bottom: [fb, mrb, nb, mlb],
face_left: [mlb, mlt, nt, nb],
face_right: [nt, mrt, mrb, nb],
face_top: [ft, mrt, nt, mlt],
outline: [ft, mrt, mrb, nb, mlb, mlt],
fb: fb,
mlb: mlb,
nb: nb,
mrb: mrb,
ft: ft,
mlt: mlt,
nt: nt,
mrt: mrt
};
return d;
};
ordering = function(a, b) {
return b.i - a.i;
};
iso_layout = function(data, shape, scale) {
if (!(scale != null)) {
scale = 1;
}
data.forEach(function(d) {
return shape(d, scale);
});
return data.sort(ordering);
};
path_generator = function(d) {
return 'M' + d.map(function(p) {
return p.join(' ');
}).join('L') + 'z';
};
DH = 5;
PAD = 4;
treemap = d3.layout.treemap().size([400, 400]).value(function(d) {
return d.size;
}).sort(function(a, b) {
return ordering(b, a);
}).padding(PAD).round(false);
color = d3.scale.category20c();
correct_x = d3.scale.linear().domain([0, width]).range([0, width * 1.05]);
correct_y = d3.scale.linear().domain([0, height]).range([0, height * 3 / 4]);
d3.json('flare.json', function(tree) {
var data, enter_labels, enter_labels_g, enter_pipedons, i, pipedons, walk, walk_i;
walk = function(n, depth) {
var child, _i, _len, _ref;
n.depth = depth;
n.dh = DH;
n.h = DH * depth;
if (n.children != null) {
_ref = n.children;
for (_i = 0, _len = _ref.length; _i < _len; _i++) {
child = _ref[_i];
walk(child, depth + 1);
}
n.children.sort(function(a, b) {
return a.size - b.size;
});
return n.size = d3.sum(n.children, function(d) {
return d.size;
});
}
};
walk(tree, 0);
i = 0;
walk_i = function(n) {
var child, _i, _len, _ref;
if (n.children != null) {
_ref = n.children;
for (_i = 0, _len = _ref.length; _i < _len; _i++) {
child = _ref[_i];
walk_i(child);
}
}
n.i = i;
return i += 1;
};
walk_i(tree);
data = treemap.nodes(tree);
iso_layout(data, parallelepipedon);
data.forEach(function(d, i) {
return d.template_color = d3.hcl(color(d.i));
});
pipedons = vis.selectAll('.pipedon').data(data);
enter_pipedons = pipedons.enter().append('g').attr({
"class": 'pipedon'
});
enter_pipedons.append('path').attr({
"class": 'iso face bottom',
d: function(d) {
return path_generator(d.iso.face_bottom);
}
});
enter_pipedons.append('path').attr({
"class": 'iso face left',
d: function(d) {
return path_generator(d.iso.face_left);
},
fill: function(d) {
return d.template_color;
}
});
enter_pipedons.append('path').attr({
"class": 'iso face right',
d: function(d) {
return path_generator(d.iso.face_right);
},
fill: function(d) {
return d3.hcl(d.template_color.h, d.template_color.c, d.template_color.l - 12);
}
});
enter_pipedons.append('path').attr({
"class": 'iso face top',
d: function(d) {
return path_generator(d.iso.face_top);
},
fill: function(d) {
return d3.hcl(d.template_color.h, d.template_color.c, d.template_color.l + 12);
}
});
enter_labels_g = enter_pipedons.append('g').classed('hidden', function(d) {
return d.children != null;
});
enter_labels = enter_labels_g.append('svg').attr({
"class": 'label'
});
enter_labels.append('text').text(function(d) {
return d.name.toUpperCase();
}).attr({
dy: '.35em'
}).each(function(node) {
var bbox, bbox_aspect, node_bbox, node_bbox_aspect, rotate;
bbox = this.getBBox();
bbox_aspect = bbox.width / bbox.height;
node_bbox = {
width: node.dx,
height: node.dy
};
node_bbox_aspect = node_bbox.width / node_bbox.height;
rotate = bbox_aspect >= 1 && node_bbox_aspect < 1 || bbox_aspect < 1 && node_bbox_aspect >= 1;
node.label_bbox = {
x: bbox.x + (bbox.width - correct_x(bbox.width)) / 2,
y: bbox.y + (bbox.height - correct_y(bbox.height)) / 2,
width: correct_x(bbox.width),
height: correct_y(bbox.height)
};
if (rotate) {
node.label_bbox = {
x: node.label_bbox.y,
y: node.label_bbox.x,
width: node.label_bbox.height,
height: node.label_bbox.width
};
return d3.select(this).attr('transform', 'rotate(90) translate(0,1)');
}
});
enter_labels.each(function(d) {
d.iso_x = isometric([d.x + d.dx / 2, d.y + d.dy / 2, d.h + d.dh])[0] - d.dx / 2;
return d.iso_y = isometric([d.x + d.dx / 2, d.y + d.dy / 2, d.h + d.dh])[1] - d.dy / 2;
});
enter_labels.attr({
x: function(d) {
return d.iso_x;
},
y: function(d) {
return d.iso_y;
},
width: function(node) {
return node.dx;
},
height: function(node) {
return node.dy;
},
viewBox: function(node) {
return "" + node.label_bbox.x + " " + node.label_bbox.y + " " + node.label_bbox.width + " " + node.label_bbox.height;
},
preserveAspectRatio: 'none',
fill: function(d) {
return d3.hcl(d.template_color.h, d.template_color.c, d.template_color.l - 12);
}
});
enter_labels_g.attr({
transform: function(d) {
return "translate(" + (d.iso_x + d.dx / 2) + "," + (d.iso_y + d.dy / 2) + ") scale(1, " + (1 / Math.sqrt(3)) + ") rotate(-45) translate(" + (-(d.iso_x + d.dx / 2)) + "," + (-(d.iso_y + d.dy / 2)) + ")";
}
});
enter_pipedons.append('path').attr({
"class": 'iso outline',
d: function(d) {
return path_generator(d.iso.outline);
}
});
return enter_pipedons.append('title').text(function(d) {
return d.name;
});
});
}).call(this);
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment