Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add option for custom node shapes #27

Draft
wants to merge 9 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 4 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ Deep trees can be expanded on demand from the visualization by clicking on unexp

The lazy loading can be controlled through two main keyword arguments:

- `lazy_expand_after_depth` which controls the initial expansion depth of the tree, before being sent as json to the visualization,
- `lazy_expand_after_depth` which controls the initial expansion depth of the tree, before being sent as json to the visualization,
- `lazy_subtree_depth` which determines the depth of on-demand expanded subtrees.

```julia
Expand All @@ -90,7 +90,8 @@ ldroot = LimitedDepthTree()
D3Tree(ldroot, lazy_expand_after_depth=0, lazy_subtree_depth=1)
```

returns
can then be expanded to a following tree by clicking on leafs:

![Expanded tree with style](img/deep_tree.png)

## Text output
Expand All @@ -115,7 +116,7 @@ julia> t = D3Tree(children)

## Browser compatibility

This package works best in the Google chrome or chromium browser.
This package works best in the Google Chrome or Chromium browser.

## Limitations

Expand Down
936 changes: 918 additions & 18 deletions examples/hello.ipynb

Large diffs are not rendered by default.

Binary file added examples/julia.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
9 changes: 8 additions & 1 deletion examples/lazy_load_deep_trees.ipynb
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@
"bt_children_ids(id::Int) = [2*id, 2*id+1]\n",
"bt_depth(n::BTNode) = bt_depth(n.id)\n",
"Base.show(io::IO, n::BTNode) = print(io, \"$(n.id) (d=$(bt_depth(n))) -> $(getfield.(AbstractTrees.children(n), :id))\")\n",
"D3Trees.style(BTNode) = \"fill:yellow\"\n",
"\n",
"\"\"\"\n",
" LimitedDepthTree(id, max_leaf_depth)\n",
Expand All @@ -68,6 +69,7 @@
"LimitedDepthTree(;root_id=1, max_leaf_depth=typemax(Int)) = LimitedDepthTree(root_id, max_leaf_depth)\n",
"expand(n::LimitedDepthTree) = bt_depth(n) < n.max_leaf_depth\n",
"\n",
"\n",
"# The required interface method\n",
"AbstractTrees.children(n::LimitedDepthTree) = expand(n) ? LimitedDepthTree.(bt_children_ids(n.id), n.max_leaf_depth) : LimitedDepthTree[]"
]
Expand All @@ -79,7 +81,8 @@
"source": [
"# Lazy loading infinite tree\n",
" - F12 to see the javascript logging\n",
" - If it does not appear on its own, run any cell below to view the Julia log"
" - If it does not appear on its own, run any cell below to view the Julia log\n",
" - If developing remotely, you might have to tunnel the tree server port, e.g. with `ssh -A -L 16370:localhost:16370 [email protected]`"
]
},
{
Expand Down Expand Up @@ -168,6 +171,10 @@
}
],
"metadata": {
"@webio": {
"lastCommId": null,
"lastKernelId": null
},
"kernelspec": {
"display_name": "Julia 1.7.2",
"language": "julia",
Expand Down
23 changes: 19 additions & 4 deletions js/tree_vis.js
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,8 @@ function addSubTreeData(subtree){
treeData.unexpanded_children = new Set([...treeData.unexpanded_children, ...subtree.unexpanded_children]);
treeData.text.push(...subtree.text);
treeData.tooltip.push(...subtree.tooltip);
treeData.style.push(...subtree.style);
// treeData.style.push(...subtree.style);
treeData.node_svg.push(...subtree.node_svg);
treeData.link_style.push(...subtree.link_style);
}

Expand Down Expand Up @@ -153,9 +154,23 @@ function showTree() {
.attr("transform", function(d) { return "translate(" + source.x0 + "," + source.y0 + ")"; })
.on("click", click)

nodeEnter.append("circle")
.attr("r", "10px")
.attr("style", function(d) { return treeData.style[d.dataID]; } )
// Enter the selected node_svg
nodeEnter.each(function(d){
d3.select(this).html(treeData.node_svg[d.dataID])
// var nodeSvg = d3.select(this).html(treeData.node_svg[d.dataID])
// nodeSvg.attr("style", function(d) { return treeData.style[d.dataID]; } )
})


// nodeEnter.append("rect")
// .attr("width", "20px")
// .attr("height", "20px")
// .attr("style", function(d) { //console.log(treeData);
// return treeData.style[d.dataID]; } )

// nodeEnter.append("circle")
// .attr("r", "10px")
// .attr("style", function(d) { return treeData.style[d.dataID]; } )

var tbox = nodeEnter.append("text")
.attr("y", 25)
Expand Down
46 changes: 38 additions & 8 deletions src/D3Trees.jl
Original file line number Diff line number Diff line change
Expand Up @@ -17,12 +17,15 @@ export
inchrome,
inbrowser

svg_circle(;style="")= isempty(style) ? "<circle r=\"10px\"></circle>" : "<circle r=\"10px\" style=\"$(style)\"></circle>"

struct D3Tree
children::Vector{Vector{Int}}
unexpanded_children::Dict{Int,Any}
text::Vector{String}
tooltip::Vector{String}
style::Vector{String}
# style::Vector{String}
node_svg::Vector{String}
link_style::Vector{String}
title::String
options::Dict{Symbol,Any}
Expand Down Expand Up @@ -101,20 +104,35 @@ Construct a tree to be displayed using D3 in a browser or ipython notebook, spec
- `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::AbstractVector{<:AbstractVector}; kwargs...)
function _D3Tree(children::AbstractVector{<:AbstractVector}; kwargs...)
kwd = Dict(kwargs)
n = length(children)
return D3Tree(children,
Dict(),
get(kwd, :text, collect(string(i) for i in 1:n)),
get(kwd, :tooltip, fill("", n)),
get(kwd, :style, fill("", n)),
get(kwd, :node_svg, fill(svg_circle(), n)),
get(kwd, :link_style, fill("", n)),
get(kwd, :title, "Julia D3Tree"),
convert(Dict{Symbol,Any}, kwd),
)
end

function D3Tree(children::AbstractVector{<:AbstractVector}; kwargs...)
kwd = Dict(kwargs)
if !haskey(kwd, :style)
return _D3Tree(children; kwargs...)
else
if haskey(kwd, :node_svg)
@warn "`style` has no effect when `node_svg` is provided directly"
return _D3Tree(children; kwargs...)
else
node_svg = map(style->svg_circle(style=style), get(kwd, :style, nothing))
return _D3Tree(children; node_svg, kwargs...)
end
end
end


"""
D3Trees.text(n)
Expand All @@ -137,6 +155,13 @@ Return the html style for the D3Trees node corresponding to AbstractTrees node `
"""
style(node) = ""

"""
D3Trees.node_svg(n)

Return the node svg of AbstractTrees node `n`
"""
node_svg(node) = svg_circle(style=style(node))

"""
D3Trees.link_style(n)

Expand All @@ -156,8 +181,10 @@ AbstractTrees.printnode(io::IO, n::D3TreeNode) = print(io, n.tree.text[n.index])
AbstractTrees.printnode(io::IO, t::D3Tree) = print(io, t.text[1])
tooltip(n::D3TreeNode) = n.tree.tooltip[n.index]
tooltip(t::D3Tree) = t.tooltip[1]
style(n::D3TreeNode) = n.tree.style[n.index]
style(t::D3Tree) = t.style[1]
# style(n::D3TreeNode) = n.tree.style[n.index]
# style(t::D3Tree) = t.style[1]
node_svg(n::D3TreeNode) = n.tree.node_svg[n.index]
node_svg(t::D3Tree) = t.node_svg[1]
link_style(n::D3TreeNode) = n.tree.link_style[n.index]
link_style(t::D3Tree) = t.link_style[1]

Expand All @@ -183,7 +210,8 @@ function push_node!(t::D3Tree, node, lazy_expand_after_depth::Int, node_dict=not
push!(t.children, Int[])
push!(t.text, text(node))
push!(t.tooltip, tooltip(node))
push!(t.style, style(node))
# push!(t.style, style(node))
push!(t.node_svg, node_svg(node))
push!(t.link_style, link_style(node))

if lazy_expand_after_depth > 0
Expand Down Expand Up @@ -214,7 +242,8 @@ struct D3OffsetSubtree
Dict(ind + offset => node for (ind, node) in pairs(subtree.unexpanded_children)),
subtree.text[2:end],
subtree.tooltip[2:end],
subtree.style[2:end],
# subtree.style[2:end],
subtree.node_svg[2:end],
subtree.link_style[2:end],
subtree.title,
subtree.options
Expand Down Expand Up @@ -243,7 +272,8 @@ function expand_node!(t::D3Tree, ind::Int, lazy_expand_after_depth::Int)
merge!(t.unexpanded_children, offset_subtree.subtree.unexpanded_children)
append!(t.text, offset_subtree.subtree.text)
append!(t.tooltip, offset_subtree.subtree.tooltip)
append!(t.style, offset_subtree.subtree.style)
# append!(t.style, offset_subtree.subtree.style)
append!(t.node_svg, offset_subtree.subtree.node_svg)
append!(t.link_style, offset_subtree.subtree.link_style)

return offset_subtree
Expand Down
4 changes: 0 additions & 4 deletions test/runtests.jl
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,10 @@ include("binary_abstract_trees.jl")
@testset "simple tree" begin
children = [[2,3], [], [4], []]
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"]
t1 = D3Tree(children,
text=text,
style=style,
tooltip=tooltip,
link_style=link_style)

Expand All @@ -25,7 +23,6 @@ include("binary_abstract_trees.jl")

t2 = D3Tree(children,
text=text,
style=style,
tooltip=tooltip,
link_style=link_style,
init_expand=1000,
Expand Down Expand Up @@ -61,7 +58,6 @@ include("binary_abstract_trees.jl")
@time t4 = D3Tree(t1, detect_repeat=false)
@test t3.text == t1.text
@test t3.tooltip == t1.tooltip
@test t3.style == t1.style
@test t3.link_style == t1.link_style

inbrowser(D3Tree([[2],[]]), `ls`)
Expand Down
1 change: 0 additions & 1 deletion test/test_tree_expansion.jl
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@
@test length(t1.children) == length(t2.children) == length(t3.children)
@test sort(t1.text) == sort(t2.text) == sort(t3.text)
@test sort(t1.tooltip) == sort(t2.tooltip) == sort(t2.tooltip)
@test sort(t1.style) == sort(t2.style) == sort(t2.style)
@test sort(t1.link_style) == sort(t2.link_style) == sort(t2.link_style)
t1_unexplored_children = sort(collect(values(t1.unexpanded_children)); lt=(x, y) -> x.id < y.id)
t2_unexplored_children = sort(collect(values(t2.unexpanded_children)); lt=(x, y) -> x.id < y.id)
Expand Down