22
$\begingroup$

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"
]
$\endgroup$
8
  • 3
    $\begingroup$ How about providing a sample dataset to facilitate experiments? $\endgroup$
    – Yves Klett
    Commented Dec 14, 2016 at 12:45
  • 1
    $\begingroup$ 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. $\endgroup$
    – Szabolcs
    Commented Dec 14, 2016 at 12:48
  • 4
    $\begingroup$ 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. $\endgroup$
    – Szabolcs
    Commented Dec 14, 2016 at 12:52
  • 1
    $\begingroup$ @Yves Klett: the sample dataset for the upper diagram is given in the source code at the bottom of the following site: bl.ocks.org/mbostock/4339083 $\endgroup$
    – mrz
    Commented Dec 14, 2016 at 13:13
  • 1
    $\begingroup$ 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) $\endgroup$
    – V-J
    Commented Dec 21, 2016 at 5:53

2 Answers 2

15
+100
$\begingroup$

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.

$\endgroup$
1
  • $\begingroup$ Wonderful solution ... $\endgroup$
    – mrz
    Commented Dec 22, 2016 at 9:59
7
$\begingroup$

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?

$\endgroup$
4
  • 1
    $\begingroup$ 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)". $\endgroup$
    – mrz
    Commented Dec 21, 2016 at 17:13
  • $\begingroup$ Ah sorry about that, I must have deleted it while formatting the code - thanks for fixing it Kuba. $\endgroup$
    – lowriniak
    Commented Dec 22, 2016 at 9:07
  • $\begingroup$ Another question since probably you can answer it: mathematica.stackexchange.com/questions/134054/… and the mentioned links. I am really disappointed ... $\endgroup$
    – mrz
    Commented Dec 22, 2016 at 13:14
  • $\begingroup$ 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. $\endgroup$
    – lowriniak
    Commented Dec 22, 2016 at 14:20

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.