22

Has somebody tried to program for data visualization an interactive Collapsible Tree like shown here: http://bl.ocks.org/mbostock/4339083. This implements the Reingold-Tilford algorithm.

enter image description here

data=ImportString[

  ("{ \"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}   ]  } ]}")
  ,
  "RawJSON"
]
Kuba
  • 136,707
  • 13
  • 279
  • 740
mrz
  • 11,686
  • 2
  • 25
  • 81
  • 3
    How about providing a sample dataset to facilitate experiments? – Yves Klett Dec 14 '16 at 12:45
  • 1
    I would use the "LayeredEmbedding" GraphLayout (which probably implements something Reingold-Tilford-like) on a tree where some branches were trimmed, then hope that it produces a consitent vertex ordering, regardless of how much you trim. If you run into issues with preserving ordering, also try IGLayoutReingoldTilford from the IGraph/M package. – Szabolcs Dec 14 '16 at 12:48
  • 4
    Realistically, this question is just too big. I upvoted because I would like to see a solution, but to actually get a solution you would have to break the problem down into pieces (as I did in the comment above), and ask about the piece you are having difficulties with. Is it the visualization? Is it the trimming? Is it making a node react to clicks? Is it storing the trimming state? Etc. – Szabolcs Dec 14 '16 at 12:52
  • 1
    @Yves Klett: the sample dataset for the upper diagram is given in the source code at the bottom of the following site: http://bl.ocks.org/mbostock/4339083 – mrz Dec 14 '16 at 13:13
  • Putting it here in a usefully formatted way would enhance your chances of getting help. It would also make this question somewhwat more self-contained. – Yves Klett Dec 14 '16 at 13:33
  • @Kuba: Thank you ... – mrz Dec 14 '16 at 14:13
  • 1
    This isn't exactly what you are looking for, but somebody did something (functionally) like this in Wolfram's last year's one-liner competition with OpenerView (last one the page / winning entry) – V-J Dec 21 '16 at 05:53
  • 1
    My interface here is for the same type of data, but with different appearance. – C. E. Dec 21 '16 at 17:18

2 Answers2

15

So I took a slightly different tack from lowriniak, making a tree object, rather than just using a tree hierarchy, but the result is much the same. What's worth noting, though, is that this gives you a somewhat more dynamic tree.

Here's the tree making boiler plate:

$tree = <|Root -> <|"Title" -> ".", "Children" -> {}, 
     "Open" -> True|>|>;
$id = 1;
childNodes[parent : _Integer | Root] := $tree[parent]["Children"];
nodeOpen[node : _Integer | Root] := TrueQ@$tree[node]["Open"];
nodeTitle[node_] := $tree[node]["Title"];
toggleNode[node_] := $tree[node]["Open"] = ! $tree[node]["Open"];
addNode[parent : _Integer | Root : Root, node_] := (
   AssociateTo[$tree, $id -> <|"Title" -> node, "Children" -> {}, 
      "Open" -> True|>];
   AppendTo[$tree[parent]["Children"], $id];
   $id++
   );
removeNode[node_Integer] :=

  KeyDropFrom[$tree, Prepend[childNodes[node], node]];
retitleNode[node_Integer, name_] := $tree[node]["Title"] = name;

Then we'll want a way to collect our visible nodes and assign them the right coordinates:

$viewNodes :=
  Block[{
    processing = {},
    nodeStack = {Root},
    visibleNodes = {{Root}},
    layerNodes = {},
    nodeDepth = 1
    },
   While[Length@nodeStack > 0,
    processing = nodeStack;
    nodeStack = {};
    layerNodes = {};
    Do[
     If[nodeOpen@node,
      AppendTo[layerNodes, childNodes@node];
      nodeStack = Join[nodeStack, childNodes@node]
      ],
     {node, processing}];
    AppendTo[visibleNodes, Flatten@layerNodes]
    ];
   visibleNodes
   ];
$allNodes := Block[{nodeOpen = (True &)}, $viewNodes];
$graphNodes :=

  With[{totalTree = $allNodes, nodes = Flatten@$viewNodes},
   Flatten@
    With[{d = Length@totalTree},
     Table[
      With[{l = Length@totalTree[[i]]},
       Table[
        If[MemberQ[nodes, totalTree[[i, j]]],
         {(i - 1), (j - Floor[l/2])/2.2} -> totalTree[[i, j]],
         Nothing],
        {j, l}]
       ],
      {i, d}
      ]
     ]
   ];

