From fcb8bfde73ea88fe7f8654c6671146b4682f8974 Mon Sep 17 00:00:00 2001 From: Zachary Sunberg Date: Tue, 3 Apr 2018 19:56:43 -0700 Subject: [PATCH 1/2] added AbstractTrees compatibility to fix #8 --- README.md | 22 ++++++++++++----- REQUIRE | 1 + src/D3Trees.jl | 61 +++++++++++++++++++++++++++++++++++++++++++----- test/runtests.jl | 14 +++++++---- 4 files changed, 81 insertions(+), 17 deletions(-) diff --git a/README.md b/README.md index d7d9897..d07a8a6 100644 --- a/README.md +++ b/README.md @@ -6,18 +6,24 @@ Flexible interactive visualization for large trees using [D3.js](d3js.org). +![Tree](img/tree.png) + ## Installation ```julia Pkg.add("D3Trees") ``` -or -```julia -Pkg.clone("https://github.com/sisl/D3Trees.jl.git") -``` ## Usage +There are two ways to create a D3Tree object described below: + +### With AbstractTrees + +Any object that implements the interface from AbstractTrees can given to the constructor: `D3Tree(object)`. + +### Without AbstractTrees + The structure of a D3Tree is specified with *lists of children for each node* stored in a `Vector` of `Int` `Vector`s. For example ```julia D3Tree([[2,3], [], [4], []]) @@ -31,8 +37,7 @@ t = D3Tree(children) inchrome(t) ``` -By clicking on the nodes, you can expand it to look like this: -![Tree](img/tree.png) +By clicking on the nodes, you can expand it to look like the image at the top of the page. Optional arguments control other aspects of the style (use `julia> ?D3Tree` for a complete list), for example ```julia @@ -80,3 +85,8 @@ julia> t = D3Tree(children) ## Browser compatibility This package works best in the Google chrome or chromium browser. + +## Limitations + +- The tree is currently transmitted to the browser in one big json string, so it can be slow for large trees and cannot handle infinite trees. +- This will not work offline because it downloads the D3 library on the fly (https://github.com/sisl/D3Trees.jl/issues/10) diff --git a/REQUIRE b/REQUIRE index 72084d0..8959a1f 100644 --- a/REQUIRE +++ b/REQUIRE @@ -1,3 +1,4 @@ julia 0.6 JSON Blink +AbstractTrees 0.1 diff --git a/src/D3Trees.jl b/src/D3Trees.jl index bb634ea..f251983 100644 --- a/src/D3Trees.jl +++ b/src/D3Trees.jl @@ -2,6 +2,8 @@ module D3Trees using JSON using Blink +using AbstractTrees +import AbstractTrees: printnode export D3Tree, @@ -46,7 +48,7 @@ Construct a tree to be displayed using D3 in a browser or ipython notebook. - `init_duration::Number` - duration of the initial animation in ms. - `svg_height::Number` - height of the svg containing the tree in px. """ -function D3Tree(children; kwargs...) +function D3Tree(children::AbstractVector{<:AbstractVector}; kwargs...) kwd = Dict(kwargs) n = length(children) return D3Tree(children, @@ -58,17 +60,64 @@ function D3Tree(children; kwargs...) convert(Dict{Symbol, Any}, kwd) ) end - + +""" + D3Tree(node; detect_repeat=true) + +Construct a tree to be displayed using D3 in a browser or ipython notebook from any object that implements the AbstractTrees interface. + +# Arguments + +## Required + +- `node`: an object that has AbstractTrees.children(node) and AbstractTrees.printnode(io::IO, node) + +## Keyword + +- `detect_repeat`: if true, uses a dictionary to detect whether a node has appeared previously +""" +function D3Tree(node; detect_repeat::Bool=true) + t = D3Tree(Vector{Int}[]) + if detect_repeat + node_dict = Dict{Any, Int}() + push_node!(t, node, node_dict) + else + push_node!(t, node) + end + return t +end + +function push_node!(t, node, node_dict=nothing) + if !(node_dict isa Void) && haskey(node_dict, node) + return node_dict[node] + end + + ind = length(t.children) + 1 + if !(node_dict isa Void) + node_dict[node] = ind + end + if length(t.children) < ind + push!(t.children, Int[]) + iob = IOBuffer() + printnode(iob, node) + push!(t.text, String(take!(iob))) + end + for c in children(node) + c_ind = push_node!(t, c, node_dict) + push!(t.children[ind], c_ind) + end + return ind +end + struct D3TreeNode tree::D3Tree index::Int end -# this should be AbstractTrees.children -children(n::D3TreeNode) = (D3TreeNode(n.tree, c) for c in n.tree.children[n.index]) +AbstractTrees.children(n::D3TreeNode) = (D3TreeNode(n.tree, c) for c in n.tree.children[n.index]) +AbstractTrees.children(t::D3Tree) = children(D3TreeNode(t, c) for c in t.children[1]) n_children(n::D3TreeNode) = length(n.tree.children[n.index]) -# this should be AbstractTrees.printnode -printnode(io::IO, n::D3TreeNode) = print(io, n.tree.text[n.index]) +AbstractTrees.printnode(io::IO, n::D3TreeNode) = print(io, n.tree.text[n.index]) struct D3TreeView root::D3TreeNode diff --git a/test/runtests.jl b/test/runtests.jl index 9d9537f..9d4d706 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -8,15 +8,15 @@ text = ["one\n(second line)", "2", "III", "four"] style = ["", "fill:red", "r:14", "opacity:0.7"] link_style = ["", "stroke:blue", "", "stroke-width:10px"] tooltip = ["pops", "up", "on", "hover"] -t = D3Tree(children, +t1 = D3Tree(children, text=text, style=style, tooltip=tooltip, link_style=link_style) -@show json(t) +@show json(t1) -stringmime(MIME("text/html"), t) +stringmime(MIME("text/html"), t1) t2 = D3Tree(children, text=text, @@ -31,8 +31,8 @@ t2 = D3Tree(children, # inchrome(t) # inchrome(t2) -@show D3Trees.children(D3TreeNode(t, 1)) -show(STDOUT, MIME("text/plain"), t) +@show D3Trees.children(D3TreeNode(t1, 1)) +show(STDOUT, MIME("text/plain"), t1) nbinclude("../examples/hello.ipynb") @@ -48,3 +48,7 @@ println("creating tree object") println("html string") @time stringmime(MIME("text/html"), t) + +println("AbstractTrees constructor") +@time t3 = D3Tree(t1) +@time t4 = D3Tree(t1, detect_repeat=false) From 578b74e6baa4848a3ac222c15f6fdff806c0cf24 Mon Sep 17 00:00:00 2001 From: Zachary Sunberg Date: Tue, 3 Apr 2018 20:00:22 -0700 Subject: [PATCH 2/2] fixed children --- src/D3Trees.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/D3Trees.jl b/src/D3Trees.jl index f251983..8bb86f2 100644 --- a/src/D3Trees.jl +++ b/src/D3Trees.jl @@ -115,7 +115,7 @@ struct D3TreeNode end AbstractTrees.children(n::D3TreeNode) = (D3TreeNode(n.tree, c) for c in n.tree.children[n.index]) -AbstractTrees.children(t::D3Tree) = children(D3TreeNode(t, c) for c in t.children[1]) +AbstractTrees.children(t::D3Tree) = children(D3TreeNode(t, 1)) n_children(n::D3TreeNode) = length(n.tree.children[n.index]) AbstractTrees.printnode(io::IO, n::D3TreeNode) = print(io, n.tree.text[n.index])