diff --git a/plotly/graph_objs/graph_objs.py b/plotly/graph_objs/graph_objs.py index 75cde188a4b..69198576889 100644 --- a/plotly/graph_objs/graph_objs.py +++ b/plotly/graph_objs/graph_objs.py @@ -919,6 +919,87 @@ def __init__(self, *args, **kwargs): kwargs['layout'] = Layout() super(Figure, self).__init__(*args, **kwargs) Figure.__init__ = __init__ # override method! + + def print_grid(self): + """Print a visual layout of the figure's axes arrangement. + + This is only valid for figures that are created + with plotly.tools.make_subplots. + """ + try: + grid_str = self._grid_str + except KeyError: + raise Exception("Use plotly.tools.make_subplots " + "to create a subplot grid.") + print(grid_str) + Figure.print_grid = print_grid + + def append_trace(self, trace, row, col): + """ Helper function to add a data traces to your figure + that is bound to axes at the row, col index. + + The row, col index is generated from figures created with + plotly.tools.make_subplots and can be viewed with Figure.print_grid. + + Example: + # 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.append_trace(Scatter(x=[1,2,3], y=[2,1,2]), 1, 1) + fig.append_trace(Scatter(x=[1,2,3], y=[2,1,2]), 2, 1) + + Arguments: + + trace (plotly trace object): + The data trace to be bound. + + row (int): + Subplot row index on the subplot grid (see Figure.print_grid) + + col (int): + Subplot column index on the subplot grid (see Figure.print_grid) + + """ + try: + grid_ref = self._grid_ref + except KeyError: + raise Exception("In order to use Figure.append_trace, " + "you must first use plotly.tools.make_subplots " + "to create a subplot grid.") + if row <= 0: + raise Exception("Row value is out of range. " + "Note: the starting cell is (1, 1)") + if col <= 0: + raise Exception("Col value is out of range. " + "Note: the starting cell is (1, 1)") + try: + ref = grid_ref[row-1][col-1] + except IndexError: + raise Exception("The (row, col) pair sent is out of range. " + "Use Figure.print_grid to view the subplot grid. ") + if 'scene' in ref[0]: + trace['scene'] = ref[0] + if ref[0] not in self['layout']: + raise Exception("Something went wrong. " + "The scene object for ({r},{c}) subplot cell " + "got deleted.".format(r=row, c=col)) + else: + xaxis_key = "xaxis{ref}".format(ref=ref[0][1:]) + yaxis_key = "yaxis{ref}".format(ref=ref[1][1:]) + if (xaxis_key not in self['layout'] + or yaxis_key not in self['layout']): + raise Exception("Something went wrong. " + "An axis object for ({r},{c}) subplot cell " + "got deleted.".format(r=row, c=col)) + trace['xaxis'] = ref[0] + trace['yaxis'] = ref[1] + self['data'] += [trace] + Figure.append_trace = append_trace + return Figure Figure = get_patched_figure_class(Figure) diff --git a/plotly/graph_reference/graph_objs_meta.json b/plotly/graph_reference/graph_objs_meta.json index 786df295e10..127e4e44862 100644 --- a/plotly/graph_reference/graph_objs_meta.json +++ b/plotly/graph_reference/graph_objs_meta.json @@ -1586,9 +1586,9 @@ }, "scene": { "key_type": "plot_info", - "val_types": "'s1' | 's2' | 's3' | etc.", + "val_types": "'scene1' | 'scene2' | 'scene3' | etc.", "required": false, - "description": "This key determines the scene on which this trace will be plotted in. More info coming soon." + "description": "This key determines the scene on which this trace will be plotted in." }, "stream": { "key_type": "object", @@ -1657,9 +1657,9 @@ }, "scene": { "key_type": "plot_info", - "val_types": "'s1' | 's2' | 's3' | etc.", + "val_types": "'scene1' | 'scene2' | 'scene3' | etc.", "required": false, - "description": "This key determines the scene on which this trace will be plotted in. More info coming soon." + "description": "This key determines the scene on which this trace will be plotted in." }, "stream": { "key_type": "object", @@ -3317,6 +3317,16 @@ ], "description": "Sets the camera position with respect to the scene. The first entry (a list or 1d numpy array of length 4) sets the angular position of the camera. The second entry (a list or 1d numpy array of length 3) sets the (x,y,z) translation of the camera. The third entry (a scalar) sets zoom of the camera." }, + "domain": { + "key_type": "plot_info", + "val_types": "domain dictionary", + "required": false, + "examples": [ + "{'x': [0, 0.4], 'y': [0.6, 1]}", + "dict(x=[0, 0.4], y=[0.6, 1])" + ], + "description": "Sets the x-y domain of this scene on the plotting surface." + }, "bgcolor": { "key_type": "style", "val_types": "a string describing color", diff --git a/plotly/tests/test_core/test_graph_objs/test_append_trace.py b/plotly/tests/test_core/test_graph_objs/test_append_trace.py new file mode 100644 index 00000000000..0ca385532f3 --- /dev/null +++ b/plotly/tests/test_core/test_graph_objs/test_append_trace.py @@ -0,0 +1,176 @@ +import plotly +from plotly.graph_objs import * +import plotly.tools as tls +from nose.tools import raises + + +@raises(Exception) +def test_print_grid_before_make_subplots(): + fig.print_grid() + +@raises(Exception) +def test_append_trace_before_make_subplots(): + trace = Scatter( + x=[1,2,3], + y=[2,3,4] + ) + fig.append_trace(trace, 2, 2) + +@raises(Exception) +def test_append_trace_row_out_of_range(): + trace = Scatter( + x=[1,2,3], + y=[2,3,4] + ) + fig = tls.make_subplots(rows=2, cols=3) + fig.append_trace(trace, 10, 2) + +@raises(Exception) +def test_append_trace_col_out_of_range(): + trace = Scatter( + x=[1,2,3], + y=[2,3,4] + ) + fig = tls.make_subplots(rows=2, cols=3) + fig.append_trace(trace, 2, 0) + +def test_append_scatter(): + expected = Figure( + data=Data([ + Scatter( + x=[1, 2, 3], + y=[2, 3, 4], + xaxis='x5', + yaxis='y5' + ) + ]), + layout=Layout( + xaxis1=XAxis( + domain=[0.0, 0.2888888888888889], + anchor='y1' + ), + xaxis2=XAxis( + domain=[0.35555555555555557, 0.6444444444444445], + anchor='y2' + ), + xaxis3=XAxis( + domain=[0.7111111111111111, 1.0], + anchor='y3' + ), + xaxis4=XAxis( + domain=[0.0, 0.2888888888888889], + anchor='y4' + ), + xaxis5=XAxis( + domain=[0.35555555555555557, 0.6444444444444445], + anchor='y5' + ), + xaxis6=XAxis( + domain=[0.7111111111111111, 1.0], + anchor='y6' + ), + yaxis1=YAxis( + domain=[0.575, 1.0], + anchor='x1' + ), + yaxis2=YAxis( + domain=[0.575, 1.0], + anchor='x2' + ), + yaxis3=YAxis( + domain=[0.575, 1.0], + anchor='x3' + ), + yaxis4=YAxis( + domain=[0.0, 0.425], + anchor='x4' + ), + yaxis5=YAxis( + domain=[0.0, 0.425], + anchor='x5' + ), + yaxis6=YAxis( + domain=[0.0, 0.425], + anchor='x6' + ) + ) + ) + + trace = Scatter( + x=[1,2,3], + y=[2,3,4] + ) + fig = tls.make_subplots(rows=2, cols=3) + fig.append_trace(trace, 2, 2) + assert fig == expected + +@raises(Exception) +def test_append_scatter_after_deleting_xaxis(): + trace = Scatter( + x=[1,2,3], + y=[2,3,4] + ) + fig = tls.make_subplots(rows=2, cols=3) + fig['layout'].pop('xaxis5', None) + fig.append_trace(trace, 2, 2) + +@raises(Exception) +def test_append_scatter_after_deleting_yaxis(): + trace = Scatter( + x=[1,2,3], + y=[2,3,4] + ) + fig = tls.make_subplots(rows=2, cols=3) + fig['layout'].pop('yaxis5', None) + fig.append_trace(trace, 2, 2) + +def test_append_scatter3d(): + expected = Figure( + data=Data([ + Scatter3d( + x=[1, 2, 3], + y=[2, 3, 4], + z=[1, 2, 3], + scene='scene2' + ), + Scatter3d( + x=[1, 2, 3], + y=[2, 3, 4], + z=[1, 2, 3], + scene='scene2' + ) + ]), + layout=Layout( + scene1=Scene( + domain={'y': [0.575, 1.0], 'x': [0.0, 1.0]} + ), + scene2=Scene( + domain={'y': [0.0, 0.425], 'x': [0.0, 1.0]} + ) + ) + ) + + fig = tls.make_subplots(rows=2, cols=1, + specs=[[{'is_3d': True}], + [{'is_3d': True}]]) + trace = Scatter3d( + x=[1,2,3], + y=[2,3,4], + z=[1,2,3] + ) + fig.append_trace(trace, 1, 1) + fig.append_trace(trace, 2, 1) + assert fig == expected + +@raises(Exception) +def test_append_scatter3d_after_deleting_scene(): + fig = tls.make_subplots(rows=2, cols=1, + specs=[[{'is_3d': True}], + [{'is_3d': True}]]) + trace = Scatter3d( + x=[1,2,3], + y=[2,3,4], + z=[1,2,3] + ) + fig['layout'].pop('scene1', None) + fig.append_trace(trace, 1, 1) diff --git a/plotly/tests/test_core/test_tools/test_get_subplots.py b/plotly/tests/test_core/test_tools/test_get_subplots.py index 1d773d06012..026dffebff5 100644 --- a/plotly/tests/test_core/test_tools/test_get_subplots.py +++ b/plotly/tests/test_core/test_tools/test_get_subplots.py @@ -1,6 +1,28 @@ import plotly from plotly.graph_objs import * import plotly.tools as tls +from nose.tools import raises + + +@raises(Exception) +def test_non_integer_rows(): + fig = tls.get_subplots(rows=2.1) + +@raises(Exception) +def test_less_than_zero_rows(): + fig = tls.make_subplots(rows=-2) + +@raises(Exception) +def test_non_integer_columns(): + fig = tls.get_subplots(columns=2/3) + +@raises(Exception) +def test_less_than_zero_cols(): + fig = tls.make_subplots(columns=-10) + +@raises(Exception) +def test_wrong_kwarg(): + fig = tls.get_subplots(stuff='no gonna work') def test_get_single_plot(): expected = Figure( @@ -298,7 +320,12 @@ def test_a_lot(): ) ) ) - assert tls.get_subplots(4, 7) == expected + + fig = tls.get_subplots(4, 7, + horizontal_spacing=0.1, + vertical_spacing=0.15) + + assert fig == expected def test_spacing(): expected = Figure( @@ -355,4 +382,259 @@ def test_spacing(): ) ) - assert expected == tls.get_subplots(2, 3, .05, .1) + fig = tls.get_subplots(2, 3, + horizontal_spacing=.05, + vertical_spacing=.1) + + assert fig == expected + +def test_default_spacing(): + expected = Figure( + data=Data(), + layout=Layout( + xaxis1=XAxis( + domain=[0.0, 0.16799999999999998], + anchor='y1' + ), + xaxis10=XAxis( + domain=[0.832, 1.0], + anchor='y10' + ), + xaxis11=XAxis( + domain=[0.0, 0.16799999999999998], + anchor='y11' + ), + xaxis12=XAxis( + domain=[0.208, 0.376], + anchor='y12' + ), + xaxis13=XAxis( + domain=[0.416, 0.584], + anchor='y13' + ), + xaxis14=XAxis( + domain=[0.624, 0.792], + anchor='y14' + ), + xaxis15=XAxis( + domain=[0.832, 1.0], + anchor='y15' + ), + xaxis16=XAxis( + domain=[0.0, 0.16799999999999998], + anchor='y16' + ), + xaxis17=XAxis( + domain=[0.208, 0.376], + anchor='y17' + ), + xaxis18=XAxis( + domain=[0.416, 0.584], + anchor='y18' + ), + xaxis19=XAxis( + domain=[0.624, 0.792], + anchor='y19' + ), + xaxis2=XAxis( + domain=[0.208, 0.376], + anchor='y2' + ), + xaxis20=XAxis( + domain=[0.832, 1.0], + anchor='y20' + ), + xaxis21=XAxis( + domain=[0.0, 0.16799999999999998], + anchor='y21' + ), + xaxis22=XAxis( + domain=[0.208, 0.376], + anchor='y22' + ), + xaxis23=XAxis( + domain=[0.416, 0.584], + anchor='y23' + ), + xaxis24=XAxis( + domain=[0.624, 0.792], + anchor='y24' + ), + xaxis25=XAxis( + domain=[0.832, 1.0], + anchor='y25' + ), + xaxis26=XAxis( + domain=[0.0, 0.16799999999999998], + anchor='y26' + ), + xaxis27=XAxis( + domain=[0.208, 0.376], + anchor='y27' + ), + xaxis28=XAxis( + domain=[0.416, 0.584], + anchor='y28' + ), + xaxis29=XAxis( + domain=[0.624, 0.792], + anchor='y29' + ), + xaxis3=XAxis( + domain=[0.416, 0.584], + anchor='y3' + ), + xaxis30=XAxis( + domain=[0.832, 1.0], + anchor='y30' + ), + xaxis4=XAxis( + domain=[0.624, 0.792], + anchor='y4' + ), + xaxis5=XAxis( + domain=[0.832, 1.0], + anchor='y5' + ), + xaxis6=XAxis( + domain=[0.0, 0.16799999999999998], + anchor='y6' + ), + xaxis7=XAxis( + domain=[0.208, 0.376], + anchor='y7' + ), + xaxis8=XAxis( + domain=[0.416, 0.584], + anchor='y8' + ), + xaxis9=XAxis( + domain=[0.624, 0.792], + anchor='y9' + ), + yaxis1=YAxis( + domain=[0.0, 0.125], + anchor='x1' + ), + yaxis10=YAxis( + domain=[0.175, 0.3], + anchor='x10' + ), + yaxis11=YAxis( + domain=[0.35, 0.475], + anchor='x11' + ), + yaxis12=YAxis( + domain=[0.35, 0.475], + anchor='x12' + ), + yaxis13=YAxis( + domain=[0.35, 0.475], + anchor='x13' + ), + yaxis14=YAxis( + domain=[0.35, 0.475], + anchor='x14' + ), + yaxis15=YAxis( + domain=[0.35, 0.475], + anchor='x15' + ), + yaxis16=YAxis( + domain=[0.5249999999999999, 0.6499999999999999], + anchor='x16' + ), + yaxis17=YAxis( + domain=[0.5249999999999999, 0.6499999999999999], + anchor='x17' + ), + yaxis18=YAxis( + domain=[0.5249999999999999, 0.6499999999999999], + anchor='x18' + ), + yaxis19=YAxis( + domain=[0.5249999999999999, 0.6499999999999999], + anchor='x19' + ), + yaxis2=YAxis( + domain=[0.0, 0.125], + anchor='x2' + ), + yaxis20=YAxis( + domain=[0.5249999999999999, 0.6499999999999999], + anchor='x20' + ), + yaxis21=YAxis( + domain=[0.7, 0.825], + anchor='x21' + ), + yaxis22=YAxis( + domain=[0.7, 0.825], + anchor='x22' + ), + yaxis23=YAxis( + domain=[0.7, 0.825], + anchor='x23' + ), + yaxis24=YAxis( + domain=[0.7, 0.825], + anchor='x24' + ), + yaxis25=YAxis( + domain=[0.7, 0.825], + anchor='x25' + ), + yaxis26=YAxis( + domain=[0.875, 1.0], + anchor='x26' + ), + yaxis27=YAxis( + domain=[0.875, 1.0], + anchor='x27' + ), + yaxis28=YAxis( + domain=[0.875, 1.0], + anchor='x28' + ), + yaxis29=YAxis( + domain=[0.875, 1.0], + anchor='x29' + ), + yaxis3=YAxis( + domain=[0.0, 0.125], + anchor='x3' + ), + yaxis30=YAxis( + domain=[0.875, 1.0], + anchor='x30' + ), + yaxis4=YAxis( + domain=[0.0, 0.125], + anchor='x4' + ), + yaxis5=YAxis( + domain=[0.0, 0.125], + anchor='x5' + ), + yaxis6=YAxis( + domain=[0.175, 0.3], + anchor='x6' + ), + yaxis7=YAxis( + domain=[0.175, 0.3], + anchor='x7' + ), + yaxis8=YAxis( + domain=[0.175, 0.3], + anchor='x8' + ), + yaxis9=YAxis( + domain=[0.175, 0.3], + anchor='x9' + ) + ) + ) + + fig = tls.get_subplots(rows=6, columns=5) + + assert fig == expected diff --git a/plotly/tests/test_core/test_tools/test_make_subplots.py b/plotly/tests/test_core/test_tools/test_make_subplots.py new file mode 100644 index 00000000000..ff449a77d2b --- /dev/null +++ b/plotly/tests/test_core/test_tools/test_make_subplots.py @@ -0,0 +1,1928 @@ +import plotly +from plotly.graph_objs import * +import plotly.tools as tls +from nose.tools import raises + + +@raises(Exception) +def test_non_integer_rows(): + fig = tls.make_subplots(rows=2.1) + +@raises(Exception) +def test_less_than_zero_rows(): + fig = tls.make_subplots(rows=-2) + +@raises(Exception) +def test_non_integer_cols(): + fig = tls.make_subplots(cols=2/3) + +@raises(Exception) +def test_less_than_zero_cols(): + fig = tls.make_subplots(cols=-10) + +@raises(Exception) +def test_wrong_kwarg(): + fig = tls.make_subplots(stuff='no gonna work') + +@raises(Exception) +def test_non_integer_rows(): + fig = tls.make_subplots(rows=2.1) + +@raises(Exception) +def test_non_integer_cols(): + fig = tls.make_subplots(cols=2/3) + +@raises(Exception) +def test_wrong_kwarg(): + fig = tls.make_subplots(stuff='no gonna work') + +@raises(Exception) +def test_start_cell_wrong_values(): + fig = tls.make_subplots(rows=2, cols=2, start_cell='not gonna work') + +@raises(Exception) +def test_specs_wrong_type(): + fig = tls.make_subplots(specs="not going to work") + +@raises(Exception) +def test_specs_wrong_inner_type(): + fig = tls.make_subplots(specs=[{}]) + +@raises(Exception) +def test_specs_wrong_item_type(): + fig = tls.make_subplots(specs=[[('not', 'going to work')]]) + +@raises(Exception) +def test_specs_wrong_item_key(): + fig = tls.make_subplots(specs=[{'not': "going to work"}]) + +@raises(Exception) +def test_specs_underspecified(): + fig = tls.make_subplots(rows=2, specs=[{}]) + fig = tls.make_subplots(rows=2, cols=2, specs=[[{}, {}], [{}]]) + +@raises(Exception) +def test_specs_overspecified(): + fig = tls.make_subplots(rows=2, specs=[[{}], [{}], [{}]]) + fig = tls.make_subplots(cols=2, specs=[{}, {}, {}]) + +@raises(Exception) +def test_specs_colspan_too_big(): + fig = tls.make_subplots(cols=3, + specs=[[{}, None, {'colspan': 2}]]) + +@raises(Exception) +def test_specs_rowspan_too_big(): + fig = tls.make_subplots(rows=3, + specs=[[{}], + [None], + [{'rowspan': 2}]]) + +@raises(Exception) +def test_insets_wrong_type(): + fig = tls.make_subplots(insets="not going to work") + +@raises(Exception) +def test_insets_wrong_item(): + fig = tls.make_subplots(insets=[{'not': "going to work"}]) + +@raises(Exception) +def test_insets_wrong_cell_row(): + fig = tls.make_subplots(insets=([{'cell': (0, 1)}])) + +@raises(Exception) +def test_insets_wrong_cell_col(): + fig = tls.make_subplots(insets=([{'cell': (1, 0)}])) + +def test_single_plot(): + expected = Figure( + data=Data(), + layout=Layout( + xaxis1=XAxis( + domain=[0.0, 1.0], + anchor='y1' + ), + yaxis1=YAxis( + domain=[0.0, 1.0], + anchor='x1' + ) + ) + ) + assert tls.make_subplots() == expected + +def test_two_row(): + expected = Figure( + data=Data(), + layout=Layout( + xaxis1=XAxis( + domain=[0.0, 1.0], + anchor='y1' + ), + xaxis2=XAxis( + domain=[0.0, 1.0], + anchor='y2' + ), + yaxis1=YAxis( + domain=[0.575, 1.0], + anchor='x1' + ), + yaxis2=YAxis( + domain=[0.0, 0.425], + anchor='x2' + ) + ) + ) + assert tls.make_subplots(rows=2) == expected + +def test_two_row_bottom_left(): + expected = Figure( + data=Data(), + layout=Layout( + xaxis1=XAxis( + domain=[0.0, 1.0], + anchor='y1' + ), + xaxis2=XAxis( + domain=[0.0, 1.0], + anchor='y2' + ), + yaxis1=YAxis( + domain=[0.0, 0.425], + anchor='x1' + ), + yaxis2=YAxis( + domain=[0.575, 1.0], + anchor='x2' + ) + ) + ) + + fig = tls.make_subplots(rows=2, start_cell='bottom-left') + assert fig == expected + +def test_two_column(): + expected = Figure( + data=Data(), + layout=Layout( + xaxis1=XAxis( + domain=[0.0, 0.45], + anchor='y1' + ), + xaxis2=XAxis( + domain=[0.55, 1.0], + anchor='y2' + ), + yaxis1=YAxis( + domain=[0.0, 1.0], + anchor='x1' + ), + yaxis2=YAxis( + domain=[0.0, 1.0], + anchor='x2' + ) + ) + ) + assert tls.make_subplots(cols=2) == expected + +def test_a_lot(): + expected = Figure( + data=Data(), + layout=Layout( + xaxis1=XAxis( + domain=[0.0, 0.1183673469387755], + anchor='y1' + ), + xaxis10=XAxis( + domain=[0.29387755102040813, 0.4122448979591836], + anchor='y10' + ), + xaxis11=XAxis( + domain=[0.4408163265306122, 0.5591836734693877], + anchor='y11' + ), + xaxis12=XAxis( + domain=[0.5877551020408163, 0.7061224489795918], + anchor='y12' + ), + xaxis13=XAxis( + domain=[0.7346938775510203, 0.8530612244897958], + anchor='y13' + ), + xaxis14=XAxis( + domain=[0.8816326530612244, 0.9999999999999999], + anchor='y14' + ), + xaxis15=XAxis( + domain=[0.0, 0.1183673469387755], + anchor='y15' + ), + xaxis16=XAxis( + domain=[0.14693877551020407, 0.26530612244897955], + anchor='y16' + ), + xaxis17=XAxis( + domain=[0.29387755102040813, 0.4122448979591836], + anchor='y17' + ), + xaxis18=XAxis( + domain=[0.4408163265306122, 0.5591836734693877], + anchor='y18' + ), + xaxis19=XAxis( + domain=[0.5877551020408163, 0.7061224489795918], + anchor='y19' + ), + xaxis2=XAxis( + domain=[0.14693877551020407, 0.26530612244897955], + anchor='y2' + ), + xaxis20=XAxis( + domain=[0.7346938775510203, 0.8530612244897958], + anchor='y20' + ), + xaxis21=XAxis( + domain=[0.8816326530612244, 0.9999999999999999], + anchor='y21' + ), + xaxis22=XAxis( + domain=[0.0, 0.1183673469387755], + anchor='y22' + ), + xaxis23=XAxis( + domain=[0.14693877551020407, 0.26530612244897955], + anchor='y23' + ), + xaxis24=XAxis( + domain=[0.29387755102040813, 0.4122448979591836], + anchor='y24' + ), + xaxis25=XAxis( + domain=[0.4408163265306122, 0.5591836734693877], + anchor='y25' + ), + xaxis26=XAxis( + domain=[0.5877551020408163, 0.7061224489795918], + anchor='y26' + ), + xaxis27=XAxis( + domain=[0.7346938775510203, 0.8530612244897958], + anchor='y27' + ), + xaxis28=XAxis( + domain=[0.8816326530612244, 0.9999999999999999], + anchor='y28' + ), + xaxis3=XAxis( + domain=[0.29387755102040813, 0.4122448979591836], + anchor='y3' + ), + xaxis4=XAxis( + domain=[0.4408163265306122, 0.5591836734693877], + anchor='y4' + ), + xaxis5=XAxis( + domain=[0.5877551020408163, 0.7061224489795918], + anchor='y5' + ), + xaxis6=XAxis( + domain=[0.7346938775510203, 0.8530612244897958], + anchor='y6' + ), + xaxis7=XAxis( + domain=[0.8816326530612244, 0.9999999999999999], + anchor='y7' + ), + xaxis8=XAxis( + domain=[0.0, 0.1183673469387755], + anchor='y8' + ), + xaxis9=XAxis( + domain=[0.14693877551020407, 0.26530612244897955], + anchor='y9' + ), + yaxis1=YAxis( + domain=[0.8062499999999999, 0.9999999999999999], + anchor='x1' + ), + yaxis10=YAxis( + domain=[0.5375, 0.73125], + anchor='x10' + ), + yaxis11=YAxis( + domain=[0.5375, 0.73125], + anchor='x11' + ), + yaxis12=YAxis( + domain=[0.5375, 0.73125], + anchor='x12' + ), + yaxis13=YAxis( + domain=[0.5375, 0.73125], + anchor='x13' + ), + yaxis14=YAxis( + domain=[0.5375, 0.73125], + anchor='x14' + ), + yaxis15=YAxis( + domain=[0.26875, 0.4625], + anchor='x15' + ), + yaxis16=YAxis( + domain=[0.26875, 0.4625], + anchor='x16' + ), + yaxis17=YAxis( + domain=[0.26875, 0.4625], + anchor='x17' + ), + yaxis18=YAxis( + domain=[0.26875, 0.4625], + anchor='x18' + ), + yaxis19=YAxis( + domain=[0.26875, 0.4625], + anchor='x19' + ), + yaxis2=YAxis( + domain=[0.8062499999999999, 0.9999999999999999], + anchor='x2' + ), + yaxis20=YAxis( + domain=[0.26875, 0.4625], + anchor='x20' + ), + yaxis21=YAxis( + domain=[0.26875, 0.4625], + anchor='x21' + ), + yaxis22=YAxis( + domain=[0.0, 0.19375], + anchor='x22' + ), + yaxis23=YAxis( + domain=[0.0, 0.19375], + anchor='x23' + ), + yaxis24=YAxis( + domain=[0.0, 0.19375], + anchor='x24' + ), + yaxis25=YAxis( + domain=[0.0, 0.19375], + anchor='x25' + ), + yaxis26=YAxis( + domain=[0.0, 0.19375], + anchor='x26' + ), + yaxis27=YAxis( + domain=[0.0, 0.19375], + anchor='x27' + ), + yaxis28=YAxis( + domain=[0.0, 0.19375], + anchor='x28' + ), + yaxis3=YAxis( + domain=[0.8062499999999999, 0.9999999999999999], + anchor='x3' + ), + yaxis4=YAxis( + domain=[0.8062499999999999, 0.9999999999999999], + anchor='x4' + ), + yaxis5=YAxis( + domain=[0.8062499999999999, 0.9999999999999999], + anchor='x5' + ), + yaxis6=YAxis( + domain=[0.8062499999999999, 0.9999999999999999], + anchor='x6' + ), + yaxis7=YAxis( + domain=[0.8062499999999999, 0.9999999999999999], + anchor='x7' + ), + yaxis8=YAxis( + domain=[0.5375, 0.73125], + anchor='x8' + ), + yaxis9=YAxis( + domain=[0.5375, 0.73125], + anchor='x9' + ) + ) + ) + + fig = tls.make_subplots(rows=4, cols=7) + assert fig == expected + +def test_a_lot_bottom_left(): + expected = Figure( + data=Data(), + layout=Layout( + xaxis1=XAxis( + domain=[0.0, 0.1183673469387755], + anchor='y1' + ), + xaxis10=XAxis( + domain=[0.29387755102040813, 0.4122448979591836], + anchor='y10' + ), + xaxis11=XAxis( + domain=[0.4408163265306122, 0.5591836734693877], + anchor='y11' + ), + xaxis12=XAxis( + domain=[0.5877551020408163, 0.7061224489795918], + anchor='y12' + ), + xaxis13=XAxis( + domain=[0.7346938775510203, 0.8530612244897958], + anchor='y13' + ), + xaxis14=XAxis( + domain=[0.8816326530612244, 0.9999999999999999], + anchor='y14' + ), + xaxis15=XAxis( + domain=[0.0, 0.1183673469387755], + anchor='y15' + ), + xaxis16=XAxis( + domain=[0.14693877551020407, 0.26530612244897955], + anchor='y16' + ), + xaxis17=XAxis( + domain=[0.29387755102040813, 0.4122448979591836], + anchor='y17' + ), + xaxis18=XAxis( + domain=[0.4408163265306122, 0.5591836734693877], + anchor='y18' + ), + xaxis19=XAxis( + domain=[0.5877551020408163, 0.7061224489795918], + anchor='y19' + ), + xaxis2=XAxis( + domain=[0.14693877551020407, 0.26530612244897955], + anchor='y2' + ), + xaxis20=XAxis( + domain=[0.7346938775510203, 0.8530612244897958], + anchor='y20' + ), + xaxis21=XAxis( + domain=[0.8816326530612244, 0.9999999999999999], + anchor='y21' + ), + xaxis22=XAxis( + domain=[0.0, 0.1183673469387755], + anchor='y22' + ), + xaxis23=XAxis( + domain=[0.14693877551020407, 0.26530612244897955], + anchor='y23' + ), + xaxis24=XAxis( + domain=[0.29387755102040813, 0.4122448979591836], + anchor='y24' + ), + xaxis25=XAxis( + domain=[0.4408163265306122, 0.5591836734693877], + anchor='y25' + ), + xaxis26=XAxis( + domain=[0.5877551020408163, 0.7061224489795918], + anchor='y26' + ), + xaxis27=XAxis( + domain=[0.7346938775510203, 0.8530612244897958], + anchor='y27' + ), + xaxis28=XAxis( + domain=[0.8816326530612244, 0.9999999999999999], + anchor='y28' + ), + xaxis3=XAxis( + domain=[0.29387755102040813, 0.4122448979591836], + anchor='y3' + ), + xaxis4=XAxis( + domain=[0.4408163265306122, 0.5591836734693877], + anchor='y4' + ), + xaxis5=XAxis( + domain=[0.5877551020408163, 0.7061224489795918], + anchor='y5' + ), + xaxis6=XAxis( + domain=[0.7346938775510203, 0.8530612244897958], + anchor='y6' + ), + xaxis7=XAxis( + domain=[0.8816326530612244, 0.9999999999999999], + anchor='y7' + ), + xaxis8=XAxis( + domain=[0.0, 0.1183673469387755], + anchor='y8' + ), + xaxis9=XAxis( + domain=[0.14693877551020407, 0.26530612244897955], + anchor='y9' + ), + yaxis1=YAxis( + domain=[0.0, 0.19375], + anchor='x1' + ), + yaxis10=YAxis( + domain=[0.26875, 0.4625], + anchor='x10' + ), + yaxis11=YAxis( + domain=[0.26875, 0.4625], + anchor='x11' + ), + yaxis12=YAxis( + domain=[0.26875, 0.4625], + anchor='x12' + ), + yaxis13=YAxis( + domain=[0.26875, 0.4625], + anchor='x13' + ), + yaxis14=YAxis( + domain=[0.26875, 0.4625], + anchor='x14' + ), + yaxis15=YAxis( + domain=[0.5375, 0.73125], + anchor='x15' + ), + yaxis16=YAxis( + domain=[0.5375, 0.73125], + anchor='x16' + ), + yaxis17=YAxis( + domain=[0.5375, 0.73125], + anchor='x17' + ), + yaxis18=YAxis( + domain=[0.5375, 0.73125], + anchor='x18' + ), + yaxis19=YAxis( + domain=[0.5375, 0.73125], + anchor='x19' + ), + yaxis2=YAxis( + domain=[0.0, 0.19375], + anchor='x2' + ), + yaxis20=YAxis( + domain=[0.5375, 0.73125], + anchor='x20' + ), + yaxis21=YAxis( + domain=[0.5375, 0.73125], + anchor='x21' + ), + yaxis22=YAxis( + domain=[0.8062499999999999, 0.9999999999999999], + anchor='x22' + ), + yaxis23=YAxis( + domain=[0.8062499999999999, 0.9999999999999999], + anchor='x23' + ), + yaxis24=YAxis( + domain=[0.8062499999999999, 0.9999999999999999], + anchor='x24' + ), + yaxis25=YAxis( + domain=[0.8062499999999999, 0.9999999999999999], + anchor='x25' + ), + yaxis26=YAxis( + domain=[0.8062499999999999, 0.9999999999999999], + anchor='x26' + ), + yaxis27=YAxis( + domain=[0.8062499999999999, 0.9999999999999999], + anchor='x27' + ), + yaxis28=YAxis( + domain=[0.8062499999999999, 0.9999999999999999], + anchor='x28' + ), + yaxis3=YAxis( + domain=[0.0, 0.19375], + anchor='x3' + ), + yaxis4=YAxis( + domain=[0.0, 0.19375], + anchor='x4' + ), + yaxis5=YAxis( + domain=[0.0, 0.19375], + anchor='x5' + ), + yaxis6=YAxis( + domain=[0.0, 0.19375], + anchor='x6' + ), + yaxis7=YAxis( + domain=[0.0, 0.19375], + anchor='x7' + ), + yaxis8=YAxis( + domain=[0.26875, 0.4625], + anchor='x8' + ), + yaxis9=YAxis( + domain=[0.26875, 0.4625], + anchor='x9' + ) + ) + ) + + fig = tls.make_subplots(rows=4, cols=7, start_cell='bottom-left') + assert fig == expected + +def test_spacing(): + expected = Figure( + data=Data(), + layout=Layout( + xaxis1=XAxis( + domain=[0.0, 0.3], + anchor='y1' + ), + xaxis2=XAxis( + domain=[0.35, 0.6499999999999999], + anchor='y2' + ), + xaxis3=XAxis( + domain=[0.7, 1.0], + anchor='y3' + ), + xaxis4=XAxis( + domain=[0.0, 0.3], + anchor='y4' + ), + xaxis5=XAxis( + domain=[0.35, 0.6499999999999999], + anchor='y5' + ), + xaxis6=XAxis( + domain=[0.7, 1.0], + anchor='y6' + ), + yaxis1=YAxis( + domain=[0.55, 1.0], + anchor='x1' + ), + yaxis2=YAxis( + domain=[0.55, 1.0], + anchor='x2' + ), + yaxis3=YAxis( + domain=[0.55, 1.0], + anchor='x3' + ), + yaxis4=YAxis( + domain=[0.0, 0.45], + anchor='x4' + ), + yaxis5=YAxis( + domain=[0.0, 0.45], + anchor='x5' + ), + yaxis6=YAxis( + domain=[0.0, 0.45], + anchor='x6' + ) + ) + ) + + fig = tls.make_subplots(rows=2, cols=3, + horizontal_spacing=.05, + vertical_spacing=.1) + assert fig == expected + +def test_specs(): + expected = Figure( + data=Data(), + layout=Layout( + xaxis1=XAxis( + domain=[0.0, 0.2888888888888889], + anchor='y1' + ), + xaxis2=XAxis( + domain=[0.0, 0.2888888888888889], + anchor='y2' + ), + xaxis3=XAxis( + domain=[0.35555555555555557, 0.6444444444444445], + anchor='y3' + ), + xaxis4=XAxis( + domain=[0.7111111111111111, 1.0], + anchor='y4' + ), + yaxis1=YAxis( + domain=[0.575, 1.0], + anchor='x1' + ), + yaxis2=YAxis( + domain=[0.0, 0.425], + anchor='x2' + ), + yaxis3=YAxis( + domain=[0.0, 0.425], + anchor='x3' + ), + yaxis4=YAxis( + domain=[0.0, 0.425], + anchor='x4' + ) + ) + ) + + fig = tls.make_subplots(rows=2, cols=3, + specs=[[{}, None, None], + [{}, {}, {}]]) + assert fig == expected + +def test_specs_bottom_left(): + expected = Figure( + data=Data(), + layout=Layout( + xaxis1=XAxis( + domain=[0.0, 0.2888888888888889], + anchor='y1' + ), + xaxis2=XAxis( + domain=[0.0, 0.2888888888888889], + anchor='y2' + ), + xaxis3=XAxis( + domain=[0.35555555555555557, 0.6444444444444445], + anchor='y3' + ), + xaxis4=XAxis( + domain=[0.7111111111111111, 1.0], + anchor='y4' + ), + yaxis1=YAxis( + domain=[0.0, 0.425], + anchor='x1' + ), + yaxis2=YAxis( + domain=[0.575, 1.0], + anchor='x2' + ), + yaxis3=YAxis( + domain=[0.575, 1.0], + anchor='x3' + ), + yaxis4=YAxis( + domain=[0.575, 1.0], + anchor='x4' + ) + ) + ) + + fig = tls.make_subplots(rows=2, cols=3, + specs=[[{}, None, None], + [{}, {}, {}]], + start_cell='bottom-left') + assert fig == expected + +def test_specs_colspan(): + expected = Figure( + data=Data(), + layout=Layout( + xaxis1=XAxis( + domain=[0.0, 1.0], + anchor='y1' + ), + xaxis2=XAxis( + domain=[0.0, 0.45], + anchor='y2' + ), + xaxis3=XAxis( + domain=[0.55, 1.0], + anchor='y3' + ), + xaxis4=XAxis( + domain=[0.0, 0.45], + anchor='y4' + ), + xaxis5=XAxis( + domain=[0.55, 1.0], + anchor='y5' + ), + yaxis1=YAxis( + domain=[0.7333333333333333, 1.0], + anchor='x1' + ), + yaxis2=YAxis( + domain=[0.36666666666666664, 0.6333333333333333], + anchor='x2' + ), + yaxis3=YAxis( + domain=[0.36666666666666664, 0.6333333333333333], + anchor='x3' + ), + yaxis4=YAxis( + domain=[0.0, 0.26666666666666666], + anchor='x4' + ), + yaxis5=YAxis( + domain=[0.0, 0.26666666666666666], + anchor='x5' + ) + ) + ) + + fig = tls.make_subplots(rows=3, cols=2, + specs=[[{'colspan':2}, None], + [{}, {}], + [{}, {}]]) + assert fig == expected + +def test_specs_rowspan(): + expected = Figure( + data=Data(), + layout=Layout( + xaxis1=XAxis( + domain=[0.0, 0.2888888888888889], + anchor='y1' + ), + xaxis2=XAxis( + domain=[0.35555555555555557, 0.6444444444444445], + anchor='y2' + ), + xaxis3=XAxis( + domain=[0.7111111111111111, 1.0], + anchor='y3' + ), + xaxis4=XAxis( + domain=[0.35555555555555557, 0.6444444444444445], + anchor='y4' + ), + xaxis5=XAxis( + domain=[0.7111111111111111, 1.0], + anchor='y5' + ), + xaxis6=XAxis( + domain=[0.35555555555555557, 1.0], + anchor='y6' + ), + yaxis1=YAxis( + domain=[0.0, 1.0], + anchor='x1' + ), + yaxis2=YAxis( + domain=[0.7333333333333333, 1.0], + anchor='x2' + ), + yaxis3=YAxis( + domain=[0.7333333333333333, 1.0], + anchor='x3' + ), + yaxis4=YAxis( + domain=[0.36666666666666664, 0.6333333333333333], + anchor='x4' + ), + yaxis5=YAxis( + domain=[0.36666666666666664, 0.6333333333333333], + anchor='x5' + ), + yaxis6=YAxis( + domain=[0.0, 0.26666666666666666], + anchor='x6' + ) + ) + ) + + fig = tls.make_subplots(rows=3, cols=3, + specs=[[{'rowspan': 3}, {}, {}], + [None, {}, {}], + [None, {'colspan': 2}, None]]) + assert fig == expected + +def test_specs_rowspan2(): + expected = Figure( + data=Data(), + layout=Layout( + xaxis1=XAxis( + domain=[0.0, 0.2888888888888889], + anchor='y1' + ), + xaxis2=XAxis( + domain=[0.35555555555555557, 0.6444444444444445], + anchor='y2' + ), + xaxis3=XAxis( + domain=[0.7111111111111111, 1.0], + anchor='y3' + ), + xaxis4=XAxis( + domain=[0.0, 0.6444444444444445], + anchor='y4' + ), + xaxis5=XAxis( + domain=[0.0, 1.0], + anchor='y5' + ), + yaxis1=YAxis( + domain=[0.7333333333333333, 1.0], + anchor='x1' + ), + yaxis2=YAxis( + domain=[0.7333333333333333, 1.0], + anchor='x2' + ), + yaxis3=YAxis( + domain=[0.36666666666666664, 1.0], + anchor='x3' + ), + yaxis4=YAxis( + domain=[0.36666666666666664, 0.6333333333333333], + anchor='x4' + ), + yaxis5=YAxis( + domain=[0.0, 0.26666666666666666], + anchor='x5' + ) + ) + ) + + fig = tls.make_subplots(rows=3, cols=3, + specs=[[{}, {}, {'rowspan': 2}], + [{'colspan': 2}, None, None], + [{'colspan': 3}, None, None]]) + assert fig == expected + +def test_specs_colspan_rowpan(): + expected = Figure( + data=Data(), + layout=Layout( + xaxis1=XAxis( + domain=[0.0, 0.6444444444444445], + anchor='y1' + ), + xaxis2=XAxis( + domain=[0.7111111111111111, 1.0], + anchor='y2' + ), + xaxis3=XAxis( + domain=[0.7111111111111111, 1.0], + anchor='y3' + ), + xaxis4=XAxis( + domain=[0.0, 0.2888888888888889], + anchor='y4' + ), + xaxis5=XAxis( + domain=[0.35555555555555557, 0.6444444444444445], + anchor='y5' + ), + xaxis6=XAxis( + domain=[0.7111111111111111, 1.0], + anchor='y6' + ), + yaxis1=YAxis( + domain=[0.36666666666666664, 1.0], + anchor='x1' + ), + yaxis2=YAxis( + domain=[0.7333333333333333, 1.0], + anchor='x2' + ), + yaxis3=YAxis( + domain=[0.36666666666666664, 0.6333333333333333], + anchor='x3' + ), + yaxis4=YAxis( + domain=[0.0, 0.26666666666666666], + anchor='x4' + ), + yaxis5=YAxis( + domain=[0.0, 0.26666666666666666], + anchor='x5' + ), + yaxis6=YAxis( + domain=[0.0, 0.26666666666666666], + anchor='x6' + ) + ) + ) + + fig = tls.make_subplots(rows=3, cols=3, + specs=[[{'colspan': 2, 'rowspan': 2}, None, {}], + [None, None, {}], + [{}, {}, {}]]) + assert fig == expected + +def test_specs_colspan_rowpan_bottom_left(): + expected = Figure( + data=Data(), + layout=Layout( + xaxis1=XAxis( + domain=[0.0, 0.6444444444444445], + anchor='y1' + ), + xaxis2=XAxis( + domain=[0.7111111111111111, 1.0], + anchor='y2' + ), + xaxis3=XAxis( + domain=[0.7111111111111111, 1.0], + anchor='y3' + ), + xaxis4=XAxis( + domain=[0.0, 0.2888888888888889], + anchor='y4' + ), + xaxis5=XAxis( + domain=[0.35555555555555557, 0.6444444444444445], + anchor='y5' + ), + xaxis6=XAxis( + domain=[0.7111111111111111, 1.0], + anchor='y6' + ), + yaxis1=YAxis( + domain=[0.0, 0.6333333333333333], + anchor='x1' + ), + yaxis2=YAxis( + domain=[0.0, 0.26666666666666666], + anchor='x2' + ), + yaxis3=YAxis( + domain=[0.36666666666666664, 0.6333333333333333], + anchor='x3' + ), + yaxis4=YAxis( + domain=[0.7333333333333333, 1.0], + anchor='x4' + ), + yaxis5=YAxis( + domain=[0.7333333333333333, 1.0], + anchor='x5' + ), + yaxis6=YAxis( + domain=[0.7333333333333333, 1.0], + anchor='x6' + ) + ) + ) + + fig = tls.make_subplots(rows=3, cols=3, + specs=[[{'colspan': 2, 'rowspan': 2}, None, {}], + [None, None, {}], + [{}, {}, {}]], + start_cell='bottom-left') + assert fig == expected + +def test_specs_is_3d(): + expected = Figure( + data=Data(), + layout=Layout( + scene1=Scene( + domain={'y': [0.575, 1.0], 'x': [0.0, 0.45]} + ), + scene2=Scene( + domain={'y': [0.0, 0.425], 'x': [0.0, 0.45]} + ), + xaxis1=XAxis( + domain=[0.55, 1.0], + anchor='y1' + ), + xaxis2=XAxis( + domain=[0.55, 1.0], + anchor='y2' + ), + yaxis1=YAxis( + domain=[0.575, 1.0], + anchor='x1' + ), + yaxis2=YAxis( + domain=[0.0, 0.425], + anchor='x2' + ) + ) + ) + + fig = tls.make_subplots(rows=2, cols=2, + specs=[[{'is_3d': True}, {}], + [{'is_3d': True}, {}]]) + assert fig == expected + +def test_specs_padding(): + expected = Figure( + data=Data(), + layout=Layout( + xaxis1=XAxis( + domain=[0.1, 0.5], + anchor='y1' + ), + xaxis2=XAxis( + domain=[0.5, 1.0], + anchor='y2' + ), + xaxis3=XAxis( + domain=[0.0, 0.5], + anchor='y3' + ), + xaxis4=XAxis( + domain=[0.5, 0.9], + anchor='y4' + ), + yaxis1=YAxis( + domain=[0.5, 1.0], + anchor='x1' + ), + yaxis2=YAxis( + domain=[0.7, 1.0], + anchor='x2' + ), + yaxis3=YAxis( + domain=[0.0, 0.3], + anchor='x3' + ), + yaxis4=YAxis( + domain=[0.0, 0.5], + anchor='x4' + ) + ) + ) + + fig = tls.make_subplots(rows=2, cols=2, + horizontal_spacing=0, vertical_spacing=0, + specs=[[{'l': 0.1}, {'b': 0.2}], + [{'t': 0.2}, {'r': 0.1}]]) + assert fig == expected + +def test_specs_padding_bottom_left(): + expected = Figure( + data=Data(), + layout=Layout( + xaxis1=XAxis( + domain=[0.1, 0.5], + anchor='y1' + ), + xaxis2=XAxis( + domain=[0.5, 1.0], + anchor='y2' + ), + xaxis3=XAxis( + domain=[0.0, 0.5], + anchor='y3' + ), + xaxis4=XAxis( + domain=[0.5, 0.9], + anchor='y4' + ), + yaxis1=YAxis( + domain=[0.0, 0.5], + anchor='x1' + ), + yaxis2=YAxis( + domain=[0.2, 0.5], + anchor='x2' + ), + yaxis3=YAxis( + domain=[0.5, 0.8], + anchor='x3' + ), + yaxis4=YAxis( + domain=[0.5, 1.0], + anchor='x4' + ) + ) + ) + + fig = tls.make_subplots(rows=2, cols=2, + horizontal_spacing=0, vertical_spacing=0, + specs=[[{'l': 0.1}, {'b': 0.2}], + [{'t': 0.2}, {'r': 0.1}]], + start_cell='bottom-left') + assert fig == expected + +def test_shared_xaxes(): + expected = Figure( + data=Data(), + layout=Layout( + xaxis1=XAxis( + domain=[0.0, 0.2888888888888889], + anchor='y4' + ), + xaxis2=XAxis( + domain=[0.35555555555555557, 0.6444444444444445], + anchor='y5' + ), + xaxis3=XAxis( + domain=[0.7111111111111111, 1.0], + anchor='y6' + ), + yaxis1=YAxis( + domain=[0.575, 1.0], + anchor='free', + position=0.0 + ), + yaxis2=YAxis( + domain=[0.575, 1.0], + anchor='free', + position=0.35555555555555557 + ), + yaxis3=YAxis( + domain=[0.575, 1.0], + anchor='free', + position=0.7111111111111111 + ), + yaxis4=YAxis( + domain=[0.0, 0.425], + anchor='x1' + ), + yaxis5=YAxis( + domain=[0.0, 0.425], + anchor='x2' + ), + yaxis6=YAxis( + domain=[0.0, 0.425], + anchor='x3' + ) + ) + ) + + fig = tls.make_subplots(rows=2, cols=3, shared_xaxes=True) + assert fig == expected + +def test_shared_xaxes_bottom_left(): + expected = Figure( + data=Data(), + layout=Layout( + xaxis1=XAxis( + domain=[0.0, 0.2888888888888889], + anchor='y1' + ), + xaxis2=XAxis( + domain=[0.35555555555555557, 0.6444444444444445], + anchor='y2' + ), + xaxis3=XAxis( + domain=[0.7111111111111111, 1.0], + anchor='y3' + ), + yaxis1=YAxis( + domain=[0.0, 0.425], + anchor='x1' + ), + yaxis2=YAxis( + domain=[0.0, 0.425], + anchor='x2' + ), + yaxis3=YAxis( + domain=[0.0, 0.425], + anchor='x3' + ), + yaxis4=YAxis( + domain=[0.575, 1.0], + anchor='free', + position=0.0 + ), + yaxis5=YAxis( + domain=[0.575, 1.0], + anchor='free', + position=0.35555555555555557 + ), + yaxis6=YAxis( + domain=[0.575, 1.0], + anchor='free', + position=0.7111111111111111 + ) + ) + ) + + fig = tls.make_subplots(rows=2, cols=3, + shared_xaxes=True, start_cell='bottom-left') + assert fig == expected + +def test_shared_yaxes(): + expected = Figure( + data=Data(), + layout=Layout( + xaxis1=XAxis( + domain=[0.0, 0.45], + anchor='y1' + ), + xaxis10=XAxis( + domain=[0.55, 1.0], + anchor='free' + ), + xaxis2=XAxis( + domain=[0.55, 1.0], + anchor='free', + position=0.848 + ), + xaxis3=XAxis( + domain=[0.0, 0.45], + anchor='y2' + ), + xaxis4=XAxis( + domain=[0.55, 1.0], + anchor='free', + position=0.636 + ), + xaxis5=XAxis( + domain=[0.0, 0.45], + anchor='y3' + ), + xaxis6=XAxis( + domain=[0.55, 1.0], + anchor='free', + position=0.424 + ), + xaxis7=XAxis( + domain=[0.0, 0.45], + anchor='y4' + ), + xaxis8=XAxis( + domain=[0.55, 1.0], + anchor='free', + position=0.212 + ), + xaxis9=XAxis( + domain=[0.0, 0.45], + anchor='y5' + ), + yaxis1=YAxis( + domain=[0.848, 1.0], + anchor='x1' + ), + yaxis2=YAxis( + domain=[0.636, 0.788], + anchor='x3' + ), + yaxis3=YAxis( + domain=[0.424, 0.576], + anchor='x5' + ), + yaxis4=YAxis( + domain=[0.212, 0.364], + anchor='x7' + ), + yaxis5=YAxis( + domain=[0.0, 0.152], + anchor='x9' + ) + ) + ) + + fig = tls.make_subplots(rows=5, cols=2, shared_yaxes=True) + assert fig == expected + +def test_shared_yaxes(): + expected = Figure( + data=Data(), + layout=Layout( + xaxis1=XAxis( + domain=[0.0, 0.45], + anchor='y1' + ), + xaxis10=XAxis( + domain=[0.55, 1.0], + anchor='free', + position=0.0 + ), + xaxis2=XAxis( + domain=[0.55, 1.0], + anchor='free', + position=0.848 + ), + xaxis3=XAxis( + domain=[0.0, 0.45], + anchor='y2' + ), + xaxis4=XAxis( + domain=[0.55, 1.0], + anchor='free', + position=0.636 + ), + xaxis5=XAxis( + domain=[0.0, 0.45], + anchor='y3' + ), + xaxis6=XAxis( + domain=[0.55, 1.0], + anchor='free', + position=0.424 + ), + xaxis7=XAxis( + domain=[0.0, 0.45], + anchor='y4' + ), + xaxis8=XAxis( + domain=[0.55, 1.0], + anchor='free', + position=0.212 + ), + xaxis9=XAxis( + domain=[0.0, 0.45], + anchor='y5' + ), + yaxis1=YAxis( + domain=[0.848, 1.0], + anchor='x1' + ), + yaxis2=YAxis( + domain=[0.636, 0.788], + anchor='x3' + ), + yaxis3=YAxis( + domain=[0.424, 0.576], + anchor='x5' + ), + yaxis4=YAxis( + domain=[0.212, 0.364], + anchor='x7' + ), + yaxis5=YAxis( + domain=[0.0, 0.152], + anchor='x9' + ) + ) + ) + + fig = tls.make_subplots(rows=5, cols=2, shared_yaxes=True) + assert fig == expected + +def test_shared_xaxes_yaxes(): + expected = Figure( + data=Data(), + layout=Layout( + xaxis1=XAxis( + domain=[0.0, 0.2888888888888889], + anchor='y3' + ), + xaxis2=XAxis( + domain=[0.35555555555555557, 0.6444444444444445], + anchor='free', + position=0.0 + ), + xaxis3=XAxis( + domain=[0.7111111111111111, 1.0], + anchor='free', + position=0.0 + ), + yaxis1=YAxis( + domain=[0.7333333333333333, 1.0], + anchor='free', + position=0.0 + ), + yaxis2=YAxis( + domain=[0.36666666666666664, 0.6333333333333333], + anchor='free', + position=0.0 + ), + yaxis3=YAxis( + domain=[0.0, 0.26666666666666666], + anchor='x1' + ) + ) + ) + + fig = tls.make_subplots(rows=3, cols=3, + shared_xaxes=True, shared_yaxes=True) + assert fig == expected + + +def test_shared_xaxes_yaxes_bottom_left(): + expected = Figure( + data=Data(), + layout=Layout( + xaxis1=XAxis( + domain=[0.0, 0.2888888888888889], + anchor='y1' + ), + xaxis2=XAxis( + domain=[0.35555555555555557, 0.6444444444444445], + anchor='free', + position=0.0 + ), + xaxis3=XAxis( + domain=[0.7111111111111111, 1.0], + anchor='free', + position=0.0 + ), + yaxis1=YAxis( + domain=[0.0, 0.26666666666666666], + anchor='x1' + ), + yaxis2=YAxis( + domain=[0.36666666666666664, 0.6333333333333333], + anchor='free', + position=0.0 + ), + yaxis3=YAxis( + domain=[0.7333333333333333, 1.0], + anchor='free', + position=0.0 + ) + ) + ) + + fig = tls.make_subplots(rows=3, cols=3, + shared_xaxes=True, shared_yaxes=True, + start_cell='bottom-left') + assert fig == expected + +def test_shared_axes_list(): + expected = Figure( + data=Data(), + layout=Layout( + xaxis1=XAxis( + domain=[0.0, 0.45], + anchor='y1' + ), + xaxis2=XAxis( + domain=[0.55, 1.0], + anchor='free', + position=0.575 + ), + xaxis3=XAxis( + domain=[0.55, 1.0], + anchor='y3' + ), + yaxis1=YAxis( + domain=[0.575, 1.0], + anchor='x1' + ), + yaxis2=YAxis( + domain=[0.0, 0.425], + anchor='free', + position=0.0 + ), + yaxis3=YAxis( + domain=[0.0, 0.425], + anchor='x3' + ) + ) + ) + + fig = tls.make_subplots(rows=2, cols=2, + shared_xaxes=[(1,1), (2,1)], + shared_yaxes=[(1,1), (1,2)]) + assert fig == expected + +def test_shared_axes_list_bottom_left(): + expected = Figure( + data=Data(), + layout=Layout( + xaxis1=XAxis( + domain=[0.0, 0.45], + anchor='y1' + ), + xaxis2=XAxis( + domain=[0.55, 1.0], + anchor='free', + position=0.0 + ), + xaxis3=XAxis( + domain=[0.55, 1.0], + anchor='y3' + ), + yaxis1=YAxis( + domain=[0.0, 0.425], + anchor='x1' + ), + yaxis2=YAxis( + domain=[0.575, 1.0], + anchor='free', + position=0.0 + ), + yaxis3=YAxis( + domain=[0.575, 1.0], + anchor='x3' + ) + ) + ) + + fig = tls.make_subplots(rows=2, cols=2, + shared_xaxes=[(1,1), (2,1)], + shared_yaxes=[(1,1), (1,2)], + start_cell='bottom-left') + assert fig == expected + +def test_shared_axes_list_of_lists(): + expected = Figure( + data=Data(), + layout=Layout( + xaxis1=XAxis( + domain=[0.0, 0.2888888888888889], + anchor='y1' + ), + xaxis2=XAxis( + domain=[0.35555555555555557, 0.6444444444444445], + anchor='y2' + ), + xaxis3=XAxis( + domain=[0.7111111111111111, 1.0], + anchor='y3' + ), + xaxis4=XAxis( + domain=[0.35555555555555557, 0.6444444444444445], + anchor='y5' + ), + yaxis1=YAxis( + domain=[0.575, 1.0], + anchor='x1' + ), + yaxis2=YAxis( + domain=[0.575, 1.0], + anchor='x2' + ), + yaxis3=YAxis( + domain=[0.575, 1.0], + anchor='x3' + ), + yaxis4=YAxis( + domain=[0.0, 0.425], + anchor='free', + position=0.0 + ), + yaxis5=YAxis( + domain=[0.0, 0.425], + anchor='x4' + ), + yaxis6=YAxis( + domain=[0.0, 0.425], + anchor='free', + position=0.7111111111111111 + ) + ) + ) + + fig = tls.make_subplots(rows=2, cols=3, + shared_xaxes=[[(1,1), (2,1)], + [(1,3), (2,3)]]) + assert fig == expected + + +def test_shared_axes_list_of_lists_bottom_left(): + expected = Figure( + data=Data(), + layout=Layout( + xaxis1=XAxis( + domain=[0.0, 0.2888888888888889], + anchor='y1' + ), + xaxis2=XAxis( + domain=[0.35555555555555557, 0.6444444444444445], + anchor='y2' + ), + xaxis3=XAxis( + domain=[0.7111111111111111, 1.0], + anchor='y3' + ), + xaxis4=XAxis( + domain=[0.35555555555555557, 0.6444444444444445], + anchor='y5' + ), + yaxis1=YAxis( + domain=[0.0, 0.425], + anchor='x1' + ), + yaxis2=YAxis( + domain=[0.0, 0.425], + anchor='x2' + ), + yaxis3=YAxis( + domain=[0.0, 0.425], + anchor='x3' + ), + yaxis4=YAxis( + domain=[0.575, 1.0], + anchor='free', + position=0.0 + ), + yaxis5=YAxis( + domain=[0.575, 1.0], + anchor='x4' + ), + yaxis6=YAxis( + domain=[0.575, 1.0], + anchor='free', + position=0.7111111111111111 + ) + ) + ) + + fig = tls.make_subplots(rows=2, cols=3, + shared_xaxes=[[(1,1), (2,1)], + [(1,3), (2,3)]], + start_cell='bottom-left') + assert fig == expected + +def test_insets(): + expected = Figure( + data=Data(), + layout=Layout( + xaxis1=XAxis( + domain=[0.0, 0.45], + anchor='y1' + ), + xaxis2=XAxis( + domain=[0.55, 1.0], + anchor='y2' + ), + xaxis3=XAxis( + domain=[0.0, 0.45], + anchor='y3' + ), + xaxis4=XAxis( + domain=[0.55, 1.0], + anchor='y4' + ), + xaxis5=XAxis( + domain=[0.865, 0.955], + anchor='y5' + ), + yaxis1=YAxis( + domain=[0.575, 1.0], + anchor='x1' + ), + yaxis2=YAxis( + domain=[0.575, 1.0], + anchor='x2' + ), + yaxis3=YAxis( + domain=[0.0, 0.425], + anchor='x3' + ), + yaxis4=YAxis( + domain=[0.0, 0.425], + anchor='x4' + ), + yaxis5=YAxis( + domain=[0.085, 0.2975], + anchor='x5' + ) + ) + ) + + fig = tls.make_subplots(rows=2, cols=2, + insets=[{'cell': (2,2), + 'l': 0.7, 'w': 0.2, + 'b': 0.2, 'h': 0.5}]) + assert fig == expected + +def test_insets_bottom_left(): + expected = Figure( + data=Data(), + layout=Layout( + xaxis1=XAxis( + domain=[0.0, 0.45], + anchor='y1' + ), + xaxis2=XAxis( + domain=[0.55, 1.0], + anchor='y2' + ), + xaxis3=XAxis( + domain=[0.0, 0.45], + anchor='y3' + ), + xaxis4=XAxis( + domain=[0.55, 1.0], + anchor='y4' + ), + xaxis5=XAxis( + domain=[0.865, 0.955], + anchor='y5' + ), + yaxis1=YAxis( + domain=[0.0, 0.425], + anchor='x1' + ), + yaxis2=YAxis( + domain=[0.0, 0.425], + anchor='x2' + ), + yaxis3=YAxis( + domain=[0.575, 1.0], + anchor='x3' + ), + yaxis4=YAxis( + domain=[0.575, 1.0], + anchor='x4' + ), + yaxis5=YAxis( + domain=[0.6599999999999999, 0.8724999999999999], + anchor='x5' + ) + ) + ) + + fig = tls.make_subplots(rows=2, cols=2, + insets=[{'cell': (2,2), + 'l': 0.7, 'w': 0.2, + 'b': 0.2, 'h': 0.5}], + start_cell='bottom-left') + assert fig == expected + +def test_insets_multiple(): + expected = Figure( + data=Data(), + layout=Layout( + xaxis1=XAxis( + domain=[0.0, 1.0], + anchor='y1' + ), + xaxis2=XAxis( + domain=[0.0, 1.0], + anchor='y2' + ), + xaxis3=XAxis( + domain=[0.8, 1.0], + anchor='y3' + ), + xaxis4=XAxis( + domain=[0.8, 1.0], + anchor='y4' + ), + yaxis1=YAxis( + domain=[0.575, 1.0], + anchor='x1' + ), + yaxis2=YAxis( + domain=[0.0, 0.425], + anchor='x2' + ), + yaxis3=YAxis( + domain=[0.575, 1.0], + anchor='x3' + ), + yaxis4=YAxis( + domain=[0.0, 0.425], + anchor='x4' + ) + ) + ) + + fig = tls.make_subplots(rows=2, + insets=[{'cell': (1,1), 'l':0.8}, + {'cell': (2,1), 'l':0.8}]) + assert fig == expected + +def test_insets_multiple_bottom_left(): + expected = Figure( + data=Data(), + layout=Layout( + xaxis1=XAxis( + domain=[0.0, 1.0], + anchor='y1' + ), + xaxis2=XAxis( + domain=[0.0, 1.0], + anchor='y2' + ), + xaxis3=XAxis( + domain=[0.8, 1.0], + anchor='y3' + ), + xaxis4=XAxis( + domain=[0.8, 1.0], + anchor='y4' + ), + yaxis1=YAxis( + domain=[0.0, 0.425], + anchor='x1' + ), + yaxis2=YAxis( + domain=[0.575, 1.0], + anchor='x2' + ), + yaxis3=YAxis( + domain=[0.0, 0.425], + anchor='x3' + ), + yaxis4=YAxis( + domain=[0.575, 1.0], + anchor='x4' + ) + ) + ) + + fig = tls.make_subplots(rows=2, + insets=[{'cell': (1,1), 'l':0.8}, + {'cell': (2,1), 'l':0.8}], + start_cell='bottom-left') + assert fig == expected diff --git a/plotly/tools.py b/plotly/tools.py index f7bdb1c492a..59b9f5fd03c 100644 --- a/plotly/tools.py +++ b/plotly/tools.py @@ -386,40 +386,76 @@ def mpl_to_plotly(fig, resize=False, strip_style=False, verbose=False): ### graph_objs related tools ### -# TODO: Scale spacing based on number of plots and figure size -def get_subplots(rows=1, columns=1, horizontal_spacing=0.1, - vertical_spacing=0.15, print_grid=False): +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')] + # 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) + # print out string showing the subplot grid you've put in the layout + fig = tools.get_subplots(rows=3, columns=2, print_grid=True) - key (types, default=default): - description. + Keywords arguments with constant defaults: - rows (int, default=1): + rows (kwarg, int greater than 0, default=1): Number of rows, evenly spaced vertically on the figure. - columns (int, default=1): + columns (kwarg, int greater than 0, default=1): Number of columns, evenly spaced horizontally on the figure. - horizontal_spacing (float in [0,1], default=0.1): + horizontal_spacing (kwarg, float in [0,1], default=0.1): Space between subplot columns. Applied to all columns. - vertical_spacing (float in [0,1], default=0.05): + vertical_spacing (kwarg, float in [0,1], default=0.05): Space between subplot rows. Applied to all rows. - print_grid (True | False, default=False): - If True, prints a tab-delimited string representation of your plot grid. + 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. """ + + 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 @@ -441,6 +477,7 @@ def get_subplots(rows=1, columns=1, horizontal_spacing=0.1, 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 = "" @@ -452,9 +489,623 @@ def get_subplots(rows=1, columns=1, horizontal_spacing=0.1, 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')] + + 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) + + 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) + """ + + # 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'] + 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 / cols + try: + horizontal_spacing = float(kwargs['horizontal_spacing']) + except KeyError: + horizontal_spacing = 0.2 / cols + try: + vertical_spacing = float(kwargs['vertical_spacing']) + except KeyError: + 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 + y_anchor = 'free' # TODO covers all cases? + + if isinstance(shared_yaxes, bool): + if shared_yaxes: + if c != 0: + y_anchor = False + x_anchor = 'free' + if shared_xaxes and r != ANCHOR_ROW: # TODO all cases? + x_anchor = False + return x_anchor, y_anchor + + elif isinstance(shared_yaxes, list): + if isinstance(shared_yaxes[0], tuple): + shared_yaxes = [shared_yaxes] # TODO put this elsewhere + for shared_yaxis in shared_yaxes: + if (r+1, c+1) in shared_yaxis[1:]: + y_anchor = False + x_anchor = 'free' # TODO covers all cases? + + return x_anchor, y_anchor + + # Function pasting x/y domains in layout object (2d case) + def _add_domain(layout, x_or_y, label, domain, anchor, position): + name = label[0] + 'axis' + label[1:] + graph_obj = '{X_or_Y}Axis'.format(X_or_Y=x_or_y.upper()) + axis = getattr(graph_objs, graph_obj)(domain=domain) + if anchor: + axis['anchor'] = anchor + if isinstance(position, float): + axis['position'] = position + layout[name] = axis + + # Function pasting x/y domains in layout object (3d case) + def _add_domain_is_3d(layout, s_label, x_domain, y_domain): + scene = graph_objs.Scene(domain={'x': x_domain, 'y': y_domain}) + layout[s_label] = scene + + x_cnt = y_cnt = s_cnt = 1 # subplot axis/scene counters + + # Loop through specs -- (r, c) <-> (row, col) + for r, spec_row in enumerate(specs): + for c, spec in enumerate(spec_row): + + if spec is None: # skip over None cells + continue + + c_spanned = c + spec['colspan'] - 1 # get spanned c + r_spanned = r + spec['rowspan'] - 1 # get spanned r + + # Throw exception if 'colspan' | 'rowspan' is too large for grid + if c_spanned >= cols: + raise Exception("Some 'colspan' value is too large for " + "this subplot grid.") + if r_spanned >= rows: + raise Exception("Some 'rowspan' value is too large for " + "this subplot grid.") + + # Get x domain using grid and colspan + x_s = grid[r][c][0] + spec['l'] + x_e = grid[r][c_spanned][0] + width - spec['r'] + x_domain = [x_s, x_e] + + # Get y domain (dep. on row_dir) using grid & r_spanned + if ROW_DIR > 0: + y_s = grid[r][c][1] + spec['b'] + y_e = grid[r_spanned][c][1] + height - spec['t'] + else: + y_s = grid[r_spanned][c][1] + spec['b'] + y_e = grid[r][c][1] + height - spec['t'] + y_domain = [y_s, y_e] + + if spec['is_3d']: + + # Add scene to layout + s_label = 'scene{0}'.format(s_cnt) + _add_domain_is_3d(layout, s_label, x_domain, y_domain) + grid_ref[r][c] = (s_label, ) + s_cnt += 1 + + else: + + # Get axis label and anchor + x_label = _get_label('x', r, c, x_cnt, shared_xaxes) + y_label = _get_label('y', r, c, y_cnt, shared_yaxes) + x_anchor, y_anchor = _get_anchors(r, c, + x_cnt, y_cnt, + shared_xaxes, + shared_yaxes) + + # Add a xaxis to layout (N.B anchor == False -> no axis) + if x_anchor: + if x_anchor == 'free': + x_position = y_domain[0] + else: + x_position = False + _add_domain(layout, 'x', x_label, x_domain, + x_anchor, x_position) + x_cnt += 1 + + # Add a yaxis to layout (N.B anchor == False -> no axis) + if y_anchor: + if y_anchor == 'free': + y_position = x_domain[0] + else: + y_position = False + _add_domain(layout, 'y', y_label, y_domain, + y_anchor, y_position) + y_cnt += 1 + + grid_ref[r][c] = (x_label, y_label) # fill in ref + + # Loop through insets + if insets: + for i_inset, inset in enumerate(insets): + + r = inset['cell'][0] - 1 + c = inset['cell'][1] - 1 + + # Throw exception if r | c is out of range + if not (0 <= r < rows): + raise Exception("Some 'cell' row value is out of range. " + "Note: the starting cell is (1, 1)") + if not (0 <= c < cols): + raise Exception("Some 'cell' col value is out of range. " + "Note: the starting cell is (1, 1)") + + # Get inset x domain using grid + x_s = grid[r][c][0] + inset['l'] * width + if inset['w'] == 'to_end': + x_e = grid[r][c][0] + width + else: + x_e = x_s + inset['w'] * width + x_domain = [x_s, x_e] + + # Get inset y domain using grid + y_s = grid[r][c][1] + inset['b'] * height + if inset['h'] == 'to_end': + y_e = grid[r][c][1] + height + else: + y_e = y_s + inset['h'] * height + y_domain = [y_s, y_e] + + if inset['is_3d']: + + # Add scene to layout + s_label = 'scene{0}'.format(s_cnt) + _add_domain_is_3d(layout, s_label, x_domain, y_domain) + insets_ref[i_inset] = (s_label, ) + s_cnt += 1 + + else: + + # Get axis label and anchor + x_label = _get_label('x', False, False, x_cnt, False) + y_label = _get_label('y', False, False, y_cnt, False) + x_anchor, y_anchor = _get_anchors(r, c, + x_cnt, y_cnt, + False, False) + + # Add a xaxis to layout (N.B insets always have anchors) + _add_domain(layout, 'x', x_label, x_domain, x_anchor, False) + x_cnt += 1 + + # Add a yayis to layout (N.B insets always have anchors) + _add_domain(layout, 'y', y_label, y_domain, y_anchor, False) + y_cnt += 1 + + insets_ref[i_inset] = (x_label, y_label) # fill in ref + + # [grid_str] Set the grid's string representation + sp = " " # space between cell + s_str = "[ " # cell start string + e_str = " ]" # cell end string + colspan_str = ' -' # colspan string + rowspan_str = ' |' # rowspan string + empty_str = ' (empty) ' # empty cell string + + # Init grid_str with intro message + grid_str = "This is the format of your plot grid:\n" + + # Init tmp list of lists of strings (sorta like 'grid_ref' but w/ strings) + _tmp = [['' for c in range(cols)] for r in range(rows)] + + # Define cell string as function of (r, c) and grid_ref + def _get_cell_str(r, c, ref): + return '({r},{c}) {ref}'.format(r=r+1, c=c+1, ref=','.join(ref)) + + # Find max len of _cell_str, add define a padding function + cell_len = max([len(_get_cell_str(r, c, ref)) + for r, row_ref in enumerate(grid_ref) + for c, ref in enumerate(row_ref) + if ref]) + len(s_str) + len(e_str) + + def _pad(s, cell_len=cell_len): + return ' ' * (cell_len - len(s)) + + # Loop through specs, fill in _tmp + for r, spec_row in enumerate(specs): + for c, spec in enumerate(spec_row): + + ref = grid_ref[r][c] + if ref is None: + if _tmp[r][c] == '': + _tmp[r][c] = empty_str + _pad(empty_str) + continue + + cell_str = s_str + _get_cell_str(r, c, ref) + + if spec['colspan'] > 1: + for cc in range(1, spec['colspan']-1): + _tmp[r][c+cc] = colspan_str + _pad(colspan_str) + _tmp[r][c+spec['colspan']-1] = ( + colspan_str + _pad(colspan_str + e_str)) + e_str + else: + cell_str += e_str + + if spec['rowspan'] > 1: + for rr in range(1, spec['rowspan']-1): + _tmp[r+rr][c] = rowspan_str + _pad(rowspan_str) + for cc in range(spec['colspan']): + _tmp[r+spec['rowspan']-1][c+cc] = ( + rowspan_str + _pad(rowspan_str)) + + _tmp[r][c] = cell_str + _pad(cell_str) + + # Append grid_str using data from _tmp in the correct order + for r in row_seq[::-1]: + grid_str += sp.join(_tmp[r]) + '\n' + + # Append grid_str to include insets info + if insets: + grid_str += "\nWith insets:\n" + for i_inset, inset in enumerate(insets): + + r = inset['cell'][0] - 1 + c = inset['cell'][1] - 1 + ref = grid_ref[r][c] + + grid_str += ( + s_str + ','.join(insets_ref[i_inset]) + e_str + + ' over ' + + s_str + _get_cell_str(r, c, ref) + e_str + '\n' + ) + + if print_grid: + print(grid_str) + + fig = graph_objs.Figure(layout=layout) + + fig._grid_ref = grid_ref + fig._grid_str = grid_str + + return fig + + def get_valid_graph_obj(obj, obj_type=None): """Returns a new graph object that is guaranteed to pass validate(). diff --git a/plotly/version.py b/plotly/version.py index 250ba523e7e..77f1c8e63c9 100644 --- a/plotly/version.py +++ b/plotly/version.py @@ -1 +1 @@ -__version__ = '1.4.14' +__version__ = '1.5.0' diff --git a/submodules/graph_reference b/submodules/graph_reference index 9d31f815edc..fb337adf934 160000 --- a/submodules/graph_reference +++ b/submodules/graph_reference @@ -1 +1 @@ -Subproject commit 9d31f815edc59bbf1a9533e241cc4f41ed859fdc +Subproject commit fb337adf934ea06242fd826ed1d613a492c6873e