From b52efe6ca77938cf39d985142f90c3877d4a7b13 Mon Sep 17 00:00:00 2001 From: Philipp Rudiger Date: Sat, 23 Feb 2019 12:57:46 +0000 Subject: [PATCH 1/3] Fixed datetime out of bounds issues on callbacks --- holoviews/plotting/bokeh/callbacks.py | 50 +++++++++++++++---- .../tests/plotting/bokeh/testcallbacks.py | 34 +++++++++++-- 2 files changed, 71 insertions(+), 13 deletions(-) diff --git a/holoviews/plotting/bokeh/callbacks.py b/holoviews/plotting/bokeh/callbacks.py index dace0dac48..cf8814931f 100644 --- a/holoviews/plotting/bokeh/callbacks.py +++ b/holoviews/plotting/bokeh/callbacks.py @@ -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, @@ -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(start, np.datetime64): + start = dt64_to_dt(start) + if isinstance(end, (int, float)): + end = convert_timestamp(end) + if isinstance(end, np.datetime64): + end = dt64_to_dt(end) + s, e = start, end + 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)): @@ -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 @@ -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(start, np.datetime64): + start = dt64_to_dt(start) + if isinstance(end, (int, float)): + end = convert_timestamp(end) + if isinstance(end, np.datetime64): + end = dt64_to_dt(end) + s, e = start, end + else: + v, s, e = value, start, end + + if v < s or v > e: value = None return value diff --git a/holoviews/tests/plotting/bokeh/testcallbacks.py b/holoviews/tests/plotting/bokeh/testcallbacks.py index a5f36c347f..0a5948c932 100644 --- a/holoviews/tests/plotting/bokeh/testcallbacks.py +++ b/holoviews/tests/plotting/bokeh/testcallbacks.py @@ -1,3 +1,4 @@ +import datetime as dt from collections import deque, namedtuple import numpy as np @@ -8,7 +9,7 @@ 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: @@ -16,7 +17,7 @@ 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') @@ -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)]) + pointer = 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)]) + pointer = 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): From 04e4268c98ca9bfb2bc668c7d13561104fe58ab1 Mon Sep 17 00:00:00 2001 From: Philipp Rudiger Date: Sat, 23 Feb 2019 13:15:44 +0000 Subject: [PATCH 2/3] Fixed flakes --- holoviews/tests/plotting/bokeh/testcallbacks.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/holoviews/tests/plotting/bokeh/testcallbacks.py b/holoviews/tests/plotting/bokeh/testcallbacks.py index 0a5948c932..9a0a834900 100644 --- a/holoviews/tests/plotting/bokeh/testcallbacks.py +++ b/holoviews/tests/plotting/bokeh/testcallbacks.py @@ -135,7 +135,7 @@ 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)]) - pointer = PointerX(source=points) + PointerX(source=points) plot = bokeh_server_renderer.get_plot(points) callback = plot.callbacks[0] self.assertIsInstance(callback, PointerXCallback) @@ -146,7 +146,7 @@ def test_pointer_x_datetime_out_of_bounds(self): def test_tap_datetime_out_of_bounds(self): points = Points([(dt.datetime(2017, 1, 1), 1), (dt.datetime(2017, 1, 3), 3)]) - pointer = SingleTap(source=points) + SingleTap(source=points) plot = bokeh_server_renderer.get_plot(points) callback = plot.callbacks[0] self.assertIsInstance(callback, TapCallback) From dcaf70176e8a2ab4a567baaf6a7a88401186bdca Mon Sep 17 00:00:00 2001 From: Philipp Rudiger Date: Sat, 23 Feb 2019 14:00:40 +0000 Subject: [PATCH 3/3] Minor fix --- holoviews/plotting/bokeh/callbacks.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/holoviews/plotting/bokeh/callbacks.py b/holoviews/plotting/bokeh/callbacks.py index cf8814931f..8b6d5938ed 100644 --- a/holoviews/plotting/bokeh/callbacks.py +++ b/holoviews/plotting/bokeh/callbacks.py @@ -529,13 +529,13 @@ def _process_out_of_bounds(self, value, start, end): v = dt64_to_dt(value) if isinstance(start, (int, float)): start = convert_timestamp(start) - if isinstance(start, np.datetime64): - start = dt64_to_dt(start) if isinstance(end, (int, float)): end = convert_timestamp(end) - if isinstance(end, np.datetime64): - end = dt64_to_dt(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 @@ -687,13 +687,13 @@ def _process_out_of_bounds(self, value, start, end): v = dt64_to_dt(value) if isinstance(start, (int, float)): start = convert_timestamp(start) - if isinstance(start, np.datetime64): - start = dt64_to_dt(start) if isinstance(end, (int, float)): end = convert_timestamp(end) - if isinstance(end, np.datetime64): - end = dt64_to_dt(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