Skip to content

Commit

Permalink
Switch to exclusive range for all use cases
Browse files Browse the repository at this point in the history
This reverts to exclusive ranges both manual and auto for all glyphs
(points/line) without introducing regressions of holoviz#318, holoviz#330, and holoviz#343.

I refactored several tests to make xarray coordinate indices easier to
read and more explicit.
  • Loading branch information
jbcrail committed Sep 27, 2017
1 parent a0bab32 commit 1d5b94d
Show file tree
Hide file tree
Showing 8 changed files with 88 additions and 104 deletions.
2 changes: 1 addition & 1 deletion datashader/core.py
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ def compute_scale_and_translate(self, range, n):
s, t : floats
"""
start, end = map(self.mapper, range)
s = n/(end - start)
s = (n-1)/(end - start)
t = -start * s
return s, t

Expand Down
6 changes: 3 additions & 3 deletions datashader/dask.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
from .compatibility import apply
from .compiler import compile_components
from .glyphs import Glyph, Line
from .utils import Dispatcher, exclusive_range
from .utils import Dispatcher

__all__ = ()

Expand All @@ -28,8 +28,8 @@ def shape_bounds_st_and_axis(df, canvas, glyph):
x_range = canvas.x_range or glyph._compute_x_bounds_dask(df)
y_range = canvas.y_range or glyph._compute_y_bounds_dask(df)
x_min, x_max, y_min, y_max = bounds = compute(*(x_range + y_range))
x_range = exclusive_range((x_min, x_max))
y_range = exclusive_range((y_min, y_max))
x_range = (x_min, x_max)
y_range = (y_min, y_max)

width = canvas.plot_width
height = canvas.plot_height
Expand Down
4 changes: 2 additions & 2 deletions datashader/glyphs.py
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,7 @@ def _extend(vt, bounds, xs, ys, *aggs_and_cols):
for i in range(xs.shape[0]):
x = xs[i]
y = ys[i]
if (xmin <= x < xmax) and (ymin <= y < ymax):
if (xmin <= x <= xmax) and (ymin <= y <= ymax):
append(i,
int(x_mapper(x) * sx + tx),
int(y_mapper(y) * sy + ty),
Expand Down Expand Up @@ -132,7 +132,7 @@ def extend(aggs, df, vt, bounds, plot_start=True):
# Scale/transform float bounds to integer space and adjust for
# exclusive upper bounds
xmin, xmax, ymin, ymax = map_onto_pixel(vt, *bounds)
mapped_bounds = (xmin, xmax - 1, ymin, ymax - 1)
mapped_bounds = (xmin, xmax, ymin, ymax)

xs = df[x_name].values
ys = df[y_name].values
Expand Down
4 changes: 0 additions & 4 deletions datashader/pandas.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@

from .core import bypixel
from .compiler import compile_components
from .utils import exclusive_range

__all__ = ()

Expand All @@ -19,9 +18,6 @@ def pandas_pipeline(df, schema, canvas, glyph, summary):
x_range = canvas.x_range or glyph._compute_x_bounds(df[glyph.x].values)
y_range = canvas.y_range or glyph._compute_y_bounds(df[glyph.y].values)

x_range = exclusive_range(x_range)
y_range = exclusive_range(y_range)

width = canvas.plot_width
height = canvas.plot_height

Expand Down
84 changes: 42 additions & 42 deletions datashader/tests/test_dask.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,15 +27,17 @@

ddf = dd.from_pandas(df, npartitions=3)

c = ds.Canvas(plot_width=2, plot_height=2, x_range=(0, 2), y_range=(0, 2))
c_logx = ds.Canvas(plot_width=2, plot_height=2, x_range=(1, 11),
y_range=(0, 2), x_axis_type='log')
c_logy = ds.Canvas(plot_width=2, plot_height=2, x_range=(0, 2),
y_range=(1, 11), y_axis_type='log')
c_logxy = ds.Canvas(plot_width=2, plot_height=2, x_range=(1, 11),
y_range=(1, 11), x_axis_type='log', y_axis_type='log')

coords = [np.arange(2, dtype='f8')+0.5, np.arange(2, dtype='f8')+0.5]
c = ds.Canvas(plot_width=2, plot_height=2, x_range=(0, 1), y_range=(0, 1))
c_logx = ds.Canvas(plot_width=2, plot_height=2, x_range=(1, 10),
y_range=(0, 1), x_axis_type='log')
c_logy = ds.Canvas(plot_width=2, plot_height=2, x_range=(0, 1),
y_range=(1, 10), y_axis_type='log')
c_logxy = ds.Canvas(plot_width=2, plot_height=2, x_range=(1, 10),
y_range=(1, 10), x_axis_type='log', y_axis_type='log')

axis = ds.core.LinearAxis()
lincoords = axis.compute_index(axis.compute_scale_and_translate((0, 1), 2), 2)
coords = [lincoords, lincoords]
dims = ['y', 'x']


Expand Down Expand Up @@ -65,12 +67,12 @@ def test_count():
def test_any():
out = xr.DataArray(np.array([[True, True], [True, True]]),
coords=coords, dims=dims)
assert_eq(c.points(df, 'x', 'y', ds.any('i64')), out)
assert_eq(c.points(df, 'x', 'y', ds.any('f64')), out)
assert_eq(c.points(df, 'x', 'y', ds.any()), out)
assert_eq(c.points(ddf, 'x', 'y', ds.any('i64')), out)
assert_eq(c.points(ddf, 'x', 'y', ds.any('f64')), out)
assert_eq(c.points(ddf, 'x', 'y', ds.any()), out)
out = xr.DataArray(np.array([[True, True], [True, False]]),
coords=coords, dims=dims)
assert_eq(c.points(df, 'x', 'y', ds.any('empty_bin')), out)
assert_eq(c.points(ddf, 'x', 'y', ds.any('empty_bin')), out)


def test_sum():
Expand Down Expand Up @@ -161,30 +163,27 @@ def test_multiple_aggregates():


def test_auto_range_points():
# Since the following tests use contiguous values of 32-bit or
# 64-bit floats, we need to adjust the theoretical expected results
# if we were using a 128-bit float or arbitrary precision float.
n = 10
fs = list(itertools.islice(floats(1.0), n))
df = pd.DataFrame({'time': np.arange(n),
'x': fs,
'y': fs})
ddf = dd.from_pandas(df, npartitions=3)

# Expect continuous left-right diagonal
cvs = ds.Canvas(plot_width=n, plot_height=n)
agg = cvs.points(ddf, 'x', 'y', ds.count('time'))
sol = np.zeros((n, n), int)
np.fill_diagonal(sol, 1)
np.testing.assert_equal(agg.data, sol)

# Expect continuous left-right diagonal w/ hole in middle
cvs = ds.Canvas(plot_width=n+1, plot_height=n+1)
agg = cvs.points(ddf, 'x', 'y', ds.count('time'))
sol = np.zeros((n+1, n+1), int)
np.fill_diagonal(sol, 1)
sol[5, 5] = 0
# For 32-bit or 64-bit floats, the hole will be in the middle due to
# rounding errors. The hole will be in the lower-right corner for
# 128-bit float or arbitrary precision float.
# sol[n, n] = 0
sol[5, 5] = 0 # adjustment
np.testing.assert_equal(agg.data, sol)

n = 4
Expand All @@ -194,37 +193,35 @@ def test_auto_range_points():
'y': fs})
ddf = dd.from_pandas(df, npartitions=3)

# Expect alternating left-right diagonal
cvs = ds.Canvas(plot_width=2*n, plot_height=2*n)
agg = cvs.points(ddf, 'x', 'y', ds.count('time'))
sol = np.zeros((2*n, 2*n), int)
np.fill_diagonal(sol, 1)
sol[[range(1, 2*n, 2)]] = 0
sol[6, 6] = 0 # adjustment
np.testing.assert_equal(agg.data, sol)

# Expect alternating left-right diagonal with hole in lower-right
# corner
cvs = ds.Canvas(plot_width=2*n+1, plot_height=2*n+1)
agg = cvs.points(ddf, 'x', 'y', ds.count('time'))
sol = np.zeros((2*n+1, 2*n+1), int)
np.fill_diagonal(sol, 1)
sol[[range(1, 2*n+1, 2)]] = 0
sol[2*n, 2*n] = 0
sol[4, 4] = 0 # adjustment
np.testing.assert_equal(agg.data, sol)


def test_log_axis_points():
# Upper bound for scale/index of x-axis
start, end = map(np.log10, (1, 11))
s = 2/(end - start)
t = -start * s
px = np.arange(2)+0.5
logcoords = 10**((px-t)/s)
axis = ds.core.LogAxis()
logcoords = axis.compute_index(axis.compute_scale_and_translate((1, 10), 2), 2)

axis = ds.core.LinearAxis()
lincoords = axis.compute_index(axis.compute_scale_and_translate((0, 1), 2), 2)

sol = np.array([[5, 5], [5, 5]], dtype='i4')
out = xr.DataArray(sol, coords=[np.array([0.5, 1.5]), logcoords],
out = xr.DataArray(sol, coords=[lincoords, logcoords],
dims=['y', 'log_x'])
assert_eq(c_logx.points(ddf, 'log_x', 'y', ds.count('i32')), out)
out = xr.DataArray(sol, coords=[logcoords, np.array([0.5, 1.5])],
out = xr.DataArray(sol, coords=[logcoords, lincoords],
dims=['log_y', 'x'])
assert_eq(c_logy.points(ddf, 'x', 'log_y', ds.count('i32')), out)
out = xr.DataArray(sol, coords=[logcoords, logcoords],
Expand All @@ -233,11 +230,14 @@ def test_log_axis_points():


def test_line():
axis = ds.core.LinearAxis()
lincoords = axis.compute_index(axis.compute_scale_and_translate((-3., 3.), 7), 7)

df = pd.DataFrame({'x': [4, 0, -4, -3, -2, -1.9, 0, 10, 10, 0, 4],
'y': [0, -4, 0, 1, 2, 2.1, 4, 20, 30, 4, 0]})
ddf = dd.from_pandas(df, npartitions=3)
cvs = ds.Canvas(plot_width=7, plot_height=7,
x_range=(-3, 4), y_range=(-3, 4))
x_range=(-3, 3), y_range=(-3, 3))
agg = cvs.line(ddf, 'x', 'y', ds.count())
sol = np.array([[0, 0, 1, 0, 1, 0, 0],
[0, 1, 0, 0, 0, 1, 0],
Expand All @@ -246,23 +246,23 @@ def test_line():
[1, 0, 0, 0, 0, 0, 1],
[0, 2, 0, 0, 0, 1, 0],
[0, 0, 1, 0, 1, 0, 0]], dtype='i4')
out = xr.DataArray(sol, coords=[np.arange(-3., 4.)+0.5, np.arange(-3., 4.)+0.5],
out = xr.DataArray(sol, coords=[lincoords, lincoords],
dims=['y', 'x'])
assert_eq(agg, out)


def test_log_axis_line():
# Upper bound for scale/index of x-axis
start, end = map(np.log10, (1, 11))
s = 2/(end - start)
t = -start * s
px = np.arange(2)+0.5
logcoords = 10**((px-t)/s)
axis = ds.core.LogAxis()
logcoords = axis.compute_index(axis.compute_scale_and_translate((1, 10), 2), 2)

axis = ds.core.LinearAxis()
lincoords = axis.compute_index(axis.compute_scale_and_translate((0, 1), 2), 2)

sol = np.array([[5, 5], [5, 5]], dtype='i4')
out = xr.DataArray(sol, coords=[np.array([0.5, 1.5]), logcoords],
out = xr.DataArray(sol, coords=[lincoords, logcoords],
dims=['y', 'log_x'])
assert_eq(c_logx.line(ddf, 'log_x', 'y', ds.count('i32')), out)
out = xr.DataArray(sol, coords=[logcoords, np.array([0.5, 1.5])],
out = xr.DataArray(sol, coords=[logcoords, lincoords],
dims=['log_y', 'x'])
assert_eq(c_logy.line(ddf, 'x', 'log_y', ds.count('i32')), out)
out = xr.DataArray(sol, coords=[logcoords, logcoords],
Expand Down
10 changes: 2 additions & 8 deletions datashader/tests/test_glyphs.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,14 +10,8 @@
def test_point_bounds_check():
df = pd.DataFrame({'x': [1, 2, 3], 'y': [5, 6, 7]})
p = Point('x', 'y')

xmin, xmax = p._compute_x_bounds(df['x'].values)
assert xmin == 1
assert xmax > 3 and np.isclose(xmax, 3)

ymin, ymax = p._compute_y_bounds(df['y'].values)
assert ymin == 5
assert ymax > 7 and np.isclose(ymax, 7)
assert p._compute_x_bounds(df['x'].values) == (1, 3)
assert p._compute_y_bounds(df['y'].values) == (5, 7)


def test_point_validate():
Expand Down
Loading

0 comments on commit 1d5b94d

Please sign in to comment.