Permalink
Browse files

allow arrow heads to contribute clipping regions (#5690)

* allow arrow heads to contribute clipping regions

* update integration test
  • Loading branch information...
1 parent 05be331 commit 18b867c685b5a5fa5d4ff991234b75c3d7dcfd50 @bryevdv bryevdv committed on GitHub Jan 11, 2017
@@ -48,38 +48,56 @@ export class ArrowView extends AnnotationView
return [start, end]
render: () ->
+ ctx = @plot_view.canvas_view.ctx
+ ctx.save()
+
+ # Order in this function is important. First we draw all the arrow heads.
[@start, @end] = @_map_data()
- @_draw_arrow_body()
- if @model.end? then @_draw_arrow_head(@model.end, @start, @end)
- if @model.start? then @_draw_arrow_head(@model.start, @end, @start)
+ if @model.end? then @_arrow_head(ctx, "render", @model.end, @start, @end)
+ if @model.start? then @_arrow_head(ctx, "render", @model.start, @end, @start)
+
+ # Next we call .clip on all the arrow heads, inside an initial canvas sized
+ # rect, to create an "inverted" clip region for the arrow heads
+ ctx.beginPath();
+ ctx.rect(0, 0, @canvas.width, @canvas.height);
+ if @model.end? then @_arrow_head(ctx, "clip", @model.end, @start, @end)
+ if @model.start? then @_arrow_head(ctx, "clip", @model.start, @end, @start)
+ ctx.closePath()
+ ctx.clip();
+
+ # Finally we draw the arrow body, with the clipping regions set up. This prevents
+ # "fat" arrows from overlapping the arrow head in a bad way.
+ @_arrow_body(ctx)
- _draw_arrow_body: () ->
- ctx = @plot_view.canvas_view.ctx
+ ctx.restore()
+
+ _arrow_body: (ctx) ->
+ if not @visuals.line.doit
+ return
- ctx.save()
for i in [0...@_x_start.length]
@visuals.line.set_vectorize(ctx, i)
+
ctx.beginPath()
ctx.moveTo(@start[0][i], @start[1][i])
ctx.lineTo(@end[0][i], @end[1][i])
+ ctx.stroke()
- if @visuals.line.doit
- ctx.stroke()
- ctx.restore()
-
- _draw_arrow_head: (head, start, end) ->
- ctx = @plot_view.canvas_view.ctx
-
+ _arrow_head: (ctx, action, head, start, end) ->
for i in [0...@_x_start.length]
# arrow head runs orthogonal to arrow body
angle = Math.PI/2 + atan2([start[0][i], start[1][i]], [end[0][i], end[1][i]])
ctx.save()
+
ctx.translate(end[0][i], end[1][i])
ctx.rotate(angle)
- head.render(ctx, i)
+ if action == "render"
+ head.render(ctx)
+ else if action == "clip"
+ head.clip(ctx)
ctx.restore()
@@ -91,15 +109,15 @@ export class Arrow extends Annotation
@mixins ['line']
@define {
- x_start: [ p.NumberSpec, ]
- y_start: [ p.NumberSpec, ]
- start_units: [ p.String, 'data' ]
- start: [ p.Instance, null ]
- x_end: [ p.NumberSpec, ]
- y_end: [ p.NumberSpec, ]
- end_units: [ p.String, 'data' ]
- end: [ p.Instance, new OpenHead({}) ]
- source: [ p.Instance ]
- x_range_name: [ p.String, 'default' ]
- y_range_name: [ p.String, 'default' ]
+ x_start: [ p.NumberSpec, ]
+ y_start: [ p.NumberSpec, ]
+ start_units: [ p.String, 'data' ]
+ start: [ p.Instance, null ]
+ x_end: [ p.NumberSpec, ]
+ y_end: [ p.NumberSpec, ]
+ end_units: [ p.String, 'data' ]
+ end: [ p.Instance, new OpenHead({}) ]
+ source: [ p.Instance ]
+ x_range_name: [ p.String, 'default' ]
+ y_range_name: [ p.String, 'default' ]
}
@@ -12,12 +12,27 @@ export class ArrowHead extends Annotation
render: (ctx, i) ->
null
+ clip: (ctx, i) ->
+ # This method should not begin or close a path
+ null
+
export class OpenHead extends ArrowHead
type: 'OpenHead'
+ clip: (ctx, i) ->
+ # This method should not begin or close a path
+ @visuals.line.set_vectorize(ctx, i)
+ ctx.moveTo(0.5*@size, @size)
+ ctx.lineTo(0.5*@size, -2)
+ ctx.lineTo(-0.5*@size, -2)
+ ctx.lineTo(-0.5*@size, @size)
+ ctx.lineTo(0, 0)
+ ctx.lineTo(0.5*@size, @size)
+
render: (ctx, i) ->
if @visuals.line.doit
@visuals.line.set_vectorize(ctx, i)
+
ctx.beginPath()
ctx.moveTo(0.5*@size, @size)
ctx.lineTo(0, 0)
@@ -27,35 +42,43 @@ export class OpenHead extends ArrowHead
@mixins ['line']
@define {
- size: [ p.Number, 25 ]
+ size: [ p.Number, 25 ]
}
export class NormalHead extends ArrowHead
type: 'NormalHead'
+ clip: (ctx, i) ->
+ # This method should not begin or close a path
+ @visuals.line.set_vectorize(ctx, i)
+ ctx.moveTo(0.5*@size, @size)
+ ctx.lineTo(0.5*@size, -2)
+ ctx.lineTo(-0.5*@size, -2)
+ ctx.lineTo(-0.5*@size, @size)
+ ctx.lineTo(0.5*@size, @size)
+
render: (ctx, i) ->
if @visuals.fill.doit
@visuals.fill.set_vectorize(ctx, i)
- ctx.beginPath()
- ctx.moveTo(0.5*@size, @size)
- ctx.lineTo(0, 0)
- ctx.lineTo(-0.5*@size, @size)
- ctx.closePath()
+ @_normal(ctx, i)
ctx.fill()
if @visuals.line.doit
@visuals.line.set_vectorize(ctx, i)
- ctx.beginPath()
- ctx.moveTo(0.5*@size, @size)
- ctx.lineTo(0, 0)
- ctx.lineTo(-0.5*@size, @size)
- ctx.closePath()
+ @_normal(ctx, i)
ctx.stroke()
+ _normal: (ctx, i) ->
+ ctx.beginPath()
+ ctx.moveTo(0.5*@size, @size)
+ ctx.lineTo(0, 0)
+ ctx.lineTo(-0.5*@size, @size)
+ ctx.closePath()
+
@mixins ['line', 'fill']
@define {
- size: [ p.Number, 25 ]
+ size: [ p.Number, 25 ]
}
@override {
@@ -65,31 +88,39 @@ export class NormalHead extends ArrowHead
export class VeeHead extends ArrowHead
type: 'VeeHead'
+ clip: (ctx, i) ->
+ # This method should not begin or close a path
+ @visuals.line.set_vectorize(ctx, i)
+ ctx.moveTo(0.5*@size, @size)
+ ctx.lineTo(0.5*@size, -2)
+ ctx.lineTo(-0.5*@size, -2)
+ ctx.lineTo(-0.5*@size, @size)
+ ctx.lineTo(0, 0.5*@size)
+ ctx.lineTo(0.5*@size, @size)
+
render: (ctx, i) ->
if @visuals.fill.doit
@visuals.fill.set_vectorize(ctx, i)
- ctx.beginPath()
- ctx.moveTo(0.5*@size, @size)
- ctx.lineTo(0, 0)
- ctx.lineTo(-0.5*@size, @size)
- ctx.lineTo(0, 0.5*@size)
- ctx.closePath()
+ @_vee(ctx, i)
ctx.fill()
if @visuals.line.doit
@visuals.line.set_vectorize(ctx, i)
- ctx.beginPath()
- ctx.moveTo(0.5*@size, @size)
- ctx.lineTo(0, 0)
- ctx.lineTo(-0.5*@size, @size)
- ctx.lineTo(0, 0.5*@size)
- ctx.closePath()
+ @_vee(ctx, i)
ctx.stroke()
+ _vee: (ctx, i) ->
+ ctx.beginPath()
+ ctx.moveTo(0.5*@size, @size)
+ ctx.lineTo(0, 0)
+ ctx.lineTo(-0.5*@size, @size)
+ ctx.lineTo(0, 0.5*@size)
+ ctx.closePath()
+
@mixins ['line', 'fill']
@define {
- size: [ p.Number, 25 ]
+ size: [ p.Number, 25 ]
}
@override {
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
@@ -34,6 +34,11 @@ def test_arrow(output_file_url, selenium, screenshot):
plot.add_layout(arrow1)
plot.add_layout(arrow2)
+ # test arrow body clipping
+ plot.add_layout(Arrow(start=VeeHead(line_width=1, fill_color="white"), x_start=6, y_start=4, x_end=8, y_end=5, line_width=10))
+ plot.add_layout(Arrow(start=NormalHead(line_width=1, fill_color="white"), x_start=6, y_start=3, x_end=8, y_end=4, line_width=10))
+ plot.add_layout(Arrow(start=OpenHead(line_width=1), x_start=6, y_start=2, x_end=8, y_end=3, line_width=10))
+
# Save the plot and start the test
save(plot)
selenium.get(output_file_url)

0 comments on commit 18b867c

Please sign in to comment.