Skip to content

Commit

Permalink
Added option for dimension range padding
Browse files Browse the repository at this point in the history
  • Loading branch information
philippjfr committed Feb 1, 2018
1 parent 3e85c68 commit 0423917
Show file tree
Hide file tree
Showing 13 changed files with 122 additions and 46 deletions.
4 changes: 3 additions & 1 deletion holoviews/core/data/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -252,7 +252,9 @@ def range(self, dim, data_range=True):
lower, upper = self.interface.range(self, dim)
else:
lower, upper = (np.NaN, np.NaN)
return dimension_range(lower, upper, dim)
if data_range == 'exclusive':
return (lower, upper)
return dimension_range(lower, upper, dim.range, dim.soft_range)


def add_dimension(self, dimension, dim_pos, dim_val, vdim=False, **kwargs):
Expand Down
4 changes: 3 additions & 1 deletion holoviews/core/dimension.py
Original file line number Diff line number Diff line change
Expand Up @@ -1067,7 +1067,9 @@ def range(self, dimension, data_range=True):
lower, upper = max_range(ranges)
else:
lower, upper = (np.NaN, np.NaN)
return dimension_range(lower, upper, dimension)
if data_range == 'exclusive':
return (lower, upper)
return dimension_range(lower, upper, dimension.range, dimension.soft_range)


def __repr__(self):
Expand Down
13 changes: 9 additions & 4 deletions holoviews/core/util.py
Original file line number Diff line number Diff line change
Expand Up @@ -710,7 +710,7 @@ def max_range(ranges):
try:
with warnings.catch_warnings():
warnings.filterwarnings('ignore', r'All-NaN (slice|axis) encountered')
values = [r for r in ranges for v in r if v is not None]
values = [tuple(np.NaN if v is None else v for v in r) for r in ranges]
if pd and all(isinstance(v, pd.Timestamp) for r in values for v in r):
values = [(v1.to_datetime64(), v2.to_datetime64()) for v1, v2 in values]
arr = np.array(values)
Expand All @@ -724,13 +724,17 @@ def max_range(ranges):
return (np.NaN, np.NaN)


def dimension_range(lower, upper, dimension):
def dimension_range(lower, upper, hard_range, soft_range, padding=0):
"""
Computes the range along a dimension by combining the data range
with the Dimension soft_range and range.
"""
lower, upper = max_range([(lower, upper), dimension.soft_range])
dmin, dmax = dimension.range
if is_number(lower) and is_number(upper) and padding != 0:
pad = (upper - lower)*padding
lower -= pad
upper += pad
lower, upper = max_range([(lower, upper), soft_range])
dmin, dmax = hard_range
lower = lower if dmin is None or not np.isfinite(dmin) else dmin
upper = upper if dmax is None or not np.isfinite(dmax) else dmax
return lower, upper
Expand Down Expand Up @@ -928,6 +932,7 @@ def dimension_sort(odict, kdims, vdims, key_index):
# Copied from param should make param version public
def is_number(obj):
if isinstance(obj, numbers.Number): return True
elif isinstance(obj, (np.str_, np.unicode_)): return False
# The extra check is for classes that behave like numbers, such as those
# found in numpy, gmpy, etc.
elif (hasattr(obj, '__int__') and hasattr(obj, '__add__')): return True
Expand Down
4 changes: 3 additions & 1 deletion holoviews/element/chart.py
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,9 @@ def range(self, dim, data_range=True):
pos_error = neg_error
lower = np.nanmin(mean-neg_error)
upper = np.nanmax(mean+pos_error)
return util.dimension_range(lower, upper, dim)
if data_range == 'exclusive':
return (lower, upper)
return util.dimension_range(lower, upper, dim.range, dim.soft_range)
return super(ErrorBars, self).range(dim, data_range)


Expand Down
2 changes: 1 addition & 1 deletion holoviews/element/raster.py
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ def range(self, dim, data_range=True):
if data_range and idx == 2:
dimension = self.get_dimension(dim)
lower, upper = np.nanmin(self.data), np.nanmax(self.data)
return dimension_range(lower, upper, dimension)
return dimension_range(lower, upper, dimension.range, dimension.soft_range)
return super(Raster, self).range(dim, data_range)


