Skip to content

Commit 54a4755

Browse files
authored
Merge pull request #885 from plotly/subplot_proportions
Subplot proportions
2 parents 2b3de41 + 6498770 commit 54a4755

File tree

3 files changed

+130
-41
lines changed

3 files changed

+130
-41
lines changed

CHANGELOG.md

+4
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,10 @@
22
All notable changes to this project will be documented in this file.
33
This project adheres to [Semantic Versioning](http://semver.org/).
44

5+
## [Unreleased] - TBA
6+
### Added
7+
-`column_width` and `row_width` parameters for `plotly.tools.make_subplots`. Call `help(plotly.tools.make_subplots)` for documentation.
8+
59
## [2.2.2] - 2017-11-23
610
### Added
711
- bullet chart figure factory. Call `help(plotly.figure_factory.create_bullet)` for examples and how to get started making bullet charts with the API.

plotly/tests/test_core/test_tools/test_make_subplots.py

+40-24
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,22 @@ def test_insets_wrong_cell_col(self):
8383
with self.assertRaises(Exception):
8484
tls.make_subplots(insets=([{'cell': (1, 0)}]))
8585

86+
def test_column_width_not_list(self):
87+
with self.assertRaises(Exception):
88+
tls.make_subplots(rows=2, cols=2, column_width='not gonna work')
89+
90+
def test_column_width_not_list_of_correct_numbers(self):
91+
with self.assertRaises(Exception):
92+
tls.make_subplots(rows=2, cols=2, column_width=[0])
93+
94+
def test_row_width_not_list(self):
95+
with self.assertRaises(Exception):
96+
tls.make_subplots(rows=2, cols=2, row_width='not gonna work')
97+
98+
def test_row_width_not_list_of_correct_numbers(self):
99+
with self.assertRaises(Exception):
100+
tls.make_subplots(rows=2, cols=2, row_width=[1])
101+
86102
def test_single_plot(self):
87103
expected = Figure(
88104
data=Data(),
@@ -193,7 +209,7 @@ def test_a_lot(self):
193209
anchor='y12'
194210
),
195211
xaxis13=XAxis(
196-
domain=[0.7346938775510203, 0.8530612244897958],
212+
domain=[0.7346938775510204, 0.8530612244897959],
197213
anchor='y13'
198214
),
199215
xaxis14=XAxis(
@@ -225,7 +241,7 @@ def test_a_lot(self):
225241
anchor='y2'
226242
),
227243
xaxis20=XAxis(
228-
domain=[0.7346938775510203, 0.8530612244897958],
244+
domain=[0.7346938775510204, 0.8530612244897959],
229245
anchor='y20'
230246
),
231247
xaxis21=XAxis(
@@ -253,7 +269,7 @@ def test_a_lot(self):
253269
anchor='y26'
254270
),
255271
xaxis27=XAxis(
256-
domain=[0.7346938775510203, 0.8530612244897958],
272+
domain=[0.7346938775510204, 0.8530612244897959],
257273
anchor='y27'
258274
),
259275
xaxis28=XAxis(
@@ -273,7 +289,7 @@ def test_a_lot(self):
273289
anchor='y5'
274290
),
275291
xaxis6=XAxis(
276-
domain=[0.7346938775510203, 0.8530612244897958],
292+
domain=[0.7346938775510204, 0.8530612244897959],
277293
anchor='y6'
278294
),
279295
xaxis7=XAxis(
@@ -289,7 +305,7 @@ def test_a_lot(self):
289305
anchor='y9'
290306
),
291307
yaxis1=YAxis(
292-
domain=[0.8062499999999999, 0.9999999999999999],
308+
domain=[0.80625, 1.0],
293309
anchor='x1'
294310
),
295311
yaxis10=YAxis(
@@ -333,7 +349,7 @@ def test_a_lot(self):
333349
anchor='x19'
334350
),
335351
yaxis2=YAxis(
336-
domain=[0.8062499999999999, 0.9999999999999999],
352+
domain=[0.80625, 1.0],
337353
anchor='x2'
338354
),
339355
yaxis20=YAxis(
@@ -373,23 +389,23 @@ def test_a_lot(self):
373389
anchor='x28'
374390
),
375391
yaxis3=YAxis(
376-
domain=[0.8062499999999999, 0.9999999999999999],
392+
domain=[0.80625, 1.0],
377393
anchor='x3'
378394
),
379395
yaxis4=YAxis(
380-
domain=[0.8062499999999999, 0.9999999999999999],
396+
domain=[0.80625, 1.0],
381397
anchor='x4'
382398
),
383399
yaxis5=YAxis(
384-
domain=[0.8062499999999999, 0.9999999999999999],
400+
domain=[0.80625, 1.0],
385401
anchor='x5'
386402
),
387403
yaxis6=YAxis(
388-
domain=[0.8062499999999999, 0.9999999999999999],
404+
domain=[0.80625, 1.0],
389405
anchor='x6'
390406
),
391407
yaxis7=YAxis(
392-
domain=[0.8062499999999999, 0.9999999999999999],
408+
domain=[0.80625, 1.0],
393409
anchor='x7'
394410
),
395411
yaxis8=YAxis(
@@ -426,7 +442,7 @@ def test_a_lot_bottom_left(self):
426442
anchor='y12'
427443
),
428444
xaxis13=XAxis(
429-
domain=[0.7346938775510203, 0.8530612244897958],
445+
domain=[0.7346938775510204, 0.8530612244897959],
430446
anchor='y13'
431447
),
432448
xaxis14=XAxis(
@@ -458,7 +474,7 @@ def test_a_lot_bottom_left(self):
458474
anchor='y2'
459475
),
460476
xaxis20=XAxis(
461-
domain=[0.7346938775510203, 0.8530612244897958],
477+
domain=[0.7346938775510204, 0.8530612244897959],
462478
anchor='y20'
463479
),
464480
xaxis21=XAxis(
@@ -486,7 +502,7 @@ def test_a_lot_bottom_left(self):
486502
anchor='y26'
487503
),
488504
xaxis27=XAxis(
489-
domain=[0.7346938775510203, 0.8530612244897958],
505+
domain=[0.7346938775510204, 0.8530612244897959],
490506
anchor='y27'
491507
),
492508
xaxis28=XAxis(
@@ -506,7 +522,7 @@ def test_a_lot_bottom_left(self):
506522
anchor='y5'
507523
),
508524
xaxis6=XAxis(
509-
domain=[0.7346938775510203, 0.8530612244897958],
525+
domain=[0.7346938775510204, 0.8530612244897959],
510526
anchor='y6'
511527
),
512528
xaxis7=XAxis(
@@ -578,31 +594,31 @@ def test_a_lot_bottom_left(self):
578594
anchor='x21'
579595
),
580596
yaxis22=YAxis(
581-
domain=[0.8062499999999999, 0.9999999999999999],
597+
domain=[0.80625, 1.0],
582598
anchor='x22'
583599
),
584600
yaxis23=YAxis(
585-
domain=[0.8062499999999999, 0.9999999999999999],
601+
domain=[0.80625, 1.0],
586602
anchor='x23'
587603
),
588604
yaxis24=YAxis(
589-
domain=[0.8062499999999999, 0.9999999999999999],
605+
domain=[0.80625, 1.0],
590606
anchor='x24'
591607
),
592608
yaxis25=YAxis(
593-
domain=[0.8062499999999999, 0.9999999999999999],
609+
domain=[0.80625, 1.0],
594610
anchor='x25'
595611
),
596612
yaxis26=YAxis(
597-
domain=[0.8062499999999999, 0.9999999999999999],
613+
domain=[0.80625, 1.0],
598614
anchor='x26'
599615
),
600616
yaxis27=YAxis(
601-
domain=[0.8062499999999999, 0.9999999999999999],
617+
domain=[0.80625, 1.0],
602618
anchor='x27'
603619
),
604620
yaxis28=YAxis(
605-
domain=[0.8062499999999999, 0.9999999999999999],
621+
domain=[0.80625, 1.0],
606622
anchor='x28'
607623
),
608624
yaxis3=YAxis(
@@ -1308,7 +1324,7 @@ def test_shared_yaxes(self):
13081324
xaxis4=XAxis(
13091325
domain=[0.55, 1.0],
13101326
anchor='free',
1311-
position=0.636
1327+
position=0.6359999999999999
13121328
),
13131329
xaxis5=XAxis(
13141330
domain=[0.0, 0.45],
@@ -1337,7 +1353,7 @@ def test_shared_yaxes(self):
13371353
anchor='x1'
13381354
),
13391355
yaxis2=YAxis(
1340-
domain=[0.636, 0.788],
1356+
domain=[0.6359999999999999, 0.7879999999999999],
13411357
anchor='x3'
13421358
),
13431359
yaxis3=YAxis(

plotly/tools.py

+86-17
Original file line numberDiff line numberDiff line change
@@ -772,6 +772,32 @@ def make_subplots(rows=1, cols=1,
772772
in fraction of cell height
773773
* h (float or 'to_end', default='to_end') inset height
774774
in fraction of cell height ('to_end': to cell top edge)
775+
776+
column_width (kwarg, list of numbers)
777+
Column_width specifications
778+
779+
- Functions similarly to `column_width` of `plotly.graph_objs.Table`.
780+
Specify a list that contains numbers where the amount of numbers in
781+
the list is equal to `cols`.
782+
783+
- The numbers in the list indicate the proportions that each column
784+
domains take across the full horizontal domain excluding padding.
785+
786+
- For example, if columns_width=[3, 1], horizontal_spacing=0, and
787+
cols=2, the domains for each column would be [0. 0.75] and [0.75, 1]
788+
789+
row_width (kwargs, list of numbers)
790+
Row_width specifications
791+
792+
- Functions similarly to `column_width`. Specify a list that contains
793+
numbers where the amount of numbers in the list is equal to `rows`.
794+
795+
- The numbers in the list indicate the proportions that each row
796+
domains take along the full vertical domain excluding padding.
797+
798+
- For example, if row_width=[3, 1], vertical_spacing=0, and
799+
cols=2, the domains for each row from top to botton would be
800+
[0. 0.75] and [0.75, 1]
775801
"""
776802
# TODO: protected until #282
777803
from plotly.graph_objs import graph_objs
@@ -807,7 +833,8 @@ def make_subplots(rows=1, cols=1,
807833

808834
# Throw exception if non-valid kwarg is sent
809835
VALID_KWARGS = ['horizontal_spacing', 'vertical_spacing',
810-
'specs', 'insets', 'subplot_titles']
836+
'specs', 'insets', 'subplot_titles', 'column_width',
837+
'row_width']
811838
for key in kwargs.keys():
812839
if key not in VALID_KWARGS:
813840
raise Exception("Invalid keyword argument: '{0}'".format(key))
@@ -907,9 +934,47 @@ def _checks(item, defaults):
907934
)
908935
_check_keys_and_fill('insets', insets, INSET_defaults)
909936

910-
# Set width & height of each subplot cell (excluding padding)
911-
width = (1. - horizontal_spacing * (cols - 1)) / cols
912-
height = (1. - vertical_spacing * (rows - 1)) / rows
937+
# set heights (with 'column_width')
938+
try:
939+
column_width = kwargs['column_width']
940+
if not isinstance(column_width, list) or len(column_width) != cols:
941+
raise Exception(
942+
"Keyword argument 'column_width' must be a list with {} "
943+
"numbers in it, the number of subplot cols.".format(cols)
944+
)
945+
except KeyError:
946+
column_width = None
947+
948+
if column_width:
949+
cum_sum = float(sum(column_width))
950+
widths = []
951+
for w in column_width:
952+
widths.append(
953+
(1. - horizontal_spacing * (cols - 1)) * (w / cum_sum)
954+
)
955+
else:
956+
widths = [(1. - horizontal_spacing * (cols - 1)) / cols] * cols
957+
958+
# set widths (with 'row_width')
959+
try:
960+
row_width = kwargs['row_width']
961+
if not isinstance(row_width, list) or len(row_width) != rows:
962+
raise Exception(
963+
"Keyword argument 'row_width' must be a list with {} "
964+
"numbers in it, the number of subplot rows.".format(rows)
965+
)
966+
except KeyError:
967+
row_width = None
968+
969+
if row_width:
970+
cum_sum = float(sum(row_width))
971+
heights = []
972+
for h in row_width:
973+
heights.append(
974+
(1. - vertical_spacing * (rows - 1)) * (h / cum_sum)
975+
)
976+
else:
977+
heights = [(1. - vertical_spacing * (rows - 1)) / rows] * rows
913978

914979
# Built row/col sequence using 'row_dir' and 'col_dir'
915980
COL_DIR = START_CELL['col_dir']
@@ -918,10 +983,14 @@ def _checks(item, defaults):
918983
row_seq = range(rows)[::ROW_DIR]
919984

920985
# [grid] Build subplot grid (coord tuple of cell)
921-
grid = [[((width + horizontal_spacing) * c,
922-
(height + vertical_spacing) * r)
923-
for c in col_seq]
924-
for r in row_seq]
986+
grid = [
987+
[
988+
(
989+
(sum(widths[:c]) + c * horizontal_spacing),
990+
(sum(heights[:r]) + r * vertical_spacing)
991+
) for c in col_seq
992+
] for r in row_seq
993+
]
925994

926995
# [grid_ref] Initialize the grid and insets' axis-reference lists
927996
grid_ref = [[None for c in range(cols)] for r in range(rows)]
@@ -1040,16 +1109,16 @@ def _add_domain_is_3d(layout, s_label, x_domain, y_domain):
10401109

10411110
# Get x domain using grid and colspan
10421111
x_s = grid[r][c][0] + spec['l']
1043-
x_e = grid[r][c_spanned][0] + width - spec['r']
1112+
x_e = grid[r][c_spanned][0] + widths[c] - spec['r']
10441113
x_domain = [x_s, x_e]
10451114

10461115
# Get y domain (dep. on row_dir) using grid & r_spanned
10471116
if ROW_DIR > 0:
10481117
y_s = grid[r][c][1] + spec['b']
1049-
y_e = grid[r_spanned][c][1] + height - spec['t']
1118+
y_e = grid[r_spanned][c][1] + heights[-1 - r] - spec['t']
10501119
else:
10511120
y_s = grid[r_spanned][c][1] + spec['b']
1052-
y_e = grid[r][c][1] + height - spec['t']
1121+
y_e = grid[r][c][1] + heights[-1 - r] - spec['t']
10531122
y_domain = [y_s, y_e]
10541123

10551124
if spec['is_3d']:
@@ -1108,19 +1177,19 @@ def _add_domain_is_3d(layout, s_label, x_domain, y_domain):
11081177
"Note: the starting cell is (1, 1)")
11091178

11101179
# Get inset x domain using grid
1111-
x_s = grid[r][c][0] + inset['l'] * width
1180+
x_s = grid[r][c][0] + inset['l'] * widths[c]
11121181
if inset['w'] == 'to_end':
1113-
x_e = grid[r][c][0] + width
1182+
x_e = grid[r][c][0] + widths[c]
11141183
else:
1115-
x_e = x_s + inset['w'] * width
1184+
x_e = x_s + inset['w'] * widths[c]
11161185
x_domain = [x_s, x_e]
11171186

11181187
# Get inset y domain using grid
1119-
y_s = grid[r][c][1] + inset['b'] * height
1188+
y_s = grid[r][c][1] + inset['b'] * heights[-1 - r]
11201189
if inset['h'] == 'to_end':
1121-
y_e = grid[r][c][1] + height
1190+
y_e = grid[r][c][1] + heights[-1 - r]
11221191
else:
1123-
y_e = y_s + inset['h'] * height
1192+
y_e = y_s + inset['h'] * heights[-1 - r]
11241193
y_domain = [y_s, y_e]
11251194

11261195
if inset['is_3d']:

0 commit comments

Comments
 (0)