Skip to content

Commit 549a34a

Browse files
committed
secondary_y WIP
1 parent 6a85e4b commit 549a34a

File tree

2 files changed

+103
-20
lines changed

2 files changed

+103
-20
lines changed

plotly/basedatatypes.py

+18-5
Original file line numberDiff line numberDiff line change
@@ -796,23 +796,32 @@ def update_traces(
796796
return self
797797

798798
def _select_layout_subplots_by_prefix(
799-
self, prefix, selector=None, row=None, col=None):
799+
self, prefix, selector=None, row=None, col=None, secondary_y=None):
800800
"""
801801
Helper called by code generated select_* methods
802802
"""
803803

804804
if row is not None or col is not None:
805805
# Build mapping from container keys ('xaxis2', 'scene4', etc.)
806-
# to row/col pairs
806+
# to (row, col, secondary_y triplets)
807807
grid_ref = self._validate_get_grid_ref()
808808
container_to_row_col = {}
809809
for r, subplot_row in enumerate(grid_ref):
810810
for c, ref in enumerate(subplot_row):
811811
if ref is None:
812812
continue
813+
814+
# collect primary keys
813815
for layout_key in ref['layout_keys']:
814816
if layout_key.startswith(prefix):
815-
container_to_row_col[layout_key] = r + 1, c + 1
817+
container_to_row_col[layout_key] = (
818+
r + 1, c + 1, False)
819+
820+
# collection secondary keys
821+
for layout_key in ref.get('secondary_layout_keys', ()):
822+
if layout_key.startswith(prefix):
823+
container_to_row_col[layout_key] = (
824+
r + 1, c + 1, True)
816825
else:
817826
container_to_row_col = None
818827

@@ -831,6 +840,10 @@ def _select_layout_subplots_by_prefix(
831840
container_to_row_col.get(k, (None, None))[1] != col):
832841
# col specified and this is not a match
833842
continue
843+
elif (secondary_y is not None and
844+
container_to_row_col.get(
845+
k, (None, None, None))[2] != secondary_y):
846+
continue
834847

835848
# Filter by selector
836849
if not self._selector_matches(self.layout[k], selector):
@@ -1494,13 +1507,13 @@ def append_trace(self, trace, row, col):
14941507

14951508
self.add_trace(trace=trace, row=row, col=col)
14961509

1497-
def _set_trace_grid_position(self, trace, row, col):
1510+
def _set_trace_grid_position(self, trace, row, col, secondary_y=False):
14981511
grid_ref = self._validate_get_grid_ref()
14991512

15001513
from _plotly_future_ import _future_flags
15011514
if 'v4_subplots' in _future_flags:
15021515
return _set_trace_grid_reference(
1503-
trace, self.layout, grid_ref, row, col)
1516+
trace, grid_ref, row, col, secondary_y)
15041517

15051518
if row <= 0:
15061519
raise Exception("Row value is out of range. "

plotly/subplots.py

+85-15
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,8 @@
2727

2828

2929
# 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'))
3132
SubplotDomain = collections.namedtuple('SubplotDomain', ('x', 'y'))
3233

3334

@@ -155,6 +156,9 @@ def make_subplots(
155156
- trace type: A trace type which will be used to determine
156157
the appropriate subplot type for that trace
157158
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'.
158162
* colspan (int, default 1): number of subplot columns
159163
for this subplot to span.
160164
* rowspan (int, default 1): number of subplot rows
@@ -461,14 +465,16 @@ def _checks(item, defaults):
461465
rows=rows, cols=cols, typ=type(specs), val=repr(specs)
462466
))
463467

464-
# For backward compatibility, convert is_3d flag to type='scene' kwarg
465468
for row in specs:
466469
for spec in row:
470+
# For backward compatibility,
471+
# convert is_3d flag to type='scene' kwarg
467472
if spec and spec.pop('is_3d', None):
468473
spec['type'] = 'scene'
469474

470475
spec_defaults = dict(
471476
type='xy',
477+
secondary_y=False,
472478
colspan=1,
473479
rowspan=1,
474480
l=0.0,
@@ -478,6 +484,15 @@ def _checks(item, defaults):
478484
)
479485
_check_keys_and_fill('specs', specs, spec_defaults)
480486

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+
481496
# ### insets ###
482497
if insets is None or insets is False:
483498
insets = []
@@ -612,8 +627,10 @@ def _checks(item, defaults):
612627

613628
# ### construct subplot container ###
614629
subplot_type = spec['type']
630+
secondary_y = spec['secondary_y']
615631
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)
617634
grid_ref[r][c] = grid_ref_element
618635

619636
_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):
842859

843860

844861
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
846863
):
847864
if max_subplot_ids is None:
848865
max_subplot_ids = _get_initial_max_subplot_ids()
@@ -873,6 +890,21 @@ def _init_subplot_xy(
873890
'trace_kwargs': {'xaxis': x_label, 'yaxis': y_label}
874891
}
875892

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+
876908
# increment max_subplot_ids
877909
max_subplot_ids['xaxis'] = x_cnt
878910
max_subplot_ids['yaxis'] = y_cnt
@@ -966,7 +998,8 @@ def _validate_coerce_subplot_type(subplot_type):
966998

967999

9681000
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
9701003
):
9711004
# Normalize subplot type
9721005
subplot_type = _validate_coerce_subplot_type(subplot_type)
@@ -982,7 +1015,7 @@ def _init_subplot(
9821015

9831016
if subplot_type == 'xy':
9841017
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
9861019
)
9871020
elif subplot_type in _single_subplot_types:
9881021
ref_element = _init_subplot_single(
@@ -1217,7 +1250,7 @@ def _pad(s, cell_len=cell_len):
12171250
return grid_str
12181251

12191252

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):
12211254
if row <= 0:
12221255
raise Exception("Row value is out of range. "
12231256
"Note: the starting cell is (1, 1)")
@@ -1238,7 +1271,18 @@ def _set_trace_grid_reference(trace, layout, grid_ref, row, col):
12381271
col=col
12391272
))
12401273

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:
12421286
if k not in trace:
12431287
raise ValueError("""\
12441288
Trace type '{typ}' is not compatible with subplot type '{subplot_type}'
@@ -1311,13 +1355,21 @@ def _get_grid_subplot(fig, row, col):
13111355
elif len(layout_keys) == 2:
13121356
return SubplotXY(
13131357
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+
)
13151367
else:
13161368
raise ValueError("""
13171369
Unexpected subplot type with layout_keys of {}""".format(layout_keys))
13181370

13191371

1320-
def _get_subplot_ref_for_trace(trace):
1372+
def _get_subplot_ref_for_trace(trace, secondary_to_primary_y_axes):
13211373

13221374
if 'domain' in trace:
13231375
return {
@@ -1331,11 +1383,29 @@ def _get_subplot_ref_for_trace(trace):
13311383
xaxis_name = 'xaxis' + trace.xaxis[1:] if trace.xaxis else 'xaxis'
13321384
yaxis_name = 'yaxis' + trace.yaxis[1:] if trace.yaxis else 'yaxis'
13331385

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+
}
13391409
elif 'geo' in trace:
13401410
return {
13411411
'subplot_type': 'geo',

0 commit comments

Comments
 (0)