@@ -26,9 +26,19 @@ def __init__(self, data=None, layout_plotly=None, frames=None):
2626
2727 layout = layout_plotly
2828
29+ # Subplots
30+ # --------
31+ self ._grid_str = None
32+ self ._grid_ref = None
33+
2934 # Handle case where data is a Figure or Figure-like dict
3035 # ------------------------------------------------------
3136 if isinstance (data , BaseFigure ):
37+ # Bring over subplot fields
38+ self ._grid_str = data ._grid_str
39+ self ._grid_ref = data ._grid_ref
40+
41+ # Extract data, layout, and frames
3242 data , layout , frames = data .data , data .layout , data .frames
3343
3444 elif (isinstance (data , dict ) and
@@ -37,6 +47,7 @@ def __init__(self, data=None, layout_plotly=None, frames=None):
3747 data .get ('layout' , None ),
3848 data .get ('frames' , None ))
3949
50+
4051 # Traces
4152 # ------
4253 from plotly .validators import DataValidator
@@ -113,6 +124,8 @@ def __init__(self, data=None, layout_plotly=None, frames=None):
113124 # -------
114125 self ._log_plotly_commands = False
115126
127+
128+
116129 # Magic Methods
117130 # -------------
118131 def __setitem__ (self , prop , value ):
@@ -181,6 +194,77 @@ def update(self, dict1=None, **dict2):
181194 for k , v in d .items ():
182195 BaseFigure ._perform_update (self [k ], v )
183196
197+ # Subplots
198+ # --------
199+ def print_grid (self ):
200+ """
201+ Print a visual layout of the figure's axes arrangement.
202+ This is only valid for figures that are created
203+ with plotly.tools.make_subplots.
204+ """
205+ if self ._grid_str is None :
206+ raise Exception ("Use plotly.tools.make_subplots "
207+ "to create a subplot grid." )
208+ print (self ._grid_str )
209+
210+ def append_trace (self , trace , row , col ):
211+ """
212+ Add a trace to your figure bound to axes at the row, col index.
213+ The row, col index is generated from figures created with
214+ plotly.tools.make_subplots and can be viewed with
215+ Figure.print_grid.
216+ :param (dict) trace: The data trace to be bound.
217+ :param (int) row: Subplot row index (see Figure.print_grid).
218+ :param (int) col: Subplot column index (see Figure.print_grid).
219+ Example:
220+ # stack two subplots vertically
221+ fig = tools.make_subplots(rows=2)
222+ This is the format of your plot grid:
223+ [ (1,1) x1,y1 ]
224+ [ (2,1) x2,y2 ]
225+ fig.append_trace(Scatter(x=[1,2,3], y=[2,1,2]), 1, 1)
226+ fig.append_trace(Scatter(x=[1,2,3], y=[2,1,2]), 2, 1)
227+ """
228+ try :
229+ grid_ref = self ._grid_ref
230+ except AttributeError :
231+ raise Exception ("In order to use Figure.append_trace, "
232+ "you must first use "
233+ "plotly.tools.make_subplots "
234+ "to create a subplot grid." )
235+ if row <= 0 :
236+ raise Exception ("Row value is out of range. "
237+ "Note: the starting cell is (1, 1)" )
238+ if col <= 0 :
239+ raise Exception ("Col value is out of range. "
240+ "Note: the starting cell is (1, 1)" )
241+ try :
242+ ref = grid_ref [row - 1 ][col - 1 ]
243+ except IndexError :
244+ raise Exception ("The (row, col) pair sent is out of "
245+ "range. Use Figure.print_grid to view the "
246+ "subplot grid. " )
247+ if 'scene' in ref [0 ]:
248+ trace ['scene' ] = ref [0 ]
249+ if ref [0 ] not in self ['layout' ]:
250+ raise Exception ("Something went wrong. "
251+ "The scene object for ({r},{c}) "
252+ "subplot cell "
253+ "got deleted." .format (r = row , c = col ))
254+ else :
255+ xaxis_key = "xaxis{ref}" .format (ref = ref [0 ][1 :])
256+ yaxis_key = "yaxis{ref}" .format (ref = ref [1 ][1 :])
257+ if (xaxis_key not in self ['layout' ]
258+ or yaxis_key not in self ['layout' ]):
259+ raise Exception ("Something went wrong. "
260+ "An axis object for ({r},{c}) subplot "
261+ "cell got deleted."
262+ .format (r = row , c = col ))
263+ trace ['xaxis' ] = ref [0 ]
264+ trace ['yaxis' ] = ref [1 ]
265+
266+ self .add_traces ([trace ])
267+
184268 # Data
185269 # ----
186270 @property
@@ -1443,7 +1527,9 @@ def __getitem__(self, prop):
14431527
14441528 return res
14451529 else :
1446- if prop not in self ._validators :
1530+ if (prop not in self ._validators and
1531+ prop not in self ._props and
1532+ prop not in self ._prop_defaults ):
14471533 raise KeyError (prop )
14481534
14491535 if prop in self ._compound_props :
@@ -1765,11 +1851,17 @@ def _set_subplotid_prop(self, prop, value):
17651851 match = self ._subplotid_prop_re .fullmatch (prop )
17661852 subplot_prop = match .group (1 )
17671853 suffix_digit = int (match .group (2 ))
1768- if suffix_digit in [0 , 1 ]:
1769- raise TypeError ('Subplot properties may only be suffixed by an integer > 1\n '
1854+ if suffix_digit == 0 :
1855+ raise TypeError ('Subplot properties may only be suffixed by an '
1856+ 'integer >= 1\n '
17701857 'Received {k}' .format (k = prop ))
17711858
1772- # Add validator
1859+ # Handle suffix_digit == 1
1860+ # In this case we remove suffix digit (e.g. xaxis1 -> xaxis)
1861+ if suffix_digit == 1 :
1862+ prop = subplot_prop
1863+
1864+ # Add validator
17731865 if prop not in self ._validators :
17741866 validator = self ._subplotid_validators [subplot_prop ](plotly_name = prop )
17751867 self ._validators [prop ] = validator
@@ -1778,13 +1870,68 @@ def _set_subplotid_prop(self, prop, value):
17781870 self ._subplotid_props [prop ] = self ._set_compound_prop (prop , value )
17791871
17801872 def __getattr__ (self , item ):
1873+
1874+ # Handle subplot suffix of 1
1875+ # Remove digit of 1 from subplot id (e.g.. xaxis1 -> xaxis)
1876+ match = self ._subplotid_prop_re .fullmatch (item )
1877+ if match :
1878+ subplot_prop = match .group (1 )
1879+ suffix_digit = int (match .group (2 ))
1880+ if subplot_prop and suffix_digit == 1 :
1881+ item = subplot_prop
1882+
17811883 # Check for subplot access (e.g. xaxis2)
17821884 # Validate then call self._get_prop(item)
17831885 if item in self ._subplotid_props :
17841886 return self ._subplotid_props [item ]
1887+ elif item in self ._validators :
1888+ return self [item ]
17851889
17861890 raise AttributeError ("'Layout' object has no attribute '{item}'" .format (item = item ))
17871891
1892+ def __getitem__ (self , item ):
1893+
1894+ # Handle subplot suffix of 1
1895+ # Remove digit of 1 from subplot id (e.g.. xaxis1 -> xaxis)
1896+ match = self ._subplotid_prop_re .fullmatch (item )
1897+ if match :
1898+ subplot_prop = match .group (1 )
1899+ suffix_digit = int (match .group (2 ))
1900+ if subplot_prop and suffix_digit == 1 :
1901+ item = subplot_prop
1902+
1903+ # Check for subplot access (e.g. xaxis2)
1904+ # Validate then call self._get_prop(item)
1905+ if item in self ._subplotid_props :
1906+ return self ._subplotid_props [item ]
1907+ elif item in self ._validators :
1908+ return super ().__getitem__ (item )
1909+
1910+ raise AttributeError ("'Layout' object has no attribute '{item}'" .format (item = item ))
1911+
1912+ def __contains__ (self , prop ):
1913+ if prop in self ._validators :
1914+ return True
1915+ else :
1916+ # Check for subplot with suffix 1
1917+ match = self ._subplotid_prop_re .fullmatch (prop )
1918+ if (match and
1919+ match .group (1 ) in self ._validators and
1920+ match .group (2 ) == '1' ):
1921+ return True
1922+ else :
1923+ return False
1924+
1925+ def __setitem__ (self , prop , value ):
1926+ # Check for subplot assignment (e.g. xaxis2)
1927+ # Call _set_compound_prop with the xaxis validator
1928+ match = self ._subplotid_prop_re .fullmatch (prop )
1929+ if match is None :
1930+ # Try setting as ordinary property
1931+ super ().__setitem__ (prop , value )
1932+ else :
1933+ self ._set_subplotid_prop (prop , value )
1934+
17881935 def __setattr__ (self , prop , value ):
17891936 # Check for subplot assignment (e.g. xaxis2)
17901937 # Call _set_compound_prop with the xaxis validator
0 commit comments