Skip to content

Commit

Permalink
Fixed datetime out of bounds issues on callbacks (#3519)
Browse files Browse the repository at this point in the history
  • Loading branch information
philippjfr authored Feb 23, 2019
1 parent be5f921 commit 4739675
Show file tree
Hide file tree
Showing 2 changed files with 71 additions and 13 deletions.
50 changes: 40 additions & 10 deletions holoviews/plotting/bokeh/callbacks.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
from pyviz_comms import JS_CALLBACK

from ...core import OrderedDict
from ...core.util import dimension_sanitizer, isscalar
from ...core.util import dimension_sanitizer, isscalar, dt64_to_dt
from ...streams import (Stream, PointerXY, RangeXY, Selection1D, RangeX,
RangeY, PointerX, PointerY, BoundsX, BoundsY,
Tap, SingleTap, DoubleTap, MouseEnter, MouseLeave,
Expand Down Expand Up @@ -525,15 +525,37 @@ class PointerXYCallback(Callback):

def _process_out_of_bounds(self, value, start, end):
"Clips out of bounds values"
if value < start:
if isinstance(value, np.datetime64):
v = dt64_to_dt(value)
if isinstance(start, (int, float)):
start = convert_timestamp(start)
if isinstance(end, (int, float)):
end = convert_timestamp(end)
s, e = start, end
if isinstance(s, np.datetime64):
s = dt64_to_dt(s)
if isinstance(e, np.datetime64):
e = dt64_to_dt(e)
else:
v, s, e = value, start, end

if v < s:
value = start
elif value > end:
elif v > e:
value = end

return value

def _process_msg(self, msg):
x_range = self.plot.handles.get('x_range')
y_range = self.plot.handles.get('y_range')
xaxis = self.plot.handles.get('xaxis')
yaxis = self.plot.handles.get('yaxis')

if 'x' in msg and isinstance(xaxis, DatetimeAxis):
msg['x'] = convert_timestamp(msg['x'])
if 'y' in msg and isinstance(yaxis, DatetimeAxis):
msg['y'] = convert_timestamp(msg['y'])

server_mode = self.plot.renderer.mode == 'server'
if isinstance(x_range, FactorRange) and isinstance(msg.get('x'), (int, float)):
Expand All @@ -560,12 +582,6 @@ def _process_msg(self, msg):
else:
msg['y'] = y

xaxis = self.plot.handles.get('xaxis')
yaxis = self.plot.handles.get('yaxis')
if 'x' in msg and isinstance(xaxis, DatetimeAxis):
msg['x'] = convert_timestamp(msg['x'])
if 'y' in msg and isinstance(yaxis, DatetimeAxis):
msg['y'] = convert_timestamp(msg['y'])
return msg


Expand Down Expand Up @@ -667,7 +683,21 @@ class TapCallback(PointerXYCallback):

def _process_out_of_bounds(self, value, start, end):
"Sets out of bounds values to None"
if value < start or value > end:
if isinstance(value, np.datetime64):
v = dt64_to_dt(value)
if isinstance(start, (int, float)):
start = convert_timestamp(start)
if isinstance(end, (int, float)):
end = convert_timestamp(end)
s, e = start, end
if isinstance(s, np.datetime64):
s = dt64_to_dt(s)
if isinstance(e, np.datetime64):
e = dt64_to_dt(e)
else:
v, s, e = value, start, end

if v < s or v > e:
value = None
return value

Expand Down
34 changes: 31 additions & 3 deletions holoviews/tests/plotting/bokeh/testcallbacks.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import datetime as dt
from collections import deque, namedtuple

import numpy as np
Expand All @@ -8,15 +9,15 @@
from holoviews.element.comparison import ComparisonTestCase
from holoviews.streams import (PointDraw, PolyDraw, PolyEdit, BoxEdit,
PointerXY, PointerX, PlotReset, Selection1D,
RangeXY, PlotSize, CDSStream)
RangeXY, PlotSize, CDSStream, SingleTap)
import pyviz_comms as comms

try:
from bokeh.events import Tap
from bokeh.models import Range1d, Plot, ColumnDataSource, Selection, PolyEditTool
from holoviews.plotting.bokeh.callbacks import (
Callback, PointDrawCallback, PolyDrawCallback, PolyEditCallback,
BoxEditCallback, Selection1DCallback
BoxEditCallback, Selection1DCallback, PointerXCallback, TapCallback
)
from holoviews.plotting.bokeh.renderer import BokehRenderer
bokeh_server_renderer = BokehRenderer.instance(mode='server')
Expand Down Expand Up @@ -128,7 +129,34 @@ def record(resetting):
self.assertEqual(resets, [True])
self.assertIs(stream.source, curve)




class TestPointerCallbacks(CallbackTestCase):

def test_pointer_x_datetime_out_of_bounds(self):
points = Points([(dt.datetime(2017, 1, 1), 1), (dt.datetime(2017, 1, 3), 3)])
PointerX(source=points)
plot = bokeh_server_renderer.get_plot(points)
callback = plot.callbacks[0]
self.assertIsInstance(callback, PointerXCallback)
msg = callback._process_msg({'x': 1000})
self.assertEqual(msg['x'], np.datetime64(dt.datetime(2017, 1, 1)))
msg = callback._process_msg({'x': 10000000000000})
self.assertEqual(msg['x'], np.datetime64(dt.datetime(2017, 1, 3)))

def test_tap_datetime_out_of_bounds(self):
points = Points([(dt.datetime(2017, 1, 1), 1), (dt.datetime(2017, 1, 3), 3)])
SingleTap(source=points)
plot = bokeh_server_renderer.get_plot(points)
callback = plot.callbacks[0]
self.assertIsInstance(callback, TapCallback)
msg = callback._process_msg({'x': 1000, 'y': 2})
self.assertEqual(msg, {})
msg = callback._process_msg({'x': 10000000000000, 'y': 1})
self.assertEqual(msg, {})



class TestEditToolCallbacks(CallbackTestCase):

def test_point_draw_callback(self):
Expand Down

0 comments on commit 4739675

Please sign in to comment.