Expand Down
21 changes: 13 additions & 8 deletions holoviews/plotting/bokeh/chart.py
Original file line number Diff line number Diff line change
Expand Up @@ -174,7 +174,7 @@ def _get_lengths(self, element, ranges):
base_dist = get_min_distance(element)
if mag_dim:
magnitudes = element.dimension_values(mag_dim)
_, max_magnitude = ranges[mag_dim.name]
_, max_magnitude = ranges[mag_dim.name]['combined']
if self.normalize_lengths and max_magnitude != 0:
magnitudes = magnitudes / max_magnitude
if self.rescale_lengths:
Expand Down Expand Up @@ -502,11 +502,16 @@ class AreaPlot(SpreadPlot):
def get_extents(self, element, ranges):
vdims = element.vdims
vdim = vdims[0].name
new_range = {}
if len(vdims) > 1:
ranges[vdim] = max_range([ranges[vd.name] for vd in vdims])
for r in ranges[vdim]:
new_range[r] = max_range([ranges[vd.name][r] for vd in vdims])
else:
vdim = vdims[0].name
ranges[vdim] = (np.nanmin([0, ranges[vdim][0]]), ranges[vdim][1])
vranges = ranges[vdim]
for r in vranges:
vrange = vranges[r]
new_range[r] = (np.nanmin([0, vrange[0]]), vrange[1])
ranges[vdim] = new_range
return super(AreaPlot, self).get_extents(element, ranges)


Expand Down Expand Up @@ -670,7 +675,7 @@ def get_extents(self, element, ranges):
element = Bars(overlay.table(), kdims=element.kdims+overlay.kdims,
vdims=element.vdims)
for kd in overlay.kdims:
ranges[kd.name] = overlay.range(kd)
ranges[kd.name]['combined'] = overlay.range(kd)

stacked = element.get_dimension(self.stack_index)
extents = super(BarPlot, self).get_extents(element, ranges)
Expand All @@ -684,7 +689,7 @@ def get_extents(self, element, ranges):
neg_range = ds.select(**{ydim.name: (None, 0)}).aggregate(xdim, function=np.sum).range(ydim)
y0, y1 = max_range([pos_range, neg_range])
else:
y0, y1 = ranges[ydim.name]
y0, y1 = ranges[ydim.name]['combined']

# Set y-baseline
if y0 < 0:
Expand Down Expand Up @@ -827,7 +832,7 @@ def get_data(self, element, ranges, style):
container_type=OrderedDict,
datatype=['dataframe', 'dictionary'])

y0, y1 = ranges.get(ydim.name, (None, None))
y0, y1 = ranges.get(ydim.name, {'combined': (None, None)})['combined']
if self.logy:
bottom = (ydim.range[0] or (10**(np.log10(y1)-2)) if y1 else 0.01)
else:
Expand Down Expand Up @@ -968,7 +973,7 @@ def get_extents(self, element, ranges):
Extents are set to '' and None because x-axis is categorical and
y-axis auto-ranges.
"""
yrange = ranges.get(element.vdims[0].name, (np.NaN, np.NaN))
yrange = ranges.get(element.vdims[0].name, {'combined': (np.NaN, np.NaN)})['combined']
return ('', yrange[0], '', yrange[1])

def _get_axis_labels(self, *args, **kwargs):
Expand Down
6 changes: 4 additions & 2 deletions holoviews/plotting/bokeh/element.py
Original file line number Diff line number Diff line change
Expand Up @@ -753,7 +753,6 @@ def initialize_plot(self, ranges=None, plot=None, plots=None, source=None):
self.current_key = key
style_element = element.last if self.batched else element
ranges = util.match_spec(style_element, ranges)

# Initialize plot, source and glyph
if plot is None:
plot = self._init_plot(key, style_element, ranges=ranges, plots=plots)
Expand Down Expand Up @@ -1093,7 +1092,10 @@ def _get_colormapper(self, dim, element, ranges, style, factors=None, colors=Non

ncolors = None if factors is None else len(factors)
if dim:
low, high = ranges.get(dim.name, element.range(dim.name))
if dim.name in ranges:
low, high = ranges[dim.name]['combined']
else:
low, high = element.range(dim.name)
else:
low, high = None, None

Expand Down
4 changes: 2 additions & 2 deletions holoviews/plotting/bokeh/graphs.py
Original file line number Diff line number Diff line change
Expand Up @@ -69,8 +69,8 @@ def _hover_opts(self, element):

def get_extents(self, element, ranges):
xdim, ydim = element.nodes.kdims[:2]
x0, x1 = ranges[xdim.name]
y0, y1 = ranges[ydim.name]
x0, x1 = ranges[xdim.name]['combined']
y0, y1 = ranges[ydim.name]['combined']
return (x0, y0, x1, y1)

def _get_axis_labels(self, *args, **kwargs):
Expand Down
17 changes: 13 additions & 4 deletions holoviews/plotting/mpl/chart.py
Original file line number Diff line number Diff line change
Expand Up @@ -169,6 +169,7 @@ def update_handles(self, key, axis, element, ranges, style):
return axis_kwargs



class AreaPlot(ChartPlot):

show_legend = param.Boolean(default=False, doc="""
Expand All @@ -193,12 +194,20 @@ def init_artists(self, ax, plot_data, plot_kwargs):
def get_extents(self, element, ranges):
vdims = element.vdims
vdim = vdims[0].name
ranges[vdim] = max_range([ranges[vd.name] for vd in vdims])
new_range = {}
if len(vdims) > 1:
for r in ranges[vdim]:
new_range[r] = max_range([ranges[vd.name][r] for vd in vdims])
else:
vranges = ranges[vdim]
for r in vranges:
vrange = vranges[r]
new_range[r] = (np.nanmin([0, vrange[0]]), vrange[1])
ranges[vdim] = new_range
return super(AreaPlot, self).get_extents(element, ranges)




