@quartic/bokehjs
Version:
Interactive, novel data visualization
188 lines (156 loc) • 5.1 kB
text/coffeescript
import {RBush} from "core/util/spatial"
import {Glyph, GlyphView} from "./glyph"
import {min, max, copy, findLastIndex} from "core/util/array"
import {isStrictNaN} from "core/util/types"
import * as hittest from "core/hittest"
export class PatchesView extends GlyphView
_build_discontinuous_object: (nanned_qs) ->
# _s is @xs, @ys, @sxs, @sys
# an object of n 1-d arrays in either data or screen units
#
# Each 1-d array gets broken to an array of arrays split
# on any NaNs
#
# So:
# { 0: [x11, x12],
# 1: [x21, x22, x23],
# 2: [x31, NaN, x32]
# }
# becomes
# { 0: [[x11, x12]],
# 1: [[x21, x22, x23]],
# 2: [[x31],[x32]]
# }
ds = {}
for i in [0...nanned_qs.length]
ds[i] = []
qs = copy(nanned_qs[i])
while qs.length > 0
nan_index = findLastIndex(qs, (q) -> isStrictNaN(q))
if nan_index >= 0
qs_part = qs.splice(nan_index)
else
qs_part = qs
qs = []
denanned = (q for q in qs_part when not isStrictNaN(q))
ds[i].push(denanned)
return ds
_index_data: () ->
xss = @_build_discontinuous_object(@_xs)
yss = @_build_discontinuous_object(@_ys)
points = []
for i in [0...@_xs.length]
for j in [0...xss[i].length]
xs = xss[i][j]
ys = yss[i][j]
if xs.length == 0
continue
points.push({
minX: min(xs),
minY: min(ys),
maxX: max(xs),
maxY: max(ys),
i: i
})
return new RBush(points)
_mask_data: (all_indices) ->
xr = @renderer.plot_view.x_range
[x0, x1] = [xr.min, xr.max]
yr = @renderer.plot_view.y_range
[y0, y1] = [yr.min, yr.max]
bbox = hittest.validate_bbox_coords([x0, x1], [y0, y1])
return @index.indices(bbox)
_render: (ctx, indices, {sxs, sys}) ->
# @sxss and @syss are used by _hit_point and sxc, syc
# This is the earliest we can build them, and only build them once
@renderer.sxss = @_build_discontinuous_object(sxs)
@renderer.syss = @_build_discontinuous_object(sys)
for i in indices
[sx, sy] = [sxs[i], sys[i]]
if @visuals.fill.doit
@visuals.fill.set_vectorize(ctx, i)
for j in [0...sx.length]
if j == 0
ctx.beginPath()
ctx.moveTo(sx[j], sy[j])
continue
else if isNaN(sx[j] + sy[j])
ctx.closePath()
ctx.fill()
ctx.beginPath()
continue
else
ctx.lineTo(sx[j], sy[j])
ctx.closePath()
ctx.fill()
if @visuals.line.doit
@visuals.line.set_vectorize(ctx, i)
for j in [0...sx.length]
if j == 0
ctx.beginPath()
ctx.moveTo(sx[j], sy[j])
continue
else if isNaN(sx[j] + sy[j])
ctx.closePath()
ctx.stroke()
ctx.beginPath()
continue
else
ctx.lineTo(sx[j], sy[j])
ctx.closePath()
ctx.stroke()
_hit_point: (geometry) ->
[vx, vy] = [geometry.vx, geometry.vy]
sx = @renderer.plot_view.canvas.vx_to_sx(vx)
sy = @renderer.plot_view.canvas.vy_to_sy(vy)
x = @renderer.xmapper.map_from_target(vx, true)
y = @renderer.ymapper.map_from_target(vy, true)
candidates = @index.indices({minX: x, minY: y, maxX: x, maxY: y})
hits = []
for i in [0...candidates.length]
idx = candidates[i]
sxs = @renderer.sxss[idx]
sys = @renderer.syss[idx]
for j in [0...sxs.length]
if hittest.point_in_poly(sx, sy, sxs[j], sys[j])
hits.push(idx)
result = hittest.create_hit_test_result()
result['1d'].indices = hits
return result
_get_snap_coord: (array) ->
sum = 0
for s in array
sum += s
return sum / array.length
scx: (i, sx, sy) ->
if @renderer.sxss[i].length is 1
# We don't have discontinuous objects so we're ok
return @_get_snap_coord(@sxs[i])
else
# We have discontinuous objects, so we need to find which
# one we're in, we can use point_in_poly again
sxs = @renderer.sxss[i]
sys = @renderer.syss[i]
for j in [0...sxs.length]
if hittest.point_in_poly(sx, sy, sxs[j], sys[j])
return @_get_snap_coord(sxs[j])
return null
scy: (i, sx, sy) ->
if @renderer.syss[i].length is 1
# We don't have discontinuous objects so we're ok
return @_get_snap_coord(@sys[i])
else
# We have discontinuous objects, so we need to find which
# one we're in, we can use point_in_poly again
sxs = @renderer.sxss[i]
sys = @renderer.syss[i]
for j in [0...sxs.length]
if hittest.point_in_poly(sx, sy, sxs[j], sys[j])
return @_get_snap_coord(sys[j])
draw_legend_for_index: (ctx, x0, x1, y0, y1, index) ->
@_generic_area_legend(ctx, x0, x1, y0, y1, index)
export class Patches extends Glyph
default_view: PatchesView
type: 'Patches'
@coords [ ['xs', 'ys'] ]
@mixins ['line', 'fill']