Skip to content

Commit e52256b

Browse files
committed
Add select_*, for_each_*, update_* methods for each layout subplot type
Also updated code generation logic to automatically determine the set of layout subplot types.
1 parent 25cc98e commit e52256b

File tree

9 files changed

+1625
-57
lines changed

9 files changed

+1625
-57
lines changed

codegen/__init__.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -126,6 +126,9 @@ def perform_codegen():
126126
all_layout_nodes = PlotlyNode.get_all_datatype_nodes(
127127
plotly_schema, LayoutNode)
128128

129+
subplot_nodes = [node for node in layout_node.child_compound_datatypes
130+
if node.node_data.get('_isSubplotObj', False)]
131+
129132
# ### FrameNode ###
130133
compound_frame_nodes = PlotlyNode.get_all_compound_datatype_nodes(
131134
plotly_schema, FrameNode)
@@ -187,7 +190,8 @@ def perform_codegen():
187190
base_traces_node,
188191
data_validator,
189192
layout_validator,
190-
frame_validator)
193+
frame_validator,
194+
subplot_nodes)
191195

192196
# Write datatype __init__.py files
193197
# --------------------------------

codegen/datatypes.py

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,48 @@ def build_datatype_py(node):
103103
104104
class {datatype_class}(_{node.name_base_datatype}):\n""")
105105

106+
# ### Layout subplot properties ###
107+
if datatype_class == 'Layout':
108+
subplot_nodes = [node for node in node.child_compound_datatypes
109+
if node.node_data.get('_isSubplotObj', False)]
110+
subplot_names = [n.name_property for n in subplot_nodes]
111+
buffer.write(f"""
112+
_subplotid_prop_names = {repr(subplot_names)}
113+
114+
import re
115+
_subplotid_prop_re = re.compile(
116+
'^(' + '|'.join(_subplotid_prop_names) + ')(\d+)$')
117+
""")
118+
119+
subplot_validator_names = [n.name_validator_class
120+
for n in subplot_nodes]
121+
122+
validator_csv = ', '.join(subplot_validator_names)
123+
subplot_dict_str = (
124+
'{' +
125+
', '.join(f"'{subname}': {valname}" for subname, valname in
126+
zip(subplot_names, subplot_validator_names)) +
127+
'}'
128+
)
129+
130+
buffer.write(f"""
131+
@property
132+
def _subplotid_validators(self):
133+
\"\"\"
134+
dict of validator classes for each subplot type
135+
136+
Returns
137+
-------
138+
dict
139+
\"\"\"
140+
from plotly.validators.layout import ({validator_csv})
141+
142+
return {subplot_dict_str}
143+
144+
def _subplot_re_match(self, prop):
145+
return self._subplotid_prop_re.match(prop)
146+
""")
147+
106148
# ### Property definitions ###
107149
child_datatype_nodes = node.child_datatypes
108150

codegen/figure.py

Lines changed: 118 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,12 @@
88
add_constructor_params, add_docstring)
99
from codegen.utils import PlotlyNode, format_and_write_source_py
1010

11+
import inflect
12+
1113

1214
def build_figure_py(trace_node, base_package, base_classname, fig_classname,
13-
data_validator, layout_validator, frame_validator):
15+
data_validator, layout_validator, frame_validator,
16+
subplot_nodes):
1417
"""
1518
1619
Parameters
@@ -30,7 +33,8 @@ def build_figure_py(trace_node, base_package, base_classname, fig_classname,
3033
LayoutValidator instance
3134
frame_validator : CompoundArrayValidator
3235
FrameValidator instance
33-
36+
subplot_nodes: list of str
37+
List of names of all of the layout subplot properties
3438
Returns
3539
-------
3640
str
@@ -53,6 +57,7 @@ def build_figure_py(trace_node, base_package, base_classname, fig_classname,
5357
# ### Import trace graph_obj classes ###
5458
trace_types_csv = ', '.join([n.name_datatype_class for n in trace_nodes])
5559
buffer.write(f'from plotly.graph_objs import ({trace_types_csv})\n')
60+
buffer.write("from plotly.subplots import _validate_v4_subplots\n")
5661

5762
# Write class definition
5863
# ----------------------
@@ -141,6 +146,111 @@ def add_{trace_node.plotly_name}(self""")
141146
buffer.write(f"""
142147
return self.add_trace(new_trace, row=row, col=col)""")
143148

149+
# update layout subplots
150+
# ----------------------
151+
inflect_eng = inflect.engine()
152+
for subplot_node in subplot_nodes:
153+
singular_name = subplot_node.name_property
154+
plural_name = inflect_eng.plural_noun(singular_name)
155+
buffer.write(f"""
156+
157+
def select_{plural_name}(self, selector=None, row=None, col=None):
158+
\"\"\"
159+
Select {singular_name} subplot objects from a particular subplot cell
160+
and/or {singular_name} subplot objects that satisfy custom selection
161+
criteria.
162+
163+
Parameters
164+
----------
165+
selector: dict or None (default None)
166+
Dict to use as selection criteria.
167+
{singular_name} objects will be selected if they contain
168+
properties corresponding to all of the dictionary's keys, with
169+
values that exactly match the supplied values. If None
170+
(the default), all {singular_name} objects are selected.
171+
row, col: int or None (default None)
172+
Subplot row and column index of {singular_name} objects to select.
173+
To select {singular_name} objects by row and column, the Figure
174+
must have been created using plotly.subplots.make_subplots.
175+
If None (the default), all {singular_name} objects are selected.
176+
177+
Returns
178+
-------
179+
generator
180+
Generator that iterates through all of the {singular_name}
181+
objects that satisfy all of the specified selection criteria
182+
\"\"\"
183+
if row is not None or col is not None:
184+
_validate_v4_subplots('select_{plural_name}')
185+
186+
return self._select_layout_subplots_by_prefix(
187+
'{singular_name}', selector, row, col)
188+
189+
def for_each_{singular_name}(self, fn, selector=None, row=None, col=None):
190+
\"\"\"
191+
Apply a function to all {singular_name} objects that satisfy the
192+
specified selection criteria
193+
194+
Parameters
195+
----------
196+
fn:
197+
Function that inputs a single {singular_name} object.
198+
selector: dict or None (default None)
199+
Dict to use as selection criteria.
200+
{singular_name} objects will be selected if they contain
201+
properties corresponding to all of the dictionary's keys, with
202+
values that exactly match the supplied values. If None
203+
(the default), all {singular_name} objects are selected.
204+
row, col: int or None (default None)
205+
Subplot row and column index of {singular_name} objects to select.
206+
To select {singular_name} objects by row and column, the Figure
207+
must have been created using plotly.subplots.make_subplots.
208+
If None (the default), all {singular_name} objects are selected.
209+
210+
Returns
211+
-------
212+
self
213+
Returns the Figure object that the method was called on
214+
\"\"\"
215+
for obj in self.select_{plural_name}(
216+
selector=selector, row=row, col=col):
217+
fn(obj)
218+
219+
return self
220+
221+
def update_{plural_name}(self, patch, selector=None, row=None, col=None):
222+
\"\"\"
223+
Perform a property update operation on all {singular_name} objects
224+
that satisfy the specified selection criteria
225+
226+
Parameters
227+
----------
228+
patch: dict
229+
Dictionary of property updates to be applied to all
230+
{singular_name} objects that satisfy the selection criteria.
231+
selector: dict or None (default None)
232+
Dict to use as selection criteria.
233+
{singular_name} objects will be selected if they contain
234+
properties corresponding to all of the dictionary's keys, with
235+
values that exactly match the supplied values. If None
236+
(the default), all {singular_name} objects are selected.
237+
row, col: int or None (default None)
238+
Subplot row and column index of {singular_name} objects to select.
239+
To select {singular_name} objects by row and column, the Figure
240+
must have been created using plotly.subplots.make_subplots.
241+
If None (the default), all {singular_name} objects are selected.
242+
243+
Returns
244+
-------
245+
self
246+
Returns the Figure object that the method was called on
247+
\"\"\"
248+
for obj in self.select_{plural_name}(
249+
selector=selector, row=row, col=col):
250+
obj.update(patch)
251+
252+
return self""")
253+
144254
# Return source string
145255
# --------------------
146256
buffer.write('\n')
@@ -150,7 +260,8 @@ def add_{trace_node.plotly_name}(self""")
150260
def write_figure_classes(outdir, trace_node,
151261
data_validator,
152262
layout_validator,
153-
frame_validator):
263+
frame_validator,
264+
subplot_nodes):
154265
"""
155266
Construct source code for the Figure and FigureWidget classes and
156267
write to graph_objs/_figure.py and graph_objs/_figurewidget.py
@@ -169,6 +280,8 @@ def write_figure_classes(outdir, trace_node,
169280
LayoutValidator instance
170281
frame_validator : CompoundArrayValidator
171282
FrameValidator instance
283+
subplot_nodes: list of str
284+
List of names of all of the layout subplot properties
172285
173286
Returns
174287
-------
@@ -195,7 +308,8 @@ def write_figure_classes(outdir, trace_node,
195308
fig_classname,
196309
data_validator,
197310
layout_validator,
198-
frame_validator)
311+
frame_validator,
312+
subplot_nodes)
199313

200314
# ### Format and write to file###
201315
filepath = opath.join(outdir, 'graph_objs',

optional-requirements.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ psutil
2323

2424
## codegen dependencies ##
2525
yapf
26+
inflect
2627

2728
## template generation ##
2829
colorcet

0 commit comments

Comments
 (0)