1
- import plotly
2
- from plotly .api .v2 .utils import build_url
1
+ """
2
+ dashboard_objs
3
+ ==========
4
+
5
+ A module which is used to create dashboard objects, manipulate them and then
6
+ upload them.
7
+
8
+ """
3
9
10
+ import copy
4
11
import json
5
12
import requests
13
+ import pprint
6
14
import webbrowser
7
- from IPython import display
15
+
16
+ import plotly
17
+
18
+ from plotly import exceptions
19
+ from plotly .utils import node_generator
20
+ from plotly .api .v2 .utils import build_url
8
21
9
22
username = plotly .tools .get_credentials_file ()['username' ]
10
23
api_key = plotly .tools .get_credentials_file ()['api_key' ]
11
24
headers = {'Plotly-Client-Platform' : 'nteract' }
12
25
13
26
14
- # little wrapper around requests.get
15
- def get (* args , ** kwargs ):
16
- return requests .get (
17
- * args , auth = (username , api_key ), headers = headers , ** kwargs
18
- )
19
-
20
- width = 350
21
- height = 350
22
- dashboard_html = """
23
- <!DOCTYPE HTML>
24
- <html>
25
- <head>
26
- <style>
27
- body {{
28
- margin: 0px;
29
- padding: 0px;
30
- /}}
31
- </style>
32
- </head>
33
- <body>
34
- <canvas id="myCanvas" width="400" height="400"></canvas>
35
- <script>
36
- var canvas = document.getElementById('myCanvas');
37
- var context = canvas.getContext('2d');
38
- <!-- Dashboard -->
39
- context.beginPath();
40
- context.rect(0, 0, {width}, {height});
41
- context.lineWidth = 2;
42
- context.strokeStyle = 'black';
43
- context.stroke();
44
- <!-- Draw some lines in -->
45
- context.beginPath();
46
- context.rect(0, {height}/2, {width}/2, {height}/2);
47
- context.lineWidth = 2;
48
- context.strokeStyle = 'black';
49
- context.stroke();
50
- <!-- Draw the box_ids -->
51
- context.font = '12pt Times New Roman';
52
- context.textAlign = 'center';
53
- context.fillText('0', 50, 50);
54
- </script>
55
- </body>
56
- </html>
57
- """ .format (width = 350 , height = 350 )
58
-
59
- box_html = """
60
- <!-- Draw some lines in -->
61
- context.beginPath();
62
- context.rect(0, {height}/2, {width}/2, {height}/2);
63
- context.lineWidth = 2;
64
- context.strokeStyle = 'black';
65
- context.stroke();
66
- """ .format (width = 350 , height = 350 )
67
-
68
-
69
- class FirstEmptyBox (dict ):
70
- def __init__ (self ):
71
- self ['type' ] = 'box'
72
- self ['boxType' ] = 'empty'
73
-
74
-
75
27
class EmptyBox (dict ):
76
28
def __init__ (self ):
77
29
self ['type' ] = 'box'
78
30
self ['boxType' ] = 'empty'
79
- self ['fileId' ] = ''
80
- self ['shareKey' ] = None
81
- self ['title' ] = ''
82
31
83
32
84
33
class Box (dict ):
@@ -101,33 +50,60 @@ def __init__(self, box_1=EmptyBox(), box_2=EmptyBox(), size=400,
101
50
self ['second' ] = box_2
102
51
103
52
104
- box_ids_to_paths = {}
53
+ class Dashboard (dict ):
54
+ def __init__ (self , dashboard_json = None , backgroundColor = '#FFFFFF' ,
55
+ boxBackgroundColor = '#ffffff' , boxBorderColor = '#d8d8d8' ,
56
+ boxHeaderBackgroundColor = '#f8f8f8' , foregroundColor = '#333333' ,
57
+ headerBackgroundColor = '#2E3A46' , headerForegroundColor = '#FFFFFF' ,
58
+ links = [], logoUrl = '' , title = 'Untitled Dashboard' ):
59
+ self .box_ids_dict = {}
60
+ if not dashboard_json :
61
+ self ['layout' ] = EmptyBox ()
62
+ self ['version' ] = 2
63
+ self ['settings' ] = {
64
+ 'backgroundColor' : backgroundColor ,
65
+ 'boxBackgroundColor' : boxBackgroundColor ,
66
+ 'boxBorderColor' : boxBorderColor ,
67
+ 'boxHeaderBackgroundColor' : boxHeaderBackgroundColor ,
68
+ 'foregroundColor' : foregroundColor ,
69
+ 'headerBackgroundColor' : headerBackgroundColor ,
70
+ 'headerForegroundColor' : headerForegroundColor ,
71
+ 'links' : links ,
72
+ 'logoUrl' : logoUrl ,
73
+ 'title' : title
74
+ }
75
+ # TODO: change name to box_id_to_path
76
+ else :
77
+ self ['layout' ] = dashboard_json ['layout' ]
78
+ self ['version' ] = dashboard_json ['layout' ]
79
+ self ['settings' ] = dashboard_json ['settings' ]
105
80
81
+ all_nodes = []
82
+ node_gen = node_generator (dashboard_json ['layout' ])
106
83
107
- class Dashboard (dict ):
108
- def __init__ (self , backgroundColor = '#FFFFFF' , boxBackgroundColor = '#ffffff' ,
109
- boxBorderColor = '#d8d8d8' , boxHeaderBackgroundColor = '#f8f8f8' ,
110
- foregroundColor = '#333333' , headerBackgroundColor = '#2E3A46' ,
111
- headerForegroundColor = '#FFFFFF' , links = [], logoUrl = '' ,
112
- title = 'Untitled Dashboard' ):
113
- self ['version' ] = 2
114
- self ['settings' ] = {
115
- 'backgroundColor' : backgroundColor ,
116
- 'boxBackgroundColor' : boxBackgroundColor ,
117
- 'boxBorderColor' : boxBorderColor ,
118
- 'boxHeaderBackgroundColor' : boxHeaderBackgroundColor ,
119
- 'foregroundColor' : foregroundColor ,
120
- 'headerBackgroundColor' : headerBackgroundColor ,
121
- 'headerForegroundColor' : headerForegroundColor ,
122
- 'links' : links ,
123
- 'logoUrl' : logoUrl ,
124
- 'title' : title
125
- }
126
- self ['layout' ] = FirstEmptyBox ()
84
+ finished_iteration = False
85
+ while not finished_iteration :
86
+ try :
87
+ all_nodes .append (node_gen .next ())
88
+ except StopIteration :
89
+ finished_iteration = True
90
+
91
+ for node in all_nodes :
92
+ if (node [1 ] != () and node [0 ]['type' ] == 'box' and
93
+ node [0 ]['boxType' ] != 'empty' ):
94
+ try :
95
+ max_id = max (self .box_ids_dict .keys ())
96
+ except ValueError :
97
+ max_id = 0
98
+ self .box_ids_dict [max_id + 1 ] = list (node [1 ])
127
99
128
- def insert (self , box_or_container , array_of_paths ):
100
+ def _insert (self , box_or_container , array_of_paths ):
101
+ """Performs user-unfriendly box and container manipulations."""
129
102
if any (path not in ['first' , 'second' ] for path in array_of_paths ):
130
- return "Invalid path."
103
+ raise exceptions .PlotlyError (
104
+ "Invalid path. Your 'array_of_paths' list must only contain "
105
+ "the strings 'first' and 'second'."
106
+ )
131
107
132
108
if 'first' in self ['layout' ]:
133
109
loc_in_dashboard = self ['layout' ]
@@ -142,25 +118,132 @@ def insert(self, box_or_container, array_of_paths):
142
118
143
119
# update box_ids
144
120
if isinstance (box_or_container , Box ):
145
- max_id = len (box_ids_to_paths )
146
- box_ids_to_paths [max_id ] = array_of_paths
121
+ # box -> container
122
+ # if replacing a container, remove box_ids for
123
+ # the boxes that belong there
124
+ for first_or_second in ['first' , 'second' ]:
125
+ extended_box_path = copy .deepcopy (array_of_paths )
126
+ extended_box_path .append (first_or_second )
127
+ for key in self .box_ids_dict .keys ():
128
+ if self .box_ids_dict [key ] == extended_box_path :
129
+ self .box_ids_dict .pop (key )
130
+
131
+ # box -> box
132
+ for key in self .box_ids_dict .keys ():
133
+ if self .box_ids_dict [key ] == array_of_paths :
134
+ self .box_ids_dict .pop (key )
135
+ try :
136
+ max_id = max (self .box_ids_dict .keys ())
137
+ except ValueError :
138
+ max_id = 0
139
+ self .box_ids_dict [max_id + 1 ] = array_of_paths
140
+
141
+ elif isinstance (box_or_container , Container ):
142
+ # container -> box
143
+ for key in self .box_ids_dict .keys ():
144
+ if self .box_ids_dict [key ] == array_of_paths :
145
+ self .box_ids_dict .pop (key )
146
+
147
+ # handles boxes already in container
148
+ for first_or_second in ['first' , 'second' ]:
149
+ if box_or_container [first_or_second ] != EmptyBox ():
150
+ path_to_box = copy .deepcopy (array_of_paths )
151
+ path_to_box .append (first_or_second )
152
+ for key in self .box_ids_dict .keys ():
153
+ if self .box_ids_dict [key ] == path_to_box :
154
+ self .box_ids_dict .pop (key )
155
+
156
+ try :
157
+ max_id = max (self .box_ids_dict .keys ())
158
+ except ValueError :
159
+ max_id = 0
160
+ self .box_ids_dict [max_id + 1 ] = path_to_box
147
161
148
162
def _get_box (self , box_id ):
163
+ """Returns box from box_id number."""
164
+
149
165
loc_in_dashboard = self ['layout' ]
150
- for path in box_ids_to_paths [box_id ]:
166
+ for path in self . box_ids_dict [box_id ]:
151
167
loc_in_dashboard = loc_in_dashboard [path ]
152
168
return loc_in_dashboard
153
169
170
+ def get_preview (self ):
171
+ """
172
+ Returns JSON and HTML respresentation of the dashboard.
173
+
174
+ HTML coming soon to a theater near you.
175
+ """
176
+ # print JSON figure
177
+ pprint .pprint (self )
178
+
179
+ def insert (self , box , box_id = None , side = 'above' ):
180
+ """
181
+ The user-friendly method for inserting boxes into the Dashboard.
182
+
183
+ box: the box you are inserting into the dashboard.
184
+ box_id: pre-existing box you use as a reference point.
185
+ """
186
+ # doesn't need box_id or side specified
187
+ if 'first' not in self ['layout' ]:
188
+ self ._insert (Container (), [])
189
+ self ._insert (box , ['first' ])
190
+ else :
191
+ if box_id is None :
192
+ raise exceptions .PlotlyError (
193
+ "Make sure the box_id is specfied if there is at least "
194
+ "one box in your dashboard."
195
+ )
196
+ if box_id not in self .box_ids_dict :
197
+ raise exceptions .PlotlyError (
198
+ "Your box_id must a number which is pointing to a box in "
199
+ "your dashboard."
200
+ )
201
+
202
+ if side == 'above' :
203
+ old_box = self ._get_box (box_id )
204
+ self ._insert (
205
+ Container (box , old_box , direction = 'vertical' ),
206
+ self .box_ids_dict [box_id ]
207
+ )
208
+ elif side == 'below' :
209
+ old_box = self ._get_box (box_id )
210
+ self ._insert (
211
+ Container (old_box , box , direction = 'vertical' ),
212
+ self .box_ids_dict [box_id ]
213
+ )
214
+ elif side == 'left' :
215
+ old_box = self ._get_box (box_id )
216
+ self ._insert (
217
+ Container (box , old_box , direction = 'horizontal' ),
218
+ self .box_ids_dict [box_id ]
219
+ )
220
+ elif side == 'right' :
221
+ old_box = self ._get_box (box_id )
222
+ self ._insert (
223
+ Container (old_box , box , direction = 'horizontal' ),
224
+ self .box_ids_dict [box_id ]
225
+ )
154
226
155
- def create_dashboard (dashboard_object , filename , world_readable , auto_open = True ):
227
+
228
+ def upload_dashboard (dashboard_object , filename , world_readable ,
229
+ auto_open = True ):
156
230
"""
157
- BETA Function for creating a dashboard.
231
+ BETA function for uploading dashboards.
232
+
233
+ Functionality that we may need to consider adding:
234
+ - filename needs to be able to support `/` to create or use folders.
235
+ This'll require a few API calls.
236
+ - this function only works if the filename is unique. Need to call
237
+ `update` if this file already exists to overwrite the file.
238
+ - world_readable really should be `sharing` and allow `public`, `private`,
239
+ or `secret` like in `py.plot`.
240
+ - auto_open parameter for opening the result.
158
241
"""
159
242
res = requests .post (
160
243
build_url ('dashboards' ),
161
244
auth = (username , api_key ),
162
245
headers = headers ,
163
- data = {
246
+ data = {
164
247
'content' : json .dumps (dashboard_object ),
165
248
'filename' : filename ,
166
249
'world_readable' : world_readable
@@ -172,3 +255,48 @@ def create_dashboard(dashboard_object, filename, world_readable, auto_open=True)
172
255
url = res .json ()['web_url' ]
173
256
webbrowser .open_new (res .json ()['web_url' ])
174
257
return url
258
+
259
+
260
+ # little wrapper around requests.get
261
+ def get (* args , ** kwargs ):
262
+ return requests .get (
263
+ * args , auth = (username , api_key ), headers = headers , ** kwargs
264
+ )
265
+
266
+
267
+ def _get_all_dashboards ():
268
+ """Grab a list of all users' dashboards."""
269
+ dashboards = []
270
+ res = get (build_url ('dashboards' )).json ()
271
+
272
+ for dashboard in res ['results' ]:
273
+ if not dashboard ['deleted' ]:
274
+ dashboards .append (dashboard )
275
+ while res ['next' ]:
276
+ res = get (res ['next' ]).json ()
277
+
278
+ for dashboard in res ['results' ]:
279
+ if not dashboard ['deleted' ]:
280
+ dashboards .append (dashboard )
281
+ return dashboards
282
+
283
+
284
+ def _get_dashboard_json (dashboard_name ):
285
+ dashboards = _get_all_dashboards ()
286
+ for index , dboard in enumerate (dashboards ):
287
+ if dboard ['filename' ] == dashboard_name :
288
+ break
289
+
290
+ dashboard = get (dashboards [index ]['api_urls' ]['dashboards' ]).json ()
291
+ dashboard_json = json .loads (dashboard ['content' ])
292
+ return dashboard_json
293
+
294
+
295
+ def get_dashboard_names ():
296
+ dashboards = _get_all_dashboards ()
297
+ return [str (dboard ['filename' ]) for dboard in dashboards ]
298
+
299
+
300
+ def get_dashboard (dashboard_name ):
301
+ dashboard_json = _get_dashboard_json (dashboard_name )
302
+ return Dashboard (dashboard_json )
0 commit comments