class SpreadPlot(AreaPlot):
"""
SpreadPlot plots the Spread Element type.
Expand Down Expand Up @@ -632,7 +641,7 @@ def get_data(self, element, ranges, style):
mag_dim = element.get_dimension(self.size_index)
if mag_dim:
magnitudes = element.dimension_values(mag_dim)
_, max_magnitude = ranges[mag_dim.name]
_, max_magnitude = ranges[mag_dim.name]['combined']
if self.normalize_lengths and max_magnitude != 0:
magnitudes = magnitudes / max_magnitude
else:
Expand Down Expand Up @@ -765,7 +774,7 @@ def get_extents(self, element, ranges):
if self.stack_index in range(element.ndims):
return 0, 0, ngroups, np.NaN
else:
vrange = ranges[vdim]
vrange = ranges[vdim]['combined']
return 0, np.nanmin([vrange[0], 0]), ngroups, vrange[1]


Expand Down
5 changes: 4 additions & 1 deletion holoviews/plotting/mpl/element.py
Original file line number Diff line number Diff line change
Expand Up @@ -656,7 +656,10 @@ def _norm_kwargs(self, element, ranges, opts, vdim, prefix=''):
if not isinstance(cs, np.ndarray):
cs = np.array(cs)
if len(cs) and cs.dtype.kind in 'if':
clim = ranges[vdim.name] if vdim.name in ranges else element.range(vdim)
if vdim.name in ranges:
clim = ranges[vdim.name]['combined']
else:
clim = element.range(vdim)
if self.logz:
# Lower clim must be >0 when logz=True
# Choose the maximum between the lowest non-zero value
Expand Down
81 changes: 62 additions & 19 deletions holoviews/plotting/plot.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
from ..core import OrderedDict
from ..core import util, traversal
from ..core.element import Element
from ..core.data import Dataset
from ..core.overlay import Overlay, CompositeOverlay
from ..core.layout import Empty, NdLayout, Layout
from ..core.options import Store, Compositor, SkipRendering
Expand Down Expand Up @@ -409,12 +410,25 @@ def _compute_group_range(group, elements, ranges):
group_ranges = OrderedDict()
for el in elements:
if isinstance(el, (Empty, Table)): continue
for dim in el.dimensions('ranges', label=True):
dim_range = el.range(dim)
if dim not in group_ranges:
group_ranges[dim] = []
group_ranges[dim].append(dim_range)
ranges[group] = OrderedDict((k, util.max_range(v)) for k, v in group_ranges.items())
for dim in el.dimensions('ranges'):
data_range = el.range(dim, data_range='exclusive')
if dim.name not in group_ranges:
group_ranges[dim.name] = {'data': [], 'hard': [], 'soft': []}
group_ranges[dim.name]['data'].append(data_range)
group_ranges[dim.name]['hard'].append(dim.range)
group_ranges[dim.name]['soft'].append(dim.soft_range)

dim_ranges = []
for dim, values in group_ranges.items():
hard_range = util.max_range(values['hard'])
soft_range = util.max_range(values['soft'])
data_range = util.max_range(values['data'])
combined = util.dimension_range(data_range[0], data_range[1],
hard_range, soft_range)
dranges = {'data': data_range, 'hard': hard_range,
'soft': soft_range, 'combined': combined}
dim_ranges.append((dim, dranges))
ranges[group] = OrderedDict(dim_ranges)


@classmethod
Expand Down Expand Up @@ -552,6 +566,9 @@ class GenericElementPlot(DimensionedPlot):
apply_extents = param.Boolean(default=True, doc="""
Whether to apply extent overrides on the Elements""")

padding = param.Number(default=0, doc="""
Amount of padding to apply to data ranges.""")

# A dictionary mapping of the plot methods used to draw the
# glyphs corresponding to the ElementPlot, can support two
# keyword arguments a 'single' implementation to draw an individual
Expand Down Expand Up @@ -669,27 +686,53 @@ def get_extents(self, view, ranges):
Gets the extents for the axes from the current View. The globally
computed ranges can optionally override the extents.
"""
ndims = len(view.dimensions())
dims = view.dimensions()
ndims = len(dims)
num = 6 if self.projection == '3d' else 4
if self.apply_ranges:
xdim = dims[0]
if ranges:
dims = view.dimensions()
x0, x1 = ranges[dims[0].name]
x0, x1 = ranges[xdim.name]['data']
xsrange = ranges[xdim.name]['soft']
xhrange = ranges[xdim.name]['hard']
if ndims > 1:
y0, y1 = ranges[dims[1].name]
ydim = dims[1].name
y0, y1 = ranges[ydim]['data']
ysrange = ranges[ydim]['soft']
yhrange = ranges[ydim]['hard']
else:
y0, y1 = ysrange = yhrange = (np.NaN, np.NaN)

