forked from plotly/plotly.py
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathtools.py
3934 lines (3280 loc) · 148 KB
/
tools.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
# -*- coding: utf-8 -*-
"""
tools
=====
Functions that USERS will possibly want access to.
"""
from __future__ import absolute_import
from collections import OrderedDict
import warnings
import six
import math
from plotly import utils
from plotly import exceptions
from plotly import graph_reference
from plotly import session
from plotly.files import (CONFIG_FILE, CREDENTIALS_FILE, FILE_CONTENT,
GRAPH_REFERENCE_FILE, check_file_permissions)
# Warning format
def warning_on_one_line(message, category, filename, lineno,
file=None, line=None):
return '%s:%s: %s:\n\n%s\n\n' % (filename, lineno, category.__name__,
message)
warnings.formatwarning = warning_on_one_line
try:
from . import matplotlylib
_matplotlylib_imported = True
except ImportError:
_matplotlylib_imported = False
try:
import IPython
import IPython.core.display
_ipython_imported = True
except ImportError:
_ipython_imported = False
try:
import numpy as np
_numpy_imported = True
except ImportError:
_numpy_imported = False
try:
import pandas as pd
_pandas_imported = True
except ImportError:
_pandas_imported = False
try:
import scipy as scp
_scipy_imported = True
except ImportError:
_scipy_imported = False
try:
import scipy.spatial as scs
_scipy__spatial_imported = True
except ImportError:
_scipy__spatial_imported = False
try:
import scipy.cluster.hierarchy as sch
_scipy__cluster__hierarchy_imported = True
except ImportError:
_scipy__cluster__hierarchy_imported = False
try:
import scipy
import scipy.stats
_scipy_imported = True
except ImportError:
_scipy_imported = False
def get_config_defaults():
"""
Convenience function to check current settings against defaults.
Example:
if plotly_domain != get_config_defaults()['plotly_domain']:
# do something
"""
return dict(FILE_CONTENT[CONFIG_FILE]) # performs a shallow copy
def ensure_local_plotly_files():
"""Ensure that filesystem is setup/filled out in a valid way.
If the config or credential files aren't filled out, then write them
to the disk.
"""
if check_file_permissions():
for fn in [CREDENTIALS_FILE, CONFIG_FILE]:
utils.ensure_file_exists(fn)
contents = utils.load_json_dict(fn)
for key, val in list(FILE_CONTENT[fn].items()):
# TODO: removed type checking below, may want to revisit
if key not in contents:
contents[key] = val
contents_keys = list(contents.keys())
for key in contents_keys:
if key not in FILE_CONTENT[fn]:
del contents[key]
utils.save_json_dict(fn, contents)
# make a request to get graph reference if DNE.
utils.ensure_file_exists(GRAPH_REFERENCE_FILE)
utils.save_json_dict(GRAPH_REFERENCE_FILE,
graph_reference.GRAPH_REFERENCE)
else:
warnings.warn("Looks like you don't have 'read-write' permission to "
"your 'home' ('~') directory or to our '~/.plotly' "
"directory. That means plotly's python api can't setup "
"local configuration files. No problem though! You'll "
"just have to sign-in using 'plotly.plotly.sign_in()'. "
"For help with that: 'help(plotly.plotly.sign_in)'."
"\nQuestions? [email protected]")
### credentials tools ###
def set_credentials_file(username=None,
api_key=None,
stream_ids=None,
proxy_username=None,
proxy_password=None):
"""Set the keyword-value pairs in `~/.plotly_credentials`.
:param (str) username: The username you'd use to sign in to Plotly
:param (str) api_key: The api key associated with above username
:param (list) stream_ids: Stream tokens for above credentials
:param (str) proxy_username: The un associated with with your Proxy
:param (str) proxy_password: The pw associated with your Proxy un
"""
if not check_file_permissions():
raise exceptions.PlotlyError("You don't have proper file permissions "
"to run this function.")
ensure_local_plotly_files() # make sure what's there is OK
credentials = get_credentials_file()
if isinstance(username, six.string_types):
credentials['username'] = username
if isinstance(api_key, six.string_types):
credentials['api_key'] = api_key
if isinstance(proxy_username, six.string_types):
credentials['proxy_username'] = proxy_username
if isinstance(proxy_password, six.string_types):
credentials['proxy_password'] = proxy_password
if isinstance(stream_ids, (list, tuple)):
credentials['stream_ids'] = stream_ids
utils.save_json_dict(CREDENTIALS_FILE, credentials)
ensure_local_plotly_files() # make sure what we just put there is OK
def get_credentials_file(*args):
"""Return specified args from `~/.plotly_credentials`. as dict.
Returns all if no arguments are specified.
Example:
get_credentials_file('username')
"""
if check_file_permissions():
ensure_local_plotly_files() # make sure what's there is OK
return utils.load_json_dict(CREDENTIALS_FILE, *args)
else:
return FILE_CONTENT[CREDENTIALS_FILE]
def reset_credentials_file():
ensure_local_plotly_files() # make sure what's there is OK
utils.save_json_dict(CREDENTIALS_FILE, {})
ensure_local_plotly_files() # put the defaults back
### config tools ###
def set_config_file(plotly_domain=None,
plotly_streaming_domain=None,
plotly_api_domain=None,
plotly_ssl_verification=None,
plotly_proxy_authorization=None,
world_readable=None,
sharing=None,
auto_open=None):
"""Set the keyword-value pairs in `~/.plotly/.config`.
:param (str) plotly_domain: ex - https://plot.ly
:param (str) plotly_streaming_domain: ex - stream.plot.ly
:param (str) plotly_api_domain: ex - https://api.plot.ly
:param (bool) plotly_ssl_verification: True = verify, False = don't verify
:param (bool) plotly_proxy_authorization: True = use plotly proxy auth creds
:param (bool) world_readable: True = public, False = private
"""
if not check_file_permissions():
raise exceptions.PlotlyError("You don't have proper file permissions "
"to run this function.")
ensure_local_plotly_files() # make sure what's there is OK
utils.validate_world_readable_and_sharing_settings({
'sharing': sharing, 'world_readable': world_readable})
settings = get_config_file()
if isinstance(plotly_domain, six.string_types):
settings['plotly_domain'] = plotly_domain
elif plotly_domain is not None:
raise TypeError('plotly_domain should be a string')
if isinstance(plotly_streaming_domain, six.string_types):
settings['plotly_streaming_domain'] = plotly_streaming_domain
elif plotly_streaming_domain is not None:
raise TypeError('plotly_streaming_domain should be a string')
if isinstance(plotly_api_domain, six.string_types):
settings['plotly_api_domain'] = plotly_api_domain
elif plotly_api_domain is not None:
raise TypeError('plotly_api_domain should be a string')
if isinstance(plotly_ssl_verification, (six.string_types, bool)):
settings['plotly_ssl_verification'] = plotly_ssl_verification
elif plotly_ssl_verification is not None:
raise TypeError('plotly_ssl_verification should be a boolean')
if isinstance(plotly_proxy_authorization, (six.string_types, bool)):
settings['plotly_proxy_authorization'] = plotly_proxy_authorization
elif plotly_proxy_authorization is not None:
raise TypeError('plotly_proxy_authorization should be a boolean')
if isinstance(auto_open, bool):
settings['auto_open'] = auto_open
elif auto_open is not None:
raise TypeError('auto_open should be a boolean')
if isinstance(world_readable, bool):
settings['world_readable'] = world_readable
settings.pop('sharing')
elif world_readable is not None:
raise TypeError('Input should be a boolean')
if isinstance(sharing, six.string_types):
settings['sharing'] = sharing
elif sharing is not None:
raise TypeError('sharing should be a string')
utils.set_sharing_and_world_readable(settings)
utils.save_json_dict(CONFIG_FILE, settings)
ensure_local_plotly_files() # make sure what we just put there is OK
def get_config_file(*args):
"""Return specified args from `~/.plotly/.config`. as tuple.
Returns all if no arguments are specified.
Example:
get_config_file('plotly_domain')
"""
if check_file_permissions():
ensure_local_plotly_files() # make sure what's there is OK
return utils.load_json_dict(CONFIG_FILE, *args)
else:
return FILE_CONTENT[CONFIG_FILE]
def reset_config_file():
ensure_local_plotly_files() # make sure what's there is OK
f = open(CONFIG_FILE, 'w')
f.close()
ensure_local_plotly_files() # put the defaults back
### embed tools ###
def get_embed(file_owner_or_url, file_id=None, width="100%", height=525):
"""Returns HTML code to embed figure on a webpage as an <iframe>
Plotly uniquely identifies figures with a 'file_owner'/'file_id' pair.
Since each file is given a corresponding unique url, you may also simply
pass a valid plotly url as the first argument.
Note, if you're using a file_owner string as the first argument, you MUST
specify a `file_id` keyword argument. Else, if you're using a url string
as the first argument, you MUST NOT specify a `file_id` keyword argument,
or file_id must be set to Python's None value.
Positional arguments:
file_owner_or_url (string) -- a valid plotly username OR a valid plotly url
Keyword arguments:
file_id (default=None) -- an int or string that can be converted to int
if you're using a url, don't fill this in!
width (default="100%") -- an int or string corresp. to width of the figure
height (default="525") -- same as width but corresp. to the height of the
figure
"""
plotly_rest_url = (session.get_session_config().get('plotly_domain') or
get_config_file()['plotly_domain'])
if file_id is None: # assume we're using a url
url = file_owner_or_url
if url[:len(plotly_rest_url)] != plotly_rest_url:
raise exceptions.PlotlyError(
"Because you didn't supply a 'file_id' in the call, "
"we're assuming you're trying to snag a figure from a url. "
"You supplied the url, '{0}', we expected it to start with "
"'{1}'."
"\nRun help on this function for more information."
"".format(url, plotly_rest_url))
urlsplit = six.moves.urllib.parse.urlparse(url)
file_owner = urlsplit.path.split('/')[1].split('~')[1]
file_id = urlsplit.path.split('/')[2]
# to check for share_key we check urlsplit.query
query_dict = six.moves.urllib.parse.parse_qs(urlsplit.query)
if query_dict:
share_key = query_dict['share_key'][-1]
else:
share_key = ''
else:
file_owner = file_owner_or_url
share_key = ''
try:
test_if_int = int(file_id)
except ValueError:
raise exceptions.PlotlyError(
"The 'file_id' argument was not able to be converted into an "
"integer number. Make sure that the positional 'file_id' argument "
"is a number that can be converted into an integer or a string "
"that can be converted into an integer."
)
if int(file_id) < 0:
raise exceptions.PlotlyError(
"The 'file_id' argument must be a non-negative number."
)
if share_key is '':
s = ("<iframe id=\"igraph\" scrolling=\"no\" style=\"border:none;\" "
"seamless=\"seamless\" "
"src=\"{plotly_rest_url}/"
"~{file_owner}/{file_id}.embed\" "
"height=\"{iframe_height}\" width=\"{iframe_width}\">"
"</iframe>").format(
plotly_rest_url=plotly_rest_url,
file_owner=file_owner, file_id=file_id,
iframe_height=height, iframe_width=width)
else:
s = ("<iframe id=\"igraph\" scrolling=\"no\" style=\"border:none;\" "
"seamless=\"seamless\" "
"src=\"{plotly_rest_url}/"
"~{file_owner}/{file_id}.embed?share_key={share_key}\" "
"height=\"{iframe_height}\" width=\"{iframe_width}\">"
"</iframe>").format(
plotly_rest_url=plotly_rest_url,
file_owner=file_owner, file_id=file_id, share_key=share_key,
iframe_height=height, iframe_width=width)
return s
def embed(file_owner_or_url, file_id=None, width="100%", height=525):
"""Embeds existing Plotly figure in IPython Notebook
Plotly uniquely identifies figures with a 'file_owner'/'file_id' pair.
Since each file is given a corresponding unique url, you may also simply
pass a valid plotly url as the first argument.
Note, if you're using a file_owner string as the first argument, you MUST
specify a `file_id` keyword argument. Else, if you're using a url string
as the first argument, you MUST NOT specify a `file_id` keyword argument,
or file_id must be set to Python's None value.
Positional arguments:
file_owner_or_url (string) -- a valid plotly username OR a valid plotly url
Keyword arguments:
file_id (default=None) -- an int or string that can be converted to int
if you're using a url, don't fill this in!
width (default="100%") -- an int or string corresp. to width of the figure
height (default="525") -- same as width but corresp. to the height of the
figure
"""
try:
s = get_embed(file_owner_or_url, file_id=file_id, width=width,
height=height)
# see if we are in the SageMath Cloud
from sage_salvus import html
return html(s, hide=False)
except:
pass
if _ipython_imported:
if file_id:
plotly_domain = (
session.get_session_config().get('plotly_domain') or
get_config_file()['plotly_domain']
)
url = "{plotly_domain}/~{un}/{fid}".format(
plotly_domain=plotly_domain,
un=file_owner_or_url,
fid=file_id)
else:
url = file_owner_or_url
return PlotlyDisplay(url, width, height)
else:
if (get_config_defaults()['plotly_domain']
!= session.get_session_config()['plotly_domain']):
feedback_email = '[email protected]'
else:
# different domain likely means enterprise
feedback_email = '[email protected]'
warnings.warn(
"Looks like you're not using IPython or Sage to embed this "
"plot. If you just want the *embed code*,\ntry using "
"`get_embed()` instead."
'\nQuestions? {}'.format(feedback_email))
### mpl-related tools ###
@utils.template_doc(**get_config_file())
def mpl_to_plotly(fig, resize=False, strip_style=False, verbose=False):
"""Convert a matplotlib figure to plotly dictionary and send.
All available information about matplotlib visualizations are stored
within a matplotlib.figure.Figure object. You can create a plot in python
using matplotlib, store the figure object, and then pass this object to
the fig_to_plotly function. In the background, mplexporter is used to
crawl through the mpl figure object for appropriate information. This
information is then systematically sent to the PlotlyRenderer which
creates the JSON structure used to make plotly visualizations. Finally,
these dictionaries are sent to plotly and your browser should open up a
new tab for viewing! Optionally, if you're working in IPython, you can
set notebook=True and the PlotlyRenderer will call plotly.iplot instead
of plotly.plot to have the graph appear directly in the IPython notebook.
Note, this function gives the user access to a simple, one-line way to
render an mpl figure in plotly. If you need to trouble shoot, you can do
this step manually by NOT running this fuction and entereing the following:
===========================================================================
from mplexporter import Exporter
from mplexporter.renderers import PlotlyRenderer
# create an mpl figure and store it under a varialble 'fig'
renderer = PlotlyRenderer()
exporter = Exporter(renderer)
exporter.run(fig)
===========================================================================
You can then inspect the JSON structures by accessing these:
renderer.layout -- a plotly layout dictionary
renderer.data -- a list of plotly data dictionaries
Positional arguments:
fig -- a matplotlib figure object
username -- a valid plotly username **
api_key -- a valid api_key for the above username **
notebook -- an option for use with an IPython notebook
** Don't have a username/api_key? Try looking here:
{plotly_domain}/plot
** Forgot your api_key? Try signing in and looking here:
{plotly_domain}/python/getting-started
"""
if _matplotlylib_imported:
renderer = matplotlylib.PlotlyRenderer()
matplotlylib.Exporter(renderer).run(fig)
if resize:
renderer.resize()
if strip_style:
renderer.strip_style()
if verbose:
print(renderer.msg)
return renderer.plotly_fig
else:
warnings.warn(
"To use Plotly's matplotlylib functionality, you'll need to have "
"matplotlib successfully installed with all of its dependencies. "
"You're getting this error because matplotlib or one of its "
"dependencies doesn't seem to be installed correctly.")
### graph_objs related tools ###
def get_subplots(rows=1, columns=1, print_grid=False, **kwargs):
"""Return a dictionary instance with the subplots set in 'layout'.
Example 1:
# stack two subplots vertically
fig = tools.get_subplots(rows=2)
fig['data'] += [Scatter(x=[1,2,3], y=[2,1,2], xaxis='x1', yaxis='y1')]
fig['data'] += [Scatter(x=[1,2,3], y=[2,1,2], xaxis='x2', yaxis='y2')]
Example 2:
# print out string showing the subplot grid you've put in the layout
fig = tools.get_subplots(rows=3, columns=2, print_grid=True)
Keywords arguments with constant defaults:
rows (kwarg, int greater than 0, default=1):
Number of rows, evenly spaced vertically on the figure.
columns (kwarg, int greater than 0, default=1):
Number of columns, evenly spaced horizontally on the figure.
horizontal_spacing (kwarg, float in [0,1], default=0.1):
Space between subplot columns. Applied to all columns.
vertical_spacing (kwarg, float in [0,1], default=0.05):
Space between subplot rows. Applied to all rows.
print_grid (kwarg, True | False, default=False):
If True, prints a tab-delimited string representation
of your plot grid.
Keyword arguments with variable defaults:
horizontal_spacing (kwarg, float in [0,1], default=0.2 / columns):
Space between subplot columns.
vertical_spacing (kwarg, float in [0,1], default=0.3 / rows):
Space between subplot rows.
"""
# TODO: protected until #282
from plotly.graph_objs import graph_objs
warnings.warn(
"tools.get_subplots is depreciated. "
"Please use tools.make_subplots instead."
)
# Throw exception for non-integer rows and columns
if not isinstance(rows, int) or rows <= 0:
raise Exception("Keyword argument 'rows' "
"must be an int greater than 0")
if not isinstance(columns, int) or columns <= 0:
raise Exception("Keyword argument 'columns' "
"must be an int greater than 0")
# Throw exception if non-valid kwarg is sent
VALID_KWARGS = ['horizontal_spacing', 'vertical_spacing']
for key in kwargs.keys():
if key not in VALID_KWARGS:
raise Exception("Invalid keyword argument: '{0}'".format(key))
# Set 'horizontal_spacing' / 'vertical_spacing' w.r.t. rows / columns
try:
horizontal_spacing = float(kwargs['horizontal_spacing'])
except KeyError:
horizontal_spacing = 0.2 / columns
try:
vertical_spacing = float(kwargs['vertical_spacing'])
except KeyError:
vertical_spacing = 0.3 / rows
fig = dict(layout=graph_objs.Layout()) # will return this at the end
plot_width = (1 - horizontal_spacing * (columns - 1)) / columns
plot_height = (1 - vertical_spacing * (rows - 1)) / rows
plot_num = 0
for rrr in range(rows):
for ccc in range(columns):
xaxis_name = 'xaxis{0}'.format(plot_num + 1)
x_anchor = 'y{0}'.format(plot_num + 1)
x_start = (plot_width + horizontal_spacing) * ccc
x_end = x_start + plot_width
yaxis_name = 'yaxis{0}'.format(plot_num + 1)
y_anchor = 'x{0}'.format(plot_num + 1)
y_start = (plot_height + vertical_spacing) * rrr
y_end = y_start + plot_height
xaxis = graph_objs.XAxis(domain=[x_start, x_end], anchor=x_anchor)
fig['layout'][xaxis_name] = xaxis
yaxis = graph_objs.YAxis(domain=[y_start, y_end], anchor=y_anchor)
fig['layout'][yaxis_name] = yaxis
plot_num += 1
if print_grid:
print("This is the format of your plot grid!")
grid_string = ""
plot = 1
for rrr in range(rows):
grid_line = ""
for ccc in range(columns):
grid_line += "[{0}]\t".format(plot)
plot += 1
grid_string = grid_line + '\n' + grid_string
print(grid_string)
return graph_objs.Figure(fig) # forces us to validate what we just did...
def make_subplots(rows=1, cols=1,
shared_xaxes=False, shared_yaxes=False,
start_cell='top-left', print_grid=True,
**kwargs):
"""Return an instance of plotly.graph_objs.Figure
with the subplots domain set in 'layout'.
Example 1:
# stack two subplots vertically
fig = tools.make_subplots(rows=2)
This is the format of your plot grid:
[ (1,1) x1,y1 ]
[ (2,1) x2,y2 ]
fig['data'] += [Scatter(x=[1,2,3], y=[2,1,2])]
fig['data'] += [Scatter(x=[1,2,3], y=[2,1,2], xaxis='x2', yaxis='y2')]
# or see Figure.append_trace
Example 2:
# subplots with shared x axes
fig = tools.make_subplots(rows=2, shared_xaxes=True)
This is the format of your plot grid:
[ (1,1) x1,y1 ]
[ (2,1) x1,y2 ]
fig['data'] += [Scatter(x=[1,2,3], y=[2,1,2])]
fig['data'] += [Scatter(x=[1,2,3], y=[2,1,2], yaxis='y2')]
Example 3:
# irregular subplot layout (more examples below under 'specs')
fig = tools.make_subplots(rows=2, cols=2,
specs=[[{}, {}],
[{'colspan': 2}, None]])
This is the format of your plot grid!
[ (1,1) x1,y1 ] [ (1,2) x2,y2 ]
[ (2,1) x3,y3 - ]
fig['data'] += [Scatter(x=[1,2,3], y=[2,1,2])]
fig['data'] += [Scatter(x=[1,2,3], y=[2,1,2], xaxis='x2', yaxis='y2')]
fig['data'] += [Scatter(x=[1,2,3], y=[2,1,2], xaxis='x3', yaxis='y3')]
Example 4:
# insets
fig = tools.make_subplots(insets=[{'cell': (1,1), 'l': 0.7, 'b': 0.3}])
This is the format of your plot grid!
[ (1,1) x1,y1 ]
With insets:
[ x2,y2 ] over [ (1,1) x1,y1 ]
fig['data'] += [Scatter(x=[1,2,3], y=[2,1,2])]
fig['data'] += [Scatter(x=[1,2,3], y=[2,1,2], xaxis='x2', yaxis='y2')]
Example 5:
# include subplot titles
fig = tools.make_subplots(rows=2, subplot_titles=('Plot 1','Plot 2'))
This is the format of your plot grid:
[ (1,1) x1,y1 ]
[ (2,1) x2,y2 ]
fig['data'] += [Scatter(x=[1,2,3], y=[2,1,2])]
fig['data'] += [Scatter(x=[1,2,3], y=[2,1,2], xaxis='x2', yaxis='y2')]
Example 6:
# Include subplot title on one plot (but not all)
fig = tools.make_subplots(insets=[{'cell': (1,1), 'l': 0.7, 'b': 0.3}],
subplot_titles=('','Inset'))
This is the format of your plot grid!
[ (1,1) x1,y1 ]
With insets:
[ x2,y2 ] over [ (1,1) x1,y1 ]
fig['data'] += [Scatter(x=[1,2,3], y=[2,1,2])]
fig['data'] += [Scatter(x=[1,2,3], y=[2,1,2], xaxis='x2', yaxis='y2')]
Keywords arguments with constant defaults:
rows (kwarg, int greater than 0, default=1):
Number of rows in the subplot grid.
cols (kwarg, int greater than 0, default=1):
Number of columns in the subplot grid.
shared_xaxes (kwarg, boolean or list, default=False)
Assign shared x axes.
If True, subplots in the same grid column have one common
shared x-axis at the bottom of the gird.
To assign shared x axes per subplot grid cell (see 'specs'),
send list (or list of lists, one list per shared x axis)
of cell index tuples.
shared_yaxes (kwarg, boolean or list, default=False)
Assign shared y axes.
If True, subplots in the same grid row have one common
shared y-axis on the left-hand side of the gird.
To assign shared y axes per subplot grid cell (see 'specs'),
send list (or list of lists, one list per shared y axis)
of cell index tuples.
start_cell (kwarg, 'bottom-left' or 'top-left', default='top-left')
Choose the starting cell in the subplot grid used to set the
domains of the subplots.
print_grid (kwarg, boolean, default=True):
If True, prints a tab-delimited string representation of
your plot grid.
Keyword arguments with variable defaults:
horizontal_spacing (kwarg, float in [0,1], default=0.2 / cols):
Space between subplot columns.
Applies to all columns (use 'specs' subplot-dependents spacing)
vertical_spacing (kwarg, float in [0,1], default=0.3 / rows):
Space between subplot rows.
Applies to all rows (use 'specs' subplot-dependents spacing)
subplot_titles (kwarg, list of strings, default=empty list):
Title of each subplot.
"" can be included in the list if no subplot title is desired in
that space so that the titles are properly indexed.
specs (kwarg, list of lists of dictionaries):
Subplot specifications.
ex1: specs=[[{}, {}], [{'colspan': 2}, None]]
ex2: specs=[[{'rowspan': 2}, {}], [None, {}]]
- Indices of the outer list correspond to subplot grid rows
starting from the bottom. The number of rows in 'specs'
must be equal to 'rows'.
- Indices of the inner lists correspond to subplot grid columns
starting from the left. The number of columns in 'specs'
must be equal to 'cols'.
- Each item in the 'specs' list corresponds to one subplot
in a subplot grid. (N.B. The subplot grid has exactly 'rows'
times 'cols' cells.)
- Use None for blank a subplot cell (or to move pass a col/row span).
- Note that specs[0][0] has the specs of the 'start_cell' subplot.
- Each item in 'specs' is a dictionary.
The available keys are:
* is_3d (boolean, default=False): flag for 3d scenes
* colspan (int, default=1): number of subplot columns
for this subplot to span.
* rowspan (int, default=1): number of subplot rows
for this subplot to span.
* l (float, default=0.0): padding left of cell
* r (float, default=0.0): padding right of cell
* t (float, default=0.0): padding right of cell
* b (float, default=0.0): padding bottom of cell
- Use 'horizontal_spacing' and 'vertical_spacing' to adjust
the spacing in between the subplots.
insets (kwarg, list of dictionaries):
Inset specifications.
- Each item in 'insets' is a dictionary.
The available keys are:
* cell (tuple, default=(1,1)): (row, col) index of the
subplot cell to overlay inset axes onto.
* is_3d (boolean, default=False): flag for 3d scenes
* l (float, default=0.0): padding left of inset
in fraction of cell width
* w (float or 'to_end', default='to_end') inset width
in fraction of cell width ('to_end': to cell right edge)
* b (float, default=0.0): padding bottom of inset
in fraction of cell height
* h (float or 'to_end', default='to_end') inset height
in fraction of cell height ('to_end': to cell top edge)
"""
# TODO: protected until #282
from plotly.graph_objs import graph_objs
# Throw exception for non-integer rows and cols
if not isinstance(rows, int) or rows <= 0:
raise Exception("Keyword argument 'rows' "
"must be an int greater than 0")
if not isinstance(cols, int) or cols <= 0:
raise Exception("Keyword argument 'cols' "
"must be an int greater than 0")
# Dictionary of things start_cell
START_CELL_all = {
'bottom-left': {
# 'natural' setup where x & y domains increase monotonically
'col_dir': 1,
'row_dir': 1
},
'top-left': {
# 'default' setup visually matching the 'specs' list of lists
'col_dir': 1,
'row_dir': -1
}
# TODO maybe add 'bottom-right' and 'top-right'
}
# Throw exception for invalid 'start_cell' values
try:
START_CELL = START_CELL_all[start_cell]
except KeyError:
raise Exception("Invalid 'start_cell' value")
# Throw exception if non-valid kwarg is sent
VALID_KWARGS = ['horizontal_spacing', 'vertical_spacing',
'specs', 'insets', 'subplot_titles']
for key in kwargs.keys():
if key not in VALID_KWARGS:
raise Exception("Invalid keyword argument: '{0}'".format(key))
# Set 'subplot_titles'
subplot_titles = kwargs.get('subplot_titles', [""] * rows * cols)
# Set 'horizontal_spacing' / 'vertical_spacing' w.r.t. rows / cols
try:
horizontal_spacing = float(kwargs['horizontal_spacing'])
except KeyError:
horizontal_spacing = 0.2 / cols
try:
vertical_spacing = float(kwargs['vertical_spacing'])
except KeyError:
if 'subplot_titles' in kwargs:
vertical_spacing = 0.5 / rows
else:
vertical_spacing = 0.3 / rows
# Sanitize 'specs' (must be a list of lists)
exception_msg = "Keyword argument 'specs' must be a list of lists"
try:
specs = kwargs['specs']
if not isinstance(specs, list):
raise Exception(exception_msg)
else:
for spec_row in specs:
if not isinstance(spec_row, list):
raise Exception(exception_msg)
except KeyError:
specs = [[{}
for c in range(cols)]
for r in range(rows)] # default 'specs'
# Throw exception if specs is over or under specified
if len(specs) != rows:
raise Exception("The number of rows in 'specs' "
"must be equal to 'rows'")
for r, spec_row in enumerate(specs):
if len(spec_row) != cols:
raise Exception("The number of columns in 'specs' "
"must be equal to 'cols'")
# Sanitize 'insets'
try:
insets = kwargs['insets']
if not isinstance(insets, list):
raise Exception("Keyword argument 'insets' must be a list")
except KeyError:
insets = False
# Throw exception if non-valid key / fill in defaults
def _check_keys_and_fill(name, arg, defaults):
def _checks(item, defaults):
if item is None:
return
if not isinstance(item, dict):
raise Exception("Items in keyword argument '{name}' must be "
"dictionaries or None".format(name=name))
for k in item.keys():
if k not in defaults.keys():
raise Exception("Invalid key '{k}' in keyword "
"argument '{name}'".format(k=k, name=name))
for k in defaults.keys():
if k not in item.keys():
item[k] = defaults[k]
for arg_i in arg:
if isinstance(arg_i, list):
for arg_ii in arg_i:
_checks(arg_ii, defaults)
elif isinstance(arg_i, dict):
_checks(arg_i, defaults)
# Default spec key-values
SPEC_defaults = dict(
is_3d=False,
colspan=1,
rowspan=1,
l=0.0,
r=0.0,
b=0.0,
t=0.0
# TODO add support for 'w' and 'h'
)
_check_keys_and_fill('specs', specs, SPEC_defaults)
# Default inset key-values
if insets:
INSET_defaults = dict(
cell=(1, 1),
is_3d=False,
l=0.0,
w='to_end',
b=0.0,
h='to_end'
)
_check_keys_and_fill('insets', insets, INSET_defaults)
# Set width & height of each subplot cell (excluding padding)
width = (1. - horizontal_spacing * (cols - 1)) / cols
height = (1. - vertical_spacing * (rows - 1)) / rows
# Built row/col sequence using 'row_dir' and 'col_dir'
COL_DIR = START_CELL['col_dir']
ROW_DIR = START_CELL['row_dir']
col_seq = range(cols)[::COL_DIR]
row_seq = range(rows)[::ROW_DIR]
# [grid] Build subplot grid (coord tuple of cell)
grid = [[((width + horizontal_spacing) * c,
(height + vertical_spacing) * r)
for c in col_seq]
for r in row_seq]
# [grid_ref] Initialize the grid and insets' axis-reference lists
grid_ref = [[None for c in range(cols)] for r in range(rows)]
insets_ref = [None for inset in range(len(insets))] if insets else None
layout = graph_objs.Layout() # init layout object
# Function handling logic around 2d axis labels
# Returns 'x{}' | 'y{}'
def _get_label(x_or_y, r, c, cnt, shared_axes):
# Default label (given strictly by cnt)
label = "{x_or_y}{cnt}".format(x_or_y=x_or_y, cnt=cnt)
if isinstance(shared_axes, bool):
if shared_axes:
if x_or_y == 'x':
label = "{x_or_y}{c}".format(x_or_y=x_or_y, c=c + 1)
if x_or_y == 'y':
label = "{x_or_y}{r}".format(x_or_y=x_or_y, r=r + 1)
if isinstance(shared_axes, list):
if isinstance(shared_axes[0], tuple):
shared_axes = [shared_axes] # TODO put this elsewhere
for shared_axis in shared_axes:
if (r + 1, c + 1) in shared_axis:
label = {
'x': "x{0}".format(shared_axis[0][1]),
'y': "y{0}".format(shared_axis[0][0])
}[x_or_y]
return label
# Row in grid of anchor row if shared_xaxes=True
ANCHOR_ROW = 0 if ROW_DIR > 0 else rows - 1
# Function handling logic around 2d axis anchors
# Return 'x{}' | 'y{}' | 'free' | False
def _get_anchors(r, c, x_cnt, y_cnt, shared_xaxes, shared_yaxes):
# Default anchors (give strictly by cnt)
x_anchor = "y{y_cnt}".format(y_cnt=y_cnt)
y_anchor = "x{x_cnt}".format(x_cnt=x_cnt)
if isinstance(shared_xaxes, bool):
if shared_xaxes:
if r != ANCHOR_ROW:
x_anchor = False
y_anchor = 'free'
if shared_yaxes and c != 0: # TODO covers all cases?
y_anchor = False
return x_anchor, y_anchor
elif isinstance(shared_xaxes, list):
if isinstance(shared_xaxes[0], tuple):
shared_xaxes = [shared_xaxes] # TODO put this elsewhere
for shared_xaxis in shared_xaxes:
if (r + 1, c + 1) in shared_xaxis[1:]:
x_anchor = False