sisl / D3Trees.jl

@@ -29,23 +29,15 @@
Loading
29 29
    css = read(joinpath(dirname(@__FILE__()), "..", "css", "tree_vis.css"), String)
30 30
    js = read(joinpath(dirname(@__FILE__()), "..", "js", "tree_vis.js"), String)
31 31
    div = "treevis$(randstring())"
32 +
    tree_url="" #Only set when running the server
32 33
33 34
    try
34 -
        # do not bother with server if tree has no unexpanded nodes.
35 +
        # do not run the server if the tree has no unexpanded nodes.
35 36
        if length(t.unexpanded_children) > 0 
36 -
            # if server has not been started yet, do so.
37 -
            if !isassigned(SERVER) 
38 -
                reset_server()
39 -
                # speedup first-click response in the visualization
40 -
                get(t.options, :dry_run_lazy_vizualization, true) ? dry_run_server(t) : nothing
41 -
            end
42 -
            
43 -
            TREE_DATA[][div]=t
44 -
            lazy_subtree_depth = get(t.options, :lazy_subtree_depth, DEFAULT_LAZY_SUBTREE_DEPTH)
45 -
            HTTP.register!(TREE_ROUTER, "GET", "/api/d3trees/v1/tree/{treediv}/{nodeid}", req -> handle_subtree_request(req, TREE_DATA[], lazy_subtree_depth))
37 +
            port = serve_tree!(SERVERS, t, div)
38 +
            tree_url = "http://$HOST:$(port)/$API_PATH/"
46 39
        end
47 40
48 -
49 41
        html_string = """
50 42
            <!DOCTYPE html>
51 43
            <html>
@@ -66,7 +58,7 @@
Loading
66 58
                var initExpand = $(get(t.options, :init_expand, 0));
67 59
                var initDuration = $(get(t.options, :init_duration, 750));
68 60
                var svgHeight = $(get(t.options, :svg_height, 600));
69 -
                var tree_url = "$TREE_URL";
61 +
                var tree_url = "$tree_url";
70 62
                $js
71 63
                })();
72 64
            </script>
@@ -87,7 +79,7 @@
Loading
87 79
        # (See https://github.com/JuliaLang/IJulia.jl/issues/1041)
88 80
        # The logging below makes sure the error is noticed
89 81
        @error "Show error:" exception=(e,catch_backtrace())
90 -
        rethrow(e)
82 +
        throw(e)
91 83
    end
92 84
end
93 85

@@ -12,36 +12,65 @@
Loading
12 12
# CORS respoonse headers (according to https://cors-errors.info/faq)
13 13
const CORS_RES_HEADERS = ["Access-Control-Allow-Origin" => "*"]
14 14
15 -
TREE_DATA = Ref{Dict{String, D3Tree}}()
16 -
const PORT = 16370 # Randomly chosen
17 -
const TREE_URL = "http://localhost:$(PORT)/api/d3trees/v1/tree/"
18 -
const HOST = Sockets.localhost
19 -
SERVER = Ref{HTTP.Servers.Server}()
20 15
cors404(::HTTP.Request) = HTTP.Response(404, CORS_RES_HEADERS, "")
21 16
cors405(::HTTP.Request) = HTTP.Response(405, CORS_RES_HEADERS, "")
22 -
const TREE_ROUTER = HTTP.Router(cors404, cors405)
23 -
const DEFAULT_LAZY_SUBTREE_DEPTH = 2
17 +
18 +
const HOST = Sockets.localhost
19 +
20 +
struct D3TreeServer
21 +
    server::HTTP.Servers.Server
22 +
    router::HTTP.Router
23 +
    tree_data::Dict{String, D3Tree}
24 +
end
25 +
HTTP.close(d3s::D3TreeServer) = close(d3s.server)
26 +
27 +
const SERVERS = Dict{Int64, D3TreeServer}()
28 +
29 +
24 30
25 31
"""
26 -
    reset_server()
32 +
    D3Trees.reset_server!(port)
