tools.coffee | |
---|---|
if this.Bokeh
Bokeh = this.Bokeh
else
Bokeh = {}
this.Bokeh = Bokeh
Collections = Continuum.Collections
safebind = Continuum.safebind
HasParent = Continuum.HasParent
BokehView = Continuum.ContinuumView
HasProperties = Continuum.HasProperties
class Bokeh.ActiveToolManager
""" This makes sure that only one tool is active at a time """
constructor : (eventSink) ->
@eventSink = eventSink
@eventSink.active = true
@bind_events()
bind_events : () ->
@eventSink.on("clear_active_tool", () =>
@eventSink.trigger("#{@eventSink.active}:deactivated")
@eventSink.active = true)
@eventSink.on("active_tool", (toolName) =>
if toolName != @eventSink.active
@eventSink.trigger("#{toolName}:activated")
@eventSink.trigger("#{@eventSink.active}:deactivated")
@eventSink.active = toolName) | |
FIXME : I'm not sure we need this special bindevents stuff.. I think we could hook in on bindbokeh_events which is being automatically called | class TwoPointEventGenerator
constructor : (options) ->
@options = options
@toolName = @options.eventBasename
@dragging = false
@basepoint_set = false
@button_activated = false
@tool_active = false
bind_events : (plotview, eventSink) ->
toolName = @toolName
@plotview = plotview
@eventSink = eventSink
@plotview.moveCallbacks.push((e, x, y) =>
if not @dragging
return
if not @tool_active
return
offset = $(e.currentTarget).offset()
e.bokehX = e.pageX - offset.left
e.bokehY = e.pageY - offset.top
if not @basepoint_set
@dragging = true
@basepoint_set = true
eventSink.trigger("#{toolName}:SetBasepoint", e)
else
eventSink.trigger("#{toolName}:UpdatingMouseMove", e)
e.preventDefault()
e.stopPropagation())
$(document).bind('keydown', (e) =>
if e[@options.keyName]
@_start_drag() |
disable the tool when ESC is pressed | if e.keyCode == 27
eventSink.trigger("clear_active_tool"))
$(document).bind('keyup', (e) =>
if not e[@options.keyName]
@_stop_drag())
@plotview.main_can_wrapper.bind('mousedown', (e) =>
if @button_activated
@_start_drag()
return false)
@plotview.main_can_wrapper.bind('mouseup', (e) =>
if @button_activated
@_stop_drag()
return false)
@$tool_button = $("<button class='btn btn-small'> #{@options.buttonText} </button>")
@plotview.$el.find('.button_bar').append(@$tool_button)
@$tool_button.click(=>
if @button_activated
eventSink.trigger("clear_active_tool")
else
eventSink.trigger("active_tool", toolName)
@button_activated = true)
eventSink.on("#{toolName}:deactivated", =>
@tool_active=false;
@button_activated = false;
@$tool_button.removeClass('active'))
eventSink.on("#{toolName}:activated", =>
@tool_active=true;
@$tool_button.addClass('active'))
return eventSink
_start_drag : ->
@eventSink.trigger("active_tool", @toolName)
if not @dragging
@dragging = true
if not @button_activated
@$tool_button.addClass('active')
_stop_drag : ->
@basepoint_set = false
if @dragging
@dragging = false
if not @button_activated
@$tool_button.removeClass('active')
@eventSink.trigger("#{@options.eventBasename}:DragEnd")
class OnePointWheelEventGenerator
constructor : (options) ->
@options = options
@toolName = @options.eventBasename
@dragging = false
@basepoint_set = false
@button_activated = false
@tool_active = false
bind_events : (plotview, eventSink) ->
toolName = @toolName
@plotview = plotview
@eventSink = eventSink
@plotview.main_can_wrapper.bind("mousewheel",
(e, delta, dX, dY) =>
if not @tool_active
return
offset = $(e.currentTarget).offset()
e.bokehX = e.pageX - offset.left
e.bokehY = e.pageY - offset.top
e.delta = delta
eventSink.trigger("#{toolName}:zoom", e)
e.preventDefault()
e.stopPropagation())
$(document).bind('keydown', (e) => |
disable the tool when ESC is pressed | if e.keyCode == 27
eventSink.trigger("clear_active_tool"))
@mouseover_count = 0 |
waiting 500 ms and testing mouseover countmakes sure that mouseouts that occur because of going over element borders don't trigger the mouseout | @plotview.$el.bind("mouseout", (e) =>
@mouseover_count -=1
_.delay((=>
if @mouseover_count == 0
eventSink.trigger("clear_active_tool")), 500))
@plotview.$el.bind("mouseover", (e) =>
@mouseover_count += 1)
@$tool_button = $("<button class='btn btn-small'> #{@options.buttonText} </button>")
@plotview.$el.find('.button_bar').append(@$tool_button)
@$tool_button.click(=>
if @button_activated
eventSink.trigger("clear_active_tool")
else
eventSink.trigger("active_tool", toolName)
@button_activated = true)
no_scroll = (el) ->
el.setAttribute("old_overflow", el.style.overflow)
el.style.overflow = "hidden"
if el == document.body
return
else
no_scroll(el.parentNode)
restore_scroll = (el) ->
el.style.overflow = el.getAttribute("old_overflow")
if el == document.body
return
else
restore_scroll(el.parentNode)
eventSink.on("#{toolName}:deactivated", =>
@tool_active=false;
@button_activated = false;
@$tool_button.removeClass('active')
restore_scroll(@plotview.$el[0])
document.body.style.overflow = @old_overflow)
eventSink.on("#{toolName}:activated", =>
@tool_active=true;
@$tool_button.addClass('active')
no_scroll(@plotview.$el[0]))
return eventSink
class ToolView extends Bokeh.PlotWidget
initialize : (options) ->
super(options)
bind_events : (plotview) ->
eventSink = plotview.eventSink
@plotview = plotview
evgen_options = { eventBasename:@cid }
evgen_options2 = _.extend(evgen_options, @evgen_options)
evgen = new @eventGeneratorClass(evgen_options2)
evgen.bind_events(plotview, eventSink)
_.each(@tool_events, (handler_f, event_name) =>
full_event_name = "#{@cid}:#{event_name}"
wrap = (e) =>
@[handler_f](e)
eventSink.on(full_event_name, wrap))
class PanToolView extends ToolView
initialize : (options) ->
super(options)
@build_mappers()
bind_bokeh_events : () ->
safebind(this, @model, 'change:dataranges', @build_mappers)
build_mappers : () =>
@mappers = []
for temp in _.zip(@mget_obj('dataranges'), @mget('dimensions'))
[datarange, dim] = temp
mapper = new Bokeh.LinearMapper({},
data_range : datarange
viewstate : @plot_view.viewstate
screendim : dim)
@mappers.push(mapper)
return @mappers
eventGeneratorClass : TwoPointEventGenerator
evgen_options : {keyName:"shiftKey", buttonText:"Pan"}
tool_events : {
UpdatingMouseMove: "_drag",
SetBasepoint : "_set_base_point"}
mouse_coords : (e, x, y) ->
[x_, y_] = [@plot_view.viewstate.rxpos(x), @plot_view.viewstate.rypos(y)]
return [x_, y_]
_set_base_point : (e) ->
[@x, @y] = @mouse_coords(e, e.bokehX, e.bokehY)
return null
_drag : (e) ->
[x, y] = @mouse_coords(e, e.bokehX, e.bokehY)
xdiff = x - @x
ydiff = y - @y
[@x, @y] = [x, y]
for mapper in @mappers
if mapper.screendim == 'width'
diff = xdiff
else
diff = ydiff
screenlow = 0 - diff
screenhigh = @plot_view.viewstate.get(mapper.screendim) - diff
[start, end] = [mapper.map_data(screenlow),
mapper.map_data(screenhigh)]
mapper.data_range.set(
start : start
end : end)
return null
class SelectionToolView extends ToolView
initialize : (options) ->
super(options)
select_callback = _.debounce((() => @_select_data()), 50)
safebind(this, @model, 'change', @request_render)
safebind(this, @model, 'change', select_callback)
for renderer in @mget_obj('renderers')
safebind(this, renderer, 'change', @request_render)
safebind(this, renderer.get_obj('xdata_range'), 'change',
@request_render)
safebind(this, renderer.get_obj('ydata_range'), 'change',
@request_render)
safebind(this, renderer.get_obj('data_source'), 'change',
@request_render)
safebind(this, renderer, 'change', select_callback)
safebind(this, renderer.get_obj('xdata_range'), 'change',
select_callback)
safebind(this, renderer.get_obj('ydata_range'), 'change',
select_callback)
eventGeneratorClass : TwoPointEventGenerator
evgen_options : {keyName:"ctrlKey", buttonText:"Select"}
tool_events : {
UpdatingMouseMove: "_selecting",
SetBasepoint : "_start_selecting",
deactivated : "_stop_selecting"}
mouse_coords : (e, x, y) ->
[x, y] = [@plot_view.viewstate.rxpos(x), @plot_view.viewstate.rypos(y)]
return [x, y]
_stop_selecting : () ->
@trigger('stopselect')
@basepoint_set = false
_start_selecting : (e) ->
@trigger('startselect')
[x, y] = @mouse_coords(e, e.bokehX, e.bokehY)
@mset({'start_x' : x, 'start_y' : y, 'current_x' : null, 'current_y' : null})
@basepoint_set = true
_get_selection_range : ->
xrange = [@mget('start_x'), @mget('current_x')]
yrange = [@mget('start_y'), @mget('current_y')]
if @mget('select_x')
xrange = [d3.min(xrange), d3.max(xrange)]
else
xrange = null
if @mget('select_y')
yrange = [d3.min(yrange), d3.max(yrange)]
else
yrange = null
return [xrange, yrange]
_selecting : (e, x_, y_) ->
[x, y] = @mouse_coords(e, e.bokehX, e.bokehY)
@mset({'current_x' : x, 'current_y' : y})
[@xrange, @yrange] = @_get_selection_range()
@trigger('boxselect', @xrange, @yrange)
return null
_select_data : () ->
if not @basepoint_set
return
datasources = {}
datasource_selections = {}
for renderer in @mget_obj('renderers')
datasource = renderer.get_obj('data_source')
datasources[datasource.id] = datasource
for renderer in @mget_obj('renderers')
datasource_id = renderer.get_obj('data_source').id
_.setdefault(datasource_selections, datasource_id, [])
selected = @plot_view.renderers[renderer.id].select(@xrange, @yrange)
datasource_selections[datasource_id].push(selected)
for own k,v of datasource_selections
selected = _.intersect.apply(_, v)
datasources[k].set('selected', selected)
console.log('selected', selected)
datasources[k].save()
return null
class ZoomToolView extends ToolView
initialize : (options) ->
super(options)
safebind(this, @model, 'change:dataranges', @build_mappers)
@build_mappers()
eventGeneratorClass : OnePointWheelEventGenerator
evgen_options : {buttonText:"Zoom"}
tool_events : {
zoom: "_zoom"}
build_mappers : () =>
@mappers = []
for temp in _.zip(@mget_obj('dataranges'), @mget('dimensions'))
[datarange, dim] = temp
mapper = new Bokeh.LinearMapper({},
data_range : datarange
viewstate : @plot_view.viewstate
screendim : dim)
@mappers.push(mapper)
return @mappers
mouse_coords : (e, x, y) ->
[x_, y_] = [@plot_view.viewstate.rxpos(x), @plot_view.viewstate.rypos(y)]
return [x_, y_]
_zoom : (e) ->
delta = e.delta
screenX = e.bokehX
scrrenY = e.bokehY
[x, y] = @mouse_coords(e, screenX, screenY)
speed = @mget('speed')
factor = - speed * (delta * 50)
for mapper in @mappers
if mapper.screendim == 'width'
eventpos = x
else
eventpos = y
screenlow = 0
screenhigh = @plot_view.viewstate.get(mapper.screendim)
start = screenlow - (eventpos - screenlow) * factor
end = screenhigh + (screenhigh - eventpos) * factor
[start, end] = [mapper.map_data(start), mapper.map_data(end)]
mapper.data_range.set(
start : start
end : end)
return null
class PanTool extends Continuum.HasParent
type : "PanTool"
default_view : PanToolView
PanTool::defaults = _.clone(PanTool::defaults)
_.extend(PanTool::defaults
,
dimensions : [] #height/width
dataranges : [] #references of datarange objects
)
class PanTools extends Continuum.Collection
model : PanTool
class ZoomTool extends Continuum.HasParent
type : "ZoomTool"
default_view : ZoomToolView
ZoomTool::defaults = _.clone(ZoomTool::defaults)
_.extend(ZoomTool::defaults
,
dimensions : []
dataranges : []
speed : 1/600
)
class ZoomTools extends Continuum.Collection
model : ZoomTool
class SelectionTool extends Continuum.HasParent
type : "SelectionTool"
default_view : SelectionToolView
SelectionTool::defaults = _.clone(SelectionTool::defaults)
_.extend(SelectionTool::defaults
,
renderers : []
select_x : true
select_y : true
data_source_options : {} #backbone options for save on datasource
)
class SelectionTools extends Continuum.Collection
model : SelectionTool
Bokeh.SelectionToolView = SelectionToolView
Bokeh.PanToolView = PanToolView
Bokeh.ZoomToolView = ZoomToolView
if not Continuum.Collections.PanTool
Continuum.Collections.PanTool = new PanTools
if not Continuum.Collections.ZoomTool
Continuum.Collections.ZoomTool = new ZoomTools
if not Continuum.Collections.SelectionTool
Continuum.Collections.SelectionTool = new SelectionTools
|