@@ -3,19 +3,29 @@ module PlotlyLight
33using Artifacts: @artifact_str
44using Downloads: download
55using Random: randstring
6- using REPL: REPL
76
87using JSON3: JSON3
98using EasyConfig: Config
10- using StructTypes: StructTypes
119using Cobweb: Cobweb, h, IFrame, Node
1210
1311# -----------------------------------------------------------------------------# exports
14- export Plot, Config, preset, plot
12+ export Config, preset, Plot, plot
13+
14+ # -----------------------------------------------------------------------------# __init__
15+ function __init__ ()
16+ # Hack since extensions with REPL are wonky
17+ for M in Base. loaded_modules_order
18+ if Symbol (M) == :REPL
19+ @eval Base. display (:: $M.REPLDisplay , o:: Plot ) = Cobweb. preview (html_page (o))
20+ end
21+ end
22+ end
23+
24+ include (" json.jl" )
1525
16- # -----------------------------------------------------------------------------# PlotlyArtifacts
1726artifact (x... ) = joinpath (artifact " plotly_artifacts" , x... )
1827
28+ # -----------------------------------------------------------------------------# plotly::PlotlyArtifacts
1929Base. @kwdef struct PlotlyArtifacts
2030 version:: VersionNumber = VersionNumber (read (artifact (" version.txt" ), String))
2131 url:: String = " https://cdn.plot.ly/plotly-$version .min.js"
@@ -28,22 +38,36 @@ plotly::PlotlyArtifacts = PlotlyArtifacts()
2838
2939# -----------------------------------------------------------------------------# Settings
3040Base. @kwdef mutable struct Settings
31- src:: Node = h. script (src= " https://cdn.plot.ly/ plotly- $(plotly . version) .min.js " , charset= " utf-8" )
32- div:: Node = h. div (; style = " height:100vh;width:100vw; " )
41+ src:: Node = h. script (src= plotly. url , charset= " utf-8" )
42+ div:: Node = h. div (; class = " plotlylight-plot-div " )
3343 layout:: Config = Config ()
34- config:: Config = Config (responsive= true )
44+ config:: Config = Config (responsive= true , displaylogo = false )
3545 reuse_preview:: Bool = true
36- style:: Dict{String,String} = Dict (" display" => " block" , " border" => " none" , " min-height" => " 350px" , " min-width" => " 350px" , " width" => " 100%" , " height" => " 100%" )
37- inject_head:: Union{Nothing, Node} = nothing
46+ page_css:: Cobweb.Node = h. style (" html, body { padding: 0px; margin: 0px; }" )
47+ iframe_style = " display:block; border:none; min-height:350px; min-width:350px; width:100%; height:100%"
48+ src_inject:: Vector = []
3849end
3950settings:: Settings = Settings ()
4051
41- # -----------------------------------------------------------------------------# utils/other
42- # Hack to change behavior of `JSON3.write` for `AbstractMatrix`
43- _fix (x:: Config ) = Config (k => _fix (v) for (k,v) in pairs (x))
44- _fix (x) = x
45- _fix (x:: AbstractMatrix ) = eachrow (x)
52+ function Settings (s:: Settings ; kw... )
53+ s2 = deepcopy (s)
54+ for (k, v) in kw
55+ setfield! (s2, k, v)
56+ end
57+ return s2
58+ end
4659
60+ function with_settings (f; kw... )
61+ old = settings
62+ try
63+ global settings = Settings (settings; kw... )
64+ f (settings)
65+ finally
66+ global settings = old
67+ end
68+ end
69+
70+ # -----------------------------------------------------------------------------# utils/other
4771attributes (t:: Symbol ) = plotly. schema. traces[t]. attributes
4872check_attribute (trace, attr:: Symbol ) = haskey (attributes (Symbol (trace)), attr) || @warn (" `$trace ` does not have attribute `$attr `." )
4973check_attributes (trace; kw... ) = foreach (k -> check_attribute (Symbol (trace), k), keys (kw))
@@ -53,63 +77,91 @@ mutable struct Plot
5377 data:: Vector{Config}
5478 layout:: Config
5579 config:: Config
56- Plot (data:: Vector{Config} , layout:: Config = Config (), config:: Config = Config ()) = new (data, Config (layout), Config (config))
80+ Plot (data:: AbstractVector , layout = Config (), config = Config ()) = new (Config .(data), Config (layout), Config (config))
81+ Plot (data, layout = Config (), config = Config ()) = new ([Config (data)], Config (layout), Config (config))
5782end
5883
59- Plot (data:: Config , layout:: Config = Config (), config:: Config = Config ()) = Plot ([data], layout, config)
60- Plot (; layout= Config (), config= Config (), kw... ) = Plot (Config (kw), Config (layout), Config (config))
84+ Base.:(== )(a:: Plot , b:: Plot ) = all (getfield (a,f) == getfield (b,f) for f in fieldnames (Plot))
85+
86+ save (p:: Plot , file:: AbstractString ) = open (io -> print (io, html_page (p)), file, " w" )
87+ save (file:: AbstractString , p:: Plot ) = save (p, file)
88+
6189(p:: Plot )(; kw... ) = p (Config (kw))
6290(p:: Plot )(data:: Config ) = (push! (p. data, data); return p)
63- (p:: Plot )(p2:: Plot ) = ( append! (p . data, p2 . data); merge! (p. layout , p2. layout); merge! (p . config, p2 . config); p )
91+ (p:: Plot )(p2:: Plot ) = merge! (p, p2)
6492
65- StructTypes. StructType (:: Plot ) = StructTypes. Struct ()
66- Base.:(== )(a:: Plot , b:: Plot ) = all (getfield (a,f) == getfield (b,f) for f in fieldnames (Plot))
93+ function Plot (; kw... )
94+ Base. depwarn (" `Plot(; kw...)` is deprecated. Use `plot(; kw...)` instead." , :Plot , force= true )
95+ plot (; kw... )
96+ end
6797
6898Base. getproperty (p:: Plot , x:: Symbol ) = x in fieldnames (Plot) ? getfield (p, x) : (; kw... ) -> p (plot (; type= x, kw... ))
6999Base. propertynames (p:: Plot ) = vcat (fieldnames (Plot)... , keys (plotly. schema. traces)... )
70100
71- save (p:: Plot , file:: AbstractString ) = open (io -> print (io, html_page (p)), file, " w" )
72- save (file:: AbstractString , p:: Plot ) = save (p, file)
101+ Base. merge! (a:: Plot , b:: Plot ) = (append! (a. data, b. data); merge! (a. layout, b. layout); merge! (a. config, b. config); a)
73102
74103# -----------------------------------------------------------------------------# plot
75- plot (; kw... ) = plot (get (kw, :type , :scatter ); kw... )
76- plot (trace; kw... ) = (check_attributes (trace; kw... ); Plot (; type= trace, kw... ))
77- Base. propertynames (:: typeof (plot)) = sort! (collect (keys (plotly. schema. traces)))
78- Base. getproperty (:: typeof (plot), x:: Symbol ) = (; kw... ) -> plot (x; kw... )
79-
80- # -----------------------------------------------------------------------------# display/show
81- function html_div (o:: Plot ; id= randstring (10 ))
82- data = JSON3. write (_fix .(o. data); allow_inf= true )
83- layout = JSON3. write (merge (settings. layout, o. layout); allow_inf= true )
84- config = JSON3. write (merge (settings. config, o. config); allow_inf= true )
85- h. div (class= " plotlylight-parent-div" ,
86- settings. src,
87- settings. div (; id, class= " plotlylight-plot-div" ),
88- h. script (" Plotly.newPlot(\" $id \" , $data , $layout , $config )" )
89- )
104+ function plot (; layout = Config (), config= Config (), type= :scatter , kw... )
105+ check_attributes (type; kw... )
106+ data = isempty (kw) ? Config[] : [Config (; type, kw... )]
107+ Plot (data, layout, config)
108+ end
109+ Base. propertynames (:: typeof (plot)) = keys (plotly. schema. traces)
110+ Base. getproperty (:: typeof (plot), type:: Symbol ) = (; kw... ) -> plot (; type= type, kw... )
111+
112+
113+ # -----------------------------------------------------------------------------# NewPlotScript
114+ # PlotlyX representation of: <script>Plotly.newPlot("$id", $data, $layout, $config)</script>
115+ struct NewPlotScript
116+ plot:: Plot
117+ settings:: Settings
118+ id:: String
119+ end
120+ function Base. show (io:: IO , :: MIME"text/html" , o:: NewPlotScript )
121+ layout = merge (o. settings. layout, o. plot. layout)
122+ config = merge (o. settings. config, o. plot. config)
123+ print (io, " <script>Plotly.newPlot(\" " , o. id, " \" ," )
124+ json (io, o. plot. data); print (io, ' ,' )
125+ json (io, layout); print (io, ' ,' )
126+ json (io, config)
127+ print (io, " )</script>" )
128+ end
129+
130+ # -----------------------------------------------------------------------------# display
131+ rand_id () = " plotlyx-" * join (rand (' a' :' z' , 10 ))
132+
133+ function html_div (o:: Plot , id= rand_id ())
134+ h. div (class= " plotlylight-parent" , settings. src, settings. src_inject... , settings. div (; id), NewPlotScript (o, settings, id))
90135end
91- function html_page (o:: Plot )
136+
137+ function html_page (o:: Plot , id= rand_id ())
92138 h. html (
93139 h. head (
94140 h. meta (charset= " utf-8" ),
95141 h. meta (name= " viewport" , content= " width=device-width, initial-scale=1" ),
96- h. meta (name= " description" , content= " PlotlyLight.jl" ),
142+ h. meta (name= " description" , content= " PlotlyLight.jl Plot " ),
97143 h. title (" PlotlyLight.jl" ),
98- h. style (" html, body { padding: 0px; margin: 0px; } /* remove scrollbar in iframe */" ),
99- isnothing (settings. inject_head) ? " " : settings. inject_head
144+ settings. page_css,
145+ settings. src_inject... ,
146+ settings. src
100147 ),
101- h. body (html_div (o ))
148+ h. body (h . div (class = " plotlylight-parent " , settings . div (; id), NewPlotScript (o, settings, id) ))
102149 )
103150end
104- function html_iframe (o:: Plot ; style= settings. style)
105- IFrame (html_page (o); style= join ([" $k :$v " for (k,v) in style], ' ;' ))
106- end
107- Base. show (io:: IO , :: MIME"text/html" , o:: Plot ) = show (io, MIME " text/html" (), html_iframe (o))
108- Base. show (io:: IO , :: MIME"juliavscode/html" , o:: Plot ) = show (io, MIME " text/html" (), o)
109151
110- Base. display (:: REPL.REPLDisplay , o:: Plot ) = Cobweb. preview (h. html (h. body (o, style= " margin: 0px;" )), reuse= settings. reuse_preview)
152+ function html_iframe (o:: Plot , id= rand_id (), kw... )
153+ with_settings () do s
154+ s. div. style = " height:100vh; width:100vw"
155+ Cobweb. IFrame (html_page (o, id); style= s. iframe_style, kw... )
156+ end
157+ end
111158
112- mathjax_script = h. script (type= " text/javascript" , async= true , src= " https://cdn.jsdelivr.net/npm/mathjax@3/es5/tex-mml-chtml.js" )
159+ function Base. show (io:: IO , :: MIME"text/html" , o:: Plot )
160+ get (io, :jupyter , false ) ?
161+ show (io, MIME (" text/html" ), html_iframe (o)) :
162+ show (io, MIME (" text/html" ), html_div (o))
163+ end
164+ Base. show (io:: IO , :: MIME"juliavscode/html" , o) = show (io, MIME (" text/html" ), o)
113165
114166# -----------------------------------------------------------------------------# preset
115167# `preset_template_<X>` overwrites `settings.layout.template`
@@ -137,6 +189,10 @@ preset = (
137189 cdn! = () -> (settings. src = h. script (src= plotly. url, charset= " utf-8" ); nothing ),
138190 local ! = () -> (settings. src = h. script (src= plotly. path, charset= " utf-8" ); nothing ),
139191 standalone! = () -> (settings. src = h. script (read (plotly. path, String), charset= " utf-8" ); nothing )
192+ ),
193+ display = (
194+ fullscreen! = () -> (settings. div. style = " height:100vh; width:100vw" ),
195+ mathjax!
= ()
-> (
push! (settings
. src_inject, h
. script (src
= " https://cdn.jsdelivr.net/npm/[email protected] /es5/tex-svg.js" ))),
140196 )
141197)
142198
0 commit comments