27 33
28 -
Restart D3Trees server and resets the TREE_DATA. 
34 +
Restart D3Trees server on given port and remove the associated tree_data holding the unexpanded nodes. 
29 35
Use it to get rid of past visualizations that are still kept in memory.
30 36
"""
31 -
function reset_server()
32 -
    @info "(Re)setting D3Trees server."
33 -
    TREE_DATA[] = Dict{String, D3Tree}()
34 -
    if isassigned(SERVER) && isopen(SERVER[])
35 -
        close(SERVER[])
36 -
    end    
37 -
    SERVER[] = HTTP.serve!(TREE_ROUTER |> CorsMiddleware |> LoggingMiddleware, HOST, PORT)
37 +
38 +
function reset_server!(port)
39 +
    @info "(Re)setting D3Trees server at $HOST:$port"
40 +
41 +
    # Kill server on given port if runnning
42 +
    if haskey(SERVERS, port)
43 +
        shutdown_server!(port)
44 +
    end
45 +
46 +
    # start new server on given port
47 +
    router = HTTP.Router(cors404, cors405)
48 +
    server = HTTP.serve!(router |> cors_middleware |> logging_middleware, HOST, port)
49 +
    tree_data = Dict{String, D3Tree}()
50 +
    d3s = D3TreeServer(server, router, tree_data)
51 +
    SERVERS[port] = d3s
52 +
    return d3s
38 53
end
39 54
55 +
"""
56 +
    D3Trees.shutdown_server!(port)
57 +
    D3Trees.shutdown_server!()
40 58
59 +
Shutdown D3Trees server (all servers if port is not specified) and remove associated tree data.
41 60
"""
42 -
LoggingMiddleware logs the request before passing it on to the tree router and request handler and then logs the returned response.
61 +
function shutdown_server!(port)
62 +
    d3s=SERVERS[port]
63 +
    isopen(d3s.server) ? close(d3s) : nothing
64 +
    @assert istaskdone(d3s.server.task)
65 +
    delete!(SERVERS, port)
66 +
end
67 +
shutdown_server!() = [shutdown_server!(port) for port in keys(SERVERS)]
68 +
69 +
43 70
"""
44 -
function LoggingMiddleware(handler)
71 +
logging_middleware logs the request before passing it on to the tree router and request handler and then logs the returned response.
72 +
"""
73 +
function logging_middleware(handler)
45 74
    # Middleware functions return *Handler* functions
46 75
    return function(req::HTTP.Request)
47 76
        @debug "Incoming server request:\n$req"
@@ -52,7 +81,7 @@
Loading
52 81
end
53 82
54 83
"""
55 -
CorsMiddleware: handles preflight request with the OPTIONS flag
84 +
cors_middleware: handles preflight request with the OPTIONS flag
56 85
If a request was recieved with the correct headers, then a response will be 
57 86
sent back with a 200 code, if the correct headers were not specified in the request,
58 87
then a CORS error will be recieved on the client side
@@ -60,7 +89,7 @@
Loading
60 89
not a preflight request, it will simply go to the rest of the layers to be passed to the
61 90
correct service function.
62 91
"""
63 -
function CorsMiddleware(handler)
92 +
function cors_middleware(handler)
64 93
    return function(req::HTTP.Request)
65 94
        if HTTP.hasheader(req, "OPTIONS")
66 95
            return HTTP.Response(200, CORS_OPT_HEADERS)
@@ -104,18 +133,44 @@
Loading
104 133
it leads to faster visualization responses on first click. This helps prevent 
105 134
errors caused by trying to fetching the same resource twice.
106 135
"""
107 -
function dry_run_server(t_orig::D3Tree)
136 +
function dry_run_server(port, tree::D3Tree)
137 +
    # t = deepcopy(tree)
138 +
    # unexpanded_ind = [keys(t.unexpanded_children)...][1]
139 +
108 140
    n = DryRunTree()
109 141
    t = D3Tree(n, max_expand_depth=0)
110 142
    unexpanded_ind=1
111 143
    
112 -
    # t = deepcopy(t_orig)
113 -
    # unexpanded_ind = [keys(t.unexpanded_children)...][1]
114 -
115 144
    div_id = "treevisDryRun"
116 145
    tree_data = Dict(div_id=>t)