if self.projection == '3d' and ndims > 2:
zdim = dims[2].name
z0, z1 = ranges[zdim]['data']
zsrange = ranges[zdim]['soft']
zhrange = ranges[zdim]['hard']
else:
y0, y1 = (np.NaN, np.NaN)
if self.projection == '3d':
if len(dims) > 2:
z0, z1 = ranges[dims[2].name]
else:
z0, z1 = np.NaN, np.NaN
z0, z1 = zsrange = zhrange = (np.NaN, np.NaN)
else:
x0, x1 = view.range(0)
y0, y1 = view.range(1) if ndims > 1 else (np.NaN, np.NaN)
if self.projection == '3d':
x0, x1 = view.range(0, data_range='exclusive')
xsrange = xdim.soft_range
xhrange = xdim.range
if ndims > 1:
ydim = dims[1]
y0, y1 = view.range(1, data_range='exclusive')
ysrange = ydim.soft_range
yhrange = ydim.range
else:
y0, y1 = ysrange = yhrange = (np.NaN, np.NaN)
if self.projection == '3d' and ndims > 2:
zdim = dims[2]
z0, z1 = view.range(2)
zsrange = zdim.soft_range
zhrange = zdim.range
else:
z0, z1 = zsrange = zhrange = (np.NaN, np.NaN)
x0, x1 = util.dimension_range(x0, x1, xhrange, xsrange, self.padding)
if ndims > 1:
y0, y1 = util.dimension_range(y0, y1, yhrange, ysrange, self.padding)
if self.projection == '3d':
z0, z1 = util.dimension_range(z0, z1, zhrange, zsrange, self.padding)
range_extents = (x0, y0, z0, x1, y1, z1)
else:
range_extents = (x0, y0, x1, y1)
Expand Down
5 changes: 4 additions & 1 deletion holoviews/plotting/plotly/element.py
Original file line number Diff line number Diff line change
Expand Up @@ -226,7 +226,10 @@ def get_color_opts(self, dim, element, ranges, style):
opts['reversescale'] = True
opts['colorscale'] = cmap
if dim:
cmin, cmax = ranges.get(dim.name, element.range(dim.name))
if dim.name in ranges:
cmin, cmax = ranges[dim.name]['combined']
else:
cmin, cmax = element.range(dim.name)
opts['cmin'] = cmin
opts['cmax'] = cmax
opts['cauto'] = False
Expand Down
2 changes: 1 addition & 1 deletion holoviews/plotting/util.py
Original file line number Diff line number Diff line change
Expand Up @@ -318,7 +318,7 @@ def get_sideplot_ranges(plot, element, main, ranges):
ranges = match_spec(range_item.last, ranges)

if dim.name in ranges:
main_range = ranges[dim.name]
main_range = ranges[dim.name]['combined']
else:
framewise = plot.lookup_options(range_item.last, 'norm').options.get('framewise')
if framewise and range_item.get(key, False):
Expand Down

0 comments on commit 0423917

Please sign in to comment.