Then we'll make a graph and formatting wrapper:

$viewTree :=
 With[{nodes = $graphNodes},
  If[Length@nodes > 1,
   Graph[
    Flatten@
     Table[
      If[nodeOpen@node, Thread[node -> childNodes@node], {}],
      {node, Last /@ nodes}],
    VertexShape -> Table[
      With[{node = node},
       node ->
        EventHandler[
         Graphics[{
           EdgeForm[Hue[.6, .5, .25]],
           GrayLevel[.95],
           Disk[{0, 0}, 50],
           Black,
           Inset@nodeTitle@node
           }
          ], "MouseClicked" :> (toggleNode@node)]
       ], {node, Last /@ nodes}],
    VertexSize -> .15,
    VertexCoordinates -> Reverse /@ nodes
    ],
   With[{node = Last@First@nodes},
    EventHandler[
     Graphics[{
       EdgeForm[Hue[.6, .5, .25]],
       GrayLevel[.95], ,
       Disk[{0, 0}, 50],
       Black,
       Inset@nodeTitle@node
       }
      ],
     "MouseClicked" :> (toggleNode@node)]
    ]
   ]
  ]

Format[HoldPattern[$TreeObject]] :=
 Interpretation[
  Dynamic[$viewTree, TrackedSymbols :> {$viewTree, $tree}],
  $TreeObject]

And this gives you a dynamically editable tree with toggle-able nodes:

Do[
  With[{i = addNode[n]},
   Do[
    With[{j = addNode[i, m]},
     Do[addNode[j, k], {k, RandomInteger[3]}]
     ],
    {m, RandomInteger[5]}
    ]
   ],
  {n, RandomInteger[10]}
  ];
$TreeObject

tree full

Then toggle some nodes:

tree toggles

It isn't as elegant as your source example, but it works as one would hope.

b3m2a1
  • 46,870
  • 3
  • 92
  • 239
7

I've made a start, but getting the vertices to stay static relative to each other is a challenge...

Set up the values:

hierarchy = {
   1 -> 11, 1 -> 12, 1 -> 13,

   11 -> 111, 11 -> 112, 11 -> 113,
   12 -> 121, 12 -> 122, 12 -> 123,
   13 -> 131, 13 -> 132, 13 -> 133,

   121 -> 1211, 121 -> 1212, 121 -> 1213
};

Make an association to track 'openness':

select = Association[# -> True & /@ Union[Flatten[List @@@ hierarchy]]];

A function to toggle the 'openness' of an object:

toggle[obj_] := select[obj] = ! select[obj];

Make a nested version so you can do it on trees:

togglenested[obj_] := (
  If[obj != 1, toggle[obj]];
  togglenested /@ Cases[
    hierarchy, 
    rule : Rule[left_, right_] /; left == obj :> right
  ];
  Null
)

Creating the vertex primitive with event handler for clicks:

action[obj_, loc_] :=  Inset[EventHandler[obj, {"MouseClicked" :> togglenested[obj]}], loc]

And make a graph that can be interacted with:

Dynamic[
  GraphPlot[
    Cases[hierarchy, rule : Rule[left_, right_] /; select[left]], 
    VertexRenderingFunction -> (action[#2, #] &), 
    Method -> "LayeredDigraphDrawing"
  ]
]

graph

It's not pretty but that's not too hard to add. The current drawbacks: collapsing the first value means the GraphPlot fails so I turned that off. Also the vertices do rearrange themselves - maybe giving specific coordinates to the values in the select association and then drawing it from there could work?

Kuba
  • 136,707
  • 13
  • 279
  • 740
lowriniak
  • 2,412
  • 10
  • 15
  • 1
    You are excellent ... after Cases should be a [ ? togglenested[obj_] produces an error ... Syntax::sntxf: "(" cannot be followed by "If[obj!=1,toggle[obj]];togglenested/@Caseshierarchy,rule:Rule[left_,right_]/;left==obj:>right Null)". – mrz Dec 21 '16 at 17:13
  • Ah sorry about that, I must have deleted it while formatting the code - thanks for fixing it Kuba. – lowriniak Dec 22 '16 at 09:07
  • Another question since probably you can answer it: http://mathematica.stackexchange.com/questions/134054/blurry-fonts-in-notebooks and the mentioned links. I am really disappointed ... – mrz Dec 22 '16 at 13:14
  • I took a look and put in what I know, though I am not a developer and don't know the internals of the front end so I might not be so much use. – lowriniak Dec 22 '16 at 14:20