27
27
28
28
29
29
# Named tuple to hold an xaxis/yaxis pair that represent a single subplot
30
- SubplotXY = collections .namedtuple ('SubplotXY' , ('xaxis' , 'yaxis' ))
30
+ SubplotXY = collections .namedtuple ('SubplotXY' ,
31
+ ('xaxis' , 'yaxis' , 'secondary_yaxis' ))
31
32
SubplotDomain = collections .namedtuple ('SubplotDomain' , ('x' , 'y' ))
32
33
33
34
@@ -155,6 +156,9 @@ def make_subplots(
155
156
- trace type: A trace type which will be used to determine
156
157
the appropriate subplot type for that trace
157
158
159
+ * secondary_y (bool, default False): If True, create a secondary
160
+ y-axis positioned on the right side of the subplot. Only valid
161
+ if type='xy'.
158
162
* colspan (int, default 1): number of subplot columns
159
163
for this subplot to span.
160
164
* rowspan (int, default 1): number of subplot rows
@@ -461,14 +465,16 @@ def _checks(item, defaults):
461
465
rows = rows , cols = cols , typ = type (specs ), val = repr (specs )
462
466
))
463
467
464
- # For backward compatibility, convert is_3d flag to type='scene' kwarg
465
468
for row in specs :
466
469
for spec in row :
470
+ # For backward compatibility,
471
+ # convert is_3d flag to type='scene' kwarg
467
472
if spec and spec .pop ('is_3d' , None ):
468
473
spec ['type' ] = 'scene'
469
474
470
475
spec_defaults = dict (
471
476
type = 'xy' ,
477
+ secondary_y = False ,
472
478
colspan = 1 ,
473
479
rowspan = 1 ,
474
480
l = 0.0 ,
@@ -478,6 +484,15 @@ def _checks(item, defaults):
478
484
)
479
485
_check_keys_and_fill ('specs' , specs , spec_defaults )
480
486
487
+ # Validate secondary_y
488
+ for row in specs :
489
+ for spec in row :
490
+ if spec and spec ['type' ] != 'xy' and spec ['secondary_y' ]:
491
+ raise ValueError ("""
492
+ The 'secondary_y' spec property is not supported for subplot of type '{s_typ}'
493
+ 'secondary_y' is only supported for subplots of type 'xy'
494
+ """ .format (s_typ = spec ['type' ]))
495
+
481
496
# ### insets ###
482
497
if insets is None or insets is False :
483
498
insets = []
@@ -612,8 +627,10 @@ def _checks(item, defaults):
612
627
613
628
# ### construct subplot container ###
614
629
subplot_type = spec ['type' ]
630
+ secondary_y = spec ['secondary_y' ]
615
631
grid_ref_element = _init_subplot (
616
- layout , subplot_type , x_domain , y_domain , max_subplot_ids )
632
+ layout , subplot_type , secondary_y ,
633
+ x_domain , y_domain , max_subplot_ids )
617
634
grid_ref [r ][c ] = grid_ref_element
618
635
619
636
_configure_shared_axes (layout , grid_ref , specs , 'x' , shared_xaxes , row_dir )
@@ -842,7 +859,7 @@ def update_axis_matches(first_axis_id, ref, spec, remove_label):
842
859
843
860
844
861
def _init_subplot_xy (
845
- layout , x_domain , y_domain , max_subplot_ids = None
862
+ layout , secondary_y , x_domain , y_domain , max_subplot_ids = None
846
863
):
847
864
if max_subplot_ids is None :
848
865
max_subplot_ids = _get_initial_max_subplot_ids ()
@@ -873,6 +890,21 @@ def _init_subplot_xy(
873
890
'trace_kwargs' : {'xaxis' : x_label , 'yaxis' : y_label }
874
891
}
875
892
893
+ if secondary_y :
894
+ y_cnt += 1
895
+ secondary_y_label = "y{cnt}" .format (cnt = y_cnt )
896
+ ref_element ['secondary_trace_kwargs' ] = {
897
+ 'xaxis' : x_label , 'yaxis' : secondary_y_label
898
+ }
899
+
900
+ secondary_yaxis_name = 'yaxis{cnt}' .format (
901
+ cnt = y_cnt if y_cnt > 1 else '' )
902
+ ref_element ['secondary_layout_keys' ] = (
903
+ xaxis_name , secondary_yaxis_name )
904
+
905
+ secondary_y_axis = {'overlaying' : y_label , 'side' : 'right' }
906
+ layout [secondary_yaxis_name ] = secondary_y_axis
907
+
876
908
# increment max_subplot_ids
877
909
max_subplot_ids ['xaxis' ] = x_cnt
878
910
max_subplot_ids ['yaxis' ] = y_cnt
@@ -966,7 +998,8 @@ def _validate_coerce_subplot_type(subplot_type):
966
998
967
999
968
1000
def _init_subplot (
969
- layout , subplot_type , x_domain , y_domain , max_subplot_ids = None
1001
+ layout , subplot_type , secondary_y ,
1002
+ x_domain , y_domain , max_subplot_ids = None
970
1003
):
971
1004
# Normalize subplot type
972
1005
subplot_type = _validate_coerce_subplot_type (subplot_type )
@@ -982,7 +1015,7 @@ def _init_subplot(
982
1015
983
1016
if subplot_type == 'xy' :
984
1017
ref_element = _init_subplot_xy (
985
- layout , x_domain , y_domain , max_subplot_ids
1018
+ layout , secondary_y , x_domain , y_domain , max_subplot_ids
986
1019
)
987
1020
elif subplot_type in _single_subplot_types :
988
1021
ref_element = _init_subplot_single (
@@ -1217,7 +1250,7 @@ def _pad(s, cell_len=cell_len):
1217
1250
return grid_str
1218
1251
1219
1252
1220
- def _set_trace_grid_reference (trace , layout , grid_ref , row , col ):
1253
+ def _set_trace_grid_reference (trace , grid_ref , row , col , secondary_y ):
1221
1254
if row <= 0 :
1222
1255
raise Exception ("Row value is out of range. "
1223
1256
"Note: the starting cell is (1, 1)" )
@@ -1238,7 +1271,18 @@ def _set_trace_grid_reference(trace, layout, grid_ref, row, col):
1238
1271
col = col
1239
1272
))
1240
1273
1241
- for k in ref ['trace_kwargs' ]:
1274
+ if secondary_y :
1275
+ if 'secondary_trace_kwargs' not in ref :
1276
+ raise ValueError ("""
1277
+ Subplot with type '{subplot_type}' at grid position ({row}, {col}) was not
1278
+ created with the secondary_y spec property set to True. See the docstring
1279
+ for the specs argument to plotly.subplots.make_subplots for more information.
1280
+ """ )
1281
+ trace_kwargs = ref ['secondary_trace_kwargs' ]
1282
+ else :
1283
+ trace_kwargs = ref ['trace_kwargs' ]
1284
+
1285
+ for k in trace_kwargs :
1242
1286
if k not in trace :
1243
1287
raise ValueError ("""\
1244
1288
Trace type '{typ}' is not compatible with subplot type '{subplot_type}'
@@ -1311,13 +1355,21 @@ def _get_grid_subplot(fig, row, col):
1311
1355
elif len (layout_keys ) == 2 :
1312
1356
return SubplotXY (
1313
1357
xaxis = fig .layout [layout_keys [0 ]],
1314
- yaxis = fig .layout [layout_keys [1 ]])
1358
+ yaxis = fig .layout [layout_keys [1 ]],
1359
+ secondary_yaxis = None
1360
+ )
1361
+ elif len (layout_keys ) == 3 :
1362
+ return SubplotXY (
1363
+ xaxis = fig .layout [layout_keys [0 ]],
1364
+ yaxis = fig .layout [layout_keys [1 ]],
1365
+ secondary_yaxis = fig .layout [layout_keys [2 ]],
1366
+ )
1315
1367
else :
1316
1368
raise ValueError ("""
1317
1369
Unexpected subplot type with layout_keys of {}""" .format (layout_keys ))
1318
1370
1319
1371
1320
- def _get_subplot_ref_for_trace (trace ):
1372
+ def _get_subplot_ref_for_trace (trace , secondary_to_primary_y_axes ):
1321
1373
1322
1374
if 'domain' in trace :
1323
1375
return {
@@ -1331,11 +1383,29 @@ def _get_subplot_ref_for_trace(trace):
1331
1383
xaxis_name = 'xaxis' + trace .xaxis [1 :] if trace .xaxis else 'xaxis'
1332
1384
yaxis_name = 'yaxis' + trace .yaxis [1 :] if trace .yaxis else 'yaxis'
1333
1385
1334
- return {
1335
- 'subplot_type' : 'xy' ,
1336
- 'layout_keys' : (xaxis_name , yaxis_name ),
1337
- 'trace_kwargs' : {'xaxis' : trace .xaxis , 'yaxis' : trace .yaxis }
1338
- }
1386
+ # Check whether this yaxis is secondary yaxis
1387
+ if yaxis_name in secondary_to_primary_y_axes :
1388
+ secondary_yaxis_name = yaxis_name
1389
+ yaxis_name = secondary_to_primary_y_axes [secondary_yaxis_name ]
1390
+ y_label = 'y' + yaxis_name [5 :]
1391
+ secondary_y_label = 'y' + secondary_yaxis_name [5 :]
1392
+ return {
1393
+ 'subplot_type' : 'xy' ,
1394
+ 'layout_keys' : (xaxis_name , yaxis_name ),
1395
+ 'trace_kwargs' : {
1396
+ 'xaxis' : trace .xaxis , 'yaxis' : y_label ,
1397
+ },
1398
+ 'secondary_trace_kwargs' : {
1399
+ 'xaxis' : trace .xaxis , 'yaxis' : secondary_y_label
1400
+ },
1401
+ 'secondary_layout_keys' : (xaxis_name , secondary_yaxis_name ),
1402
+ }
1403
+ else :
1404
+ return {
1405
+ 'subplot_type' : 'xy' ,
1406
+ 'layout_keys' : (xaxis_name , yaxis_name ),
1407
+ 'trace_kwargs' : {'xaxis' : trace .xaxis , 'yaxis' : trace .yaxis }
1408
+ }
1339
1409
elif 'geo' in trace :
1340
1410
return {
1341
1411
'subplot_type' : 'geo' ,
0 commit comments