@quartic/bokehjs
Version:
Interactive, novel data visualization
191 lines (156 loc) • 6.6 kB
text/coffeescript
import {XYGlyph, XYGlyphView} from "./xy_glyph"
import * as hittest from "core/hittest"
import * as p from "core/properties"
import {max} from "core/util/array"
import {isString} from "core/util/types"
import {CategoricalMapper} from "../mappers/categorical_mapper"
export class RectView extends XYGlyphView
_set_data: () ->
= 0
if .properties.width.units == "data"
= /2
= 0
if .properties.height.units == "data"
= /2
_map_data: () ->
canvas = .plot_view.canvas
if .properties.width.units == "data"
[, ] =
else
=
= ([i] - [i]/2 for i in [0....length])
if .properties.height.units == "data"
[, ] =
else
=
= ([i] - [i]/2 for i in [0....length])
= (Math.sqrt([i]/2 * [i]/2 + [i]/2 * [i]/2) for i in [0....length])
_render: (ctx, indices, {sx, sy, sx0, sy1, sw, sh, _angle}) ->
if .fill.doit
for i in indices
if isNaN(sx[i] + sy[i] + sx0[i] + sy1[i] + sw[i] + sh[i] + _angle[i])
continue
#no need to test the return value, we call fillRect for every glyph anyway
.fill.set_vectorize(ctx, i)
if _angle[i]
ctx.translate(sx[i], sy[i])
ctx.rotate(_angle[i])
ctx.fillRect(-sw[i]/2, -sh[i]/2, sw[i], sh[i])
ctx.rotate(-_angle[i])
ctx.translate(-sx[i], -sy[i])
else
ctx.fillRect(sx0[i], sy1[i], sw[i], sh[i])
if .line.doit
ctx.beginPath()
for i in indices
if isNaN(sx[i] + sy[i] + sx0[i] + sy1[i] + sw[i] + sh[i] + _angle[i])
continue
# fillRect does not fill zero-height or -width rects, but rect(...)
# does seem to stroke them (1px wide or tall). Explicitly ignore rects
# with zero width or height to be consistent
if sw[i]==0 or sh[i]==0
continue
if _angle[i]
ctx.translate(sx[i], sy[i])
ctx.rotate(_angle[i])
ctx.rect(-sw[i]/2, -sh[i]/2, sw[i], sh[i])
ctx.rotate(-_angle[i])
ctx.translate(-sx[i], -sy[i])
else
ctx.rect(sx0[i], sy1[i], sw[i], sh[i])
.line.set_vectorize(ctx, i)
ctx.stroke()
ctx.beginPath()
ctx.stroke()
_hit_rect: (geometry) ->
[x0, x1] = .xmapper.v_map_from_target([geometry.vx0, geometry.vx1], true)
[y0, y1] = .ymapper.v_map_from_target([geometry.vy0, geometry.vy1], true)
bbox = hittest.validate_bbox_coords([x0, x1], [y0, y1])
result = hittest.create_hit_test_result()
result['1d'].indices = .indices(bbox)
return result
_hit_point: (geometry) ->
[vx, vy] = [geometry.vx, geometry.vy]
x = .xmapper.map_from_target(vx, true)
y = .ymapper.map_from_target(vy, true)
scenter_x = ([i] + [i]/2 for i in [0....length])
scenter_y = ([i] + [i]/2 for i in [0....length])
max_x2_ddist = max()
max_y2_ddist = max()
x0 = x - max_x2_ddist
x1 = x + max_x2_ddist
y0 = y - max_y2_ddist
y1 = y + max_y2_ddist
hits = []
bbox = hittest.validate_bbox_coords([x0, x1], [y0, y1])
for i in .indices(bbox)
sx = .plot_view.canvas.vx_to_sx(vx)
sy = .plot_view.canvas.vy_to_sy(vy)
if [i]
d = Math.sqrt(Math.pow((sx - [i]), 2) + Math.pow((sy - [i]),2))
s = Math.sin(-[i])
c = Math.cos(-[i])
px = c * (sx-[i]) - s * (sy-[i]) + [i]
py = s * (sx-[i]) + c * (sy-[i]) + [i]
sx = px
sy = py
width_in = Math.abs([i]-sx) <= [i]/2
height_in = Math.abs([i]-sy) <= [i]/2
else
width_in = sx - [i] <= [i] and sx - [i] >= 0
height_in = sy - [i] <= [i] and sy - [i] >= 0
if height_in and width_in
hits.push(i)
result = hittest.create_hit_test_result()
result['1d'].indices = hits
return result
_map_dist_corner_for_data_side_length: (coord, side_length, mapper, canvas, dim) ->
if isString(coord[0]) and mapper instanceof CategoricalMapper
return_synthetic = true
synthetic_pt = mapper.v_map_to_target(coord, return_synthetic)
if dim == 0
synthetic_pt_corner = (synthetic_pt[i] - side_length[i]/2 for i in [0...coord.length])
else if dim == 1
synthetic_pt_corner = (synthetic_pt[i] + side_length[i]/2 for i in [0...coord.length])
vpt_corner = mapper.v_map_to_target(synthetic_pt_corner)
sside_length =
else
pt0 = (Number(coord[i]) - side_length[i]/2 for i in [0...coord.length])
pt1 = (Number(coord[i]) + side_length[i]/2 for i in [0...coord.length])
vpt0 = mapper.v_map_to_target(pt0)
vpt1 = mapper.v_map_to_target(pt1)
sside_length =
if dim == 0
vpt_corner = if vpt0[0] < vpt1[0] then vpt0 else vpt1
else if dim == 1
vpt_corner = if vpt0[0] < vpt1[0] then vpt1 else vpt0
if dim == 0
return [sside_length, canvas.v_vx_to_sx(vpt_corner)]
else if dim == 1
return [sside_length, canvas.v_vy_to_sy(vpt_corner)]
_ddist: (dim, spts, spans) ->
if dim == 0
vpts = .plot_view.canvas.v_sx_to_vx(spts)
mapper = .xmapper
else
vpts = .plot_view.canvas.v_vy_to_sy(spts)
mapper = .ymapper
vpt0 = vpts
vpt1 = (vpt0[i] + spans[i] for i in [0...vpt0.length])
pt0 = mapper.v_map_from_target(vpt0)
pt1 = mapper.v_map_from_target(vpt1)
return (Math.abs(pt1[i] - pt0[i]) for i in [0...pt0.length])
draw_legend_for_index: (ctx, x0, x1, y0, y1, index) ->
_bounds: (bds) ->
return
export class Rect extends XYGlyph
default_view: RectView
type: 'Rect'
['line', 'fill']
{
angle: [ p.AngleSpec, 0 ]
width: [ p.DistanceSpec ]
height: [ p.DistanceSpec ]
dilate: [ p.Bool, false ]
}