@@ -3,19 +3,29 @@ module PlotlyLight
3
3
using Artifacts: @artifact_str
4
4
using Downloads: download
5
5
using Random: randstring
6
- using REPL: REPL
7
6
8
7
using JSON3: JSON3
9
8
using EasyConfig: Config
10
- using StructTypes: StructTypes
11
9
using Cobweb: Cobweb, h, IFrame, Node
12
10
13
11
# -----------------------------------------------------------------------------# 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" )
15
25
16
- # -----------------------------------------------------------------------------# PlotlyArtifacts
17
26
artifact (x... ) = joinpath (artifact " plotly_artifacts" , x... )
18
27
28
+ # -----------------------------------------------------------------------------# plotly::PlotlyArtifacts
19
29
Base. @kwdef struct PlotlyArtifacts
20
30
version:: VersionNumber = VersionNumber (read (artifact (" version.txt" ), String))
21
31
url:: String = " https://cdn.plot.ly/plotly-$version .min.js"
@@ -28,22 +38,36 @@ plotly::PlotlyArtifacts = PlotlyArtifacts()
28
38
29
39
# -----------------------------------------------------------------------------# Settings
30
40
Base. @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 " )
33
43
layout:: Config = Config ()
34
- config:: Config = Config (responsive= true )
44
+ config:: Config = Config (responsive= true , displaylogo = false )
35
45
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 = []
38
49
end
39
50
settings:: Settings = Settings ()
40
51
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
46
59
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
47
71
attributes (t:: Symbol ) = plotly. schema. traces[t]. attributes
48
72
check_attribute (trace, attr:: Symbol ) = haskey (attributes (Symbol (trace)), attr) || @warn (" `$trace ` does not have attribute `$attr `." )
49
73
check_attributes (trace; kw... ) = foreach (k -> check_attribute (Symbol (trace), k), keys (kw))
@@ -53,63 +77,91 @@ mutable struct Plot
53
77
data:: Vector{Config}
54
78
layout:: Config
55
79
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))
57
82
end
58
83
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
+
61
89
(p:: Plot )(; kw... ) = p (Config (kw))
62
90
(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)
64
92
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
67
97
68
98
Base. getproperty (p:: Plot , x:: Symbol ) = x in fieldnames (Plot) ? getfield (p, x) : (; kw... ) -> p (plot (; type= x, kw... ))
69
99
Base. propertynames (p:: Plot ) = vcat (fieldnames (Plot)... , keys (plotly. schema. traces)... )
70
100
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)
73
102
74
103
# -----------------------------------------------------------------------------# 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))
90
135
end
91
- function html_page (o:: Plot )
136
+
137
+ function html_page (o:: Plot , id= rand_id ())
92
138
h. html (
93
139
h. head (
94
140
h. meta (charset= " utf-8" ),
95
141
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 " ),
97
143
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
100
147
),
101
- h. body (html_div (o ))
148
+ h. body (h . div (class = " plotlylight-parent " , settings . div (; id), NewPlotScript (o, settings, id) ))
102
149
)
103
150
end
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)
109
151
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
111
158
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)
113
165
114
166
# -----------------------------------------------------------------------------# preset
115
167
# `preset_template_<X>` overwrites `settings.layout.template`
@@ -137,6 +189,10 @@ preset = (
137
189
cdn! = () -> (settings. src = h. script (src= plotly. url, charset= " utf-8" ); nothing ),
138
190
local ! = () -> (settings. src = h. script (src= plotly. path, charset= " utf-8" ); nothing ),
139
191
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" ))),
140
196
)
141
197
)
142
198
0 commit comments