block by mr23 9783ff1e53ecc02198a10d5bf0ba252e

Radial tree with "packed" nodes (flare)

Full Screen

This fork: Adjusted positioning and rotation to keep plot in view, and label orientation correct at any tree starting angle. Adjusted jitter for parent nodes. Re-added text labels. Added rollup of the size feature. Pulled flare data out of plot code file. Renamed files for re-use. Added node coloring for special cases (missing rollup feature, color callout for a nodes including certain naming).

Forked from nitaku‘s block: Radial tree with “packed” nodes (flare)

  • An experiment that adds some jitter (sort of) to the nodes of a [radial tree layout](http://bl.ocks.org/mbostock/4063550), in order to pack them more efficiently into space. Each node is moved alternatively towards to or away from the center by an amount of pixels that's equal to the radius of the node.
  • The data structure that's represented is the `flare` software package hierarchy.
  • index.html

    <!DOCTYPE html>
    <html>
    	<head>
            <meta charset="utf-8">
            <meta name="description" content='Radial tree with "packed" nodes' />
            <title>Radial tree with "packed" nodes</title>
    		<link type="text/css" href="d3RadialTree.css" rel="stylesheet"/>
            <script src='d3.v3.min.js'></script>
    	</head>
    	<body>
        <div id="d3svgdiv">
        </div>
        	  <script src="flare.js"></script>
            <script src="d3RadialTree.js"></script>
    	</body>
    </html>

    d3RadialTree.css

    svg {
      background: white;
    }
    .node {
      fill: #fff;
      stroke: steelblue;
      //stroke-width: 1.0px;
      font: 10px sans-serif;
    }
    
    .node:hover {
      fill: steelblue;
    }
    .internal.node {
      stroke: #A55;
    }
    
    .missing.node {
      stroke: #FF0000;
    }
    
    .alpha.node {
      stroke: #FF8800;
    }
    
    .beta.node {
      stroke: #00d8ff;
    }
    
    .root.node {
      stroke: #d29369;
      stroke-width: 3.0px;
    }
    
    .root.node text {
      stroke-width: 1.0px;
    }
    
    .internal.node:hover {
      fill: #A55;
    }
    
    .link, .root_link {
      fill: none;
      stroke: #ccc;
      stroke-width: 1.2px;
    }
    

    d3RadialTree.js

    // requires a global 'flare' tree data
    // this adds a central root with straight links (MikeB's original used flared from a point),
    // and adds jitter to node positions to pack nodes more tightly.
    // Added labels back in, from http://bl.ocks.org/mbostock/2e12b0bd732e7fe4000e2d11ecab0268
    
    var diameter = 880;
    var radius_unspec = 5;
    var radius_minspec = 3;
    var radius_maxspec = 10;
    var label_expected = 100; // could measure and adjust, but function of a css setting
    var max_value = 100000;   // survey from flare
    var min_value = 0;
    var sizeFeature = "size"; // for radius of node circles
    var titleFeature = "name";
    var sortFeature = "size";
    var attrFeature = "size";
    var treeXYMargin = 0;
    var treeArcDegrees = 360;
    var treeStartDegrees = 90;
    
    var piOver180 = Math.PI/180.0;
    var radius_scale = d3.scale.sqrt()
      .domain([min_value, max_value])
      .range([radius_minspec, radius_maxspec]);
    
    var rho_scale = d3.scale.pow()
      .exponent(1.0)  //0.8
      .domain([0, diameter/2])
      .range([0, diameter/2]);
    
    
    var tree = d3.layout.tree()
        .size([treeArcDegrees, diameter / 2 - radius_maxspec - label_expected])
        .separation(function(a, b) { return (a.parent == b.parent ? 1 : 2) / a.depth; });
    
    var diagonal = d3.svg.diagonal.radial()
        .projection(function(d) { return [d.y, (d.x + (90-treeStartDegrees)) * piOver180]; });
    
    var g = d3.select("#d3svgdiv").append("svg:svg")
        .attr("width", diameter + 2*treeXYMargin)
        .attr("height", diameter + 2*treeXYMargin)
    .append("svg:g")
        .attr("transform", "translate(" + (diameter / 2 + treeXYMargin) + "," + (diameter / 2 + treeXYMargin) + ")");
    
    
    function rollup(node) {
      if (node.children === undefined)
        return node[sizeFeature] === undefined ? 0 : node[sizeFeature];
      var sum=0;
      node.children.forEach( function(n, i) {
        sum = sum + rollup(n);
      });
      if (node[sizeFeature] === undefined)
        node[sizeFeature] = sum;
      return sum;
    }
    
    
    rollup(flare);
    tree.sort(comparator); // before further ops
    
    var nodes = tree.nodes(flare),
        links = tree.links(nodes);
    
    function comparator(a, b) {
      return b[sortFeature] - a[sortFeature];
    }
    
    function jitter(node) {
      if(node.children === undefined)
        return;
      var jCoef = 1.0;  // start with +, as with original
      node.children.forEach( function(n, i) {
        if(n[sizeFeature] !== undefined) {
          if (n.children !== undefined) // no need for jitter, but a sibling w/o children should be inward
            jCoef = 1.0;
          else
            jCoef = -1.0 * jCoef; // invert each cycle thus, until next w/children
          n.y = n.y + jCoef * radius_scale(n[sizeFeature]);
        }
        n.y = rho_scale(n.y);
        jitter(n);
      });
    }
    jitter(flare);
    
    function fliptext(a) {
      var tA = ((a + (90-treeStartDegrees))%360);
      if (tA<0) tA = tA + 360;
      return tA < 180;
    }
    
    var link = g.selectAll(".link")
      .data(links.filter(function(l) { return l.source.depth > 0; }));
    
    link
      .enter().append("path")
        .attr("class", "link")
        .attr("d", diagonal);
    
    var root_link = g.selectAll(".root_link")
      .data(links.filter(function(l) { return l.source.depth === 0; }));
    
    root_link
      .enter().append("path")
        .attr("class", "root_link")
        .attr("d", function(d){	// add link line(s) from root to first level nodes
          var theta = (d.target.x-treeStartDegrees)*piOver180;
          var x1 = Math.cos(theta)*d.source.y;
          var y1 = Math.sin(theta)*d.source.y;
          var x2 = Math.cos(theta)*d.target.y;
          var y2 = Math.sin(theta)*d.target.y;
          return 'M'+x1+' '+y1+' L'+x2+' '+y2;
        });
    
    var node = g.selectAll(".node")
        .data(nodes)
      .enter().append("g") //g fixes text
        .attr("class", "node")
        .attr("transform", function(d){ return "rotate(" + (d.x - treeStartDegrees) + ")translate(" + d.y + ")"; })
        .classed('internal', function(d){ return d.children && d.depth>0; })
        .classed('root', function(d) { return d.depth===0; })
    	  .classed('missing', function(d) { return d[attrFeature] === undefined; })
    	  // this(these) should be externally applied
    	  .classed('beta', function(d) { var s = (""+d.name).toLowerCase().indexOf("list")>=0; return s; })
    	  .classed('alpha', function(d) { var s = (""+d.name).toLowerCase().indexOf("legend")>=0; return s; })
    	;
    
    node.append("circle")
    	.attr("r", function(d){
          if(d[attrFeature] === undefined)
            return radius_unspec;
          return radius_scale(d[attrFeature]);
        });
    
    node.append("title")
      .text(function(d) {
        if(d[attrFeature] === undefined)
          return d[titleFeature];
        else
          return d[titleFeature] + ' (' + d3.format(',')(d[attrFeature]) + ')';
      });
    
    node.append("text")
          .attr("dy", ".31em")
          .attr("x", function(d) { return d.depth===0 ? 0 : d.children? fliptext(d.x) ? -20 : 20 : fliptext(d.x)  ? 6 : -6; })
     .attr("text-anchor", function(d) { return fliptext(d.x) === !d.children ? "start" : "end"; })
        .attr("transform", function(d) { return fliptext(d.x) ? "translate(8)" : "rotate(180)translate(-8)"; })
          .text(function(d) { return d[titleFeature]; });
    
    //d3.select(self.frameElement).style('height', diameter+'px');
    
    

    flare.js

    var flare = {
     "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}
       ]
      }
     ]
    };