@@ -97,7 +97,30 @@ def running_in_notebook():
9797 return False # Other type (?)
9898 except NameError :
9999 return False # Probably standard Python interpreter
100-
100+
101+
102+ DEFAULT_D3JS_URL = "https://d3js.org/d3.v7.min"
103+
104+ D3jsSource = collections .namedtuple ("D3jsSource" , ["url" , "inline_script" ])
105+ """
106+ Result of resolve_d3js_source. Exactly one field is meaningful:
107+ - url is a string URL (and inline_script is ""), or
108+ - url is None and inline_script is a "<script>…</script>" HTML fragment.
109+ """
110+
111+
112+ def resolve_d3js_source (d3js ):
113+ if d3js is None :
114+ return D3jsSource (url = DEFAULT_D3JS_URL , inline_script = "" )
115+ try :
116+ d3_content = d3js .read ()
117+ except AttributeError :
118+ return D3jsSource (url = str (d3js ), inline_script = "" )
119+ if isinstance (d3_content , bytes ):
120+ d3_content = d3_content .decode ("utf-8" )
121+ return D3jsSource (url = None , inline_script = f"<script>{ d3_content } </script>" )
122+
123+
101124def calculate_evenly_distributed_positions (num_elements , start = 0 , end = 1 , round_to = 0 ):
102125 """Returns a list of `num_elements` evenly distributed positions on a given `length`
103126
@@ -179,21 +202,23 @@ def convert_time_to_position(t, min_time, max_time, scale, unique_times, h_spaci
179202 return (1 - (t - min_time )/ time_range ) * (height - 100 ) + y_shift
180203
181204
182- def draw_D3 (arg_json , styles = None , is_notebook = None ):
205+ def draw_D3 (arg_json , styles = None , is_notebook = None , d3js = None ):
206+ d3js_source = resolve_d3js_source (d3js )
183207 if is_notebook is None :
184208 is_notebook = running_in_notebook ()
185209 arg_json ["source" ] = json .dumps (arg_json .copy ()) # first escape the plain json data
210+ arg_json ["d3_url" ] = d3js_source .url # Could be None if an inline script is used
186211 arg_json = {k : json .dumps (v ) for k , v in arg_json .items ()} # now escape all
187212 arg_json ["divnum" ] = str (random .randint (0 ,9999999999 ))
188213 arg_id = "arg_" + arg_json ['divnum' ]
189214 JS_text = Template ((
190- '<div id="{}" class="d3arg" style="min-width:{}px; min-height:{}px;"></div>'
215+ '<div id="{}" class="d3arg" style="min-width:{}px; min-height:{}px;"></div>$d3_inline_script '
191216 '<script>$main_text</script>'
192217 ).format (arg_id , float (arg_json ["width" ]) + 40 , float (arg_json ["height" ]) + 80 ))
193218 with open (os .path .dirname (__file__ ) + "/visualizer.js" , "r" ) as visualizerjs :
194219 main_text_template = Template (visualizerjs .read ())
195220 main_text = main_text_template .safe_substitute (arg_json )
196- html = JS_text .safe_substitute ({'main_text' : main_text })
221+ html = JS_text .safe_substitute ({'main_text' : main_text , 'd3_inline_script' : d3js_source . inline_script })
197222 with open (os .path .dirname (__file__ ) + "/visualizer.css" , "r" ) as css :
198223 general_styles = css .read ()
199224 specific_styles = ""
@@ -1459,6 +1484,7 @@ def draw(
14591484 styles = None ,
14601485 preamble = None ,
14611486 save_filename = None ,
1487+ d3js = None ,
14621488 ):
14631489 """Draws the D3ARG using D3.js by sending a custom JSON object to visualizer.js
14641490
@@ -1533,6 +1559,12 @@ def draw(
15331559 save_filename : str
15341560 Filename to use when selecting "Download as" in the visualization
15351561 (default=None, treated as "tskit_arg_visualizer")
1562+ d3js : optional
1563+ Source for loading D3.js. If None, uses the default URL
1564+ https://d3js.org/d3.v7.min. Otherwise, this is first treated as a file-like
1565+ object by trying `.read()`: if successful, the returned JavaScript source is
1566+ embedded directly in the output HTML (bytes are decoded as UTF-8). If `.read()`
1567+ is unavailable, the value is converted to `str(...)` and used as a URL.
15361568
15371569 Returns
15381570 -------
@@ -1574,7 +1606,7 @@ def draw(
15741606 preamble = preamble ,
15751607 save_filename = save_filename ,
15761608 )
1577- info = draw_D3 (arg_json = arg , styles = styles , is_notebook = is_notebook )
1609+ info = draw_D3 (arg_json = arg , styles = styles , is_notebook = is_notebook , d3js = d3js )
15781610 info .included .nodes = included_nodes ["id" ].tolist ()
15791611 return info
15801612
@@ -1723,6 +1755,7 @@ def draw_node(
17231755 styles = None ,
17241756 preamble = None ,
17251757 save_filename = None ,
1758+ d3js = None ,
17261759 ):
17271760 """Draws a subgraph of the D3ARG using D3.js by sending a custom JSON object to visualizer.js.
17281761
@@ -1789,6 +1822,12 @@ def draw_node(
17891822 save_filename : str
17901823 Filename to use when selecting "Download as" in the visualization
17911824 (default=None, treated as "tskit_arg_visualizer")
1825+ d3js : optional
1826+ Source for loading D3.js. If None, uses the default URL
1827+ https://d3js.org/d3.v7.min. Otherwise, this is first treated as a file-like
1828+ object by trying `.read()`: if successful, the returned JavaScript source is
1829+ embedded directly in the output HTML (bytes are decoded as UTF-8). If `.read()`
1830+ is unavailable, the value is converted to `str(...)` and used as a URL.
17921831
17931832 Returns
17941833 -------
@@ -1825,7 +1864,7 @@ def draw_node(
18251864 preamble = preamble ,
18261865 save_filename = save_filename ,
18271866 )
1828- info = draw_D3 (arg_json = arg , styles = styles , is_notebook = is_notebook )
1867+ info = draw_D3 (arg_json = arg , styles = styles , is_notebook = is_notebook , d3js = d3js )
18291868 info .included .nodes = included .nodes ["id" ].tolist ()
18301869 return info
18311870
@@ -1839,6 +1878,7 @@ def draw_genome_bar(
18391878 windows = None ,
18401879 show_mutations = False ,
18411880 is_notebook = None ,
1881+ d3js = None ,
18421882 ):
18431883 """Draws a genome bar for the D3ARG using D3.js
18441884
@@ -1857,10 +1897,16 @@ def draw_genome_bar(
18571897 whether it is being called in a notebook environment. This may not work in
18581898 some untested environments, in which case you may wish to set this explicitly
18591899 to True (to force a notebook display) or False (to force a standalone HTML page).
1900+ d3js : optional
1901+ Source for loading D3.js. If None, uses the default URL
1902+ https://d3js.org/d3.v7.min. Otherwise, this is first treated as a file-like
1903+ object by trying `.read()`: if successful, the returned JavaScript source is
1904+ embedded directly in the output HTML (bytes are decoded as UTF-8). If `.read()`
1905+ is unavailable, the value is converted to `str(...)` and used as a URL.
18601906 """
18611907 if is_notebook is None :
18621908 is_notebook = running_in_notebook ()
1863-
1909+ d3js_source = resolve_d3js_source ( d3js )
18641910 transformed_bps = self .breakpoints .loc [:,:]
18651911 transformed_bps ["x_pos" ] = transformed_bps ["x_pos_01" ] * width
18661912 transformed_bps ["width" ] = transformed_bps ["width_01" ] * width
@@ -1897,13 +1943,15 @@ def draw_genome_bar(
18971943 }
18981944
18991945 genome_bar_json ["source" ] = genome_bar_json .copy ()
1946+ genome_bar_json ["d3_url" ] = d3js_source .url # Could be None if an inline script is used. Will be escaped below
1947+ genome_bar_json = {k : json .dumps (v ) for k , v in genome_bar_json .items ()}
19001948 genome_bar_json ["divnum" ] = str (random .randint (0 ,9999999999 ))
1901- JS_text = Template ("<div id='genome_bar_" + genome_bar_json ['divnum' ] + "'class='d3arg' style='min-width:" + str (genome_bar_json ["width" ]+ 40 ) + "px; min-height:180px;'></div><script>$main_text</script>" )
1949+ JS_text = Template ("<div id='genome_bar_" + genome_bar_json ['divnum' ] + "'class='d3arg' style='min-width:" + str (json . loads ( genome_bar_json ["width" ]) + 40 ) + "px; min-height:180px;'></div>$d3_inline_script <script>$main_text</script>" )
19021950 breakpointsjs = open (os .path .dirname (__file__ ) + "/alternative_plots/genome_bar.js" , "r" )
19031951 main_text_template = Template (breakpointsjs .read ())
19041952 breakpointsjs .close ()
19051953 main_text = main_text_template .safe_substitute (genome_bar_json )
1906- html = JS_text .safe_substitute ({'main_text' : main_text })
1954+ html = JS_text .safe_substitute ({'main_text' : main_text , 'd3_inline_script' : d3js_source . inline_script })
19071955 css = open (os .path .dirname (__file__ ) + "/visualizer.css" , "r" )
19081956 styles = css .read ()
19091957 css .close ()
@@ -1912,7 +1960,7 @@ def draw_genome_bar(
19121960 else :
19131961 with tempfile .NamedTemporaryFile ("w" , delete = False , suffix = ".html" ) as f :
19141962 url = "file://" + f .name
1915- f .write ("<!DOCTYPE html><html><head><style>" + styles + "</style><script src='https://cdn.rawgit.com/eligrey/canvas-toBlob.js/f1a01896135ab378aa5c0118eadd81da55e698d8/canvas-toBlob.js'></script><script src='https://cdn.rawgit.com/eligrey/FileSaver.js/e9d941381475b5df8b7d7691013401e171014e89/FileSaver.min.js'></script><script src='https://d3js.org/d3.v7.min.js'></script>< /head><body>" + html + "</body></html>" )
1963+ f .write ("<!DOCTYPE html><html><head><style>" + styles + "</style><script src='https://cdn.rawgit.com/eligrey/canvas-toBlob.js/f1a01896135ab378aa5c0118eadd81da55e698d8/canvas-toBlob.js'></script><script src='https://cdn.rawgit.com/eligrey/FileSaver.js/e9d941381475b5df8b7d7691013401e171014e89/FileSaver.min.js'></script></head><body>" + html + "</body></html>" )
19161964 webbrowser .open (url , new = 2 )
19171965
19181966
0 commit comments