Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Reimplemented Layout aspects handling #457

Merged
merged 4 commits into from
Feb 6, 2016
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
93 changes: 45 additions & 48 deletions holoviews/plotting/mpl/plot.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
from ..plot import DimensionedPlot, GenericLayoutPlot, GenericCompositePlot
from ..util import get_dynamic_mode, initialize_sampled
from .renderer import MPLRenderer
from .util import compute_ratios


class MPLPlot(DimensionedPlot):
Expand Down Expand Up @@ -681,6 +682,11 @@ class LayoutPlot(GenericLayoutPlot, CompositePlot):
displays the elements in a cartesian grid in scanline order.
"""

absolute_scaling = param.ObjectSelector(default=False, doc="""
If aspect_weight is enabled absolute_scaling determines whether
axes are scaled relative to the widest plot or whether the
aspect scales the axes in absolute terms.""")

aspect_weight = param.Number(default=0, doc="""
Weighting of the individual aspects when computing the Layout
grid aspects and overall figure size.""")
Expand Down Expand Up @@ -727,8 +733,7 @@ def _compute_gridspec(self, layout):
layout_dimensions = layout.kdims if isinstance(layout, NdLayout) else None

layouts = {}
row_heightratios, col_widthratios = {}, {}
col_aspects, row_aspects = defaultdict(lambda: [0, 0]), defaultdict(lambda: [0, 0])
col_widthratios, row_heightratios = {}, {}
for (r, c) in self.coords:
# Get view at layout position and wrap in AdjointLayout
_, view = layout_items.get((r, c), (None, None))
Expand All @@ -744,86 +749,78 @@ def _compute_gridspec(self, layout):
main = main.last if isinstance(main, HoloMap) else main
main_options = self.lookup_options(main, 'plot').options if main else {}
if main and not isinstance(main_options.get('aspect', 1), basestring):
main_aspect = main_options.get('aspect', 1)
main_aspect = np.nan if isinstance(main, Empty) else main_options.get('aspect', 1)
main_aspect = self.aspect_weight*main_aspect + 1-self.aspect_weight
else:
main_aspect = 1
main_aspect = np.nan

if layout_type in ['Dual', 'Triple']:
el = layout_view.get('right', None)
eltype = type(el)
if el and eltype in MPLPlot.sideplots:
plot_type = MPLPlot.sideplots[type(el)]
ratio = plot_type.border_size + plot_type.subplot_size
ratio = 0.6*(plot_type.subplot_size+plot_type.border_size)
width_ratios = [4, 4*ratio]
else:
width_ratios = [4, 1]
col_aspect = [main_aspect, 1/(4/width_ratios[1])]
else:
width_ratios = [4]
col_aspect = [main_aspect, 0]

inv_aspect = 1./main_aspect if main_aspect else np.NaN
if layout_type in ['Embedded Dual', 'Triple']:
el = layout_view.get('top', None)
eltype = type(el)
if el and eltype in MPLPlot.sideplots:
plot_type = MPLPlot.sideplots[type(el)]
ratio = plot_type.border_size + plot_type.subplot_size
ratio = 0.6*(plot_type.subplot_size+plot_type.border_size)
height_ratios = [4*ratio, 4]
else:
height_ratios = [1, 4]
row_aspect = [1/(4/height_ratios[0]), 1./main_aspect]
hidx = 1
else:
height_ratios = [4]
row_aspect = [0, 1./main_aspect]
hidx = 0

if not isinstance(main_aspect, (basestring, type(None))):
width_ratios[0] = (width_ratios[0] * main_aspect)
height_ratios[-1] = (height_ratios[-1] * 1./main_aspect)
width_ratios = [ratio * main_aspect for ratio in width_ratios]
height_ratios = [ratio * inv_aspect for ratio in height_ratios]
layout_shape = (len(width_ratios), len(height_ratios))

# For each row and column record the width and height ratios
# of the LayoutPlot with the most horizontal or vertical splits
# and largest aspect
if layout_shape[1] > row_heightratios.get(r, (0, None))[0]:
row_heightratios[r] = [layout_shape[1], height_ratios]
if height_ratios[hidx] > row_heightratios[r][1][hidx]:
row_heightratios[r][1][-1] = height_ratios[hidx]

if layout_shape[0] > col_widthratios.get(c, (0, None))[0]:
col_widthratios[c] = (layout_shape[0], width_ratios)
if width_ratios[0] > col_widthratios[c][1][0]:
col_widthratios[c][1][0] = width_ratios[0]

for i in range(2):
if col_aspect[i] > col_aspects.get(c, [0,0])[i]:
col_aspects[c][i] = col_aspect[i]
if row_aspect[i] > row_aspects.get(r, [0,0])[i]:
row_aspects[r][i] = row_aspect[i]

# In order of row/column collect the largest width and height ratios
height_ratios = [v[1] for k, v in sorted(row_heightratios.items())]
width_ratios = [v[1] for k, v in sorted(col_widthratios.items())]
col_aspect_ratios = [v for k, v in sorted(col_aspects.items())]
row_aspect_ratios = [v for k, v in sorted(row_aspects.items())]
prev_heights = row_heightratios.get(r, (0, []))
if layout_shape[1] > prev_heights[0]:
row_heightratios[r] = [layout_shape[1], prev_heights[1]]
row_heightratios[r][1].append(height_ratios)

prev_widths = col_widthratios.get(c, (0, []))
if layout_shape[0] > prev_widths[0]:
col_widthratios[c] = (layout_shape[0], prev_widths[1])
col_widthratios[c][1].append(width_ratios)


col_splits = [v[0] for _, v in sorted(col_widthratios.items())]
row_splits = [v[0] for _, v in sorted(row_heightratios.items())]

widths = np.array([r for col in col_widthratios.values()
for ratios in col[1] for r in ratios])/4

hr_unnormalized = compute_ratios(row_heightratios, False)
wr_unnormalized = compute_ratios(col_widthratios, False)
hr_list = compute_ratios(row_heightratios)
wr_list = compute_ratios(col_widthratios)

# Compute the number of rows and cols
cols = np.sum([len(wr) for wr in width_ratios])
rows = np.sum([len(hr) for hr in height_ratios])
cols, rows = len(wr_list), len(hr_list)


# Flatten the width and height ratio lists
wr_list = [wr for wrs in width_ratios for wr in wrs]
hr_list = [hr for hrs in height_ratios for hr in hrs]
wr_list = [r if np.isfinite(r) else 1 for r in wr_list]
hr_list = [r if np.isfinite(r) else 1 for r in hr_list]

# Compute and set the plot size if not explicitly supplied
col_ars = [ar for ars in col_aspect_ratios for ar in ars]
row_ars = [ar for ars in row_aspect_ratios for ar in ars]
col_ars = [ar/max(col_ars) if ar else 0 for ar in col_ars]
width = sum([r if np.isfinite(r) else 1 for r in wr_list])
yscale = width/sum([(1/v)*4 if np.isfinite(v) else 4 for v in wr_unnormalized])
if self.absolute_scaling:
width = width*np.nanmax(widths)

width = len(col_ars[::2]) + sum(col_ars[1::2])
yscale = sum(col_ars)/sum(row_ars)
xinches, yinches = None, None
if not isinstance(self.fig_inches, (tuple, list)):
xinches = self.fig_inches * width
Expand Down Expand Up @@ -855,8 +852,8 @@ def _compute_gridspec(self, layout):
layout_subplots, layout_axes = {}, {}
for r, c in self.coords:
# Compute the layout type from shape
wsplits = len(width_ratios[c])
hsplits = len(height_ratios[r])
wsplits = col_splits[c]
hsplits = row_splits[r]
if (wsplits, hsplits) == (1,1):
layout_type = 'Single'
elif (wsplits, hsplits) == (2,1):
Expand Down
33 changes: 32 additions & 1 deletion holoviews/plotting/mpl/util.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
import re

from ...core.util import basestring
import numpy as np
from matplotlib import ticker

from ...core.util import basestring


def wrap_formatter(formatter):
"""
Wraps formatting function or string in
Expand All @@ -17,3 +20,31 @@ def wrap_formatter(formatter):
return ticker.StrMethodFormatter(formatter)
else:
return ticker.FormatStrFormatter(formatter)

def unpack_adjoints(ratios):
new_ratios = {}
offset = 0
for k, (num, ratios) in sorted(ratios.items()):
unpacked = [[] for _ in range(num)]
for r in ratios:
nr = len(r)
for i in range(num):
unpacked[i].append(r[i] if i < nr else np.nan)
for i, r in enumerate(unpacked):
new_ratios[k+i+offset] = r
offset += num-1
return new_ratios

def normalize_ratios(ratios):
normalized = {}
for i, v in enumerate(zip(*ratios.values())):
arr = np.array(v)
normalized[i] = arr/float(np.nanmax(arr))
return normalized

def compute_ratios(ratios, normalized=True):
unpacked = unpack_adjoints(ratios)
if normalized:
unpacked = normalize_ratios(unpacked)
sorted_ratios = sorted(unpacked.items())
return np.nanmax(np.vstack([v for _, v in sorted_ratios]), axis=0)