diff --git a/.gitignore b/.gitignore index 8c960ec..3f02ca7 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ *.jl.cov *.jl.*.cov *.jl.mem +Manifest.toml diff --git a/Project.toml b/Project.toml new file mode 100644 index 0000000..c976dc8 --- /dev/null +++ b/Project.toml @@ -0,0 +1,32 @@ +name = "GraphRecipes" +uuid = "bd48cda9-67a9-57be-86fa-5b3c104eda73" +version = "0.5.0" + +[deps] +AbstractTrees = "1520ce14-60c1-5f80-bbc7-55ef81b5835c" +GeometryTypes = "4d00f742-c7ba-57c2-abde-4428a4b178cb" +InteractiveUtils = "b77e0a4c-d291-57a0-90e8-8db25a27a240" +Interpolations = "a98d9a8b-a2ab-59e6-89dd-64a1c18fca59" +LightGraphs = "093fc24a-ae57-5d10-9952-331d41423f4d" +LinearAlgebra = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e" +NaNMath = "77ba4419-2d1f-58cd-9bb1-8ffee604a2e3" +NetworkLayout = "46757867-2c16-5918-afeb-47bfcb05e46a" +PlotUtils = "995b91a9-d308-5afd-9ec6-746e21dbc043" +RecipesBase = "3cdcf5f2-1ef4-517c-9805-6587b60abb01" +SparseArrays = "2f01184e-e22b-5df5-ae63-d93ebab69eaf" +Statistics = "10745b16-79ce-11e8-11f9-7d13ad32a3b2" + +[compat] +julia = "^1" + +[extras] +ImageMagick = "6218d12a-5da1-5696-b52f-db25d2ecc6d1" +LinearAlgebra = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e" +Plots = "91a5bcdd-55d7-5caf-9e0b-520d859cae80" +Random = "9a3f8284-a2c9-5f02-9a11-845980a1fd5c" +SparseArrays = "2f01184e-e22b-5df5-ae63-d93ebab69eaf" +Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40" +VisualRegressionTests = "34922c18-7c2a-561c-bac1-01e79b2c4c92" + +[targets] +test = ["VisualRegressionTests", "LinearAlgebra", "Plots", "ImageMagick", "Random", "SparseArrays", "Test"] diff --git a/README.md b/README.md index 4f6769b..9605bc8 100644 --- a/README.md +++ b/README.md @@ -107,3 +107,27 @@ plot(AbstractFloat, method=:tree, fontsize=10, nodeshape=:ellipse) ``` ![](assets/julia_type_tree.png) + + +#### `AbstractTrees` Trees + +```julia +using AbstractTrees + +AbstractTrees.children(d::Dict) = [p for p in d] +AbstractTrees.children(p::Pair) = AbstractTrees.children(p[2]) +function AbstractTrees.printnode(io::IO, p::Pair) + str = isempty(AbstractTrees.children(p[2])) ? string(p[1], ": ", p[2]) : string(p[1], ": ") + print(io, str) +end + +d = Dict(:a => 2,:d => Dict(:b => 4,:c => "Hello"),:e => 5.0) + +using GraphRecipes +using Plots +default(size=(1000, 1000)) + +plot(TreePlot(d), method=:tree, fontsize=10, nodeshape=:ellipse) + +``` +![](assets/julia_dict_tree.png) \ No newline at end of file diff --git a/REQUIRE b/REQUIRE deleted file mode 100644 index 2628eae..0000000 --- a/REQUIRE +++ /dev/null @@ -1,9 +0,0 @@ -julia 1.0 - -RecipesBase -NetworkLayout -LightGraphs -PlotUtils -NaNMath -GeometryTypes -Interpolations diff --git a/assets/julia_dict_tree.png b/assets/julia_dict_tree.png new file mode 100644 index 0000000..952a6eb Binary files /dev/null and b/assets/julia_dict_tree.png differ diff --git a/src/GraphRecipes.jl b/src/GraphRecipes.jl index 6b9e1a9..7d92225 100644 --- a/src/GraphRecipes.jl +++ b/src/GraphRecipes.jl @@ -18,5 +18,6 @@ include("utils.jl") include("graph_layouts.jl") include("graphs.jl") include("misc.jl") +include("trees.jl") end # module diff --git a/src/trees.jl b/src/trees.jl new file mode 100644 index 0000000..79c7dda --- /dev/null +++ b/src/trees.jl @@ -0,0 +1,60 @@ +import AbstractTrees +using AbstractTrees: children + +export TreePlot + +""" + TreePlot(root) + +Wrap a tree-like object for plotting. Uses `AbstractTrees.children()` to recursively add children to the plot and `AbstractTrees.printnode()` to generate the labels. + +# Example + +```julia +using AbstractTrees, GraphRecipes +AbstractTrees.children(d::Dict) = [p for p in d] +AbstractTrees.children(p::Pair) = AbstractTrees.children(p[2]) +function AbstractTrees.printnode(io::IO, p::Pair) + str = isempty(AbstractTrees.children(p[2])) ? string(p[1], ": ", p[2]) : string(p[1], ": ") + print(io, str) +end + +d = Dict(:a => 2,:d => Dict(:b => 4,:c => "Hello"),:e => 5.0) + +plot(TreePlot(d)) +```` +""" +struct TreePlot{T} + root::T +end + +function add_children!(nodes, source, destiny, node, parent_idx) + for child in children(node) + push!(nodes, child) + child_idx = length(nodes) + push!(source, parent_idx) + push!(destiny, child_idx) + add_children!(nodes, source, destiny, child, child_idx) + end +end + +function string_from_node(node) + io = IOBuffer() + AbstractTrees.printnode(io, node) + String(take!(io)) +end + +# recursively build a graph of children of `tree_wrapper.root` +@recipe function f(tree_wrapper::TreePlot; namefunc = string_from_node ) + root = tree_wrapper.root + # recursively add children + nodes = Any[root] + source, destiny = Int[], Int[] + add_children!(nodes, source, destiny, root, 1) + + # set up the graphplot + names := map(namefunc, nodes) + method --> :buchheim + root --> :top + GraphPlot((source, destiny)) +end diff --git a/test/REQUIRE b/test/REQUIRE deleted file mode 100644 index a333d68..0000000 --- a/test/REQUIRE +++ /dev/null @@ -1,3 +0,0 @@ -Plots -VisualRegressionTests -ImageMagick diff --git a/test/generate_readme_images.jl b/test/generate_readme_images.jl index a37cf49..0ffac35 100644 --- a/test/generate_readme_images.jl +++ b/test/generate_readme_images.jl @@ -70,3 +70,18 @@ savefig("AST_example.png") plot(AbstractFloat, method=:tree, fontsize=10, nodeshape=:ellipse) savefig("julia_type_tree.png") + + +# `AbstractTrees` example +using AbstractTrees +AbstractTrees.children(d::Dict) = [p for p in d] +AbstractTrees.children(p::Pair) = AbstractTrees.children(p[2]) +function AbstractTrees.printnode(io::IO, p::Pair) + str = isempty(AbstractTrees.children(p[2])) ? string(p[1], ": ", p[2]) : string(p[1], ": ") + print(io, str) +end + +d = Dict(:a => 2,:d => Dict(:b => 4,:c => "Hello"),:e => 5.0) + +plot(TreePlot(d), method=:tree, fontsize=10, nodeshape=:ellipse) +savefig("julia_dict_tree.png") diff --git a/test/readme.jl b/test/readme.jl index 83bcabd..cdba250 100644 --- a/test/readme.jl +++ b/test/readme.jl @@ -4,6 +4,14 @@ using LinearAlgebra using SparseArrays using ImageMagick +using AbstractTrees +AbstractTrees.children(d::Dict) = [p for p in d] +AbstractTrees.children(p::Pair) = AbstractTrees.children(p[2]) +function AbstractTrees.printnode(io::IO, p::Pair) + str = isempty(AbstractTrees.children(p[2])) ? string(p[1], ": ", p[2]) : string(p[1], ": ") + print(io, str) +end + istravis = "TRAVIS" ∈ keys(ENV) default(show=false, reuse=true) @@ -68,4 +76,8 @@ cd("../assets") default(size=(1000, 1000)) @plottest plot(code, fontsize=10, shorten=0.01, axis_buffer=0.15, nodeshape=:rect) "AST_example.png" popup=!istravis tol=0.02 @plottest plot(AbstractFloat, method=:tree, fontsize=10, nodeshape=:ellipse) "julia_type_tree.png" popup=!istravis tol=0.2 + + d = Dict(:a => 2,:d => Dict(:b => 4,:c => "Hello"),:e => 5.0) + @plottest plot(TreePlot(d), method=:tree, fontsize=10, nodeshape=:ellipse) "julia_dict_tree.png" popup=!istravis tol=0.2 + end