117 -
    HTTP.register!(TREE_ROUTER, "GET", "/api/d3trees/v1/dryrun/{treediv}/{nodeid}", req -> handle_subtree_request(req, tree_data, 1))
118 -
    HTTP.get("http://localhost:$(PORT)/api/d3trees/v1/dryrun/$div_id/$unexpanded_ind")
146 +
    HTTP.register!(SERVERS[port].router, "GET", "/api/d3trees/v1/dryrun/{treediv}/{nodeid}", req -> handle_subtree_request(req, tree_data, 1))
147 +
    HTTP.get("http://localhost:$(port)/api/d3trees/v1/dryrun/$div_id/$unexpanded_ind")
148 +
end
149 +
150 +
151 +
const DEFAULT_LAZY_SUBTREE_DEPTH = 2
152 +
const DEFAULT_PORT =16370
153 +
const API_PATH = "api/d3trees/v1/tree"
154 +
155 +
"""
156 +
    Start serving tree from existing or new server on some port. Returns port for the server.
157 +
"""
158 +
function serve_tree!(servers::Dict{<:Integer, D3TreeServer}, t::D3Tree, div::String)
159 +
    port = get(t.options, :port, DEFAULT_PORT)
160 +
    lazy_subtree_depth = get(t.options, :lazy_subtree_depth, DEFAULT_LAZY_SUBTREE_DEPTH)
161 +
162 +
    # if server on this port is not yet running, start it.
163 +
    if !haskey(servers, port) 
164 +
        d3server = reset_server!(port)
165 +
        # speedup first-click response in the visualization
166 +
        get(t.options, :dry_run_lazy_vizualization, t -> dry_run_server(port, t))(t)
167 +
    else
168 +
        d3server = servers[port]
169 +
    end
170 +
    
171 +
    d3server.tree_data[div] = t 
172 +
    HTTP.register!(servers[port].router, "GET", "/$API_PATH/{treediv}/{nodeid}", req -> handle_subtree_request(req, d3server.tree_data, lazy_subtree_depth))
173 +
    return port
119 174
end
120 175
121 176
"""

@@ -95,8 +95,9 @@
Loading
95 95
- `init_expand::Integer` - levels to expand initially.
96 96
- `init_duration::Number` - duration of the initial animation in ms.
97 97
- `svg_height::Number` - height of the svg containing the tree in px.
98 +
- `port::Integer` - specify server port for D3Trees server that will serve subtrees for visualization. Shutdown server by `shutdown_server(port)`.
98 99
- `lazy_subtree_depth::Integer` - (default: 2) sets depth of subtrees fetched from D3Trees server
99 -
- `dry_run_lazy_vizualization::Bool` - (default: true) if true, when starting the visualization, the server methods are ran once on a deepcopy of the tree to speed up first fetch in the visualization. Disable if the initial tree is large or subtrees are slow to compute.
100 +
- `dry_run_lazy_vizualization::Function` - (default: t -> D3Trees.dry_run_server(port, t)) function that is ran once before visualization is started to speed up first fetch in the visualization. Provide custom function if your tree's children method takes a long time on first run.
100 101
"""
101 102
function D3Tree(children::AbstractVector{<:AbstractVector}; kwargs...)
102 103
    kwd = Dict(kwargs)
Files Coverage
src 86.34%
Project Totals (5 files) 86.34%
codecov-umbrella
Build #2671044013 -
unittests
codecov-umbrella
Build #2671044013 -
unittests
codecov-umbrella
Build #2671044013 -
unittests
codecov-umbrella
Build #2671044013 -
unittests
codecov-umbrella
Build #2671044013 -
unittests
codecov-umbrella
Build #2671044013 -
unittests

No yaml found.

Create your codecov.yml to customize your Codecov experience

Sunburst
The inner-most circle is the entire project, moving away from the center are folders then, finally, a single file. The size and color of each slice is representing the number of statements and the coverage, respectively.
Icicle
The top section represents the entire project. Proceeding with folders and finally individual files. The size and color of each slice is representing the number of statements and the coverage, respectively.
Grid
Each block represents a single file in the project. The size and color of each block is represented by the number of statements and the coverage, respectively.
Loading