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

Fix upper-bound bin error for auto-ranged data #459

Merged
merged 22 commits into from
Sep 30, 2017
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
4 changes: 1 addition & 3 deletions datashader/core.py
Original file line number Diff line number Diff line change
Expand Up @@ -54,15 +54,13 @@ def compute_scale_and_translate(self, range, n):
----------
range : tuple
A tuple representing the range ``[min, max]`` along the axis, in
data space. min is inclusive and max is exclusive.
data space. Both min and max are inclusive.
n : int
The number of bins along the axis.

Returns
-------
s, t : floats
Parameters represe

"""
start, end = map(self.mapper, range)
s = n/(end - start)
Expand Down
1 change: 1 addition & 0 deletions datashader/dask.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ def shape_bounds_st_and_axis(df, canvas, glyph):
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, y_range = (x_min, x_max), (y_min, y_max)

width = canvas.plot_width
height = canvas.plot_height

Expand Down
98 changes: 50 additions & 48 deletions datashader/glyphs.py
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,8 @@ class Point(_PointLike):
"""A point, with center at ``x`` and ``y``.

Points map each record to a single bin.
Points falling exactly on the upper bounds are treated as a special case,
mapping into the previous bin rather than being cropped off.

Parameters
----------
Expand All @@ -92,14 +94,20 @@ def _build_extend(self, x_mapper, y_mapper, info, append):
def _extend(vt, bounds, xs, ys, *aggs_and_cols):
sx, tx, sy, ty = vt
xmin, xmax, ymin, ymax = bounds

def map_onto_pixel(x, y):
xx = int(x_mapper(x) * sx + tx)
yy = int(y_mapper(y) * sy + ty)
# Points falling on upper bound are mapped into previous bin
return (xx - 1 if x == xmax else xx,
yy - 1 if y == ymax else yy)

for i in range(xs.shape[0]):
x = xs[i]
y = ys[i]
if (xmin <= x < xmax) and (ymin <= y < ymax):
append(i,
int(x_mapper(x) * sx + tx),
int(y_mapper(y) * sy + ty),
*aggs_and_cols)
if (xmin <= x <= xmax) and (ymin <= y <= ymax):
xi, yi = map_onto_pixel(x, y)
append(i, xi, yi, *aggs_and_cols)

def extend(aggs, df, vt, bounds):
xs = df[x_name].values
Expand Down Expand Up @@ -127,16 +135,10 @@ def _build_extend(self, x_mapper, y_mapper, info, append):
y_name = self.y

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)

xs = df[x_name].values
ys = df[y_name].values
cols = aggs + info(df)

extend_line(vt, bounds, mapped_bounds, xs, ys, plot_start, *cols)
extend_line(vt, bounds, xs, ys, plot_start, *cols)

return extend

Expand Down Expand Up @@ -169,13 +171,15 @@ def _compute_outcode(x, y, xmin, xmax, ymin, ymax):

def _build_map_onto_pixel(x_mapper, y_mapper):
@ngjit
def map_onto_pixel(vt, x0, x1, y0, y1):
def map_onto_pixel(vt, bounds, x, y):
"""Map points onto pixel grid"""
sx, tx, sy, ty = vt
return (int(x_mapper(x0) * sx + tx),
int(x_mapper(x1) * sx + tx),
int(y_mapper(y0) * sy + ty),
int(y_mapper(y1) * sy + ty))
_, xmax, _, ymax = bounds
xx = int(x_mapper(x) * sx + tx)
yy = int(y_mapper(y) * sy + ty)
# Points falling on upper bound are mapped into previous bin
return (xx - 1 if x == xmax else xx,
yy - 1 if y == ymax else yy)

return map_onto_pixel

Expand Down Expand Up @@ -234,9 +238,9 @@ def draw_line(x0i, y0i, x1i, y1i, i, plot_start, clipped, *aggs_and_cols):

