11# __precompile__(true)
22
33module Plotly
4- using Compat
5- using Compat: String
6- using Requests
7- using JSON
8- using Reexport: @reexport
9- import Requests: URI, post
104
5+ using URIParser
6+ using Reexport
117@reexport using PlotlyJS
128export post
139export set_credentials_file, RemotePlot, download_plot
1410
15- include ( " utils.jl " )
16-
17- const api_version = " v2 "
18-
19- const default_kwargs = Dict { Symbol,Any} ( :filename => " Plot from Julia API " ,
20- :world_readable => true )
11+ const _SRC_ATTRS = let
12+ _src_attr_path = joinpath ( dirname (PlotlyJS . _js_path), " src_attrs.csv " )
13+ raw_src_attrs = vec ( readdlm (_src_attr_path))
14+ src_attrs = map (x -> x[ 1 : end - 3 ], raw_src_attrs) # remove the `src` suffix
15+ Set ( map ( Symbol, src_attrs))
16+ end :: Set{Symbol}
2117
22- const default_opts = Dict {Symbol,Any} (:origin => " plot" ,
23- :platform => " Julia" ,
24- :version => " 0.2" )
18+ include (" utils.jl" )
19+ include (" v2.jl" )
2520
2621# # Taken from https://github.com/johnmyleswhite/Vega.jl/blob/master/src/Vega.jl#L51
2722# Open a URL in a browser
3732
3833openurl (url:: URI ) = openurl (string (url))
3934
40- get_plot_endpoint () = " $(get_config (). plotly_domain) /clientresp"
41-
4235"""
4336Proxy for a plot stored on the Plotly cloud.
4437"""
45- immutable RemotePlot
38+ struct RemotePlot
4639 url:: URI
4740end
4841RemotePlot (url:: String ) = RemotePlot (URI (url))
4942
43+ """
44+ fid(rp::RemotePlot)
45+
46+ Return the unique plotly `fid` for `rp`. Throws an error if the url inside
47+ `rp` is not correctly formed.
48+ """
49+ function fid (rp:: RemotePlot )
50+ parts = match (r" ^/~([^/]+)/(\d +)/?" , rp. url. path)
51+ if parts === nothing
52+ error (" Malformed RemotePlot url" )
53+ end
54+ " $(parts[1 ]) :$(parts[2 ]) "
55+ end
56+
5057"""
5158Display a plot stored in the Plotly cloud in a browser window.
5259"""
@@ -57,105 +64,215 @@ Post a local Plotly plot to the Plotly cloud.
5764
5865Must be signed in first.
5966"""
60- function post (p:: Plot ; kwargs... )
67+ function post (p:: Plot ; fileopt= :overwrite , filename= nothing , kwargs... )
68+ fileopt = filename == nothing ? :create : Symbol (fileopt)
69+ grid_fn = string (filename, " _" , " Grid" )
70+ clean_p = srcify (p; fileopt= fileopt, grid_fn= grid_fn, kwargs... )
71+ if fileopt == :overwrite
72+ file_data = try_lookup (filename)
73+ if file_data == nothing
74+ fileopt = :create
75+ else
76+ res = plot_update (file_data[" fid" ], figure= clean_p)
77+ return RemotePlot (res[" web_url" ])
78+ end
79+ end
80+ if fileopt == :create
81+ res = plot_create (clean_p; filename= filename, kwargs... )
82+ return RemotePlot (res[" file" ][" web_url" ])
83+ else
84+ error (" fileopt must be one of `overwrite` and `create`" )
85+ end
86+ end
87+
88+ function post_v1 (p:: Plot ; kwargs... )
89+ default_kwargs = Dict {Symbol,Any} (:filename => " Plot from Julia API" ,
90+ :world_readable => true )
91+ default_opts = Dict {Symbol,Any} (:origin => " plot" ,
92+ :platform => " Julia" ,
93+ :version => " 0.2" )
6194 creds = get_credentials ()
62- endpoint = get_plot_endpoint ()
63- opt = merge (default_kwargs, Dict (:layout => p. layout. fields),
64- Dict (kwargs))
95+ endpoint = " $(get_config (). plotly_domain) /clientresp"
96+ opt = merge (
97+ default_kwargs,
98+ Dict (:layout => p. layout. fields),
99+ Dict (kwargs)
100+ )
65101
66- data = merge (default_opts,
67- Dict (" un" => creds. username,
68- " key" => creds. api_key,
69- " args" => json (p. data),
70- " kwargs" => json (opt)))
102+ data = merge (
103+ default_opts,
104+ Dict (
105+ " un" => creds. username,
106+ " key" => creds. api_key,
107+ " args" => json (p. data),
108+ " kwargs" => json (opt)
109+ )
110+ )
71111
72112 r = post (endpoint, data= data)
73- body = parse_response (r)
113+ body = Requests. json (r)
114+ if Requests. statuscode (r) ≠ 200
115+ throw (PlotlyError (" Non-sucessful status code: $(statuscode (r)) " ))
116+ elseif " error" ∈ keys (body) && body[" error" ] ≠ " "
117+ throw (PlotlyError (body[" error" ]))
118+ elseif " detail" ∈ keys (body) && body[" detail" ] ≠ " "
119+ throw (PlotlyError (body[" detail" ]))
120+ end
74121 return RemotePlot (URI (body[" url" ]))
75122end
76123
77- function Requests. post (l:: AbstractLayout , meta_opts= Dict (); meta_kwargs... )
78- creds = get_credentials ()
79- endpoint = get_plot_endpoint ()
80-
81- meta = merge (meta_opts,
82- get_required_params ([" filename" , " fileopt" ], meta_opts),
83- Dict (meta_kwargs))
84- data = merge (default_opts,
85- Dict (" un" => creds. username,
86- " key" => creds. api_key,
87- " args" => json (l),
88- " origin" => " layout" ,
89- " kwargs" => json (meta)))
90-
91- parse_response (post (endpoint, data= data))
92- end
93-
94124post (p:: PlotlyJS.SyncPlot ; kwargs... ) = post (p. plot; kwargs... )
95125
96- function style (style_opts, meta_opts= Dict (); meta_kwargs... )
97- creds = get_credentials ()
98- endpoint = get_plot_endpoint ()
99-
100- meta = merge (meta_opts,
101- get_required_params ([" filename" , " fileopt" ], meta_opts),
102- Dict (meta_kwargs))
103- data = merge (default_opts,
104- Dict (" un" => creds. username,
105- " key" => creds. api_key,
106- " args" => json ([style_opts]),
107- " origin" => " style" ,
108- " kwargs" => json (meta_opts)))
109-
110- parse_response (post (endpoint, data= data))
126+ """
127+ srcify!(p::Plot; fileopt::Symbol=:overwrite, grid_fn=nothing, kwargs...)
128+
129+ Look through each trace and the Layout for attributes that have a value of type
130+ Union{AbstractArray,Tuple} and are able to be set via `(attributename)(src)` in
131+ the plotly.js api. For each of these, do the following:
132+
133+ 1. Extract the value so it can become a column in a Grid
134+ 2. Remove that key/value pair from the trace/Layout
135+
136+ This happens in place, so the `src`ified fields will be removed within the
137+ input to this function.
138+ """
139+ function extract_grid_data! (p:: Plot )
140+ data_for_grid = Dict ()
141+ function add_to_grid! (k:: Vector , v:: Union{AbstractArray,Tuple} )
142+ if ! (k[end ] in Plotly. _SRC_ATTRS)
143+ return
144+ # only do what follows if k is one of the src attrs
145+ end
146+ # all the magic happens here
147+ the_key = join (map (string, k), " _" )
148+ setindex! (data_for_grid, Dict {Any,Any} (" data" => v, " temp" => k), the_key) # step 1
149+
150+ # now remove this from the plot
151+ if k[1 ] == " trace"
152+ ind = k[2 ]
153+ attr = join (map (String, k[3 : end ]), " _" )
154+ pop! (p. data[ind], attr)
155+ elseif k[1 ] == " layout"
156+ attr = join (map (String, k[2 : end ]), " _" )
157+ pop! (p. layout, attr)
158+ else
159+ error (" bad key..." )
160+ end
161+ end
162+ function add_to_grid! (k1:: Vector , v:: Associative )
163+ for (k2, v2) in v
164+ add_to_grid! (vcat (k1, k2), v2)
165+ end
166+ end
167+ add_to_grid! (k:: Vector , v) = nothing # otherwise don't do anything...
168+
169+ for (i, t) in enumerate (p. data)
170+ k = [" trace" , i]
171+ for (field, val) in t
172+ add_to_grid! (vcat (k, field), val)
173+ end
174+ end
175+
176+ k = [" layout" ]
177+ for (field, val) in p. layout
178+ add_to_grid! (vcat (k, field), val)
179+ end
180+ data_for_grid
111181end
112182
183+ extract_grid_data (p:: Plot ) = extract_grid_data! (deepcopy (p))
184+
113185"""
114- Transport a plot from the Plotly cloud to a local `Plot` object.
186+ srcify!(p::Plot; fileopt::Symbol=:overwrite, grid_fn=nothing, kwargs...)
115187
116- Must be signed in first if the plot is not public.
188+ This function does three things:
189+
190+ 1. Calls `extract_grid_data!(p)` (see docs) to remove attributes that can be
191+ set via `(attributename)(src)` in the plotly.js api.
192+ 2. Creates a grid on the plotly server containing the extract data
193+ 3. Maps the `(attributename)(src)` attribute to the grid column
194+
195+ If fileopt is `:create` and `grid_fn` exists under the User's plotly account,
196+ then the changes described above will happen in-place on the grid.
197+
198+ If either of those conditions are not met, then a new grid will be created.
117199"""
118- function Base. download (plot:: RemotePlot )
119- creds = get_credentials ()
120- username = creds. username
121- api_key = creds. api_key
122- lib_version = string (default_opts[:platform ], " " , default_opts[:version ])
123- auth = string (" Basic " , base64encode (" $username :$api_key " ))
124- options = Dict (" Authorization" => auth, " Plotly-Client-Platform" => lib_version)
125- original_path = plot. url. path
126- if original_path[end ] == ' /'
127- path = original_path[1 : end - 1 ]
200+ function srcify! (p:: Plot ; fileopt:: Symbol = :overwrite , grid_fn= nothing , kwargs... )
201+ data_for_grid = extract_grid_data! (p)
202+ temp_map = Dict ()
203+ for (k, v) in data_for_grid
204+ temp_map[k] = pop! (v, " temp" )
205+ end
206+
207+ if fileopt == :overwrite
208+ grid_info = try_me (grid_lookup, grid_fn )
209+ fid = lookup_info[" fid" ]
210+ if lookup_info == nothing
211+ fileopt = :create
212+ else
213+ uid_map = grid_overwrite! (data_for_grid; fid= lookup_info[" fid" ])
214+ @goto add_src_attrs
215+ end
216+ end
217+
218+ if fileopt == :create
219+ # add order to each grid
220+ for (i, (k, v)) in enumerate (data_for_grid)
221+ v[" order" ] = i- 1
222+ end
223+ res = grid_create (Dict (" cols" => data_for_grid); filename= grid_fn, kwargs... )
224+ fid = res[" file" ][" fid" ]
225+
226+ uid_map = Dict ()
227+ for col in res[" file" ][" cols" ]
228+ uid_map[col[" name" ]] = col[" uid" ]
229+ end
128230 else
129- path = original_path
231+ error ( " Can only create or overwrite " )
130232 end
131- endpoint = URI (plot. url, path= " $path .json" )
132- response = get (endpoint, headers= options)
133- local_plot = JSON. parse (Plot, bytestring (response))
134- return PlotlyJS. SyncPlot (local_plot)
233+
234+ @label add_src_attrs
235+ # Add (attributename)(src) = uid fields to plot
236+ for k in keys (data_for_grid)
237+ key = temp_map[k]
238+ uid = uid_map[k]
239+ if key[1 ] == " trace"
240+ trace_ind = key[2 ]
241+ the_key = join (vcat (key[3 : end - 1 ], string (key[end ], " src" )), " _" )
242+ col_uid = string (fid, " :" , uid)
243+ p. data[trace_ind][the_key] = col_uid
244+ elseif key[1 ] == " layout"
245+ the_key = join (vcat (key[2 : end - 1 ], string (key[end ], " src" )), " _" )
246+ col_uid = string (fid, " :" , uid)
247+ p. layout[the_key] = col_uid
248+ else
249+ error (" bad key..." )
250+ end
251+ end
252+ p
135253end
136254
137- download_plot (url) = download ( RemotePlot (url))
138- download_plot (plot :: RemotePlot ) = download (plot )
255+ """
256+ srcify(p::Plot )
139257
140- immutable PlotlyError <: Exception
141- msg:: String
142- end
258+ Allocating version of `srcify!` the plot will be deepcopied before passing
259+ to `srcify!`, so the argument passed to this function will not be modified.
260+ """
261+ srcify (p:: Plot ; kwargs... ) = srcify! (deepcopy (p); kwargs... )
143262
144- function Base. show (io:: IO , err:: PlotlyError )
145- print (io, " Plotly error: $(err. msg) " )
146- end
263+ """
264+ Transport a plot from the Plotly cloud to a local `Plot` object.
147265
148- function parse_response (r)
149- body = Requests. json (r)
150- if statuscode (r) ≠ 200
151- throw (PlotlyError (" Non-sucessful status code: $(statuscode (r)) " ))
152- elseif " error" ∈ keys (body) && body[" error" ] ≠ " "
153- throw (PlotlyError (body[" error" ]))
154- elseif " detail" ∈ keys (body) && body[" detail" ] ≠ " "
155- throw (PlotlyError (body[" detail" ]))
156- else
157- body
158- end
266+ Must be signed in first if the plot is not public.
267+ """
268+ function Base. download (p:: RemotePlot )
269+ res = plot_content (fid (p), inline_data= true )
270+ data = GenericTrace[GenericTrace (tr) for tr in res[" data" ]]
271+ layout = Layout (res[" layout" ])
272+ plot (data, layout)
159273end
160274
275+ download_plot (url) = download (RemotePlot (url))
276+ download_plot (plot:: RemotePlot ) = download (plot)
277+
161278end
0 commit comments