def _build_extend_line(draw_line, map_onto_pixel):
@ngjit
def extend_line(vt, bounds, mapped_bounds, xs, ys, plot_start, *aggs_and_cols):
def extend_line(vt, bounds, xs, ys, plot_start, *aggs_and_cols):
"""Aggregate along a line formed by ``xs`` and ``ys``"""
xmin, xmax, ymin, ymax = mapped_bounds
xmin, xmax, ymin, ymax = bounds
nrows = xs.shape[0]
i = 0
while i < nrows - 1:
Expand All @@ -252,11 +256,9 @@ def extend_line(vt, bounds, mapped_bounds, xs, ys, plot_start, *aggs_and_cols):
i += 1
continue

x0i, x1i, y0i, y1i = map_onto_pixel(vt, x0, x1, y0, y1)

# Use Cohen-Sutherland to clip the segment to a bounding box
outcode0 = _compute_outcode(x0i, y0i, xmin, xmax, ymin, ymax)
outcode1 = _compute_outcode(x1i, y1i, xmin, xmax, ymin, ymax)
outcode0 = _compute_outcode(x0, y0, xmin, xmax, ymin, ymax)
outcode1 = _compute_outcode(x1, y1, xmin, xmax, ymin, ymax)

accept = False
clipped = False
Expand All @@ -268,34 +270,34 @@ def extend_line(vt, bounds, mapped_bounds, xs, ys, plot_start, *aggs_and_cols):
elif outcode0 & outcode1:
plot_start = True
break

clipped = True
outcode_out = outcode0 if outcode0 else outcode1
if outcode_out & TOP:
x = x0 + (x1 - x0) * (ymax - y0) / (y1 - y0)
y = ymax
elif outcode_out & BOTTOM:
x = x0 + (x1 - x0) * (ymin - y0) / (y1 - y0)
y = ymin
elif outcode_out & RIGHT:
y = y0 + (y1 - y0) * (xmax - x0) / (x1 - x0)
x = xmax
elif outcode_out & LEFT:
y = y0 + (y1 - y0) * (xmin - x0) / (x1 - x0)
x = xmin

if outcode_out == outcode0:
x0, y0 = x, y
outcode0 = _compute_outcode(x0, y0, xmin, xmax, ymin, ymax)
# If x0 is clipped, we need to plot the new start
plot_start = True
else:
clipped = True
outcode_out = outcode0 if outcode0 else outcode1
if outcode_out & TOP:
x = x0i + int((x1i - x0i) * (ymax - y0i) / (y1i - y0i))
y = ymax
elif outcode_out & BOTTOM:
x = x0i + int((x1i - x0i) * (ymin - y0i) / (y1i - y0i))
y = ymin
elif outcode_out & RIGHT:
y = y0i + int((y1i - y0i) * (xmax - x0i) / (x1i - x0i))
x = xmax
elif outcode_out & LEFT:
y = y0i + int((y1i - y0i) * (xmin - x0i) / (x1i - x0i))
x = xmin

if outcode_out == outcode0:
x0i, y0i = x, y
outcode0 = _compute_outcode(x0i, y0i, xmin, xmax,
ymin, ymax)
# If x0i is clipped, we need to plot the new start
plot_start = True
else:
x1i, y1i = x, y
outcode1 = _compute_outcode(x1i, y1i, xmin, xmax,
ymin, ymax)
x1, y1 = x, y
outcode1 = _compute_outcode(x1, y1, xmin, xmax, ymin, ymax)

if accept:
x0i, y0i = map_onto_pixel(vt, bounds, x0, y0)
x1i, y1i = map_onto_pixel(vt, bounds, x1, y1)
draw_line(x0i, y0i, x1i, y1i, i, plot_start, clipped, *aggs_and_cols)
plot_start = False
i += 1
Expand Down
1 change: 1 addition & 0 deletions datashader/pandas.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ 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)

width = canvas.plot_width
height = canvas.plot_height

Expand Down
Loading