From 35ee7ff2dadf52d9a91be9a2950f12e9c2d88521 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 22 Oct 2025 02:41:16 +0000 Subject: [PATCH 01/14] Initial plan From 0e21f8eaebb6014a9ee16b314d34b4c01c17db13 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 22 Oct 2025 02:42:00 +0000 Subject: [PATCH 02/14] Initial plan From df11305bb67cb7634ee3f781ba9726e4946d7114 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 22 Oct 2025 02:44:12 +0000 Subject: [PATCH 03/14] Initial plan From c320271f55cfe501ebc8510183a012f697cf6b91 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 22 Oct 2025 02:46:08 +0000 Subject: [PATCH 04/14] Remove all trailing whitespace from Python files in kivy_matplotlib_widget Co-authored-by: mp-007 <19823482+mp-007@users.noreply.github.com> --- .../tools/clipboard_tool.py | 28 +- kivy_matplotlib_widget/tools/cursors.py | 10 +- .../tools/interactive_converter.py | 95 +- kivy_matplotlib_widget/tools/pick_info.py | 28 +- .../uix/graph_subplot_widget.py | 896 ++++++------ kivy_matplotlib_widget/uix/graph_widget.py | 906 ++++++------ kivy_matplotlib_widget/uix/graph_widget_3d.py | 258 ++-- .../uix/graph_widget_crop_factor.py | 282 ++-- .../uix/graph_widget_general.py | 64 +- .../uix/graph_widget_scatter.py | 882 ++++++------ .../uix/graph_widget_twinx.py | 1258 ++++++++--------- kivy_matplotlib_widget/uix/hover_widget.py | 548 +++---- kivy_matplotlib_widget/uix/legend_widget.py | 638 ++++----- kivy_matplotlib_widget/uix/minmax_widget.py | 110 +- .../uix/navigation_bar_widget.py | 106 +- kivy_matplotlib_widget/uix/selector_widget.py | 698 ++++----- 16 files changed, 3403 insertions(+), 3404 deletions(-) diff --git a/kivy_matplotlib_widget/tools/clipboard_tool.py b/kivy_matplotlib_widget/tools/clipboard_tool.py index 0358f18..b66a7bf 100644 --- a/kivy_matplotlib_widget/tools/clipboard_tool.py +++ b/kivy_matplotlib_widget/tools/clipboard_tool.py @@ -3,7 +3,7 @@ manage windows, linux (via xclip) and MacOS platform -not functionnal with Android platform +not functionnal with Android platform (need to do something similar as kivy\core\clipboard\clipboard_android.py) Note: A new image clipboard is be planned in kivy 3.0.0 @@ -24,7 +24,7 @@ """ import subprocess import tempfile - + elif platform == 'macosx': """ Appkit come with pyobjc @@ -34,30 +34,30 @@ NSPasteboardTypePNG, ) from Foundation import NSData - + def image2clipboard(widget): - if platform == 'win': + if platform == 'win': def send_to_clipboard(clip_type, data): win32clipboard.OpenClipboard() win32clipboard.EmptyClipboard() win32clipboard.SetClipboardData(clip_type, data) win32clipboard.CloseClipboard() - + img = widget.export_as_image() #export widget as image pil_img = PILImage.frombytes('RGBA', img.texture.size, img.texture.pixels) - + output = BytesIO() pil_img.convert("RGB").save(output, "BMP") data = output.getvalue()[14:] - output.close() - send_to_clipboard(win32clipboard.CF_DIB, data) + output.close() + send_to_clipboard(win32clipboard.CF_DIB, data) + + elif platform == 'linux': - elif platform == 'linux': - def _copy_linux_xclip(image): """On Linux, copy the `image` to the clipboard. The `image` arg can either be a PIL.Image.Image object or a str/Path refering to an image file. @@ -73,7 +73,7 @@ def _copy_linux_xclip(image): img.texture.pixels) _copy_linux_xclip(pil_img) - elif platform == 'macosx': + elif platform == 'macosx': img = widget.export_as_image() #export widget as image pil_img = PILImage.frombytes('RGBA', img.texture.size, @@ -81,10 +81,10 @@ def _copy_linux_xclip(image): output = BytesIO() pil_img.save(output, format="PNG") data = output.getvalue() - output.close() - + output.close() + image_data = NSData.dataWithBytes_length_(data, len(data)) - + pasteboard = NSPasteboard.generalPasteboard() format_type = NSPasteboardTypePNG pasteboard.clearContents() diff --git a/kivy_matplotlib_widget/tools/cursors.py b/kivy_matplotlib_widget/tools/cursors.py index 4527099..3ac4901 100644 --- a/kivy_matplotlib_widget/tools/cursors.py +++ b/kivy_matplotlib_widget/tools/cursors.py @@ -1,4 +1,4 @@ -"""This file is based on mplcursors project. Some changes as been made to +"""This file is based on mplcursors project. Some changes as been made to worked with kivy and my project mplcursors project @@ -174,8 +174,8 @@ def add_highlight(self, artist, *args, **kwargs): return hl # def _on_select_event(self, event): - - + + # if (not self._filter_mouse_event(event) # # See _on_pick. (We only suppress selects, not deselects.) # or event in self._suppressed_events): @@ -209,7 +209,7 @@ def add_highlight(self, artist, *args, **kwargs): # pass def xy_event(self, event): - + # Work around lack of support for twinned axes. per_axes_event = {ax: _reassigned_axes_event(event, ax) for ax in {artist.axes for artist in self.artists}} @@ -231,7 +231,7 @@ def xy_event(self, event): == (other.artist, tuple(other.target)) for other in self._selections)), key=lambda pi: pi.dist, default=None) - + if pi: if event.compare_xdata: min_distance=pi.dist diff --git a/kivy_matplotlib_widget/tools/interactive_converter.py b/kivy_matplotlib_widget/tools/interactive_converter.py index fc4e242..29d9a6f 100644 --- a/kivy_matplotlib_widget/tools/interactive_converter.py +++ b/kivy_matplotlib_widget/tools/interactive_converter.py @@ -11,7 +11,7 @@ from kivy.core.window import Window import matplotlib.pyplot as plt -import multiprocessing as mp +import multiprocessing as mp from kivy_matplotlib_widget.uix.legend_widget import MatplotlibInteractiveLegend from kivy_matplotlib_widget.uix.minmax_widget import add_minmax @@ -20,16 +20,16 @@ KV = ''' Screen figure_wgt:figure_wgt - + BoxLayout: orientation:'vertical' - + canvas.before: Color: rgba: (1, 1, 1, 1) Rectangle: pos: self.pos - size: self.size + size: self.size KivyMatplotNavToolbar: id:nav_bar nav_icon:'all' @@ -47,17 +47,17 @@ max_hover_rate:app.max_hover_rate legend_do_scroll_x:app.legend_do_scroll_x hist_range:app.hist_range - autoscale_tight:app.autoscale_tight - + autoscale_tight:app.autoscale_tight + ''' KV3D = ''' Screen figure_wgt_layout:figure_wgt_layout - + BoxLayout: orientation:'vertical' - + canvas.before: Color: rgba: (1, 1, 1, 1) @@ -80,18 +80,18 @@ class GraphApp(App): show_cursor_data = BooleanProperty(True) drag_legend = BooleanProperty(False) legend_do_scroll_x = BooleanProperty(True) - max_hover_rate = NumericProperty(5/60,allownone=True) + max_hover_rate = NumericProperty(5/60,allownone=True) fast_draw = BooleanProperty(False) hist_range = BooleanProperty(True) autoscale_tight = BooleanProperty(False) - def __init__(self, + def __init__(self, figure, show_cursor_data=True, hover_widget=PlotlyHover, compare_hover_widget=TagCompareHover, compare_hover=False, - legend_instance=None, + legend_instance=None, custom_handlers=None, multi_legend=False, drag_legend=False, @@ -114,10 +114,10 @@ def __init__(self, self.multi_legend=multi_legend self.disable_interactive_legend=disable_interactive_legend self.disable_hover=disable_hover - + # print(self.figure.get()) super(GraphApp, self).__init__(**kwargs) - + self.drag_legend=drag_legend self.show_cursor_data=show_cursor_data self.compare_hover=compare_hover @@ -128,50 +128,50 @@ def __init__(self, self.autoscale_tight=autoscale_tight self.register_cursor=register_cursor self.figsize=figsize - + def build(self): if self.figsize: Window.size = self.figsize - + # Set minimum window size Window.minimum_width, Window.minimum_height = (200, 200) - + self.screen=Builder.load_string(KV) return self.screen - def on_start(self, *args): + def on_start(self, *args): if hasattr(self.figure,'get'): figure = self.figure.get()[0] else: figure= self.figure - + if isinstance(figure,list): self.screen.figure_wgt.figure = figure[0] else: self.screen.figure_wgt.figure = figure - + if self.register_cursor: self.screen.figure_wgt.register_cursor(pickables=self.register_cursor) - + if self.compare_hover: if self.compare_hover_widget: add_hover(self.screen.figure_wgt,mode='desktop',hover_type='compare',hover_widget=self.compare_hover_widget()) else: add_hover(self.screen.figure_wgt,mode='desktop',hover_type='compare') - + if not self.disable_hover: if self.hover_widget: add_hover(self.screen.figure_wgt,mode='desktop',hover_widget=self.hover_widget()) else: add_hover(self.screen.figure_wgt,mode='desktop') add_minmax(self.screen.figure_wgt) - + if not self.disable_interactive_legend: if len(self.screen.figure_wgt.figure.axes) > 0 and \ (self.screen.figure_wgt.figure.axes[0].get_legend() or \ self.legend_instance): - + if self.multi_legend: for i,current_legend_instance in enumerate(self.legend_instance): if i==0: @@ -182,9 +182,9 @@ def on_start(self, *args): MatplotlibInteractiveLegend(self.screen.figure_wgt, legend_instance=current_legend_instance, custom_handlers=self.custom_handlers[i], - multi_legend=True) - - else: + multi_legend=True) + + else: MatplotlibInteractiveLegend(self.screen.figure_wgt, legend_instance=self.legend_instance, custom_handlers=self.custom_handlers) @@ -192,37 +192,37 @@ def on_start(self, *args): def app_window(plot_queue,**kwargs): GraphApp(plot_queue,**kwargs).run() - + class GraphApp3D(App): figure = None figsize = None #figure size in pixel. inpu is a tuple ex: (1200,400) - def __init__(self, + def __init__(self, figure, figsize=None, **kwargs): """__init__ function class""" self.figure=figure self.figsize=figsize - + # print(self.figure.get()) super(GraphApp3D, self).__init__(**kwargs) - + def build(self): if self.figsize: Window.size = self.figsize - + # Set minimum window size - Window.minimum_width, Window.minimum_height = (200, 200) + Window.minimum_width, Window.minimum_height = (200, 200) self.screen=Builder.load_string(KV3D) return self.screen - def on_start(self, *args): + def on_start(self, *args): if hasattr(self.figure,'get'): figure = self.figure.get()[0] else: figure= self.figure - + if isinstance(figure,list): self.screen.figure_wgt_layout.figure_wgt.figure = figure[0] @@ -231,15 +231,15 @@ def on_start(self, *args): def app_window_3D(plot_queue,**kwargs): - GraphApp3D(plot_queue,**kwargs).run() - + GraphApp3D(plot_queue,**kwargs).run() + def interactive_graph(fig,**kwargs): - """ Interactive grpah using multiprocessing method. + """ Interactive grpah using multiprocessing method. function need to be call in if __name__ == "__main__": method """ # Create a queue to pass the Matplotlib instance object plot_queue = mp.Queue() - + #switch to agg backend plt.switch_backend('Agg') @@ -249,20 +249,20 @@ def interactive_graph(fig,**kwargs): # Create and start the subprocess p = mp.Process(target=app_window, args=(plot_queue,), kwargs=kwargs) p.start() - + def interactive_graph_ipython(fig,**kwargs): app_window(fig,**kwargs) - + def interactive_graph3D_ipython(fig,**kwargs): app_window_3D(fig,**kwargs) - + def interactive_graph3D(fig,**kwargs): - """ Interactive grpah using multiprocessing method. + """ Interactive grpah using multiprocessing method. function need to be call in if __name__ == "__main__": method """ # Create a queue to pass the Matplotlib instance object plot_queue = mp.Queue() - + #switch to agg backend plt.switch_backend('Agg') @@ -275,14 +275,13 @@ def interactive_graph3D(fig,**kwargs): if __name__ == "__main__": fig, ax1 = plt.subplots(1, 1) - + line1, = ax1.plot([0,1,2,3,4], [1,2,8,9,4],label='line1') line2, = ax1.plot([2,8,10,15], [15,0,2,4],label='line2') - + ax1.legend() - + interactive_graph(fig,show_cursor_data=False,drag_legend=True) - + interactive_graph_ipython(fig,show_cursor_data=True) - \ No newline at end of file diff --git a/kivy_matplotlib_widget/tools/pick_info.py b/kivy_matplotlib_widget/tools/pick_info.py index 832cdc3..cdee136 100644 --- a/kivy_matplotlib_widget/tools/pick_info.py +++ b/kivy_matplotlib_widget/tools/pick_info.py @@ -1,4 +1,4 @@ -"""This file is based on mplcursors project. Some changes as been made to +"""This file is based on mplcursors project. Some changes as been made to worked with kivy and my project mplcursors project @@ -315,7 +315,7 @@ def _(artist, event): ds = np.hypot(*(xy - data_screen_xy).T) elif event.pick_radius_axis == 'x': ds = abs(xy[0] - data_screen_xy[:,0]) - elif event.pick_radius_axis == 'y': + elif event.pick_radius_axis == 'y': ds = abs(xy[1] - data_screen_xy[:,1]) try: argmin = np.nanargmin(ds) @@ -324,7 +324,7 @@ def _(artist, event): else: target = _untransform( # More precise than transforming back. data_xy[argmin], data_screen_xy[argmin], artist.axes) - + sels.append( Selection(artist, target, argmin, ds[argmin], None, None)) # If lines are visible, find the closest projection. @@ -362,7 +362,7 @@ def _(artist, event): offsets = artist.get_offsets() paths = artist.get_paths() if _is_scatter(artist): - + # Use the C implementation to prune the list of segments -- but only # for scatter plots as that implementation is inconsistent with Line2D # for segment-like collections (matplotlib/matplotlib#17279). @@ -375,7 +375,7 @@ def _(artist, event): if offsets.any(): offsets_screen = artist.get_offset_transform().transform(offsets) ds = np.hypot(*(offsets_screen - [event.x, event.y]).T) - + argmin = ds.argmin() target = _untransform( offsets[argmin], offsets_screen[argmin], artist.axes) @@ -458,7 +458,7 @@ def _(container, event): if patch.contains(event)[0]} except ValueError: return - + if event.projection: target = [event.xdata, event.ydata] if patch.sticky_edges.x: @@ -468,11 +468,11 @@ def _(container, event): if patch.sticky_edges.y: target[1], = ( y for y in [patch.get_y(), patch.get_y() + patch.get_height()] - if y not in patch.sticky_edges.y) - + if y not in patch.sticky_edges.y) + else: x, y, width, height = container[idx].get_bbox().bounds - target = [x + width / 2, y + height] + target = [x + width / 2, y + height] return Selection(container, target, idx, 0, None, None) @compute_pick.register(Wedge) @@ -481,16 +481,16 @@ def _(container, event): ang = (container.theta2 - container.theta1)/2. + container.theta1 radius=container.r center_x,center_y=container.center - y = np.sin(np.deg2rad(ang))*radius*.95+center_x - x = np.cos(np.deg2rad(ang))*radius*.95+center_y + y = np.sin(np.deg2rad(ang))*radius*.95+center_x + x = np.cos(np.deg2rad(ang))*radius*.95+center_y except ValueError: return - - target = [x, y] + + target = [x, y] offsets_screen = container.get_data_transform().transform(target) ds = np.hypot(*(offsets_screen - [event.x, event.y]).T) argmin=0 - + sel = Selection(container, target, argmin, ds, None, None) return sel if sel and sel.dist < event.pickradius else None diff --git a/kivy_matplotlib_widget/uix/graph_subplot_widget.py b/kivy_matplotlib_widget/uix/graph_subplot_widget.py index 2441155..3d3e6f1 100644 --- a/kivy_matplotlib_widget/uix/graph_subplot_widget.py +++ b/kivy_matplotlib_widget/uix/graph_subplot_widget.py @@ -1,4 +1,4 @@ -""" Custom MatplotFigure +""" Custom MatplotFigure """ import math @@ -36,33 +36,33 @@ class MatplotFigureSubplot(MatplotFigure): interactive_axis_pad= NumericProperty(dp(100)) _pick_info = None draw_all_axes = BooleanProperty(False) - max_hover_rate = NumericProperty(None,allownone=True) - last_hover_time=None + max_hover_rate = NumericProperty(None,allownone=True) + last_hover_time=None cursor_last_axis=None current_anchor_axis=None min_max_option = BooleanProperty(False) - box_axes=[] + box_axes=[] def my_in_axes(self,ax, mouseevent): """ variante of matplotlib in_axes (get interactive axis) - + Return whether the given event (in display coords) is in the Axes or in margin axis. """ result1 = False#ax.patch.contains(mouseevent)[0] - + result2 = False if not result1: - + xlabelsize = self.interactive_axis_pad - ylabelsize = self.interactive_axis_pad - + ylabelsize = self.interactive_axis_pad + #get label left/right information xlabelbottom = ax.xaxis._major_tick_kw.get('tick1On') xlabeltop = ax.xaxis._major_tick_kw.get('tick2On') ylabelleft = ax.yaxis._major_tick_kw.get('tick1On') - ylabelright = ax.yaxis._major_tick_kw.get('tick2On') - + ylabelright = ax.yaxis._major_tick_kw.get('tick2On') + #check if tick zone #y left axis if ylabelleft and mouseevent.x > ax.bbox.bounds[0] - ylabelsize and \ @@ -78,11 +78,11 @@ def my_in_axes(self,ax, mouseevent): mouseevent.y < ax.bbox.bounds[1]: result2 = True - #y right axis + #y right axis elif ylabelright and mouseevent.x < ax.bbox.bounds[2] + ax.bbox.bounds[0] + self.interactive_axis_pad and \ mouseevent.x > ax.bbox.bounds[2] + ax.bbox.bounds[0] and \ mouseevent.y < ax.bbox.bounds[1] + ax.bbox.bounds[3] and \ - mouseevent.y > ax.bbox.bounds[1]: + mouseevent.y > ax.bbox.bounds[1]: result2 = True # #x top axis @@ -91,12 +91,12 @@ def my_in_axes(self,ax, mouseevent): mouseevent.y > ax.bbox.bounds[1] + ax.bbox.bounds[3] and \ mouseevent.y < ax.bbox.bounds[1] + ax.bbox.bounds[3] + xlabelsize: result2 = True - + if result1 == result2: result=result1 elif result1 or result2: result=True - + return result def on_figure(self, obj, value): @@ -124,11 +124,11 @@ def on_figure(self, obj, value): ax.spines[pos].set_zorder(10) patch_cpy.set_zorder(9) self.background_patch_copy.append(ax.add_patch(patch_cpy)) - + #set default axes self.axes = self.figure.axes[0] self.cursor_last_axis = self.figure.axes[0] - + #set min/max axes attribute self.xmin,self.xmax = [],[] self.ymin,self.ymax = [],[] @@ -136,18 +136,18 @@ def on_figure(self, obj, value): #set default xmin/xmax and ymin/ymax xmin,xmax = my_axes.get_xlim() ymin,ymax = my_axes.get_ylim() - + self.xmin.append(xmin) self.xmax.append(xmax) self.ymin.append(ymin) self.ymax.append(ymax) - + if self.legend_instance: #remove all legend_instance from parent widget for current_legend in self.legend_instance: current_legend.parent.remove_widget(current_legend) self.legend_instance=[] - + if self.auto_cursor and len(self.figure.axes) > 0: lines=[] for ax in self.figure.axes: @@ -158,29 +158,29 @@ def on_figure(self, obj, value): if self.selector and self.axes: self.selector.resize_wgt.ax = self.axes - + # Texture self._img_texture = Texture.create(size=(w, h)) - + #close last figure in memory (avoid max figure warning) matplotlib.pyplot.close() - + def __init__(self, **kwargs): self.kv_post_done = False - self.selector = None + self.selector = None super(MatplotFigureSubplot, self).__init__(**kwargs) - self.background_patch_copy=[] + self.background_patch_copy=[] def register_cursor(self,pickables=None): - + remove_artists=[] if hasattr(self,'horizontal_line'): remove_artists.append(self.horizontal_line) if hasattr(self,'vertical_line'): - remove_artists.append(self.vertical_line) + remove_artists.append(self.vertical_line) if hasattr(self,'text'): remove_artists.append(self.text) - + self.cursor_cls = cursor(self.figure,pickables=pickables,remove_artists=remove_artists) def autoscale(self): @@ -190,10 +190,10 @@ def autoscale(self): for i,ax in enumerate(axes): twinx = any(ax.get_shared_x_axes().joined(ax, prev) for prev in axes[:i]) - + twiny = any(ax.get_shared_y_axes().joined(ax, prev) for prev in axes[:i]) - + autoscale_axis = self.autoscale_axis if twinx: autoscale_axis = "y" @@ -201,10 +201,10 @@ def autoscale(self): autoscale_axis = "x" no_visible = self.myrelim(ax,visible_only=self.autoscale_visible_only) ax.autoscale_view(tight=self.autoscale_tight, - scalex=True if autoscale_axis!="y" else False, + scalex=True if autoscale_axis!="y" else False, scaley=True if autoscale_axis!="x" else False) - ax.autoscale(axis=autoscale_axis,tight=self.autoscale_tight) - + ax.autoscale(axis=autoscale_axis,tight=self.autoscale_tight) + current_xlim = ax.get_xlim() current_ylim = ax.get_ylim() lim_collection,invert_xaxis,invert_yaxis = self.data_limit_collection(ax,visible_only=self.autoscale_visible_only) @@ -214,7 +214,7 @@ def autoscale(self): current_margins = (0,0) else: current_margins = ax.margins() - + if self.autoscale_axis!="y": if invert_xaxis: if lim_collection[0]>current_xlim[0] or no_visible: @@ -222,7 +222,7 @@ def autoscale(self): xchanged=True if lim_collection[2]current_xlim[1] or no_visible: ax.set_xlim(right=lim_collection[2]) xchanged=True - + #recalculed margin if xchanged: xlim = ax.get_xlim() ax.set_xlim(left=xlim[0] - current_margins[0]*(xlim[1]-xlim[0])) ax.set_xlim(right=xlim[1] + current_margins[0]*(xlim[1]-xlim[0])) - - ychanged=False - + + ychanged=False + if self.autoscale_axis!="x": if invert_yaxis: if lim_collection[1]>current_ylim[0] or no_visible: ax.set_ylim(bottom=lim_collection[1]) ychanged=True if lim_collection[3]current_ylim[1] or no_visible: - ax.set_ylim(top=lim_collection[3]) + ax.set_ylim(top=lim_collection[3]) ychanged=True - + if ychanged: ylim = ax.get_ylim() ax.set_ylim(bottom=ylim[0] - current_margins[1]*(ylim[1]-ylim[0])) - ax.set_ylim(top=ylim[1] + current_margins[1]*(ylim[1]-ylim[0])) - + ax.set_ylim(top=ylim[1] + current_margins[1]*(ylim[1]-ylim[0])) + index = self.figure.axes.index(ax) self.xmin[index],self.xmax[index] = ax.get_xlim() self.ymin[index],self.ymax[index] = ax.get_ylim() ax.set_autoscale_on(False) - + ax.figure.canvas.draw_idle() - ax.figure.canvas.flush_events() - + ax.figure.canvas.flush_events() + def myrelim(self,ax, visible_only=False): """ @@ -295,11 +295,11 @@ def myrelim(self,ax, visible_only=False): ax._update_patch_limits(artist) no_visible=False elif isinstance(artist, mimage.AxesImage): - ax._update_image_limits(artist) + ax._update_image_limits(artist) no_visible=False - + return no_visible - + def data_limit_collection(self,ax,visible_only=False): datalim = None datalim_list = [] @@ -308,7 +308,7 @@ def data_limit_collection(self,ax,visible_only=False): if visible_only: if not collection.get_visible(): eval_lim=False - + if eval_lim: datalim_list.append(collection.get_datalim(ax.transData)) @@ -318,7 +318,7 @@ def data_limit_collection(self,ax,visible_only=False): if ax.xaxis_inverted(): invert_xaxis=True if ax.yaxis_inverted(): - invert_yaxis=True + invert_yaxis=True for i,current_datalim in enumerate(datalim_list): if i==0: if invert_xaxis: @@ -326,20 +326,20 @@ def data_limit_collection(self,ax,visible_only=False): xright = current_datalim.x0 else: xleft = current_datalim.x0 - xright = current_datalim.x1 + xright = current_datalim.x1 if invert_yaxis: ybottom = current_datalim.y1 - ytop = current_datalim.y0 + ytop = current_datalim.y0 else: ybottom = current_datalim.y0 ytop = current_datalim.y1 - - else: + + else: if invert_xaxis: if current_datalim.x1>xleft: xleft = current_datalim.x1 if current_datalim.x0ybottom: ybottom = current_datalim.y1 - if current_datalim.y0ytop: - ytop = current_datalim.y1 + if current_datalim.y1>ytop: + ytop = current_datalim.y1 datalim = [xleft,ybottom,xright,ytop] - - return datalim,invert_xaxis,invert_yaxis + + return datalim,invert_xaxis,invert_yaxis def main_home(self, *args): """ @@ -371,44 +371,44 @@ def main_home(self, *args): self._nav_stack.home() self.set_history_buttons() self._update_view() - + def home(self, event=None): - + if self.xmin is not None and \ self.xmax is not None and \ self.ymin is not None and \ self.ymax is not None: - + if event: self.myevent.x=event.x - self.pos[0] self.myevent.y=event.y - self.pos[1] - self.myevent.inaxes=self.figure.canvas.inaxes((event.x - self.pos[0], - event.y - self.pos[1])) - + self.myevent.inaxes=self.figure.canvas.inaxes((event.x - self.pos[0], + event.y - self.pos[1])) + axes = [a for a in self.figure.canvas.figure.get_axes() - if a.in_axes(self.myevent)] + if a.in_axes(self.myevent)] if not axes: return - + for ax in axes: xleft,xright=ax.get_xlim() - ybottom,ytop=ax.get_ylim() - + ybottom,ytop=ax.get_ylim() + #check inverted data inverted_x = False if xleft>xright: inverted_x=True inverted_y = False if ybottom>ytop: - inverted_y=True - + inverted_y=True + index = self.figure.axes.index(ax) xmin=self.xmin[index] xmax=self.xmax[index] ymin=self.ymin[index] ymax=self.ymax[index] - + if inverted_x: ax.set_xlim(right=xmin,left=xmax) else: @@ -416,28 +416,28 @@ def home(self, event=None): if inverted_y: ax.set_ylim(top=ymin,bottom=ymax) else: - ax.set_ylim(bottom=ymin,top=ymax) + ax.set_ylim(bottom=ymin,top=ymax) else: ax = self.axes xleft,xright=ax.get_xlim() - ybottom,ytop=ax.get_ylim() - + ybottom,ytop=ax.get_ylim() + #check inverted data inverted_x = False if xleft>xright: inverted_x=True inverted_y = False if ybottom>ytop: - inverted_y=True - + inverted_y=True + index = self.figure.axes.index(ax) xmin=self.xmin[index] xmax=self.xmax[index] ymin=self.ymin[index] ymax=self.ymax[index] - + if inverted_x: ax.set_xlim(right=xmin,left=xmax) else: @@ -445,39 +445,39 @@ def home(self, event=None): if inverted_y: ax.set_ylim(top=ymin,bottom=ymax) else: - ax.set_ylim(bottom=ymin,top=ymax) + ax.set_ylim(bottom=ymin,top=ymax) if self.last_line is not None: - self.clear_line_prop() - + self.clear_line_prop() + ax.figure.canvas.draw_idle() - ax.figure.canvas.flush_events() + ax.figure.canvas.flush_events() def get_data_xy(self,x,y): """ manage x y data in navigation bar """ self.myevent_cursor.x=x - self.pos[0] self.myevent_cursor.y=y - self.pos[1] self.myevent_cursor.inaxes=self.figure.canvas.inaxes((x - self.pos[0], - y - self.pos[1])) + y - self.pos[1])) #find all axis axes = [a for a in self.figure.canvas.figure.get_axes() - if a.in_axes(self.myevent_cursor)] - + if a.in_axes(self.myevent_cursor)] + if axes: trans = axes[0].transData.inverted() #always use first axis xdata, ydata = trans.transform_point((x - self.pos[0], y - self.pos[1])) - + if self.cursor_xaxis_formatter: - x_format = self.cursor_xaxis_formatter.format_data(xdata) + x_format = self.cursor_xaxis_formatter.format_data(xdata) else: x_format = self.axes.xaxis.get_major_formatter().format_data_short(xdata) - + if self.cursor_yaxis_formatter: - y_format = self.cursor_yaxis_formatter.format_data(ydata) + y_format = self.cursor_yaxis_formatter.format_data(ydata) else: y_format = self.axes.yaxis.get_major_formatter().format_data_short(ydata) - + return x_format,y_format else: return None,None @@ -500,7 +500,7 @@ def on_touch_down(self, event): break if select_legend: if self.touch_mode!='drag_legend': - return False + return False else: event.grab(self) self._touches.append(event) @@ -508,11 +508,11 @@ def on_touch_down(self, event): if len(self._touches)>1: #new touch, reset background self.background=None - - return True + + return True else: self.current_legend = None - + if event.is_mouse_scrolling: if not self.disable_mouse_scrolling: self.zoom_factory(event, base_scale=1.2) @@ -520,55 +520,55 @@ def on_touch_down(self, event): elif event.is_double_tap: if not self.disable_double_tap: - if self.touch_mode!='selector': + if self.touch_mode!='selector': self.home(event) return True - + else: if self.touch_mode=='cursor': self.hover_on=True - self.hover(event) + self.hover(event) elif self.touch_mode=='zoombox': real_x, real_y = x - self.pos[0], y - self.pos[1] self.x_init=x self.y_init=real_y - self.draw_box(event, x, real_y, x, real_y,onpress=True) + self.draw_box(event, x, real_y, x, real_y,onpress=True) elif self.touch_mode=='minmax': - self.min_max(event) + self.min_max(event) elif self.touch_mode=='selector': - pass - + pass + event.grab(self) self._touches.append(event) self._last_touch_pos[event] = event.pos if len(self._touches)>1: #new touch, reset background self.background=None - + return True else: - return False + return False def hover(self, event) -> None: """ hover cursor method (cursor to nearest value) - + Args: event: touch kivy event - + Return: None - + """ - + #if cursor is set -> hover is on if self.hover_on: - + #trick to improve app fps if self.max_hover_rate is not None: if self.last_hover_time is None: self.last_hover_time = time.time() - + elif time.time() - self.last_hover_time < self.max_hover_rate: return else: @@ -577,38 +577,38 @@ def hover(self, event) -> None: #mimic matplotlib mouse event with kivy touch evant self.myevent.x=event.x - self.pos[0] self.myevent.y=event.y - self.pos[1] - self.myevent.inaxes=self.figure.canvas.inaxes((event.x - self.pos[0], + self.myevent.inaxes=self.figure.canvas.inaxes((event.x - self.pos[0], event.y - self.pos[1])) self.myevent.pickradius=self.pickradius self.myevent.projection=self.projection self.myevent.compare_xdata=self.compare_xdata self.myevent.pick_radius_axis = self.pick_radius_axis #find closest artist from kivy event - sel = self.cursor_cls.xy_event(self.myevent) - + sel = self.cursor_cls.xy_event(self.myevent) + #case if no good result if not sel: if hasattr(self,'horizontal_line'): - self.set_cross_hair_visible(False) + self.set_cross_hair_visible(False) if self.hover_instance: self.hover_instance.x_hover_pos=self.x - self.hover_instance.y_hover_pos=self.y + self.hover_instance.y_hover_pos=self.y self.hover_instance.show_cursor=False self.x_hover_data = None self.y_hover_data = None if self.highlight_hover: axes = [a for a in self.figure.canvas.figure.get_axes() - if a.in_axes(self.myevent)] - - if not axes: - + if a.in_axes(self.myevent)] + + if not axes: + if self.last_line: - self.clear_line_prop() + self.clear_line_prop() ax=self.axes if self.background: ax.figure.canvas.restore_region(self.background) #draw (blit method) - ax.figure.canvas.blit(ax.bbox) + ax.figure.canvas.blit(ax.bbox) ax.figure.canvas.flush_events() self.background = None return @@ -621,17 +621,17 @@ def hover(self, event) -> None: line=sel[0].artist custom_x=None if not hasattr(line,'axes'): - if hasattr(line,'_ContainerArtist__keep_alive'): + if hasattr(line,'_ContainerArtist__keep_alive'): if self.hist_range and isinstance(line,BarContainer): x_hist, y_hist, width_hist, height_hist = line[sel.index].get_bbox().bounds if self.cursor_xaxis_formatter: - custom_x = f"{self.cursor_xaxis_formatter.format_data(x_hist)}-{self.cursor_xaxis_formatter.format_data(x_hist+ width_hist)}" + custom_x = f"{self.cursor_xaxis_formatter.format_data(x_hist)}-{self.cursor_xaxis_formatter.format_data(x_hist+ width_hist)}" else: custom_x = f"{x_hist}-{x_hist+ width_hist}" - + line =line._ContainerArtist__keep_alive[0] ax=line.axes - + if ax is None: return @@ -640,60 +640,60 @@ def hover(self, event) -> None: x = sel[0].target[0] y = sel[0].target[1] - xy_pos = ax.transData.transform([(x,y)]) + xy_pos = ax.transData.transform([(x,y)]) self.x_hover_data = x self.y_hover_data = y self.hover_instance.x_hover_pos=float(xy_pos[0][0]) + self.x self.hover_instance.y_hover_pos=float(xy_pos[0][1]) + self.y self.hover_instance.y_touch_pos=float(event.y) - + if self.first_call_compare_hover: - self.hover_instance.show_cursor=True + self.hover_instance.show_cursor=True else: self.first_call_compare_hover=True - + available_widget = self.hover_instance.children_list nb_widget=len(available_widget) index_list=list(range(nb_widget)) for i in range(len(sel)): - + if i > nb_widget-1: break else: - + line=sel[i].artist line_label = line.get_label() if line_label in self.hover_instance.children_names: - # index= self.hover_instance.children_names.index(line_label) + # index= self.hover_instance.children_names.index(line_label) index = [ii for ii, x in enumerate(self.hover_instance.children_names) \ if x == line_label and ii in index_list][0] - - y = sel[i].target[1] - xy_pos = ax.transData.transform([(x,y)]) + y = sel[i].target[1] + + xy_pos = ax.transData.transform([(x,y)]) pos_y=float(xy_pos[0][1]) + self.y - + if pos_yself.y+ax.bbox.bounds[1]: available_widget[index].x_hover_pos=float(xy_pos[0][0]) + self.x available_widget[index].y_hover_pos=float(xy_pos[0][1]) + self.y available_widget[index].custom_color = get_color_from_hex(to_hex(line.get_color())) - + if self.cursor_yaxis_formatter: - y = self.cursor_yaxis_formatter.format_data(y) + y = self.cursor_yaxis_formatter.format_data(y) else: y = ax.yaxis.get_major_formatter().format_data_short(y) available_widget[index].label_y_value=f"{y}" available_widget[index].show_widget=True index_list.remove(index) - + if i None: self.hover_instance.overlap_check() self.hover_instance.xmin_line = float(ax.bbox.bounds[0]) + self.x - self.hover_instance.xmax_line = float(ax.bbox.bounds[0] + ax.bbox.bounds[2]) + self.x + self.hover_instance.xmax_line = float(ax.bbox.bounds[0] + ax.bbox.bounds[2]) + self.x self.hover_instance.ymin_line = float(ax.bbox.bounds[1]) + self.y self.hover_instance.ymax_line = float(ax.bbox.bounds[1] + ax.bbox.bounds[3]) + self.y - + if self.hover_instance.x_hover_pos>self.x+ax.bbox.bounds[2] + ax.bbox.bounds[0] or \ - self.hover_instance.x_hover_pos None: extra_data = sel.artist.get_cursor_data(self.myevent) ax=line.axes - + if ax is None: return - + self.cursor_last_axis=ax - + if hasattr(self,'horizontal_line'): self.horizontal_line.set_ydata([y,]) - + if hasattr(self,'vertical_line'): self.vertical_line.set_xdata([x,]) - + #x y label - if self.hover_instance: - xy_pos = ax.transData.transform([(x,y)]) + if self.hover_instance: + xy_pos = ax.transData.transform([(x,y)]) self.x_hover_data = x self.y_hover_data = y self.hover_instance.x_hover_pos=float(xy_pos[0][0]) + self.x self.hover_instance.y_hover_pos=float(xy_pos[0][1]) + self.y self.hover_instance.show_cursor=True - + if self.cursor_xaxis_formatter: x = self.cursor_xaxis_formatter.format_data(x) else: x = ax.xaxis.get_major_formatter().format_data_short(x) if self.cursor_yaxis_formatter: - y = self.cursor_yaxis_formatter.format_data(y) + y = self.cursor_yaxis_formatter.format_data(y) else: - y = ax.yaxis.get_major_formatter().format_data_short(y) + y = ax.yaxis.get_major_formatter().format_data_short(y) if custom_x: self.hover_instance.label_x_value=custom_x else: self.hover_instance.label_x_value=f"{x}" - + if invert_xy: self.hover_instance.label_y_value=f"{x}" else: self.hover_instance.label_y_value=f"{y}" - + if extra_data is not None: self.hover_instance.label_y_value+=' [' + str(extra_data) + ']' self.hover_instance.xmin_line = float(ax.bbox.bounds[0]) + self.x - self.hover_instance.xmax_line = float(ax.bbox.bounds[0] + ax.bbox.bounds[2]) + self.x + self.hover_instance.xmax_line = float(ax.bbox.bounds[0] + ax.bbox.bounds[2]) + self.x self.hover_instance.ymin_line = float(ax.bbox.bounds[1]) + self.y self.hover_instance.ymax_line = float(ax.bbox.bounds[1] + ax.bbox.bounds[3]) + self.y - + if hasattr(line,'get_label'): self.hover_instance.custom_label = line.get_label() if hasattr(line,'get_color'): self.hover_instance.custom_color = get_color_from_hex(to_hex(line.get_color())) - + if self.hover_instance.x_hover_pos>self.x+ax.bbox.bounds[2] + ax.bbox.bounds[0] or \ self.hover_instance.x_hover_posself.y+ax.bbox.bounds[1] + ax.bbox.bounds[3] or \ - self.hover_instance.y_hover_pos None: for current_line in lines_list: default_alpha.append(current_line.get_alpha()) current_line.set_alpha(self.highlight_alpha) - + ax.figure.canvas.draw_idle() - ax.figure.canvas.flush_events() + ax.figure.canvas.flush_events() self.background_highlight=ax.figure.canvas.copy_from_bbox(ax.figure.bbox) self.last_line=line for i,current_line in enumerate(lines_list): current_line.set_alpha(default_alpha[i]) default_alpha[i] - + if self.highlight_prop: self.last_line_prop={} for key in self.highlight_prop: @@ -854,53 +854,53 @@ def hover(self, event) -> None: elif self.last_line_prop: for key in self.last_line_prop: set_line_attr = getattr(self.last_line,'set_' + key) - set_line_attr(self.last_line_prop[key]) + set_line_attr(self.last_line_prop[key]) self.hover_instance.custom_color = get_color_from_hex(to_hex(line.get_color())) self.last_line_prop={} for key in self.highlight_prop: line_attr = getattr(line,'get_' + key) self.last_line_prop.update({key:line_attr()}) set_line_attr = getattr(line,'set_' + key) - set_line_attr(self.highlight_prop[key]) + set_line_attr(self.highlight_prop[key]) self.last_line=line ax.figure.canvas.restore_region(self.background_highlight) ax.draw_artist(line) - + #draw (blit method) - ax.figure.canvas.blit(self.axes.bbox) - ax.figure.canvas.flush_events() - + ax.figure.canvas.blit(self.axes.bbox) + ax.figure.canvas.flush_events() + return else: if self.cursor_xaxis_formatter: x = self.cursor_xaxis_formatter.format_data(x) else: - x = ax.xaxis.get_major_formatter().format_data_short(x) + x = ax.xaxis.get_major_formatter().format_data_short(x) if self.cursor_yaxis_formatter: - y = self.cursor_yaxis_formatter.format_data(y) + y = self.cursor_yaxis_formatter.format_data(y) else: - y = ax.yaxis.get_major_formatter().format_data_short(y) + y = ax.yaxis.get_major_formatter().format_data_short(y) self.text.set_text(f"x={x}, y={y}") - - #blit method (always use because same visual effect as draw) + + #blit method (always use because same visual effect as draw) if self.background is None: self.set_cross_hair_visible(False) self.figcanvas.draw_idle() - self.figcanvas.flush_events() + self.figcanvas.flush_events() self.background = self.figcanvas.copy_from_bbox(ax.figure.bbox) - self.set_cross_hair_visible(True) + self.set_cross_hair_visible(True) if self.last_line is not None: self.clear_line_prop() - + self.figcanvas.restore_region(self.background) ax.draw_artist(self.text) - + ax.draw_artist(self.horizontal_line) - ax.draw_artist(self.vertical_line) - + ax.draw_artist(self.vertical_line) + #draw (blit method) - self.figcanvas.blit(ax.bbox) + self.figcanvas.blit(ax.bbox) self.figcanvas.flush_events() def zoom_factory(self, event, base_scale=1.1): @@ -912,56 +912,56 @@ def zoom_factory(self, event, base_scale=1.1): if not self._pick_info: self.myevent.x=event.x - self.pos[0] self.myevent.y=event.y - self.pos[1] - self.myevent.inaxes=self.figure.canvas.inaxes((x,y)) + self.myevent.inaxes=self.figure.canvas.inaxes((x,y)) #press event axes = [a for a in self.figure.canvas.figure.get_axes() - if a.in_axes(self.myevent)] + if a.in_axes(self.myevent)] self._pick_info = axes - + if not self._pick_info: - return + return for i,ax in enumerate(self._pick_info): self.axes = ax - + twinx = any(ax.get_shared_x_axes().joined(ax, prev) for prev in self._pick_info[:i]) - + twiny = any(ax.get_shared_y_axes().joined(ax, prev) for prev in self._pick_info[:i]) - + trans = ax.transData.inverted() - xdata, ydata = trans.transform_point((x, y)) - + xdata, ydata = trans.transform_point((x, y)) + cur_xlim = ax.get_xlim() cur_ylim = ax.get_ylim() - + scale=ax.get_xscale() yscale=ax.get_yscale() - + if scale == 'linear': old_min=cur_xlim[0] old_max=cur_xlim[1] - + else: min_=cur_xlim[0] - max_=cur_xlim[1] + max_=cur_xlim[1] old_min = self.transform_eval(min_,ax.yaxis) xdata = self.transform_eval(xdata,ax.yaxis) - old_max = self.transform_eval(max_,ax.yaxis) - + old_max = self.transform_eval(max_,ax.yaxis) + if yscale == 'linear': yold_min=cur_ylim[0] yold_max=cur_ylim[1] - + else: ymin_=cur_ylim[0] - ymax_=cur_ylim[1] + ymax_=cur_ylim[1] yold_min = self.transform_eval(ymin_,ax.yaxis) ydata = self.transform_eval(ydata,ax.yaxis) yold_max = self.transform_eval(ymax_,ax.yaxis) - + if event.button == 'scrolldown': # deal with zoom in scale_factor = 1 / base_scale @@ -972,13 +972,13 @@ def zoom_factory(self, event, base_scale=1.1): # deal with something that should never happen scale_factor = 1 print(event.button) - + new_width = (old_max - old_min) * scale_factor new_height = (yold_max - yold_min) * scale_factor - + relx = (old_max - xdata) / (old_max - old_min) rely = (yold_max - ydata) / (yold_max - yold_min) - + if self.do_zoom_x and not twinx: if scale == 'linear': ax.set_xlim([xdata - new_width * (1 - relx), xdata + new_width * (relx)]) @@ -990,9 +990,9 @@ def zoom_factory(self, event, base_scale=1.1): except OverflowError: # Limit case new_min, new_max = min_, max_ if new_min <= 0. or new_max <= 0.: # Limit case - new_min, new_max = min_, max_ + new_min, new_max = min_, max_ ax.set_xlim([new_min, new_max]) - + if self.do_zoom_y and not twiny: if yscale == 'linear': ax.set_ylim([ydata - new_height * (1 - rely), ydata + new_height * (rely)]) @@ -1004,8 +1004,8 @@ def zoom_factory(self, event, base_scale=1.1): except OverflowError: # Limit case new_ymin, new_ymax = ymin_, ymax_ if new_ymin <= 0. or new_ymax <= 0.: # Limit case - new_ymin, new_ymax = ymin_, ymax_ - ax.set_ylim([new_ymin, new_ymax]) + new_ymin, new_ymax = ymin_, ymax_ + ax.set_ylim([new_ymin, new_ymax]) self._pick_info = None ax.figure.canvas.draw_idle() @@ -1015,7 +1015,7 @@ def apply_zoom(self, scale_factor, ax, anchor=(0, 0),new_line=None): """ zoom touch method """ if self.touch_mode=='selector': return - + x = anchor[0]-self.pos[0] y = anchor[1]-self.pos[1] @@ -1023,66 +1023,66 @@ def apply_zoom(self, scale_factor, ax, anchor=(0, 0),new_line=None): if not self._pick_info: self.myevent.x=x self.myevent.y=y - self.myevent.inaxes=self.figure.canvas.inaxes((x,y)) + self.myevent.inaxes=self.figure.canvas.inaxes((x,y)) #press event axes = [a for a in self.figure.canvas.figure.get_axes() - if a.in_axes(self.myevent)] + if a.in_axes(self.myevent)] self._pick_info = axes - + if not self._pick_info: - return - + return + artists=[] for i,ax in enumerate(self._pick_info): - + self.axes = ax - + twinx = any(ax.get_shared_x_axes().joined(ax, prev) for prev in self._pick_info[:i]) - + twiny = any(ax.get_shared_y_axes().joined(ax, prev) for prev in self._pick_info[:i]) artists.extend(_iter_axes_subartists(ax)) trans = ax.transData.inverted() - xdata, ydata = trans.transform_point((x+new_line.x, y+new_line.y)) - + xdata, ydata = trans.transform_point((x+new_line.x, y+new_line.y)) + cur_xlim = ax.get_xlim() - cur_ylim = ax.get_ylim() - + cur_ylim = ax.get_ylim() + scale=ax.get_xscale() yscale=ax.get_yscale() - + if scale == 'linear': old_min=cur_xlim[0] old_max=cur_xlim[1] - + else: min_=cur_xlim[0] - max_=cur_xlim[1] + max_=cur_xlim[1] old_min = self.transform_eval(min_,ax.yaxis) xdata = self.transform_eval(xdata,ax.yaxis) - old_max = self.transform_eval(max_,ax.yaxis) - + old_max = self.transform_eval(max_,ax.yaxis) + if yscale == 'linear': yold_min=cur_ylim[0] yold_max=cur_ylim[1] - + else: ymin_=cur_ylim[0] - ymax_=cur_ylim[1] + ymax_=cur_ylim[1] yold_min = self.transform_eval(ymin_,ax.yaxis) ydata = self.transform_eval(ydata,ax.yaxis) yold_max = self.transform_eval(ymax_,ax.yaxis) - + new_width = (old_max - old_min) * scale_factor new_height = (yold_max - yold_min) * scale_factor - + relx = (old_max - xdata) / (old_max - old_min) rely = (yold_max - ydata) / (yold_max - yold_min) - + if self.do_zoom_x and not twinx: if scale == 'linear': ax.set_xlim([xdata - new_width * (1 - relx), xdata + new_width * (relx)]) @@ -1094,9 +1094,9 @@ def apply_zoom(self, scale_factor, ax, anchor=(0, 0),new_line=None): except OverflowError: # Limit case new_min, new_max = min_, max_ if new_min <= 0. or new_max <= 0.: # Limit case - new_min, new_max = min_, max_ + new_min, new_max = min_, max_ ax.set_xlim([new_min, new_max]) - + if self.do_zoom_y and not twiny: if yscale == 'linear': ax.set_ylim([ydata - new_height * (1 - rely), ydata + new_height * (rely)]) @@ -1108,51 +1108,51 @@ def apply_zoom(self, scale_factor, ax, anchor=(0, 0),new_line=None): except OverflowError: # Limit case new_ymin, new_ymax = ymin_, ymax_ if new_ymin <= 0. or new_ymax <= 0.: # Limit case - new_ymin, new_ymax = ymin_, ymax_ - ax.set_ylim([new_ymin, new_ymax]) - - if self.fast_draw: - #use blit method + new_ymin, new_ymax = ymin_, ymax_ + ax.set_ylim([new_ymin, new_ymax]) + + if self.fast_draw: + #use blit method if self.background is None or self.last_line is not None: if self.draw_all_axes: for myax in self.figure.canvas.figure.get_axes(): - index = self.figure.axes.index(myax) - self.background_patch_copy[index].set_visible(True) + index = self.figure.axes.index(myax) + self.background_patch_copy[index].set_visible(True) else: - index = self.figure.axes.index(ax) + index = self.figure.axes.index(ax) self.background_patch_copy[index].set_visible(True) self.figcanvas.draw_idle() - self.figcanvas.flush_events() + self.figcanvas.flush_events() self.background = self.figcanvas.copy_from_bbox(ax.figure.bbox) if self.draw_all_axes: for myax in self.figure.canvas.figure.get_axes(): - index = self.figure.axes.index(myax) - self.background_patch_copy[index].set_visible(False) - else: - self.background_patch_copy[index].set_visible(False) + index = self.figure.axes.index(myax) + self.background_patch_copy[index].set_visible(False) + else: + self.background_patch_copy[index].set_visible(False) if self.last_line is not None: - self.clear_line_prop() - self.figcanvas.restore_region(self.background) - + self.clear_line_prop() + self.figcanvas.restore_region(self.background) + if self.draw_all_axes: for myax in self.figure.canvas.figure.get_axes(): artists = _iter_axes_subartists(myax) - for artist in artists: + for artist in artists: ax.draw_artist(artist) - + else: - for artist in artists: + for artist in artists: ax.draw_artist(artist) - + self.figcanvas.blit(ax.bbox) - self.figcanvas.flush_events() - + self.figcanvas.flush_events() + self.update_hover() - + else: self.figcanvas.draw_idle() - self.figcanvas.flush_events() + self.figcanvas.flush_events() def apply_pan(self, my_ax, event, mode='pan'): @@ -1162,56 +1162,56 @@ def apply_pan(self, my_ax, event, mode='pan'): if not self._pick_info: self.myevent.x=event.x - self.pos[0] self.myevent.y=event.y - self.pos[1] - self.myevent.inaxes=self.figure.canvas.inaxes((event.x, - event.y)) + self.myevent.inaxes=self.figure.canvas.inaxes((event.x, + event.y)) #press event axes = [a for a in self.figure.canvas.figure.get_axes() - if a.in_axes(self.myevent)] - if not axes: + if a.in_axes(self.myevent)] + if not axes: if self.first_touch_pan!='pan' and self.interactive_axis: axes = [a for a in self.figure.canvas.figure.get_axes() - if self.my_in_axes(a,self.myevent)] + if self.my_in_axes(a,self.myevent)] self._pick_info = axes - + if not self._pick_info: return artists=[] for i,ax in enumerate(self._pick_info): - + self.axes = ax artists.extend(_iter_axes_subartists(ax)) twinx = any(ax.get_shared_x_axes().joined(ax, prev) for prev in self._pick_info[:i]) - + twiny = any(ax.get_shared_y_axes().joined(ax, prev) for prev in self._pick_info[:i]) trans = ax.transData.inverted() xdata, ydata = trans.transform_point((event.x-self.pos[0], event.y-self.pos[1])) xpress, ypress = trans.transform_point((self._last_touch_pos[event][0]-self.pos[0], self._last_touch_pos[event][1]-self.pos[1])) - + scale=ax.get_xscale() yscale=ax.get_yscale() - + if scale == 'linear': dx = xdata - xpress else: dx = self.transform_eval(xdata,ax.xaxis) - \ self.transform_eval(xpress,ax.xaxis) - + if yscale == 'linear': dy = ydata - ypress else: dy = self.transform_eval(ydata,ax.yaxis) - \ - self.transform_eval(ypress,ax.yaxis) - + self.transform_eval(ypress,ax.yaxis) + xleft,xright=ax.get_xlim() ybottom,ytop=ax.get_ylim() - + #check inverted data inverted_x = False if xleft>xright: @@ -1221,26 +1221,26 @@ def apply_pan(self, my_ax, event, mode='pan'): cur_xlim=(xleft,xright) inverted_y = False if ybottom>ytop: - inverted_y=True + inverted_y=True cur_ylim=(ytop,ybottom) else: - cur_ylim=(ybottom,ytop) - + cur_ylim=(ybottom,ytop) + if self.interactive_axis and self.touch_mode=='pan' and not self.first_touch_pan=='pan': xlabelsize = self.interactive_axis_pad - ylabelsize = self.interactive_axis_pad - + ylabelsize = self.interactive_axis_pad + xlabelbottom = ax.xaxis._major_tick_kw.get('tick1On') xlabeltop = ax.xaxis._major_tick_kw.get('tick2On') ylabelleft = ax.yaxis._major_tick_kw.get('tick1On') ylabelright = ax.yaxis._major_tick_kw.get('tick2On') - + # if (ydata < cur_ylim[0] and not inverted_y) or (ydata > cur_ylim[1] and inverted_y): if xlabelbottom and event.x>self.x +ax.bbox.bounds[0] and \ event.xself.y + ax.bbox.bounds[1] - xlabelsize and \ - event.yself.y + ax.bbox.bounds[1]: - + top_lim = self.y+ax.bbox.bounds[3]+ax.bbox.bounds[1] - bottom_lim = self.y+ax.bbox.bounds[1] + bottom_lim = self.y+ax.bbox.bounds[1] bottom_anchor_zone= (top_lim - bottom_lim)*.2 + bottom_lim - top_anchor_zone= (top_lim - bottom_lim)*.8 + bottom_lim + top_anchor_zone= (top_lim - bottom_lim)*.8 + bottom_lim if event.y < bottom_anchor_zone or event.y > top_anchor_zone: mode = 'adjust_y' self.current_anchor_axis = ax else: - mode= 'pan_y' + mode= 'pan_y' self.touch_mode = mode - + elif ylabelright and event.xself.x + ax.bbox.bounds[2] + ax.bbox.bounds[0] and \ event.yself.y + ax.bbox.bounds[1]: + event.y>self.y + ax.bbox.bounds[1]: top_lim = self.y+ax.bbox.bounds[3]+ax.bbox.bounds[1] - bottom_lim = self.y+ax.bbox.bounds[1] + bottom_lim = self.y+ax.bbox.bounds[1] bottom_anchor_zone= (top_lim - bottom_lim)*.2 + bottom_lim - top_anchor_zone= (top_lim - bottom_lim)*.8 + bottom_lim + top_anchor_zone= (top_lim - bottom_lim)*.8 + bottom_lim if event.y < bottom_anchor_zone or event.y > top_anchor_zone: mode = 'adjust_y' self.current_anchor_axis = ax else: - mode= 'pan_y' - self.touch_mode = mode + mode= 'pan_y' + self.touch_mode = mode elif xlabeltop and event.x>self.x +ax.bbox.bounds[0] and \ event.xself.y +ax.bbox.bounds[1] + ax.bbox.bounds[3] and \ - event.y midpoint: if inverted_x: @@ -1320,16 +1320,16 @@ def apply_pan(self, my_ax, event, mode='pan'): self.anchor_x='left' else: self.anchor_x='right' - if self.anchor_x=='left': + if self.anchor_x=='left': if xdata> cur_xlim[0]: if scale == 'linear': cur_xlim -= dx else: try: cur_xlim = [self.inv_transform_eval((self.transform_eval(cur_xlim[0],ax.xaxis) - dx),ax.xaxis), - self.inv_transform_eval((self.transform_eval(cur_xlim[1],ax.xaxis) - dx),ax.xaxis)] + self.inv_transform_eval((self.transform_eval(cur_xlim[1],ax.xaxis) - dx),ax.xaxis)] except (ValueError, OverflowError): - cur_xlim = cur_xlim # Keep previous limits + cur_xlim = cur_xlim # Keep previous limits if inverted_x: ax.set_xlim(cur_xlim[1],None) else: @@ -1341,9 +1341,9 @@ def apply_pan(self, my_ax, event, mode='pan'): else: try: cur_xlim = [self.inv_transform_eval((self.transform_eval(cur_xlim[0],ax.xaxis) - dx),ax.xaxis), - self.inv_transform_eval((self.transform_eval(cur_xlim[1],ax.xaxis) - dx),ax.xaxis)] + self.inv_transform_eval((self.transform_eval(cur_xlim[1],ax.xaxis) - dx),ax.xaxis)] except (ValueError, OverflowError): - cur_xlim = cur_xlim # Keep previous limits + cur_xlim = cur_xlim # Keep previous limits if inverted_x: ax.set_xlim(None,cur_xlim[0]) else: @@ -1355,14 +1355,14 @@ def apply_pan(self, my_ax, event, mode='pan'): else: try: cur_xlim = [self.inv_transform_eval((self.transform_eval(cur_xlim[0],ax.xaxis) - dx),ax.xaxis), - self.inv_transform_eval((self.transform_eval(cur_xlim[1],ax.xaxis) - dx),ax.xaxis)] + self.inv_transform_eval((self.transform_eval(cur_xlim[1],ax.xaxis) - dx),ax.xaxis)] except (ValueError, OverflowError): - cur_xlim = cur_xlim # Keep previous limits + cur_xlim = cur_xlim # Keep previous limits if inverted_x: ax.set_xlim(cur_xlim[1],cur_xlim[0]) else: ax.set_xlim(cur_xlim) - + if not mode=='pan_x' and not mode=='adjust_x': if mode=='adjust_y': if self.current_anchor_axis == ax: @@ -1370,27 +1370,27 @@ def apply_pan(self, my_ax, event, mode='pan'): midpoint = ((self.y+ax.bbox.bounds[3] + ax.bbox.bounds[1]) + (self.y+ax.bbox.bounds[1]))/2 if event.y > midpoint: if inverted_y: - self.anchor_y='bottom' + self.anchor_y='bottom' else: self.anchor_y='top' else: if inverted_y: - self.anchor_y='top' + self.anchor_y='top' else: - self.anchor_y='bottom' - + self.anchor_y='bottom' + if self.anchor_y=='top': if ydata> cur_ylim[0]: if yscale == 'linear': cur_ylim -= dy - + else: try: cur_ylim = [self.inv_transform_eval((self.transform_eval(cur_ylim[0],ax.yaxis) - dy),ax.yaxis), self.inv_transform_eval((self.transform_eval(cur_ylim[1],ax.yaxis) - dy),ax.yaxis)] except (ValueError, OverflowError): - cur_ylim = cur_ylim # Keep previous limits - + cur_ylim = cur_ylim # Keep previous limits + if inverted_y: ax.set_ylim(cur_ylim[1],None) else: @@ -1399,28 +1399,28 @@ def apply_pan(self, my_ax, event, mode='pan'): if ydata< cur_ylim[1]: if yscale == 'linear': cur_ylim -= dy - + else: try: cur_ylim = [self.inv_transform_eval((self.transform_eval(cur_ylim[0],ax.yaxis) - dy),ax.yaxis), self.inv_transform_eval((self.transform_eval(cur_ylim[1],ax.yaxis) - dy),ax.yaxis)] except (ValueError, OverflowError): - cur_ylim = cur_ylim # Keep previous limits + cur_ylim = cur_ylim # Keep previous limits if inverted_y: - ax.set_ylim(None,cur_ylim[0]) + ax.set_ylim(None,cur_ylim[0]) else: ax.set_ylim(cur_ylim[0],None) - else: + else: if not twiny: if yscale == 'linear': cur_ylim -= dy - + else: try: cur_ylim = [self.inv_transform_eval((self.transform_eval(cur_ylim[0],ax.yaxis) - dy),ax.yaxis), self.inv_transform_eval((self.transform_eval(cur_ylim[1],ax.yaxis) - dy),ax.yaxis)] except (ValueError, OverflowError): - cur_ylim = cur_ylim # Keep previous limits + cur_ylim = cur_ylim # Keep previous limits if inverted_y: ax.set_ylim(cur_ylim[1],cur_ylim[0]) else: @@ -1428,87 +1428,87 @@ def apply_pan(self, my_ax, event, mode='pan'): if self.first_touch_pan is None: self.first_touch_pan=self.touch_mode - - if self.fast_draw: - #use blit method + + if self.fast_draw: + #use blit method if self.background is None or self.last_line is not None: if self.draw_all_axes: for myax in self.figure.canvas.figure.get_axes(): - index = self.figure.axes.index(myax) - self.background_patch_copy[index].set_visible(True) + index = self.figure.axes.index(myax) + self.background_patch_copy[index].set_visible(True) else: - index = self.figure.axes.index(ax) + index = self.figure.axes.index(ax) self.background_patch_copy[index].set_visible(True) self.figcanvas.draw_idle() - self.figcanvas.flush_events() + self.figcanvas.flush_events() self.background = self.figcanvas.copy_from_bbox(ax.figure.bbox) if self.draw_all_axes: for myax in self.figure.canvas.figure.get_axes(): - index = self.figure.axes.index(myax) - self.background_patch_copy[index].set_visible(False) - else: + index = self.figure.axes.index(myax) + self.background_patch_copy[index].set_visible(False) + else: self.background_patch_copy[index].set_visible(False) if self.last_line is not None: - self.clear_line_prop() - self.figcanvas.restore_region(self.background) - + self.clear_line_prop() + self.figcanvas.restore_region(self.background) + if self.draw_all_axes: for myax in self.figure.canvas.figure.get_axes(): artists = _iter_axes_subartists(myax) - for artist in artists: + for artist in artists: ax.draw_artist(artist) - + else: - for artist in artists: + for artist in artists: ax.draw_artist(artist) self.figcanvas.blit(ax.bbox) - self.figcanvas.flush_events() - + self.figcanvas.flush_events() + self.update_hover() - + else: self.figcanvas.draw_idle() - self.figcanvas.flush_events() + self.figcanvas.flush_events() def update_hover(self): """ update hover on fast draw (if exist)""" if self.hover_instance: #update hover pos if needed - if self.hover_instance.show_cursor and self.x_hover_data is not None and self.y_hover_data is not None: + if self.hover_instance.show_cursor and self.x_hover_data is not None and self.y_hover_data is not None: # if self.cursor_last_axis.axes==self.axes: - xy_pos = self.cursor_last_axis.transData.transform([(self.x_hover_data,self.y_hover_data)]) + xy_pos = self.cursor_last_axis.transData.transform([(self.x_hover_data,self.y_hover_data)]) self.hover_instance.x_hover_pos=float(xy_pos[0][0]) + self.x self.hover_instance.y_hover_pos=float(xy_pos[0][1]) + self.y self.hover_instance.xmin_line = float(self.axes.bbox.bounds[0]) + self.x - self.hover_instance.xmax_line = float(self.axes.bbox.bounds[0] + self.axes.bbox.bounds[2]) + self.x + self.hover_instance.xmax_line = float(self.axes.bbox.bounds[0] + self.axes.bbox.bounds[2]) + self.x self.hover_instance.ymin_line = float(self.axes.bbox.bounds[1]) + self.y self.hover_instance.ymax_line = float(self.axes.bbox.bounds[1] + self.axes.bbox.bounds[3] )+ self.y - + if self.hover_instance.x_hover_pos>self.x+self.axes.bbox.bounds[2] + self.axes.bbox.bounds[0] or \ self.hover_instance.x_hover_posself.y+self.axes.bbox.bounds[1] + self.axes.bbox.bounds[3] or \ - self.hover_instance.y_hover_posself.x +ax.bbox.bounds[0] and \ event.xself.y + ax.bbox.bounds[1] - xlabelsize and \ - event.y right_anchor_zone: + if event.x < left_anchor_zone or event.x > right_anchor_zone: if self.text_instance: if not self.text_instance.show_text: @@ -1539,10 +1539,10 @@ def min_max(self, event): self.text_instance.x_text_pos=float(self.x+ax.bbox.bounds[2] + ax.bbox.bounds[0]) self.text_instance.y_text_pos=float(self.y+ax.bbox.bounds[1]) - self.text_instance.text_height self.text_instance.offset_text = True - + self.text_instance.current_axis = ax self.text_instance.kind = {'axis':'x','anchor':anchor} - + self.text_instance.show_text=True return @@ -1550,11 +1550,11 @@ def min_max(self, event): event.xself.y + ax.bbox.bounds[1]: - + top_lim = self.y+ax.bbox.bounds[3]+ax.bbox.bounds[1] - bottom_lim = self.y+ax.bbox.bounds[1] + bottom_lim = self.y+ax.bbox.bounds[1] bottom_anchor_zone= (top_lim - bottom_lim)*.2 + bottom_lim - top_anchor_zone= (top_lim - bottom_lim)*.8 + bottom_lim + top_anchor_zone= (top_lim - bottom_lim)*.8 + bottom_lim if event.y < bottom_anchor_zone or event.y > top_anchor_zone: if self.text_instance: @@ -1572,18 +1572,18 @@ def min_max(self, event): self.text_instance.offset_text = False self.text_instance.current_axis = ax self.text_instance.kind = {'axis':'y','anchor':anchor} - + self.text_instance.show_text=True - return + return elif ylabelright and event.xself.x + ax.bbox.bounds[2] + ax.bbox.bounds[0] and \ event.yself.y + ax.bbox.bounds[1]: + event.y>self.y + ax.bbox.bounds[1]: top_lim = self.y+ax.bbox.bounds[3]+ax.bbox.bounds[1] - bottom_lim = self.y+ax.bbox.bounds[1] + bottom_lim = self.y+ax.bbox.bounds[1] bottom_anchor_zone= (top_lim - bottom_lim)*.2 + bottom_lim - top_anchor_zone= (top_lim - bottom_lim)*.8 + bottom_lim + top_anchor_zone= (top_lim - bottom_lim)*.8 + bottom_lim if event.y < bottom_anchor_zone or event.y > top_anchor_zone: if self.text_instance: @@ -1595,21 +1595,21 @@ def min_max(self, event): self.text_instance.y_text_pos=float(self.y+ax.bbox.bounds[1] + ax.bbox.bounds[3]) - self.text_instance.text_height self.text_instance.offset_text = True else: - + anchor='bottom' self.text_instance.x_text_pos=float(self.x+ax.bbox.bounds[0] + ax.bbox.bounds[2]) + dp(40) self.text_instance.y_text_pos=float(self.y+ax.bbox.bounds[1]) - dp(6) self.text_instance.offset_text = True self.text_instance.current_axis = ax self.text_instance.kind = {'axis':'y','anchor':anchor} - + self.text_instance.show_text=True - return - + return + elif xlabeltop and event.x>self.x +ax.bbox.bounds[0] and \ event.xself.y +ax.bbox.bounds[1] + ax.bbox.bounds[3] and \ - event.y 1 or abs(self._box_size[1]) > 1 or self.touch_mode=='zoombox': - self.reset_box() + self.reset_box() if not self.collide_point(x, y) and self.do_update: #update axis lim if zoombox is used and touch outside widget - self.update_lim() + self.update_lim() ax=self.axes ax.figure.canvas.draw_idle() - ax.figure.canvas.flush_events() + ax.figure.canvas.flush_events() return True - + # stop propagating if its within our bounds if self.collide_point(x, y) and self.figure: self._pressed = False if self.do_update: - self.update_lim() + self.update_lim() self.anchor_x=None self.anchor_y=None - + ax=self.axes self.background=None self.show_compare_cursor=True if self.last_line is None or self.touch_mode!='cursor': ax.figure.canvas.draw_idle() - ax.figure.canvas.flush_events() - + ax.figure.canvas.flush_events() + return True def draw_box(self, event, x0, y0, x1, y1,onpress=False) -> None: """ Draw zoombox method - + Args: event: touch kivy event x0: x coordonnate init x1: y coordonnate of move touch y0: y coordonnate init y1: x coordonnate of move touch - + Return: None """ @@ -1707,50 +1707,50 @@ def draw_box(self, event, x0, y0, x1, y1,onpress=False) -> None: # Kivy coords y0 = pos_y + y0 y1 = pos_y + y1 - + if abs(y1-y0)>dp(5) or abs(x1-x0)>dp(5): - self._alpha_box=0.3 + self._alpha_box=0.3 self._alpha_rect=0 - + if onpress: - ax = self.figure.canvas.inaxes((event.x - self.pos[0], + ax = self.figure.canvas.inaxes((event.x - self.pos[0], event.y - self.pos[1])) if ax is None: return self.axes = ax - + self.myevent.x=event.x - self.pos[0] self.myevent.y=event.y - self.pos[1] - self.myevent.inaxes=self.figure.canvas.inaxes((event.x - self.pos[0], - event.y - self.pos[1])) - + self.myevent.inaxes=self.figure.canvas.inaxes((event.x - self.pos[0], + event.y - self.pos[1])) + self.box_axes = [a for a in self.figure.canvas.figure.get_axes() - if a.in_axes(self.myevent)] - + if a.in_axes(self.myevent)] + else: - ax=self.axes + ax=self.axes trans = ax.transData.inverted() - xdata, ydata = trans.transform_point((event.x-pos_x, event.y-pos_y)) + xdata, ydata = trans.transform_point((event.x-pos_x, event.y-pos_y)) xleft,xright=ax.get_xlim() ybottom,ytop=ax.get_ylim() - + xmax = max(xleft,xright) xmin = min(xleft,xright) ymax = max(ybottom,ytop) ymin = min(ybottom,ytop) - + #check inverted data inverted_x = False if xleft>xright: inverted_x=True inverted_y = False if ybottom>ytop: - inverted_y=True + inverted_y=True + + x0data, y0data = trans.transform_point((x0-pos_x, y0-pos_y)) - x0data, y0data = trans.transform_point((x0-pos_x, y0-pos_y)) - if x0data>xmax or x0dataymax or y0data None: if xdata>xmax: x0_max = ax.transData.transform([(xmax,ymin)]) if (x1>x0 and not inverted_x) or (x1 None: y1=y0_max[0][1]+pos_y else: y0=y0_max[0][1]+pos_y - + if abs(x1-x0)self.minzoom: self.pos_x_rect_ver=x0 - self.pos_y_rect_ver=y0 - + self.pos_y_rect_ver=y0 + x1_min = self.axes.transData.transform([(xmin,ymin)]) x0=x1_min[0][0]+pos_x @@ -1794,22 +1794,22 @@ def draw_box(self, event, x0, y0, x1, y1,onpress=False) -> None: self._alpha_ver=1 self._alpha_hor=0 - + elif abs(y1-y0)self.minzoom: self.pos_x_rect_hor=x0 - self.pos_y_rect_hor=y0 + self.pos_y_rect_hor=y0 y1_min = self.axes.transData.transform([(xmin,ymin)]) y0=y1_min[0][1]+pos_y - + y0_max = self.axes.transData.transform([(xmax,ymax)]) - y1=y0_max[0][1]+pos_y + y1=y0_max[0][1]+pos_y self._alpha_hor=1 self._alpha_ver=0 - + else: - self._alpha_hor=0 + self._alpha_hor=0 self._alpha_ver=0 if x1>x0: @@ -1820,25 +1820,25 @@ def draw_box(self, event, x0, y0, x1, y1,onpress=False) -> None: self.invert_rect_hor=False else: self.invert_rect_hor=True - + self._box_pos = x0, y0 self._box_size = x1 - x0, y1 - y0 - + def update_lim(self): """ update axis lim if zoombox is used""" ax=self.axes self.do_update=False - + if not self.box_axes: self.box_axes=[ax] - + for index,ax in enumerate(self.box_axes): #check if inverted axis xleft,xright=ax.get_xlim() ybottom,ytop=ax.get_ylim() - + if xright>xleft: ax.set_xlim(left=min(self.x0_box[index],self.x1_box[index]),right=max(self.x0_box[index],self.x1_box[index])) else: @@ -1846,23 +1846,23 @@ def update_lim(self): if ytop>ybottom: ax.set_ylim(bottom=min(self.y0_box[index],self.y1_box[index]),top=max(self.y0_box[index],self.y1_box[index])) else: - ax.set_ylim(top=min(self.y0_box[index],self.y1_box[index]),bottom=max(self.y0_box[index],self.y1_box[index])) + ax.set_ylim(top=min(self.y0_box[index],self.y1_box[index]),bottom=max(self.y0_box[index],self.y1_box[index])) def reset_box(self): """ reset zoombox and apply zoombox limit if zoombox option if selected""" if min(abs(self._box_size[0]),abs(self._box_size[1]))>self.minzoom: self.x0_box, self.y0_box = [], [] - self.x1_box, self.y1_box = [], [] + self.x1_box, self.y1_box = [], [] for ax in self.box_axes: trans = ax.transData.inverted() - x0_box, y0_box = trans.transform_point((self._box_pos[0]-self.pos[0], self._box_pos[1]-self.pos[1])) + x0_box, y0_box = trans.transform_point((self._box_pos[0]-self.pos[0], self._box_pos[1]-self.pos[1])) x1_box, y1_box = trans.transform_point((self._box_size[0]+self._box_pos[0]-self.pos[0], self._box_size[1]+self._box_pos[1]-self.pos[1])) self.x0_box.append(x0_box) self.y0_box.append(y0_box) self.x1_box.append(x1_box) self.y1_box.append(y1_box) self.do_update=True - + self._box_size = 0, 0 self._box_pos = 0, 0 self._alpha_box=0 @@ -1870,12 +1870,12 @@ def reset_box(self): self._pos_x_rect_hor = 0 self._pos_y_rect_hor = 0 self._pos_x_rect_ver = 0 - self._pos_y_rect_ver = 0 - self._alpha_hor=0 + self._pos_y_rect_ver = 0 + self._alpha_hor=0 self._alpha_ver=0 self.invert_rect_hor = False - self.invert_rect_ver = False - + self.invert_rect_ver = False + def _draw_bitmap(self): """ draw bitmap method. based on kivy scatter method""" if self._bitmap is None: @@ -1885,6 +1885,6 @@ def _draw_bitmap(self): self._img_texture.blit_buffer( bytes(self._bitmap), colorfmt="rgba", bufferfmt='ubyte') self._img_texture.flip_vertical() - - self.update_hover() - self.update_selector() \ No newline at end of file + + self.update_hover() + self.update_selector() \ No newline at end of file diff --git a/kivy_matplotlib_widget/uix/graph_widget.py b/kivy_matplotlib_widget/uix/graph_widget.py index d7b75a8..6beb747 100644 --- a/kivy_matplotlib_widget/uix/graph_widget.py +++ b/kivy_matplotlib_widget/uix/graph_widget.py @@ -1,4 +1,4 @@ -""" MatplotFigure is based on https://github.com/jeysonmc/kivy_matplotlib +""" MatplotFigure is based on https://github.com/jeysonmc/kivy_matplotlib and kivy scatter """ @@ -39,7 +39,7 @@ class MatplotlibEvent: projection=False compare_xdata=False pick_radius_axis='both' - + class MatplotFigure(Widget): """Widget to show a matplotlib figure in kivy. The figure is rendered internally in an AGG backend then @@ -50,7 +50,7 @@ class MatplotFigure(Widget): _box_pos = ListProperty([0, 0]) _box_size = ListProperty([0, 0]) _img_texture = ObjectProperty(None) - _alpha_box = NumericProperty(0) + _alpha_box = NumericProperty(0) _bitmap = None _pressed = False do_update=False @@ -65,25 +65,25 @@ class MatplotFigure(Widget): pos_x_rect_hor=NumericProperty(0) pos_y_rect_hor=NumericProperty(0) pos_x_rect_ver=NumericProperty(0) - pos_y_rect_ver=NumericProperty(0) + pos_y_rect_ver=NumericProperty(0) invert_rect_ver = BooleanProperty(False) invert_rect_hor = BooleanProperty(False) legend_do_scroll_x = BooleanProperty(True) legend_do_scroll_y = BooleanProperty(True) - interactive_axis = BooleanProperty(False) + interactive_axis = BooleanProperty(False) do_pan_x = BooleanProperty(True) - do_pan_y = BooleanProperty(True) + do_pan_y = BooleanProperty(True) do_zoom_x = BooleanProperty(True) - do_zoom_y = BooleanProperty(True) + do_zoom_y = BooleanProperty(True) fast_draw = BooleanProperty(True) #True will don't draw axis xsorted = BooleanProperty(False) #to manage x sorted data minzoom = NumericProperty(dp(20)) - compare_xdata = BooleanProperty(False) + compare_xdata = BooleanProperty(False) hover_instance = ObjectProperty(None, allownone=True) nearest_hover_instance = ObjectProperty(None, allownone=True) compare_hover_instance = ObjectProperty(None, allownone=True) - disable_mouse_scrolling = BooleanProperty(False) - disable_double_tap = BooleanProperty(False) + disable_mouse_scrolling = BooleanProperty(False) + disable_double_tap = BooleanProperty(False) text_instance = None auto_zoom = BooleanProperty(False) zoom_angle_detection=NumericProperty(15) #in degree @@ -91,9 +91,9 @@ class MatplotFigure(Widget): autoscale_visible_only = BooleanProperty(True) autoscale_axis = OptionProperty("both", options=["both", "x", "y"]) autoscale_tight = BooleanProperty(False) - desktop_mode = BooleanProperty(True) #change mouse hover for selector widget + desktop_mode = BooleanProperty(True) #change mouse hover for selector widget current_selector = OptionProperty("None", - options = ["None",'rectangle','lasso','ellipse','span','custom']) + options = ["None",'rectangle','lasso','ellipse','span','custom']) highlight_hover = BooleanProperty(False) highlight_prop = DictProperty({}) highlight_alpha = NumericProperty(0.2) @@ -101,7 +101,7 @@ class MatplotFigure(Widget): pick_minimum_radius=NumericProperty(dp(50)) pick_radius_axis = OptionProperty("both", options=["both", "x", "y"]) - + def on_figure(self, obj, value): self.figcanvas = _FigureCanvas(self.figure, self) self.figcanvas._isDrawn = False @@ -116,7 +116,7 @@ def on_figure(self, obj, value): ax=self.figure.axes[0] patch_cpy=copy.copy(ax.patch) patch_cpy.set_visible(False) - + if hasattr(ax,'PolarTransform'): for pos in list(ax.spines._dict.keys()): ax.spines[pos].set_zorder(10) @@ -126,34 +126,34 @@ def on_figure(self, obj, value): ax.spines[pos].set_zorder(10) patch_cpy.set_zorder(9) self.background_patch_copy= ax.add_patch(patch_cpy) - + #set xmin axes attribute self.axes = self.figure.axes[0] - + #set default xmin/xmax and ymin/ymax self.xmin,self.xmax = self.axes.get_xlim() self.ymin,self.ymax = self.axes.get_ylim() - + if self.legend_instance: #remove all legend_instance from parent widget for current_legend in self.legend_instance: current_legend.parent.remove_widget(current_legend) self.legend_instance=[] - + if self.auto_cursor and len(self.figure.axes) > 0: self.register_lines(list(self.axes.lines)) - + if self.selector and self.axes: self.selector.resize_wgt.ax = self.axes # Texture self._img_texture = Texture.create(size=(w, h)) - + #close last figure in memory (avoid max figure warning) matplotlib.pyplot.close() def __init__(self, **kwargs): super(MatplotFigure, self).__init__(**kwargs) - + #figure info self.figure = None self.axes = None @@ -162,7 +162,7 @@ def __init__(self, **kwargs): self.ymin = None self.ymax = None self.lines = [] - + #option self.touch_mode='pan' self.hover_on = False @@ -174,52 +174,52 @@ def __init__(self, **kwargs): self.y0_box = None self.x1_box = None self.y1_box = None - + #clear touches on touch up self._touches = [] self._last_touch_pos = {} - #background + #background self.background=None - self.background_patch_copy=None + self.background_patch_copy=None #manage adjust x and y self.anchor_x = None - self.anchor_y = None - + self.anchor_y = None + #trick to manage wrong canvas size on first call (compare_hover) self.first_call_compare_hover=False - + #manage hover data self.x_hover_data = None self.y_hover_data = None - + #pan management self.first_touch_pan = None - + #manage show compare cursor on release self.show_compare_cursor = False - + #manage back and next event if hasattr(cbook,'_Stack'): #manage matplotlib version with no Stack (replace by _Stack) self._nav_stack = cbook._Stack() else: - self._nav_stack = cbook.Stack() - self.set_history_buttons() - + self._nav_stack = cbook.Stack() + self.set_history_buttons() + #legend management self.legend_instance = [] self.current_legend=None - + #selector management self.kv_post_done = False - self.selector = None + self.selector = None #highlight management self.last_line = None self.last_line_prop = {} - + self.bind(size=self._onSize) def on_kv_post(self,_): @@ -228,39 +228,39 @@ def on_kv_post(self,_): if self.current_selector == 'rectangle': self.set_selector(ResizeRelativeLayout) elif self.current_selector == 'lasso': - self.set_selector(LassoRelativeLayout) + self.set_selector(LassoRelativeLayout) elif self.current_selector == 'ellipse': - self.set_selector(EllipseRelativeLayout) + self.set_selector(EllipseRelativeLayout) elif self.current_selector == 'span': - self.set_selector(SpanRelativeLayout) + self.set_selector(SpanRelativeLayout) self.kv_post_done=True - + def transform_eval(self,x,axis): custom_transform=axis.get_transform() return custom_transform.transform_non_affine(np.array([x]))[0] - + def inv_transform_eval(self,x,axis): inv_custom_transform=axis.get_transform().inverted() return inv_custom_transform.transform_non_affine(np.array([x]))[0] - + def on_current_selector(self,instance,value,*args): - + if self.kv_post_done and selector_widgets_available: if value == 'rectangle': self.set_selector(ResizeRelativeLayout) elif value == 'lasso': - self.set_selector(LassoRelativeLayout) + self.set_selector(LassoRelativeLayout) elif value == 'ellipse': - self.set_selector(EllipseRelativeLayout) + self.set_selector(EllipseRelativeLayout) elif self.current_selector == 'span': - self.set_selector(SpanRelativeLayout) + self.set_selector(SpanRelativeLayout) elif value == "None": if self.selector: Window.unbind(mouse_pos=self.selector.resize_wgt.on_mouse_pos) self.parent.remove_widget(self.selector) self.selector = None - + def set_selector(self,selector,*args): selector_collection=None selector_line=None @@ -273,7 +273,7 @@ def set_selector(self,selector,*args): callback_clear = self.selector.resize_wgt.callback_clear Window.unbind(mouse_pos=self.selector.resize_wgt.on_mouse_pos) self.parent.remove_widget(self.selector) - + self.selector = selector(figure_wgt=self,desktop_mode=self.desktop_mode) self.selector.resize_wgt.ax = self.axes if selector_collection: @@ -284,92 +284,92 @@ def set_selector(self,selector,*args): self.set_callback(callback) if callback_clear: self.set_callback_clear(callback_clear) - - self.parent.add_widget(self.selector) - - def set_collection(self): + + self.parent.add_widget(self.selector) + + def set_collection(self): self.selector.resize_wgt.ax = self.axes - collections = self.axes.collections - + collections = self.axes.collections + if collections: - self.selector.resize_wgt.set_collection(collections[0]) - + self.selector.resize_wgt.set_collection(collections[0]) + def set_line(self,line): - self.selector.resize_wgt.ax = self.axes - self.selector.resize_wgt.set_line(line) - + self.selector.resize_wgt.ax = self.axes + self.selector.resize_wgt.set_line(line) + def set_callback(self,callback): self.selector.resize_wgt.set_callback(callback) - + def set_callback_clear(self,callback): - self.selector.resize_wgt.set_callback_clear(callback) - + self.selector.resize_wgt.set_callback_clear(callback) + def register_lines(self,lines:list) -> None: """ register lines method - + Args: lines (list): list of matplolib line class - + Return: - None - """ + None + """ #use sel,axes limit to avoid graph rescale xmin,xmax = self.axes.get_xlim() ymin,ymax = self.axes.get_ylim() #create cross hair cusor self.horizontal_line = self.axes.axhline(y=self.ymin,color='k', lw=0.8, ls='--', visible=False) self.vertical_line = self.axes.axvline(x=self.xmin,color='k', lw=0.8, ls='--', visible=False) - + #register lines self.lines=lines - + #cursor text - self.text = self.axes.text(1.0, 1.01, '', + self.text = self.axes.text(1.0, 1.01, '', transform=self.axes.transAxes, ha='right') def set_cross_hair_visible(self, visible:bool) -> None: """ set curcor visibility - + Args: visible (bool): make cursor visble or not - + Return: None - - """ + + """ self.horizontal_line.set_visible(visible) self.vertical_line.set_visible(visible) self.text.set_visible(visible) def clear_line_prop(self) -> None: """ clear attribute line_prop method - + Args: None - + Return: None - - """ + + """ if self.last_line_prop: for key in self.last_line_prop: set_line_attr = getattr(self.last_line,'set_' + key) - set_line_attr(self.last_line_prop[key]) - self.last_line_prop={} + set_line_attr(self.last_line_prop[key]) + self.last_line_prop={} self.last_line=None def hover(self, event) -> None: """ hover cursor method (cursor to nearest value) - + Args: event: touch kivy event - + Return: None - + """ - + #if cursor is set -> hover is on if self.hover_on: @@ -383,28 +383,28 @@ def hover(self, event) -> None: good_index=[] for line in self.lines: #get only visible lines - if line.get_visible(): + if line.get_visible(): #get line x,y datas self.x_cursor, self.y_cursor = line.get_xydata().T - + #check if line is not empty - if len(self.x_cursor)!=0: - + if len(self.x_cursor)!=0: + #find closest data index from touch (x axis) if self.xsorted: index = min(np.searchsorted(self.x_cursor, xdata), len(self.y_cursor) - 1) - + else: index = np.argsort(abs(self.x_cursor - xdata))[0] #get x data from index x = self.x_cursor[index] - + if self.compare_xdata: y = self.y_cursor[index] - + #get distance between line and touch (in pixels) - ax=line.axes + ax=line.axes #left axis xy_pixels_mouse = ax.transData.transform([(xdata,ydata)]) if np.ma.is_masked(x) or np.ma.is_masked(y) or np.isnan(x) or np.isnan(y): @@ -413,13 +413,13 @@ def hover(self, event) -> None: xy_pixels = ax.transData.transform([(x,ydata)]) dx2 = (xy_pixels_mouse[0][0]-xy_pixels[0][0]) distance.append(abs(dx2)) - else: - + else: + #find ydata corresponding to xdata y = self.y_cursor[index] - + #get distance between line and touch (in pixels) - ax=line.axes + ax=line.axes #left axis xy_pixels_mouse = ax.transData.transform([(xdata,ydata)]) if np.ma.is_masked(x) or np.ma.is_masked(y): @@ -427,51 +427,51 @@ def hover(self, event) -> None: else: xy_pixels = ax.transData.transform([(x,y)]) dx2 = (xy_pixels_mouse[0][0]-xy_pixels[0][0])**2 - dy2 = (xy_pixels_mouse[0][1]-xy_pixels[0][1])**2 - + dy2 = (xy_pixels_mouse[0][1]-xy_pixels[0][1])**2 + #store distance if self.pick_radius_axis == 'both': distance.append((dx2 + dy2)**0.5) if self.pick_radius_axis == 'x': distance.append(abs(dx2)) if self.pick_radius_axis == 'y': - distance.append(abs(dy2)) + distance.append(abs(dy2)) #store all best lines and index good_line.append(line) good_index.append(index) - + #case if no good line if len(good_line)==0: return - #if minimum distance if lower than 50 pixels, get line datas with - #minimum distance + #if minimum distance if lower than 50 pixels, get line datas with + #minimum distance if np.nanmin(distance)0: available_widget = self.hover_instance.children_list nb_widget=len(available_widget) @@ -483,130 +483,130 @@ def hover(self, event) -> None: line=good_line[idx_best_list[i]] line_label = line.get_label() if line_label in self.hover_instance.children_names: - index= self.hover_instance.children_names.index(line_label) + index= self.hover_instance.children_names.index(line_label) y_cursor = line.get_ydata() - y = y_cursor[good_index[idx_best_list[i]]] + y = y_cursor[good_index[idx_best_list[i]]] - xy_pos = ax.transData.transform([(x,y)]) + xy_pos = ax.transData.transform([(x,y)]) pos_y=float(xy_pos[0][1]) + self.y - + if pos_yself.y+self.axes.bbox.bounds[1]: available_widget[index].x_hover_pos=float(xy_pos[0][0]) + self.x available_widget[index].y_hover_pos=float(xy_pos[0][1]) + self.y available_widget[index].custom_color = get_color_from_hex(to_hex(line.get_color())) - + if self.cursor_yaxis_formatter: - y = self.cursor_yaxis_formatter.format_data(y) + y = self.cursor_yaxis_formatter.format_data(y) else: y = ax.yaxis.get_major_formatter().format_data_short(y) available_widget[index].label_y_value=f"{y}" available_widget[index].show_widget=True index_list.remove(index) - + for ii in index_list: available_widget[ii].show_widget=False if self.cursor_xaxis_formatter: - x = self.cursor_xaxis_formatter.format_data(x) + x = self.cursor_xaxis_formatter.format_data(x) else: x = ax.xaxis.get_major_formatter().format_data_short(x) - + self.hover_instance.label_x_value=f"{x}" - + if hasattr(self.hover_instance,'overlap_check'): self.hover_instance.overlap_check() self.hover_instance.xmin_line = float(ax.bbox.bounds[0]) + self.x - self.hover_instance.xmax_line = float(ax.bbox.bounds[0] + ax.bbox.bounds[2]) + self.x + self.hover_instance.xmax_line = float(ax.bbox.bounds[0] + ax.bbox.bounds[2]) + self.x self.hover_instance.ymin_line = float(ax.bbox.bounds[1]) + self.y self.hover_instance.ymax_line = float(ax.bbox.bounds[1] + ax.bbox.bounds[3]) + self.y - + if self.hover_instance.x_hover_pos>self.x+self.axes.bbox.bounds[2] + self.axes.bbox.bounds[0] or \ - self.hover_instance.x_hover_posself.x+self.axes.bbox.bounds[2] + self.axes.bbox.bounds[0] or \ self.hover_instance.x_hover_posself.y+self.axes.bbox.bounds[1] + self.axes.bbox.bounds[3] or \ - self.hover_instance.y_hover_pos None: for current_line in lines_list: default_alpha.append(current_line.get_alpha()) current_line.set_alpha(self.highlight_alpha) - + self.axes.figure.canvas.draw_idle() - self.axes.figure.canvas.flush_events() + self.axes.figure.canvas.flush_events() self.background_highlight=self.axes.figure.canvas.copy_from_bbox(self.axes.figure.bbox) self.last_line=line for i,current_line in enumerate(lines_list): current_line.set_alpha(default_alpha[i]) default_alpha[i] - + if self.highlight_prop: self.last_line_prop={} for key in self.highlight_prop: @@ -634,22 +634,22 @@ def hover(self, event) -> None: elif self.last_line_prop: for key in self.last_line_prop: set_line_attr = getattr(self.last_line,'set_' + key) - set_line_attr(self.last_line_prop[key]) + set_line_attr(self.last_line_prop[key]) self.hover_instance.custom_color = get_color_from_hex(to_hex(line.get_color())) self.last_line_prop={} for key in self.highlight_prop: line_attr = getattr(line,'get_' + key) self.last_line_prop.update({key:line_attr()}) set_line_attr = getattr(line,'set_' + key) - set_line_attr(self.highlight_prop[key]) + set_line_attr(self.highlight_prop[key]) self.last_line=line self.axes.figure.canvas.restore_region(self.background_highlight) self.axes.draw_artist(line) - + #draw (blit method) - self.axes.figure.canvas.blit(self.axes.bbox) - self.axes.figure.canvas.flush_events() + self.axes.figure.canvas.blit(self.axes.bbox) + self.axes.figure.canvas.flush_events() return else: if self.cursor_xaxis_formatter: @@ -657,60 +657,60 @@ def hover(self, event) -> None: else: x = ax.xaxis.get_major_formatter().format_data_short(x) if self.cursor_yaxis_formatter: - y = self.cursor_yaxis_formatter.format_data(y) + y = self.cursor_yaxis_formatter.format_data(y) else: y = ax.yaxis.get_major_formatter().format_data_short(y) self.text.set_text(f"x={x}, y={y}") - - #blit method (always use because same visual effect as draw) + + #blit method (always use because same visual effect as draw) if self.background is None: self.set_cross_hair_visible(False) self.axes.figure.canvas.draw_idle() - self.axes.figure.canvas.flush_events() + self.axes.figure.canvas.flush_events() self.background = self.axes.figure.canvas.copy_from_bbox(self.axes.figure.bbox) - self.set_cross_hair_visible(True) + self.set_cross_hair_visible(True) if self.last_line is not None: - self.clear_line_prop() - + self.clear_line_prop() + self.axes.figure.canvas.restore_region(self.background) self.axes.draw_artist(self.text) - + self.axes.draw_artist(self.horizontal_line) - self.axes.draw_artist(self.vertical_line) - + self.axes.draw_artist(self.vertical_line) + #draw (blit method) - self.axes.figure.canvas.blit(self.axes.bbox) + self.axes.figure.canvas.blit(self.axes.bbox) self.axes.figure.canvas.flush_events() #if touch is too far, hide cross hair cursor else: - self.set_cross_hair_visible(False) + self.set_cross_hair_visible(False) if self.hover_instance: self.hover_instance.x_hover_pos=self.x - self.hover_instance.y_hover_pos=self.y + self.hover_instance.y_hover_pos=self.y self.hover_instance.show_cursor=False self.x_hover_data = None self.y_hover_data = None if self.highlight_hover: self.myevent.x=event.x - self.pos[0] self.myevent.y=event.y - self.pos[1] - self.myevent.inaxes=self.figure.canvas.inaxes((event.x - self.pos[0], - event.y - self.pos[1])) - + self.myevent.inaxes=self.figure.canvas.inaxes((event.x - self.pos[0], + event.y - self.pos[1])) + axes = [a for a in self.figure.canvas.figure.get_axes() - if a.in_axes(self.myevent)] - - if not axes: - + if a.in_axes(self.myevent)] + + if not axes: + if self.last_line: - self.clear_line_prop() + self.clear_line_prop() if self.background: self.axes.figure.canvas.restore_region(self.background) #draw (blit method) - self.axes.figure.canvas.blit(self.axes.bbox) - self.axes.figure.canvas.flush_events() + self.axes.figure.canvas.blit(self.axes.bbox) + self.axes.figure.canvas.flush_events() self.background = None - + return def autoscale(self): if self.disabled: @@ -718,19 +718,19 @@ def autoscale(self): ax=self.axes ax.relim(visible_only=self.autoscale_visible_only) ax.autoscale_view(tight=self.autoscale_tight, - scalex=True if self.autoscale_axis!="y" else False, + scalex=True if self.autoscale_axis!="y" else False, scaley=True if self.autoscale_axis!="x" else False) - ax.autoscale(axis=self.autoscale_axis,tight=self.autoscale_tight) + ax.autoscale(axis=self.autoscale_axis,tight=self.autoscale_tight) ax.figure.canvas.draw_idle() - ax.figure.canvas.flush_events() + ax.figure.canvas.flush_events() ax.set_autoscale_on(False) self.xmin,self.xmax = ax.get_xlim() - self.ymin,self.ymax = ax.get_ylim() + self.ymin,self.ymax = ax.get_ylim() def home(self) -> None: """ reset data axis - + Return: None """ @@ -739,19 +739,19 @@ def home(self) -> None: self.xmax is not None and \ self.ymin is not None and \ self.ymax is not None: - + ax = self.axes xleft,xright=ax.get_xlim() - ybottom,ytop=ax.get_ylim() - + ybottom,ytop=ax.get_ylim() + #check inverted data inverted_x = False if xleft>xright: inverted_x=True inverted_y = False if ybottom>ytop: - inverted_y=True - + inverted_y=True + if inverted_x: ax.set_xlim(right=self.xmin,left=self.xmax) else: @@ -759,13 +759,13 @@ def home(self) -> None: if inverted_y: ax.set_ylim(top=self.ymin,bottom=self.ymax) else: - ax.set_ylim(bottom=self.ymin,top=self.ymax) + ax.set_ylim(bottom=self.ymin,top=self.ymax) if self.last_line is not None: - self.clear_line_prop() - + self.clear_line_prop() + ax.figure.canvas.draw_idle() - ax.figure.canvas.flush_events() + ax.figure.canvas.flush_events() def back(self, *args): """ @@ -788,7 +788,7 @@ def forward(self, *args): self._nav_stack.forward() self.set_history_buttons() self._update_view() - + def push_current(self): """Push the current view limits and position onto the stack.""" self._nav_stack.push( @@ -798,13 +798,13 @@ def push_current(self): (ax.get_position(True).frozen(), ax.get_position().frozen())) for ax in self.figure.axes})) - self.set_history_buttons() + self.set_history_buttons() def update(self): """Reset the Axes stack.""" self._nav_stack.clear() self.set_history_buttons() - + def _update_view(self): """ Update the viewlim and position from the view and position stack for @@ -821,7 +821,7 @@ def _update_view(self): # Restore both the original and modified positions ax._set_position(pos_orig, 'original') ax._set_position(pos_active, 'active') - self.figure.canvas.draw_idle() + self.figure.canvas.draw_idle() self.figure.canvas.flush_events() def set_history_buttons(self): @@ -829,13 +829,13 @@ def set_history_buttons(self): def reset_touch(self) -> None: """ reset touch - + Return: None """ self._touches = [] self._last_touch_pos = {} - + def _get_scale(self): """ kivy scatter _get_scale method """ p1 = Vector(*self.to_parent(0, 0)) @@ -877,10 +877,10 @@ def _draw_bitmap(self): self._img_texture.blit_buffer( bytes(self._bitmap), colorfmt="rgba", bufferfmt='ubyte') self._img_texture.flip_vertical() - - self.update_hover() - self.update_selector() - + + self.update_hover() + self.update_selector() + def transform_with_touch(self, event): """ manage touch behaviour. based on kivy scatter method""" @@ -888,43 +888,43 @@ def transform_with_touch(self, event): changed = False if len(self._touches) == self.translation_touches: - + if self.touch_mode=='pan': if self._nav_stack() is None: - self.push_current() + self.push_current() self.apply_pan(self.axes, event) - + if self.touch_mode=='pan_x' or self.touch_mode=='pan_y' \ or self.touch_mode=='adjust_x' or self.touch_mode=='adjust_y': if self._nav_stack() is None: - self.push_current() - self.apply_pan(self.axes, event, mode=self.touch_mode) - + self.push_current() + self.apply_pan(self.axes, event, mode=self.touch_mode) + elif self.touch_mode=='drag_legend': if self.legend_instance: self.apply_drag_legend(self.axes, event) - + elif self.touch_mode=='zoombox': if self._nav_stack() is None: - self.push_current() + self.push_current() real_x, real_y = event.x - self.pos[0], event.y - self.pos[1] #in case x_init is not create if not hasattr(self,'x_init'): self.x_init = event.x self.y_init = real_y self.draw_box(event, self.x_init,self.y_init, event.x, real_y) - + #mode cursor elif self.touch_mode=='cursor': self.hover_on=True self.hover(event) - + changed = True #note: avoid zoom in/out on touch mode zoombox if len(self._touches) == 1:# return changed - + # we have more than one touch... list of last known pos points = [Vector(self._last_touch_pos[t]) for t in self._touches if t is not event] @@ -957,13 +957,13 @@ def transform_with_touch(self, event): self.do_zoom_y=True elif angle>90-self.zoom_angle_detection and angle<90+self.zoom_angle_detection: self.do_zoom_x=True - self.do_zoom_y=False + self.do_zoom_y=False elif angle>180-self.zoom_angle_detection and angle<180+self.zoom_angle_detection: self.do_zoom_x=False - self.do_zoom_y=True + self.do_zoom_y=True elif angle>270-self.zoom_angle_detection and angle<270+self.zoom_angle_detection: self.do_zoom_x=True - self.do_zoom_y=False + self.do_zoom_y=False else: self.do_zoom_x=True self.do_zoom_y=True @@ -976,7 +976,7 @@ def transform_with_touch(self, event): scale = self.scale_min / self.scale elif new_scale > self.scale_max: scale = self.scale_max / self.scale - + self.apply_zoom(scale, self.axes, anchor=anchor,new_line=new_line) changed = True @@ -993,7 +993,7 @@ def on_motion(self,*args): x = newcoord[0] y = newcoord[1] inside = self.collide_point(x,y) - if inside: + if inside: # will receive all motion events. if self.figcanvas and self.hover_instance: @@ -1003,22 +1003,22 @@ def on_motion(self,*args): FakeEvent.x=x FakeEvent.y=y self.hover(FakeEvent) - + def get_data_xy(self,x,y): """ manage x y data in navigation bar """ trans = self.axes.transData.inverted() xdata, ydata = trans.transform_point((x - self.pos[0], y - self.pos[1])) if self.cursor_xaxis_formatter: - x_format = self.cursor_xaxis_formatter.format_data(xdata) + x_format = self.cursor_xaxis_formatter.format_data(xdata) else: x_format = self.axes.xaxis.get_major_formatter().format_data_short(xdata) - + if self.cursor_yaxis_formatter: - y_format = self.cursor_yaxis_formatter.format_data(ydata) + y_format = self.cursor_yaxis_formatter.format_data(ydata) else: y_format = self.axes.yaxis.get_major_formatter().format_data_short(ydata) - + return x_format,y_format def on_touch_down(self, event): @@ -1039,7 +1039,7 @@ def on_touch_down(self, event): break if select_legend: if self.touch_mode!='drag_legend': - return False + return False else: event.grab(self) self._touches.append(event) @@ -1047,11 +1047,11 @@ def on_touch_down(self, event): if len(self._touches)>1: #new touch, reset background self.background=None - - return True + + return True else: self.current_legend = None - + if event.is_mouse_scrolling: if not self.disable_mouse_scrolling: ax = self.axes @@ -1060,32 +1060,32 @@ def on_touch_down(self, event): elif event.is_double_tap: if not self.disable_double_tap: - if self.touch_mode!='selector': + if self.touch_mode!='selector': self.home() return True - + else: if self.touch_mode=='cursor': self.hover_on=True - self.hover(event) + self.hover(event) elif self.touch_mode=='zoombox': real_x, real_y = x - self.pos[0], y - self.pos[1] self.x_init=x self.y_init=real_y - self.draw_box(event, x, real_y, x, real_y) + self.draw_box(event, x, real_y, x, real_y) elif self.touch_mode=='minmax': self.min_max(event) elif self.touch_mode=='selector': - pass - + pass + event.grab(self) self._touches.append(event) self._last_touch_pos[event] = event.pos if len(self._touches)>1: #new touch, reset background self.background=None - + return True else: @@ -1099,8 +1099,8 @@ def on_touch_move(self, event): if event.is_double_tap: if not self.disable_double_tap: - if self.touch_mode!='selector': - self.home() + if self.touch_mode!='selector': + self.home() return True # scale/translate @@ -1124,75 +1124,75 @@ def on_touch_up(self, event): if self.touch_mode=='pan' or self.touch_mode=='zoombox' or \ self.touch_mode=='pan_x' or self.touch_mode=='pan_y' \ or self.touch_mode=='adjust_x' or self.touch_mode=='adjust_y' \ - or self.touch_mode=='minmax': - + or self.touch_mode=='minmax': + self.push_current() if self.interactive_axis: if self.touch_mode=='pan_x' or self.touch_mode=='pan_y' \ or self.touch_mode=='adjust_x' or self.touch_mode=='adjust_y': self.touch_mode='pan' self.first_touch_pan=None - + if self.last_line is not None: self.clear_line_prop() - + x, y = event.x, event.y if abs(self._box_size[0]) > 1 or abs(self._box_size[1]) > 1 or self.touch_mode=='zoombox': - self.reset_box() + self.reset_box() if not self.collide_point(x, y) and self.do_update: #update axis lim if zoombox is used and touch outside widget - self.update_lim() + self.update_lim() ax=self.axes ax.figure.canvas.draw_idle() - ax.figure.canvas.flush_events() + ax.figure.canvas.flush_events() return True - + # stop propagating if its within our bounds if self.collide_point(x, y) and self.figure: self._pressed = False if self.do_update: - self.update_lim() + self.update_lim() self.anchor_x=None self.anchor_y=None - + ax=self.axes self.background=None self.show_compare_cursor=True if self.last_line is None or self.touch_mode!='cursor': ax.figure.canvas.draw_idle() - ax.figure.canvas.flush_events() - + ax.figure.canvas.flush_events() + return True def apply_zoom(self, scale_factor, ax, anchor=(0, 0),new_line=None): """ zoom touch method """ if self.touch_mode=='selector': return - + x = anchor[0]-self.pos[0] y = anchor[1]-self.pos[1] trans = ax.transData.inverted() - xdata, ydata = trans.transform_point((x+new_line.x, y+new_line.y)) - + xdata, ydata = trans.transform_point((x+new_line.x, y+new_line.y)) + cur_xlim = ax.get_xlim() - cur_ylim = ax.get_ylim() + cur_ylim = ax.get_ylim() scale=ax.get_xscale() yscale=ax.get_yscale() - + if scale == 'linear': old_min=cur_xlim[0] old_max=cur_xlim[1] else: min_=cur_xlim[0] - max_=cur_xlim[1] + max_=cur_xlim[1] old_min = self.transform_eval(min_,ax.yaxis) xdata = self.transform_eval(xdata,ax.yaxis) - old_max = self.transform_eval(max_,ax.yaxis) + old_max = self.transform_eval(max_,ax.yaxis) if yscale == 'linear': yold_min=cur_ylim[0] @@ -1200,7 +1200,7 @@ def apply_zoom(self, scale_factor, ax, anchor=(0, 0),new_line=None): else: ymin_=cur_ylim[0] - ymax_=cur_ylim[1] + ymax_=cur_ylim[1] yold_min = self.transform_eval(ymin_,ax.yaxis) ydata = self.transform_eval(ydata,ax.yaxis) yold_max = self.transform_eval(ymax_,ax.yaxis) @@ -1222,9 +1222,9 @@ def apply_zoom(self, scale_factor, ax, anchor=(0, 0),new_line=None): except OverflowError: # Limit case new_min, new_max = min_, max_ if new_min <= 0. or new_max <= 0.: # Limit case - new_min, new_max = min_, max_ + new_min, new_max = min_, max_ ax.set_xlim([new_min, new_max]) - + if self.do_zoom_y: if yscale == 'linear': ax.set_ylim([ydata - new_height * (1 - rely), ydata + new_height * (rely)]) @@ -1236,21 +1236,21 @@ def apply_zoom(self, scale_factor, ax, anchor=(0, 0),new_line=None): except OverflowError: # Limit case new_ymin, new_ymax = ymin_, ymax_ if new_ymin <= 0. or new_ymax <= 0.: # Limit case - new_ymin, new_ymax = ymin_, ymax_ - ax.set_ylim([new_ymin, new_ymax]) + new_ymin, new_ymax = ymin_, ymax_ + ax.set_ylim([new_ymin, new_ymax]) - if self.fast_draw: - #use blit method + if self.fast_draw: + #use blit method if self.background is None or self.last_line is not None: self.background_patch_copy.set_visible(True) ax.figure.canvas.draw_idle() - ax.figure.canvas.flush_events() + ax.figure.canvas.flush_events() self.background = ax.figure.canvas.copy_from_bbox(ax.figure.bbox) - self.background_patch_copy.set_visible(False) + self.background_patch_copy.set_visible(False) if self.last_line is not None: - self.clear_line_prop() + self.clear_line_prop() ax.figure.canvas.restore_region(self.background) - + for line in ax.lines: ax.draw_artist(line) ax.figure.canvas.blit(ax.bbox) @@ -1259,33 +1259,33 @@ def apply_zoom(self, scale_factor, ax, anchor=(0, 0),new_line=None): self.update_hover() else: ax.figure.canvas.draw_idle() - ax.figure.canvas.flush_events() + ax.figure.canvas.flush_events() def apply_pan(self, ax, event, mode='pan'): """ pan method """ - + trans = ax.transData.inverted() xdata, ydata = trans.transform_point((event.x-self.pos[0], event.y-self.pos[1])) xpress, ypress = trans.transform_point((self._last_touch_pos[event][0]-self.pos[0], self._last_touch_pos[event][1]-self.pos[1])) - + scale=ax.get_xscale() yscale=ax.get_yscale() - + if scale == 'linear': dx = xdata - xpress else: dx = self.transform_eval(xdata,ax.xaxis) - \ self.transform_eval(xpress,ax.xaxis) - + if yscale == 'linear': dy = ydata - ypress else: dy = self.transform_eval(ydata,ax.yaxis) - \ - self.transform_eval(ypress,ax.yaxis) + self.transform_eval(ypress,ax.yaxis) xleft,xright=self.axes.get_xlim() ybottom,ytop=self.axes.get_ylim() - + #check inverted data inverted_x = False if xleft>xright: @@ -1295,11 +1295,11 @@ def apply_pan(self, ax, event, mode='pan'): cur_xlim=(xleft,xright) inverted_y = False if ybottom>ytop: - inverted_y=True + inverted_y=True cur_ylim=(ytop,ybottom) else: - cur_ylim=(ybottom,ytop) - + cur_ylim=(ybottom,ytop) + if self.interactive_axis and self.touch_mode=='pan' and not self.first_touch_pan=='pan': if (ydata < cur_ylim[0] and not inverted_y) or (ydata > cur_ylim[1] and inverted_y): left_anchor_zone= (cur_xlim[1] - cur_xlim[0])*.2 + cur_xlim[0] @@ -1311,16 +1311,16 @@ def apply_pan(self, ax, event, mode='pan'): self.touch_mode = mode elif (xdata < cur_xlim[0] and not inverted_x) or (xdata > cur_xlim[1] and inverted_x): bottom_anchor_zone= (cur_ylim[1] - cur_ylim[0])*.2 + cur_ylim[0] - top_anchor_zone= (cur_ylim[1] - cur_ylim[0])*.8 + cur_ylim[0] + top_anchor_zone= (cur_ylim[1] - cur_ylim[0])*.8 + cur_ylim[0] if ydata < bottom_anchor_zone or ydata > top_anchor_zone: mode = 'adjust_y' else: - mode= 'pan_y' + mode= 'pan_y' self.touch_mode = mode else: self.touch_mode = 'pan' - if not mode=='pan_y' and not mode=='adjust_y': + if not mode=='pan_y' and not mode=='adjust_y': if mode=='adjust_x': if self.anchor_x is None: midpoint= (cur_xlim[1] + cur_xlim[0])/2 @@ -1328,16 +1328,16 @@ def apply_pan(self, ax, event, mode='pan'): self.anchor_x='left' else: self.anchor_x='right' - if self.anchor_x=='left': + if self.anchor_x=='left': if xdata> cur_xlim[0]: if scale == 'linear': cur_xlim -= dx else: try: cur_xlim = [self.inv_transform_eval((self.transform_eval(cur_xlim[0],ax.xaxis) - dx),ax.xaxis), - self.inv_transform_eval((self.transform_eval(cur_xlim[1],ax.xaxis) - dx),ax.xaxis)] + self.inv_transform_eval((self.transform_eval(cur_xlim[1],ax.xaxis) - dx),ax.xaxis)] except (ValueError, OverflowError): - cur_xlim = cur_xlim # Keep previous limits + cur_xlim = cur_xlim # Keep previous limits if inverted_x: ax.set_xlim(cur_xlim[1],None) else: @@ -1349,9 +1349,9 @@ def apply_pan(self, ax, event, mode='pan'): else: try: cur_xlim = [self.inv_transform_eval((self.transform_eval(cur_xlim[0],ax.xaxis) - dx),ax.xaxis), - self.inv_transform_eval((self.transform_eval(cur_xlim[1],ax.xaxis) - dx),ax.xaxis)] + self.inv_transform_eval((self.transform_eval(cur_xlim[1],ax.xaxis) - dx),ax.xaxis)] except (ValueError, OverflowError): - cur_xlim = cur_xlim # Keep previous limits + cur_xlim = cur_xlim # Keep previous limits if inverted_x: ax.set_xlim(None,cur_xlim[0]) else: @@ -1362,14 +1362,14 @@ def apply_pan(self, ax, event, mode='pan'): else: try: cur_xlim = [self.inv_transform_eval((self.transform_eval(cur_xlim[0],ax.xaxis) - dx),ax.xaxis), - self.inv_transform_eval((self.transform_eval(cur_xlim[1],ax.xaxis) - dx),ax.xaxis)] + self.inv_transform_eval((self.transform_eval(cur_xlim[1],ax.xaxis) - dx),ax.xaxis)] except (ValueError, OverflowError): - cur_xlim = cur_xlim # Keep previous limits + cur_xlim = cur_xlim # Keep previous limits if inverted_x: ax.set_xlim(cur_xlim[1],cur_xlim[0]) else: ax.set_xlim(cur_xlim) - + if not mode=='pan_x' and not mode=='adjust_x': if mode=='adjust_y': if self.anchor_y is None: @@ -1377,20 +1377,20 @@ def apply_pan(self, ax, event, mode='pan'): if ydata>midpoint: self.anchor_y='top' else: - self.anchor_y='bottom' - + self.anchor_y='bottom' + if self.anchor_y=='top': if ydata> cur_ylim[0]: if yscale == 'linear': cur_ylim -= dy - + else: try: cur_ylim = [self.inv_transform_eval((self.transform_eval(cur_ylim[0],ax.yaxis) - dy),ax.yaxis), self.inv_transform_eval((self.transform_eval(cur_ylim[1],ax.yaxis) - dy),ax.yaxis)] except (ValueError, OverflowError): - cur_ylim = cur_ylim # Keep previous limits - + cur_ylim = cur_ylim # Keep previous limits + if inverted_y: ax.set_ylim(cur_ylim[1],None) else: @@ -1399,27 +1399,27 @@ def apply_pan(self, ax, event, mode='pan'): if ydata< cur_ylim[1]: if yscale == 'linear': cur_ylim -= dy - + else: try: cur_ylim = [self.inv_transform_eval((self.transform_eval(cur_ylim[0],ax.yaxis) - dy),ax.yaxis), self.inv_transform_eval((self.transform_eval(cur_ylim[1],ax.yaxis) - dy),ax.yaxis)] except (ValueError, OverflowError): - cur_ylim = cur_ylim # Keep previous limits + cur_ylim = cur_ylim # Keep previous limits if inverted_y: - ax.set_ylim(None,cur_ylim[0]) + ax.set_ylim(None,cur_ylim[0]) else: ax.set_ylim(cur_ylim[0],None) - else: + else: if yscale == 'linear': - cur_ylim -= dy - + cur_ylim -= dy + else: try: cur_ylim = [self.inv_transform_eval((self.transform_eval(cur_ylim[0],ax.yaxis) - dy),ax.yaxis), self.inv_transform_eval((self.transform_eval(cur_ylim[1],ax.yaxis) - dy),ax.yaxis)] except (ValueError, OverflowError): - cur_ylim = cur_ylim # Keep previous limits + cur_ylim = cur_ylim # Keep previous limits if inverted_y: ax.set_ylim(cur_ylim[1],cur_ylim[0]) else: @@ -1428,26 +1428,26 @@ def apply_pan(self, ax, event, mode='pan'): if self.first_touch_pan is None: self.first_touch_pan=self.touch_mode - if self.fast_draw: - #use blit method + if self.fast_draw: + #use blit method if self.background is None or self.last_line is not None: self.background_patch_copy.set_visible(True) ax.figure.canvas.draw_idle() - ax.figure.canvas.flush_events() + ax.figure.canvas.flush_events() self.background = ax.figure.canvas.copy_from_bbox(ax.figure.bbox) - self.background_patch_copy.set_visible(False) + self.background_patch_copy.set_visible(False) if self.last_line is not None: - self.clear_line_prop() - ax.figure.canvas.restore_region(self.background) - + self.clear_line_prop() + ax.figure.canvas.restore_region(self.background) + for line in ax.lines: ax.draw_artist(line) - + ax.figure.canvas.blit(ax.bbox) - ax.figure.canvas.flush_events() - + ax.figure.canvas.flush_events() + self.update_hover() - + else: ax.figure.canvas.draw_idle() ax.figure.canvas.flush_events() @@ -1458,15 +1458,15 @@ def update_hover(self): if self.compare_xdata: if (self.touch_mode!='cursor' or len(self._touches) > 1) and not self.show_compare_cursor: self.hover_instance.hover_outside_bound=True - + elif self.show_compare_cursor and self.touch_mode=='cursor': self.show_compare_cursor=False else: self.hover_instance.hover_outside_bound=True #update hover pos if needed - elif self.hover_instance.show_cursor and self.x_hover_data is not None and self.y_hover_data is not None: - xy_pos = self.axes.transData.transform([(self.x_hover_data,self.y_hover_data)]) + elif self.hover_instance.show_cursor and self.x_hover_data is not None and self.y_hover_data is not None: + xy_pos = self.axes.transData.transform([(self.x_hover_data,self.y_hover_data)]) self.hover_instance.x_hover_pos=float(xy_pos[0][0]) + self.x self.hover_instance.y_hover_pos=float(xy_pos[0][1]) + self.y @@ -1474,20 +1474,20 @@ def update_hover(self): self.hover_instance.xmax_line = float(self.axes.bbox.bounds[0] + self.axes.bbox.bounds[2]) + self.x self.hover_instance.ymin_line = float(self.axes.bbox.bounds[1]) + self.y self.hover_instance.ymax_line = float(self.axes.bbox.bounds[1] + self.axes.bbox.bounds[3] )+ self.y - + if self.hover_instance.x_hover_pos>self.x+self.axes.bbox.bounds[2] + self.axes.bbox.bounds[0] or \ self.hover_instance.x_hover_posself.y+self.axes.bbox.bounds[1] + self.axes.bbox.bounds[3] or \ - self.hover_instance.y_hover_pos 1 and resize_wgt.size[1] > 1): return - + #rectangle or spann selector if hasattr(resize_wgt,'span_orientation'): #span selector if resize_wgt.span_orientation == 'vertical': - xy_pos = resize_wgt.ax.transData.transform([(resize_wgt.verts[0][0],resize_wgt.verts[0][1])]) + xy_pos = resize_wgt.ax.transData.transform([(resize_wgt.verts[0][0],resize_wgt.verts[0][1])]) new_pos=resize_wgt.to_widget(*(float(xy_pos[0][0]),float(xy_pos[0][1]))) resize_wgt.pos[0] = new_pos[0] + self.x - + top_bound = float(self.y +resize_wgt.ax.bbox.bounds[3] + resize_wgt.ax.bbox.bounds[1]) bottom_bound = float(self.y +resize_wgt.ax.bbox.bounds[1]) resize_wgt.pos[1] = bottom_bound - self.y - + #recalcul size - xy_pos2 = resize_wgt.ax.transData.transform([(resize_wgt.verts[3][0],resize_wgt.verts[3][1])]) + xy_pos2 = resize_wgt.ax.transData.transform([(resize_wgt.verts[3][0],resize_wgt.verts[3][1])]) resize_wgt.size[0] = float(xy_pos2[0][0] - xy_pos[0][0]) resize_wgt.size[1] = top_bound-bottom_bound - + elif resize_wgt.span_orientation == 'horizontal': - xy_pos = resize_wgt.ax.transData.transform([(resize_wgt.verts[0][0],resize_wgt.verts[0][1])]) + xy_pos = resize_wgt.ax.transData.transform([(resize_wgt.verts[0][0],resize_wgt.verts[0][1])]) new_pos=resize_wgt.to_widget(*(float(xy_pos[0][0]),float(xy_pos[0][1]))) left_bound = float(self.x +resize_wgt.ax.bbox.bounds[0]) right_bound = float(self.x +resize_wgt.ax.bbox.bounds[2] +resize_wgt.ax.bbox.bounds[0] ) - + width = right_bound-left_bound - + left_bound,right_bound = resize_wgt.to_widget(left_bound,right_bound) - + resize_wgt.pos[0] = left_bound resize_wgt.pos[1] = new_pos[1] + self.y #recalcul size - xy_pos2 = resize_wgt.ax.transData.transform([(resize_wgt.verts[0][1],resize_wgt.verts[1][1])]) + xy_pos2 = resize_wgt.ax.transData.transform([(resize_wgt.verts[0][1],resize_wgt.verts[1][1])]) resize_wgt.size[0] = width - resize_wgt.size[1] = float(xy_pos2[0][1] - xy_pos[0][1]) - + resize_wgt.size[1] = float(xy_pos2[0][1] - xy_pos[0][1]) + else: #rectangle selector - + #update all selector pts #recalcul pos - xy_pos = resize_wgt.ax.transData.transform([(resize_wgt.verts[0][0],resize_wgt.verts[0][1])]) + xy_pos = resize_wgt.ax.transData.transform([(resize_wgt.verts[0][0],resize_wgt.verts[0][1])]) new_pos=resize_wgt.to_widget(*(float(xy_pos[0][0]),float(xy_pos[0][1]))) resize_wgt.pos[0] = new_pos[0] + self.x resize_wgt.pos[1] = new_pos[1] + self.y - + #recalcul size - xy_pos2 = resize_wgt.ax.transData.transform([(resize_wgt.verts[2][0],resize_wgt.verts[2][1])]) + xy_pos2 = resize_wgt.ax.transData.transform([(resize_wgt.verts[2][0],resize_wgt.verts[2][1])]) resize_wgt.size[0] = float(xy_pos2[0][0] - xy_pos[0][0]) resize_wgt.size[1] = float(xy_pos2[0][1] - xy_pos[0][1]) - + if self.collide_point(*resize_wgt.to_window(resize_wgt.pos[0],resize_wgt.pos[1])) and \ self.collide_point(*resize_wgt.to_window(resize_wgt.pos[0] + resize_wgt.size[0],resize_wgt.pos[1]+ resize_wgt.size[1])): resize_wgt.opacity = 1 else: - resize_wgt.opacity = 0 + resize_wgt.opacity = 0 def min_max(self, event): """ manage min/max touch mode """ @@ -1644,14 +1644,14 @@ def min_max(self, event): if xlabelbottom and event.x>self.x +ax.bbox.bounds[0] and \ event.x right_anchor_zone: + if event.x < left_anchor_zone or event.x > right_anchor_zone: if self.text_instance: if not self.text_instance.show_text: @@ -1678,9 +1678,9 @@ def min_max(self, event): event.y>self.y + ax.bbox.bounds[1]: top_lim = self.y+ax.bbox.bounds[3]+ax.bbox.bounds[1] - bottom_lim = self.y+ax.bbox.bounds[1] + bottom_lim = self.y+ax.bbox.bounds[1] bottom_anchor_zone= (top_lim - bottom_lim)*.2 + bottom_lim - top_anchor_zone= (top_lim - bottom_lim)*.8 + bottom_lim + top_anchor_zone= (top_lim - bottom_lim)*.8 + bottom_lim if event.y < bottom_anchor_zone or event.y > top_anchor_zone: if self.text_instance: @@ -1701,49 +1701,49 @@ def min_max(self, event): self.text_instance.show_text=True return - + def apply_drag_legend(self, ax, event): """ drag legend method """ - + dx = event.x - self._last_touch_pos[event][0] if not self.legend_do_scroll_x: dx=0 - dy = event.y - self._last_touch_pos[event][1] + dy = event.y - self._last_touch_pos[event][1] if not self.legend_do_scroll_y: - dy=0 - + dy=0 + legend=None if self.current_legend: if self.current_legend.legend_instance: legend = self.current_legend.legend_instance else: legend = ax.get_legend() - + if legend is not None: - + bbox = legend.get_window_extent() legend_x = bbox.xmin legend_y = bbox.ymin - + loc_in_canvas = legend_x +dx, legend_y+dy loc_in_norm_axes = legend.parent.transAxes.inverted().transform_point(loc_in_canvas) legend._loc = tuple(loc_in_norm_axes) - - #use blit method + + #use blit method if self.background is None: legend.set_visible(False) ax.figure.canvas.draw_idle() - ax.figure.canvas.flush_events() + ax.figure.canvas.flush_events() self.background = ax.figure.canvas.copy_from_bbox(ax.figure.bbox) legend.set_visible(True) if self.last_line is not None: - self.clear_line_prop() - ax.figure.canvas.restore_region(self.background) - + self.clear_line_prop() + ax.figure.canvas.restore_region(self.background) + ax.draw_artist(legend) - + ax.figure.canvas.blit(ax.bbox) - ax.figure.canvas.flush_events() + ax.figure.canvas.flush_events() self.current_legend.update_size() @@ -1755,24 +1755,24 @@ def zoom_factory(self, event, ax, base_scale=1.1): y = newcoord[1] trans = ax.transData.inverted() - xdata, ydata = trans.transform_point((x, y)) + xdata, ydata = trans.transform_point((x, y)) cur_xlim = ax.get_xlim() cur_ylim = ax.get_ylim() scale=ax.get_xscale() yscale=ax.get_yscale() - + if scale == 'linear': old_min=cur_xlim[0] old_max=cur_xlim[1] else: min_=cur_xlim[0] - max_=cur_xlim[1] + max_=cur_xlim[1] old_min = self.transform_eval(min_,ax.yaxis) xdata = self.transform_eval(xdata,ax.yaxis) - old_max = self.transform_eval(max_,ax.yaxis) + old_max = self.transform_eval(max_,ax.yaxis) if yscale == 'linear': yold_min=cur_ylim[0] @@ -1780,7 +1780,7 @@ def zoom_factory(self, event, ax, base_scale=1.1): else: ymin_=cur_ylim[0] - ymax_=cur_ylim[1] + ymax_=cur_ylim[1] yold_min = self.transform_eval(ymin_,ax.yaxis) ydata = self.transform_eval(ydata,ax.yaxis) yold_max = self.transform_eval(ymax_,ax.yaxis) @@ -1813,9 +1813,9 @@ def zoom_factory(self, event, ax, base_scale=1.1): except OverflowError: # Limit case new_min, new_max = min_, max_ if new_min <= 0. or new_max <= 0.: # Limit case - new_min, new_max = min_, max_ + new_min, new_max = min_, max_ ax.set_xlim([new_min, new_max]) - + if self.do_zoom_y: if yscale == 'linear': ax.set_ylim([ydata - new_height * (1 - rely), ydata + new_height * (rely)]) @@ -1827,14 +1827,14 @@ def zoom_factory(self, event, ax, base_scale=1.1): except OverflowError: # Limit case new_ymin, new_ymax = ymin_, ymax_ if new_ymin <= 0. or new_ymax <= 0.: # Limit case - new_ymin, new_ymax = ymin_, ymax_ - ax.set_ylim([new_ymin, new_ymax]) + new_ymin, new_ymax = ymin_, ymax_ + ax.set_ylim([new_ymin, new_ymax]) if self.last_line is not None: - self.clear_line_prop() - + self.clear_line_prop() + ax.figure.canvas.draw_idle() - ax.figure.canvas.flush_events() + ax.figure.canvas.flush_events() def _onSize(self, o, size): """ _onsize method """ @@ -1855,9 +1855,9 @@ def _onSize(self, o, size): s = 'resize_event' event = ResizeEvent(s, self.figcanvas) self.figcanvas.callbacks.process(s, event) - self.figcanvas.draw_idle() - - self.figcanvas.draw() + self.figcanvas.draw_idle() + + self.figcanvas.draw() if self.legend_instance: for current_legend in self.legend_instance: current_legend.update_size() @@ -1868,18 +1868,18 @@ def _onSize(self, o, size): self.hover_instance.figy = self.y if self.selector and self.selector.resize_wgt.verts: #update selector next frame to have correct position - Clock.schedule_once(self.update_selector) - + Clock.schedule_once(self.update_selector) + def update_lim(self): """ update axis lim if zoombox is used""" ax=self.axes self.do_update=False - + #check if inverted axis xleft,xright=self.axes.get_xlim() ybottom,ytop=self.axes.get_ylim() - + if xright>xleft: ax.set_xlim(left=min(self.x0_box,self.x1_box),right=max(self.x0_box,self.x1_box)) else: @@ -1893,10 +1893,10 @@ def reset_box(self): """ reset zoombox and apply zoombox limit if zoombox option if selected""" if min(abs(self._box_size[0]),abs(self._box_size[1]))>self.minzoom: trans = self.axes.transData.inverted() - self.x0_box, self.y0_box = trans.transform_point((self._box_pos[0]-self.pos[0], self._box_pos[1]-self.pos[1])) + self.x0_box, self.y0_box = trans.transform_point((self._box_pos[0]-self.pos[0], self._box_pos[1]-self.pos[1])) self.x1_box, self.y1_box = trans.transform_point((self._box_size[0]+self._box_pos[0]-self.pos[0], self._box_size[1]+self._box_pos[1]-self.pos[1])) self.do_update=True - + self._box_size = 0, 0 self._box_pos = 0, 0 self._alpha_box=0 @@ -1904,22 +1904,22 @@ def reset_box(self): self._pos_x_rect_hor = 0 self._pos_y_rect_hor = 0 self._pos_x_rect_ver = 0 - self._pos_y_rect_ver = 0 - self._alpha_hor=0 + self._pos_y_rect_ver = 0 + self._alpha_hor=0 self._alpha_ver=0 self.invert_rect_hor = False self.invert_rect_ver = False - + def draw_box(self, event, x0, y0, x1, y1) -> None: """ Draw zoombox method - + Args: event: touch kivy event x0: x coordonnate init x1: y coordonnate of move touch y0: y coordonnate init y1: x coordonnate of move touch - + Return: None """ @@ -1927,32 +1927,32 @@ def draw_box(self, event, x0, y0, x1, y1) -> None: # Kivy coords y0 = pos_y + y0 y1 = pos_y + y1 - + if abs(y1-y0)>dp(5) or abs(x1-x0)>dp(5): - self._alpha_box=0.3 + self._alpha_box=0.3 self._alpha_rect=0 - + trans = self.axes.transData.inverted() - xdata, ydata = trans.transform_point((event.x-pos_x, event.y-pos_y)) + xdata, ydata = trans.transform_point((event.x-pos_x, event.y-pos_y)) xleft,xright=self.axes.get_xlim() ybottom,ytop=self.axes.get_ylim() - + xmax = max(xleft,xright) xmin = min(xleft,xright) ymax = max(ybottom,ytop) ymin = min(ybottom,ytop) - + #check inverted data inverted_x = False if xleft>xright: inverted_x=True inverted_y = False if ybottom>ytop: - inverted_y=True + inverted_y=True + + x0data, y0data = trans.transform_point((x0-pos_x, y0-pos_y)) - x0data, y0data = trans.transform_point((x0-pos_x, y0-pos_y)) - if x0data>xmax or x0dataymax or y0data None: if xdata>xmax: x0_max = self.axes.transData.transform([(xmax,ymin)]) if (x1>x0 and not inverted_x) or (x1 None: y1=y0_max[0][1]+pos_y else: y0=y0_max[0][1]+pos_y - + if abs(x1-x0)self.minzoom: self.pos_x_rect_ver=x0 - self.pos_y_rect_ver=y0 - + self.pos_y_rect_ver=y0 + x1_min = self.axes.transData.transform([(xmin,ymin)]) x0=x1_min[0][0]+pos_x @@ -1996,22 +1996,22 @@ def draw_box(self, event, x0, y0, x1, y1) -> None: self._alpha_ver=1 self._alpha_hor=0 - + elif abs(y1-y0)self.minzoom: self.pos_x_rect_hor=x0 - self.pos_y_rect_hor=y0 + self.pos_y_rect_hor=y0 y1_min = self.axes.transData.transform([(xmin,ymin)]) y0=y1_min[0][1]+pos_y - + y0_max = self.axes.transData.transform([(xmax,ymax)]) - y1=y0_max[0][1]+pos_y + y1=y0_max[0][1]+pos_y self._alpha_hor=1 self._alpha_ver=0 - + else: - self._alpha_hor=0 + self._alpha_hor=0 self._alpha_ver=0 if x1>x0: @@ -2022,7 +2022,7 @@ def draw_box(self, event, x0, y0, x1, y1) -> None: self.invert_rect_hor=False else: self.invert_rect_hor=True - + self._box_pos = x0, y0 self._box_size = x1 - x0, y1 - y0 @@ -2050,7 +2050,7 @@ def draw(self): def blit(self, bbox=None): """ Render the figure using agg (blit method). - """ + """ agg = self.get_renderer() w, h = agg.width, agg.height self.widget._bitmap = agg.buffer_rgba() @@ -2061,7 +2061,7 @@ def blit(self, bbox=None): class FakeEvent: x:None y:None - + from kivy.factory import Factory Factory.register('MatplotFigure', MatplotFigure) @@ -2086,8 +2086,8 @@ class FakeEvent: dp(1) if root.invert_rect_ver else -dp(1), \ dp(1) if root.invert_rect_hor else -dp(1), \ dp(1) if root.invert_rect_ver else -dp(1) - - canvas.after: + + canvas.after: #horizontal rectangle left Color: rgba:0, 0, 0, self._alpha_hor @@ -2095,7 +2095,7 @@ class FakeEvent: width: dp(1) rectangle: (self.pos_x_rect_hor+dp(1) if root.invert_rect_ver \ - else self.pos_x_rect_hor-dp(4),self.pos_y_rect_hor-dp(20), dp(4),dp(40)) + else self.pos_x_rect_hor-dp(4),self.pos_y_rect_hor-dp(20), dp(4),dp(40)) #horizontal rectangle right Color: @@ -2104,7 +2104,7 @@ class FakeEvent: width: dp(1) rectangle: (self.pos_x_rect_hor-dp(4)+self._box_size[0] if root.invert_rect_ver \ - else self.pos_x_rect_hor+dp(1)+self._box_size[0], self.pos_y_rect_hor-dp(20), dp(4),dp(40)) + else self.pos_x_rect_hor+dp(1)+self._box_size[0], self.pos_y_rect_hor-dp(20), dp(4),dp(40)) #vertical rectangle bottom Color: @@ -2113,7 +2113,7 @@ class FakeEvent: width: dp(1) rectangle: (self.pos_x_rect_ver-dp(20),self.pos_y_rect_ver+dp(1) if root.invert_rect_hor else \ - self.pos_y_rect_ver-dp(4), dp(40),dp(4)) + self.pos_y_rect_ver-dp(4), dp(40),dp(4)) #vertical rectangle top Color: diff --git a/kivy_matplotlib_widget/uix/graph_widget_3d.py b/kivy_matplotlib_widget/uix/graph_widget_3d.py index f0090b0..0081379 100644 --- a/kivy_matplotlib_widget/uix/graph_widget_3d.py +++ b/kivy_matplotlib_widget/uix/graph_widget_3d.py @@ -102,7 +102,7 @@ class MatplotFigure3D(ScatterLayout): _box_pos = ListProperty([0, 0]) _box_size = ListProperty([0, 0]) _img_texture = ObjectProperty(None) - _alpha_box = NumericProperty(0) + _alpha_box = NumericProperty(0) _bitmap = None do_update=False figcanvas = ObjectProperty(None) @@ -116,7 +116,7 @@ class MatplotFigure3D(ScatterLayout): pos_x_rect_hor=NumericProperty(0) pos_y_rect_hor=NumericProperty(0) pos_x_rect_ver=NumericProperty(0) - pos_y_rect_ver=NumericProperty(0) + pos_y_rect_ver=NumericProperty(0) move_lock = False scale_lock_left = False scale_lock_right = False @@ -124,15 +124,15 @@ class MatplotFigure3D(ScatterLayout): scale_lock_bottom = False center_graph = ListProperty([0, 0]) cursor_label=None - max_hover_rate = NumericProperty(None,allownone=True) - last_hover_time=None + max_hover_rate = NumericProperty(None,allownone=True) + last_hover_time=None cursor_alpha = NumericProperty(0.0) - + default_dpi=None - crop_factor = NumericProperty(1.0) + crop_factor = NumericProperty(1.0) cursor_size=NumericProperty("10dp") - matplot_figure_layout = ObjectProperty(None) - disable_double_tap = BooleanProperty(False) + matplot_figure_layout = ObjectProperty(None) + disable_double_tap = BooleanProperty(False) def on_figure(self, obj, value): if self.default_dpi is None: @@ -148,9 +148,9 @@ def on_figure(self, obj, value): # Texture self._img_texture = Texture.create(size=(w, h)) - + if self.matplot_figure_layout: - + self.cursor = self.figure.axes[0].scatter([0], [0], [0], marker="s",color="k", s=100,alpha=0) self.cursor_cls = cursor(self.figure,remove_artists=[self.cursor]) @@ -159,11 +159,11 @@ def on_figure(self, obj, value): self.cursor_label.xmin_line = self.x + dp(20) self.cursor_label.ymax_line = self.y + h - dp(48) self.matplot_figure_layout.add_widget(self.cursor_label) - + def __init__(self, **kwargs): super(MatplotFigure3D, self).__init__(**kwargs) - + #figure info self.figure = None self.axes = None @@ -171,7 +171,7 @@ def __init__(self, **kwargs): self.xmax = None self.ymin = None self.ymax = None - + #option self.zoompan = None self.fast_draw=True @@ -190,13 +190,13 @@ def __init__(self, **kwargs): #manage matplotlib version with no Stack (replace by _Stack) self._nav_stack = cbook._Stack() else: - self._nav_stack = cbook.Stack() - self.set_history_buttons() - + self._nav_stack = cbook.Stack() + self.set_history_buttons() + #clear touches on touch up self._touches = [] self._last_touch_pos = {} - + self.bind(size=self._onSize) def on_crop_factor(self, obj, value): @@ -205,12 +205,12 @@ def on_crop_factor(self, obj, value): if self.default_dpi is None: #get default matplotlib figure dpi self.default_dpi = self.figure.get_dpi() - + self.figure.dpi = self.default_dpi / value - + self.figcanvas.draw() - + def _get_scale(self): p1 = Vector(*self.to_parent(0, 0)) p2 = Vector(*self.to_parent(1, 0)) @@ -230,7 +230,7 @@ def _get_scale(self): def _set_scale(self, scale): rescale = scale * 1.0 / self.scale - + #update center new_center = self.parent.to_local(*self.parent.center) self.apply_transform(Matrix().scale(rescale, rescale, rescale), @@ -241,8 +241,8 @@ def _set_scale(self, scale): def set_custom_label_widget(self,custom_widget): self.cursor_label = custom_widget - - + + def home(self, *args): """ Restore the original view. @@ -265,7 +265,7 @@ def home(self, *args): self.pos=(0,0) if self.cursor_alpha == 1.0: self.recalcul_cursor() - + def push_current(self): """Push the current view limits and position onto the stack.""" self._nav_stack.push( @@ -275,13 +275,13 @@ def push_current(self): (ax.get_position(True).frozen(), ax.get_position().frozen())) for ax in self.figure.axes})) - self.set_history_buttons() + self.set_history_buttons() def update(self): """Reset the Axes stack.""" self._nav_stack.clear() self.set_history_buttons() - + def _update_view(self): """ Update the viewlim and position from the view and position stack for @@ -301,18 +301,18 @@ def _update_view(self): self.figcanvas.draw() def set_history_buttons(self): - """Enable or disable the back/forward button.""" - + """Enable or disable the back/forward button.""" + def reset_touch(self) -> None: """ reset touch - + Return: None """ self._touches = [] self._last_touch_pos = {} - + def transform_with_touch(self, touch): changed = False @@ -326,7 +326,7 @@ def transform_with_touch(self, touch): * self.do_translation_y dx = dx / self.translation_touches dy = dy / self.translation_touches - + scale_x = self.bbox[1][0]/self.size[0] scale_y = self.bbox[1][1]/self.size[1] if scale_x>1.0: @@ -336,17 +336,17 @@ def transform_with_touch(self, touch): scale_x = scale_x**0.5 scale_y = scale_y**0.5 - - + + self.apply_transform(Matrix().translate(dx/2/scale_x, dy/2/scale_y, 0)) changed = True - + if self.cursor_alpha == 1.0: self.recalcul_cursor() - + if len(self._touches) == 1:# return changed - + # we have more than one touch... list of last known pos points = [Vector(self._last_touch_pos[t]) for t in self._touches if t is not touch] @@ -378,27 +378,27 @@ def transform_with_touch(self, touch): scale = self.scale_min / self.scale elif new_scale > self.scale_max: scale = self.scale_max / self.scale - + if scale<1.0: ## zoom in if self.scale < 5: self.scale = self.scale /scale - + self.crop_factor = 1/self.scale else: ## zoom out if self.scale > 0.6: self.scale = self.scale /scale - + self.crop_factor = 1/self.scale - + if self.cursor_alpha == 1.0: self.recalcul_cursor() changed = True return changed - + def _draw_bitmap(self): @@ -425,7 +425,7 @@ def transform_with_touch2(self, event): # pan view (takes pixel coordinate input) ax.drag_pan(2, None, event.x, event.y) ax.end_pan() - + # Store the event coordinates for the next time through. # ax._sx, ax._sy = x, y trans = ax.transData.inverted() @@ -436,13 +436,13 @@ def transform_with_touch2(self, event): self.figcanvas.draw() if self.cursor_alpha == 1.0: self.recalcul_cursor() - + changed = True #note: avoid zoom in/out on touch mode zoombox if len(self._touches) == 1:# return changed - + # we have more than one touch... list of last known pos points = [Vector(self._last_touch_pos[t]) for t in self._touches if t is not event] @@ -474,7 +474,7 @@ def transform_with_touch2(self, event): scale = self.scale_min / self.scale elif new_scale > self.scale_max: scale = self.scale_max / self.scale - + ax=self.figure.axes[0] ax._scale_axis_limits(scale, scale, scale) self.figcanvas.draw() @@ -489,26 +489,26 @@ def on_touch_down(self, touch): x, y = touch.x, touch.y self.prev_x = touch.x self.prev_y = touch.y - + if self.collide_point(x, y): # real_x, real_y = x - self.pos[0], y - self.pos[1] # self.figcanvas.button_press_event(x, real_y, 1, guiEvent=event) if touch.is_mouse_scrolling: - + if self.touch_mode == 'figure_zoom_pan': - + if touch.button == 'scrolldown': ## zoom in if self.scale < 5: self.scale = self.scale * 1.1 - + self.crop_factor = 1/self.scale - + elif touch.button == 'scrollup': ## zoom out if self.scale > 0.6: self.scale = self.scale * 0.8 - + self.crop_factor = 1/self.scale if self.cursor_alpha == 1.0: @@ -518,7 +518,7 @@ def on_touch_down(self, touch): if touch.button == 'scrollup': ## zoom in scale_factor = 1.1 - + elif touch.button == 'scrolldown': ## zoom out scale_factor = 0.8 @@ -532,7 +532,7 @@ def on_touch_down(self, touch): if not self.disable_double_tap: self.home() return True - + elif self.touch_mode == 'pan': ax=self.figure.axes[0] #transform kivy x,y touch event to x,y data @@ -544,14 +544,14 @@ def on_touch_down(self, touch): # real_x, real_y = x - self.pos[0], y - self.pos[1] x = (touch.x) / self.crop_factor real_y = (touch.y) / self.crop_factor - + self.figcanvas._button = 1 s = 'button_press_event' mouseevent = MouseEvent(s, self.figcanvas, x, real_y, 1, self.figcanvas._key, dblclick=False, guiEvent=touch) - self.figcanvas.callbacks.process(s, mouseevent) - - + self.figcanvas.callbacks.process(s, mouseevent) + + # if the touch isnt on the widget we do nothing if not self.do_collide_after_children: if not self.collide_point(x, y): @@ -592,8 +592,8 @@ def on_touch_down(self, touch): self._last_touch_pos[touch] = (touch.pos[0], touch.pos[1]) - # return True - return False + # return True + return False def on_touch_move(self, event): """ Mouse move while pressed """ @@ -610,9 +610,9 @@ def on_touch_move(self, event): if not self.disable_double_tap: self.home() return True - + elif self.touch_mode == 'figure_zoom_pan': - + touch =event # let the child widgets handle the event if they want if self.collide_point(x, y) and not touch.grab_current == self: @@ -622,18 +622,18 @@ def on_touch_move(self, event): touch.pop() return True touch.pop() - + # rotate/scale/translate if touch in self._touches and touch.grab_current == self: self.transform_with_touch(touch) self._last_touch_pos[touch] = (touch.pos[0], touch.pos[1]) - + # stop propagating if its within our bounds if self.collide_point(x, y): - return True - - + return True + + elif self.touch_mode == 'cursor': if self.cursor_label is None: return @@ -663,21 +663,21 @@ def on_touch_move(self, event): self.myevent.projection=self.projection self.myevent.compare_xdata=False #find closest artist from kivy event - sel = self.cursor_cls.xy_event(self.myevent) - + sel = self.cursor_cls.xy_event(self.myevent) + if not sel: self.cursor_alpha = 0.0 else: - - self.myevent.xdata = sel.target[0] - self.myevent.ydata = sel.target[1] + + self.myevent.xdata = sel.target[0] + self.myevent.ydata = sel.target[1] try: if hasattr(sel.artist,'get_data_3d'): #3d line result = [sel.artist.get_data_3d()[0][sel.index], sel.artist.get_data_3d()[1][sel.index], - sel.artist.get_data_3d()[2][sel.index]] - + sel.artist.get_data_3d()[2][sel.index]] + elif hasattr(sel.artist,'_offsets3d'): #scatter result = [sel.artist._offsets3d[0].data[sel.index], sel.artist._offsets3d[1].data[sel.index], @@ -688,13 +688,13 @@ def on_touch_move(self, event): result = get_xyz_mouse_click(self.myevent, self.figure.axes[0]) self.cursor_alpha = 1.0 - + ax=self.figure.axes[0] - + self.last_x=result[0] self.last_y=result[1] self.last_z=result[2] - + x = ax.xaxis.get_major_formatter().format_data_short(result[0]) y = ax.yaxis.get_major_formatter().format_data_short(result[1]) z = ax.yaxis.get_major_formatter().format_data_short(result[2]) @@ -702,7 +702,7 @@ def on_touch_move(self, event): self.cursor_label.label_x_value = f"{x}" self.cursor_label.label_y_value = f"{y}" self.cursor_label.label_z_value = f"{z}" - + self.recalcul_cursor() elif self.touch_mode == 'pan': @@ -715,42 +715,42 @@ def on_touch_move(self, event): touch.pop() return True touch.pop() - + # rotate/scale/translate if touch in self._touches and touch.grab_current == self: self.transform_with_touch2(touch) self._last_touch_pos[touch] = (touch.pos[0], touch.pos[1]) - + # stop propagating if its within our bounds if self.collide_point(x, y): - return True + return True elif self.touch_mode == 'zoom': - ax=self.figure.axes[0] + ax=self.figure.axes[0] else: x = (event.x) / self.crop_factor real_y = (event.y - self.pos[1]) / self.crop_factor y = (event.y) / self.crop_factor - + self.figcanvas._lastx, self.figcanvas._lasty = x, real_y - + s = 'motion_notify_event' event = MouseEvent(s, self.figcanvas, x, y, self.figcanvas._button, self.figcanvas._key, guiEvent=None) event.inaxes = self.figure.axes[0] self.figcanvas.callbacks.process(s, event) - + if self.cursor_alpha == 1.0: self.recalcul_cursor() def recalcul_cursor(self): ax=self.figure.axes[0] x, y, z = mplot3d.proj3d.transform(self.last_x, self.last_y, self.last_z, ax.M) - xy_pos = ax.transData.transform([(x,y)]) + xy_pos = ax.transData.transform([(x,y)]) self.center_graph = (float(xy_pos[0][0])*self.crop_factor + self.x ,float(xy_pos[0][1])*self.crop_factor + self.y) - + def _onSize(self, o, size): """ _onsize method """ if self.figure is None: @@ -766,14 +766,14 @@ def _onSize(self, o, size): winch = self._width / dpival hinch = self._height / dpival self.figure.set_size_inches(winch / self.crop_factor, hinch / self.crop_factor) - + s = 'resize_event' event = ResizeEvent(s, self.figcanvas) self.figcanvas.callbacks.process(s, event) - self.figcanvas.draw_idle() - - self.figcanvas.draw() - + self.figcanvas.draw_idle() + + self.figcanvas.draw() + l, b, w, h = self.figure.bbox.bounds if self.cursor_label and self.figure and self.cursor_label is not None: @@ -795,7 +795,7 @@ def draw(self): agg = self.get_renderer() w, h = agg.width, agg.height self._isDrawn = True - + self.widget.bt_w = w self.widget.bt_h = h @@ -805,31 +805,31 @@ def draw(self): def blit(self, bbox=None): """ Render the figure using agg (blit method). - """ + """ agg = self.get_renderer() w, h = agg.width, agg.height self.widget._bitmap = agg.buffer_rgba() self.widget.bt_w = w self.widget.bt_h = h self.widget._draw_bitmap() - + class MatplotFigure3DLayout(BoxLayout): - """This handle figure zoom and pan inside the widget. + """This handle figure zoom and pan inside the widget. Cursor label is also not rescale when zooming """ pickradius = NumericProperty(dp(50)) projection = BooleanProperty(False) figure_wgt = ObjectProperty(None) - max_hover_rate = NumericProperty(None,allownone=True) - crop_factor = NumericProperty(1.0) - cursor_size=NumericProperty("10dp") + max_hover_rate = NumericProperty(None,allownone=True) + crop_factor = NumericProperty(1.0) + cursor_size=NumericProperty("10dp") figure_background = ColorProperty([1,1,1,1]) def __init__(self, **kwargs): """ init class """ - super().__init__(**kwargs) - + super().__init__(**kwargs) + def set_figure_background(self,color): """ @@ -846,32 +846,32 @@ def set_figure_background(self,color): self.figure_wgt.figure.axes[0].set_facecolor(to_hex(color)) self.figure_wgt.figure.set_facecolor(to_hex(color)) self.figure_wgt.figcanvas.draw() - + class CursorInfo(FloatLayout): figure_wgt = ObjectProperty(None) xmin_line = NumericProperty(1) - ymax_line = NumericProperty(1) + ymax_line = NumericProperty(1) show_cursor = BooleanProperty(False) - label_x = StringProperty('x') - label_y = StringProperty('y') - label_z = StringProperty('z') - label_x_value = StringProperty('') - label_y_value = StringProperty('') - label_z_value = StringProperty('') + label_x = StringProperty('x') + label_y = StringProperty('y') + label_z = StringProperty('z') + label_x_value = StringProperty('') + label_y_value = StringProperty('') + label_z_value = StringProperty('') text_color=ColorProperty([0,0,0,1]) text_font=StringProperty("Roboto") text_size = NumericProperty(dp(14)) background_color=ColorProperty([1,1,1,1]) - + def __init__(self, **kwargs): """ init class """ - super().__init__(**kwargs) + super().__init__(**kwargs) from kivy.factory import Factory Factory.register('MatplotFigure3D', MatplotFigure3D) -Builder.load_string(''' +Builder.load_string(''' figure_wgt : figure_wgt.__self__ canvas.before: @@ -880,21 +880,21 @@ def __init__(self, **kwargs): Rectangle: pos: self.pos size: self.size - + ScreenManager: Screen: size: self.manager.size - pos: self.manager.pos + pos: self.manager.pos BoxLayout: MatplotFigure3D: id:figure_wgt pickradius : root.pickradius projection : root.projection - max_hover_rate : root.max_hover_rate + max_hover_rate : root.max_hover_rate crop_factor : root.crop_factor - cursor_size : root.cursor_size + cursor_size : root.cursor_size matplot_figure_layout:root - + canvas.before: Color: @@ -903,53 +903,53 @@ def __init__(self, **kwargs): pos: self.pos size: self.size texture: self._img_texture - + Color: rgba: (0, 0, 0, self.cursor_alpha) Rectangle: pos: (self.center_graph[0]-root.cursor_size/2,self.center_graph[1]-root.cursor_size/2) - size: root.cursor_size,root.cursor_size - + size: root.cursor_size,root.cursor_size + - custom_color: [0,0,0,1] + custom_color: [0,0,0,1] size_hint: None, None height: dp(0.01) width: dp(0.01) - + BoxLayout: id:main_box x:root.xmin_line - y: root.ymax_line + dp(4) + y: root.ymax_line + dp(4) size_hint: None, None height: self.minimum_height - width: + width: self.minimum_width + dp(12) if root.show_cursor \ else dp(0.0001) orientation:'vertical' padding: 0,dp(4),0,dp(4) - - + + BoxLayout: size_hint:None,None width:label.texture_size[0] + dp(12) height:label.texture_size[1] + dp(12) - canvas.before: + canvas.before: Color: rgb: root.background_color a:0.8 Rectangle: pos: self.pos - size: self.size + size: self.size Label: id:label - text: + text: root.label_x + ': ' + root.label_x_value + \ ' ' + root.label_y + ': ' + root.label_y_value + \ ' ' + root.label_z + ': ' + root.label_z_value font_size:root.text_size font_name : root.text_font color: root.text_color - + ''') diff --git a/kivy_matplotlib_widget/uix/graph_widget_crop_factor.py b/kivy_matplotlib_widget/uix/graph_widget_crop_factor.py index ef7dc27..e4bf35c 100644 --- a/kivy_matplotlib_widget/uix/graph_widget_crop_factor.py +++ b/kivy_matplotlib_widget/uix/graph_widget_crop_factor.py @@ -35,7 +35,7 @@ class MatplotFigureCropFactor(Widget): _box_pos = ListProperty([0, 0]) _box_size = ListProperty([0, 0]) _img_texture = ObjectProperty(None) - _alpha_box = NumericProperty(0) + _alpha_box = NumericProperty(0) _bitmap = None _pressed = False do_update=False @@ -50,32 +50,32 @@ class MatplotFigureCropFactor(Widget): pos_x_rect_hor=NumericProperty(0) pos_y_rect_hor=NumericProperty(0) pos_x_rect_ver=NumericProperty(0) - pos_y_rect_ver=NumericProperty(0) + pos_y_rect_ver=NumericProperty(0) invert_rect_ver = BooleanProperty(False) invert_rect_hor = BooleanProperty(False) legend_do_scroll_x = BooleanProperty(True) legend_do_scroll_y = BooleanProperty(True) - interactive_axis = BooleanProperty(False) + interactive_axis = BooleanProperty(False) do_pan_x = BooleanProperty(True) - do_pan_y = BooleanProperty(True) + do_pan_y = BooleanProperty(True) do_zoom_x = BooleanProperty(True) - do_zoom_y = BooleanProperty(True) + do_zoom_y = BooleanProperty(True) fast_draw = BooleanProperty(True) #True will don't draw axis xsorted = BooleanProperty(False) #to manage x sorted data minzoom = NumericProperty(dp(20)) - compare_xdata = BooleanProperty(False) + compare_xdata = BooleanProperty(False) hover_instance = ObjectProperty(None, allownone=True) nearest_hover_instance = ObjectProperty(None, allownone=True) compare_hover_instance = ObjectProperty(None, allownone=True) - disable_mouse_scrolling = BooleanProperty(False) - disable_double_tap = BooleanProperty(False) + disable_mouse_scrolling = BooleanProperty(False) + disable_double_tap = BooleanProperty(False) text_instance = None auto_zoom = BooleanProperty(False) zoom_angle_detection=NumericProperty(15) #in degree auto_cursor = BooleanProperty(False) autoscale_visible_only = BooleanProperty(True) autoscale_axis = OptionProperty("both", options=["both", "x", "y"]) - autoscale_tight = BooleanProperty(False) + autoscale_tight = BooleanProperty(False) scale_min = NumericProperty(0.01) scale_max = NumericProperty(1e20) @@ -84,7 +84,7 @@ class MatplotFigureCropFactor(Widget): x_cursor_label = StringProperty("x") y_cursor_label = StringProperty("y") show_cursor = BooleanProperty(False) - + default_dpi=None crop_factor = NumericProperty(2.2) @@ -92,7 +92,7 @@ def on_figure(self, obj, value): if self.default_dpi is None: #get default matplotlib figure dpi self.default_dpi = self.figure.get_dpi() - + self.figure.dpi = self.default_dpi / self.crop_factor self.figcanvas = _FigureCanvas(self.figure, self) self.figcanvas._isDrawn = False @@ -135,13 +135,13 @@ def __init__(self, **kwargs): self.text=None self.cursor_xaxis_formatter=None #used matplotlib formatter to display x cursor value self.cursor_yaxis_formatter=None #used matplotlib formatter to display y cursor value - + # clear touches on touch up self._touches = [] self._last_touch_pos = {} - + #pan management - self.first_touch_pan = None + self.first_touch_pan = None # background self.background = None @@ -156,7 +156,7 @@ def __init__(self, **kwargs): #manage matplotlib version with no Stack (replace by _Stack) self._nav_stack = cbook._Stack() else: - self._nav_stack = cbook.Stack() + self._nav_stack = cbook.Stack() self.bind(size=self._onSize) @@ -165,11 +165,11 @@ def on_crop_factor(self, obj, value): if self.default_dpi is None: #get default matplotlib figure dpi self.default_dpi = self.figure.get_dpi() - + self.figure.dpi = self.default_dpi / value - + self.figcanvas.draw() - + def transform_eval(self, x, axis): custom_transform = axis.get_transform() return custom_transform.transform_non_affine(np.array([x]))[0] @@ -322,28 +322,28 @@ def transform_with_touch(self, event): event_pos = event.pos[0] / self.crop_factor, event.pos[1] / self.crop_factor if len(self._touches) == self.translation_touches: - + if self.touch_mode=='pan': if self._nav_stack() is None: - self.push_current() + self.push_current() self.apply_pan(self.axes, event) - + if self.touch_mode=='pan_x' or self.touch_mode=='pan_y' \ or self.touch_mode=='adjust_x' or self.touch_mode=='adjust_y': if self._nav_stack() is None: - self.push_current() - self.apply_pan(self.axes, event, mode=self.touch_mode) - + self.push_current() + self.apply_pan(self.axes, event, mode=self.touch_mode) + elif self.touch_mode=='zoombox': if self._nav_stack() is None: - self.push_current() + self.push_current() real_x, real_y = event.x - self.pos[0], event.y - self.pos[1] #in case x_init is not create if not hasattr(self,'x_init'): self.x_init = event.x self.y_init = real_y self.draw_box(event, self.x_init,self.y_init, event.x, real_y) - + changed = True #note: avoid zoom in/out on touch mode zoombox @@ -411,7 +411,7 @@ def on_touch_down(self, event): real_x, real_y = x - self.pos[0], y - self.pos[1] self.x_init=x self.y_init=real_y - self.draw_box(event, x, real_y, x, real_y) + self.draw_box(event, x, real_y, x, real_y) event.grab(self) self._touches.append(event) self._last_touch_pos[event] = event.pos[0] / self.crop_factor, event.pos[1] / self.crop_factor @@ -452,8 +452,8 @@ def on_touch_up(self, event): if self.touch_mode=='pan' or self.touch_mode=='zoombox' or \ self.touch_mode=='pan_x' or self.touch_mode=='pan_y' \ or self.touch_mode=='adjust_x' or self.touch_mode=='adjust_y' \ - or self.touch_mode=='minmax': - + or self.touch_mode=='minmax': + self.push_current() if self.interactive_axis: if self.touch_mode=='pan_x' or self.touch_mode=='pan_y' \ @@ -463,31 +463,31 @@ def on_touch_up(self, event): x, y = event.x, event.y if abs(self._box_size[0]) > 1 or abs(self._box_size[1]) > 1 or self.touch_mode=='zoombox': - self.reset_box() + self.reset_box() if not self.collide_point(x, y) and self.do_update: #update axis lim if zoombox is used and touch outside widget - self.update_lim() + self.update_lim() ax=self.axes ax.figure.canvas.draw_idle() - ax.figure.canvas.flush_events() + ax.figure.canvas.flush_events() return True - + # stop propagating if its within our bounds if self.collide_point(x, y) and self.figure: self._pressed = False if self.do_update: - self.update_lim() + self.update_lim() self.anchor_x=None self.anchor_y=None - + ax=self.axes self.background=None self.show_compare_cursor=True ax.figure.canvas.draw_idle() - ax.figure.canvas.flush_events() - + ax.figure.canvas.flush_events() + return True def apply_zoom(self, scale_factor, ax, anchor=(0, 0), new_line=None): @@ -570,29 +570,29 @@ def apply_pan(self, ax, event, mode='pan'): (event.y - self.pos[1]) / self.crop_factor)) xpress, ypress = trans.transform_point(((self._last_touch_pos[event][0] - self.pos[0] / self.crop_factor), (self._last_touch_pos[event][1] - self.pos[1] / self.crop_factor))) - + # trans = ax.transData.inverted() # xdata, ydata = trans.transform_point((event.x-self.pos[0], event.y-self.pos[1])) # xpress, ypress = trans.transform_point((self._last_touch_pos[event][0]-self.pos[0], self._last_touch_pos[event][1]-self.pos[1])) - + scale=ax.get_xscale() yscale=ax.get_yscale() - + if scale == 'linear': dx = xdata - xpress else: dx = self.transform_eval(xdata,ax.xaxis) - \ self.transform_eval(xpress,ax.xaxis) - + if yscale == 'linear': dy = ydata - ypress else: dy = self.transform_eval(ydata,ax.yaxis) - \ - self.transform_eval(ypress,ax.yaxis) + self.transform_eval(ypress,ax.yaxis) xleft,xright=self.axes.get_xlim() ybottom,ytop=self.axes.get_ylim() - + #check inverted data inverted_x = False if xleft>xright: @@ -602,11 +602,11 @@ def apply_pan(self, ax, event, mode='pan'): cur_xlim=(xleft,xright) inverted_y = False if ybottom>ytop: - inverted_y=True + inverted_y=True cur_ylim=(ytop,ybottom) else: - cur_ylim=(ybottom,ytop) - + cur_ylim=(ybottom,ytop) + if self.interactive_axis and self.touch_mode=='pan' and not self.first_touch_pan=='pan': if (ydata < cur_ylim[0] and not inverted_y) or (ydata > cur_ylim[1] and inverted_y): left_anchor_zone= (cur_xlim[1] - cur_xlim[0])*.2 + cur_xlim[0] @@ -618,16 +618,16 @@ def apply_pan(self, ax, event, mode='pan'): self.touch_mode = mode elif (xdata < cur_xlim[0] and not inverted_x) or (xdata > cur_xlim[1] and inverted_x): bottom_anchor_zone= (cur_ylim[1] - cur_ylim[0])*.2 + cur_ylim[0] - top_anchor_zone= (cur_ylim[1] - cur_ylim[0])*.8 + cur_ylim[0] + top_anchor_zone= (cur_ylim[1] - cur_ylim[0])*.8 + cur_ylim[0] if ydata < bottom_anchor_zone or ydata > top_anchor_zone: mode = 'adjust_y' else: - mode= 'pan_y' + mode= 'pan_y' self.touch_mode = mode else: self.touch_mode = 'pan' - if not mode=='pan_y' and not mode=='adjust_y': + if not mode=='pan_y' and not mode=='adjust_y': if mode=='adjust_x': if self.anchor_x is None: midpoint= (cur_xlim[1] + cur_xlim[0])/2 @@ -635,16 +635,16 @@ def apply_pan(self, ax, event, mode='pan'): self.anchor_x='left' else: self.anchor_x='right' - if self.anchor_x=='left': + if self.anchor_x=='left': if xdata> cur_xlim[0]: if scale == 'linear': cur_xlim -= dx else: try: cur_xlim = [self.inv_transform_eval((self.transform_eval(cur_xlim[0],ax.xaxis) - dx),ax.xaxis), - self.inv_transform_eval((self.transform_eval(cur_xlim[1],ax.xaxis) - dx),ax.xaxis)] + self.inv_transform_eval((self.transform_eval(cur_xlim[1],ax.xaxis) - dx),ax.xaxis)] except (ValueError, OverflowError): - cur_xlim = cur_xlim # Keep previous limits + cur_xlim = cur_xlim # Keep previous limits if self.stop_xlimits is not None: if cur_xlim[0] > self.stop_xlimits[0] and cur_xlim[1] < self.stop_xlimits[1]: @@ -652,7 +652,7 @@ def apply_pan(self, ax, event, mode='pan'): ax.set_xlim(cur_xlim[1], None) else: ax.set_xlim(None, cur_xlim[1]) - else: + else: if inverted_x: ax.set_xlim(cur_xlim[1],None) else: @@ -664,17 +664,17 @@ def apply_pan(self, ax, event, mode='pan'): else: try: cur_xlim = [self.inv_transform_eval((self.transform_eval(cur_xlim[0],ax.xaxis) - dx),ax.xaxis), - self.inv_transform_eval((self.transform_eval(cur_xlim[1],ax.xaxis) - dx),ax.xaxis)] + self.inv_transform_eval((self.transform_eval(cur_xlim[1],ax.xaxis) - dx),ax.xaxis)] except (ValueError, OverflowError): - cur_xlim = cur_xlim # Keep previous limits - + cur_xlim = cur_xlim # Keep previous limits + if self.stop_xlimits is not None: if cur_xlim[0] > self.stop_xlimits[0] and cur_xlim[1] < self.stop_xlimits[1]: if inverted_x: ax.set_xlim(None, cur_xlim[0]) else: ax.set_xlim(cur_xlim[0], None) - else: + else: if inverted_x: ax.set_xlim(None,cur_xlim[0]) else: @@ -685,10 +685,10 @@ def apply_pan(self, ax, event, mode='pan'): else: try: cur_xlim = [self.inv_transform_eval((self.transform_eval(cur_xlim[0],ax.xaxis) - dx),ax.xaxis), - self.inv_transform_eval((self.transform_eval(cur_xlim[1],ax.xaxis) - dx),ax.xaxis)] + self.inv_transform_eval((self.transform_eval(cur_xlim[1],ax.xaxis) - dx),ax.xaxis)] except (ValueError, OverflowError): - cur_xlim = cur_xlim # Keep previous limits - + cur_xlim = cur_xlim # Keep previous limits + if self.stop_xlimits is not None: if cur_xlim[0] < self.stop_xlimits[0] or cur_xlim[1] > self.stop_xlimits[1]: side = 'left' if (xleft - cur_xlim[0]) > 0 else 'right' @@ -701,12 +701,12 @@ def apply_pan(self, ax, event, mode='pan'): ax.set_xlim(cur_xlim[1], cur_xlim[0]) else: ax.set_xlim(cur_xlim) - else: + else: if inverted_x: ax.set_xlim(cur_xlim[1],cur_xlim[0]) else: ax.set_xlim(cur_xlim) - + if not mode=='pan_x' and not mode=='adjust_x': if mode=='adjust_y': if self.anchor_y is None: @@ -714,20 +714,20 @@ def apply_pan(self, ax, event, mode='pan'): if ydata>midpoint: self.anchor_y='top' else: - self.anchor_y='bottom' - + self.anchor_y='bottom' + if self.anchor_y=='top': if ydata> cur_ylim[0]: if yscale == 'linear': cur_ylim -= dy - + else: try: cur_ylim = [self.inv_transform_eval((self.transform_eval(cur_ylim[0],ax.yaxis) - dy),ax.yaxis), self.inv_transform_eval((self.transform_eval(cur_ylim[1],ax.yaxis) - dy),ax.yaxis)] except (ValueError, OverflowError): - cur_ylim = cur_ylim # Keep previous limits - + cur_ylim = cur_ylim # Keep previous limits + if inverted_y: ax.set_ylim(cur_ylim[1],None) else: @@ -736,27 +736,27 @@ def apply_pan(self, ax, event, mode='pan'): if ydata< cur_ylim[1]: if yscale == 'linear': cur_ylim -= dy - + else: try: cur_ylim = [self.inv_transform_eval((self.transform_eval(cur_ylim[0],ax.yaxis) - dy),ax.yaxis), self.inv_transform_eval((self.transform_eval(cur_ylim[1],ax.yaxis) - dy),ax.yaxis)] except (ValueError, OverflowError): - cur_ylim = cur_ylim # Keep previous limits + cur_ylim = cur_ylim # Keep previous limits if inverted_y: - ax.set_ylim(None,cur_ylim[0]) + ax.set_ylim(None,cur_ylim[0]) else: ax.set_ylim(cur_ylim[0],None) - else: + else: if yscale == 'linear': - cur_ylim -= dy - + cur_ylim -= dy + else: try: cur_ylim = [self.inv_transform_eval((self.transform_eval(cur_ylim[0],ax.yaxis) - dy),ax.yaxis), self.inv_transform_eval((self.transform_eval(cur_ylim[1],ax.yaxis) - dy),ax.yaxis)] except (ValueError, OverflowError): - cur_ylim = cur_ylim # Keep previous limits + cur_ylim = cur_ylim # Keep previous limits if inverted_y: ax.set_ylim(cur_ylim[1],cur_ylim[0]) else: @@ -766,27 +766,27 @@ def apply_pan(self, ax, event, mode='pan'): if self.first_touch_pan is None: self.first_touch_pan=self.touch_mode - if self.fast_draw: - #use blit method + if self.fast_draw: + #use blit method if self.background is None: self.background_patch_copy.set_visible(True) ax.figure.canvas.draw_idle() - ax.figure.canvas.flush_events() + ax.figure.canvas.flush_events() self.background = ax.figure.canvas.copy_from_bbox(ax.figure.bbox) - self.background_patch_copy.set_visible(False) - ax.figure.canvas.restore_region(self.background) - + self.background_patch_copy.set_visible(False) + ax.figure.canvas.restore_region(self.background) + for line in ax.lines: ax.draw_artist(line) - + ax.figure.canvas.blit(ax.bbox) - ax.figure.canvas.flush_events() - + ax.figure.canvas.flush_events() + # self.update_hover() - + else: ax.figure.canvas.draw_idle() - ax.figure.canvas.flush_events() + ax.figure.canvas.flush_events() def zoom_factory(self, event, ax, base_scale=1.1): """ zoom with scrolling mouse method """ @@ -893,10 +893,10 @@ def _update_background(self): if self.text: self.set_cross_hair_visible(False) self.axes.figure.canvas.draw_idle() - self.axes.figure.canvas.flush_events() + self.axes.figure.canvas.flush_events() self.background = self.axes.figure.canvas.copy_from_bbox(self.axes.figure.bbox) if self.text: - self.set_cross_hair_visible(True) + self.set_cross_hair_visible(True) def _draw(self, ax): if self.fast_draw: @@ -939,35 +939,35 @@ def on_motion(self, window, pos): if self.hover_instance: ax=self.axes self.hover_instance.xmin_line = float(ax.bbox.bounds[0]) *self.crop_factor + self.x - self.hover_instance.xmax_line = float(ax.bbox.bounds[0] + ax.bbox.bounds[2]) *self.crop_factor + self.x + self.hover_instance.xmax_line = float(ax.bbox.bounds[0] + ax.bbox.bounds[2]) *self.crop_factor + self.x self.hover_instance.ymin_line = float(ax.bbox.bounds[1])*self.crop_factor + self.y self.hover_instance.ymax_line = float(ax.bbox.bounds[1] + ax.bbox.bounds[3])*self.crop_factor - xy_pos = ax.transData.transform([(x,y)]) + xy_pos = ax.transData.transform([(x,y)]) self.x_hover_data = x self.y_hover_data = y self.hover_instance.x_hover_pos=float(xy_pos[0][0])*self.crop_factor + self.x self.hover_instance.y_hover_pos=float(xy_pos[0][1])*self.crop_factor + self.y self.hover_instance.show_cursor=True - + if self.cursor_xaxis_formatter: x = self.cursor_xaxis_formatter.format_data(x) else: x = ax.xaxis.get_major_formatter().format_data_short(x) if self.cursor_yaxis_formatter: - y = self.cursor_yaxis_formatter.format_data(y) + y = self.cursor_yaxis_formatter.format_data(y) else: y = ax.yaxis.get_major_formatter().format_data_short(y) self.hover_instance.label_x_value=f"{x}" self.hover_instance.label_y_value=f"{y}" - + if self.hover_instance.x_hover_pos>self.x+(self.axes.bbox.bounds[2] + self.axes.bbox.bounds[0])*self.crop_factor or \ self.hover_instance.x_hover_posself.y+(self.axes.bbox.bounds[1] + self.axes.bbox.bounds[3])*self.crop_factor or \ - self.hover_instance.y_hover_posself.x+(self.axes.bbox.bounds[2] + self.axes.bbox.bounds[0])*self.crop_factor or \ self.hover_instance.x_hover_posself.y+(self.axes.bbox.bounds[1] + self.axes.bbox.bounds[3])*self.crop_factor or \ - self.hover_instance.y_hover_pos None: """ Draw zoombox method - + Args: event: touch kivy event x0: x coordonnate init x1: y coordonnate of move touch y0: y coordonnate init y1: x coordonnate of move touch - + Return: None """ @@ -1025,34 +1025,34 @@ def draw_box(self, event, x0, y0, x1, y1) -> None: # Kivy coords y0 = pos_y + y0 y1 = pos_y + y1 - + if abs(y1-y0)>dp(5) or abs(x1-x0)>dp(5): - self._alpha_box=0.3 + self._alpha_box=0.3 self._alpha_rect=0 - + trans = self.axes.transData.inverted() - # xdata, ydata = trans.transform_point((event.x-pos_x, event.y-pos_y)) + # xdata, ydata = trans.transform_point((event.x-pos_x, event.y-pos_y)) xdata, ydata = trans.transform_point(((event.x-pos_x) / self.crop_factor, (event.y-pos_y) / self.crop_factor)) xleft,xright=self.axes.get_xlim() ybottom,ytop=self.axes.get_ylim() - + xmax = max(xleft,xright) xmin = min(xleft,xright) ymax = max(ybottom,ytop) ymin = min(ybottom,ytop) - + #check inverted data inverted_x = False if xleft>xright: inverted_x=True inverted_y = False if ybottom>ytop: - inverted_y=True + inverted_y=True - # x0data, y0data = trans.transform_point((x0-pos_x, y0-pos_y)) + # x0data, y0data = trans.transform_point((x0-pos_x, y0-pos_y)) x0data, y0data = trans.transform_point(((x0-pos_x) / self.crop_factor, (y0-pos_y) / self.crop_factor)) - + if x0data>xmax or x0dataymax or y0data None: if xdata>xmax: x0_max = self.axes.transData.transform([(xmax,ymin)]) if (x1>x0 and not inverted_x) or (x1 None: y1=y0_max[0][1]*self.crop_factor+pos_y else: y0=y0_max[0][1]*self.crop_factor+pos_y - + if abs(x1-x0)self.minzoom: self.pos_x_rect_ver=x0 - self.pos_y_rect_ver=y0 - + self.pos_y_rect_ver=y0 + x1_min = self.axes.transData.transform([(xmin,ymin)]) x0=x1_min[0][0]*self.crop_factor+pos_x @@ -1096,22 +1096,22 @@ def draw_box(self, event, x0, y0, x1, y1) -> None: self._alpha_ver=1 self._alpha_hor=0 - + elif abs(y1-y0)self.minzoom: self.pos_x_rect_hor=x0 - self.pos_y_rect_hor=y0 + self.pos_y_rect_hor=y0 y1_min = self.axes.transData.transform([(xmin,ymin)]) y0=y1_min[0][1]*self.crop_factor+pos_y - + y0_max = self.axes.transData.transform([(xmax,ymax)]) - y1=y0_max[0][1]*self.crop_factor+pos_y + y1=y0_max[0][1]*self.crop_factor+pos_y self._alpha_hor=1 self._alpha_ver=0 - + else: - self._alpha_hor=0 + self._alpha_hor=0 self._alpha_ver=0 if x1>x0: @@ -1122,20 +1122,20 @@ def draw_box(self, event, x0, y0, x1, y1) -> None: self.invert_rect_hor=False else: self.invert_rect_hor=True - + self._box_pos = x0, y0 self._box_size = x1 - x0, y1 - y0 - + def update_lim(self): """ update axis lim if zoombox is used""" ax=self.axes self.do_update=False - + #check if inverted axis xleft,xright=self.axes.get_xlim() ybottom,ytop=self.axes.get_ylim() - + if xright>xleft: ax.set_xlim(left=min(self.x0_box,self.x1_box),right=max(self.x0_box,self.x1_box)) else: @@ -1149,10 +1149,10 @@ def reset_box(self): """ reset zoombox and apply zoombox limit if zoombox option if selected""" if min(abs(self._box_size[0]),abs(self._box_size[1]))>self.minzoom: trans = self.axes.transData.inverted() - self.x0_box, self.y0_box = trans.transform_point(((self._box_pos[0]-self.pos[0])/ self.crop_factor, (self._box_pos[1]-self.pos[1])/ self.crop_factor)) + self.x0_box, self.y0_box = trans.transform_point(((self._box_pos[0]-self.pos[0])/ self.crop_factor, (self._box_pos[1]-self.pos[1])/ self.crop_factor)) self.x1_box, self.y1_box = trans.transform_point(((self._box_size[0]+self._box_pos[0]-self.pos[0])/ self.crop_factor, (self._box_size[1]+self._box_pos[1]-self.pos[1])/ self.crop_factor)) self.do_update=True - + self._box_size = 0, 0 self._box_pos = 0, 0 self._alpha_box=0 @@ -1160,8 +1160,8 @@ def reset_box(self): self._pos_x_rect_hor = 0 self._pos_y_rect_hor = 0 self._pos_x_rect_ver = 0 - self._pos_y_rect_ver = 0 - self._alpha_hor=0 + self._pos_y_rect_ver = 0 + self._alpha_hor=0 self._alpha_ver=0 self.invert_rect_hor = False self.invert_rect_ver = False @@ -1204,7 +1204,7 @@ def clear(self): Builder.load_string(''' - + canvas: Color: rgba: (1, 1, 1, 1) @@ -1223,8 +1223,8 @@ def clear(self): dp(1) if root.invert_rect_ver else -dp(1), \ dp(1) if root.invert_rect_hor else -dp(1), \ dp(1) if root.invert_rect_ver else -dp(1) - - canvas.after: + + canvas.after: #horizontal rectangle left Color: rgba:0, 0, 0, self._alpha_hor @@ -1232,7 +1232,7 @@ def clear(self): width: dp(1) rectangle: (self.pos_x_rect_hor+dp(1) if root.invert_rect_ver \ - else self.pos_x_rect_hor-dp(4),self.pos_y_rect_hor-dp(20), dp(4),dp(40)) + else self.pos_x_rect_hor-dp(4),self.pos_y_rect_hor-dp(20), dp(4),dp(40)) #horizontal rectangle right Color: @@ -1241,7 +1241,7 @@ def clear(self): width: dp(1) rectangle: (self.pos_x_rect_hor-dp(4)+self._box_size[0] if root.invert_rect_ver \ - else self.pos_x_rect_hor+dp(1)+self._box_size[0], self.pos_y_rect_hor-dp(20), dp(4),dp(40)) + else self.pos_x_rect_hor+dp(1)+self._box_size[0], self.pos_y_rect_hor-dp(20), dp(4),dp(40)) #vertical rectangle bottom Color: @@ -1250,7 +1250,7 @@ def clear(self): width: dp(1) rectangle: (self.pos_x_rect_ver-dp(20),self.pos_y_rect_ver+dp(1) if root.invert_rect_hor else \ - self.pos_y_rect_ver-dp(4), dp(40),dp(4)) + self.pos_y_rect_ver-dp(4), dp(40),dp(4)) #vertical rectangle top Color: @@ -1260,6 +1260,6 @@ def clear(self): rectangle: (self.pos_x_rect_ver-dp(20),self.pos_y_rect_ver-dp(4)+self._box_size[1] \ if root.invert_rect_hor else self.pos_y_rect_ver+dp(1)+self._box_size[1], \ - dp(40),dp(4)) + dp(40),dp(4)) ''') FigureCanvas = _FigureCanvas diff --git a/kivy_matplotlib_widget/uix/graph_widget_general.py b/kivy_matplotlib_widget/uix/graph_widget_general.py index cda7f5c..a51f9e4 100644 --- a/kivy_matplotlib_widget/uix/graph_widget_general.py +++ b/kivy_matplotlib_widget/uix/graph_widget_general.py @@ -1,4 +1,4 @@ -""" MatplotFigure is based on https://github.com/jeysonmc/kivy_matplotlib +""" MatplotFigure is based on https://github.com/jeysonmc/kivy_matplotlib and kivy scatter """ @@ -32,7 +32,7 @@ class MatplotFigureGeneral(Widget): _box_pos = ListProperty([0, 0]) _box_size = ListProperty([0, 0]) _img_texture = ObjectProperty(None) - _alpha_box = NumericProperty(0) + _alpha_box = NumericProperty(0) _bitmap = None _pressed = False do_update=False @@ -47,7 +47,7 @@ class MatplotFigureGeneral(Widget): pos_x_rect_hor=NumericProperty(0) pos_y_rect_hor=NumericProperty(0) pos_x_rect_ver=NumericProperty(0) - pos_y_rect_ver=NumericProperty(0) + pos_y_rect_ver=NumericProperty(0) invert_rect_ver = BooleanProperty(False) invert_rect_hor = BooleanProperty(False) @@ -59,13 +59,13 @@ def on_figure(self, obj, value): h = int(math.ceil(h)) self.width = w self.height = h - + # Texture self._img_texture = Texture.create(size=(w, h)) def __init__(self, **kwargs): super(MatplotFigureGeneral, self).__init__(**kwargs) - + #figure info self.figure = None self.axes = None @@ -73,7 +73,7 @@ def __init__(self, **kwargs): self.xmax = None self.ymin = None self.ymax = None - + #option self.zoompan = None self.fast_draw=True @@ -88,22 +88,22 @@ def __init__(self, **kwargs): self.y0_box = None self.x1_box = None self.y1_box = None - + #clear touches on touch up self._touches = [] self._last_touch_pos = {} - #background + #background self.background=None - self.background_patch_copy=None - + self.background_patch_copy=None + EventLoop.window.bind(mouse_pos=self.on_mouse_move) self.bind(size=self._onSize) def reset_touch(self) -> None: """ reset touch - + Return: None """ @@ -129,13 +129,13 @@ def on_mouse_move(self, window, mouse_pos): real_x, real_y = x - self.pos[0], y - self.pos[1] if self.figcanvas: # self.figcanvas.motion_notify_event(x, real_y, guiEvent=None) - + self.figcanvas._lastx, self.figcanvas._lasty = x, real_y s = 'motion_notify_event' event = MouseEvent(s, self.figcanvas, x, y, self.figcanvas._button, self.figcanvas._key, guiEvent=None) self.figcanvas.callbacks.process(s, event) - + def on_touch_down(self, event): x, y = event.x, event.y @@ -171,13 +171,13 @@ def on_touch_up(self, event): pos_x, pos_y = self.pos real_x, real_y = x - pos_x, y - pos_y # self.figcanvas.button_release_event(x, real_y, 1, guiEvent=event) - + s = 'button_release_event' event = MouseEvent(s, self.figcanvas, x, real_y, 1, self.figcanvas._key, guiEvent=event) self.figcanvas.callbacks.process(s, event) - self.figcanvas._button = None - - self._pressed = False + self.figcanvas._button = None + + self._pressed = False def _onSize(self, o, size): """ _onsize method """ @@ -194,20 +194,20 @@ def _onSize(self, o, size): winch = self._width / dpival hinch = self._height / dpival self.figure.set_size_inches(winch, hinch) - + s = 'resize_event' event = ResizeEvent(s, self.figcanvas) self.figcanvas.callbacks.process(s, event) - self.figcanvas.draw_idle() - - self.figcanvas.draw() + self.figcanvas.draw_idle() + + self.figcanvas.draw() def update_lim(self): """ update axis lim if zoombox is used""" ax=self.axes self.do_update=False - + ax.set_xlim(left=min(self.x0_box,self.x1_box),right=max(self.x0_box,self.x1_box)) ax.set_ylim(bottom=min(self.y0_box,self.y1_box),top=max(self.y0_box,self.y1_box)) @@ -215,29 +215,29 @@ def reset_box(self): """ reset zoombox and apply zoombox limit if zoombox option if selected""" # if min(abs(self._box_size[0]),abs(self._box_size[1]))>self.minzoom: # trans = self.axes.transData.inverted() - # self.x0_box, self.y0_box = trans.transform_point((self._box_pos[0]-self.pos[0], self._box_pos[1]-self.pos[1])) + # self.x0_box, self.y0_box = trans.transform_point((self._box_pos[0]-self.pos[0], self._box_pos[1]-self.pos[1])) # self.x1_box, self.y1_box = trans.transform_point((self._box_size[0]+self._box_pos[0]-self.pos[0], self._box_size[1]+self._box_pos[1]-self.pos[1])) # self.do_update=True - + self._box_size = 0, 0 self._box_pos = 0, 0 self._alpha_box=0 - self._alpha_hor=0 + self._alpha_hor=0 self._alpha_ver=0 self.invert_rect_hor = False self.invert_rect_ver = False - + def draw_box(self, event, x0, y0, x1, y1) -> None: """ Draw zoombox method - + Args: event: touch kivy event x0: x coordonnate init x1: y coordonnate of move touch y0: y coordonnate init y1: x coordonnate of move touch - + Return: None """ @@ -245,9 +245,9 @@ def draw_box(self, event, x0, y0, x1, y1) -> None: # Kivy coords y0 = pos_y + y0 y1 = pos_y + y1 - + if abs(y1-y0)>dp(5) or abs(x1-x0)>dp(5): - self._alpha_box=0.3 + self._alpha_box=0.3 self._alpha_rect=0 if x1>x0: @@ -258,7 +258,7 @@ def draw_box(self, event, x0, y0, x1, y1) -> None: self.invert_rect_hor=False else: self.invert_rect_hor=True - + self._box_pos = x0, y0 self._box_size = x1 - x0, y1 - y0 @@ -286,7 +286,7 @@ def draw(self): def blit(self, bbox=None): """ Render the figure using agg (blit method). - """ + """ agg = self.get_renderer() w, h = agg.width, agg.height self.widget._bitmap = agg.buffer_rgba() diff --git a/kivy_matplotlib_widget/uix/graph_widget_scatter.py b/kivy_matplotlib_widget/uix/graph_widget_scatter.py index fc4dd2c..0d8bdc8 100644 --- a/kivy_matplotlib_widget/uix/graph_widget_scatter.py +++ b/kivy_matplotlib_widget/uix/graph_widget_scatter.py @@ -1,4 +1,4 @@ -""" MatplotFigure is based on https://github.com/jeysonmc/kivy_matplotlib +""" MatplotFigure is based on https://github.com/jeysonmc/kivy_matplotlib and kivy scatter """ @@ -45,7 +45,7 @@ class MatplotFigureScatter(Widget): _box_pos = ListProperty([0, 0]) _box_size = ListProperty([0, 0]) _img_texture = ObjectProperty(None) - _alpha_box = NumericProperty(0) + _alpha_box = NumericProperty(0) _bitmap = None _pressed = False do_update=False @@ -60,14 +60,14 @@ class MatplotFigureScatter(Widget): pos_x_rect_hor=NumericProperty(0) pos_y_rect_hor=NumericProperty(0) pos_x_rect_ver=NumericProperty(0) - pos_y_rect_ver=NumericProperty(0) + pos_y_rect_ver=NumericProperty(0) invert_rect_ver = BooleanProperty(False) invert_rect_hor = BooleanProperty(False) legend_do_scroll_x = BooleanProperty(True) - legend_do_scroll_y = BooleanProperty(True) + legend_do_scroll_y = BooleanProperty(True) interactive_axis = BooleanProperty(False) do_pan_x = BooleanProperty(True) - do_pan_y = BooleanProperty(True) + do_pan_y = BooleanProperty(True) do_zoom_x = BooleanProperty(True) do_zoom_y = BooleanProperty(True) fast_draw = BooleanProperty(True) #True will don't draw axis @@ -75,26 +75,26 @@ class MatplotFigureScatter(Widget): minzoom = NumericProperty(dp(20)) multi_xdata = BooleanProperty(False) multi_xdata_res = NumericProperty(dp(20)) - compare_xdata = BooleanProperty(False) + compare_xdata = BooleanProperty(False) hover_instance = ObjectProperty(None, allownone=True) nearest_hover_instance = ObjectProperty(None, allownone=True) compare_hover_instance = ObjectProperty(None, allownone=True) - disable_mouse_scrolling = BooleanProperty(False) - disable_double_tap = BooleanProperty(False) + disable_mouse_scrolling = BooleanProperty(False) + disable_double_tap = BooleanProperty(False) text_instance = None min_max_option = BooleanProperty(True) auto_zoom = BooleanProperty(False) - zoom_angle_detection=NumericProperty(15) #in degree + zoom_angle_detection=NumericProperty(15) #in degree auto_cursor = BooleanProperty(False) autoscale_visible_only = BooleanProperty(True) autoscale_axis = OptionProperty("both", options=["both", "x", "y"]) autoscale_tight = BooleanProperty(False) - desktop_mode = BooleanProperty(True) #change mouse hover for selector widget + desktop_mode = BooleanProperty(True) #change mouse hover for selector widget current_selector = OptionProperty("None", - options = ["None",'rectangle','lasso','ellipse','span','custom']) + options = ["None",'rectangle','lasso','ellipse','span','custom']) pick_minimum_radius=NumericProperty(dp(50)) pick_radius_axis = OptionProperty("both", options=["both", "x", "y"]) - + def on_figure(self, obj, value): self.figcanvas = _FigureCanvas(self.figure, self) self.figcanvas._isDrawn = False @@ -116,7 +116,7 @@ def on_figure(self, obj, value): #set xmin axes attribute self.axes = self.figure.axes[0] - + #set default xmin/xmax and ymin/ymax self.xmin,self.xmax = self.axes.get_xlim() self.ymin,self.ymax = self.axes.get_ylim() @@ -126,14 +126,14 @@ def on_figure(self, obj, value): for current_legend in self.legend_instance: current_legend.parent.remove_widget(current_legend) self.legend_instance=[] - + if self.auto_cursor and len(self.figure.axes) > 0: self.register_lines(list(self.axes.lines)) self.register_scatters(list(self.axes.collections)) if self.selector and self.axes: self.selector.resize_wgt.ax = self.axes - + # Texture self._img_texture = Texture.create(size=(w, h)) @@ -142,7 +142,7 @@ def on_figure(self, obj, value): def __init__(self, **kwargs): super(MatplotFigureScatter, self).__init__(**kwargs) - + #figure info self.figure = None self.axes = None @@ -152,7 +152,7 @@ def __init__(self, **kwargs): self.ymax = None self.lines = [] self.scatters =[] - + #option self.touch_mode='pan' self.hover_on = False @@ -164,22 +164,22 @@ def __init__(self, **kwargs): self.y0_box = None self.x1_box = None self.y1_box = None - + #clear touches on touch up self._touches = [] self._last_touch_pos = {} - + self.lines=[] self.scatters=[] self.scatter_label = None - #background + #background self.background=None - self.background_patch_copy=None + self.background_patch_copy=None #manage adjust x and y self.anchor_x = None - self.anchor_y = None + self.anchor_y = None #manage hover data self.x_hover_data = None @@ -187,64 +187,64 @@ def __init__(self, **kwargs): #pan management self.first_touch_pan = None - + #manage back and next event if hasattr(cbook,'_Stack'): #manage matplotlib version with no Stack (replace by _Stack) self._nav_stack = cbook._Stack() else: - self._nav_stack = cbook.Stack() - self.set_history_buttons() - + self._nav_stack = cbook.Stack() + self.set_history_buttons() + #legend management self.legend_instance = [] - self.current_legend=None + self.current_legend=None #selector management self.kv_post_done = False - self.selector = None - + self.selector = None + self.bind(size=self._onSize) - + def on_kv_post(self,_): # if not self.selector: if self.current_selector != "None" and selector_widgets_available: if self.current_selector == 'rectangle': self.set_selector(ResizeRelativeLayout) elif self.current_selector == 'lasso': - self.set_selector(LassoRelativeLayout) + self.set_selector(LassoRelativeLayout) elif self.current_selector == 'ellipse': - self.set_selector(EllipseRelativeLayout) + self.set_selector(EllipseRelativeLayout) elif self.current_selector == 'span': - self.set_selector(SpanRelativeLayout) + self.set_selector(SpanRelativeLayout) self.kv_post_done=True def transform_eval(self,x,axis): custom_transform=axis.get_transform() return custom_transform.transform_non_affine(np.array([x]))[0] - + def inv_transform_eval(self,x,axis): inv_custom_transform=axis.get_transform().inverted() return inv_custom_transform.transform_non_affine(np.array([x]))[0] - + def on_current_selector(self,instance,value,*args): - + if self.kv_post_done and selector_widgets_available: if value == 'rectangle': self.set_selector(ResizeRelativeLayout) elif value == 'lasso': - self.set_selector(LassoRelativeLayout) + self.set_selector(LassoRelativeLayout) elif value == 'ellipse': - self.set_selector(EllipseRelativeLayout) + self.set_selector(EllipseRelativeLayout) elif self.current_selector == 'span': - self.set_selector(SpanRelativeLayout) + self.set_selector(SpanRelativeLayout) elif value == "None": if self.selector: Window.unbind(mouse_pos=self.selector.resize_wgt.on_mouse_pos) self.parent.remove_widget(self.selector) self.selector = None - + def set_selector(self,selector,*args): selector_collection=None selector_line=None @@ -257,7 +257,7 @@ def set_selector(self,selector,*args): callback_clear = self.selector.resize_wgt.callback_clear Window.unbind(mouse_pos=self.selector.resize_wgt.on_mouse_pos) self.parent.remove_widget(self.selector) - + self.selector = selector(figure_wgt=self,desktop_mode=self.desktop_mode) self.selector.resize_wgt.ax = self.axes if selector_collection: @@ -268,87 +268,87 @@ def set_selector(self,selector,*args): self.set_callback(callback) if callback_clear: self.set_callback_clear(callback_clear) - - self.parent.add_widget(self.selector) - - def set_collection(self): + + self.parent.add_widget(self.selector) + + def set_collection(self): self.selector.resize_wgt.ax = self.axes - collections = self.axes.collections - + collections = self.axes.collections + if collections: - self.selector.resize_wgt.set_collection(collections[0]) - + self.selector.resize_wgt.set_collection(collections[0]) + def set_line(self,line): - self.selector.resize_wgt.ax = self.axes - self.selector.resize_wgt.set_line(line) - + self.selector.resize_wgt.ax = self.axes + self.selector.resize_wgt.set_line(line) + def set_callback(self,callback): self.selector.resize_wgt.set_callback(callback) - + def set_callback_clear(self,callback): - self.selector.resize_wgt.set_callback_clear(callback) - + self.selector.resize_wgt.set_callback_clear(callback) + def register_lines(self,lines:list) -> None: """ register lines method - + Args: lines (list): list of matplolib line class - + Return: - None - """ + None + """ #use sel,axes limit to avoid graph rescale xmin,xmax = self.axes.get_xlim() ymin,ymax = self.axes.get_ylim() #create cross hair cusor self.horizontal_line = self.axes.axhline(y=self.ymin,color='k', lw=0.8, ls='--', visible=False) self.vertical_line = self.axes.axvline(x=self.xmin,color='k', lw=0.8, ls='--', visible=False) - + #register lines self.lines=lines - + #cursor text - self.text = self.axes.text(1.0, 1.01, '', + self.text = self.axes.text(1.0, 1.01, '', transform=self.axes.transAxes, ha='right') def register_scatters(self,scatters:list) -> None: """ register scatters method - + Args: scatters (list): list of matplolib scatters class - + Return: - None - """ + None + """ #register lines self.scatters=scatters - + def set_cross_hair_visible(self, visible:bool) -> None: """ set curcor visibility - + Args: visible (bool): make cursor visble or not - + Return: None - - """ + + """ self.horizontal_line.set_visible(visible) self.vertical_line.set_visible(visible) self.text.set_visible(visible) def hover(self, event) -> None: """ hover cursor method (cursor to nearest value) - + Args: event: touch kivy event - + Return: None - + """ - + #if cursor is set -> hover is on if self.hover_on: @@ -365,74 +365,74 @@ def hover(self, event) -> None: good_scatter=[] good_index_scatter=[] if self.multi_xdata: - xdata_res, ydata_dump = trans.transform_point((event.x-self.pos[0]+self.multi_xdata_res, + xdata_res, ydata_dump = trans.transform_point((event.x-self.pos[0]+self.multi_xdata_res, event.y - self.pos[1])) delta = abs(xdata_res-xdata) - + for line in self.lines: #get only visible lines - if line.get_visible(): + if line.get_visible(): #get line x,y datas self.x_cursor, self.y_cursor = line.get_xydata().T - + #check if line is not empty - if len(self.x_cursor)!=0: - + if len(self.x_cursor)!=0: + #find closest data index from touch (x axis) if self.xsorted: - index = min(np.searchsorted(self.x_cursor, xdata), len(self.y_cursor) - 1) + index = min(np.searchsorted(self.x_cursor, xdata), len(self.y_cursor) - 1) else: index = np.argsort(abs(self.x_cursor - xdata))[0] #get x data from index x = self.x_cursor[index] - + #find ydata corresponding to xdata if self.multi_xdata: - - #find closest ydata from lines + + #find closest ydata from lines idx_good_y=np.where(abs(np.array(self.x_cursor) - x) None: #find ydata corresponding to xdata #if x axis multiple values if self.multi_xdata: - #find closest ydata from lines + #find closest ydata from lines idx_good_y=np.where(abs(np.array(self.x_cursor) - x) len(good_line)-1: #get datas from closest scatter idx_best -= len(good_line) scatter=good_scatter[idx_best] self.x_cursor, self.y_cursor = scatter.get_offsets().T - + if self.multi_xdata: x = self.x_cursor[good_index2_scatter[idx_best]] - y = self.y_cursor[good_index2_scatter[idx_best]] + y = self.y_cursor[good_index2_scatter[idx_best]] else: x = self.x_cursor[good_index_scatter[idx_best]] - y = self.y_cursor[good_index_scatter[idx_best]] - - ax=scatter.axes + y = self.y_cursor[good_index_scatter[idx_best]] + + ax=scatter.axes else: #get datas from closest line line=good_line[idx_best] self.x_cursor, self.y_cursor = line.get_xydata().T - + if self.multi_xdata: x = self.x_cursor[good_index2[idx_best]] - y = self.y_cursor[good_index2[idx_best]] + y = self.y_cursor[good_index2[idx_best]] else: x = self.x_cursor[good_index[idx_best]] - y = self.y_cursor[good_index[idx_best]] - ax=line.axes - + y = self.y_cursor[good_index[idx_best]] + ax=line.axes + if not self.hover_instance: self.set_cross_hair_visible(True) - + # update the cursor x,y data self.horizontal_line.set_ydata([y,]) self.vertical_line.set_xdata([x,]) #x y label - if self.hover_instance: - xy_pos = ax.transData.transform([(x,y)]) + if self.hover_instance: + xy_pos = ax.transData.transform([(x,y)]) self.x_hover_data = x self.y_hover_data = y self.hover_instance.x_hover_pos=float(xy_pos[0][0]) + self.x self.hover_instance.y_hover_pos=float(xy_pos[0][1]) + self.y self.hover_instance.show_cursor=True - + if self.cursor_xaxis_formatter: x = self.cursor_xaxis_formatter.format_data(x) else: - x = ax.xaxis.get_major_formatter().format_data_short(x) + x = ax.xaxis.get_major_formatter().format_data_short(x) if self.cursor_yaxis_formatter: - y = self.cursor_yaxis_formatter.format_data(y) + y = self.cursor_yaxis_formatter.format_data(y) else: - y = ax.yaxis.get_major_formatter().format_data_short(y) + y = ax.yaxis.get_major_formatter().format_data_short(y) self.hover_instance.label_x_value=f"{x}" self.hover_instance.label_y_value=f"{y}" - + self.hover_instance.xmin_line = float(ax.bbox.bounds[0]) + self.x - self.hover_instance.xmax_line = float(ax.bbox.bounds[0] + ax.bbox.bounds[2]) + self.x + self.hover_instance.xmax_line = float(ax.bbox.bounds[0] + ax.bbox.bounds[2]) + self.x self.hover_instance.ymin_line = float(ax.bbox.bounds[1]) + self.y self.hover_instance.ymax_line = float(ax.bbox.bounds[1] + ax.bbox.bounds[3]) + self.y - if self.scatter_label and idx_best > len(good_line)-1: - if self.multi_xdata: + if self.scatter_label and idx_best > len(good_line)-1: + if self.multi_xdata: self.hover_instance.custom_label = self.scatter_label[good_index2_scatter[idx_best]] - else: + else: self.hover_instance.custom_label = self.scatter_label[good_index_scatter[idx_best]] if line: self.hover_instance.custom_color = get_color_from_hex(to_hex(line.get_color())) @@ -561,31 +561,31 @@ def hover(self, event) -> None: if len(self.x_cursor)==len(scatter.get_facecolors()): color=scatter.get_facecolors()[good_index_scatter[idx_best]] else: - color = scatter.get_facecolors() + color = scatter.get_facecolors() self.hover_instance.custom_color = get_color_from_hex(to_hex(color)) - + if self.hover_instance.x_hover_pos>self.x+self.axes.bbox.bounds[2] + self.axes.bbox.bounds[0] or \ self.hover_instance.x_hover_posself.y+self.axes.bbox.bounds[1] + self.axes.bbox.bounds[3] or \ - self.hover_instance.y_hover_pos len(good_line)-1: - if self.multi_xdata: + y = ax.yaxis.get_major_formatter().format_data_short(y) + + if self.scatter_label and idx_best > len(good_line)-1: + if self.multi_xdata: self.text.set_text(f"{self.scatter_label[good_index2_scatter[idx_best]]} x={x}, y={y}") else: self.text.set_text(f"{self.scatter_label[good_index_scatter[idx_best]]} x={x}, y={y}") @@ -596,29 +596,29 @@ def hover(self, event) -> None: if self.background is None: self.set_cross_hair_visible(False) self.axes.figure.canvas.draw_idle() - self.axes.figure.canvas.flush_events() + self.axes.figure.canvas.flush_events() self.background = self.axes.figure.canvas.copy_from_bbox(self.axes.figure.bbox) - self.set_cross_hair_visible(True) + self.set_cross_hair_visible(True) self.axes.figure.canvas.restore_region(self.background) self.axes.draw_artist(self.text) self.axes.draw_artist(self.horizontal_line) self.axes.draw_artist(self.vertical_line) - + #draw (blit method) - self.axes.figure.canvas.blit(self.axes.bbox) + self.axes.figure.canvas.blit(self.axes.bbox) self.axes.figure.canvas.flush_events() #if touch is too far, hide cross hair cursor else: - self.set_cross_hair_visible(False) + self.set_cross_hair_visible(False) if self.hover_instance: self.hover_instance.x_hover_pos=self.x - self.hover_instance.y_hover_pos=self.y + self.hover_instance.y_hover_pos=self.y self.hover_instance.show_cursor=False self.x_hover_data = None - self.y_hover_data = None + self.y_hover_data = None def autoscale(self): if self.disabled: @@ -626,9 +626,9 @@ def autoscale(self): ax=self.axes no_visible = self.myrelim(ax,visible_only=self.autoscale_visible_only) ax.autoscale_view(tight=self.autoscale_tight, - scalex=True if self.autoscale_axis!="y" else False, + scalex=True if self.autoscale_axis!="y" else False, scaley=True if self.autoscale_axis!="x" else False) - ax.autoscale(axis=self.autoscale_axis,tight=self.autoscale_tight) + ax.autoscale(axis=self.autoscale_axis,tight=self.autoscale_tight) current_xlim = ax.get_xlim() current_ylim = ax.get_ylim() @@ -639,7 +639,7 @@ def autoscale(self): current_margins = (0,0) else: current_margins = ax.margins() - + if self.autoscale_axis!="y": if invert_xaxis: if lim_collection[0]>current_xlim[0] or no_visible: @@ -647,7 +647,7 @@ def autoscale(self): xchanged=True if lim_collection[2]current_xlim[1] or no_visible: ax.set_xlim(right=lim_collection[2]) xchanged=True - + #recalculed margin if xchanged: xlim = ax.get_xlim() ax.set_xlim(left=xlim[0] - current_margins[0]*(xlim[1]-xlim[0])) ax.set_xlim(right=xlim[1] + current_margins[0]*(xlim[1]-xlim[0])) - - ychanged=False - + + ychanged=False + if self.autoscale_axis!="x": if invert_yaxis: if lim_collection[1]>current_ylim[0] or no_visible: ax.set_ylim(bottom=lim_collection[1]) ychanged=True if lim_collection[3]current_ylim[1] or no_visible: - ax.set_ylim(top=lim_collection[3]) + ax.set_ylim(top=lim_collection[3]) ychanged=True - + if ychanged: ylim = ax.get_ylim() ax.set_ylim(bottom=ylim[0] - current_margins[1]*(ylim[1]-ylim[0])) - ax.set_ylim(top=ylim[1] + current_margins[1]*(ylim[1]-ylim[0])) + ax.set_ylim(top=ylim[1] + current_margins[1]*(ylim[1]-ylim[0])) + - ax.figure.canvas.draw_idle() - ax.figure.canvas.flush_events() + ax.figure.canvas.flush_events() ax.set_autoscale_on(False) self.xmin,self.xmax = ax.get_xlim() @@ -719,11 +719,11 @@ def myrelim(self,ax, visible_only=False): ax._update_patch_limits(artist) no_visible=False elif isinstance(artist, mimage.AxesImage): - ax._update_image_limits(artist) + ax._update_image_limits(artist) no_visible=False - + return no_visible - + def data_limit_collection(self,ax,visible_only=False): datalim = None datalim_list = [] @@ -732,7 +732,7 @@ def data_limit_collection(self,ax,visible_only=False): if visible_only: if not collection.get_visible(): eval_lim=False - + if eval_lim: datalim_list.append(collection.get_datalim(ax.transData)) @@ -742,7 +742,7 @@ def data_limit_collection(self,ax,visible_only=False): if ax.xaxis_inverted(): invert_xaxis=True if ax.yaxis_inverted(): - invert_yaxis=True + invert_yaxis=True for i,current_datalim in enumerate(datalim_list): if i==0: if invert_xaxis: @@ -750,20 +750,20 @@ def data_limit_collection(self,ax,visible_only=False): xright = current_datalim.x0 else: xleft = current_datalim.x0 - xright = current_datalim.x1 + xright = current_datalim.x1 if invert_yaxis: ybottom = current_datalim.y1 - ytop = current_datalim.y0 + ytop = current_datalim.y0 else: ybottom = current_datalim.y0 ytop = current_datalim.y1 - - else: + + else: if invert_xaxis: if current_datalim.x1>xleft: xleft = current_datalim.x1 if current_datalim.x0ybottom: ybottom = current_datalim.y1 - if current_datalim.y0ytop: - ytop = current_datalim.y1 + if current_datalim.y1>ytop: + ytop = current_datalim.y1 datalim = [xleft,ybottom,xright,ytop] - - return datalim,invert_xaxis,invert_yaxis + + return datalim,invert_xaxis,invert_yaxis def home(self) -> None: """ reset data axis - + Return: None """ @@ -795,19 +795,19 @@ def home(self) -> None: self.xmax is not None and \ self.ymin is not None and \ self.ymax is not None: - + ax = self.axes xleft,xright=ax.get_xlim() - ybottom,ytop=ax.get_ylim() - + ybottom,ytop=ax.get_ylim() + #check inverted data inverted_x = False if xleft>xright: inverted_x=True inverted_y = False if ybottom>ytop: - inverted_y=True - + inverted_y=True + if inverted_x: ax.set_xlim(right=self.xmin,left=self.xmax) else: @@ -815,10 +815,10 @@ def home(self) -> None: if inverted_y: ax.set_ylim(top=self.ymin,bottom=self.ymax) else: - ax.set_ylim(bottom=self.ymin,top=self.ymax) - + ax.set_ylim(bottom=self.ymin,top=self.ymax) + ax.figure.canvas.draw_idle() - ax.figure.canvas.flush_events() + ax.figure.canvas.flush_events() def back(self, *args): """ @@ -851,7 +851,7 @@ def push_current(self): (ax.get_position(True).frozen(), ax.get_position().frozen())) for ax in self.figure.axes})) - self.set_history_buttons() + self.set_history_buttons() def update(self): """Reset the Axes stack.""" @@ -874,7 +874,7 @@ def _update_view(self): # Restore both the original and modified positions ax._set_position(pos_orig, 'original') ax._set_position(pos_active, 'active') - self.figure.canvas.draw_idle() + self.figure.canvas.draw_idle() self.figure.canvas.flush_events() def set_history_buttons(self): @@ -882,13 +882,13 @@ def set_history_buttons(self): def reset_touch(self) -> None: """ reset touch - + Return: None """ self._touches = [] self._last_touch_pos = {} - + def _get_scale(self): """ kivy scatter _get_scale method """ p1 = Vector(*self.to_parent(0, 0)) @@ -942,40 +942,40 @@ def transform_with_touch(self, event): if len(self._touches) == self.translation_touches: if self.touch_mode=='pan': if self._nav_stack() is None: - self.push_current() + self.push_current() self.apply_pan(self.axes, event) if self.touch_mode=='pan_x' or self.touch_mode=='pan_y' \ or self.touch_mode=='adjust_x' or self.touch_mode=='adjust_y': if self._nav_stack() is None: - self.push_current() - self.apply_pan(self.axes, event, mode=self.touch_mode) - + self.push_current() + self.apply_pan(self.axes, event, mode=self.touch_mode) + elif self.touch_mode=='drag_legend': if self.legend_instance: - self.apply_drag_legend(self.axes, event) - + self.apply_drag_legend(self.axes, event) + elif self.touch_mode=='zoombox': if self._nav_stack() is None: - self.push_current() + self.push_current() real_x, real_y = event.x - self.pos[0], event.y - self.pos[1] #in case x_init is not create if not hasattr(self,'x_init'): self.x_init = event.x self.y_init = real_y self.draw_box(event, self.x_init,self.y_init, event.x, real_y) - + #mode cursor elif self.touch_mode=='cursor': self.hover_on=True self.hover(event) - + changed = True #note: avoid zoom in/out on touch mode zoombox if len(self._touches) == 1:# return changed - + # we have more than one touch... list of last known pos points = [Vector(self._last_touch_pos[t]) for t in self._touches if t is not event] @@ -1008,13 +1008,13 @@ def transform_with_touch(self, event): self.do_zoom_y=True elif angle>90-self.zoom_angle_detection and angle<90+self.zoom_angle_detection: self.do_zoom_x=True - self.do_zoom_y=False + self.do_zoom_y=False elif angle>180-self.zoom_angle_detection and angle<180+self.zoom_angle_detection: self.do_zoom_x=False - self.do_zoom_y=True + self.do_zoom_y=True elif angle>270-self.zoom_angle_detection and angle<270+self.zoom_angle_detection: self.do_zoom_x=True - self.do_zoom_y=False + self.do_zoom_y=False else: self.do_zoom_x=True self.do_zoom_y=True @@ -1027,7 +1027,7 @@ def transform_with_touch(self, event): scale = self.scale_min / self.scale elif new_scale > self.scale_max: scale = self.scale_max / self.scale - + self.apply_zoom(scale, self.axes, anchor=anchor,new_line=new_line) changed = True @@ -1039,13 +1039,13 @@ def on_motion(self,*args): ''' if self._pressed or self.disabled: # Do not process this event if there's a touch_move return - + pos = args[1] newcoord = self.to_widget(pos[0], pos[1]) x = newcoord[0] y = newcoord[1] inside = self.collide_point(x,y) - if inside: + if inside: # will receive all motion events. if self.figcanvas and self.hover_instance: @@ -1059,12 +1059,12 @@ def on_motion(self,*args): def get_data_xy(self,x,y): """ manage x y data in navigation bar TODO""" return None,None - + def on_touch_down(self, event): """ Manage Mouse/touch press """ if self.disabled: return - + x, y = event.x, event.y if self.collide_point(x, y) and self.figure: @@ -1078,7 +1078,7 @@ def on_touch_down(self, event): break if select_legend: if self.touch_mode!='drag_legend': - return False + return False else: event.grab(self) self._touches.append(event) @@ -1086,45 +1086,45 @@ def on_touch_down(self, event): if len(self._touches)>1: #new touch, reset background self.background=None - - return True + + return True else: - self.current_legend = None - + self.current_legend = None + if event.is_mouse_scrolling: if not self.disable_mouse_scrolling: ax = self.axes self.zoom_factory(event, ax, base_scale=1.2) return True - elif event.is_double_tap: + elif event.is_double_tap: if not self.disable_double_tap: - if self.touch_mode!='selector': + if self.touch_mode!='selector': self.home() return True - + else: if self.touch_mode=='cursor': self.hover_on=True - self.hover(event) + self.hover(event) elif self.touch_mode=='zoombox': real_x, real_y = x - self.pos[0], y - self.pos[1] self.x_init=x self.y_init=real_y - self.draw_box(event, x, real_y, x, real_y) + self.draw_box(event, x, real_y, x, real_y) elif self.touch_mode=='minmax': self.min_max(event) elif self.touch_mode=='selector': - pass - + pass + event.grab(self) self._touches.append(event) self._last_touch_pos[event] = event.pos if len(self._touches)>1: #new touch, reset background self.background=None - + return True else: @@ -1134,13 +1134,13 @@ def on_touch_move(self, event): """ Manage Mouse/touch move while pressed """ if self.disabled: return - + x, y = event.x, event.y if event.is_double_tap: if not self.disable_double_tap: - if self.touch_mode!='selector': - self.home() + if self.touch_mode!='selector': + self.home() return True # scale/translate @@ -1156,7 +1156,7 @@ def on_touch_up(self, event): """ Manage Mouse/touch release """ if self.disabled: return - + # remove it from our saved touches if event in self._touches and event.grab_state: event.ungrab(self) @@ -1165,69 +1165,69 @@ def on_touch_up(self, event): if self.touch_mode=='pan' or self.touch_mode=='zoombox' or \ self.touch_mode=='pan_x' or self.touch_mode=='pan_y' \ or self.touch_mode=='adjust_x' or self.touch_mode=='adjust_y' \ - or self.touch_mode=='minmax': + or self.touch_mode=='minmax': self.push_current() if self.interactive_axis: if self.touch_mode=='pan_x' or self.touch_mode=='pan_y' \ or self.touch_mode=='adjust_x' or self.touch_mode=='adjust_y': - self.touch_mode='pan' + self.touch_mode='pan' self.first_touch_pan=None - + x, y = event.x, event.y if abs(self._box_size[0]) > 1 or abs(self._box_size[1]) > 1 or self.touch_mode=='zoombox': - self.reset_box() + self.reset_box() if not self.collide_point(x, y) and self.do_update: #update axis lim if zoombox is used and touch outside widget - self.update_lim() + self.update_lim() ax=self.axes ax.figure.canvas.draw_idle() - ax.figure.canvas.flush_events() + ax.figure.canvas.flush_events() return True - + # stop propagating if its within our bounds if self.collide_point(x, y) and self.figure: self._pressed = False if self.do_update: - self.update_lim() + self.update_lim() self.anchor_x=None self.anchor_y=None - + ax=self.axes self.background=None ax.figure.canvas.draw_idle() - ax.figure.canvas.flush_events() - + ax.figure.canvas.flush_events() + return True def apply_zoom(self, scale_factor, ax, anchor=(0, 0),new_line=None): """ zoom touch method """ if self.touch_mode=='selector': return - + x = anchor[0] y = anchor[1]-self.pos[1] trans = ax.transData.inverted() - xdata, ydata = trans.transform_point((x+new_line.x, y+new_line.y)) - + xdata, ydata = trans.transform_point((x+new_line.x, y+new_line.y)) + cur_xlim = ax.get_xlim() - cur_ylim = ax.get_ylim() + cur_ylim = ax.get_ylim() scale=ax.get_xscale() yscale=ax.get_yscale() - + if scale == 'linear': old_min=cur_xlim[0] old_max=cur_xlim[1] else: min_=cur_xlim[0] - max_=cur_xlim[1] + max_=cur_xlim[1] old_min = self.transform_eval(min_,ax.yaxis) xdata = self.transform_eval(xdata,ax.yaxis) - old_max = self.transform_eval(max_,ax.yaxis) + old_max = self.transform_eval(max_,ax.yaxis) if yscale == 'linear': yold_min=cur_ylim[0] @@ -1235,7 +1235,7 @@ def apply_zoom(self, scale_factor, ax, anchor=(0, 0),new_line=None): else: ymin_=cur_ylim[0] - ymax_=cur_ylim[1] + ymax_=cur_ylim[1] yold_min = self.transform_eval(ymin_,ax.yaxis) ydata = self.transform_eval(ydata,ax.yaxis) yold_max = self.transform_eval(ymax_,ax.yaxis) @@ -1257,9 +1257,9 @@ def apply_zoom(self, scale_factor, ax, anchor=(0, 0),new_line=None): except OverflowError: # Limit case new_min, new_max = min_, max_ if new_min <= 0. or new_max <= 0.: # Limit case - new_min, new_max = min_, max_ + new_min, new_max = min_, max_ ax.set_xlim([new_min, new_max]) - + if self.do_zoom_y: if yscale == 'linear': ax.set_ylim([ydata - new_height * (1 - rely), ydata + new_height * (rely)]) @@ -1271,55 +1271,55 @@ def apply_zoom(self, scale_factor, ax, anchor=(0, 0),new_line=None): except OverflowError: # Limit case new_ymin, new_ymax = ymin_, ymax_ if new_ymin <= 0. or new_ymax <= 0.: # Limit case - new_ymin, new_ymax = ymin_, ymax_ + new_ymin, new_ymax = ymin_, ymax_ ax.set_ylim([new_ymin, new_ymax]) - if self.fast_draw: - #use blit method + if self.fast_draw: + #use blit method if self.background is None: self.background_patch_copy.set_visible(True) ax.figure.canvas.draw_idle() - ax.figure.canvas.flush_events() + ax.figure.canvas.flush_events() self.background = ax.figure.canvas.copy_from_bbox(ax.figure.bbox) - self.background_patch_copy.set_visible(False) + self.background_patch_copy.set_visible(False) ax.figure.canvas.restore_region(self.background) - + for line in ax.lines: ax.draw_artist(line) - + for scatter in self.scatters: self.axes.draw_artist(scatter) - + #TODO annotation do not render correctly on fast draw #find annotation # for child in ax.get_children(): - # if isinstance(child, matplotlib.text.Annotation): - # self.axes.draw_artist(child) - + # if isinstance(child, matplotlib.text.Annotation): + # self.axes.draw_artist(child) + ax.figure.canvas.blit(ax.bbox) ax.figure.canvas.flush_events() - + self.update_hover() else: ax.figure.canvas.draw_idle() - ax.figure.canvas.flush_events() + ax.figure.canvas.flush_events() def apply_pan(self, ax, event, mode='pan'): """ pan method """ - + trans = ax.transData.inverted() xdata, ydata = trans.transform_point((event.x-self.pos[0], event.y-self.pos[1])) xpress, ypress = trans.transform_point((self._last_touch_pos[event][0]-self.pos[0], self._last_touch_pos[event][1]-self.pos[1])) scale=ax.get_xscale() yscale=ax.get_yscale() - + if scale == 'linear': dx = xdata - xpress else: dx = self.transform_eval(xdata,ax.xaxis) - \ self.transform_eval(xpress,ax.xaxis) - + if yscale == 'linear': dy = ydata - ypress else: @@ -1328,7 +1328,7 @@ def apply_pan(self, ax, event, mode='pan'): xleft,xright=self.axes.get_xlim() ybottom,ytop=self.axes.get_ylim() - + #check inverted data inverted_x = False if xleft>xright: @@ -1338,11 +1338,11 @@ def apply_pan(self, ax, event, mode='pan'): cur_xlim=(xleft,xright) inverted_y = False if ybottom>ytop: - inverted_y=True + inverted_y=True cur_ylim=(ytop,ybottom) else: - cur_ylim=(ybottom,ytop) - + cur_ylim=(ybottom,ytop) + if self.interactive_axis and self.touch_mode=='pan' and not self.first_touch_pan=='pan': if (ydata < cur_ylim[0] and not inverted_y) or (ydata > cur_ylim[1] and inverted_y): left_anchor_zone= (cur_xlim[1] - cur_xlim[0])*.2 + cur_xlim[0] @@ -1354,16 +1354,16 @@ def apply_pan(self, ax, event, mode='pan'): self.touch_mode = mode elif (xdata < cur_xlim[0] and not inverted_x) or (xdata > cur_xlim[1] and inverted_x): bottom_anchor_zone= (cur_ylim[1] - cur_ylim[0])*.2 + cur_ylim[0] - top_anchor_zone= (cur_ylim[1] - cur_ylim[0])*.8 + cur_ylim[0] + top_anchor_zone= (cur_ylim[1] - cur_ylim[0])*.8 + cur_ylim[0] if ydata < bottom_anchor_zone or ydata > top_anchor_zone: mode = 'adjust_y' else: - mode= 'pan_y' + mode= 'pan_y' self.touch_mode = mode else: - self.touch_mode = 'pan' + self.touch_mode = 'pan' - if not mode=='pan_y' and not mode=='adjust_y': + if not mode=='pan_y' and not mode=='adjust_y': if mode=='adjust_x': if self.anchor_x is None: midpoint= (cur_xlim[1] + cur_xlim[0])/2 @@ -1371,16 +1371,16 @@ def apply_pan(self, ax, event, mode='pan'): self.anchor_x='left' else: self.anchor_x='right' - if self.anchor_x=='left': + if self.anchor_x=='left': if xdata> cur_xlim[0]: if scale == 'linear': cur_xlim -= dx else: try: cur_xlim = [self.inv_transform_eval((self.transform_eval(cur_xlim[0],ax.xaxis) - dx),ax.xaxis), - self.inv_transform_eval((self.transform_eval(cur_xlim[1],ax.xaxis) - dx),ax.xaxis)] + self.inv_transform_eval((self.transform_eval(cur_xlim[1],ax.xaxis) - dx),ax.xaxis)] except (ValueError, OverflowError): - cur_xlim = cur_xlim # Keep previous limits + cur_xlim = cur_xlim # Keep previous limits if inverted_x: ax.set_xlim(cur_xlim[1],None) else: @@ -1392,9 +1392,9 @@ def apply_pan(self, ax, event, mode='pan'): else: try: cur_xlim = [self.inv_transform_eval((self.transform_eval(cur_xlim[0],ax.xaxis) - dx),ax.xaxis), - self.inv_transform_eval((self.transform_eval(cur_xlim[1],ax.xaxis) - dx),ax.xaxis)] + self.inv_transform_eval((self.transform_eval(cur_xlim[1],ax.xaxis) - dx),ax.xaxis)] except (ValueError, OverflowError): - cur_xlim = cur_xlim # Keep previous limits + cur_xlim = cur_xlim # Keep previous limits if inverted_x: ax.set_xlim(None,cur_xlim[0]) else: @@ -1405,14 +1405,14 @@ def apply_pan(self, ax, event, mode='pan'): else: try: cur_xlim = [self.inv_transform_eval((self.transform_eval(cur_xlim[0],ax.xaxis) - dx),ax.xaxis), - self.inv_transform_eval((self.transform_eval(cur_xlim[1],ax.xaxis) - dx),ax.xaxis)] + self.inv_transform_eval((self.transform_eval(cur_xlim[1],ax.xaxis) - dx),ax.xaxis)] except (ValueError, OverflowError): - cur_xlim = cur_xlim # Keep previous limits + cur_xlim = cur_xlim # Keep previous limits if inverted_x: ax.set_xlim(cur_xlim[1],cur_xlim[0]) else: ax.set_xlim(cur_xlim) - + if not mode=='pan_x' and not mode=='adjust_x': if mode=='adjust_y': if self.anchor_y is None: @@ -1420,19 +1420,19 @@ def apply_pan(self, ax, event, mode='pan'): if ydata>midpoint: self.anchor_y='top' else: - self.anchor_y='bottom' - + self.anchor_y='bottom' + if self.anchor_y=='top': if ydata> cur_ylim[0]: if yscale == 'linear': - cur_ylim -= dy - + cur_ylim -= dy + else: try: cur_ylim = [self.inv_transform_eval((self.transform_eval(cur_ylim[0],ax.yaxis) - dy),ax.yaxis), self.inv_transform_eval((self.transform_eval(cur_ylim[1],ax.yaxis) - dy),ax.yaxis)] except (ValueError, OverflowError): - cur_ylim = cur_ylim # Keep previous limits + cur_ylim = cur_ylim # Keep previous limits if inverted_y: ax.set_ylim(cur_ylim[1],None) else: @@ -1441,27 +1441,27 @@ def apply_pan(self, ax, event, mode='pan'): if ydata< cur_ylim[1]: if yscale == 'linear': cur_ylim -= dy - + else: try: cur_ylim = [self.inv_transform_eval((self.transform_eval(cur_ylim[0],ax.yaxis) - dy),ax.yaxis), self.inv_transform_eval((self.transform_eval(cur_ylim[1],ax.yaxis) - dy),ax.yaxis)] except (ValueError, OverflowError): - cur_ylim = cur_ylim # Keep previous limits + cur_ylim = cur_ylim # Keep previous limits if inverted_y: - ax.set_ylim(None,cur_ylim[0]) + ax.set_ylim(None,cur_ylim[0]) else: ax.set_ylim(cur_ylim[0],None) - else: + else: if yscale == 'linear': - cur_ylim -= dy - + cur_ylim -= dy + else: try: cur_ylim = [self.inv_transform_eval((self.transform_eval(cur_ylim[0],ax.yaxis) - dy),ax.yaxis), self.inv_transform_eval((self.transform_eval(cur_ylim[1],ax.yaxis) - dy),ax.yaxis)] except (ValueError, OverflowError): - cur_ylim = cur_ylim # Keep previous limits + cur_ylim = cur_ylim # Keep previous limits if inverted_y: ax.set_ylim(cur_ylim[1],cur_ylim[0]) else: @@ -1469,34 +1469,34 @@ def apply_pan(self, ax, event, mode='pan'): if self.first_touch_pan is None: self.first_touch_pan=self.touch_mode - - if self.fast_draw: + + if self.fast_draw: #use blit method if self.background is None: self.background_patch_copy.set_visible(True) ax.figure.canvas.draw_idle() - ax.figure.canvas.flush_events() + ax.figure.canvas.flush_events() self.background = ax.figure.canvas.copy_from_bbox(ax.figure.bbox) - self.background_patch_copy.set_visible(False) - ax.figure.canvas.restore_region(self.background) - + self.background_patch_copy.set_visible(False) + ax.figure.canvas.restore_region(self.background) + for line in ax.lines: ax.draw_artist(line) - + for scatter in ax.collections: self.axes.draw_artist(scatter) - + #TODO annotation do not render correctly on fast draw - #find annotation + #find annotation # for child in ax.get_children(): - # if isinstance(child, matplotlib.text.Annotation): - # self.axes.draw_artist(child) - + # if isinstance(child, matplotlib.text.Annotation): + # self.axes.draw_artist(child) + ax.figure.canvas.blit(ax.bbox) - ax.figure.canvas.flush_events() - + ax.figure.canvas.flush_events() + self.update_hover() - + else: ax.figure.canvas.draw_idle() ax.figure.canvas.flush_events() @@ -1505,29 +1505,29 @@ def update_hover(self): """ update hover on fast draw (if exist)""" if self.hover_instance: #update hover pos if needed - if self.hover_instance.show_cursor and self.x_hover_data is not None and self.y_hover_data is not None: - xy_pos = self.axes.transData.transform([(self.x_hover_data,self.y_hover_data)]) + if self.hover_instance.show_cursor and self.x_hover_data is not None and self.y_hover_data is not None: + xy_pos = self.axes.transData.transform([(self.x_hover_data,self.y_hover_data)]) self.hover_instance.x_hover_pos=float(xy_pos[0][0]) + self.x self.hover_instance.y_hover_pos=float(xy_pos[0][1]) + self.y - + self.hover_instance.xmin_line = float(self.axes.bbox.bounds[0]) + self.x - self.hover_instance.xmax_line = float(self.axes.bbox.bounds[0] + self.axes.bbox.bounds[2]) + self.x + self.hover_instance.xmax_line = float(self.axes.bbox.bounds[0] + self.axes.bbox.bounds[2]) + self.x self.hover_instance.ymin_line = float(self.axes.bbox.bounds[1]) + self.y self.hover_instance.ymax_line = float(self.axes.bbox.bounds[1] + self.axes.bbox.bounds[3] )+ self.y - + if self.hover_instance.x_hover_pos>self.x+self.axes.bbox.bounds[2] + self.axes.bbox.bounds[0] or \ self.hover_instance.x_hover_posself.y+self.axes.bbox.bounds[1] + self.axes.bbox.bounds[3] or \ - self.hover_instance.y_hover_pos 1 and resize_wgt.size[1] > 1): return - + #rectangle or spann selector if hasattr(resize_wgt,'span_orientation'): #span selector if resize_wgt.span_orientation == 'vertical': - xy_pos = resize_wgt.ax.transData.transform([(resize_wgt.verts[0][0],resize_wgt.verts[0][1])]) + xy_pos = resize_wgt.ax.transData.transform([(resize_wgt.verts[0][0],resize_wgt.verts[0][1])]) new_pos=resize_wgt.to_widget(*(float(xy_pos[0][0]),float(xy_pos[0][1]))) resize_wgt.pos[0] = new_pos[0] + self.x - + top_bound = float(self.y +resize_wgt.ax.bbox.bounds[3] + resize_wgt.ax.bbox.bounds[1]) bottom_bound = float(self.y +resize_wgt.ax.bbox.bounds[1]) resize_wgt.pos[1] = bottom_bound - self.y - + #recalcul size - xy_pos2 = resize_wgt.ax.transData.transform([(resize_wgt.verts[3][0],resize_wgt.verts[3][1])]) + xy_pos2 = resize_wgt.ax.transData.transform([(resize_wgt.verts[3][0],resize_wgt.verts[3][1])]) resize_wgt.size[0] = float(xy_pos2[0][0] - xy_pos[0][0]) resize_wgt.size[1] = top_bound-bottom_bound - + elif resize_wgt.span_orientation == 'horizontal': - xy_pos = resize_wgt.ax.transData.transform([(resize_wgt.verts[0][0],resize_wgt.verts[0][1])]) + xy_pos = resize_wgt.ax.transData.transform([(resize_wgt.verts[0][0],resize_wgt.verts[0][1])]) new_pos=resize_wgt.to_widget(*(float(xy_pos[0][0]),float(xy_pos[0][1]))) left_bound = float(self.x +resize_wgt.ax.bbox.bounds[0]) right_bound = float(self.x +resize_wgt.ax.bbox.bounds[2] +resize_wgt.ax.bbox.bounds[0] ) - + width = right_bound-left_bound - + left_bound,right_bound = resize_wgt.to_widget(left_bound,right_bound) - + resize_wgt.pos[0] = left_bound resize_wgt.pos[1] = new_pos[1] + self.y #recalcul size - xy_pos2 = resize_wgt.ax.transData.transform([(resize_wgt.verts[0][1],resize_wgt.verts[1][1])]) + xy_pos2 = resize_wgt.ax.transData.transform([(resize_wgt.verts[0][1],resize_wgt.verts[1][1])]) resize_wgt.size[0] = width - resize_wgt.size[1] = float(xy_pos2[0][1] - xy_pos[0][1]) - + resize_wgt.size[1] = float(xy_pos2[0][1] - xy_pos[0][1]) + else: #rectangle selector - + #update all selector pts #recalcul pos - xy_pos = resize_wgt.ax.transData.transform([(resize_wgt.verts[0][0],resize_wgt.verts[0][1])]) + xy_pos = resize_wgt.ax.transData.transform([(resize_wgt.verts[0][0],resize_wgt.verts[0][1])]) new_pos=resize_wgt.to_widget(*(float(xy_pos[0][0]),float(xy_pos[0][1]))) resize_wgt.pos[0] = new_pos[0] + self.x resize_wgt.pos[1] = new_pos[1] + self.y - + #recalcul size - xy_pos2 = resize_wgt.ax.transData.transform([(resize_wgt.verts[2][0],resize_wgt.verts[2][1])]) + xy_pos2 = resize_wgt.ax.transData.transform([(resize_wgt.verts[2][0],resize_wgt.verts[2][1])]) resize_wgt.size[0] = float(xy_pos2[0][0] - xy_pos[0][0]) resize_wgt.size[1] = float(xy_pos2[0][1] - xy_pos[0][1]) - + if self.collide_point(*resize_wgt.to_window(resize_wgt.pos[0],resize_wgt.pos[1])) and \ self.collide_point(*resize_wgt.to_window(resize_wgt.pos[0] + resize_wgt.size[0],resize_wgt.pos[1]+ resize_wgt.size[1])): resize_wgt.opacity = 1 else: - resize_wgt.opacity = 0 + resize_wgt.opacity = 0 def min_max(self, event): """ manage min/max touch mode """ @@ -1684,14 +1684,14 @@ def min_max(self, event): if xlabelbottom and event.x>self.x +ax.bbox.bounds[0] and \ event.x right_anchor_zone: + if event.x < left_anchor_zone or event.x > right_anchor_zone: if self.text_instance: if not self.text_instance.show_text: @@ -1718,9 +1718,9 @@ def min_max(self, event): event.y>self.y + ax.bbox.bounds[1]: top_lim = self.y+ax.bbox.bounds[3]+ax.bbox.bounds[1] - bottom_lim = self.y+ax.bbox.bounds[1] + bottom_lim = self.y+ax.bbox.bounds[1] bottom_anchor_zone= (top_lim - bottom_lim)*.2 + bottom_lim - top_anchor_zone= (top_lim - bottom_lim)*.8 + bottom_lim + top_anchor_zone= (top_lim - bottom_lim)*.8 + bottom_lim if event.y < bottom_anchor_zone or event.y > top_anchor_zone: if self.text_instance: @@ -1745,43 +1745,43 @@ def min_max(self, event): def apply_drag_legend(self, ax, event): """ drag legend method """ - + dx = event.x - self._last_touch_pos[event][0] if not self.legend_do_scroll_x: dx=0 - dy = event.y - self._last_touch_pos[event][1] + dy = event.y - self._last_touch_pos[event][1] if not self.legend_do_scroll_y: - dy=0 + dy=0 legend=None if self.current_legend: if self.current_legend.legend_instance: legend = self.current_legend.legend_instance else: legend = ax.get_legend() - + if legend is not None: - + bbox = legend.get_window_extent() legend_x = bbox.xmin legend_y = bbox.ymin - + loc_in_canvas = legend_x +dx, legend_y+dy loc_in_norm_axes = legend.parent.transAxes.inverted().transform_point(loc_in_canvas) legend._loc = tuple(loc_in_norm_axes) - - #use blit method + + #use blit method if self.background is None: legend.set_visible(False) ax.figure.canvas.draw_idle() - ax.figure.canvas.flush_events() + ax.figure.canvas.flush_events() self.background = ax.figure.canvas.copy_from_bbox(ax.figure.bbox) legend.set_visible(True) - ax.figure.canvas.restore_region(self.background) - + ax.figure.canvas.restore_region(self.background) + ax.draw_artist(legend) - + ax.figure.canvas.blit(ax.bbox) - ax.figure.canvas.flush_events() + ax.figure.canvas.flush_events() self.current_legend.update_size() @@ -1793,24 +1793,24 @@ def zoom_factory(self, event, ax, base_scale=1.1): y = newcoord[1] trans = ax.transData.inverted() - xdata, ydata = trans.transform_point((x, y)) + xdata, ydata = trans.transform_point((x, y)) cur_xlim = ax.get_xlim() cur_ylim = ax.get_ylim() scale=ax.get_xscale() yscale=ax.get_yscale() - + if scale == 'linear': old_min=cur_xlim[0] old_max=cur_xlim[1] else: min_=cur_xlim[0] - max_=cur_xlim[1] + max_=cur_xlim[1] old_min = self.transform_eval(min_,ax.yaxis) xdata = self.transform_eval(xdata,ax.yaxis) - old_max = self.transform_eval(max_,ax.yaxis) + old_max = self.transform_eval(max_,ax.yaxis) if yscale == 'linear': yold_min=cur_ylim[0] @@ -1818,7 +1818,7 @@ def zoom_factory(self, event, ax, base_scale=1.1): else: ymin_=cur_ylim[0] - ymax_=cur_ylim[1] + ymax_=cur_ylim[1] yold_min = self.transform_eval(ymin_,ax.yaxis) ydata = self.transform_eval(ydata,ax.yaxis) yold_max = self.transform_eval(ymax_,ax.yaxis) @@ -1851,9 +1851,9 @@ def zoom_factory(self, event, ax, base_scale=1.1): except OverflowError: # Limit case new_min, new_max = min_, max_ if new_min <= 0. or new_max <= 0.: # Limit case - new_min, new_max = min_, max_ + new_min, new_max = min_, max_ ax.set_xlim([new_min, new_max]) - + if self.do_zoom_y: if yscale == 'linear': ax.set_ylim([ydata - new_height * (1 - rely), ydata + new_height * (rely)]) @@ -1865,11 +1865,11 @@ def zoom_factory(self, event, ax, base_scale=1.1): except OverflowError: # Limit case new_ymin, new_ymax = ymin_, ymax_ if new_ymin <= 0. or new_ymax <= 0.: # Limit case - new_ymin, new_ymax = ymin_, ymax_ - ax.set_ylim([new_ymin, new_ymax]) + new_ymin, new_ymax = ymin_, ymax_ + ax.set_ylim([new_ymin, new_ymax]) ax.figure.canvas.draw_idle() - ax.figure.canvas.flush_events() + ax.figure.canvas.flush_events() def _onSize(self, o, size): """ _onsize method """ @@ -1886,35 +1886,35 @@ def _onSize(self, o, size): winch = self._width / dpival hinch = self._height / dpival self.figure.set_size_inches(winch, hinch) - + s = 'resize_event' event = ResizeEvent(s, self.figcanvas) self.figcanvas.callbacks.process(s, event) - self.figcanvas.draw_idle() - + self.figcanvas.draw_idle() + self.figcanvas.draw() if self.legend_instance: for current_legend in self.legend_instance: - current_legend.update_size() + current_legend.update_size() if self.hover_instance: self.hover_instance.figwidth = self.width self.hover_instance.figheight = self.height self.hover_instance.figx = self.x - self.hover_instance.figy = self.y + self.hover_instance.figy = self.y if self.selector and self.selector.resize_wgt.verts: #update selector next frame to have correct position - Clock.schedule_once(self.update_selector) - + Clock.schedule_once(self.update_selector) + def update_lim(self): """ update axis lim if zoombox is used""" ax=self.axes self.do_update=False - + #check if inverted axis xleft,xright=self.axes.get_xlim() ybottom,ytop=self.axes.get_ylim() - + if xright>xleft: ax.set_xlim(left=min(self.x0_box,self.x1_box),right=max(self.x0_box,self.x1_box)) else: @@ -1928,10 +1928,10 @@ def reset_box(self): """ reset zoombox and apply zoombox limit if zoombox option if selected""" if min(abs(self._box_size[0]),abs(self._box_size[1]))>self.minzoom: trans = self.axes.transData.inverted() - self.x0_box, self.y0_box = trans.transform_point((self._box_pos[0]-self.pos[0], self._box_pos[1]-self.pos[1])) + self.x0_box, self.y0_box = trans.transform_point((self._box_pos[0]-self.pos[0], self._box_pos[1]-self.pos[1])) self.x1_box, self.y1_box = trans.transform_point((self._box_size[0]+self._box_pos[0]-self.pos[0], self._box_size[1]+self._box_pos[1]-self.pos[1])) self.do_update=True - + self._box_size = 0, 0 self._box_pos = 0, 0 self._alpha_box=0 @@ -1939,20 +1939,20 @@ def reset_box(self): self._pos_x_rect_hor = 0 self._pos_y_rect_hor = 0 self._pos_x_rect_ver = 0 - self._pos_y_rect_ver = 0 - self._alpha_hor=0 + self._pos_y_rect_ver = 0 + self._alpha_hor=0 self._alpha_ver=0 - + def draw_box(self, event, x0, y0, x1, y1) -> None: """ Draw zoombox method - + Args: event: touch kivy event x0: x coordonnate init x1: y coordonnate of move touch y0: y coordonnate init y1: x coordonnate of move touch - + Return: None """ @@ -1960,32 +1960,32 @@ def draw_box(self, event, x0, y0, x1, y1) -> None: # Kivy coords y0 = pos_y + y0 y1 = pos_y + y1 - + if abs(y1-y0)>dp(5) or abs(x1-x0)>dp(5): - self._alpha_box=0.3 + self._alpha_box=0.3 self._alpha_rect=0 - + trans = self.axes.transData.inverted() - xdata, ydata = trans.transform_point((event.x-pos_x, event.y-pos_y)) + xdata, ydata = trans.transform_point((event.x-pos_x, event.y-pos_y)) xleft,xright=self.axes.get_xlim() ybottom,ytop=self.axes.get_ylim() - + xmax = max(xleft,xright) xmin = min(xleft,xright) ymax = max(ybottom,ytop) ymin = min(ybottom,ytop) - + #check inverted data inverted_x = False if xleft>xright: inverted_x=True inverted_y = False if ybottom>ytop: - inverted_y=True + inverted_y=True + + x0data, y0data = trans.transform_point((x0-pos_x, y0-pos_y)) - x0data, y0data = trans.transform_point((x0-pos_x, y0-pos_y)) - if x0data>xmax or x0dataymax or y0data None: if xdata>xmax: x0_max = self.axes.transData.transform([(xmax,ymin)]) if (x1>x0 and not inverted_x) or (x1 None: y1=y0_max[0][1]+pos_y else: y0=y0_max[0][1]+pos_y - + if abs(x1-x0)self.minzoom: self.pos_x_rect_ver=x0 - self.pos_y_rect_ver=y0 - + self.pos_y_rect_ver=y0 + x1_min = self.axes.transData.transform([(xmin,ymin)]) x0=x1_min[0][0]+pos_x x0_max = self.axes.transData.transform([(xmax,ymin)]) - x1=x0_max[0][0]+pos_x + x1=x0_max[0][0]+pos_x self._alpha_ver=1 self._alpha_hor=0 - + elif abs(y1-y0)self.minzoom: self.pos_x_rect_hor=x0 - self.pos_y_rect_hor=y0 + self.pos_y_rect_hor=y0 y1_min = self.axes.transData.transform([(xmin,ymin)]) y0=y1_min[0][1]+pos_y - + y0_max = self.axes.transData.transform([(xmax,ymax)]) - y1=y0_max[0][1]+pos_y + y1=y0_max[0][1]+pos_y self._alpha_hor=1 self._alpha_ver=0 - + else: - self._alpha_hor=0 + self._alpha_hor=0 self._alpha_ver=0 if x1>x0: @@ -2055,7 +2055,7 @@ def draw_box(self, event, x0, y0, x1, y1) -> None: self.invert_rect_hor=False else: self.invert_rect_hor=True - + self._box_pos = x0, y0 self._box_size = x1 - x0, y1 - y0 @@ -2083,14 +2083,14 @@ def draw(self): def blit(self, bbox=None): """ Render the figure using agg (blit method). - """ + """ agg = self.get_renderer() w, h = agg.width, agg.height self.widget._bitmap = agg.buffer_rgba() self.widget.bt_w = w self.widget.bt_h = h self.widget._draw_bitmap() - + class FakeEventScatter: x:None y:None @@ -2119,8 +2119,8 @@ class FakeEventScatter: dp(1) if root.invert_rect_ver else -dp(1), \ dp(1) if root.invert_rect_hor else -dp(1), \ dp(1) if root.invert_rect_ver else -dp(1) - - canvas.after: + + canvas.after: #horizontal rectangle left Color: rgba:0, 0, 0, self._alpha_hor @@ -2128,7 +2128,7 @@ class FakeEventScatter: width: dp(1) rectangle: (self.pos_x_rect_hor+dp(1) if root.invert_rect_ver \ - else self.pos_x_rect_hor-dp(4),self.pos_y_rect_hor-dp(20), dp(4),dp(40)) + else self.pos_x_rect_hor-dp(4),self.pos_y_rect_hor-dp(20), dp(4),dp(40)) #horizontal rectangle right Color: @@ -2137,7 +2137,7 @@ class FakeEventScatter: width: dp(1) rectangle: (self.pos_x_rect_hor-dp(4)+self._box_size[0] if root.invert_rect_ver \ - else self.pos_x_rect_hor+dp(1)+self._box_size[0], self.pos_y_rect_hor-dp(20), dp(4),dp(40)) + else self.pos_x_rect_hor+dp(1)+self._box_size[0], self.pos_y_rect_hor-dp(20), dp(4),dp(40)) #vertical rectangle bottom Color: @@ -2146,7 +2146,7 @@ class FakeEventScatter: width: dp(1) rectangle: (self.pos_x_rect_ver-dp(20),self.pos_y_rect_ver+dp(1) if root.invert_rect_hor else \ - self.pos_y_rect_ver-dp(4), dp(40),dp(4)) + self.pos_y_rect_ver-dp(4), dp(40),dp(4)) #vertical rectangle top Color: diff --git a/kivy_matplotlib_widget/uix/graph_widget_twinx.py b/kivy_matplotlib_widget/uix/graph_widget_twinx.py index 287c6c5..a04a4e8 100644 --- a/kivy_matplotlib_widget/uix/graph_widget_twinx.py +++ b/kivy_matplotlib_widget/uix/graph_widget_twinx.py @@ -1,4 +1,4 @@ -""" MatplotFigure is based on https://github.com/jeysonmc/kivy_matplotlib +""" MatplotFigure is based on https://github.com/jeysonmc/kivy_matplotlib and kivy scatter """ @@ -51,7 +51,7 @@ class MatplotFigureTwinx(Widget): _box_pos = ListProperty([0, 0]) _box_size = ListProperty([0, 0]) _img_texture = ObjectProperty(None) - _alpha_box = NumericProperty(0) + _alpha_box = NumericProperty(0) _bitmap = None _pressed = False do_update=False @@ -66,44 +66,44 @@ class MatplotFigureTwinx(Widget): pos_x_rect_hor=NumericProperty(0) pos_y_rect_hor=NumericProperty(0) pos_x_rect_ver=NumericProperty(0) - pos_y_rect_ver=NumericProperty(0) + pos_y_rect_ver=NumericProperty(0) invert_rect_ver = BooleanProperty(False) invert_rect_hor = BooleanProperty(False) legend_do_scroll_x = BooleanProperty(True) legend_do_scroll_y = BooleanProperty(True) - interactive_axis = BooleanProperty(False) + interactive_axis = BooleanProperty(False) do_pan_x = BooleanProperty(True) - do_pan_y = BooleanProperty(True) + do_pan_y = BooleanProperty(True) do_zoom_x = BooleanProperty(True) do_zoom_y = BooleanProperty(True) fast_draw = BooleanProperty(True) #True will don't draw axis xsorted = BooleanProperty(False) #to manage x sorted data - minzoom = NumericProperty(dp(20)) + minzoom = NumericProperty(dp(20)) twinx = BooleanProperty(False) - compare_xdata = BooleanProperty(False) + compare_xdata = BooleanProperty(False) hover_instance = ObjectProperty(None, allownone=True) nearest_hover_instance = ObjectProperty(None, allownone=True) compare_hover_instance = ObjectProperty(None, allownone=True) - disable_mouse_scrolling = BooleanProperty(False) - disable_double_tap = BooleanProperty(False) + disable_mouse_scrolling = BooleanProperty(False) + disable_double_tap = BooleanProperty(False) text_instance = None min_max_option = BooleanProperty(True) auto_zoom = BooleanProperty(False) - zoom_angle_detection=NumericProperty(15) #in degree + zoom_angle_detection=NumericProperty(15) #in degree auto_cursor = BooleanProperty(False) autoscale_visible_only = BooleanProperty(True) autoscale_axis = OptionProperty("both", options=["both", "x", "y"]) autoscale_tight = BooleanProperty(False) - desktop_mode = BooleanProperty(True) #change mouse hover for selector widget + desktop_mode = BooleanProperty(True) #change mouse hover for selector widget current_selector = OptionProperty("None", - options = ["None",'rectangle','lasso','ellipse','span','custom']) + options = ["None",'rectangle','lasso','ellipse','span','custom']) highlight_hover = BooleanProperty(False) highlight_prop = DictProperty({}) highlight_alpha = NumericProperty(0.2) - myevent = MatplotlibEvent() + myevent = MatplotlibEvent() pick_minimum_radius=NumericProperty(dp(50)) pick_radius_axis = OptionProperty("both", options=["both", "x", "y"]) - + def on_figure(self, obj, value): self.figcanvas = _FigureCanvas(self.figure, self) self.figcanvas._isDrawn = False @@ -123,11 +123,11 @@ def on_figure(self, obj, value): ax.spines[pos].set_zorder(10) patch_cpy.set_zorder(9) self.background_patch_copy= ax.add_patch(patch_cpy) - + #set default xmin/xmax and ymin/ymax self.xmin,self.xmax = ax.get_xlim() self.ymin,self.ymax = ax.get_ylim() - + if len(self.figure.axes)==2: self.twinx=True ax2=self.figure.axes[1] @@ -136,38 +136,38 @@ def on_figure(self, obj, value): for pos in ['right', 'top', 'bottom', 'left']: ax2.spines[pos].set_zorder(10) patch_cpy_ax2.set_zorder(9) - self.background_ax2_patch_copy= ax2.add_patch(patch_cpy_ax2) + self.background_ax2_patch_copy= ax2.add_patch(patch_cpy_ax2) self.ymin2,self.ymax2 = ax.get_ylim() else: - self.twinx=False - self.background_ax2_patch_copy= None + self.twinx=False + self.background_ax2_patch_copy= None self.ymin2 = None - self.ymax2 = None - + self.ymax2 = None + if self.legend_instance: #remove all legend_instance from parent widget for current_legend in self.legend_instance: current_legend.parent.remove_widget(current_legend) self.legend_instance=[] - + if self.auto_cursor: if len(self.figure.axes)==2: self.register_lines(list(self.figure.axes[0].lines+self.figure.axes[1].lines)) elif len(self.figure.axes) > 0: self.register_lines(list(self.figure.axes[0].lines)) - + # Texture self._img_texture = Texture.create(size=(w, h)) if self.selector and self.axes: self.selector.resize_wgt.ax = self.axes - + #close last figure in memory (avoid max figure warning) matplotlib.pyplot.close() def __init__(self, **kwargs): super(MatplotFigureTwinx, self).__init__(**kwargs) - + #figure info self.figure = None self.axes = None @@ -178,54 +178,54 @@ def __init__(self, **kwargs): self.ymin2 = None self.ymax2 = None self.lines = [] - + #option self.touch_mode='pan' self.hover_on = False self.cursor_xaxis_formatter=None #used matplotlib formatter to display x cursor value self.cursor_yaxis_formatter=None #used matplotlib formatter to display y cursor value (left axis) - self.cursor_yaxis2_formatter=None #used matplotlib formatter to display y cursor value (right axis) + self.cursor_yaxis2_formatter=None #used matplotlib formatter to display y cursor value (right axis) #zoom box coordonnate self.x0_box = None self.y0_box = None self.x1_box = None self.y1_box = None - + #clear touches on touch up self._touches = [] self._last_touch_pos = {} - #background + #background self.background=None - self.background_patch_copy=None + self.background_patch_copy=None self.background_ax2_patch_copy=None - + #twin x axis self.twinx=False - + #manage adjust x and y self.anchor_x = None - self.anchor_y = None + self.anchor_y = None #manage hover data self.x_hover_data = None self.y_hover_data = None - + #pan management - self.first_touch_pan = None - + self.first_touch_pan = None + #trick to manage wrong canvas size on first call (compare_hover) - self.first_call_compare_hover=False + self.first_call_compare_hover=False #cross hair cursor self.horizontal_line = None self.vertical_line = None - + #manage cursor update on right axis self.cursor_last_axis=None self.cursor_last_y=0 - + #manage show compare cursor on release self.show_compare_cursor=False @@ -235,20 +235,20 @@ def __init__(self, **kwargs): self._nav_stack = cbook._Stack() else: self._nav_stack = cbook.Stack() - self.set_history_buttons() + self.set_history_buttons() #legend management self.legend_instance = [] - self.current_legend=None - + self.current_legend=None + #selector management self.kv_post_done = False - self.selector = None - + self.selector = None + #highlight management self.last_line = None - self.last_line_prop = {} - + self.last_line_prop = {} + self.bind(size=self._onSize) def on_kv_post(self,_): @@ -257,39 +257,39 @@ def on_kv_post(self,_): if self.current_selector == 'rectangle': self.set_selector(ResizeRelativeLayout) elif self.current_selector == 'lasso': - self.set_selector(LassoRelativeLayout) + self.set_selector(LassoRelativeLayout) elif self.current_selector == 'ellipse': - self.set_selector(EllipseRelativeLayout) + self.set_selector(EllipseRelativeLayout) elif self.current_selector == 'span': - self.set_selector(SpanRelativeLayout) + self.set_selector(SpanRelativeLayout) self.kv_post_done=True def transform_eval(self,x,axis): custom_transform=axis.get_transform() return custom_transform.transform_non_affine(np.array([x]))[0] - + def inv_transform_eval(self,x,axis): inv_custom_transform=axis.get_transform().inverted() return inv_custom_transform.transform_non_affine(np.array([x]))[0] def on_current_selector(self,instance,value,*args): - + if self.kv_post_done and selector_widgets_available: if value == 'rectangle': self.set_selector(ResizeRelativeLayout) elif value == 'lasso': - self.set_selector(LassoRelativeLayout) + self.set_selector(LassoRelativeLayout) elif value == 'ellipse': - self.set_selector(EllipseRelativeLayout) + self.set_selector(EllipseRelativeLayout) elif self.current_selector == 'span': - self.set_selector(SpanRelativeLayout) + self.set_selector(SpanRelativeLayout) elif value == "None": if self.selector: Window.unbind(mouse_pos=self.selector.resize_wgt.on_mouse_pos) self.parent.remove_widget(self.selector) self.selector = None - + def set_selector(self,selector,*args): selector_collection=None selector_line=None @@ -302,7 +302,7 @@ def set_selector(self,selector,*args): callback_clear = self.selector.resize_wgt.callback_clear Window.unbind(mouse_pos=self.selector.resize_wgt.on_mouse_pos) self.parent.remove_widget(self.selector) - + self.selector = selector(figure_wgt=self,desktop_mode=self.desktop_mode) self.selector.resize_wgt.ax = self.axes if selector_collection: @@ -313,35 +313,35 @@ def set_selector(self,selector,*args): self.set_callback(callback) if callback_clear: self.set_callback_clear(callback_clear) - - self.parent.add_widget(self.selector) - - def set_collection(self): + + self.parent.add_widget(self.selector) + + def set_collection(self): self.selector.resize_wgt.ax = self.axes - collections = self.figure.axes[0].collections - + collections = self.figure.axes[0].collections + if collections: - self.selector.resize_wgt.set_collection(collections[0]) - + self.selector.resize_wgt.set_collection(collections[0]) + def set_line(self,line): - self.selector.resize_wgt.ax = self.axes - self.selector.resize_wgt.set_line(line) - + self.selector.resize_wgt.ax = self.axes + self.selector.resize_wgt.set_line(line) + def set_callback(self,callback): self.selector.resize_wgt.set_callback(callback) - + def set_callback_clear(self,callback): - self.selector.resize_wgt.set_callback_clear(callback) + self.selector.resize_wgt.set_callback_clear(callback) def register_lines(self,lines:list) -> None: """ register lines method - + Args: lines (list): list of matplolib line class - + Return: - None - """ + None + """ ax=self.figure.axes[0] #use sel,axes limit to avoid graph rescale xmin,xmax = ax.get_xlim() @@ -350,46 +350,46 @@ def register_lines(self,lines:list) -> None: #create cross hair cursor self.horizontal_line = ax.axhline(y=self.ymin,color='k', lw=0.8, ls='--', visible=False) self.vertical_line = ax.axvline(x=self.xmin,color='k', lw=0.8, ls='--', visible=False) - + #register lines self.lines=lines - + #cursor text - self.text = ax.text(1.0, 1.01, '', + self.text = ax.text(1.0, 1.01, '', transform=ax.transAxes, ha='right') def set_cross_hair_visible(self, visible:bool) -> None: """ set curcor visibility - + Args: visible (bool): make cursor visble or not - + Return: None - - """ + + """ self.horizontal_line.set_visible(visible) self.vertical_line.set_visible(visible) self.text.set_visible(visible) def update_cursor(self): if self.twinx and self.horizontal_line and self.cursor_last_axis: - + if self.horizontal_line.get_visible() or self.hover_instance: - + if self.cursor_last_axis==self.figure.axes[1]: - + if self.hover_instance: - if self.hover_instance.show_cursor and self.x_hover_data is not None and self.y_hover_data is not None: + if self.hover_instance.show_cursor and self.x_hover_data is not None and self.y_hover_data is not None: self.y_hover_data=self.cursor_last_y - xy_pos = self.figure.axes[1].transData.transform([(self.x_hover_data,self.y_hover_data)]) + xy_pos = self.figure.axes[1].transData.transform([(self.x_hover_data,self.y_hover_data)]) self.hover_instance.y_hover_pos=float(xy_pos[0][1]) + self.y - + else: new_y = self.cursor_last_y - x=self.vertical_line.get_xdata() + x=self.vertical_line.get_xdata() trans = self.figure.axes[0].transData.inverted() xy_pos = self.figure.axes[1].transData.transform([(x,new_y)]) xdata, ydata = trans.transform_point((xy_pos[0][0], xy_pos[0][1])) @@ -397,32 +397,32 @@ def update_cursor(self): def clear_line_prop(self) -> None: """ clear attribute line_prop method - + Args: None - + Return: None - - """ + + """ if self.last_line_prop: for key in self.last_line_prop: set_line_attr = getattr(self.last_line,'set_' + key) - set_line_attr(self.last_line_prop[key]) - self.last_line_prop={} - self.last_line=None + set_line_attr(self.last_line_prop[key]) + self.last_line_prop={} + self.last_line=None def hover(self, event) -> None: """ hover cursor method (cursor to nearest value) - + Args: event: touch kivy event - + Return: None - + """ - + #if cursor is set -> hover is on if self.hover_on: @@ -437,116 +437,116 @@ def hover(self, event) -> None: good_index2=[] for line in self.lines: #get only visible lines - if line.get_visible(): + if line.get_visible(): #get line x,y datas self.x_cursor, self.y_cursor = line.get_xydata().T - + #check if line is not empty - if len(self.x_cursor)!=0: - + if len(self.x_cursor)!=0: + #find closest data index from touch (x axis) if self.xsorted: index = min(np.searchsorted(self.x_cursor, xdata), len(self.y_cursor) - 1) - + else: index = np.argsort(abs(self.x_cursor - xdata))[0] #get x data from index x = self.x_cursor[index] - + if self.compare_xdata: y = self.y_cursor[index] - + #get distance between line and touch (in pixels) - ax=line.axes + ax=line.axes #left axis if self.twinx: if ax==self.figure.axes[1]: #right axis trans = self.figure.axes[1].transData.inverted() - xdata2, ydata2 = trans.transform_point((event.x - self.pos[0], event.y - self.pos[1])) + xdata2, ydata2 = trans.transform_point((event.x - self.pos[0], event.y - self.pos[1])) xy_pixels_mouse = ax.transData.transform(np.vstack([xdata2,ydata2]).T) - else: - #left axis - xy_pixels_mouse = ax.transData.transform([(xdata,ydata)]) - else: - #left axis + else: + #left axis + xy_pixels_mouse = ax.transData.transform([(xdata,ydata)]) + else: + #left axis xy_pixels_mouse = ax.transData.transform([(xdata,ydata)]) if np.ma.is_masked(x) or np.ma.is_masked(y) or np.isnan(x) or np.isnan(y): distance.append(np.nan) - else: + else: xy_pixels = ax.transData.transform([(x,ydata)]) - dx2 = (xy_pixels_mouse[0][0]-xy_pixels[0][0]) + dx2 = (xy_pixels_mouse[0][0]-xy_pixels[0][0]) distance.append(abs(dx2)) - else: + else: #find ydata corresponding to xdata y = self.y_cursor[index] - + #get distance between line and touch (in pixels) - ax=line.axes + ax=line.axes if self.twinx: if ax==self.figure.axes[1]: #right axis trans = self.figure.axes[1].transData.inverted() - xdata2, ydata2 = trans.transform_point((event.x - self.pos[0], event.y - self.pos[1])) + xdata2, ydata2 = trans.transform_point((event.x - self.pos[0], event.y - self.pos[1])) xy_pixels_mouse = ax.transData.transform(np.vstack([xdata2,ydata2]).T) - else: - #left axis - xy_pixels_mouse = ax.transData.transform([(xdata,ydata)]) - else: - #left axis + else: + #left axis + xy_pixels_mouse = ax.transData.transform([(xdata,ydata)]) + else: + #left axis xy_pixels_mouse = ax.transData.transform([(xdata,ydata)]) if np.ma.is_masked(x) or np.ma.is_masked(y): distance.append(np.nan) - else: + else: xy_pixels = ax.transData.transform([(x,y)]) dx2 = (xy_pixels_mouse[0][0]-xy_pixels[0][0])**2 - dy2 = (xy_pixels_mouse[0][1]-xy_pixels[0][1])**2 - + dy2 = (xy_pixels_mouse[0][1]-xy_pixels[0][1])**2 + #store distance if self.pick_radius_axis == 'both': distance.append((dx2 + dy2)**0.5) if self.pick_radius_axis == 'x': distance.append(abs(dx2)) if self.pick_radius_axis == 'y': - distance.append(abs(dy2)) - + distance.append(abs(dy2)) + #store all best lines and index good_line.append(line) good_index.append(index) - + #case if no good line if len(good_line)==0: return - #if minimum distance if lower than 50 pixels, get line datas with - #minimum distance + #if minimum distance if lower than 50 pixels, get line datas with + #minimum distance if np.nanmin(distance)0: available_widget = self.hover_instance.children_list nb_widget=len(available_widget) @@ -558,188 +558,188 @@ def hover(self, event) -> None: line=good_line[idx_best_list[i]] line_label = line.get_label() if line_label in self.hover_instance.children_names: - index= self.hover_instance.children_names.index(line_label) + index= self.hover_instance.children_names.index(line_label) y_cursor = line.get_ydata() - y = y_cursor[good_index[idx_best_list[i]]] + y = y_cursor[good_index[idx_best_list[i]]] ax=line.axes - - xy_pos = ax.transData.transform([(x,y)]) + + xy_pos = ax.transData.transform([(x,y)]) pos_y=float(xy_pos[0][1]) + self.y - + if pos_yself.y+ax.bbox.bounds[1]: - + available_widget[index].x_hover_pos=float(xy_pos[0][0]) + self.x available_widget[index].y_hover_pos=float(xy_pos[0][1]) + self.y available_widget[index].custom_color = get_color_from_hex(to_hex(line.get_color())) - + if self.twinx: if ax==self.figure.axes[1]: if self.cursor_yaxis2_formatter: - y = self.cursor_yaxis2_formatter.format_data(y) + y = self.cursor_yaxis2_formatter.format_data(y) else: - y = ax.yaxis.get_major_formatter().format_data_short(y) + y = ax.yaxis.get_major_formatter().format_data_short(y) else: if self.cursor_yaxis_formatter: - y = self.cursor_yaxis_formatter.format_data(y) - - else: + y = self.cursor_yaxis_formatter.format_data(y) + + else: if self.cursor_yaxis_formatter: - y = self.cursor_yaxis_formatter.format_data(y) + y = self.cursor_yaxis_formatter.format_data(y) else: - y = ax.yaxis.get_major_formatter().format_data_short(y) + y = ax.yaxis.get_major_formatter().format_data_short(y) available_widget[index].label_y_value=f"{y}" available_widget[index].show_widget=True index_list.remove(index) - + for ii in index_list: available_widget[ii].show_widget=False if self.cursor_xaxis_formatter: x = self.cursor_xaxis_formatter.format_data(x) else: - x = ax.xaxis.get_major_formatter().format_data_short(x) - + x = ax.xaxis.get_major_formatter().format_data_short(x) + self.hover_instance.label_x_value=f"{x}" if hasattr(self.hover_instance,'overlap_check'): self.hover_instance.overlap_check() self.hover_instance.xmin_line = float(ax.bbox.bounds[0]) + self.x - self.hover_instance.xmax_line = float(ax.bbox.bounds[0] + ax.bbox.bounds[2]) + self.x + self.hover_instance.xmax_line = float(ax.bbox.bounds[0] + ax.bbox.bounds[2]) + self.x self.hover_instance.ymin_line = float(ax.bbox.bounds[1]) + self.y self.hover_instance.ymax_line = float(ax.bbox.bounds[1] + ax.bbox.bounds[3]) + self.y - + if self.hover_instance.x_hover_pos>self.x+ax.bbox.bounds[2] + ax.bbox.bounds[0] or \ - self.hover_instance.x_hover_posself.x+self.figure.axes[0].bbox.bounds[2] + self.figure.axes[0].bbox.bounds[0] or \ self.hover_instance.x_hover_posself.y+self.figure.axes[0].bbox.bounds[1] + self.figure.axes[0].bbox.bounds[3] or \ - self.hover_instance.y_hover_pos None: for current_line in lines_list: default_alpha.append(current_line.get_alpha()) current_line.set_alpha(self.highlight_alpha) - + ax.figure.canvas.draw_idle() - ax.figure.canvas.flush_events() + ax.figure.canvas.flush_events() self.background_highlight=ax.figure.canvas.copy_from_bbox(ax.figure.bbox) self.last_line=line for i,current_line in enumerate(lines_list): current_line.set_alpha(default_alpha[i]) default_alpha[i] - + if self.highlight_prop: self.last_line_prop={} for key in self.highlight_prop: @@ -767,77 +767,77 @@ def hover(self, event) -> None: elif self.last_line_prop: for key in self.last_line_prop: set_line_attr = getattr(self.last_line,'set_' + key) - set_line_attr(self.last_line_prop[key]) + set_line_attr(self.last_line_prop[key]) self.hover_instance.custom_color = get_color_from_hex(to_hex(line.get_color())) self.last_line_prop={} for key in self.highlight_prop: line_attr = getattr(line,'get_' + key) self.last_line_prop.update({key:line_attr()}) set_line_attr = getattr(line,'set_' + key) - set_line_attr(self.highlight_prop[key]) + set_line_attr(self.highlight_prop[key]) self.last_line=line - + ax.figure.canvas.restore_region(self.background_highlight) ax.draw_artist(line) - + #draw (blit method) - ax.figure.canvas.blit(ax.bbox) - ax.figure.canvas.flush_events() - + ax.figure.canvas.blit(ax.bbox) + ax.figure.canvas.flush_events() + return else: - self.text.set_text(f"x={x}, y={y}") - - #blit method (always use because same visual effect as draw) + self.text.set_text(f"x={x}, y={y}") + + #blit method (always use because same visual effect as draw) if self.background is None: self.set_cross_hair_visible(False) self.figure.canvas.draw_idle() - self.figure.canvas.flush_events() + self.figure.canvas.flush_events() self.background = self.figure.canvas.copy_from_bbox(self.figure.bbox) self.set_cross_hair_visible(True) if self.last_line is not None: self.clear_line_prop() - + self.figure.canvas.restore_region(self.background) self.figure.axes[0].draw_artist(self.text) - + self.figure.axes[0].draw_artist(self.horizontal_line) - self.figure.axes[0].draw_artist(self.vertical_line) - + self.figure.axes[0].draw_artist(self.vertical_line) + #draw (blit method) - self.figure.canvas.blit(self.figure.axes[0].bbox) + self.figure.canvas.blit(self.figure.axes[0].bbox) self.figure.canvas.flush_events() #if touch is too far, hide cross hair cursor else: - self.set_cross_hair_visible(False) + self.set_cross_hair_visible(False) if self.hover_instance: self.hover_instance.x_hover_pos=self.x - self.hover_instance.y_hover_pos=self.y + self.hover_instance.y_hover_pos=self.y self.hover_instance.show_cursor=False self.x_hover_data = None - self.y_hover_data = None + self.y_hover_data = None if self.highlight_hover: self.myevent.x=event.x - self.pos[0] self.myevent.y=event.y - self.pos[1] - self.myevent.inaxes=self.figure.canvas.inaxes((event.x - self.pos[0], - event.y - self.pos[1])) + self.myevent.inaxes=self.figure.canvas.inaxes((event.x - self.pos[0], + event.y - self.pos[1])) axes = [a for a in self.figure.canvas.figure.get_axes() - if a.in_axes(self.myevent)] + if a.in_axes(self.myevent)] - if not axes: + if not axes: if self.last_line: - self.clear_line_prop() + self.clear_line_prop() if self.background: self.figure.canvas.restore_region(self.background) #draw (blit method) - self.figure.canvas.blit(self.figure.axes[0].bbox) + self.figure.canvas.blit(self.figure.axes[0].bbox) self.figure.canvas.flush_events() self.background = None - return + return def autoscale(self): if self.disabled: @@ -845,43 +845,43 @@ def autoscale(self): ax=self.figure.axes[0] ax.relim(visible_only=self.autoscale_visible_only) ax.autoscale_view(tight=self.autoscale_tight, - scalex=True if self.autoscale_axis!="y" else False, + scalex=True if self.autoscale_axis!="y" else False, scaley=True if self.autoscale_axis!="x" else False) ax.autoscale(axis=self.autoscale_axis,tight=self.autoscale_tight) - if self.twinx: + if self.twinx: ax2 = self.figure.axes[1] if self.autoscale_axis!="x": ax2.relim(visible_only=self.autoscale_visible_only) ax2.autoscale_view(tight=self.autoscale_tight, - scalex=False, + scalex=False, scaley=True) - ax2.autoscale(axis="y",tight=self.autoscale_tight) - - self.ymin2,self.ymax2 = ax2.get_ylim() + ax2.autoscale(axis="y",tight=self.autoscale_tight) + + self.ymin2,self.ymax2 = ax2.get_ylim() ax.figure.canvas.draw_idle() - ax.figure.canvas.flush_events() + ax.figure.canvas.flush_events() ax.set_autoscale_on(False) self.xmin,self.xmax = ax.get_xlim() - self.ymin,self.ymax = ax.get_ylim() - + self.ymin,self.ymax = ax.get_ylim() + def home(self) -> None: """ reset data axis - + Return: None """ ax = self.figure.axes[0] xleft,xright=ax.get_xlim() - ybottom,ytop=ax.get_ylim() - + ybottom,ytop=ax.get_ylim() + #check inverted data inverted_x = False if xleft>xright: inverted_x=True inverted_y = False if ybottom>ytop: - inverted_y=True + inverted_y=True if inverted_x: ax.set_xlim(right=self.xmin,left=self.xmax) @@ -892,24 +892,24 @@ def home(self) -> None: else: ax.set_ylim(bottom=self.ymin,top=self.ymax) - if self.twinx: + if self.twinx: ax2 = self.figure.axes[1] - ybottom2,ytop2=ax2.get_ylim() + ybottom2,ytop2=ax2.get_ylim() inverted_y2 = False if ybottom2>ytop2: - inverted_y2=True + inverted_y2=True if inverted_y2: ax2.set_ylim(top=self.ymin2,bottom=self.ymax2) else: - ax2.set_ylim(bottom=self.ymin2,top=self.ymax2) - + ax2.set_ylim(bottom=self.ymin2,top=self.ymax2) + self.update_cursor() if self.last_line is not None: - self.clear_line_prop() + self.clear_line_prop() ax.figure.canvas.draw_idle() - ax.figure.canvas.flush_events() + ax.figure.canvas.flush_events() def back(self, *args): """ @@ -942,7 +942,7 @@ def push_current(self): (ax.get_position(True).frozen(), ax.get_position().frozen())) for ax in self.figure.axes})) - self.set_history_buttons() + self.set_history_buttons() def update(self): """Reset the Axes stack.""" @@ -965,7 +965,7 @@ def _update_view(self): # Restore both the original and modified positions ax._set_position(pos_orig, 'original') ax._set_position(pos_active, 'active') - self.figure.canvas.draw_idle() + self.figure.canvas.draw_idle() self.figure.canvas.flush_events() def set_history_buttons(self): @@ -973,13 +973,13 @@ def set_history_buttons(self): def reset_touch(self) -> None: """ reset touch - + Return: None """ self._touches = [] self._last_touch_pos = {} - + def _get_scale(self): """ kivy scatter _get_scale method """ p1 = Vector(*self.to_parent(0, 0)) @@ -1021,9 +1021,9 @@ def _draw_bitmap(self): self._img_texture.blit_buffer( bytes(self._bitmap), colorfmt="rgba", bufferfmt='ubyte') self._img_texture.flip_vertical() - - self.update_hover() - self.update_selector() + + self.update_hover() + self.update_selector() def transform_with_touch(self, event): """ manage touch behaviour. based on kivy scatter method""" @@ -1033,7 +1033,7 @@ def transform_with_touch(self, event): if len(self._touches) == self.translation_touches: if self.touch_mode=='pan': if self._nav_stack() is None: - self.push_current() + self.push_current() if self.twinx: self.apply_pan_twinx(self.figure.axes[0], self.figure.axes[1], event) else: @@ -1043,37 +1043,37 @@ def transform_with_touch(self, event): if self.touch_mode=='pan_x' or self.touch_mode=='pan_y' \ or self.touch_mode=='adjust_x' or self.touch_mode=='adjust_y': if self._nav_stack() is None: - self.push_current() + self.push_current() if self.twinx: self.apply_pan_twinx(self.figure.axes[0], self.figure.axes[1], event, mode=self.touch_mode) else: self.apply_pan(self.figure.axes[0], event, mode=self.touch_mode) - + elif self.touch_mode=='drag_legend': if self.legend_instance: self.apply_drag_legend(self.figure.axes[0], event) - + elif self.touch_mode=='zoombox': if self._nav_stack() is None: - self.push_current() + self.push_current() real_x, real_y = event.x - self.pos[0], event.y - self.pos[1] #in case x_init is not create if not hasattr(self,'x_init'): self.x_init = event.x self.y_init = real_y self.draw_box(event, self.x_init,self.y_init, event.x, real_y) - + #mode cursor elif self.touch_mode=='cursor': self.hover_on=True self.hover(event) - + changed = True #note: avoid zoom in/out on touch mode zoombox if len(self._touches) == 1:# return changed - + # we have more than one touch... list of last known pos points = [Vector(self._last_touch_pos[t]) for t in self._touches if t is not event] @@ -1106,13 +1106,13 @@ def transform_with_touch(self, event): self.do_zoom_y=True elif angle>90-self.zoom_angle_detection and angle<90+self.zoom_angle_detection: self.do_zoom_x=True - self.do_zoom_y=False + self.do_zoom_y=False elif angle>180-self.zoom_angle_detection and angle<180+self.zoom_angle_detection: self.do_zoom_x=False - self.do_zoom_y=True + self.do_zoom_y=True elif angle>270-self.zoom_angle_detection and angle<270+self.zoom_angle_detection: self.do_zoom_x=True - self.do_zoom_y=False + self.do_zoom_y=False else: self.do_zoom_x=True self.do_zoom_y=True @@ -1125,7 +1125,7 @@ def transform_with_touch(self, event): scale = self.scale_min / self.scale elif new_scale > self.scale_max: scale = self.scale_max / self.scale - + if self.twinx: self.apply_zoom_twinx(scale, self.figure.axes[0], self.figure.axes[1], anchor=anchor,new_line=new_line) else: @@ -1139,13 +1139,13 @@ def on_motion(self,*args): `enter_notify_event`. ''' if self._pressed or self.disabled: # Do not process this event if there's a touch_move - return + return pos = args[1] newcoord = self.to_widget(pos[0], pos[1]) x = newcoord[0] y = newcoord[1] inside = self.collide_point(x,y) - if inside: + if inside: # will receive all motion events. if self.figcanvas and self.hover_instance: @@ -1159,12 +1159,12 @@ def on_motion(self,*args): def get_data_xy(self,x,y): """ manage x y data in navigation bar TODO""" return None,None - + def on_touch_down(self, event): """ Manage Mouse/touch press """ if self.disabled: return - + x, y = event.x, event.y if self.collide_point(x, y) and self.figure: @@ -1179,7 +1179,7 @@ def on_touch_down(self, event): break if select_legend: if self.touch_mode!='drag_legend': - return False + return False else: event.grab(self) self._touches.append(event) @@ -1187,41 +1187,41 @@ def on_touch_down(self, event): if len(self._touches)>1: #new touch, reset background self.background=None - - return True + + return True else: - self.current_legend = None - + self.current_legend = None + if event.is_mouse_scrolling: if not self.disable_mouse_scrolling: ax = self.figure.axes[0] if self.twinx: ax2 = self.figure.axes[1] self.zoom_factory_twin(event, ax, ax2, base_scale=1.2) - else: + else: self.zoom_factory(event, ax, base_scale=1.2) return True elif event.is_double_tap: if not self.disable_double_tap: - if self.touch_mode!='selector': + if self.touch_mode!='selector': self.home() return True - + else: if self.touch_mode=='cursor': self.hover_on=True - self.hover(event) + self.hover(event) elif self.touch_mode=='zoombox': real_x, real_y = x - self.pos[0], y - self.pos[1] self.x_init=x self.y_init=real_y - self.draw_box(event, x, real_y, x, real_y) + self.draw_box(event, x, real_y, x, real_y) elif self.touch_mode=='minmax': - self.min_max(event) + self.min_max(event) elif self.touch_mode=='selector': - pass + pass event.grab(self) self._touches.append(event) @@ -1229,7 +1229,7 @@ def on_touch_down(self, event): if len(self._touches)>1: #new touch, reset background self.background=None - + return True else: @@ -1239,13 +1239,13 @@ def on_touch_move(self, event): """ Manage Mouse/touch move while pressed """ if self.disabled: return - + x, y = event.x, event.y if event.is_double_tap: if not self.disable_double_tap: - if self.touch_mode!='selector': - self.home() + if self.touch_mode!='selector': + self.home() return True # scale/translate @@ -1261,7 +1261,7 @@ def on_touch_up(self, event): """ Manage Mouse/touch release """ if self.disabled: return - + # remove it from our saved touches if event in self._touches and event.grab_state: event.ungrab(self) @@ -1270,7 +1270,7 @@ def on_touch_up(self, event): if self.touch_mode=='pan' or self.touch_mode=='zoombox' or \ self.touch_mode=='pan_x' or self.touch_mode=='pan_y' \ or self.touch_mode=='adjust_x' or self.touch_mode=='adjust_y' \ - or self.touch_mode=='minmax': + or self.touch_mode=='minmax': self.push_current() if self.interactive_axis: if self.touch_mode=='pan_x' or self.touch_mode=='pan_y' \ @@ -1279,24 +1279,24 @@ def on_touch_up(self, event): self.first_touch_pan=None if self.last_line is not None: - self.clear_line_prop() - + self.clear_line_prop() + x, y = event.x, event.y if abs(self._box_size[0]) > 1 or abs(self._box_size[1]) > 1 or self.touch_mode=='zoombox': - self.reset_box() + self.reset_box() if not self.collide_point(x, y) and self.do_update: #update axis lim if zoombox is used and touch outside widget - self.update_lim() + self.update_lim() self.figure.canvas.draw_idle() - self.figure.canvas.flush_events() + self.figure.canvas.flush_events() return True - + # stop propagating if its within our bounds if self.collide_point(x, y) and self.figure: self._pressed = False if self.do_update: - self.update_lim() + self.update_lim() self.anchor_x=None self.anchor_y=None @@ -1304,40 +1304,40 @@ def on_touch_up(self, event): self.background=None self.show_compare_cursor=True self.figure.canvas.draw_idle() - self.figure.canvas.flush_events() + self.figure.canvas.flush_events() if self.last_line is None or self.touch_mode!='cursor': self.figure.canvas.draw_idle() - self.figure.canvas.flush_events() - + self.figure.canvas.flush_events() + return True def apply_zoom(self, scale_factor, ax, anchor=(0, 0),new_line=None): """ zoom touch method """ if self.touch_mode=='selector': return - + x = anchor[0]-self.pos[0] y = anchor[1]-self.pos[1] trans = ax.transData.inverted() - xdata, ydata = trans.transform_point((x+new_line.x, y+new_line.y)) - + xdata, ydata = trans.transform_point((x+new_line.x, y+new_line.y)) + cur_xlim = ax.get_xlim() - cur_ylim = ax.get_ylim() + cur_ylim = ax.get_ylim() scale=ax.get_xscale() yscale=ax.get_yscale() - + if scale == 'linear': old_min=cur_xlim[0] old_max=cur_xlim[1] else: min_=cur_xlim[0] - max_=cur_xlim[1] + max_=cur_xlim[1] old_min = self.transform_eval(min_,ax.yaxis) xdata = self.transform_eval(xdata,ax.yaxis) - old_max = self.transform_eval(max_,ax.yaxis) + old_max = self.transform_eval(max_,ax.yaxis) if yscale == 'linear': yold_min=cur_ylim[0] @@ -1345,7 +1345,7 @@ def apply_zoom(self, scale_factor, ax, anchor=(0, 0),new_line=None): else: ymin_=cur_ylim[0] - ymax_=cur_ylim[1] + ymax_=cur_ylim[1] yold_min = self.transform_eval(ymin_,ax.yaxis) ydata = self.transform_eval(ydata,ax.yaxis) yold_max = self.transform_eval(ymax_,ax.yaxis) @@ -1367,9 +1367,9 @@ def apply_zoom(self, scale_factor, ax, anchor=(0, 0),new_line=None): except OverflowError: # Limit case new_min, new_max = min_, max_ if new_min <= 0. or new_max <= 0.: # Limit case - new_min, new_max = min_, max_ + new_min, new_max = min_, max_ ax.set_xlim([new_min, new_max]) - + if self.do_zoom_y: if yscale == 'linear': ax.set_ylim([ydata - new_height * (1 - rely), ydata + new_height * (rely)]) @@ -1381,35 +1381,35 @@ def apply_zoom(self, scale_factor, ax, anchor=(0, 0),new_line=None): except OverflowError: # Limit case new_ymin, new_ymax = ymin_, ymax_ if new_ymin <= 0. or new_ymax <= 0.: # Limit case - new_ymin, new_ymax = ymin_, ymax_ + new_ymin, new_ymax = ymin_, ymax_ ax.set_ylim([new_ymin, new_ymax]) - if self.fast_draw: - #use blit method + if self.fast_draw: + #use blit method if self.background is None or self.last_line is not None: self.background_patch_copy.set_visible(True) ax.figure.canvas.draw_idle() - ax.figure.canvas.flush_events() + ax.figure.canvas.flush_events() self.background = ax.figure.canvas.copy_from_bbox(ax.figure.bbox) self.background_patch_copy.set_visible(False) if self.last_line is not None: self.clear_line_prop() ax.figure.canvas.restore_region(self.background) - + for line in ax.lines: ax.draw_artist(line) ax.figure.canvas.blit(ax.bbox) ax.figure.canvas.flush_events() - + self.update_hover() else: ax.figure.canvas.draw_idle() - ax.figure.canvas.flush_events() + ax.figure.canvas.flush_events() def apply_zoom_twinx(self, scale_factor, ax, ax2, anchor=(0, 0),new_line=None): """twin axis zoom method""" if self.touch_mode=='selector': - return + return x = anchor[0]-self.pos[0] y = anchor[1]-self.pos[1] @@ -1420,22 +1420,22 @@ def apply_zoom_twinx(self, scale_factor, ax, ax2, anchor=(0, 0),new_line=None): cur_xlim = ax.get_xlim() cur_ylim = ax.get_ylim() - cur_ylim2 = ax2.get_ylim() - + cur_ylim2 = ax2.get_ylim() + scale=ax.get_xscale() yscale=ax.get_yscale() yscale2=ax2.get_yscale() - + if scale == 'linear': old_min=cur_xlim[0] old_max=cur_xlim[1] else: min_=cur_xlim[0] - max_=cur_xlim[1] + max_=cur_xlim[1] old_min = self.transform_eval(min_,ax.yaxis) xdata = self.transform_eval(xdata,ax.yaxis) - old_max = self.transform_eval(max_,ax.yaxis) + old_max = self.transform_eval(max_,ax.yaxis) if yscale == 'linear': yold_min=cur_ylim[0] @@ -1443,7 +1443,7 @@ def apply_zoom_twinx(self, scale_factor, ax, ax2, anchor=(0, 0),new_line=None): else: ymin_=cur_ylim[0] - ymax_=cur_ylim[1] + ymax_=cur_ylim[1] yold_min = self.transform_eval(ymin_,ax.yaxis) ydata = self.transform_eval(ydata,ax.yaxis) yold_max = self.transform_eval(ymax_,ax.yaxis) @@ -1465,9 +1465,9 @@ def apply_zoom_twinx(self, scale_factor, ax, ax2, anchor=(0, 0),new_line=None): except OverflowError: # Limit case new_min, new_max = min_, max_ if new_min <= 0. or new_max <= 0.: # Limit case - new_min, new_max = min_, max_ + new_min, new_max = min_, max_ ax.set_xlim([new_min, new_max]) - + if self.do_zoom_y: if yscale == 'linear': ax.set_ylim([ydata - new_height * (1 - rely), ydata + new_height * (rely)]) @@ -1479,7 +1479,7 @@ def apply_zoom_twinx(self, scale_factor, ax, ax2, anchor=(0, 0),new_line=None): except OverflowError: # Limit case new_ymin, new_ymax = ymin_, ymax_ if new_ymin <= 0. or new_ymax <= 0.: # Limit case - new_ymin, new_ymax = ymin_, ymax_ + new_ymin, new_ymax = ymin_, ymax_ ax.set_ylim([new_ymin, new_ymax]) if yscale2 == 'linear': @@ -1488,13 +1488,13 @@ def apply_zoom_twinx(self, scale_factor, ax, ax2, anchor=(0, 0),new_line=None): else: ymin2_=cur_ylim2[0] - ymax2_=cur_ylim2[1] + ymax2_=cur_ylim2[1] yold2_min = self.transform_eval(ymin2_,ax2.yaxis) ydata2 = self.transform_eval(ydata2,ax2.yaxis) yold2_max = self.transform_eval(ymax2_,ax2.yaxis) - - new_height2 = (yold2_max - yold2_min) * scale_factor - + + new_height2 = (yold2_max - yold2_min) * scale_factor + rely2 = (yold2_max - ydata2) / (yold2_max - yold2_min) if self.do_zoom_y: ax2.set_ylim([ydata2 - new_height2 * (1 - rely2), ydata2 + new_height2 * (rely2)]) @@ -1510,64 +1510,64 @@ def apply_zoom_twinx(self, scale_factor, ax, ax2, anchor=(0, 0),new_line=None): except OverflowError: # Limit case new_ymin2, new_ymax2 = ymin2_, ymax2_ if new_ymin2 <= 0. or new_ymax2 <= 0.: # Limit case - new_ymin2, new_ymax2 = ymin2_, ymax2_ + new_ymin2, new_ymax2 = ymin2_, ymax2_ ax2.set_ylim([new_ymin2, new_ymax2]) - if self.fast_draw: + if self.fast_draw: #use blit method if self.background is None or self.last_line is not None: self.background_patch_copy.set_visible(True) self.background_ax2_patch_copy.set_visible(True) ax.figure.canvas.draw_idle() - ax.figure.canvas.flush_events() + ax.figure.canvas.flush_events() self.background = ax.figure.canvas.copy_from_bbox(ax.figure.bbox) - self.background_patch_copy.set_visible(False) + self.background_patch_copy.set_visible(False) self.background_ax2_patch_copy.set_visible(False) if self.last_line is not None: self.clear_line_prop() - ax.figure.canvas.restore_region(self.background) - + ax.figure.canvas.restore_region(self.background) + for line in ax.lines: if line.get_visible(): ax.draw_artist(line) for line in ax2.lines: if line.get_visible(): ax2.draw_artist(line) - + ax.figure.canvas.blit(ax.bbox) - ax.figure.canvas.flush_events() - - self.update_hover() - + ax.figure.canvas.flush_events() + + self.update_hover() + else: ax.figure.canvas.draw_idle() - ax.figure.canvas.flush_events() - + ax.figure.canvas.flush_events() + def apply_pan(self, ax, event, mode='pan'): """ pan method """ - + trans = ax.transData.inverted() xdata, ydata = trans.transform_point((event.x-self.pos[0], event.y-self.pos[1])) xpress, ypress = trans.transform_point((self._last_touch_pos[event][0]-self.pos[0], self._last_touch_pos[event][1]-self.pos[1])) - + scale=ax.get_xscale() yscale=ax.get_yscale() - + if scale == 'linear': dx = xdata - xpress else: dx = self.transform_eval(xdata,ax.xaxis) - \ self.transform_eval(xpress,ax.xaxis) - + if yscale == 'linear': dy = ydata - ypress else: dy = self.transform_eval(ydata,ax.yaxis) - \ - self.transform_eval(ypress,ax.yaxis) + self.transform_eval(ypress,ax.yaxis) xleft,xright=self.figure.axes[0].get_xlim() ybottom,ytop=self.figure.axes[0].get_ylim() - + #check inverted data inverted_x = False if xleft>xright: @@ -1577,11 +1577,11 @@ def apply_pan(self, ax, event, mode='pan'): cur_xlim=(xleft,xright) inverted_y = False if ybottom>ytop: - inverted_y=True + inverted_y=True cur_ylim=(ytop,ybottom) else: - cur_ylim=(ybottom,ytop) - + cur_ylim=(ybottom,ytop) + if self.interactive_axis and self.touch_mode=='pan' and not self.first_touch_pan=='pan': if (ydata < cur_ylim[0] and not inverted_y) or (ydata > cur_ylim[1] and inverted_y): left_anchor_zone= (cur_xlim[1] - cur_xlim[0])*.2 + cur_xlim[0] @@ -1593,16 +1593,16 @@ def apply_pan(self, ax, event, mode='pan'): self.touch_mode = mode elif (xdata < cur_xlim[0] and not inverted_x) or (xdata > cur_xlim[1] and inverted_x): bottom_anchor_zone= (cur_ylim[1] - cur_ylim[0])*.2 + cur_ylim[0] - top_anchor_zone= (cur_ylim[1] - cur_ylim[0])*.8 + cur_ylim[0] + top_anchor_zone= (cur_ylim[1] - cur_ylim[0])*.8 + cur_ylim[0] if ydata < bottom_anchor_zone or ydata > top_anchor_zone: mode = 'adjust_y' else: - mode= 'pan_y' + mode= 'pan_y' self.touch_mode = mode else: self.touch_mode = 'pan' - if not mode=='pan_y' and not mode=='adjust_y': + if not mode=='pan_y' and not mode=='adjust_y': if mode=='adjust_x': if self.anchor_x is None: midpoint= (cur_xlim[1] + cur_xlim[0])/2 @@ -1610,16 +1610,16 @@ def apply_pan(self, ax, event, mode='pan'): self.anchor_x='left' else: self.anchor_x='right' - if self.anchor_x=='left': + if self.anchor_x=='left': if xdata> cur_xlim[0]: if scale == 'linear': cur_xlim -= dx else: try: cur_xlim = [self.inv_transform_eval((self.transform_eval(cur_xlim[0],ax.xaxis) - dx),ax.xaxis), - self.inv_transform_eval((self.transform_eval(cur_xlim[1],ax.xaxis) - dx),ax.xaxis)] + self.inv_transform_eval((self.transform_eval(cur_xlim[1],ax.xaxis) - dx),ax.xaxis)] except (ValueError, OverflowError): - cur_xlim = cur_xlim # Keep previous limits + cur_xlim = cur_xlim # Keep previous limits if inverted_x: ax.set_xlim(cur_xlim[1],None) else: @@ -1631,9 +1631,9 @@ def apply_pan(self, ax, event, mode='pan'): else: try: cur_xlim = [self.inv_transform_eval((self.transform_eval(cur_xlim[0],ax.xaxis) - dx),ax.xaxis), - self.inv_transform_eval((self.transform_eval(cur_xlim[1],ax.xaxis) - dx),ax.xaxis)] + self.inv_transform_eval((self.transform_eval(cur_xlim[1],ax.xaxis) - dx),ax.xaxis)] except (ValueError, OverflowError): - cur_xlim = cur_xlim # Keep previous limits + cur_xlim = cur_xlim # Keep previous limits if inverted_x: ax.set_xlim(None,cur_xlim[0]) else: @@ -1644,14 +1644,14 @@ def apply_pan(self, ax, event, mode='pan'): else: try: cur_xlim = [self.inv_transform_eval((self.transform_eval(cur_xlim[0],ax.xaxis) - dx),ax.xaxis), - self.inv_transform_eval((self.transform_eval(cur_xlim[1],ax.xaxis) - dx),ax.xaxis)] + self.inv_transform_eval((self.transform_eval(cur_xlim[1],ax.xaxis) - dx),ax.xaxis)] except (ValueError, OverflowError): - cur_xlim = cur_xlim # Keep previous limits + cur_xlim = cur_xlim # Keep previous limits if inverted_x: ax.set_xlim(cur_xlim[1],cur_xlim[0]) else: ax.set_xlim(cur_xlim) - + if not mode=='pan_x' and not mode=='adjust_x': if mode=='adjust_y': if self.anchor_y is None: @@ -1659,20 +1659,20 @@ def apply_pan(self, ax, event, mode='pan'): if ydata>midpoint: self.anchor_y='top' else: - self.anchor_y='bottom' - + self.anchor_y='bottom' + if self.anchor_y=='top': if ydata> cur_ylim[0]: if yscale == 'linear': cur_ylim -= dy - + else: try: cur_ylim = [self.inv_transform_eval((self.transform_eval(cur_ylim[0],ax.yaxis) - dy),ax.yaxis), self.inv_transform_eval((self.transform_eval(cur_ylim[1],ax.yaxis) - dy),ax.yaxis)] except (ValueError, OverflowError): - cur_ylim = cur_ylim # Keep previous limits - + cur_ylim = cur_ylim # Keep previous limits + if inverted_y: ax.set_ylim(cur_ylim[1],None) else: @@ -1680,28 +1680,28 @@ def apply_pan(self, ax, event, mode='pan'): else: if ydata< cur_ylim[1]: if yscale == 'linear': - cur_ylim -= dy - + cur_ylim -= dy + else: try: cur_ylim = [self.inv_transform_eval((self.transform_eval(cur_ylim[0],ax.yaxis) - dy),ax.yaxis), self.inv_transform_eval((self.transform_eval(cur_ylim[1],ax.yaxis) - dy),ax.yaxis)] except (ValueError, OverflowError): - cur_ylim = cur_ylim # Keep previous limits + cur_ylim = cur_ylim # Keep previous limits if inverted_y: - ax.set_ylim(None,cur_ylim[0]) + ax.set_ylim(None,cur_ylim[0]) else: ax.set_ylim(cur_ylim[0],None) - else: + else: if yscale == 'linear': - cur_ylim -= dy - + cur_ylim -= dy + else: try: cur_ylim = [self.inv_transform_eval((self.transform_eval(cur_ylim[0],ax.yaxis) - dy),ax.yaxis), self.inv_transform_eval((self.transform_eval(cur_ylim[1],ax.yaxis) - dy),ax.yaxis)] except (ValueError, OverflowError): - cur_ylim = cur_ylim # Keep previous limits + cur_ylim = cur_ylim # Keep previous limits if inverted_y: ax.set_ylim(cur_ylim[1],cur_ylim[0]) else: @@ -1710,26 +1710,26 @@ def apply_pan(self, ax, event, mode='pan'): if self.first_touch_pan is None: self.first_touch_pan=self.touch_mode - if self.fast_draw: - #use blit method + if self.fast_draw: + #use blit method if self.background is None or self.last_line is not None: self.background_patch_copy.set_visible(True) ax.figure.canvas.draw_idle() - ax.figure.canvas.flush_events() + ax.figure.canvas.flush_events() self.background = ax.figure.canvas.copy_from_bbox(ax.figure.bbox) self.background_patch_copy.set_visible(False) if self.last_line is not None: self.clear_line_prop() - ax.figure.canvas.restore_region(self.background) - + ax.figure.canvas.restore_region(self.background) + for line in ax.lines: ax.draw_artist(line) - + ax.figure.canvas.blit(ax.bbox) - ax.figure.canvas.flush_events() - - self.update_hover() - + ax.figure.canvas.flush_events() + + self.update_hover() + else: ax.figure.canvas.draw_idle() ax.figure.canvas.flush_events() @@ -1743,25 +1743,25 @@ def apply_pan_twinx(self, ax, ax2, event, mode='pan'): scale=ax.get_xscale() yscale=ax.get_yscale() yscale2=ax2.get_yscale() - + update_cursor=False - + if scale == 'linear': dx = xdata - xpress else: dx = self.transform_eval(xdata,ax.xaxis) - \ self.transform_eval(xpress,ax.xaxis) - + if yscale == 'linear': dy = ydata - ypress else: dy = self.transform_eval(ydata,ax.yaxis) - \ self.transform_eval(ypress,ax.yaxis) - + xleft,xright=ax.get_xlim() ybottom,ytop=ax.get_ylim() ybottom2,ytop2=ax2.get_ylim() - + #check inverted data inverted_x = False if xleft>xright: @@ -1771,27 +1771,27 @@ def apply_pan_twinx(self, ax, ax2, event, mode='pan'): cur_xlim=(xleft,xright) inverted_y = False if ybottom>ytop: - inverted_y=True + inverted_y=True cur_ylim=(ytop,ybottom) else: - cur_ylim=(ybottom,ytop) + cur_ylim=(ybottom,ytop) inverted_y2 = False if ybottom2>ytop2: - inverted_y2=True + inverted_y2=True cur_ylim2=(ytop2,ybottom2) else: - cur_ylim2=(ybottom2,ytop2) - + cur_ylim2=(ybottom2,ytop2) + ratio = (cur_ylim2[1] - cur_ylim2[0]) / (cur_ylim[1] - cur_ylim[0]) ydata2 = ydata * ratio + cur_ylim2[0] ypress2 = ypress * ratio + cur_ylim2[0] - + if yscale2 == 'linear': - dy2 = ydata2 - ypress2 + dy2 = ydata2 - ypress2 else: dy2 = self.transform_eval(ydata2,ax2.yaxis) - \ - self.transform_eval(ypress2,ax2.yaxis) - + self.transform_eval(ypress2,ax2.yaxis) + if self.interactive_axis and self.touch_mode=='pan' and not self.first_touch_pan=='pan': if (ydata < cur_ylim[0] and not inverted_y) or (ydata > cur_ylim[1] and inverted_y): left_anchor_zone= (cur_xlim[1] - cur_xlim[0])*.2 + cur_xlim[0] @@ -1804,17 +1804,17 @@ def apply_pan_twinx(self, ax, ax2, event, mode='pan'): elif (xdata < cur_xlim[0] and not inverted_y) or (xdata > cur_xlim[1] and inverted_y) \ or (xdata > cur_xlim[1] and not inverted_y) or (xdata < cur_xlim[0] and inverted_y): bottom_anchor_zone= (cur_ylim[1] - cur_ylim[0])*.2 + cur_ylim[0] - top_anchor_zone= (cur_ylim[1] - cur_ylim[0])*.8 + cur_ylim[0] + top_anchor_zone= (cur_ylim[1] - cur_ylim[0])*.8 + cur_ylim[0] if ydata < bottom_anchor_zone or ydata > top_anchor_zone: mode = 'adjust_y' else: - mode= 'pan_y' + mode= 'pan_y' self.touch_mode = mode - + else: - self.touch_mode = 'pan' + self.touch_mode = 'pan' - if not mode=='pan_y' and not mode=='adjust_y': + if not mode=='pan_y' and not mode=='adjust_y': if mode=='adjust_x': if self.anchor_x is None: midpoint= (cur_xlim[1] + cur_xlim[0])/2 @@ -1822,21 +1822,21 @@ def apply_pan_twinx(self, ax, ax2, event, mode='pan'): self.anchor_x='left' else: self.anchor_x='right' - if self.anchor_x=='left': + if self.anchor_x=='left': if xdata> cur_xlim[0]: if scale == 'linear': cur_xlim -= dx else: try: cur_xlim = [self.inv_transform_eval((self.transform_eval(cur_xlim[0],ax.xaxis) - dx),ax.xaxis), - self.inv_transform_eval((self.transform_eval(cur_xlim[1],ax.xaxis) - dx),ax.xaxis)] + self.inv_transform_eval((self.transform_eval(cur_xlim[1],ax.xaxis) - dx),ax.xaxis)] except (ValueError, OverflowError): - cur_xlim = cur_xlim # Keep previous limits + cur_xlim = cur_xlim # Keep previous limits if inverted_x: ax.set_xlim(cur_xlim[1],None) else: ax.set_xlim(None,cur_xlim[1]) - + else: if xdata< cur_xlim[1]: if scale == 'linear': @@ -1844,32 +1844,32 @@ def apply_pan_twinx(self, ax, ax2, event, mode='pan'): else: try: cur_xlim = [self.inv_transform_eval((self.transform_eval(cur_xlim[0],ax.xaxis) - dx),ax.xaxis), - self.inv_transform_eval((self.transform_eval(cur_xlim[1],ax.xaxis) - dx),ax.xaxis)] + self.inv_transform_eval((self.transform_eval(cur_xlim[1],ax.xaxis) - dx),ax.xaxis)] except (ValueError, OverflowError): cur_xlim = cur_xlim # Keep previous limits if inverted_x: ax.set_xlim(None,cur_xlim[0]) else: ax.set_xlim(cur_xlim[0],None) - + else: if scale == 'linear': cur_xlim -= dx else: try: cur_xlim = [self.inv_transform_eval((self.transform_eval(cur_xlim[0],ax.xaxis) - dx),ax.xaxis), - self.inv_transform_eval((self.transform_eval(cur_xlim[1],ax.xaxis) - dx),ax.xaxis)] + self.inv_transform_eval((self.transform_eval(cur_xlim[1],ax.xaxis) - dx),ax.xaxis)] except (ValueError, OverflowError): cur_xlim = cur_xlim # Keep previous limits if inverted_x: ax.set_xlim(cur_xlim[1],cur_xlim[0]) - else: + else: ax.set_xlim(cur_xlim) if not mode=='pan_x' and not mode=='adjust_x': if mode=='adjust_y': trans_ax2 = ax2.transData.inverted() - xdata_ax2, ydata_ax2 = trans_ax2.transform_point((event.x-self.pos[0], event.y-self.pos[1])) + xdata_ax2, ydata_ax2 = trans_ax2.transform_point((event.x-self.pos[0], event.y-self.pos[1])) if self.anchor_y is None: midpoint_x = (cur_xlim[1] + cur_xlim[0])/2 midpoint_ax1= (cur_ylim[1] + cur_ylim[0])/2 @@ -1878,105 +1878,105 @@ def apply_pan_twinx(self, ax, ax2, event, mode='pan'): ax_anchor='right' else: ax_anchor='left' - + if ax_anchor=='left': if ydata>midpoint_ax1: self.anchor_y='top_left' else: - self.anchor_y='bottom_left' - else: - + self.anchor_y='bottom_left' + else: + if ydata_ax2>midpoint_ax2: self.anchor_y='top_right' else: - self.anchor_y='bottom_right' + self.anchor_y='bottom_right' # print(self.anchor_y) if self.anchor_y=='top_left': if ydata > cur_ylim[0]: if yscale == 'linear': cur_ylim -= dy - + else: try: cur_ylim = [self.inv_transform_eval((self.transform_eval(cur_ylim[0],ax.yaxis) - dy),ax.yaxis), self.inv_transform_eval((self.transform_eval(cur_ylim[1],ax.yaxis) - dy),ax.yaxis)] except (ValueError, OverflowError): - cur_ylim = cur_ylim # Keep previous limits + cur_ylim = cur_ylim # Keep previous limits if inverted_y: ax.set_ylim(cur_ylim[1],None) else: ax.set_ylim(None,cur_ylim[1]) - + update_cursor=True - + elif self.anchor_y=='top_right': if ydata_ax2 > cur_ylim2[0]: if yscale2 == 'linear': - cur_ylim2 -= dy2 - + cur_ylim2 -= dy2 + else: try: cur_ylim2 = [self.inv_transform_eval((self.transform_eval(cur_ylim2[0],ax2.yaxis) - dy2),ax2.yaxis), self.inv_transform_eval((self.transform_eval(cur_ylim2[1],ax2.yaxis) - dy2),ax2.yaxis)] except (ValueError, OverflowError): - cur_ylim2 = cur_ylim2 # Keep previous limits - + cur_ylim2 = cur_ylim2 # Keep previous limits + if inverted_y2: ax2.set_ylim(cur_ylim2[1],None) else: - ax2.set_ylim(None,cur_ylim2[1]) - + ax2.set_ylim(None,cur_ylim2[1]) + update_cursor=True - + elif self.anchor_y=='bottom_left': if ydata < cur_ylim[1]: if yscale == 'linear': cur_ylim -= dy - + else: try: cur_ylim = [self.inv_transform_eval((self.transform_eval(cur_ylim[0],ax.yaxis) - dy),ax.yaxis), self.inv_transform_eval((self.transform_eval(cur_ylim[1],ax.yaxis) - dy),ax.yaxis)] except (ValueError, OverflowError): - cur_ylim = cur_ylim # Keep previous limits + cur_ylim = cur_ylim # Keep previous limits if inverted_y: - ax.set_ylim(None,cur_ylim[0]) + ax.set_ylim(None,cur_ylim[0]) else: ax.set_ylim(cur_ylim[0],None) - + update_cursor=True else: if ydata_ax2 < cur_ylim2[1]: if yscale2 == 'linear': - cur_ylim2 -= dy2 - + cur_ylim2 -= dy2 + else: try: cur_ylim2 = [self.inv_transform_eval((self.transform_eval(cur_ylim2[0],ax2.yaxis) - dy2),ax2.yaxis), self.inv_transform_eval((self.transform_eval(cur_ylim2[1],ax2.yaxis) - dy2),ax2.yaxis)] except (ValueError, OverflowError): - cur_ylim2 = cur_ylim2 # Keep previous limits - # ax2.set_ylim(cur_ylim2[0],None) + cur_ylim2 = cur_ylim2 # Keep previous limits + # ax2.set_ylim(cur_ylim2[0],None) if inverted_y2: ax2.set_ylim(None,cur_ylim2[0]) else: - ax2.set_ylim(cur_ylim2[0],None) - + ax2.set_ylim(cur_ylim2[0],None) + update_cursor=True - else: + else: if yscale == 'linear': - cur_ylim -= dy - + cur_ylim -= dy + else: try: cur_ylim = [self.inv_transform_eval((self.transform_eval(cur_ylim[0],ax.yaxis) - dy),ax.yaxis), self.inv_transform_eval((self.transform_eval(cur_ylim[1],ax.yaxis) - dy),ax.yaxis)] except (ValueError, OverflowError): cur_ylim = cur_ylim # Keep previous limits - + if yscale2 == 'linear': - cur_ylim2 -= dy2 - + cur_ylim2 -= dy2 + else: try: cur_ylim2 = [self.inv_transform_eval((self.transform_eval(cur_ylim2[0],ax2.yaxis) - dy2),ax2.yaxis), @@ -1993,42 +1993,42 @@ def apply_pan_twinx(self, ax, ax2, event, mode='pan'): ax2.set_ylim(cur_ylim2[1],cur_ylim2[0]) else: ax2.set_ylim(cur_ylim2) - + if self.first_touch_pan is None: self.first_touch_pan=self.touch_mode - + if update_cursor: self.update_cursor() - if self.fast_draw: - #use blit method + if self.fast_draw: + #use blit method if self.background is None or self.last_line is not None: self.background_patch_copy.set_visible(True) self.background_ax2_patch_copy.set_visible(True) ax.figure.canvas.draw_idle() - ax.figure.canvas.flush_events() + ax.figure.canvas.flush_events() self.background = ax.figure.canvas.copy_from_bbox(ax.figure.bbox) - self.background_patch_copy.set_visible(False) + self.background_patch_copy.set_visible(False) self.background_ax2_patch_copy.set_visible(False) if self.last_line is not None: - self.clear_line_prop() - ax.figure.canvas.restore_region(self.background) - + self.clear_line_prop() + ax.figure.canvas.restore_region(self.background) + for line in ax.lines: if line.get_visible(): ax.draw_artist(line) for line in ax2.lines: if line.get_visible(): ax2.draw_artist(line) - + ax.figure.canvas.blit(ax.bbox) - ax.figure.canvas.flush_events() - - self.update_hover() - + ax.figure.canvas.flush_events() + + self.update_hover() + else: ax.figure.canvas.draw_idle() - ax.figure.canvas.flush_events() + ax.figure.canvas.flush_events() def update_hover(self): """ update hover on fast draw (if exist)""" @@ -2036,39 +2036,39 @@ def update_hover(self): if self.compare_xdata and self.hover_instance: if (self.touch_mode!='cursor' or len(self._touches) > 1) and not self.show_compare_cursor: self.hover_instance.hover_outside_bound=True - + elif self.show_compare_cursor and self.touch_mode=='cursor': self.show_compare_cursor=False else: self.hover_instance.hover_outside_bound=True #update hover pos if needed - elif self.hover_instance.show_cursor and self.x_hover_data is not None and self.y_hover_data is not None: + elif self.hover_instance.show_cursor and self.x_hover_data is not None and self.y_hover_data is not None: if self.cursor_last_axis: xy_pos = self.cursor_last_axis.transData.transform([(self.x_hover_data,self.y_hover_data)]) else: - xy_pos = self.figure.axes[0].transData.transform([(self.x_hover_data,self.y_hover_data)]) + xy_pos = self.figure.axes[0].transData.transform([(self.x_hover_data,self.y_hover_data)]) self.hover_instance.x_hover_pos=float(xy_pos[0][0]) + self.x self.hover_instance.y_hover_pos=float(xy_pos[0][1]) + self.y self.hover_instance.xmin_line = float(self.figure.axes[0].bbox.bounds[0]) + self.x - self.hover_instance.xmax_line = float(self.figure.axes[0].bbox.bounds[0] + self.figure.axes[0].bbox.bounds[2]) + self.x + self.hover_instance.xmax_line = float(self.figure.axes[0].bbox.bounds[0] + self.figure.axes[0].bbox.bounds[2]) + self.x self.hover_instance.ymin_line = float(self.figure.axes[0].bbox.bounds[1]) + self.y self.hover_instance.ymax_line = float(self.figure.axes[0].bbox.bounds[1] + self.figure.axes[0].bbox.bounds[3] )+ self.y - + if self.hover_instance.x_hover_pos>self.x+self.figure.axes[0].bbox.bounds[2] + self.figure.axes[0].bbox.bounds[0] or \ self.hover_instance.x_hover_posself.y+self.figure.axes[0].bbox.bounds[1] + self.figure.axes[0].bbox.bounds[3] or \ - self.hover_instance.y_hover_pos 1 and resize_wgt.size[1] > 1): return - + #rectangle or spann selector if hasattr(resize_wgt,'span_orientation'): #span selector if resize_wgt.span_orientation == 'vertical': - xy_pos = resize_wgt.ax.transData.transform([(resize_wgt.verts[0][0],resize_wgt.verts[0][1])]) + xy_pos = resize_wgt.ax.transData.transform([(resize_wgt.verts[0][0],resize_wgt.verts[0][1])]) new_pos=resize_wgt.to_widget(*(float(xy_pos[0][0]),float(xy_pos[0][1]))) resize_wgt.pos[0] = new_pos[0] + self.x - + top_bound = float(self.y +resize_wgt.ax.bbox.bounds[3] + resize_wgt.ax.bbox.bounds[1]) bottom_bound = float(self.y +resize_wgt.ax.bbox.bounds[1]) resize_wgt.pos[1] = bottom_bound - self.y - + #recalcul size - xy_pos2 = resize_wgt.ax.transData.transform([(resize_wgt.verts[3][0],resize_wgt.verts[3][1])]) + xy_pos2 = resize_wgt.ax.transData.transform([(resize_wgt.verts[3][0],resize_wgt.verts[3][1])]) resize_wgt.size[0] = float(xy_pos2[0][0] - xy_pos[0][0]) resize_wgt.size[1] = top_bound-bottom_bound - + elif resize_wgt.span_orientation == 'horizontal': - xy_pos = resize_wgt.ax.transData.transform([(resize_wgt.verts[0][0],resize_wgt.verts[0][1])]) + xy_pos = resize_wgt.ax.transData.transform([(resize_wgt.verts[0][0],resize_wgt.verts[0][1])]) new_pos=resize_wgt.to_widget(*(float(xy_pos[0][0]),float(xy_pos[0][1]))) left_bound = float(self.x +resize_wgt.ax.bbox.bounds[0]) right_bound = float(self.x +resize_wgt.ax.bbox.bounds[2] +resize_wgt.ax.bbox.bounds[0] ) - + width = right_bound-left_bound - + left_bound,right_bound = resize_wgt.to_widget(left_bound,right_bound) - + resize_wgt.pos[0] = left_bound resize_wgt.pos[1] = new_pos[1] + self.y #recalcul size - xy_pos2 = resize_wgt.ax.transData.transform([(resize_wgt.verts[0][1],resize_wgt.verts[1][1])]) + xy_pos2 = resize_wgt.ax.transData.transform([(resize_wgt.verts[0][1],resize_wgt.verts[1][1])]) resize_wgt.size[0] = width - resize_wgt.size[1] = float(xy_pos2[0][1] - xy_pos[0][1]) - + resize_wgt.size[1] = float(xy_pos2[0][1] - xy_pos[0][1]) + else: #rectangle selector - + #update all selector pts #recalcul pos - xy_pos = resize_wgt.ax.transData.transform([(resize_wgt.verts[0][0],resize_wgt.verts[0][1])]) + xy_pos = resize_wgt.ax.transData.transform([(resize_wgt.verts[0][0],resize_wgt.verts[0][1])]) new_pos=resize_wgt.to_widget(*(float(xy_pos[0][0]),float(xy_pos[0][1]))) resize_wgt.pos[0] = new_pos[0] + self.x resize_wgt.pos[1] = new_pos[1] + self.y - + #recalcul size - xy_pos2 = resize_wgt.ax.transData.transform([(resize_wgt.verts[2][0],resize_wgt.verts[2][1])]) + xy_pos2 = resize_wgt.ax.transData.transform([(resize_wgt.verts[2][0],resize_wgt.verts[2][1])]) resize_wgt.size[0] = float(xy_pos2[0][0] - xy_pos[0][0]) resize_wgt.size[1] = float(xy_pos2[0][1] - xy_pos[0][1]) - + if self.collide_point(*resize_wgt.to_window(resize_wgt.pos[0],resize_wgt.pos[1])) and \ self.collide_point(*resize_wgt.to_window(resize_wgt.pos[0] + resize_wgt.size[0],resize_wgt.pos[1]+ resize_wgt.size[1])): resize_wgt.opacity = 1 else: - resize_wgt.opacity = 0 + resize_wgt.opacity = 0 def min_max(self, event): """ manage min/max touch mode """ @@ -2226,14 +2226,14 @@ def min_max(self, event): if xlabelbottom and event.x>self.x +ax.bbox.bounds[0] and \ event.x right_anchor_zone: + if event.x < left_anchor_zone or event.x > right_anchor_zone: if self.text_instance: if not self.text_instance.show_text: @@ -2260,9 +2260,9 @@ def min_max(self, event): event.y>self.y + ax.bbox.bounds[1]: top_lim = self.y+ax.bbox.bounds[3]+ax.bbox.bounds[1] - bottom_lim = self.y+ax.bbox.bounds[1] + bottom_lim = self.y+ax.bbox.bounds[1] bottom_anchor_zone= (top_lim - bottom_lim)*.2 + bottom_lim - top_anchor_zone= (top_lim - bottom_lim)*.8 + bottom_lim + top_anchor_zone= (top_lim - bottom_lim)*.8 + bottom_lim if event.y < bottom_anchor_zone or event.y > top_anchor_zone: if self.text_instance: @@ -2283,15 +2283,15 @@ def min_max(self, event): self.text_instance.show_text=True return - + elif ylabelright and event.x>self.x + ax.bbox.bounds[2] + ax.bbox.bounds[0] and \ event.yself.y + ax.bbox.bounds[1]: + event.y>self.y + ax.bbox.bounds[1]: top_lim = self.y+ax.bbox.bounds[3]+ax.bbox.bounds[1] - bottom_lim = self.y+ax.bbox.bounds[1] + bottom_lim = self.y+ax.bbox.bounds[1] bottom_anchor_zone= (top_lim - bottom_lim)*.2 + bottom_lim - top_anchor_zone= (top_lim - bottom_lim)*.8 + bottom_lim + top_anchor_zone= (top_lim - bottom_lim)*.8 + bottom_lim if event.y < bottom_anchor_zone or event.y > top_anchor_zone: if self.text_instance: @@ -2303,26 +2303,26 @@ def min_max(self, event): self.text_instance.y_text_pos=float(self.y+ax.bbox.bounds[1] + ax.bbox.bounds[3]) - self.text_instance.text_height self.text_instance.offset_text = True else: - + anchor='bottom' self.text_instance.x_text_pos=float(self.x+ax.bbox.bounds[0] + ax.bbox.bounds[2]) + dp(40) self.text_instance.y_text_pos=float(self.y+ax.bbox.bounds[1]) - dp(6) self.text_instance.offset_text = True self.text_instance.current_axis = self.figure.axes[1] #left axis self.text_instance.kind = {'axis':'y','anchor':anchor} - + self.text_instance.show_text=True - return + return def apply_drag_legend(self, ax, event): """ drag legend method """ - + dx = event.x - self._last_touch_pos[event][0] if not self.legend_do_scroll_x: dx=0 - dy = event.y - self._last_touch_pos[event][1] + dy = event.y - self._last_touch_pos[event][1] if not self.legend_do_scroll_y: - dy=0 + dy=0 legend=None if self.current_legend: if self.current_legend.legend_instance: @@ -2330,33 +2330,33 @@ def apply_drag_legend(self, ax, event): else: legend = ax.get_legend() if legend is not None: - + bbox = legend.get_window_extent() legend_x = bbox.xmin legend_y = bbox.ymin - + loc_in_canvas = legend_x +dx, legend_y+dy loc_in_norm_axes = legend.parent.transAxes.inverted().transform_point(loc_in_canvas) legend._loc = tuple(loc_in_norm_axes) - - #use blit method + + #use blit method if self.background is None or self.last_line is not None: legend.set_visible(False) ax.figure.canvas.draw_idle() - ax.figure.canvas.flush_events() + ax.figure.canvas.flush_events() self.background = ax.figure.canvas.copy_from_bbox(ax.figure.bbox) legend.set_visible(True) if self.last_line is not None: self.clear_line_prop() - ax.figure.canvas.restore_region(self.background) - + ax.figure.canvas.restore_region(self.background) + ax.draw_artist(legend) - + ax.figure.canvas.blit(ax.bbox) - ax.figure.canvas.flush_events() + ax.figure.canvas.flush_events() self.current_legend.update_size() - + def zoom_factory(self, event, ax, base_scale=1.1): """ zoom with scrolling mouse method """ @@ -2365,24 +2365,24 @@ def zoom_factory(self, event, ax, base_scale=1.1): y = newcoord[1] trans = ax.transData.inverted() - xdata, ydata = trans.transform_point((x, y)) + xdata, ydata = trans.transform_point((x, y)) cur_xlim = ax.get_xlim() cur_ylim = ax.get_ylim() scale=ax.get_xscale() yscale=ax.get_yscale() - + if scale == 'linear': old_min=cur_xlim[0] old_max=cur_xlim[1] else: min_=cur_xlim[0] - max_=cur_xlim[1] + max_=cur_xlim[1] old_min = self.transform_eval(min_,ax.yaxis) xdata = self.transform_eval(xdata,ax.yaxis) - old_max = self.transform_eval(max_,ax.yaxis) + old_max = self.transform_eval(max_,ax.yaxis) if yscale == 'linear': yold_min=cur_ylim[0] @@ -2390,7 +2390,7 @@ def zoom_factory(self, event, ax, base_scale=1.1): else: ymin_=cur_ylim[0] - ymax_=cur_ylim[1] + ymax_=cur_ylim[1] yold_min = self.transform_eval(ymin_,ax.yaxis) ydata = self.transform_eval(ydata,ax.yaxis) yold_max = self.transform_eval(ymax_,ax.yaxis) @@ -2423,9 +2423,9 @@ def zoom_factory(self, event, ax, base_scale=1.1): except OverflowError: # Limit case new_min, new_max = min_, max_ if new_min <= 0. or new_max <= 0.: # Limit case - new_min, new_max = min_, max_ + new_min, new_max = min_, max_ ax.set_xlim([new_min, new_max]) - + if self.do_zoom_y: if yscale == 'linear': ax.set_ylim([ydata - new_height * (1 - rely), ydata + new_height * (rely)]) @@ -2437,14 +2437,14 @@ def zoom_factory(self, event, ax, base_scale=1.1): except OverflowError: # Limit case new_ymin, new_ymax = ymin_, ymax_ if new_ymin <= 0. or new_ymax <= 0.: # Limit case - new_ymin, new_ymax = ymin_, ymax_ + new_ymin, new_ymax = ymin_, ymax_ ax.set_ylim([new_ymin, new_ymax]) if self.last_line is not None: - self.clear_line_prop() - + self.clear_line_prop() + ax.figure.canvas.draw_idle() - ax.figure.canvas.flush_events() + ax.figure.canvas.flush_events() def zoom_factory_twin(self, event, ax, ax2, base_scale=2.): """twin axis zoom method from scroll mouse""" @@ -2457,7 +2457,7 @@ def zoom_factory_twin(self, event, ax, ax2, base_scale=2.): trans = ax.transData.inverted() xdata, ydata = trans.transform_point((x, y)) trans2 = ax2.transData.inverted() - xdata2, ydata2 = trans2.transform_point((x, y)) + xdata2, ydata2 = trans2.transform_point((x, y)) scale=ax.get_xscale() yscale=ax.get_yscale() @@ -2469,10 +2469,10 @@ def zoom_factory_twin(self, event, ax, ax2, base_scale=2.): else: min_=cur_xlim[0] - max_=cur_xlim[1] + max_=cur_xlim[1] old_min = self.transform_eval(min_,ax.yaxis) xdata = self.transform_eval(xdata,ax.yaxis) - old_max = self.transform_eval(max_,ax.yaxis) + old_max = self.transform_eval(max_,ax.yaxis) if yscale == 'linear': yold_min=cur_ylim[0] @@ -2480,7 +2480,7 @@ def zoom_factory_twin(self, event, ax, ax2, base_scale=2.): else: ymin_=cur_ylim[0] - ymax_=cur_ylim[1] + ymax_=cur_ylim[1] yold_min = self.transform_eval(ymin_,ax.yaxis) ydata = self.transform_eval(ydata,ax.yaxis) yold_max = self.transform_eval(ymax_,ax.yaxis) @@ -2513,9 +2513,9 @@ def zoom_factory_twin(self, event, ax, ax2, base_scale=2.): except OverflowError: # Limit case new_min, new_max = min_, max_ if new_min <= 0. or new_max <= 0.: # Limit case - new_min, new_max = min_, max_ + new_min, new_max = min_, max_ ax.set_xlim([new_min, new_max]) - + if self.do_zoom_y: if yscale == 'linear': ax.set_ylim([ydata - new_height * (1 - rely), ydata + new_height * (rely)]) @@ -2527,7 +2527,7 @@ def zoom_factory_twin(self, event, ax, ax2, base_scale=2.): except OverflowError: # Limit case new_ymin, new_ymax = ymin_, ymax_ if new_ymin <= 0. or new_ymax <= 0.: # Limit case - new_ymin, new_ymax = ymin_, ymax_ + new_ymin, new_ymax = ymin_, ymax_ ax.set_ylim([new_ymin, new_ymax]) if yscale2 == 'linear': @@ -2536,13 +2536,13 @@ def zoom_factory_twin(self, event, ax, ax2, base_scale=2.): else: ymin2_=cur_ylim2[0] - ymax2_=cur_ylim2[1] + ymax2_=cur_ylim2[1] yold2_min = self.transform_eval(ymin2_,ax2.yaxis) ydata2 = self.transform_eval(ydata2,ax2.yaxis) yold2_max = self.transform_eval(ymax2_,ax2.yaxis) - - new_height2 = (yold2_max - yold2_min) * scale_factor - + + new_height2 = (yold2_max - yold2_min) * scale_factor + rely2 = (yold2_max - ydata2) / (yold2_max - yold2_min) if self.do_zoom_y: ax2.set_ylim([ydata2 - new_height2 * (1 - rely2), ydata2 + new_height2 * (rely2)]) @@ -2558,15 +2558,15 @@ def zoom_factory_twin(self, event, ax, ax2, base_scale=2.): except OverflowError: # Limit case new_ymin2, new_ymax2 = ymin2_, ymax2_ if new_ymin2 <= 0. or new_ymax2 <= 0.: # Limit case - new_ymin2, new_ymax2 = ymin2_, ymax2_ + new_ymin2, new_ymax2 = ymin2_, ymax2_ ax2.set_ylim([new_ymin2, new_ymax2]) if self.last_line is not None: - self.clear_line_prop() + self.clear_line_prop() ax.figure.canvas.draw_idle() - ax.figure.canvas.flush_events() - + ax.figure.canvas.flush_events() + def _onSize(self, o, size): """ _onsize method """ if self.figure is None: @@ -2586,20 +2586,20 @@ def _onSize(self, o, size): s = 'resize_event' event = ResizeEvent(s, self.figcanvas) self.figcanvas.callbacks.process(s, event) - self.figcanvas.draw_idle() + self.figcanvas.draw_idle() - self.figcanvas.draw() + self.figcanvas.draw() if self.legend_instance: for current_legend in self.legend_instance: current_legend.update_size() if self.hover_instance: - self.hover_instance.figwidth = self.width + self.hover_instance.figwidth = self.width self.hover_instance.figheight = self.height self.hover_instance.figx = self.x - self.hover_instance.figy = self.y + self.hover_instance.figy = self.y if self.selector and self.selector.resize_wgt.verts: #update selector next frame to have correct position - Clock.schedule_once(self.update_selector) + Clock.schedule_once(self.update_selector) def update_lim(self): """ update axis lim if zoombox is used""" @@ -2609,9 +2609,9 @@ def update_lim(self): #check if inverted axis xleft,xright=ax.get_xlim() - ybottom,ytop=ax.get_ylim() - ybottom2,ytop2=ax2.get_ylim() - + ybottom,ytop=ax.get_ylim() + ybottom2,ytop2=ax2.get_ylim() + #check inverted data inverted_x = False if xleft>xright: @@ -2621,23 +2621,23 @@ def update_lim(self): cur_xlim=(xleft,xright) inverted_y = False if ybottom>ytop: - inverted_y=True + inverted_y=True cur_ylim=(ytop,ybottom) else: - cur_ylim=(ybottom,ytop) + cur_ylim=(ybottom,ytop) inverted_y2 = False if ybottom2>ytop2: - inverted_y2=True + inverted_y2=True cur_ylim2=(ytop2,ybottom2) else: - cur_ylim2=(ybottom2,ytop2) - + cur_ylim2=(ybottom2,ytop2) + range_old = cur_ylim[1] - cur_ylim[0] range_old2 = cur_ylim2[1] - cur_ylim2[0] - + ymin2 = (min(self.y0_box,self.y1_box)-cur_ylim[0])/range_old*range_old2+cur_ylim2[0] - ymax2 = (max(self.y0_box,self.y1_box)-cur_ylim[0])/range_old*range_old2+cur_ylim2[0] - + ymax2 = (max(self.y0_box,self.y1_box)-cur_ylim[0])/range_old*range_old2+cur_ylim2[0] + if inverted_x: ax.set_xlim(right=min(self.x0_box,self.x1_box),left=max(self.x0_box,self.x1_box)) else: @@ -2650,15 +2650,15 @@ def update_lim(self): ax2.set_ylim(top=ymin2,bottom=ymax2) else: ax2.set_ylim(bottom=ymin2,top=ymax2) - + def reset_box(self): """ reset zoombox and apply zoombox limit if zoombox option if selected""" if min(abs(self._box_size[0]),abs(self._box_size[1]))>self.minzoom: trans = self.figure.axes[0].transData.inverted() - self.x0_box, self.y0_box = trans.transform_point((self._box_pos[0]-self.pos[0], self._box_pos[1]-self.pos[1])) + self.x0_box, self.y0_box = trans.transform_point((self._box_pos[0]-self.pos[0], self._box_pos[1]-self.pos[1])) self.x1_box, self.y1_box = trans.transform_point((self._box_size[0]+self._box_pos[0]-self.pos[0], self._box_size[1]+self._box_pos[1]-self.pos[1])) self.do_update=True - + self._box_size = 0, 0 self._box_pos = 0, 0 self._alpha_box=0 @@ -2666,22 +2666,22 @@ def reset_box(self): self._pos_x_rect_hor = 0 self._pos_y_rect_hor = 0 self._pos_x_rect_ver = 0 - self._pos_y_rect_ver = 0 - self._alpha_hor=0 + self._pos_y_rect_ver = 0 + self._alpha_hor=0 self._alpha_ver=0 self.invert_rect_hor = False self.invert_rect_ver = False - + def draw_box(self, event, x0, y0, x1, y1) -> None: """ Draw zoombox method - + Args: event: touch kivy event x0: x coordonnate init x1: y coordonnate of move touch y0: y coordonnate init y1: x coordonnate of move touch - + Return: None """ @@ -2689,34 +2689,34 @@ def draw_box(self, event, x0, y0, x1, y1) -> None: # Kivy coords y0 = pos_y + y0 y1 = pos_y + y1 - + if abs(y1-y0)>dp(5) or abs(x1-x0)>dp(5): - self._alpha_box=0.3 + self._alpha_box=0.3 self._alpha_rect=0 - + trans = self.figure.axes[0].transData.inverted() - xdata, ydata = trans.transform_point((event.x-pos_x, event.y-pos_y)) + xdata, ydata = trans.transform_point((event.x-pos_x, event.y-pos_y)) # xmin,xmax=self.figure.axes[0].get_xlim() # ymin,ymax=self.figure.axes[0].get_ylim() - + xleft,xright=self.figure.axes[0].get_xlim() ybottom,ytop=self.figure.axes[0].get_ylim() - + xmax = max(xleft,xright) xmin = min(xleft,xright) ymax = max(ybottom,ytop) - ymin = min(ybottom,ytop) - + ymin = min(ybottom,ytop) + #check inverted data inverted_x = False if xleft>xright: inverted_x=True inverted_y = False if ybottom>ytop: - inverted_y=True - - x0data, y0data = trans.transform_point((x0-pos_x, y0-pos_y)) + inverted_y=True + + x0data, y0data = trans.transform_point((x0-pos_x, y0-pos_y)) if x0data>xmax or x0dataymax or y0data None: if xdata>xmax: x0_max = self.figure.axes[0].transData.transform([(xmax,ymin)]) if (x1>x0 and not inverted_x) or (x1 None: y1=y1_min[0][1]+pos_y else: y0=y1_min[0][1]+pos_y - + if ydata>ymax: y0_max = self.figure.axes[0].transData.transform([(xmax,ymax)]) if (y1>y0 and not inverted_y) or (y1self.minzoom: self.pos_x_rect_ver=x0 - self.pos_y_rect_ver=y0 - + self.pos_y_rect_ver=y0 + x1_min = self.figure.axes[0].transData.transform([(xmin,ymin)]) x0=x1_min[0][0]+pos_x @@ -2760,22 +2760,22 @@ def draw_box(self, event, x0, y0, x1, y1) -> None: self._alpha_ver=1 self._alpha_hor=0 - + elif abs(y1-y0)self.minzoom: self.pos_x_rect_hor=x0 - self.pos_y_rect_hor=y0 + self.pos_y_rect_hor=y0 y1_min = self.figure.axes[0].transData.transform([(xmin,ymin)]) y0=y1_min[0][1]+pos_y - + y0_max = self.figure.axes[0].transData.transform([(xmax,ymax)]) - y1=y0_max[0][1]+pos_y + y1=y0_max[0][1]+pos_y self._alpha_hor=1 self._alpha_ver=0 - + else: - self._alpha_hor=0 + self._alpha_hor=0 self._alpha_ver=0 if x1>x0: @@ -2786,7 +2786,7 @@ def draw_box(self, event, x0, y0, x1, y1) -> None: self.invert_rect_hor=False else: self.invert_rect_hor=True - + self._box_pos = x0, y0 self._box_size = x1 - x0, y1 - y0 @@ -2814,7 +2814,7 @@ def draw(self): def blit(self, bbox=None): """ Render the figure using agg (blit method). - """ + """ agg = self.get_renderer() w, h = agg.width, agg.height self.widget._bitmap = agg.buffer_rgba() @@ -2850,8 +2850,8 @@ class FakeEventTwinx: dp(1) if root.invert_rect_ver else -dp(1), \ dp(1) if root.invert_rect_hor else -dp(1), \ dp(1) if root.invert_rect_ver else -dp(1) - - canvas.after: + + canvas.after: #horizontal rectangle left Color: rgba:0, 0, 0, self._alpha_hor @@ -2859,7 +2859,7 @@ class FakeEventTwinx: width: dp(1) rectangle: (self.pos_x_rect_hor+dp(1) if root.invert_rect_ver \ - else self.pos_x_rect_hor-dp(4),self.pos_y_rect_hor-dp(20), dp(4),dp(40)) + else self.pos_x_rect_hor-dp(4),self.pos_y_rect_hor-dp(20), dp(4),dp(40)) #horizontal rectangle right Color: @@ -2868,7 +2868,7 @@ class FakeEventTwinx: width: dp(1) rectangle: (self.pos_x_rect_hor-dp(4)+self._box_size[0] if root.invert_rect_ver \ - else self.pos_x_rect_hor+dp(1)+self._box_size[0], self.pos_y_rect_hor-dp(20), dp(4),dp(40)) + else self.pos_x_rect_hor+dp(1)+self._box_size[0], self.pos_y_rect_hor-dp(20), dp(4),dp(40)) #vertical rectangle bottom Color: @@ -2877,7 +2877,7 @@ class FakeEventTwinx: width: dp(1) rectangle: (self.pos_x_rect_ver-dp(20),self.pos_y_rect_ver+dp(1) if root.invert_rect_hor else \ - self.pos_y_rect_ver-dp(4), dp(40),dp(4)) + self.pos_y_rect_ver-dp(4), dp(40),dp(4)) #vertical rectangle top Color: diff --git a/kivy_matplotlib_widget/uix/hover_widget.py b/kivy_matplotlib_widget/uix/hover_widget.py index 97ff97c..c37e2f1 100644 --- a/kivy_matplotlib_widget/uix/hover_widget.py +++ b/kivy_matplotlib_widget/uix/hover_widget.py @@ -10,14 +10,14 @@ ColorProperty ) -from kivy.lang import Builder -from kivy.core.window import Window +from kivy.lang import Builder +from kivy.core.window import Window from kivy.metrics import dp import numpy as np - + def add_hover(figure_wgt,mode='touch',label_x='x',label_y='y',hover_widget=None,hover_type='nearest'): """ add hover to matpotlib figure - + Args: figure_wgt: figure widget from kivy_matplotlib_widget package mode : 'touch' (touch device) or 'desktop' (desktop with mouse) @@ -25,91 +25,91 @@ def add_hover(figure_wgt,mode='touch',label_x='x',label_y='y',hover_widget=None, """ if figure_wgt.hover_instance: - + if hover_type=='compare': if not figure_wgt.compare_hover_instance: if hover_widget is None: - hover_widget = GeneralCompareHover() + hover_widget = GeneralCompareHover() hover_widget.x_hover_pos=figure_wgt.x - hover_widget.y_hover_pos=figure_wgt.y + hover_widget.y_hover_pos=figure_wgt.y hover_widget.label_x=label_x hover_widget.label_y=label_y figure_wgt.parent.add_widget(hover_widget) - figure_wgt.compare_hover_instance = hover_widget - figure_wgt.compare_hover_instance.create_child(figure_wgt.lines) + figure_wgt.compare_hover_instance = hover_widget + figure_wgt.compare_hover_instance.create_child(figure_wgt.lines) figure_wgt.hover_instance = figure_wgt.compare_hover_instance - figure_wgt.compare_xdata=True + figure_wgt.compare_xdata=True if figure_wgt.nearest_hover_instance: figure_wgt.nearest_hover_instance.show_cursor=False - + else: if not figure_wgt.nearest_hover_instance: if hover_widget is None: - hover_widget = GeneralHover() + hover_widget = GeneralHover() hover_widget.x_hover_pos=figure_wgt.x - hover_widget.y_hover_pos=figure_wgt.y + hover_widget.y_hover_pos=figure_wgt.y hover_widget.label_x=label_x hover_widget.label_y=label_y - figure_wgt.parent.add_widget(hover_widget) + figure_wgt.parent.add_widget(hover_widget) figure_wgt.nearest_hover_instance = hover_widget - - figure_wgt.hover_instance = figure_wgt.nearest_hover_instance + + figure_wgt.hover_instance = figure_wgt.nearest_hover_instance figure_wgt.hover_instance.reset_hover() figure_wgt.hover_instance.label_x=label_x figure_wgt.hover_instance.label_y=label_y - figure_wgt.compare_xdata=False + figure_wgt.compare_xdata=False if figure_wgt.compare_hover_instance: - figure_wgt.compare_hover_instance.show_cursor=False + figure_wgt.compare_hover_instance.show_cursor=False else: if hover_widget is None: if hover_type=='compare': hover_widget = GeneralCompareHover() else: hover_widget = GeneralHover() - + if hover_type=='compare': hover_widget.create_child(figure_wgt.lines) figure_wgt.compare_hover_instance = hover_widget - figure_wgt.compare_xdata=True + figure_wgt.compare_xdata=True else: figure_wgt.nearest_hover_instance = hover_widget figure_wgt.compare_xdata=False hover_widget.x_hover_pos=figure_wgt.x - hover_widget.y_hover_pos=figure_wgt.y + hover_widget.y_hover_pos=figure_wgt.y hover_widget.label_x=label_x hover_widget.label_y=label_y - + figure_wgt.parent.add_widget(hover_widget) - figure_wgt.hover_instance=hover_widget + figure_wgt.hover_instance=hover_widget if mode=='desktop': figure_wgt.hover_on=True - Window.bind(mouse_pos=figure_wgt.on_motion) - - + Window.bind(mouse_pos=figure_wgt.on_motion) + + class BaseHoverFloatLayout(FloatLayout): """ Touch egend kivy class""" figure_wgt = ObjectProperty(None) x_hover_pos = NumericProperty(1) - y_hover_pos = NumericProperty(1) - xmin_line = NumericProperty(1) - xmax_line = NumericProperty(1) - ymin_line = NumericProperty(1) - ymax_line = NumericProperty(1) + y_hover_pos = NumericProperty(1) + xmin_line = NumericProperty(1) + xmax_line = NumericProperty(1) + ymin_line = NumericProperty(1) + ymax_line = NumericProperty(1) hover_outside_bound = BooleanProperty(False) show_cursor = BooleanProperty(False) - label_x = StringProperty('x') - label_y = StringProperty('y') - label_x_value = StringProperty('') - label_y_value = StringProperty('') + label_x = StringProperty('x') + label_y = StringProperty('y') + label_x_value = StringProperty('') + label_y_value = StringProperty('') custom_label = StringProperty('',allownone=True) #futur used for dynamic label custom_color=ColorProperty([0,0,0,1],allownone=True) #futur used for dynamic color - figwidth = NumericProperty(2) - figheight = NumericProperty(2) + figwidth = NumericProperty(2) + figheight = NumericProperty(2) figx = NumericProperty(0) figy = NumericProperty(0) - + def __init__(self, **kwargs): """ init class """ super().__init__(**kwargs) @@ -118,45 +118,45 @@ def reset_hover(self): """ reset hover attribute """ self.x_hover_pos = 1 self.y_hover_pos = 1 - self.ymin_line = 1 - self.ymax_line = 1 + self.ymin_line = 1 + self.ymax_line = 1 class GeneralHover(BaseHoverFloatLayout): - """ GeneralHover """ + """ GeneralHover """ text_color=ColorProperty([0,0,0,1]) text_font=StringProperty("Roboto") text_size = NumericProperty(dp(14)) background_color=ColorProperty([0,0,1,0.3]) hover_height = NumericProperty(dp(24)) - + def __init__(self, **kwargs): """ init class """ - super().__init__(**kwargs) + super().__init__(**kwargs) class HoverVerticalText(BaseHoverFloatLayout): - """ Hover with vertical text""" + """ Hover with vertical text""" text_color=ColorProperty([0,0,0,1]) text_font=StringProperty("Roboto") text_size = NumericProperty(dp(14)) background_color=ColorProperty([1,1,0,1]) hover_height = NumericProperty(dp(48)) - + def __init__(self, **kwargs): """ init class """ super().__init__(**kwargs) class GeneralCompareHover(BaseHoverFloatLayout): - """ GeneralCompareHover""" + """ GeneralCompareHover""" text_color=ColorProperty([0,0,0,1]) text_font=StringProperty("Roboto") text_size = NumericProperty(dp(14)) background_color=ColorProperty([1,1,1,1]) hover_height = NumericProperty(dp(48)) - y_touch_pos = NumericProperty(1) - + y_touch_pos = NumericProperty(1) + def __init__(self, **kwargs): """ init class """ - super().__init__(**kwargs) + super().__init__(**kwargs) self.children_names=[] self.children_list=[] @@ -167,7 +167,7 @@ def create_child(self,lines): self.ids.main_box.remove_widget(self.ids.main_box.children[0]) self.children_names=[] - self.children_list=[] + self.children_list=[] for i,line in enumerate(lines): label=line.get_label() if i==0: @@ -179,17 +179,17 @@ def create_child(self,lines): self.ids.main_box.add_widget(mywidget) self.children_names.append(label) self.children_list.append(mywidget) - + class BoxShadowCompareHover(BaseHoverFloatLayout): - """ GeneralCompareHover with a box shadow""" + """ GeneralCompareHover with a box shadow""" text_color=ColorProperty([0,0,0,1]) text_font=StringProperty("Roboto") text_size = NumericProperty(dp(14)) background_color=ColorProperty([1,1,1,1]) hover_height = NumericProperty(dp(48)) - y_touch_pos = NumericProperty(1) + y_touch_pos = NumericProperty(1) reorder_data = BooleanProperty(True) - + def __init__(self, **kwargs): """ init class """ super(BoxShadowCompareHover, self).__init__(**kwargs) @@ -203,7 +203,7 @@ def create_child(self,lines): self.ids.main_box.remove_widget(self.ids.main_box.children[0]) self.children_names=[] - self.children_list=[] + self.children_list=[] for i,line in enumerate(lines): label=line.get_label() if i==0: @@ -215,7 +215,7 @@ def create_child(self,lines): self.ids.main_box.add_widget(mywidget) self.children_names.append(label) self.children_list.append(mywidget) - + def overlap_check(self): """ reorder label base on y_hover_pos of data""" if self.reorder_data and len(self.ids.main_box.children)>2: @@ -231,14 +231,14 @@ def overlap_check(self): # heigh_child = child.ids.label.texture_size[1]+dp(6) sorting_args= np.argsort(y_hover_pos_list) - for index in range(len(sorting_args)): + for index in range(len(sorting_args)): child_list[sorting_args[index]].y = y_pos_list[index] class CompareHoverBox(BoxLayout): - """ Hover with vertical text""" + """ Hover with vertical text""" text_color=ColorProperty([0,0,0,1]) text_font=StringProperty("Roboto") - text_size = NumericProperty(dp(14)) + text_size = NumericProperty(dp(14)) label_y = StringProperty('y') label_y_value = StringProperty('y') x_hover_pos = NumericProperty(1) @@ -248,13 +248,13 @@ class CompareHoverBox(BoxLayout): def __init__(self, **kwargs): """ init class """ - super().__init__(**kwargs) + super().__init__(**kwargs) class DotCompareHoverBox(FloatLayout): - """ Hover with vertical text""" + """ Hover with vertical text""" text_color=ColorProperty([0,0,0,1]) text_font=StringProperty("Roboto") - text_size = NumericProperty(dp(14)) + text_size = NumericProperty(dp(14)) label_y = StringProperty('y') label_y_value = StringProperty('y') x_hover_pos = NumericProperty(1) @@ -264,20 +264,20 @@ class DotCompareHoverBox(FloatLayout): def __init__(self, **kwargs): """ init class """ - super().__init__(**kwargs) + super().__init__(**kwargs) class TagCompareHover(BaseHoverFloatLayout): - """ TagCompareHover""" + """ TagCompareHover""" text_color=ColorProperty([0,0,0,1]) text_font=StringProperty("Roboto") text_size = NumericProperty(dp(14)) background_color=ColorProperty([1,1,1,1]) hover_height = NumericProperty(dp(48)) - y_touch_pos = NumericProperty(1) - + y_touch_pos = NumericProperty(1) + def __init__(self, **kwargs): """ init class """ - super().__init__(**kwargs) + super().__init__(**kwargs) self.children_names=[] self.children_list=[] @@ -288,7 +288,7 @@ def create_child(self,lines): self.ids.main_box.remove_widget(self.ids.main_box.children[0]) self.children_names=[] - self.children_list=[] + self.children_list=[] for i,line in enumerate(lines): label=line.get_label() if i==0: @@ -300,7 +300,7 @@ def create_child(self,lines): self.ids.main_box.add_widget(mywidget) self.children_names.append(label) self.children_list.append(mywidget) - + def overlap_check(self): if len(self.ids.main_box.children)>2: y_pos_list=[] @@ -322,10 +322,10 @@ def overlap_check(self): child_list[sorting_args[index+1]].hover_offset = offset class TagCompareHoverBox(FloatLayout): - """ Hover with vertical text""" + """ Hover with vertical text""" text_color=ColorProperty([0,0,0,1]) text_font=StringProperty("Roboto") - text_size = NumericProperty(dp(14)) + text_size = NumericProperty(dp(14)) label_y = StringProperty('y') label_y_value = StringProperty('y') x_hover_pos = NumericProperty(1) @@ -336,37 +336,37 @@ class TagCompareHoverBox(FloatLayout): def __init__(self, **kwargs): """ init class """ - super().__init__(**kwargs) + super().__init__(**kwargs) class InfoHover(BaseHoverFloatLayout): - """ InfoHover adapt the background and the font color with the line or scatter color""" + """ InfoHover adapt the background and the font color with the line or scatter color""" text_color=ColorProperty([0,0,0,1]) text_font=StringProperty("Roboto") text_size = NumericProperty(dp(14)) hover_height = NumericProperty(dp(48)) - + def __init__(self, **kwargs): """ init class """ - super().__init__(**kwargs) + super().__init__(**kwargs) class MatplotlibStyleHover(BaseHoverFloatLayout): """MatplotlibStyleHover look like matplotlib cursor but do not use matplotlib draw. Usefull in live blit drawing - - """ + + """ text_color=ColorProperty([0,0,0,1]) text_font=StringProperty("Roboto") text_size = NumericProperty(dp(14)) hover_height = NumericProperty(dp(48)) background_color=ColorProperty([1,1,1,1]) - + def __init__(self, **kwargs): """ init class """ - super().__init__(**kwargs) + super().__init__(**kwargs) class HightChartHover(BaseHoverFloatLayout): - """ PlotlyHover adapt the background and the font color with the line or scatter color""" + """ PlotlyHover adapt the background and the font color with the line or scatter color""" text_color=ColorProperty([0,0,0,1]) text_font=StringProperty("Roboto") text_size = NumericProperty(dp(14)) @@ -379,11 +379,11 @@ class HightChartHover(BaseHoverFloatLayout): options=('top', 'bottom', 'left', 'right', 'top_start','top_end','bottom_start','bottom_end', 'left_start','left_end','right_start','right_end')) - - + + def __init__(self, **kwargs): """ init class """ - super().__init__(**kwargs) + super().__init__(**kwargs) class RichTooltip(BoxLayout): position = OptionProperty('top', @@ -392,7 +392,7 @@ class RichTooltip(BoxLayout): 'left_start','left_end','right_start','right_end')) class PlotlyHover(BaseHoverFloatLayout): - """ PlotlyHover adapt the background and the font color with the line or scatter color""" + """ PlotlyHover adapt the background and the font color with the line or scatter color""" text_color=ColorProperty([0,0,0,1]) text_font=StringProperty("Roboto") text_size = NumericProperty(dp(14)) @@ -400,7 +400,7 @@ class PlotlyHover(BaseHoverFloatLayout): use_position = StringProperty('right') position = OptionProperty('right', options=('right', 'left')) - + def __init__(self, **kwargs): """ init class """ super().__init__(**kwargs) @@ -409,10 +409,10 @@ def __init__(self, **kwargs): size_hint: None,None - width: dp(0.01) + width: dp(0.01) height: dp(0.01) opacity:1 if root.show_cursor and not root.hover_outside_bound else 0 - + BoxLayout: x: @@ -423,39 +423,39 @@ def __init__(self, **kwargs): size_hint: None, None padding: dp(4),0,dp(4),0 height: root.hover_height - width: + width: label.texture_size[0] + self.padding[0] * 2 if root.show_cursor \ - else dp(0.0001) - - canvas: + else dp(0.0001) + + canvas: Color: rgba: root.background_color RoundedRectangle: pos: self.pos size: self.size - radius: [dp(7)] - canvas.after: + radius: [dp(7)] + canvas.after: Color: rgba: 0,0,1,1 Ellipse: size: (dp(8),dp(8)) - pos: + pos: (root.x_hover_pos-dp(8/2), \ root.y_hover_pos-dp(8/2)) Color: rgba: 0,0,1,1 Line: width: dp(1) - points: + points: root.x_hover_pos, \ root.ymin_line, \ root.x_hover_pos, \ - root.ymax_line + root.ymax_line Label: id:label - text: + text: root.label_x + ': ' + root.label_x_value + ', ' + \ - root.label_y + ': ' + root.label_y_value + root.label_y + ': ' + root.label_y_value font_size:root.text_size color: root.text_color font_name : root.text_font @@ -469,61 +469,61 @@ def __init__(self, **kwargs): root.y_hover_pos + dp(4) size_hint: None, None height: root.hover_height - width: + width: max(label.texture_size[0],label2.texture_size[0]) + dp(12) if root.show_cursor \ - else dp(0.0001) + else dp(0.0001) orientation:'vertical' padding: 0,dp(4),0,dp(4) - - canvas: + + canvas: Color: rgba: root.background_color RoundedRectangle: pos: self.pos size: self.size - radius: [dp(4)] + radius: [dp(4)] Color: rgba: 0,0,0,1 - + Line: - width: 1 + width: 1 rounded_rectangle: (self.x, self.y, self.width, self.height,\ dp(4), dp(4), dp(4), dp(4),\ - self.height) - - - canvas.after: + self.height) + + + canvas.after: Color: rgba: 0,0,0,1 Rectangle: size: (dp(8),dp(8)) - pos: + pos: (root.x_hover_pos-dp(8/2), \ root.y_hover_pos-dp(8/2)) - + BoxLayout: size_hint_x:None width:label.texture_size[0] padding: dp(12),0,0,0 Label: id:label - text: - root.label_x + ': ' + root.label_x_value + text: + root.label_x + ': ' + root.label_x_value font_size:root.text_size color: root.text_color font_name : root.text_font BoxLayout: size_hint_x:None - width:label2.texture_size[0] + width:label2.texture_size[0] padding: dp(12),0,0,0 Label: id:label2 text: - root.label_y + ': ' + root.label_y_value + root.label_y + ': ' + root.label_y_value font_size:root.text_size - color: root.text_color + color: root.text_color font_name : root.text_font @@ -537,13 +537,13 @@ def __init__(self, **kwargs): root.y_hover_pos + dp(4) size_hint: None, None height: root.hover_height - width: + width: max(label.texture_size[0],label2.texture_size[0]) + dp(12) if root.show_cursor \ - else dp(0.0001) + else dp(0.0001) orientation:'vertical' padding: 0,dp(4),0,dp(4) - - canvas: + + canvas: Color: rgba: root.custom_color if root.custom_color else [0,0,0,1] Rectangle: @@ -551,32 +551,32 @@ def __init__(self, **kwargs): size: self.size Color: rgba: 0,0,0,1 - + Line: - width: 1 + width: 1 rounded_rectangle: (self.x, self.y, self.width, self.height,\ dp(4), dp(4), dp(4), dp(4),\ - self.height) - - - canvas.after: + self.height) + + + canvas.after: Color: rgba: 0,0,0,1 Rectangle: size: (dp(8),dp(8)) - pos: + pos: (root.x_hover_pos-dp(8/2), \ root.y_hover_pos-dp(8/2)) - + BoxLayout: size_hint_x:None width:label.texture_size[0] padding: dp(12),0,0,0 Label: id:label - text: - root.label_x + ': ' + root.label_x_value + text: + root.label_x + ': ' + root.label_x_value font_size:root.text_size color: [0,0,0,1] if (root.custom_color[0]*0.299 + \ @@ -586,12 +586,12 @@ def __init__(self, **kwargs): BoxLayout: size_hint_x:None - width:label2.texture_size[0] + width:label2.texture_size[0] padding: dp(12),0,0,0 Label: id:label2 text: - root.label_y + ': ' + root.label_y_value + root.label_y + ': ' + root.label_y_value font_size:root.text_size color: [0,0,0,1] if (root.custom_color[0]*0.299 + \ @@ -600,8 +600,8 @@ def __init__(self, **kwargs): font_name : root.text_font FloatLayout: size_hint: None,None - width: dp(0.01) - height: dp(0.01) + width: dp(0.01) + height: dp(0.01) BoxLayout: size_hint:None,None x:main_box.x + main_box.width + dp(4) @@ -610,15 +610,15 @@ def __init__(self, **kwargs): height:label3.texture_size[1] Label: id:label3 - text: - root.custom_label if root.custom_label else '' + text: + root.custom_label if root.custom_label else '' font_size:root.text_size color: root.text_color - font_name : root.text_font - + font_name : root.text_font + - custom_color: [0,0,0,1] - + custom_color: [0,0,0,1] + BoxLayout: id:main_box x: @@ -628,41 +628,41 @@ def __init__(self, **kwargs): root.ymin_line + dp(4) if abs(root.y_touch_pos -root.ymin_line) > abs(root.y_touch_pos -root.ymax_line) else root.ymax_line - dp(4) - self.height size_hint: None, None height: self.minimum_height - width: + width: self.minimum_width + dp(12) if root.show_cursor \ else dp(0.0001) orientation:'vertical' padding: 0,dp(4),0,dp(4) - - canvas: + + canvas: Color: rgba: root.background_color RoundedRectangle: pos: self.pos size: self.size - radius: [dp(4)] + radius: [dp(4)] Color: rgba: 0,0,0,1 - + Line: - width: 1 + width: 1 rounded_rectangle: (self.x, self.y, self.width, self.height,\ dp(4), dp(4), dp(4), dp(4),\ - self.height) - - canvas.after: + self.height) + + canvas.after: Color: rgba: 0,0,1,1 Line: width: dp(1) - points: + points: root.x_hover_pos, \ root.ymin_line, \ root.x_hover_pos, \ root.ymax_line - - + + BoxLayout: size_hint:None,None width:label.texture_size[0] + dp(12) @@ -670,15 +670,15 @@ def __init__(self, **kwargs): padding: dp(12),0,0,0 Label: id:label - text: - root.label_x + ': ' + root.label_x_value + text: + root.label_x + ': ' + root.label_x_value font_size:root.text_size font_name : root.text_font color: root.text_color - - CompareHoverBox: - id:comparehoverbox - + + CompareHoverBox: + id:comparehoverbox + size_hint:None,None width:label.texture_size[0] + dp(12) + dp(24) if self.show_widget else dp(12) @@ -688,22 +688,22 @@ def __init__(self, **kwargs): Widget: size_hint_x:None width:dp(16) - canvas: + canvas: Color: rgba: root.custom_color if root.custom_color else [0,0,0,1] Rectangle: pos: self.pos[0],self.pos[1]+self.height/2-dp(6) - size: dp(12),dp(12) + size: dp(12),dp(12) Label: id:label - text: root.label_y + ': ' + root.label_y_value + text: root.label_y + ': ' + root.label_y_value color: root.text_color font_size:root.text_size - font_name : root.text_font + font_name : root.text_font - custom_color: [0,0,0,1] - + custom_color: [0,0,0,1] + BoxLayout: id:main_box x: @@ -713,40 +713,40 @@ def __init__(self, **kwargs): root.ymin_line if abs(root.y_touch_pos -root.ymin_line) > abs(root.y_touch_pos -root.ymax_line) else root.ymax_line - self.height size_hint: None, None height: root.hover_height - width: + width: self.minimum_width + dp(12) if root.show_cursor \ else dp(0.0001) orientation:'vertical' padding: 0,dp(4),0,dp(4) - - canvas.after: + + canvas.after: Color: rgba: 0,0,1,0 Line: width: dp(1) - points: + points: root.x_hover_pos, \ root.ymin_line, \ root.x_hover_pos, \ root.ymax_line - - + + FloatLayout: size_hint:None,None width:dp(0.01) height:dp(0.01) - BoxLayout: + BoxLayout: size_hint:None,None width:label.texture_size[0] + dp(8) height:label.texture_size[1] + dp(6) x: root.x_hover_pos - label.texture_size[0]/2 - dp(4) y: root.ymin_line - label.texture_size[1] - dp(10) - canvas.before: + canvas.before: Color: rgba: [0,0,0,1] Rectangle: pos: self.pos - size: self.size + size: self.size Triangle: points: [ \ @@ -754,19 +754,19 @@ def __init__(self, **kwargs): root.x_hover_pos, root.ymin_line, \ root.x_hover_pos +dp(4), root.ymin_line-dp(4) \ ] - + Label: id:label - text: - root.label_x_value + text: + root.label_x_value font_size:root.text_size font_name : root.text_font color: [1,1,1,1] - - TagCompareHoverBox: - id:ycomparehoverbox - + TagCompareHoverBox: + id:ycomparehoverbox + + size_hint:None,None width:dp(0.01) @@ -780,13 +780,13 @@ def __init__(self, **kwargs): padding: dp(2),0,0,0 x: root.x_hover_pos + dp(4) y: root.y_hover_pos - label.texture_size[1]/2-dp(3) + root.hover_offset - - canvas.before: + + canvas.before: Color: rgba: root.custom_color if root.custom_color else [0,0,0,1] Rectangle: pos: self.pos - size: self.size + size: self.size Triangle: points: @@ -801,93 +801,93 @@ def __init__(self, **kwargs): [ \ root.x_hover_pos, root.y_hover_pos, \ main_box.x, root.y_hover_pos + root.hover_offset \ - ] + ] Label: id:label - text: root.label_y_value + text: root.label_y_value color: [0,0,0,1] if (root.custom_color[0]*0.299 + \ root.custom_color[1]*0.587 + root.custom_color[2]*0.114) > 186/255 \ else [1,1,1,1] font_size:root.text_size - font_name : root.text_font - + font_name : root.text_font + FloatLayout: size_hint: None,None - width: dp(0.01) - height: dp(0.01) + width: dp(0.01) + height: dp(0.01) BoxLayout: size_hint:None,None x:main_box.x + main_box.width + dp(2) y:main_box.y + main_box.height/2-label2.texture_size[1]/2 width:label2.texture_size[0] height:label2.texture_size[1] - canvas.before: + canvas.before: Color: rgba: [1,1,1,0.7] Rectangle: pos: self.pos - size: self.size + size: self.size Label: id:label2 - text: root.label_y + text: root.label_y font_size:root.text_size color: [0,0,0,1] - font_name : root.text_font - + font_name : root.text_font + - custom_color: [0,0,0,1] - + custom_color: [0,0,0,1] + BoxLayout: id:main_box x:root.xmin_line - y: root.ymax_line + dp(4) + y: root.ymax_line + dp(4) size_hint: None, None height: self.minimum_height - width: + width: self.minimum_width + dp(12) if root.show_cursor \ else dp(0.0001) orientation:'vertical' padding: 0,dp(4),0,dp(4) - - canvas: + + canvas: Color: rgba: root.background_color Rectangle: pos: self.pos size: self.size - - canvas.after: + + canvas.after: Color: rgba: 0,0,0,1 Line: width: dp(1) - points: + points: root.x_hover_pos, \ root.ymin_line, \ root.x_hover_pos, \ root.ymax_line dash_offset: 2 dash_length: 10 - + Line: width: dp(1) - points: + points: root.xmin_line, \ root.y_hover_pos, \ root.xmax_line, \ - root.y_hover_pos + root.y_hover_pos dash_offset: 2 - dash_length: 10 - + dash_length: 10 + BoxLayout: size_hint:None,None width:label.texture_size[0] + dp(12) height:label.texture_size[1] + dp(12) Label: id:label - text: - root.label_x + ': ' + root.label_x_value + ' ' + root.label_y + ': ' + root.label_y_value + text: + root.label_x + ': ' + root.label_x_value + ' ' + root.label_y + ': ' + root.label_y_value font_size:root.text_size font_name : root.text_font color: root.text_color @@ -901,20 +901,20 @@ def __init__(self, **kwargs): '[/font][/color][/size]' + root.extra_text + "\\n" + 'x:' + \ root.label_x_value +"\\n"+ "y:" + root.label_y_value - canvas.before: + canvas.before: Color: rgb: root.custom_color if root.custom_color else [0,0,0,1] a:0.5 Ellipse: size: (dp(12),dp(12)) - pos: (root.x_hover_pos - dp(6),root.y_hover_pos-dp(6)) - - use_position: + pos: (root.x_hover_pos - dp(6),root.y_hover_pos-dp(6)) + + use_position: 'left_' + root.position.split('_')[1] if ('right_' in root.position and root.x_hover_pos + main_box.width + dp(14) > root.figwidth + root.figx) else \ 'left' if ('right'==root.position and root.x_hover_pos + main_box.width + dp(14) > root.figwidth + root.figx) else \ 'right' if ('left'==root.position and root.x_hover_pos - main_box.width -dp(14) < root.figx) else \ - 'right_' + root.position.split('_')[1] if ('left_' in root.position and root.x_hover_pos - main_box.width -dp(14) < root.figx) else root.position - + 'right_' + root.position.split('_')[1] if ('left_' in root.position and root.x_hover_pos - main_box.width -dp(14) < root.figx) else root.position + RichTooltip: id:main_box x: @@ -925,16 +925,16 @@ def __init__(self, **kwargs): root.y_hover_pos + dp(14) if 'top' in root.position else \ root.y_hover_pos - self.height - dp(14) if 'bottom' in root.position else \ root.y_hover_pos - self.height/2 - main_box.offset2 - + size_hint: None, None height: label.texture_size[1]+ root.label_padding_height*2 - width: + width: label.texture_size[0] + root.label_padding_width*2 if root.show_cursor \ - else dp(0.0001) + else dp(0.0001) orientation:'vertical' padding: 0,-dp(1),0,0 position:root.use_position - + Label: id:label text: root.label_format @@ -948,10 +948,10 @@ def __init__(self, **kwargs): size_hint: None,None width:dp(200) height:dp(60) - offset1: + offset1: 0 if not '_' in root.position else -mainbox.width//2 + dp(20) \ if 'start' in root.position else mainbox.width//2 - dp(20) - offset2: + offset2: 0 if not '_' in root.position else mainbox.height//2 - dp(20) \ if 'start' in root.position else - mainbox.height//2 + dp(20) canvas.before: @@ -959,18 +959,18 @@ def __init__(self, **kwargs): rgba: 0, 0, 0, .65 BoxShadow: pos: self.pos - size: self.size + size: self.size offset: 0, -2 spread_radius: -4, -4 border_radius: 4, 4, 4, 4 - blur_radius: 14 + blur_radius: 14 Color: rgba: 1, 1, 1, 1 SmoothRoundedRectangle: pos: self.pos size: self.size - radius: [7,] - + radius: [7,] + FloatLayout: size_hint: None,None size:dp(0.01),dp(0.01) @@ -979,14 +979,14 @@ def __init__(self, **kwargs): mainbox.x+mainbox.width//2 - self.width//2 + root.offset1 \ if ('top' in root.position or 'bottom' in root.position) \ else mainbox.x +mainbox.width - self.width//2 - dp(1) \ - if 'left' in root.position else mainbox.x - self.width//2 + dp(1) + if 'left' in root.position else mainbox.x - self.width//2 + dp(1) y: mainbox.y - self.height//2 +dp(1) if 'top' in root.position \ else mainbox.y + mainbox.height - self.height//2 - dp(1) \ - if 'bottom' in root.position else mainbox.y + mainbox.height//2 - self.height//2 + root.offset2 + if 'bottom' in root.position else mainbox.y + mainbox.height//2 - self.height//2 + root.offset2 size_hint: None,None width:dp(12) - height:dp(12) + height:dp(12) canvas.before: StencilPush Rectangle: @@ -999,7 +999,7 @@ def __init__(self, **kwargs): (self.size[0]+self.width,self.size[1]+dp(5)) \ if ('top' in root.position or 'bottom' in root.position ) \ else (self.size[0]+dp(5) ,self.size[1] + dp(40)) - StencilUse + StencilUse Color: rgba: 0, 0, 0, .65 PushMatrix @@ -1017,35 +1017,35 @@ def __init__(self, **kwargs): rgba: 1, 1, 1, 1 SmoothRectangle: pos: self.pos - size: self.size - PopMatrix - StencilPop + size: self.size + PopMatrix + StencilPop custom_color: [0,0,0,1] - - use_position: + + use_position: 'left' if ('right'==root.position and root.x_hover_pos + main_box.width + label3.texture_size[0] + dp(6) > root.figwidth + root.figx) else \ 'right' if ('left'==root.position and root.x_hover_pos - main_box.width - label3.texture_size[0] - dp(6) < root.figx) else \ root.position - + BoxLayout: id:main_box x: root.x_hover_pos + dp(4) if 'right' in root.use_position else \ - root.x_hover_pos - self.width - dp(4) + root.x_hover_pos - self.width - dp(4) y: root.y_hover_pos - root.hover_height/2 - + size_hint: None, None height: label.texture_size[1]+ dp(4) - width: + width: self.minimum_width + dp(12) if root.show_cursor \ - else dp(0.0001) + else dp(0.0001) orientation:'vertical' padding: 0,-dp(1),0,0 - - canvas: + + canvas: Color: rgba: root.custom_color if root.custom_color else [0,0,0,1] Rectangle: @@ -1062,7 +1062,7 @@ def __init__(self, **kwargs): root.x_hover_pos, root.y_hover_pos, \ main_box.x + main_box.width, root.y_hover_pos+ dp(4), \ main_box.x + main_box.width, root.y_hover_pos- dp(4) \ - ] + ] SmoothLine: width:dp(1) points: @@ -1074,15 +1074,15 @@ def __init__(self, **kwargs): root.x_hover_pos, root.y_hover_pos, \ main_box.x + main_box.width, root.y_hover_pos \ ] - - + + BoxLayout: size_hint_x:None width:label.texture_size[0] padding: dp(12),0,0,0 Label: id:label - text: + text: '(' + root.label_x_value +','+ root.label_y_value +')' font_size:root.text_size color: @@ -1092,11 +1092,11 @@ def __init__(self, **kwargs): font_name : root.text_font font_name : root.text_font - + FloatLayout: size_hint: None,None - width: dp(0.01) - height: dp(0.01) + width: dp(0.01) + height: dp(0.01) BoxLayout: size_hint:None,None x: @@ -1107,14 +1107,14 @@ def __init__(self, **kwargs): height:label3.texture_size[1] Label: id:label3 - text: - root.custom_label if root.custom_label and not '_child' in root.custom_label else '' + text: + root.custom_label if root.custom_label and not '_child' in root.custom_label else '' font_size:root.text_size color: root.text_color - font_name : root.text_font + font_name : root.text_font - custom_color: [0,0,0,1] + custom_color: [0,0,0,1] BoxLayout: id:main_box @@ -1125,41 +1125,41 @@ def __init__(self, **kwargs): root.ymin_line + dp(4) if abs(root.y_touch_pos -root.ymin_line) > abs(root.y_touch_pos -root.ymax_line) else root.ymax_line - dp(4) - self.height size_hint: None, None height: self.minimum_height - width: + width: self.minimum_width + dp(12) if root.show_cursor \ else dp(0.0001) orientation:'vertical' padding: 0,dp(4),0,dp(4) - - canvas: + + canvas: Color: rgba: 0, 0, 0, .65 BoxShadow: pos: self.pos - size: self.size + size: self.size offset: 0, -2 spread_radius: -4, -4 border_radius: 4, 4, 4, 4 - blur_radius: 14 + blur_radius: 14 Color: rgba: root.background_color SmoothRoundedRectangle: pos: self.pos size: self.size radius: [7,] - - canvas.after: + + canvas.after: Color: rgba: 0,0,1,0.1 SmoothLine: width: dp(8) - points: + points: root.x_hover_pos, \ root.ymin_line, \ root.x_hover_pos, \ root.ymax_line - - + + BoxLayout: size_hint:None,None width:label.texture_size[0] + dp(12) @@ -1167,14 +1167,14 @@ def __init__(self, **kwargs): padding: dp(6),0,0,0 Label: id:label - text: + text: root.label_x + ': ' + root.label_x_value if root.label_x else root.label_x_value font_size:root.text_size font_name : root.text_font color: root.text_color - - DotCompareHoverBox: - id:comparehoverbox + + DotCompareHoverBox: + id:comparehoverbox size_hint:None,None @@ -1185,22 +1185,22 @@ def __init__(self, **kwargs): extra_text:root.label_y if root.label_y and not '_child' in root.label_y else '' label_format:'[size={}]'.format(int(root.text_size + dp(6))) + '[color={}]'.format(get_hex_from_color(root.custom_color)) + \ '[font=NavigationIcons]' + u"{}".format("\U00000EB1") + \ - '[/font][/color][/size]' + root.extra_text + ": " + root.label_y_value - - canvas.before: + '[/font][/color][/size]' + root.extra_text + ": " + root.label_y_value + + canvas.before: Color: rgb: root.custom_color if root.custom_color else [0,0,0,1] a:0.7 SmoothEllipse: size: (dp(12),dp(12)) - pos: (root.x_hover_pos - dp(6),root.y_hover_pos-dp(6)) + pos: (root.x_hover_pos - dp(6),root.y_hover_pos-dp(6)) Color: rgb: [1,1,1] a:1.0 SmoothLine: width: 1. - ellipse: (root.x_hover_pos - dp(7), root.y_hover_pos-dp(7), dp(14), dp(14)) - + ellipse: (root.x_hover_pos - dp(7), root.y_hover_pos-dp(7), dp(14), dp(14)) + Label: id:label @@ -1210,5 +1210,5 @@ def __init__(self, **kwargs): font_size:root.text_size color:[0,0,0,1] font_name : root.text_font - markup:True + markup:True ''') diff --git a/kivy_matplotlib_widget/uix/legend_widget.py b/kivy_matplotlib_widget/uix/legend_widget.py index e421adf..bf0f1ce 100644 --- a/kivy_matplotlib_widget/uix/legend_widget.py +++ b/kivy_matplotlib_widget/uix/legend_widget.py @@ -29,7 +29,7 @@ class LegendGestures(Widget): """ This widget is based on CommonGestures from gestures4kivy project https://github.com/Android-for-Python/gestures4kivy - + For more gesture features like long press or swipe, replace LegendGestures by CommonGestures (available on gestures4kivy project). """ @@ -52,7 +52,7 @@ def __init__(self, **kwargs): ##################### # In the case of a RelativeLayout, the touch.pos value is not persistent. # Because the same Touch is called twice, once with Window relative and - # once with the RelativeLayout relative values. + # once with the RelativeLayout relative values. # The on_touch_* callbacks have the required value because of collide_point # but only within the scope of that touch callback. # @@ -60,8 +60,8 @@ def __init__(self, **kwargs): # So if we have a RelativeLayout we can't rely on the value in touch.pos . # So regardless of there being a RelativeLayout, we save each touch.pos # in self._persistent_pos[] and use that when the current value is - # required. - + # required. + ### touch down ### def on_touch_down(self, touch): if self.collide_point(touch.x, touch.y): @@ -79,8 +79,8 @@ def on_touch_down(self, touch): # Two finger tap or right click pass else: - self._gesture_state = 'Dont Know' - # schedule a posssible tap + self._gesture_state = 'Dont Know' + # schedule a posssible tap if not self._single_tap_schedule: self._single_tap_schedule =\ Clock.schedule_once(partial(self._single_tap_event, @@ -90,7 +90,7 @@ def on_touch_down(self, touch): self._persistent_pos[0] = tuple(touch.pos) elif len(self._touches) == 2: - # two fingers + # two fingers self._not_single_tap() self._persistent_pos[1] = tuple(touch.pos) @@ -112,7 +112,7 @@ def on_touch_up(self, touch): else: self._new_gesture() - return super().on_touch_up(touch) + return super().on_touch_up(touch) ############################################ # gesture utilities @@ -144,7 +144,7 @@ def _remove_gesture(self, touch): if touch and len(self._touches): if touch in self._touches: self._touches.remove(touch) - + def _new_gesture(self): self._touches = [] self._long_press_schedule = None @@ -169,9 +169,9 @@ def cg_double_tap(self, touch, x, y): class LegendRv(BoxLayout): """Legend class - - """ - figure_wgt = ObjectProperty(None) + + """ + figure_wgt = ObjectProperty(None) data=ListProperty() text_color=ColorProperty([0,0,0,1]) text_font=StringProperty("Roboto") @@ -182,27 +182,27 @@ class LegendRv(BoxLayout): def __init__(self, **kwargs): """init class - - """ + + """ super(LegendRv, self).__init__(**kwargs) - self.data=[] + self.data=[] def set_data(self,content:list) -> None: - """set legend data - + """set legend data + Args: - content (list):list of dictionary with matplotlib line(s) - + content (list):list of dictionary with matplotlib line(s) + Returns: None - """ + """ self.data=[] - + #reset scroll self.ids.view.scroll_y = 1 for i,row_content in enumerate(content): - + r_data = { "row_index":int(i), "viewclass": "CellLegend" @@ -210,7 +210,7 @@ def set_data(self,content:list) -> None: r_data["text"] = str(row_content.get_label()) r_data["mycolor"] = get_color_from_hex(to_hex(row_content.get_color())) r_data["line_type"] = row_content.get_linestyle() - r_data["legend_rv"] = self + r_data["legend_rv"] = self r_data["selected"] = False r_data["matplotlib_line"] = row_content r_data["text_color"] = self.text_color @@ -219,16 +219,16 @@ def set_data(self,content:list) -> None: r_data["box_height"] = self.box_height self.data.append(r_data) - + def add_data(self,line) -> None: """add line method - + Args: line:matplotlib line - + Returns: None - """ + """ nb_data=len(self.data) r_data = { "row_index":int(nb_data), @@ -237,7 +237,7 @@ def add_data(self,line) -> None: r_data["text"] = str(line.get_label()) r_data["mycolor"] = get_color_from_hex(to_hex(line.get_color())) r_data["line_type"] = line.get_linestyle() - r_data["legend_rv"] = self + r_data["legend_rv"] = self r_data["selected"] = False r_data["matplotlib_line"] = line r_data["text_color"] = self.text_color @@ -247,28 +247,28 @@ def add_data(self,line) -> None: self.data.append(r_data) self.figure_wgt.figure.canvas.draw_idle() - self.figure_wgt.figure.canvas.flush_events() - + self.figure_wgt.figure.canvas.flush_events() + def remove_data(self,remove_line) -> None: """add line method - + Args: remove_line: matplotlib line - + Returns: None - """ + """ remove_idx=None for idx,current_data in enumerate(self.data): if current_data["matplotlib_line"] == remove_line: remove_idx=idx - break + break if remove_idx: del self.data[remove_idx] remove_line.remove() self.figure_wgt.figure.canvas.draw_idle() - self.figure_wgt.figure.canvas.flush_events() - + self.figure_wgt.figure.canvas.flush_events() + def show_hide_wgt(self,row_index) -> None: if self.data[row_index]["selected"]: #show line @@ -276,9 +276,9 @@ def show_hide_wgt(self,row_index) -> None: self.data[row_index]["matplotlib_line"].set_visible(True) if self.autoscale and hasattr(self.figure_wgt,'autoscale_axis'): self.figure_wgt.autoscale() - else: + else: self.figure_wgt.figure.canvas.draw_idle() - self.figure_wgt.figure.canvas.flush_events() + self.figure_wgt.figure.canvas.flush_events() else: #hide line self.data[row_index]["selected"] = True @@ -287,10 +287,10 @@ def show_hide_wgt(self,row_index) -> None: self.figure_wgt.autoscale() else: self.figure_wgt.figure.canvas.draw_idle() - self.figure_wgt.figure.canvas.flush_events() + self.figure_wgt.figure.canvas.flush_events() self.ids.view.refresh_from_layout() - - def doubletap(self,row_index) -> None: + + def doubletap(self,row_index) -> None: """ double tap behavior is based on plotly behavior """ if not self.data[row_index]["selected"]: current_line = self.data[row_index]["matplotlib_line"] @@ -305,44 +305,44 @@ def doubletap(self,row_index) -> None: for line in figure_lines: if line != current_line and line.get_visible(): need_isolate=True - break - + break + if need_isolate: #isolate line' for idx,line in enumerate(figure_lines): - if line != current_line: - line.set_visible(False) + if line != current_line: + line.set_visible(False) self.data[idx]["selected"] = True else: self.data[idx]["selected"] = False else: #show all lines' - for idx,line in enumerate(figure_lines): + for idx,line in enumerate(figure_lines): line.set_visible(True) - self.data[idx]["selected"] = False + self.data[idx]["selected"] = False else: #show all lines if len(self.figure_wgt.figure.axes)>1: figure_lines = self.figure_wgt.figure.axes[0].get_lines() + \ self.figure_wgt.figure.axes[1].get_lines() else: - figure_lines = list(self.figure_wgt.figure.axes[0].get_lines()) - for idx,line in enumerate(figure_lines): - line.set_visible(True) - self.data[idx]["selected"] = False - + figure_lines = list(self.figure_wgt.figure.axes[0].get_lines()) + for idx,line in enumerate(figure_lines): + line.set_visible(True) + self.data[idx]["selected"] = False + self.ids.view.refresh_from_layout() if self.autoscale and hasattr(self.figure_wgt,'autoscale_axis'): - self.figure_wgt.autoscale() + self.figure_wgt.autoscale() else: self.figure_wgt.figure.canvas.draw_idle() self.figure_wgt.figure.canvas.flush_events() - + class LegendRvHorizontal(BoxLayout): """Legend Horizontal class - - """ - figure_wgt = ObjectProperty(None) + + """ + figure_wgt = ObjectProperty(None) data = ListProperty() text_color = ColorProperty([0,0,0,1]) text_font = StringProperty("Roboto") @@ -350,30 +350,30 @@ class LegendRvHorizontal(BoxLayout): background_color = ColorProperty([1,1,1,1]) box_height = NumericProperty(dp(48)) autoscale=BooleanProperty(False) - + def __init__(self, **kwargs): """init class - - """ + + """ super(LegendRvHorizontal, self).__init__(**kwargs) - self.data=[] + self.data=[] def set_data(self,content:list) -> None: - """set legend data - + """set legend data + Args: - content (list):list of dictionary with matplotlib line(s) - + content (list):list of dictionary with matplotlib line(s) + Returns: None - """ + """ self.data=[] - + #reset scroll self.ids.view.scroll_y = 1 for i,row_content in enumerate(content): - + r_data = { "row_index":int(i), "viewclass": "CellLegend" @@ -381,7 +381,7 @@ def set_data(self,content:list) -> None: r_data["text"] = str(row_content.get_label()) r_data["mycolor"] = get_color_from_hex(to_hex(row_content.get_color())) r_data["line_type"] = row_content.get_linestyle() - r_data["legend_rv"] = self + r_data["legend_rv"] = self r_data["selected"] = False r_data["matplotlib_line"] = row_content r_data["text_color"] = self.text_color @@ -390,16 +390,16 @@ def set_data(self,content:list) -> None: r_data['box_height'] = self.box_height self.data.append(r_data) - + def add_data(self,line) -> None: """add line method - + Args: line:matplotlib line - + Returns: None - """ + """ nb_data=len(self.data) r_data = { "row_index":int(nb_data), @@ -408,7 +408,7 @@ def add_data(self,line) -> None: r_data["text"] = str(line.get_label()) r_data["mycolor"] = get_color_from_hex(to_hex(line.get_color())) r_data["line_type"] = line.get_linestyle() - r_data["legend_rv"] = self + r_data["legend_rv"] = self r_data["selected"] = False r_data["matplotlib_line"] = line r_data["text_color"] = self.text_color @@ -418,28 +418,28 @@ def add_data(self,line) -> None: self.data.append(r_data) self.figure_wgt.figure.canvas.draw_idle() - self.figure_wgt.figure.canvas.flush_events() - + self.figure_wgt.figure.canvas.flush_events() + def remove_data(self,remove_line) -> None: """add line method - + Args: remove_line: matplotlib line - + Returns: None - """ + """ remove_idx=None for idx,current_data in enumerate(self.data): if current_data["matplotlib_line"] == remove_line: remove_idx=idx - break - if remove_idx: + break + if remove_idx: del self.data[remove_idx] remove_line.remove() self.figure_wgt.figure.canvas.draw_idle() - self.figure_wgt.figure.canvas.flush_events() - + self.figure_wgt.figure.canvas.flush_events() + def show_hide_wgt(self,row_index) -> None: if self.data[row_index]["selected"]: #show line @@ -449,7 +449,7 @@ def show_hide_wgt(self,row_index) -> None: self.figure_wgt.autoscale() else: self.figure_wgt.figure.canvas.draw_idle() - self.figure_wgt.figure.canvas.flush_events() + self.figure_wgt.figure.canvas.flush_events() else: #hide line self.data[row_index]["selected"] = True @@ -458,10 +458,10 @@ def show_hide_wgt(self,row_index) -> None: self.figure_wgt.autoscale() else: self.figure_wgt.figure.canvas.draw_idle() - self.figure_wgt.figure.canvas.flush_events() + self.figure_wgt.figure.canvas.flush_events() self.ids.view.refresh_from_layout() - - def doubletap(self,row_index) -> None: + + def doubletap(self,row_index) -> None: """ double tap behavior is based on plotly behavior """ if not self.data[row_index]["selected"]: current_line = self.data[row_index]["matplotlib_line"] @@ -472,54 +472,54 @@ def doubletap(self,row_index) -> None: figure_lines = self.figure_wgt.figure.axes[0].get_lines() + \ self.figure_wgt.figure.axes[1].get_lines() else: - figure_lines = list(self.figure_wgt.figure.axes[0].get_lines()) + figure_lines = list(self.figure_wgt.figure.axes[0].get_lines()) for line in figure_lines: if line != current_line and line.get_visible(): need_isolate=True - break - + break + if need_isolate: #isolate line' for idx,line in enumerate(figure_lines): - if line != current_line: - line.set_visible(False) + if line != current_line: + line.set_visible(False) self.data[idx]["selected"] = True else: self.data[idx]["selected"] = False else: #show all lines' - for idx,line in enumerate(figure_lines): + for idx,line in enumerate(figure_lines): line.set_visible(True) - self.data[idx]["selected"] = False + self.data[idx]["selected"] = False else: #show all lines if len(self.figure_wgt.figure.axes)>1: figure_lines = self.figure_wgt.figure.axes[0].get_lines() + \ self.figure_wgt.figure.axes[1].get_lines() else: - figure_lines = list(self.figure_wgt.figure.axes[0].get_lines()) - for idx,line in enumerate(figure_lines): - line.set_visible(True) - self.data[idx]["selected"] = False - + figure_lines = list(self.figure_wgt.figure.axes[0].get_lines()) + for idx,line in enumerate(figure_lines): + line.set_visible(True) + self.data[idx]["selected"] = False + self.ids.view.refresh_from_layout() if self.autoscale and hasattr(self.figure_wgt,'autoscale_axis'): - self.figure_wgt.autoscale() + self.figure_wgt.autoscale() else: self.figure_wgt.figure.canvas.draw_idle() self.figure_wgt.figure.canvas.flush_events() class CellLegendMatplotlib(LegendGestures, BoxLayout): - """ Touch legend kivy class""" + """ Touch legend kivy class""" selected = BooleanProperty(False) row_index = NumericProperty(0) matplotlib_legend_box = ObjectProperty(None) matplotlib_line = ObjectProperty(None) matplotlib_text = ObjectProperty(None) - + def __init__(self, **kwargs): """ init class """ - super().__init__(**kwargs) + super().__init__(**kwargs) def cg_tap(self, touch, x, y): #single tap @@ -529,24 +529,24 @@ def cg_tap(self, touch, x, y): def cg_double_tap(self, touch, x, y): #double tap self.matplotlib_legend_box.doubletap(self.row_index) - + class CellLegend(LegendGestures, BoxLayout): - """ Touch legend kivy class""" + """ Touch legend kivy class""" selected = BooleanProperty(False) text = StringProperty("") row_index = NumericProperty(0) legend_rv = ObjectProperty(None) matplotlib_line = ObjectProperty(None) - line_type = StringProperty('-') + line_type = StringProperty('-') mycolor= ListProperty([0,0,1]) text_color=ColorProperty([0,0,0,1]) text_font=StringProperty("Roboto") text_font_size=NumericProperty(dp(18.5)) - box_height = NumericProperty(dp(48)) - + box_height = NumericProperty(dp(48)) + def __init__(self, **kwargs): """ init class """ - super().__init__(**kwargs) + super().__init__(**kwargs) def cg_tap(self, touch, x, y): #single tap @@ -555,24 +555,24 @@ def cg_tap(self, touch, x, y): def cg_double_tap(self, touch, x, y): #double tap self.legend_rv.doubletap(self.row_index) - + class CellLegendHorizontal(LegendGestures, BoxLayout): - """ Touch legend kivy class""" + """ Touch legend kivy class""" selected = BooleanProperty(False) text = StringProperty("") row_index = NumericProperty(0) legend_rv = ObjectProperty(None) matplotlib_line = ObjectProperty(None) - line_type = StringProperty('-') + line_type = StringProperty('-') mycolor = ListProperty([0,0,1]) text_color = ColorProperty([0,0,0,1]) text_font = StringProperty("Roboto") text_font_size = NumericProperty(dp(18.5)) - box_height = NumericProperty(dp(48)) + box_height = NumericProperty(dp(48)) def __init__(self, **kwargs): """ init class """ - super().__init__(**kwargs) + super().__init__(**kwargs) def cg_tap(self, touch, x, y): #single tap @@ -581,7 +581,7 @@ def cg_tap(self, touch, x, y): def cg_double_tap(self, touch, x, y): #double tap self.legend_rv.doubletap(self.row_index) - + def MatplotlibInteractiveLegend(figure_wgt, legend_handles='auto', delay=None, @@ -593,7 +593,7 @@ def MatplotlibInteractiveLegend(figure_wgt, scatter=None, autoscale=False): """ transform matplotlib legend to interactive legend - + Args: figure_wgt: figure widget from kivy_matplotlib_widget package legend_handles : 'auto' (general purpose) or variante (ex: for seaborn legend) @@ -604,7 +604,7 @@ def MatplotlibInteractiveLegend(figure_wgt, multi_legend (bool): Set it True if you have multiple legend in graph """ - + #check if the figure has a legend if legend_instance is None: leg = figure_wgt.figure.axes[0].get_legend() @@ -612,26 +612,26 @@ def MatplotlibInteractiveLegend(figure_wgt, #create a defaut legend if no figure exist ax=figure_wgt.figure.axes[0] ax.legend() - leg = figure_wgt.figure.axes[0].get_legend() + leg = figure_wgt.figure.axes[0].get_legend() else: leg = legend_instance - + #put the legend on top (useful for drag behavior) leg.set_zorder(20) figure_wgt.figcanvas.draw() - + #detect is the legend use column (ex: horizontal legend) if hasattr(leg,'_ncols'): #matplotlib version >3.6 legend_ncol = leg._ncols else: legend_ncol = leg._ncol - + if legend_ncol>1: ncol=legend_ncol else: ncol=None - + # create_touch_legend(figure_wgt,leg,ncol,legend_handles,0) if delay is None: #no delay case @@ -659,7 +659,7 @@ def MatplotlibInteractiveLegend(figure_wgt, scatter, autoscale), delay) - + def create_touch_legend(figure_wgt, leg,ncol, @@ -671,26 +671,26 @@ def create_touch_legend(figure_wgt, current_handles_text, scatter, autoscale, - _): + _): """ create touch legend """ - + bbox = leg.get_window_extent() - + if legend_instance is None: ax=figure_wgt.figure.axes[0] else: ax=figure_wgt.figure.axes - legend_x0 = bbox.x0 - legend_y0 = bbox.y0 - legend_x1 = bbox.x1 - legend_y1 = bbox.y1 + legend_x0 = bbox.x0 + legend_y0 = bbox.y0 + legend_x1 = bbox.x1 + legend_y1 = bbox.y1 pos_x, pos_y = figure_wgt.pos if leg._get_loc() == 0: #location best. Need to fix the legend location loc_in_canvas = bbox.xmin, bbox.ymin loc_in_norm_axes = leg.parent.transAxes.inverted().transform_point(loc_in_canvas) - leg._loc = tuple(loc_in_norm_axes) + leg._loc = tuple(loc_in_norm_axes) #position for kivy widget x0_pos=int(legend_x0)+pos_x @@ -698,7 +698,7 @@ def create_touch_legend(figure_wgt, x1_pos=int(legend_x1)+pos_x y1_pos=int(legend_y1)+pos_y instance_dict = dict() - + if custom_handlers: current_handles=custom_handlers else: @@ -712,9 +712,9 @@ def create_touch_legend(figure_wgt, current_handles0, current_labels0 = current_ax.get_legend_handles_labels() current_handles+=current_handles0 current_labels+=current_labels0 - + nb_group=len(current_handles) - + if nb_group==0: print('no legend available') return @@ -723,7 +723,7 @@ def create_touch_legend(figure_wgt, have_title=False if leg.get_title().get_text(): have_title=True - + figure_wgt_as_legend=False if figure_wgt.legend_instance and not multi_legend: for current_legend in figure_wgt.legend_instance: @@ -732,10 +732,10 @@ def create_touch_legend(figure_wgt, figure_wgt_as_legend=True else: matplotlib_legend_box = MatplotlibLegendGrid() - + matplotlib_legend_box.prop=prop matplotlib_legend_box.autoscale=autoscale - + if prop: matplotlib_legend_box.facecolor = scatter.get_facecolor() matplotlib_legend_box.mysize=float(scatter.get_sizes()[0]) @@ -746,15 +746,15 @@ def create_touch_legend(figure_wgt, if ncol: #reorder legend handles to fit with grid layout orientation #note: all kivy grid layout orientation don't fit with matpotlib grid position - + matplotlib_legend_box.ids.box.cols=ncol - + m, n = ceil(nb_group/ncol), ncol - index_arr = np.pad(np.arange(nb_group).astype(float), (0, m*n - np.arange(nb_group).size), + index_arr = np.pad(np.arange(nb_group).astype(float), (0, m*n - np.arange(nb_group).size), mode='constant', constant_values=np.nan) - index_arr2 = np.pad(np.arange(nb_group).astype(float), (0, m*n - np.arange(nb_group).size), - mode='constant', constant_values=np.nan).reshape(m,n) - + index_arr2 = np.pad(np.arange(nb_group).astype(float), (0, m*n - np.arange(nb_group).size), + mode='constant', constant_values=np.nan).reshape(m,n) + i=0 for col in range(ncol): for row in range(m): @@ -768,42 +768,42 @@ def create_touch_legend(figure_wgt, else: matplotlib_legend_box.ids.box.cols=1 - + #update kivy widget attributes matplotlib_legend_box.x_pos = x0_pos matplotlib_legend_box.y_pos = y0_pos matplotlib_legend_box.legend_height = y1_pos-y0_pos if legend_instance: matplotlib_legend_box.legend_instance = legend_instance - + if have_title: - + #we create an offset for the matplotlib position if ncol: title_padding = (y1_pos-y0_pos)/(ceil(nb_group/ncol)+1) else: title_padding = (y1_pos-y0_pos)/(nb_group+1) - + matplotlib_legend_box.title_padding = title_padding matplotlib_legend_box.legend_height -= title_padding - + matplotlib_legend_box.legend_width = x1_pos-x0_pos - + if leg.get_patches()[:] and legend_handles=='variante': #sometime the legend handles not perfectly match with graph #in this case, this experimenta section try to fix this - ax_instances = ax.get_children() + ax_instances = ax.get_children() hist_instance=[] for current_inst in ax_instances: if isinstance(current_inst, mpl.spines.Spine): break hist_instance.append(current_inst) - - nb_instance_by_hist = len(hist_instance)//nb_group + + nb_instance_by_hist = len(hist_instance)//nb_group for i,leg_instance in enumerate(leg.get_patches()[:]): - instance_dict[leg_instance] = hist_instance[int(i*nb_instance_by_hist):int((i+1)*nb_instance_by_hist)] + instance_dict[leg_instance] = hist_instance[int(i*nb_instance_by_hist):int((i+1)*nb_instance_by_hist)] current_legend_cell = CellLegendMatplotlib() current_legend_cell.matplotlib_line = leg_instance current_legend_cell.matplotlib_text = leg.get_texts()[:][i] @@ -813,8 +813,8 @@ def create_touch_legend(figure_wgt, matplotlib_legend_box.box.add_widget(current_legend_cell) else: - ##general purpose interactive position - #get matplotlib text object + ##general purpose interactive position + #get matplotlib text object if hasattr(leg,'legend_handles'): #matplotlib>3.7 legeng_marker = leg.legend_handles @@ -836,19 +836,19 @@ def create_touch_legend(figure_wgt, else: current_legend_cell.height=int(matplotlib_legend_box.legend_height/max(nb_group,1)) current_legend_cell.width=matplotlib_legend_box.legend_width - + if isinstance(current_handles[i],list): - instance_dict[legeng_marker[i]] = current_handles[i] + instance_dict[legeng_marker[i]] = current_handles[i] else: instance_dict[legeng_marker[i]] = [current_handles[i]] current_legend_cell.matplotlib_line = legeng_marker[i] - + current_legend_cell.matplotlib_text = leg_instance current_legend_cell.matplotlib_legend_box=matplotlib_legend_box current_legend_cell.row_index=i - + #add cell in kivy widget - matplotlib_legend_box.box.add_widget(current_legend_cell) + matplotlib_legend_box.box.add_widget(current_legend_cell) #update some attributes matplotlib_legend_box.figure_wgt=figure_wgt @@ -858,22 +858,22 @@ def create_touch_legend(figure_wgt, if not figure_wgt_as_legend: figure_wgt.parent.add_widget(matplotlib_legend_box) figure_wgt.legend_instance.append(matplotlib_legend_box) - - + + class MatplotlibLegendGrid(FloatLayout): """ Touch egend kivy class""" figure_wgt= ObjectProperty(None) x_pos = NumericProperty(1) y_pos = NumericProperty(1) legend_height = NumericProperty(1) - legend_width = NumericProperty(1) - title_padding = NumericProperty(0) + legend_width = NumericProperty(1) + title_padding = NumericProperty(0) legend_instance = ObjectProperty(None,allow_none=True) prop = StringProperty(None,allow_none=True) autoscale=BooleanProperty(False) instance_dict=dict() - + def __init__(self, **kwargs): """ init class """ self.facecolor=[] @@ -882,8 +882,8 @@ def __init__(self, **kwargs): self.mysize_list=[] self.original_size=None self.current_handles_text=None - - super().__init__(**kwargs) + + super().__init__(**kwargs) def update_size(self): """ update size """ @@ -894,47 +894,47 @@ def update_size(self): leg = self.figure_wgt.figure.axes[0].get_legend() if leg: bbox = leg.get_window_extent() - - legend_x0 = bbox.x0 - legend_y0 = bbox.y0 - legend_x1 = bbox.x1 - legend_y1 = bbox.y1 + + legend_x0 = bbox.x0 + legend_y0 = bbox.y0 + legend_x1 = bbox.x1 + legend_y1 = bbox.y1 pos_x, pos_y = self.figure_wgt.pos x0_pos=int(legend_x0)+pos_x y0_pos=int(legend_y0)+pos_y x1_pos=int(legend_x1)+pos_x - y1_pos=int(legend_y1)+pos_y - + y1_pos=int(legend_y1)+pos_y + self.x_pos = x0_pos self.y_pos = y0_pos self.legend_height = y1_pos-y0_pos have_title=False if leg.get_title().get_text(): - have_title=True + have_title=True if have_title: if hasattr(leg,'legend_handles'): #matplotlib>3.7 current_handles = leg.legend_handles else: current_handles = leg.legendHandles - + nb_group=len(current_handles) - + if hasattr(leg,'_ncols'): #matplotlib version >3.6 legend_ncol = leg._ncols else: legend_ncol = leg._ncol - + if legend_ncol>1: ncol=legend_ncol else: - ncol=None + ncol=None if ncol: title_padding = (y1_pos-y0_pos)/(ceil(nb_group/ncol)+1) else: title_padding = (y1_pos-y0_pos)/(nb_group+1) - + self.title_padding = title_padding self.legend_height -= title_padding self.legend_width = x1_pos-x0_pos @@ -943,10 +943,10 @@ def reset_legend(self): """ reset_legend and clear all children """ self.x_pos = 1 self.y_pos = 1 - self.legend_height = 1 - self.legend_width = 1 - self.title_padding = 0 - self.box.clear_widgets() + self.legend_height = 1 + self.legend_width = 1 + self.title_padding = 0 + self.box.clear_widgets() def show_hide_wgt(self,row_index) -> None: if self.box.children[::-1][row_index].selected: @@ -954,7 +954,7 @@ def show_hide_wgt(self,row_index) -> None: self.box.children[::-1][row_index].selected = False self.box.children[::-1][row_index].matplotlib_line.set_alpha(1) self.box.children[::-1][row_index].matplotlib_text.set_alpha(1) - + hist = self.instance_dict[self.box.children[::-1][row_index].matplotlib_line] for current_hist in hist: self.set_visible(current_hist,True,row_index) @@ -965,13 +965,13 @@ def show_hide_wgt(self,row_index) -> None: self.figure_wgt.autoscale() else: self.figure_wgt.figure.canvas.draw_idle() - self.figure_wgt.figure.canvas.flush_events() + self.figure_wgt.figure.canvas.flush_events() else: #hide line self.box.children[::-1][row_index].selected = True self.box.children[::-1][row_index].matplotlib_line.set_alpha(0.5) self.box.children[::-1][row_index].matplotlib_text.set_alpha(0.5) - + hist = self.instance_dict[self.box.children[::-1][row_index].matplotlib_line] for current_hist in hist: self.set_visible(current_hist,False,row_index) @@ -979,18 +979,18 @@ def show_hide_wgt(self,row_index) -> None: if self.prop: self.prop_autoscale() else: - self.figure_wgt.autoscale() + self.figure_wgt.autoscale() else: self.figure_wgt.figure.canvas.draw_idle() self.figure_wgt.figure.canvas.flush_events() - - def doubletap(self,row_index) -> None: + + def doubletap(self,row_index) -> None: """ double tap behavior is based on plotly behavior """ if not self.box.children[::-1][row_index].selected: hist = self.instance_dict current_line = hist[self.box.children[::-1][row_index].matplotlib_line][0] - hist_keys= list(self.instance_dict.keys()) - + hist_keys= list(self.instance_dict.keys()) + #check if we isolate line or show all lines need_isolate = False # for line in hist_keys: @@ -998,14 +998,14 @@ def doubletap(self,row_index) -> None: if hist[line][0] != current_line: if not self.box.children[::-1][idx].selected: need_isolate=True - break - + break + if need_isolate: #isolate line' for idx,line in enumerate(hist_keys): if hist[line][0] != current_line: for current_hist in hist[line]: - self.set_visible(current_hist,False,idx) + self.set_visible(current_hist,False,idx) self.box.children[::-1][idx].selected = True self.box.children[::-1][idx].matplotlib_line.set_alpha(0.5) self.box.children[::-1][idx].matplotlib_text.set_alpha(0.5) @@ -1015,48 +1015,48 @@ def doubletap(self,row_index) -> None: self.box.children[::-1][idx].matplotlib_text.set_alpha(1) else: #show all lines' - for idx,line in enumerate(hist_keys): + for idx,line in enumerate(hist_keys): for current_hist in hist[line]: self.set_visible(current_hist,True,idx) - self.box.children[::-1][idx].selected = False - self.box.children[::-1][idx].matplotlib_line.set_alpha(1) - self.box.children[::-1][idx].matplotlib_text.set_alpha(1) + self.box.children[::-1][idx].selected = False + self.box.children[::-1][idx].matplotlib_line.set_alpha(1) + self.box.children[::-1][idx].matplotlib_text.set_alpha(1) else: hist = self.instance_dict #show all lines - for idx,line in enumerate(hist): + for idx,line in enumerate(hist): for current_hist in hist[line]: - self.set_visible(current_hist,True,idx) - self.box.children[::-1][idx].selected = False - self.box.children[::-1][idx].matplotlib_line.set_alpha(1) - self.box.children[::-1][idx].matplotlib_text.set_alpha(1) + self.set_visible(current_hist,True,idx) + self.box.children[::-1][idx].selected = False + self.box.children[::-1][idx].matplotlib_line.set_alpha(1) + self.box.children[::-1][idx].matplotlib_text.set_alpha(1) if self.autoscale and hasattr(self.figure_wgt,'autoscale_axis'): if self.prop: self.prop_autoscale() - self.figure_wgt.autoscale() - else: - self.figure_wgt.autoscale() - else: + self.figure_wgt.autoscale() + else: + self.figure_wgt.autoscale() + else: self.figure_wgt.figure.canvas.draw_idle() - self.figure_wgt.figure.canvas.flush_events() + self.figure_wgt.figure.canvas.flush_events() def prop_autoscale(self) -> None: """set autoscale hen use prop - + Args: None - + Returns: None - """ + """ ax=self.scatter.axes if self.prop=='colors': if not self.facecolor.any(): ax.figure.canvas.draw_idle() ax.figure.canvas.flush_events() - return - + return + if isinstance(self.mysize_list,list): mysize_list = self.mysize_list else: @@ -1064,31 +1064,31 @@ def prop_autoscale(self) -> None: if mysize_list and not all(x == 0.0 for x in mysize_list): - + x,y = self.scatter.get_offsets().T indices_nozero = [i for i, v in enumerate(mysize_list) if v != 0.0] if len(indices_nozero)==0: ax.set_autoscale_on(False) - + ax.figure.canvas.draw_idle() ax.figure.canvas.flush_events() return - else: + else: xmin = np.min(x[indices_nozero]) xmax = np.max(x[indices_nozero]) ymin = np.min(y[indices_nozero]) ymax = np.max(y[indices_nozero]) - + autoscale_axis = self.figure_wgt.autoscale_axis no_visible = self.figure_wgt.myrelim(ax,visible_only=self.figure_wgt.autoscale_visible_only) ax.autoscale_view(tight=self.figure_wgt.autoscale_tight, - scalex=True if autoscale_axis!="y" else False, + scalex=True if autoscale_axis!="y" else False, scaley=True if autoscale_axis!="x" else False) - ax.autoscale(axis=autoscale_axis,tight=self.figure_wgt.autoscale_tight) - + ax.autoscale(axis=autoscale_axis,tight=self.figure_wgt.autoscale_tight) + current_xlim = ax.get_xlim() - current_ylim = ax.get_ylim() + current_ylim = ax.get_ylim() invert_xaxis = False invert_yaxis = False @@ -1099,14 +1099,14 @@ def prop_autoscale(self) -> None: else: xleft = xmin xright = xmax - + if ax.yaxis_inverted(): - invert_yaxis=True + invert_yaxis=True ybottom = ymax ytop = ymin else: ybottom = ymin - ytop = ymax + ytop = ymax lim_collection = [xleft,ybottom,xright,ytop] @@ -1116,7 +1116,7 @@ def prop_autoscale(self) -> None: current_margins = (0,0) else: current_margins = ax.margins() - + if self.figure_wgt.autoscale_axis!="y": if invert_xaxis: if lim_collection[0]>current_xlim[0] or no_visible: @@ -1124,7 +1124,7 @@ def prop_autoscale(self) -> None: xchanged=True if lim_collection[2] None: if lim_collection[2]>current_xlim[1] or no_visible: ax.set_xlim(right=lim_collection[2]) xchanged=True - + #recalculed margin if xchanged: xlim = ax.get_xlim() ax.set_xlim(left=xlim[0] - current_margins[0]*(xlim[1]-xlim[0])) ax.set_xlim(right=xlim[1] + current_margins[0]*(xlim[1]-xlim[0])) - - ychanged=False - + + ychanged=False + if self.figure_wgt.autoscale_axis!="x": if invert_yaxis: if lim_collection[1]>current_ylim[0] or no_visible: ax.set_ylim(bottom=lim_collection[1]) ychanged=True if lim_collection[3]current_ylim[1] or no_visible: - ax.set_ylim(top=lim_collection[3]) + ax.set_ylim(top=lim_collection[3]) ychanged=True - + if ychanged: ylim = ax.get_ylim() ax.set_ylim(bottom=ylim[0] - current_margins[1]*(ylim[1]-ylim[0])) - ax.set_ylim(top=ylim[1] + current_margins[1]*(ylim[1]-ylim[0])) - + ax.set_ylim(top=ylim[1] + current_margins[1]*(ylim[1]-ylim[0])) + index = self.figure_wgt.figure.axes.index(ax) self.figure_wgt.xmin[index],self.figure_wgt.xmax[index] = ax.get_xlim() self.figure_wgt.ymin[index],self.figure_wgt.ymax[index] = ax.get_ylim() ax.set_autoscale_on(False) - + ax.figure.canvas.draw_idle() ax.figure.canvas.flush_events() - + def set_visible(self,instance,value,row_index=None) -> None: """set visible method - + Args: instance: matplotlib instance value: True or False - + Returns: None - """ + """ if self.prop: if self.prop=='colors': if self.facecolor.any(): unique_color = np.unique(self.facecolor,axis=0) ncol = np.shape(unique_color)[0] - + if not self.mysize_list: - + self.mysize_list =[self.mysize] * np.shape(self.facecolor)[0] for i in range(np.shape(self.facecolor)[0]): if (self.facecolor[i,:] == unique_color[row_index,:]).all(): if value: self.mysize_list[i] = self.mysize else: - self.mysize_list[i] = 0.0 + self.mysize_list[i] = 0.0 # else: # if value: # mysize_list.append(0.0) # else: # mysize_list.append(self.mysize) - + # self.figure_wgt.figure.axes[0].get_children()[0].set_facecolor(newfacecolor) self.scatter.set_sizes(self.mysize_list) if self.prop=='sizes': if self.mysize: legend_size = len(self.current_handles_text) legend_value = self.current_handles_text[row_index] - + float_legend_value_before=None if row_index!=0: legend_value_before = self.current_handles_text[row_index-1] match_legend_value_before = re.search(r"\{(.+?)\}", legend_value_before) - if match_legend_value_before: + if match_legend_value_before: float_legend_value_before = float(match_legend_value_before.group(1)) else: - return - + return + match_legend_value = re.search(r"\{(.+?)\}", legend_value) - if match_legend_value: + if match_legend_value: float_legend_value = float(match_legend_value.group(1)) else: return - + if isinstance(self.mysize_list,list) and len(self.mysize_list)==0: - + self.mysize_list = copy.copy(self.original_size) - + if row_index==0: index_match=np.where(float_legend_value>=self.original_size)[0] elif row_index==legend_size-1: index_match=np.where(float_legend_value_before<=self.original_size)[0] else: - index_match=np.where((float_legend_value>=self.original_size) & + index_match=np.where((float_legend_value>=self.original_size) & (float_legend_value_before<=self.original_size))[0] if value: self.mysize_list[index_match] = self.original_size[index_match] else: - self.mysize_list[index_match] = 0.0 + self.mysize_list[index_match] = 0.0 self.scatter.set_sizes(self.mysize_list) - + else: if hasattr(instance,'set_visible'): instance.set_visible(value) @@ -1251,13 +1251,13 @@ def set_visible(self,instance,value,row_index=None) -> None: def get_visible(self,instance) -> bool: """get visible method - + Args: instance: matplotlib instance - + Returns: bool - """ + """ if hasattr(instance,'get_visible'): return instance.get_visible() elif hasattr(instance,'get_children'): @@ -1268,7 +1268,7 @@ def get_visible(self,instance) -> bool: return return_value else: return False - + from kivy.factory import Factory Factory.register('LegendRv', LegendRv) @@ -1277,60 +1277,60 @@ def get_visible(self,instance) -> bool: canvas.before: Color: - rgba: root.background_color + rgba: root.background_color Rectangle: pos: self.pos - size: self.size - + size: self.size + RecycleView: id:view viewclass: "CellLegend" - size_hint_y:1 + size_hint_y:1 data: root.data scroll_timeout:5000 effect_cls: "ScrollEffect" - + RecycleBoxLayout: default_size_hint: 1, None default_size: None, None - id:body + id:body orientation: 'vertical' size_hint_y:None height:self.minimum_height - + canvas.before: Color: - rgba: root.background_color + rgba: root.background_color Rectangle: pos: self.pos - size: self.size - + size: self.size + RecycleView: id:view viewclass: "CellLegendHorizontal" - size_hint_y:1 + size_hint_y:1 data: root.data scroll_timeout:5000 effect_cls: "ScrollEffect" - + RecycleBoxLayout: default_size_hint: None, 1 default_size: None, None - id:body + id:body orientation: 'horizontal' size_hint_y:1 size_hint_x:None width: self.minimum_width - + box:box size_hint: None,None - width: dp(0.01) - height: dp(0.01) - - GridLayout: - id:box + width: dp(0.01) + height: dp(0.01) + + GridLayout: + id:box x:root.x_pos y:root.y_pos padding:0,root.title_padding,0,0 @@ -1341,22 +1341,22 @@ def get_visible(self,instance) -> bool: opacity: 0.5 if self.selected else 1 size_hint_y: None - height: dp(48) + height: dp(48) size_hint_x: None - width: dp(48) - - + width: dp(48) + + mycolor: [1,0,0] line_type:'-' opacity: 0.5 if self.selected else 1 size_hint_y: None - height: root.box_height + height: root.box_height size_hint_x:None width:dp(78) + line_label.texture_size[0] - + Widget: size_hint_x:None - width:dp(18) + width:dp(18) BoxLayout: size_hint_x:None @@ -1372,7 +1372,7 @@ def get_visible(self,instance) -> bool: Line: width: dp(2.5) cap:'square' - points: + points: self.pos[0], \ self.pos[1]+self.height/2, \ self.pos[0]+self. \ @@ -1384,7 +1384,7 @@ def get_visible(self,instance) -> bool: Line: width: dp(2.5) cap:'square' - points: + points: self.pos[0], \ self.pos[1]+self.height/2, \ self.pos[0]+self.width*0.4, \ @@ -1396,7 +1396,7 @@ def get_visible(self,instance) -> bool: self.pos[0]+self.width*0.6, \ self.pos[1]+self.height/2, \ self.pos[0]+self.width, \ - self.pos[1]+self.height/2 + self.pos[1]+self.height/2 Color: rgb:root.mycolor @@ -1404,51 +1404,51 @@ def get_visible(self,instance) -> bool: Line: width: dp(2.5) cap:'square' - points: + points: self.pos[0], \ self.pos[1]+self.height/2, \ self.pos[0]+self.width*0.3, \ self.pos[1]+self.height/2 Ellipse: size: (dp(5),dp(5)) - pos: + pos: (self.pos[0]+self.width*0.5-dp(5/2), \ self.pos[1]+self.height/2-dp(5/2)) Line: width: dp(2.5) cap:'square' - points: + points: self.pos[0]+self.width*0.7, \ self.pos[1]+self.height/2, \ self.pos[0]+self.width, \ - self.pos[1]+self.height/2 + self.pos[1]+self.height/2 Color: - rgb:root.mycolor + rgb:root.mycolor a:self.opacity if root.line_type==':' else 0 Ellipse: size: (dp(5),dp(5)) - pos: + pos: self.pos[0]-dp(5/2), \ self.pos[1]+self.height/2-dp(5/2) Ellipse: size: (dp(5),dp(5)) - pos: + pos: (self.pos[0]+self.width*0.5-dp(5/2), \ self.pos[1]+self.height/2-dp(5/2)) Ellipse: size: (dp(5),dp(5)) - pos: + pos: self.pos[0]+self.width-dp(5/2), \ - self.pos[1]+self.height/2-dp(5/2) + self.pos[1]+self.height/2-dp(5/2) Widget: size_hint_x:None - width:dp(18) + width:dp(18) - BoxLayout: + BoxLayout: size_hint_x:None width:line_label.texture_size[0] Label: @@ -1459,21 +1459,21 @@ def get_visible(self,instance) -> bool: color:root.text_color font_size: root.text_font_size font_name:root.text_font - + Widget: size_hint_x:None width:dp(4) - - + + mycolor: [1,0,0] line_type:'-' opacity: 0.5 if self.selected else 1 size_hint_y: None height: root.box_height - + Widget: size_hint_x:None - width:dp(4) + width:dp(4) BoxLayout: size_hint_x:None @@ -1489,7 +1489,7 @@ def get_visible(self,instance) -> bool: Line: width: dp(2.5) cap:'square' - points: + points: self.pos[0], \ self.pos[1]+self.height/2, \ self.pos[0]+self. \ @@ -1501,7 +1501,7 @@ def get_visible(self,instance) -> bool: Line: width: dp(2.5) cap:'square' - points: + points: self.pos[0], \ self.pos[1]+self.height/2, \ self.pos[0]+self.width*0.4, \ @@ -1513,7 +1513,7 @@ def get_visible(self,instance) -> bool: self.pos[0]+self.width*0.6, \ self.pos[1]+self.height/2, \ self.pos[0]+self.width, \ - self.pos[1]+self.height/2 + self.pos[1]+self.height/2 Color: rgb:root.mycolor @@ -1521,50 +1521,50 @@ def get_visible(self,instance) -> bool: Line: width: dp(2.5) cap:'square' - points: + points: self.pos[0], \ self.pos[1]+self.height/2, \ self.pos[0]+self.width*0.3, \ self.pos[1]+self.height/2 Ellipse: size: (dp(5),dp(5)) - pos: + pos: (self.pos[0]+self.width*0.5-dp(5/2), \ self.pos[1]+self.height/2-dp(5/2)) Line: width: dp(2.5) cap:'square' - points: + points: self.pos[0]+self.width*0.7, \ self.pos[1]+self.height/2, \ self.pos[0]+self.width, \ - self.pos[1]+self.height/2 + self.pos[1]+self.height/2 Color: - rgb:root.mycolor + rgb:root.mycolor a:self.opacity if root.line_type==':' else 0 Ellipse: size: (dp(5),dp(5)) - pos: + pos: self.pos[0]-dp(5/2), \ self.pos[1]+self.height/2-dp(5/2) Ellipse: size: (dp(5),dp(5)) - pos: + pos: (self.pos[0]+self.width*0.5-dp(5/2), \ self.pos[1]+self.height/2-dp(5/2)) Ellipse: size: (dp(5),dp(5)) - pos: + pos: self.pos[0]+self.width-dp(5/2), \ - self.pos[1]+self.height/2-dp(5/2) + self.pos[1]+self.height/2-dp(5/2) Widget: size_hint_x:None - width:dp(18) - + width:dp(18) + Label: text:root.text text_size:self.size diff --git a/kivy_matplotlib_widget/uix/minmax_widget.py b/kivy_matplotlib_widget/uix/minmax_widget.py index da05f6a..f703d1b 100644 --- a/kivy_matplotlib_widget/uix/minmax_widget.py +++ b/kivy_matplotlib_widget/uix/minmax_widget.py @@ -10,12 +10,12 @@ DictProperty ) -from kivy.lang import Builder +from kivy.lang import Builder from kivy.metrics import dp from kivy.clock import Clock from kivy.core import text as coretext - + def add_minmax(figure_wgt, xaxis_formatter = None, invert_xaxis_formatter = None, @@ -23,34 +23,34 @@ def add_minmax(figure_wgt, invert_yaxis_formatter = None): if hasattr(figure_wgt,'text_instance'): - if figure_wgt.text_instance is None: + if figure_wgt.text_instance is None: text_widget = TextBox() else: text_widget = figure_wgt.text_instance - + text_widget.figure_wgt = figure_wgt text_widget.xaxis_formatter = xaxis_formatter text_widget.invert_xaxis_formatter = invert_xaxis_formatter text_widget.yaxis_formatter = yaxis_formatter - text_widget.invert_yaxis_formatter = invert_yaxis_formatter - + text_widget.invert_yaxis_formatter = invert_yaxis_formatter + text_widget.x_text_pos=figure_wgt.x - text_widget.y_text_pos=figure_wgt.y - + text_widget.y_text_pos=figure_wgt.y + if figure_wgt.text_instance is None: figure_wgt.parent.add_widget(text_widget) figure_wgt.text_instance=text_widget - - + + class BaseTextFloatLayout(FloatLayout): """ Touch egend kivy class""" figure_wgt = ObjectProperty(None) current_axis = ObjectProperty(None) - kind = DictProperty({'axis':'x','anchor':'right'}) + kind = DictProperty({'axis':'x','anchor':'right'}) x_text_pos = NumericProperty(10) - y_text_pos = NumericProperty(10) + y_text_pos = NumericProperty(10) show_text = BooleanProperty(False) - + def __init__(self, **kwargs): """ init class """ super().__init__(**kwargs) @@ -61,7 +61,7 @@ def reset_text(self): self.y_text_pos = 1 class TextBox(BaseTextFloatLayout): - """ text box widget """ + """ text box widget """ text_color=ColorProperty([0,0,0,1]) text_font=StringProperty("Roboto") text_size = NumericProperty(dp(14)) @@ -74,11 +74,11 @@ class TextBox(BaseTextFloatLayout): yaxis_formatter = None invert_yaxis_formatter = None current_text = "" - + def __init__(self, **kwargs): """ init class """ - super().__init__(**kwargs) - + super().__init__(**kwargs) + def on_axis_validation(self,instance) -> bool: """Called to validate axis value. @@ -96,9 +96,9 @@ def on_axis_validation(self,instance) -> bool: if self.kind.get('axis') == 'x' and self.invert_xaxis_formatter: number = float(self.invert_xaxis_formatter(instance.text)) elif self.kind.get('axis') == 'y' and self.invert_yaxis_formatter: - number = float(self.invert_yaxis_formatter(instance.text)) + number = float(self.invert_yaxis_formatter(instance.text)) else: - number = float(instance.text) + number = float(instance.text) except ValueError: return False return True @@ -109,7 +109,7 @@ def on_set_axis(self,instance): Args: instance (widget) : kivy widget object - """ + """ if not instance.focused and \ self.on_axis_validation(instance) and \ self.current_text!=instance.text: @@ -118,41 +118,41 @@ def on_set_axis(self,instance): if self.kind.get('axis') == 'x' and self.invert_xaxis_formatter: number = float(self.invert_xaxis_formatter(instance.text)) elif self.kind.get('axis') == 'y' and self.invert_yaxis_formatter: - number = float(self.invert_yaxis_formatter(instance.text)) + number = float(self.invert_yaxis_formatter(instance.text)) else: - number = float(instance.text) + number = float(instance.text) if kind.get('axis') == 'x': if kind.get('anchor') == 'left': self.current_axis.set_xlim((number,None)) elif kind.get('anchor') == 'right': - self.current_axis.set_xlim((None,number)) + self.current_axis.set_xlim((None,number)) elif kind.get('axis') == 'y': if kind.get('anchor') == 'bottom': self.current_axis.set_ylim((number,None)) elif kind.get('anchor') == 'top': - self.current_axis.set_ylim((None,number)) + self.current_axis.set_ylim((None,number)) self.current_axis.figure.canvas.draw_idle() - self.current_axis.figure.canvas.flush_events() + self.current_axis.figure.canvas.flush_events() self.show_text=False def autofocus_text(self,*args) -> None: """ auto focus text input - + Returns: None """ Clock.schedule_once(self.scheduled_autofocus_text,0.2) - + def scheduled_autofocus_text(self,*args): - self.ids.text_input.focus = True + self.ids.text_input.focus = True Clock.schedule_once(self.select_all_text,0.2) - def select_all_text(self,*args): + def select_all_text(self,*args): self.ids.text_input.select_all() - + def on_show_text(self,instance,val): if val: kind = self.kind @@ -164,16 +164,16 @@ def on_show_text(self,instance,val): else self.current_axis.xaxis.get_major_formatter().format_data_short) else: axis_formatter = self.xaxis_formatter - + if kind.get('anchor') == 'left': #u"\u2212" is to manage unicode minus self.ids.text_input.text=f"{axis_formatter(xlim[0])}".replace(u"\u2212","-") - + elif kind.get('anchor') == 'right': #u"\u2212" is to manage unicode minus self.ids.text_input.text=f"{axis_formatter(xlim[1])}".replace(u"\u2212","-") self.current_value = self.ids.text_input.text - + elif kind.get('axis') == 'y': ylim=self.current_axis.get_ylim() if self.yaxis_formatter is None: @@ -197,28 +197,28 @@ class CustomTextInput(TextInput): """ text_width = NumericProperty(100) - + '''The text width ''' - + text_box_instance = figure_wgt = ObjectProperty(None) textcenter=BooleanProperty(True) - + def __init__(self, *args, **kwargs): super(CustomTextInput, self).__init__(*args, **kwargs) self.bind(focus=self.on_focus_text) - + def update_textbox(self, *args): ''' Update the text box from text width ''' if self.text: - + try: string = self.text text_texture_width = coretext.Label(font_size=self.font_size).get_extents(string)[0] - + except: print('get text width failed') else: @@ -234,18 +234,18 @@ def on_focus_text(self, instance,value): pass else: #User defocused - self.text_box_instance.show_text=False - - + self.text_box_instance.show_text=False + + Builder.load_string(''' size_hint: None,None - width: dp(0.01) + width: dp(0.01) height: dp(0.01) opacity:1 if root.show_text else 0 - + BoxLayout: x: @@ -254,32 +254,32 @@ def on_focus_text(self, instance,value): root.y_text_pos - dp(3) size_hint: None, None height: root.text_height - width: + width: root.text_width if root.show_text \ - else dp(0.0001) - - canvas: + else dp(0.0001) + + canvas: Color: rgba: root.background_color Rectangle: pos: self.pos - size: self.size + size: self.size CustomTextInput: - id:text_input + id:text_input text_box_instance:root font_size:root.text_size color: root.text_color font_name : root.text_font last_good_entry:None - on_text_validate: - root.on_axis_validation(self) - on_focus: + on_text_validate: + root.on_axis_validation(self) + on_focus: root.on_set_axis(self) foreground_color: (0,0,0,1) background_color: (0,0,0,0) font_size:dp(14) - on_text: root.update_textbox() - multiline:False + on_text: root.update_textbox() + multiline:False ''') diff --git a/kivy_matplotlib_widget/uix/navigation_bar_widget.py b/kivy_matplotlib_widget/uix/navigation_bar_widget.py index 5cb7247..ce72552 100644 --- a/kivy_matplotlib_widget/uix/navigation_bar_widget.py +++ b/kivy_matplotlib_widget/uix/navigation_bar_widget.py @@ -7,7 +7,7 @@ from kivy.factory import Factory from kivy_matplotlib_widget.uix.hover_widget import add_hover from kivy.core.window import Window -from kivy.metrics import dp +from kivy.metrics import dp class MatplotNavToolbar(BoxLayout): @@ -39,7 +39,7 @@ class _NavigationToolbar(NavigationToolbar2): def __init__(self, canvas, widget): self.widget = widget super(_NavigationToolbar, self).__init__(canvas) - + def on_kv_post(self): if self.figure_widget: self._init_toolbar() @@ -48,7 +48,7 @@ def _init_toolbar(self): print('init toolbar') self.widget.home_btn.bind(on_press=self.home) self.widget.pan_btn.bind(on_press=self.pan) - self.widget.zoom_btn.bind(on_press=self.zoom) + self.widget.zoom_btn.bind(on_press=self.zoom) self.widget.back_btn.bind(on_press=self.back) self.widget.forward_btn.bind(on_press=self.forward) @@ -78,8 +78,8 @@ class KivyMatplotNavToolbar(RelativeLayout): nav_icon = OptionProperty( "normal", options=["minimal", "normal","all","3D","custom"]) hover_mode = OptionProperty( - "touch", options=["touch", "desktop"]) - + "touch", options=["touch", "desktop"]) + custom_icon = ListProperty([]) show_cursor_data = BooleanProperty(False) cursor_data_font_size = NumericProperty(dp(12)) @@ -92,7 +92,7 @@ class KivyMatplotNavToolbar(RelativeLayout): def __init__(self, figure_wgt=None, *args, **kwargs): super(KivyMatplotNavToolbar, self).__init__(*args, **kwargs) self.figure_wgt = figure_wgt - + def on_kv_post(self,_): if self.nav_icon=="minimal" and not self.custom_icon: @@ -120,13 +120,13 @@ def on_kv_post(self,_): #nearest hover button self.add_nav_btn("hover",self.change_hover_type,mode='nearest',btn_type='hover_type') #compare hover button - self.add_nav_btn("hover_compare",self.change_hover_type,mode='compare',btn_type='hover_type') - + self.add_nav_btn("hover_compare",self.change_hover_type,mode='compare',btn_type='hover_type') + elif self.compare_hover: #nearest hover button self.add_nav_btn("hover",self.change_hover_type,mode='nearest',btn_type='hover_type') #compare hover button - self.add_nav_btn("hover_compare",self.change_hover_type,mode='compare',btn_type='hover_type') + self.add_nav_btn("hover_compare",self.change_hover_type,mode='compare',btn_type='hover_type') elif self.nav_icon=="all" and not self.custom_icon: @@ -137,8 +137,8 @@ def on_kv_post(self,_): self.add_nav_btn("back",self.back) #home button - self.add_nav_btn("forward",self.forward) - + self.add_nav_btn("forward",self.forward) + #pan button self.add_nav_btn("pan",self.set_touch_mode,mode='pan',btn_type='group') @@ -153,46 +153,46 @@ def on_kv_post(self,_): #nearest hover button self.add_nav_btn("hover",self.change_hover_type,mode='nearest',btn_type='group') #compare hover button - self.add_nav_btn("hover_compare",self.change_hover_type,mode='compare',btn_type='group') - + self.add_nav_btn("hover_compare",self.change_hover_type,mode='compare',btn_type='group') + elif self.compare_hover: #nearest hover button self.add_nav_btn("hover",self.change_hover_type,mode='nearest',btn_type='group') #compare hover button - self.add_nav_btn("hover_compare",self.change_hover_type,mode='compare',btn_type='group') + self.add_nav_btn("hover_compare",self.change_hover_type,mode='compare',btn_type='group') #minmax button self.add_nav_btn("minmax",self.set_touch_mode,mode='minmax',btn_type='group') #home button self.add_nav_btn("autoscale",self.autoscale) - + #add_drag_legend if self.drag_legend: self.add_nav_btn("drag_legend",self.set_touch_mode,mode='drag_legend',btn_type='group') elif self.custom_icon: pass - + if self.nav_icon=="3D" and not self.custom_icon: #home button - self.add_nav_btn("home",self.home_3D) + self.add_nav_btn("home",self.home_3D) #data pan/zoom button - self.add_nav_btn("axis-z-rotate-clockwise",self.set_touch_mode_3D,mode='rotate',btn_type='group') - + self.add_nav_btn("axis-z-rotate-clockwise",self.set_touch_mode_3D,mode='rotate',btn_type='group') + #data pan/zoom button - self.add_nav_btn("pan",self.set_touch_mode_3D,mode='pan',btn_type='group') - + self.add_nav_btn("pan",self.set_touch_mode_3D,mode='pan',btn_type='group') + #figure pan/zoom button - self.add_nav_btn("magnify",self.set_touch_mode_3D,mode='figure_zoom_pan',btn_type='group') + self.add_nav_btn("magnify",self.set_touch_mode_3D,mode='figure_zoom_pan',btn_type='group') #cursor button self.add_nav_btn("cursor",self.set_touch_mode_3D,mode='cursor',btn_type='group') - + if self.show_cursor_data: - Window.bind(mouse_pos=self.on_motion) + Window.bind(mouse_pos=self.on_motion) def on_motion(self,*args): '''Kivy Event to trigger mouse event on motion @@ -205,14 +205,14 @@ def on_motion(self,*args): x = newcoord[0] y = newcoord[1] inside = self.figure_wgt.collide_point(x,y) - if inside: + if inside: # will receive all motion events. if self.figure_wgt.figcanvas: #avoid in motion if touch is detected if not len(self.figure_wgt._touches)==0: return - + #transform kivy x,y touch event to x,y data x_format,y_format=self.figure_wgt.get_data_xy(x,y) @@ -220,23 +220,23 @@ def on_motion(self,*args): self.current_label.text=f"{x_format}\n{y_format}" else: self.current_label.text="" - + def add_nav_btn(self,icon,fct,mode=None,btn_type=None): if btn_type=='group': if 'hover' in icon: btn = Factory.NavToggleButton(group= "hover_type") if self.hover_mode=="touch": btn.bind(on_release=lambda x:self.set_touch_mode('cursor')) - + elif mode == 'nearest': btn.state='down' - - + + else: btn = Factory.NavToggleButton(group= "toolbar_btn") btn.orientation_type=self.orientation_type else: - btn = Factory.NavButton() + btn = Factory.NavButton() btn.orientation_type=self.orientation_type btn.icon = icon btn.icon_font_size=self.icon_font_size @@ -245,16 +245,16 @@ def add_nav_btn(self,icon,fct,mode=None,btn_type=None): btn.bind(on_release=lambda x:fct(mode)) else: btn.bind(on_release=lambda x:fct()) - + if self.nav_icon!="3D" and mode == 'pan': #by default pan button is press down btn.state='down' - + if self.nav_icon=="3D" and mode =='rotate': btn.state='down' - self.ids.container.add_widget(btn) - + self.ids.container.add_widget(btn) + def set_touch_mode(self,mode): self.figure_wgt.touch_mode=mode def home(self): @@ -263,12 +263,12 @@ def home(self): else: self.figure_wgt.home() def back(self): - self.figure_wgt.back() + self.figure_wgt.back() def forward(self): - self.figure_wgt.forward() + self.figure_wgt.forward() def autoscale(self): - self.figure_wgt.autoscale() - + self.figure_wgt.autoscale() + def change_hover_type(self,hover_type): add_hover(self.figure_wgt,mode=self.hover_mode,hover_type=hover_type) @@ -280,8 +280,8 @@ def home_3D(self): Factory.register('MatplotNavToolbar', MatplotNavToolbar) Builder.load_string(''' -#:import nav_icons kivy_matplotlib_widget.icon_definitions.nav_icons - +#:import nav_icons kivy_matplotlib_widget.icon_definitions.nav_icons + : orientation: 'vertical' home_btn: home_btn @@ -292,7 +292,7 @@ def home_3D(self): forward_btn: forward_btn Label: id: info_lbl - size_hint: 1, 0.3 + size_hint: 1, 0.3 BoxLayout: size_hint: 1, 0.7 Button: @@ -304,7 +304,7 @@ def home_3D(self): font_name:"NavigationIcons" Button: id: forward_btn - text: "Forward" + text: "Forward" font_name:"NavigationIcons" ToggleButton: id: pan_btn @@ -316,7 +316,7 @@ def home_3D(self): text: "Zoom" group: "toolbar_btn" font_name:"NavigationIcons" - + : current_label: info_lbl2 if root.orientation_type=='rail' else info_lbl info_lbl: info_lbl @@ -333,12 +333,12 @@ def home_3D(self): rgba: (1, 1, 1, 1) Rectangle: pos: self.pos - size: self.size + size: self.size BoxLayout: size_hint_y: None size_hint_x: None if root.orientation_type=='rail' else 1 - height: "0.01dp" if root.orientation_type=='rail'else root.nav_btn_size - width:root.nav_btn_size + height: "0.01dp" if root.orientation_type=='rail'else root.nav_btn_size + width:root.nav_btn_size Label: id: info_lbl color: 0,0,0,1 @@ -352,7 +352,7 @@ def home_3D(self): height: root.nav_btn_size*len(self.children) if root.orientation_type=='rail'else root.nav_btn_size width: root.nav_btn_size if root.orientation_type=='rail'else root.nav_btn_size*len(self.children) - BoxLayout: + BoxLayout: size_hint_y: 1 if root.orientation_type=='rail' else None size_hint_x: None height:root.nav_btn_size @@ -362,7 +362,7 @@ def home_3D(self): color: 0,0,0,1 font_size: root.cursor_data_font_size font_name:root.cursor_data_font - + icon:"pan" @@ -376,12 +376,12 @@ def home_3D(self): color: 0,0,0,1 if self.state=='down' else 0.38 size_hint:1 if root.orientation_type=='rail' else None,None if root.orientation_type=='rail' else 1 height: root.nav_btn_size - width:root.nav_btn_size + width:root.nav_btn_size background_normal: '' background_down: '' - background_color :1,1,1,1 + background_color :1,1,1,1 on_release: - self.state='down' + self.state='down' icon:"home" @@ -394,7 +394,7 @@ def home_3D(self): font_size:root.icon_font_size color: 0,0,0,1 height: root.nav_btn_size - width:root.nav_btn_size + width:root.nav_btn_size background_normal: '' background_color :1,1,1,1 diff --git a/kivy_matplotlib_widget/uix/selector_widget.py b/kivy_matplotlib_widget/uix/selector_widget.py index 46f1f2e..3b4ccd2 100644 --- a/kivy_matplotlib_widget/uix/selector_widget.py +++ b/kivy_matplotlib_widget/uix/selector_widget.py @@ -29,14 +29,14 @@ kv = ''' -: +: canvas.before: # TOP LINE Color: rgba: root.top_color Line: width: dp(1) - points: + points: (self.x + dp(7), self.top, self.right - dp(7), self.top) if root.alpha \ else (self.x, self.top, self.right, self.top)# Top line cap: 'round' @@ -50,7 +50,7 @@ rgba: root.bottom_color Line: width: dp(1) - points: + points: (self.x + dp(7), self.y, self.right - dp(7), self.y) if root.alpha \ else (self.x, self.y, self.right, self.y)# Bottom cap: 'round' @@ -64,8 +64,8 @@ rgba: root.left_color Line: width: dp(1) - points: - (self.x, self.y+dp(7), self.x, self.top - dp(7)) if root.alpha \ + points: + (self.x, self.y+dp(7), self.x, self.top - dp(7)) if root.alpha \ else (self.x, self.y, self.x, self.top)# Left cap: 'round' joint: 'round' @@ -78,7 +78,7 @@ rgba: root.right_color Line: width: dp(1) - points: + points: (self.right, self.y + dp(7), self.right, self.top - dp(7)) if root.alpha \ else (self.right, self.y, self.right, self.top)# Right cap: 'round' @@ -97,7 +97,7 @@ rgba: [0,0,0,root.alpha] if (root.bg_color[0]*0.299 + \ root.bg_color[1]*0.587 + root.bg_color[2]*0.114) > 186/255 \ - else [1,1,1,root.alpha] + else [1,1,1,root.alpha] Line: width: dp(1) points: ( \ @@ -119,7 +119,7 @@ rgba: [0,0,0,root.alpha] if (root.bg_color[0]*0.299 + \ root.bg_color[1]*0.587 + root.bg_color[2]*0.114) > 186/255 \ - else [1,1,1,root.alpha] + else [1,1,1,root.alpha] Line: width: dp(1) points: ( \ @@ -141,7 +141,7 @@ rgba: [0,0,0,root.alpha] if (root.bg_color[0]*0.299 + \ root.bg_color[1]*0.587 + root.bg_color[2]*0.114) > 186/255 \ - else [1,1,1,root.alpha] + else [1,1,1,root.alpha] Line: width: dp(1) points: ( \ @@ -152,7 +152,7 @@ cap: 'round' joint: 'round' close: True - + # Upper right rectangle Color: rgba: 62/255, 254/255, 1, root.alpha @@ -163,7 +163,7 @@ rgba: [0,0,0,root.alpha] if (root.bg_color[0]*0.299 + \ root.bg_color[1]*0.587 + root.bg_color[2]*0.114) > 186/255 \ - else [1,1,1,root.alpha] + else [1,1,1,root.alpha] Line: width: dp(1) points: ( \ @@ -185,7 +185,7 @@ rgba: [0,0,0,root.alpha] if (root.bg_color[0]*0.299 + \ root.bg_color[1]*0.587 + root.bg_color[2]*0.114) > 186/255 \ - else [1,1,1,root.alpha] + else [1,1,1,root.alpha] Line: width: dp(1) points: ( \ @@ -207,7 +207,7 @@ rgba: [0,0,0,root.alpha] if (root.bg_color[0]*0.299 + \ root.bg_color[1]*0.587 + root.bg_color[2]*0.114) > 186/255 \ - else [1,1,1,root.alpha] + else [1,1,1,root.alpha] Line: width: dp(1) points: ( \ @@ -230,7 +230,7 @@ rgba: [0,0,0,root.alpha] if (root.bg_color[0]*0.299 + \ root.bg_color[1]*0.587 + root.bg_color[2]*0.114) > 186/255 \ - else [1,1,1,root.alpha] + else [1,1,1,root.alpha] Line: width: dp(1) points: ( \ @@ -253,7 +253,7 @@ rgba: [0,0,0,root.alpha] if (root.bg_color[0]*0.299 + \ root.bg_color[1]*0.587 + root.bg_color[2]*0.114) > 186/255 \ - else [1,1,1,root.alpha] + else [1,1,1,root.alpha] Line: width: dp(1) points: ( \ @@ -265,16 +265,16 @@ cap: 'round' joint: 'round' close: True - - -: + + +: canvas.before: # TOP LINE Color: rgba: root.top_color Line: width: dp(1) - points: + points: (self.x, self.top, self.right, self.top) if root.alpha \ else (self.x, self.top, self.right, self.top)# Top line @@ -283,7 +283,7 @@ rgba: root.bottom_color Line: width: dp(1) - points: + points: (self.x, self.y, self.right, self.y) if root.alpha \ else (self.x, self.y, self.right, self.y)# Bottom @@ -292,8 +292,8 @@ rgba: root.left_color Line: width: dp(1) - points: - (self.x, self.y, self.x, self.top) if root.alpha \ + points: + (self.x, self.y, self.x, self.top) if root.alpha \ else (self.x, self.y, self.x, self.top)# Left @@ -302,24 +302,24 @@ rgba: root.right_color Line: width: dp(1) - points: + points: (self.right, self.y, self.right, self.top) if root.alpha \ else (self.right, self.y, self.right, self.top)# Right - + Color: rgba: root.span_color[0], root.span_color[1], root.span_color[2], self.span_alpha Rectangle: pos: self.pos size: self.size - - - + + + resize_wgt:resize_wgt size_hint: None,None height: dp(0.001) - width:dp(0.001) + width:dp(0.001) ResizeSelect: id:resize_wgt desktop_mode:root.desktop_mode @@ -328,12 +328,12 @@ size: dp(0.001),dp(0.001) pos: 0, 0 opacity:0 - - + + resize_wgt:resize_wgt size_hint: None,None height: dp(0.001) - width:dp(0.001) + width:dp(0.001) PainterWidget: id:resize_wgt pointsize:dp(0.01) @@ -346,12 +346,12 @@ opacity:1 line_color : 0, 0, 0, 1 selection_point_color : 62/255, 254/255, 1,1 - - + + resize_wgt:resize_wgt size_hint: None,None height: dp(0.001) - width:dp(0.001) + width:dp(0.001) PainterWidget2: id:resize_wgt pointsize:dp(0.01) @@ -364,12 +364,12 @@ opacity:1 line_color : 0, 0, 0, 1 selection_point_color : 62/255, 254/255, 1,1 - - + + resize_wgt:resize_wgt size_hint: None,None height: dp(0.001) - width:dp(0.001) + width:dp(0.001) SpanSelect: id:resize_wgt desktop_mode:root.desktop_mode @@ -392,7 +392,7 @@ def __init__(self,figure_wgt=None,desktop_mode=True, **kwargs): self.figure_wgt=figure_wgt self.desktop_mode=desktop_mode super().__init__(**kwargs) - + class LassoRelativeLayout(RelativeLayout): figure_wgt = ObjectProperty() resize_wgt = ObjectProperty() @@ -401,7 +401,7 @@ def __init__(self,figure_wgt=None,desktop_mode=True, **kwargs): self.figure_wgt=figure_wgt self.desktop_mode=desktop_mode super().__init__(**kwargs) - + class EllipseRelativeLayout(RelativeLayout): figure_wgt = ObjectProperty() resize_wgt = ObjectProperty() @@ -410,7 +410,7 @@ def __init__(self,figure_wgt=None,desktop_mode=True, **kwargs): self.figure_wgt=figure_wgt self.desktop_mode=desktop_mode super().__init__(**kwargs) - + class SpanRelativeLayout(RelativeLayout): figure_wgt = ObjectProperty() resize_wgt = ObjectProperty() @@ -419,7 +419,7 @@ def __init__(self,figure_wgt=None,desktop_mode=True, **kwargs): self.figure_wgt=figure_wgt self.desktop_mode=desktop_mode super().__init__(**kwargs) - + def line_dists(points, start, end): if np.all(start == end): return np.linalg.norm(points - start, axis=1) @@ -457,7 +457,7 @@ class ResizeSelect(FloatLayout): figure_wgt = ObjectProperty() desktop_mode = BooleanProperty(True) alpha = NumericProperty(1) - dynamic_callback = BooleanProperty(False) + dynamic_callback = BooleanProperty(False) def __init__(self,**kwargs): super().__init__(**kwargs) @@ -465,7 +465,7 @@ def __init__(self,**kwargs): self.verts = [] self.ax = None self.callback = None - + self.alpha_other = 0.3 self.ind = [] @@ -473,16 +473,16 @@ def __init__(self,**kwargs): self.xys = None self.Npts = None self.fc = None - - self.line = None + + self.line = None self.ind_line=[] - + self.first_touch=None - def on_kv_post(self,_): + def on_kv_post(self,_): if platform != 'android' and self.desktop_mode: #only bind mouse position if not android or if the user set desktop mode to false Window.bind(mouse_pos=self.on_mouse_pos) - + def update_bg_color(self): fig_bg_color = self.figure_wgt.figure.get_facecolor() rgb_fig_bg_color = mcolors.to_rgb(fig_bg_color) @@ -491,13 +491,13 @@ def update_bg_color(self): self.bottom_color = ( self.top_colors ) = self.left_color = self.right_color = [1,1,1,1] - + else: self.bg_color = [0,0,0,1] self.bottom_color = ( self.top_colors ) = self.left_color = self.right_color = [0,0,0,1] - + def set_collection(self,collection): self.collection = collection self.xys = collection.get_offsets() @@ -509,9 +509,9 @@ def set_collection(self,collection): raise ValueError('Collection must have a facecolor') elif len(self.fc) == 1: self.fc = np.tile(self.fc, (self.Npts, 1)) - + def set_line(self,line): - self.line = line + self.line = line def on_mouse_pos(self, something, touch): """ @@ -519,7 +519,7 @@ def on_mouse_pos(self, something, touch): and update the cursor accordingly. """ if self.opacity and self.figure_wgt.touch_mode=='selector' and self.collide_point(*self.to_widget(*touch)): - + collision = self.collides_with_control_points(something, self.to_widget(*touch)) if collision in ["top left", "bottom right"]: Window.set_system_cursor("size_nwse") @@ -531,7 +531,7 @@ def on_mouse_pos(self, something, touch): Window.set_system_cursor("size_we") else: Window.set_system_cursor("size_all") - + elif self.figure_wgt.collide_point(*touch): Window.set_system_cursor("arrow") @@ -571,7 +571,7 @@ def collides_with_control_points(self, _, touch): def on_touch_down(self, touch): if self.figure_wgt.touch_mode != 'selector': return - + if self.collide_point(*touch.pos) and self.opacity: touch.grab(self) x, y = touch.pos[0], touch.pos[1] @@ -618,7 +618,7 @@ def on_touch_down(self, touch): if touch.is_double_tap and self.callback_clear: self.callback_clear() return - + touch.grab(self) x, y = touch.pos[0], touch.pos[1] self.pos = (x,y-5) @@ -626,25 +626,25 @@ def on_touch_down(self, touch): self.opacity=1 self.first_touch = (x,y-5) self.selected_side = "new select" - - + + return super().on_touch_down(touch) def on_touch_move(self, touch): if self.figure_wgt.touch_mode != 'selector': return - + if touch.grab_current is self: x, y = self.to_window(*self.pos) top = y + self.height # top of our widget right = x + self.width # right of our widget - + if self.selected_side == "top": if self.height + touch.dy <= MINIMUM_HEIGHT: return False self.height += touch.dy - + if self.dynamic_callback and self.verts is not None: self.verts = self._get_box_data() self.onselect(self.verts) @@ -658,7 +658,7 @@ def on_touch_move(self, touch): if self.dynamic_callback and self.verts is not None: self.verts = self._get_box_data() self.onselect(self.verts) - + elif self.selected_side == "left": if self.width - touch.dx <= MINIMUM_WIDTH: return False @@ -668,7 +668,7 @@ def on_touch_move(self, touch): if self.dynamic_callback and self.verts is not None: self.verts = self._get_box_data() self.onselect(self.verts) - + elif self.selected_side == "right": if self.width + touch.dx <= MINIMUM_WIDTH: return False @@ -677,7 +677,7 @@ def on_touch_move(self, touch): if self.dynamic_callback and self.verts is not None: self.verts = self._get_box_data() self.onselect(self.verts) - + elif self.selected_side == "top left": if touch.dx > 0: if self.width - touch.dx <= MINIMUM_WIDTH: @@ -692,7 +692,7 @@ def on_touch_move(self, touch): if self.dynamic_callback and self.verts is not None: self.verts = self._get_box_data() - self.onselect(self.verts) + self.onselect(self.verts) elif self.selected_side == "top right": if touch.dx < 0: @@ -704,10 +704,10 @@ def on_touch_move(self, touch): self.width += touch.dx self.height += touch.dy - + if self.dynamic_callback and self.verts is not None: self.verts = self._get_box_data() - self.onselect(self.verts) + self.onselect(self.verts) elif self.selected_side == "bottom left": if touch.dx > 0: @@ -724,7 +724,7 @@ def on_touch_move(self, touch): if self.dynamic_callback and self.verts is not None: self.verts = self._get_box_data() - self.onselect(self.verts) + self.onselect(self.verts) elif self.selected_side == "bottom right": if touch.dx < 0: @@ -737,15 +737,15 @@ def on_touch_move(self, touch): self.width += touch.dx self.height -= touch.dy self.y += touch.dy - + if self.dynamic_callback and self.verts is not None: self.verts = self._get_box_data() - self.onselect(self.verts) - + self.onselect(self.verts) + elif self.selected_side == "new select": self.width += touch.dx self.height -= touch.dy - self.y += touch.dy + self.y += touch.dy elif not self.selected_side: if self.figure_wgt.collide_point(*self.to_window(self.pos[0]+touch.dx,self.pos[1]+touch.dy )) and \ @@ -755,8 +755,8 @@ def on_touch_move(self, touch): if self.dynamic_callback and self.verts is not None: self.verts = self._get_box_data() - self.onselect(self.verts) - + self.onselect(self.verts) + if self.selected_side == "new select": self.alpha = 0 else: @@ -767,7 +767,7 @@ def on_touch_move(self, touch): def on_touch_up(self, touch): if self.figure_wgt.touch_mode != 'selector': return - + if touch.grab_current is self: touch.ungrab(self) self.alpha = 1 @@ -779,11 +779,11 @@ def on_touch_up(self, touch): else: self.bottom_color = ( self.top_colors - ) = self.left_color = self.right_color = [1,1,1,1] - + ) = self.left_color = self.right_color = [1,1,1,1] + if self.first_touch and self.selected_side == "new select": self.check_if_reverse_selection(touch) - + if abs(self.size[0]) self.first_touch[0] and \ last_touch.y < self.first_touch[1]: return - + else: #reverse selection' if last_touch.x < self.first_touch[0]: self.pos[0] = last_touch.x self.size[0] = self.first_touch[0] - last_touch.x + 5 - + if last_touch.y > self.first_touch[1]: - self.size[1] = last_touch.y - self.first_touch[1] + self.size[1] = last_touch.y - self.first_touch[1] self.pos[1] = last_touch.y - self.size[1] - + return @@ -819,9 +819,9 @@ def reset_selection(self): self.size = (dp(0.01),dp(0.01)) self.opacity=0 - + def _get_box_data(self): - trans = self.ax.transData.inverted() + trans = self.ax.transData.inverted() #get box 4points xis data x0 = self.to_window(*self.pos)[0]-self.figure_wgt.pos[0] y0 = self.to_window(*self.pos)[1]-self.figure_wgt.pos[1] @@ -831,8 +831,8 @@ def _get_box_data(self): y3 = self.to_window(*self.pos)[1]-self.figure_wgt.pos[1] x2 = self.to_window(*self.pos)[0] + self.width -self.figure_wgt.pos[0] y2 = self.to_window(*self.pos)[1] + self.height -self.figure_wgt.pos[1] - - x0_box, y0_box = trans.transform_point((x0, y0)) + + x0_box, y0_box = trans.transform_point((x0, y0)) x1_box, y1_box = trans.transform_point((x1, y1)) x2_box, y2_box = trans.transform_point((x2, y2)) x3_box, y3_box = trans.transform_point((x3, y3)) @@ -853,18 +853,18 @@ def onselect(self, verts): self.collection.set_facecolors(self.fc) if self.line: xdata,ydata = self.line.get_xydata().T - self.ind_line = np.nonzero(path.contains_points(np.array([xdata,ydata]).T))[0] + self.ind_line = np.nonzero(path.contains_points(np.array([xdata,ydata]).T))[0] self.figure_wgt.figure.canvas.draw_idle() if self.callback: self.callback(self) - + def set_callback(self,callback): self.callback=callback def set_callback_clear(self,callback): - self.callback_clear=callback - + self.callback_clear=callback + def clear_selection(self): if self.collection: @@ -873,9 +873,9 @@ def clear_selection(self): self.collection.set_facecolors(self.fc) if self.line: self.ind_line=[] - + self.reset_selection() - self.figure_wgt.figure.canvas.draw_idle() + self.figure_wgt.figure.canvas.draw_idle() def _rotate_pos(x, y, cx, cy, angle, base_angle=0.): @@ -1307,7 +1307,7 @@ def on_touch_down(self, touch): if super(PaintCanvasBehaviorBase, self).on_touch_down(touch): return True - + if not self.collide_point(touch.x, touch.y): return False @@ -2172,7 +2172,7 @@ class PaintEllipse(PaintShape): ready_to_finish = True is_valid = True - + during_creation = False def __init__(self, **kwargs): @@ -2220,7 +2220,7 @@ def add_area_graphics_to_canvas(self, name, canvas): x, y = self.center rx, ry = self.radius_x, self.radius_y angle = self.angle - + PushMatrix(group=name) Rotate(angle=angle / pi * 180., origin=(x, y), group=name) Ellipse(size=(rx * 2., ry * 2.), pos=(x - rx, y - ry), group=name) @@ -2231,7 +2231,7 @@ def add_shape_to_canvas(self, paint_widget): return False colors = self.color_instructions = [] - + x, y = self.center rx, ry = self.radius_x, self.radius_y angle = self.angle @@ -2247,11 +2247,11 @@ def add_shape_to_canvas(self, paint_widget): i4 = self.perim_ellipse_inst = Line( ellipse=(x - rx, y - ry, 2 * rx, 2 * ry), width=self.line_width, group=self.graphics_name) - + i66 = self.perim_color_inst2 = Color( *self.selection_point_color, group=self.graphics_name) colors.append(i66) - + i6 = self.selection_point_inst2 = Point( points=[x, y + ry], pointsize=self.pointsize, group=self.graphics_name) @@ -2442,25 +2442,25 @@ def get_widget_verts(self): cx, cy = self.center x0,y0 = self._get_coord_from_angle(self.selection_point_inst.points) x1,y1 = self._get_coord_from_angle(self.selection_point_inst2.points,base_angle = pi / 2.) - + return np.array([[cx,cy], [x0,y0], [x1,y1]]) - + def _get_coord_from_angle(self,pos,base_angle=0.): x1, y1 = pos x2, y2 = self.center x, y = _rotate_pos(x1, y1, x2, y2, self.angle, base_angle=base_angle) return x,y - + def get_min_max(self): """ Calculates the min/max x and y coordinates for a rotated ellipse. - + Args: None - + Returns: dict: A dictionary containing min_x, max_x, min_y, max_y. """ @@ -2468,30 +2468,30 @@ def get_min_max(self): a = self.radius_x # semi-major axis b = self.radius_y # semi-minor axis theta = self.angle # convert angle to radians - + # Parametric equations for the ellipse t = np.linspace(0, 2 * pi, 1000) # Parameter t from 0 to 2*pi - + # Parametric points on the unrotated ellipse x_ellipse = a * np.cos(t) y_ellipse = b * np.sin(t) - + # Rotation matrix applied to the points x_rotated = x_ellipse * cos(theta) - y_ellipse * sin(theta) y_rotated = x_ellipse * sin(theta) + y_ellipse * cos(theta) - + # Translating the rotated points to the ellipse's center x_final = cx + x_rotated y_final = cy + y_rotated - + # Finding the min and max values of x and y min_x = np.min(x_final) max_x = np.max(x_final) min_y = np.min(y_final) max_y = np.max(y_final) - - return min_x, max_x, min_y, max_y - + + return min_x, max_x, min_y, max_y + class PaintPolygon(PaintShape): """A shape that represents a polygon. @@ -2634,11 +2634,11 @@ def add_shape_to_canvas(self, paint_widget): i2 = self.perim_line_inst = Line( points=self.points, width=self.line_width, close=self.finished, group=self.graphics_name) - + i33 = self.perim_color_inst2 = Color( *self.selection_point_color, group=self.graphics_name) colors.append(i33) - + i3 = self.perim_points_inst = Point( points=self.points, pointsize=self.pointsize, group=self.graphics_name) @@ -2858,7 +2858,7 @@ def rescale(self, scale): points = [val for point in zip(x_vals, y_vals) for val in point] self.points = points - self.selection_point = points[:2] + self.selection_point = points[:2] class PaintFreeformPolygon(PaintPolygon): """A shape that represents a polygon. @@ -3007,7 +3007,7 @@ def create_shape_from_state(self, state, add=True): if add: self.add_shape(shape) return shape - + class PainterWidget(PaintCanvasBehavior, FloatLayout): figure_wgt = ObjectProperty() desktop_mode = BooleanProperty(True) @@ -3015,7 +3015,7 @@ class PainterWidget(PaintCanvasBehavior, FloatLayout): max_rate = NumericProperty(2/60) def __init__(self,**kwargs): - super().__init__(**kwargs) + super().__init__(**kwargs) self.selected_side = None self.last_touch_time=None self.verts = [] @@ -3023,7 +3023,7 @@ def __init__(self,**kwargs): self.ax = None self.callback = None self.callback_clear = None - + self.alpha_other = 0.3 self.ind = [] @@ -3031,18 +3031,18 @@ def __init__(self,**kwargs): self.xys = None self.Npts = None self.fc = None - - self.line = None + + self.line = None self.ind_line=[] - + self.first_touch=None self.current_shape_close=None #TODO manage mouse system cursor - # def on_kv_post(self,_): + # def on_kv_post(self,_): # if platform != 'android' and self.desktop_mode: #only bind mouse position if not android or if the user set desktop mode to false # Window.bind(mouse_pos=self.on_mouse_pos) - + def on_mouse_pos(self, something, touch): """ TODO @@ -3050,7 +3050,7 @@ def on_mouse_pos(self, something, touch): and update the cursor accordingly. """ if self.opacity and self.collide_point(*self.to_widget(*touch)): - + collision = self.collides_with_control_points(something, self.to_widget(*touch)) if collision in ["top left", "bottom right"]: Window.set_system_cursor("size_nwse") @@ -3062,10 +3062,10 @@ def on_mouse_pos(self, something, touch): Window.set_system_cursor("size_we") else: Window.set_system_cursor("size_all") - + elif self.figure_wgt.collide_point(*touch): Window.set_system_cursor("arrow") - + def create_shape_with_touch(self, touch): shape = super(PainterWidget, self).create_shape_with_touch(touch) @@ -3078,7 +3078,7 @@ def add_shape(self, shape): shape.add_shape_to_canvas(self) return True return False - + def on_touch_down(self, touch): if self.figure_wgt.touch_mode!='selector': return False @@ -3109,7 +3109,7 @@ def on_touch_down(self, touch): if touch.is_double_tap and self.callback_clear: self.callback_clear() return - + x, y = touch.pos[0], touch.pos[1] result=False if self.shapes and self.opacity==1: @@ -3133,7 +3133,7 @@ def on_touch_down(self, touch): touch, outside=not self.collide_point(touch.x, touch.y)) if self.check_new_shape_done(shape2, 'up'): self.finish_current_shape() - + else: self.selected_side = "pan" else: @@ -3155,15 +3155,15 @@ def on_touch_down(self, touch): touch, outside=not self.collide_point(touch.x, touch.y)) if self.check_new_shape_done(shape2, 'up'): self.finish_current_shape() - + else: self.selected_side = "new select" self.delete_all_shapes() - + ud['paint_interacted'] = True self._processing_touch = touch touch.grab(self) - + # if we have a current shape, all touch will go to it current_shape = self.current_shape if current_shape is not None: @@ -3172,13 +3172,13 @@ def on_touch_down(self, touch): >= dp(self.min_touch_dist) if ud['paint_cleared_selection']: self.finish_current_shape() - + else: ud['paint_interaction'] = 'current' current_shape.handle_touch_down(touch) return True - + # next try to interact by selecting or interacting with selected shapes shape = self.get_closest_selection_point_shape(touch.x, touch.y) if shape is not None: @@ -3186,7 +3186,7 @@ def on_touch_down(self, touch): ud['paint_selected_shape'] = shape ud['paint_was_selected'] = shape not in self.selected_shapes return True - + if self._ctrl_down: ud['paint_interaction'] = 'done' return True @@ -3195,37 +3195,37 @@ def on_touch_down(self, touch): for shape in self.shapes: shape.pointsize = dp(0.01) - - return True - + + return True + elif not self.collide_point(touch.x, touch.y): return False def on_touch_move(self, touch): if self.figure_wgt.touch_mode!='selector': - return False + return False ud = touch.ud if touch.grab_current is self: # for move, only use normal touch, not touch outside range x, y = self.to_window(*self.pos) - + if self.selected_side == "new select" or self.selected_side == "modify": if ud['paint_interaction'] == 'done': return True ud['paint_touch_moved'] = True - + if self.figure_wgt.collide_point(*self.to_window(*touch.pos)): if not ud['paint_interaction']: if ud['paint_cleared_selection'] or self.clear_selected_shapes(): ud['paint_interaction'] = 'done' return True - + # finally try creating a new shape # touch must have originally collided otherwise we wouldn't be here shape = self.create_shape_with_touch(touch) @@ -3237,21 +3237,21 @@ def on_touch_move(self, touch): self.finish_current_shape() ud['paint_interaction'] = 'done' return True - + ud['paint_interaction'] = 'current_new' else: ud['paint_interaction'] = 'done' return True - + if ud['paint_interaction'] in ('current', 'current_new'): if self.current_shape is None: ud['paint_interaction'] = 'done' else: self.current_shape.handle_touch_move(touch) return True - + if self.selected_side == "pan": - + shape = ud['paint_selected_shape'] dataxy = np.array(self.shapes[0].points).reshape(-1,2) @@ -3269,11 +3269,11 @@ def on_touch_move(self, touch): def on_touch_up(self, touch): if self.figure_wgt.touch_mode!='selector': - return False + return False ud = touch.ud if touch.grab_current is self: - touch.ungrab(self) + touch.ungrab(self) # don't process the same touch up again paint_interaction = ud['paint_interaction'] ud['paint_interaction'] = 'done' @@ -3281,7 +3281,7 @@ def on_touch_up(self, touch): self.finish_current_shape() self.alpha = 1 - + if self.selected_side != "modify": if self.selected_side == "new select" and self.shapes: self.filter_path(self.shapes[0]) @@ -3298,15 +3298,15 @@ def on_touch_up(self, touch): else: for shape in self.shapes: shape.pointsize = dp(7) - + if self.widget_verts is not None: self.verts = self._get_lasso_data(self.widget_verts) self.onselect(self.verts) - + if self.selected_side == "modify": paint_interaction = ud['paint_interaction'] - ud['paint_interaction'] = 'done' + ud['paint_interaction'] = 'done' self._processing_touch = None if not paint_interaction: @@ -3356,20 +3356,20 @@ def on_touch_up(self, touch): self.selected_shapes[0] != shape: self.clear_selected_shapes() self.select_shape(shape) - + return True - - return True - - if self.figure_wgt.collide_point(*self.to_window(*touch.pos)): + + return True + + if self.figure_wgt.collide_point(*self.to_window(*touch.pos)): self._processing_touch = None - return True - + return True + return super().on_touch_up(touch) - + def check_if_inside_path(self,pts,x,y): verts = np.array(pts).reshape(-1, 2) @@ -3379,7 +3379,7 @@ def check_if_inside_path(self,pts,x,y): return True else: return False - + def get_closest_selection_point_shape(self, x, y): """Given a position, it returns the shape whose selection point is the closest to this position among all the shapes. @@ -3405,7 +3405,7 @@ def get_closest_selection_point_shape(self, x, y): min_dist = dist return closest_shape - + def do_long_touch(self, touch, *largs): """Handles a long touch by the user. """ @@ -3432,41 +3432,41 @@ def do_long_touch(self, touch, *largs): self.start_shape_interaction(shape, (touch.x, touch.y)) else: ud['paint_interaction'] = 'done' - touch.pop() - + touch.pop() + def filter_path(self,shape): pts = shape.points verts = np.array(pts).reshape(-1, 2) filter_pts = rdp(verts,5.0) - + if len(filter_pts) < 6 : self.widget_verts = None return - - + + #delete old shape self.delete_all_shapes() - + shape = self.create_shape_with_touch(self.first_touch) if shape is not None: shape.handle_touch_down(self.first_touch, opos=self.first_touch.opos) self.current_shape = shape - - + + #crete new filter shape for data_xy in filter_pts: - + if not self.current_shape.selection_point: self.current_shape.selection_point = data_xy - + self.current_shape.points.extend(data_xy) if self.current_shape.perim_close_inst is not None: self.current_shape.perim_close_inst.points = self.current_shape.points[-2:] + self.current_shape.points[:2] if len(self.current_shape.points) >= 6: self.current_shape.is_valid = True - + self.finish_current_shape() - + self.widget_verts = filter_pts def set_collection(self,collection): @@ -3480,10 +3480,10 @@ def set_collection(self,collection): raise ValueError('Collection must have a facecolor') elif len(self.fc) == 1: self.fc = np.tile(self.fc, (self.Npts, 1)) - + def set_line(self,line): - self.line = line - + self.line = line + def onselect(self, verts): if verts: path = Path(verts) @@ -3494,18 +3494,18 @@ def onselect(self, verts): self.collection.set_facecolors(self.fc) if self.line: xdata,ydata = self.line.get_xydata().T - self.ind_line = np.nonzero(path.contains_points(np.array([xdata,ydata]).T))[0] - + self.ind_line = np.nonzero(path.contains_points(np.array([xdata,ydata]).T))[0] + self.figure_wgt.figure.canvas.draw_idle() if self.callback: self.callback(self) - + def set_callback(self,callback): self.callback=callback - + def set_callback_clear(self,callback): - self.callback_clear=callback - + self.callback_clear=callback + def clear_selection(self): if self.collection: @@ -3516,11 +3516,11 @@ def clear_selection(self): self.ind_line=[] self.delete_all_shapes() - self.figure_wgt.figure.canvas.draw_idle() - + self.figure_wgt.figure.canvas.draw_idle() + def _get_lasso_data(self,widget_verts): - trans = self.ax.transData.inverted() - + trans = self.ax.transData.inverted() + verts=[] for data_xy in widget_verts: x, y = trans.transform_point((data_xy[0] + self.to_window(*self.pos)[0]-self.figure_wgt.pos[0], @@ -3528,7 +3528,7 @@ def _get_lasso_data(self,widget_verts): verts.append((x, y)) return verts - + class PainterWidget2(PaintCanvasBehavior, FloatLayout): figure_wgt = ObjectProperty() desktop_mode = BooleanProperty(True) @@ -3536,7 +3536,7 @@ class PainterWidget2(PaintCanvasBehavior, FloatLayout): max_rate = NumericProperty(2/60) def __init__(self,**kwargs): - super().__init__(**kwargs) + super().__init__(**kwargs) self.selected_side = None self.last_touch_time=None self.verts = [] @@ -3544,7 +3544,7 @@ def __init__(self,**kwargs): self.ax = None self.callback = None self.callback_clear = None - + self.alpha_other = 0.3 self.ind = [] @@ -3552,18 +3552,18 @@ def __init__(self,**kwargs): self.xys = None self.Npts = None self.fc = None - - self.line = None + + self.line = None self.ind_line=[] - + self.first_touch=None self.current_shape_close=None #TODO manage mouse system cursor - # def on_kv_post(self,_): + # def on_kv_post(self,_): # if platform != 'android' and self.desktop_mode: #only bind mouse position if not android or if the user set desktop mode to false # Window.bind(mouse_pos=self.on_mouse_pos) - + def on_mouse_pos(self, something, touch): """ TODO @@ -3571,7 +3571,7 @@ def on_mouse_pos(self, something, touch): and update the cursor accordingly. """ if self.opacity and self.collide_point(*self.to_widget(*touch)): - + collision = self.collides_with_control_points(something, self.to_widget(*touch)) if collision in ["top left", "bottom right"]: Window.set_system_cursor("size_nwse") @@ -3583,16 +3583,16 @@ def on_mouse_pos(self, something, touch): Window.set_system_cursor("size_we") else: Window.set_system_cursor("size_all") - + elif self.figure_wgt.collide_point(*touch): Window.set_system_cursor("arrow") - + def create_shape_with_touch(self, touch, check=True): shape = super(PainterWidget2, self).create_shape_with_touch(touch) if check and shape.radius_x==dp(10) and shape.radius_y==dp(15): - return None - + return None + if shape is not None: shape.add_shape_to_canvas(self) return shape @@ -3602,7 +3602,7 @@ def add_shape(self, shape): shape.add_shape_to_canvas(self) return True return False - + def on_touch_down(self, touch): if self.figure_wgt.touch_mode!='selector': return False @@ -3627,12 +3627,12 @@ def on_touch_down(self, touch): if super(PaintCanvasBehaviorBase, self).on_touch_down(touch): return True - + if self.figure_wgt.collide_point(*self.to_window(*touch.pos)): - - + + if self.figure_wgt.touch_mode=='selector': - + if touch.is_double_tap and self.callback_clear: self.callback_clear() return @@ -3641,7 +3641,7 @@ def on_touch_down(self, touch): result=False if self.shapes and self.opacity==1: result = self.check_if_inside_path(self.shapes[0],x,y) - + self.opacity=1 if result: shape = self.get_closest_selection_point_shape(touch.x, touch.y) @@ -3660,7 +3660,7 @@ def on_touch_down(self, touch): touch, outside=not self.collide_point(touch.x, touch.y)) if self.check_new_shape_done(shape2, 'up'): self.finish_current_shape() - + else: self.selected_side = "pan" touch.grab(self) @@ -3684,11 +3684,11 @@ def on_touch_down(self, touch): touch, outside=not self.collide_point(touch.x, touch.y)) if self.check_new_shape_done(shape2, 'up'): self.finish_current_shape() - + else: self.selected_side = "new select" self.delete_all_shapes() - + ud['paint_interacted'] = True self._processing_touch = touch touch.grab(self) @@ -3700,14 +3700,14 @@ def on_touch_down(self, touch): >= dp(self.min_touch_dist) if ud['paint_cleared_selection']: self.finish_current_shape() - + else: ud['paint_interaction'] = 'current' current_shape.handle_touch_down(touch) - + return True - + # next try to interact by selecting or interacting with selected shapes shape = self.get_closest_selection_point_shape(touch.x, touch.y) if shape is not None: @@ -3715,7 +3715,7 @@ def on_touch_down(self, touch): ud['paint_selected_shape'] = shape ud['paint_was_selected'] = shape not in self.selected_shapes return True - + if self._ctrl_down: ud['paint_interaction'] = 'done' return True @@ -3724,10 +3724,10 @@ def on_touch_down(self, touch): for shape in self.shapes: shape.pointsize = dp(7) - - return True - - + + return True + + elif not self.collide_point(touch.x, touch.y): if self.figure_wgt.collide_point(*self.to_window(*touch.pos)): self.delete_all_shapes() @@ -3736,28 +3736,28 @@ def on_touch_down(self, touch): def on_touch_move(self, touch): if self.figure_wgt.touch_mode!='selector': - return False + return False ud = touch.ud if touch.grab_current is self: # for move, only use normal touch, not touch outside range x, y = self.to_window(*self.pos) - + if self.selected_side == "new select" or self.selected_side == "modify": # if ud['paint_interaction'] == 'done': # return True ud['paint_touch_moved'] = True - + if self.figure_wgt.collide_point(*self.to_window(*touch.pos)): if not ud['paint_interaction']: if ud['paint_cleared_selection'] or self.clear_selected_shapes(): ud['paint_interaction'] = 'done' return True - + # finally try creating a new shape # touch must have originally collided otherwise we wouldn't be here shape = self.create_shape_with_touch(touch,check=False) @@ -3766,20 +3766,20 @@ def on_touch_move(self, touch): self.first_touch = touch self.current_shape = shape self.current_shape.during_creation = True - + if self.check_new_shape_done(shape, 'down'): - + self.finish_current_shape() self.current_shape = self.shapes[0] ud['paint_interaction'] = 'done' return True - + ud['paint_interaction'] = 'current_new' else: ud['paint_interaction'] = 'done' shape.handle_touch_move(touch) return True - + # if ud['paint_interaction'] in ('current', 'current_new'): else: if self.current_shape is None: @@ -3788,7 +3788,7 @@ def on_touch_move(self, touch): self.current_shape.start_interaction((touch.x, touch.y)) self.current_shape.handle_touch_move(touch) return True - + if self.selected_side == "pan": shape = ud['paint_selected_shape'] @@ -3804,23 +3804,23 @@ def on_touch_move(self, touch): return super().on_touch_move(touch) def on_touch_up(self, touch): - + if self.figure_wgt.touch_mode!='selector': - return False - + return False + ud = touch.ud - + if touch.grab_current is self: - touch.ungrab(self) + touch.ungrab(self) # don't process the same touch up again paint_interaction = ud['paint_interaction'] ud['paint_interaction'] = 'done' self._processing_touch = None self.finish_current_shape() self.alpha = 1 - - + + if self.selected_side != "modify": if self.selected_side == "new select" and self.shapes: self.shapes[0].during_creation = False @@ -3838,16 +3838,16 @@ def on_touch_up(self, touch): else: for shape in self.shapes: shape.pointsize = dp(7) - + if self.widget_verts is not None: self.verts = self._get_ellipse_data(self.widget_verts) self.onselect(self.verts) - - + + if self.selected_side == "modify": paint_interaction = ud['paint_interaction'] - ud['paint_interaction'] = 'done' + ud['paint_interaction'] = 'done' self._processing_touch = None if not paint_interaction: @@ -3898,35 +3898,35 @@ def on_touch_up(self, touch): self.selected_shapes[0] != shape: self.clear_selected_shapes() self.select_shape(shape) - + return True - - return True - - if self.figure_wgt.collide_point(*self.to_window(*touch.pos)): + + return True + + if self.figure_wgt.collide_point(*self.to_window(*touch.pos)): self._processing_touch = None - return True - + return True + self.finish_current_shape() return super().on_touch_up(touch) - + def check_if_inside_path(self,shape,x,y): cx, cy = shape.center width= shape.radius_x * 2 height= shape.radius_y * 2 angle= shape.rotate_inst.angle - + path = Ellipse_mpl((cx,cy), width, height,angle=angle) ind = np.nonzero(path.contains_points(np.array([[x,y]])))[0] if len(ind)!=0: return True else: return False - + def get_closest_selection_point_shape(self, x, y): """Given a position, it returns the shape whose selection point is the closest to this position among all the shapes. @@ -3952,7 +3952,7 @@ def get_closest_selection_point_shape(self, x, y): min_dist = dist return closest_shape - + def do_long_touch(self, touch, *largs): """Handles a long touch by the user. """ @@ -3979,8 +3979,8 @@ def do_long_touch(self, touch, *largs): self.start_shape_interaction(shape, (touch.x, touch.y)) else: ud['paint_interaction'] = 'done' - touch.pop() - + touch.pop() + def set_collection(self,collection): self.collection = collection @@ -3993,21 +3993,21 @@ def set_collection(self,collection): raise ValueError('Collection must have a facecolor') elif len(self.fc) == 1: self.fc = np.tile(self.fc, (self.Npts, 1)) - + def set_line(self,line): - self.line = line - + self.line = line + def onselect(self, verts): - if verts and self.shapes: + if verts and self.shapes: cx, cy = self.shapes[0].center width= self.shapes[0].radius_x * 2 height= self.shapes[0].radius_y * 2 angle= self.shapes[0].rotate_inst.angle - + path = Ellipse_mpl((cx,cy), width, height,angle=angle) - + newverts = self._get_ellipse_data(path.get_verts()) - + path = Path(newverts) if self.collection: @@ -4017,18 +4017,18 @@ def onselect(self, verts): self.collection.set_facecolors(self.fc) if self.line: xdata,ydata = self.line.get_xydata().T - self.ind_line = np.nonzero(path.contains_points(np.array([xdata,ydata]).T))[0] - + self.ind_line = np.nonzero(path.contains_points(np.array([xdata,ydata]).T))[0] + self.figure_wgt.figure.canvas.draw_idle() if self.callback: self.callback(self) - + def set_callback(self,callback): self.callback=callback - + def set_callback_clear(self,callback): - self.callback_clear=callback - + self.callback_clear=callback + def clear_selection(self): if self.collection: @@ -4039,11 +4039,11 @@ def clear_selection(self): self.ind_line=[] self.delete_all_shapes() - self.figure_wgt.figure.canvas.draw_idle() - + self.figure_wgt.figure.canvas.draw_idle() + def _get_ellipse_data(self,widget_verts): - trans = self.ax.transData.inverted() - + trans = self.ax.transData.inverted() + verts=[] for data_xy in widget_verts: x, y = trans.transform_point((data_xy[0] + self.to_window(*self.pos)[0]-self.figure_wgt.pos[0], @@ -4051,7 +4051,7 @@ def _get_ellipse_data(self,widget_verts): verts.append((x, y)) return verts - + class SpanSelect(FloatLayout): top_color = ColorProperty("black") @@ -4059,7 +4059,7 @@ class SpanSelect(FloatLayout): left_color = ColorProperty("black") right_color = ColorProperty("black") highlight_color = ColorProperty("red") - bg_color = ColorProperty([1,1,1,1]) + bg_color = ColorProperty([1,1,1,1]) figure_wgt = ObjectProperty() desktop_mode = BooleanProperty(True) span_orientation = OptionProperty('vertical', options=['vertical','horizontal']) @@ -4068,16 +4068,16 @@ class SpanSelect(FloatLayout): span_hide_on_release = BooleanProperty(False) alpha = NumericProperty(1) invert_rect_ver = BooleanProperty(False) - invert_rect_hor = BooleanProperty(False) - dynamic_callback = BooleanProperty(False) + invert_rect_hor = BooleanProperty(False) + dynamic_callback = BooleanProperty(False) stay_in_axis_limit = BooleanProperty(False) - + def __init__(self,**kwargs): super().__init__(**kwargs) self.verts = [] self.ax = None self.callback = None - + self.alpha_other = 0.3 self.ind = [] @@ -4085,16 +4085,16 @@ def __init__(self,**kwargs): self.xys = None self.Npts = None self.fc = None - - self.line = None + + self.line = None self.ind_line=[] - + self.first_touch=None - def on_kv_post(self,_): + def on_kv_post(self,_): if platform != 'android' and self.desktop_mode: #only bind mouse position if not android or if the user set desktop mode to false Window.bind(mouse_pos=self.on_mouse_pos) - + def update_bg_color(self): fig_bg_color = self.figure_wgt.figure.get_facecolor() rgb_fig_bg_color = mcolors.to_rgb(fig_bg_color) @@ -4103,13 +4103,13 @@ def update_bg_color(self): self.bottom_color = ( self.top_colors ) = self.left_color = self.right_color = [1,1,1,1] - + else: self.bg_color = [0,0,0,1] self.bottom_color = ( self.top_colors ) = self.left_color = self.right_color = [0,0,0,1] - + def set_collection(self,collection): self.collection = collection self.xys = collection.get_offsets() @@ -4121,9 +4121,9 @@ def set_collection(self,collection): raise ValueError('Collection must have a facecolor') elif len(self.fc) == 1: self.fc = np.tile(self.fc, (self.Npts, 1)) - + def set_line(self,line): - self.line = line + self.line = line def on_mouse_pos(self, something, touch): """ @@ -4131,7 +4131,7 @@ def on_mouse_pos(self, something, touch): and update the cursor accordingly. """ if self.opacity and self.figure_wgt.touch_mode=='selector' and self.collide_point(*self.to_widget(*touch)): - + collision = self.collides_with_control_points(something, self.to_widget(*touch)) if collision in ["top left", "bottom right"]: Window.set_system_cursor("size_nwse") @@ -4143,7 +4143,7 @@ def on_mouse_pos(self, something, touch): Window.set_system_cursor("size_we") else: Window.set_system_cursor("size_all") - + # elif self.figure_wgt.collide_point(*touch): else: Window.set_system_cursor("arrow") @@ -4154,7 +4154,7 @@ def collides_with_control_points(self, _, touch): """ x, y = touch[0], touch[1] - + if self.span_orientation == 'vertical': # Checking mouse is on left edge if self.x - dp(7) <= x <= self.x + dp(7): @@ -4168,7 +4168,7 @@ def collides_with_control_points(self, _, touch): if self.y<= y <= self.y + self.height: return "right" else: - return False + return False else: return False @@ -4217,62 +4217,62 @@ def on_touch_down(self, touch): if touch.is_double_tap and self.callback_clear: self.callback_clear() return - + touch.grab(self) #get figure boundaries (in pixels) - + left_bound = float(self.figure_wgt.x +self.ax.bbox.bounds[0]) right_bound = float(self.figure_wgt.x +self.ax.bbox.bounds[2] +self.ax.bbox.bounds[0] ) top_bound = float(self.figure_wgt.y +self.ax.bbox.bounds[3] + self.ax.bbox.bounds[1]) bottom_bound = float(self.figure_wgt.y +self.ax.bbox.bounds[1]) - + width = right_bound-left_bound - + left_bound,right_bound = self.to_widget(left_bound,right_bound) x, y = touch.pos[0], touch.pos[1] - + self.opacity=1 - + if self.span_orientation == 'vertical': self.pos = (x,bottom_bound - self.figure_wgt.y) - self.size = (5,top_bound-bottom_bound) + self.size = (5,top_bound-bottom_bound) elif self.span_orientation == 'horizontal': self.pos = (left_bound,y) - self.size = (width,-5) - + self.size = (width,-5) + # self.size = (5,5) self.opacity=1 if self.span_orientation == 'vertical': - self.first_touch = (x,bottom_bound - self.figure_wgt.y) + self.first_touch = (x,bottom_bound - self.figure_wgt.y) elif self.span_orientation == 'horizontal': self.first_touch = (left_bound,y) self.selected_side = "new select" - - + + return super().on_touch_down(touch) def on_touch_move(self, touch): if self.figure_wgt.touch_mode != 'selector': return - + if touch.grab_current is self: x, y = self.to_window(*self.pos) top = y + self.height # top of our widget right = x + self.width # right of our widget - + if self.stay_in_axis_limit: #get figure boundaries (in pixels) left_bound = float(self.figure_wgt.x +self.ax.bbox.bounds[0]) right_bound = float(self.figure_wgt.x +self.ax.bbox.bounds[2] +self.ax.bbox.bounds[0] ) top_bound = float(self.figure_wgt.y +self.ax.bbox.bounds[3] + self.ax.bbox.bounds[1]) bottom_bound = float(self.figure_wgt.y +self.ax.bbox.bounds[1]) - + width = right_bound-left_bound - + left_bound,right_bound = self.to_widget(left_bound,right_bound) - + if self.selected_side == "top": if self.height + touch.dy <= MINIMUM_HEIGHT: return False @@ -4287,19 +4287,19 @@ def on_touch_move(self, touch): elif self.selected_side == "left": if self.width - touch.dx <= MINIMUM_WIDTH: return False - + if self.stay_in_axis_limit and self.x + touch.dx <= left_bound: self.width -= (left_bound-self.x) self.x = left_bound - + if self.dynamic_callback and self.verts is not None: self.verts = self._get_box_data() self.onselect(self.verts) - + else: self.width -= touch.dx self.x += touch.dx - + if self.dynamic_callback and self.verts is not None: self.verts = self._get_box_data() self.onselect(self.verts) @@ -4313,15 +4313,15 @@ def on_touch_move(self, touch): if self.dynamic_callback and self.verts is not None: self.verts = self._get_box_data() self.onselect(self.verts) - + else: - + self.width += touch.dx - + if self.dynamic_callback and self.verts is not None: self.verts = self._get_box_data() self.onselect(self.verts) - + elif self.selected_side == "new select": if self.span_orientation == 'vertical': self.width += touch.dx @@ -4331,7 +4331,7 @@ def on_touch_move(self, touch): elif not self.selected_side: if self.figure_wgt.collide_point(*self.to_window(self.pos[0]+touch.dx,self.pos[1]+touch.dy )) and \ self.figure_wgt.collide_point(*self.to_window(self.pos[0] + self.size[0]+touch.dx,self.pos[1]+ self.size[1]+touch.dy )): - if self.span_orientation == 'vertical': + if self.span_orientation == 'vertical': # print(touch.dx) if self.stay_in_axis_limit and self.x + touch.dx < left_bound: self.x = left_bound @@ -4339,10 +4339,10 @@ def on_touch_move(self, touch): self.verts = self._get_box_data() self.onselect(self.verts) elif self.stay_in_axis_limit and self.width + touch.dx+self.x > left_bound+width: - self.x = left_bound+width-self.width + self.x = left_bound+width-self.width if self.dynamic_callback and self.verts is not None: self.verts = self._get_box_data() - self.onselect(self.verts) + self.onselect(self.verts) else: self.x += touch.dx if self.dynamic_callback and self.verts is not None: @@ -4356,7 +4356,7 @@ def on_touch_move(self, touch): def on_touch_up(self, touch): if self.figure_wgt.touch_mode != 'selector': return - + if touch.grab_current is self: touch.ungrab(self) self.alpha = 1 @@ -4368,11 +4368,11 @@ def on_touch_up(self, touch): else: self.bottom_color = ( self.top_colors - ) = self.left_color = self.right_color = [1,1,1,1] - + ) = self.left_color = self.right_color = [1,1,1,1] + if self.first_touch and self.selected_side == "new select": self.check_if_reverse_selection(touch) - + if abs(self.size[0])xmax: out_of_bound=True self.reset_selection() - + if self.verts is not None and not out_of_bound: self.verts = self._get_box_data() self.onselect(self.verts) return True return super().on_touch_up(touch) - - def check_if_reverse_selection(self,last_touch): + + def check_if_reverse_selection(self,last_touch): if self.span_orientation == 'vertical': if last_touch.x > self.first_touch[0]: return else: - self.pos[0] = last_touch.x + 5 + self.pos[0] = last_touch.x + 5 self.size[0] = abs(self.size[0]) #self.first_touch[0] - last_touch.x - + elif self.span_orientation == 'horizontal': if last_touch.y > self.first_touch[1]: return else: self.pos[1] = last_touch.y - 5 self.size[1] = abs(self.size[1]) - + else: return @@ -4418,9 +4418,9 @@ def reset_selection(self): self.size = (dp(0.01),dp(0.01)) self.opacity=0 - + def _get_box_data(self): - trans = self.ax.transData.inverted() + trans = self.ax.transData.inverted() #get box 4points xis data x0 = self.to_window(*self.pos)[0]-self.figure_wgt.pos[0] y0 = self.to_window(*self.pos)[1]-self.figure_wgt.pos[1] @@ -4430,9 +4430,9 @@ def _get_box_data(self): y3 = self.to_window(*self.pos)[1]-self.figure_wgt.pos[1] x2 = self.to_window(*self.pos)[0] + self.width -self.figure_wgt.pos[0] y2 = self.to_window(*self.pos)[1] + self.height -self.figure_wgt.pos[1] - - - x0_box, y0_box = trans.transform_point((x0, y0)) + + + x0_box, y0_box = trans.transform_point((x0, y0)) x1_box, y1_box = trans.transform_point((x1, y1)) x2_box, y2_box = trans.transform_point((x2, y2)) x3_box, y3_box = trans.transform_point((x3, y3)) @@ -4441,7 +4441,7 @@ def _get_box_data(self): verts.append((x1_box, y1_box)) verts.append((x2_box, y2_box)) verts.append((x3_box, y3_box)) - + if self.span_orientation == 'vertical': if self.collection: ymin = np.nanmin(self.xys[:,1]) @@ -4449,27 +4449,27 @@ def _get_box_data(self): if self.line: ydata = self.line.get_ydata() ymin = np.nanmin(ydata) - ymax = np.nanmax(ydata) - + ymax = np.nanmax(ydata) + verts[0] = (verts[0][0],ymin-1) verts[3] = (verts[3][0],ymin-1) verts[1] = (verts[1][0],ymax+1) verts[2] = (verts[2][0],ymax+1) - + elif self.span_orientation == 'horizontal': if self.collection: xmin = np.nanmin(self.xys[:,1]) xmax = np.nanmax(self.xys[:,1]) if self.line: - xdata = self.line.get_xdata() + xdata = self.line.get_xdata() xmin = np.nanmin(xdata) xmax = np.nanmax(xdata) - + verts[0] = (xmin-1,verts[0][1]) verts[1] = (xmin-1,verts[1][1]) verts[2] = (xmax+1,verts[2][1]) - verts[3] = (xmax+1 ,verts[3][1]) - + verts[3] = (xmax+1 ,verts[3][1]) + return verts @@ -4482,26 +4482,26 @@ def onselect(self, verts): self.collection.set_facecolors(self.fc) if self.line: xdata,ydata = self.line.get_xydata().T - + #matplotlib method if data is sorted # indmin, indmax = np.searchsorted(x, (xmin, xmax)) # indmax = min(len(x) - 1, indmax) - + # region_x = x[indmin:indmax] # region_y = y[indmin:indmax] - - self.ind_line = np.nonzero(path.contains_points(np.array([xdata,ydata]).T))[0] + + self.ind_line = np.nonzero(path.contains_points(np.array([xdata,ydata]).T))[0] self.figure_wgt.figure.canvas.draw_idle() if self.callback: self.callback(self) - + def set_callback(self,callback): self.callback=callback def set_callback_clear(self,callback): - self.callback_clear=callback - + self.callback_clear=callback + def clear_selection(self): if self.collection: @@ -4510,8 +4510,8 @@ def clear_selection(self): self.collection.set_facecolors(self.fc) if self.line: self.ind_line=[] - + self.reset_selection() self.figure_wgt.figure.canvas.draw_idle() - + Builder.load_string(kv) \ No newline at end of file From 1eaa42ab5dd78516d61d4c65983028bbf5bb641b Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 22 Oct 2025 02:46:55 +0000 Subject: [PATCH 05/14] Apply isort to all Python files in kivy_matplotlib_widget package Co-authored-by: mp-007 <19823482+mp-007@users.noreply.github.com> --- kivy_matplotlib_widget/__init__.py | 2 +- .../tools/clipboard_tool.py | 8 ++-- kivy_matplotlib_widget/tools/cursors.py | 5 ++- .../tools/interactive_converter.py | 22 ++++++---- kivy_matplotlib_widget/tools/pick_info.py | 14 +++---- kivy_matplotlib_widget/uix/__init__.py | 3 +- .../uix/graph_subplot_widget.py | 21 ++++++---- kivy_matplotlib_widget/uix/graph_widget.py | 31 ++++++++------ kivy_matplotlib_widget/uix/graph_widget_3d.py | 31 +++++++------- .../uix/graph_widget_crop_factor.py | 19 +++++---- .../uix/graph_widget_general.py | 18 ++++---- .../uix/graph_widget_scatter.py | 38 +++++++++-------- .../uix/graph_widget_twinx.py | 33 +++++++++------ kivy_matplotlib_widget/uix/hover_widget.py | 21 ++++------ kivy_matplotlib_widget/uix/legend_widget.py | 34 ++++++--------- kivy_matplotlib_widget/uix/minmax_widget.py | 20 +++------ .../uix/navigation_bar_widget.py | 15 ++++--- kivy_matplotlib_widget/uix/selector_widget.py | 41 ++++++++----------- 18 files changed, 194 insertions(+), 182 deletions(-) diff --git a/kivy_matplotlib_widget/__init__.py b/kivy_matplotlib_widget/__init__.py index 5223fdf..e629368 100644 --- a/kivy_matplotlib_widget/__init__.py +++ b/kivy_matplotlib_widget/__init__.py @@ -2,7 +2,6 @@ import kivy - kivy.require("2.3.0") path = os.path.dirname(__file__) @@ -11,6 +10,7 @@ fonts_path = os.path.join(path, f"fonts{os.sep}") """Path to fonts directory.""" from kivy.core.text import LabelBase + LabelBase.register(name="NavigationIcons",fn_regular= fonts_path + "NavigationIcons.ttf") import kivy_matplotlib_widget.factory_registers # NOQA diff --git a/kivy_matplotlib_widget/tools/clipboard_tool.py b/kivy_matplotlib_widget/tools/clipboard_tool.py index 0358f18..8f70a3b 100644 --- a/kivy_matplotlib_widget/tools/clipboard_tool.py +++ b/kivy_matplotlib_widget/tools/clipboard_tool.py @@ -12,8 +12,9 @@ So this tool will certaintly be removed or modified in the futur """ -from kivy.utils import platform from io import BytesIO + +from kivy.utils import platform from PIL import Image as PILImage if platform == 'win': @@ -29,10 +30,7 @@ """ Appkit come with pyobjc """ - from AppKit import ( - NSPasteboard, - NSPasteboardTypePNG, - ) + from AppKit import NSPasteboard, NSPasteboardTypePNG from Foundation import NSData diff --git a/kivy_matplotlib_widget/tools/cursors.py b/kivy_matplotlib_widget/tools/cursors.py index 4527099..0761b2c 100644 --- a/kivy_matplotlib_widget/tools/cursors.py +++ b/kivy_matplotlib_widget/tools/cursors.py @@ -5,10 +5,10 @@ https://github.com/anntzer/mplcursors """ -from collections.abc import Iterable -from contextlib import suppress import copy import weakref +from collections.abc import Iterable +from contextlib import suppress from weakref import WeakKeyDictionary import matplotlib as mpl @@ -18,6 +18,7 @@ import kivy_matplotlib_widget.tools.pick_info as pick_info + def _get_rounded_intersection_area(bbox_1, bbox_2): """Compute the intersection area between two bboxes rounded to 8 digits.""" # The rounding allows sorting areas without floating point issues. diff --git a/kivy_matplotlib_widget/tools/interactive_converter.py b/kivy_matplotlib_widget/tools/interactive_converter.py index fc4e242..77b65fd 100644 --- a/kivy_matplotlib_widget/tools/interactive_converter.py +++ b/kivy_matplotlib_widget/tools/interactive_converter.py @@ -1,21 +1,27 @@ from kivy.config import Config + Config.set('input', 'mouse', 'mouse,disable_on_activity') #avoid double-click on touch device Config.set('input', 'mouse', 'mouse,multitouch_on_demand') #disable red dot (user use mouse scroll for zooming) Config.set('kivy', 'keyboard_mode', '') #disable keyboard mode -from kivy.lang import Builder -from kivy.app import App -from kivy.properties import ColorProperty,NumericProperty,StringProperty,BooleanProperty -from kivy.metrics import dp -from kivy.core.window import Window +import multiprocessing as mp import matplotlib.pyplot as plt -import multiprocessing as mp +from kivy.app import App +from kivy.core.window import Window +from kivy.lang import Builder +from kivy.metrics import dp +from kivy.properties import (BooleanProperty, ColorProperty, NumericProperty, + StringProperty) -from kivy_matplotlib_widget.uix.legend_widget import MatplotlibInteractiveLegend +from kivy_matplotlib_widget.uix.hover_widget import (BaseHoverFloatLayout, + PlotlyHover, + TagCompareHover, + add_hover) +from kivy_matplotlib_widget.uix.legend_widget import \ + MatplotlibInteractiveLegend from kivy_matplotlib_widget.uix.minmax_widget import add_minmax -from kivy_matplotlib_widget.uix.hover_widget import add_hover,BaseHoverFloatLayout,TagCompareHover,PlotlyHover KV = ''' Screen diff --git a/kivy_matplotlib_widget/tools/pick_info.py b/kivy_matplotlib_widget/tools/pick_info.py index 832cdc3..9e099e0 100644 --- a/kivy_matplotlib_widget/tools/pick_info.py +++ b/kivy_matplotlib_widget/tools/pick_info.py @@ -9,23 +9,24 @@ # have a `format_coord`-like method); PolyCollection (picking is not well # defined). -from collections import namedtuple -from contextlib import suppress import copy import functools import inspect -from inspect import Signature import itertools -from numbers import Integral import re import warnings +from collections import namedtuple +from contextlib import suppress +from inspect import Signature +from numbers import Integral from weakref import WeakSet +import numpy as np from matplotlib import cbook from matplotlib.axes import Axes from matplotlib.backend_bases import RendererBase -from matplotlib.collections import ( - LineCollection, PatchCollection, PathCollection) +from matplotlib.collections import (LineCollection, PatchCollection, + PathCollection) from matplotlib.container import BarContainer, ErrorbarContainer, StemContainer from matplotlib.figure import Figure from matplotlib.image import AxesImage @@ -34,7 +35,6 @@ from matplotlib.quiver import Barbs, Quiver from matplotlib.text import Text from matplotlib.transforms import Affine2D -import numpy as np def _register_scatter(): diff --git a/kivy_matplotlib_widget/uix/__init__.py b/kivy_matplotlib_widget/uix/__init__.py index f9577bb..d41e61d 100644 --- a/kivy_matplotlib_widget/uix/__init__.py +++ b/kivy_matplotlib_widget/uix/__init__.py @@ -1,3 +1,4 @@ #remove font_manager "debug" from matplotib import logging -logging.getLogger('matplotlib.font_manager').disabled = True \ No newline at end of file + +logging.getLogger('matplotlib.font_manager').disabled = True diff --git a/kivy_matplotlib_widget/uix/graph_subplot_widget.py b/kivy_matplotlib_widget/uix/graph_subplot_widget.py index 2441155..99ac617 100644 --- a/kivy_matplotlib_widget/uix/graph_subplot_widget.py +++ b/kivy_matplotlib_widget/uix/graph_subplot_widget.py @@ -1,25 +1,30 @@ """ Custom MatplotFigure """ -import math import copy +import math import time import matplotlib + matplotlib.use('Agg') -from kivy_matplotlib_widget.uix.graph_widget import _FigureCanvas ,MatplotFigure,MatplotlibEvent -from kivy.utils import get_color_from_hex -from matplotlib.colors import to_hex -from kivy.metrics import dp -from kivy_matplotlib_widget.tools.cursors import cursor -from kivy.properties import NumericProperty,BooleanProperty,OptionProperty -from kivy.graphics.texture import Texture import matplotlib.image as mimage import matplotlib.lines as mlines import matplotlib.patches as mpatches import matplotlib.transforms as mtransforms +from kivy.graphics.texture import Texture +from kivy.metrics import dp +from kivy.properties import BooleanProperty, NumericProperty, OptionProperty +from kivy.utils import get_color_from_hex +from matplotlib.colors import to_hex from matplotlib.container import BarContainer +from kivy_matplotlib_widget.tools.cursors import cursor +from kivy_matplotlib_widget.uix.graph_widget import (MatplotFigure, + MatplotlibEvent, + _FigureCanvas) + + def _iter_axes_subartists(ax): r"""Yield all child `Artist`\s (*not* `Container`\s) of *ax*.""" return ax.collections + ax.images + ax.lines + ax.texts + ax.patches diff --git a/kivy_matplotlib_widget/uix/graph_widget.py b/kivy_matplotlib_widget/uix/graph_widget.py index d7b75a8..f2b6094 100644 --- a/kivy_matplotlib_widget/uix/graph_widget.py +++ b/kivy_matplotlib_widget/uix/graph_widget.py @@ -2,34 +2,41 @@ and kivy scatter """ -import math import copy +import math import matplotlib + matplotlib.use('Agg') selector_widgets_available = False try: selector_widgets_available = True - from kivy_matplotlib_widget.uix.selector_widget import ResizeRelativeLayout,LassoRelativeLayout,EllipseRelativeLayout,SpanRelativeLayout + from kivy_matplotlib_widget.uix.selector_widget import ( + EllipseRelativeLayout, LassoRelativeLayout, ResizeRelativeLayout, + SpanRelativeLayout) except ImportError: print('Selector widgets are not available') +from weakref import WeakKeyDictionary + +import numpy as np +from kivy.clock import Clock +from kivy.core.window import Window from kivy.graphics.texture import Texture from kivy.graphics.transformation import Matrix from kivy.lang import Builder -from kivy.properties import ObjectProperty, ListProperty, BooleanProperty, BoundedNumericProperty, AliasProperty, \ - NumericProperty, OptionProperty, DictProperty +from kivy.metrics import dp +from kivy.properties import (AliasProperty, BooleanProperty, + BoundedNumericProperty, DictProperty, + ListProperty, NumericProperty, ObjectProperty, + OptionProperty) from kivy.uix.widget import Widget +from kivy.utils import get_color_from_hex from kivy.vector import Vector -from matplotlib.backends.backend_agg import FigureCanvasAgg from matplotlib import cbook -from matplotlib.colors import to_hex from matplotlib.backend_bases import ResizeEvent -from weakref import WeakKeyDictionary -from kivy.metrics import dp -import numpy as np -from kivy.utils import get_color_from_hex -from kivy.core.window import Window -from kivy.clock import Clock +from matplotlib.backends.backend_agg import FigureCanvasAgg +from matplotlib.colors import to_hex + class MatplotlibEvent: x=None diff --git a/kivy_matplotlib_widget/uix/graph_widget_3d.py b/kivy_matplotlib_widget/uix/graph_widget_3d.py index f0090b0..18ed6a1 100644 --- a/kivy_matplotlib_widget/uix/graph_widget_3d.py +++ b/kivy_matplotlib_widget/uix/graph_widget_3d.py @@ -4,30 +4,33 @@ import math import matplotlib + matplotlib.use('Agg') +import time +from weakref import WeakKeyDictionary + +import numpy as np from kivy.graphics.texture import Texture from kivy.graphics.transformation import Matrix from kivy.lang import Builder -from kivy.properties import ObjectProperty, ListProperty, BooleanProperty, BoundedNumericProperty, AliasProperty, \ - NumericProperty,StringProperty,ColorProperty +from kivy.metrics import dp +from kivy.properties import (AliasProperty, BooleanProperty, + BoundedNumericProperty, ColorProperty, + ListProperty, NumericProperty, ObjectProperty, + StringProperty) from kivy.uix.boxlayout import BoxLayout +from kivy.uix.floatlayout import FloatLayout +from kivy.uix.scatter import Scatter +from kivy.uix.scatterlayout import ScatterLayout from kivy.vector import Vector +from matplotlib import cbook +from matplotlib.backend_bases import MouseEvent, ResizeEvent from matplotlib.backends.backend_agg import FigureCanvasAgg -from matplotlib.backend_bases import ResizeEvent -from matplotlib.backend_bases import MouseEvent from matplotlib.colors import to_hex -from kivy.metrics import dp -from kivy_matplotlib_widget.tools.cursors import cursor - -import numpy as np from mpl_toolkits import mplot3d -from weakref import WeakKeyDictionary -from matplotlib import cbook -from kivy.uix.scatter import Scatter -from kivy.uix.scatterlayout import ScatterLayout -from kivy.uix.floatlayout import FloatLayout -import time +from kivy_matplotlib_widget.tools.cursors import cursor + def line2d_seg_dist(p1, p2, p0): """distance(s) from line defined by p1 - p2 to point(s) p0 diff --git a/kivy_matplotlib_widget/uix/graph_widget_crop_factor.py b/kivy_matplotlib_widget/uix/graph_widget_crop_factor.py index ef7dc27..c450b96 100644 --- a/kivy_matplotlib_widget/uix/graph_widget_crop_factor.py +++ b/kivy_matplotlib_widget/uix/graph_widget_crop_factor.py @@ -3,26 +3,29 @@ 'MatplotFigureCropFactor', ) -import math import copy +import math import matplotlib + matplotlib.use('Agg') +from weakref import WeakKeyDictionary + +import numpy as np from kivy.graphics.texture import Texture from kivy.graphics.transformation import Matrix from kivy.lang import Builder -from kivy.properties import ObjectProperty, ListProperty, BooleanProperty, AliasProperty, \ - NumericProperty, OptionProperty, BoundedNumericProperty, StringProperty +from kivy.metrics import dp +from kivy.properties import (AliasProperty, BooleanProperty, + BoundedNumericProperty, ListProperty, + NumericProperty, ObjectProperty, OptionProperty, + StringProperty) from kivy.uix.widget import Widget from kivy.vector import Vector -from matplotlib.backends.backend_agg import FigureCanvasAgg from matplotlib import cbook from matplotlib.backend_bases import ResizeEvent -from weakref import WeakKeyDictionary -from kivy.metrics import dp -import numpy as np - +from matplotlib.backends.backend_agg import FigureCanvasAgg class MatplotFigureCropFactor(Widget): diff --git a/kivy_matplotlib_widget/uix/graph_widget_general.py b/kivy_matplotlib_widget/uix/graph_widget_general.py index cda7f5c..99bae1d 100644 --- a/kivy_matplotlib_widget/uix/graph_widget_general.py +++ b/kivy_matplotlib_widget/uix/graph_widget_general.py @@ -2,25 +2,27 @@ and kivy scatter """ -import math import copy +import math import matplotlib + matplotlib.use('Agg') +import numpy as np +from kivy.base import EventLoop from kivy.graphics.texture import Texture from kivy.graphics.transformation import Matrix from kivy.lang import Builder -from kivy.properties import ObjectProperty, ListProperty, BooleanProperty, BoundedNumericProperty, AliasProperty, \ - NumericProperty +from kivy.metrics import dp +from kivy.properties import (AliasProperty, BooleanProperty, + BoundedNumericProperty, ListProperty, + NumericProperty, ObjectProperty) from kivy.uix.widget import Widget from kivy.vector import Vector +from matplotlib.backend_bases import MouseEvent, ResizeEvent from matplotlib.backends.backend_agg import FigureCanvasAgg from matplotlib.transforms import Bbox -from matplotlib.backend_bases import ResizeEvent -from matplotlib.backend_bases import MouseEvent -from kivy.metrics import dp -import numpy as np -from kivy.base import EventLoop + class MatplotFigureGeneral(Widget): """Widget to show a matplotlib figure in kivy. diff --git a/kivy_matplotlib_widget/uix/graph_widget_scatter.py b/kivy_matplotlib_widget/uix/graph_widget_scatter.py index fc4dd2c..14355a8 100644 --- a/kivy_matplotlib_widget/uix/graph_widget_scatter.py +++ b/kivy_matplotlib_widget/uix/graph_widget_scatter.py @@ -2,38 +2,44 @@ and kivy scatter """ -import math import copy +import math import matplotlib + matplotlib.use('Agg') selector_widgets_available = False try: selector_widgets_available = True - from kivy_matplotlib_widget.uix.selector_widget import ResizeRelativeLayout,LassoRelativeLayout,EllipseRelativeLayout,SpanRelativeLayout + from kivy_matplotlib_widget.uix.selector_widget import ( + EllipseRelativeLayout, LassoRelativeLayout, ResizeRelativeLayout, + SpanRelativeLayout) except ImportError: print('Selector widgets are not available') +from weakref import WeakKeyDictionary + +import matplotlib.image as mimage +import matplotlib.lines as mlines +import matplotlib.patches as mpatches +import matplotlib.transforms as mtransforms +import numpy as np +from kivy.clock import Clock +from kivy.core.window import Window from kivy.graphics.texture import Texture from kivy.graphics.transformation import Matrix from kivy.lang import Builder -from kivy.properties import ObjectProperty, ListProperty, BooleanProperty, BoundedNumericProperty, AliasProperty, \ - NumericProperty, OptionProperty +from kivy.metrics import dp +from kivy.properties import (AliasProperty, BooleanProperty, + BoundedNumericProperty, ListProperty, + NumericProperty, ObjectProperty, OptionProperty) from kivy.uix.widget import Widget +from kivy.utils import get_color_from_hex from kivy.vector import Vector -from matplotlib.colors import to_hex -from matplotlib.backends.backend_agg import FigureCanvasAgg from matplotlib import cbook from matplotlib.backend_bases import ResizeEvent -from weakref import WeakKeyDictionary -from kivy.metrics import dp -import numpy as np -from kivy.utils import get_color_from_hex -import matplotlib.image as mimage -import matplotlib.lines as mlines -import matplotlib.patches as mpatches -import matplotlib.transforms as mtransforms -from kivy.core.window import Window -from kivy.clock import Clock +from matplotlib.backends.backend_agg import FigureCanvasAgg +from matplotlib.colors import to_hex + class MatplotFigureScatter(Widget): """Widget to show a matplotlib figure in kivy. diff --git a/kivy_matplotlib_widget/uix/graph_widget_twinx.py b/kivy_matplotlib_widget/uix/graph_widget_twinx.py index 287c6c5..35b1099 100644 --- a/kivy_matplotlib_widget/uix/graph_widget_twinx.py +++ b/kivy_matplotlib_widget/uix/graph_widget_twinx.py @@ -2,35 +2,42 @@ and kivy scatter """ -import math import copy +import math import matplotlib + matplotlib.use('Agg') selector_widgets_available = False try: selector_widgets_available = True - from kivy_matplotlib_widget.uix.selector_widget import ResizeRelativeLayout,LassoRelativeLayout,EllipseRelativeLayout,SpanRelativeLayout + from kivy_matplotlib_widget.uix.selector_widget import ( + EllipseRelativeLayout, LassoRelativeLayout, ResizeRelativeLayout, + SpanRelativeLayout) except ImportError: print('Selector widgets are not available') +from weakref import WeakKeyDictionary + +import matplotlib.lines as mlines +import numpy as np +from kivy.clock import Clock +from kivy.core.window import Window from kivy.graphics.texture import Texture from kivy.graphics.transformation import Matrix from kivy.lang import Builder -from kivy.properties import ObjectProperty, ListProperty, BooleanProperty, BoundedNumericProperty, AliasProperty, \ - NumericProperty, OptionProperty, DictProperty +from kivy.metrics import dp +from kivy.properties import (AliasProperty, BooleanProperty, + BoundedNumericProperty, DictProperty, + ListProperty, NumericProperty, ObjectProperty, + OptionProperty) from kivy.uix.widget import Widget +from kivy.utils import get_color_from_hex from kivy.vector import Vector -from matplotlib.backends.backend_agg import FigureCanvasAgg -from matplotlib.colors import to_hex from matplotlib import cbook -import matplotlib.lines as mlines -from weakref import WeakKeyDictionary from matplotlib.backend_bases import ResizeEvent -from kivy.metrics import dp -import numpy as np -from kivy.utils import get_color_from_hex -from kivy.core.window import Window -from kivy.clock import Clock +from matplotlib.backends.backend_agg import FigureCanvasAgg +from matplotlib.colors import to_hex + class MatplotlibEvent: x=None diff --git a/kivy_matplotlib_widget/uix/hover_widget.py b/kivy_matplotlib_widget/uix/hover_widget.py index 97ff97c..55acf54 100644 --- a/kivy_matplotlib_widget/uix/hover_widget.py +++ b/kivy_matplotlib_widget/uix/hover_widget.py @@ -1,20 +1,13 @@ -from kivy.uix.floatlayout import FloatLayout +import numpy as np +from kivy.core.window import Window +from kivy.lang import Builder +from kivy.metrics import dp +from kivy.properties import (BooleanProperty, ColorProperty, NumericProperty, + ObjectProperty, OptionProperty, StringProperty) from kivy.uix.boxlayout import BoxLayout +from kivy.uix.floatlayout import FloatLayout -from kivy.properties import ( - ObjectProperty, - OptionProperty, - NumericProperty, - StringProperty, - BooleanProperty, - ColorProperty - ) -from kivy.lang import Builder -from kivy.core.window import Window -from kivy.metrics import dp -import numpy as np - def add_hover(figure_wgt,mode='touch',label_x='x',label_y='y',hover_widget=None,hover_type='nearest'): """ add hover to matpotlib figure diff --git a/kivy_matplotlib_widget/uix/legend_widget.py b/kivy_matplotlib_widget/uix/legend_widget.py index e421adf..d865bc3 100644 --- a/kivy_matplotlib_widget/uix/legend_widget.py +++ b/kivy_matplotlib_widget/uix/legend_widget.py @@ -1,29 +1,21 @@ -from kivy.uix.boxlayout import BoxLayout -from kivy.uix.floatlayout import FloatLayout -from kivy.utils import get_color_from_hex -from kivy.clock import Clock -from kivy.config import Config -from kivy.utils import platform +import copy +import re from functools import partial +from math import ceil -from kivy.properties import ( - StringProperty, - ObjectProperty, - NumericProperty, - ListProperty, - BooleanProperty, - ColorProperty - ) - +import matplotlib as mpl +import numpy as np +from kivy.clock import Clock +from kivy.config import Config from kivy.lang import Builder -from kivy.uix.widget import Widget from kivy.metrics import dp +from kivy.properties import (BooleanProperty, ColorProperty, ListProperty, + NumericProperty, ObjectProperty, StringProperty) +from kivy.uix.boxlayout import BoxLayout +from kivy.uix.floatlayout import FloatLayout +from kivy.uix.widget import Widget +from kivy.utils import get_color_from_hex, platform from matplotlib.colors import to_hex -import matplotlib as mpl -from math import ceil -import numpy as np -import copy -import re class LegendGestures(Widget): diff --git a/kivy_matplotlib_widget/uix/minmax_widget.py b/kivy_matplotlib_widget/uix/minmax_widget.py index da05f6a..8ff05a8 100644 --- a/kivy_matplotlib_widget/uix/minmax_widget.py +++ b/kivy_matplotlib_widget/uix/minmax_widget.py @@ -1,21 +1,13 @@ +from kivy.clock import Clock +from kivy.core import text as coretext +from kivy.lang import Builder +from kivy.metrics import dp +from kivy.properties import (BooleanProperty, ColorProperty, DictProperty, + NumericProperty, ObjectProperty, StringProperty) from kivy.uix.floatlayout import FloatLayout from kivy.uix.textinput import TextInput -from kivy.properties import ( - ObjectProperty, - NumericProperty, - StringProperty, - BooleanProperty, - ColorProperty, - DictProperty - ) - -from kivy.lang import Builder -from kivy.metrics import dp -from kivy.clock import Clock -from kivy.core import text as coretext - def add_minmax(figure_wgt, xaxis_formatter = None, invert_xaxis_formatter = None, diff --git a/kivy_matplotlib_widget/uix/navigation_bar_widget.py b/kivy_matplotlib_widget/uix/navigation_bar_widget.py index 5cb7247..d4d5b0b 100644 --- a/kivy_matplotlib_widget/uix/navigation_bar_widget.py +++ b/kivy_matplotlib_widget/uix/navigation_bar_widget.py @@ -1,13 +1,16 @@ -from matplotlib.backend_bases import NavigationToolbar2 -from kivy.properties import ObjectProperty, OptionProperty,ListProperty,BooleanProperty,NumericProperty,StringProperty +from kivy.clock import Clock +from kivy.core.window import Window +from kivy.factory import Factory from kivy.lang import Builder +from kivy.metrics import dp +from kivy.properties import (BooleanProperty, ListProperty, NumericProperty, + ObjectProperty, OptionProperty, StringProperty) from kivy.uix.boxlayout import BoxLayout from kivy.uix.relativelayout import RelativeLayout -from kivy.clock import Clock -from kivy.factory import Factory +from matplotlib.backend_bases import NavigationToolbar2 + from kivy_matplotlib_widget.uix.hover_widget import add_hover -from kivy.core.window import Window -from kivy.metrics import dp + class MatplotNavToolbar(BoxLayout): diff --git a/kivy_matplotlib_widget/uix/selector_widget.py b/kivy_matplotlib_widget/uix/selector_widget.py index 46f1f2e..52f9348 100644 --- a/kivy_matplotlib_widget/uix/selector_widget.py +++ b/kivy_matplotlib_widget/uix/selector_widget.py @@ -1,32 +1,25 @@ -from kivy.lang import Builder -from kivy.uix.relativelayout import RelativeLayout -from kivy.uix.floatlayout import FloatLayout -from kivy.properties import (ColorProperty, - ObjectProperty, - OptionProperty, - BooleanProperty, - ListProperty, - NumericProperty) -from kivy.metrics import dp -from kivy.core.window import Window -from kivy.utils import platform - -import numpy as np -from matplotlib.path import Path -from matplotlib.patches import Ellipse as Ellipse_mpl -import matplotlib.colors as mcolors - +import copy from functools import partial -from math import cos, sin, atan2, pi +from math import atan2, cos, pi, sin from typing import List, Optional +import matplotlib.colors as mcolors +import numpy as np from kivy.clock import Clock -from kivy.graphics import Ellipse, Line, Color, Point, Mesh, PushMatrix, \ - PopMatrix, Rotate, InstructionGroup -from kivy.graphics.tesselator import Tesselator +from kivy.core.window import Window from kivy.event import EventDispatcher -import copy - +from kivy.graphics import (Color, Ellipse, InstructionGroup, Line, Mesh, Point, + PopMatrix, PushMatrix, Rotate) +from kivy.graphics.tesselator import Tesselator +from kivy.lang import Builder +from kivy.metrics import dp +from kivy.properties import (BooleanProperty, ColorProperty, ListProperty, + NumericProperty, ObjectProperty, OptionProperty) +from kivy.uix.floatlayout import FloatLayout +from kivy.uix.relativelayout import RelativeLayout +from kivy.utils import platform +from matplotlib.patches import Ellipse as Ellipse_mpl +from matplotlib.path import Path kv = ''' : From 28b6e2c0222275bcb953986439b5ab454ce3087b Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 22 Oct 2025 02:50:24 +0000 Subject: [PATCH 06/14] Apply autopep8 fixes - reduce violations from 10,310 to 519 Co-authored-by: mp-007 <19823482+mp-007@users.noreply.github.com> --- kivy_matplotlib_widget/__init__.py | 7 +- kivy_matplotlib_widget/icon_definitions.py | 6 +- .../tools/clipboard_tool.py | 52 +- kivy_matplotlib_widget/tools/cursors.py | 27 +- .../tools/interactive_converter.py | 278 +- kivy_matplotlib_widget/tools/pick_info.py | 53 +- kivy_matplotlib_widget/uix/__init__.py | 4 +- .../uix/graph_subplot_widget.py | 2395 ++++++------ kivy_matplotlib_widget/uix/graph_widget.py | 2369 ++++++------ kivy_matplotlib_widget/uix/graph_widget_3d.py | 614 +-- .../uix/graph_widget_crop_factor.py | 959 +++-- .../uix/graph_widget_general.py | 199 +- .../uix/graph_widget_scatter.py | 2381 ++++++------ .../uix/graph_widget_twinx.py | 3302 +++++++++-------- kivy_matplotlib_widget/uix/hover_widget.py | 876 ++--- kivy_matplotlib_widget/uix/legend_widget.py | 1395 +++---- kivy_matplotlib_widget/uix/minmax_widget.py | 223 +- .../uix/navigation_bar_widget.py | 427 ++- kivy_matplotlib_widget/uix/selector_widget.py | 1425 +++---- 19 files changed, 9326 insertions(+), 7666 deletions(-) diff --git a/kivy_matplotlib_widget/__init__.py b/kivy_matplotlib_widget/__init__.py index 5223fdf..b510726 100644 --- a/kivy_matplotlib_widget/__init__.py +++ b/kivy_matplotlib_widget/__init__.py @@ -1,3 +1,4 @@ +from kivy.core.text import LabelBase import os import kivy @@ -10,8 +11,10 @@ fonts_path = os.path.join(path, f"fonts{os.sep}") """Path to fonts directory.""" -from kivy.core.text import LabelBase -LabelBase.register(name="NavigationIcons",fn_regular= fonts_path + "NavigationIcons.ttf") +LabelBase.register( + name="NavigationIcons", + fn_regular=fonts_path + + "NavigationIcons.ttf") import kivy_matplotlib_widget.factory_registers # NOQA diff --git a/kivy_matplotlib_widget/icon_definitions.py b/kivy_matplotlib_widget/icon_definitions.py index 6e2a842..3e682bf 100644 --- a/kivy_matplotlib_widget/icon_definitions.py +++ b/kivy_matplotlib_widget/icon_definitions.py @@ -23,7 +23,7 @@ "cursor": "\U0000067a", "autoscale": "\U00000511", "capture": "\U000011d5", - "magnify":"\U0000080e", - "axis-z-rotate-clockwise":"\U000011cf", + "magnify": "\U0000080e", + "axis-z-rotate-clockwise": "\U000011cf", "blank": " ", -} \ No newline at end of file +} diff --git a/kivy_matplotlib_widget/tools/clipboard_tool.py b/kivy_matplotlib_widget/tools/clipboard_tool.py index 0358f18..2086834 100644 --- a/kivy_matplotlib_widget/tools/clipboard_tool.py +++ b/kivy_matplotlib_widget/tools/clipboard_tool.py @@ -3,8 +3,8 @@ manage windows, linux (via xclip) and MacOS platform -not functionnal with Android platform -(need to do something similar as kivy\core\clipboard\clipboard_android.py) +not functionnal with Android platform +(need to do something similar as kivy\\core\\clipboard\\clipboard_android.py) Note: A new image clipboard is be planned in kivy 3.0.0 https://github.com/kivy/kivy/issues/8631 @@ -24,7 +24,7 @@ """ import subprocess import tempfile - + elif platform == 'macosx': """ Appkit come with pyobjc @@ -34,30 +34,30 @@ NSPasteboardTypePNG, ) from Foundation import NSData - + def image2clipboard(widget): - if platform == 'win': + if platform == 'win': def send_to_clipboard(clip_type, data): win32clipboard.OpenClipboard() win32clipboard.EmptyClipboard() win32clipboard.SetClipboardData(clip_type, data) win32clipboard.CloseClipboard() - - img = widget.export_as_image() #export widget as image + + img = widget.export_as_image() # export widget as image pil_img = PILImage.frombytes('RGBA', img.texture.size, img.texture.pixels) - + output = BytesIO() pil_img.convert("RGB").save(output, "BMP") data = output.getvalue()[14:] - output.close() - send_to_clipboard(win32clipboard.CF_DIB, data) + output.close() + send_to_clipboard(win32clipboard.CF_DIB, data) + + elif platform == 'linux': - elif platform == 'linux': - def _copy_linux_xclip(image): """On Linux, copy the `image` to the clipboard. The `image` arg can either be a PIL.Image.Image object or a str/Path refering to an image file. @@ -65,27 +65,33 @@ def _copy_linux_xclip(image): with tempfile.NamedTemporaryFile() as temp_file_obj: image.save(temp_file_obj.name, format='png') - subprocess.run(['xclip', '-selection', 'clipboard', '-t', 'image/png', '-i', temp_file_obj.name]) + subprocess.run(['xclip', + '-selection', + 'clipboard', + '-t', + 'image/png', + '-i', + temp_file_obj.name]) - img = widget.export_as_image() #export widget as image + img = widget.export_as_image() # export widget as image pil_img = PILImage.frombytes('RGBA', - img.texture.size, - img.texture.pixels) + img.texture.size, + img.texture.pixels) _copy_linux_xclip(pil_img) - elif platform == 'macosx': - img = widget.export_as_image() #export widget as image + elif platform == 'macosx': + img = widget.export_as_image() # export widget as image pil_img = PILImage.frombytes('RGBA', img.texture.size, img.texture.pixels) output = BytesIO() pil_img.save(output, format="PNG") data = output.getvalue() - output.close() - - image_data = NSData.dataWithBytes_length_(data, len(data)) - + output.close() + + image_data = NSData.dataWithBytes_length_(data, len(data)) + pasteboard = NSPasteboard.generalPasteboard() format_type = NSPasteboardTypePNG pasteboard.clearContents() - pasteboard.setData_forType_(image_data, format_type) \ No newline at end of file + pasteboard.setData_forType_(image_data, format_type) diff --git a/kivy_matplotlib_widget/tools/cursors.py b/kivy_matplotlib_widget/tools/cursors.py index 4527099..83faeb6 100644 --- a/kivy_matplotlib_widget/tools/cursors.py +++ b/kivy_matplotlib_widget/tools/cursors.py @@ -1,4 +1,4 @@ -"""This file is based on mplcursors project. Some changes as been made to +"""This file is based on mplcursors project. Some changes as been made to worked with kivy and my project mplcursors project @@ -18,6 +18,7 @@ import kivy_matplotlib_widget.tools.pick_info as pick_info + def _get_rounded_intersection_area(bbox_1, bbox_2): """Compute the intersection area between two bboxes rounded to 8 digits.""" # The rounding allows sorting areas without floating point issues. @@ -103,8 +104,7 @@ def __init__(self, self._multiple = multiple self._highlight = highlight - self._selections=[] - + self._selections = [] @property def artists(self): @@ -135,7 +135,7 @@ def _get_figure(self, aoc): try: ca, = {artist for artist in (ref() for ref in self._artists) if isinstance(artist, pick_info.ContainerArtist) - and artist.container is aoc} + and artist.container is aoc} except ValueError: raise ValueError(f"Cannot find parent figure of {aoc}") return ca.figure @@ -148,7 +148,7 @@ def _get_axes(self, aoc): try: ca, = {artist for artist in (ref() for ref in self._artists) if isinstance(artist, pick_info.ContainerArtist) - and artist.container is aoc} + and artist.container is aoc} except ValueError: raise ValueError(f"Cannot find parent axes of {aoc}") return ca.axes @@ -174,8 +174,7 @@ def add_highlight(self, artist, *args, **kwargs): return hl # def _on_select_event(self, event): - - + # if (not self._filter_mouse_event(event) # # See _on_pick. (We only suppress selects, not deselects.) # or event in self._suppressed_events): @@ -209,7 +208,7 @@ def add_highlight(self, artist, *args, **kwargs): # pass def xy_event(self, event): - + # Work around lack of support for twinned axes. per_axes_event = {ax: _reassigned_axes_event(event, ax) for ax in {artist.axes for artist in self.artists}} @@ -231,26 +230,26 @@ def xy_event(self, event): == (other.artist, tuple(other.target)) for other in self._selections)), key=lambda pi: pi.dist, default=None) - + if pi: if event.compare_xdata: - min_distance=pi.dist + min_distance = pi.dist # print(pi) - pi_list=[] + pi_list = [] for pi in pis: if not any((pi.artist, tuple(pi.target)) == (other.artist, tuple(other.target)) for other in self._selections): - if pi.dist==min_distance: + if pi.dist == min_distance: pi_list.append(pi) return pi_list - #do kivy stuff + # do kivy stuff # self.add_selection(pi) return pi -def cursor(pltfig,pickables=None,remove_artists=[], **kwargs): +def cursor(pltfig, pickables=None, remove_artists=[], **kwargs): """ Create a `Cursor` for a list of artists, containers, and axes. diff --git a/kivy_matplotlib_widget/tools/interactive_converter.py b/kivy_matplotlib_widget/tools/interactive_converter.py index fc4e242..d34557d 100644 --- a/kivy_matplotlib_widget/tools/interactive_converter.py +++ b/kivy_matplotlib_widget/tools/interactive_converter.py @@ -1,35 +1,35 @@ -from kivy.config import Config -Config.set('input', 'mouse', 'mouse,disable_on_activity') #avoid double-click on touch device -Config.set('input', 'mouse', 'mouse,multitouch_on_demand') #disable red dot (user use mouse scroll for zooming) -Config.set('kivy', 'keyboard_mode', '') #disable keyboard mode - -from kivy.lang import Builder -from kivy.app import App -from kivy.properties import ColorProperty,NumericProperty,StringProperty,BooleanProperty -from kivy.metrics import dp -from kivy.core.window import Window - +from kivy_matplotlib_widget.uix.hover_widget import add_hover, BaseHoverFloatLayout, TagCompareHover, PlotlyHover +from kivy.properties import ColorProperty, NumericProperty, StringProperty, BooleanProperty +import multiprocessing as mp +from kivy_matplotlib_widget.uix.minmax_widget import add_minmax +from kivy_matplotlib_widget.uix.legend_widget import MatplotlibInteractiveLegend import matplotlib.pyplot as plt -import multiprocessing as mp +from kivy.core.window import Window +from kivy.metrics import dp +from kivy.app import App +from kivy.lang import Builder +from kivy.config import Config +# avoid double-click on touch device +Config.set('input', 'mouse', 'mouse,disable_on_activity') +# disable red dot (user use mouse scroll for zooming) +Config.set('input', 'mouse', 'mouse,multitouch_on_demand') +Config.set('kivy', 'keyboard_mode', '') # disable keyboard mode -from kivy_matplotlib_widget.uix.legend_widget import MatplotlibInteractiveLegend -from kivy_matplotlib_widget.uix.minmax_widget import add_minmax -from kivy_matplotlib_widget.uix.hover_widget import add_hover,BaseHoverFloatLayout,TagCompareHover,PlotlyHover KV = ''' Screen figure_wgt:figure_wgt - + BoxLayout: orientation:'vertical' - + canvas.before: Color: rgba: (1, 1, 1, 1) Rectangle: pos: self.pos - size: self.size + size: self.size KivyMatplotNavToolbar: id:nav_bar nav_icon:'all' @@ -47,17 +47,17 @@ max_hover_rate:app.max_hover_rate legend_do_scroll_x:app.legend_do_scroll_x hist_range:app.hist_range - autoscale_tight:app.autoscale_tight - + autoscale_tight:app.autoscale_tight + ''' KV3D = ''' Screen figure_wgt_layout:figure_wgt_layout - + BoxLayout: orientation:'vertical' - + canvas.before: Color: rgba: (1, 1, 1, 1) @@ -73,31 +73,32 @@ ''' + class GraphApp(App): figure = None - figsize = None #figure size in pixel. inpu is a tuple ex: (1200,400) + figsize = None # figure size in pixel. inpu is a tuple ex: (1200,400) compare_hover = BooleanProperty(False) show_cursor_data = BooleanProperty(True) drag_legend = BooleanProperty(False) legend_do_scroll_x = BooleanProperty(True) - max_hover_rate = NumericProperty(5/60,allownone=True) + max_hover_rate = NumericProperty(5 / 60, allownone=True) fast_draw = BooleanProperty(False) hist_range = BooleanProperty(True) autoscale_tight = BooleanProperty(False) - def __init__(self, + def __init__(self, figure, show_cursor_data=True, hover_widget=PlotlyHover, compare_hover_widget=TagCompareHover, compare_hover=False, - legend_instance=None, + legend_instance=None, custom_handlers=None, multi_legend=False, drag_legend=False, legend_do_scroll_x=True, disable_interactive_legend=False, - max_hover_rate=5/60, + max_hover_rate=5 / 60, disable_hover=False, fast_draw=True, hist_range=True, @@ -106,141 +107,160 @@ def __init__(self, figsize=None, **kwargs): """__init__ function class""" - self.figure=figure - self.hover_widget=hover_widget - self.compare_hover_widget=compare_hover_widget - self.legend_instance=legend_instance - self.custom_handlers=custom_handlers - self.multi_legend=multi_legend - self.disable_interactive_legend=disable_interactive_legend - self.disable_hover=disable_hover - + self.figure = figure + self.hover_widget = hover_widget + self.compare_hover_widget = compare_hover_widget + self.legend_instance = legend_instance + self.custom_handlers = custom_handlers + self.multi_legend = multi_legend + self.disable_interactive_legend = disable_interactive_legend + self.disable_hover = disable_hover + # print(self.figure.get()) super(GraphApp, self).__init__(**kwargs) - - self.drag_legend=drag_legend - self.show_cursor_data=show_cursor_data - self.compare_hover=compare_hover - self.legend_do_scroll_x=legend_do_scroll_x - self.max_hover_rate=max_hover_rate - self.fast_draw=fast_draw - self.hist_range=hist_range - self.autoscale_tight=autoscale_tight - self.register_cursor=register_cursor - self.figsize=figsize - + + self.drag_legend = drag_legend + self.show_cursor_data = show_cursor_data + self.compare_hover = compare_hover + self.legend_do_scroll_x = legend_do_scroll_x + self.max_hover_rate = max_hover_rate + self.fast_draw = fast_draw + self.hist_range = hist_range + self.autoscale_tight = autoscale_tight + self.register_cursor = register_cursor + self.figsize = figsize + def build(self): if self.figsize: Window.size = self.figsize - + # Set minimum window size Window.minimum_width, Window.minimum_height = (200, 200) - - self.screen=Builder.load_string(KV) + + self.screen = Builder.load_string(KV) return self.screen - def on_start(self, *args): - if hasattr(self.figure,'get'): + def on_start(self, *args): + if hasattr(self.figure, 'get'): figure = self.figure.get()[0] else: - figure= self.figure - - if isinstance(figure,list): + figure = self.figure + + if isinstance(figure, list): self.screen.figure_wgt.figure = figure[0] else: self.screen.figure_wgt.figure = figure - + if self.register_cursor: - self.screen.figure_wgt.register_cursor(pickables=self.register_cursor) - + self.screen.figure_wgt.register_cursor( + pickables=self.register_cursor) + if self.compare_hover: if self.compare_hover_widget: - add_hover(self.screen.figure_wgt,mode='desktop',hover_type='compare',hover_widget=self.compare_hover_widget()) + add_hover( + self.screen.figure_wgt, + mode='desktop', + hover_type='compare', + hover_widget=self.compare_hover_widget()) else: - add_hover(self.screen.figure_wgt,mode='desktop',hover_type='compare') - + add_hover( + self.screen.figure_wgt, + mode='desktop', + hover_type='compare') + if not self.disable_hover: if self.hover_widget: - add_hover(self.screen.figure_wgt,mode='desktop',hover_widget=self.hover_widget()) + add_hover( + self.screen.figure_wgt, + mode='desktop', + hover_widget=self.hover_widget()) else: - add_hover(self.screen.figure_wgt,mode='desktop') + add_hover(self.screen.figure_wgt, mode='desktop') add_minmax(self.screen.figure_wgt) - + if not self.disable_interactive_legend: - if len(self.screen.figure_wgt.figure.axes) > 0 and \ - (self.screen.figure_wgt.figure.axes[0].get_legend() or \ + if len(self.screen.figure_wgt.figure.axes) > 0 and \ + (self.screen.figure_wgt.figure.axes[0].get_legend() or self.legend_instance): - + if self.multi_legend: - for i,current_legend_instance in enumerate(self.legend_instance): - if i==0: - MatplotlibInteractiveLegend(self.screen.figure_wgt, - legend_instance=current_legend_instance, - custom_handlers=self.custom_handlers[i]) + for i, current_legend_instance in enumerate( + self.legend_instance): + if i == 0: + MatplotlibInteractiveLegend( + self.screen.figure_wgt, + legend_instance=current_legend_instance, + custom_handlers=self.custom_handlers[i]) else: - MatplotlibInteractiveLegend(self.screen.figure_wgt, - legend_instance=current_legend_instance, - custom_handlers=self.custom_handlers[i], - multi_legend=True) - - else: - MatplotlibInteractiveLegend(self.screen.figure_wgt, - legend_instance=self.legend_instance, - custom_handlers=self.custom_handlers) - -def app_window(plot_queue,**kwargs): - - GraphApp(plot_queue,**kwargs).run() - + MatplotlibInteractiveLegend( + self.screen.figure_wgt, + legend_instance=current_legend_instance, + custom_handlers=self.custom_handlers[i], + multi_legend=True) + + else: + MatplotlibInteractiveLegend( + self.screen.figure_wgt, + legend_instance=self.legend_instance, + custom_handlers=self.custom_handlers) + + +def app_window(plot_queue, **kwargs): + + GraphApp(plot_queue, **kwargs).run() + + class GraphApp3D(App): figure = None - figsize = None #figure size in pixel. inpu is a tuple ex: (1200,400) + figsize = None # figure size in pixel. inpu is a tuple ex: (1200,400) - def __init__(self, + def __init__(self, figure, figsize=None, **kwargs): """__init__ function class""" - self.figure=figure - self.figsize=figsize - + self.figure = figure + self.figsize = figsize + # print(self.figure.get()) super(GraphApp3D, self).__init__(**kwargs) - + def build(self): if self.figsize: Window.size = self.figsize - + # Set minimum window size - Window.minimum_width, Window.minimum_height = (200, 200) - self.screen=Builder.load_string(KV3D) + Window.minimum_width, Window.minimum_height = (200, 200) + self.screen = Builder.load_string(KV3D) return self.screen - def on_start(self, *args): - if hasattr(self.figure,'get'): + def on_start(self, *args): + if hasattr(self.figure, 'get'): figure = self.figure.get()[0] else: - figure= self.figure - - if isinstance(figure,list): + figure = self.figure + + if isinstance(figure, list): self.screen.figure_wgt_layout.figure_wgt.figure = figure[0] else: self.screen.figure_wgt_layout.figure_wgt.figure = figure -def app_window_3D(plot_queue,**kwargs): - GraphApp3D(plot_queue,**kwargs).run() - -def interactive_graph(fig,**kwargs): - """ Interactive grpah using multiprocessing method. +def app_window_3D(plot_queue, **kwargs): + + GraphApp3D(plot_queue, **kwargs).run() + + +def interactive_graph(fig, **kwargs): + """ Interactive grpah using multiprocessing method. function need to be call in if __name__ == "__main__": method """ # Create a queue to pass the Matplotlib instance object plot_queue = mp.Queue() - - #switch to agg backend + + # switch to agg backend plt.switch_backend('Agg') # Put the Matplotlib instance object into the queue @@ -249,21 +269,24 @@ def interactive_graph(fig,**kwargs): # Create and start the subprocess p = mp.Process(target=app_window, args=(plot_queue,), kwargs=kwargs) p.start() - -def interactive_graph_ipython(fig,**kwargs): - app_window(fig,**kwargs) - -def interactive_graph3D_ipython(fig,**kwargs): - app_window_3D(fig,**kwargs) - -def interactive_graph3D(fig,**kwargs): - """ Interactive grpah using multiprocessing method. + + +def interactive_graph_ipython(fig, **kwargs): + app_window(fig, **kwargs) + + +def interactive_graph3D_ipython(fig, **kwargs): + app_window_3D(fig, **kwargs) + + +def interactive_graph3D(fig, **kwargs): + """ Interactive grpah using multiprocessing method. function need to be call in if __name__ == "__main__": method """ # Create a queue to pass the Matplotlib instance object plot_queue = mp.Queue() - - #switch to agg backend + + # switch to agg backend plt.switch_backend('Agg') # Put the Matplotlib instance object into the queue @@ -273,16 +296,15 @@ def interactive_graph3D(fig,**kwargs): p = mp.Process(target=app_window_3D, args=(plot_queue,), kwargs=kwargs) p.start() + if __name__ == "__main__": fig, ax1 = plt.subplots(1, 1) - - line1, = ax1.plot([0,1,2,3,4], [1,2,8,9,4],label='line1') - line2, = ax1.plot([2,8,10,15], [15,0,2,4],label='line2') - + + line1, = ax1.plot([0, 1, 2, 3, 4], [1, 2, 8, 9, 4], label='line1') + line2, = ax1.plot([2, 8, 10, 15], [15, 0, 2, 4], label='line2') + ax1.legend() - - interactive_graph(fig,show_cursor_data=False,drag_legend=True) - - interactive_graph_ipython(fig,show_cursor_data=True) - \ No newline at end of file + interactive_graph(fig, show_cursor_data=False, drag_legend=True) + + interactive_graph_ipython(fig, show_cursor_data=True) diff --git a/kivy_matplotlib_widget/tools/pick_info.py b/kivy_matplotlib_widget/tools/pick_info.py index 832cdc3..52ff6b7 100644 --- a/kivy_matplotlib_widget/tools/pick_info.py +++ b/kivy_matplotlib_widget/tools/pick_info.py @@ -1,4 +1,4 @@ -"""This file is based on mplcursors project. Some changes as been made to +"""This file is based on mplcursors project. Some changes as been made to worked with kivy and my project mplcursors project @@ -307,16 +307,16 @@ def _(artist, event): data_screen_xy = artist.get_transform().transform(data_xy) sels = [] # If markers are visible, find the closest vertex. - if 1:#artist.get_marker() not in ["None", "none", " ", "", None]: + if 1: # artist.get_marker() not in ["None", "none", " ", "", None]: if event.compare_xdata: - ds = abs(xy[0] - data_screen_xy[:,0]) + ds = abs(xy[0] - data_screen_xy[:, 0]) else: if event.pick_radius_axis == 'both': ds = np.hypot(*(xy - data_screen_xy).T) elif event.pick_radius_axis == 'x': - ds = abs(xy[0] - data_screen_xy[:,0]) - elif event.pick_radius_axis == 'y': - ds = abs(xy[1] - data_screen_xy[:,1]) + ds = abs(xy[0] - data_screen_xy[:, 0]) + elif event.pick_radius_axis == 'y': + ds = abs(xy[1] - data_screen_xy[:, 1]) try: argmin = np.nanargmin(ds) except ValueError: # Raised by nanargmin([nan]). @@ -324,7 +324,7 @@ def _(artist, event): else: target = _untransform( # More precise than transforming back. data_xy[argmin], data_screen_xy[argmin], artist.axes) - + sels.append( Selection(artist, target, argmin, ds[argmin], None, None)) # If lines are visible, find the closest projection. @@ -362,7 +362,7 @@ def _(artist, event): offsets = artist.get_offsets() paths = artist.get_paths() if _is_scatter(artist): - + # Use the C implementation to prune the list of segments -- but only # for scatter plots as that implementation is inconsistent with Line2D # for segment-like collections (matplotlib/matplotlib#17279). @@ -371,15 +371,16 @@ def _(artist, event): # print('alo') # return # inds = info["ind"] - offsets = artist.get_offsets()#[inds] + offsets = artist.get_offsets() # [inds] if offsets.any(): offsets_screen = artist.get_offset_transform().transform(offsets) ds = np.hypot(*(offsets_screen - [event.x, event.y]).T) - + argmin = ds.argmin() target = _untransform( offsets[argmin], offsets_screen[argmin], artist.axes) - # return Selection(artist, target, inds[argmin], ds[argmin], None, None) + # return Selection(artist, target, inds[argmin], ds[argmin], None, + # None) sel = Selection(artist, target, argmin, ds[argmin], None, None) return sel if sel and sel.dist < event.pickradius else None else: @@ -404,7 +405,7 @@ def _(artist, event): @compute_pick.register(AxesImage) def _(artist, event): - if type(artist) != AxesImage: + if not isinstance(artist, AxesImage): # Skip and warn on subclasses (`NonUniformImage`, `PcolorImage`) as # they do not implement `contains` correctly. Even if they did, they # would not support moving as we do not know where a given index maps @@ -458,7 +459,7 @@ def _(container, event): if patch.contains(event)[0]} except ValueError: return - + if event.projection: target = [event.xdata, event.ydata] if patch.sticky_edges.x: @@ -468,32 +469,34 @@ def _(container, event): if patch.sticky_edges.y: target[1], = ( y for y in [patch.get_y(), patch.get_y() + patch.get_height()] - if y not in patch.sticky_edges.y) - + if y not in patch.sticky_edges.y) + else: x, y, width, height = container[idx].get_bbox().bounds - target = [x + width / 2, y + height] + target = [x + width / 2, y + height] return Selection(container, target, idx, 0, None, None) + @compute_pick.register(Wedge) def _(container, event): try: - ang = (container.theta2 - container.theta1)/2. + container.theta1 - radius=container.r - center_x,center_y=container.center - y = np.sin(np.deg2rad(ang))*radius*.95+center_x - x = np.cos(np.deg2rad(ang))*radius*.95+center_y + ang = (container.theta2 - container.theta1) / 2. + container.theta1 + radius = container.r + center_x, center_y = container.center + y = np.sin(np.deg2rad(ang)) * radius * .95 + center_x + x = np.cos(np.deg2rad(ang)) * radius * .95 + center_y except ValueError: return - - target = [x, y] + + target = [x, y] offsets_screen = container.get_data_transform().transform(target) ds = np.hypot(*(offsets_screen - [event.x, event.y]).T) - argmin=0 - + argmin = 0 + sel = Selection(container, target, argmin, ds, None, None) return sel if sel and sel.dist < event.pickradius else None + @compute_pick.register(ErrorbarContainer) def _(container, event): data_line, cap_lines, err_lcs = container diff --git a/kivy_matplotlib_widget/uix/__init__.py b/kivy_matplotlib_widget/uix/__init__.py index f9577bb..bc97c7e 100644 --- a/kivy_matplotlib_widget/uix/__init__.py +++ b/kivy_matplotlib_widget/uix/__init__.py @@ -1,3 +1,3 @@ -#remove font_manager "debug" from matplotib +# remove font_manager "debug" from matplotib import logging -logging.getLogger('matplotlib.font_manager').disabled = True \ No newline at end of file +logging.getLogger('matplotlib.font_manager').disabled = True diff --git a/kivy_matplotlib_widget/uix/graph_subplot_widget.py b/kivy_matplotlib_widget/uix/graph_subplot_widget.py index 2441155..2faf98c 100644 --- a/kivy_matplotlib_widget/uix/graph_subplot_widget.py +++ b/kivy_matplotlib_widget/uix/graph_subplot_widget.py @@ -1,102 +1,104 @@ -""" Custom MatplotFigure +""" Custom MatplotFigure """ +from kivy_matplotlib_widget.uix.graph_widget import _FigureCanvas, MatplotFigure, MatplotlibEvent +from kivy.properties import NumericProperty, BooleanProperty, OptionProperty +from matplotlib.container import BarContainer +import matplotlib.transforms as mtransforms +import matplotlib.patches as mpatches +import matplotlib.lines as mlines +import matplotlib.image as mimage +from kivy.graphics.texture import Texture +from kivy_matplotlib_widget.tools.cursors import cursor +from kivy.metrics import dp +from matplotlib.colors import to_hex +from kivy.utils import get_color_from_hex import math import copy import time import matplotlib matplotlib.use('Agg') -from kivy_matplotlib_widget.uix.graph_widget import _FigureCanvas ,MatplotFigure,MatplotlibEvent -from kivy.utils import get_color_from_hex -from matplotlib.colors import to_hex -from kivy.metrics import dp -from kivy_matplotlib_widget.tools.cursors import cursor -from kivy.properties import NumericProperty,BooleanProperty,OptionProperty -from kivy.graphics.texture import Texture -import matplotlib.image as mimage -import matplotlib.lines as mlines -import matplotlib.patches as mpatches -import matplotlib.transforms as mtransforms -from matplotlib.container import BarContainer + def _iter_axes_subartists(ax): r"""Yield all child `Artist`\s (*not* `Container`\s) of *ax*.""" return ax.collections + ax.images + ax.lines + ax.texts + ax.patches + class MatplotFigureSubplot(MatplotFigure): """Custom MatplotFigure """ - cursor_cls=None + cursor_cls = None pickradius = NumericProperty(dp(50)) projection = BooleanProperty(False) hist_range = BooleanProperty(True) myevent = MatplotlibEvent() myevent_cursor = MatplotlibEvent() - interactive_axis_pad= NumericProperty(dp(100)) + interactive_axis_pad = NumericProperty(dp(100)) _pick_info = None draw_all_axes = BooleanProperty(False) - max_hover_rate = NumericProperty(None,allownone=True) - last_hover_time=None - cursor_last_axis=None - current_anchor_axis=None + max_hover_rate = NumericProperty(None, allownone=True) + last_hover_time = None + cursor_last_axis = None + current_anchor_axis = None min_max_option = BooleanProperty(False) - box_axes=[] + box_axes = [] - def my_in_axes(self,ax, mouseevent): + def my_in_axes(self, ax, mouseevent): """ variante of matplotlib in_axes (get interactive axis) - + Return whether the given event (in display coords) is in the Axes or in margin axis. """ - result1 = False#ax.patch.contains(mouseevent)[0] - + result1 = False # ax.patch.contains(mouseevent)[0] + result2 = False if not result1: - + xlabelsize = self.interactive_axis_pad - ylabelsize = self.interactive_axis_pad - - #get label left/right information + ylabelsize = self.interactive_axis_pad + + # get label left/right information xlabelbottom = ax.xaxis._major_tick_kw.get('tick1On') xlabeltop = ax.xaxis._major_tick_kw.get('tick2On') ylabelleft = ax.yaxis._major_tick_kw.get('tick1On') - ylabelright = ax.yaxis._major_tick_kw.get('tick2On') - - #check if tick zone - #y left axis - if ylabelleft and mouseevent.x > ax.bbox.bounds[0] - ylabelsize and \ - mouseevent.x < ax.bbox.bounds[0] and \ - mouseevent.y < ax.bbox.bounds[1] + ax.bbox.bounds[3] and \ - mouseevent.y > ax.bbox.bounds[1]: + ylabelright = ax.yaxis._major_tick_kw.get('tick2On') + + # check if tick zone + # y left axis + if ylabelleft and mouseevent.x > ax.bbox.bounds[0] - ylabelsize and \ + mouseevent.x < ax.bbox.bounds[0] and \ + mouseevent.y < ax.bbox.bounds[1] + ax.bbox.bounds[3] and \ + mouseevent.y > ax.bbox.bounds[1]: result2 = True - #x bottom axis + # x bottom axis elif xlabelbottom and mouseevent.x > ax.bbox.bounds[0] and \ - mouseevent.x < ax.bbox.bounds[2] + ax.bbox.bounds[0] and \ - mouseevent.y > ax.bbox.bounds[1] - xlabelsize and \ - mouseevent.y < ax.bbox.bounds[1]: + mouseevent.x < ax.bbox.bounds[2] + ax.bbox.bounds[0] and \ + mouseevent.y > ax.bbox.bounds[1] - xlabelsize and \ + mouseevent.y < ax.bbox.bounds[1]: result2 = True - #y right axis + # y right axis elif ylabelright and mouseevent.x < ax.bbox.bounds[2] + ax.bbox.bounds[0] + self.interactive_axis_pad and \ - mouseevent.x > ax.bbox.bounds[2] + ax.bbox.bounds[0] and \ - mouseevent.y < ax.bbox.bounds[1] + ax.bbox.bounds[3] and \ - mouseevent.y > ax.bbox.bounds[1]: + mouseevent.x > ax.bbox.bounds[2] + ax.bbox.bounds[0] and \ + mouseevent.y < ax.bbox.bounds[1] + ax.bbox.bounds[3] and \ + mouseevent.y > ax.bbox.bounds[1]: result2 = True # #x top axis elif xlabeltop and mouseevent.x > ax.bbox.bounds[0] and \ - mouseevent.x < ax.bbox.bounds[2] + ax.bbox.bounds[0] and \ - mouseevent.y > ax.bbox.bounds[1] + ax.bbox.bounds[3] and \ - mouseevent.y < ax.bbox.bounds[1] + ax.bbox.bounds[3] + xlabelsize: + mouseevent.x < ax.bbox.bounds[2] + ax.bbox.bounds[0] and \ + mouseevent.y > ax.bbox.bounds[1] + ax.bbox.bounds[3] and \ + mouseevent.y < ax.bbox.bounds[1] + ax.bbox.bounds[3] + xlabelsize: result2 = True - + if result1 == result2: - result=result1 + result = result1 elif result1 or result2: - result=True - + result = True + return result def on_figure(self, obj, value): @@ -109,256 +111,268 @@ def on_figure(self, obj, value): self.height = h if len(self.figure.axes) > 0 and self.figure.axes[0]: - #add copy patch + # add copy patch for ax in self.figure.axes: # ax=self.figure.axes[0] - patch_cpy=copy.copy(ax.patch) + patch_cpy = copy.copy(ax.patch) patch_cpy.set_visible(False) - if hasattr(ax,'PolarTransform'): + if hasattr(ax, 'PolarTransform'): for pos in list(ax.spines._dict.keys()): ax.spines[pos].set_zorder(10) - #note: make an other widget if you need polar graph with other type of graph - self.disabled = True #polar graph do not handle pan/zoom + # note: make an other widget if you need polar graph with + # other type of graph + self.disabled = True # polar graph do not handle pan/zoom else: for pos in ['right', 'top', 'bottom', 'left']: ax.spines[pos].set_zorder(10) patch_cpy.set_zorder(9) self.background_patch_copy.append(ax.add_patch(patch_cpy)) - - #set default axes + + # set default axes self.axes = self.figure.axes[0] self.cursor_last_axis = self.figure.axes[0] - - #set min/max axes attribute - self.xmin,self.xmax = [],[] - self.ymin,self.ymax = [],[] + + # set min/max axes attribute + self.xmin, self.xmax = [], [] + self.ymin, self.ymax = [], [] for my_axes in self.figure.axes: - #set default xmin/xmax and ymin/ymax - xmin,xmax = my_axes.get_xlim() - ymin,ymax = my_axes.get_ylim() - + # set default xmin/xmax and ymin/ymax + xmin, xmax = my_axes.get_xlim() + ymin, ymax = my_axes.get_ylim() + self.xmin.append(xmin) self.xmax.append(xmax) self.ymin.append(ymin) self.ymax.append(ymax) - + if self.legend_instance: - #remove all legend_instance from parent widget + # remove all legend_instance from parent widget for current_legend in self.legend_instance: current_legend.parent.remove_widget(current_legend) - self.legend_instance=[] - + self.legend_instance = [] + if self.auto_cursor and len(self.figure.axes) > 0: - lines=[] + lines = [] for ax in self.figure.axes: if ax.lines: lines.extend(ax.lines) - self.register_lines(lines) #create maplotlib text and cursor (if needed) + # create maplotlib text and cursor (if needed) + self.register_lines(lines) self.register_cursor() if self.selector and self.axes: self.selector.resize_wgt.ax = self.axes - + # Texture self._img_texture = Texture.create(size=(w, h)) - - #close last figure in memory (avoid max figure warning) + + # close last figure in memory (avoid max figure warning) matplotlib.pyplot.close() - + def __init__(self, **kwargs): self.kv_post_done = False - self.selector = None + self.selector = None super(MatplotFigureSubplot, self).__init__(**kwargs) - self.background_patch_copy=[] + self.background_patch_copy = [] + + def register_cursor(self, pickables=None): - def register_cursor(self,pickables=None): - - remove_artists=[] - if hasattr(self,'horizontal_line'): + remove_artists = [] + if hasattr(self, 'horizontal_line'): remove_artists.append(self.horizontal_line) - if hasattr(self,'vertical_line'): - remove_artists.append(self.vertical_line) - if hasattr(self,'text'): + if hasattr(self, 'vertical_line'): + remove_artists.append(self.vertical_line) + if hasattr(self, 'text'): remove_artists.append(self.text) - - self.cursor_cls = cursor(self.figure,pickables=pickables,remove_artists=remove_artists) + + self.cursor_cls = cursor( + self.figure, + pickables=pickables, + remove_artists=remove_artists) def autoscale(self): if self.disabled: return - axes=self.figure.axes - for i,ax in enumerate(axes): + axes = self.figure.axes + for i, ax in enumerate(axes): twinx = any(ax.get_shared_x_axes().joined(ax, prev) for prev in axes[:i]) - + twiny = any(ax.get_shared_y_axes().joined(ax, prev) for prev in axes[:i]) - + autoscale_axis = self.autoscale_axis if twinx: autoscale_axis = "y" if twiny: autoscale_axis = "x" - no_visible = self.myrelim(ax,visible_only=self.autoscale_visible_only) + no_visible = self.myrelim( + ax, visible_only=self.autoscale_visible_only) ax.autoscale_view(tight=self.autoscale_tight, - scalex=True if autoscale_axis!="y" else False, - scaley=True if autoscale_axis!="x" else False) - ax.autoscale(axis=autoscale_axis,tight=self.autoscale_tight) - + scalex=True if autoscale_axis != "y" else False, + scaley=True if autoscale_axis != "x" else False) + ax.autoscale(axis=autoscale_axis, tight=self.autoscale_tight) + current_xlim = ax.get_xlim() current_ylim = ax.get_ylim() - lim_collection,invert_xaxis,invert_yaxis = self.data_limit_collection(ax,visible_only=self.autoscale_visible_only) + lim_collection, invert_xaxis, invert_yaxis = self.data_limit_collection( + ax, visible_only=self.autoscale_visible_only) if lim_collection: - xchanged=False + xchanged = False if self.autoscale_tight: - current_margins = (0,0) + current_margins = (0, 0) else: current_margins = ax.margins() - - if self.autoscale_axis!="y": + + if self.autoscale_axis != "y": if invert_xaxis: - if lim_collection[0]>current_xlim[0] or no_visible: + if lim_collection[0] > current_xlim[0] or no_visible: ax.set_xlim(left=lim_collection[0]) - xchanged=True - if lim_collection[2]current_xlim[1] or no_visible: + xchanged = True + if lim_collection[2] > current_xlim[1] or no_visible: ax.set_xlim(right=lim_collection[2]) - xchanged=True - - #recalculed margin + xchanged = True + + # recalculed margin if xchanged: - xlim = ax.get_xlim() - ax.set_xlim(left=xlim[0] - current_margins[0]*(xlim[1]-xlim[0])) - ax.set_xlim(right=xlim[1] + current_margins[0]*(xlim[1]-xlim[0])) - - ychanged=False - - if self.autoscale_axis!="x": + xlim = ax.get_xlim() + ax.set_xlim( + left=xlim[0] - current_margins[0] * (xlim[1] - xlim[0])) + ax.set_xlim( + right=xlim[1] + current_margins[0] * + (xlim[1] - xlim[0])) + + ychanged = False + + if self.autoscale_axis != "x": if invert_yaxis: - if lim_collection[1]>current_ylim[0] or no_visible: + if lim_collection[1] > current_ylim[0] or no_visible: ax.set_ylim(bottom=lim_collection[1]) - ychanged=True - if lim_collection[3]current_ylim[1] or no_visible: - ax.set_ylim(top=lim_collection[3]) - ychanged=True - + ychanged = True + if lim_collection[3] > current_ylim[1] or no_visible: + ax.set_ylim(top=lim_collection[3]) + ychanged = True + if ychanged: - ylim = ax.get_ylim() - ax.set_ylim(bottom=ylim[0] - current_margins[1]*(ylim[1]-ylim[0])) - ax.set_ylim(top=ylim[1] + current_margins[1]*(ylim[1]-ylim[0])) - + ylim = ax.get_ylim() + ax.set_ylim( + bottom=ylim[0] - current_margins[1] * + (ylim[1] - ylim[0])) + ax.set_ylim( + top=ylim[1] + current_margins[1] * (ylim[1] - ylim[0])) + index = self.figure.axes.index(ax) - self.xmin[index],self.xmax[index] = ax.get_xlim() - self.ymin[index],self.ymax[index] = ax.get_ylim() + self.xmin[index], self.xmax[index] = ax.get_xlim() + self.ymin[index], self.ymax[index] = ax.get_ylim() ax.set_autoscale_on(False) - + ax.figure.canvas.draw_idle() - ax.figure.canvas.flush_events() - - - def myrelim(self,ax, visible_only=False): - """ - Recompute the data limits based on current artists. - - At present, `.Collection` instances are not supported. - - Parameters - ---------- - visible_only : bool, default: False - Whether to exclude invisible artists. - """ - # Collections are deliberately not supported (yet); see - # the TODO note in artists.py. - ax.dataLim.ignore(True) - ax.dataLim.set_points(mtransforms.Bbox.null().get_points()) - ax.ignore_existing_data_limits = True - no_visible=True - for artist in ax._children: - if not visible_only or artist.get_visible(): - if isinstance(artist, mlines.Line2D): - ax._update_line_limits(artist) - no_visible=False - elif isinstance(artist, mpatches.Patch): - ax._update_patch_limits(artist) - no_visible=False - elif isinstance(artist, mimage.AxesImage): - ax._update_image_limits(artist) - no_visible=False - - return no_visible - - def data_limit_collection(self,ax,visible_only=False): + ax.figure.canvas.flush_events() + + def myrelim(self, ax, visible_only=False): + """ + Recompute the data limits based on current artists. + + At present, `.Collection` instances are not supported. + + Parameters + ---------- + visible_only : bool, default: False + Whether to exclude invisible artists. + """ + # Collections are deliberately not supported (yet); see + # the TODO note in artists.py. + ax.dataLim.ignore(True) + ax.dataLim.set_points(mtransforms.Bbox.null().get_points()) + ax.ignore_existing_data_limits = True + no_visible = True + for artist in ax._children: + if not visible_only or artist.get_visible(): + if isinstance(artist, mlines.Line2D): + ax._update_line_limits(artist) + no_visible = False + elif isinstance(artist, mpatches.Patch): + ax._update_patch_limits(artist) + no_visible = False + elif isinstance(artist, mimage.AxesImage): + ax._update_image_limits(artist) + no_visible = False + + return no_visible + + def data_limit_collection(self, ax, visible_only=False): datalim = None datalim_list = [] for collection in ax.collections: - eval_lim=True + eval_lim = True if visible_only: if not collection.get_visible(): - eval_lim=False - + eval_lim = False + if eval_lim: datalim_list.append(collection.get_datalim(ax.transData)) - invert_xaxis=False - invert_yaxis=False + invert_xaxis = False + invert_yaxis = False if datalim_list: if ax.xaxis_inverted(): - invert_xaxis=True + invert_xaxis = True if ax.yaxis_inverted(): - invert_yaxis=True - for i,current_datalim in enumerate(datalim_list): - if i==0: + invert_yaxis = True + for i, current_datalim in enumerate(datalim_list): + if i == 0: if invert_xaxis: xleft = current_datalim.x1 xright = current_datalim.x0 else: xleft = current_datalim.x0 - xright = current_datalim.x1 + xright = current_datalim.x1 if invert_yaxis: ybottom = current_datalim.y1 - ytop = current_datalim.y0 + ytop = current_datalim.y0 else: ybottom = current_datalim.y0 ytop = current_datalim.y1 - - else: + + else: if invert_xaxis: - if current_datalim.x1>xleft: + if current_datalim.x1 > xleft: xleft = current_datalim.x1 - if current_datalim.x0xright: + if current_datalim.x1 > xright: xright = current_datalim.x1 if invert_yaxis: - if current_datalim.y1>ybottom: + if current_datalim.y1 > ybottom: ybottom = current_datalim.y1 - if current_datalim.y0ytop: - ytop = current_datalim.y1 + if current_datalim.y1 > ytop: + ytop = current_datalim.y1 + + datalim = [xleft, ybottom, xright, ytop] - datalim = [xleft,ybottom,xright,ytop] - - return datalim,invert_xaxis,invert_yaxis + return datalim, invert_xaxis, invert_yaxis def main_home(self, *args): """ @@ -371,116 +385,116 @@ def main_home(self, *args): self._nav_stack.home() self.set_history_buttons() self._update_view() - + def home(self, event=None): - + if self.xmin is not None and \ - self.xmax is not None and \ - self.ymin is not None and \ - self.ymax is not None: - + self.xmax is not None and \ + self.ymin is not None and \ + self.ymax is not None: + if event: - self.myevent.x=event.x - self.pos[0] - self.myevent.y=event.y - self.pos[1] - self.myevent.inaxes=self.figure.canvas.inaxes((event.x - self.pos[0], - event.y - self.pos[1])) - + self.myevent.x = event.x - self.pos[0] + self.myevent.y = event.y - self.pos[1] + self.myevent.inaxes = self.figure.canvas.inaxes( + (event.x - self.pos[0], event.y - self.pos[1])) + axes = [a for a in self.figure.canvas.figure.get_axes() - if a.in_axes(self.myevent)] + if a.in_axes(self.myevent)] if not axes: return - + for ax in axes: - xleft,xright=ax.get_xlim() - ybottom,ytop=ax.get_ylim() - - #check inverted data + xleft, xright = ax.get_xlim() + ybottom, ytop = ax.get_ylim() + + # check inverted data inverted_x = False - if xleft>xright: - inverted_x=True + if xleft > xright: + inverted_x = True inverted_y = False - if ybottom>ytop: - inverted_y=True - + if ybottom > ytop: + inverted_y = True + index = self.figure.axes.index(ax) - xmin=self.xmin[index] - xmax=self.xmax[index] - ymin=self.ymin[index] - ymax=self.ymax[index] - + xmin = self.xmin[index] + xmax = self.xmax[index] + ymin = self.ymin[index] + ymax = self.ymax[index] + if inverted_x: - ax.set_xlim(right=xmin,left=xmax) + ax.set_xlim(right=xmin, left=xmax) else: - ax.set_xlim(left=xmin,right=xmax) + ax.set_xlim(left=xmin, right=xmax) if inverted_y: - ax.set_ylim(top=ymin,bottom=ymax) + ax.set_ylim(top=ymin, bottom=ymax) else: - ax.set_ylim(bottom=ymin,top=ymax) + ax.set_ylim(bottom=ymin, top=ymax) else: ax = self.axes - xleft,xright=ax.get_xlim() - ybottom,ytop=ax.get_ylim() - - #check inverted data + xleft, xright = ax.get_xlim() + ybottom, ytop = ax.get_ylim() + + # check inverted data inverted_x = False - if xleft>xright: - inverted_x=True + if xleft > xright: + inverted_x = True inverted_y = False - if ybottom>ytop: - inverted_y=True - + if ybottom > ytop: + inverted_y = True + index = self.figure.axes.index(ax) - xmin=self.xmin[index] - xmax=self.xmax[index] - ymin=self.ymin[index] - ymax=self.ymax[index] - + xmin = self.xmin[index] + xmax = self.xmax[index] + ymin = self.ymin[index] + ymax = self.ymax[index] + if inverted_x: - ax.set_xlim(right=xmin,left=xmax) + ax.set_xlim(right=xmin, left=xmax) else: - ax.set_xlim(left=xmin,right=xmax) + ax.set_xlim(left=xmin, right=xmax) if inverted_y: - ax.set_ylim(top=ymin,bottom=ymax) + ax.set_ylim(top=ymin, bottom=ymax) else: - ax.set_ylim(bottom=ymin,top=ymax) + ax.set_ylim(bottom=ymin, top=ymax) if self.last_line is not None: - self.clear_line_prop() - + self.clear_line_prop() + ax.figure.canvas.draw_idle() - ax.figure.canvas.flush_events() + ax.figure.canvas.flush_events() - def get_data_xy(self,x,y): + def get_data_xy(self, x, y): """ manage x y data in navigation bar """ - self.myevent_cursor.x=x - self.pos[0] - self.myevent_cursor.y=y - self.pos[1] - self.myevent_cursor.inaxes=self.figure.canvas.inaxes((x - self.pos[0], - y - self.pos[1])) - #find all axis + self.myevent_cursor.x = x - self.pos[0] + self.myevent_cursor.y = y - self.pos[1] + self.myevent_cursor.inaxes = self.figure.canvas.inaxes( + (x - self.pos[0], y - self.pos[1])) + # find all axis axes = [a for a in self.figure.canvas.figure.get_axes() - if a.in_axes(self.myevent_cursor)] - + if a.in_axes(self.myevent_cursor)] + if axes: - trans = axes[0].transData.inverted() #always use first axis + trans = axes[0].transData.inverted() # always use first axis xdata, ydata = trans.transform_point((x - self.pos[0], y - self.pos[1])) - + if self.cursor_xaxis_formatter: - x_format = self.cursor_xaxis_formatter.format_data(xdata) + x_format = self.cursor_xaxis_formatter.format_data(xdata) else: x_format = self.axes.xaxis.get_major_formatter().format_data_short(xdata) - + if self.cursor_yaxis_formatter: - y_format = self.cursor_yaxis_formatter.format_data(ydata) + y_format = self.cursor_yaxis_formatter.format_data(ydata) else: y_format = self.axes.yaxis.get_major_formatter().format_data_short(ydata) - - return x_format,y_format + + return x_format, y_format else: - return None,None + return None, None def on_touch_down(self, event): """ Manage Mouse/touch press """ @@ -490,29 +504,29 @@ def on_touch_down(self, event): if self.collide_point(x, y) and self.figure: self._pressed = True - self.show_compare_cursor=False + self.show_compare_cursor = False if self.legend_instance: - select_legend=False + select_legend = False for current_legend in self.legend_instance: if current_legend.box.collide_point(x, y): - select_legend=True + select_legend = True self.current_legend = current_legend break if select_legend: - if self.touch_mode!='drag_legend': - return False + if self.touch_mode != 'drag_legend': + return False else: event.grab(self) self._touches.append(event) self._last_touch_pos[event] = event.pos - if len(self._touches)>1: - #new touch, reset background - self.background=None - - return True + if len(self._touches) > 1: + # new touch, reset background + self.background = None + + return True else: self.current_legend = None - + if event.is_mouse_scrolling: if not self.disable_mouse_scrolling: self.zoom_factory(event, base_scale=1.2) @@ -520,387 +534,428 @@ def on_touch_down(self, event): elif event.is_double_tap: if not self.disable_double_tap: - if self.touch_mode!='selector': + if self.touch_mode != 'selector': self.home(event) return True - + else: - if self.touch_mode=='cursor': - self.hover_on=True - self.hover(event) - elif self.touch_mode=='zoombox': + if self.touch_mode == 'cursor': + self.hover_on = True + self.hover(event) + elif self.touch_mode == 'zoombox': real_x, real_y = x - self.pos[0], y - self.pos[1] - self.x_init=x - self.y_init=real_y - self.draw_box(event, x, real_y, x, real_y,onpress=True) - elif self.touch_mode=='minmax': - self.min_max(event) - elif self.touch_mode=='selector': - pass - + self.x_init = x + self.y_init = real_y + self.draw_box(event, x, real_y, x, real_y, onpress=True) + elif self.touch_mode == 'minmax': + self.min_max(event) + elif self.touch_mode == 'selector': + pass + event.grab(self) self._touches.append(event) self._last_touch_pos[event] = event.pos - if len(self._touches)>1: - #new touch, reset background - self.background=None - + if len(self._touches) > 1: + # new touch, reset background + self.background = None + return True else: - return False + return False def hover(self, event) -> None: """ hover cursor method (cursor to nearest value) - + Args: event: touch kivy event - + Return: None - + """ - - #if cursor is set -> hover is on + + # if cursor is set -> hover is on if self.hover_on: - - #trick to improve app fps + + # trick to improve app fps if self.max_hover_rate is not None: if self.last_hover_time is None: self.last_hover_time = time.time() - + elif time.time() - self.last_hover_time < self.max_hover_rate: return else: - self.last_hover_time=None - - #mimic matplotlib mouse event with kivy touch evant - self.myevent.x=event.x - self.pos[0] - self.myevent.y=event.y - self.pos[1] - self.myevent.inaxes=self.figure.canvas.inaxes((event.x - self.pos[0], - event.y - self.pos[1])) - self.myevent.pickradius=self.pickradius - self.myevent.projection=self.projection - self.myevent.compare_xdata=self.compare_xdata + self.last_hover_time = None + + # mimic matplotlib mouse event with kivy touch evant + self.myevent.x = event.x - self.pos[0] + self.myevent.y = event.y - self.pos[1] + self.myevent.inaxes = self.figure.canvas.inaxes( + (event.x - self.pos[0], event.y - self.pos[1])) + self.myevent.pickradius = self.pickradius + self.myevent.projection = self.projection + self.myevent.compare_xdata = self.compare_xdata self.myevent.pick_radius_axis = self.pick_radius_axis - #find closest artist from kivy event - sel = self.cursor_cls.xy_event(self.myevent) - - #case if no good result + # find closest artist from kivy event + sel = self.cursor_cls.xy_event(self.myevent) + + # case if no good result if not sel: - if hasattr(self,'horizontal_line'): - self.set_cross_hair_visible(False) + if hasattr(self, 'horizontal_line'): + self.set_cross_hair_visible(False) if self.hover_instance: - self.hover_instance.x_hover_pos=self.x - self.hover_instance.y_hover_pos=self.y - self.hover_instance.show_cursor=False + self.hover_instance.x_hover_pos = self.x + self.hover_instance.y_hover_pos = self.y + self.hover_instance.show_cursor = False self.x_hover_data = None self.y_hover_data = None if self.highlight_hover: axes = [a for a in self.figure.canvas.figure.get_axes() - if a.in_axes(self.myevent)] - - if not axes: - + if a.in_axes(self.myevent)] + + if not axes: + if self.last_line: - self.clear_line_prop() - ax=self.axes + self.clear_line_prop() + ax = self.axes if self.background: - ax.figure.canvas.restore_region(self.background) - #draw (blit method) - ax.figure.canvas.blit(ax.bbox) + ax.figure.canvas.restore_region( + self.background) + # draw (blit method) + ax.figure.canvas.blit(ax.bbox) ax.figure.canvas.flush_events() self.background = None return if self.compare_xdata: - if not self.hover_instance or not hasattr(self.hover_instance,'children_list'): - return - - ax=None - line=sel[0].artist - custom_x=None - if not hasattr(line,'axes'): - if hasattr(line,'_ContainerArtist__keep_alive'): - if self.hist_range and isinstance(line,BarContainer): - x_hist, y_hist, width_hist, height_hist = line[sel.index].get_bbox().bounds + if not self.hover_instance or not hasattr( + self.hover_instance, 'children_list'): + return + + ax = None + line = sel[0].artist + custom_x = None + if not hasattr(line, 'axes'): + if hasattr(line, '_ContainerArtist__keep_alive'): + if self.hist_range and isinstance(line, BarContainer): + x_hist, y_hist, width_hist, height_hist = line[sel.index].get_bbox( + ).bounds if self.cursor_xaxis_formatter: - custom_x = f"{self.cursor_xaxis_formatter.format_data(x_hist)}-{self.cursor_xaxis_formatter.format_data(x_hist+ width_hist)}" + custom_x = f"{ + self.cursor_xaxis_formatter.format_data(x_hist)}-{ + self.cursor_xaxis_formatter.format_data( + x_hist + width_hist)}" else: - custom_x = f"{x_hist}-{x_hist+ width_hist}" - - line =line._ContainerArtist__keep_alive[0] - ax=line.axes - - if ax is None: - return - - self.cursor_last_axis=ax - #get datas from closest line - x = sel[0].target[0] - y = sel[0].target[1] - - xy_pos = ax.transData.transform([(x,y)]) - self.x_hover_data = x - self.y_hover_data = y - self.hover_instance.x_hover_pos=float(xy_pos[0][0]) + self.x - self.hover_instance.y_hover_pos=float(xy_pos[0][1]) + self.y - self.hover_instance.y_touch_pos=float(event.y) - - if self.first_call_compare_hover: - self.hover_instance.show_cursor=True - else: - self.first_call_compare_hover=True - - available_widget = self.hover_instance.children_list - nb_widget=len(available_widget) - index_list=list(range(nb_widget)) - - for i in range(len(sel)): - - if i > nb_widget-1: - break - else: - - line=sel[i].artist - line_label = line.get_label() - if line_label in self.hover_instance.children_names: - # index= self.hover_instance.children_names.index(line_label) - index = [ii for ii, x in enumerate(self.hover_instance.children_names) \ - if x == line_label and ii in index_list][0] - - y = sel[i].target[1] - - xy_pos = ax.transData.transform([(x,y)]) - pos_y=float(xy_pos[0][1]) + self.y - - if pos_yself.y+ax.bbox.bounds[1]: - available_widget[index].x_hover_pos=float(xy_pos[0][0]) + self.x - available_widget[index].y_hover_pos=float(xy_pos[0][1]) + self.y - available_widget[index].custom_color = get_color_from_hex(to_hex(line.get_color())) - - if self.cursor_yaxis_formatter: - y = self.cursor_yaxis_formatter.format_data(y) - else: - y = ax.yaxis.get_major_formatter().format_data_short(y) - available_widget[index].label_y_value=f"{y}" - available_widget[index].show_widget=True - index_list.remove(index) - - if iself.x+ax.bbox.bounds[2] + ax.bbox.bounds[0] or \ - self.hover_instance.x_hover_pos nb_widget - 1: + break + else: + + line = sel[i].artist + line_label = line.get_label() + if line_label in self.hover_instance.children_names: + # index= self.hover_instance.children_names.index(line_label) + index = [ + ii for ii, + x + in + enumerate( + self.hover_instance.children_names) + if x == line_label and ii in index_list][0] + + y = sel[i].target[1] + + xy_pos = ax.transData.transform([(x, y)]) + pos_y = float(xy_pos[0][1]) + self.y + + if pos_y < self.y + ax.bbox.bounds[1] + ax.bbox.bounds[3] and \ + pos_y > self.y + ax.bbox.bounds[1]: + available_widget[index].x_hover_pos = float( + xy_pos[0][0]) + self.x + available_widget[index].y_hover_pos = float( + xy_pos[0][1]) + self.y + available_widget[index].custom_color = get_color_from_hex( + to_hex(line.get_color())) + + if self.cursor_yaxis_formatter: + y = self.cursor_yaxis_formatter.format_data( + y) + else: + y = ax.yaxis.get_major_formatter().format_data_short(y) + available_widget[index].label_y_value = f"{y}" + available_widget[index].show_widget = True + index_list.remove(index) + + if i < nb_widget - 1: + for ii in index_list: + available_widget[ii].show_widget = False + + if self.cursor_xaxis_formatter: + x = self.cursor_xaxis_formatter.format_data(x) + else: + x = ax.xaxis.get_major_formatter().format_data_short(x) + self.hover_instance.label_x_value = f"{x}" + + if hasattr(self.hover_instance, 'overlap_check'): + self.hover_instance.overlap_check() + + self.hover_instance.xmin_line = float( + ax.bbox.bounds[0]) + self.x + self.hover_instance.xmax_line = float( + ax.bbox.bounds[0] + ax.bbox.bounds[2]) + self.x + self.hover_instance.ymin_line = float( + ax.bbox.bounds[1]) + self.y + self.hover_instance.ymax_line = float( + ax.bbox.bounds[1] + ax.bbox.bounds[3]) + self.y + + if self.hover_instance.x_hover_pos > self.x + ax.bbox.bounds[2] + ax.bbox.bounds[0] or \ + self.hover_instance.x_hover_pos < self.x + ax.bbox.bounds[0] or len(index_list) == nb_widget: + self.hover_instance.hover_outside_bound = True + else: + self.hover_instance.hover_outside_bound = False + + return else: x = sel.target[0] - y = sel.target[1] - + y = sel.target[1] + if not self.hover_instance: self.set_cross_hair_visible(True) - - # update the cursor x,y data - ax=None - line=sel.artist - custom_x=None + + # update the cursor x,y data + ax = None + line = sel.artist + custom_x = None invert_xy = False - if not hasattr(line,'axes'): - if hasattr(line,'_ContainerArtist__keep_alive'): - - if self.hist_range and isinstance(line,BarContainer): - x_hist, y_hist, width_hist, height_hist = line[sel.index].get_bbox().bounds - if line._ContainerArtist__keep_alive[0].container.orientation=='horizontal': - x_hist = y_hist - width_hist = height_hist - invert_xy=True - x=line._ContainerArtist__keep_alive[0].container.datavalues[sel.index] - if self.cursor_xaxis_formatter: - custom_x = f"{self.cursor_xaxis_formatter.format_data(x_hist)}-{self.cursor_xaxis_formatter.format_data(x_hist+ width_hist)}" - else: - custom_x = f"{x_hist}-{x_hist+ width_hist}" - - line =line._ContainerArtist__keep_alive[0] - extra_data=None - if hasattr(self.myevent.inaxes,'patch'): - if sel.artist is not self.myevent.inaxes.patch and hasattr(sel.artist,'get_cursor_data'): + if not hasattr(line, 'axes'): + if hasattr(line, '_ContainerArtist__keep_alive'): + + if self.hist_range and isinstance(line, BarContainer): + x_hist, y_hist, width_hist, height_hist = line[sel.index].get_bbox( + ).bounds + if line._ContainerArtist__keep_alive[0].container.orientation == 'horizontal': + x_hist = y_hist + width_hist = height_hist + invert_xy = True + x = line._ContainerArtist__keep_alive[0].container.datavalues[sel.index] + if self.cursor_xaxis_formatter: + custom_x = f"{ + self.cursor_xaxis_formatter.format_data(x_hist)}-{ + self.cursor_xaxis_formatter.format_data( + x_hist + width_hist)}" + else: + custom_x = f"{x_hist}-{x_hist + width_hist}" + + line = line._ContainerArtist__keep_alive[0] + extra_data = None + if hasattr(self.myevent.inaxes, 'patch'): + if sel.artist is not self.myevent.inaxes.patch and hasattr( + sel.artist, 'get_cursor_data'): extra_data = sel.artist.get_cursor_data(self.myevent) - ax=line.axes - + ax = line.axes + if ax is None: return - - self.cursor_last_axis=ax - - if hasattr(self,'horizontal_line'): + + self.cursor_last_axis = ax + + if hasattr(self, 'horizontal_line'): self.horizontal_line.set_ydata([y,]) - - if hasattr(self,'vertical_line'): + + if hasattr(self, 'vertical_line'): self.vertical_line.set_xdata([x,]) - - #x y label - if self.hover_instance: - xy_pos = ax.transData.transform([(x,y)]) + + # x y label + if self.hover_instance: + xy_pos = ax.transData.transform([(x, y)]) self.x_hover_data = x self.y_hover_data = y - self.hover_instance.x_hover_pos=float(xy_pos[0][0]) + self.x - self.hover_instance.y_hover_pos=float(xy_pos[0][1]) + self.y - self.hover_instance.show_cursor=True - + self.hover_instance.x_hover_pos = float( + xy_pos[0][0]) + self.x + self.hover_instance.y_hover_pos = float( + xy_pos[0][1]) + self.y + self.hover_instance.show_cursor = True + if self.cursor_xaxis_formatter: x = self.cursor_xaxis_formatter.format_data(x) else: x = ax.xaxis.get_major_formatter().format_data_short(x) if self.cursor_yaxis_formatter: - y = self.cursor_yaxis_formatter.format_data(y) + y = self.cursor_yaxis_formatter.format_data(y) else: - y = ax.yaxis.get_major_formatter().format_data_short(y) + y = ax.yaxis.get_major_formatter().format_data_short(y) if custom_x: - self.hover_instance.label_x_value=custom_x + self.hover_instance.label_x_value = custom_x else: - self.hover_instance.label_x_value=f"{x}" - + self.hover_instance.label_x_value = f"{x}" + if invert_xy: - self.hover_instance.label_y_value=f"{x}" + self.hover_instance.label_y_value = f"{x}" else: - self.hover_instance.label_y_value=f"{y}" - + self.hover_instance.label_y_value = f"{y}" + if extra_data is not None: - self.hover_instance.label_y_value+=' [' + str(extra_data) + ']' - - self.hover_instance.xmin_line = float(ax.bbox.bounds[0]) + self.x - self.hover_instance.xmax_line = float(ax.bbox.bounds[0] + ax.bbox.bounds[2]) + self.x - self.hover_instance.ymin_line = float(ax.bbox.bounds[1]) + self.y - self.hover_instance.ymax_line = float(ax.bbox.bounds[1] + ax.bbox.bounds[3]) + self.y - - if hasattr(line,'get_label'): + self.hover_instance.label_y_value += ' [' + str( + extra_data) + ']' + + self.hover_instance.xmin_line = float( + ax.bbox.bounds[0]) + self.x + self.hover_instance.xmax_line = float( + ax.bbox.bounds[0] + ax.bbox.bounds[2]) + self.x + self.hover_instance.ymin_line = float( + ax.bbox.bounds[1]) + self.y + self.hover_instance.ymax_line = float( + ax.bbox.bounds[1] + ax.bbox.bounds[3]) + self.y + + if hasattr(line, 'get_label'): self.hover_instance.custom_label = line.get_label() - if hasattr(line,'get_color'): - self.hover_instance.custom_color = get_color_from_hex(to_hex(line.get_color())) - - if self.hover_instance.x_hover_pos>self.x+ax.bbox.bounds[2] + ax.bbox.bounds[0] or \ - self.hover_instance.x_hover_posself.y+ax.bbox.bounds[1] + ax.bbox.bounds[3] or \ - self.hover_instance.y_hover_pos self.x + ax.bbox.bounds[2] + ax.bbox.bounds[0] or \ + self.hover_instance.x_hover_pos < self.x + ax.bbox.bounds[0] or \ + self.hover_instance.y_hover_pos > self.y + ax.bbox.bounds[1] + ax.bbox.bounds[3] or \ + self.hover_instance.y_hover_pos < self.y + ax.bbox.bounds[1]: + self.hover_instance.hover_outside_bound = True else: - self.hover_instance.hover_outside_bound=False + self.hover_instance.hover_outside_bound = False if self.highlight_hover: axes = [a for a in self.figure.canvas.figure.get_axes() - if a.in_axes(self.myevent)] - - if not axes or not isinstance(line, mlines.Line2D): - + if a.in_axes(self.myevent)] + + if not axes or not isinstance(line, mlines.Line2D): + if self.last_line: - self.clear_line_prop() + self.clear_line_prop() if self.background: - ax.figure.canvas.restore_region(self.background) - #draw (blit method) - ax.figure.canvas.blit(ax.bbox) - ax.figure.canvas.flush_events() + ax.figure.canvas.restore_region( + self.background) + # draw (blit method) + ax.figure.canvas.blit(ax.bbox) + ax.figure.canvas.flush_events() self.background = None - + return - #blit method (always use because same visual effect as draw) - if self.background is None: - self.background = ax.figure.canvas.copy_from_bbox(ax.figure.bbox) + # blit method (always use because same visual effect as + # draw) + if self.background is None: + self.background = ax.figure.canvas.copy_from_bbox( + ax.figure.bbox) if self.last_line is None: - default_alpha=[] - lines_list=ax.lines + default_alpha = [] + lines_list = ax.lines for current_line in lines_list: default_alpha.append(current_line.get_alpha()) current_line.set_alpha(self.highlight_alpha) - + ax.figure.canvas.draw_idle() - ax.figure.canvas.flush_events() - self.background_highlight=ax.figure.canvas.copy_from_bbox(ax.figure.bbox) - self.last_line=line - for i,current_line in enumerate(lines_list): + ax.figure.canvas.flush_events() + self.background_highlight = ax.figure.canvas.copy_from_bbox( + ax.figure.bbox) + self.last_line = line + for i, current_line in enumerate(lines_list): current_line.set_alpha(default_alpha[i]) default_alpha[i] - + if self.highlight_prop: - self.last_line_prop={} + self.last_line_prop = {} for key in self.highlight_prop: # if hasattr(line,key): - line_attr = getattr(line,'get_' + key) - self.last_line_prop.update({key:line_attr()}) - set_line_attr = getattr(line,'set_' + key) + line_attr = getattr(line, 'get_' + key) + self.last_line_prop.update( + {key: line_attr()}) + set_line_attr = getattr(line, 'set_' + key) set_line_attr(self.highlight_prop[key]) elif self.last_line_prop: for key in self.last_line_prop: - set_line_attr = getattr(self.last_line,'set_' + key) - set_line_attr(self.last_line_prop[key]) - self.hover_instance.custom_color = get_color_from_hex(to_hex(line.get_color())) - self.last_line_prop={} + set_line_attr = getattr( + self.last_line, 'set_' + key) + set_line_attr(self.last_line_prop[key]) + self.hover_instance.custom_color = get_color_from_hex( + to_hex(line.get_color())) + self.last_line_prop = {} for key in self.highlight_prop: - line_attr = getattr(line,'get_' + key) - self.last_line_prop.update({key:line_attr()}) - set_line_attr = getattr(line,'set_' + key) - set_line_attr(self.highlight_prop[key]) - self.last_line=line - - ax.figure.canvas.restore_region(self.background_highlight) + line_attr = getattr(line, 'get_' + key) + self.last_line_prop.update({key: line_attr()}) + set_line_attr = getattr(line, 'set_' + key) + set_line_attr(self.highlight_prop[key]) + self.last_line = line + + ax.figure.canvas.restore_region( + self.background_highlight) ax.draw_artist(line) - - #draw (blit method) - ax.figure.canvas.blit(self.axes.bbox) - ax.figure.canvas.flush_events() - + + # draw (blit method) + ax.figure.canvas.blit(self.axes.bbox) + ax.figure.canvas.flush_events() + return else: if self.cursor_xaxis_formatter: x = self.cursor_xaxis_formatter.format_data(x) else: - x = ax.xaxis.get_major_formatter().format_data_short(x) + x = ax.xaxis.get_major_formatter().format_data_short(x) if self.cursor_yaxis_formatter: - y = self.cursor_yaxis_formatter.format_data(y) + y = self.cursor_yaxis_formatter.format_data(y) else: - y = ax.yaxis.get_major_formatter().format_data_short(y) + y = ax.yaxis.get_major_formatter().format_data_short(y) self.text.set_text(f"x={x}, y={y}") - - #blit method (always use because same visual effect as draw) + + # blit method (always use because same visual effect as draw) if self.background is None: self.set_cross_hair_visible(False) self.figcanvas.draw_idle() - self.figcanvas.flush_events() - self.background = self.figcanvas.copy_from_bbox(ax.figure.bbox) - self.set_cross_hair_visible(True) + self.figcanvas.flush_events() + self.background = self.figcanvas.copy_from_bbox( + ax.figure.bbox) + self.set_cross_hair_visible(True) if self.last_line is not None: self.clear_line_prop() - + self.figcanvas.restore_region(self.background) ax.draw_artist(self.text) - + ax.draw_artist(self.horizontal_line) - ax.draw_artist(self.vertical_line) - - #draw (blit method) - self.figcanvas.blit(ax.bbox) + ax.draw_artist(self.vertical_line) + + # draw (blit method) + self.figcanvas.blit(ax.bbox) self.figcanvas.flush_events() def zoom_factory(self, event, base_scale=1.1): @@ -910,58 +965,58 @@ def zoom_factory(self, event, base_scale=1.1): y = event.y - self.pos[1] if not self._pick_info: - self.myevent.x=event.x - self.pos[0] - self.myevent.y=event.y - self.pos[1] - self.myevent.inaxes=self.figure.canvas.inaxes((x,y)) - #press event + self.myevent.x = event.x - self.pos[0] + self.myevent.y = event.y - self.pos[1] + self.myevent.inaxes = self.figure.canvas.inaxes((x, y)) + # press event axes = [a for a in self.figure.canvas.figure.get_axes() - if a.in_axes(self.myevent)] + if a.in_axes(self.myevent)] self._pick_info = axes - + if not self._pick_info: - return + return - for i,ax in enumerate(self._pick_info): + for i, ax in enumerate(self._pick_info): self.axes = ax - + twinx = any(ax.get_shared_x_axes().joined(ax, prev) for prev in self._pick_info[:i]) - + twiny = any(ax.get_shared_y_axes().joined(ax, prev) for prev in self._pick_info[:i]) - + trans = ax.transData.inverted() - xdata, ydata = trans.transform_point((x, y)) - + xdata, ydata = trans.transform_point((x, y)) + cur_xlim = ax.get_xlim() cur_ylim = ax.get_ylim() - - scale=ax.get_xscale() - yscale=ax.get_yscale() - + + scale = ax.get_xscale() + yscale = ax.get_yscale() + if scale == 'linear': - old_min=cur_xlim[0] - old_max=cur_xlim[1] - + old_min = cur_xlim[0] + old_max = cur_xlim[1] + else: - min_=cur_xlim[0] - max_=cur_xlim[1] - old_min = self.transform_eval(min_,ax.yaxis) - xdata = self.transform_eval(xdata,ax.yaxis) - old_max = self.transform_eval(max_,ax.yaxis) - + min_ = cur_xlim[0] + max_ = cur_xlim[1] + old_min = self.transform_eval(min_, ax.yaxis) + xdata = self.transform_eval(xdata, ax.yaxis) + old_max = self.transform_eval(max_, ax.yaxis) + if yscale == 'linear': - yold_min=cur_ylim[0] - yold_max=cur_ylim[1] - + yold_min = cur_ylim[0] + yold_max = cur_ylim[1] + else: - ymin_=cur_ylim[0] - ymax_=cur_ylim[1] - yold_min = self.transform_eval(ymin_,ax.yaxis) - ydata = self.transform_eval(ydata,ax.yaxis) - yold_max = self.transform_eval(ymax_,ax.yaxis) - + ymin_ = cur_ylim[0] + ymax_ = cur_ylim[1] + yold_min = self.transform_eval(ymin_, ax.yaxis) + ydata = self.transform_eval(ydata, ax.yaxis) + yold_max = self.transform_eval(ymax_, ax.yaxis) + if event.button == 'scrolldown': # deal with zoom in scale_factor = 1 / base_scale @@ -972,279 +1027,294 @@ def zoom_factory(self, event, base_scale=1.1): # deal with something that should never happen scale_factor = 1 print(event.button) - + new_width = (old_max - old_min) * scale_factor new_height = (yold_max - yold_min) * scale_factor - + relx = (old_max - xdata) / (old_max - old_min) rely = (yold_max - ydata) / (yold_max - yold_min) - + if self.do_zoom_x and not twinx: if scale == 'linear': - ax.set_xlim([xdata - new_width * (1 - relx), xdata + new_width * (relx)]) + ax.set_xlim([xdata - new_width * (1 - relx), + xdata + new_width * (relx)]) else: new_min = xdata - new_width * (1 - relx) new_max = xdata + new_width * (relx) try: - new_min, new_max = self.inv_transform_eval(new_min,ax.yaxis), self.inv_transform_eval(new_max,ax.yaxis) + new_min, new_max = self.inv_transform_eval( + new_min, ax.yaxis), self.inv_transform_eval( + new_max, ax.yaxis) except OverflowError: # Limit case new_min, new_max = min_, max_ if new_min <= 0. or new_max <= 0.: # Limit case - new_min, new_max = min_, max_ + new_min, new_max = min_, max_ ax.set_xlim([new_min, new_max]) - + if self.do_zoom_y and not twiny: if yscale == 'linear': - ax.set_ylim([ydata - new_height * (1 - rely), ydata + new_height * (rely)]) + ax.set_ylim([ydata - new_height * (1 - rely), + ydata + new_height * (rely)]) else: new_ymin = ydata - new_height * (1 - rely) new_ymax = ydata + new_height * (rely) try: - new_ymin, new_ymax = self.inv_transform_eval(new_ymin,ax.yaxis), self.inv_transform_eval(new_ymax,ax.yaxis) + new_ymin, new_ymax = self.inv_transform_eval( + new_ymin, ax.yaxis), self.inv_transform_eval( + new_ymax, ax.yaxis) except OverflowError: # Limit case new_ymin, new_ymax = ymin_, ymax_ if new_ymin <= 0. or new_ymax <= 0.: # Limit case - new_ymin, new_ymax = ymin_, ymax_ - ax.set_ylim([new_ymin, new_ymax]) + new_ymin, new_ymax = ymin_, ymax_ + ax.set_ylim([new_ymin, new_ymax]) self._pick_info = None ax.figure.canvas.draw_idle() ax.figure.canvas.flush_events() - def apply_zoom(self, scale_factor, ax, anchor=(0, 0),new_line=None): + def apply_zoom(self, scale_factor, ax, anchor=(0, 0), new_line=None): """ zoom touch method """ - if self.touch_mode=='selector': + if self.touch_mode == 'selector': return - - x = anchor[0]-self.pos[0] - y = anchor[1]-self.pos[1] - #manage press and drag + x = anchor[0] - self.pos[0] + y = anchor[1] - self.pos[1] + + # manage press and drag if not self._pick_info: - self.myevent.x=x - self.myevent.y=y - self.myevent.inaxes=self.figure.canvas.inaxes((x,y)) - #press event + self.myevent.x = x + self.myevent.y = y + self.myevent.inaxes = self.figure.canvas.inaxes((x, y)) + # press event axes = [a for a in self.figure.canvas.figure.get_axes() - if a.in_axes(self.myevent)] + if a.in_axes(self.myevent)] self._pick_info = axes - + if not self._pick_info: - return - - artists=[] - for i,ax in enumerate(self._pick_info): - + return + + artists = [] + for i, ax in enumerate(self._pick_info): + self.axes = ax - + twinx = any(ax.get_shared_x_axes().joined(ax, prev) for prev in self._pick_info[:i]) - + twiny = any(ax.get_shared_y_axes().joined(ax, prev) for prev in self._pick_info[:i]) artists.extend(_iter_axes_subartists(ax)) trans = ax.transData.inverted() - xdata, ydata = trans.transform_point((x+new_line.x, y+new_line.y)) - + xdata, ydata = trans.transform_point( + (x + new_line.x, y + new_line.y)) + cur_xlim = ax.get_xlim() - cur_ylim = ax.get_ylim() - - scale=ax.get_xscale() - yscale=ax.get_yscale() - + cur_ylim = ax.get_ylim() + + scale = ax.get_xscale() + yscale = ax.get_yscale() + if scale == 'linear': - old_min=cur_xlim[0] - old_max=cur_xlim[1] - + old_min = cur_xlim[0] + old_max = cur_xlim[1] + else: - min_=cur_xlim[0] - max_=cur_xlim[1] - old_min = self.transform_eval(min_,ax.yaxis) - xdata = self.transform_eval(xdata,ax.yaxis) - old_max = self.transform_eval(max_,ax.yaxis) - + min_ = cur_xlim[0] + max_ = cur_xlim[1] + old_min = self.transform_eval(min_, ax.yaxis) + xdata = self.transform_eval(xdata, ax.yaxis) + old_max = self.transform_eval(max_, ax.yaxis) + if yscale == 'linear': - yold_min=cur_ylim[0] - yold_max=cur_ylim[1] - + yold_min = cur_ylim[0] + yold_max = cur_ylim[1] + else: - ymin_=cur_ylim[0] - ymax_=cur_ylim[1] - yold_min = self.transform_eval(ymin_,ax.yaxis) - ydata = self.transform_eval(ydata,ax.yaxis) - yold_max = self.transform_eval(ymax_,ax.yaxis) - + ymin_ = cur_ylim[0] + ymax_ = cur_ylim[1] + yold_min = self.transform_eval(ymin_, ax.yaxis) + ydata = self.transform_eval(ydata, ax.yaxis) + yold_max = self.transform_eval(ymax_, ax.yaxis) + new_width = (old_max - old_min) * scale_factor new_height = (yold_max - yold_min) * scale_factor - + relx = (old_max - xdata) / (old_max - old_min) rely = (yold_max - ydata) / (yold_max - yold_min) - + if self.do_zoom_x and not twinx: if scale == 'linear': - ax.set_xlim([xdata - new_width * (1 - relx), xdata + new_width * (relx)]) + ax.set_xlim([xdata - new_width * (1 - relx), + xdata + new_width * (relx)]) else: new_min = xdata - new_width * (1 - relx) new_max = xdata + new_width * (relx) try: - new_min, new_max = self.inv_transform_eval(new_min,ax.yaxis), self.inv_transform_eval(new_max,ax.yaxis) + new_min, new_max = self.inv_transform_eval( + new_min, ax.yaxis), self.inv_transform_eval( + new_max, ax.yaxis) except OverflowError: # Limit case new_min, new_max = min_, max_ if new_min <= 0. or new_max <= 0.: # Limit case - new_min, new_max = min_, max_ + new_min, new_max = min_, max_ ax.set_xlim([new_min, new_max]) - + if self.do_zoom_y and not twiny: if yscale == 'linear': - ax.set_ylim([ydata - new_height * (1 - rely), ydata + new_height * (rely)]) + ax.set_ylim([ydata - new_height * (1 - rely), + ydata + new_height * (rely)]) else: new_ymin = ydata - new_height * (1 - rely) new_ymax = ydata + new_height * (rely) try: - new_ymin, new_ymax = self.inv_transform_eval(new_ymin,ax.yaxis), self.inv_transform_eval(new_ymax,ax.yaxis) + new_ymin, new_ymax = self.inv_transform_eval( + new_ymin, ax.yaxis), self.inv_transform_eval( + new_ymax, ax.yaxis) except OverflowError: # Limit case new_ymin, new_ymax = ymin_, ymax_ if new_ymin <= 0. or new_ymax <= 0.: # Limit case - new_ymin, new_ymax = ymin_, ymax_ - ax.set_ylim([new_ymin, new_ymax]) - - if self.fast_draw: - #use blit method + new_ymin, new_ymax = ymin_, ymax_ + ax.set_ylim([new_ymin, new_ymax]) + + if self.fast_draw: + # use blit method if self.background is None or self.last_line is not None: if self.draw_all_axes: for myax in self.figure.canvas.figure.get_axes(): - index = self.figure.axes.index(myax) - self.background_patch_copy[index].set_visible(True) + index = self.figure.axes.index(myax) + self.background_patch_copy[index].set_visible(True) else: - index = self.figure.axes.index(ax) + index = self.figure.axes.index(ax) self.background_patch_copy[index].set_visible(True) self.figcanvas.draw_idle() - self.figcanvas.flush_events() + self.figcanvas.flush_events() self.background = self.figcanvas.copy_from_bbox(ax.figure.bbox) if self.draw_all_axes: for myax in self.figure.canvas.figure.get_axes(): - index = self.figure.axes.index(myax) - self.background_patch_copy[index].set_visible(False) - else: - self.background_patch_copy[index].set_visible(False) + index = self.figure.axes.index(myax) + self.background_patch_copy[index].set_visible(False) + else: + self.background_patch_copy[index].set_visible(False) if self.last_line is not None: - self.clear_line_prop() - self.figcanvas.restore_region(self.background) - + self.clear_line_prop() + self.figcanvas.restore_region(self.background) if self.draw_all_axes: for myax in self.figure.canvas.figure.get_axes(): artists = _iter_axes_subartists(myax) - for artist in artists: + for artist in artists: ax.draw_artist(artist) - + else: - for artist in artists: + for artist in artists: ax.draw_artist(artist) - + self.figcanvas.blit(ax.bbox) - self.figcanvas.flush_events() - + self.figcanvas.flush_events() + self.update_hover() - + else: self.figcanvas.draw_idle() - self.figcanvas.flush_events() - + self.figcanvas.flush_events() def apply_pan(self, my_ax, event, mode='pan'): """ pan method """ - #manage press and drag + # manage press and drag if not self._pick_info: - self.myevent.x=event.x - self.pos[0] - self.myevent.y=event.y - self.pos[1] - self.myevent.inaxes=self.figure.canvas.inaxes((event.x, - event.y)) - #press event + self.myevent.x = event.x - self.pos[0] + self.myevent.y = event.y - self.pos[1] + self.myevent.inaxes = self.figure.canvas.inaxes((event.x, + event.y)) + # press event axes = [a for a in self.figure.canvas.figure.get_axes() - if a.in_axes(self.myevent)] - if not axes: - if self.first_touch_pan!='pan' and self.interactive_axis: + if a.in_axes(self.myevent)] + if not axes: + if self.first_touch_pan != 'pan' and self.interactive_axis: axes = [a for a in self.figure.canvas.figure.get_axes() - if self.my_in_axes(a,self.myevent)] + if self.my_in_axes(a, self.myevent)] self._pick_info = axes - + if not self._pick_info: return - artists=[] - for i,ax in enumerate(self._pick_info): - + artists = [] + for i, ax in enumerate(self._pick_info): + self.axes = ax artists.extend(_iter_axes_subartists(ax)) twinx = any(ax.get_shared_x_axes().joined(ax, prev) for prev in self._pick_info[:i]) - + twiny = any(ax.get_shared_y_axes().joined(ax, prev) for prev in self._pick_info[:i]) trans = ax.transData.inverted() - xdata, ydata = trans.transform_point((event.x-self.pos[0], event.y-self.pos[1])) - xpress, ypress = trans.transform_point((self._last_touch_pos[event][0]-self.pos[0], self._last_touch_pos[event][1]-self.pos[1])) - - scale=ax.get_xscale() - yscale=ax.get_yscale() - + xdata, ydata = trans.transform_point( + (event.x - self.pos[0], event.y - self.pos[1])) + xpress, ypress = trans.transform_point( + (self._last_touch_pos[event][0] - self.pos[0], + self._last_touch_pos[event][1] - self.pos[1])) + + scale = ax.get_xscale() + yscale = ax.get_yscale() + if scale == 'linear': dx = xdata - xpress else: - dx = self.transform_eval(xdata,ax.xaxis) - \ - self.transform_eval(xpress,ax.xaxis) - + dx = self.transform_eval(xdata, ax.xaxis) - \ + self.transform_eval(xpress, ax.xaxis) + if yscale == 'linear': dy = ydata - ypress else: - dy = self.transform_eval(ydata,ax.yaxis) - \ - self.transform_eval(ypress,ax.yaxis) - - xleft,xright=ax.get_xlim() - ybottom,ytop=ax.get_ylim() - - #check inverted data + dy = self.transform_eval(ydata, ax.yaxis) - \ + self.transform_eval(ypress, ax.yaxis) + + xleft, xright = ax.get_xlim() + ybottom, ytop = ax.get_ylim() + + # check inverted data inverted_x = False - if xleft>xright: - inverted_x=True - cur_xlim=(xright,xleft) + if xleft > xright: + inverted_x = True + cur_xlim = (xright, xleft) else: - cur_xlim=(xleft,xright) + cur_xlim = (xleft, xright) inverted_y = False - if ybottom>ytop: - inverted_y=True - cur_ylim=(ytop,ybottom) + if ybottom > ytop: + inverted_y = True + cur_ylim = (ytop, ybottom) else: - cur_ylim=(ybottom,ytop) - - if self.interactive_axis and self.touch_mode=='pan' and not self.first_touch_pan=='pan': + cur_ylim = (ybottom, ytop) + + if self.interactive_axis and self.touch_mode == 'pan' and not self.first_touch_pan == 'pan': xlabelsize = self.interactive_axis_pad - ylabelsize = self.interactive_axis_pad - + ylabelsize = self.interactive_axis_pad + xlabelbottom = ax.xaxis._major_tick_kw.get('tick1On') xlabeltop = ax.xaxis._major_tick_kw.get('tick2On') ylabelleft = ax.yaxis._major_tick_kw.get('tick1On') ylabelright = ax.yaxis._major_tick_kw.get('tick2On') - - # if (ydata < cur_ylim[0] and not inverted_y) or (ydata > cur_ylim[1] and inverted_y): - if xlabelbottom and event.x>self.x +ax.bbox.bounds[0] and \ - event.xself.y + ax.bbox.bounds[1] - xlabelsize and \ - event.y + # cur_ylim[1] and inverted_y): + if xlabelbottom and event.x > self.x + ax.bbox.bounds[0] and \ + event.x < self.x + ax.bbox.bounds[2] + ax.bbox.bounds[0] and \ + event.y > self.y + ax.bbox.bounds[1] - xlabelsize and \ + event.y < self.y + ax.bbox.bounds[1]: + + right_lim = self.x + ax.bbox.bounds[2] + ax.bbox.bounds[0] + left_lim = self.x + ax.bbox.bounds[0] + left_anchor_zone = (right_lim - left_lim) * .2 + left_lim + right_anchor_zone = (right_lim - left_lim) * .8 + left_lim if event.x < left_anchor_zone or event.x > right_anchor_zone: mode = 'adjust_x' @@ -1252,454 +1322,551 @@ def apply_pan(self, my_ax, event, mode='pan'): else: mode = 'pan_x' self.touch_mode = mode - # elif (xdata < cur_xlim[0] and not inverted_x) or (xdata > cur_xlim[1] and inverted_x): - elif ylabelleft and event.x>self.x +ax.bbox.bounds[0] - ylabelsize and \ - event.xself.y + ax.bbox.bounds[1]: - - top_lim = self.y+ax.bbox.bounds[3]+ax.bbox.bounds[1] - bottom_lim = self.y+ax.bbox.bounds[1] - bottom_anchor_zone= (top_lim - bottom_lim)*.2 + bottom_lim - top_anchor_zone= (top_lim - bottom_lim)*.8 + bottom_lim + # elif (xdata < cur_xlim[0] and not inverted_x) or (xdata > + # cur_xlim[1] and inverted_x): + elif ylabelleft and event.x > self.x + ax.bbox.bounds[0] - ylabelsize and \ + event.x < self.x + ax.bbox.bounds[0] and \ + event.y < self.y + ax.bbox.bounds[1] + ax.bbox.bounds[3] and \ + event.y > self.y + ax.bbox.bounds[1]: + + top_lim = self.y + ax.bbox.bounds[3] + ax.bbox.bounds[1] + bottom_lim = self.y + ax.bbox.bounds[1] + bottom_anchor_zone = ( + top_lim - bottom_lim) * .2 + bottom_lim + top_anchor_zone = (top_lim - bottom_lim) * .8 + bottom_lim if event.y < bottom_anchor_zone or event.y > top_anchor_zone: mode = 'adjust_y' self.current_anchor_axis = ax else: - mode= 'pan_y' + mode = 'pan_y' self.touch_mode = mode - - elif ylabelright and event.xself.x + ax.bbox.bounds[2] + ax.bbox.bounds[0] and \ - event.yself.y + ax.bbox.bounds[1]: - top_lim = self.y+ax.bbox.bounds[3]+ax.bbox.bounds[1] - bottom_lim = self.y+ax.bbox.bounds[1] - bottom_anchor_zone= (top_lim - bottom_lim)*.2 + bottom_lim - top_anchor_zone= (top_lim - bottom_lim)*.8 + bottom_lim + elif ylabelright and event.x < self.x + ax.bbox.bounds[2] + ax.bbox.bounds[0] + self.interactive_axis_pad and \ + event.x > self.x + ax.bbox.bounds[2] + ax.bbox.bounds[0] and \ + event.y < self.y + ax.bbox.bounds[1] + ax.bbox.bounds[3] and \ + event.y > self.y + ax.bbox.bounds[1]: + + top_lim = self.y + ax.bbox.bounds[3] + ax.bbox.bounds[1] + bottom_lim = self.y + ax.bbox.bounds[1] + bottom_anchor_zone = ( + top_lim - bottom_lim) * .2 + bottom_lim + top_anchor_zone = (top_lim - bottom_lim) * .8 + bottom_lim if event.y < bottom_anchor_zone or event.y > top_anchor_zone: mode = 'adjust_y' self.current_anchor_axis = ax else: - mode= 'pan_y' - self.touch_mode = mode - - elif xlabeltop and event.x>self.x +ax.bbox.bounds[0] and \ - event.xself.y +ax.bbox.bounds[1] + ax.bbox.bounds[3] and \ - event.y self.x + ax.bbox.bounds[0] and \ + event.x < self.x + ax.bbox.bounds[2] + ax.bbox.bounds[0] and \ + event.y > self.y + ax.bbox.bounds[1] + ax.bbox.bounds[3] and \ + event.y < self.y + ax.bbox.bounds[1] + ax.bbox.bounds[3] + xlabelsize: + right_lim = self.x + ax.bbox.bounds[2] + ax.bbox.bounds[0] + left_lim = self.x + ax.bbox.bounds[0] + left_anchor_zone = (right_lim - left_lim) * .2 + left_lim + right_anchor_zone = (right_lim - left_lim) * .8 + left_lim if event.x < left_anchor_zone or event.x > right_anchor_zone: mode = 'adjust_x' self.current_anchor_axis = ax else: mode = 'pan_x' - self.touch_mode = mode + self.touch_mode = mode else: self.touch_mode = 'pan' - - if not mode=='pan_y' and not mode=='adjust_y': - if mode=='adjust_x': + + if not mode == 'pan_y' and not mode == 'adjust_y': + if mode == 'adjust_x': if self.current_anchor_axis == ax: - if self.anchor_x is None: - midpoint = ((self.x+ax.bbox.bounds[2] + ax.bbox.bounds[0]) + (self.x+ax.bbox.bounds[0]))/2 + if self.anchor_x is None: + midpoint = ( + (self.x + ax.bbox.bounds[2] + ax.bbox.bounds + [0]) + (self.x + ax.bbox.bounds[0])) / 2 if event.x > midpoint: if inverted_x: - self.anchor_x='right' + self.anchor_x = 'right' else: - self.anchor_x='left' + self.anchor_x = 'left' else: if inverted_x: - self.anchor_x='left' + self.anchor_x = 'left' else: - self.anchor_x='right' - if self.anchor_x=='left': - if xdata> cur_xlim[0]: + self.anchor_x = 'right' + if self.anchor_x == 'left': + if xdata > cur_xlim[0]: if scale == 'linear': cur_xlim -= dx else: try: - cur_xlim = [self.inv_transform_eval((self.transform_eval(cur_xlim[0],ax.xaxis) - dx),ax.xaxis), - self.inv_transform_eval((self.transform_eval(cur_xlim[1],ax.xaxis) - dx),ax.xaxis)] + cur_xlim = [ + self.inv_transform_eval( + (self.transform_eval( + cur_xlim[0], + ax.xaxis) - dx), + ax.xaxis), + self.inv_transform_eval( + (self.transform_eval( + cur_xlim[1], + ax.xaxis) - dx), + ax.xaxis)] except (ValueError, OverflowError): - cur_xlim = cur_xlim # Keep previous limits + cur_xlim = cur_xlim # Keep previous limits if inverted_x: - ax.set_xlim(cur_xlim[1],None) + ax.set_xlim(cur_xlim[1], None) else: - ax.set_xlim(None,cur_xlim[1]) + ax.set_xlim(None, cur_xlim[1]) else: - if xdata< cur_xlim[1]: + if xdata < cur_xlim[1]: if scale == 'linear': cur_xlim -= dx else: try: - cur_xlim = [self.inv_transform_eval((self.transform_eval(cur_xlim[0],ax.xaxis) - dx),ax.xaxis), - self.inv_transform_eval((self.transform_eval(cur_xlim[1],ax.xaxis) - dx),ax.xaxis)] + cur_xlim = [ + self.inv_transform_eval( + (self.transform_eval( + cur_xlim[0], + ax.xaxis) - dx), + ax.xaxis), + self.inv_transform_eval( + (self.transform_eval( + cur_xlim[1], + ax.xaxis) - dx), + ax.xaxis)] except (ValueError, OverflowError): - cur_xlim = cur_xlim # Keep previous limits + cur_xlim = cur_xlim # Keep previous limits if inverted_x: - ax.set_xlim(None,cur_xlim[0]) + ax.set_xlim(None, cur_xlim[0]) else: - ax.set_xlim(cur_xlim[0],None) + ax.set_xlim(cur_xlim[0], None) else: - if not twinx: + if not twinx: if scale == 'linear': cur_xlim -= dx else: try: - cur_xlim = [self.inv_transform_eval((self.transform_eval(cur_xlim[0],ax.xaxis) - dx),ax.xaxis), - self.inv_transform_eval((self.transform_eval(cur_xlim[1],ax.xaxis) - dx),ax.xaxis)] + cur_xlim = [ + self.inv_transform_eval( + (self.transform_eval( + cur_xlim[0], + ax.xaxis) - dx), + ax.xaxis), + self.inv_transform_eval( + (self.transform_eval( + cur_xlim[1], + ax.xaxis) - dx), + ax.xaxis)] except (ValueError, OverflowError): - cur_xlim = cur_xlim # Keep previous limits + cur_xlim = cur_xlim # Keep previous limits if inverted_x: - ax.set_xlim(cur_xlim[1],cur_xlim[0]) + ax.set_xlim(cur_xlim[1], cur_xlim[0]) else: ax.set_xlim(cur_xlim) - - if not mode=='pan_x' and not mode=='adjust_x': - if mode=='adjust_y': + + if not mode == 'pan_x' and not mode == 'adjust_x': + if mode == 'adjust_y': if self.current_anchor_axis == ax: if self.anchor_y is None: - midpoint = ((self.y+ax.bbox.bounds[3] + ax.bbox.bounds[1]) + (self.y+ax.bbox.bounds[1]))/2 - if event.y > midpoint: + midpoint = ( + (self.y + ax.bbox.bounds[3] + ax.bbox.bounds + [1]) + (self.y + ax.bbox.bounds[1])) / 2 + if event.y > midpoint: if inverted_y: - self.anchor_y='bottom' + self.anchor_y = 'bottom' else: - self.anchor_y='top' + self.anchor_y = 'top' else: if inverted_y: - self.anchor_y='top' + self.anchor_y = 'top' else: - self.anchor_y='bottom' - - if self.anchor_y=='top': - if ydata> cur_ylim[0]: + self.anchor_y = 'bottom' + + if self.anchor_y == 'top': + if ydata > cur_ylim[0]: if yscale == 'linear': cur_ylim -= dy - + else: try: - cur_ylim = [self.inv_transform_eval((self.transform_eval(cur_ylim[0],ax.yaxis) - dy),ax.yaxis), - self.inv_transform_eval((self.transform_eval(cur_ylim[1],ax.yaxis) - dy),ax.yaxis)] + cur_ylim = [ + self.inv_transform_eval( + (self.transform_eval( + cur_ylim[0], + ax.yaxis) - dy), + ax.yaxis), + self.inv_transform_eval( + (self.transform_eval( + cur_ylim[1], + ax.yaxis) - dy), + ax.yaxis)] except (ValueError, OverflowError): - cur_ylim = cur_ylim # Keep previous limits - + cur_ylim = cur_ylim # Keep previous limits + if inverted_y: - ax.set_ylim(cur_ylim[1],None) + ax.set_ylim(cur_ylim[1], None) else: - ax.set_ylim(None,cur_ylim[1]) + ax.set_ylim(None, cur_ylim[1]) else: - if ydata< cur_ylim[1]: + if ydata < cur_ylim[1]: if yscale == 'linear': cur_ylim -= dy - + else: try: - cur_ylim = [self.inv_transform_eval((self.transform_eval(cur_ylim[0],ax.yaxis) - dy),ax.yaxis), - self.inv_transform_eval((self.transform_eval(cur_ylim[1],ax.yaxis) - dy),ax.yaxis)] + cur_ylim = [ + self.inv_transform_eval( + (self.transform_eval( + cur_ylim[0], + ax.yaxis) - dy), + ax.yaxis), + self.inv_transform_eval( + (self.transform_eval( + cur_ylim[1], + ax.yaxis) - dy), + ax.yaxis)] except (ValueError, OverflowError): - cur_ylim = cur_ylim # Keep previous limits + cur_ylim = cur_ylim # Keep previous limits if inverted_y: - ax.set_ylim(None,cur_ylim[0]) + ax.set_ylim(None, cur_ylim[0]) else: - ax.set_ylim(cur_ylim[0],None) - else: + ax.set_ylim(cur_ylim[0], None) + else: if not twiny: if yscale == 'linear': cur_ylim -= dy - + else: try: - cur_ylim = [self.inv_transform_eval((self.transform_eval(cur_ylim[0],ax.yaxis) - dy),ax.yaxis), - self.inv_transform_eval((self.transform_eval(cur_ylim[1],ax.yaxis) - dy),ax.yaxis)] + cur_ylim = [ + self.inv_transform_eval( + (self.transform_eval( + cur_ylim[0], + ax.yaxis) - dy), + ax.yaxis), + self.inv_transform_eval( + (self.transform_eval( + cur_ylim[1], + ax.yaxis) - dy), + ax.yaxis)] except (ValueError, OverflowError): - cur_ylim = cur_ylim # Keep previous limits + cur_ylim = cur_ylim # Keep previous limits if inverted_y: - ax.set_ylim(cur_ylim[1],cur_ylim[0]) + ax.set_ylim(cur_ylim[1], cur_ylim[0]) else: ax.set_ylim(cur_ylim) if self.first_touch_pan is None: - self.first_touch_pan=self.touch_mode - - if self.fast_draw: - #use blit method + self.first_touch_pan = self.touch_mode + + if self.fast_draw: + # use blit method if self.background is None or self.last_line is not None: if self.draw_all_axes: for myax in self.figure.canvas.figure.get_axes(): - index = self.figure.axes.index(myax) - self.background_patch_copy[index].set_visible(True) + index = self.figure.axes.index(myax) + self.background_patch_copy[index].set_visible(True) else: - index = self.figure.axes.index(ax) + index = self.figure.axes.index(ax) self.background_patch_copy[index].set_visible(True) self.figcanvas.draw_idle() - self.figcanvas.flush_events() + self.figcanvas.flush_events() self.background = self.figcanvas.copy_from_bbox(ax.figure.bbox) if self.draw_all_axes: for myax in self.figure.canvas.figure.get_axes(): - index = self.figure.axes.index(myax) - self.background_patch_copy[index].set_visible(False) - else: + index = self.figure.axes.index(myax) + self.background_patch_copy[index].set_visible(False) + else: self.background_patch_copy[index].set_visible(False) if self.last_line is not None: - self.clear_line_prop() - self.figcanvas.restore_region(self.background) - + self.clear_line_prop() + self.figcanvas.restore_region(self.background) if self.draw_all_axes: for myax in self.figure.canvas.figure.get_axes(): artists = _iter_axes_subartists(myax) - for artist in artists: + for artist in artists: ax.draw_artist(artist) - + else: - for artist in artists: + for artist in artists: ax.draw_artist(artist) self.figcanvas.blit(ax.bbox) - self.figcanvas.flush_events() - + self.figcanvas.flush_events() + self.update_hover() - + else: self.figcanvas.draw_idle() - self.figcanvas.flush_events() + self.figcanvas.flush_events() def update_hover(self): """ update hover on fast draw (if exist)""" if self.hover_instance: - #update hover pos if needed - if self.hover_instance.show_cursor and self.x_hover_data is not None and self.y_hover_data is not None: + # update hover pos if needed + if self.hover_instance.show_cursor and self.x_hover_data is not None and self.y_hover_data is not None: # if self.cursor_last_axis.axes==self.axes: - xy_pos = self.cursor_last_axis.transData.transform([(self.x_hover_data,self.y_hover_data)]) - self.hover_instance.x_hover_pos=float(xy_pos[0][0]) + self.x - self.hover_instance.y_hover_pos=float(xy_pos[0][1]) + self.y - - self.hover_instance.xmin_line = float(self.axes.bbox.bounds[0]) + self.x - self.hover_instance.xmax_line = float(self.axes.bbox.bounds[0] + self.axes.bbox.bounds[2]) + self.x - self.hover_instance.ymin_line = float(self.axes.bbox.bounds[1]) + self.y - self.hover_instance.ymax_line = float(self.axes.bbox.bounds[1] + self.axes.bbox.bounds[3] )+ self.y - - if self.hover_instance.x_hover_pos>self.x+self.axes.bbox.bounds[2] + self.axes.bbox.bounds[0] or \ - self.hover_instance.x_hover_posself.y+self.axes.bbox.bounds[1] + self.axes.bbox.bounds[3] or \ - self.hover_instance.y_hover_pos self.x + self.axes.bbox.bounds[2] + self.axes.bbox.bounds[0] or \ + self.hover_instance.x_hover_pos < self.x + self.axes.bbox.bounds[0] or \ + self.hover_instance.y_hover_pos > self.y + self.axes.bbox.bounds[1] + self.axes.bbox.bounds[3] or \ + self.hover_instance.y_hover_pos < self.y + self.axes.bbox.bounds[1]: + self.hover_instance.hover_outside_bound = True else: - self.hover_instance.hover_outside_bound=False + self.hover_instance.hover_outside_bound = False def min_max(self, event): """ manage min/max touch mode """ - - self.myevent.x=event.x - self.pos[0] - self.myevent.y=event.y - self.pos[1] - self.myevent.inaxes=self.figure.canvas.inaxes((event.x, - event.y)) + + self.myevent.x = event.x - self.pos[0] + self.myevent.y = event.y - self.pos[1] + self.myevent.inaxes = self.figure.canvas.inaxes((event.x, + event.y)) axes = [a for a in self.figure.canvas.figure.get_axes() - if self.my_in_axes(a,self.myevent)] + if self.my_in_axes(a, self.myevent)] for ax in axes: xlabelsize = self.interactive_axis_pad - ylabelsize = self.interactive_axis_pad + ylabelsize = self.interactive_axis_pad xlabelbottom = ax.xaxis._major_tick_kw.get('tick1On') xlabeltop = ax.xaxis._major_tick_kw.get('tick2On') ylabelleft = ax.yaxis._major_tick_kw.get('tick1On') ylabelright = ax.yaxis._major_tick_kw.get('tick2On') - if xlabelbottom and event.x>self.x +ax.bbox.bounds[0] and \ - event.xself.y + ax.bbox.bounds[1] - xlabelsize and \ - event.y self.x + ax.bbox.bounds[0] and \ + event.x < self.x + ax.bbox.bounds[2] + ax.bbox.bounds[0] and \ + event.y > self.y + ax.bbox.bounds[1] - xlabelsize and \ + event.y < self.y + ax.bbox.bounds[1]: + + right_lim = self.x + ax.bbox.bounds[2] + ax.bbox.bounds[0] + left_lim = self.x + ax.bbox.bounds[0] + left_anchor_zone = (right_lim - left_lim) * .2 + left_lim + right_anchor_zone = (right_lim - left_lim) * .8 + left_lim - if event.x < left_anchor_zone or event.x > right_anchor_zone: + if event.x < left_anchor_zone or event.x > right_anchor_zone: if self.text_instance: if not self.text_instance.show_text: - midpoint = ((self.x+ax.bbox.bounds[2] + ax.bbox.bounds[0]) + (self.x+ax.bbox.bounds[0]))/2 + midpoint = ( + (self.x + ax.bbox.bounds[2] + ax.bbox.bounds + [0]) + (self.x + ax.bbox.bounds[0])) / 2 if event.x < midpoint: - anchor='left' - self.text_instance.x_text_pos=float(self.x+ax.bbox.bounds[0]) - self.text_instance.y_text_pos=float(self.y+ax.bbox.bounds[1]) - self.text_instance.text_height + anchor = 'left' + self.text_instance.x_text_pos = float( + self.x + ax.bbox.bounds[0]) + self.text_instance.y_text_pos = float( + self.y + ax.bbox.bounds[1]) - self.text_instance.text_height self.text_instance.offset_text = False else: - anchor='right' - self.text_instance.x_text_pos=float(self.x+ax.bbox.bounds[2] + ax.bbox.bounds[0]) - self.text_instance.y_text_pos=float(self.y+ax.bbox.bounds[1]) - self.text_instance.text_height + anchor = 'right' + self.text_instance.x_text_pos = float( + self.x + ax.bbox.bounds[2] + ax.bbox.bounds[0]) + self.text_instance.y_text_pos = float( + self.y + ax.bbox.bounds[1]) - self.text_instance.text_height self.text_instance.offset_text = True - + self.text_instance.current_axis = ax - self.text_instance.kind = {'axis':'x','anchor':anchor} - - self.text_instance.show_text=True + self.text_instance.kind = { + 'axis': 'x', 'anchor': anchor} + + self.text_instance.show_text = True return - elif ylabelleft and event.x>self.x +ax.bbox.bounds[0] - ylabelsize and \ - event.xself.y + ax.bbox.bounds[1]: - - top_lim = self.y+ax.bbox.bounds[3]+ax.bbox.bounds[1] - bottom_lim = self.y+ax.bbox.bounds[1] - bottom_anchor_zone= (top_lim - bottom_lim)*.2 + bottom_lim - top_anchor_zone= (top_lim - bottom_lim)*.8 + bottom_lim + elif ylabelleft and event.x > self.x + ax.bbox.bounds[0] - ylabelsize and \ + event.x < self.x + ax.bbox.bounds[0] and \ + event.y < self.y + ax.bbox.bounds[1] + ax.bbox.bounds[3] and \ + event.y > self.y + ax.bbox.bounds[1]: + + top_lim = self.y + ax.bbox.bounds[3] + ax.bbox.bounds[1] + bottom_lim = self.y + ax.bbox.bounds[1] + bottom_anchor_zone = (top_lim - bottom_lim) * .2 + bottom_lim + top_anchor_zone = (top_lim - bottom_lim) * .8 + bottom_lim if event.y < bottom_anchor_zone or event.y > top_anchor_zone: if self.text_instance: if not self.text_instance.show_text: - midpoint = ((self.y+ax.bbox.bounds[3] + ax.bbox.bounds[1]) + (self.y+ax.bbox.bounds[1]))/2 - if event.y > midpoint: - anchor='top' - self.text_instance.x_text_pos=float(self.x+ax.bbox.bounds[0]) - dp(40) - self.text_instance.y_text_pos=float(self.y+ax.bbox.bounds[1] + ax.bbox.bounds[3]) - self.text_instance.text_height + midpoint = ( + (self.y + ax.bbox.bounds[3] + ax.bbox.bounds + [1]) + (self.y + ax.bbox.bounds[1])) / 2 + if event.y > midpoint: + anchor = 'top' + self.text_instance.x_text_pos = float( + self.x + ax.bbox.bounds[0]) - dp(40) + self.text_instance.y_text_pos = float( + self.y + ax.bbox.bounds[1] + ax.bbox.bounds + [3]) - self.text_instance.text_height self.text_instance.offset_text = False else: - anchor='bottom' - self.text_instance.x_text_pos=float(self.x+ax.bbox.bounds[0]) - dp(40) - self.text_instance.y_text_pos=float(self.y+ax.bbox.bounds[1]) - dp(6) + anchor = 'bottom' + self.text_instance.x_text_pos = float( + self.x + ax.bbox.bounds[0]) - dp(40) + self.text_instance.y_text_pos = float( + self.y + ax.bbox.bounds[1]) - dp(6) self.text_instance.offset_text = False self.text_instance.current_axis = ax - self.text_instance.kind = {'axis':'y','anchor':anchor} - - self.text_instance.show_text=True - return - elif ylabelright and event.xself.x + ax.bbox.bounds[2] + ax.bbox.bounds[0] and \ - event.yself.y + ax.bbox.bounds[1]: - - top_lim = self.y+ax.bbox.bounds[3]+ax.bbox.bounds[1] - bottom_lim = self.y+ax.bbox.bounds[1] - bottom_anchor_zone= (top_lim - bottom_lim)*.2 + bottom_lim - top_anchor_zone= (top_lim - bottom_lim)*.8 + bottom_lim + self.text_instance.kind = { + 'axis': 'y', 'anchor': anchor} + + self.text_instance.show_text = True + return + elif ylabelright and event.x < self.x + ax.bbox.bounds[2] + ax.bbox.bounds[0] + ylabelsize and \ + event.x > self.x + ax.bbox.bounds[2] + ax.bbox.bounds[0] and \ + event.y < self.y + ax.bbox.bounds[1] + ax.bbox.bounds[3] and \ + event.y > self.y + ax.bbox.bounds[1]: + + top_lim = self.y + ax.bbox.bounds[3] + ax.bbox.bounds[1] + bottom_lim = self.y + ax.bbox.bounds[1] + bottom_anchor_zone = (top_lim - bottom_lim) * .2 + bottom_lim + top_anchor_zone = (top_lim - bottom_lim) * .8 + bottom_lim if event.y < bottom_anchor_zone or event.y > top_anchor_zone: if self.text_instance: if not self.text_instance.show_text: - midpoint = ((self.y+ax.bbox.bounds[3] + ax.bbox.bounds[1]) + (self.y+ax.bbox.bounds[1]))/2 - if event.y > midpoint: - anchor='top' - self.text_instance.x_text_pos=float(self.x+ax.bbox.bounds[0] + ax.bbox.bounds[2]) + dp(40) - self.text_instance.y_text_pos=float(self.y+ax.bbox.bounds[1] + ax.bbox.bounds[3]) - self.text_instance.text_height + midpoint = ( + (self.y + ax.bbox.bounds[3] + ax.bbox.bounds + [1]) + (self.y + ax.bbox.bounds[1])) / 2 + if event.y > midpoint: + anchor = 'top' + self.text_instance.x_text_pos = float( + self.x + ax.bbox.bounds[0] + ax.bbox.bounds[2]) + dp(40) + self.text_instance.y_text_pos = float( + self.y + ax.bbox.bounds[1] + ax.bbox.bounds + [3]) - self.text_instance.text_height self.text_instance.offset_text = True else: - - anchor='bottom' - self.text_instance.x_text_pos=float(self.x+ax.bbox.bounds[0] + ax.bbox.bounds[2]) + dp(40) - self.text_instance.y_text_pos=float(self.y+ax.bbox.bounds[1]) - dp(6) + + anchor = 'bottom' + self.text_instance.x_text_pos = float( + self.x + ax.bbox.bounds[0] + ax.bbox.bounds[2]) + dp(40) + self.text_instance.y_text_pos = float( + self.y + ax.bbox.bounds[1]) - dp(6) self.text_instance.offset_text = True self.text_instance.current_axis = ax - self.text_instance.kind = {'axis':'y','anchor':anchor} - - self.text_instance.show_text=True - return - - elif xlabeltop and event.x>self.x +ax.bbox.bounds[0] and \ - event.xself.y +ax.bbox.bounds[1] + ax.bbox.bounds[3] and \ - event.y self.x + ax.bbox.bounds[0] and \ + event.x < self.x + ax.bbox.bounds[2] + ax.bbox.bounds[0] and \ + event.y > self.y + ax.bbox.bounds[1] + ax.bbox.bounds[3] and \ + event.y < self.y + ax.bbox.bounds[1] + ax.bbox.bounds[3] + xlabelsize: + right_lim = self.x + ax.bbox.bounds[2] + ax.bbox.bounds[0] + left_lim = self.x + ax.bbox.bounds[0] + left_anchor_zone = (right_lim - left_lim) * .2 + left_lim + right_anchor_zone = (right_lim - left_lim) * .8 + left_lim if event.x < left_anchor_zone or event.x > right_anchor_zone: if self.text_instance: if not self.text_instance.show_text: - midpoint = ((self.x+ax.bbox.bounds[2] + ax.bbox.bounds[0]) + (self.x+ax.bbox.bounds[0]))/2 + midpoint = ( + (self.x + ax.bbox.bounds[2] + ax.bbox.bounds + [0]) + (self.x + ax.bbox.bounds[0])) / 2 if event.x < midpoint: - anchor='left' - self.text_instance.x_text_pos=float(self.x+ax.bbox.bounds[0]) - self.text_instance.y_text_pos=float(self.y+ax.bbox.bounds[1] + ax.bbox.bounds[3]) + dp(2) + anchor = 'left' + self.text_instance.x_text_pos = float( + self.x + ax.bbox.bounds[0]) + self.text_instance.y_text_pos = float( + self.y + ax.bbox.bounds[1] + ax.bbox.bounds[3]) + dp(2) self.text_instance.offset_text = False else: - anchor='right' - self.text_instance.x_text_pos=float(self.x+ax.bbox.bounds[2] + ax.bbox.bounds[0]) - self.text_instance.y_text_pos=float(self.y+ax.bbox.bounds[1] + ax.bbox.bounds[3]) + dp(2) + anchor = 'right' + self.text_instance.x_text_pos = float( + self.x + ax.bbox.bounds[2] + ax.bbox.bounds[0]) + self.text_instance.y_text_pos = float( + self.y + ax.bbox.bounds[1] + ax.bbox.bounds[3]) + dp(2) self.text_instance.offset_text = True - + self.text_instance.current_axis = ax - self.text_instance.kind = {'axis':'x','anchor':anchor} - - self.text_instance.show_text=True + self.text_instance.kind = { + 'axis': 'x', 'anchor': anchor} + + self.text_instance.show_text = True return - + def on_touch_up(self, event): """ Manage Mouse/touch release """ if self.disabled: - return + return # remove it from our saved touches if event in self._touches and event.grab_state: event.ungrab(self) del self._last_touch_pos[event] self._touches.remove(event) - if self.touch_mode=='pan' or self.touch_mode=='zoombox' or \ - self.touch_mode=='pan_x' or self.touch_mode=='pan_y' \ - or self.touch_mode=='adjust_x' or self.touch_mode=='adjust_y' \ - or self.touch_mode=='minmax': + if self.touch_mode == 'pan' or self.touch_mode == 'zoombox' or \ + self.touch_mode == 'pan_x' or self.touch_mode == 'pan_y' \ + or self.touch_mode == 'adjust_x' or self.touch_mode == 'adjust_y' \ + or self.touch_mode == 'minmax': self.push_current() - self._pick_info=None + self._pick_info = None if self.interactive_axis: - if self.touch_mode=='pan_x' or self.touch_mode=='pan_y' \ - or self.touch_mode=='adjust_x' or self.touch_mode=='adjust_y': - self.touch_mode='pan' - self.first_touch_pan=None - + if self.touch_mode == 'pan_x' or self.touch_mode == 'pan_y' \ + or self.touch_mode == 'adjust_x' or self.touch_mode == 'adjust_y': + self.touch_mode = 'pan' + self.first_touch_pan = None + if self.last_line is not None: self.clear_line_prop() - + x, y = event.x, event.y - if abs(self._box_size[0]) > 1 or abs(self._box_size[1]) > 1 or self.touch_mode=='zoombox': - self.reset_box() + if abs( + self._box_size[0]) > 1 or abs( + self._box_size[1]) > 1 or self.touch_mode == 'zoombox': + self.reset_box() if not self.collide_point(x, y) and self.do_update: - #update axis lim if zoombox is used and touch outside widget - self.update_lim() - ax=self.axes + # update axis lim if zoombox is used and touch outside widget + self.update_lim() + ax = self.axes ax.figure.canvas.draw_idle() - ax.figure.canvas.flush_events() + ax.figure.canvas.flush_events() return True - + # stop propagating if its within our bounds if self.collide_point(x, y) and self.figure: self._pressed = False if self.do_update: - self.update_lim() - - self.anchor_x=None - self.anchor_y=None - - ax=self.axes - self.background=None - self.show_compare_cursor=True - if self.last_line is None or self.touch_mode!='cursor': + self.update_lim() + + self.anchor_x = None + self.anchor_y = None + + ax = self.axes + self.background = None + self.show_compare_cursor = True + if self.last_line is None or self.touch_mode != 'cursor': ax.figure.canvas.draw_idle() - ax.figure.canvas.flush_events() - + ax.figure.canvas.flush_events() + return True - def draw_box(self, event, x0, y0, x1, y1,onpress=False) -> None: + def draw_box(self, event, x0, y0, x1, y1, onpress=False) -> None: """ Draw zoombox method - + Args: event: touch kivy event x0: x coordonnate init x1: y coordonnate of move touch y0: y coordonnate init y1: x coordonnate of move touch - + Return: None """ @@ -1707,175 +1874,197 @@ def draw_box(self, event, x0, y0, x1, y1,onpress=False) -> None: # Kivy coords y0 = pos_y + y0 y1 = pos_y + y1 - - if abs(y1-y0)>dp(5) or abs(x1-x0)>dp(5): - self._alpha_box=0.3 - self._alpha_rect=0 - + + if abs(y1 - y0) > dp(5) or abs(x1 - x0) > dp(5): + self._alpha_box = 0.3 + self._alpha_rect = 0 + if onpress: - ax = self.figure.canvas.inaxes((event.x - self.pos[0], + ax = self.figure.canvas.inaxes((event.x - self.pos[0], event.y - self.pos[1])) if ax is None: return self.axes = ax - - self.myevent.x=event.x - self.pos[0] - self.myevent.y=event.y - self.pos[1] - self.myevent.inaxes=self.figure.canvas.inaxes((event.x - self.pos[0], - event.y - self.pos[1])) - + + self.myevent.x = event.x - self.pos[0] + self.myevent.y = event.y - self.pos[1] + self.myevent.inaxes = self.figure.canvas.inaxes( + (event.x - self.pos[0], event.y - self.pos[1])) + self.box_axes = [a for a in self.figure.canvas.figure.get_axes() - if a.in_axes(self.myevent)] - + if a.in_axes(self.myevent)] + else: - ax=self.axes + ax = self.axes trans = ax.transData.inverted() - xdata, ydata = trans.transform_point((event.x-pos_x, event.y-pos_y)) - - xleft,xright=ax.get_xlim() - ybottom,ytop=ax.get_ylim() - - xmax = max(xleft,xright) - xmin = min(xleft,xright) - ymax = max(ybottom,ytop) - ymin = min(ybottom,ytop) - - #check inverted data + xdata, ydata = trans.transform_point( + (event.x - pos_x, event.y - pos_y)) + + xleft, xright = ax.get_xlim() + ybottom, ytop = ax.get_ylim() + + xmax = max(xleft, xright) + xmin = min(xleft, xright) + ymax = max(ybottom, ytop) + ymin = min(ybottom, ytop) + + # check inverted data inverted_x = False - if xleft>xright: - inverted_x=True + if xleft > xright: + inverted_x = True inverted_y = False - if ybottom>ytop: - inverted_y=True + if ybottom > ytop: + inverted_y = True - x0data, y0data = trans.transform_point((x0-pos_x, y0-pos_y)) - - if x0data>xmax or x0dataymax or y0data xmax or x0data < xmin or y0data > ymax or y0data < ymin: return - if xdatax0 and inverted_x): - x1=x1_min[0][0]+pos_x + if xdata < xmin: + x1_min = ax.transData.transform([(xmin, ymin)]) + if (x1 < x0 and not inverted_x) or (x1 > x0 and inverted_x): + x1 = x1_min[0][0] + pos_x else: - x0=x1_min[0][0] + x0 = x1_min[0][0] - if xdata>xmax: - x0_max = ax.transData.transform([(xmax,ymin)]) - if (x1>x0 and not inverted_x) or (x1 xmax: + x0_max = ax.transData.transform([(xmax, ymin)]) + if (x1 > x0 and not inverted_x) or (x1 < x0 and inverted_x): + x1 = x0_max[0][0] + pos_x else: - x0=x0_max[0][0] + x0 = x0_max[0][0] - if ydatay0 and inverted_y): - y1=y1_min[0][1]+pos_y + if ydata < ymin: + y1_min = ax.transData.transform([(xmin, ymin)]) + if (y1 < y0 and not inverted_y) or (y1 > y0 and inverted_y): + y1 = y1_min[0][1] + pos_y else: - y0=y1_min[0][1]+pos_y + y0 = y1_min[0][1] + pos_y - if ydata>ymax: - y0_max = ax.transData.transform([(xmax,ymax)]) - if (y1>y0 and not inverted_y) or (y1 ymax: + y0_max = ax.transData.transform([(xmax, ymax)]) + if (y1 > y0 and not inverted_y) or (y1 < y0 and inverted_y): + y1 = y0_max[0][1] + pos_y else: - y0=y0_max[0][1]+pos_y - - if abs(x1-x0)self.minzoom: - self.pos_x_rect_ver=x0 - self.pos_y_rect_ver=y0 - - x1_min = self.axes.transData.transform([(xmin,ymin)]) - x0=x1_min[0][0]+pos_x - - x0_max = self.axes.transData.transform([(xmax,ymin)]) - x1=x0_max[0][0]+pos_x - - self._alpha_ver=1 - self._alpha_hor=0 - - elif abs(y1-y0)self.minzoom: - self.pos_x_rect_hor=x0 - self.pos_y_rect_hor=y0 - - y1_min = self.axes.transData.transform([(xmin,ymin)]) - y0=y1_min[0][1]+pos_y - - y0_max = self.axes.transData.transform([(xmax,ymax)]) - y1=y0_max[0][1]+pos_y - - self._alpha_hor=1 - self._alpha_ver=0 - + y0 = y0_max[0][1] + pos_y + + if abs(x1 - x0) < dp(20) and abs(y1 - y0) > self.minzoom: + self.pos_x_rect_ver = x0 + self.pos_y_rect_ver = y0 + + x1_min = self.axes.transData.transform([(xmin, ymin)]) + x0 = x1_min[0][0] + pos_x + + x0_max = self.axes.transData.transform([(xmax, ymin)]) + x1 = x0_max[0][0] + pos_x + + self._alpha_ver = 1 + self._alpha_hor = 0 + + elif abs(y1 - y0) < dp(20) and abs(x1 - x0) > self.minzoom: + self.pos_x_rect_hor = x0 + self.pos_y_rect_hor = y0 + + y1_min = self.axes.transData.transform([(xmin, ymin)]) + y0 = y1_min[0][1] + pos_y + + y0_max = self.axes.transData.transform([(xmax, ymax)]) + y1 = y0_max[0][1] + pos_y + + self._alpha_hor = 1 + self._alpha_ver = 0 + else: - self._alpha_hor=0 - self._alpha_ver=0 + self._alpha_hor = 0 + self._alpha_ver = 0 - if x1>x0: - self.invert_rect_ver=False + if x1 > x0: + self.invert_rect_ver = False else: - self.invert_rect_ver=True - if y1>y0: - self.invert_rect_hor=False + self.invert_rect_ver = True + if y1 > y0: + self.invert_rect_hor = False else: - self.invert_rect_hor=True - + self.invert_rect_hor = True + self._box_pos = x0, y0 self._box_size = x1 - x0, y1 - y0 - + def update_lim(self): """ update axis lim if zoombox is used""" - ax=self.axes + ax = self.axes + + self.do_update = False - self.do_update=False - if not self.box_axes: - self.box_axes=[ax] - - for index,ax in enumerate(self.box_axes): - #check if inverted axis - xleft,xright=ax.get_xlim() - ybottom,ytop=ax.get_ylim() - - if xright>xleft: - ax.set_xlim(left=min(self.x0_box[index],self.x1_box[index]),right=max(self.x0_box[index],self.x1_box[index])) + self.box_axes = [ax] + + for index, ax in enumerate(self.box_axes): + # check if inverted axis + xleft, xright = ax.get_xlim() + ybottom, ytop = ax.get_ylim() + + if xright > xleft: + ax.set_xlim( + left=min( + self.x0_box[index], + self.x1_box[index]), + right=max( + self.x0_box[index], + self.x1_box[index])) else: - ax.set_xlim(right=min(self.x0_box[index],self.x1_box[index]),left=max(self.x0_box[index],self.x1_box[index])) - if ytop>ybottom: - ax.set_ylim(bottom=min(self.y0_box[index],self.y1_box[index]),top=max(self.y0_box[index],self.y1_box[index])) + ax.set_xlim( + right=min( + self.x0_box[index], self.x1_box[index]), left=max( + self.x0_box[index], self.x1_box[index])) + if ytop > ybottom: + ax.set_ylim( + bottom=min( + self.y0_box[index], self.y1_box[index]), top=max( + self.y0_box[index], self.y1_box[index])) else: - ax.set_ylim(top=min(self.y0_box[index],self.y1_box[index]),bottom=max(self.y0_box[index],self.y1_box[index])) + ax.set_ylim( + top=min( + self.y0_box[index], + self.y1_box[index]), + bottom=max( + self.y0_box[index], + self.y1_box[index])) def reset_box(self): """ reset zoombox and apply zoombox limit if zoombox option if selected""" - if min(abs(self._box_size[0]),abs(self._box_size[1]))>self.minzoom: + if min(abs(self._box_size[0]), abs(self._box_size[1])) > self.minzoom: self.x0_box, self.y0_box = [], [] - self.x1_box, self.y1_box = [], [] + self.x1_box, self.y1_box = [], [] for ax in self.box_axes: trans = ax.transData.inverted() - x0_box, y0_box = trans.transform_point((self._box_pos[0]-self.pos[0], self._box_pos[1]-self.pos[1])) - x1_box, y1_box = trans.transform_point((self._box_size[0]+self._box_pos[0]-self.pos[0], self._box_size[1]+self._box_pos[1]-self.pos[1])) + x0_box, y0_box = trans.transform_point( + (self._box_pos[0] - self.pos[0], self._box_pos[1] - self.pos[1])) + x1_box, y1_box = trans.transform_point( + (self._box_size[0] + self._box_pos[0] - self.pos[0], + self._box_size[1] + self._box_pos[1] - self.pos[1])) self.x0_box.append(x0_box) self.y0_box.append(y0_box) self.x1_box.append(x1_box) self.y1_box.append(y1_box) - self.do_update=True - + self.do_update = True + self._box_size = 0, 0 self._box_pos = 0, 0 - self._alpha_box=0 + self._alpha_box = 0 self._pos_x_rect_hor = 0 self._pos_y_rect_hor = 0 self._pos_x_rect_ver = 0 - self._pos_y_rect_ver = 0 - self._alpha_hor=0 - self._alpha_ver=0 + self._pos_y_rect_ver = 0 + self._alpha_hor = 0 + self._alpha_ver = 0 self.invert_rect_hor = False - self.invert_rect_ver = False - + self.invert_rect_ver = False + def _draw_bitmap(self): """ draw bitmap method. based on kivy scatter method""" if self._bitmap is None: @@ -1885,6 +2074,6 @@ def _draw_bitmap(self): self._img_texture.blit_buffer( bytes(self._bitmap), colorfmt="rgba", bufferfmt='ubyte') self._img_texture.flip_vertical() - - self.update_hover() - self.update_selector() \ No newline at end of file + + self.update_hover() + self.update_selector() diff --git a/kivy_matplotlib_widget/uix/graph_widget.py b/kivy_matplotlib_widget/uix/graph_widget.py index d7b75a8..6c63a0f 100644 --- a/kivy_matplotlib_widget/uix/graph_widget.py +++ b/kivy_matplotlib_widget/uix/graph_widget.py @@ -1,7 +1,25 @@ -""" MatplotFigure is based on https://github.com/jeysonmc/kivy_matplotlib +""" MatplotFigure is based on https://github.com/jeysonmc/kivy_matplotlib and kivy scatter """ +from kivy.factory import Factory +from kivy.clock import Clock +from kivy.core.window import Window +from kivy.utils import get_color_from_hex +import numpy as np +from kivy.metrics import dp +from weakref import WeakKeyDictionary +from matplotlib.backend_bases import ResizeEvent +from matplotlib.colors import to_hex +from matplotlib import cbook +from matplotlib.backends.backend_agg import FigureCanvasAgg +from kivy.vector import Vector +from kivy.uix.widget import Widget +from kivy.properties import ObjectProperty, ListProperty, BooleanProperty, BoundedNumericProperty, AliasProperty, \ + NumericProperty, OptionProperty, DictProperty +from kivy.lang import Builder +from kivy.graphics.transformation import Matrix +from kivy.graphics.texture import Texture import math import copy @@ -10,36 +28,21 @@ selector_widgets_available = False try: selector_widgets_available = True - from kivy_matplotlib_widget.uix.selector_widget import ResizeRelativeLayout,LassoRelativeLayout,EllipseRelativeLayout,SpanRelativeLayout + from kivy_matplotlib_widget.uix.selector_widget import ResizeRelativeLayout, LassoRelativeLayout, EllipseRelativeLayout, SpanRelativeLayout except ImportError: print('Selector widgets are not available') -from kivy.graphics.texture import Texture -from kivy.graphics.transformation import Matrix -from kivy.lang import Builder -from kivy.properties import ObjectProperty, ListProperty, BooleanProperty, BoundedNumericProperty, AliasProperty, \ - NumericProperty, OptionProperty, DictProperty -from kivy.uix.widget import Widget -from kivy.vector import Vector -from matplotlib.backends.backend_agg import FigureCanvasAgg -from matplotlib import cbook -from matplotlib.colors import to_hex -from matplotlib.backend_bases import ResizeEvent -from weakref import WeakKeyDictionary -from kivy.metrics import dp -import numpy as np -from kivy.utils import get_color_from_hex -from kivy.core.window import Window -from kivy.clock import Clock + class MatplotlibEvent: - x=None - y=None - pickradius=None - inaxes=None - projection=False - compare_xdata=False - pick_radius_axis='both' - + x = None + y = None + pickradius = None + inaxes = None + projection = False + compare_xdata = False + pick_radius_axis = 'both' + + class MatplotFigure(Widget): """Widget to show a matplotlib figure in kivy. The figure is rendered internally in an AGG backend then @@ -50,10 +53,10 @@ class MatplotFigure(Widget): _box_pos = ListProperty([0, 0]) _box_size = ListProperty([0, 0]) _img_texture = ObjectProperty(None) - _alpha_box = NumericProperty(0) + _alpha_box = NumericProperty(0) _bitmap = None _pressed = False - do_update=False + do_update = False figcanvas = ObjectProperty(None) translation_touches = BoundedNumericProperty(1, min=1) do_scale = BooleanProperty(True) @@ -62,46 +65,54 @@ class MatplotFigure(Widget): transform = ObjectProperty(Matrix()) _alpha_hor = NumericProperty(0) _alpha_ver = NumericProperty(0) - pos_x_rect_hor=NumericProperty(0) - pos_y_rect_hor=NumericProperty(0) - pos_x_rect_ver=NumericProperty(0) - pos_y_rect_ver=NumericProperty(0) + pos_x_rect_hor = NumericProperty(0) + pos_y_rect_hor = NumericProperty(0) + pos_x_rect_ver = NumericProperty(0) + pos_y_rect_ver = NumericProperty(0) invert_rect_ver = BooleanProperty(False) invert_rect_hor = BooleanProperty(False) legend_do_scroll_x = BooleanProperty(True) legend_do_scroll_y = BooleanProperty(True) - interactive_axis = BooleanProperty(False) + interactive_axis = BooleanProperty(False) do_pan_x = BooleanProperty(True) - do_pan_y = BooleanProperty(True) + do_pan_y = BooleanProperty(True) do_zoom_x = BooleanProperty(True) - do_zoom_y = BooleanProperty(True) - fast_draw = BooleanProperty(True) #True will don't draw axis - xsorted = BooleanProperty(False) #to manage x sorted data + do_zoom_y = BooleanProperty(True) + fast_draw = BooleanProperty(True) # True will don't draw axis + xsorted = BooleanProperty(False) # to manage x sorted data minzoom = NumericProperty(dp(20)) - compare_xdata = BooleanProperty(False) + compare_xdata = BooleanProperty(False) hover_instance = ObjectProperty(None, allownone=True) nearest_hover_instance = ObjectProperty(None, allownone=True) compare_hover_instance = ObjectProperty(None, allownone=True) - disable_mouse_scrolling = BooleanProperty(False) - disable_double_tap = BooleanProperty(False) + disable_mouse_scrolling = BooleanProperty(False) + disable_double_tap = BooleanProperty(False) text_instance = None auto_zoom = BooleanProperty(False) - zoom_angle_detection=NumericProperty(15) #in degree + zoom_angle_detection = NumericProperty(15) # in degree auto_cursor = BooleanProperty(False) autoscale_visible_only = BooleanProperty(True) autoscale_axis = OptionProperty("both", options=["both", "x", "y"]) autoscale_tight = BooleanProperty(False) - desktop_mode = BooleanProperty(True) #change mouse hover for selector widget - current_selector = OptionProperty("None", - options = ["None",'rectangle','lasso','ellipse','span','custom']) + # change mouse hover for selector widget + desktop_mode = BooleanProperty(True) + current_selector = OptionProperty( + "None", + options=[ + "None", + 'rectangle', + 'lasso', + 'ellipse', + 'span', + 'custom']) highlight_hover = BooleanProperty(False) highlight_prop = DictProperty({}) - highlight_alpha = NumericProperty(0.2) + highlight_alpha = NumericProperty(0.2) myevent = MatplotlibEvent() - pick_minimum_radius=NumericProperty(dp(50)) + pick_minimum_radius = NumericProperty(dp(50)) pick_radius_axis = OptionProperty("both", options=["both", "x", "y"]) - + def on_figure(self, obj, value): self.figcanvas = _FigureCanvas(self.figure, self) self.figcanvas._isDrawn = False @@ -112,49 +123,49 @@ def on_figure(self, obj, value): self.height = h if len(self.figure.axes) > 0 and self.figure.axes[0]: - #add copy patch - ax=self.figure.axes[0] - patch_cpy=copy.copy(ax.patch) + # add copy patch + ax = self.figure.axes[0] + patch_cpy = copy.copy(ax.patch) patch_cpy.set_visible(False) - - if hasattr(ax,'PolarTransform'): + + if hasattr(ax, 'PolarTransform'): for pos in list(ax.spines._dict.keys()): ax.spines[pos].set_zorder(10) - self.disabled = True #polar graph do not handle pan/zoom + self.disabled = True # polar graph do not handle pan/zoom else: for pos in ['right', 'top', 'bottom', 'left']: ax.spines[pos].set_zorder(10) patch_cpy.set_zorder(9) - self.background_patch_copy= ax.add_patch(patch_cpy) - - #set xmin axes attribute + self.background_patch_copy = ax.add_patch(patch_cpy) + + # set xmin axes attribute self.axes = self.figure.axes[0] - - #set default xmin/xmax and ymin/ymax - self.xmin,self.xmax = self.axes.get_xlim() - self.ymin,self.ymax = self.axes.get_ylim() - + + # set default xmin/xmax and ymin/ymax + self.xmin, self.xmax = self.axes.get_xlim() + self.ymin, self.ymax = self.axes.get_ylim() + if self.legend_instance: - #remove all legend_instance from parent widget + # remove all legend_instance from parent widget for current_legend in self.legend_instance: current_legend.parent.remove_widget(current_legend) - self.legend_instance=[] - + self.legend_instance = [] + if self.auto_cursor and len(self.figure.axes) > 0: self.register_lines(list(self.axes.lines)) - + if self.selector and self.axes: self.selector.resize_wgt.ax = self.axes # Texture self._img_texture = Texture.create(size=(w, h)) - - #close last figure in memory (avoid max figure warning) + + # close last figure in memory (avoid max figure warning) matplotlib.pyplot.close() def __init__(self, **kwargs): super(MatplotFigure, self).__init__(**kwargs) - - #figure info + + # figure info self.figure = None self.axes = None self.xmin = None @@ -162,119 +173,124 @@ def __init__(self, **kwargs): self.ymin = None self.ymax = None self.lines = [] - - #option - self.touch_mode='pan' + + # option + self.touch_mode = 'pan' self.hover_on = False - self.cursor_xaxis_formatter=None #used matplotlib formatter to display x cursor value - self.cursor_yaxis_formatter=None #used matplotlib formatter to display y cursor value + # used matplotlib formatter to display x cursor value + self.cursor_xaxis_formatter = None + # used matplotlib formatter to display y cursor value + self.cursor_yaxis_formatter = None - #zoom box coordonnate + # zoom box coordonnate self.x0_box = None self.y0_box = None self.x1_box = None self.y1_box = None - - #clear touches on touch up + + # clear touches on touch up self._touches = [] self._last_touch_pos = {} - #background - self.background=None - self.background_patch_copy=None + # background + self.background = None + self.background_patch_copy = None - #manage adjust x and y + # manage adjust x and y self.anchor_x = None - self.anchor_y = None - - #trick to manage wrong canvas size on first call (compare_hover) - self.first_call_compare_hover=False - - #manage hover data + self.anchor_y = None + + # trick to manage wrong canvas size on first call (compare_hover) + self.first_call_compare_hover = False + + # manage hover data self.x_hover_data = None self.y_hover_data = None - - #pan management + + # pan management self.first_touch_pan = None - - #manage show compare cursor on release + + # manage show compare cursor on release self.show_compare_cursor = False - - #manage back and next event - if hasattr(cbook,'_Stack'): - #manage matplotlib version with no Stack (replace by _Stack) + + # manage back and next event + if hasattr(cbook, '_Stack'): + # manage matplotlib version with no Stack (replace by _Stack) self._nav_stack = cbook._Stack() else: - self._nav_stack = cbook.Stack() - self.set_history_buttons() - - #legend management + self._nav_stack = cbook.Stack() + self.set_history_buttons() + + # legend management self.legend_instance = [] - self.current_legend=None - - #selector management + self.current_legend = None + + # selector management self.kv_post_done = False - self.selector = None + self.selector = None - #highlight management + # highlight management self.last_line = None self.last_line_prop = {} - + self.bind(size=self._onSize) - def on_kv_post(self,_): + def on_kv_post(self, _): # if not self.selector: if self.current_selector != "None" and selector_widgets_available: if self.current_selector == 'rectangle': self.set_selector(ResizeRelativeLayout) elif self.current_selector == 'lasso': - self.set_selector(LassoRelativeLayout) + self.set_selector(LassoRelativeLayout) elif self.current_selector == 'ellipse': - self.set_selector(EllipseRelativeLayout) + self.set_selector(EllipseRelativeLayout) elif self.current_selector == 'span': - self.set_selector(SpanRelativeLayout) - self.kv_post_done=True - - def transform_eval(self,x,axis): - custom_transform=axis.get_transform() + self.set_selector(SpanRelativeLayout) + self.kv_post_done = True + + def transform_eval(self, x, axis): + custom_transform = axis.get_transform() return custom_transform.transform_non_affine(np.array([x]))[0] - - def inv_transform_eval(self,x,axis): - inv_custom_transform=axis.get_transform().inverted() + + def inv_transform_eval(self, x, axis): + inv_custom_transform = axis.get_transform().inverted() return inv_custom_transform.transform_non_affine(np.array([x]))[0] - - def on_current_selector(self,instance,value,*args): - + + def on_current_selector(self, instance, value, *args): + if self.kv_post_done and selector_widgets_available: if value == 'rectangle': self.set_selector(ResizeRelativeLayout) elif value == 'lasso': - self.set_selector(LassoRelativeLayout) + self.set_selector(LassoRelativeLayout) elif value == 'ellipse': - self.set_selector(EllipseRelativeLayout) + self.set_selector(EllipseRelativeLayout) elif self.current_selector == 'span': - self.set_selector(SpanRelativeLayout) + self.set_selector(SpanRelativeLayout) elif value == "None": if self.selector: - Window.unbind(mouse_pos=self.selector.resize_wgt.on_mouse_pos) + Window.unbind( + mouse_pos=self.selector.resize_wgt.on_mouse_pos) self.parent.remove_widget(self.selector) self.selector = None - - def set_selector(self,selector,*args): - selector_collection=None - selector_line=None + + def set_selector(self, selector, *args): + selector_collection = None + selector_line = None callback = None callback_clear = None if self.selector: selector_collection = self.selector.resize_wgt.collection selector_line = self.selector.resize_wgt.line callback = self.selector.resize_wgt.callback - callback_clear = self.selector.resize_wgt.callback_clear + callback_clear = self.selector.resize_wgt.callback_clear Window.unbind(mouse_pos=self.selector.resize_wgt.on_mouse_pos) self.parent.remove_widget(self.selector) - - self.selector = selector(figure_wgt=self,desktop_mode=self.desktop_mode) + + self.selector = selector( + figure_wgt=self, + desktop_mode=self.desktop_mode) self.selector.resize_wgt.ax = self.axes if selector_collection: self.set_collection() @@ -284,372 +300,421 @@ def set_selector(self,selector,*args): self.set_callback(callback) if callback_clear: self.set_callback_clear(callback_clear) - - self.parent.add_widget(self.selector) - - def set_collection(self): + + self.parent.add_widget(self.selector) + + def set_collection(self): self.selector.resize_wgt.ax = self.axes - collections = self.axes.collections - + collections = self.axes.collections + if collections: - self.selector.resize_wgt.set_collection(collections[0]) - - def set_line(self,line): - self.selector.resize_wgt.ax = self.axes - self.selector.resize_wgt.set_line(line) - - def set_callback(self,callback): + self.selector.resize_wgt.set_collection(collections[0]) + + def set_line(self, line): + self.selector.resize_wgt.ax = self.axes + self.selector.resize_wgt.set_line(line) + + def set_callback(self, callback): self.selector.resize_wgt.set_callback(callback) - - def set_callback_clear(self,callback): - self.selector.resize_wgt.set_callback_clear(callback) - - def register_lines(self,lines:list) -> None: + + def set_callback_clear(self, callback): + self.selector.resize_wgt.set_callback_clear(callback) + + def register_lines(self, lines: list) -> None: """ register lines method - + Args: lines (list): list of matplolib line class - + Return: - None - """ - #use sel,axes limit to avoid graph rescale - xmin,xmax = self.axes.get_xlim() - ymin,ymax = self.axes.get_ylim() - #create cross hair cusor - self.horizontal_line = self.axes.axhline(y=self.ymin,color='k', lw=0.8, ls='--', visible=False) - self.vertical_line = self.axes.axvline(x=self.xmin,color='k', lw=0.8, ls='--', visible=False) - - #register lines - self.lines=lines - - #cursor text - self.text = self.axes.text(1.0, 1.01, '', - transform=self.axes.transAxes, - ha='right') - - def set_cross_hair_visible(self, visible:bool) -> None: + None + """ + # use sel,axes limit to avoid graph rescale + xmin, xmax = self.axes.get_xlim() + ymin, ymax = self.axes.get_ylim() + # create cross hair cusor + self.horizontal_line = self.axes.axhline( + y=self.ymin, color='k', lw=0.8, ls='--', visible=False) + self.vertical_line = self.axes.axvline( + x=self.xmin, color='k', lw=0.8, ls='--', visible=False) + + # register lines + self.lines = lines + + # cursor text + self.text = self.axes.text(1.0, 1.01, '', + transform=self.axes.transAxes, + ha='right') + + def set_cross_hair_visible(self, visible: bool) -> None: """ set curcor visibility - + Args: visible (bool): make cursor visble or not - + Return: None - - """ + + """ self.horizontal_line.set_visible(visible) self.vertical_line.set_visible(visible) self.text.set_visible(visible) def clear_line_prop(self) -> None: """ clear attribute line_prop method - + Args: None - + Return: None - - """ + + """ if self.last_line_prop: for key in self.last_line_prop: - set_line_attr = getattr(self.last_line,'set_' + key) - set_line_attr(self.last_line_prop[key]) - self.last_line_prop={} - self.last_line=None + set_line_attr = getattr(self.last_line, 'set_' + key) + set_line_attr(self.last_line_prop[key]) + self.last_line_prop = {} + self.last_line = None def hover(self, event) -> None: """ hover cursor method (cursor to nearest value) - + Args: event: touch kivy event - + Return: None - + """ - - #if cursor is set -> hover is on + + # if cursor is set -> hover is on if self.hover_on: - #transform kivy x,y touch event to x,y data + # transform kivy x,y touch event to x,y data trans = self.axes.transData.inverted() - xdata, ydata = trans.transform_point((event.x - self.pos[0], event.y - self.pos[1])) - - #loop all register lines and find closest x,y data for each valid line - distance=[] - good_line=[] - good_index=[] + xdata, ydata = trans.transform_point( + (event.x - self.pos[0], event.y - self.pos[1])) + + # loop all register lines and find closest x,y data for each valid + # line + distance = [] + good_line = [] + good_index = [] for line in self.lines: - #get only visible lines - if line.get_visible(): - #get line x,y datas + # get only visible lines + if line.get_visible(): + # get line x,y datas self.x_cursor, self.y_cursor = line.get_xydata().T - - #check if line is not empty - if len(self.x_cursor)!=0: - - #find closest data index from touch (x axis) + + # check if line is not empty + if len(self.x_cursor) != 0: + + # find closest data index from touch (x axis) if self.xsorted: - index = min(np.searchsorted(self.x_cursor, xdata), len(self.y_cursor) - 1) - + index = min( + np.searchsorted( + self.x_cursor, xdata), len( + self.y_cursor) - 1) + else: index = np.argsort(abs(self.x_cursor - xdata))[0] - #get x data from index + # get x data from index x = self.x_cursor[index] - + if self.compare_xdata: y = self.y_cursor[index] - - #get distance between line and touch (in pixels) - ax=line.axes - #left axis - xy_pixels_mouse = ax.transData.transform([(xdata,ydata)]) - if np.ma.is_masked(x) or np.ma.is_masked(y) or np.isnan(x) or np.isnan(y): + + # get distance between line and touch (in pixels) + ax = line.axes + # left axis + xy_pixels_mouse = ax.transData.transform( + [(xdata, ydata)]) + if np.ma.is_masked(x) or np.ma.is_masked( + y) or np.isnan(x) or np.isnan(y): distance.append(np.nan) else: - xy_pixels = ax.transData.transform([(x,ydata)]) - dx2 = (xy_pixels_mouse[0][0]-xy_pixels[0][0]) + xy_pixels = ax.transData.transform( + [(x, ydata)]) + dx2 = (xy_pixels_mouse[0][0] - xy_pixels[0][0]) distance.append(abs(dx2)) - else: - - #find ydata corresponding to xdata + else: + + # find ydata corresponding to xdata y = self.y_cursor[index] - - #get distance between line and touch (in pixels) - ax=line.axes - #left axis - xy_pixels_mouse = ax.transData.transform([(xdata,ydata)]) + + # get distance between line and touch (in pixels) + ax = line.axes + # left axis + xy_pixels_mouse = ax.transData.transform( + [(xdata, ydata)]) if np.ma.is_masked(x) or np.ma.is_masked(y): distance.append(np.nan) else: - xy_pixels = ax.transData.transform([(x,y)]) - dx2 = (xy_pixels_mouse[0][0]-xy_pixels[0][0])**2 - dy2 = (xy_pixels_mouse[0][1]-xy_pixels[0][1])**2 - - #store distance + xy_pixels = ax.transData.transform([(x, y)]) + dx2 = ( + xy_pixels_mouse[0][0] - xy_pixels[0][0])**2 + dy2 = ( + xy_pixels_mouse[0][1] - xy_pixels[0][1])**2 + + # store distance if self.pick_radius_axis == 'both': distance.append((dx2 + dy2)**0.5) if self.pick_radius_axis == 'x': distance.append(abs(dx2)) if self.pick_radius_axis == 'y': - distance.append(abs(dy2)) - #store all best lines and index + distance.append(abs(dy2)) + # store all best lines and index good_line.append(line) good_index.append(index) - - #case if no good line - if len(good_line)==0: + + # case if no good line + if len(good_line) == 0: return - #if minimum distance if lower than 50 pixels, get line datas with - #minimum distance - if np.nanmin(distance)0: + self.first_call_compare_hover = True + + if len(idx_best_list) > 0: available_widget = self.hover_instance.children_list - nb_widget=len(available_widget) - index_list=list(range(nb_widget)) + nb_widget = len(available_widget) + index_list = list(range(nb_widget)) for i, current_idx_best in enumerate(idx_best_list): - if i > nb_widget-1: + if i > nb_widget - 1: break else: - line=good_line[idx_best_list[i]] + line = good_line[idx_best_list[i]] line_label = line.get_label() if line_label in self.hover_instance.children_names: - index= self.hover_instance.children_names.index(line_label) + index = self.hover_instance.children_names.index( + line_label) y_cursor = line.get_ydata() - y = y_cursor[good_index[idx_best_list[i]]] - - xy_pos = ax.transData.transform([(x,y)]) - pos_y=float(xy_pos[0][1]) + self.y - - if pos_yself.y+self.axes.bbox.bounds[1]: - available_widget[index].x_hover_pos=float(xy_pos[0][0]) + self.x - available_widget[index].y_hover_pos=float(xy_pos[0][1]) + self.y - available_widget[index].custom_color = get_color_from_hex(to_hex(line.get_color())) - + y = y_cursor[good_index[idx_best_list[i]]] + + xy_pos = ax.transData.transform([(x, y)]) + pos_y = float(xy_pos[0][1]) + self.y + + if pos_y < self.y + self.axes.bbox.bounds[1] + self.axes.bbox.bounds[3] and \ + pos_y > self.y + self.axes.bbox.bounds[1]: + available_widget[index].x_hover_pos = float( + xy_pos[0][0]) + self.x + available_widget[index].y_hover_pos = float( + xy_pos[0][1]) + self.y + available_widget[index].custom_color = get_color_from_hex( + to_hex(line.get_color())) + if self.cursor_yaxis_formatter: - y = self.cursor_yaxis_formatter.format_data(y) + y = self.cursor_yaxis_formatter.format_data( + y) else: y = ax.yaxis.get_major_formatter().format_data_short(y) - available_widget[index].label_y_value=f"{y}" - available_widget[index].show_widget=True + available_widget[index].label_y_value = f"{y}" + available_widget[index].show_widget = True index_list.remove(index) - + for ii in index_list: - available_widget[ii].show_widget=False + available_widget[ii].show_widget = False if self.cursor_xaxis_formatter: - x = self.cursor_xaxis_formatter.format_data(x) + x = self.cursor_xaxis_formatter.format_data(x) else: x = ax.xaxis.get_major_formatter().format_data_short(x) - - self.hover_instance.label_x_value=f"{x}" - - if hasattr(self.hover_instance,'overlap_check'): + + self.hover_instance.label_x_value = f"{x}" + + if hasattr(self.hover_instance, 'overlap_check'): self.hover_instance.overlap_check() - self.hover_instance.xmin_line = float(ax.bbox.bounds[0]) + self.x - self.hover_instance.xmax_line = float(ax.bbox.bounds[0] + ax.bbox.bounds[2]) + self.x - self.hover_instance.ymin_line = float(ax.bbox.bounds[1]) + self.y - self.hover_instance.ymax_line = float(ax.bbox.bounds[1] + ax.bbox.bounds[3]) + self.y - - if self.hover_instance.x_hover_pos>self.x+self.axes.bbox.bounds[2] + self.axes.bbox.bounds[0] or \ - self.hover_instance.x_hover_pos self.x + self.axes.bbox.bounds[2] + self.axes.bbox.bounds[0] or \ + self.hover_instance.x_hover_pos < self.x + self.axes.bbox.bounds[0] or len(index_list) == nb_widget: + self.hover_instance.hover_outside_bound = True else: - self.hover_instance.hover_outside_bound=False - + self.hover_instance.hover_outside_bound = False + return - + else: - idx_best=np.nanargmin(distance) - - #get datas from closest line - line=good_line[idx_best] + idx_best = np.nanargmin(distance) + + # get datas from closest line + line = good_line[idx_best] self.x_cursor, self.y_cursor = line.get_xydata().T x = self.x_cursor[good_index[idx_best]] - y = self.y_cursor[good_index[idx_best]] - + y = self.y_cursor[good_index[idx_best]] + if not self.hover_instance: self.set_cross_hair_visible(True) - - # update the cursor x,y data - ax=line.axes + + # update the cursor x,y data + ax = line.axes self.horizontal_line.set_ydata([y,]) self.vertical_line.set_xdata([x,]) - - #x y label - if self.hover_instance: - xy_pos = ax.transData.transform([(x,y)]) + + # x y label + if self.hover_instance: + xy_pos = ax.transData.transform([(x, y)]) self.x_hover_data = x self.y_hover_data = y - self.hover_instance.x_hover_pos=float(xy_pos[0][0]) + self.x - self.hover_instance.y_hover_pos=float(xy_pos[0][1]) + self.y - self.hover_instance.show_cursor=True - + self.hover_instance.x_hover_pos = float( + xy_pos[0][0]) + self.x + self.hover_instance.y_hover_pos = float( + xy_pos[0][1]) + self.y + self.hover_instance.show_cursor = True + if self.cursor_xaxis_formatter: x = self.cursor_xaxis_formatter.format_data(x) else: x = ax.xaxis.get_major_formatter().format_data_short(x) if self.cursor_yaxis_formatter: - y = self.cursor_yaxis_formatter.format_data(y) + y = self.cursor_yaxis_formatter.format_data(y) else: y = ax.yaxis.get_major_formatter().format_data_short(y) - self.hover_instance.label_x_value=f"{x}" - self.hover_instance.label_y_value=f"{y}" - - self.hover_instance.xmin_line = float(ax.bbox.bounds[0]) + self.x - self.hover_instance.xmax_line = float(ax.bbox.bounds[0] + ax.bbox.bounds[2]) + self.x - self.hover_instance.ymin_line = float(ax.bbox.bounds[1]) + self.y - self.hover_instance.ymax_line = float(ax.bbox.bounds[1] + ax.bbox.bounds[3]) + self.y - + self.hover_instance.label_x_value = f"{x}" + self.hover_instance.label_y_value = f"{y}" + + self.hover_instance.xmin_line = float( + ax.bbox.bounds[0]) + self.x + self.hover_instance.xmax_line = float( + ax.bbox.bounds[0] + ax.bbox.bounds[2]) + self.x + self.hover_instance.ymin_line = float( + ax.bbox.bounds[1]) + self.y + self.hover_instance.ymax_line = float( + ax.bbox.bounds[1] + ax.bbox.bounds[3]) + self.y + self.hover_instance.custom_label = line.get_label() - self.hover_instance.custom_color = get_color_from_hex(to_hex(line.get_color())) - - if self.hover_instance.x_hover_pos>self.x+self.axes.bbox.bounds[2] + self.axes.bbox.bounds[0] or \ - self.hover_instance.x_hover_posself.y+self.axes.bbox.bounds[1] + self.axes.bbox.bounds[3] or \ - self.hover_instance.y_hover_pos self.x + self.axes.bbox.bounds[2] + self.axes.bbox.bounds[0] or \ + self.hover_instance.x_hover_pos < self.x + self.axes.bbox.bounds[0] or \ + self.hover_instance.y_hover_pos > self.y + self.axes.bbox.bounds[1] + self.axes.bbox.bounds[3] or \ + self.hover_instance.y_hover_pos < self.y + self.axes.bbox.bounds[1]: + self.hover_instance.hover_outside_bound = True else: - self.hover_instance.hover_outside_bound=False - + self.hover_instance.hover_outside_bound = False + if self.highlight_hover: - self.myevent.x=event.x - self.pos[0] - self.myevent.y=event.y - self.pos[1] - self.myevent.inaxes=self.figure.canvas.inaxes((event.x - self.pos[0], - event.y - self.pos[1])) - - axes = [a for a in self.figure.canvas.figure.get_axes() - if a.in_axes(self.myevent)] - - if not axes: - + self.myevent.x = event.x - self.pos[0] + self.myevent.y = event.y - self.pos[1] + self.myevent.inaxes = self.figure.canvas.inaxes( + (event.x - self.pos[0], event.y - self.pos[1])) + + axes = [a + for a in + self.figure.canvas.figure.get_axes() + if a.in_axes(self.myevent)] + + if not axes: + if self.last_line: - self.clear_line_prop() + self.clear_line_prop() if self.background: - self.axes.figure.canvas.restore_region(self.background) - #draw (blit method) - self.axes.figure.canvas.blit(self.axes.bbox) - self.axes.figure.canvas.flush_events() + self.axes.figure.canvas.restore_region( + self.background) + # draw (blit method) + self.axes.figure.canvas.blit( + self.axes.bbox) + self.axes.figure.canvas.flush_events() self.background = None - + return - #blit method (always use because same visual effect as draw) - if self.background is None: - self.background = self.axes.figure.canvas.copy_from_bbox(self.axes.figure.bbox) + # blit method (always use because same visual + # effect as draw) + if self.background is None: + self.background = self.axes.figure.canvas.copy_from_bbox( + self.axes.figure.bbox) if self.last_line is None: - default_alpha=[] - lines_list=self.axes.lines + default_alpha = [] + lines_list = self.axes.lines for current_line in lines_list: - default_alpha.append(current_line.get_alpha()) - current_line.set_alpha(self.highlight_alpha) - + default_alpha.append( + current_line.get_alpha()) + current_line.set_alpha( + self.highlight_alpha) + self.axes.figure.canvas.draw_idle() - self.axes.figure.canvas.flush_events() - self.background_highlight=self.axes.figure.canvas.copy_from_bbox(self.axes.figure.bbox) - self.last_line=line - for i,current_line in enumerate(lines_list): + self.axes.figure.canvas.flush_events() + self.background_highlight = self.axes.figure.canvas.copy_from_bbox( + self.axes.figure.bbox) + self.last_line = line + for i, current_line in enumerate(lines_list): current_line.set_alpha(default_alpha[i]) default_alpha[i] - + if self.highlight_prop: - self.last_line_prop={} + self.last_line_prop = {} for key in self.highlight_prop: # if hasattr(line,key): - line_attr = getattr(line,'get_' + key) - self.last_line_prop.update({key:line_attr()}) - set_line_attr = getattr(line,'set_' + key) + line_attr = getattr(line, 'get_' + key) + self.last_line_prop.update( + {key: line_attr()}) + set_line_attr = getattr( + line, 'set_' + key) set_line_attr(self.highlight_prop[key]) elif self.last_line_prop: for key in self.last_line_prop: - set_line_attr = getattr(self.last_line,'set_' + key) - set_line_attr(self.last_line_prop[key]) - self.hover_instance.custom_color = get_color_from_hex(to_hex(line.get_color())) - self.last_line_prop={} + set_line_attr = getattr( + self.last_line, 'set_' + key) + set_line_attr(self.last_line_prop[key]) + self.hover_instance.custom_color = get_color_from_hex( + to_hex(line.get_color())) + self.last_line_prop = {} for key in self.highlight_prop: - line_attr = getattr(line,'get_' + key) - self.last_line_prop.update({key:line_attr()}) - set_line_attr = getattr(line,'set_' + key) - set_line_attr(self.highlight_prop[key]) - self.last_line=line - - self.axes.figure.canvas.restore_region(self.background_highlight) + line_attr = getattr(line, 'get_' + key) + self.last_line_prop.update( + {key: line_attr()}) + set_line_attr = getattr(line, 'set_' + key) + set_line_attr(self.highlight_prop[key]) + self.last_line = line + + self.axes.figure.canvas.restore_region( + self.background_highlight) self.axes.draw_artist(line) - - #draw (blit method) - self.axes.figure.canvas.blit(self.axes.bbox) - self.axes.figure.canvas.flush_events() + + # draw (blit method) + self.axes.figure.canvas.blit(self.axes.bbox) + self.axes.figure.canvas.flush_events() return else: if self.cursor_xaxis_formatter: @@ -657,115 +722,120 @@ def hover(self, event) -> None: else: x = ax.xaxis.get_major_formatter().format_data_short(x) if self.cursor_yaxis_formatter: - y = self.cursor_yaxis_formatter.format_data(y) + y = self.cursor_yaxis_formatter.format_data(y) else: y = ax.yaxis.get_major_formatter().format_data_short(y) self.text.set_text(f"x={x}, y={y}") - - #blit method (always use because same visual effect as draw) + + # blit method (always use because same visual effect as + # draw) if self.background is None: self.set_cross_hair_visible(False) self.axes.figure.canvas.draw_idle() - self.axes.figure.canvas.flush_events() - self.background = self.axes.figure.canvas.copy_from_bbox(self.axes.figure.bbox) - self.set_cross_hair_visible(True) + self.axes.figure.canvas.flush_events() + self.background = self.axes.figure.canvas.copy_from_bbox( + self.axes.figure.bbox) + self.set_cross_hair_visible(True) if self.last_line is not None: - self.clear_line_prop() - + self.clear_line_prop() + self.axes.figure.canvas.restore_region(self.background) self.axes.draw_artist(self.text) - + self.axes.draw_artist(self.horizontal_line) - self.axes.draw_artist(self.vertical_line) - - #draw (blit method) - self.axes.figure.canvas.blit(self.axes.bbox) + self.axes.draw_artist(self.vertical_line) + + # draw (blit method) + self.axes.figure.canvas.blit(self.axes.bbox) self.axes.figure.canvas.flush_events() - #if touch is too far, hide cross hair cursor + # if touch is too far, hide cross hair cursor else: - self.set_cross_hair_visible(False) + self.set_cross_hair_visible(False) if self.hover_instance: - self.hover_instance.x_hover_pos=self.x - self.hover_instance.y_hover_pos=self.y - self.hover_instance.show_cursor=False + self.hover_instance.x_hover_pos = self.x + self.hover_instance.y_hover_pos = self.y + self.hover_instance.show_cursor = False self.x_hover_data = None self.y_hover_data = None if self.highlight_hover: - self.myevent.x=event.x - self.pos[0] - self.myevent.y=event.y - self.pos[1] - self.myevent.inaxes=self.figure.canvas.inaxes((event.x - self.pos[0], - event.y - self.pos[1])) - + self.myevent.x = event.x - self.pos[0] + self.myevent.y = event.y - self.pos[1] + self.myevent.inaxes = self.figure.canvas.inaxes( + (event.x - self.pos[0], event.y - self.pos[1])) + axes = [a for a in self.figure.canvas.figure.get_axes() - if a.in_axes(self.myevent)] - - if not axes: - + if a.in_axes(self.myevent)] + + if not axes: + if self.last_line: - self.clear_line_prop() + self.clear_line_prop() if self.background: - self.axes.figure.canvas.restore_region(self.background) - #draw (blit method) - self.axes.figure.canvas.blit(self.axes.bbox) - self.axes.figure.canvas.flush_events() + self.axes.figure.canvas.restore_region( + self.background) + # draw (blit method) + self.axes.figure.canvas.blit( + self.axes.bbox) + self.axes.figure.canvas.flush_events() self.background = None - + return + def autoscale(self): if self.disabled: return - ax=self.axes + ax = self.axes ax.relim(visible_only=self.autoscale_visible_only) ax.autoscale_view(tight=self.autoscale_tight, - scalex=True if self.autoscale_axis!="y" else False, - scaley=True if self.autoscale_axis!="x" else False) - ax.autoscale(axis=self.autoscale_axis,tight=self.autoscale_tight) + scalex=True if self.autoscale_axis != "y" else False, + scaley=True if self.autoscale_axis != "x" else False) + ax.autoscale(axis=self.autoscale_axis, tight=self.autoscale_tight) ax.figure.canvas.draw_idle() - ax.figure.canvas.flush_events() + ax.figure.canvas.flush_events() ax.set_autoscale_on(False) - self.xmin,self.xmax = ax.get_xlim() - self.ymin,self.ymax = ax.get_ylim() + self.xmin, self.xmax = ax.get_xlim() + self.ymin, self.ymax = ax.get_ylim() def home(self) -> None: """ reset data axis - + Return: None """ - #do nothing is all min/max are not set + # do nothing is all min/max are not set if self.xmin is not None and \ - self.xmax is not None and \ - self.ymin is not None and \ - self.ymax is not None: - + self.xmax is not None and \ + self.ymin is not None and \ + self.ymax is not None: + ax = self.axes - xleft,xright=ax.get_xlim() - ybottom,ytop=ax.get_ylim() - - #check inverted data + xleft, xright = ax.get_xlim() + ybottom, ytop = ax.get_ylim() + + # check inverted data inverted_x = False - if xleft>xright: - inverted_x=True + if xleft > xright: + inverted_x = True inverted_y = False - if ybottom>ytop: - inverted_y=True - + if ybottom > ytop: + inverted_y = True + if inverted_x: - ax.set_xlim(right=self.xmin,left=self.xmax) + ax.set_xlim(right=self.xmin, left=self.xmax) else: - ax.set_xlim(left=self.xmin,right=self.xmax) + ax.set_xlim(left=self.xmin, right=self.xmax) if inverted_y: - ax.set_ylim(top=self.ymin,bottom=self.ymax) + ax.set_ylim(top=self.ymin, bottom=self.ymax) else: - ax.set_ylim(bottom=self.ymin,top=self.ymax) + ax.set_ylim(bottom=self.ymin, top=self.ymax) if self.last_line is not None: - self.clear_line_prop() - + self.clear_line_prop() + ax.figure.canvas.draw_idle() - ax.figure.canvas.flush_events() + ax.figure.canvas.flush_events() def back(self, *args): """ @@ -788,23 +858,23 @@ def forward(self, *args): self._nav_stack.forward() self.set_history_buttons() self._update_view() - + def push_current(self): - """Push the current view limits and position onto the stack.""" - self._nav_stack.push( - WeakKeyDictionary( - {ax: (ax._get_view(), - # Store both the original and modified positions. - (ax.get_position(True).frozen(), - ax.get_position().frozen())) - for ax in self.figure.axes})) - self.set_history_buttons() + """Push the current view limits and position onto the stack.""" + self._nav_stack.push( + WeakKeyDictionary( + {ax: (ax._get_view(), + # Store both the original and modified positions. + (ax.get_position(True).frozen(), + ax.get_position().frozen())) + for ax in self.figure.axes})) + self.set_history_buttons() def update(self): """Reset the Axes stack.""" self._nav_stack.clear() self.set_history_buttons() - + def _update_view(self): """ Update the viewlim and position from the view and position stack for @@ -821,7 +891,7 @@ def _update_view(self): # Restore both the original and modified positions ax._set_position(pos_orig, 'original') ax._set_position(pos_active, 'active') - self.figure.canvas.draw_idle() + self.figure.canvas.draw_idle() self.figure.canvas.flush_events() def set_history_buttons(self): @@ -829,13 +899,13 @@ def set_history_buttons(self): def reset_touch(self) -> None: """ reset touch - + Return: None """ self._touches = [] self._last_touch_pos = {} - + def _get_scale(self): """ kivy scatter _get_scale method """ p1 = Vector(*self.to_parent(0, 0)) @@ -877,10 +947,9 @@ def _draw_bitmap(self): self._img_texture.blit_buffer( bytes(self._bitmap), colorfmt="rgba", bufferfmt='ubyte') self._img_texture.flip_vertical() - - self.update_hover() - self.update_selector() - + + self.update_hover() + self.update_selector() def transform_with_touch(self, event): """ manage touch behaviour. based on kivy scatter method""" @@ -888,43 +957,43 @@ def transform_with_touch(self, event): changed = False if len(self._touches) == self.translation_touches: - - if self.touch_mode=='pan': + + if self.touch_mode == 'pan': if self._nav_stack() is None: - self.push_current() + self.push_current() self.apply_pan(self.axes, event) - - if self.touch_mode=='pan_x' or self.touch_mode=='pan_y' \ - or self.touch_mode=='adjust_x' or self.touch_mode=='adjust_y': + + if self.touch_mode == 'pan_x' or self.touch_mode == 'pan_y' \ + or self.touch_mode == 'adjust_x' or self.touch_mode == 'adjust_y': if self._nav_stack() is None: - self.push_current() - self.apply_pan(self.axes, event, mode=self.touch_mode) - - elif self.touch_mode=='drag_legend': + self.push_current() + self.apply_pan(self.axes, event, mode=self.touch_mode) + + elif self.touch_mode == 'drag_legend': if self.legend_instance: self.apply_drag_legend(self.axes, event) - - elif self.touch_mode=='zoombox': + + elif self.touch_mode == 'zoombox': if self._nav_stack() is None: - self.push_current() + self.push_current() real_x, real_y = event.x - self.pos[0], event.y - self.pos[1] - #in case x_init is not create - if not hasattr(self,'x_init'): + # in case x_init is not create + if not hasattr(self, 'x_init'): self.x_init = event.x self.y_init = real_y - self.draw_box(event, self.x_init,self.y_init, event.x, real_y) - - #mode cursor - elif self.touch_mode=='cursor': - self.hover_on=True + self.draw_box(event, self.x_init, self.y_init, event.x, real_y) + + # mode cursor + elif self.touch_mode == 'cursor': + self.hover_on = True self.hover(event) - + changed = True - #note: avoid zoom in/out on touch mode zoombox - if len(self._touches) == 1:# + # note: avoid zoom in/out on touch mode zoombox + if len(self._touches) == 1: return changed - + # we have more than one touch... list of last known pos points = [Vector(self._last_touch_pos[t]) for t in self._touches if t is not event] @@ -952,21 +1021,21 @@ def transform_with_touch(self, event): if self.auto_zoom: v1 = Vector(0, 10) angle = v1.angle(new_line) + 180 - if angle<0+self.zoom_angle_detection or angle>360-self.zoom_angle_detection: - self.do_zoom_x=False - self.do_zoom_y=True - elif angle>90-self.zoom_angle_detection and angle<90+self.zoom_angle_detection: - self.do_zoom_x=True - self.do_zoom_y=False - elif angle>180-self.zoom_angle_detection and angle<180+self.zoom_angle_detection: - self.do_zoom_x=False - self.do_zoom_y=True - elif angle>270-self.zoom_angle_detection and angle<270+self.zoom_angle_detection: - self.do_zoom_x=True - self.do_zoom_y=False + if angle < 0 + self.zoom_angle_detection or angle > 360 - self.zoom_angle_detection: + self.do_zoom_x = False + self.do_zoom_y = True + elif angle > 90 - self.zoom_angle_detection and angle < 90 + self.zoom_angle_detection: + self.do_zoom_x = True + self.do_zoom_y = False + elif angle > 180 - self.zoom_angle_detection and angle < 180 + self.zoom_angle_detection: + self.do_zoom_x = False + self.do_zoom_y = True + elif angle > 270 - self.zoom_angle_detection and angle < 270 + self.zoom_angle_detection: + self.do_zoom_x = True + self.do_zoom_y = False else: - self.do_zoom_x=True - self.do_zoom_y=True + self.do_zoom_x = True + self.do_zoom_y = True if self.do_scale: # scale = new_line.length() / old_line.length() @@ -976,13 +1045,13 @@ def transform_with_touch(self, event): scale = self.scale_min / self.scale elif new_scale > self.scale_max: scale = self.scale_max / self.scale - - self.apply_zoom(scale, self.axes, anchor=anchor,new_line=new_line) + + self.apply_zoom(scale, self.axes, anchor=anchor, new_line=new_line) changed = True return changed - def on_motion(self,*args): + def on_motion(self, *args): '''Kivy Event to trigger mouse event on motion `enter_notify_event`. ''' @@ -992,34 +1061,34 @@ def on_motion(self,*args): newcoord = self.to_widget(pos[0], pos[1]) x = newcoord[0] y = newcoord[1] - inside = self.collide_point(x,y) - if inside: + inside = self.collide_point(x, y) + if inside: # will receive all motion events. if self.figcanvas and self.hover_instance: - #avoid in motion if touch is detected - if not len(self._touches)==0: + # avoid in motion if touch is detected + if not len(self._touches) == 0: return - FakeEvent.x=x - FakeEvent.y=y + FakeEvent.x = x + FakeEvent.y = y self.hover(FakeEvent) - - def get_data_xy(self,x,y): + + def get_data_xy(self, x, y): """ manage x y data in navigation bar """ trans = self.axes.transData.inverted() xdata, ydata = trans.transform_point((x - self.pos[0], y - self.pos[1])) if self.cursor_xaxis_formatter: - x_format = self.cursor_xaxis_formatter.format_data(xdata) + x_format = self.cursor_xaxis_formatter.format_data(xdata) else: x_format = self.axes.xaxis.get_major_formatter().format_data_short(xdata) - + if self.cursor_yaxis_formatter: - y_format = self.cursor_yaxis_formatter.format_data(ydata) + y_format = self.cursor_yaxis_formatter.format_data(ydata) else: y_format = self.axes.yaxis.get_major_formatter().format_data_short(ydata) - - return x_format,y_format + + return x_format, y_format def on_touch_down(self, event): """ Manage Mouse/touch press """ @@ -1029,29 +1098,29 @@ def on_touch_down(self, event): if self.collide_point(x, y) and self.figure: self._pressed = True - self.show_compare_cursor=False + self.show_compare_cursor = False if self.legend_instance: - select_legend=False + select_legend = False for current_legend in self.legend_instance: if current_legend.box.collide_point(x, y): - select_legend=True + select_legend = True self.current_legend = current_legend break if select_legend: - if self.touch_mode!='drag_legend': - return False + if self.touch_mode != 'drag_legend': + return False else: event.grab(self) self._touches.append(event) self._last_touch_pos[event] = event.pos - if len(self._touches)>1: - #new touch, reset background - self.background=None - - return True + if len(self._touches) > 1: + # new touch, reset background + self.background = None + + return True else: self.current_legend = None - + if event.is_mouse_scrolling: if not self.disable_mouse_scrolling: ax = self.axes @@ -1060,32 +1129,32 @@ def on_touch_down(self, event): elif event.is_double_tap: if not self.disable_double_tap: - if self.touch_mode!='selector': + if self.touch_mode != 'selector': self.home() return True - + else: - if self.touch_mode=='cursor': - self.hover_on=True - self.hover(event) - elif self.touch_mode=='zoombox': + if self.touch_mode == 'cursor': + self.hover_on = True + self.hover(event) + elif self.touch_mode == 'zoombox': real_x, real_y = x - self.pos[0], y - self.pos[1] - self.x_init=x - self.y_init=real_y - self.draw_box(event, x, real_y, x, real_y) + self.x_init = x + self.y_init = real_y + self.draw_box(event, x, real_y, x, real_y) - elif self.touch_mode=='minmax': + elif self.touch_mode == 'minmax': self.min_max(event) - elif self.touch_mode=='selector': - pass - + elif self.touch_mode == 'selector': + pass + event.grab(self) self._touches.append(event) self._last_touch_pos[event] = event.pos - if len(self._touches)>1: - #new touch, reset background - self.background=None - + if len(self._touches) > 1: + # new touch, reset background + self.background = None + return True else: @@ -1099,8 +1168,8 @@ def on_touch_move(self, event): if event.is_double_tap: if not self.disable_double_tap: - if self.touch_mode!='selector': - self.home() + if self.touch_mode != 'selector': + self.home() return True # scale/translate @@ -1121,89 +1190,91 @@ def on_touch_up(self, event): event.ungrab(self) del self._last_touch_pos[event] self._touches.remove(event) - if self.touch_mode=='pan' or self.touch_mode=='zoombox' or \ - self.touch_mode=='pan_x' or self.touch_mode=='pan_y' \ - or self.touch_mode=='adjust_x' or self.touch_mode=='adjust_y' \ - or self.touch_mode=='minmax': - + if self.touch_mode == 'pan' or self.touch_mode == 'zoombox' or \ + self.touch_mode == 'pan_x' or self.touch_mode == 'pan_y' \ + or self.touch_mode == 'adjust_x' or self.touch_mode == 'adjust_y' \ + or self.touch_mode == 'minmax': + self.push_current() if self.interactive_axis: - if self.touch_mode=='pan_x' or self.touch_mode=='pan_y' \ - or self.touch_mode=='adjust_x' or self.touch_mode=='adjust_y': - self.touch_mode='pan' - self.first_touch_pan=None - + if self.touch_mode == 'pan_x' or self.touch_mode == 'pan_y' \ + or self.touch_mode == 'adjust_x' or self.touch_mode == 'adjust_y': + self.touch_mode = 'pan' + self.first_touch_pan = None + if self.last_line is not None: self.clear_line_prop() - + x, y = event.x, event.y - if abs(self._box_size[0]) > 1 or abs(self._box_size[1]) > 1 or self.touch_mode=='zoombox': - self.reset_box() + if abs( + self._box_size[0]) > 1 or abs( + self._box_size[1]) > 1 or self.touch_mode == 'zoombox': + self.reset_box() if not self.collide_point(x, y) and self.do_update: - #update axis lim if zoombox is used and touch outside widget - self.update_lim() - ax=self.axes + # update axis lim if zoombox is used and touch outside widget + self.update_lim() + ax = self.axes ax.figure.canvas.draw_idle() - ax.figure.canvas.flush_events() + ax.figure.canvas.flush_events() return True - + # stop propagating if its within our bounds if self.collide_point(x, y) and self.figure: self._pressed = False if self.do_update: - self.update_lim() - - self.anchor_x=None - self.anchor_y=None - - ax=self.axes - self.background=None - self.show_compare_cursor=True - if self.last_line is None or self.touch_mode!='cursor': + self.update_lim() + + self.anchor_x = None + self.anchor_y = None + + ax = self.axes + self.background = None + self.show_compare_cursor = True + if self.last_line is None or self.touch_mode != 'cursor': ax.figure.canvas.draw_idle() - ax.figure.canvas.flush_events() - + ax.figure.canvas.flush_events() + return True - def apply_zoom(self, scale_factor, ax, anchor=(0, 0),new_line=None): + def apply_zoom(self, scale_factor, ax, anchor=(0, 0), new_line=None): """ zoom touch method """ - if self.touch_mode=='selector': + if self.touch_mode == 'selector': return - - x = anchor[0]-self.pos[0] - y = anchor[1]-self.pos[1] + + x = anchor[0] - self.pos[0] + y = anchor[1] - self.pos[1] trans = ax.transData.inverted() - xdata, ydata = trans.transform_point((x+new_line.x, y+new_line.y)) - + xdata, ydata = trans.transform_point((x + new_line.x, y + new_line.y)) + cur_xlim = ax.get_xlim() - cur_ylim = ax.get_ylim() + cur_ylim = ax.get_ylim() + + scale = ax.get_xscale() + yscale = ax.get_yscale() - scale=ax.get_xscale() - yscale=ax.get_yscale() - if scale == 'linear': - old_min=cur_xlim[0] - old_max=cur_xlim[1] + old_min = cur_xlim[0] + old_max = cur_xlim[1] else: - min_=cur_xlim[0] - max_=cur_xlim[1] - old_min = self.transform_eval(min_,ax.yaxis) - xdata = self.transform_eval(xdata,ax.yaxis) - old_max = self.transform_eval(max_,ax.yaxis) + min_ = cur_xlim[0] + max_ = cur_xlim[1] + old_min = self.transform_eval(min_, ax.yaxis) + xdata = self.transform_eval(xdata, ax.yaxis) + old_max = self.transform_eval(max_, ax.yaxis) if yscale == 'linear': - yold_min=cur_ylim[0] - yold_max=cur_ylim[1] + yold_min = cur_ylim[0] + yold_max = cur_ylim[1] else: - ymin_=cur_ylim[0] - ymax_=cur_ylim[1] - yold_min = self.transform_eval(ymin_,ax.yaxis) - ydata = self.transform_eval(ydata,ax.yaxis) - yold_max = self.transform_eval(ymax_,ax.yaxis) + ymin_ = cur_ylim[0] + ymax_ = cur_ylim[1] + yold_min = self.transform_eval(ymin_, ax.yaxis) + ydata = self.transform_eval(ydata, ax.yaxis) + yold_max = self.transform_eval(ymax_, ax.yaxis) new_width = (old_max - old_min) * scale_factor new_height = (yold_max - yold_min) * scale_factor @@ -1213,44 +1284,51 @@ def apply_zoom(self, scale_factor, ax, anchor=(0, 0),new_line=None): if self.do_zoom_x: if scale == 'linear': - ax.set_xlim([xdata - new_width * (1 - relx), xdata + new_width * (relx)]) + ax.set_xlim([xdata - new_width * (1 - relx), + xdata + new_width * (relx)]) else: new_min = xdata - new_width * (1 - relx) new_max = xdata + new_width * (relx) try: - new_min, new_max = self.inv_transform_eval(new_min,ax.yaxis), self.inv_transform_eval(new_max,ax.yaxis) + new_min, new_max = self.inv_transform_eval( + new_min, ax.yaxis), self.inv_transform_eval( + new_max, ax.yaxis) except OverflowError: # Limit case new_min, new_max = min_, max_ if new_min <= 0. or new_max <= 0.: # Limit case - new_min, new_max = min_, max_ + new_min, new_max = min_, max_ ax.set_xlim([new_min, new_max]) - + if self.do_zoom_y: if yscale == 'linear': - ax.set_ylim([ydata - new_height * (1 - rely), ydata + new_height * (rely)]) + ax.set_ylim([ydata - new_height * (1 - rely), + ydata + new_height * (rely)]) else: new_ymin = ydata - new_height * (1 - rely) new_ymax = ydata + new_height * (rely) try: - new_ymin, new_ymax = self.inv_transform_eval(new_ymin,ax.yaxis), self.inv_transform_eval(new_ymax,ax.yaxis) + new_ymin, new_ymax = self.inv_transform_eval( + new_ymin, ax.yaxis), self.inv_transform_eval( + new_ymax, ax.yaxis) except OverflowError: # Limit case new_ymin, new_ymax = ymin_, ymax_ if new_ymin <= 0. or new_ymax <= 0.: # Limit case - new_ymin, new_ymax = ymin_, ymax_ - ax.set_ylim([new_ymin, new_ymax]) + new_ymin, new_ymax = ymin_, ymax_ + ax.set_ylim([new_ymin, new_ymax]) - if self.fast_draw: - #use blit method + if self.fast_draw: + # use blit method if self.background is None or self.last_line is not None: self.background_patch_copy.set_visible(True) ax.figure.canvas.draw_idle() - ax.figure.canvas.flush_events() - self.background = ax.figure.canvas.copy_from_bbox(ax.figure.bbox) - self.background_patch_copy.set_visible(False) + ax.figure.canvas.flush_events() + self.background = ax.figure.canvas.copy_from_bbox( + ax.figure.bbox) + self.background_patch_copy.set_visible(False) if self.last_line is not None: - self.clear_line_prop() + self.clear_line_prop() ax.figure.canvas.restore_region(self.background) - + for line in ax.lines: ax.draw_artist(line) ax.figure.canvas.blit(ax.bbox) @@ -1259,195 +1337,258 @@ def apply_zoom(self, scale_factor, ax, anchor=(0, 0),new_line=None): self.update_hover() else: ax.figure.canvas.draw_idle() - ax.figure.canvas.flush_events() + ax.figure.canvas.flush_events() def apply_pan(self, ax, event, mode='pan'): """ pan method """ - + trans = ax.transData.inverted() - xdata, ydata = trans.transform_point((event.x-self.pos[0], event.y-self.pos[1])) - xpress, ypress = trans.transform_point((self._last_touch_pos[event][0]-self.pos[0], self._last_touch_pos[event][1]-self.pos[1])) - - scale=ax.get_xscale() - yscale=ax.get_yscale() - + xdata, ydata = trans.transform_point( + (event.x - self.pos[0], event.y - self.pos[1])) + xpress, ypress = trans.transform_point( + (self._last_touch_pos[event][0] - self.pos[0], + self._last_touch_pos[event][1] - self.pos[1])) + + scale = ax.get_xscale() + yscale = ax.get_yscale() + if scale == 'linear': dx = xdata - xpress else: - dx = self.transform_eval(xdata,ax.xaxis) - \ - self.transform_eval(xpress,ax.xaxis) - + dx = self.transform_eval(xdata, ax.xaxis) - \ + self.transform_eval(xpress, ax.xaxis) + if yscale == 'linear': dy = ydata - ypress else: - dy = self.transform_eval(ydata,ax.yaxis) - \ - self.transform_eval(ypress,ax.yaxis) + dy = self.transform_eval(ydata, ax.yaxis) - \ + self.transform_eval(ypress, ax.yaxis) - xleft,xright=self.axes.get_xlim() - ybottom,ytop=self.axes.get_ylim() - - #check inverted data + xleft, xright = self.axes.get_xlim() + ybottom, ytop = self.axes.get_ylim() + + # check inverted data inverted_x = False - if xleft>xright: - inverted_x=True - cur_xlim=(xright,xleft) + if xleft > xright: + inverted_x = True + cur_xlim = (xright, xleft) else: - cur_xlim=(xleft,xright) + cur_xlim = (xleft, xright) inverted_y = False - if ybottom>ytop: - inverted_y=True - cur_ylim=(ytop,ybottom) + if ybottom > ytop: + inverted_y = True + cur_ylim = (ytop, ybottom) else: - cur_ylim=(ybottom,ytop) - - if self.interactive_axis and self.touch_mode=='pan' and not self.first_touch_pan=='pan': - if (ydata < cur_ylim[0] and not inverted_y) or (ydata > cur_ylim[1] and inverted_y): - left_anchor_zone= (cur_xlim[1] - cur_xlim[0])*.2 + cur_xlim[0] - right_anchor_zone= (cur_xlim[1] - cur_xlim[0])*.8 + cur_xlim[0] + cur_ylim = (ybottom, ytop) + + if self.interactive_axis and self.touch_mode == 'pan' and not self.first_touch_pan == 'pan': + if (ydata < cur_ylim[0] and not inverted_y) or ( + ydata > cur_ylim[1] and inverted_y): + left_anchor_zone = ( + cur_xlim[1] - cur_xlim[0]) * .2 + cur_xlim[0] + right_anchor_zone = ( + cur_xlim[1] - cur_xlim[0]) * .8 + cur_xlim[0] if xdata < left_anchor_zone or xdata > right_anchor_zone: mode = 'adjust_x' else: mode = 'pan_x' self.touch_mode = mode elif (xdata < cur_xlim[0] and not inverted_x) or (xdata > cur_xlim[1] and inverted_x): - bottom_anchor_zone= (cur_ylim[1] - cur_ylim[0])*.2 + cur_ylim[0] - top_anchor_zone= (cur_ylim[1] - cur_ylim[0])*.8 + cur_ylim[0] + bottom_anchor_zone = ( + cur_ylim[1] - cur_ylim[0]) * .2 + cur_ylim[0] + top_anchor_zone = ( + cur_ylim[1] - cur_ylim[0]) * .8 + cur_ylim[0] if ydata < bottom_anchor_zone or ydata > top_anchor_zone: mode = 'adjust_y' else: - mode= 'pan_y' + mode = 'pan_y' self.touch_mode = mode else: self.touch_mode = 'pan' - if not mode=='pan_y' and not mode=='adjust_y': - if mode=='adjust_x': + if not mode == 'pan_y' and not mode == 'adjust_y': + if mode == 'adjust_x': if self.anchor_x is None: - midpoint= (cur_xlim[1] + cur_xlim[0])/2 - if xdata>midpoint: - self.anchor_x='left' + midpoint = (cur_xlim[1] + cur_xlim[0]) / 2 + if xdata > midpoint: + self.anchor_x = 'left' else: - self.anchor_x='right' - if self.anchor_x=='left': - if xdata> cur_xlim[0]: + self.anchor_x = 'right' + if self.anchor_x == 'left': + if xdata > cur_xlim[0]: if scale == 'linear': cur_xlim -= dx else: try: - cur_xlim = [self.inv_transform_eval((self.transform_eval(cur_xlim[0],ax.xaxis) - dx),ax.xaxis), - self.inv_transform_eval((self.transform_eval(cur_xlim[1],ax.xaxis) - dx),ax.xaxis)] + cur_xlim = [ + self.inv_transform_eval( + (self.transform_eval( + cur_xlim[0], + ax.xaxis) - dx), + ax.xaxis), + self.inv_transform_eval( + (self.transform_eval( + cur_xlim[1], + ax.xaxis) - dx), + ax.xaxis)] except (ValueError, OverflowError): - cur_xlim = cur_xlim # Keep previous limits + cur_xlim = cur_xlim # Keep previous limits if inverted_x: - ax.set_xlim(cur_xlim[1],None) + ax.set_xlim(cur_xlim[1], None) else: - ax.set_xlim(None,cur_xlim[1]) + ax.set_xlim(None, cur_xlim[1]) else: - if xdata< cur_xlim[1]: + if xdata < cur_xlim[1]: if scale == 'linear': cur_xlim -= dx else: try: - cur_xlim = [self.inv_transform_eval((self.transform_eval(cur_xlim[0],ax.xaxis) - dx),ax.xaxis), - self.inv_transform_eval((self.transform_eval(cur_xlim[1],ax.xaxis) - dx),ax.xaxis)] + cur_xlim = [ + self.inv_transform_eval( + (self.transform_eval( + cur_xlim[0], + ax.xaxis) - dx), + ax.xaxis), + self.inv_transform_eval( + (self.transform_eval( + cur_xlim[1], + ax.xaxis) - dx), + ax.xaxis)] except (ValueError, OverflowError): - cur_xlim = cur_xlim # Keep previous limits + cur_xlim = cur_xlim # Keep previous limits if inverted_x: - ax.set_xlim(None,cur_xlim[0]) + ax.set_xlim(None, cur_xlim[0]) else: - ax.set_xlim(cur_xlim[0],None) + ax.set_xlim(cur_xlim[0], None) else: if scale == 'linear': cur_xlim -= dx else: try: - cur_xlim = [self.inv_transform_eval((self.transform_eval(cur_xlim[0],ax.xaxis) - dx),ax.xaxis), - self.inv_transform_eval((self.transform_eval(cur_xlim[1],ax.xaxis) - dx),ax.xaxis)] + cur_xlim = [ + self.inv_transform_eval( + (self.transform_eval( + cur_xlim[0], + ax.xaxis) - dx), + ax.xaxis), + self.inv_transform_eval( + (self.transform_eval( + cur_xlim[1], + ax.xaxis) - dx), + ax.xaxis)] except (ValueError, OverflowError): - cur_xlim = cur_xlim # Keep previous limits + cur_xlim = cur_xlim # Keep previous limits if inverted_x: - ax.set_xlim(cur_xlim[1],cur_xlim[0]) + ax.set_xlim(cur_xlim[1], cur_xlim[0]) else: ax.set_xlim(cur_xlim) - - if not mode=='pan_x' and not mode=='adjust_x': - if mode=='adjust_y': + + if not mode == 'pan_x' and not mode == 'adjust_x': + if mode == 'adjust_y': if self.anchor_y is None: - midpoint= (cur_ylim[1] + cur_ylim[0])/2 - if ydata>midpoint: - self.anchor_y='top' + midpoint = (cur_ylim[1] + cur_ylim[0]) / 2 + if ydata > midpoint: + self.anchor_y = 'top' else: - self.anchor_y='bottom' - - if self.anchor_y=='top': - if ydata> cur_ylim[0]: + self.anchor_y = 'bottom' + + if self.anchor_y == 'top': + if ydata > cur_ylim[0]: if yscale == 'linear': cur_ylim -= dy - + else: try: - cur_ylim = [self.inv_transform_eval((self.transform_eval(cur_ylim[0],ax.yaxis) - dy),ax.yaxis), - self.inv_transform_eval((self.transform_eval(cur_ylim[1],ax.yaxis) - dy),ax.yaxis)] + cur_ylim = [ + self.inv_transform_eval( + (self.transform_eval( + cur_ylim[0], + ax.yaxis) - dy), + ax.yaxis), + self.inv_transform_eval( + (self.transform_eval( + cur_ylim[1], + ax.yaxis) - dy), + ax.yaxis)] except (ValueError, OverflowError): - cur_ylim = cur_ylim # Keep previous limits - + cur_ylim = cur_ylim # Keep previous limits + if inverted_y: - ax.set_ylim(cur_ylim[1],None) + ax.set_ylim(cur_ylim[1], None) else: - ax.set_ylim(None,cur_ylim[1]) + ax.set_ylim(None, cur_ylim[1]) else: - if ydata< cur_ylim[1]: + if ydata < cur_ylim[1]: if yscale == 'linear': cur_ylim -= dy - + else: try: - cur_ylim = [self.inv_transform_eval((self.transform_eval(cur_ylim[0],ax.yaxis) - dy),ax.yaxis), - self.inv_transform_eval((self.transform_eval(cur_ylim[1],ax.yaxis) - dy),ax.yaxis)] + cur_ylim = [ + self.inv_transform_eval( + (self.transform_eval( + cur_ylim[0], + ax.yaxis) - dy), + ax.yaxis), + self.inv_transform_eval( + (self.transform_eval( + cur_ylim[1], + ax.yaxis) - dy), + ax.yaxis)] except (ValueError, OverflowError): - cur_ylim = cur_ylim # Keep previous limits + cur_ylim = cur_ylim # Keep previous limits if inverted_y: - ax.set_ylim(None,cur_ylim[0]) + ax.set_ylim(None, cur_ylim[0]) else: - ax.set_ylim(cur_ylim[0],None) - else: + ax.set_ylim(cur_ylim[0], None) + else: if yscale == 'linear': - cur_ylim -= dy - + cur_ylim -= dy + else: try: - cur_ylim = [self.inv_transform_eval((self.transform_eval(cur_ylim[0],ax.yaxis) - dy),ax.yaxis), - self.inv_transform_eval((self.transform_eval(cur_ylim[1],ax.yaxis) - dy),ax.yaxis)] + cur_ylim = [ + self.inv_transform_eval( + (self.transform_eval( + cur_ylim[0], + ax.yaxis) - dy), + ax.yaxis), + self.inv_transform_eval( + (self.transform_eval( + cur_ylim[1], + ax.yaxis) - dy), + ax.yaxis)] except (ValueError, OverflowError): - cur_ylim = cur_ylim # Keep previous limits + cur_ylim = cur_ylim # Keep previous limits if inverted_y: - ax.set_ylim(cur_ylim[1],cur_ylim[0]) + ax.set_ylim(cur_ylim[1], cur_ylim[0]) else: ax.set_ylim(cur_ylim) if self.first_touch_pan is None: - self.first_touch_pan=self.touch_mode + self.first_touch_pan = self.touch_mode - if self.fast_draw: - #use blit method + if self.fast_draw: + # use blit method if self.background is None or self.last_line is not None: self.background_patch_copy.set_visible(True) ax.figure.canvas.draw_idle() - ax.figure.canvas.flush_events() - self.background = ax.figure.canvas.copy_from_bbox(ax.figure.bbox) - self.background_patch_copy.set_visible(False) + ax.figure.canvas.flush_events() + self.background = ax.figure.canvas.copy_from_bbox( + ax.figure.bbox) + self.background_patch_copy.set_visible(False) if self.last_line is not None: - self.clear_line_prop() - ax.figure.canvas.restore_region(self.background) - + self.clear_line_prop() + ax.figure.canvas.restore_region(self.background) + for line in ax.lines: ax.draw_artist(line) - + ax.figure.canvas.blit(ax.bbox) - ax.figure.canvas.flush_events() - + ax.figure.canvas.flush_events() + self.update_hover() - + else: ax.figure.canvas.draw_idle() ax.figure.canvas.flush_events() @@ -1456,294 +1597,373 @@ def update_hover(self): """ update hover on fast draw (if exist)""" if self.hover_instance: if self.compare_xdata: - if (self.touch_mode!='cursor' or len(self._touches) > 1) and not self.show_compare_cursor: - self.hover_instance.hover_outside_bound=True - - elif self.show_compare_cursor and self.touch_mode=='cursor': - self.show_compare_cursor=False + if (self.touch_mode != 'cursor' or len(self._touches) + > 1) and not self.show_compare_cursor: + self.hover_instance.hover_outside_bound = True + + elif self.show_compare_cursor and self.touch_mode == 'cursor': + self.show_compare_cursor = False else: - self.hover_instance.hover_outside_bound=True - - #update hover pos if needed - elif self.hover_instance.show_cursor and self.x_hover_data is not None and self.y_hover_data is not None: - xy_pos = self.axes.transData.transform([(self.x_hover_data,self.y_hover_data)]) - self.hover_instance.x_hover_pos=float(xy_pos[0][0]) + self.x - self.hover_instance.y_hover_pos=float(xy_pos[0][1]) + self.y - - self.hover_instance.xmin_line = float(self.axes.bbox.bounds[0]) + self.x - self.hover_instance.xmax_line = float(self.axes.bbox.bounds[0] + self.axes.bbox.bounds[2]) + self.x - self.hover_instance.ymin_line = float(self.axes.bbox.bounds[1]) + self.y - self.hover_instance.ymax_line = float(self.axes.bbox.bounds[1] + self.axes.bbox.bounds[3] )+ self.y - - if self.hover_instance.x_hover_pos>self.x+self.axes.bbox.bounds[2] + self.axes.bbox.bounds[0] or \ - self.hover_instance.x_hover_posself.y+self.axes.bbox.bounds[1] + self.axes.bbox.bounds[3] or \ - self.hover_instance.y_hover_pos self.x + self.axes.bbox.bounds[2] + self.axes.bbox.bounds[0] or \ + self.hover_instance.x_hover_pos < self.x + self.axes.bbox.bounds[0] or \ + self.hover_instance.y_hover_pos > self.y + self.axes.bbox.bounds[1] + self.axes.bbox.bounds[3] or \ + self.hover_instance.y_hover_pos < self.y + self.axes.bbox.bounds[1]: + self.hover_instance.hover_outside_bound = True else: - self.hover_instance.hover_outside_bound=False - - def update_selector(self,*args): + self.hover_instance.hover_outside_bound = False + + def update_selector(self, *args): """ update selector on fast draw (if exist)""" if self.selector: - #update selector pos if needed - if self.selector.resize_wgt.verts and (len(args)!=0 or self.touch_mode!='selector'): + # update selector pos if needed + if self.selector.resize_wgt.verts and ( + len(args) != 0 or self.touch_mode != 'selector'): resize_wgt = self.selector.resize_wgt - if hasattr(resize_wgt,'shapes'): - #lasso widget or ellipse + if hasattr(resize_wgt, 'shapes'): + # lasso widget or ellipse if resize_wgt.shapes: - if hasattr(resize_wgt.shapes[0],'radius_x'): - #ellipse widget - xy_pos = resize_wgt.ax.transData.transform([(resize_wgt.verts[1][0],resize_wgt.verts[1][1])]) - new_pos=resize_wgt.to_widget(*(float(xy_pos[0][0]),float(xy_pos[0][1]))) + if hasattr(resize_wgt.shapes[0], 'radius_x'): + # ellipse widget + xy_pos = resize_wgt.ax.transData.transform( + [(resize_wgt.verts[1][0], resize_wgt.verts[1][1])]) + new_pos = resize_wgt.to_widget( + *(float(xy_pos[0][0]), float(xy_pos[0][1]))) pos0 = new_pos[0] + self.x - pos1 = new_pos[1] + self.y - - xy_pos2 = resize_wgt.ax.transData.transform([(resize_wgt.verts[2][0],resize_wgt.verts[2][1])]) - new_pos2=resize_wgt.to_widget(*(float(xy_pos2[0][0]),float(xy_pos2[0][1]))) + pos1 = new_pos[1] + self.y + + xy_pos2 = resize_wgt.ax.transData.transform( + [(resize_wgt.verts[2][0], resize_wgt.verts[2][1])]) + new_pos2 = resize_wgt.to_widget( + *(float(xy_pos2[0][0]), float(xy_pos2[0][1]))) pos0_2 = new_pos2[0] + self.x - pos1_2 = new_pos2[1] + self.y - - current_shape=resize_wgt.shapes[0] + pos1_2 = new_pos2[1] + self.y + + current_shape = resize_wgt.shapes[0] dataxy1 = current_shape.selection_point_inst.points - dataxy2 = current_shape.selection_point_inst2.points - - #note: the 2 first points are the same in current_shape.points + dataxy2 = current_shape.selection_point_inst2.points + + # note: the 2 first points are the same in + # current_shape.points pos0_old = dataxy1[0] pos1_old = dataxy1[1] - + pos0_2_old = dataxy2[0] - pos1_2_old = dataxy2[1] - - old_length = np.sqrt((pos0_2_old - pos0_old)**2 + (pos1_2_old - pos1_old)**2) - new_length = np.sqrt((pos0_2 - pos0)**2 + (pos1_2 - pos1)**2) - + pos1_2_old = dataxy2[1] + + old_length = np.sqrt( + (pos0_2_old - pos0_old) ** 2 + + (pos1_2_old - pos1_old) ** 2) + new_length = np.sqrt( + (pos0_2 - pos0)**2 + (pos1_2 - pos1)**2) + scale = float(new_length / old_length) - - xy_pos3 = resize_wgt.ax.transData.transform([(resize_wgt.verts[0][0],resize_wgt.verts[0][1])]) - new_pos3=resize_wgt.to_widget(*(float(xy_pos3[0][0]),float(xy_pos3[0][1]))) + + xy_pos3 = resize_wgt.ax.transData.transform( + [(resize_wgt.verts[0][0], resize_wgt.verts[0][1])]) + new_pos3 = resize_wgt.to_widget( + *(float(xy_pos3[0][0]), float(xy_pos3[0][1]))) pos0_c = new_pos3[0] + self.x pos1_c = new_pos3[1] + self.y - + for s in resize_wgt.shapes: s.rescale(scale) - + for s in resize_wgt.shapes: - s.translate(pos=(pos0_c,pos1_c)) - - xmin,xmax,ymin,ymax = resize_wgt.shapes[0].get_min_max() + s.translate(pos=(pos0_c, pos1_c)) + + xmin, xmax, ymin, ymax = resize_wgt.shapes[0].get_min_max( + ) else: - #lasso widget - xy_pos = resize_wgt.ax.transData.transform([(resize_wgt.verts[0][0],resize_wgt.verts[0][1])]) - new_pos=resize_wgt.to_widget(*(float(xy_pos[0][0]),float(xy_pos[0][1]))) + # lasso widget + xy_pos = resize_wgt.ax.transData.transform( + [(resize_wgt.verts[0][0], resize_wgt.verts[0][1])]) + new_pos = resize_wgt.to_widget( + *(float(xy_pos[0][0]), float(xy_pos[0][1]))) pos0 = new_pos[0] + self.x - pos1 = new_pos[1] + self.y - - xy_pos2 = resize_wgt.ax.transData.transform([(resize_wgt.verts[1][0],resize_wgt.verts[1][1])]) - new_pos2=resize_wgt.to_widget(*(float(xy_pos2[0][0]),float(xy_pos2[0][1]))) + pos1 = new_pos[1] + self.y + + xy_pos2 = resize_wgt.ax.transData.transform( + [(resize_wgt.verts[1][0], resize_wgt.verts[1][1])]) + new_pos2 = resize_wgt.to_widget( + *(float(xy_pos2[0][0]), float(xy_pos2[0][1]))) pos0_2 = new_pos2[0] + self.x - pos1_2 = new_pos2[1] + self.y - - current_shape=resize_wgt.shapes[0] - dataxy = np.array(current_shape.points).reshape(-1,2) - - #note: the 2 first points are the same in current_shape.points + pos1_2 = new_pos2[1] + self.y + + current_shape = resize_wgt.shapes[0] + dataxy = np.array( + current_shape.points).reshape(-1, 2) + + # note: the 2 first points are the same in + # current_shape.points pos0_old = dataxy[1][0] pos1_old = dataxy[1][1] - + pos0_2_old = dataxy[2][0] pos1_2_old = dataxy[2][1] - - old_length = np.sqrt((pos0_2_old - pos0_old)**2 + (pos1_2_old - pos1_old)**2) - new_length = np.sqrt((pos0_2 - pos0)**2 + (pos1_2 - pos1)**2) - + + old_length = np.sqrt( + (pos0_2_old - pos0_old) ** 2 + + (pos1_2_old - pos1_old) ** 2) + new_length = np.sqrt( + (pos0_2 - pos0)**2 + (pos1_2 - pos1)**2) + scale = new_length / old_length - + for s in resize_wgt.shapes: s.rescale(scale) - + for s in resize_wgt.shapes: - s.translate(pos=(pos0,pos1)) - + s.translate(pos=(pos0, pos1)) + xmax, ymax = dataxy.max(axis=0) - xmin, ymin = dataxy.min(axis=0) - - if self.collide_point(*resize_wgt.to_window(xmin,ymin)) and \ - self.collide_point(*resize_wgt.to_window(xmax,ymax)): + xmin, ymin = dataxy.min(axis=0) + + if self.collide_point( + * + resize_wgt.to_window( + xmin, + ymin)) and self.collide_point( + * + resize_wgt.to_window( + xmax, + ymax)): resize_wgt.opacity = 1 else: - resize_wgt.opacity = 0 - - elif self.selector.resize_wgt.verts and (len(args)!=0 or self.touch_mode!='selector'): + resize_wgt.opacity = 0 + + elif self.selector.resize_wgt.verts and (len(args) != 0 or self.touch_mode != 'selector'): resize_wgt = self.selector.resize_wgt if not (resize_wgt.size[0] > 1 and resize_wgt.size[1] > 1): return - - #rectangle or spann selector - if hasattr(resize_wgt,'span_orientation'): - #span selector + + # rectangle or spann selector + if hasattr(resize_wgt, 'span_orientation'): + # span selector if resize_wgt.span_orientation == 'vertical': - xy_pos = resize_wgt.ax.transData.transform([(resize_wgt.verts[0][0],resize_wgt.verts[0][1])]) - new_pos=resize_wgt.to_widget(*(float(xy_pos[0][0]),float(xy_pos[0][1]))) + xy_pos = resize_wgt.ax.transData.transform( + [(resize_wgt.verts[0][0], resize_wgt.verts[0][1])]) + new_pos = resize_wgt.to_widget( + *(float(xy_pos[0][0]), float(xy_pos[0][1]))) resize_wgt.pos[0] = new_pos[0] + self.x - - top_bound = float(self.y +resize_wgt.ax.bbox.bounds[3] + resize_wgt.ax.bbox.bounds[1]) - bottom_bound = float(self.y +resize_wgt.ax.bbox.bounds[1]) + + top_bound = float( + self.y + + resize_wgt.ax.bbox.bounds[3] + + resize_wgt.ax.bbox.bounds[1]) + bottom_bound = float( + self.y + resize_wgt.ax.bbox.bounds[1]) resize_wgt.pos[1] = bottom_bound - self.y - - #recalcul size - xy_pos2 = resize_wgt.ax.transData.transform([(resize_wgt.verts[3][0],resize_wgt.verts[3][1])]) - resize_wgt.size[0] = float(xy_pos2[0][0] - xy_pos[0][0]) - resize_wgt.size[1] = top_bound-bottom_bound - + + # recalcul size + xy_pos2 = resize_wgt.ax.transData.transform( + [(resize_wgt.verts[3][0], resize_wgt.verts[3][1])]) + resize_wgt.size[0] = float( + xy_pos2[0][0] - xy_pos[0][0]) + resize_wgt.size[1] = top_bound - bottom_bound + elif resize_wgt.span_orientation == 'horizontal': - xy_pos = resize_wgt.ax.transData.transform([(resize_wgt.verts[0][0],resize_wgt.verts[0][1])]) - new_pos=resize_wgt.to_widget(*(float(xy_pos[0][0]),float(xy_pos[0][1]))) - left_bound = float(self.x +resize_wgt.ax.bbox.bounds[0]) - right_bound = float(self.x +resize_wgt.ax.bbox.bounds[2] +resize_wgt.ax.bbox.bounds[0] ) - - width = right_bound-left_bound - - left_bound,right_bound = resize_wgt.to_widget(left_bound,right_bound) - + xy_pos = resize_wgt.ax.transData.transform( + [(resize_wgt.verts[0][0], resize_wgt.verts[0][1])]) + new_pos = resize_wgt.to_widget( + *(float(xy_pos[0][0]), float(xy_pos[0][1]))) + left_bound = float( + self.x + resize_wgt.ax.bbox.bounds[0]) + right_bound = float( + self.x + resize_wgt.ax.bbox.bounds[2] + + resize_wgt.ax.bbox.bounds[0]) + + width = right_bound - left_bound + + left_bound, right_bound = resize_wgt.to_widget( + left_bound, right_bound) + resize_wgt.pos[0] = left_bound resize_wgt.pos[1] = new_pos[1] + self.y - #recalcul size - xy_pos2 = resize_wgt.ax.transData.transform([(resize_wgt.verts[0][1],resize_wgt.verts[1][1])]) + # recalcul size + xy_pos2 = resize_wgt.ax.transData.transform( + [(resize_wgt.verts[0][1], resize_wgt.verts[1][1])]) resize_wgt.size[0] = width - resize_wgt.size[1] = float(xy_pos2[0][1] - xy_pos[0][1]) - + resize_wgt.size[1] = float( + xy_pos2[0][1] - xy_pos[0][1]) + else: - #rectangle selector - - #update all selector pts - #recalcul pos - xy_pos = resize_wgt.ax.transData.transform([(resize_wgt.verts[0][0],resize_wgt.verts[0][1])]) - new_pos=resize_wgt.to_widget(*(float(xy_pos[0][0]),float(xy_pos[0][1]))) + # rectangle selector + + # update all selector pts + # recalcul pos + xy_pos = resize_wgt.ax.transData.transform( + [(resize_wgt.verts[0][0], resize_wgt.verts[0][1])]) + new_pos = resize_wgt.to_widget( + *(float(xy_pos[0][0]), float(xy_pos[0][1]))) resize_wgt.pos[0] = new_pos[0] + self.x resize_wgt.pos[1] = new_pos[1] + self.y - - #recalcul size - xy_pos2 = resize_wgt.ax.transData.transform([(resize_wgt.verts[2][0],resize_wgt.verts[2][1])]) - resize_wgt.size[0] = float(xy_pos2[0][0] - xy_pos[0][0]) - resize_wgt.size[1] = float(xy_pos2[0][1] - xy_pos[0][1]) - - if self.collide_point(*resize_wgt.to_window(resize_wgt.pos[0],resize_wgt.pos[1])) and \ - self.collide_point(*resize_wgt.to_window(resize_wgt.pos[0] + resize_wgt.size[0],resize_wgt.pos[1]+ resize_wgt.size[1])): + + # recalcul size + xy_pos2 = resize_wgt.ax.transData.transform( + [(resize_wgt.verts[2][0], resize_wgt.verts[2][1])]) + resize_wgt.size[0] = float( + xy_pos2[0][0] - xy_pos[0][0]) + resize_wgt.size[1] = float( + xy_pos2[0][1] - xy_pos[0][1]) + + if self.collide_point( + * + resize_wgt.to_window( + resize_wgt.pos[0], + resize_wgt.pos[1])) and self.collide_point( + * + resize_wgt.to_window( + resize_wgt.pos[0] + + resize_wgt.size[0], + resize_wgt.pos[1] + + resize_wgt.size[1])): resize_wgt.opacity = 1 else: - resize_wgt.opacity = 0 + resize_wgt.opacity = 0 def min_max(self, event): """ manage min/max touch mode """ - ax=self.axes + ax = self.axes xlabelbottom = ax.xaxis._major_tick_kw.get('tick1On') ylabelleft = ax.yaxis._major_tick_kw.get('tick1On') - if xlabelbottom and event.x>self.x +ax.bbox.bounds[0] and \ - event.x self.x + ax.bbox.bounds[0] and \ + event.x < self.x + ax.bbox.bounds[2] + ax.bbox.bounds[0] and \ + event.y < self.y + ax.bbox.bounds[1]: - right_lim = self.x+ax.bbox.bounds[2]+ax.bbox.bounds[0] - left_lim = self.x+ax.bbox.bounds[0] - left_anchor_zone= (right_lim - left_lim)*.2 + left_lim - right_anchor_zone= (right_lim - left_lim)*.8 + left_lim + right_lim = self.x + ax.bbox.bounds[2] + ax.bbox.bounds[0] + left_lim = self.x + ax.bbox.bounds[0] + left_anchor_zone = (right_lim - left_lim) * .2 + left_lim + right_anchor_zone = (right_lim - left_lim) * .8 + left_lim - if event.x < left_anchor_zone or event.x > right_anchor_zone: + if event.x < left_anchor_zone or event.x > right_anchor_zone: if self.text_instance: if not self.text_instance.show_text: - midpoint = ((self.x+ax.bbox.bounds[2] + ax.bbox.bounds[0]) + (self.x+ax.bbox.bounds[0]))/2 + midpoint = ( + (self.x + ax.bbox.bounds[2] + ax.bbox.bounds[0]) + + (self.x + ax.bbox.bounds[0])) / 2 if event.x < midpoint: - anchor='left' - self.text_instance.x_text_pos=float(self.x+ax.bbox.bounds[0]) - self.text_instance.y_text_pos=float(self.y+ax.bbox.bounds[1]) - self.text_instance.text_height + anchor = 'left' + self.text_instance.x_text_pos = float( + self.x + ax.bbox.bounds[0]) + self.text_instance.y_text_pos = float( + self.y + ax.bbox.bounds[1]) - self.text_instance.text_height self.text_instance.offset_text = False else: - anchor='right' - self.text_instance.x_text_pos=float(self.x+ax.bbox.bounds[2] + ax.bbox.bounds[0]) - self.text_instance.y_text_pos=float(self.y+ax.bbox.bounds[1]) - self.text_instance.text_height + anchor = 'right' + self.text_instance.x_text_pos = float( + self.x + ax.bbox.bounds[2] + ax.bbox.bounds[0]) + self.text_instance.y_text_pos = float( + self.y + ax.bbox.bounds[1]) - self.text_instance.text_height self.text_instance.offset_text = True self.text_instance.current_axis = ax - self.text_instance.kind = {'axis':'x','anchor':anchor} + self.text_instance.kind = { + 'axis': 'x', 'anchor': anchor} - self.text_instance.show_text=True + self.text_instance.show_text = True return - elif ylabelleft and event.xself.y + ax.bbox.bounds[1]: + elif ylabelleft and event.x < self.x + ax.bbox.bounds[0] and \ + event.y < self.y + ax.bbox.bounds[1] + ax.bbox.bounds[3] and \ + event.y > self.y + ax.bbox.bounds[1]: - top_lim = self.y+ax.bbox.bounds[3]+ax.bbox.bounds[1] - bottom_lim = self.y+ax.bbox.bounds[1] - bottom_anchor_zone= (top_lim - bottom_lim)*.2 + bottom_lim - top_anchor_zone= (top_lim - bottom_lim)*.8 + bottom_lim + top_lim = self.y + ax.bbox.bounds[3] + ax.bbox.bounds[1] + bottom_lim = self.y + ax.bbox.bounds[1] + bottom_anchor_zone = (top_lim - bottom_lim) * .2 + bottom_lim + top_anchor_zone = (top_lim - bottom_lim) * .8 + bottom_lim if event.y < bottom_anchor_zone or event.y > top_anchor_zone: if self.text_instance: if not self.text_instance.show_text: - midpoint = ((self.y+ax.bbox.bounds[3] + ax.bbox.bounds[1]) + (self.y+ax.bbox.bounds[1]))/2 - if event.y > midpoint: - anchor='top' - self.text_instance.x_text_pos=float(self.x+ax.bbox.bounds[0]) - dp(40) - self.text_instance.y_text_pos=float(self.y+ax.bbox.bounds[1] + ax.bbox.bounds[3]) - self.text_instance.text_height + midpoint = ( + (self.y + ax.bbox.bounds[3] + ax.bbox.bounds[1]) + + (self.y + ax.bbox.bounds[1])) / 2 + if event.y > midpoint: + anchor = 'top' + self.text_instance.x_text_pos = float( + self.x + ax.bbox.bounds[0]) - dp(40) + self.text_instance.y_text_pos = float( + self.y + ax.bbox.bounds[1] + ax.bbox.bounds[3]) - self.text_instance.text_height self.text_instance.offset_text = False else: - anchor='bottom' - self.text_instance.x_text_pos=float(self.x+ax.bbox.bounds[0]) - dp(40) - self.text_instance.y_text_pos=float(self.y+ax.bbox.bounds[1]) - dp(6) + anchor = 'bottom' + self.text_instance.x_text_pos = float( + self.x + ax.bbox.bounds[0]) - dp(40) + self.text_instance.y_text_pos = float( + self.y + ax.bbox.bounds[1]) - dp(6) self.text_instance.offset_text = False self.text_instance.current_axis = ax - self.text_instance.kind = {'axis':'y','anchor':anchor} + self.text_instance.kind = { + 'axis': 'y', 'anchor': anchor} - self.text_instance.show_text=True + self.text_instance.show_text = True return - + def apply_drag_legend(self, ax, event): """ drag legend method """ - + dx = event.x - self._last_touch_pos[event][0] if not self.legend_do_scroll_x: - dx=0 - dy = event.y - self._last_touch_pos[event][1] + dx = 0 + dy = event.y - self._last_touch_pos[event][1] if not self.legend_do_scroll_y: - dy=0 - - legend=None + dy = 0 + + legend = None if self.current_legend: if self.current_legend.legend_instance: legend = self.current_legend.legend_instance else: legend = ax.get_legend() - + if legend is not None: - + bbox = legend.get_window_extent() legend_x = bbox.xmin legend_y = bbox.ymin - - loc_in_canvas = legend_x +dx, legend_y+dy + + loc_in_canvas = legend_x + dx, legend_y + dy loc_in_norm_axes = legend.parent.transAxes.inverted().transform_point(loc_in_canvas) legend._loc = tuple(loc_in_norm_axes) - - #use blit method + + # use blit method if self.background is None: legend.set_visible(False) ax.figure.canvas.draw_idle() - ax.figure.canvas.flush_events() - self.background = ax.figure.canvas.copy_from_bbox(ax.figure.bbox) + ax.figure.canvas.flush_events() + self.background = ax.figure.canvas.copy_from_bbox( + ax.figure.bbox) legend.set_visible(True) if self.last_line is not None: - self.clear_line_prop() - ax.figure.canvas.restore_region(self.background) - + self.clear_line_prop() + ax.figure.canvas.restore_region(self.background) + ax.draw_artist(legend) - + ax.figure.canvas.blit(ax.bbox) - ax.figure.canvas.flush_events() + ax.figure.canvas.flush_events() self.current_legend.update_size() @@ -1755,35 +1975,35 @@ def zoom_factory(self, event, ax, base_scale=1.1): y = newcoord[1] trans = ax.transData.inverted() - xdata, ydata = trans.transform_point((x, y)) + xdata, ydata = trans.transform_point((x, y)) cur_xlim = ax.get_xlim() cur_ylim = ax.get_ylim() - scale=ax.get_xscale() - yscale=ax.get_yscale() - + scale = ax.get_xscale() + yscale = ax.get_yscale() + if scale == 'linear': - old_min=cur_xlim[0] - old_max=cur_xlim[1] + old_min = cur_xlim[0] + old_max = cur_xlim[1] else: - min_=cur_xlim[0] - max_=cur_xlim[1] - old_min = self.transform_eval(min_,ax.yaxis) - xdata = self.transform_eval(xdata,ax.yaxis) - old_max = self.transform_eval(max_,ax.yaxis) + min_ = cur_xlim[0] + max_ = cur_xlim[1] + old_min = self.transform_eval(min_, ax.yaxis) + xdata = self.transform_eval(xdata, ax.yaxis) + old_max = self.transform_eval(max_, ax.yaxis) if yscale == 'linear': - yold_min=cur_ylim[0] - yold_max=cur_ylim[1] + yold_min = cur_ylim[0] + yold_max = cur_ylim[1] else: - ymin_=cur_ylim[0] - ymax_=cur_ylim[1] - yold_min = self.transform_eval(ymin_,ax.yaxis) - ydata = self.transform_eval(ydata,ax.yaxis) - yold_max = self.transform_eval(ymax_,ax.yaxis) + ymin_ = cur_ylim[0] + ymax_ = cur_ylim[1] + yold_min = self.transform_eval(ymin_, ax.yaxis) + ydata = self.transform_eval(ydata, ax.yaxis) + yold_max = self.transform_eval(ymax_, ax.yaxis) if event.button == 'scrolldown': # deal with zoom in @@ -1804,37 +2024,43 @@ def zoom_factory(self, event, ax, base_scale=1.1): if self.do_zoom_x: if scale == 'linear': - ax.set_xlim([xdata - new_width * (1 - relx), xdata + new_width * (relx)]) + ax.set_xlim([xdata - new_width * (1 - relx), + xdata + new_width * (relx)]) else: new_min = xdata - new_width * (1 - relx) new_max = xdata + new_width * (relx) try: - new_min, new_max = self.inv_transform_eval(new_min,ax.yaxis), self.inv_transform_eval(new_max,ax.yaxis) + new_min, new_max = self.inv_transform_eval( + new_min, ax.yaxis), self.inv_transform_eval( + new_max, ax.yaxis) except OverflowError: # Limit case new_min, new_max = min_, max_ if new_min <= 0. or new_max <= 0.: # Limit case - new_min, new_max = min_, max_ + new_min, new_max = min_, max_ ax.set_xlim([new_min, new_max]) - + if self.do_zoom_y: if yscale == 'linear': - ax.set_ylim([ydata - new_height * (1 - rely), ydata + new_height * (rely)]) + ax.set_ylim([ydata - new_height * (1 - rely), + ydata + new_height * (rely)]) else: new_ymin = ydata - new_height * (1 - rely) new_ymax = ydata + new_height * (rely) try: - new_ymin, new_ymax = self.inv_transform_eval(new_ymin,ax.yaxis), self.inv_transform_eval(new_ymax,ax.yaxis) + new_ymin, new_ymax = self.inv_transform_eval( + new_ymin, ax.yaxis), self.inv_transform_eval( + new_ymax, ax.yaxis) except OverflowError: # Limit case new_ymin, new_ymax = ymin_, ymax_ if new_ymin <= 0. or new_ymax <= 0.: # Limit case - new_ymin, new_ymax = ymin_, ymax_ - ax.set_ylim([new_ymin, new_ymax]) + new_ymin, new_ymax = ymin_, ymax_ + ax.set_ylim([new_ymin, new_ymax]) if self.last_line is not None: - self.clear_line_prop() - + self.clear_line_prop() + ax.figure.canvas.draw_idle() - ax.figure.canvas.flush_events() + ax.figure.canvas.flush_events() def _onSize(self, o, size): """ _onsize method """ @@ -1855,9 +2081,9 @@ def _onSize(self, o, size): s = 'resize_event' event = ResizeEvent(s, self.figcanvas) self.figcanvas.callbacks.process(s, event) - self.figcanvas.draw_idle() - - self.figcanvas.draw() + self.figcanvas.draw_idle() + + self.figcanvas.draw() if self.legend_instance: for current_legend in self.legend_instance: current_legend.update_size() @@ -1867,59 +2093,74 @@ def _onSize(self, o, size): self.hover_instance.figx = self.x self.hover_instance.figy = self.y if self.selector and self.selector.resize_wgt.verts: - #update selector next frame to have correct position - Clock.schedule_once(self.update_selector) - + # update selector next frame to have correct position + Clock.schedule_once(self.update_selector) + def update_lim(self): """ update axis lim if zoombox is used""" - ax=self.axes - - self.do_update=False - - #check if inverted axis - xleft,xright=self.axes.get_xlim() - ybottom,ytop=self.axes.get_ylim() - - if xright>xleft: - ax.set_xlim(left=min(self.x0_box,self.x1_box),right=max(self.x0_box,self.x1_box)) + ax = self.axes + + self.do_update = False + + # check if inverted axis + xleft, xright = self.axes.get_xlim() + ybottom, ytop = self.axes.get_ylim() + + if xright > xleft: + ax.set_xlim( + left=min( + self.x0_box, self.x1_box), right=max( + self.x0_box, self.x1_box)) else: - ax.set_xlim(right=min(self.x0_box,self.x1_box),left=max(self.x0_box,self.x1_box)) - if ytop>ybottom: - ax.set_ylim(bottom=min(self.y0_box,self.y1_box),top=max(self.y0_box,self.y1_box)) + ax.set_xlim( + right=min( + self.x0_box, self.x1_box), left=max( + self.x0_box, self.x1_box)) + if ytop > ybottom: + ax.set_ylim( + bottom=min( + self.y0_box, self.y1_box), top=max( + self.y0_box, self.y1_box)) else: - ax.set_ylim(top=min(self.y0_box,self.y1_box),bottom=max(self.y0_box,self.y1_box)) + ax.set_ylim( + top=min( + self.y0_box, self.y1_box), bottom=max( + self.y0_box, self.y1_box)) def reset_box(self): """ reset zoombox and apply zoombox limit if zoombox option if selected""" - if min(abs(self._box_size[0]),abs(self._box_size[1]))>self.minzoom: + if min(abs(self._box_size[0]), abs(self._box_size[1])) > self.minzoom: trans = self.axes.transData.inverted() - self.x0_box, self.y0_box = trans.transform_point((self._box_pos[0]-self.pos[0], self._box_pos[1]-self.pos[1])) - self.x1_box, self.y1_box = trans.transform_point((self._box_size[0]+self._box_pos[0]-self.pos[0], self._box_size[1]+self._box_pos[1]-self.pos[1])) - self.do_update=True - + self.x0_box, self.y0_box = trans.transform_point( + (self._box_pos[0] - self.pos[0], self._box_pos[1] - self.pos[1])) + self.x1_box, self.y1_box = trans.transform_point( + (self._box_size[0] + self._box_pos[0] - self.pos[0], + self._box_size[1] + self._box_pos[1] - self.pos[1])) + self.do_update = True + self._box_size = 0, 0 self._box_pos = 0, 0 - self._alpha_box=0 + self._alpha_box = 0 self._pos_x_rect_hor = 0 self._pos_y_rect_hor = 0 self._pos_x_rect_ver = 0 - self._pos_y_rect_ver = 0 - self._alpha_hor=0 - self._alpha_ver=0 + self._pos_y_rect_ver = 0 + self._alpha_hor = 0 + self._alpha_ver = 0 self.invert_rect_hor = False self.invert_rect_ver = False - + def draw_box(self, event, x0, y0, x1, y1) -> None: """ Draw zoombox method - + Args: event: touch kivy event x0: x coordonnate init x1: y coordonnate of move touch y0: y coordonnate init y1: x coordonnate of move touch - + Return: None """ @@ -1927,105 +2168,107 @@ def draw_box(self, event, x0, y0, x1, y1) -> None: # Kivy coords y0 = pos_y + y0 y1 = pos_y + y1 - - if abs(y1-y0)>dp(5) or abs(x1-x0)>dp(5): - self._alpha_box=0.3 - self._alpha_rect=0 - + + if abs(y1 - y0) > dp(5) or abs(x1 - x0) > dp(5): + self._alpha_box = 0.3 + self._alpha_rect = 0 + trans = self.axes.transData.inverted() - xdata, ydata = trans.transform_point((event.x-pos_x, event.y-pos_y)) - - xleft,xright=self.axes.get_xlim() - ybottom,ytop=self.axes.get_ylim() - - xmax = max(xleft,xright) - xmin = min(xleft,xright) - ymax = max(ybottom,ytop) - ymin = min(ybottom,ytop) - - #check inverted data + xdata, ydata = trans.transform_point( + (event.x - pos_x, event.y - pos_y)) + + xleft, xright = self.axes.get_xlim() + ybottom, ytop = self.axes.get_ylim() + + xmax = max(xleft, xright) + xmin = min(xleft, xright) + ymax = max(ybottom, ytop) + ymin = min(ybottom, ytop) + + # check inverted data inverted_x = False - if xleft>xright: - inverted_x=True + if xleft > xright: + inverted_x = True inverted_y = False - if ybottom>ytop: - inverted_y=True + if ybottom > ytop: + inverted_y = True - x0data, y0data = trans.transform_point((x0-pos_x, y0-pos_y)) - - if x0data>xmax or x0dataymax or y0data xmax or x0data < xmin or y0data > ymax or y0data < ymin: return - if xdatax0 and inverted_x): - x1=x1_min[0][0]+pos_x + if xdata < xmin: + x1_min = self.axes.transData.transform([(xmin, ymin)]) + if (x1 < x0 and not inverted_x) or (x1 > x0 and inverted_x): + x1 = x1_min[0][0] + pos_x else: - x0=x1_min[0][0] + x0 = x1_min[0][0] - if xdata>xmax: - x0_max = self.axes.transData.transform([(xmax,ymin)]) - if (x1>x0 and not inverted_x) or (x1 xmax: + x0_max = self.axes.transData.transform([(xmax, ymin)]) + if (x1 > x0 and not inverted_x) or (x1 < x0 and inverted_x): + x1 = x0_max[0][0] + pos_x else: - x0=x0_max[0][0] + x0 = x0_max[0][0] - if ydatay0 and inverted_y): - y1=y1_min[0][1]+pos_y + if ydata < ymin: + y1_min = self.axes.transData.transform([(xmin, ymin)]) + if (y1 < y0 and not inverted_y) or (y1 > y0 and inverted_y): + y1 = y1_min[0][1] + pos_y else: - y0=y1_min[0][1]+pos_y + y0 = y1_min[0][1] + pos_y - if ydata>ymax: - y0_max = self.axes.transData.transform([(xmax,ymax)]) - if (y1>y0 and not inverted_y) or (y1 ymax: + y0_max = self.axes.transData.transform([(xmax, ymax)]) + if (y1 > y0 and not inverted_y) or (y1 < y0 and inverted_y): + y1 = y0_max[0][1] + pos_y else: - y0=y0_max[0][1]+pos_y - - if abs(x1-x0)self.minzoom: - self.pos_x_rect_ver=x0 - self.pos_y_rect_ver=y0 - - x1_min = self.axes.transData.transform([(xmin,ymin)]) - x0=x1_min[0][0]+pos_x - - x0_max = self.axes.transData.transform([(xmax,ymin)]) - x1=x0_max[0][0]+pos_x - - self._alpha_ver=1 - self._alpha_hor=0 - - elif abs(y1-y0)self.minzoom: - self.pos_x_rect_hor=x0 - self.pos_y_rect_hor=y0 - - y1_min = self.axes.transData.transform([(xmin,ymin)]) - y0=y1_min[0][1]+pos_y - - y0_max = self.axes.transData.transform([(xmax,ymax)]) - y1=y0_max[0][1]+pos_y - - self._alpha_hor=1 - self._alpha_ver=0 - + y0 = y0_max[0][1] + pos_y + + if abs(x1 - x0) < dp(20) and abs(y1 - y0) > self.minzoom: + self.pos_x_rect_ver = x0 + self.pos_y_rect_ver = y0 + + x1_min = self.axes.transData.transform([(xmin, ymin)]) + x0 = x1_min[0][0] + pos_x + + x0_max = self.axes.transData.transform([(xmax, ymin)]) + x1 = x0_max[0][0] + pos_x + + self._alpha_ver = 1 + self._alpha_hor = 0 + + elif abs(y1 - y0) < dp(20) and abs(x1 - x0) > self.minzoom: + self.pos_x_rect_hor = x0 + self.pos_y_rect_hor = y0 + + y1_min = self.axes.transData.transform([(xmin, ymin)]) + y0 = y1_min[0][1] + pos_y + + y0_max = self.axes.transData.transform([(xmax, ymax)]) + y1 = y0_max[0][1] + pos_y + + self._alpha_hor = 1 + self._alpha_ver = 0 + else: - self._alpha_hor=0 - self._alpha_ver=0 + self._alpha_hor = 0 + self._alpha_ver = 0 - if x1>x0: - self.invert_rect_ver=False + if x1 > x0: + self.invert_rect_ver = False else: - self.invert_rect_ver=True - if y1>y0: - self.invert_rect_hor=False + self.invert_rect_ver = True + if y1 > y0: + self.invert_rect_hor = False else: - self.invert_rect_hor=True - + self.invert_rect_hor = True + self._box_pos = x0, y0 self._box_size = x1 - x0, y1 - y0 + class _FigureCanvas(FigureCanvasAgg): """Internal AGG Canvas""" @@ -2050,7 +2293,7 @@ def draw(self): def blit(self, bbox=None): """ Render the figure using agg (blit method). - """ + """ agg = self.get_renderer() w, h = agg.width, agg.height self.widget._bitmap = agg.buffer_rgba() @@ -2058,11 +2301,11 @@ def blit(self, bbox=None): self.widget.bt_h = h self.widget._draw_bitmap() + class FakeEvent: - x:None - y:None - -from kivy.factory import Factory + x: None + y: None + Factory.register('MatplotFigure', MatplotFigure) @@ -2086,42 +2329,42 @@ class FakeEvent: dp(1) if root.invert_rect_ver else -dp(1), \ dp(1) if root.invert_rect_hor else -dp(1), \ dp(1) if root.invert_rect_ver else -dp(1) - - canvas.after: + + canvas.after: #horizontal rectangle left - Color: - rgba:0, 0, 0, self._alpha_hor - Line: - width: dp(1) - rectangle: - (self.pos_x_rect_hor+dp(1) if root.invert_rect_ver \ - else self.pos_x_rect_hor-dp(4),self.pos_y_rect_hor-dp(20), dp(4),dp(40)) + Color: + rgba:0, 0, 0, self._alpha_hor + Line: + width: dp(1) + rectangle: + (self.pos_x_rect_hor+dp(1) if root.invert_rect_ver \ + else self.pos_x_rect_hor-dp(4),self.pos_y_rect_hor-dp(20), dp(4),dp(40)) #horizontal rectangle right - Color: - rgba:0, 0, 0, self._alpha_hor - Line: - width: dp(1) - rectangle: - (self.pos_x_rect_hor-dp(4)+self._box_size[0] if root.invert_rect_ver \ - else self.pos_x_rect_hor+dp(1)+self._box_size[0], self.pos_y_rect_hor-dp(20), dp(4),dp(40)) + Color: + rgba:0, 0, 0, self._alpha_hor + Line: + width: dp(1) + rectangle: + (self.pos_x_rect_hor-dp(4)+self._box_size[0] if root.invert_rect_ver \ + else self.pos_x_rect_hor+dp(1)+self._box_size[0], self.pos_y_rect_hor-dp(20), dp(4),dp(40)) #vertical rectangle bottom - Color: - rgba:0, 0, 0, self._alpha_ver - Line: - width: dp(1) - rectangle: - (self.pos_x_rect_ver-dp(20),self.pos_y_rect_ver+dp(1) if root.invert_rect_hor else \ - self.pos_y_rect_ver-dp(4), dp(40),dp(4)) + Color: + rgba:0, 0, 0, self._alpha_ver + Line: + width: dp(1) + rectangle: + (self.pos_x_rect_ver-dp(20),self.pos_y_rect_ver+dp(1) if root.invert_rect_hor else \ + self.pos_y_rect_ver-dp(4), dp(40),dp(4)) #vertical rectangle top - Color: - rgba:0, 0, 0, self._alpha_ver - Line: - width: dp(1) - rectangle: - (self.pos_x_rect_ver-dp(20),self.pos_y_rect_ver-dp(4)+self._box_size[1] \ + Color: + rgba:0, 0, 0, self._alpha_ver + Line: + width: dp(1) + rectangle: + (self.pos_x_rect_ver-dp(20),self.pos_y_rect_ver-dp(4)+self._box_size[1] \ if root.invert_rect_hor else self.pos_y_rect_ver+dp(1)+self._box_size[1], \ dp(40),dp(4)) ''') diff --git a/kivy_matplotlib_widget/uix/graph_widget_3d.py b/kivy_matplotlib_widget/uix/graph_widget_3d.py index f0090b0..8f64d68 100644 --- a/kivy_matplotlib_widget/uix/graph_widget_3d.py +++ b/kivy_matplotlib_widget/uix/graph_widget_3d.py @@ -1,33 +1,33 @@ """ MatplotFigure3D for matplotlib 3D graph """ +from kivy.factory import Factory +import time +from kivy.uix.floatlayout import FloatLayout +from kivy.uix.scatterlayout import ScatterLayout +from kivy.uix.scatter import Scatter +from matplotlib import cbook +from weakref import WeakKeyDictionary +from mpl_toolkits import mplot3d +import numpy as np +from kivy_matplotlib_widget.tools.cursors import cursor +from kivy.metrics import dp +from matplotlib.colors import to_hex +from matplotlib.backend_bases import MouseEvent +from matplotlib.backend_bases import ResizeEvent +from matplotlib.backends.backend_agg import FigureCanvasAgg +from kivy.vector import Vector +from kivy.uix.boxlayout import BoxLayout +from kivy.properties import ObjectProperty, ListProperty, BooleanProperty, BoundedNumericProperty, AliasProperty, \ + NumericProperty, StringProperty, ColorProperty +from kivy.lang import Builder +from kivy.graphics.transformation import Matrix +from kivy.graphics.texture import Texture import math import matplotlib matplotlib.use('Agg') -from kivy.graphics.texture import Texture -from kivy.graphics.transformation import Matrix -from kivy.lang import Builder -from kivy.properties import ObjectProperty, ListProperty, BooleanProperty, BoundedNumericProperty, AliasProperty, \ - NumericProperty,StringProperty,ColorProperty -from kivy.uix.boxlayout import BoxLayout -from kivy.vector import Vector -from matplotlib.backends.backend_agg import FigureCanvasAgg -from matplotlib.backend_bases import ResizeEvent -from matplotlib.backend_bases import MouseEvent -from matplotlib.colors import to_hex -from kivy.metrics import dp -from kivy_matplotlib_widget.tools.cursors import cursor - -import numpy as np -from mpl_toolkits import mplot3d -from weakref import WeakKeyDictionary -from matplotlib import cbook -from kivy.uix.scatter import Scatter -from kivy.uix.scatterlayout import ScatterLayout -from kivy.uix.floatlayout import FloatLayout -import time def line2d_seg_dist(p1, p2, p0): """distance(s) from line defined by p1 - p2 to point(s) p0 @@ -44,12 +44,13 @@ def line2d_seg_dist(p1, p2, p0): x01 = np.asarray(p0[0]) - p1[0] y01 = np.asarray(p0[1]) - p1[1] - u = (x01*x21 + y01*y21) / (x21**2 + y21**2) + u = (x01 * x21 + y01 * y21) / (x21**2 + y21**2) u = np.clip(u, 0, 1) - d = np.hypot(x01 - u*x21, y01 - u*y21) + d = np.hypot(x01 - u * x21, y01 - u * y21) return d + def get_xyz_mouse_click(event, ax): """ Get coordinates clicked by user @@ -60,8 +61,8 @@ def get_xyz_mouse_click(event, ax): xd, yd = event.xdata, event.ydata p = (xd, yd) edges = ax.tunit_edges() - ldists = [(line2d_seg_dist(p0, p1, p), i) for \ - i, (p0, p1) in enumerate(edges)] + ldists = [(line2d_seg_dist(p0, p1, p), i) for + i, (p0, p1) in enumerate(edges)] ldists.sort() # nearest edge @@ -72,21 +73,23 @@ def get_xyz_mouse_click(event, ax): # scale the z value to match x0, y0, z0 = p0 x1, y1, z1 = p1 - d0 = np.hypot(x0-xd, y0-yd) - d1 = np.hypot(x1-xd, y1-yd) - dt = d0+d1 - z = d1/dt * z0 + d0/dt * z1 + d0 = np.hypot(x0 - xd, y0 - yd) + d1 = np.hypot(x1 - xd, y1 - yd) + dt = d0 + d1 + z = d1 / dt * z0 + d0 / dt * z1 x, y, z = mplot3d.proj3d.inv_transform(xd, yd, z, ax.M) return x, y, z + class MatplotlibEvent: - x:None - y:None - pickradius:None - inaxes:None - projection:False - compare_xdata:False + x: None + y: None + pickradius: None + inaxes: None + projection: False + compare_xdata: False + class MatplotFigure3D(ScatterLayout): """Widget to show a matplotlib figure in kivy. @@ -94,7 +97,7 @@ class MatplotFigure3D(ScatterLayout): the rgba data is obtained and blitted into a kivy texture. """ - cursor_cls=None + cursor_cls = None pickradius = NumericProperty(dp(50)) projection = BooleanProperty(False) myevent = MatplotlibEvent() @@ -102,9 +105,9 @@ class MatplotFigure3D(ScatterLayout): _box_pos = ListProperty([0, 0]) _box_size = ListProperty([0, 0]) _img_texture = ObjectProperty(None) - _alpha_box = NumericProperty(0) + _alpha_box = NumericProperty(0) _bitmap = None - do_update=False + do_update = False figcanvas = ObjectProperty(None) translation_touches = BoundedNumericProperty(1, min=1) do_scale = BooleanProperty(True) @@ -113,30 +116,30 @@ class MatplotFigure3D(ScatterLayout): transform = ObjectProperty(Matrix()) _alpha_hor = NumericProperty(0) _alpha_ver = NumericProperty(0) - pos_x_rect_hor=NumericProperty(0) - pos_y_rect_hor=NumericProperty(0) - pos_x_rect_ver=NumericProperty(0) - pos_y_rect_ver=NumericProperty(0) + pos_x_rect_hor = NumericProperty(0) + pos_y_rect_hor = NumericProperty(0) + pos_x_rect_ver = NumericProperty(0) + pos_y_rect_ver = NumericProperty(0) move_lock = False scale_lock_left = False scale_lock_right = False scale_lock_top = False scale_lock_bottom = False center_graph = ListProperty([0, 0]) - cursor_label=None - max_hover_rate = NumericProperty(None,allownone=True) - last_hover_time=None - cursor_alpha = NumericProperty(0.0) - - default_dpi=None - crop_factor = NumericProperty(1.0) - cursor_size=NumericProperty("10dp") - matplot_figure_layout = ObjectProperty(None) - disable_double_tap = BooleanProperty(False) + cursor_label = None + max_hover_rate = NumericProperty(None, allownone=True) + last_hover_time = None + cursor_alpha = NumericProperty(0.0) + + default_dpi = None + crop_factor = NumericProperty(1.0) + cursor_size = NumericProperty("10dp") + matplot_figure_layout = ObjectProperty(None) + disable_double_tap = BooleanProperty(False) def on_figure(self, obj, value): if self.default_dpi is None: - #get default matplotlib figure dpi + # get default matplotlib figure dpi self.default_dpi = self.figure.get_dpi() self.figcanvas = _FigureCanvas(self.figure, self) self.figcanvas._isDrawn = False @@ -148,69 +151,68 @@ def on_figure(self, obj, value): # Texture self._img_texture = Texture.create(size=(w, h)) - + if self.matplot_figure_layout: - - self.cursor = self.figure.axes[0].scatter([0], [0], [0], marker="s",color="k", s=100,alpha=0) - self.cursor_cls = cursor(self.figure,remove_artists=[self.cursor]) + + self.cursor = self.figure.axes[0].scatter( + [0], [0], [0], marker="s", color="k", s=100, alpha=0) + self.cursor_cls = cursor(self.figure, remove_artists=[self.cursor]) self.cursor_label = CursorInfo() - self.cursor_label.figure=self.figure + self.cursor_label.figure = self.figure self.cursor_label.xmin_line = self.x + dp(20) - self.cursor_label.ymax_line = self.y + h - dp(48) + self.cursor_label.ymax_line = self.y + h - dp(48) self.matplot_figure_layout.add_widget(self.cursor_label) - def __init__(self, **kwargs): super(MatplotFigure3D, self).__init__(**kwargs) - - #figure info + + # figure info self.figure = None self.axes = None self.xmin = None self.xmax = None self.ymin = None self.ymax = None - - #option + + # option self.zoompan = None - self.fast_draw=True - self.draw_left_spline=False #available only when fast_draw is True - self.touch_mode='rotate' + self.fast_draw = True + self.draw_left_spline = False # available only when fast_draw is True + self.touch_mode = 'rotate' self.hover_on = False - self.xsorted = True #to manage x sorted data (if numpy is used) + self.xsorted = True # to manage x sorted data (if numpy is used) - #zoom box coordonnate + # zoom box coordonnate self.x0_box = None self.y0_box = None self.x1_box = None self.y1_box = None - if hasattr(cbook,'_Stack'): - #manage matplotlib version with no Stack (replace by _Stack) + if hasattr(cbook, '_Stack'): + # manage matplotlib version with no Stack (replace by _Stack) self._nav_stack = cbook._Stack() else: - self._nav_stack = cbook.Stack() - self.set_history_buttons() - - #clear touches on touch up + self._nav_stack = cbook.Stack() + self.set_history_buttons() + + # clear touches on touch up self._touches = [] self._last_touch_pos = {} - + self.bind(size=self._onSize) def on_crop_factor(self, obj, value): # return if self.figure: if self.default_dpi is None: - #get default matplotlib figure dpi + # get default matplotlib figure dpi self.default_dpi = self.figure.get_dpi() - + self.figure.dpi = self.default_dpi / value - + self.figcanvas.draw() - def _get_scale(self): p1 = Vector(*self.to_parent(0, 0)) p2 = Vector(*self.to_parent(1, 0)) @@ -230,19 +232,28 @@ def _get_scale(self): def _set_scale(self, scale): rescale = scale * 1.0 / self.scale - - #update center + + # update center new_center = self.parent.to_local(*self.parent.center) - self.apply_transform(Matrix().scale(rescale, rescale, rescale), - post_multiply=True, - anchor=(new_center[0]/2/scale,new_center[1]/2/scale)) + self.apply_transform( + Matrix().scale( + rescale, + rescale, + rescale), + post_multiply=True, + anchor=( + new_center[0] / + 2 / + scale, + new_center[1] / + 2 / + scale)) scale = AliasProperty(_get_scale, _set_scale, bind=('x', 'y', 'transform')) - def set_custom_label_widget(self,custom_widget): + def set_custom_label_widget(self, custom_widget): self.cursor_label = custom_widget - - + def home(self, *args): """ Restore the original view. @@ -251,37 +262,37 @@ def home(self, *args): often get passed additional parameters, this method accepts arbitrary parameters, but does not use them. """ - ax=self.figure.axes[0] + ax = self.figure.axes[0] ax.view_init(ax.initial_elev, ax.initial_azim, ax.initial_roll) self._nav_stack.home() self.set_history_buttons() self._update_view() self.figure.axes[0].autoscale() - if self.crop_factor!=1.0: - self.crop_factor=1.0 + if self.crop_factor != 1.0: + self.crop_factor = 1.0 else: self.figcanvas.draw() - self.scale=1.0 - self.pos=(0,0) + self.scale = 1.0 + self.pos = (0, 0) if self.cursor_alpha == 1.0: self.recalcul_cursor() - + def push_current(self): - """Push the current view limits and position onto the stack.""" - self._nav_stack.push( - WeakKeyDictionary( - {ax: (ax._get_view(), - # Store both the original and modified positions. - (ax.get_position(True).frozen(), - ax.get_position().frozen())) - for ax in self.figure.axes})) - self.set_history_buttons() + """Push the current view limits and position onto the stack.""" + self._nav_stack.push( + WeakKeyDictionary( + {ax: (ax._get_view(), + # Store both the original and modified positions. + (ax.get_position(True).frozen(), + ax.get_position().frozen())) + for ax in self.figure.axes})) + self.set_history_buttons() def update(self): """Reset the Axes stack.""" self._nav_stack.clear() self.set_history_buttons() - + def _update_view(self): """ Update the viewlim and position from the view and position stack for @@ -301,18 +312,17 @@ def _update_view(self): self.figcanvas.draw() def set_history_buttons(self): - """Enable or disable the back/forward button.""" - + """Enable or disable the back/forward button.""" def reset_touch(self) -> None: """ reset touch - + Return: None """ self._touches = [] self._last_touch_pos = {} - + def transform_with_touch(self, touch): changed = False @@ -321,32 +331,34 @@ def transform_with_touch(self, touch): # _last_touch_pos has last pos in correct parent space, # just like incoming touch dx = (touch.x - self._last_touch_pos[touch][0]) \ - * self.do_translation_x - dy = (touch.y - self._last_touch_pos[touch][1]) \ - * self.do_translation_y + * self.do_translation_x + dy = (touch.y - self._last_touch_pos[touch][1]) \ + * self.do_translation_y dx = dx / self.translation_touches dy = dy / self.translation_touches - - scale_x = self.bbox[1][0]/self.size[0] - scale_y = self.bbox[1][1]/self.size[1] - if scale_x>1.0: + + scale_x = self.bbox[1][0] / self.size[0] + scale_y = self.bbox[1][1] / self.size[1] + if scale_x > 1.0: scale_x = scale_x**0.5 scale_y = scale_y**0.5 - elif scale_y<1.0: + elif scale_y < 1.0: scale_x = scale_x**0.5 scale_y = scale_y**0.5 - - - self.apply_transform(Matrix().translate(dx/2/scale_x, dy/2/scale_y, 0)) + self.apply_transform( + Matrix().translate( + dx / 2 / scale_x, + dy / 2 / scale_y, + 0)) changed = True - + if self.cursor_alpha == 1.0: self.recalcul_cursor() - - if len(self._touches) == 1:# + + if len(self._touches) == 1: return changed - + # we have more than one touch... list of last known pos points = [Vector(self._last_touch_pos[t]) for t in self._touches if t is not touch] @@ -378,28 +390,26 @@ def transform_with_touch(self, touch): scale = self.scale_min / self.scale elif new_scale > self.scale_max: scale = self.scale_max / self.scale - - if scale<1.0: - ## zoom in + + if scale < 1.0: + # zoom in if self.scale < 5: - self.scale = self.scale /scale - - self.crop_factor = 1/self.scale + self.scale = self.scale / scale + + self.crop_factor = 1 / self.scale else: - ## zoom out + # zoom out if self.scale > 0.6: - self.scale = self.scale /scale - - self.crop_factor = 1/self.scale - + self.scale = self.scale / scale + + self.crop_factor = 1 / self.scale + if self.cursor_alpha == 1.0: self.recalcul_cursor() changed = True return changed - - def _draw_bitmap(self): """ draw bitmap method. based on kivy scatter method""" @@ -417,15 +427,15 @@ def transform_with_touch2(self, event): changed = False if len(self._touches) == self.translation_touches: - if self.touch_mode=='pan': - ax=self.figure.axes[0] + if self.touch_mode == 'pan': + ax = self.figure.axes[0] # Start the pan event with pixel coordinates px, py = ax.transData.transform([ax._sx, ax._sy]) ax.start_pan(px, py, 2) # pan view (takes pixel coordinate input) ax.drag_pan(2, None, event.x, event.y) ax.end_pan() - + # Store the event coordinates for the next time through. # ax._sx, ax._sy = x, y trans = ax.transData.inverted() @@ -436,13 +446,13 @@ def transform_with_touch2(self, event): self.figcanvas.draw() if self.cursor_alpha == 1.0: self.recalcul_cursor() - + changed = True - #note: avoid zoom in/out on touch mode zoombox - if len(self._touches) == 1:# + # note: avoid zoom in/out on touch mode zoombox + if len(self._touches) == 1: return changed - + # we have more than one touch... list of last known pos points = [Vector(self._last_touch_pos[t]) for t in self._touches if t is not event] @@ -474,8 +484,8 @@ def transform_with_touch2(self, event): scale = self.scale_min / self.scale elif new_scale > self.scale_max: scale = self.scale_max / self.scale - - ax=self.figure.axes[0] + + ax = self.figure.axes[0] ax._scale_axis_limits(scale, scale, scale) self.figcanvas.draw() if self.cursor_alpha == 1.0: @@ -489,41 +499,42 @@ def on_touch_down(self, touch): x, y = touch.x, touch.y self.prev_x = touch.x self.prev_y = touch.y - + if self.collide_point(x, y): # real_x, real_y = x - self.pos[0], y - self.pos[1] # self.figcanvas.button_press_event(x, real_y, 1, guiEvent=event) if touch.is_mouse_scrolling: - + if self.touch_mode == 'figure_zoom_pan': - + if touch.button == 'scrolldown': - ## zoom in + # zoom in if self.scale < 5: self.scale = self.scale * 1.1 - - self.crop_factor = 1/self.scale - + + self.crop_factor = 1 / self.scale + elif touch.button == 'scrollup': - ## zoom out + # zoom out if self.scale > 0.6: self.scale = self.scale * 0.8 - - self.crop_factor = 1/self.scale + + self.crop_factor = 1 / self.scale if self.cursor_alpha == 1.0: self.recalcul_cursor() else: if touch.button == 'scrollup': - ## zoom in + # zoom in scale_factor = 1.1 - + elif touch.button == 'scrolldown': - ## zoom out + # zoom out scale_factor = 0.8 - ax=self.figure.axes[0] - ax._scale_axis_limits(scale_factor, scale_factor, scale_factor) + ax = self.figure.axes[0] + ax._scale_axis_limits( + scale_factor, scale_factor, scale_factor) self.figcanvas.draw() if self.cursor_alpha == 1.0: self.recalcul_cursor() @@ -532,10 +543,10 @@ def on_touch_down(self, touch): if not self.disable_double_tap: self.home() return True - + elif self.touch_mode == 'pan': - ax=self.figure.axes[0] - #transform kivy x,y touch event to x,y data + ax = self.figure.axes[0] + # transform kivy x,y touch event to x,y data trans = ax.transData.inverted() xdata, ydata = trans.transform_point((touch.x, touch.y)) ax._sx, ax._sy = xdata, ydata @@ -544,14 +555,20 @@ def on_touch_down(self, touch): # real_x, real_y = x - self.pos[0], y - self.pos[1] x = (touch.x) / self.crop_factor real_y = (touch.y) / self.crop_factor - + self.figcanvas._button = 1 s = 'button_press_event' - mouseevent = MouseEvent(s, self.figcanvas, x, real_y, 1, self.figcanvas._key, - dblclick=False, guiEvent=touch) - self.figcanvas.callbacks.process(s, mouseevent) - - + mouseevent = MouseEvent( + s, + self.figcanvas, + x, + real_y, + 1, + self.figcanvas._key, + dblclick=False, + guiEvent=touch) + self.figcanvas.callbacks.process(s, mouseevent) + # if the touch isnt on the widget we do nothing if not self.do_collide_after_children: if not self.collide_point(x, y): @@ -592,8 +609,8 @@ def on_touch_down(self, touch): self._last_touch_pos[touch] = (touch.pos[0], touch.pos[1]) - # return True - return False + # return True + return False def on_touch_move(self, event): """ Mouse move while pressed """ @@ -610,10 +627,10 @@ def on_touch_move(self, event): if not self.disable_double_tap: self.home() return True - + elif self.touch_mode == 'figure_zoom_pan': - - touch =event + + touch = event # let the child widgets handle the event if they want if self.collide_point(x, y) and not touch.grab_current == self: touch.push() @@ -622,22 +639,21 @@ def on_touch_move(self, event): touch.pop() return True touch.pop() - + # rotate/scale/translate if touch in self._touches and touch.grab_current == self: self.transform_with_touch(touch) self._last_touch_pos[touch] = (touch.pos[0], touch.pos[1]) - + # stop propagating if its within our bounds if self.collide_point(x, y): - return True - - + return True + elif self.touch_mode == 'cursor': if self.cursor_label is None: return - #trick to improve app fps (use for big data) + # trick to improve app fps (use for big data) if self.max_hover_rate is not None: if self.last_hover_time is None: @@ -645,68 +661,75 @@ def on_touch_move(self, event): elif time.time() - self.last_hover_time < self.max_hover_rate: return else: - self.last_hover_time=None + self.last_hover_time = None - self.myevent.x=event.x - self.pos[0] - self.bbox[0][0] - self.myevent.y=event.y - self.pos[1]- self.bbox[0][1] + self.myevent.x = event.x - self.pos[0] - self.bbox[0][0] + self.myevent.y = event.y - self.pos[1] - self.bbox[0][1] if self.scale != 1.0: - self.myevent.x=event.x/self.scale - self.pos[0]/self.scale - self.bbox[0][0] - self.myevent.y=event.y/self.scale - self.pos[1]/self.scale- self.bbox[0][1] - - self.myevent.x=self.myevent.x / self.crop_factor - self.myevent.y=self.myevent.y / self.crop_factor - self.myevent.inaxes=self.figure.canvas.inaxes((self.myevent.x, - self.myevent.y)) - self.myevent.pickradius=self.pickradius - self.myevent.projection=self.projection - self.myevent.compare_xdata=False - #find closest artist from kivy event - sel = self.cursor_cls.xy_event(self.myevent) - + self.myevent.x = event.x / self.scale - \ + self.pos[0] / self.scale - self.bbox[0][0] + self.myevent.y = event.y / self.scale - \ + self.pos[1] / self.scale - self.bbox[0][1] + + self.myevent.x = self.myevent.x / self.crop_factor + self.myevent.y = self.myevent.y / self.crop_factor + self.myevent.inaxes = self.figure.canvas.inaxes( + (self.myevent.x, self.myevent.y)) + self.myevent.pickradius = self.pickradius + self.myevent.projection = self.projection + self.myevent.compare_xdata = False + # find closest artist from kivy event + sel = self.cursor_cls.xy_event(self.myevent) + if not sel: self.cursor_alpha = 0.0 else: - - self.myevent.xdata = sel.target[0] - self.myevent.ydata = sel.target[1] + + self.myevent.xdata = sel.target[0] + self.myevent.ydata = sel.target[1] try: - if hasattr(sel.artist,'get_data_3d'): #3d line + if hasattr(sel.artist, 'get_data_3d'): # 3d line result = [sel.artist.get_data_3d()[0][sel.index], sel.artist.get_data_3d()[1][sel.index], - sel.artist.get_data_3d()[2][sel.index]] - - elif hasattr(sel.artist,'_offsets3d'): #scatter + sel.artist.get_data_3d()[2][sel.index]] + + elif hasattr(sel.artist, '_offsets3d'): # scatter result = [sel.artist._offsets3d[0].data[sel.index], sel.artist._offsets3d[1].data[sel.index], sel.artist._offsets3d[2][sel.index]] - else: #other z is a projectio. so it can not reflrct a real value - result = get_xyz_mouse_click(self.myevent, self.figure.axes[0]) - except: - result = get_xyz_mouse_click(self.myevent, self.figure.axes[0]) + else: # other z is a projectio. so it can not reflrct a real value + result = get_xyz_mouse_click( + self.myevent, self.figure.axes[0]) + except BaseException: + result = get_xyz_mouse_click( + self.myevent, self.figure.axes[0]) self.cursor_alpha = 1.0 - - ax=self.figure.axes[0] - - self.last_x=result[0] - self.last_y=result[1] - self.last_z=result[2] - - x = ax.xaxis.get_major_formatter().format_data_short(result[0]) - y = ax.yaxis.get_major_formatter().format_data_short(result[1]) - z = ax.yaxis.get_major_formatter().format_data_short(result[2]) + + ax = self.figure.axes[0] + + self.last_x = result[0] + self.last_y = result[1] + self.last_z = result[2] + + x = ax.xaxis.get_major_formatter( + ).format_data_short(result[0]) + y = ax.yaxis.get_major_formatter( + ).format_data_short(result[1]) + z = ax.yaxis.get_major_formatter( + ).format_data_short(result[2]) # self.text.set_text(f"x={x}, y={y}, z={z}") self.cursor_label.label_x_value = f"{x}" self.cursor_label.label_y_value = f"{y}" self.cursor_label.label_z_value = f"{z}" - + self.recalcul_cursor() elif self.touch_mode == 'pan': - touch =event + touch = event # let the child widgets handle the event if they want if self.collide_point(x, y) and not touch.grab_current == self: touch.push() @@ -715,42 +738,57 @@ def on_touch_move(self, event): touch.pop() return True touch.pop() - + # rotate/scale/translate if touch in self._touches and touch.grab_current == self: self.transform_with_touch2(touch) self._last_touch_pos[touch] = (touch.pos[0], touch.pos[1]) - + # stop propagating if its within our bounds if self.collide_point(x, y): - return True + return True elif self.touch_mode == 'zoom': - ax=self.figure.axes[0] + ax = self.figure.axes[0] else: x = (event.x) / self.crop_factor real_y = (event.y - self.pos[1]) / self.crop_factor y = (event.y) / self.crop_factor - + self.figcanvas._lastx, self.figcanvas._lasty = x, real_y - + s = 'motion_notify_event' - event = MouseEvent(s, self.figcanvas, x, y, self.figcanvas._button, self.figcanvas._key, - guiEvent=None) + event = MouseEvent( + s, + self.figcanvas, + x, + y, + self.figcanvas._button, + self.figcanvas._key, + guiEvent=None) event.inaxes = self.figure.axes[0] self.figcanvas.callbacks.process(s, event) - + if self.cursor_alpha == 1.0: self.recalcul_cursor() def recalcul_cursor(self): - ax=self.figure.axes[0] - x, y, z = mplot3d.proj3d.transform(self.last_x, self.last_y, self.last_z, ax.M) - xy_pos = ax.transData.transform([(x,y)]) - self.center_graph = (float(xy_pos[0][0])*self.crop_factor + self.x ,float(xy_pos[0][1])*self.crop_factor + self.y) - + ax = self.figure.axes[0] + x, y, z = mplot3d.proj3d.transform( + self.last_x, self.last_y, self.last_z, ax.M) + xy_pos = ax.transData.transform([(x, y)]) + self.center_graph = ( + float( + xy_pos[0][0]) * + self.crop_factor + + self.x, + float( + xy_pos[0][1]) * + self.crop_factor + + self.y) + def _onSize(self, o, size): """ _onsize method """ if self.figure is None: @@ -765,20 +803,24 @@ def _onSize(self, o, size): dpival = self.figure.dpi winch = self._width / dpival hinch = self._height / dpival - self.figure.set_size_inches(winch / self.crop_factor, hinch / self.crop_factor) - + self.figure.set_size_inches( + winch / self.crop_factor, + hinch / self.crop_factor) + s = 'resize_event' event = ResizeEvent(s, self.figcanvas) self.figcanvas.callbacks.process(s, event) - self.figcanvas.draw_idle() - - self.figcanvas.draw() - + self.figcanvas.draw_idle() + + self.figcanvas.draw() + l, b, w, h = self.figure.bbox.bounds if self.cursor_label and self.figure and self.cursor_label is not None: self.cursor_label.xmin_line = self.parent.x + dp(20) - self.cursor_label.ymax_line = self.parent.y + int(math.ceil(h))*self.crop_factor - dp(48) + self.cursor_label.ymax_line = self.parent.y + \ + int(math.ceil(h)) * self.crop_factor - dp(48) + class _FigureCanvas(FigureCanvasAgg): """Internal AGG Canvas""" @@ -795,7 +837,6 @@ def draw(self): agg = self.get_renderer() w, h = agg.width, agg.height self._isDrawn = True - self.widget.bt_w = w self.widget.bt_h = h @@ -805,32 +846,33 @@ def draw(self): def blit(self, bbox=None): """ Render the figure using agg (blit method). - """ + """ agg = self.get_renderer() w, h = agg.width, agg.height self.widget._bitmap = agg.buffer_rgba() self.widget.bt_w = w self.widget.bt_h = h self.widget._draw_bitmap() - + + class MatplotFigure3DLayout(BoxLayout): - """This handle figure zoom and pan inside the widget. + """This handle figure zoom and pan inside the widget. Cursor label is also not rescale when zooming """ pickradius = NumericProperty(dp(50)) projection = BooleanProperty(False) figure_wgt = ObjectProperty(None) - max_hover_rate = NumericProperty(None,allownone=True) - crop_factor = NumericProperty(1.0) - cursor_size=NumericProperty("10dp") - figure_background = ColorProperty([1,1,1,1]) + max_hover_rate = NumericProperty(None, allownone=True) + crop_factor = NumericProperty(1.0) + cursor_size = NumericProperty("10dp") + figure_background = ColorProperty([1, 1, 1, 1]) def __init__(self, **kwargs): """ init class """ - super().__init__(**kwargs) - - def set_figure_background(self,color): + super().__init__(**kwargs) + + def set_figure_background(self, color): """ Parameters @@ -842,36 +884,36 @@ def set_figure_background(self,color): None. """ - self.figure_background=color + self.figure_background = color self.figure_wgt.figure.axes[0].set_facecolor(to_hex(color)) self.figure_wgt.figure.set_facecolor(to_hex(color)) self.figure_wgt.figcanvas.draw() - + + class CursorInfo(FloatLayout): figure_wgt = ObjectProperty(None) xmin_line = NumericProperty(1) - ymax_line = NumericProperty(1) + ymax_line = NumericProperty(1) show_cursor = BooleanProperty(False) - label_x = StringProperty('x') - label_y = StringProperty('y') - label_z = StringProperty('z') - label_x_value = StringProperty('') - label_y_value = StringProperty('') - label_z_value = StringProperty('') - text_color=ColorProperty([0,0,0,1]) - text_font=StringProperty("Roboto") + label_x = StringProperty('x') + label_y = StringProperty('y') + label_z = StringProperty('z') + label_x_value = StringProperty('') + label_y_value = StringProperty('') + label_z_value = StringProperty('') + text_color = ColorProperty([0, 0, 0, 1]) + text_font = StringProperty("Roboto") text_size = NumericProperty(dp(14)) - background_color=ColorProperty([1,1,1,1]) - + background_color = ColorProperty([1, 1, 1, 1]) + def __init__(self, **kwargs): """ init class """ - super().__init__(**kwargs) + super().__init__(**kwargs) -from kivy.factory import Factory Factory.register('MatplotFigure3D', MatplotFigure3D) -Builder.load_string(''' +Builder.load_string(''' figure_wgt : figure_wgt.__self__ canvas.before: @@ -880,21 +922,21 @@ def __init__(self, **kwargs): Rectangle: pos: self.pos size: self.size - + ScreenManager: Screen: size: self.manager.size - pos: self.manager.pos + pos: self.manager.pos BoxLayout: MatplotFigure3D: id:figure_wgt pickradius : root.pickradius projection : root.projection - max_hover_rate : root.max_hover_rate + max_hover_rate : root.max_hover_rate crop_factor : root.crop_factor - cursor_size : root.cursor_size + cursor_size : root.cursor_size matplot_figure_layout:root - + canvas.before: Color: @@ -903,53 +945,53 @@ def __init__(self, **kwargs): pos: self.pos size: self.size texture: self._img_texture - + Color: rgba: (0, 0, 0, self.cursor_alpha) Rectangle: pos: (self.center_graph[0]-root.cursor_size/2,self.center_graph[1]-root.cursor_size/2) - size: root.cursor_size,root.cursor_size - + size: root.cursor_size,root.cursor_size + - custom_color: [0,0,0,1] + custom_color: [0,0,0,1] size_hint: None, None height: dp(0.01) width: dp(0.01) - + BoxLayout: id:main_box x:root.xmin_line - y: root.ymax_line + dp(4) + y: root.ymax_line + dp(4) size_hint: None, None height: self.minimum_height - width: + width: self.minimum_width + dp(12) if root.show_cursor \ else dp(0.0001) orientation:'vertical' padding: 0,dp(4),0,dp(4) - - + + BoxLayout: size_hint:None,None width:label.texture_size[0] + dp(12) height:label.texture_size[1] + dp(12) - canvas.before: + canvas.before: Color: rgb: root.background_color a:0.8 Rectangle: pos: self.pos - size: self.size + size: self.size Label: id:label - text: + text: root.label_x + ': ' + root.label_x_value + \ ' ' + root.label_y + ': ' + root.label_y_value + \ ' ' + root.label_z + ': ' + root.label_z_value font_size:root.text_size font_name : root.text_font color: root.text_color - + ''') diff --git a/kivy_matplotlib_widget/uix/graph_widget_crop_factor.py b/kivy_matplotlib_widget/uix/graph_widget_crop_factor.py index ef7dc27..6e54ab8 100644 --- a/kivy_matplotlib_widget/uix/graph_widget_crop_factor.py +++ b/kivy_matplotlib_widget/uix/graph_widget_crop_factor.py @@ -1,4 +1,17 @@ """ MatplotFigure is based on https://github.com/mp-007/kivy_matplotlib_widget """ +import numpy as np +from kivy.metrics import dp +from weakref import WeakKeyDictionary +from matplotlib.backend_bases import ResizeEvent +from matplotlib import cbook +from matplotlib.backends.backend_agg import FigureCanvasAgg +from kivy.vector import Vector +from kivy.uix.widget import Widget +from kivy.properties import ObjectProperty, ListProperty, BooleanProperty, AliasProperty, \ + NumericProperty, OptionProperty, BoundedNumericProperty, StringProperty +from kivy.lang import Builder +from kivy.graphics.transformation import Matrix +from kivy.graphics.texture import Texture __all__ = ( 'MatplotFigureCropFactor', ) @@ -9,21 +22,6 @@ import matplotlib matplotlib.use('Agg') -from kivy.graphics.texture import Texture -from kivy.graphics.transformation import Matrix -from kivy.lang import Builder -from kivy.properties import ObjectProperty, ListProperty, BooleanProperty, AliasProperty, \ - NumericProperty, OptionProperty, BoundedNumericProperty, StringProperty -from kivy.uix.widget import Widget -from kivy.vector import Vector -from matplotlib.backends.backend_agg import FigureCanvasAgg -from matplotlib import cbook -from matplotlib.backend_bases import ResizeEvent -from weakref import WeakKeyDictionary -from kivy.metrics import dp -import numpy as np - - class MatplotFigureCropFactor(Widget): """Widget to show a matplotlib figure in kivy. @@ -35,10 +33,10 @@ class MatplotFigureCropFactor(Widget): _box_pos = ListProperty([0, 0]) _box_size = ListProperty([0, 0]) _img_texture = ObjectProperty(None) - _alpha_box = NumericProperty(0) + _alpha_box = NumericProperty(0) _bitmap = None _pressed = False - do_update=False + do_update = False figcanvas = ObjectProperty(None) translation_touches = BoundedNumericProperty(1, min=1) do_scale = BooleanProperty(True) @@ -47,35 +45,35 @@ class MatplotFigureCropFactor(Widget): transform = ObjectProperty(Matrix()) _alpha_hor = NumericProperty(0) _alpha_ver = NumericProperty(0) - pos_x_rect_hor=NumericProperty(0) - pos_y_rect_hor=NumericProperty(0) - pos_x_rect_ver=NumericProperty(0) - pos_y_rect_ver=NumericProperty(0) + pos_x_rect_hor = NumericProperty(0) + pos_y_rect_hor = NumericProperty(0) + pos_x_rect_ver = NumericProperty(0) + pos_y_rect_ver = NumericProperty(0) invert_rect_ver = BooleanProperty(False) invert_rect_hor = BooleanProperty(False) legend_do_scroll_x = BooleanProperty(True) legend_do_scroll_y = BooleanProperty(True) - interactive_axis = BooleanProperty(False) + interactive_axis = BooleanProperty(False) do_pan_x = BooleanProperty(True) - do_pan_y = BooleanProperty(True) + do_pan_y = BooleanProperty(True) do_zoom_x = BooleanProperty(True) - do_zoom_y = BooleanProperty(True) - fast_draw = BooleanProperty(True) #True will don't draw axis - xsorted = BooleanProperty(False) #to manage x sorted data + do_zoom_y = BooleanProperty(True) + fast_draw = BooleanProperty(True) # True will don't draw axis + xsorted = BooleanProperty(False) # to manage x sorted data minzoom = NumericProperty(dp(20)) - compare_xdata = BooleanProperty(False) + compare_xdata = BooleanProperty(False) hover_instance = ObjectProperty(None, allownone=True) nearest_hover_instance = ObjectProperty(None, allownone=True) compare_hover_instance = ObjectProperty(None, allownone=True) - disable_mouse_scrolling = BooleanProperty(False) - disable_double_tap = BooleanProperty(False) + disable_mouse_scrolling = BooleanProperty(False) + disable_double_tap = BooleanProperty(False) text_instance = None auto_zoom = BooleanProperty(False) - zoom_angle_detection=NumericProperty(15) #in degree + zoom_angle_detection = NumericProperty(15) # in degree auto_cursor = BooleanProperty(False) autoscale_visible_only = BooleanProperty(True) autoscale_axis = OptionProperty("both", options=["both", "x", "y"]) - autoscale_tight = BooleanProperty(False) + autoscale_tight = BooleanProperty(False) scale_min = NumericProperty(0.01) scale_max = NumericProperty(1e20) @@ -84,15 +82,15 @@ class MatplotFigureCropFactor(Widget): x_cursor_label = StringProperty("x") y_cursor_label = StringProperty("y") show_cursor = BooleanProperty(False) - - default_dpi=None + + default_dpi = None crop_factor = NumericProperty(2.2) def on_figure(self, obj, value): if self.default_dpi is None: - #get default matplotlib figure dpi + # get default matplotlib figure dpi self.default_dpi = self.figure.get_dpi() - + self.figure.dpi = self.default_dpi / self.crop_factor self.figcanvas = _FigureCanvas(self.figure, self) self.figcanvas._isDrawn = False @@ -132,16 +130,18 @@ def __init__(self, **kwargs): # option self.touch_mode = 'pan' # pan, pan_x, pan_y, adjust_x, adjust_y - self.text=None - self.cursor_xaxis_formatter=None #used matplotlib formatter to display x cursor value - self.cursor_yaxis_formatter=None #used matplotlib formatter to display y cursor value - + self.text = None + # used matplotlib formatter to display x cursor value + self.cursor_xaxis_formatter = None + # used matplotlib formatter to display y cursor value + self.cursor_yaxis_formatter = None + # clear touches on touch up self._touches = [] self._last_touch_pos = {} - - #pan management - self.first_touch_pan = None + + # pan management + self.first_touch_pan = None # background self.background = None @@ -152,24 +152,24 @@ def __init__(self, **kwargs): self.anchor_y = None # manage back and next event - if hasattr(cbook,'_Stack'): - #manage matplotlib version with no Stack (replace by _Stack) + if hasattr(cbook, '_Stack'): + # manage matplotlib version with no Stack (replace by _Stack) self._nav_stack = cbook._Stack() else: - self._nav_stack = cbook.Stack() + self._nav_stack = cbook.Stack() self.bind(size=self._onSize) def on_crop_factor(self, obj, value): if self.figure: if self.default_dpi is None: - #get default matplotlib figure dpi + # get default matplotlib figure dpi self.default_dpi = self.figure.get_dpi() - + self.figure.dpi = self.default_dpi / value - + self.figcanvas.draw() - + def transform_eval(self, x, axis): custom_transform = axis.get_transform() return custom_transform.transform_non_affine(np.array([x]))[0] @@ -179,12 +179,20 @@ def inv_transform_eval(self, x, axis): return inv_custom_transform.transform_non_affine(np.array([x]))[0] def register_lines(self) -> None: - self.horizontal_line = self.axes.axhline(color='orange', lw=0.8, ls='--', visible=False, animated=True) - self.vertical_line = self.axes.axvline(color='orange', lw=0.8, ls='--', visible=False, animated=True) - - self.text = self.axes.text(0.0, 1.01, ' ', - transform=self.axes.transAxes, - ha='left', color='orange', fontsize=14, animated=True) + self.horizontal_line = self.axes.axhline( + color='orange', lw=0.8, ls='--', visible=False, animated=True) + self.vertical_line = self.axes.axvline( + color='orange', lw=0.8, ls='--', visible=False, animated=True) + + self.text = self.axes.text( + 0.0, + 1.01, + ' ', + transform=self.axes.transAxes, + ha='left', + color='orange', + fontsize=14, + animated=True) self._text_data = ' ' self.horizontal_line.set_zorder(1000) @@ -312,46 +320,55 @@ def _draw_bitmap(self): self._img_texture = Texture.create(size=(self.bt_w, self.bt_h)) self._img_texture.flip_vertical() self._img_texture.blit_buffer( - bytes(self._bitmap), size=(self.bt_w, self.bt_h), colorfmt='rgba', bufferfmt='ubyte') + bytes( + self._bitmap), + size=( + self.bt_w, + self.bt_h), + colorfmt='rgba', + bufferfmt='ubyte') self.update_hover() def transform_with_touch(self, event): """ manage touch behaviour. based on kivy scatter method""" # just do a simple one finger drag - event_ppos = event.ppos[0] / self.crop_factor, event.ppos[1] / self.crop_factor - event_pos = event.pos[0] / self.crop_factor, event.pos[1] / self.crop_factor + event_ppos = event.ppos[0] / \ + self.crop_factor, event.ppos[1] / self.crop_factor + event_pos = event.pos[0] / \ + self.crop_factor, event.pos[1] / self.crop_factor if len(self._touches) == self.translation_touches: - - if self.touch_mode=='pan': + + if self.touch_mode == 'pan': if self._nav_stack() is None: - self.push_current() + self.push_current() self.apply_pan(self.axes, event) - - if self.touch_mode=='pan_x' or self.touch_mode=='pan_y' \ - or self.touch_mode=='adjust_x' or self.touch_mode=='adjust_y': + + if self.touch_mode == 'pan_x' or self.touch_mode == 'pan_y' \ + or self.touch_mode == 'adjust_x' or self.touch_mode == 'adjust_y': if self._nav_stack() is None: - self.push_current() - self.apply_pan(self.axes, event, mode=self.touch_mode) - - elif self.touch_mode=='zoombox': + self.push_current() + self.apply_pan(self.axes, event, mode=self.touch_mode) + + elif self.touch_mode == 'zoombox': if self._nav_stack() is None: - self.push_current() + self.push_current() real_x, real_y = event.x - self.pos[0], event.y - self.pos[1] - #in case x_init is not create - if not hasattr(self,'x_init'): + # in case x_init is not create + if not hasattr(self, 'x_init'): self.x_init = event.x self.y_init = real_y - self.draw_box(event, self.x_init,self.y_init, event.x, real_y) - + self.draw_box(event, self.x_init, self.y_init, event.x, real_y) + changed = True - #note: avoid zoom in/out on touch mode zoombox - if len(self._touches) == 1:# + # note: avoid zoom in/out on touch mode zoombox + if len(self._touches) == 1: return changed # we have more than one touch... list of last known pos - points = [Vector(self._last_touch_pos[t]) for t in self._touches if t is not event] + points = [Vector(self._last_touch_pos[t]) + for t in self._touches if t is not event] points.append(Vector(event_pos)) # we only want to transform if the touch is part of the two touches @@ -406,15 +423,16 @@ def on_touch_down(self, event): return True else: - if self.touch_mode=='zoombox': + if self.touch_mode == 'zoombox': x, y = event.x, event.y real_x, real_y = x - self.pos[0], y - self.pos[1] - self.x_init=x - self.y_init=real_y - self.draw_box(event, x, real_y, x, real_y) + self.x_init = x + self.y_init = real_y + self.draw_box(event, x, real_y, x, real_y) event.grab(self) self._touches.append(event) - self._last_touch_pos[event] = event.pos[0] / self.crop_factor, event.pos[1] / self.crop_factor + self._last_touch_pos[event] = event.pos[0] / \ + self.crop_factor, event.pos[1] / self.crop_factor if len(self._touches) > 1: self.background = None @@ -436,7 +454,8 @@ def on_touch_move(self, event): # scale/translate if event in self._touches and event.grab_current == self: self.transform_with_touch(event) - self._last_touch_pos[event] = event.pos[0] / self.crop_factor, event.pos[1] / self.crop_factor + self._last_touch_pos[event] = event.pos[0] / \ + self.crop_factor, event.pos[1] / self.crop_factor # stop propagating if its within our bounds if self.collide_point(*event.pos): @@ -449,45 +468,47 @@ def on_touch_up(self, event): event.ungrab(self) del self._last_touch_pos[event] self._touches.remove(event) - if self.touch_mode=='pan' or self.touch_mode=='zoombox' or \ - self.touch_mode=='pan_x' or self.touch_mode=='pan_y' \ - or self.touch_mode=='adjust_x' or self.touch_mode=='adjust_y' \ - or self.touch_mode=='minmax': - + if self.touch_mode == 'pan' or self.touch_mode == 'zoombox' or \ + self.touch_mode == 'pan_x' or self.touch_mode == 'pan_y' \ + or self.touch_mode == 'adjust_x' or self.touch_mode == 'adjust_y' \ + or self.touch_mode == 'minmax': + self.push_current() if self.interactive_axis: - if self.touch_mode=='pan_x' or self.touch_mode=='pan_y' \ - or self.touch_mode=='adjust_x' or self.touch_mode=='adjust_y': - self.touch_mode='pan' - self.first_touch_pan=None + if self.touch_mode == 'pan_x' or self.touch_mode == 'pan_y' \ + or self.touch_mode == 'adjust_x' or self.touch_mode == 'adjust_y': + self.touch_mode = 'pan' + self.first_touch_pan = None x, y = event.x, event.y - if abs(self._box_size[0]) > 1 or abs(self._box_size[1]) > 1 or self.touch_mode=='zoombox': - self.reset_box() + if abs( + self._box_size[0]) > 1 or abs( + self._box_size[1]) > 1 or self.touch_mode == 'zoombox': + self.reset_box() if not self.collide_point(x, y) and self.do_update: - #update axis lim if zoombox is used and touch outside widget - self.update_lim() - ax=self.axes + # update axis lim if zoombox is used and touch outside widget + self.update_lim() + ax = self.axes ax.figure.canvas.draw_idle() - ax.figure.canvas.flush_events() + ax.figure.canvas.flush_events() return True - + # stop propagating if its within our bounds if self.collide_point(x, y) and self.figure: self._pressed = False if self.do_update: - self.update_lim() - - self.anchor_x=None - self.anchor_y=None - - ax=self.axes - self.background=None - self.show_compare_cursor=True + self.update_lim() + + self.anchor_x = None + self.anchor_y = None + + ax = self.axes + self.background = None + self.show_compare_cursor = True ax.figure.canvas.draw_idle() - ax.figure.canvas.flush_events() - + ax.figure.canvas.flush_events() + return True def apply_zoom(self, scale_factor, ax, anchor=(0, 0), new_line=None): @@ -498,7 +519,8 @@ def apply_zoom(self, scale_factor, ax, anchor=(0, 0), new_line=None): y = anchor[1] - self.pos[1] / self.crop_factor trans = ax.transData.inverted() - xdata, ydata = trans.transform_point((x + new_line.x / 2, y + new_line.y / 2)) + xdata, ydata = trans.transform_point( + (x + new_line.x / 2, y + new_line.y / 2)) cur_xlim = ax.get_xlim() cur_ylim = ax.get_ylim() @@ -537,13 +559,16 @@ def apply_zoom(self, scale_factor, ax, anchor=(0, 0), new_line=None): x_right = xdata + new_width * (relx) if scale != 'linear': try: - x_left, x_right = self.inv_transform_eval(x_left, ax.xaxis), self.inv_transform_eval(x_right, ax.xaxis) + x_left, x_right = self.inv_transform_eval( + x_left, ax.xaxis), self.inv_transform_eval( + x_right, ax.xaxis) except OverflowError: # Limit case x_left, x_right = min_, max_ if x_left <= 0. or x_right <= 0.: # Limit case x_left, x_right = min_, max_ - x_left, x_right = self.normalize_axis_stops(self.stop_xlimits, (x_left, x_right)) + x_left, x_right = self.normalize_axis_stops( + self.stop_xlimits, (x_left, x_right)) ax.set_xlim([x_left, x_right]) if self.do_zoom_y: @@ -551,13 +576,16 @@ def apply_zoom(self, scale_factor, ax, anchor=(0, 0), new_line=None): y_right = ydata + new_height * (rely) if yscale != 'linear': try: - y_left, y_right = self.inv_transform_eval(y_left, ax.yaxis), self.inv_transform_eval(y_right, ax.yaxis) + y_left, y_right = self.inv_transform_eval( + y_left, ax.yaxis), self.inv_transform_eval( + y_right, ax.yaxis) except OverflowError: # Limit case y_left, y_right = ymin_, ymax_ if y_left <= 0. or y_right <= 0.: # Limit case y_left, y_right = ymin_, ymax_ - y_left, y_right = self.normalize_axis_stops(self.stop_ylimits, (y_left, y_right)) + y_left, y_right = self.normalize_axis_stops( + self.stop_ylimits, (y_left, y_right)) ax.set_ylim([y_left, y_right]) self._draw(ax) @@ -566,85 +594,101 @@ def apply_pan(self, ax, event, mode='pan'): """ pan method """ # self.background = None trans = ax.transData.inverted() - xdata, ydata = trans.transform_point(((event.x - self.pos[0]) / self.crop_factor, - (event.y - self.pos[1]) / self.crop_factor)) - xpress, ypress = trans.transform_point(((self._last_touch_pos[event][0] - self.pos[0] / self.crop_factor), - (self._last_touch_pos[event][1] - self.pos[1] / self.crop_factor))) - + xdata, ydata = trans.transform_point( + ((event.x - self.pos[0]) / self.crop_factor, + (event.y - self.pos[1]) / self.crop_factor)) + xpress, ypress = trans.transform_point( + ((self._last_touch_pos[event][0] - self.pos[0] / self.crop_factor), + (self._last_touch_pos[event][1] - self.pos[1] / self.crop_factor))) + # trans = ax.transData.inverted() # xdata, ydata = trans.transform_point((event.x-self.pos[0], event.y-self.pos[1])) # xpress, ypress = trans.transform_point((self._last_touch_pos[event][0]-self.pos[0], self._last_touch_pos[event][1]-self.pos[1])) - - scale=ax.get_xscale() - yscale=ax.get_yscale() - + + scale = ax.get_xscale() + yscale = ax.get_yscale() + if scale == 'linear': dx = xdata - xpress else: - dx = self.transform_eval(xdata,ax.xaxis) - \ - self.transform_eval(xpress,ax.xaxis) - + dx = self.transform_eval(xdata, ax.xaxis) - \ + self.transform_eval(xpress, ax.xaxis) + if yscale == 'linear': dy = ydata - ypress else: - dy = self.transform_eval(ydata,ax.yaxis) - \ - self.transform_eval(ypress,ax.yaxis) + dy = self.transform_eval(ydata, ax.yaxis) - \ + self.transform_eval(ypress, ax.yaxis) + + xleft, xright = self.axes.get_xlim() + ybottom, ytop = self.axes.get_ylim() - xleft,xright=self.axes.get_xlim() - ybottom,ytop=self.axes.get_ylim() - - #check inverted data + # check inverted data inverted_x = False - if xleft>xright: - inverted_x=True - cur_xlim=(xright,xleft) + if xleft > xright: + inverted_x = True + cur_xlim = (xright, xleft) else: - cur_xlim=(xleft,xright) + cur_xlim = (xleft, xright) inverted_y = False - if ybottom>ytop: - inverted_y=True - cur_ylim=(ytop,ybottom) + if ybottom > ytop: + inverted_y = True + cur_ylim = (ytop, ybottom) else: - cur_ylim=(ybottom,ytop) - - if self.interactive_axis and self.touch_mode=='pan' and not self.first_touch_pan=='pan': - if (ydata < cur_ylim[0] and not inverted_y) or (ydata > cur_ylim[1] and inverted_y): - left_anchor_zone= (cur_xlim[1] - cur_xlim[0])*.2 + cur_xlim[0] - right_anchor_zone= (cur_xlim[1] - cur_xlim[0])*.8 + cur_xlim[0] + cur_ylim = (ybottom, ytop) + + if self.interactive_axis and self.touch_mode == 'pan' and not self.first_touch_pan == 'pan': + if (ydata < cur_ylim[0] and not inverted_y) or ( + ydata > cur_ylim[1] and inverted_y): + left_anchor_zone = ( + cur_xlim[1] - cur_xlim[0]) * .2 + cur_xlim[0] + right_anchor_zone = ( + cur_xlim[1] - cur_xlim[0]) * .8 + cur_xlim[0] if xdata < left_anchor_zone or xdata > right_anchor_zone: mode = 'adjust_x' else: mode = 'pan_x' self.touch_mode = mode elif (xdata < cur_xlim[0] and not inverted_x) or (xdata > cur_xlim[1] and inverted_x): - bottom_anchor_zone= (cur_ylim[1] - cur_ylim[0])*.2 + cur_ylim[0] - top_anchor_zone= (cur_ylim[1] - cur_ylim[0])*.8 + cur_ylim[0] + bottom_anchor_zone = ( + cur_ylim[1] - cur_ylim[0]) * .2 + cur_ylim[0] + top_anchor_zone = ( + cur_ylim[1] - cur_ylim[0]) * .8 + cur_ylim[0] if ydata < bottom_anchor_zone or ydata > top_anchor_zone: mode = 'adjust_y' else: - mode= 'pan_y' + mode = 'pan_y' self.touch_mode = mode else: self.touch_mode = 'pan' - if not mode=='pan_y' and not mode=='adjust_y': - if mode=='adjust_x': + if not mode == 'pan_y' and not mode == 'adjust_y': + if mode == 'adjust_x': if self.anchor_x is None: - midpoint= (cur_xlim[1] + cur_xlim[0])/2 - if xdata>midpoint: - self.anchor_x='left' + midpoint = (cur_xlim[1] + cur_xlim[0]) / 2 + if xdata > midpoint: + self.anchor_x = 'left' else: - self.anchor_x='right' - if self.anchor_x=='left': - if xdata> cur_xlim[0]: + self.anchor_x = 'right' + if self.anchor_x == 'left': + if xdata > cur_xlim[0]: if scale == 'linear': cur_xlim -= dx else: try: - cur_xlim = [self.inv_transform_eval((self.transform_eval(cur_xlim[0],ax.xaxis) - dx),ax.xaxis), - self.inv_transform_eval((self.transform_eval(cur_xlim[1],ax.xaxis) - dx),ax.xaxis)] + cur_xlim = [ + self.inv_transform_eval( + (self.transform_eval( + cur_xlim[0], + ax.xaxis) - dx), + ax.xaxis), + self.inv_transform_eval( + (self.transform_eval( + cur_xlim[1], + ax.xaxis) - dx), + ax.xaxis)] except (ValueError, OverflowError): - cur_xlim = cur_xlim # Keep previous limits + cur_xlim = cur_xlim # Keep previous limits if self.stop_xlimits is not None: if cur_xlim[0] > self.stop_xlimits[0] and cur_xlim[1] < self.stop_xlimits[1]: @@ -652,47 +696,67 @@ def apply_pan(self, ax, event, mode='pan'): ax.set_xlim(cur_xlim[1], None) else: ax.set_xlim(None, cur_xlim[1]) - else: + else: if inverted_x: - ax.set_xlim(cur_xlim[1],None) + ax.set_xlim(cur_xlim[1], None) else: - ax.set_xlim(None,cur_xlim[1]) + ax.set_xlim(None, cur_xlim[1]) else: - if xdata< cur_xlim[1]: + if xdata < cur_xlim[1]: if scale == 'linear': cur_xlim -= dx else: try: - cur_xlim = [self.inv_transform_eval((self.transform_eval(cur_xlim[0],ax.xaxis) - dx),ax.xaxis), - self.inv_transform_eval((self.transform_eval(cur_xlim[1],ax.xaxis) - dx),ax.xaxis)] + cur_xlim = [ + self.inv_transform_eval( + (self.transform_eval( + cur_xlim[0], + ax.xaxis) - dx), + ax.xaxis), + self.inv_transform_eval( + (self.transform_eval( + cur_xlim[1], + ax.xaxis) - dx), + ax.xaxis)] except (ValueError, OverflowError): - cur_xlim = cur_xlim # Keep previous limits - + cur_xlim = cur_xlim # Keep previous limits + if self.stop_xlimits is not None: if cur_xlim[0] > self.stop_xlimits[0] and cur_xlim[1] < self.stop_xlimits[1]: if inverted_x: ax.set_xlim(None, cur_xlim[0]) else: ax.set_xlim(cur_xlim[0], None) - else: + else: if inverted_x: - ax.set_xlim(None,cur_xlim[0]) + ax.set_xlim(None, cur_xlim[0]) else: - ax.set_xlim(cur_xlim[0],None) + ax.set_xlim(cur_xlim[0], None) else: if scale == 'linear': cur_xlim -= dx else: try: - cur_xlim = [self.inv_transform_eval((self.transform_eval(cur_xlim[0],ax.xaxis) - dx),ax.xaxis), - self.inv_transform_eval((self.transform_eval(cur_xlim[1],ax.xaxis) - dx),ax.xaxis)] + cur_xlim = [ + self.inv_transform_eval( + (self.transform_eval( + cur_xlim[0], + ax.xaxis) - dx), + ax.xaxis), + self.inv_transform_eval( + (self.transform_eval( + cur_xlim[1], + ax.xaxis) - dx), + ax.xaxis)] except (ValueError, OverflowError): - cur_xlim = cur_xlim # Keep previous limits - + cur_xlim = cur_xlim # Keep previous limits + if self.stop_xlimits is not None: if cur_xlim[0] < self.stop_xlimits[0] or cur_xlim[1] > self.stop_xlimits[1]: side = 'left' if (xleft - cur_xlim[0]) > 0 else 'right' - diff = min(abs(cur_xlim[0] - self.stop_xlimits[0]), abs(cur_xlim[1] - self.stop_xlimits[1])) + diff = min( + abs(cur_xlim[0] - self.stop_xlimits[0]), + abs(cur_xlim[1] - self.stop_xlimits[1])) if side == 'left': cur_xlim = cur_xlim[0] + diff, cur_xlim[1] + diff else: @@ -701,97 +765,126 @@ def apply_pan(self, ax, event, mode='pan'): ax.set_xlim(cur_xlim[1], cur_xlim[0]) else: ax.set_xlim(cur_xlim) - else: + else: if inverted_x: - ax.set_xlim(cur_xlim[1],cur_xlim[0]) + ax.set_xlim(cur_xlim[1], cur_xlim[0]) else: ax.set_xlim(cur_xlim) - - if not mode=='pan_x' and not mode=='adjust_x': - if mode=='adjust_y': + + if not mode == 'pan_x' and not mode == 'adjust_x': + if mode == 'adjust_y': if self.anchor_y is None: - midpoint= (cur_ylim[1] + cur_ylim[0])/2 - if ydata>midpoint: - self.anchor_y='top' + midpoint = (cur_ylim[1] + cur_ylim[0]) / 2 + if ydata > midpoint: + self.anchor_y = 'top' else: - self.anchor_y='bottom' - - if self.anchor_y=='top': - if ydata> cur_ylim[0]: + self.anchor_y = 'bottom' + + if self.anchor_y == 'top': + if ydata > cur_ylim[0]: if yscale == 'linear': cur_ylim -= dy - + else: try: - cur_ylim = [self.inv_transform_eval((self.transform_eval(cur_ylim[0],ax.yaxis) - dy),ax.yaxis), - self.inv_transform_eval((self.transform_eval(cur_ylim[1],ax.yaxis) - dy),ax.yaxis)] + cur_ylim = [ + self.inv_transform_eval( + (self.transform_eval( + cur_ylim[0], + ax.yaxis) - dy), + ax.yaxis), + self.inv_transform_eval( + (self.transform_eval( + cur_ylim[1], + ax.yaxis) - dy), + ax.yaxis)] except (ValueError, OverflowError): - cur_ylim = cur_ylim # Keep previous limits - + cur_ylim = cur_ylim # Keep previous limits + if inverted_y: - ax.set_ylim(cur_ylim[1],None) + ax.set_ylim(cur_ylim[1], None) else: - ax.set_ylim(None,cur_ylim[1]) + ax.set_ylim(None, cur_ylim[1]) else: - if ydata< cur_ylim[1]: + if ydata < cur_ylim[1]: if yscale == 'linear': cur_ylim -= dy - + else: try: - cur_ylim = [self.inv_transform_eval((self.transform_eval(cur_ylim[0],ax.yaxis) - dy),ax.yaxis), - self.inv_transform_eval((self.transform_eval(cur_ylim[1],ax.yaxis) - dy),ax.yaxis)] + cur_ylim = [ + self.inv_transform_eval( + (self.transform_eval( + cur_ylim[0], + ax.yaxis) - dy), + ax.yaxis), + self.inv_transform_eval( + (self.transform_eval( + cur_ylim[1], + ax.yaxis) - dy), + ax.yaxis)] except (ValueError, OverflowError): - cur_ylim = cur_ylim # Keep previous limits + cur_ylim = cur_ylim # Keep previous limits if inverted_y: - ax.set_ylim(None,cur_ylim[0]) + ax.set_ylim(None, cur_ylim[0]) else: - ax.set_ylim(cur_ylim[0],None) - else: + ax.set_ylim(cur_ylim[0], None) + else: if yscale == 'linear': - cur_ylim -= dy - + cur_ylim -= dy + else: try: - cur_ylim = [self.inv_transform_eval((self.transform_eval(cur_ylim[0],ax.yaxis) - dy),ax.yaxis), - self.inv_transform_eval((self.transform_eval(cur_ylim[1],ax.yaxis) - dy),ax.yaxis)] + cur_ylim = [ + self.inv_transform_eval( + (self.transform_eval( + cur_ylim[0], + ax.yaxis) - dy), + ax.yaxis), + self.inv_transform_eval( + (self.transform_eval( + cur_ylim[1], + ax.yaxis) - dy), + ax.yaxis)] except (ValueError, OverflowError): - cur_ylim = cur_ylim # Keep previous limits + cur_ylim = cur_ylim # Keep previous limits if inverted_y: - ax.set_ylim(cur_ylim[1],cur_ylim[0]) + ax.set_ylim(cur_ylim[1], cur_ylim[0]) else: ax.set_ylim(cur_ylim) # self._draw(ax) if self.first_touch_pan is None: - self.first_touch_pan=self.touch_mode + self.first_touch_pan = self.touch_mode - if self.fast_draw: - #use blit method + if self.fast_draw: + # use blit method if self.background is None: self.background_patch_copy.set_visible(True) ax.figure.canvas.draw_idle() - ax.figure.canvas.flush_events() - self.background = ax.figure.canvas.copy_from_bbox(ax.figure.bbox) - self.background_patch_copy.set_visible(False) - ax.figure.canvas.restore_region(self.background) - + ax.figure.canvas.flush_events() + self.background = ax.figure.canvas.copy_from_bbox( + ax.figure.bbox) + self.background_patch_copy.set_visible(False) + ax.figure.canvas.restore_region(self.background) + for line in ax.lines: ax.draw_artist(line) - + ax.figure.canvas.blit(ax.bbox) - ax.figure.canvas.flush_events() - + ax.figure.canvas.flush_events() + # self.update_hover() - + else: ax.figure.canvas.draw_idle() - ax.figure.canvas.flush_events() + ax.figure.canvas.flush_events() def zoom_factory(self, event, ax, base_scale=1.1): """ zoom with scrolling mouse method """ self.background = None - xdata, ydata = self._calculate_mouse_position(self.to_local(*event.pos)) + xdata, ydata = self._calculate_mouse_position( + self.to_local(*event.pos)) cur_xlim = ax.get_xlim() cur_ylim = ax.get_ylim() @@ -842,13 +935,16 @@ def zoom_factory(self, event, ax, base_scale=1.1): if scale != 'linear': try: - x_left, x_right = self.inv_transform_eval(x_left, ax.yaxis), self.inv_transform_eval(x_right, ax.yaxis) + x_left, x_right = self.inv_transform_eval( + x_left, ax.yaxis), self.inv_transform_eval( + x_right, ax.yaxis) except OverflowError: # Limit case x_left, x_right = min_, max_ if x_left <= 0. or x_right <= 0.: # Limit case x_left, x_right = min_, max_ - x_left, x_right = self.normalize_axis_stops(self.stop_xlimits, (x_left, x_right)) + x_left, x_right = self.normalize_axis_stops( + self.stop_xlimits, (x_left, x_right)) ax.set_xlim([x_left, x_right]) if self.do_zoom_y: @@ -857,13 +953,16 @@ def zoom_factory(self, event, ax, base_scale=1.1): if yscale != 'linear': try: - y_left, y_right = self.inv_transform_eval(y_left, ax.yaxis), self.inv_transform_eval(y_right, ax.yaxis) + y_left, y_right = self.inv_transform_eval( + y_left, ax.yaxis), self.inv_transform_eval( + y_right, ax.yaxis) except OverflowError: # Limit case y_left, y_right = ymin_, ymax_ if y_left <= 0. or y_right <= 0.: # Limit case y_left, y_right = ymin_, ymax_ - y_left, y_right = self.normalize_axis_stops(self.stop_ylimits, (y_left, y_right)) + y_left, y_right = self.normalize_axis_stops( + self.stop_ylimits, (y_left, y_right)) ax.set_ylim([y_left, y_right]) self._draw(ax) @@ -884,7 +983,9 @@ def _onSize(self, o, size): dpival = self.figure.dpi winch = self._width / dpival hinch = self._height / dpival - self.figure.set_size_inches(winch / self.crop_factor, hinch / self.crop_factor) + self.figure.set_size_inches( + winch / self.crop_factor, + hinch / self.crop_factor) event = ResizeEvent('resize_event', self.figcanvas) self.figcanvas.callbacks.process('resize_event', event) @@ -893,10 +994,11 @@ def _update_background(self): if self.text: self.set_cross_hair_visible(False) self.axes.figure.canvas.draw_idle() - self.axes.figure.canvas.flush_events() - self.background = self.axes.figure.canvas.copy_from_bbox(self.axes.figure.bbox) + self.axes.figure.canvas.flush_events() + self.background = self.axes.figure.canvas.copy_from_bbox( + self.axes.figure.bbox) if self.text: - self.set_cross_hair_visible(True) + self.set_cross_hair_visible(True) def _draw(self, ax): if self.fast_draw: @@ -929,7 +1031,9 @@ def _draw(self, ax): def _calculate_mouse_position(self, pos): trans = self.axes.transData.inverted() - x, y = trans.transform_point(((pos[0] - self.pos[0]) / self.crop_factor, (pos[1] - self.pos[1]) / self.crop_factor)) + x, y = trans.transform_point( + ((pos[0] - self.pos[0]) / self.crop_factor, (pos[1] - self.pos[1]) / + self.crop_factor)) return x, y def on_motion(self, window, pos): @@ -937,52 +1041,59 @@ def on_motion(self, window, pos): # self.set_cross_hair_visible(True) x, y = self._calculate_mouse_position(self.to_local(*pos)) if self.hover_instance: - ax=self.axes - self.hover_instance.xmin_line = float(ax.bbox.bounds[0]) *self.crop_factor + self.x - self.hover_instance.xmax_line = float(ax.bbox.bounds[0] + ax.bbox.bounds[2]) *self.crop_factor + self.x - self.hover_instance.ymin_line = float(ax.bbox.bounds[1])*self.crop_factor + self.y - self.hover_instance.ymax_line = float(ax.bbox.bounds[1] + ax.bbox.bounds[3])*self.crop_factor - xy_pos = ax.transData.transform([(x,y)]) + ax = self.axes + self.hover_instance.xmin_line = float( + ax.bbox.bounds[0]) * self.crop_factor + self.x + self.hover_instance.xmax_line = float( + ax.bbox.bounds[0] + ax.bbox.bounds[2]) * self.crop_factor + self.x + self.hover_instance.ymin_line = float( + ax.bbox.bounds[1]) * self.crop_factor + self.y + self.hover_instance.ymax_line = float( + ax.bbox.bounds[1] + ax.bbox.bounds[3]) * self.crop_factor + xy_pos = ax.transData.transform([(x, y)]) self.x_hover_data = x self.y_hover_data = y - self.hover_instance.x_hover_pos=float(xy_pos[0][0])*self.crop_factor + self.x - self.hover_instance.y_hover_pos=float(xy_pos[0][1])*self.crop_factor + self.y - self.hover_instance.show_cursor=True - + self.hover_instance.x_hover_pos = float( + xy_pos[0][0]) * self.crop_factor + self.x + self.hover_instance.y_hover_pos = float( + xy_pos[0][1]) * self.crop_factor + self.y + self.hover_instance.show_cursor = True + if self.cursor_xaxis_formatter: x = self.cursor_xaxis_formatter.format_data(x) else: x = ax.xaxis.get_major_formatter().format_data_short(x) if self.cursor_yaxis_formatter: - y = self.cursor_yaxis_formatter.format_data(y) + y = self.cursor_yaxis_formatter.format_data(y) else: y = ax.yaxis.get_major_formatter().format_data_short(y) - self.hover_instance.label_x_value=f"{x}" - self.hover_instance.label_y_value=f"{y}" - - if self.hover_instance.x_hover_pos>self.x+(self.axes.bbox.bounds[2] + self.axes.bbox.bounds[0])*self.crop_factor or \ - self.hover_instance.x_hover_posself.y+(self.axes.bbox.bounds[1] + self.axes.bbox.bounds[3])*self.crop_factor or \ - self.hover_instance.y_hover_pos self.x + (self.axes.bbox.bounds[2] + self.axes.bbox.bounds[0]) * self.crop_factor or \ + self.hover_instance.x_hover_pos < self.x + self.axes.bbox.bounds[0] * self.crop_factor or \ + self.hover_instance.y_hover_pos > self.y + (self.axes.bbox.bounds[1] + self.axes.bbox.bounds[3]) * self.crop_factor or \ + self.hover_instance.y_hover_pos < self.y + self.axes.bbox.bounds[1] * self.crop_factor: + self.hover_instance.hover_outside_bound = True else: - self.hover_instance.hover_outside_bound=False - + self.hover_instance.hover_outside_bound = False + elif self.text: self.horizontal_line.set_ydata([y]) self.vertical_line.set_xdata([x]) - self.text.set_text(f'{self.x_cursor_label}: {x:.3f} {self.y_cursor_label}: {y:.3f}') + self.text.set_text( + f'{self.x_cursor_label}: {x:.3f} {self.y_cursor_label}: {y:.3f}') self._draw(self.axes) elif self.show_cursor: if self.hover_instance: - self.hover_instance.x_hover_pos=self.x - self.hover_instance.y_hover_pos=self.y - self.hover_instance.show_cursor=False + self.hover_instance.x_hover_pos = self.x + self.hover_instance.y_hover_pos = self.y + self.hover_instance.show_cursor = False self.x_hover_data = None self.y_hover_data = None - elif self.text: + elif self.text: if self.text._text != ' ': self.set_cross_hair_visible(False) self._draw(self.axes) @@ -990,34 +1101,42 @@ def on_motion(self, window, pos): def update_hover(self): """ update hover on fast draw (if exist)""" if self.hover_instance: - #update hover pos if needed - if self.hover_instance.show_cursor and self.x_hover_data and self.y_hover_data: - xy_pos = self.axes.transData.transform([(self.x_hover_data,self.y_hover_data)]) - self.hover_instance.x_hover_pos=float(xy_pos[0][0])*self.crop_factor + self.x - self.hover_instance.y_hover_pos=float(xy_pos[0][1])*self.crop_factor + self.y - - self.hover_instance.xmin_line = float(self.axes.bbox.bounds[0]) *self.crop_factor + self.x - self.hover_instance.xmax_line = float(self.axes.bbox.bounds[0] + self.axes.bbox.bounds[2]) *self.crop_factor + self.x - self.hover_instance.ymin_line = float(self.axes.bbox.bounds[1])*self.crop_factor + self.y - self.hover_instance.ymax_line = float(self.axes.bbox.bounds[1] + self.axes.bbox.bounds[3])*self.crop_factor - - if self.hover_instance.x_hover_pos>self.x+(self.axes.bbox.bounds[2] + self.axes.bbox.bounds[0])*self.crop_factor or \ - self.hover_instance.x_hover_posself.y+(self.axes.bbox.bounds[1] + self.axes.bbox.bounds[3])*self.crop_factor or \ - self.hover_instance.y_hover_pos self.x + (self.axes.bbox.bounds[2] + self.axes.bbox.bounds[0]) * self.crop_factor or \ + self.hover_instance.x_hover_pos < self.x + self.axes.bbox.bounds[0] * self.crop_factor or \ + self.hover_instance.y_hover_pos > self.y + (self.axes.bbox.bounds[1] + self.axes.bbox.bounds[3]) * self.crop_factor or \ + self.hover_instance.y_hover_pos < self.y + self.axes.bbox.bounds[1] * self.crop_factor: + self.hover_instance.hover_outside_bound = True else: - self.hover_instance.hover_outside_bound=False + self.hover_instance.hover_outside_bound = False + def draw_box(self, event, x0, y0, x1, y1) -> None: """ Draw zoombox method - + Args: event: touch kivy event x0: x coordonnate init x1: y coordonnate of move touch y0: y coordonnate init y1: x coordonnate of move touch - + Return: None """ @@ -1025,147 +1144,165 @@ def draw_box(self, event, x0, y0, x1, y1) -> None: # Kivy coords y0 = pos_y + y0 y1 = pos_y + y1 - - if abs(y1-y0)>dp(5) or abs(x1-x0)>dp(5): - self._alpha_box=0.3 - self._alpha_rect=0 - + + if abs(y1 - y0) > dp(5) or abs(x1 - x0) > dp(5): + self._alpha_box = 0.3 + self._alpha_rect = 0 + trans = self.axes.transData.inverted() - # xdata, ydata = trans.transform_point((event.x-pos_x, event.y-pos_y)) - xdata, ydata = trans.transform_point(((event.x-pos_x) / self.crop_factor, (event.y-pos_y) / self.crop_factor)) - - xleft,xright=self.axes.get_xlim() - ybottom,ytop=self.axes.get_ylim() - - xmax = max(xleft,xright) - xmin = min(xleft,xright) - ymax = max(ybottom,ytop) - ymin = min(ybottom,ytop) - - #check inverted data + # xdata, ydata = trans.transform_point((event.x-pos_x, event.y-pos_y)) + xdata, ydata = trans.transform_point( + ((event.x - pos_x) / self.crop_factor, (event.y - pos_y) / self.crop_factor)) + + xleft, xright = self.axes.get_xlim() + ybottom, ytop = self.axes.get_ylim() + + xmax = max(xleft, xright) + xmin = min(xleft, xright) + ymax = max(ybottom, ytop) + ymin = min(ybottom, ytop) + + # check inverted data inverted_x = False - if xleft>xright: - inverted_x=True + if xleft > xright: + inverted_x = True inverted_y = False - if ybottom>ytop: - inverted_y=True + if ybottom > ytop: + inverted_y = True + + # x0data, y0data = trans.transform_point((x0-pos_x, y0-pos_y)) + x0data, y0data = trans.transform_point( + ((x0 - pos_x) / self.crop_factor, (y0 - pos_y) / self.crop_factor)) - # x0data, y0data = trans.transform_point((x0-pos_x, y0-pos_y)) - x0data, y0data = trans.transform_point(((x0-pos_x) / self.crop_factor, (y0-pos_y) / self.crop_factor)) - - if x0data>xmax or x0dataymax or y0data xmax or x0data < xmin or y0data > ymax or y0data < ymin: return - if xdatax0 and inverted_x): - x1=x1_min[0][0]*self.crop_factor+pos_x + if xdata < xmin: + x1_min = self.axes.transData.transform([(xmin, ymin)]) + if (x1 < x0 and not inverted_x) or (x1 > x0 and inverted_x): + x1 = x1_min[0][0] * self.crop_factor + pos_x else: - x0=x1_min[0][0]*self.crop_factor + x0 = x1_min[0][0] * self.crop_factor - if xdata>xmax: - x0_max = self.axes.transData.transform([(xmax,ymin)]) - if (x1>x0 and not inverted_x) or (x1 xmax: + x0_max = self.axes.transData.transform([(xmax, ymin)]) + if (x1 > x0 and not inverted_x) or (x1 < x0 and inverted_x): + x1 = x0_max[0][0] * self.crop_factor + pos_x else: - x0=x0_max[0][0] *self.crop_factor + x0 = x0_max[0][0] * self.crop_factor - if ydatay0 and inverted_y): - y1=y1_min[0][1]*self.crop_factor+pos_y + if ydata < ymin: + y1_min = self.axes.transData.transform([(xmin, ymin)]) + if (y1 < y0 and not inverted_y) or (y1 > y0 and inverted_y): + y1 = y1_min[0][1] * self.crop_factor + pos_y else: - y0=y1_min[0][1]*self.crop_factor+pos_y + y0 = y1_min[0][1] * self.crop_factor + pos_y - if ydata>ymax: - y0_max = self.axes.transData.transform([(xmax,ymax)]) - if (y1>y0 and not inverted_y) or (y1 ymax: + y0_max = self.axes.transData.transform([(xmax, ymax)]) + if (y1 > y0 and not inverted_y) or (y1 < y0 and inverted_y): + y1 = y0_max[0][1] * self.crop_factor + pos_y else: - y0=y0_max[0][1]*self.crop_factor+pos_y - - if abs(x1-x0)self.minzoom: - self.pos_x_rect_ver=x0 - self.pos_y_rect_ver=y0 - - x1_min = self.axes.transData.transform([(xmin,ymin)]) - x0=x1_min[0][0]*self.crop_factor+pos_x - - x0_max = self.axes.transData.transform([(xmax,ymin)]) - x1=x0_max[0][0]*self.crop_factor+pos_x - - self._alpha_ver=1 - self._alpha_hor=0 - - elif abs(y1-y0)self.minzoom: - self.pos_x_rect_hor=x0 - self.pos_y_rect_hor=y0 - - y1_min = self.axes.transData.transform([(xmin,ymin)]) - y0=y1_min[0][1]*self.crop_factor+pos_y - - y0_max = self.axes.transData.transform([(xmax,ymax)]) - y1=y0_max[0][1]*self.crop_factor+pos_y - - self._alpha_hor=1 - self._alpha_ver=0 - + y0 = y0_max[0][1] * self.crop_factor + pos_y + + if abs(x1 - x0) < dp(20) and abs(y1 - y0) > self.minzoom: + self.pos_x_rect_ver = x0 + self.pos_y_rect_ver = y0 + + x1_min = self.axes.transData.transform([(xmin, ymin)]) + x0 = x1_min[0][0] * self.crop_factor + pos_x + + x0_max = self.axes.transData.transform([(xmax, ymin)]) + x1 = x0_max[0][0] * self.crop_factor + pos_x + + self._alpha_ver = 1 + self._alpha_hor = 0 + + elif abs(y1 - y0) < dp(20) and abs(x1 - x0) > self.minzoom: + self.pos_x_rect_hor = x0 + self.pos_y_rect_hor = y0 + + y1_min = self.axes.transData.transform([(xmin, ymin)]) + y0 = y1_min[0][1] * self.crop_factor + pos_y + + y0_max = self.axes.transData.transform([(xmax, ymax)]) + y1 = y0_max[0][1] * self.crop_factor + pos_y + + self._alpha_hor = 1 + self._alpha_ver = 0 + else: - self._alpha_hor=0 - self._alpha_ver=0 + self._alpha_hor = 0 + self._alpha_ver = 0 - if x1>x0: - self.invert_rect_ver=False + if x1 > x0: + self.invert_rect_ver = False else: - self.invert_rect_ver=True - if y1>y0: - self.invert_rect_hor=False + self.invert_rect_ver = True + if y1 > y0: + self.invert_rect_hor = False else: - self.invert_rect_hor=True - + self.invert_rect_hor = True + self._box_pos = x0, y0 self._box_size = x1 - x0, y1 - y0 - + def update_lim(self): """ update axis lim if zoombox is used""" - ax=self.axes - - self.do_update=False - - #check if inverted axis - xleft,xright=self.axes.get_xlim() - ybottom,ytop=self.axes.get_ylim() - - if xright>xleft: - ax.set_xlim(left=min(self.x0_box,self.x1_box),right=max(self.x0_box,self.x1_box)) + ax = self.axes + + self.do_update = False + + # check if inverted axis + xleft, xright = self.axes.get_xlim() + ybottom, ytop = self.axes.get_ylim() + + if xright > xleft: + ax.set_xlim( + left=min( + self.x0_box, self.x1_box), right=max( + self.x0_box, self.x1_box)) else: - ax.set_xlim(right=min(self.x0_box,self.x1_box),left=max(self.x0_box,self.x1_box)) - if ytop>ybottom: - ax.set_ylim(bottom=min(self.y0_box,self.y1_box),top=max(self.y0_box,self.y1_box)) + ax.set_xlim( + right=min( + self.x0_box, self.x1_box), left=max( + self.x0_box, self.x1_box)) + if ytop > ybottom: + ax.set_ylim( + bottom=min( + self.y0_box, self.y1_box), top=max( + self.y0_box, self.y1_box)) else: - ax.set_ylim(top=min(self.y0_box,self.y1_box),bottom=max(self.y0_box,self.y1_box)) + ax.set_ylim( + top=min( + self.y0_box, self.y1_box), bottom=max( + self.y0_box, self.y1_box)) def reset_box(self): """ reset zoombox and apply zoombox limit if zoombox option if selected""" - if min(abs(self._box_size[0]),abs(self._box_size[1]))>self.minzoom: + if min(abs(self._box_size[0]), abs(self._box_size[1])) > self.minzoom: trans = self.axes.transData.inverted() - self.x0_box, self.y0_box = trans.transform_point(((self._box_pos[0]-self.pos[0])/ self.crop_factor, (self._box_pos[1]-self.pos[1])/ self.crop_factor)) - self.x1_box, self.y1_box = trans.transform_point(((self._box_size[0]+self._box_pos[0]-self.pos[0])/ self.crop_factor, (self._box_size[1]+self._box_pos[1]-self.pos[1])/ self.crop_factor)) - self.do_update=True - + self.x0_box, self.y0_box = trans.transform_point( + ((self._box_pos[0] - self.pos[0]) / self.crop_factor, + (self._box_pos[1] - self.pos[1]) / self.crop_factor)) + self.x1_box, self.y1_box = trans.transform_point( + ((self._box_size[0] + self._box_pos[0] - self.pos[0]) / self.crop_factor, (self._box_size[1] + self._box_pos[1] - self.pos[1]) / self.crop_factor)) + self.do_update = True + self._box_size = 0, 0 self._box_pos = 0, 0 - self._alpha_box=0 + self._alpha_box = 0 self._pos_x_rect_hor = 0 self._pos_y_rect_hor = 0 self._pos_x_rect_ver = 0 - self._pos_y_rect_ver = 0 - self._alpha_hor=0 - self._alpha_ver=0 + self._pos_y_rect_ver = 0 + self._alpha_hor = 0 + self._alpha_ver = 0 self.invert_rect_hor = False self.invert_rect_ver = False + class _FigureCanvas(FigureCanvasAgg): """Internal AGG Canvas""" @@ -1204,7 +1341,7 @@ def clear(self): Builder.load_string(''' - + canvas: Color: rgba: (1, 1, 1, 1) @@ -1223,43 +1360,43 @@ def clear(self): dp(1) if root.invert_rect_ver else -dp(1), \ dp(1) if root.invert_rect_hor else -dp(1), \ dp(1) if root.invert_rect_ver else -dp(1) - - canvas.after: + + canvas.after: #horizontal rectangle left - Color: - rgba:0, 0, 0, self._alpha_hor - Line: - width: dp(1) - rectangle: - (self.pos_x_rect_hor+dp(1) if root.invert_rect_ver \ - else self.pos_x_rect_hor-dp(4),self.pos_y_rect_hor-dp(20), dp(4),dp(40)) + Color: + rgba:0, 0, 0, self._alpha_hor + Line: + width: dp(1) + rectangle: + (self.pos_x_rect_hor+dp(1) if root.invert_rect_ver \ + else self.pos_x_rect_hor-dp(4),self.pos_y_rect_hor-dp(20), dp(4),dp(40)) #horizontal rectangle right - Color: - rgba:0, 0, 0, self._alpha_hor - Line: - width: dp(1) - rectangle: - (self.pos_x_rect_hor-dp(4)+self._box_size[0] if root.invert_rect_ver \ - else self.pos_x_rect_hor+dp(1)+self._box_size[0], self.pos_y_rect_hor-dp(20), dp(4),dp(40)) + Color: + rgba:0, 0, 0, self._alpha_hor + Line: + width: dp(1) + rectangle: + (self.pos_x_rect_hor-dp(4)+self._box_size[0] if root.invert_rect_ver \ + else self.pos_x_rect_hor+dp(1)+self._box_size[0], self.pos_y_rect_hor-dp(20), dp(4),dp(40)) #vertical rectangle bottom - Color: - rgba:0, 0, 0, self._alpha_ver - Line: - width: dp(1) - rectangle: - (self.pos_x_rect_ver-dp(20),self.pos_y_rect_ver+dp(1) if root.invert_rect_hor else \ - self.pos_y_rect_ver-dp(4), dp(40),dp(4)) + Color: + rgba:0, 0, 0, self._alpha_ver + Line: + width: dp(1) + rectangle: + (self.pos_x_rect_ver-dp(20),self.pos_y_rect_ver+dp(1) if root.invert_rect_hor else \ + self.pos_y_rect_ver-dp(4), dp(40),dp(4)) #vertical rectangle top - Color: - rgba:0, 0, 0, self._alpha_ver - Line: - width: dp(1) - rectangle: - (self.pos_x_rect_ver-dp(20),self.pos_y_rect_ver-dp(4)+self._box_size[1] \ + Color: + rgba:0, 0, 0, self._alpha_ver + Line: + width: dp(1) + rectangle: + (self.pos_x_rect_ver-dp(20),self.pos_y_rect_ver-dp(4)+self._box_size[1] \ if root.invert_rect_hor else self.pos_y_rect_ver+dp(1)+self._box_size[1], \ - dp(40),dp(4)) + dp(40),dp(4)) ''') FigureCanvas = _FigureCanvas diff --git a/kivy_matplotlib_widget/uix/graph_widget_general.py b/kivy_matplotlib_widget/uix/graph_widget_general.py index cda7f5c..33adc52 100644 --- a/kivy_matplotlib_widget/uix/graph_widget_general.py +++ b/kivy_matplotlib_widget/uix/graph_widget_general.py @@ -1,26 +1,28 @@ -""" MatplotFigure is based on https://github.com/jeysonmc/kivy_matplotlib +""" MatplotFigure is based on https://github.com/jeysonmc/kivy_matplotlib and kivy scatter """ +from kivy.factory import Factory +from kivy.base import EventLoop +import numpy as np +from kivy.metrics import dp +from matplotlib.backend_bases import MouseEvent +from matplotlib.backend_bases import ResizeEvent +from matplotlib.transforms import Bbox +from matplotlib.backends.backend_agg import FigureCanvasAgg +from kivy.vector import Vector +from kivy.uix.widget import Widget +from kivy.properties import ObjectProperty, ListProperty, BooleanProperty, BoundedNumericProperty, AliasProperty, \ + NumericProperty +from kivy.lang import Builder +from kivy.graphics.transformation import Matrix +from kivy.graphics.texture import Texture import math import copy import matplotlib matplotlib.use('Agg') -from kivy.graphics.texture import Texture -from kivy.graphics.transformation import Matrix -from kivy.lang import Builder -from kivy.properties import ObjectProperty, ListProperty, BooleanProperty, BoundedNumericProperty, AliasProperty, \ - NumericProperty -from kivy.uix.widget import Widget -from kivy.vector import Vector -from matplotlib.backends.backend_agg import FigureCanvasAgg -from matplotlib.transforms import Bbox -from matplotlib.backend_bases import ResizeEvent -from matplotlib.backend_bases import MouseEvent -from kivy.metrics import dp -import numpy as np -from kivy.base import EventLoop + class MatplotFigureGeneral(Widget): """Widget to show a matplotlib figure in kivy. @@ -32,10 +34,10 @@ class MatplotFigureGeneral(Widget): _box_pos = ListProperty([0, 0]) _box_size = ListProperty([0, 0]) _img_texture = ObjectProperty(None) - _alpha_box = NumericProperty(0) + _alpha_box = NumericProperty(0) _bitmap = None _pressed = False - do_update=False + do_update = False figcanvas = ObjectProperty(None) translation_touches = BoundedNumericProperty(1, min=1) do_scale = BooleanProperty(True) @@ -44,10 +46,10 @@ class MatplotFigureGeneral(Widget): transform = ObjectProperty(Matrix()) _alpha_hor = NumericProperty(0) _alpha_ver = NumericProperty(0) - pos_x_rect_hor=NumericProperty(0) - pos_y_rect_hor=NumericProperty(0) - pos_x_rect_ver=NumericProperty(0) - pos_y_rect_ver=NumericProperty(0) + pos_x_rect_hor = NumericProperty(0) + pos_y_rect_hor = NumericProperty(0) + pos_x_rect_ver = NumericProperty(0) + pos_y_rect_ver = NumericProperty(0) invert_rect_ver = BooleanProperty(False) invert_rect_hor = BooleanProperty(False) @@ -59,51 +61,50 @@ def on_figure(self, obj, value): h = int(math.ceil(h)) self.width = w self.height = h - + # Texture self._img_texture = Texture.create(size=(w, h)) def __init__(self, **kwargs): super(MatplotFigureGeneral, self).__init__(**kwargs) - - #figure info + + # figure info self.figure = None self.axes = None self.xmin = None self.xmax = None self.ymin = None self.ymax = None - - #option + + # option self.zoompan = None - self.fast_draw=True - self.draw_left_spline=False #available only when fast_draw is True - self.touch_mode='pan' + self.fast_draw = True + self.draw_left_spline = False # available only when fast_draw is True + self.touch_mode = 'pan' self.hover_on = False - self.xsorted = True #to manage x sorted data (if numpy is used) - self.minzoom = dp(20) #minimum pixel distance to apply zoom + self.xsorted = True # to manage x sorted data (if numpy is used) + self.minzoom = dp(20) # minimum pixel distance to apply zoom - #zoom box coordonnate + # zoom box coordonnate self.x0_box = None self.y0_box = None self.x1_box = None self.y1_box = None - - #clear touches on touch up + + # clear touches on touch up self._touches = [] self._last_touch_pos = {} - #background - self.background=None - self.background_patch_copy=None - + # background + self.background = None + self.background_patch_copy = None + EventLoop.window.bind(mouse_pos=self.on_mouse_move) self.bind(size=self._onSize) - def reset_touch(self) -> None: """ reset touch - + Return: None """ @@ -129,13 +130,19 @@ def on_mouse_move(self, window, mouse_pos): real_x, real_y = x - self.pos[0], y - self.pos[1] if self.figcanvas: # self.figcanvas.motion_notify_event(x, real_y, guiEvent=None) - + self.figcanvas._lastx, self.figcanvas._lasty = x, real_y s = 'motion_notify_event' - event = MouseEvent(s, self.figcanvas, x, y, self.figcanvas._button, self.figcanvas._key, - guiEvent=None) + event = MouseEvent( + s, + self.figcanvas, + x, + y, + self.figcanvas._button, + self.figcanvas._key, + guiEvent=None) self.figcanvas.callbacks.process(s, event) - + def on_touch_down(self, event): x, y = event.x, event.y @@ -146,8 +153,15 @@ def on_touch_down(self, event): self.figcanvas._button = 1 s = 'button_press_event' - mouseevent = MouseEvent(s, self.figcanvas, x, real_y, 1, self.figcanvas._key, - dblclick=False, guiEvent=event) + mouseevent = MouseEvent( + s, + self.figcanvas, + x, + real_y, + 1, + self.figcanvas._key, + dblclick=False, + guiEvent=event) self.figcanvas.callbacks.process(s, mouseevent) def on_touch_move(self, event): @@ -159,8 +173,14 @@ def on_touch_move(self, event): self.figcanvas._lastx, self.figcanvas._lasty = x, real_y s = 'motion_notify_event' - event = MouseEvent(s, self.figcanvas, x, y, self.figcanvas._button, self.figcanvas._key, - guiEvent=event) + event = MouseEvent( + s, + self.figcanvas, + x, + y, + self.figcanvas._button, + self.figcanvas._key, + guiEvent=event) self.figcanvas.callbacks.process(s, event) def on_touch_up(self, event): @@ -171,13 +191,20 @@ def on_touch_up(self, event): pos_x, pos_y = self.pos real_x, real_y = x - pos_x, y - pos_y # self.figcanvas.button_release_event(x, real_y, 1, guiEvent=event) - + s = 'button_release_event' - event = MouseEvent(s, self.figcanvas, x, real_y, 1, self.figcanvas._key, guiEvent=event) + event = MouseEvent( + s, + self.figcanvas, + x, + real_y, + 1, + self.figcanvas._key, + guiEvent=event) self.figcanvas.callbacks.process(s, event) - self.figcanvas._button = None - - self._pressed = False + self.figcanvas._button = None + + self._pressed = False def _onSize(self, o, size): """ _onsize method """ @@ -194,50 +221,56 @@ def _onSize(self, o, size): winch = self._width / dpival hinch = self._height / dpival self.figure.set_size_inches(winch, hinch) - + s = 'resize_event' event = ResizeEvent(s, self.figcanvas) self.figcanvas.callbacks.process(s, event) - self.figcanvas.draw_idle() - - self.figcanvas.draw() + self.figcanvas.draw_idle() + + self.figcanvas.draw() def update_lim(self): """ update axis lim if zoombox is used""" - ax=self.axes + ax = self.axes + + self.do_update = False - self.do_update=False - - ax.set_xlim(left=min(self.x0_box,self.x1_box),right=max(self.x0_box,self.x1_box)) - ax.set_ylim(bottom=min(self.y0_box,self.y1_box),top=max(self.y0_box,self.y1_box)) + ax.set_xlim( + left=min( + self.x0_box, self.x1_box), right=max( + self.x0_box, self.x1_box)) + ax.set_ylim( + bottom=min( + self.y0_box, self.y1_box), top=max( + self.y0_box, self.y1_box)) def reset_box(self): """ reset zoombox and apply zoombox limit if zoombox option if selected""" # if min(abs(self._box_size[0]),abs(self._box_size[1]))>self.minzoom: # trans = self.axes.transData.inverted() - # self.x0_box, self.y0_box = trans.transform_point((self._box_pos[0]-self.pos[0], self._box_pos[1]-self.pos[1])) + # self.x0_box, self.y0_box = trans.transform_point((self._box_pos[0]-self.pos[0], self._box_pos[1]-self.pos[1])) # self.x1_box, self.y1_box = trans.transform_point((self._box_size[0]+self._box_pos[0]-self.pos[0], self._box_size[1]+self._box_pos[1]-self.pos[1])) # self.do_update=True - + self._box_size = 0, 0 self._box_pos = 0, 0 - self._alpha_box=0 + self._alpha_box = 0 - self._alpha_hor=0 - self._alpha_ver=0 + self._alpha_hor = 0 + self._alpha_ver = 0 self.invert_rect_hor = False self.invert_rect_ver = False - + def draw_box(self, event, x0, y0, x1, y1) -> None: """ Draw zoombox method - + Args: event: touch kivy event x0: x coordonnate init x1: y coordonnate of move touch y0: y coordonnate init y1: x coordonnate of move touch - + Return: None """ @@ -245,23 +278,24 @@ def draw_box(self, event, x0, y0, x1, y1) -> None: # Kivy coords y0 = pos_y + y0 y1 = pos_y + y1 - - if abs(y1-y0)>dp(5) or abs(x1-x0)>dp(5): - self._alpha_box=0.3 - self._alpha_rect=0 - if x1>x0: - self.invert_rect_ver=False + if abs(y1 - y0) > dp(5) or abs(x1 - x0) > dp(5): + self._alpha_box = 0.3 + self._alpha_rect = 0 + + if x1 > x0: + self.invert_rect_ver = False else: - self.invert_rect_ver=True - if y1>y0: - self.invert_rect_hor=False + self.invert_rect_ver = True + if y1 > y0: + self.invert_rect_hor = False else: - self.invert_rect_hor=True - + self.invert_rect_hor = True + self._box_pos = x0, y0 self._box_size = x1 - x0, y1 - y0 + class _FigureCanvas(FigureCanvasAgg): """Internal AGG Canvas""" @@ -286,7 +320,7 @@ def draw(self): def blit(self, bbox=None): """ Render the figure using agg (blit method). - """ + """ agg = self.get_renderer() w, h = agg.width, agg.height self.widget._bitmap = agg.buffer_rgba() @@ -294,7 +328,6 @@ def blit(self, bbox=None): self.widget.bt_h = h self.widget._draw_bitmap() -from kivy.factory import Factory Factory.register('MatplotFigureGeneral', MatplotFigureGeneral) diff --git a/kivy_matplotlib_widget/uix/graph_widget_scatter.py b/kivy_matplotlib_widget/uix/graph_widget_scatter.py index fc4dd2c..ac1d794 100644 --- a/kivy_matplotlib_widget/uix/graph_widget_scatter.py +++ b/kivy_matplotlib_widget/uix/graph_widget_scatter.py @@ -1,7 +1,29 @@ -""" MatplotFigure is based on https://github.com/jeysonmc/kivy_matplotlib +""" MatplotFigure is based on https://github.com/jeysonmc/kivy_matplotlib and kivy scatter """ +from kivy.factory import Factory +from kivy.clock import Clock +from kivy.core.window import Window +import matplotlib.transforms as mtransforms +import matplotlib.patches as mpatches +import matplotlib.lines as mlines +import matplotlib.image as mimage +from kivy.utils import get_color_from_hex +import numpy as np +from kivy.metrics import dp +from weakref import WeakKeyDictionary +from matplotlib.backend_bases import ResizeEvent +from matplotlib import cbook +from matplotlib.backends.backend_agg import FigureCanvasAgg +from matplotlib.colors import to_hex +from kivy.vector import Vector +from kivy.uix.widget import Widget +from kivy.properties import ObjectProperty, ListProperty, BooleanProperty, BoundedNumericProperty, AliasProperty, \ + NumericProperty, OptionProperty +from kivy.lang import Builder +from kivy.graphics.transformation import Matrix +from kivy.graphics.texture import Texture import math import copy @@ -10,30 +32,10 @@ selector_widgets_available = False try: selector_widgets_available = True - from kivy_matplotlib_widget.uix.selector_widget import ResizeRelativeLayout,LassoRelativeLayout,EllipseRelativeLayout,SpanRelativeLayout + from kivy_matplotlib_widget.uix.selector_widget import ResizeRelativeLayout, LassoRelativeLayout, EllipseRelativeLayout, SpanRelativeLayout except ImportError: print('Selector widgets are not available') -from kivy.graphics.texture import Texture -from kivy.graphics.transformation import Matrix -from kivy.lang import Builder -from kivy.properties import ObjectProperty, ListProperty, BooleanProperty, BoundedNumericProperty, AliasProperty, \ - NumericProperty, OptionProperty -from kivy.uix.widget import Widget -from kivy.vector import Vector -from matplotlib.colors import to_hex -from matplotlib.backends.backend_agg import FigureCanvasAgg -from matplotlib import cbook -from matplotlib.backend_bases import ResizeEvent -from weakref import WeakKeyDictionary -from kivy.metrics import dp -import numpy as np -from kivy.utils import get_color_from_hex -import matplotlib.image as mimage -import matplotlib.lines as mlines -import matplotlib.patches as mpatches -import matplotlib.transforms as mtransforms -from kivy.core.window import Window -from kivy.clock import Clock + class MatplotFigureScatter(Widget): """Widget to show a matplotlib figure in kivy. @@ -45,10 +47,10 @@ class MatplotFigureScatter(Widget): _box_pos = ListProperty([0, 0]) _box_size = ListProperty([0, 0]) _img_texture = ObjectProperty(None) - _alpha_box = NumericProperty(0) + _alpha_box = NumericProperty(0) _bitmap = None _pressed = False - do_update=False + do_update = False figcanvas = ObjectProperty(None) translation_touches = BoundedNumericProperty(1, min=1) do_scale = BooleanProperty(True) @@ -57,44 +59,52 @@ class MatplotFigureScatter(Widget): transform = ObjectProperty(Matrix()) _alpha_hor = NumericProperty(0) _alpha_ver = NumericProperty(0) - pos_x_rect_hor=NumericProperty(0) - pos_y_rect_hor=NumericProperty(0) - pos_x_rect_ver=NumericProperty(0) - pos_y_rect_ver=NumericProperty(0) + pos_x_rect_hor = NumericProperty(0) + pos_y_rect_hor = NumericProperty(0) + pos_x_rect_ver = NumericProperty(0) + pos_y_rect_ver = NumericProperty(0) invert_rect_ver = BooleanProperty(False) invert_rect_hor = BooleanProperty(False) legend_do_scroll_x = BooleanProperty(True) - legend_do_scroll_y = BooleanProperty(True) + legend_do_scroll_y = BooleanProperty(True) interactive_axis = BooleanProperty(False) do_pan_x = BooleanProperty(True) - do_pan_y = BooleanProperty(True) + do_pan_y = BooleanProperty(True) do_zoom_x = BooleanProperty(True) do_zoom_y = BooleanProperty(True) - fast_draw = BooleanProperty(True) #True will don't draw axis - xsorted = BooleanProperty(False) #to manage x sorted data + fast_draw = BooleanProperty(True) # True will don't draw axis + xsorted = BooleanProperty(False) # to manage x sorted data minzoom = NumericProperty(dp(20)) multi_xdata = BooleanProperty(False) multi_xdata_res = NumericProperty(dp(20)) - compare_xdata = BooleanProperty(False) + compare_xdata = BooleanProperty(False) hover_instance = ObjectProperty(None, allownone=True) nearest_hover_instance = ObjectProperty(None, allownone=True) compare_hover_instance = ObjectProperty(None, allownone=True) - disable_mouse_scrolling = BooleanProperty(False) - disable_double_tap = BooleanProperty(False) + disable_mouse_scrolling = BooleanProperty(False) + disable_double_tap = BooleanProperty(False) text_instance = None min_max_option = BooleanProperty(True) auto_zoom = BooleanProperty(False) - zoom_angle_detection=NumericProperty(15) #in degree + zoom_angle_detection = NumericProperty(15) # in degree auto_cursor = BooleanProperty(False) autoscale_visible_only = BooleanProperty(True) autoscale_axis = OptionProperty("both", options=["both", "x", "y"]) autoscale_tight = BooleanProperty(False) - desktop_mode = BooleanProperty(True) #change mouse hover for selector widget - current_selector = OptionProperty("None", - options = ["None",'rectangle','lasso','ellipse','span','custom']) - pick_minimum_radius=NumericProperty(dp(50)) + # change mouse hover for selector widget + desktop_mode = BooleanProperty(True) + current_selector = OptionProperty( + "None", + options=[ + "None", + 'rectangle', + 'lasso', + 'ellipse', + 'span', + 'custom']) + pick_minimum_radius = NumericProperty(dp(50)) pick_radius_axis = OptionProperty("both", options=["both", "x", "y"]) - + def on_figure(self, obj, value): self.figcanvas = _FigureCanvas(self.figure, self) self.figcanvas._isDrawn = False @@ -105,45 +115,45 @@ def on_figure(self, obj, value): self.height = h if len(self.figure.axes) > 0 and self.figure.axes[0]: - #add copy patch - ax=self.figure.axes[0] - patch_cpy=copy.copy(ax.patch) + # add copy patch + ax = self.figure.axes[0] + patch_cpy = copy.copy(ax.patch) patch_cpy.set_visible(False) for pos in ['right', 'top', 'bottom', 'left']: ax.spines[pos].set_zorder(10) patch_cpy.set_zorder(9) - self.background_patch_copy= ax.add_patch(patch_cpy) + self.background_patch_copy = ax.add_patch(patch_cpy) - #set xmin axes attribute + # set xmin axes attribute self.axes = self.figure.axes[0] - - #set default xmin/xmax and ymin/ymax - self.xmin,self.xmax = self.axes.get_xlim() - self.ymin,self.ymax = self.axes.get_ylim() + + # set default xmin/xmax and ymin/ymax + self.xmin, self.xmax = self.axes.get_xlim() + self.ymin, self.ymax = self.axes.get_ylim() if self.legend_instance: - #remove all legend_instance from parent widget + # remove all legend_instance from parent widget for current_legend in self.legend_instance: current_legend.parent.remove_widget(current_legend) - self.legend_instance=[] - + self.legend_instance = [] + if self.auto_cursor and len(self.figure.axes) > 0: self.register_lines(list(self.axes.lines)) self.register_scatters(list(self.axes.collections)) if self.selector and self.axes: self.selector.resize_wgt.ax = self.axes - + # Texture self._img_texture = Texture.create(size=(w, h)) - #close last figure in memory (avoid max figure warning) + # close last figure in memory (avoid max figure warning) matplotlib.pyplot.close() def __init__(self, **kwargs): super(MatplotFigureScatter, self).__init__(**kwargs) - - #figure info + + # figure info self.figure = None self.axes = None self.xmin = None @@ -151,114 +161,119 @@ def __init__(self, **kwargs): self.ymin = None self.ymax = None self.lines = [] - self.scatters =[] - - #option - self.touch_mode='pan' + self.scatters = [] + + # option + self.touch_mode = 'pan' self.hover_on = False - self.cursor_xaxis_formatter=None #used matplotlib formatter to display x cursor value - self.cursor_yaxis_formatter=None #used matplotlib formatter to display y cursor value + # used matplotlib formatter to display x cursor value + self.cursor_xaxis_formatter = None + # used matplotlib formatter to display y cursor value + self.cursor_yaxis_formatter = None - #zoom box coordonnate + # zoom box coordonnate self.x0_box = None self.y0_box = None self.x1_box = None self.y1_box = None - - #clear touches on touch up + + # clear touches on touch up self._touches = [] self._last_touch_pos = {} - - self.lines=[] - self.scatters=[] + + self.lines = [] + self.scatters = [] self.scatter_label = None - #background - self.background=None - self.background_patch_copy=None + # background + self.background = None + self.background_patch_copy = None - #manage adjust x and y + # manage adjust x and y self.anchor_x = None - self.anchor_y = None + self.anchor_y = None - #manage hover data + # manage hover data self.x_hover_data = None self.y_hover_data = None - #pan management + # pan management self.first_touch_pan = None - - #manage back and next event - if hasattr(cbook,'_Stack'): - #manage matplotlib version with no Stack (replace by _Stack) + + # manage back and next event + if hasattr(cbook, '_Stack'): + # manage matplotlib version with no Stack (replace by _Stack) self._nav_stack = cbook._Stack() else: - self._nav_stack = cbook.Stack() - self.set_history_buttons() - - #legend management + self._nav_stack = cbook.Stack() + self.set_history_buttons() + + # legend management self.legend_instance = [] - self.current_legend=None + self.current_legend = None - #selector management + # selector management self.kv_post_done = False - self.selector = None - + self.selector = None + self.bind(size=self._onSize) - - def on_kv_post(self,_): + + def on_kv_post(self, _): # if not self.selector: if self.current_selector != "None" and selector_widgets_available: if self.current_selector == 'rectangle': self.set_selector(ResizeRelativeLayout) elif self.current_selector == 'lasso': - self.set_selector(LassoRelativeLayout) + self.set_selector(LassoRelativeLayout) elif self.current_selector == 'ellipse': - self.set_selector(EllipseRelativeLayout) + self.set_selector(EllipseRelativeLayout) elif self.current_selector == 'span': - self.set_selector(SpanRelativeLayout) - self.kv_post_done=True + self.set_selector(SpanRelativeLayout) + self.kv_post_done = True - def transform_eval(self,x,axis): - custom_transform=axis.get_transform() + def transform_eval(self, x, axis): + custom_transform = axis.get_transform() return custom_transform.transform_non_affine(np.array([x]))[0] - - def inv_transform_eval(self,x,axis): - inv_custom_transform=axis.get_transform().inverted() + + def inv_transform_eval(self, x, axis): + inv_custom_transform = axis.get_transform().inverted() return inv_custom_transform.transform_non_affine(np.array([x]))[0] - - def on_current_selector(self,instance,value,*args): - + + def on_current_selector(self, instance, value, *args): + if self.kv_post_done and selector_widgets_available: if value == 'rectangle': self.set_selector(ResizeRelativeLayout) elif value == 'lasso': - self.set_selector(LassoRelativeLayout) + self.set_selector(LassoRelativeLayout) elif value == 'ellipse': - self.set_selector(EllipseRelativeLayout) + self.set_selector(EllipseRelativeLayout) elif self.current_selector == 'span': - self.set_selector(SpanRelativeLayout) + self.set_selector(SpanRelativeLayout) elif value == "None": if self.selector: - Window.unbind(mouse_pos=self.selector.resize_wgt.on_mouse_pos) + Window.unbind( + mouse_pos=self.selector.resize_wgt.on_mouse_pos) self.parent.remove_widget(self.selector) self.selector = None - - def set_selector(self,selector,*args): - selector_collection=None - selector_line=None + + def set_selector(self, selector, *args): + selector_collection = None + selector_line = None callback = None callback_clear = None if self.selector: selector_collection = self.selector.resize_wgt.collection selector_line = self.selector.resize_wgt.line callback = self.selector.resize_wgt.callback - callback_clear = self.selector.resize_wgt.callback_clear + callback_clear = self.selector.resize_wgt.callback_clear Window.unbind(mouse_pos=self.selector.resize_wgt.on_mouse_pos) self.parent.remove_widget(self.selector) - - self.selector = selector(figure_wgt=self,desktop_mode=self.desktop_mode) + + self.selector = selector( + figure_wgt=self, + desktop_mode=self.desktop_mode) self.selector.resize_wgt.ax = self.axes if selector_collection: self.set_collection() @@ -268,557 +283,597 @@ def set_selector(self,selector,*args): self.set_callback(callback) if callback_clear: self.set_callback_clear(callback_clear) - - self.parent.add_widget(self.selector) - - def set_collection(self): + + self.parent.add_widget(self.selector) + + def set_collection(self): self.selector.resize_wgt.ax = self.axes - collections = self.axes.collections - + collections = self.axes.collections + if collections: - self.selector.resize_wgt.set_collection(collections[0]) - - def set_line(self,line): - self.selector.resize_wgt.ax = self.axes - self.selector.resize_wgt.set_line(line) - - def set_callback(self,callback): + self.selector.resize_wgt.set_collection(collections[0]) + + def set_line(self, line): + self.selector.resize_wgt.ax = self.axes + self.selector.resize_wgt.set_line(line) + + def set_callback(self, callback): self.selector.resize_wgt.set_callback(callback) - - def set_callback_clear(self,callback): - self.selector.resize_wgt.set_callback_clear(callback) - - def register_lines(self,lines:list) -> None: + + def set_callback_clear(self, callback): + self.selector.resize_wgt.set_callback_clear(callback) + + def register_lines(self, lines: list) -> None: """ register lines method - + Args: lines (list): list of matplolib line class - + Return: - None - """ - #use sel,axes limit to avoid graph rescale - xmin,xmax = self.axes.get_xlim() - ymin,ymax = self.axes.get_ylim() - #create cross hair cusor - self.horizontal_line = self.axes.axhline(y=self.ymin,color='k', lw=0.8, ls='--', visible=False) - self.vertical_line = self.axes.axvline(x=self.xmin,color='k', lw=0.8, ls='--', visible=False) - - #register lines - self.lines=lines - - #cursor text - self.text = self.axes.text(1.0, 1.01, '', - transform=self.axes.transAxes, - ha='right') - - def register_scatters(self,scatters:list) -> None: + None + """ + # use sel,axes limit to avoid graph rescale + xmin, xmax = self.axes.get_xlim() + ymin, ymax = self.axes.get_ylim() + # create cross hair cusor + self.horizontal_line = self.axes.axhline( + y=self.ymin, color='k', lw=0.8, ls='--', visible=False) + self.vertical_line = self.axes.axvline( + x=self.xmin, color='k', lw=0.8, ls='--', visible=False) + + # register lines + self.lines = lines + + # cursor text + self.text = self.axes.text(1.0, 1.01, '', + transform=self.axes.transAxes, + ha='right') + + def register_scatters(self, scatters: list) -> None: """ register scatters method - + Args: scatters (list): list of matplolib scatters class - + Return: - None - """ - #register lines - self.scatters=scatters - - def set_cross_hair_visible(self, visible:bool) -> None: + None + """ + # register lines + self.scatters = scatters + + def set_cross_hair_visible(self, visible: bool) -> None: """ set curcor visibility - + Args: visible (bool): make cursor visble or not - + Return: None - - """ + + """ self.horizontal_line.set_visible(visible) self.vertical_line.set_visible(visible) self.text.set_visible(visible) def hover(self, event) -> None: """ hover cursor method (cursor to nearest value) - + Args: event: touch kivy event - + Return: None - + """ - - #if cursor is set -> hover is on + + # if cursor is set -> hover is on if self.hover_on: - #transform kivy x,y touch event to x,y data + # transform kivy x,y touch event to x,y data trans = self.axes.transData.inverted() - xdata, ydata = trans.transform_point((event.x - self.pos[0], event.y - self.pos[1])) - - #loop all register lines and find closest x,y data for each valid line - distance=[] - good_line=[] - good_index=[] - good_index2=[] - good_index2_scatter=[] - good_scatter=[] - good_index_scatter=[] + xdata, ydata = trans.transform_point( + (event.x - self.pos[0], event.y - self.pos[1])) + + # loop all register lines and find closest x,y data for each valid + # line + distance = [] + good_line = [] + good_index = [] + good_index2 = [] + good_index2_scatter = [] + good_scatter = [] + good_index_scatter = [] if self.multi_xdata: - xdata_res, ydata_dump = trans.transform_point((event.x-self.pos[0]+self.multi_xdata_res, - event.y - self.pos[1])) - delta = abs(xdata_res-xdata) - + xdata_res, ydata_dump = trans.transform_point( + (event.x - self.pos[0] + self.multi_xdata_res, event.y - self.pos[1])) + delta = abs(xdata_res - xdata) + for line in self.lines: - #get only visible lines - if line.get_visible(): - #get line x,y datas + # get only visible lines + if line.get_visible(): + # get line x,y datas self.x_cursor, self.y_cursor = line.get_xydata().T - - #check if line is not empty - if len(self.x_cursor)!=0: - - #find closest data index from touch (x axis) + + # check if line is not empty + if len(self.x_cursor) != 0: + + # find closest data index from touch (x axis) if self.xsorted: - index = min(np.searchsorted(self.x_cursor, xdata), len(self.y_cursor) - 1) + index = min( + np.searchsorted( + self.x_cursor, xdata), len( + self.y_cursor) - 1) else: index = np.argsort(abs(self.x_cursor - xdata))[0] - #get x data from index + # get x data from index x = self.x_cursor[index] - - #find ydata corresponding to xdata + + # find ydata corresponding to xdata if self.multi_xdata: - - #find closest ydata from lines - idx_good_y=np.where(abs(np.array(self.x_cursor) - x) len(good_line)-1: - #get datas from closest scatter + + line = None + scatter = None + + if idx_best > len(good_line) - 1: + # get datas from closest scatter idx_best -= len(good_line) - scatter=good_scatter[idx_best] + scatter = good_scatter[idx_best] self.x_cursor, self.y_cursor = scatter.get_offsets().T - + if self.multi_xdata: x = self.x_cursor[good_index2_scatter[idx_best]] - y = self.y_cursor[good_index2_scatter[idx_best]] + y = self.y_cursor[good_index2_scatter[idx_best]] else: x = self.x_cursor[good_index_scatter[idx_best]] - y = self.y_cursor[good_index_scatter[idx_best]] - - ax=scatter.axes + y = self.y_cursor[good_index_scatter[idx_best]] + + ax = scatter.axes else: - #get datas from closest line - line=good_line[idx_best] + # get datas from closest line + line = good_line[idx_best] self.x_cursor, self.y_cursor = line.get_xydata().T - + if self.multi_xdata: x = self.x_cursor[good_index2[idx_best]] - y = self.y_cursor[good_index2[idx_best]] + y = self.y_cursor[good_index2[idx_best]] else: x = self.x_cursor[good_index[idx_best]] - y = self.y_cursor[good_index[idx_best]] - ax=line.axes - + y = self.y_cursor[good_index[idx_best]] + ax = line.axes + if not self.hover_instance: self.set_cross_hair_visible(True) - + # update the cursor x,y data self.horizontal_line.set_ydata([y,]) self.vertical_line.set_xdata([x,]) - #x y label - if self.hover_instance: - xy_pos = ax.transData.transform([(x,y)]) + # x y label + if self.hover_instance: + xy_pos = ax.transData.transform([(x, y)]) self.x_hover_data = x self.y_hover_data = y - self.hover_instance.x_hover_pos=float(xy_pos[0][0]) + self.x - self.hover_instance.y_hover_pos=float(xy_pos[0][1]) + self.y - self.hover_instance.show_cursor=True - + self.hover_instance.x_hover_pos = float( + xy_pos[0][0]) + self.x + self.hover_instance.y_hover_pos = float( + xy_pos[0][1]) + self.y + self.hover_instance.show_cursor = True + if self.cursor_xaxis_formatter: x = self.cursor_xaxis_formatter.format_data(x) else: - x = ax.xaxis.get_major_formatter().format_data_short(x) + x = ax.xaxis.get_major_formatter().format_data_short(x) if self.cursor_yaxis_formatter: - y = self.cursor_yaxis_formatter.format_data(y) + y = self.cursor_yaxis_formatter.format_data(y) else: - y = ax.yaxis.get_major_formatter().format_data_short(y) - self.hover_instance.label_x_value=f"{x}" - self.hover_instance.label_y_value=f"{y}" - - self.hover_instance.xmin_line = float(ax.bbox.bounds[0]) + self.x - self.hover_instance.xmax_line = float(ax.bbox.bounds[0] + ax.bbox.bounds[2]) + self.x - self.hover_instance.ymin_line = float(ax.bbox.bounds[1]) + self.y - self.hover_instance.ymax_line = float(ax.bbox.bounds[1] + ax.bbox.bounds[3]) + self.y - - if self.scatter_label and idx_best > len(good_line)-1: - if self.multi_xdata: - self.hover_instance.custom_label = self.scatter_label[good_index2_scatter[idx_best]] - else: - self.hover_instance.custom_label = self.scatter_label[good_index_scatter[idx_best]] + y = ax.yaxis.get_major_formatter().format_data_short(y) + self.hover_instance.label_x_value = f"{x}" + self.hover_instance.label_y_value = f"{y}" + + self.hover_instance.xmin_line = float( + ax.bbox.bounds[0]) + self.x + self.hover_instance.xmax_line = float( + ax.bbox.bounds[0] + ax.bbox.bounds[2]) + self.x + self.hover_instance.ymin_line = float( + ax.bbox.bounds[1]) + self.y + self.hover_instance.ymax_line = float( + ax.bbox.bounds[1] + ax.bbox.bounds[3]) + self.y + + if self.scatter_label and idx_best > len(good_line) - 1: + if self.multi_xdata: + self.hover_instance.custom_label = self.scatter_label[ + good_index2_scatter[idx_best]] + else: + self.hover_instance.custom_label = self.scatter_label[ + good_index_scatter[idx_best]] if line: - self.hover_instance.custom_color = get_color_from_hex(to_hex(line.get_color())) + self.hover_instance.custom_color = get_color_from_hex( + to_hex(line.get_color())) elif scatter: if self.multi_xdata: - if len(self.x_cursor)==len(scatter.get_facecolors()): - color=scatter.get_facecolors()[good_index2_scatter[idx_best]] + if len( + self.x_cursor) == len( + scatter.get_facecolors()): + color = scatter.get_facecolors( + )[good_index2_scatter[idx_best]] else: color = scatter.get_facecolors() - self.hover_instance.custom_color = get_color_from_hex(to_hex(color)) + self.hover_instance.custom_color = get_color_from_hex( + to_hex(color)) else: - if len(self.x_cursor)==len(scatter.get_facecolors()): - color=scatter.get_facecolors()[good_index_scatter[idx_best]] + if len( + self.x_cursor) == len( + scatter.get_facecolors()): + color = scatter.get_facecolors( + )[good_index_scatter[idx_best]] else: - color = scatter.get_facecolors() - self.hover_instance.custom_color = get_color_from_hex(to_hex(color)) - - if self.hover_instance.x_hover_pos>self.x+self.axes.bbox.bounds[2] + self.axes.bbox.bounds[0] or \ - self.hover_instance.x_hover_posself.y+self.axes.bbox.bounds[1] + self.axes.bbox.bounds[3] or \ - self.hover_instance.y_hover_pos self.x + self.axes.bbox.bounds[2] + self.axes.bbox.bounds[0] or \ + self.hover_instance.x_hover_pos < self.x + self.axes.bbox.bounds[0] or \ + self.hover_instance.y_hover_pos > self.y + self.axes.bbox.bounds[1] + self.axes.bbox.bounds[3] or \ + self.hover_instance.y_hover_pos < self.y + self.axes.bbox.bounds[1]: + self.hover_instance.hover_outside_bound = True else: - self.hover_instance.hover_outside_bound=False - + self.hover_instance.hover_outside_bound = False + return - else: + else: if self.cursor_xaxis_formatter: x = self.cursor_xaxis_formatter.format_data(x) else: - x = ax.xaxis.get_major_formatter().format_data_short(x) + x = ax.xaxis.get_major_formatter().format_data_short(x) if self.cursor_yaxis_formatter: y = self.cursor_yaxis_formatter.format_data(y) else: - y = ax.yaxis.get_major_formatter().format_data_short(y) - - if self.scatter_label and idx_best > len(good_line)-1: - if self.multi_xdata: - self.text.set_text(f"{self.scatter_label[good_index2_scatter[idx_best]]} x={x}, y={y}") + y = ax.yaxis.get_major_formatter().format_data_short(y) + + if self.scatter_label and idx_best > len(good_line) - 1: + if self.multi_xdata: + self.text.set_text( + f"{self.scatter_label[good_index2_scatter[idx_best]]} x={x}, y={y}") else: - self.text.set_text(f"{self.scatter_label[good_index_scatter[idx_best]]} x={x}, y={y}") + self.text.set_text( + f"{self.scatter_label[good_index_scatter[idx_best]]} x={x}, y={y}") else: self.text.set_text(f"x={x}, y={y}") - #blit method (always use because same visual effect as draw) + # blit method (always use because same visual effect as draw) if self.background is None: self.set_cross_hair_visible(False) self.axes.figure.canvas.draw_idle() - self.axes.figure.canvas.flush_events() - self.background = self.axes.figure.canvas.copy_from_bbox(self.axes.figure.bbox) - self.set_cross_hair_visible(True) + self.axes.figure.canvas.flush_events() + self.background = self.axes.figure.canvas.copy_from_bbox( + self.axes.figure.bbox) + self.set_cross_hair_visible(True) self.axes.figure.canvas.restore_region(self.background) self.axes.draw_artist(self.text) self.axes.draw_artist(self.horizontal_line) self.axes.draw_artist(self.vertical_line) - - #draw (blit method) - self.axes.figure.canvas.blit(self.axes.bbox) + + # draw (blit method) + self.axes.figure.canvas.blit(self.axes.bbox) self.axes.figure.canvas.flush_events() - #if touch is too far, hide cross hair cursor + # if touch is too far, hide cross hair cursor else: - self.set_cross_hair_visible(False) + self.set_cross_hair_visible(False) if self.hover_instance: - self.hover_instance.x_hover_pos=self.x - self.hover_instance.y_hover_pos=self.y - self.hover_instance.show_cursor=False + self.hover_instance.x_hover_pos = self.x + self.hover_instance.y_hover_pos = self.y + self.hover_instance.show_cursor = False self.x_hover_data = None - self.y_hover_data = None + self.y_hover_data = None def autoscale(self): if self.disabled: return - ax=self.axes - no_visible = self.myrelim(ax,visible_only=self.autoscale_visible_only) + ax = self.axes + no_visible = self.myrelim(ax, visible_only=self.autoscale_visible_only) ax.autoscale_view(tight=self.autoscale_tight, - scalex=True if self.autoscale_axis!="y" else False, - scaley=True if self.autoscale_axis!="x" else False) - ax.autoscale(axis=self.autoscale_axis,tight=self.autoscale_tight) + scalex=True if self.autoscale_axis != "y" else False, + scaley=True if self.autoscale_axis != "x" else False) + ax.autoscale(axis=self.autoscale_axis, tight=self.autoscale_tight) current_xlim = ax.get_xlim() current_ylim = ax.get_ylim() - lim_collection,invert_xaxis,invert_yaxis = self.data_limit_collection(ax,visible_only=self.autoscale_visible_only) + lim_collection, invert_xaxis, invert_yaxis = self.data_limit_collection( + ax, visible_only=self.autoscale_visible_only) if lim_collection: - xchanged=False + xchanged = False if self.autoscale_tight: - current_margins = (0,0) + current_margins = (0, 0) else: current_margins = ax.margins() - - if self.autoscale_axis!="y": + + if self.autoscale_axis != "y": if invert_xaxis: - if lim_collection[0]>current_xlim[0] or no_visible: + if lim_collection[0] > current_xlim[0] or no_visible: ax.set_xlim(left=lim_collection[0]) - xchanged=True - if lim_collection[2]current_xlim[1] or no_visible: + xchanged = True + if lim_collection[2] > current_xlim[1] or no_visible: ax.set_xlim(right=lim_collection[2]) - xchanged=True - - #recalculed margin + xchanged = True + + # recalculed margin if xchanged: - xlim = ax.get_xlim() - ax.set_xlim(left=xlim[0] - current_margins[0]*(xlim[1]-xlim[0])) - ax.set_xlim(right=xlim[1] + current_margins[0]*(xlim[1]-xlim[0])) - - ychanged=False - - if self.autoscale_axis!="x": + xlim = ax.get_xlim() + ax.set_xlim(left=xlim[0] - + current_margins[0] * (xlim[1] - xlim[0])) + ax.set_xlim( + right=xlim[1] + current_margins[0] * (xlim[1] - xlim[0])) + + ychanged = False + + if self.autoscale_axis != "x": if invert_yaxis: - if lim_collection[1]>current_ylim[0] or no_visible: + if lim_collection[1] > current_ylim[0] or no_visible: ax.set_ylim(bottom=lim_collection[1]) - ychanged=True - if lim_collection[3]current_ylim[1] or no_visible: - ax.set_ylim(top=lim_collection[3]) - ychanged=True - + ychanged = True + if lim_collection[3] > current_ylim[1] or no_visible: + ax.set_ylim(top=lim_collection[3]) + ychanged = True + if ychanged: - ylim = ax.get_ylim() - ax.set_ylim(bottom=ylim[0] - current_margins[1]*(ylim[1]-ylim[0])) - ax.set_ylim(top=ylim[1] + current_margins[1]*(ylim[1]-ylim[0])) + ylim = ax.get_ylim() + ax.set_ylim( + bottom=ylim[0] - current_margins[1] * (ylim[1] - ylim[0])) + ax.set_ylim(top=ylim[1] + current_margins[1] + * (ylim[1] - ylim[0])) - ax.figure.canvas.draw_idle() - ax.figure.canvas.flush_events() + ax.figure.canvas.flush_events() ax.set_autoscale_on(False) - self.xmin,self.xmax = ax.get_xlim() - self.ymin,self.ymax = ax.get_ylim() - - def myrelim(self,ax, visible_only=False): - """ - Recompute the data limits based on current artists. - - At present, `.Collection` instances are not supported. - - Parameters - ---------- - visible_only : bool, default: False - Whether to exclude invisible artists. - """ - # Collections are deliberately not supported (yet); see - # the TODO note in artists.py. - ax.dataLim.ignore(True) - ax.dataLim.set_points(mtransforms.Bbox.null().get_points()) - ax.ignore_existing_data_limits = True - no_visible=True - for artist in ax._children: - if not visible_only or artist.get_visible(): - if isinstance(artist, mlines.Line2D): - ax._update_line_limits(artist) - no_visible=False - elif isinstance(artist, mpatches.Patch): - ax._update_patch_limits(artist) - no_visible=False - elif isinstance(artist, mimage.AxesImage): - ax._update_image_limits(artist) - no_visible=False - - return no_visible - - def data_limit_collection(self,ax,visible_only=False): + self.xmin, self.xmax = ax.get_xlim() + self.ymin, self.ymax = ax.get_ylim() + + def myrelim(self, ax, visible_only=False): + """ + Recompute the data limits based on current artists. + + At present, `.Collection` instances are not supported. + + Parameters + ---------- + visible_only : bool, default: False + Whether to exclude invisible artists. + """ + # Collections are deliberately not supported (yet); see + # the TODO note in artists.py. + ax.dataLim.ignore(True) + ax.dataLim.set_points(mtransforms.Bbox.null().get_points()) + ax.ignore_existing_data_limits = True + no_visible = True + for artist in ax._children: + if not visible_only or artist.get_visible(): + if isinstance(artist, mlines.Line2D): + ax._update_line_limits(artist) + no_visible = False + elif isinstance(artist, mpatches.Patch): + ax._update_patch_limits(artist) + no_visible = False + elif isinstance(artist, mimage.AxesImage): + ax._update_image_limits(artist) + no_visible = False + + return no_visible + + def data_limit_collection(self, ax, visible_only=False): datalim = None datalim_list = [] for collection in ax.collections: - eval_lim=True + eval_lim = True if visible_only: if not collection.get_visible(): - eval_lim=False - + eval_lim = False + if eval_lim: datalim_list.append(collection.get_datalim(ax.transData)) - invert_xaxis=False - invert_yaxis=False + invert_xaxis = False + invert_yaxis = False if datalim_list: if ax.xaxis_inverted(): - invert_xaxis=True + invert_xaxis = True if ax.yaxis_inverted(): - invert_yaxis=True - for i,current_datalim in enumerate(datalim_list): - if i==0: + invert_yaxis = True + for i, current_datalim in enumerate(datalim_list): + if i == 0: if invert_xaxis: xleft = current_datalim.x1 xright = current_datalim.x0 else: xleft = current_datalim.x0 - xright = current_datalim.x1 + xright = current_datalim.x1 if invert_yaxis: ybottom = current_datalim.y1 - ytop = current_datalim.y0 + ytop = current_datalim.y0 else: ybottom = current_datalim.y0 ytop = current_datalim.y1 - - else: + + else: if invert_xaxis: - if current_datalim.x1>xleft: + if current_datalim.x1 > xleft: xleft = current_datalim.x1 - if current_datalim.x0xright: + if current_datalim.x1 > xright: xright = current_datalim.x1 if invert_yaxis: - if current_datalim.y1>ybottom: + if current_datalim.y1 > ybottom: ybottom = current_datalim.y1 - if current_datalim.y0ytop: - ytop = current_datalim.y1 + if current_datalim.y1 > ytop: + ytop = current_datalim.y1 - datalim = [xleft,ybottom,xright,ytop] - - return datalim,invert_xaxis,invert_yaxis + datalim = [xleft, ybottom, xright, ytop] + + return datalim, invert_xaxis, invert_yaxis def home(self) -> None: """ reset data axis - + Return: None """ - #do nothing is all min/max are not set + # do nothing is all min/max are not set if self.xmin is not None and \ - self.xmax is not None and \ - self.ymin is not None and \ - self.ymax is not None: - + self.xmax is not None and \ + self.ymin is not None and \ + self.ymax is not None: + ax = self.axes - xleft,xright=ax.get_xlim() - ybottom,ytop=ax.get_ylim() - - #check inverted data + xleft, xright = ax.get_xlim() + ybottom, ytop = ax.get_ylim() + + # check inverted data inverted_x = False - if xleft>xright: - inverted_x=True + if xleft > xright: + inverted_x = True inverted_y = False - if ybottom>ytop: - inverted_y=True - + if ybottom > ytop: + inverted_y = True + if inverted_x: - ax.set_xlim(right=self.xmin,left=self.xmax) + ax.set_xlim(right=self.xmin, left=self.xmax) else: - ax.set_xlim(left=self.xmin,right=self.xmax) + ax.set_xlim(left=self.xmin, right=self.xmax) if inverted_y: - ax.set_ylim(top=self.ymin,bottom=self.ymax) + ax.set_ylim(top=self.ymin, bottom=self.ymax) else: - ax.set_ylim(bottom=self.ymin,top=self.ymax) - + ax.set_ylim(bottom=self.ymin, top=self.ymax) + ax.figure.canvas.draw_idle() - ax.figure.canvas.flush_events() + ax.figure.canvas.flush_events() def back(self, *args): """ @@ -843,15 +898,15 @@ def forward(self, *args): self._update_view() def push_current(self): - """Push the current view limits and position onto the stack.""" - self._nav_stack.push( - WeakKeyDictionary( - {ax: (ax._get_view(), - # Store both the original and modified positions. - (ax.get_position(True).frozen(), - ax.get_position().frozen())) - for ax in self.figure.axes})) - self.set_history_buttons() + """Push the current view limits and position onto the stack.""" + self._nav_stack.push( + WeakKeyDictionary( + {ax: (ax._get_view(), + # Store both the original and modified positions. + (ax.get_position(True).frozen(), + ax.get_position().frozen())) + for ax in self.figure.axes})) + self.set_history_buttons() def update(self): """Reset the Axes stack.""" @@ -874,7 +929,7 @@ def _update_view(self): # Restore both the original and modified positions ax._set_position(pos_orig, 'original') ax._set_position(pos_active, 'active') - self.figure.canvas.draw_idle() + self.figure.canvas.draw_idle() self.figure.canvas.flush_events() def set_history_buttons(self): @@ -882,13 +937,13 @@ def set_history_buttons(self): def reset_touch(self) -> None: """ reset touch - + Return: None """ self._touches = [] self._last_touch_pos = {} - + def _get_scale(self): """ kivy scatter _get_scale method """ p1 = Vector(*self.to_parent(0, 0)) @@ -940,42 +995,42 @@ def transform_with_touch(self, event): changed = False if len(self._touches) == self.translation_touches: - if self.touch_mode=='pan': + if self.touch_mode == 'pan': if self._nav_stack() is None: - self.push_current() + self.push_current() self.apply_pan(self.axes, event) - if self.touch_mode=='pan_x' or self.touch_mode=='pan_y' \ - or self.touch_mode=='adjust_x' or self.touch_mode=='adjust_y': + if self.touch_mode == 'pan_x' or self.touch_mode == 'pan_y' \ + or self.touch_mode == 'adjust_x' or self.touch_mode == 'adjust_y': if self._nav_stack() is None: - self.push_current() - self.apply_pan(self.axes, event, mode=self.touch_mode) - - elif self.touch_mode=='drag_legend': + self.push_current() + self.apply_pan(self.axes, event, mode=self.touch_mode) + + elif self.touch_mode == 'drag_legend': if self.legend_instance: - self.apply_drag_legend(self.axes, event) - - elif self.touch_mode=='zoombox': + self.apply_drag_legend(self.axes, event) + + elif self.touch_mode == 'zoombox': if self._nav_stack() is None: - self.push_current() + self.push_current() real_x, real_y = event.x - self.pos[0], event.y - self.pos[1] - #in case x_init is not create - if not hasattr(self,'x_init'): + # in case x_init is not create + if not hasattr(self, 'x_init'): self.x_init = event.x self.y_init = real_y - self.draw_box(event, self.x_init,self.y_init, event.x, real_y) - - #mode cursor - elif self.touch_mode=='cursor': - self.hover_on=True + self.draw_box(event, self.x_init, self.y_init, event.x, real_y) + + # mode cursor + elif self.touch_mode == 'cursor': + self.hover_on = True self.hover(event) - + changed = True - #note: avoid zoom in/out on touch mode zoombox - if len(self._touches) == 1:# + # note: avoid zoom in/out on touch mode zoombox + if len(self._touches) == 1: return changed - + # we have more than one touch... list of last known pos points = [Vector(self._last_touch_pos[t]) for t in self._touches if t is not event] @@ -1003,21 +1058,21 @@ def transform_with_touch(self, event): if self.auto_zoom: v1 = Vector(0, 10) angle = v1.angle(new_line) + 180 - if angle<0+self.zoom_angle_detection or angle>360-self.zoom_angle_detection: - self.do_zoom_x=False - self.do_zoom_y=True - elif angle>90-self.zoom_angle_detection and angle<90+self.zoom_angle_detection: - self.do_zoom_x=True - self.do_zoom_y=False - elif angle>180-self.zoom_angle_detection and angle<180+self.zoom_angle_detection: - self.do_zoom_x=False - self.do_zoom_y=True - elif angle>270-self.zoom_angle_detection and angle<270+self.zoom_angle_detection: - self.do_zoom_x=True - self.do_zoom_y=False + if angle < 0 + self.zoom_angle_detection or angle > 360 - self.zoom_angle_detection: + self.do_zoom_x = False + self.do_zoom_y = True + elif angle > 90 - self.zoom_angle_detection and angle < 90 + self.zoom_angle_detection: + self.do_zoom_x = True + self.do_zoom_y = False + elif angle > 180 - self.zoom_angle_detection and angle < 180 + self.zoom_angle_detection: + self.do_zoom_x = False + self.do_zoom_y = True + elif angle > 270 - self.zoom_angle_detection and angle < 270 + self.zoom_angle_detection: + self.do_zoom_x = True + self.do_zoom_y = False else: - self.do_zoom_x=True - self.do_zoom_y=True + self.do_zoom_x = True + self.do_zoom_y = True if self.do_scale: # scale = new_line.length() / old_line.length() @@ -1027,104 +1082,104 @@ def transform_with_touch(self, event): scale = self.scale_min / self.scale elif new_scale > self.scale_max: scale = self.scale_max / self.scale - - self.apply_zoom(scale, self.axes, anchor=anchor,new_line=new_line) + + self.apply_zoom(scale, self.axes, anchor=anchor, new_line=new_line) changed = True return changed - def on_motion(self,*args): + def on_motion(self, *args): '''Kivy Event to trigger mouse event on motion `enter_notify_event`. ''' if self._pressed or self.disabled: # Do not process this event if there's a touch_move return - + pos = args[1] newcoord = self.to_widget(pos[0], pos[1]) x = newcoord[0] y = newcoord[1] - inside = self.collide_point(x,y) - if inside: + inside = self.collide_point(x, y) + if inside: # will receive all motion events. if self.figcanvas and self.hover_instance: - #avoid in motion if touch is detected - if not len(self._touches)==0: + # avoid in motion if touch is detected + if not len(self._touches) == 0: return - FakeEventScatter.x=x - FakeEventScatter.y=y + FakeEventScatter.x = x + FakeEventScatter.y = y self.hover(FakeEventScatter) - def get_data_xy(self,x,y): + def get_data_xy(self, x, y): """ manage x y data in navigation bar TODO""" - return None,None - + return None, None + def on_touch_down(self, event): """ Manage Mouse/touch press """ if self.disabled: return - + x, y = event.x, event.y if self.collide_point(x, y) and self.figure: self._pressed = True if self.legend_instance: - select_legend=False + select_legend = False for current_legend in self.legend_instance: if current_legend.box.collide_point(x, y): - select_legend=True + select_legend = True self.current_legend = current_legend break if select_legend: - if self.touch_mode!='drag_legend': - return False + if self.touch_mode != 'drag_legend': + return False else: event.grab(self) self._touches.append(event) self._last_touch_pos[event] = event.pos - if len(self._touches)>1: - #new touch, reset background - self.background=None - - return True + if len(self._touches) > 1: + # new touch, reset background + self.background = None + + return True else: - self.current_legend = None - + self.current_legend = None + if event.is_mouse_scrolling: if not self.disable_mouse_scrolling: ax = self.axes self.zoom_factory(event, ax, base_scale=1.2) return True - elif event.is_double_tap: + elif event.is_double_tap: if not self.disable_double_tap: - if self.touch_mode!='selector': + if self.touch_mode != 'selector': self.home() return True - + else: - if self.touch_mode=='cursor': - self.hover_on=True - self.hover(event) - elif self.touch_mode=='zoombox': + if self.touch_mode == 'cursor': + self.hover_on = True + self.hover(event) + elif self.touch_mode == 'zoombox': real_x, real_y = x - self.pos[0], y - self.pos[1] - self.x_init=x - self.y_init=real_y - self.draw_box(event, x, real_y, x, real_y) + self.x_init = x + self.y_init = real_y + self.draw_box(event, x, real_y, x, real_y) - elif self.touch_mode=='minmax': + elif self.touch_mode == 'minmax': self.min_max(event) - elif self.touch_mode=='selector': - pass - + elif self.touch_mode == 'selector': + pass + event.grab(self) self._touches.append(event) self._last_touch_pos[event] = event.pos - if len(self._touches)>1: - #new touch, reset background - self.background=None - + if len(self._touches) > 1: + # new touch, reset background + self.background = None + return True else: @@ -1134,13 +1189,13 @@ def on_touch_move(self, event): """ Manage Mouse/touch move while pressed """ if self.disabled: return - + x, y = event.x, event.y if event.is_double_tap: if not self.disable_double_tap: - if self.touch_mode!='selector': - self.home() + if self.touch_mode != 'selector': + self.home() return True # scale/translate @@ -1156,89 +1211,91 @@ def on_touch_up(self, event): """ Manage Mouse/touch release """ if self.disabled: return - + # remove it from our saved touches if event in self._touches and event.grab_state: event.ungrab(self) del self._last_touch_pos[event] self._touches.remove(event) - if self.touch_mode=='pan' or self.touch_mode=='zoombox' or \ - self.touch_mode=='pan_x' or self.touch_mode=='pan_y' \ - or self.touch_mode=='adjust_x' or self.touch_mode=='adjust_y' \ - or self.touch_mode=='minmax': + if self.touch_mode == 'pan' or self.touch_mode == 'zoombox' or \ + self.touch_mode == 'pan_x' or self.touch_mode == 'pan_y' \ + or self.touch_mode == 'adjust_x' or self.touch_mode == 'adjust_y' \ + or self.touch_mode == 'minmax': self.push_current() if self.interactive_axis: - if self.touch_mode=='pan_x' or self.touch_mode=='pan_y' \ - or self.touch_mode=='adjust_x' or self.touch_mode=='adjust_y': - self.touch_mode='pan' - self.first_touch_pan=None - + if self.touch_mode == 'pan_x' or self.touch_mode == 'pan_y' \ + or self.touch_mode == 'adjust_x' or self.touch_mode == 'adjust_y': + self.touch_mode = 'pan' + self.first_touch_pan = None + x, y = event.x, event.y - if abs(self._box_size[0]) > 1 or abs(self._box_size[1]) > 1 or self.touch_mode=='zoombox': - self.reset_box() + if abs( + self._box_size[0]) > 1 or abs( + self._box_size[1]) > 1 or self.touch_mode == 'zoombox': + self.reset_box() if not self.collide_point(x, y) and self.do_update: - #update axis lim if zoombox is used and touch outside widget - self.update_lim() - ax=self.axes + # update axis lim if zoombox is used and touch outside widget + self.update_lim() + ax = self.axes ax.figure.canvas.draw_idle() - ax.figure.canvas.flush_events() + ax.figure.canvas.flush_events() return True - + # stop propagating if its within our bounds if self.collide_point(x, y) and self.figure: self._pressed = False if self.do_update: - self.update_lim() + self.update_lim() - self.anchor_x=None - self.anchor_y=None - - ax=self.axes - self.background=None + self.anchor_x = None + self.anchor_y = None + + ax = self.axes + self.background = None ax.figure.canvas.draw_idle() - ax.figure.canvas.flush_events() - + ax.figure.canvas.flush_events() + return True - def apply_zoom(self, scale_factor, ax, anchor=(0, 0),new_line=None): + def apply_zoom(self, scale_factor, ax, anchor=(0, 0), new_line=None): """ zoom touch method """ - if self.touch_mode=='selector': + if self.touch_mode == 'selector': return - + x = anchor[0] - y = anchor[1]-self.pos[1] + y = anchor[1] - self.pos[1] trans = ax.transData.inverted() - xdata, ydata = trans.transform_point((x+new_line.x, y+new_line.y)) - + xdata, ydata = trans.transform_point((x + new_line.x, y + new_line.y)) + cur_xlim = ax.get_xlim() - cur_ylim = ax.get_ylim() + cur_ylim = ax.get_ylim() + + scale = ax.get_xscale() + yscale = ax.get_yscale() - scale=ax.get_xscale() - yscale=ax.get_yscale() - if scale == 'linear': - old_min=cur_xlim[0] - old_max=cur_xlim[1] + old_min = cur_xlim[0] + old_max = cur_xlim[1] else: - min_=cur_xlim[0] - max_=cur_xlim[1] - old_min = self.transform_eval(min_,ax.yaxis) - xdata = self.transform_eval(xdata,ax.yaxis) - old_max = self.transform_eval(max_,ax.yaxis) + min_ = cur_xlim[0] + max_ = cur_xlim[1] + old_min = self.transform_eval(min_, ax.yaxis) + xdata = self.transform_eval(xdata, ax.yaxis) + old_max = self.transform_eval(max_, ax.yaxis) if yscale == 'linear': - yold_min=cur_ylim[0] - yold_max=cur_ylim[1] + yold_min = cur_ylim[0] + yold_max = cur_ylim[1] else: - ymin_=cur_ylim[0] - ymax_=cur_ylim[1] - yold_min = self.transform_eval(ymin_,ax.yaxis) - ydata = self.transform_eval(ydata,ax.yaxis) - yold_max = self.transform_eval(ymax_,ax.yaxis) + ymin_ = cur_ylim[0] + ymax_ = cur_ylim[1] + yold_min = self.transform_eval(ymin_, ax.yaxis) + ydata = self.transform_eval(ydata, ax.yaxis) + yold_max = self.transform_eval(ymax_, ax.yaxis) new_width = (old_max - old_min) * scale_factor new_height = (yold_max - yold_min) * scale_factor @@ -1248,255 +1305,325 @@ def apply_zoom(self, scale_factor, ax, anchor=(0, 0),new_line=None): if self.do_zoom_x: if scale == 'linear': - ax.set_xlim([xdata - new_width * (1 - relx), xdata + new_width * (relx)]) + ax.set_xlim([xdata - new_width * (1 - relx), + xdata + new_width * (relx)]) else: new_min = xdata - new_width * (1 - relx) new_max = xdata + new_width * (relx) try: - new_min, new_max = self.inv_transform_eval(new_min,ax.yaxis), self.inv_transform_eval(new_max,ax.yaxis) + new_min, new_max = self.inv_transform_eval( + new_min, ax.yaxis), self.inv_transform_eval( + new_max, ax.yaxis) except OverflowError: # Limit case new_min, new_max = min_, max_ if new_min <= 0. or new_max <= 0.: # Limit case - new_min, new_max = min_, max_ + new_min, new_max = min_, max_ ax.set_xlim([new_min, new_max]) - + if self.do_zoom_y: if yscale == 'linear': - ax.set_ylim([ydata - new_height * (1 - rely), ydata + new_height * (rely)]) + ax.set_ylim([ydata - new_height * (1 - rely), + ydata + new_height * (rely)]) else: new_ymin = ydata - new_height * (1 - rely) new_ymax = ydata + new_height * (rely) try: - new_ymin, new_ymax = self.inv_transform_eval(new_ymin,ax.yaxis), self.inv_transform_eval(new_ymax,ax.yaxis) + new_ymin, new_ymax = self.inv_transform_eval( + new_ymin, ax.yaxis), self.inv_transform_eval( + new_ymax, ax.yaxis) except OverflowError: # Limit case new_ymin, new_ymax = ymin_, ymax_ if new_ymin <= 0. or new_ymax <= 0.: # Limit case - new_ymin, new_ymax = ymin_, ymax_ + new_ymin, new_ymax = ymin_, ymax_ ax.set_ylim([new_ymin, new_ymax]) - if self.fast_draw: - #use blit method + if self.fast_draw: + # use blit method if self.background is None: self.background_patch_copy.set_visible(True) ax.figure.canvas.draw_idle() - ax.figure.canvas.flush_events() - self.background = ax.figure.canvas.copy_from_bbox(ax.figure.bbox) - self.background_patch_copy.set_visible(False) + ax.figure.canvas.flush_events() + self.background = ax.figure.canvas.copy_from_bbox( + ax.figure.bbox) + self.background_patch_copy.set_visible(False) ax.figure.canvas.restore_region(self.background) - + for line in ax.lines: ax.draw_artist(line) - + for scatter in self.scatters: self.axes.draw_artist(scatter) - - #TODO annotation do not render correctly on fast draw - #find annotation + + # TODO annotation do not render correctly on fast draw + # find annotation # for child in ax.get_children(): - # if isinstance(child, matplotlib.text.Annotation): - # self.axes.draw_artist(child) - + # if isinstance(child, matplotlib.text.Annotation): + # self.axes.draw_artist(child) + ax.figure.canvas.blit(ax.bbox) ax.figure.canvas.flush_events() - + self.update_hover() else: ax.figure.canvas.draw_idle() - ax.figure.canvas.flush_events() + ax.figure.canvas.flush_events() def apply_pan(self, ax, event, mode='pan'): """ pan method """ - + trans = ax.transData.inverted() - xdata, ydata = trans.transform_point((event.x-self.pos[0], event.y-self.pos[1])) - xpress, ypress = trans.transform_point((self._last_touch_pos[event][0]-self.pos[0], self._last_touch_pos[event][1]-self.pos[1])) + xdata, ydata = trans.transform_point( + (event.x - self.pos[0], event.y - self.pos[1])) + xpress, ypress = trans.transform_point( + (self._last_touch_pos[event][0] - self.pos[0], + self._last_touch_pos[event][1] - self.pos[1])) + + scale = ax.get_xscale() + yscale = ax.get_yscale() - scale=ax.get_xscale() - yscale=ax.get_yscale() - if scale == 'linear': dx = xdata - xpress else: - dx = self.transform_eval(xdata,ax.xaxis) - \ - self.transform_eval(xpress,ax.xaxis) - + dx = self.transform_eval(xdata, ax.xaxis) - \ + self.transform_eval(xpress, ax.xaxis) + if yscale == 'linear': dy = ydata - ypress else: - dy = self.transform_eval(ydata,ax.yaxis) - \ - self.transform_eval(ypress,ax.yaxis) + dy = self.transform_eval(ydata, ax.yaxis) - \ + self.transform_eval(ypress, ax.yaxis) + + xleft, xright = self.axes.get_xlim() + ybottom, ytop = self.axes.get_ylim() - xleft,xright=self.axes.get_xlim() - ybottom,ytop=self.axes.get_ylim() - - #check inverted data + # check inverted data inverted_x = False - if xleft>xright: - inverted_x=True - cur_xlim=(xright,xleft) + if xleft > xright: + inverted_x = True + cur_xlim = (xright, xleft) else: - cur_xlim=(xleft,xright) + cur_xlim = (xleft, xright) inverted_y = False - if ybottom>ytop: - inverted_y=True - cur_ylim=(ytop,ybottom) + if ybottom > ytop: + inverted_y = True + cur_ylim = (ytop, ybottom) else: - cur_ylim=(ybottom,ytop) - - if self.interactive_axis and self.touch_mode=='pan' and not self.first_touch_pan=='pan': - if (ydata < cur_ylim[0] and not inverted_y) or (ydata > cur_ylim[1] and inverted_y): - left_anchor_zone= (cur_xlim[1] - cur_xlim[0])*.2 + cur_xlim[0] - right_anchor_zone= (cur_xlim[1] - cur_xlim[0])*.8 + cur_xlim[0] + cur_ylim = (ybottom, ytop) + + if self.interactive_axis and self.touch_mode == 'pan' and not self.first_touch_pan == 'pan': + if (ydata < cur_ylim[0] and not inverted_y) or ( + ydata > cur_ylim[1] and inverted_y): + left_anchor_zone = ( + cur_xlim[1] - cur_xlim[0]) * .2 + cur_xlim[0] + right_anchor_zone = ( + cur_xlim[1] - cur_xlim[0]) * .8 + cur_xlim[0] if xdata < left_anchor_zone or xdata > right_anchor_zone: mode = 'adjust_x' else: mode = 'pan_x' self.touch_mode = mode elif (xdata < cur_xlim[0] and not inverted_x) or (xdata > cur_xlim[1] and inverted_x): - bottom_anchor_zone= (cur_ylim[1] - cur_ylim[0])*.2 + cur_ylim[0] - top_anchor_zone= (cur_ylim[1] - cur_ylim[0])*.8 + cur_ylim[0] + bottom_anchor_zone = ( + cur_ylim[1] - cur_ylim[0]) * .2 + cur_ylim[0] + top_anchor_zone = ( + cur_ylim[1] - cur_ylim[0]) * .8 + cur_ylim[0] if ydata < bottom_anchor_zone or ydata > top_anchor_zone: mode = 'adjust_y' else: - mode= 'pan_y' + mode = 'pan_y' self.touch_mode = mode else: - self.touch_mode = 'pan' + self.touch_mode = 'pan' - if not mode=='pan_y' and not mode=='adjust_y': - if mode=='adjust_x': + if not mode == 'pan_y' and not mode == 'adjust_y': + if mode == 'adjust_x': if self.anchor_x is None: - midpoint= (cur_xlim[1] + cur_xlim[0])/2 - if xdata>midpoint: - self.anchor_x='left' + midpoint = (cur_xlim[1] + cur_xlim[0]) / 2 + if xdata > midpoint: + self.anchor_x = 'left' else: - self.anchor_x='right' - if self.anchor_x=='left': - if xdata> cur_xlim[0]: + self.anchor_x = 'right' + if self.anchor_x == 'left': + if xdata > cur_xlim[0]: if scale == 'linear': cur_xlim -= dx else: try: - cur_xlim = [self.inv_transform_eval((self.transform_eval(cur_xlim[0],ax.xaxis) - dx),ax.xaxis), - self.inv_transform_eval((self.transform_eval(cur_xlim[1],ax.xaxis) - dx),ax.xaxis)] + cur_xlim = [ + self.inv_transform_eval( + (self.transform_eval( + cur_xlim[0], + ax.xaxis) - dx), + ax.xaxis), + self.inv_transform_eval( + (self.transform_eval( + cur_xlim[1], + ax.xaxis) - dx), + ax.xaxis)] except (ValueError, OverflowError): - cur_xlim = cur_xlim # Keep previous limits + cur_xlim = cur_xlim # Keep previous limits if inverted_x: - ax.set_xlim(cur_xlim[1],None) + ax.set_xlim(cur_xlim[1], None) else: - ax.set_xlim(None,cur_xlim[1]) + ax.set_xlim(None, cur_xlim[1]) else: - if xdata< cur_xlim[1]: + if xdata < cur_xlim[1]: if scale == 'linear': cur_xlim -= dx else: try: - cur_xlim = [self.inv_transform_eval((self.transform_eval(cur_xlim[0],ax.xaxis) - dx),ax.xaxis), - self.inv_transform_eval((self.transform_eval(cur_xlim[1],ax.xaxis) - dx),ax.xaxis)] + cur_xlim = [ + self.inv_transform_eval( + (self.transform_eval( + cur_xlim[0], + ax.xaxis) - dx), + ax.xaxis), + self.inv_transform_eval( + (self.transform_eval( + cur_xlim[1], + ax.xaxis) - dx), + ax.xaxis)] except (ValueError, OverflowError): - cur_xlim = cur_xlim # Keep previous limits + cur_xlim = cur_xlim # Keep previous limits if inverted_x: - ax.set_xlim(None,cur_xlim[0]) + ax.set_xlim(None, cur_xlim[0]) else: - ax.set_xlim(cur_xlim[0],None) + ax.set_xlim(cur_xlim[0], None) else: if scale == 'linear': cur_xlim -= dx else: try: - cur_xlim = [self.inv_transform_eval((self.transform_eval(cur_xlim[0],ax.xaxis) - dx),ax.xaxis), - self.inv_transform_eval((self.transform_eval(cur_xlim[1],ax.xaxis) - dx),ax.xaxis)] + cur_xlim = [ + self.inv_transform_eval( + (self.transform_eval( + cur_xlim[0], + ax.xaxis) - dx), + ax.xaxis), + self.inv_transform_eval( + (self.transform_eval( + cur_xlim[1], + ax.xaxis) - dx), + ax.xaxis)] except (ValueError, OverflowError): - cur_xlim = cur_xlim # Keep previous limits + cur_xlim = cur_xlim # Keep previous limits if inverted_x: - ax.set_xlim(cur_xlim[1],cur_xlim[0]) + ax.set_xlim(cur_xlim[1], cur_xlim[0]) else: ax.set_xlim(cur_xlim) - - if not mode=='pan_x' and not mode=='adjust_x': - if mode=='adjust_y': + + if not mode == 'pan_x' and not mode == 'adjust_x': + if mode == 'adjust_y': if self.anchor_y is None: - midpoint= (cur_ylim[1] + cur_ylim[0])/2 - if ydata>midpoint: - self.anchor_y='top' + midpoint = (cur_ylim[1] + cur_ylim[0]) / 2 + if ydata > midpoint: + self.anchor_y = 'top' else: - self.anchor_y='bottom' - - if self.anchor_y=='top': - if ydata> cur_ylim[0]: + self.anchor_y = 'bottom' + + if self.anchor_y == 'top': + if ydata > cur_ylim[0]: if yscale == 'linear': - cur_ylim -= dy - + cur_ylim -= dy + else: try: - cur_ylim = [self.inv_transform_eval((self.transform_eval(cur_ylim[0],ax.yaxis) - dy),ax.yaxis), - self.inv_transform_eval((self.transform_eval(cur_ylim[1],ax.yaxis) - dy),ax.yaxis)] + cur_ylim = [ + self.inv_transform_eval( + (self.transform_eval( + cur_ylim[0], + ax.yaxis) - dy), + ax.yaxis), + self.inv_transform_eval( + (self.transform_eval( + cur_ylim[1], + ax.yaxis) - dy), + ax.yaxis)] except (ValueError, OverflowError): - cur_ylim = cur_ylim # Keep previous limits + cur_ylim = cur_ylim # Keep previous limits if inverted_y: - ax.set_ylim(cur_ylim[1],None) + ax.set_ylim(cur_ylim[1], None) else: - ax.set_ylim(None,cur_ylim[1]) + ax.set_ylim(None, cur_ylim[1]) else: - if ydata< cur_ylim[1]: + if ydata < cur_ylim[1]: if yscale == 'linear': cur_ylim -= dy - + else: try: - cur_ylim = [self.inv_transform_eval((self.transform_eval(cur_ylim[0],ax.yaxis) - dy),ax.yaxis), - self.inv_transform_eval((self.transform_eval(cur_ylim[1],ax.yaxis) - dy),ax.yaxis)] + cur_ylim = [ + self.inv_transform_eval( + (self.transform_eval( + cur_ylim[0], + ax.yaxis) - dy), + ax.yaxis), + self.inv_transform_eval( + (self.transform_eval( + cur_ylim[1], + ax.yaxis) - dy), + ax.yaxis)] except (ValueError, OverflowError): - cur_ylim = cur_ylim # Keep previous limits + cur_ylim = cur_ylim # Keep previous limits if inverted_y: - ax.set_ylim(None,cur_ylim[0]) + ax.set_ylim(None, cur_ylim[0]) else: - ax.set_ylim(cur_ylim[0],None) - else: + ax.set_ylim(cur_ylim[0], None) + else: if yscale == 'linear': - cur_ylim -= dy - + cur_ylim -= dy + else: try: - cur_ylim = [self.inv_transform_eval((self.transform_eval(cur_ylim[0],ax.yaxis) - dy),ax.yaxis), - self.inv_transform_eval((self.transform_eval(cur_ylim[1],ax.yaxis) - dy),ax.yaxis)] + cur_ylim = [ + self.inv_transform_eval( + (self.transform_eval( + cur_ylim[0], + ax.yaxis) - dy), + ax.yaxis), + self.inv_transform_eval( + (self.transform_eval( + cur_ylim[1], + ax.yaxis) - dy), + ax.yaxis)] except (ValueError, OverflowError): - cur_ylim = cur_ylim # Keep previous limits + cur_ylim = cur_ylim # Keep previous limits if inverted_y: - ax.set_ylim(cur_ylim[1],cur_ylim[0]) + ax.set_ylim(cur_ylim[1], cur_ylim[0]) else: ax.set_ylim(cur_ylim) if self.first_touch_pan is None: - self.first_touch_pan=self.touch_mode - - if self.fast_draw: - #use blit method + self.first_touch_pan = self.touch_mode + + if self.fast_draw: + # use blit method if self.background is None: self.background_patch_copy.set_visible(True) ax.figure.canvas.draw_idle() - ax.figure.canvas.flush_events() - self.background = ax.figure.canvas.copy_from_bbox(ax.figure.bbox) - self.background_patch_copy.set_visible(False) - ax.figure.canvas.restore_region(self.background) - + ax.figure.canvas.flush_events() + self.background = ax.figure.canvas.copy_from_bbox( + ax.figure.bbox) + self.background_patch_copy.set_visible(False) + ax.figure.canvas.restore_region(self.background) + for line in ax.lines: ax.draw_artist(line) - + for scatter in ax.collections: self.axes.draw_artist(scatter) - - #TODO annotation do not render correctly on fast draw - #find annotation + + # TODO annotation do not render correctly on fast draw + # find annotation # for child in ax.get_children(): - # if isinstance(child, matplotlib.text.Annotation): - # self.axes.draw_artist(child) - + # if isinstance(child, matplotlib.text.Annotation): + # self.axes.draw_artist(child) + ax.figure.canvas.blit(ax.bbox) - ax.figure.canvas.flush_events() - + ax.figure.canvas.flush_events() + self.update_hover() - + else: ax.figure.canvas.draw_idle() ax.figure.canvas.flush_events() @@ -1504,284 +1631,361 @@ def apply_pan(self, ax, event, mode='pan'): def update_hover(self): """ update hover on fast draw (if exist)""" if self.hover_instance: - #update hover pos if needed - if self.hover_instance.show_cursor and self.x_hover_data is not None and self.y_hover_data is not None: - xy_pos = self.axes.transData.transform([(self.x_hover_data,self.y_hover_data)]) - self.hover_instance.x_hover_pos=float(xy_pos[0][0]) + self.x - self.hover_instance.y_hover_pos=float(xy_pos[0][1]) + self.y - - self.hover_instance.xmin_line = float(self.axes.bbox.bounds[0]) + self.x - self.hover_instance.xmax_line = float(self.axes.bbox.bounds[0] + self.axes.bbox.bounds[2]) + self.x - self.hover_instance.ymin_line = float(self.axes.bbox.bounds[1]) + self.y - self.hover_instance.ymax_line = float(self.axes.bbox.bounds[1] + self.axes.bbox.bounds[3] )+ self.y - - if self.hover_instance.x_hover_pos>self.x+self.axes.bbox.bounds[2] + self.axes.bbox.bounds[0] or \ - self.hover_instance.x_hover_posself.y+self.axes.bbox.bounds[1] + self.axes.bbox.bounds[3] or \ - self.hover_instance.y_hover_pos self.x + self.axes.bbox.bounds[2] + self.axes.bbox.bounds[0] or \ + self.hover_instance.x_hover_pos < self.x + self.axes.bbox.bounds[0] or \ + self.hover_instance.y_hover_pos > self.y + self.axes.bbox.bounds[1] + self.axes.bbox.bounds[3] or \ + self.hover_instance.y_hover_pos < self.y + self.axes.bbox.bounds[1]: + self.hover_instance.hover_outside_bound = True else: - self.hover_instance.hover_outside_bound=False - - def update_selector(self,*args): + self.hover_instance.hover_outside_bound = False + + def update_selector(self, *args): """ update selector on fast draw (if exist)""" if self.selector: - #update selector pos if needed - if self.selector.resize_wgt.verts and (len(args)!=0 or self.touch_mode!='selector'): + # update selector pos if needed + if self.selector.resize_wgt.verts and ( + len(args) != 0 or self.touch_mode != 'selector'): resize_wgt = self.selector.resize_wgt - if hasattr(resize_wgt,'shapes'): - #lasso widget or ellipse + if hasattr(resize_wgt, 'shapes'): + # lasso widget or ellipse if resize_wgt.shapes: - if hasattr(resize_wgt.shapes[0],'radius_x'): - #ellipse widget - xy_pos = resize_wgt.ax.transData.transform([(resize_wgt.verts[1][0],resize_wgt.verts[1][1])]) - new_pos=resize_wgt.to_widget(*(float(xy_pos[0][0]),float(xy_pos[0][1]))) + if hasattr(resize_wgt.shapes[0], 'radius_x'): + # ellipse widget + xy_pos = resize_wgt.ax.transData.transform( + [(resize_wgt.verts[1][0], resize_wgt.verts[1][1])]) + new_pos = resize_wgt.to_widget( + *(float(xy_pos[0][0]), float(xy_pos[0][1]))) pos0 = new_pos[0] + self.x - pos1 = new_pos[1] + self.y - - xy_pos2 = resize_wgt.ax.transData.transform([(resize_wgt.verts[2][0],resize_wgt.verts[2][1])]) - new_pos2=resize_wgt.to_widget(*(float(xy_pos2[0][0]),float(xy_pos2[0][1]))) + pos1 = new_pos[1] + self.y + + xy_pos2 = resize_wgt.ax.transData.transform( + [(resize_wgt.verts[2][0], resize_wgt.verts[2][1])]) + new_pos2 = resize_wgt.to_widget( + *(float(xy_pos2[0][0]), float(xy_pos2[0][1]))) pos0_2 = new_pos2[0] + self.x - pos1_2 = new_pos2[1] + self.y - - current_shape=resize_wgt.shapes[0] + pos1_2 = new_pos2[1] + self.y + + current_shape = resize_wgt.shapes[0] dataxy1 = current_shape.selection_point_inst.points - dataxy2 = current_shape.selection_point_inst2.points - - #note: the 2 first points are the same in current_shape.points + dataxy2 = current_shape.selection_point_inst2.points + + # note: the 2 first points are the same in + # current_shape.points pos0_old = dataxy1[0] pos1_old = dataxy1[1] - + pos0_2_old = dataxy2[0] - pos1_2_old = dataxy2[1] - - old_length = np.sqrt((pos0_2_old - pos0_old)**2 + (pos1_2_old - pos1_old)**2) - new_length = np.sqrt((pos0_2 - pos0)**2 + (pos1_2 - pos1)**2) - + pos1_2_old = dataxy2[1] + + old_length = np.sqrt( + (pos0_2_old - pos0_old) ** 2 + + (pos1_2_old - pos1_old) ** 2) + new_length = np.sqrt( + (pos0_2 - pos0)**2 + (pos1_2 - pos1)**2) + scale = float(new_length / old_length) - - xy_pos3 = resize_wgt.ax.transData.transform([(resize_wgt.verts[0][0],resize_wgt.verts[0][1])]) - new_pos3=resize_wgt.to_widget(*(float(xy_pos3[0][0]),float(xy_pos3[0][1]))) + + xy_pos3 = resize_wgt.ax.transData.transform( + [(resize_wgt.verts[0][0], resize_wgt.verts[0][1])]) + new_pos3 = resize_wgt.to_widget( + *(float(xy_pos3[0][0]), float(xy_pos3[0][1]))) pos0_c = new_pos3[0] + self.x pos1_c = new_pos3[1] + self.y - + for s in resize_wgt.shapes: s.rescale(scale) - + for s in resize_wgt.shapes: - s.translate(pos=(pos0_c,pos1_c)) - - xmin,xmax,ymin,ymax = resize_wgt.shapes[0].get_min_max() + s.translate(pos=(pos0_c, pos1_c)) + + xmin, xmax, ymin, ymax = resize_wgt.shapes[0].get_min_max( + ) else: - #lasso widget - xy_pos = resize_wgt.ax.transData.transform([(resize_wgt.verts[0][0],resize_wgt.verts[0][1])]) - new_pos=resize_wgt.to_widget(*(float(xy_pos[0][0]),float(xy_pos[0][1]))) + # lasso widget + xy_pos = resize_wgt.ax.transData.transform( + [(resize_wgt.verts[0][0], resize_wgt.verts[0][1])]) + new_pos = resize_wgt.to_widget( + *(float(xy_pos[0][0]), float(xy_pos[0][1]))) pos0 = new_pos[0] + self.x - pos1 = new_pos[1] + self.y - - xy_pos2 = resize_wgt.ax.transData.transform([(resize_wgt.verts[1][0],resize_wgt.verts[1][1])]) - new_pos2=resize_wgt.to_widget(*(float(xy_pos2[0][0]),float(xy_pos2[0][1]))) + pos1 = new_pos[1] + self.y + + xy_pos2 = resize_wgt.ax.transData.transform( + [(resize_wgt.verts[1][0], resize_wgt.verts[1][1])]) + new_pos2 = resize_wgt.to_widget( + *(float(xy_pos2[0][0]), float(xy_pos2[0][1]))) pos0_2 = new_pos2[0] + self.x - pos1_2 = new_pos2[1] + self.y - - current_shape=resize_wgt.shapes[0] - dataxy = np.array(current_shape.points).reshape(-1,2) - - #note: the 2 first points are the same in current_shape.points + pos1_2 = new_pos2[1] + self.y + + current_shape = resize_wgt.shapes[0] + dataxy = np.array( + current_shape.points).reshape(-1, 2) + + # note: the 2 first points are the same in + # current_shape.points pos0_old = dataxy[1][0] pos1_old = dataxy[1][1] - + pos0_2_old = dataxy[2][0] pos1_2_old = dataxy[2][1] - - old_length = np.sqrt((pos0_2_old - pos0_old)**2 + (pos1_2_old - pos1_old)**2) - new_length = np.sqrt((pos0_2 - pos0)**2 + (pos1_2 - pos1)**2) - + + old_length = np.sqrt( + (pos0_2_old - pos0_old) ** 2 + + (pos1_2_old - pos1_old) ** 2) + new_length = np.sqrt( + (pos0_2 - pos0)**2 + (pos1_2 - pos1)**2) + scale = new_length / old_length - + for s in resize_wgt.shapes: s.rescale(scale) - + for s in resize_wgt.shapes: - s.translate(pos=(pos0,pos1)) - + s.translate(pos=(pos0, pos1)) + xmax, ymax = dataxy.max(axis=0) - xmin, ymin = dataxy.min(axis=0) - - if self.collide_point(*resize_wgt.to_window(xmin,ymin)) and \ - self.collide_point(*resize_wgt.to_window(xmax,ymax)): + xmin, ymin = dataxy.min(axis=0) + + if self.collide_point( + * + resize_wgt.to_window( + xmin, + ymin)) and self.collide_point( + * + resize_wgt.to_window( + xmax, + ymax)): resize_wgt.opacity = 1 else: - resize_wgt.opacity = 0 - - elif self.selector.resize_wgt.verts and (len(args)!=0 or self.touch_mode!='selector'): + resize_wgt.opacity = 0 + + elif self.selector.resize_wgt.verts and (len(args) != 0 or self.touch_mode != 'selector'): resize_wgt = self.selector.resize_wgt if not (resize_wgt.size[0] > 1 and resize_wgt.size[1] > 1): return - - #rectangle or spann selector - if hasattr(resize_wgt,'span_orientation'): - #span selector + + # rectangle or spann selector + if hasattr(resize_wgt, 'span_orientation'): + # span selector if resize_wgt.span_orientation == 'vertical': - xy_pos = resize_wgt.ax.transData.transform([(resize_wgt.verts[0][0],resize_wgt.verts[0][1])]) - new_pos=resize_wgt.to_widget(*(float(xy_pos[0][0]),float(xy_pos[0][1]))) + xy_pos = resize_wgt.ax.transData.transform( + [(resize_wgt.verts[0][0], resize_wgt.verts[0][1])]) + new_pos = resize_wgt.to_widget( + *(float(xy_pos[0][0]), float(xy_pos[0][1]))) resize_wgt.pos[0] = new_pos[0] + self.x - - top_bound = float(self.y +resize_wgt.ax.bbox.bounds[3] + resize_wgt.ax.bbox.bounds[1]) - bottom_bound = float(self.y +resize_wgt.ax.bbox.bounds[1]) + + top_bound = float( + self.y + + resize_wgt.ax.bbox.bounds[3] + + resize_wgt.ax.bbox.bounds[1]) + bottom_bound = float( + self.y + resize_wgt.ax.bbox.bounds[1]) resize_wgt.pos[1] = bottom_bound - self.y - - #recalcul size - xy_pos2 = resize_wgt.ax.transData.transform([(resize_wgt.verts[3][0],resize_wgt.verts[3][1])]) - resize_wgt.size[0] = float(xy_pos2[0][0] - xy_pos[0][0]) - resize_wgt.size[1] = top_bound-bottom_bound - + + # recalcul size + xy_pos2 = resize_wgt.ax.transData.transform( + [(resize_wgt.verts[3][0], resize_wgt.verts[3][1])]) + resize_wgt.size[0] = float( + xy_pos2[0][0] - xy_pos[0][0]) + resize_wgt.size[1] = top_bound - bottom_bound + elif resize_wgt.span_orientation == 'horizontal': - xy_pos = resize_wgt.ax.transData.transform([(resize_wgt.verts[0][0],resize_wgt.verts[0][1])]) - new_pos=resize_wgt.to_widget(*(float(xy_pos[0][0]),float(xy_pos[0][1]))) - left_bound = float(self.x +resize_wgt.ax.bbox.bounds[0]) - right_bound = float(self.x +resize_wgt.ax.bbox.bounds[2] +resize_wgt.ax.bbox.bounds[0] ) - - width = right_bound-left_bound - - left_bound,right_bound = resize_wgt.to_widget(left_bound,right_bound) - + xy_pos = resize_wgt.ax.transData.transform( + [(resize_wgt.verts[0][0], resize_wgt.verts[0][1])]) + new_pos = resize_wgt.to_widget( + *(float(xy_pos[0][0]), float(xy_pos[0][1]))) + left_bound = float( + self.x + resize_wgt.ax.bbox.bounds[0]) + right_bound = float( + self.x + resize_wgt.ax.bbox.bounds[2] + + resize_wgt.ax.bbox.bounds[0]) + + width = right_bound - left_bound + + left_bound, right_bound = resize_wgt.to_widget( + left_bound, right_bound) + resize_wgt.pos[0] = left_bound resize_wgt.pos[1] = new_pos[1] + self.y - #recalcul size - xy_pos2 = resize_wgt.ax.transData.transform([(resize_wgt.verts[0][1],resize_wgt.verts[1][1])]) + # recalcul size + xy_pos2 = resize_wgt.ax.transData.transform( + [(resize_wgt.verts[0][1], resize_wgt.verts[1][1])]) resize_wgt.size[0] = width - resize_wgt.size[1] = float(xy_pos2[0][1] - xy_pos[0][1]) - + resize_wgt.size[1] = float( + xy_pos2[0][1] - xy_pos[0][1]) + else: - #rectangle selector - - #update all selector pts - #recalcul pos - xy_pos = resize_wgt.ax.transData.transform([(resize_wgt.verts[0][0],resize_wgt.verts[0][1])]) - new_pos=resize_wgt.to_widget(*(float(xy_pos[0][0]),float(xy_pos[0][1]))) + # rectangle selector + + # update all selector pts + # recalcul pos + xy_pos = resize_wgt.ax.transData.transform( + [(resize_wgt.verts[0][0], resize_wgt.verts[0][1])]) + new_pos = resize_wgt.to_widget( + *(float(xy_pos[0][0]), float(xy_pos[0][1]))) resize_wgt.pos[0] = new_pos[0] + self.x resize_wgt.pos[1] = new_pos[1] + self.y - - #recalcul size - xy_pos2 = resize_wgt.ax.transData.transform([(resize_wgt.verts[2][0],resize_wgt.verts[2][1])]) - resize_wgt.size[0] = float(xy_pos2[0][0] - xy_pos[0][0]) - resize_wgt.size[1] = float(xy_pos2[0][1] - xy_pos[0][1]) - - if self.collide_point(*resize_wgt.to_window(resize_wgt.pos[0],resize_wgt.pos[1])) and \ - self.collide_point(*resize_wgt.to_window(resize_wgt.pos[0] + resize_wgt.size[0],resize_wgt.pos[1]+ resize_wgt.size[1])): + + # recalcul size + xy_pos2 = resize_wgt.ax.transData.transform( + [(resize_wgt.verts[2][0], resize_wgt.verts[2][1])]) + resize_wgt.size[0] = float( + xy_pos2[0][0] - xy_pos[0][0]) + resize_wgt.size[1] = float( + xy_pos2[0][1] - xy_pos[0][1]) + + if self.collide_point( + * + resize_wgt.to_window( + resize_wgt.pos[0], + resize_wgt.pos[1])) and self.collide_point( + * + resize_wgt.to_window( + resize_wgt.pos[0] + + resize_wgt.size[0], + resize_wgt.pos[1] + + resize_wgt.size[1])): resize_wgt.opacity = 1 else: - resize_wgt.opacity = 0 + resize_wgt.opacity = 0 def min_max(self, event): """ manage min/max touch mode """ - ax=self.axes + ax = self.axes xlabelbottom = ax.xaxis._major_tick_kw.get('tick1On') ylabelleft = ax.yaxis._major_tick_kw.get('tick1On') - if xlabelbottom and event.x>self.x +ax.bbox.bounds[0] and \ - event.x self.x + ax.bbox.bounds[0] and \ + event.x < self.x + ax.bbox.bounds[2] + ax.bbox.bounds[0] and \ + event.y < self.y + ax.bbox.bounds[1]: - right_lim = self.x+ax.bbox.bounds[2]+ax.bbox.bounds[0] - left_lim = self.x+ax.bbox.bounds[0] - left_anchor_zone= (right_lim - left_lim)*.2 + left_lim - right_anchor_zone= (right_lim - left_lim)*.8 + left_lim + right_lim = self.x + ax.bbox.bounds[2] + ax.bbox.bounds[0] + left_lim = self.x + ax.bbox.bounds[0] + left_anchor_zone = (right_lim - left_lim) * .2 + left_lim + right_anchor_zone = (right_lim - left_lim) * .8 + left_lim - if event.x < left_anchor_zone or event.x > right_anchor_zone: + if event.x < left_anchor_zone or event.x > right_anchor_zone: if self.text_instance: if not self.text_instance.show_text: - midpoint = ((self.x+ax.bbox.bounds[2] + ax.bbox.bounds[0]) + (self.x+ax.bbox.bounds[0]))/2 + midpoint = ( + (self.x + ax.bbox.bounds[2] + ax.bbox.bounds[0]) + + (self.x + ax.bbox.bounds[0])) / 2 if event.x < midpoint: - anchor='left' - self.text_instance.x_text_pos=float(self.x+ax.bbox.bounds[0]) - self.text_instance.y_text_pos=float(self.y+ax.bbox.bounds[1]) - self.text_instance.text_height + anchor = 'left' + self.text_instance.x_text_pos = float( + self.x + ax.bbox.bounds[0]) + self.text_instance.y_text_pos = float( + self.y + ax.bbox.bounds[1]) - self.text_instance.text_height self.text_instance.offset_text = False else: - anchor='right' - self.text_instance.x_text_pos=float(self.x+ax.bbox.bounds[2] + ax.bbox.bounds[0]) - self.text_instance.y_text_pos=float(self.y+ax.bbox.bounds[1]) - self.text_instance.text_height + anchor = 'right' + self.text_instance.x_text_pos = float( + self.x + ax.bbox.bounds[2] + ax.bbox.bounds[0]) + self.text_instance.y_text_pos = float( + self.y + ax.bbox.bounds[1]) - self.text_instance.text_height self.text_instance.offset_text = True self.text_instance.current_axis = ax - self.text_instance.kind = {'axis':'x','anchor':anchor} + self.text_instance.kind = { + 'axis': 'x', 'anchor': anchor} - self.text_instance.show_text=True + self.text_instance.show_text = True return - elif ylabelleft and event.xself.y + ax.bbox.bounds[1]: + elif ylabelleft and event.x < self.x + ax.bbox.bounds[0] and \ + event.y < self.y + ax.bbox.bounds[1] + ax.bbox.bounds[3] and \ + event.y > self.y + ax.bbox.bounds[1]: - top_lim = self.y+ax.bbox.bounds[3]+ax.bbox.bounds[1] - bottom_lim = self.y+ax.bbox.bounds[1] - bottom_anchor_zone= (top_lim - bottom_lim)*.2 + bottom_lim - top_anchor_zone= (top_lim - bottom_lim)*.8 + bottom_lim + top_lim = self.y + ax.bbox.bounds[3] + ax.bbox.bounds[1] + bottom_lim = self.y + ax.bbox.bounds[1] + bottom_anchor_zone = (top_lim - bottom_lim) * .2 + bottom_lim + top_anchor_zone = (top_lim - bottom_lim) * .8 + bottom_lim if event.y < bottom_anchor_zone or event.y > top_anchor_zone: if self.text_instance: if not self.text_instance.show_text: - midpoint = ((self.y+ax.bbox.bounds[3] + ax.bbox.bounds[1]) + (self.y+ax.bbox.bounds[1]))/2 - if event.y > midpoint: - anchor='top' - self.text_instance.x_text_pos=float(self.x+ax.bbox.bounds[0]) - dp(40) - self.text_instance.y_text_pos=float(self.y+ax.bbox.bounds[1] + ax.bbox.bounds[3]) - self.text_instance.text_height + midpoint = ( + (self.y + ax.bbox.bounds[3] + ax.bbox.bounds[1]) + + (self.y + ax.bbox.bounds[1])) / 2 + if event.y > midpoint: + anchor = 'top' + self.text_instance.x_text_pos = float( + self.x + ax.bbox.bounds[0]) - dp(40) + self.text_instance.y_text_pos = float( + self.y + ax.bbox.bounds[1] + ax.bbox.bounds[3]) - self.text_instance.text_height self.text_instance.offset_text = False else: - anchor='bottom' - self.text_instance.x_text_pos=float(self.x+ax.bbox.bounds[0]) - dp(40) - self.text_instance.y_text_pos=float(self.y+ax.bbox.bounds[1]) - dp(6) + anchor = 'bottom' + self.text_instance.x_text_pos = float( + self.x + ax.bbox.bounds[0]) - dp(40) + self.text_instance.y_text_pos = float( + self.y + ax.bbox.bounds[1]) - dp(6) self.text_instance.offset_text = False self.text_instance.current_axis = ax - self.text_instance.kind = {'axis':'y','anchor':anchor} + self.text_instance.kind = { + 'axis': 'y', 'anchor': anchor} - self.text_instance.show_text=True + self.text_instance.show_text = True return - def apply_drag_legend(self, ax, event): """ drag legend method """ - + dx = event.x - self._last_touch_pos[event][0] if not self.legend_do_scroll_x: - dx=0 - dy = event.y - self._last_touch_pos[event][1] + dx = 0 + dy = event.y - self._last_touch_pos[event][1] if not self.legend_do_scroll_y: - dy=0 - legend=None + dy = 0 + legend = None if self.current_legend: if self.current_legend.legend_instance: legend = self.current_legend.legend_instance else: legend = ax.get_legend() - + if legend is not None: - + bbox = legend.get_window_extent() legend_x = bbox.xmin legend_y = bbox.ymin - - loc_in_canvas = legend_x +dx, legend_y+dy + + loc_in_canvas = legend_x + dx, legend_y + dy loc_in_norm_axes = legend.parent.transAxes.inverted().transform_point(loc_in_canvas) legend._loc = tuple(loc_in_norm_axes) - - #use blit method + + # use blit method if self.background is None: legend.set_visible(False) ax.figure.canvas.draw_idle() - ax.figure.canvas.flush_events() - self.background = ax.figure.canvas.copy_from_bbox(ax.figure.bbox) + ax.figure.canvas.flush_events() + self.background = ax.figure.canvas.copy_from_bbox( + ax.figure.bbox) legend.set_visible(True) - ax.figure.canvas.restore_region(self.background) - + ax.figure.canvas.restore_region(self.background) + ax.draw_artist(legend) - + ax.figure.canvas.blit(ax.bbox) - ax.figure.canvas.flush_events() + ax.figure.canvas.flush_events() self.current_legend.update_size() @@ -1793,35 +1997,35 @@ def zoom_factory(self, event, ax, base_scale=1.1): y = newcoord[1] trans = ax.transData.inverted() - xdata, ydata = trans.transform_point((x, y)) + xdata, ydata = trans.transform_point((x, y)) cur_xlim = ax.get_xlim() cur_ylim = ax.get_ylim() - scale=ax.get_xscale() - yscale=ax.get_yscale() - + scale = ax.get_xscale() + yscale = ax.get_yscale() + if scale == 'linear': - old_min=cur_xlim[0] - old_max=cur_xlim[1] + old_min = cur_xlim[0] + old_max = cur_xlim[1] else: - min_=cur_xlim[0] - max_=cur_xlim[1] - old_min = self.transform_eval(min_,ax.yaxis) - xdata = self.transform_eval(xdata,ax.yaxis) - old_max = self.transform_eval(max_,ax.yaxis) + min_ = cur_xlim[0] + max_ = cur_xlim[1] + old_min = self.transform_eval(min_, ax.yaxis) + xdata = self.transform_eval(xdata, ax.yaxis) + old_max = self.transform_eval(max_, ax.yaxis) if yscale == 'linear': - yold_min=cur_ylim[0] - yold_max=cur_ylim[1] + yold_min = cur_ylim[0] + yold_max = cur_ylim[1] else: - ymin_=cur_ylim[0] - ymax_=cur_ylim[1] - yold_min = self.transform_eval(ymin_,ax.yaxis) - ydata = self.transform_eval(ydata,ax.yaxis) - yold_max = self.transform_eval(ymax_,ax.yaxis) + ymin_ = cur_ylim[0] + ymax_ = cur_ylim[1] + yold_min = self.transform_eval(ymin_, ax.yaxis) + ydata = self.transform_eval(ydata, ax.yaxis) + yold_max = self.transform_eval(ymax_, ax.yaxis) if event.button == 'scrolldown': # deal with zoom in @@ -1842,34 +2046,40 @@ def zoom_factory(self, event, ax, base_scale=1.1): if self.do_zoom_x: if scale == 'linear': - ax.set_xlim([xdata - new_width * (1 - relx), xdata + new_width * (relx)]) + ax.set_xlim([xdata - new_width * (1 - relx), + xdata + new_width * (relx)]) else: new_min = xdata - new_width * (1 - relx) new_max = xdata + new_width * (relx) try: - new_min, new_max = self.inv_transform_eval(new_min,ax.yaxis), self.inv_transform_eval(new_max,ax.yaxis) + new_min, new_max = self.inv_transform_eval( + new_min, ax.yaxis), self.inv_transform_eval( + new_max, ax.yaxis) except OverflowError: # Limit case new_min, new_max = min_, max_ if new_min <= 0. or new_max <= 0.: # Limit case - new_min, new_max = min_, max_ + new_min, new_max = min_, max_ ax.set_xlim([new_min, new_max]) - + if self.do_zoom_y: if yscale == 'linear': - ax.set_ylim([ydata - new_height * (1 - rely), ydata + new_height * (rely)]) + ax.set_ylim([ydata - new_height * (1 - rely), + ydata + new_height * (rely)]) else: new_ymin = ydata - new_height * (1 - rely) new_ymax = ydata + new_height * (rely) try: - new_ymin, new_ymax = self.inv_transform_eval(new_ymin,ax.yaxis), self.inv_transform_eval(new_ymax,ax.yaxis) + new_ymin, new_ymax = self.inv_transform_eval( + new_ymin, ax.yaxis), self.inv_transform_eval( + new_ymax, ax.yaxis) except OverflowError: # Limit case new_ymin, new_ymax = ymin_, ymax_ if new_ymin <= 0. or new_ymax <= 0.: # Limit case - new_ymin, new_ymax = ymin_, ymax_ - ax.set_ylim([new_ymin, new_ymax]) + new_ymin, new_ymax = ymin_, ymax_ + ax.set_ylim([new_ymin, new_ymax]) ax.figure.canvas.draw_idle() - ax.figure.canvas.flush_events() + ax.figure.canvas.flush_events() def _onSize(self, o, size): """ _onsize method """ @@ -1886,73 +2096,88 @@ def _onSize(self, o, size): winch = self._width / dpival hinch = self._height / dpival self.figure.set_size_inches(winch, hinch) - + s = 'resize_event' event = ResizeEvent(s, self.figcanvas) self.figcanvas.callbacks.process(s, event) - self.figcanvas.draw_idle() - + self.figcanvas.draw_idle() + self.figcanvas.draw() if self.legend_instance: for current_legend in self.legend_instance: - current_legend.update_size() + current_legend.update_size() if self.hover_instance: self.hover_instance.figwidth = self.width self.hover_instance.figheight = self.height self.hover_instance.figx = self.x - self.hover_instance.figy = self.y + self.hover_instance.figy = self.y if self.selector and self.selector.resize_wgt.verts: - #update selector next frame to have correct position - Clock.schedule_once(self.update_selector) - + # update selector next frame to have correct position + Clock.schedule_once(self.update_selector) + def update_lim(self): """ update axis lim if zoombox is used""" - ax=self.axes - - self.do_update=False - - #check if inverted axis - xleft,xright=self.axes.get_xlim() - ybottom,ytop=self.axes.get_ylim() - - if xright>xleft: - ax.set_xlim(left=min(self.x0_box,self.x1_box),right=max(self.x0_box,self.x1_box)) + ax = self.axes + + self.do_update = False + + # check if inverted axis + xleft, xright = self.axes.get_xlim() + ybottom, ytop = self.axes.get_ylim() + + if xright > xleft: + ax.set_xlim( + left=min( + self.x0_box, self.x1_box), right=max( + self.x0_box, self.x1_box)) else: - ax.set_xlim(right=min(self.x0_box,self.x1_box),left=max(self.x0_box,self.x1_box)) - if ytop>ybottom: - ax.set_ylim(bottom=min(self.y0_box,self.y1_box),top=max(self.y0_box,self.y1_box)) + ax.set_xlim( + right=min( + self.x0_box, self.x1_box), left=max( + self.x0_box, self.x1_box)) + if ytop > ybottom: + ax.set_ylim( + bottom=min( + self.y0_box, self.y1_box), top=max( + self.y0_box, self.y1_box)) else: - ax.set_ylim(top=min(self.y0_box,self.y1_box),bottom=max(self.y0_box,self.y1_box)) + ax.set_ylim( + top=min( + self.y0_box, self.y1_box), bottom=max( + self.y0_box, self.y1_box)) def reset_box(self): """ reset zoombox and apply zoombox limit if zoombox option if selected""" - if min(abs(self._box_size[0]),abs(self._box_size[1]))>self.minzoom: + if min(abs(self._box_size[0]), abs(self._box_size[1])) > self.minzoom: trans = self.axes.transData.inverted() - self.x0_box, self.y0_box = trans.transform_point((self._box_pos[0]-self.pos[0], self._box_pos[1]-self.pos[1])) - self.x1_box, self.y1_box = trans.transform_point((self._box_size[0]+self._box_pos[0]-self.pos[0], self._box_size[1]+self._box_pos[1]-self.pos[1])) - self.do_update=True - + self.x0_box, self.y0_box = trans.transform_point( + (self._box_pos[0] - self.pos[0], self._box_pos[1] - self.pos[1])) + self.x1_box, self.y1_box = trans.transform_point( + (self._box_size[0] + self._box_pos[0] - self.pos[0], + self._box_size[1] + self._box_pos[1] - self.pos[1])) + self.do_update = True + self._box_size = 0, 0 self._box_pos = 0, 0 - self._alpha_box=0 + self._alpha_box = 0 self._pos_x_rect_hor = 0 self._pos_y_rect_hor = 0 self._pos_x_rect_ver = 0 - self._pos_y_rect_ver = 0 - self._alpha_hor=0 - self._alpha_ver=0 - + self._pos_y_rect_ver = 0 + self._alpha_hor = 0 + self._alpha_ver = 0 + def draw_box(self, event, x0, y0, x1, y1) -> None: """ Draw zoombox method - + Args: event: touch kivy event x0: x coordonnate init x1: y coordonnate of move touch y0: y coordonnate init y1: x coordonnate of move touch - + Return: None """ @@ -1960,105 +2185,107 @@ def draw_box(self, event, x0, y0, x1, y1) -> None: # Kivy coords y0 = pos_y + y0 y1 = pos_y + y1 - - if abs(y1-y0)>dp(5) or abs(x1-x0)>dp(5): - self._alpha_box=0.3 - self._alpha_rect=0 - + + if abs(y1 - y0) > dp(5) or abs(x1 - x0) > dp(5): + self._alpha_box = 0.3 + self._alpha_rect = 0 + trans = self.axes.transData.inverted() - xdata, ydata = trans.transform_point((event.x-pos_x, event.y-pos_y)) - - xleft,xright=self.axes.get_xlim() - ybottom,ytop=self.axes.get_ylim() - - xmax = max(xleft,xright) - xmin = min(xleft,xright) - ymax = max(ybottom,ytop) - ymin = min(ybottom,ytop) - - #check inverted data + xdata, ydata = trans.transform_point( + (event.x - pos_x, event.y - pos_y)) + + xleft, xright = self.axes.get_xlim() + ybottom, ytop = self.axes.get_ylim() + + xmax = max(xleft, xright) + xmin = min(xleft, xright) + ymax = max(ybottom, ytop) + ymin = min(ybottom, ytop) + + # check inverted data inverted_x = False - if xleft>xright: - inverted_x=True + if xleft > xright: + inverted_x = True inverted_y = False - if ybottom>ytop: - inverted_y=True + if ybottom > ytop: + inverted_y = True - x0data, y0data = trans.transform_point((x0-pos_x, y0-pos_y)) - - if x0data>xmax or x0dataymax or y0data xmax or x0data < xmin or y0data > ymax or y0data < ymin: return - if xdatax0 and inverted_x): - x1=x1_min[0][0]+pos_x + if xdata < xmin: + x1_min = self.axes.transData.transform([(xmin, ymin)]) + if (x1 < x0 and not inverted_x) or (x1 > x0 and inverted_x): + x1 = x1_min[0][0] + pos_x else: - x0=x1_min[0][0] + x0 = x1_min[0][0] - if xdata>xmax: - x0_max = self.axes.transData.transform([(xmax,ymin)]) - if (x1>x0 and not inverted_x) or (x1 xmax: + x0_max = self.axes.transData.transform([(xmax, ymin)]) + if (x1 > x0 and not inverted_x) or (x1 < x0 and inverted_x): + x1 = x0_max[0][0] + pos_x else: - x0=x0_max[0][0] + x0 = x0_max[0][0] - if ydatay0 and inverted_y): - y1=y1_min[0][1]+pos_y + if ydata < ymin: + y1_min = self.axes.transData.transform([(xmin, ymin)]) + if (y1 < y0 and not inverted_y) or (y1 > y0 and inverted_y): + y1 = y1_min[0][1] + pos_y else: - y0=y1_min[0][1]+pos_y + y0 = y1_min[0][1] + pos_y - if ydata>ymax: - y0_max = self.axes.transData.transform([(xmax,ymax)]) - if (y1>y0 and not inverted_y) or (y1 ymax: + y0_max = self.axes.transData.transform([(xmax, ymax)]) + if (y1 > y0 and not inverted_y) or (y1 < y0 and inverted_y): + y1 = y0_max[0][1] + pos_y else: - y0=y0_max[0][1]+pos_y - - if abs(x1-x0)self.minzoom: - self.pos_x_rect_ver=x0 - self.pos_y_rect_ver=y0 - - x1_min = self.axes.transData.transform([(xmin,ymin)]) - x0=x1_min[0][0]+pos_x - - x0_max = self.axes.transData.transform([(xmax,ymin)]) - x1=x0_max[0][0]+pos_x - - self._alpha_ver=1 - self._alpha_hor=0 - - elif abs(y1-y0)self.minzoom: - self.pos_x_rect_hor=x0 - self.pos_y_rect_hor=y0 - - y1_min = self.axes.transData.transform([(xmin,ymin)]) - y0=y1_min[0][1]+pos_y - - y0_max = self.axes.transData.transform([(xmax,ymax)]) - y1=y0_max[0][1]+pos_y - - self._alpha_hor=1 - self._alpha_ver=0 - + y0 = y0_max[0][1] + pos_y + + if abs(x1 - x0) < dp(20) and abs(y1 - y0) > self.minzoom: + self.pos_x_rect_ver = x0 + self.pos_y_rect_ver = y0 + + x1_min = self.axes.transData.transform([(xmin, ymin)]) + x0 = x1_min[0][0] + pos_x + + x0_max = self.axes.transData.transform([(xmax, ymin)]) + x1 = x0_max[0][0] + pos_x + + self._alpha_ver = 1 + self._alpha_hor = 0 + + elif abs(y1 - y0) < dp(20) and abs(x1 - x0) > self.minzoom: + self.pos_x_rect_hor = x0 + self.pos_y_rect_hor = y0 + + y1_min = self.axes.transData.transform([(xmin, ymin)]) + y0 = y1_min[0][1] + pos_y + + y0_max = self.axes.transData.transform([(xmax, ymax)]) + y1 = y0_max[0][1] + pos_y + + self._alpha_hor = 1 + self._alpha_ver = 0 + else: - self._alpha_hor=0 - self._alpha_ver=0 + self._alpha_hor = 0 + self._alpha_ver = 0 - if x1>x0: - self.invert_rect_ver=False + if x1 > x0: + self.invert_rect_ver = False else: - self.invert_rect_ver=True - if y1>y0: - self.invert_rect_hor=False + self.invert_rect_ver = True + if y1 > y0: + self.invert_rect_hor = False else: - self.invert_rect_hor=True - + self.invert_rect_hor = True + self._box_pos = x0, y0 self._box_size = x1 - x0, y1 - y0 + class _FigureCanvas(FigureCanvasAgg): """Internal AGG Canvas""" @@ -2083,19 +2310,19 @@ def draw(self): def blit(self, bbox=None): """ Render the figure using agg (blit method). - """ + """ agg = self.get_renderer() w, h = agg.width, agg.height self.widget._bitmap = agg.buffer_rgba() self.widget.bt_w = w self.widget.bt_h = h self.widget._draw_bitmap() - + + class FakeEventScatter: - x:None - y:None + x: None + y: None -from kivy.factory import Factory Factory.register('MatplotFigureScatter', MatplotFigureScatter) @@ -2119,42 +2346,42 @@ class FakeEventScatter: dp(1) if root.invert_rect_ver else -dp(1), \ dp(1) if root.invert_rect_hor else -dp(1), \ dp(1) if root.invert_rect_ver else -dp(1) - - canvas.after: + + canvas.after: #horizontal rectangle left - Color: - rgba:0, 0, 0, self._alpha_hor - Line: - width: dp(1) - rectangle: - (self.pos_x_rect_hor+dp(1) if root.invert_rect_ver \ - else self.pos_x_rect_hor-dp(4),self.pos_y_rect_hor-dp(20), dp(4),dp(40)) + Color: + rgba:0, 0, 0, self._alpha_hor + Line: + width: dp(1) + rectangle: + (self.pos_x_rect_hor+dp(1) if root.invert_rect_ver \ + else self.pos_x_rect_hor-dp(4),self.pos_y_rect_hor-dp(20), dp(4),dp(40)) #horizontal rectangle right - Color: - rgba:0, 0, 0, self._alpha_hor - Line: - width: dp(1) - rectangle: - (self.pos_x_rect_hor-dp(4)+self._box_size[0] if root.invert_rect_ver \ - else self.pos_x_rect_hor+dp(1)+self._box_size[0], self.pos_y_rect_hor-dp(20), dp(4),dp(40)) + Color: + rgba:0, 0, 0, self._alpha_hor + Line: + width: dp(1) + rectangle: + (self.pos_x_rect_hor-dp(4)+self._box_size[0] if root.invert_rect_ver \ + else self.pos_x_rect_hor+dp(1)+self._box_size[0], self.pos_y_rect_hor-dp(20), dp(4),dp(40)) #vertical rectangle bottom - Color: - rgba:0, 0, 0, self._alpha_ver - Line: - width: dp(1) - rectangle: - (self.pos_x_rect_ver-dp(20),self.pos_y_rect_ver+dp(1) if root.invert_rect_hor else \ - self.pos_y_rect_ver-dp(4), dp(40),dp(4)) + Color: + rgba:0, 0, 0, self._alpha_ver + Line: + width: dp(1) + rectangle: + (self.pos_x_rect_ver-dp(20),self.pos_y_rect_ver+dp(1) if root.invert_rect_hor else \ + self.pos_y_rect_ver-dp(4), dp(40),dp(4)) #vertical rectangle top - Color: - rgba:0, 0, 0, self._alpha_ver - Line: - width: dp(1) - rectangle: - (self.pos_x_rect_ver-dp(20),self.pos_y_rect_ver-dp(4)+self._box_size[1] \ + Color: + rgba:0, 0, 0, self._alpha_ver + Line: + width: dp(1) + rectangle: + (self.pos_x_rect_ver-dp(20),self.pos_y_rect_ver-dp(4)+self._box_size[1] \ if root.invert_rect_hor else self.pos_y_rect_ver+dp(1)+self._box_size[1], \ dp(40),dp(4)) ''') diff --git a/kivy_matplotlib_widget/uix/graph_widget_twinx.py b/kivy_matplotlib_widget/uix/graph_widget_twinx.py index 287c6c5..73f7009 100644 --- a/kivy_matplotlib_widget/uix/graph_widget_twinx.py +++ b/kivy_matplotlib_widget/uix/graph_widget_twinx.py @@ -1,7 +1,26 @@ -""" MatplotFigure is based on https://github.com/jeysonmc/kivy_matplotlib +""" MatplotFigure is based on https://github.com/jeysonmc/kivy_matplotlib and kivy scatter """ +from kivy.factory import Factory +from kivy.clock import Clock +from kivy.core.window import Window +from kivy.utils import get_color_from_hex +import numpy as np +from kivy.metrics import dp +from matplotlib.backend_bases import ResizeEvent +from weakref import WeakKeyDictionary +import matplotlib.lines as mlines +from matplotlib import cbook +from matplotlib.colors import to_hex +from matplotlib.backends.backend_agg import FigureCanvasAgg +from kivy.vector import Vector +from kivy.uix.widget import Widget +from kivy.properties import ObjectProperty, ListProperty, BooleanProperty, BoundedNumericProperty, AliasProperty, \ + NumericProperty, OptionProperty, DictProperty +from kivy.lang import Builder +from kivy.graphics.transformation import Matrix +from kivy.graphics.texture import Texture import math import copy @@ -10,36 +29,20 @@ selector_widgets_available = False try: selector_widgets_available = True - from kivy_matplotlib_widget.uix.selector_widget import ResizeRelativeLayout,LassoRelativeLayout,EllipseRelativeLayout,SpanRelativeLayout + from kivy_matplotlib_widget.uix.selector_widget import ResizeRelativeLayout, LassoRelativeLayout, EllipseRelativeLayout, SpanRelativeLayout except ImportError: print('Selector widgets are not available') -from kivy.graphics.texture import Texture -from kivy.graphics.transformation import Matrix -from kivy.lang import Builder -from kivy.properties import ObjectProperty, ListProperty, BooleanProperty, BoundedNumericProperty, AliasProperty, \ - NumericProperty, OptionProperty, DictProperty -from kivy.uix.widget import Widget -from kivy.vector import Vector -from matplotlib.backends.backend_agg import FigureCanvasAgg -from matplotlib.colors import to_hex -from matplotlib import cbook -import matplotlib.lines as mlines -from weakref import WeakKeyDictionary -from matplotlib.backend_bases import ResizeEvent -from kivy.metrics import dp -import numpy as np -from kivy.utils import get_color_from_hex -from kivy.core.window import Window -from kivy.clock import Clock + class MatplotlibEvent: - x=None - y=None - pickradius=None - inaxes=None - projection=False - compare_xdata=False - pick_radius_axis='both' + x = None + y = None + pickradius = None + inaxes = None + projection = False + compare_xdata = False + pick_radius_axis = 'both' + class MatplotFigureTwinx(Widget): """Widget to show a matplotlib figure in kivy. @@ -51,10 +54,10 @@ class MatplotFigureTwinx(Widget): _box_pos = ListProperty([0, 0]) _box_size = ListProperty([0, 0]) _img_texture = ObjectProperty(None) - _alpha_box = NumericProperty(0) + _alpha_box = NumericProperty(0) _bitmap = None _pressed = False - do_update=False + do_update = False figcanvas = ObjectProperty(None) translation_touches = BoundedNumericProperty(1, min=1) do_scale = BooleanProperty(True) @@ -63,47 +66,55 @@ class MatplotFigureTwinx(Widget): transform = ObjectProperty(Matrix()) _alpha_hor = NumericProperty(0) _alpha_ver = NumericProperty(0) - pos_x_rect_hor=NumericProperty(0) - pos_y_rect_hor=NumericProperty(0) - pos_x_rect_ver=NumericProperty(0) - pos_y_rect_ver=NumericProperty(0) + pos_x_rect_hor = NumericProperty(0) + pos_y_rect_hor = NumericProperty(0) + pos_x_rect_ver = NumericProperty(0) + pos_y_rect_ver = NumericProperty(0) invert_rect_ver = BooleanProperty(False) invert_rect_hor = BooleanProperty(False) legend_do_scroll_x = BooleanProperty(True) legend_do_scroll_y = BooleanProperty(True) - interactive_axis = BooleanProperty(False) + interactive_axis = BooleanProperty(False) do_pan_x = BooleanProperty(True) - do_pan_y = BooleanProperty(True) + do_pan_y = BooleanProperty(True) do_zoom_x = BooleanProperty(True) do_zoom_y = BooleanProperty(True) - fast_draw = BooleanProperty(True) #True will don't draw axis - xsorted = BooleanProperty(False) #to manage x sorted data - minzoom = NumericProperty(dp(20)) + fast_draw = BooleanProperty(True) # True will don't draw axis + xsorted = BooleanProperty(False) # to manage x sorted data + minzoom = NumericProperty(dp(20)) twinx = BooleanProperty(False) - compare_xdata = BooleanProperty(False) + compare_xdata = BooleanProperty(False) hover_instance = ObjectProperty(None, allownone=True) nearest_hover_instance = ObjectProperty(None, allownone=True) compare_hover_instance = ObjectProperty(None, allownone=True) - disable_mouse_scrolling = BooleanProperty(False) - disable_double_tap = BooleanProperty(False) + disable_mouse_scrolling = BooleanProperty(False) + disable_double_tap = BooleanProperty(False) text_instance = None min_max_option = BooleanProperty(True) auto_zoom = BooleanProperty(False) - zoom_angle_detection=NumericProperty(15) #in degree + zoom_angle_detection = NumericProperty(15) # in degree auto_cursor = BooleanProperty(False) autoscale_visible_only = BooleanProperty(True) autoscale_axis = OptionProperty("both", options=["both", "x", "y"]) autoscale_tight = BooleanProperty(False) - desktop_mode = BooleanProperty(True) #change mouse hover for selector widget - current_selector = OptionProperty("None", - options = ["None",'rectangle','lasso','ellipse','span','custom']) + # change mouse hover for selector widget + desktop_mode = BooleanProperty(True) + current_selector = OptionProperty( + "None", + options=[ + "None", + 'rectangle', + 'lasso', + 'ellipse', + 'span', + 'custom']) highlight_hover = BooleanProperty(False) highlight_prop = DictProperty({}) - highlight_alpha = NumericProperty(0.2) - myevent = MatplotlibEvent() - pick_minimum_radius=NumericProperty(dp(50)) + highlight_alpha = NumericProperty(0.2) + myevent = MatplotlibEvent() + pick_minimum_radius = NumericProperty(dp(50)) pick_radius_axis = OptionProperty("both", options=["both", "x", "y"]) - + def on_figure(self, obj, value): self.figcanvas = _FigureCanvas(self.figure, self) self.figcanvas._isDrawn = False @@ -114,61 +125,64 @@ def on_figure(self, obj, value): self.height = h if len(self.figure.axes) > 0 and self.figure.axes[0]: - #add copy patch - ax=self.figure.axes[0] - self.axes=ax - patch_cpy=copy.copy(ax.patch) + # add copy patch + ax = self.figure.axes[0] + self.axes = ax + patch_cpy = copy.copy(ax.patch) patch_cpy.set_visible(False) for pos in ['right', 'top', 'bottom', 'left']: ax.spines[pos].set_zorder(10) patch_cpy.set_zorder(9) - self.background_patch_copy= ax.add_patch(patch_cpy) - - #set default xmin/xmax and ymin/ymax - self.xmin,self.xmax = ax.get_xlim() - self.ymin,self.ymax = ax.get_ylim() - - if len(self.figure.axes)==2: - self.twinx=True - ax2=self.figure.axes[1] - patch_cpy_ax2=copy.copy(ax2.patch) + self.background_patch_copy = ax.add_patch(patch_cpy) + + # set default xmin/xmax and ymin/ymax + self.xmin, self.xmax = ax.get_xlim() + self.ymin, self.ymax = ax.get_ylim() + + if len(self.figure.axes) == 2: + self.twinx = True + ax2 = self.figure.axes[1] + patch_cpy_ax2 = copy.copy(ax2.patch) patch_cpy_ax2.set_visible(False) for pos in ['right', 'top', 'bottom', 'left']: ax2.spines[pos].set_zorder(10) patch_cpy_ax2.set_zorder(9) - self.background_ax2_patch_copy= ax2.add_patch(patch_cpy_ax2) - self.ymin2,self.ymax2 = ax.get_ylim() + self.background_ax2_patch_copy = ax2.add_patch(patch_cpy_ax2) + self.ymin2, self.ymax2 = ax.get_ylim() else: - self.twinx=False - self.background_ax2_patch_copy= None + self.twinx = False + self.background_ax2_patch_copy = None self.ymin2 = None - self.ymax2 = None - + self.ymax2 = None + if self.legend_instance: - #remove all legend_instance from parent widget + # remove all legend_instance from parent widget for current_legend in self.legend_instance: current_legend.parent.remove_widget(current_legend) - self.legend_instance=[] - + self.legend_instance = [] + if self.auto_cursor: - if len(self.figure.axes)==2: - self.register_lines(list(self.figure.axes[0].lines+self.figure.axes[1].lines)) + if len(self.figure.axes) == 2: + self.register_lines( + list( + self.figure.axes[0].lines + + self.figure.axes[1].lines)) elif len(self.figure.axes) > 0: self.register_lines(list(self.figure.axes[0].lines)) - + # Texture self._img_texture = Texture.create(size=(w, h)) if self.selector and self.axes: self.selector.resize_wgt.ax = self.axes - - #close last figure in memory (avoid max figure warning) + + # close last figure in memory (avoid max figure warning) matplotlib.pyplot.close() def __init__(self, **kwargs): super(MatplotFigureTwinx, self).__init__(**kwargs) - - #figure info + + # figure info self.figure = None self.axes = None self.xmin = None @@ -178,132 +192,138 @@ def __init__(self, **kwargs): self.ymin2 = None self.ymax2 = None self.lines = [] - - #option - self.touch_mode='pan' - self.hover_on = False - self.cursor_xaxis_formatter=None #used matplotlib formatter to display x cursor value - self.cursor_yaxis_formatter=None #used matplotlib formatter to display y cursor value (left axis) - self.cursor_yaxis2_formatter=None #used matplotlib formatter to display y cursor value (right axis) - #zoom box coordonnate + # option + self.touch_mode = 'pan' + self.hover_on = False + # used matplotlib formatter to display x cursor value + self.cursor_xaxis_formatter = None + # used matplotlib formatter to display y cursor value (left axis) + self.cursor_yaxis_formatter = None + # used matplotlib formatter to display y cursor value (right axis) + self.cursor_yaxis2_formatter = None + + # zoom box coordonnate self.x0_box = None self.y0_box = None self.x1_box = None self.y1_box = None - - #clear touches on touch up + + # clear touches on touch up self._touches = [] self._last_touch_pos = {} - #background - self.background=None - self.background_patch_copy=None - self.background_ax2_patch_copy=None - - #twin x axis - self.twinx=False - - #manage adjust x and y + # background + self.background = None + self.background_patch_copy = None + self.background_ax2_patch_copy = None + + # twin x axis + self.twinx = False + + # manage adjust x and y self.anchor_x = None - self.anchor_y = None + self.anchor_y = None - #manage hover data + # manage hover data self.x_hover_data = None self.y_hover_data = None - - #pan management - self.first_touch_pan = None - - #trick to manage wrong canvas size on first call (compare_hover) - self.first_call_compare_hover=False - - #cross hair cursor + + # pan management + self.first_touch_pan = None + + # trick to manage wrong canvas size on first call (compare_hover) + self.first_call_compare_hover = False + + # cross hair cursor self.horizontal_line = None self.vertical_line = None - - #manage cursor update on right axis - self.cursor_last_axis=None - self.cursor_last_y=0 - - #manage show compare cursor on release - self.show_compare_cursor=False - - #manage back and next event - if hasattr(cbook,'_Stack'): - #manage matplotlib version with no Stack (replace by _Stack) + + # manage cursor update on right axis + self.cursor_last_axis = None + self.cursor_last_y = 0 + + # manage show compare cursor on release + self.show_compare_cursor = False + + # manage back and next event + if hasattr(cbook, '_Stack'): + # manage matplotlib version with no Stack (replace by _Stack) self._nav_stack = cbook._Stack() else: self._nav_stack = cbook.Stack() - self.set_history_buttons() + self.set_history_buttons() - #legend management + # legend management self.legend_instance = [] - self.current_legend=None - - #selector management + self.current_legend = None + + # selector management self.kv_post_done = False - self.selector = None - - #highlight management + self.selector = None + + # highlight management self.last_line = None - self.last_line_prop = {} - + self.last_line_prop = {} + self.bind(size=self._onSize) - def on_kv_post(self,_): + def on_kv_post(self, _): # if not self.selector: if self.current_selector != "None" and selector_widgets_available: if self.current_selector == 'rectangle': self.set_selector(ResizeRelativeLayout) elif self.current_selector == 'lasso': - self.set_selector(LassoRelativeLayout) + self.set_selector(LassoRelativeLayout) elif self.current_selector == 'ellipse': - self.set_selector(EllipseRelativeLayout) + self.set_selector(EllipseRelativeLayout) elif self.current_selector == 'span': - self.set_selector(SpanRelativeLayout) - self.kv_post_done=True + self.set_selector(SpanRelativeLayout) + self.kv_post_done = True - def transform_eval(self,x,axis): - custom_transform=axis.get_transform() + def transform_eval(self, x, axis): + custom_transform = axis.get_transform() return custom_transform.transform_non_affine(np.array([x]))[0] - - def inv_transform_eval(self,x,axis): - inv_custom_transform=axis.get_transform().inverted() + + def inv_transform_eval(self, x, axis): + inv_custom_transform = axis.get_transform().inverted() return inv_custom_transform.transform_non_affine(np.array([x]))[0] - def on_current_selector(self,instance,value,*args): - + def on_current_selector(self, instance, value, *args): + if self.kv_post_done and selector_widgets_available: if value == 'rectangle': self.set_selector(ResizeRelativeLayout) elif value == 'lasso': - self.set_selector(LassoRelativeLayout) + self.set_selector(LassoRelativeLayout) elif value == 'ellipse': - self.set_selector(EllipseRelativeLayout) + self.set_selector(EllipseRelativeLayout) elif self.current_selector == 'span': - self.set_selector(SpanRelativeLayout) + self.set_selector(SpanRelativeLayout) elif value == "None": if self.selector: - Window.unbind(mouse_pos=self.selector.resize_wgt.on_mouse_pos) + Window.unbind( + mouse_pos=self.selector.resize_wgt.on_mouse_pos) self.parent.remove_widget(self.selector) self.selector = None - - def set_selector(self,selector,*args): - selector_collection=None - selector_line=None + + def set_selector(self, selector, *args): + selector_collection = None + selector_line = None callback = None callback_clear = None if self.selector: selector_collection = self.selector.resize_wgt.collection selector_line = self.selector.resize_wgt.line callback = self.selector.resize_wgt.callback - callback_clear = self.selector.resize_wgt.callback_clear + callback_clear = self.selector.resize_wgt.callback_clear Window.unbind(mouse_pos=self.selector.resize_wgt.on_mouse_pos) self.parent.remove_widget(self.selector) - - self.selector = selector(figure_wgt=self,desktop_mode=self.desktop_mode) + + self.selector = selector( + figure_wgt=self, + desktop_mode=self.desktop_mode) self.selector.resize_wgt.ax = self.axes if selector_collection: self.set_collection() @@ -313,603 +333,672 @@ def set_selector(self,selector,*args): self.set_callback(callback) if callback_clear: self.set_callback_clear(callback_clear) - - self.parent.add_widget(self.selector) - - def set_collection(self): + + self.parent.add_widget(self.selector) + + def set_collection(self): self.selector.resize_wgt.ax = self.axes - collections = self.figure.axes[0].collections - + collections = self.figure.axes[0].collections + if collections: - self.selector.resize_wgt.set_collection(collections[0]) - - def set_line(self,line): - self.selector.resize_wgt.ax = self.axes - self.selector.resize_wgt.set_line(line) - - def set_callback(self,callback): + self.selector.resize_wgt.set_collection(collections[0]) + + def set_line(self, line): + self.selector.resize_wgt.ax = self.axes + self.selector.resize_wgt.set_line(line) + + def set_callback(self, callback): self.selector.resize_wgt.set_callback(callback) - - def set_callback_clear(self,callback): - self.selector.resize_wgt.set_callback_clear(callback) - def register_lines(self,lines:list) -> None: + def set_callback_clear(self, callback): + self.selector.resize_wgt.set_callback_clear(callback) + + def register_lines(self, lines: list) -> None: """ register lines method - + Args: lines (list): list of matplolib line class - + Return: - None - """ - ax=self.figure.axes[0] - #use sel,axes limit to avoid graph rescale - xmin,xmax = ax.get_xlim() - ymin,ymax = ax.get_ylim() - - #create cross hair cursor - self.horizontal_line = ax.axhline(y=self.ymin,color='k', lw=0.8, ls='--', visible=False) - self.vertical_line = ax.axvline(x=self.xmin,color='k', lw=0.8, ls='--', visible=False) - - #register lines - self.lines=lines - - #cursor text - self.text = ax.text(1.0, 1.01, '', + None + """ + ax = self.figure.axes[0] + # use sel,axes limit to avoid graph rescale + xmin, xmax = ax.get_xlim() + ymin, ymax = ax.get_ylim() + + # create cross hair cursor + self.horizontal_line = ax.axhline( + y=self.ymin, color='k', lw=0.8, ls='--', visible=False) + self.vertical_line = ax.axvline( + x=self.xmin, color='k', lw=0.8, ls='--', visible=False) + + # register lines + self.lines = lines + + # cursor text + self.text = ax.text(1.0, 1.01, '', transform=ax.transAxes, ha='right') - def set_cross_hair_visible(self, visible:bool) -> None: + def set_cross_hair_visible(self, visible: bool) -> None: """ set curcor visibility - + Args: visible (bool): make cursor visble or not - + Return: None - - """ + + """ self.horizontal_line.set_visible(visible) self.vertical_line.set_visible(visible) self.text.set_visible(visible) def update_cursor(self): if self.twinx and self.horizontal_line and self.cursor_last_axis: - + if self.horizontal_line.get_visible() or self.hover_instance: - - if self.cursor_last_axis==self.figure.axes[1]: - + + if self.cursor_last_axis == self.figure.axes[1]: + if self.hover_instance: - if self.hover_instance.show_cursor and self.x_hover_data is not None and self.y_hover_data is not None: - self.y_hover_data=self.cursor_last_y - xy_pos = self.figure.axes[1].transData.transform([(self.x_hover_data,self.y_hover_data)]) - self.hover_instance.y_hover_pos=float(xy_pos[0][1]) + self.y - + if self.hover_instance.show_cursor and self.x_hover_data is not None and self.y_hover_data is not None: + self.y_hover_data = self.cursor_last_y + xy_pos = self.figure.axes[1].transData.transform( + [(self.x_hover_data, self.y_hover_data)]) + self.hover_instance.y_hover_pos = float( + xy_pos[0][1]) + self.y + else: new_y = self.cursor_last_y - x=self.vertical_line.get_xdata() + x = self.vertical_line.get_xdata() trans = self.figure.axes[0].transData.inverted() - xy_pos = self.figure.axes[1].transData.transform([(x,new_y)]) - xdata, ydata = trans.transform_point((xy_pos[0][0], xy_pos[0][1])) + xy_pos = self.figure.axes[1].transData.transform( + [(x, new_y)]) + xdata, ydata = trans.transform_point( + (xy_pos[0][0], xy_pos[0][1])) self.horizontal_line.set_ydata([ydata,]) def clear_line_prop(self) -> None: """ clear attribute line_prop method - + Args: None - + Return: None - - """ + + """ if self.last_line_prop: for key in self.last_line_prop: - set_line_attr = getattr(self.last_line,'set_' + key) - set_line_attr(self.last_line_prop[key]) - self.last_line_prop={} - self.last_line=None + set_line_attr = getattr(self.last_line, 'set_' + key) + set_line_attr(self.last_line_prop[key]) + self.last_line_prop = {} + self.last_line = None def hover(self, event) -> None: """ hover cursor method (cursor to nearest value) - + Args: event: touch kivy event - + Return: None - + """ - - #if cursor is set -> hover is on + + # if cursor is set -> hover is on if self.hover_on: - #transform kivy x,y touch event to x,y data + # transform kivy x,y touch event to x,y data trans = self.figure.axes[0].transData.inverted() - xdata, ydata = trans.transform_point((event.x - self.pos[0], event.y - self.pos[1])) - - #loop all register lines and find closest x,y data for each valid line - distance=[] - good_line=[] - good_index=[] - good_index2=[] + xdata, ydata = trans.transform_point( + (event.x - self.pos[0], event.y - self.pos[1])) + + # loop all register lines and find closest x,y data for each valid + # line + distance = [] + good_line = [] + good_index = [] + good_index2 = [] for line in self.lines: - #get only visible lines - if line.get_visible(): - #get line x,y datas + # get only visible lines + if line.get_visible(): + # get line x,y datas self.x_cursor, self.y_cursor = line.get_xydata().T - - #check if line is not empty - if len(self.x_cursor)!=0: - - #find closest data index from touch (x axis) + + # check if line is not empty + if len(self.x_cursor) != 0: + + # find closest data index from touch (x axis) if self.xsorted: - index = min(np.searchsorted(self.x_cursor, xdata), len(self.y_cursor) - 1) - + index = min( + np.searchsorted( + self.x_cursor, xdata), len( + self.y_cursor) - 1) + else: index = np.argsort(abs(self.x_cursor - xdata))[0] - #get x data from index + # get x data from index x = self.x_cursor[index] - + if self.compare_xdata: y = self.y_cursor[index] - - #get distance between line and touch (in pixels) - ax=line.axes - #left axis + + # get distance between line and touch (in pixels) + ax = line.axes + # left axis if self.twinx: - if ax==self.figure.axes[1]: - #right axis - trans = self.figure.axes[1].transData.inverted() - xdata2, ydata2 = trans.transform_point((event.x - self.pos[0], event.y - self.pos[1])) - xy_pixels_mouse = ax.transData.transform(np.vstack([xdata2,ydata2]).T) - else: - #left axis - xy_pixels_mouse = ax.transData.transform([(xdata,ydata)]) - else: - #left axis - xy_pixels_mouse = ax.transData.transform([(xdata,ydata)]) - if np.ma.is_masked(x) or np.ma.is_masked(y) or np.isnan(x) or np.isnan(y): + if ax == self.figure.axes[1]: + # right axis + trans = self.figure.axes[1].transData.inverted( + ) + xdata2, ydata2 = trans.transform_point( + (event.x - self.pos[0], event.y - self.pos[1])) + xy_pixels_mouse = ax.transData.transform( + np.vstack([xdata2, ydata2]).T) + else: + # left axis + xy_pixels_mouse = ax.transData.transform( + [(xdata, ydata)]) + else: + # left axis + xy_pixels_mouse = ax.transData.transform( + [(xdata, ydata)]) + if np.ma.is_masked(x) or np.ma.is_masked( + y) or np.isnan(x) or np.isnan(y): distance.append(np.nan) - else: - xy_pixels = ax.transData.transform([(x,ydata)]) - dx2 = (xy_pixels_mouse[0][0]-xy_pixels[0][0]) + else: + xy_pixels = ax.transData.transform( + [(x, ydata)]) + dx2 = (xy_pixels_mouse[0][0] - xy_pixels[0][0]) distance.append(abs(dx2)) - else: - #find ydata corresponding to xdata + else: + # find ydata corresponding to xdata y = self.y_cursor[index] - - #get distance between line and touch (in pixels) - ax=line.axes + + # get distance between line and touch (in pixels) + ax = line.axes if self.twinx: - if ax==self.figure.axes[1]: - #right axis - trans = self.figure.axes[1].transData.inverted() - xdata2, ydata2 = trans.transform_point((event.x - self.pos[0], event.y - self.pos[1])) - xy_pixels_mouse = ax.transData.transform(np.vstack([xdata2,ydata2]).T) - else: - #left axis - xy_pixels_mouse = ax.transData.transform([(xdata,ydata)]) - else: - #left axis - xy_pixels_mouse = ax.transData.transform([(xdata,ydata)]) + if ax == self.figure.axes[1]: + # right axis + trans = self.figure.axes[1].transData.inverted( + ) + xdata2, ydata2 = trans.transform_point( + (event.x - self.pos[0], event.y - self.pos[1])) + xy_pixels_mouse = ax.transData.transform( + np.vstack([xdata2, ydata2]).T) + else: + # left axis + xy_pixels_mouse = ax.transData.transform( + [(xdata, ydata)]) + else: + # left axis + xy_pixels_mouse = ax.transData.transform( + [(xdata, ydata)]) if np.ma.is_masked(x) or np.ma.is_masked(y): distance.append(np.nan) - else: - xy_pixels = ax.transData.transform([(x,y)]) - dx2 = (xy_pixels_mouse[0][0]-xy_pixels[0][0])**2 - dy2 = (xy_pixels_mouse[0][1]-xy_pixels[0][1])**2 - - #store distance + else: + xy_pixels = ax.transData.transform([(x, y)]) + dx2 = ( + xy_pixels_mouse[0][0] - xy_pixels[0][0])**2 + dy2 = ( + xy_pixels_mouse[0][1] - xy_pixels[0][1])**2 + + # store distance if self.pick_radius_axis == 'both': distance.append((dx2 + dy2)**0.5) if self.pick_radius_axis == 'x': distance.append(abs(dx2)) if self.pick_radius_axis == 'y': - distance.append(abs(dy2)) - - #store all best lines and index + distance.append(abs(dy2)) + + # store all best lines and index good_line.append(line) good_index.append(index) - - #case if no good line - if len(good_line)==0: + + # case if no good line + if len(good_line) == 0: return - #if minimum distance if lower than 50 pixels, get line datas with - #minimum distance - if np.nanmin(distance)0: + self.first_call_compare_hover = True + + if len(idx_best_list) > 0: available_widget = self.hover_instance.children_list - nb_widget=len(available_widget) - index_list=list(range(nb_widget)) + nb_widget = len(available_widget) + index_list = list(range(nb_widget)) for i, current_idx_best in enumerate(idx_best_list): - if i > nb_widget-1: + if i > nb_widget - 1: break else: - line=good_line[idx_best_list[i]] + line = good_line[idx_best_list[i]] line_label = line.get_label() if line_label in self.hover_instance.children_names: - index= self.hover_instance.children_names.index(line_label) + index = self.hover_instance.children_names.index( + line_label) y_cursor = line.get_ydata() - y = y_cursor[good_index[idx_best_list[i]]] - ax=line.axes - - xy_pos = ax.transData.transform([(x,y)]) - pos_y=float(xy_pos[0][1]) + self.y - - if pos_yself.y+ax.bbox.bounds[1]: - - available_widget[index].x_hover_pos=float(xy_pos[0][0]) + self.x - available_widget[index].y_hover_pos=float(xy_pos[0][1]) + self.y - available_widget[index].custom_color = get_color_from_hex(to_hex(line.get_color())) - + y = y_cursor[good_index[idx_best_list[i]]] + ax = line.axes + + xy_pos = ax.transData.transform([(x, y)]) + pos_y = float(xy_pos[0][1]) + self.y + + if pos_y < self.y + ax.bbox.bounds[1] + ax.bbox.bounds[3] and \ + pos_y > self.y + ax.bbox.bounds[1]: + + available_widget[index].x_hover_pos = float( + xy_pos[0][0]) + self.x + available_widget[index].y_hover_pos = float( + xy_pos[0][1]) + self.y + available_widget[index].custom_color = get_color_from_hex( + to_hex(line.get_color())) + if self.twinx: - if ax==self.figure.axes[1]: + if ax == self.figure.axes[1]: if self.cursor_yaxis2_formatter: - y = self.cursor_yaxis2_formatter.format_data(y) + y = self.cursor_yaxis2_formatter.format_data( + y) else: - y = ax.yaxis.get_major_formatter().format_data_short(y) + y = ax.yaxis.get_major_formatter().format_data_short(y) else: if self.cursor_yaxis_formatter: - y = self.cursor_yaxis_formatter.format_data(y) - - else: + y = self.cursor_yaxis_formatter.format_data( + y) + + else: if self.cursor_yaxis_formatter: - y = self.cursor_yaxis_formatter.format_data(y) + y = self.cursor_yaxis_formatter.format_data( + y) else: - y = ax.yaxis.get_major_formatter().format_data_short(y) - available_widget[index].label_y_value=f"{y}" - available_widget[index].show_widget=True + y = ax.yaxis.get_major_formatter().format_data_short(y) + available_widget[index].label_y_value = f"{y}" + available_widget[index].show_widget = True index_list.remove(index) - + for ii in index_list: - available_widget[ii].show_widget=False + available_widget[ii].show_widget = False if self.cursor_xaxis_formatter: x = self.cursor_xaxis_formatter.format_data(x) else: - x = ax.xaxis.get_major_formatter().format_data_short(x) - - self.hover_instance.label_x_value=f"{x}" + x = ax.xaxis.get_major_formatter().format_data_short(x) + + self.hover_instance.label_x_value = f"{x}" - if hasattr(self.hover_instance,'overlap_check'): + if hasattr(self.hover_instance, 'overlap_check'): self.hover_instance.overlap_check() - self.hover_instance.xmin_line = float(ax.bbox.bounds[0]) + self.x - self.hover_instance.xmax_line = float(ax.bbox.bounds[0] + ax.bbox.bounds[2]) + self.x - self.hover_instance.ymin_line = float(ax.bbox.bounds[1]) + self.y - self.hover_instance.ymax_line = float(ax.bbox.bounds[1] + ax.bbox.bounds[3]) + self.y - - if self.hover_instance.x_hover_pos>self.x+ax.bbox.bounds[2] + ax.bbox.bounds[0] or \ - self.hover_instance.x_hover_pos self.x + ax.bbox.bounds[2] + ax.bbox.bounds[0] or \ + self.hover_instance.x_hover_pos < self.x + ax.bbox.bounds[0] or len(index_list) == nb_widget: + self.hover_instance.hover_outside_bound = True else: - self.hover_instance.hover_outside_bound=False - - return - + self.hover_instance.hover_outside_bound = False + + return + else: - idx_best=np.nanargmin(distance) - - #get datas from closest line - line=good_line[idx_best] + idx_best = np.nanargmin(distance) + + # get datas from closest line + line = good_line[idx_best] self.x_cursor, self.y_cursor = line.get_xydata().T x = self.x_cursor[good_index[idx_best]] - y = self.y_cursor[good_index[idx_best]] - + y = self.y_cursor[good_index[idx_best]] + if not self.hover_instance: self.set_cross_hair_visible(True) - - # update the cursor x,y data - ax=line.axes - self.cursor_last_axis=ax + + # update the cursor x,y data + ax = line.axes + self.cursor_last_axis = ax if self.twinx: - if ax==self.figure.axes[1]: + if ax == self.figure.axes[1]: cur_ylim = self.figure.axes[0].get_ylim() cur_ylim2 = self.figure.axes[1].get_ylim() - - ratio = (cur_ylim2[1] - cur_ylim2[0]) / (cur_ylim[1] - cur_ylim[0]) - new_y = (y-cur_ylim2[0])/ ratio + cur_ylim[0] + + ratio = (cur_ylim2[1] - cur_ylim2[0] + ) / (cur_ylim[1] - cur_ylim[0]) + new_y = (y - cur_ylim2[0]) / ratio + cur_ylim[0] self.horizontal_line.set_ydata([new_y,]) - - self.cursor_last_y=new_y + + self.cursor_last_y = new_y if self.cursor_yaxis2_formatter and not self.hover_instance: y = self.cursor_yaxis2_formatter.format_data(y) elif not self.hover_instance: - y = ax.yaxis.get_major_formatter().format_data_short(y) + y = ax.yaxis.get_major_formatter().format_data_short(y) else: self.horizontal_line.set_ydata([y,]) if self.cursor_yaxis_formatter and not self.hover_instance: - y = self.cursor_yaxis_formatter.format_data(y) + y = self.cursor_yaxis_formatter.format_data(y) elif not self.hover_instance: - y = ax.yaxis.get_major_formatter().format_data_short(y) + y = ax.yaxis.get_major_formatter().format_data_short(y) else: self.horizontal_line.set_ydata([y,]) if self.cursor_yaxis_formatter and not self.hover_instance: - y = self.cursor_yaxis_formatter.format_data(y) + y = self.cursor_yaxis_formatter.format_data(y) elif not self.hover_instance: - y = ax.yaxis.get_major_formatter().format_data_short(y) + y = ax.yaxis.get_major_formatter().format_data_short(y) self.vertical_line.set_xdata([x,]) if self.cursor_xaxis_formatter and not self.hover_instance: x = self.cursor_xaxis_formatter.format_data(x) elif not self.hover_instance: - x = ax.xaxis.get_major_formatter().format_data_short(x) - - #x y label - if self.hover_instance: - xy_pos = ax.transData.transform([(x,y)]) + x = ax.xaxis.get_major_formatter().format_data_short(x) + + # x y label + if self.hover_instance: + xy_pos = ax.transData.transform([(x, y)]) self.x_hover_data = x self.y_hover_data = y - self.hover_instance.x_hover_pos=float(xy_pos[0][0]) + self.x - self.hover_instance.y_hover_pos=float(xy_pos[0][1]) + self.y - self.hover_instance.show_cursor=True - + self.hover_instance.x_hover_pos = float( + xy_pos[0][0]) + self.x + self.hover_instance.y_hover_pos = float( + xy_pos[0][1]) + self.y + self.hover_instance.show_cursor = True + if self.twinx: - if ax==self.figure.axes[1]: + if ax == self.figure.axes[1]: if self.cursor_yaxis2_formatter: - y = self.cursor_yaxis2_formatter.format_data(y) + y = self.cursor_yaxis2_formatter.format_data( + y) else: - y = ax.yaxis.get_major_formatter().format_data_short(y) + y = ax.yaxis.get_major_formatter().format_data_short(y) else: if self.cursor_yaxis_formatter: - y = self.cursor_yaxis_formatter.format_data(y) + y = self.cursor_yaxis_formatter.format_data( + y) else: y = ax.yaxis.get_major_formatter().format_data_short(y) - - else: + + else: if self.cursor_yaxis_formatter: y = self.cursor_yaxis_formatter.format_data(y) else: - y = ax.yaxis.get_major_formatter().format_data_short(y) - + y = ax.yaxis.get_major_formatter().format_data_short(y) + if self.cursor_xaxis_formatter: x = self.cursor_xaxis_formatter.format_data(x) else: - x = ax.xaxis.get_major_formatter().format_data_short(x) - - self.hover_instance.label_x_value=f"{x}" - self.hover_instance.label_y_value=f"{y}" - - self.hover_instance.xmin_line = float(ax.bbox.bounds[0]) + self.x - self.hover_instance.xmax_line = float(ax.bbox.bounds[0] + ax.bbox.bounds[2]) + self.x - self.hover_instance.ymin_line = float(ax.bbox.bounds[1]) + self.y - self.hover_instance.ymax_line = float(ax.bbox.bounds[1] + ax.bbox.bounds[3]) + self.y - + x = ax.xaxis.get_major_formatter().format_data_short(x) + + self.hover_instance.label_x_value = f"{x}" + self.hover_instance.label_y_value = f"{y}" + + self.hover_instance.xmin_line = float( + ax.bbox.bounds[0]) + self.x + self.hover_instance.xmax_line = float( + ax.bbox.bounds[0] + ax.bbox.bounds[2]) + self.x + self.hover_instance.ymin_line = float( + ax.bbox.bounds[1]) + self.y + self.hover_instance.ymax_line = float( + ax.bbox.bounds[1] + ax.bbox.bounds[3]) + self.y + self.hover_instance.custom_label = line.get_label() - self.hover_instance.custom_color = get_color_from_hex(to_hex(line.get_color())) - - if self.hover_instance.x_hover_pos>self.x+self.figure.axes[0].bbox.bounds[2] + self.figure.axes[0].bbox.bounds[0] or \ - self.hover_instance.x_hover_posself.y+self.figure.axes[0].bbox.bounds[1] + self.figure.axes[0].bbox.bounds[3] or \ - self.hover_instance.y_hover_pos self.x + self.figure.axes[0].bbox.bounds[2] + self.figure.axes[0].bbox.bounds[0] or \ + self.hover_instance.x_hover_pos < self.x + self.figure.axes[0].bbox.bounds[0] or \ + self.hover_instance.y_hover_pos > self.y + self.figure.axes[0].bbox.bounds[1] + self.figure.axes[0].bbox.bounds[3] or \ + self.hover_instance.y_hover_pos < self.y + self.figure.axes[0].bbox.bounds[1]: + self.hover_instance.hover_outside_bound = True else: - self.hover_instance.hover_outside_bound=False + self.hover_instance.hover_outside_bound = False if self.highlight_hover: - self.myevent.x=event.x - self.pos[0] - self.myevent.y=event.y - self.pos[1] - self.myevent.inaxes=self.figure.canvas.inaxes((event.x - self.pos[0], - event.y - self.pos[1])) + self.myevent.x = event.x - self.pos[0] + self.myevent.y = event.y - self.pos[1] + self.myevent.inaxes = self.figure.canvas.inaxes( + (event.x - self.pos[0], event.y - self.pos[1])) + + axes = [a + for a in + self.figure.canvas.figure.get_axes() + if a.in_axes(self.myevent)] - axes = [a for a in self.figure.canvas.figure.get_axes() - if a.in_axes(self.myevent)] + if not axes or not isinstance(line, mlines.Line2D): - if not axes or not isinstance(line, mlines.Line2D): - if self.last_line: - self.clear_line_prop() + self.clear_line_prop() if self.background: - ax.figure.canvas.restore_region(self.background) - #draw (blit method) - ax.figure.canvas.blit(ax.bbox) + ax.figure.canvas.restore_region( + self.background) + # draw (blit method) + ax.figure.canvas.blit(ax.bbox) ax.figure.canvas.flush_events() self.background = None - + return - #blit method (always use because same visual effect as draw) - if self.background is None: - self.background = ax.figure.canvas.copy_from_bbox(ax.figure.bbox) + # blit method (always use because same visual + # effect as draw) + if self.background is None: + self.background = ax.figure.canvas.copy_from_bbox( + ax.figure.bbox) if self.last_line is None: - default_alpha=[] - lines_list=self.lines #get all register lines + default_alpha = [] + lines_list = self.lines # get all register lines for current_line in lines_list: - default_alpha.append(current_line.get_alpha()) - current_line.set_alpha(self.highlight_alpha) - + default_alpha.append( + current_line.get_alpha()) + current_line.set_alpha( + self.highlight_alpha) + ax.figure.canvas.draw_idle() - ax.figure.canvas.flush_events() - self.background_highlight=ax.figure.canvas.copy_from_bbox(ax.figure.bbox) - self.last_line=line - for i,current_line in enumerate(lines_list): + ax.figure.canvas.flush_events() + self.background_highlight = ax.figure.canvas.copy_from_bbox( + ax.figure.bbox) + self.last_line = line + for i, current_line in enumerate(lines_list): current_line.set_alpha(default_alpha[i]) default_alpha[i] - + if self.highlight_prop: - self.last_line_prop={} + self.last_line_prop = {} for key in self.highlight_prop: # if hasattr(line,key): - line_attr = getattr(line,'get_' + key) - self.last_line_prop.update({key:line_attr()}) - set_line_attr = getattr(line,'set_' + key) + line_attr = getattr(line, 'get_' + key) + self.last_line_prop.update( + {key: line_attr()}) + set_line_attr = getattr( + line, 'set_' + key) set_line_attr(self.highlight_prop[key]) elif self.last_line_prop: for key in self.last_line_prop: - set_line_attr = getattr(self.last_line,'set_' + key) - set_line_attr(self.last_line_prop[key]) - self.hover_instance.custom_color = get_color_from_hex(to_hex(line.get_color())) - self.last_line_prop={} + set_line_attr = getattr( + self.last_line, 'set_' + key) + set_line_attr(self.last_line_prop[key]) + self.hover_instance.custom_color = get_color_from_hex( + to_hex(line.get_color())) + self.last_line_prop = {} for key in self.highlight_prop: - line_attr = getattr(line,'get_' + key) - self.last_line_prop.update({key:line_attr()}) - set_line_attr = getattr(line,'set_' + key) - set_line_attr(self.highlight_prop[key]) - self.last_line=line - - ax.figure.canvas.restore_region(self.background_highlight) + line_attr = getattr(line, 'get_' + key) + self.last_line_prop.update( + {key: line_attr()}) + set_line_attr = getattr(line, 'set_' + key) + set_line_attr(self.highlight_prop[key]) + self.last_line = line + + ax.figure.canvas.restore_region( + self.background_highlight) ax.draw_artist(line) - - #draw (blit method) - ax.figure.canvas.blit(ax.bbox) - ax.figure.canvas.flush_events() - + + # draw (blit method) + ax.figure.canvas.blit(ax.bbox) + ax.figure.canvas.flush_events() + return else: - self.text.set_text(f"x={x}, y={y}") - - #blit method (always use because same visual effect as draw) + self.text.set_text(f"x={x}, y={y}") + + # blit method (always use because same visual effect as + # draw) if self.background is None: self.set_cross_hair_visible(False) self.figure.canvas.draw_idle() - self.figure.canvas.flush_events() - self.background = self.figure.canvas.copy_from_bbox(self.figure.bbox) + self.figure.canvas.flush_events() + self.background = self.figure.canvas.copy_from_bbox( + self.figure.bbox) self.set_cross_hair_visible(True) if self.last_line is not None: self.clear_line_prop() - + self.figure.canvas.restore_region(self.background) self.figure.axes[0].draw_artist(self.text) - + self.figure.axes[0].draw_artist(self.horizontal_line) - self.figure.axes[0].draw_artist(self.vertical_line) - - #draw (blit method) - self.figure.canvas.blit(self.figure.axes[0].bbox) + self.figure.axes[0].draw_artist(self.vertical_line) + + # draw (blit method) + self.figure.canvas.blit(self.figure.axes[0].bbox) self.figure.canvas.flush_events() - #if touch is too far, hide cross hair cursor + # if touch is too far, hide cross hair cursor else: - self.set_cross_hair_visible(False) + self.set_cross_hair_visible(False) if self.hover_instance: - self.hover_instance.x_hover_pos=self.x - self.hover_instance.y_hover_pos=self.y - self.hover_instance.show_cursor=False + self.hover_instance.x_hover_pos = self.x + self.hover_instance.y_hover_pos = self.y + self.hover_instance.show_cursor = False self.x_hover_data = None - self.y_hover_data = None + self.y_hover_data = None if self.highlight_hover: - self.myevent.x=event.x - self.pos[0] - self.myevent.y=event.y - self.pos[1] - self.myevent.inaxes=self.figure.canvas.inaxes((event.x - self.pos[0], - event.y - self.pos[1])) + self.myevent.x = event.x - self.pos[0] + self.myevent.y = event.y - self.pos[1] + self.myevent.inaxes = self.figure.canvas.inaxes( + (event.x - self.pos[0], event.y - self.pos[1])) axes = [a for a in self.figure.canvas.figure.get_axes() - if a.in_axes(self.myevent)] + if a.in_axes(self.myevent)] - if not axes: + if not axes: if self.last_line: - self.clear_line_prop() + self.clear_line_prop() if self.background: - self.figure.canvas.restore_region(self.background) - #draw (blit method) - self.figure.canvas.blit(self.figure.axes[0].bbox) + self.figure.canvas.restore_region( + self.background) + # draw (blit method) + self.figure.canvas.blit( + self.figure.axes[0].bbox) self.figure.canvas.flush_events() self.background = None - return + return def autoscale(self): if self.disabled: return - ax=self.figure.axes[0] + ax = self.figure.axes[0] ax.relim(visible_only=self.autoscale_visible_only) ax.autoscale_view(tight=self.autoscale_tight, - scalex=True if self.autoscale_axis!="y" else False, - scaley=True if self.autoscale_axis!="x" else False) - ax.autoscale(axis=self.autoscale_axis,tight=self.autoscale_tight) - if self.twinx: + scalex=True if self.autoscale_axis != "y" else False, + scaley=True if self.autoscale_axis != "x" else False) + ax.autoscale(axis=self.autoscale_axis, tight=self.autoscale_tight) + if self.twinx: ax2 = self.figure.axes[1] - if self.autoscale_axis!="x": + if self.autoscale_axis != "x": ax2.relim(visible_only=self.autoscale_visible_only) ax2.autoscale_view(tight=self.autoscale_tight, - scalex=False, - scaley=True) - ax2.autoscale(axis="y",tight=self.autoscale_tight) - - self.ymin2,self.ymax2 = ax2.get_ylim() + scalex=False, + scaley=True) + ax2.autoscale(axis="y", tight=self.autoscale_tight) + + self.ymin2, self.ymax2 = ax2.get_ylim() ax.figure.canvas.draw_idle() - ax.figure.canvas.flush_events() + ax.figure.canvas.flush_events() ax.set_autoscale_on(False) - self.xmin,self.xmax = ax.get_xlim() - self.ymin,self.ymax = ax.get_ylim() - + self.xmin, self.xmax = ax.get_xlim() + self.ymin, self.ymax = ax.get_ylim() + def home(self) -> None: """ reset data axis - + Return: None """ ax = self.figure.axes[0] - xleft,xright=ax.get_xlim() - ybottom,ytop=ax.get_ylim() - - #check inverted data + xleft, xright = ax.get_xlim() + ybottom, ytop = ax.get_ylim() + + # check inverted data inverted_x = False - if xleft>xright: - inverted_x=True + if xleft > xright: + inverted_x = True inverted_y = False - if ybottom>ytop: - inverted_y=True + if ybottom > ytop: + inverted_y = True if inverted_x: - ax.set_xlim(right=self.xmin,left=self.xmax) + ax.set_xlim(right=self.xmin, left=self.xmax) else: - ax.set_xlim(left=self.xmin,right=self.xmax) + ax.set_xlim(left=self.xmin, right=self.xmax) if inverted_y: - ax.set_ylim(top=self.ymin,bottom=self.ymax) + ax.set_ylim(top=self.ymin, bottom=self.ymax) else: - ax.set_ylim(bottom=self.ymin,top=self.ymax) + ax.set_ylim(bottom=self.ymin, top=self.ymax) - if self.twinx: + if self.twinx: ax2 = self.figure.axes[1] - ybottom2,ytop2=ax2.get_ylim() + ybottom2, ytop2 = ax2.get_ylim() inverted_y2 = False - if ybottom2>ytop2: - inverted_y2=True + if ybottom2 > ytop2: + inverted_y2 = True if inverted_y2: - ax2.set_ylim(top=self.ymin2,bottom=self.ymax2) + ax2.set_ylim(top=self.ymin2, bottom=self.ymax2) else: - ax2.set_ylim(bottom=self.ymin2,top=self.ymax2) - + ax2.set_ylim(bottom=self.ymin2, top=self.ymax2) + self.update_cursor() if self.last_line is not None: - self.clear_line_prop() + self.clear_line_prop() ax.figure.canvas.draw_idle() - ax.figure.canvas.flush_events() + ax.figure.canvas.flush_events() def back(self, *args): """ @@ -934,15 +1023,15 @@ def forward(self, *args): self._update_view() def push_current(self): - """Push the current view limits and position onto the stack.""" - self._nav_stack.push( - WeakKeyDictionary( - {ax: (ax._get_view(), - # Store both the original and modified positions. - (ax.get_position(True).frozen(), - ax.get_position().frozen())) - for ax in self.figure.axes})) - self.set_history_buttons() + """Push the current view limits and position onto the stack.""" + self._nav_stack.push( + WeakKeyDictionary( + {ax: (ax._get_view(), + # Store both the original and modified positions. + (ax.get_position(True).frozen(), + ax.get_position().frozen())) + for ax in self.figure.axes})) + self.set_history_buttons() def update(self): """Reset the Axes stack.""" @@ -965,7 +1054,7 @@ def _update_view(self): # Restore both the original and modified positions ax._set_position(pos_orig, 'original') ax._set_position(pos_active, 'active') - self.figure.canvas.draw_idle() + self.figure.canvas.draw_idle() self.figure.canvas.flush_events() def set_history_buttons(self): @@ -973,13 +1062,13 @@ def set_history_buttons(self): def reset_touch(self) -> None: """ reset touch - + Return: None """ self._touches = [] self._last_touch_pos = {} - + def _get_scale(self): """ kivy scatter _get_scale method """ p1 = Vector(*self.to_parent(0, 0)) @@ -1021,9 +1110,9 @@ def _draw_bitmap(self): self._img_texture.blit_buffer( bytes(self._bitmap), colorfmt="rgba", bufferfmt='ubyte') self._img_texture.flip_vertical() - - self.update_hover() - self.update_selector() + + self.update_hover() + self.update_selector() def transform_with_touch(self, event): """ manage touch behaviour. based on kivy scatter method""" @@ -1031,49 +1120,54 @@ def transform_with_touch(self, event): changed = False if len(self._touches) == self.translation_touches: - if self.touch_mode=='pan': + if self.touch_mode == 'pan': if self._nav_stack() is None: - self.push_current() + self.push_current() if self.twinx: - self.apply_pan_twinx(self.figure.axes[0], self.figure.axes[1], event) + self.apply_pan_twinx( + self.figure.axes[0], self.figure.axes[1], event) else: self.apply_pan(self.figure.axes[0], event) - - if self.touch_mode=='pan_x' or self.touch_mode=='pan_y' \ - or self.touch_mode=='adjust_x' or self.touch_mode=='adjust_y': + if self.touch_mode == 'pan_x' or self.touch_mode == 'pan_y' \ + or self.touch_mode == 'adjust_x' or self.touch_mode == 'adjust_y': if self._nav_stack() is None: - self.push_current() + self.push_current() if self.twinx: - self.apply_pan_twinx(self.figure.axes[0], self.figure.axes[1], event, mode=self.touch_mode) + self.apply_pan_twinx( + self.figure.axes[0], + self.figure.axes[1], + event, + mode=self.touch_mode) else: - self.apply_pan(self.figure.axes[0], event, mode=self.touch_mode) - - elif self.touch_mode=='drag_legend': + self.apply_pan( + self.figure.axes[0], event, mode=self.touch_mode) + + elif self.touch_mode == 'drag_legend': if self.legend_instance: self.apply_drag_legend(self.figure.axes[0], event) - - elif self.touch_mode=='zoombox': + + elif self.touch_mode == 'zoombox': if self._nav_stack() is None: - self.push_current() + self.push_current() real_x, real_y = event.x - self.pos[0], event.y - self.pos[1] - #in case x_init is not create - if not hasattr(self,'x_init'): + # in case x_init is not create + if not hasattr(self, 'x_init'): self.x_init = event.x self.y_init = real_y - self.draw_box(event, self.x_init,self.y_init, event.x, real_y) - - #mode cursor - elif self.touch_mode=='cursor': - self.hover_on=True + self.draw_box(event, self.x_init, self.y_init, event.x, real_y) + + # mode cursor + elif self.touch_mode == 'cursor': + self.hover_on = True self.hover(event) - + changed = True - #note: avoid zoom in/out on touch mode zoombox - if len(self._touches) == 1:# + # note: avoid zoom in/out on touch mode zoombox + if len(self._touches) == 1: return changed - + # we have more than one touch... list of last known pos points = [Vector(self._last_touch_pos[t]) for t in self._touches if t is not event] @@ -1101,21 +1195,21 @@ def transform_with_touch(self, event): if self.auto_zoom: v1 = Vector(0, 10) angle = v1.angle(new_line) + 180 - if angle<0+self.zoom_angle_detection or angle>360-self.zoom_angle_detection: - self.do_zoom_x=False - self.do_zoom_y=True - elif angle>90-self.zoom_angle_detection and angle<90+self.zoom_angle_detection: - self.do_zoom_x=True - self.do_zoom_y=False - elif angle>180-self.zoom_angle_detection and angle<180+self.zoom_angle_detection: - self.do_zoom_x=False - self.do_zoom_y=True - elif angle>270-self.zoom_angle_detection and angle<270+self.zoom_angle_detection: - self.do_zoom_x=True - self.do_zoom_y=False + if angle < 0 + self.zoom_angle_detection or angle > 360 - self.zoom_angle_detection: + self.do_zoom_x = False + self.do_zoom_y = True + elif angle > 90 - self.zoom_angle_detection and angle < 90 + self.zoom_angle_detection: + self.do_zoom_x = True + self.do_zoom_y = False + elif angle > 180 - self.zoom_angle_detection and angle < 180 + self.zoom_angle_detection: + self.do_zoom_x = False + self.do_zoom_y = True + elif angle > 270 - self.zoom_angle_detection and angle < 270 + self.zoom_angle_detection: + self.do_zoom_x = True + self.do_zoom_y = False else: - self.do_zoom_x=True - self.do_zoom_y=True + self.do_zoom_x = True + self.do_zoom_y = True if self.do_scale: # scale = new_line.length() / old_line.length() @@ -1125,111 +1219,120 @@ def transform_with_touch(self, event): scale = self.scale_min / self.scale elif new_scale > self.scale_max: scale = self.scale_max / self.scale - + if self.twinx: - self.apply_zoom_twinx(scale, self.figure.axes[0], self.figure.axes[1], anchor=anchor,new_line=new_line) + self.apply_zoom_twinx( + scale, + self.figure.axes[0], + self.figure.axes[1], + anchor=anchor, + new_line=new_line) else: - self.apply_zoom(scale, self.figure.axes[0], anchor=anchor,new_line=new_line) + self.apply_zoom( + scale, + self.figure.axes[0], + anchor=anchor, + new_line=new_line) changed = True return changed - def on_motion(self,*args): + def on_motion(self, *args): '''Kivy Event to trigger mouse event on motion `enter_notify_event`. ''' if self._pressed or self.disabled: # Do not process this event if there's a touch_move - return + return pos = args[1] newcoord = self.to_widget(pos[0], pos[1]) x = newcoord[0] y = newcoord[1] - inside = self.collide_point(x,y) - if inside: + inside = self.collide_point(x, y) + if inside: # will receive all motion events. if self.figcanvas and self.hover_instance: - #avoid in motion if touch is detected - if not len(self._touches)==0: + # avoid in motion if touch is detected + if not len(self._touches) == 0: return - FakeEventTwinx.x=x - FakeEventTwinx.y=y + FakeEventTwinx.x = x + FakeEventTwinx.y = y self.hover(FakeEventTwinx) - def get_data_xy(self,x,y): + def get_data_xy(self, x, y): """ manage x y data in navigation bar TODO""" - return None,None - + return None, None + def on_touch_down(self, event): """ Manage Mouse/touch press """ if self.disabled: return - + x, y = event.x, event.y if self.collide_point(x, y) and self.figure: self._pressed = True - self.show_compare_cursor=False + self.show_compare_cursor = False if self.legend_instance: - select_legend=False + select_legend = False for current_legend in self.legend_instance: if current_legend.box.collide_point(x, y): - select_legend=True + select_legend = True self.current_legend = current_legend break if select_legend: - if self.touch_mode!='drag_legend': - return False + if self.touch_mode != 'drag_legend': + return False else: event.grab(self) self._touches.append(event) self._last_touch_pos[event] = event.pos - if len(self._touches)>1: - #new touch, reset background - self.background=None - - return True + if len(self._touches) > 1: + # new touch, reset background + self.background = None + + return True else: - self.current_legend = None - + self.current_legend = None + if event.is_mouse_scrolling: if not self.disable_mouse_scrolling: ax = self.figure.axes[0] if self.twinx: ax2 = self.figure.axes[1] self.zoom_factory_twin(event, ax, ax2, base_scale=1.2) - else: + else: self.zoom_factory(event, ax, base_scale=1.2) return True elif event.is_double_tap: if not self.disable_double_tap: - if self.touch_mode!='selector': + if self.touch_mode != 'selector': self.home() return True - + else: - if self.touch_mode=='cursor': - self.hover_on=True - self.hover(event) - elif self.touch_mode=='zoombox': + if self.touch_mode == 'cursor': + self.hover_on = True + self.hover(event) + elif self.touch_mode == 'zoombox': real_x, real_y = x - self.pos[0], y - self.pos[1] - self.x_init=x - self.y_init=real_y - self.draw_box(event, x, real_y, x, real_y) + self.x_init = x + self.y_init = real_y + self.draw_box(event, x, real_y, x, real_y) - elif self.touch_mode=='minmax': - self.min_max(event) - elif self.touch_mode=='selector': - pass + elif self.touch_mode == 'minmax': + self.min_max(event) + elif self.touch_mode == 'selector': + pass event.grab(self) self._touches.append(event) self._last_touch_pos[event] = event.pos - if len(self._touches)>1: - #new touch, reset background - self.background=None - + if len(self._touches) > 1: + # new touch, reset background + self.background = None + return True else: @@ -1239,13 +1342,13 @@ def on_touch_move(self, event): """ Manage Mouse/touch move while pressed """ if self.disabled: return - + x, y = event.x, event.y if event.is_double_tap: if not self.disable_double_tap: - if self.touch_mode!='selector': - self.home() + if self.touch_mode != 'selector': + self.home() return True # scale/translate @@ -1261,94 +1364,96 @@ def on_touch_up(self, event): """ Manage Mouse/touch release """ if self.disabled: return - + # remove it from our saved touches if event in self._touches and event.grab_state: event.ungrab(self) del self._last_touch_pos[event] self._touches.remove(event) - if self.touch_mode=='pan' or self.touch_mode=='zoombox' or \ - self.touch_mode=='pan_x' or self.touch_mode=='pan_y' \ - or self.touch_mode=='adjust_x' or self.touch_mode=='adjust_y' \ - or self.touch_mode=='minmax': + if self.touch_mode == 'pan' or self.touch_mode == 'zoombox' or \ + self.touch_mode == 'pan_x' or self.touch_mode == 'pan_y' \ + or self.touch_mode == 'adjust_x' or self.touch_mode == 'adjust_y' \ + or self.touch_mode == 'minmax': self.push_current() if self.interactive_axis: - if self.touch_mode=='pan_x' or self.touch_mode=='pan_y' \ - or self.touch_mode=='adjust_x' or self.touch_mode=='adjust_y': - self.touch_mode='pan' - self.first_touch_pan=None + if self.touch_mode == 'pan_x' or self.touch_mode == 'pan_y' \ + or self.touch_mode == 'adjust_x' or self.touch_mode == 'adjust_y': + self.touch_mode = 'pan' + self.first_touch_pan = None if self.last_line is not None: - self.clear_line_prop() - + self.clear_line_prop() + x, y = event.x, event.y - if abs(self._box_size[0]) > 1 or abs(self._box_size[1]) > 1 or self.touch_mode=='zoombox': - self.reset_box() + if abs( + self._box_size[0]) > 1 or abs( + self._box_size[1]) > 1 or self.touch_mode == 'zoombox': + self.reset_box() if not self.collide_point(x, y) and self.do_update: - #update axis lim if zoombox is used and touch outside widget - self.update_lim() + # update axis lim if zoombox is used and touch outside widget + self.update_lim() self.figure.canvas.draw_idle() - self.figure.canvas.flush_events() + self.figure.canvas.flush_events() return True - + # stop propagating if its within our bounds if self.collide_point(x, y) and self.figure: self._pressed = False if self.do_update: - self.update_lim() + self.update_lim() - self.anchor_x=None - self.anchor_y=None + self.anchor_x = None + self.anchor_y = None - self.background=None - self.show_compare_cursor=True + self.background = None + self.show_compare_cursor = True self.figure.canvas.draw_idle() - self.figure.canvas.flush_events() - if self.last_line is None or self.touch_mode!='cursor': + self.figure.canvas.flush_events() + if self.last_line is None or self.touch_mode != 'cursor': self.figure.canvas.draw_idle() - self.figure.canvas.flush_events() - + self.figure.canvas.flush_events() + return True - def apply_zoom(self, scale_factor, ax, anchor=(0, 0),new_line=None): + def apply_zoom(self, scale_factor, ax, anchor=(0, 0), new_line=None): """ zoom touch method """ - if self.touch_mode=='selector': + if self.touch_mode == 'selector': return - - x = anchor[0]-self.pos[0] - y = anchor[1]-self.pos[1] + + x = anchor[0] - self.pos[0] + y = anchor[1] - self.pos[1] trans = ax.transData.inverted() - xdata, ydata = trans.transform_point((x+new_line.x, y+new_line.y)) - + xdata, ydata = trans.transform_point((x + new_line.x, y + new_line.y)) + cur_xlim = ax.get_xlim() - cur_ylim = ax.get_ylim() + cur_ylim = ax.get_ylim() + + scale = ax.get_xscale() + yscale = ax.get_yscale() - scale=ax.get_xscale() - yscale=ax.get_yscale() - if scale == 'linear': - old_min=cur_xlim[0] - old_max=cur_xlim[1] + old_min = cur_xlim[0] + old_max = cur_xlim[1] else: - min_=cur_xlim[0] - max_=cur_xlim[1] - old_min = self.transform_eval(min_,ax.yaxis) - xdata = self.transform_eval(xdata,ax.yaxis) - old_max = self.transform_eval(max_,ax.yaxis) + min_ = cur_xlim[0] + max_ = cur_xlim[1] + old_min = self.transform_eval(min_, ax.yaxis) + xdata = self.transform_eval(xdata, ax.yaxis) + old_max = self.transform_eval(max_, ax.yaxis) if yscale == 'linear': - yold_min=cur_ylim[0] - yold_max=cur_ylim[1] + yold_min = cur_ylim[0] + yold_max = cur_ylim[1] else: - ymin_=cur_ylim[0] - ymax_=cur_ylim[1] - yold_min = self.transform_eval(ymin_,ax.yaxis) - ydata = self.transform_eval(ydata,ax.yaxis) - yold_max = self.transform_eval(ymax_,ax.yaxis) + ymin_ = cur_ylim[0] + ymax_ = cur_ylim[1] + yold_min = self.transform_eval(ymin_, ax.yaxis) + ydata = self.transform_eval(ydata, ax.yaxis) + yold_max = self.transform_eval(ymax_, ax.yaxis) new_width = (old_max - old_min) * scale_factor new_height = (yold_max - yold_min) * scale_factor @@ -1358,95 +1463,111 @@ def apply_zoom(self, scale_factor, ax, anchor=(0, 0),new_line=None): if self.do_zoom_x: if scale == 'linear': - ax.set_xlim([xdata - new_width * (1 - relx), xdata + new_width * (relx)]) + ax.set_xlim([xdata - new_width * (1 - relx), + xdata + new_width * (relx)]) else: new_min = xdata - new_width * (1 - relx) new_max = xdata + new_width * (relx) try: - new_min, new_max = self.inv_transform_eval(new_min,ax.yaxis), self.inv_transform_eval(new_max,ax.yaxis) + new_min, new_max = self.inv_transform_eval( + new_min, ax.yaxis), self.inv_transform_eval( + new_max, ax.yaxis) except OverflowError: # Limit case new_min, new_max = min_, max_ if new_min <= 0. or new_max <= 0.: # Limit case - new_min, new_max = min_, max_ + new_min, new_max = min_, max_ ax.set_xlim([new_min, new_max]) - + if self.do_zoom_y: if yscale == 'linear': - ax.set_ylim([ydata - new_height * (1 - rely), ydata + new_height * (rely)]) + ax.set_ylim([ydata - new_height * (1 - rely), + ydata + new_height * (rely)]) else: new_ymin = ydata - new_height * (1 - rely) new_ymax = ydata + new_height * (rely) try: - new_ymin, new_ymax = self.inv_transform_eval(new_ymin,ax.yaxis), self.inv_transform_eval(new_ymax,ax.yaxis) + new_ymin, new_ymax = self.inv_transform_eval( + new_ymin, ax.yaxis), self.inv_transform_eval( + new_ymax, ax.yaxis) except OverflowError: # Limit case new_ymin, new_ymax = ymin_, ymax_ if new_ymin <= 0. or new_ymax <= 0.: # Limit case - new_ymin, new_ymax = ymin_, ymax_ + new_ymin, new_ymax = ymin_, ymax_ ax.set_ylim([new_ymin, new_ymax]) - if self.fast_draw: - #use blit method + if self.fast_draw: + # use blit method if self.background is None or self.last_line is not None: self.background_patch_copy.set_visible(True) ax.figure.canvas.draw_idle() - ax.figure.canvas.flush_events() - self.background = ax.figure.canvas.copy_from_bbox(ax.figure.bbox) + ax.figure.canvas.flush_events() + self.background = ax.figure.canvas.copy_from_bbox( + ax.figure.bbox) self.background_patch_copy.set_visible(False) if self.last_line is not None: self.clear_line_prop() ax.figure.canvas.restore_region(self.background) - + for line in ax.lines: ax.draw_artist(line) ax.figure.canvas.blit(ax.bbox) ax.figure.canvas.flush_events() - + self.update_hover() else: ax.figure.canvas.draw_idle() - ax.figure.canvas.flush_events() + ax.figure.canvas.flush_events() - def apply_zoom_twinx(self, scale_factor, ax, ax2, anchor=(0, 0),new_line=None): + def apply_zoom_twinx( + self, + scale_factor, + ax, + ax2, + anchor=( + 0, + 0), + new_line=None): """twin axis zoom method""" - if self.touch_mode=='selector': - return - x = anchor[0]-self.pos[0] - y = anchor[1]-self.pos[1] + if self.touch_mode == 'selector': + return + x = anchor[0] - self.pos[0] + y = anchor[1] - self.pos[1] trans = ax.transData.inverted() - xdata, ydata = trans.transform_point((x+new_line.x, y+new_line.y)) + xdata, ydata = trans.transform_point((x + new_line.x, y + new_line.y)) trans2 = ax2.transData.inverted() - xdata2, ydata2 = trans2.transform_point((x+new_line.x, y+new_line.y)) + xdata2, ydata2 = trans2.transform_point( + (x + new_line.x, y + new_line.y)) cur_xlim = ax.get_xlim() cur_ylim = ax.get_ylim() - cur_ylim2 = ax2.get_ylim() - - scale=ax.get_xscale() - yscale=ax.get_yscale() - yscale2=ax2.get_yscale() - + cur_ylim2 = ax2.get_ylim() + + scale = ax.get_xscale() + yscale = ax.get_yscale() + yscale2 = ax2.get_yscale() + if scale == 'linear': - old_min=cur_xlim[0] - old_max=cur_xlim[1] + old_min = cur_xlim[0] + old_max = cur_xlim[1] else: - min_=cur_xlim[0] - max_=cur_xlim[1] - old_min = self.transform_eval(min_,ax.yaxis) - xdata = self.transform_eval(xdata,ax.yaxis) - old_max = self.transform_eval(max_,ax.yaxis) + min_ = cur_xlim[0] + max_ = cur_xlim[1] + old_min = self.transform_eval(min_, ax.yaxis) + xdata = self.transform_eval(xdata, ax.yaxis) + old_max = self.transform_eval(max_, ax.yaxis) if yscale == 'linear': - yold_min=cur_ylim[0] - yold_max=cur_ylim[1] + yold_min = cur_ylim[0] + yold_max = cur_ylim[1] else: - ymin_=cur_ylim[0] - ymax_=cur_ylim[1] - yold_min = self.transform_eval(ymin_,ax.yaxis) - ydata = self.transform_eval(ydata,ax.yaxis) - yold_max = self.transform_eval(ymax_,ax.yaxis) + ymin_ = cur_ylim[0] + ymax_ = cur_ylim[1] + yold_min = self.transform_eval(ymin_, ax.yaxis) + ydata = self.transform_eval(ydata, ax.yaxis) + yold_max = self.transform_eval(ymax_, ax.yaxis) new_width = (old_max - old_min) * scale_factor new_height = (yold_max - yold_min) * scale_factor @@ -1456,280 +1577,353 @@ def apply_zoom_twinx(self, scale_factor, ax, ax2, anchor=(0, 0),new_line=None): if self.do_zoom_x: if scale == 'linear': - ax.set_xlim([xdata - new_width * (1 - relx), xdata + new_width * (relx)]) + ax.set_xlim([xdata - new_width * (1 - relx), + xdata + new_width * (relx)]) else: new_min = xdata - new_width * (1 - relx) new_max = xdata + new_width * (relx) try: - new_min, new_max = self.inv_transform_eval(new_min,ax.yaxis), self.inv_transform_eval(new_max,ax.yaxis) + new_min, new_max = self.inv_transform_eval( + new_min, ax.yaxis), self.inv_transform_eval( + new_max, ax.yaxis) except OverflowError: # Limit case new_min, new_max = min_, max_ if new_min <= 0. or new_max <= 0.: # Limit case - new_min, new_max = min_, max_ + new_min, new_max = min_, max_ ax.set_xlim([new_min, new_max]) - + if self.do_zoom_y: if yscale == 'linear': - ax.set_ylim([ydata - new_height * (1 - rely), ydata + new_height * (rely)]) + ax.set_ylim([ydata - new_height * (1 - rely), + ydata + new_height * (rely)]) else: new_ymin = ydata - new_height * (1 - rely) new_ymax = ydata + new_height * (rely) try: - new_ymin, new_ymax = self.inv_transform_eval(new_ymin,ax.yaxis), self.inv_transform_eval(new_ymax,ax.yaxis) + new_ymin, new_ymax = self.inv_transform_eval( + new_ymin, ax.yaxis), self.inv_transform_eval( + new_ymax, ax.yaxis) except OverflowError: # Limit case new_ymin, new_ymax = ymin_, ymax_ if new_ymin <= 0. or new_ymax <= 0.: # Limit case - new_ymin, new_ymax = ymin_, ymax_ + new_ymin, new_ymax = ymin_, ymax_ ax.set_ylim([new_ymin, new_ymax]) if yscale2 == 'linear': - yold2_min=cur_ylim2[0] - yold2_max=cur_ylim2[1] + yold2_min = cur_ylim2[0] + yold2_max = cur_ylim2[1] else: - ymin2_=cur_ylim2[0] - ymax2_=cur_ylim2[1] - yold2_min = self.transform_eval(ymin2_,ax2.yaxis) - ydata2 = self.transform_eval(ydata2,ax2.yaxis) - yold2_max = self.transform_eval(ymax2_,ax2.yaxis) - - new_height2 = (yold2_max - yold2_min) * scale_factor - + ymin2_ = cur_ylim2[0] + ymax2_ = cur_ylim2[1] + yold2_min = self.transform_eval(ymin2_, ax2.yaxis) + ydata2 = self.transform_eval(ydata2, ax2.yaxis) + yold2_max = self.transform_eval(ymax2_, ax2.yaxis) + + new_height2 = (yold2_max - yold2_min) * scale_factor + rely2 = (yold2_max - ydata2) / (yold2_max - yold2_min) if self.do_zoom_y: - ax2.set_ylim([ydata2 - new_height2 * (1 - rely2), ydata2 + new_height2 * (rely2)]) - + ax2.set_ylim([ydata2 - new_height2 * (1 - rely2), + ydata2 + new_height2 * (rely2)]) if yscale2 == 'linear': - ax2.set_ylim([ydata2 - new_height2 * (1 - rely2), ydata2 + new_height2 * (rely2)]) + ax2.set_ylim([ydata2 - new_height2 * (1 - rely2), + ydata2 + new_height2 * (rely2)]) else: new_ymin2 = ydata2 - new_height2 * (1 - rely2) new_ymax2 = ydata2 + new_height2 * (rely2) try: - new_ymin2, new_ymax2 = self.inv_transform_eval(new_ymin2,ax2.yaxis), self.inv_transform_eval(new_ymax2,ax2.yaxis) + new_ymin2, new_ymax2 = self.inv_transform_eval( + new_ymin2, ax2.yaxis), self.inv_transform_eval( + new_ymax2, ax2.yaxis) except OverflowError: # Limit case new_ymin2, new_ymax2 = ymin2_, ymax2_ if new_ymin2 <= 0. or new_ymax2 <= 0.: # Limit case - new_ymin2, new_ymax2 = ymin2_, ymax2_ + new_ymin2, new_ymax2 = ymin2_, ymax2_ ax2.set_ylim([new_ymin2, new_ymax2]) - if self.fast_draw: - #use blit method + if self.fast_draw: + # use blit method if self.background is None or self.last_line is not None: self.background_patch_copy.set_visible(True) self.background_ax2_patch_copy.set_visible(True) ax.figure.canvas.draw_idle() - ax.figure.canvas.flush_events() - self.background = ax.figure.canvas.copy_from_bbox(ax.figure.bbox) - self.background_patch_copy.set_visible(False) + ax.figure.canvas.flush_events() + self.background = ax.figure.canvas.copy_from_bbox( + ax.figure.bbox) + self.background_patch_copy.set_visible(False) self.background_ax2_patch_copy.set_visible(False) if self.last_line is not None: self.clear_line_prop() - ax.figure.canvas.restore_region(self.background) - + ax.figure.canvas.restore_region(self.background) + for line in ax.lines: if line.get_visible(): ax.draw_artist(line) for line in ax2.lines: if line.get_visible(): ax2.draw_artist(line) - + ax.figure.canvas.blit(ax.bbox) - ax.figure.canvas.flush_events() - - self.update_hover() - + ax.figure.canvas.flush_events() + + self.update_hover() + else: ax.figure.canvas.draw_idle() - ax.figure.canvas.flush_events() - + ax.figure.canvas.flush_events() + def apply_pan(self, ax, event, mode='pan'): """ pan method """ - + trans = ax.transData.inverted() - xdata, ydata = trans.transform_point((event.x-self.pos[0], event.y-self.pos[1])) - xpress, ypress = trans.transform_point((self._last_touch_pos[event][0]-self.pos[0], self._last_touch_pos[event][1]-self.pos[1])) - - scale=ax.get_xscale() - yscale=ax.get_yscale() - + xdata, ydata = trans.transform_point( + (event.x - self.pos[0], event.y - self.pos[1])) + xpress, ypress = trans.transform_point( + (self._last_touch_pos[event][0] - self.pos[0], + self._last_touch_pos[event][1] - self.pos[1])) + + scale = ax.get_xscale() + yscale = ax.get_yscale() + if scale == 'linear': dx = xdata - xpress else: - dx = self.transform_eval(xdata,ax.xaxis) - \ - self.transform_eval(xpress,ax.xaxis) - + dx = self.transform_eval(xdata, ax.xaxis) - \ + self.transform_eval(xpress, ax.xaxis) + if yscale == 'linear': dy = ydata - ypress else: - dy = self.transform_eval(ydata,ax.yaxis) - \ - self.transform_eval(ypress,ax.yaxis) + dy = self.transform_eval(ydata, ax.yaxis) - \ + self.transform_eval(ypress, ax.yaxis) - xleft,xright=self.figure.axes[0].get_xlim() - ybottom,ytop=self.figure.axes[0].get_ylim() - - #check inverted data + xleft, xright = self.figure.axes[0].get_xlim() + ybottom, ytop = self.figure.axes[0].get_ylim() + + # check inverted data inverted_x = False - if xleft>xright: - inverted_x=True - cur_xlim=(xright,xleft) + if xleft > xright: + inverted_x = True + cur_xlim = (xright, xleft) else: - cur_xlim=(xleft,xright) + cur_xlim = (xleft, xright) inverted_y = False - if ybottom>ytop: - inverted_y=True - cur_ylim=(ytop,ybottom) + if ybottom > ytop: + inverted_y = True + cur_ylim = (ytop, ybottom) else: - cur_ylim=(ybottom,ytop) - - if self.interactive_axis and self.touch_mode=='pan' and not self.first_touch_pan=='pan': - if (ydata < cur_ylim[0] and not inverted_y) or (ydata > cur_ylim[1] and inverted_y): - left_anchor_zone= (cur_xlim[1] - cur_xlim[0])*.2 + cur_xlim[0] - right_anchor_zone= (cur_xlim[1] - cur_xlim[0])*.8 + cur_xlim[0] + cur_ylim = (ybottom, ytop) + + if self.interactive_axis and self.touch_mode == 'pan' and not self.first_touch_pan == 'pan': + if (ydata < cur_ylim[0] and not inverted_y) or ( + ydata > cur_ylim[1] and inverted_y): + left_anchor_zone = ( + cur_xlim[1] - cur_xlim[0]) * .2 + cur_xlim[0] + right_anchor_zone = ( + cur_xlim[1] - cur_xlim[0]) * .8 + cur_xlim[0] if xdata < left_anchor_zone or xdata > right_anchor_zone: mode = 'adjust_x' else: mode = 'pan_x' self.touch_mode = mode elif (xdata < cur_xlim[0] and not inverted_x) or (xdata > cur_xlim[1] and inverted_x): - bottom_anchor_zone= (cur_ylim[1] - cur_ylim[0])*.2 + cur_ylim[0] - top_anchor_zone= (cur_ylim[1] - cur_ylim[0])*.8 + cur_ylim[0] + bottom_anchor_zone = ( + cur_ylim[1] - cur_ylim[0]) * .2 + cur_ylim[0] + top_anchor_zone = ( + cur_ylim[1] - cur_ylim[0]) * .8 + cur_ylim[0] if ydata < bottom_anchor_zone or ydata > top_anchor_zone: mode = 'adjust_y' else: - mode= 'pan_y' + mode = 'pan_y' self.touch_mode = mode else: self.touch_mode = 'pan' - if not mode=='pan_y' and not mode=='adjust_y': - if mode=='adjust_x': + if not mode == 'pan_y' and not mode == 'adjust_y': + if mode == 'adjust_x': if self.anchor_x is None: - midpoint= (cur_xlim[1] + cur_xlim[0])/2 - if xdata>midpoint: - self.anchor_x='left' + midpoint = (cur_xlim[1] + cur_xlim[0]) / 2 + if xdata > midpoint: + self.anchor_x = 'left' else: - self.anchor_x='right' - if self.anchor_x=='left': - if xdata> cur_xlim[0]: + self.anchor_x = 'right' + if self.anchor_x == 'left': + if xdata > cur_xlim[0]: if scale == 'linear': cur_xlim -= dx else: try: - cur_xlim = [self.inv_transform_eval((self.transform_eval(cur_xlim[0],ax.xaxis) - dx),ax.xaxis), - self.inv_transform_eval((self.transform_eval(cur_xlim[1],ax.xaxis) - dx),ax.xaxis)] + cur_xlim = [ + self.inv_transform_eval( + (self.transform_eval( + cur_xlim[0], + ax.xaxis) - dx), + ax.xaxis), + self.inv_transform_eval( + (self.transform_eval( + cur_xlim[1], + ax.xaxis) - dx), + ax.xaxis)] except (ValueError, OverflowError): - cur_xlim = cur_xlim # Keep previous limits + cur_xlim = cur_xlim # Keep previous limits if inverted_x: - ax.set_xlim(cur_xlim[1],None) + ax.set_xlim(cur_xlim[1], None) else: - ax.set_xlim(None,cur_xlim[1]) + ax.set_xlim(None, cur_xlim[1]) else: - if xdata< cur_xlim[1]: + if xdata < cur_xlim[1]: if scale == 'linear': cur_xlim -= dx else: try: - cur_xlim = [self.inv_transform_eval((self.transform_eval(cur_xlim[0],ax.xaxis) - dx),ax.xaxis), - self.inv_transform_eval((self.transform_eval(cur_xlim[1],ax.xaxis) - dx),ax.xaxis)] + cur_xlim = [ + self.inv_transform_eval( + (self.transform_eval( + cur_xlim[0], + ax.xaxis) - dx), + ax.xaxis), + self.inv_transform_eval( + (self.transform_eval( + cur_xlim[1], + ax.xaxis) - dx), + ax.xaxis)] except (ValueError, OverflowError): - cur_xlim = cur_xlim # Keep previous limits + cur_xlim = cur_xlim # Keep previous limits if inverted_x: - ax.set_xlim(None,cur_xlim[0]) + ax.set_xlim(None, cur_xlim[0]) else: - ax.set_xlim(cur_xlim[0],None) + ax.set_xlim(cur_xlim[0], None) else: if scale == 'linear': cur_xlim -= dx else: try: - cur_xlim = [self.inv_transform_eval((self.transform_eval(cur_xlim[0],ax.xaxis) - dx),ax.xaxis), - self.inv_transform_eval((self.transform_eval(cur_xlim[1],ax.xaxis) - dx),ax.xaxis)] + cur_xlim = [ + self.inv_transform_eval( + (self.transform_eval( + cur_xlim[0], + ax.xaxis) - dx), + ax.xaxis), + self.inv_transform_eval( + (self.transform_eval( + cur_xlim[1], + ax.xaxis) - dx), + ax.xaxis)] except (ValueError, OverflowError): - cur_xlim = cur_xlim # Keep previous limits + cur_xlim = cur_xlim # Keep previous limits if inverted_x: - ax.set_xlim(cur_xlim[1],cur_xlim[0]) + ax.set_xlim(cur_xlim[1], cur_xlim[0]) else: ax.set_xlim(cur_xlim) - - if not mode=='pan_x' and not mode=='adjust_x': - if mode=='adjust_y': + + if not mode == 'pan_x' and not mode == 'adjust_x': + if mode == 'adjust_y': if self.anchor_y is None: - midpoint= (cur_ylim[1] + cur_ylim[0])/2 - if ydata>midpoint: - self.anchor_y='top' + midpoint = (cur_ylim[1] + cur_ylim[0]) / 2 + if ydata > midpoint: + self.anchor_y = 'top' else: - self.anchor_y='bottom' - - if self.anchor_y=='top': - if ydata> cur_ylim[0]: + self.anchor_y = 'bottom' + + if self.anchor_y == 'top': + if ydata > cur_ylim[0]: if yscale == 'linear': cur_ylim -= dy - + else: try: - cur_ylim = [self.inv_transform_eval((self.transform_eval(cur_ylim[0],ax.yaxis) - dy),ax.yaxis), - self.inv_transform_eval((self.transform_eval(cur_ylim[1],ax.yaxis) - dy),ax.yaxis)] + cur_ylim = [ + self.inv_transform_eval( + (self.transform_eval( + cur_ylim[0], + ax.yaxis) - dy), + ax.yaxis), + self.inv_transform_eval( + (self.transform_eval( + cur_ylim[1], + ax.yaxis) - dy), + ax.yaxis)] except (ValueError, OverflowError): - cur_ylim = cur_ylim # Keep previous limits - + cur_ylim = cur_ylim # Keep previous limits + if inverted_y: - ax.set_ylim(cur_ylim[1],None) + ax.set_ylim(cur_ylim[1], None) else: - ax.set_ylim(None,cur_ylim[1]) + ax.set_ylim(None, cur_ylim[1]) else: - if ydata< cur_ylim[1]: + if ydata < cur_ylim[1]: if yscale == 'linear': - cur_ylim -= dy - + cur_ylim -= dy + else: try: - cur_ylim = [self.inv_transform_eval((self.transform_eval(cur_ylim[0],ax.yaxis) - dy),ax.yaxis), - self.inv_transform_eval((self.transform_eval(cur_ylim[1],ax.yaxis) - dy),ax.yaxis)] + cur_ylim = [ + self.inv_transform_eval( + (self.transform_eval( + cur_ylim[0], + ax.yaxis) - dy), + ax.yaxis), + self.inv_transform_eval( + (self.transform_eval( + cur_ylim[1], + ax.yaxis) - dy), + ax.yaxis)] except (ValueError, OverflowError): - cur_ylim = cur_ylim # Keep previous limits + cur_ylim = cur_ylim # Keep previous limits if inverted_y: - ax.set_ylim(None,cur_ylim[0]) + ax.set_ylim(None, cur_ylim[0]) else: - ax.set_ylim(cur_ylim[0],None) - else: + ax.set_ylim(cur_ylim[0], None) + else: if yscale == 'linear': - cur_ylim -= dy - + cur_ylim -= dy + else: try: - cur_ylim = [self.inv_transform_eval((self.transform_eval(cur_ylim[0],ax.yaxis) - dy),ax.yaxis), - self.inv_transform_eval((self.transform_eval(cur_ylim[1],ax.yaxis) - dy),ax.yaxis)] + cur_ylim = [ + self.inv_transform_eval( + (self.transform_eval( + cur_ylim[0], + ax.yaxis) - dy), + ax.yaxis), + self.inv_transform_eval( + (self.transform_eval( + cur_ylim[1], + ax.yaxis) - dy), + ax.yaxis)] except (ValueError, OverflowError): - cur_ylim = cur_ylim # Keep previous limits + cur_ylim = cur_ylim # Keep previous limits if inverted_y: - ax.set_ylim(cur_ylim[1],cur_ylim[0]) + ax.set_ylim(cur_ylim[1], cur_ylim[0]) else: ax.set_ylim(cur_ylim) if self.first_touch_pan is None: - self.first_touch_pan=self.touch_mode + self.first_touch_pan = self.touch_mode - if self.fast_draw: - #use blit method + if self.fast_draw: + # use blit method if self.background is None or self.last_line is not None: self.background_patch_copy.set_visible(True) ax.figure.canvas.draw_idle() - ax.figure.canvas.flush_events() - self.background = ax.figure.canvas.copy_from_bbox(ax.figure.bbox) + ax.figure.canvas.flush_events() + self.background = ax.figure.canvas.copy_from_bbox( + ax.figure.bbox) self.background_patch_copy.set_visible(False) if self.last_line is not None: self.clear_line_prop() - ax.figure.canvas.restore_region(self.background) - + ax.figure.canvas.restore_region(self.background) + for line in ax.lines: ax.draw_artist(line) - + ax.figure.canvas.blit(ax.bbox) - ax.figure.canvas.flush_events() - - self.update_hover() - + ax.figure.canvas.flush_events() + + self.update_hover() + else: ax.figure.canvas.draw_idle() ax.figure.canvas.flush_events() @@ -1737,626 +1931,806 @@ def apply_pan(self, ax, event, mode='pan'): def apply_pan_twinx(self, ax, ax2, event, mode='pan'): """twin axis pan method""" trans = ax.transData.inverted() - xdata, ydata = trans.transform_point((event.x-self.pos[0], event.y-self.pos[1])) - xpress, ypress = trans.transform_point((self._last_touch_pos[event][0]-self.pos[0], self._last_touch_pos[event][1]-self.pos[1])) - - scale=ax.get_xscale() - yscale=ax.get_yscale() - yscale2=ax2.get_yscale() - - update_cursor=False - + xdata, ydata = trans.transform_point( + (event.x - self.pos[0], event.y - self.pos[1])) + xpress, ypress = trans.transform_point( + (self._last_touch_pos[event][0] - self.pos[0], + self._last_touch_pos[event][1] - self.pos[1])) + + scale = ax.get_xscale() + yscale = ax.get_yscale() + yscale2 = ax2.get_yscale() + + update_cursor = False + if scale == 'linear': dx = xdata - xpress else: - dx = self.transform_eval(xdata,ax.xaxis) - \ - self.transform_eval(xpress,ax.xaxis) - + dx = self.transform_eval(xdata, ax.xaxis) - \ + self.transform_eval(xpress, ax.xaxis) + if yscale == 'linear': dy = ydata - ypress else: - dy = self.transform_eval(ydata,ax.yaxis) - \ - self.transform_eval(ypress,ax.yaxis) - - xleft,xright=ax.get_xlim() - ybottom,ytop=ax.get_ylim() - ybottom2,ytop2=ax2.get_ylim() - - #check inverted data + dy = self.transform_eval(ydata, ax.yaxis) - \ + self.transform_eval(ypress, ax.yaxis) + + xleft, xright = ax.get_xlim() + ybottom, ytop = ax.get_ylim() + ybottom2, ytop2 = ax2.get_ylim() + + # check inverted data inverted_x = False - if xleft>xright: - inverted_x=True - cur_xlim=(xright,xleft) + if xleft > xright: + inverted_x = True + cur_xlim = (xright, xleft) else: - cur_xlim=(xleft,xright) + cur_xlim = (xleft, xright) inverted_y = False - if ybottom>ytop: - inverted_y=True - cur_ylim=(ytop,ybottom) + if ybottom > ytop: + inverted_y = True + cur_ylim = (ytop, ybottom) else: - cur_ylim=(ybottom,ytop) + cur_ylim = (ybottom, ytop) inverted_y2 = False - if ybottom2>ytop2: - inverted_y2=True - cur_ylim2=(ytop2,ybottom2) + if ybottom2 > ytop2: + inverted_y2 = True + cur_ylim2 = (ytop2, ybottom2) else: - cur_ylim2=(ybottom2,ytop2) - + cur_ylim2 = (ybottom2, ytop2) + ratio = (cur_ylim2[1] - cur_ylim2[0]) / (cur_ylim[1] - cur_ylim[0]) ydata2 = ydata * ratio + cur_ylim2[0] ypress2 = ypress * ratio + cur_ylim2[0] - + if yscale2 == 'linear': - dy2 = ydata2 - ypress2 + dy2 = ydata2 - ypress2 else: - dy2 = self.transform_eval(ydata2,ax2.yaxis) - \ - self.transform_eval(ypress2,ax2.yaxis) - - if self.interactive_axis and self.touch_mode=='pan' and not self.first_touch_pan=='pan': - if (ydata < cur_ylim[0] and not inverted_y) or (ydata > cur_ylim[1] and inverted_y): - left_anchor_zone= (cur_xlim[1] - cur_xlim[0])*.2 + cur_xlim[0] - right_anchor_zone= (cur_xlim[1] - cur_xlim[0])*.8 + cur_xlim[0] + dy2 = self.transform_eval(ydata2, ax2.yaxis) - \ + self.transform_eval(ypress2, ax2.yaxis) + + if self.interactive_axis and self.touch_mode == 'pan' and not self.first_touch_pan == 'pan': + if (ydata < cur_ylim[0] and not inverted_y) or ( + ydata > cur_ylim[1] and inverted_y): + left_anchor_zone = ( + cur_xlim[1] - cur_xlim[0]) * .2 + cur_xlim[0] + right_anchor_zone = ( + cur_xlim[1] - cur_xlim[0]) * .8 + cur_xlim[0] if xdata < left_anchor_zone or xdata > right_anchor_zone: mode = 'adjust_x' else: mode = 'pan_x' self.touch_mode = mode elif (xdata < cur_xlim[0] and not inverted_y) or (xdata > cur_xlim[1] and inverted_y) \ - or (xdata > cur_xlim[1] and not inverted_y) or (xdata < cur_xlim[0] and inverted_y): - bottom_anchor_zone= (cur_ylim[1] - cur_ylim[0])*.2 + cur_ylim[0] - top_anchor_zone= (cur_ylim[1] - cur_ylim[0])*.8 + cur_ylim[0] + or (xdata > cur_xlim[1] and not inverted_y) or (xdata < cur_xlim[0] and inverted_y): + bottom_anchor_zone = ( + cur_ylim[1] - cur_ylim[0]) * .2 + cur_ylim[0] + top_anchor_zone = ( + cur_ylim[1] - cur_ylim[0]) * .8 + cur_ylim[0] if ydata < bottom_anchor_zone or ydata > top_anchor_zone: mode = 'adjust_y' else: - mode= 'pan_y' + mode = 'pan_y' self.touch_mode = mode - + else: - self.touch_mode = 'pan' + self.touch_mode = 'pan' - if not mode=='pan_y' and not mode=='adjust_y': - if mode=='adjust_x': + if not mode == 'pan_y' and not mode == 'adjust_y': + if mode == 'adjust_x': if self.anchor_x is None: - midpoint= (cur_xlim[1] + cur_xlim[0])/2 - if xdata>midpoint: - self.anchor_x='left' + midpoint = (cur_xlim[1] + cur_xlim[0]) / 2 + if xdata > midpoint: + self.anchor_x = 'left' else: - self.anchor_x='right' - if self.anchor_x=='left': - if xdata> cur_xlim[0]: + self.anchor_x = 'right' + if self.anchor_x == 'left': + if xdata > cur_xlim[0]: if scale == 'linear': cur_xlim -= dx else: try: - cur_xlim = [self.inv_transform_eval((self.transform_eval(cur_xlim[0],ax.xaxis) - dx),ax.xaxis), - self.inv_transform_eval((self.transform_eval(cur_xlim[1],ax.xaxis) - dx),ax.xaxis)] + cur_xlim = [ + self.inv_transform_eval( + (self.transform_eval( + cur_xlim[0], + ax.xaxis) - dx), + ax.xaxis), + self.inv_transform_eval( + (self.transform_eval( + cur_xlim[1], + ax.xaxis) - dx), + ax.xaxis)] except (ValueError, OverflowError): - cur_xlim = cur_xlim # Keep previous limits + cur_xlim = cur_xlim # Keep previous limits if inverted_x: - ax.set_xlim(cur_xlim[1],None) + ax.set_xlim(cur_xlim[1], None) else: - ax.set_xlim(None,cur_xlim[1]) - + ax.set_xlim(None, cur_xlim[1]) + else: - if xdata< cur_xlim[1]: + if xdata < cur_xlim[1]: if scale == 'linear': cur_xlim -= dx else: try: - cur_xlim = [self.inv_transform_eval((self.transform_eval(cur_xlim[0],ax.xaxis) - dx),ax.xaxis), - self.inv_transform_eval((self.transform_eval(cur_xlim[1],ax.xaxis) - dx),ax.xaxis)] + cur_xlim = [ + self.inv_transform_eval( + (self.transform_eval( + cur_xlim[0], + ax.xaxis) - dx), + ax.xaxis), + self.inv_transform_eval( + (self.transform_eval( + cur_xlim[1], + ax.xaxis) - dx), + ax.xaxis)] except (ValueError, OverflowError): cur_xlim = cur_xlim # Keep previous limits if inverted_x: - ax.set_xlim(None,cur_xlim[0]) + ax.set_xlim(None, cur_xlim[0]) else: - ax.set_xlim(cur_xlim[0],None) - + ax.set_xlim(cur_xlim[0], None) + else: if scale == 'linear': cur_xlim -= dx else: try: - cur_xlim = [self.inv_transform_eval((self.transform_eval(cur_xlim[0],ax.xaxis) - dx),ax.xaxis), - self.inv_transform_eval((self.transform_eval(cur_xlim[1],ax.xaxis) - dx),ax.xaxis)] + cur_xlim = [ + self.inv_transform_eval( + (self.transform_eval( + cur_xlim[0], + ax.xaxis) - dx), + ax.xaxis), + self.inv_transform_eval( + (self.transform_eval( + cur_xlim[1], + ax.xaxis) - dx), + ax.xaxis)] except (ValueError, OverflowError): cur_xlim = cur_xlim # Keep previous limits if inverted_x: - ax.set_xlim(cur_xlim[1],cur_xlim[0]) - else: + ax.set_xlim(cur_xlim[1], cur_xlim[0]) + else: ax.set_xlim(cur_xlim) - if not mode=='pan_x' and not mode=='adjust_x': - if mode=='adjust_y': + if not mode == 'pan_x' and not mode == 'adjust_x': + if mode == 'adjust_y': trans_ax2 = ax2.transData.inverted() - xdata_ax2, ydata_ax2 = trans_ax2.transform_point((event.x-self.pos[0], event.y-self.pos[1])) + xdata_ax2, ydata_ax2 = trans_ax2.transform_point( + (event.x - self.pos[0], event.y - self.pos[1])) if self.anchor_y is None: - midpoint_x = (cur_xlim[1] + cur_xlim[0])/2 - midpoint_ax1= (cur_ylim[1] + cur_ylim[0])/2 - midpoint_ax2= (cur_ylim2[1] + cur_ylim2[0])/2 - if (xdata>midpoint_x and not inverted_x) or (xdata midpoint_x and not inverted_x) or ( + xdata < midpoint_x and inverted_x): + ax_anchor = 'right' else: - ax_anchor='left' - - if ax_anchor=='left': - if ydata>midpoint_ax1: - self.anchor_y='top_left' + ax_anchor = 'left' + + if ax_anchor == 'left': + if ydata > midpoint_ax1: + self.anchor_y = 'top_left' else: - self.anchor_y='bottom_left' - else: - - if ydata_ax2>midpoint_ax2: - self.anchor_y='top_right' + self.anchor_y = 'bottom_left' + else: + + if ydata_ax2 > midpoint_ax2: + self.anchor_y = 'top_right' else: - self.anchor_y='bottom_right' + self.anchor_y = 'bottom_right' # print(self.anchor_y) - if self.anchor_y=='top_left': + if self.anchor_y == 'top_left': if ydata > cur_ylim[0]: if yscale == 'linear': cur_ylim -= dy - + else: try: - cur_ylim = [self.inv_transform_eval((self.transform_eval(cur_ylim[0],ax.yaxis) - dy),ax.yaxis), - self.inv_transform_eval((self.transform_eval(cur_ylim[1],ax.yaxis) - dy),ax.yaxis)] + cur_ylim = [ + self.inv_transform_eval( + (self.transform_eval( + cur_ylim[0], + ax.yaxis) - dy), + ax.yaxis), + self.inv_transform_eval( + (self.transform_eval( + cur_ylim[1], + ax.yaxis) - dy), + ax.yaxis)] except (ValueError, OverflowError): - cur_ylim = cur_ylim # Keep previous limits + cur_ylim = cur_ylim # Keep previous limits if inverted_y: - ax.set_ylim(cur_ylim[1],None) + ax.set_ylim(cur_ylim[1], None) else: - ax.set_ylim(None,cur_ylim[1]) - - update_cursor=True - - elif self.anchor_y=='top_right': + ax.set_ylim(None, cur_ylim[1]) + + update_cursor = True + + elif self.anchor_y == 'top_right': if ydata_ax2 > cur_ylim2[0]: if yscale2 == 'linear': - cur_ylim2 -= dy2 - + cur_ylim2 -= dy2 + else: try: - cur_ylim2 = [self.inv_transform_eval((self.transform_eval(cur_ylim2[0],ax2.yaxis) - dy2),ax2.yaxis), - self.inv_transform_eval((self.transform_eval(cur_ylim2[1],ax2.yaxis) - dy2),ax2.yaxis)] + cur_ylim2 = [ + self.inv_transform_eval( + (self.transform_eval( + cur_ylim2[0], + ax2.yaxis) - dy2), + ax2.yaxis), + self.inv_transform_eval( + (self.transform_eval( + cur_ylim2[1], + ax2.yaxis) - dy2), + ax2.yaxis)] except (ValueError, OverflowError): - cur_ylim2 = cur_ylim2 # Keep previous limits - + cur_ylim2 = cur_ylim2 # Keep previous limits + if inverted_y2: - ax2.set_ylim(cur_ylim2[1],None) + ax2.set_ylim(cur_ylim2[1], None) else: - ax2.set_ylim(None,cur_ylim2[1]) - - update_cursor=True - - elif self.anchor_y=='bottom_left': + ax2.set_ylim(None, cur_ylim2[1]) + + update_cursor = True + + elif self.anchor_y == 'bottom_left': if ydata < cur_ylim[1]: if yscale == 'linear': cur_ylim -= dy - + else: try: - cur_ylim = [self.inv_transform_eval((self.transform_eval(cur_ylim[0],ax.yaxis) - dy),ax.yaxis), - self.inv_transform_eval((self.transform_eval(cur_ylim[1],ax.yaxis) - dy),ax.yaxis)] + cur_ylim = [ + self.inv_transform_eval( + (self.transform_eval( + cur_ylim[0], + ax.yaxis) - dy), + ax.yaxis), + self.inv_transform_eval( + (self.transform_eval( + cur_ylim[1], + ax.yaxis) - dy), + ax.yaxis)] except (ValueError, OverflowError): - cur_ylim = cur_ylim # Keep previous limits + cur_ylim = cur_ylim # Keep previous limits if inverted_y: - ax.set_ylim(None,cur_ylim[0]) + ax.set_ylim(None, cur_ylim[0]) else: - ax.set_ylim(cur_ylim[0],None) - - update_cursor=True + ax.set_ylim(cur_ylim[0], None) + + update_cursor = True else: if ydata_ax2 < cur_ylim2[1]: if yscale2 == 'linear': - cur_ylim2 -= dy2 - + cur_ylim2 -= dy2 + else: try: - cur_ylim2 = [self.inv_transform_eval((self.transform_eval(cur_ylim2[0],ax2.yaxis) - dy2),ax2.yaxis), - self.inv_transform_eval((self.transform_eval(cur_ylim2[1],ax2.yaxis) - dy2),ax2.yaxis)] + cur_ylim2 = [ + self.inv_transform_eval( + (self.transform_eval( + cur_ylim2[0], + ax2.yaxis) - dy2), + ax2.yaxis), + self.inv_transform_eval( + (self.transform_eval( + cur_ylim2[1], + ax2.yaxis) - dy2), + ax2.yaxis)] except (ValueError, OverflowError): - cur_ylim2 = cur_ylim2 # Keep previous limits - # ax2.set_ylim(cur_ylim2[0],None) + cur_ylim2 = cur_ylim2 # Keep previous limits + # ax2.set_ylim(cur_ylim2[0],None) if inverted_y2: - ax2.set_ylim(None,cur_ylim2[0]) + ax2.set_ylim(None, cur_ylim2[0]) else: - ax2.set_ylim(cur_ylim2[0],None) - - update_cursor=True - else: + ax2.set_ylim(cur_ylim2[0], None) + + update_cursor = True + else: if yscale == 'linear': - cur_ylim -= dy - + cur_ylim -= dy + else: try: - cur_ylim = [self.inv_transform_eval((self.transform_eval(cur_ylim[0],ax.yaxis) - dy),ax.yaxis), - self.inv_transform_eval((self.transform_eval(cur_ylim[1],ax.yaxis) - dy),ax.yaxis)] + cur_ylim = [ + self.inv_transform_eval( + (self.transform_eval( + cur_ylim[0], + ax.yaxis) - dy), + ax.yaxis), + self.inv_transform_eval( + (self.transform_eval( + cur_ylim[1], + ax.yaxis) - dy), + ax.yaxis)] except (ValueError, OverflowError): cur_ylim = cur_ylim # Keep previous limits - + if yscale2 == 'linear': - cur_ylim2 -= dy2 - + cur_ylim2 -= dy2 + else: try: - cur_ylim2 = [self.inv_transform_eval((self.transform_eval(cur_ylim2[0],ax2.yaxis) - dy2),ax2.yaxis), - self.inv_transform_eval((self.transform_eval(cur_ylim2[1],ax2.yaxis) - dy2),ax2.yaxis)] + cur_ylim2 = [ + self.inv_transform_eval( + (self.transform_eval( + cur_ylim2[0], + ax2.yaxis) - dy2), + ax2.yaxis), + self.inv_transform_eval( + (self.transform_eval( + cur_ylim2[1], + ax2.yaxis) - dy2), + ax2.yaxis)] except (ValueError, OverflowError): cur_ylim2 = cur_ylim2 # Keep previous limits # ax.set_ylim(cur_ylim) # ax2.set_ylim(cur_ylim2) if inverted_y: - ax.set_ylim(cur_ylim[1],cur_ylim[0]) + ax.set_ylim(cur_ylim[1], cur_ylim[0]) else: ax.set_ylim(cur_ylim) if inverted_y2: - ax2.set_ylim(cur_ylim2[1],cur_ylim2[0]) + ax2.set_ylim(cur_ylim2[1], cur_ylim2[0]) else: ax2.set_ylim(cur_ylim2) - + if self.first_touch_pan is None: - self.first_touch_pan=self.touch_mode - + self.first_touch_pan = self.touch_mode + if update_cursor: self.update_cursor() - if self.fast_draw: - #use blit method + if self.fast_draw: + # use blit method if self.background is None or self.last_line is not None: self.background_patch_copy.set_visible(True) self.background_ax2_patch_copy.set_visible(True) ax.figure.canvas.draw_idle() - ax.figure.canvas.flush_events() - self.background = ax.figure.canvas.copy_from_bbox(ax.figure.bbox) - self.background_patch_copy.set_visible(False) + ax.figure.canvas.flush_events() + self.background = ax.figure.canvas.copy_from_bbox( + ax.figure.bbox) + self.background_patch_copy.set_visible(False) self.background_ax2_patch_copy.set_visible(False) if self.last_line is not None: - self.clear_line_prop() - ax.figure.canvas.restore_region(self.background) - + self.clear_line_prop() + ax.figure.canvas.restore_region(self.background) + for line in ax.lines: if line.get_visible(): ax.draw_artist(line) for line in ax2.lines: if line.get_visible(): ax2.draw_artist(line) - + ax.figure.canvas.blit(ax.bbox) - ax.figure.canvas.flush_events() - - self.update_hover() - + ax.figure.canvas.flush_events() + + self.update_hover() + else: ax.figure.canvas.draw_idle() - ax.figure.canvas.flush_events() + ax.figure.canvas.flush_events() def update_hover(self): """ update hover on fast draw (if exist)""" if self.hover_instance: if self.compare_xdata and self.hover_instance: - if (self.touch_mode!='cursor' or len(self._touches) > 1) and not self.show_compare_cursor: - self.hover_instance.hover_outside_bound=True - - elif self.show_compare_cursor and self.touch_mode=='cursor': - self.show_compare_cursor=False + if (self.touch_mode != 'cursor' or len(self._touches) + > 1) and not self.show_compare_cursor: + self.hover_instance.hover_outside_bound = True + + elif self.show_compare_cursor and self.touch_mode == 'cursor': + self.show_compare_cursor = False else: - self.hover_instance.hover_outside_bound=True + self.hover_instance.hover_outside_bound = True - #update hover pos if needed - elif self.hover_instance.show_cursor and self.x_hover_data is not None and self.y_hover_data is not None: + # update hover pos if needed + elif self.hover_instance.show_cursor and self.x_hover_data is not None and self.y_hover_data is not None: if self.cursor_last_axis: - xy_pos = self.cursor_last_axis.transData.transform([(self.x_hover_data,self.y_hover_data)]) + xy_pos = self.cursor_last_axis.transData.transform( + [(self.x_hover_data, self.y_hover_data)]) else: - xy_pos = self.figure.axes[0].transData.transform([(self.x_hover_data,self.y_hover_data)]) - self.hover_instance.x_hover_pos=float(xy_pos[0][0]) + self.x - self.hover_instance.y_hover_pos=float(xy_pos[0][1]) + self.y - - self.hover_instance.xmin_line = float(self.figure.axes[0].bbox.bounds[0]) + self.x - self.hover_instance.xmax_line = float(self.figure.axes[0].bbox.bounds[0] + self.figure.axes[0].bbox.bounds[2]) + self.x - self.hover_instance.ymin_line = float(self.figure.axes[0].bbox.bounds[1]) + self.y - self.hover_instance.ymax_line = float(self.figure.axes[0].bbox.bounds[1] + self.figure.axes[0].bbox.bounds[3] )+ self.y - - if self.hover_instance.x_hover_pos>self.x+self.figure.axes[0].bbox.bounds[2] + self.figure.axes[0].bbox.bounds[0] or \ - self.hover_instance.x_hover_posself.y+self.figure.axes[0].bbox.bounds[1] + self.figure.axes[0].bbox.bounds[3] or \ - self.hover_instance.y_hover_pos self.x + self.figure.axes[0].bbox.bounds[2] + self.figure.axes[0].bbox.bounds[0] or \ + self.hover_instance.x_hover_pos < self.x + self.figure.axes[0].bbox.bounds[0] or \ + self.hover_instance.y_hover_pos > self.y + self.figure.axes[0].bbox.bounds[1] + self.figure.axes[0].bbox.bounds[3] or \ + self.hover_instance.y_hover_pos < self.y + self.figure.axes[0].bbox.bounds[1]: + self.hover_instance.hover_outside_bound = True else: - self.hover_instance.hover_outside_bound=False - - def update_selector(self,*args): + self.hover_instance.hover_outside_bound = False + + def update_selector(self, *args): """ update selector on fast draw (if exist)""" if self.selector: - #update selector pos if needed - if self.selector.resize_wgt.verts and (len(args)!=0 or self.touch_mode!='selector'): + # update selector pos if needed + if self.selector.resize_wgt.verts and ( + len(args) != 0 or self.touch_mode != 'selector'): resize_wgt = self.selector.resize_wgt - if hasattr(resize_wgt,'shapes'): - #lasso widget or ellipse + if hasattr(resize_wgt, 'shapes'): + # lasso widget or ellipse if resize_wgt.shapes: - if hasattr(resize_wgt.shapes[0],'radius_x'): - #ellipse widget - xy_pos = resize_wgt.ax.transData.transform([(resize_wgt.verts[1][0],resize_wgt.verts[1][1])]) - new_pos=resize_wgt.to_widget(*(float(xy_pos[0][0]),float(xy_pos[0][1]))) + if hasattr(resize_wgt.shapes[0], 'radius_x'): + # ellipse widget + xy_pos = resize_wgt.ax.transData.transform( + [(resize_wgt.verts[1][0], resize_wgt.verts[1][1])]) + new_pos = resize_wgt.to_widget( + *(float(xy_pos[0][0]), float(xy_pos[0][1]))) pos0 = new_pos[0] + self.x - pos1 = new_pos[1] + self.y - - xy_pos2 = resize_wgt.ax.transData.transform([(resize_wgt.verts[2][0],resize_wgt.verts[2][1])]) - new_pos2=resize_wgt.to_widget(*(float(xy_pos2[0][0]),float(xy_pos2[0][1]))) + pos1 = new_pos[1] + self.y + + xy_pos2 = resize_wgt.ax.transData.transform( + [(resize_wgt.verts[2][0], resize_wgt.verts[2][1])]) + new_pos2 = resize_wgt.to_widget( + *(float(xy_pos2[0][0]), float(xy_pos2[0][1]))) pos0_2 = new_pos2[0] + self.x - pos1_2 = new_pos2[1] + self.y - - current_shape=resize_wgt.shapes[0] + pos1_2 = new_pos2[1] + self.y + + current_shape = resize_wgt.shapes[0] dataxy1 = current_shape.selection_point_inst.points - dataxy2 = current_shape.selection_point_inst2.points - - #note: the 2 first points are the same in current_shape.points + dataxy2 = current_shape.selection_point_inst2.points + + # note: the 2 first points are the same in + # current_shape.points pos0_old = dataxy1[0] pos1_old = dataxy1[1] - + pos0_2_old = dataxy2[0] - pos1_2_old = dataxy2[1] - - old_length = np.sqrt((pos0_2_old - pos0_old)**2 + (pos1_2_old - pos1_old)**2) - new_length = np.sqrt((pos0_2 - pos0)**2 + (pos1_2 - pos1)**2) - + pos1_2_old = dataxy2[1] + + old_length = np.sqrt( + (pos0_2_old - pos0_old) ** 2 + + (pos1_2_old - pos1_old) ** 2) + new_length = np.sqrt( + (pos0_2 - pos0)**2 + (pos1_2 - pos1)**2) + scale = float(new_length / old_length) - - xy_pos3 = resize_wgt.ax.transData.transform([(resize_wgt.verts[0][0],resize_wgt.verts[0][1])]) - new_pos3=resize_wgt.to_widget(*(float(xy_pos3[0][0]),float(xy_pos3[0][1]))) + + xy_pos3 = resize_wgt.ax.transData.transform( + [(resize_wgt.verts[0][0], resize_wgt.verts[0][1])]) + new_pos3 = resize_wgt.to_widget( + *(float(xy_pos3[0][0]), float(xy_pos3[0][1]))) pos0_c = new_pos3[0] + self.x pos1_c = new_pos3[1] + self.y - + for s in resize_wgt.shapes: s.rescale(scale) - + for s in resize_wgt.shapes: - s.translate(pos=(pos0_c,pos1_c)) - - xmin,xmax,ymin,ymax = resize_wgt.shapes[0].get_min_max() + s.translate(pos=(pos0_c, pos1_c)) + + xmin, xmax, ymin, ymax = resize_wgt.shapes[0].get_min_max( + ) else: - #lasso widget - xy_pos = resize_wgt.ax.transData.transform([(resize_wgt.verts[0][0],resize_wgt.verts[0][1])]) - new_pos=resize_wgt.to_widget(*(float(xy_pos[0][0]),float(xy_pos[0][1]))) + # lasso widget + xy_pos = resize_wgt.ax.transData.transform( + [(resize_wgt.verts[0][0], resize_wgt.verts[0][1])]) + new_pos = resize_wgt.to_widget( + *(float(xy_pos[0][0]), float(xy_pos[0][1]))) pos0 = new_pos[0] + self.x - pos1 = new_pos[1] + self.y - - xy_pos2 = resize_wgt.ax.transData.transform([(resize_wgt.verts[1][0],resize_wgt.verts[1][1])]) - new_pos2=resize_wgt.to_widget(*(float(xy_pos2[0][0]),float(xy_pos2[0][1]))) + pos1 = new_pos[1] + self.y + + xy_pos2 = resize_wgt.ax.transData.transform( + [(resize_wgt.verts[1][0], resize_wgt.verts[1][1])]) + new_pos2 = resize_wgt.to_widget( + *(float(xy_pos2[0][0]), float(xy_pos2[0][1]))) pos0_2 = new_pos2[0] + self.x - pos1_2 = new_pos2[1] + self.y - - current_shape=resize_wgt.shapes[0] - dataxy = np.array(current_shape.points).reshape(-1,2) - - #note: the 2 first points are the same in current_shape.points + pos1_2 = new_pos2[1] + self.y + + current_shape = resize_wgt.shapes[0] + dataxy = np.array( + current_shape.points).reshape(-1, 2) + + # note: the 2 first points are the same in + # current_shape.points pos0_old = dataxy[1][0] pos1_old = dataxy[1][1] - + pos0_2_old = dataxy[2][0] pos1_2_old = dataxy[2][1] - - old_length = np.sqrt((pos0_2_old - pos0_old)**2 + (pos1_2_old - pos1_old)**2) - new_length = np.sqrt((pos0_2 - pos0)**2 + (pos1_2 - pos1)**2) - + + old_length = np.sqrt( + (pos0_2_old - pos0_old) ** 2 + + (pos1_2_old - pos1_old) ** 2) + new_length = np.sqrt( + (pos0_2 - pos0)**2 + (pos1_2 - pos1)**2) + scale = new_length / old_length - + for s in resize_wgt.shapes: s.rescale(scale) - + for s in resize_wgt.shapes: - s.translate(pos=(pos0,pos1)) - + s.translate(pos=(pos0, pos1)) + xmax, ymax = dataxy.max(axis=0) - xmin, ymin = dataxy.min(axis=0) - - if self.collide_point(*resize_wgt.to_window(xmin,ymin)) and \ - self.collide_point(*resize_wgt.to_window(xmax,ymax)): + xmin, ymin = dataxy.min(axis=0) + + if self.collide_point( + * + resize_wgt.to_window( + xmin, + ymin)) and self.collide_point( + * + resize_wgt.to_window( + xmax, + ymax)): resize_wgt.opacity = 1 else: - resize_wgt.opacity = 0 - - elif self.selector.resize_wgt.verts and (len(args)!=0 or self.touch_mode!='selector'): + resize_wgt.opacity = 0 + + elif self.selector.resize_wgt.verts and (len(args) != 0 or self.touch_mode != 'selector'): resize_wgt = self.selector.resize_wgt if not (resize_wgt.size[0] > 1 and resize_wgt.size[1] > 1): return - - #rectangle or spann selector - if hasattr(resize_wgt,'span_orientation'): - #span selector + + # rectangle or spann selector + if hasattr(resize_wgt, 'span_orientation'): + # span selector if resize_wgt.span_orientation == 'vertical': - xy_pos = resize_wgt.ax.transData.transform([(resize_wgt.verts[0][0],resize_wgt.verts[0][1])]) - new_pos=resize_wgt.to_widget(*(float(xy_pos[0][0]),float(xy_pos[0][1]))) + xy_pos = resize_wgt.ax.transData.transform( + [(resize_wgt.verts[0][0], resize_wgt.verts[0][1])]) + new_pos = resize_wgt.to_widget( + *(float(xy_pos[0][0]), float(xy_pos[0][1]))) resize_wgt.pos[0] = new_pos[0] + self.x - - top_bound = float(self.y +resize_wgt.ax.bbox.bounds[3] + resize_wgt.ax.bbox.bounds[1]) - bottom_bound = float(self.y +resize_wgt.ax.bbox.bounds[1]) + + top_bound = float( + self.y + + resize_wgt.ax.bbox.bounds[3] + + resize_wgt.ax.bbox.bounds[1]) + bottom_bound = float( + self.y + resize_wgt.ax.bbox.bounds[1]) resize_wgt.pos[1] = bottom_bound - self.y - - #recalcul size - xy_pos2 = resize_wgt.ax.transData.transform([(resize_wgt.verts[3][0],resize_wgt.verts[3][1])]) - resize_wgt.size[0] = float(xy_pos2[0][0] - xy_pos[0][0]) - resize_wgt.size[1] = top_bound-bottom_bound - + + # recalcul size + xy_pos2 = resize_wgt.ax.transData.transform( + [(resize_wgt.verts[3][0], resize_wgt.verts[3][1])]) + resize_wgt.size[0] = float( + xy_pos2[0][0] - xy_pos[0][0]) + resize_wgt.size[1] = top_bound - bottom_bound + elif resize_wgt.span_orientation == 'horizontal': - xy_pos = resize_wgt.ax.transData.transform([(resize_wgt.verts[0][0],resize_wgt.verts[0][1])]) - new_pos=resize_wgt.to_widget(*(float(xy_pos[0][0]),float(xy_pos[0][1]))) - left_bound = float(self.x +resize_wgt.ax.bbox.bounds[0]) - right_bound = float(self.x +resize_wgt.ax.bbox.bounds[2] +resize_wgt.ax.bbox.bounds[0] ) - - width = right_bound-left_bound - - left_bound,right_bound = resize_wgt.to_widget(left_bound,right_bound) - + xy_pos = resize_wgt.ax.transData.transform( + [(resize_wgt.verts[0][0], resize_wgt.verts[0][1])]) + new_pos = resize_wgt.to_widget( + *(float(xy_pos[0][0]), float(xy_pos[0][1]))) + left_bound = float( + self.x + resize_wgt.ax.bbox.bounds[0]) + right_bound = float( + self.x + resize_wgt.ax.bbox.bounds[2] + + resize_wgt.ax.bbox.bounds[0]) + + width = right_bound - left_bound + + left_bound, right_bound = resize_wgt.to_widget( + left_bound, right_bound) + resize_wgt.pos[0] = left_bound resize_wgt.pos[1] = new_pos[1] + self.y - #recalcul size - xy_pos2 = resize_wgt.ax.transData.transform([(resize_wgt.verts[0][1],resize_wgt.verts[1][1])]) + # recalcul size + xy_pos2 = resize_wgt.ax.transData.transform( + [(resize_wgt.verts[0][1], resize_wgt.verts[1][1])]) resize_wgt.size[0] = width - resize_wgt.size[1] = float(xy_pos2[0][1] - xy_pos[0][1]) - + resize_wgt.size[1] = float( + xy_pos2[0][1] - xy_pos[0][1]) + else: - #rectangle selector - - #update all selector pts - #recalcul pos - xy_pos = resize_wgt.ax.transData.transform([(resize_wgt.verts[0][0],resize_wgt.verts[0][1])]) - new_pos=resize_wgt.to_widget(*(float(xy_pos[0][0]),float(xy_pos[0][1]))) + # rectangle selector + + # update all selector pts + # recalcul pos + xy_pos = resize_wgt.ax.transData.transform( + [(resize_wgt.verts[0][0], resize_wgt.verts[0][1])]) + new_pos = resize_wgt.to_widget( + *(float(xy_pos[0][0]), float(xy_pos[0][1]))) resize_wgt.pos[0] = new_pos[0] + self.x resize_wgt.pos[1] = new_pos[1] + self.y - - #recalcul size - xy_pos2 = resize_wgt.ax.transData.transform([(resize_wgt.verts[2][0],resize_wgt.verts[2][1])]) - resize_wgt.size[0] = float(xy_pos2[0][0] - xy_pos[0][0]) - resize_wgt.size[1] = float(xy_pos2[0][1] - xy_pos[0][1]) - - if self.collide_point(*resize_wgt.to_window(resize_wgt.pos[0],resize_wgt.pos[1])) and \ - self.collide_point(*resize_wgt.to_window(resize_wgt.pos[0] + resize_wgt.size[0],resize_wgt.pos[1]+ resize_wgt.size[1])): + + # recalcul size + xy_pos2 = resize_wgt.ax.transData.transform( + [(resize_wgt.verts[2][0], resize_wgt.verts[2][1])]) + resize_wgt.size[0] = float( + xy_pos2[0][0] - xy_pos[0][0]) + resize_wgt.size[1] = float( + xy_pos2[0][1] - xy_pos[0][1]) + + if self.collide_point( + * + resize_wgt.to_window( + resize_wgt.pos[0], + resize_wgt.pos[1])) and self.collide_point( + * + resize_wgt.to_window( + resize_wgt.pos[0] + + resize_wgt.size[0], + resize_wgt.pos[1] + + resize_wgt.size[1])): resize_wgt.opacity = 1 else: - resize_wgt.opacity = 0 + resize_wgt.opacity = 0 def min_max(self, event): """ manage min/max touch mode """ - ax=self.figure.axes[0] #left axis + ax = self.figure.axes[0] # left axis xlabelbottom = ax.xaxis._major_tick_kw.get('tick1On') ylabelleft = ax.yaxis._major_tick_kw.get('tick1On') ylabelright = ax.yaxis._major_tick_kw.get('tick2On') - if xlabelbottom and event.x>self.x +ax.bbox.bounds[0] and \ - event.x self.x + ax.bbox.bounds[0] and \ + event.x < self.x + ax.bbox.bounds[2] + ax.bbox.bounds[0] and \ + event.y < self.y + ax.bbox.bounds[1]: - right_lim = self.x+ax.bbox.bounds[2]+ax.bbox.bounds[0] - left_lim = self.x+ax.bbox.bounds[0] - left_anchor_zone= (right_lim - left_lim)*.2 + left_lim - right_anchor_zone= (right_lim - left_lim)*.8 + left_lim + right_lim = self.x + ax.bbox.bounds[2] + ax.bbox.bounds[0] + left_lim = self.x + ax.bbox.bounds[0] + left_anchor_zone = (right_lim - left_lim) * .2 + left_lim + right_anchor_zone = (right_lim - left_lim) * .8 + left_lim - if event.x < left_anchor_zone or event.x > right_anchor_zone: + if event.x < left_anchor_zone or event.x > right_anchor_zone: if self.text_instance: if not self.text_instance.show_text: - midpoint = ((self.x+ax.bbox.bounds[2] + ax.bbox.bounds[0]) + (self.x+ax.bbox.bounds[0]))/2 + midpoint = ( + (self.x + ax.bbox.bounds[2] + ax.bbox.bounds[0]) + + (self.x + ax.bbox.bounds[0])) / 2 if event.x < midpoint: - anchor='left' - self.text_instance.x_text_pos=float(self.x+ax.bbox.bounds[0]) - self.text_instance.y_text_pos=float(self.y+ax.bbox.bounds[1]) - self.text_instance.text_height + anchor = 'left' + self.text_instance.x_text_pos = float( + self.x + ax.bbox.bounds[0]) + self.text_instance.y_text_pos = float( + self.y + ax.bbox.bounds[1]) - self.text_instance.text_height self.text_instance.offset_text = False else: - anchor='right' - self.text_instance.x_text_pos=float(self.x+ax.bbox.bounds[2] + ax.bbox.bounds[0]) - self.text_instance.y_text_pos=float(self.y+ax.bbox.bounds[1]) - self.text_instance.text_height + anchor = 'right' + self.text_instance.x_text_pos = float( + self.x + ax.bbox.bounds[2] + ax.bbox.bounds[0]) + self.text_instance.y_text_pos = float( + self.y + ax.bbox.bounds[1]) - self.text_instance.text_height self.text_instance.offset_text = True self.text_instance.current_axis = ax - self.text_instance.kind = {'axis':'x','anchor':anchor} + self.text_instance.kind = { + 'axis': 'x', 'anchor': anchor} - self.text_instance.show_text=True + self.text_instance.show_text = True return - elif ylabelleft and event.xself.y + ax.bbox.bounds[1]: + elif ylabelleft and event.x < self.x + ax.bbox.bounds[0] and \ + event.y < self.y + ax.bbox.bounds[1] + ax.bbox.bounds[3] and \ + event.y > self.y + ax.bbox.bounds[1]: - top_lim = self.y+ax.bbox.bounds[3]+ax.bbox.bounds[1] - bottom_lim = self.y+ax.bbox.bounds[1] - bottom_anchor_zone= (top_lim - bottom_lim)*.2 + bottom_lim - top_anchor_zone= (top_lim - bottom_lim)*.8 + bottom_lim + top_lim = self.y + ax.bbox.bounds[3] + ax.bbox.bounds[1] + bottom_lim = self.y + ax.bbox.bounds[1] + bottom_anchor_zone = (top_lim - bottom_lim) * .2 + bottom_lim + top_anchor_zone = (top_lim - bottom_lim) * .8 + bottom_lim if event.y < bottom_anchor_zone or event.y > top_anchor_zone: if self.text_instance: if not self.text_instance.show_text: - midpoint = ((self.y+ax.bbox.bounds[3] + ax.bbox.bounds[1]) + (self.y+ax.bbox.bounds[1]))/2 - if event.y > midpoint: - anchor='top' - self.text_instance.x_text_pos=float(self.x+ax.bbox.bounds[0]) - dp(40) - self.text_instance.y_text_pos=float(self.y+ax.bbox.bounds[1] + ax.bbox.bounds[3]) - self.text_instance.text_height + midpoint = ( + (self.y + ax.bbox.bounds[3] + ax.bbox.bounds[1]) + + (self.y + ax.bbox.bounds[1])) / 2 + if event.y > midpoint: + anchor = 'top' + self.text_instance.x_text_pos = float( + self.x + ax.bbox.bounds[0]) - dp(40) + self.text_instance.y_text_pos = float( + self.y + ax.bbox.bounds[1] + ax.bbox.bounds[3]) - self.text_instance.text_height self.text_instance.offset_text = False else: - anchor='bottom' - self.text_instance.x_text_pos=float(self.x+ax.bbox.bounds[0]) - dp(40) - self.text_instance.y_text_pos=float(self.y+ax.bbox.bounds[1]) - dp(6) + anchor = 'bottom' + self.text_instance.x_text_pos = float( + self.x + ax.bbox.bounds[0]) - dp(40) + self.text_instance.y_text_pos = float( + self.y + ax.bbox.bounds[1]) - dp(6) self.text_instance.offset_text = False self.text_instance.current_axis = ax - self.text_instance.kind = {'axis':'y','anchor':anchor} + self.text_instance.kind = { + 'axis': 'y', 'anchor': anchor} - self.text_instance.show_text=True + self.text_instance.show_text = True return - - elif ylabelright and event.x>self.x + ax.bbox.bounds[2] + ax.bbox.bounds[0] and \ - event.yself.y + ax.bbox.bounds[1]: - top_lim = self.y+ax.bbox.bounds[3]+ax.bbox.bounds[1] - bottom_lim = self.y+ax.bbox.bounds[1] - bottom_anchor_zone= (top_lim - bottom_lim)*.2 + bottom_lim - top_anchor_zone= (top_lim - bottom_lim)*.8 + bottom_lim + elif ylabelright and event.x > self.x + ax.bbox.bounds[2] + ax.bbox.bounds[0] and \ + event.y < self.y + ax.bbox.bounds[1] + ax.bbox.bounds[3] and \ + event.y > self.y + ax.bbox.bounds[1]: + + top_lim = self.y + ax.bbox.bounds[3] + ax.bbox.bounds[1] + bottom_lim = self.y + ax.bbox.bounds[1] + bottom_anchor_zone = (top_lim - bottom_lim) * .2 + bottom_lim + top_anchor_zone = (top_lim - bottom_lim) * .8 + bottom_lim if event.y < bottom_anchor_zone or event.y > top_anchor_zone: if self.text_instance: if not self.text_instance.show_text: - midpoint = ((self.y+ax.bbox.bounds[3] + ax.bbox.bounds[1]) + (self.y+ax.bbox.bounds[1]))/2 - if event.y > midpoint: - anchor='top' - self.text_instance.x_text_pos=float(self.x+ax.bbox.bounds[0] + ax.bbox.bounds[2]) + dp(40) - self.text_instance.y_text_pos=float(self.y+ax.bbox.bounds[1] + ax.bbox.bounds[3]) - self.text_instance.text_height + midpoint = ( + (self.y + ax.bbox.bounds[3] + ax.bbox.bounds[1]) + + (self.y + ax.bbox.bounds[1])) / 2 + if event.y > midpoint: + anchor = 'top' + self.text_instance.x_text_pos = float( + self.x + ax.bbox.bounds[0] + ax.bbox.bounds[2]) + dp(40) + self.text_instance.y_text_pos = float( + self.y + ax.bbox.bounds[1] + ax.bbox.bounds[3]) - self.text_instance.text_height self.text_instance.offset_text = True else: - - anchor='bottom' - self.text_instance.x_text_pos=float(self.x+ax.bbox.bounds[0] + ax.bbox.bounds[2]) + dp(40) - self.text_instance.y_text_pos=float(self.y+ax.bbox.bounds[1]) - dp(6) + + anchor = 'bottom' + self.text_instance.x_text_pos = float( + self.x + ax.bbox.bounds[0] + ax.bbox.bounds[2]) + dp(40) + self.text_instance.y_text_pos = float( + self.y + ax.bbox.bounds[1]) - dp(6) self.text_instance.offset_text = True - self.text_instance.current_axis = self.figure.axes[1] #left axis - self.text_instance.kind = {'axis':'y','anchor':anchor} - - self.text_instance.show_text=True - return + # left axis + self.text_instance.current_axis = self.figure.axes[1] + self.text_instance.kind = { + 'axis': 'y', 'anchor': anchor} + + self.text_instance.show_text = True + return def apply_drag_legend(self, ax, event): """ drag legend method """ - + dx = event.x - self._last_touch_pos[event][0] if not self.legend_do_scroll_x: - dx=0 - dy = event.y - self._last_touch_pos[event][1] + dx = 0 + dy = event.y - self._last_touch_pos[event][1] if not self.legend_do_scroll_y: - dy=0 - legend=None + dy = 0 + legend = None if self.current_legend: if self.current_legend.legend_instance: legend = self.current_legend.legend_instance else: legend = ax.get_legend() if legend is not None: - + bbox = legend.get_window_extent() legend_x = bbox.xmin legend_y = bbox.ymin - - loc_in_canvas = legend_x +dx, legend_y+dy + + loc_in_canvas = legend_x + dx, legend_y + dy loc_in_norm_axes = legend.parent.transAxes.inverted().transform_point(loc_in_canvas) legend._loc = tuple(loc_in_norm_axes) - - #use blit method + + # use blit method if self.background is None or self.last_line is not None: legend.set_visible(False) ax.figure.canvas.draw_idle() - ax.figure.canvas.flush_events() - self.background = ax.figure.canvas.copy_from_bbox(ax.figure.bbox) + ax.figure.canvas.flush_events() + self.background = ax.figure.canvas.copy_from_bbox( + ax.figure.bbox) legend.set_visible(True) if self.last_line is not None: self.clear_line_prop() - ax.figure.canvas.restore_region(self.background) - + ax.figure.canvas.restore_region(self.background) + ax.draw_artist(legend) - + ax.figure.canvas.blit(ax.bbox) - ax.figure.canvas.flush_events() + ax.figure.canvas.flush_events() self.current_legend.update_size() - + def zoom_factory(self, event, ax, base_scale=1.1): """ zoom with scrolling mouse method """ @@ -2365,35 +2739,35 @@ def zoom_factory(self, event, ax, base_scale=1.1): y = newcoord[1] trans = ax.transData.inverted() - xdata, ydata = trans.transform_point((x, y)) + xdata, ydata = trans.transform_point((x, y)) cur_xlim = ax.get_xlim() cur_ylim = ax.get_ylim() - scale=ax.get_xscale() - yscale=ax.get_yscale() - + scale = ax.get_xscale() + yscale = ax.get_yscale() + if scale == 'linear': - old_min=cur_xlim[0] - old_max=cur_xlim[1] + old_min = cur_xlim[0] + old_max = cur_xlim[1] else: - min_=cur_xlim[0] - max_=cur_xlim[1] - old_min = self.transform_eval(min_,ax.yaxis) - xdata = self.transform_eval(xdata,ax.yaxis) - old_max = self.transform_eval(max_,ax.yaxis) + min_ = cur_xlim[0] + max_ = cur_xlim[1] + old_min = self.transform_eval(min_, ax.yaxis) + xdata = self.transform_eval(xdata, ax.yaxis) + old_max = self.transform_eval(max_, ax.yaxis) if yscale == 'linear': - yold_min=cur_ylim[0] - yold_max=cur_ylim[1] + yold_min = cur_ylim[0] + yold_max = cur_ylim[1] else: - ymin_=cur_ylim[0] - ymax_=cur_ylim[1] - yold_min = self.transform_eval(ymin_,ax.yaxis) - ydata = self.transform_eval(ydata,ax.yaxis) - yold_max = self.transform_eval(ymax_,ax.yaxis) + ymin_ = cur_ylim[0] + ymax_ = cur_ylim[1] + yold_min = self.transform_eval(ymin_, ax.yaxis) + ydata = self.transform_eval(ydata, ax.yaxis) + yold_max = self.transform_eval(ymax_, ax.yaxis) if event.button == 'scrolldown': # deal with zoom in @@ -2414,37 +2788,43 @@ def zoom_factory(self, event, ax, base_scale=1.1): if self.do_zoom_x: if scale == 'linear': - ax.set_xlim([xdata - new_width * (1 - relx), xdata + new_width * (relx)]) + ax.set_xlim([xdata - new_width * (1 - relx), + xdata + new_width * (relx)]) else: new_min = xdata - new_width * (1 - relx) new_max = xdata + new_width * (relx) try: - new_min, new_max = self.inv_transform_eval(new_min,ax.yaxis), self.inv_transform_eval(new_max,ax.yaxis) + new_min, new_max = self.inv_transform_eval( + new_min, ax.yaxis), self.inv_transform_eval( + new_max, ax.yaxis) except OverflowError: # Limit case new_min, new_max = min_, max_ if new_min <= 0. or new_max <= 0.: # Limit case - new_min, new_max = min_, max_ + new_min, new_max = min_, max_ ax.set_xlim([new_min, new_max]) - + if self.do_zoom_y: if yscale == 'linear': - ax.set_ylim([ydata - new_height * (1 - rely), ydata + new_height * (rely)]) + ax.set_ylim([ydata - new_height * (1 - rely), + ydata + new_height * (rely)]) else: new_ymin = ydata - new_height * (1 - rely) new_ymax = ydata + new_height * (rely) try: - new_ymin, new_ymax = self.inv_transform_eval(new_ymin,ax.yaxis), self.inv_transform_eval(new_ymax,ax.yaxis) + new_ymin, new_ymax = self.inv_transform_eval( + new_ymin, ax.yaxis), self.inv_transform_eval( + new_ymax, ax.yaxis) except OverflowError: # Limit case new_ymin, new_ymax = ymin_, ymax_ if new_ymin <= 0. or new_ymax <= 0.: # Limit case - new_ymin, new_ymax = ymin_, ymax_ + new_ymin, new_ymax = ymin_, ymax_ ax.set_ylim([new_ymin, new_ymax]) if self.last_line is not None: - self.clear_line_prop() - + self.clear_line_prop() + ax.figure.canvas.draw_idle() - ax.figure.canvas.flush_events() + ax.figure.canvas.flush_events() def zoom_factory_twin(self, event, ax, ax2, base_scale=2.): """twin axis zoom method from scroll mouse""" @@ -2457,33 +2837,33 @@ def zoom_factory_twin(self, event, ax, ax2, base_scale=2.): trans = ax.transData.inverted() xdata, ydata = trans.transform_point((x, y)) trans2 = ax2.transData.inverted() - xdata2, ydata2 = trans2.transform_point((x, y)) + xdata2, ydata2 = trans2.transform_point((x, y)) - scale=ax.get_xscale() - yscale=ax.get_yscale() - yscale2=ax2.get_yscale() + scale = ax.get_xscale() + yscale = ax.get_yscale() + yscale2 = ax2.get_yscale() if scale == 'linear': - old_min=cur_xlim[0] - old_max=cur_xlim[1] + old_min = cur_xlim[0] + old_max = cur_xlim[1] else: - min_=cur_xlim[0] - max_=cur_xlim[1] - old_min = self.transform_eval(min_,ax.yaxis) - xdata = self.transform_eval(xdata,ax.yaxis) - old_max = self.transform_eval(max_,ax.yaxis) + min_ = cur_xlim[0] + max_ = cur_xlim[1] + old_min = self.transform_eval(min_, ax.yaxis) + xdata = self.transform_eval(xdata, ax.yaxis) + old_max = self.transform_eval(max_, ax.yaxis) if yscale == 'linear': - yold_min=cur_ylim[0] - yold_max=cur_ylim[1] + yold_min = cur_ylim[0] + yold_max = cur_ylim[1] else: - ymin_=cur_ylim[0] - ymax_=cur_ylim[1] - yold_min = self.transform_eval(ymin_,ax.yaxis) - ydata = self.transform_eval(ydata,ax.yaxis) - yold_max = self.transform_eval(ymax_,ax.yaxis) + ymin_ = cur_ylim[0] + ymax_ = cur_ylim[1] + yold_min = self.transform_eval(ymin_, ax.yaxis) + ydata = self.transform_eval(ydata, ax.yaxis) + yold_max = self.transform_eval(ymax_, ax.yaxis) if event.button == 'scrolldown': # deal with zoom in @@ -2504,69 +2884,78 @@ def zoom_factory_twin(self, event, ax, ax2, base_scale=2.): if self.do_zoom_x: if scale == 'linear': - ax.set_xlim([xdata - new_width * (1 - relx), xdata + new_width * (relx)]) + ax.set_xlim([xdata - new_width * (1 - relx), + xdata + new_width * (relx)]) else: new_min = xdata - new_width * (1 - relx) new_max = xdata + new_width * (relx) try: - new_min, new_max = self.inv_transform_eval(new_min,ax.yaxis), self.inv_transform_eval(new_max,ax.yaxis) + new_min, new_max = self.inv_transform_eval( + new_min, ax.yaxis), self.inv_transform_eval( + new_max, ax.yaxis) except OverflowError: # Limit case new_min, new_max = min_, max_ if new_min <= 0. or new_max <= 0.: # Limit case - new_min, new_max = min_, max_ + new_min, new_max = min_, max_ ax.set_xlim([new_min, new_max]) - + if self.do_zoom_y: if yscale == 'linear': - ax.set_ylim([ydata - new_height * (1 - rely), ydata + new_height * (rely)]) + ax.set_ylim([ydata - new_height * (1 - rely), + ydata + new_height * (rely)]) else: new_ymin = ydata - new_height * (1 - rely) new_ymax = ydata + new_height * (rely) try: - new_ymin, new_ymax = self.inv_transform_eval(new_ymin,ax.yaxis), self.inv_transform_eval(new_ymax,ax.yaxis) + new_ymin, new_ymax = self.inv_transform_eval( + new_ymin, ax.yaxis), self.inv_transform_eval( + new_ymax, ax.yaxis) except OverflowError: # Limit case new_ymin, new_ymax = ymin_, ymax_ if new_ymin <= 0. or new_ymax <= 0.: # Limit case - new_ymin, new_ymax = ymin_, ymax_ + new_ymin, new_ymax = ymin_, ymax_ ax.set_ylim([new_ymin, new_ymax]) if yscale2 == 'linear': - yold2_min=cur_ylim2[0] - yold2_max=cur_ylim2[1] + yold2_min = cur_ylim2[0] + yold2_max = cur_ylim2[1] else: - ymin2_=cur_ylim2[0] - ymax2_=cur_ylim2[1] - yold2_min = self.transform_eval(ymin2_,ax2.yaxis) - ydata2 = self.transform_eval(ydata2,ax2.yaxis) - yold2_max = self.transform_eval(ymax2_,ax2.yaxis) - - new_height2 = (yold2_max - yold2_min) * scale_factor - + ymin2_ = cur_ylim2[0] + ymax2_ = cur_ylim2[1] + yold2_min = self.transform_eval(ymin2_, ax2.yaxis) + ydata2 = self.transform_eval(ydata2, ax2.yaxis) + yold2_max = self.transform_eval(ymax2_, ax2.yaxis) + + new_height2 = (yold2_max - yold2_min) * scale_factor + rely2 = (yold2_max - ydata2) / (yold2_max - yold2_min) if self.do_zoom_y: - ax2.set_ylim([ydata2 - new_height2 * (1 - rely2), ydata2 + new_height2 * (rely2)]) - + ax2.set_ylim([ydata2 - new_height2 * (1 - rely2), + ydata2 + new_height2 * (rely2)]) if yscale2 == 'linear': - ax2.set_ylim([ydata2 - new_height2 * (1 - rely2), ydata2 + new_height2 * (rely2)]) + ax2.set_ylim([ydata2 - new_height2 * (1 - rely2), + ydata2 + new_height2 * (rely2)]) else: new_ymin2 = ydata2 - new_height2 * (1 - rely2) new_ymax2 = ydata2 + new_height2 * (rely2) try: - new_ymin2, new_ymax2 = self.inv_transform_eval(new_ymin2,ax2.yaxis), self.inv_transform_eval(new_ymax2,ax2.yaxis) + new_ymin2, new_ymax2 = self.inv_transform_eval( + new_ymin2, ax2.yaxis), self.inv_transform_eval( + new_ymax2, ax2.yaxis) except OverflowError: # Limit case new_ymin2, new_ymax2 = ymin2_, ymax2_ if new_ymin2 <= 0. or new_ymax2 <= 0.: # Limit case - new_ymin2, new_ymax2 = ymin2_, ymax2_ + new_ymin2, new_ymax2 = ymin2_, ymax2_ ax2.set_ylim([new_ymin2, new_ymax2]) if self.last_line is not None: - self.clear_line_prop() + self.clear_line_prop() ax.figure.canvas.draw_idle() - ax.figure.canvas.flush_events() - + ax.figure.canvas.flush_events() + def _onSize(self, o, size): """ _onsize method """ if self.figure is None: @@ -2586,102 +2975,119 @@ def _onSize(self, o, size): s = 'resize_event' event = ResizeEvent(s, self.figcanvas) self.figcanvas.callbacks.process(s, event) - self.figcanvas.draw_idle() + self.figcanvas.draw_idle() - self.figcanvas.draw() + self.figcanvas.draw() if self.legend_instance: for current_legend in self.legend_instance: current_legend.update_size() if self.hover_instance: - self.hover_instance.figwidth = self.width + self.hover_instance.figwidth = self.width self.hover_instance.figheight = self.height self.hover_instance.figx = self.x - self.hover_instance.figy = self.y + self.hover_instance.figy = self.y if self.selector and self.selector.resize_wgt.verts: - #update selector next frame to have correct position - Clock.schedule_once(self.update_selector) + # update selector next frame to have correct position + Clock.schedule_once(self.update_selector) def update_lim(self): """ update axis lim if zoombox is used""" - ax=self.figure.axes[0] - ax2=self.figure.axes[1] - self.do_update=False - - #check if inverted axis - xleft,xright=ax.get_xlim() - ybottom,ytop=ax.get_ylim() - ybottom2,ytop2=ax2.get_ylim() - - #check inverted data + ax = self.figure.axes[0] + ax2 = self.figure.axes[1] + self.do_update = False + + # check if inverted axis + xleft, xright = ax.get_xlim() + ybottom, ytop = ax.get_ylim() + ybottom2, ytop2 = ax2.get_ylim() + + # check inverted data inverted_x = False - if xleft>xright: - inverted_x=True - cur_xlim=(xright,xleft) + if xleft > xright: + inverted_x = True + cur_xlim = (xright, xleft) else: - cur_xlim=(xleft,xright) + cur_xlim = (xleft, xright) inverted_y = False - if ybottom>ytop: - inverted_y=True - cur_ylim=(ytop,ybottom) + if ybottom > ytop: + inverted_y = True + cur_ylim = (ytop, ybottom) else: - cur_ylim=(ybottom,ytop) + cur_ylim = (ybottom, ytop) inverted_y2 = False - if ybottom2>ytop2: - inverted_y2=True - cur_ylim2=(ytop2,ybottom2) + if ybottom2 > ytop2: + inverted_y2 = True + cur_ylim2 = (ytop2, ybottom2) else: - cur_ylim2=(ybottom2,ytop2) - + cur_ylim2 = (ybottom2, ytop2) + range_old = cur_ylim[1] - cur_ylim[0] range_old2 = cur_ylim2[1] - cur_ylim2[0] - - ymin2 = (min(self.y0_box,self.y1_box)-cur_ylim[0])/range_old*range_old2+cur_ylim2[0] - ymax2 = (max(self.y0_box,self.y1_box)-cur_ylim[0])/range_old*range_old2+cur_ylim2[0] - + + ymin2 = (min(self.y0_box, self.y1_box) - + cur_ylim[0]) / range_old * range_old2 + cur_ylim2[0] + ymax2 = (max(self.y0_box, self.y1_box) - + cur_ylim[0]) / range_old * range_old2 + cur_ylim2[0] + if inverted_x: - ax.set_xlim(right=min(self.x0_box,self.x1_box),left=max(self.x0_box,self.x1_box)) + ax.set_xlim( + right=min( + self.x0_box, self.x1_box), left=max( + self.x0_box, self.x1_box)) else: - ax.set_xlim(left=min(self.x0_box,self.x1_box),right=max(self.x0_box,self.x1_box)) + ax.set_xlim( + left=min( + self.x0_box, self.x1_box), right=max( + self.x0_box, self.x1_box)) if inverted_y: - ax.set_ylim(top=min(self.y0_box,self.y1_box),bottom=max(self.y0_box,self.y1_box)) + ax.set_ylim( + top=min( + self.y0_box, self.y1_box), bottom=max( + self.y0_box, self.y1_box)) else: - ax.set_ylim(bottom=min(self.y0_box,self.y1_box),top=max(self.y0_box,self.y1_box)) + ax.set_ylim( + bottom=min( + self.y0_box, self.y1_box), top=max( + self.y0_box, self.y1_box)) if inverted_y2: - ax2.set_ylim(top=ymin2,bottom=ymax2) + ax2.set_ylim(top=ymin2, bottom=ymax2) else: - ax2.set_ylim(bottom=ymin2,top=ymax2) - + ax2.set_ylim(bottom=ymin2, top=ymax2) + def reset_box(self): """ reset zoombox and apply zoombox limit if zoombox option if selected""" - if min(abs(self._box_size[0]),abs(self._box_size[1]))>self.minzoom: + if min(abs(self._box_size[0]), abs(self._box_size[1])) > self.minzoom: trans = self.figure.axes[0].transData.inverted() - self.x0_box, self.y0_box = trans.transform_point((self._box_pos[0]-self.pos[0], self._box_pos[1]-self.pos[1])) - self.x1_box, self.y1_box = trans.transform_point((self._box_size[0]+self._box_pos[0]-self.pos[0], self._box_size[1]+self._box_pos[1]-self.pos[1])) - self.do_update=True - + self.x0_box, self.y0_box = trans.transform_point( + (self._box_pos[0] - self.pos[0], self._box_pos[1] - self.pos[1])) + self.x1_box, self.y1_box = trans.transform_point( + (self._box_size[0] + self._box_pos[0] - self.pos[0], + self._box_size[1] + self._box_pos[1] - self.pos[1])) + self.do_update = True + self._box_size = 0, 0 self._box_pos = 0, 0 - self._alpha_box=0 + self._alpha_box = 0 self._pos_x_rect_hor = 0 self._pos_y_rect_hor = 0 self._pos_x_rect_ver = 0 - self._pos_y_rect_ver = 0 - self._alpha_hor=0 - self._alpha_ver=0 + self._pos_y_rect_ver = 0 + self._alpha_hor = 0 + self._alpha_ver = 0 self.invert_rect_hor = False self.invert_rect_ver = False - + def draw_box(self, event, x0, y0, x1, y1) -> None: """ Draw zoombox method - + Args: event: touch kivy event x0: x coordonnate init x1: y coordonnate of move touch y0: y coordonnate init y1: x coordonnate of move touch - + Return: None """ @@ -2689,107 +3095,109 @@ def draw_box(self, event, x0, y0, x1, y1) -> None: # Kivy coords y0 = pos_y + y0 y1 = pos_y + y1 - - if abs(y1-y0)>dp(5) or abs(x1-x0)>dp(5): - self._alpha_box=0.3 - self._alpha_rect=0 - + + if abs(y1 - y0) > dp(5) or abs(x1 - x0) > dp(5): + self._alpha_box = 0.3 + self._alpha_rect = 0 + trans = self.figure.axes[0].transData.inverted() - xdata, ydata = trans.transform_point((event.x-pos_x, event.y-pos_y)) + xdata, ydata = trans.transform_point( + (event.x - pos_x, event.y - pos_y)) # xmin,xmax=self.figure.axes[0].get_xlim() # ymin,ymax=self.figure.axes[0].get_ylim() - - xleft,xright=self.figure.axes[0].get_xlim() - ybottom,ytop=self.figure.axes[0].get_ylim() - - xmax = max(xleft,xright) - xmin = min(xleft,xright) - ymax = max(ybottom,ytop) - ymin = min(ybottom,ytop) - - #check inverted data + + xleft, xright = self.figure.axes[0].get_xlim() + ybottom, ytop = self.figure.axes[0].get_ylim() + + xmax = max(xleft, xright) + xmin = min(xleft, xright) + ymax = max(ybottom, ytop) + ymin = min(ybottom, ytop) + + # check inverted data inverted_x = False - if xleft>xright: - inverted_x=True + if xleft > xright: + inverted_x = True inverted_y = False - if ybottom>ytop: - inverted_y=True - - x0data, y0data = trans.transform_point((x0-pos_x, y0-pos_y)) - if x0data>xmax or x0dataymax or y0data ytop: + inverted_y = True + + x0data, y0data = trans.transform_point((x0 - pos_x, y0 - pos_y)) + if x0data > xmax or x0data < xmin or y0data > ymax or y0data < ymin: return - if xdatax0 and inverted_x): - x1=x1_min[0][0]+pos_x + if xdata < xmin: + x1_min = self.figure.axes[0].transData.transform([(xmin, ymin)]) + if (x1 < x0 and not inverted_x) or (x1 > x0 and inverted_x): + x1 = x1_min[0][0] + pos_x else: - x0=x1_min[0][0] + x0 = x1_min[0][0] - if xdata>xmax: - x0_max = self.figure.axes[0].transData.transform([(xmax,ymin)]) - if (x1>x0 and not inverted_x) or (x1 xmax: + x0_max = self.figure.axes[0].transData.transform([(xmax, ymin)]) + if (x1 > x0 and not inverted_x) or (x1 < x0 and inverted_x): + x1 = x0_max[0][0] + pos_x else: - x0=x0_max[0][0] + x0 = x0_max[0][0] - if ydatay0 and inverted_y): - y1=y1_min[0][1]+pos_y + if ydata < ymin: + y1_min = self.figure.axes[0].transData.transform([(xmin, ymin)]) + if (y1 < y0 and not inverted_y) or (y1 > y0 and inverted_y): + y1 = y1_min[0][1] + pos_y else: - y0=y1_min[0][1]+pos_y - - if ydata>ymax: - y0_max = self.figure.axes[0].transData.transform([(xmax,ymax)]) - if (y1>y0 and not inverted_y) or (y1 ymax: + y0_max = self.figure.axes[0].transData.transform([(xmax, ymax)]) + if (y1 > y0 and not inverted_y) or (y1 < y0 and inverted_y): + y1 = y0_max[0][1] + pos_y else: - y0=y0_max[0][1]+pos_y - - if abs(x1-x0)self.minzoom: - self.pos_x_rect_ver=x0 - self.pos_y_rect_ver=y0 - - x1_min = self.figure.axes[0].transData.transform([(xmin,ymin)]) - x0=x1_min[0][0]+pos_x - - x0_max = self.figure.axes[0].transData.transform([(xmax,ymin)]) - x1=x0_max[0][0]+pos_x - - self._alpha_ver=1 - self._alpha_hor=0 - - elif abs(y1-y0)self.minzoom: - self.pos_x_rect_hor=x0 - self.pos_y_rect_hor=y0 - - y1_min = self.figure.axes[0].transData.transform([(xmin,ymin)]) - y0=y1_min[0][1]+pos_y - - y0_max = self.figure.axes[0].transData.transform([(xmax,ymax)]) - y1=y0_max[0][1]+pos_y - - self._alpha_hor=1 - self._alpha_ver=0 - + y0 = y0_max[0][1] + pos_y + + if abs(x1 - x0) < dp(20) and abs(y1 - y0) > self.minzoom: + self.pos_x_rect_ver = x0 + self.pos_y_rect_ver = y0 + + x1_min = self.figure.axes[0].transData.transform([(xmin, ymin)]) + x0 = x1_min[0][0] + pos_x + + x0_max = self.figure.axes[0].transData.transform([(xmax, ymin)]) + x1 = x0_max[0][0] + pos_x + + self._alpha_ver = 1 + self._alpha_hor = 0 + + elif abs(y1 - y0) < dp(20) and abs(x1 - x0) > self.minzoom: + self.pos_x_rect_hor = x0 + self.pos_y_rect_hor = y0 + + y1_min = self.figure.axes[0].transData.transform([(xmin, ymin)]) + y0 = y1_min[0][1] + pos_y + + y0_max = self.figure.axes[0].transData.transform([(xmax, ymax)]) + y1 = y0_max[0][1] + pos_y + + self._alpha_hor = 1 + self._alpha_ver = 0 + else: - self._alpha_hor=0 - self._alpha_ver=0 + self._alpha_hor = 0 + self._alpha_ver = 0 - if x1>x0: - self.invert_rect_ver=False + if x1 > x0: + self.invert_rect_ver = False else: - self.invert_rect_ver=True - if y1>y0: - self.invert_rect_hor=False + self.invert_rect_ver = True + if y1 > y0: + self.invert_rect_hor = False else: - self.invert_rect_hor=True - + self.invert_rect_hor = True + self._box_pos = x0, y0 self._box_size = x1 - x0, y1 - y0 + class _FigureCanvas(FigureCanvasAgg): """Internal AGG Canvas""" @@ -2814,7 +3222,7 @@ def draw(self): def blit(self, bbox=None): """ Render the figure using agg (blit method). - """ + """ agg = self.get_renderer() w, h = agg.width, agg.height self.widget._bitmap = agg.buffer_rgba() @@ -2822,11 +3230,11 @@ def blit(self, bbox=None): self.widget.bt_h = h self.widget._draw_bitmap() + class FakeEventTwinx: - x:None - y:None + x: None + y: None -from kivy.factory import Factory Factory.register('MatplotFigureTwinx', MatplotFigureTwinx) @@ -2850,42 +3258,42 @@ class FakeEventTwinx: dp(1) if root.invert_rect_ver else -dp(1), \ dp(1) if root.invert_rect_hor else -dp(1), \ dp(1) if root.invert_rect_ver else -dp(1) - - canvas.after: + + canvas.after: #horizontal rectangle left - Color: - rgba:0, 0, 0, self._alpha_hor - Line: - width: dp(1) - rectangle: - (self.pos_x_rect_hor+dp(1) if root.invert_rect_ver \ - else self.pos_x_rect_hor-dp(4),self.pos_y_rect_hor-dp(20), dp(4),dp(40)) + Color: + rgba:0, 0, 0, self._alpha_hor + Line: + width: dp(1) + rectangle: + (self.pos_x_rect_hor+dp(1) if root.invert_rect_ver \ + else self.pos_x_rect_hor-dp(4),self.pos_y_rect_hor-dp(20), dp(4),dp(40)) #horizontal rectangle right - Color: - rgba:0, 0, 0, self._alpha_hor - Line: - width: dp(1) - rectangle: - (self.pos_x_rect_hor-dp(4)+self._box_size[0] if root.invert_rect_ver \ - else self.pos_x_rect_hor+dp(1)+self._box_size[0], self.pos_y_rect_hor-dp(20), dp(4),dp(40)) + Color: + rgba:0, 0, 0, self._alpha_hor + Line: + width: dp(1) + rectangle: + (self.pos_x_rect_hor-dp(4)+self._box_size[0] if root.invert_rect_ver \ + else self.pos_x_rect_hor+dp(1)+self._box_size[0], self.pos_y_rect_hor-dp(20), dp(4),dp(40)) #vertical rectangle bottom - Color: - rgba:0, 0, 0, self._alpha_ver - Line: - width: dp(1) - rectangle: - (self.pos_x_rect_ver-dp(20),self.pos_y_rect_ver+dp(1) if root.invert_rect_hor else \ - self.pos_y_rect_ver-dp(4), dp(40),dp(4)) + Color: + rgba:0, 0, 0, self._alpha_ver + Line: + width: dp(1) + rectangle: + (self.pos_x_rect_ver-dp(20),self.pos_y_rect_ver+dp(1) if root.invert_rect_hor else \ + self.pos_y_rect_ver-dp(4), dp(40),dp(4)) #vertical rectangle top - Color: - rgba:0, 0, 0, self._alpha_ver - Line: - width: dp(1) - rectangle: - (self.pos_x_rect_ver-dp(20),self.pos_y_rect_ver-dp(4)+self._box_size[1] \ + Color: + rgba:0, 0, 0, self._alpha_ver + Line: + width: dp(1) + rectangle: + (self.pos_x_rect_ver-dp(20),self.pos_y_rect_ver-dp(4)+self._box_size[1] \ if root.invert_rect_hor else self.pos_y_rect_ver+dp(1)+self._box_size[1], \ dp(40),dp(4)) ''') diff --git a/kivy_matplotlib_widget/uix/hover_widget.py b/kivy_matplotlib_widget/uix/hover_widget.py index 97ff97c..f2245f8 100644 --- a/kivy_matplotlib_widget/uix/hover_widget.py +++ b/kivy_matplotlib_widget/uix/hover_widget.py @@ -8,16 +8,23 @@ StringProperty, BooleanProperty, ColorProperty - ) +) -from kivy.lang import Builder -from kivy.core.window import Window +from kivy.lang import Builder +from kivy.core.window import Window from kivy.metrics import dp import numpy as np - -def add_hover(figure_wgt,mode='touch',label_x='x',label_y='y',hover_widget=None,hover_type='nearest'): + + +def add_hover( + figure_wgt, + mode='touch', + label_x='x', + label_y='y', + hover_widget=None, + hover_type='nearest'): """ add hover to matpotlib figure - + Args: figure_wgt: figure widget from kivy_matplotlib_widget package mode : 'touch' (touch device) or 'desktop' (desktop with mouse) @@ -25,91 +32,93 @@ def add_hover(figure_wgt,mode='touch',label_x='x',label_y='y',hover_widget=None, """ if figure_wgt.hover_instance: - - if hover_type=='compare': + + if hover_type == 'compare': if not figure_wgt.compare_hover_instance: if hover_widget is None: - hover_widget = GeneralCompareHover() - hover_widget.x_hover_pos=figure_wgt.x - hover_widget.y_hover_pos=figure_wgt.y - hover_widget.label_x=label_x - hover_widget.label_y=label_y + hover_widget = GeneralCompareHover() + hover_widget.x_hover_pos = figure_wgt.x + hover_widget.y_hover_pos = figure_wgt.y + hover_widget.label_x = label_x + hover_widget.label_y = label_y figure_wgt.parent.add_widget(hover_widget) - figure_wgt.compare_hover_instance = hover_widget - figure_wgt.compare_hover_instance.create_child(figure_wgt.lines) + figure_wgt.compare_hover_instance = hover_widget + figure_wgt.compare_hover_instance.create_child(figure_wgt.lines) figure_wgt.hover_instance = figure_wgt.compare_hover_instance - figure_wgt.compare_xdata=True + figure_wgt.compare_xdata = True if figure_wgt.nearest_hover_instance: - figure_wgt.nearest_hover_instance.show_cursor=False - + figure_wgt.nearest_hover_instance.show_cursor = False + else: if not figure_wgt.nearest_hover_instance: if hover_widget is None: - hover_widget = GeneralHover() - hover_widget.x_hover_pos=figure_wgt.x - hover_widget.y_hover_pos=figure_wgt.y - hover_widget.label_x=label_x - hover_widget.label_y=label_y - figure_wgt.parent.add_widget(hover_widget) + hover_widget = GeneralHover() + hover_widget.x_hover_pos = figure_wgt.x + hover_widget.y_hover_pos = figure_wgt.y + hover_widget.label_x = label_x + hover_widget.label_y = label_y + figure_wgt.parent.add_widget(hover_widget) figure_wgt.nearest_hover_instance = hover_widget - - figure_wgt.hover_instance = figure_wgt.nearest_hover_instance + + figure_wgt.hover_instance = figure_wgt.nearest_hover_instance figure_wgt.hover_instance.reset_hover() - figure_wgt.hover_instance.label_x=label_x - figure_wgt.hover_instance.label_y=label_y - figure_wgt.compare_xdata=False + figure_wgt.hover_instance.label_x = label_x + figure_wgt.hover_instance.label_y = label_y + figure_wgt.compare_xdata = False if figure_wgt.compare_hover_instance: - figure_wgt.compare_hover_instance.show_cursor=False + figure_wgt.compare_hover_instance.show_cursor = False else: if hover_widget is None: - if hover_type=='compare': + if hover_type == 'compare': hover_widget = GeneralCompareHover() else: hover_widget = GeneralHover() - - if hover_type=='compare': + + if hover_type == 'compare': hover_widget.create_child(figure_wgt.lines) figure_wgt.compare_hover_instance = hover_widget - figure_wgt.compare_xdata=True + figure_wgt.compare_xdata = True else: figure_wgt.nearest_hover_instance = hover_widget - figure_wgt.compare_xdata=False + figure_wgt.compare_xdata = False + + hover_widget.x_hover_pos = figure_wgt.x + hover_widget.y_hover_pos = figure_wgt.y + hover_widget.label_x = label_x + hover_widget.label_y = label_y - hover_widget.x_hover_pos=figure_wgt.x - hover_widget.y_hover_pos=figure_wgt.y - hover_widget.label_x=label_x - hover_widget.label_y=label_y - figure_wgt.parent.add_widget(hover_widget) - figure_wgt.hover_instance=hover_widget - if mode=='desktop': - figure_wgt.hover_on=True - Window.bind(mouse_pos=figure_wgt.on_motion) - - + figure_wgt.hover_instance = hover_widget + if mode == 'desktop': + figure_wgt.hover_on = True + Window.bind(mouse_pos=figure_wgt.on_motion) + + class BaseHoverFloatLayout(FloatLayout): """ Touch egend kivy class""" figure_wgt = ObjectProperty(None) x_hover_pos = NumericProperty(1) - y_hover_pos = NumericProperty(1) - xmin_line = NumericProperty(1) - xmax_line = NumericProperty(1) - ymin_line = NumericProperty(1) - ymax_line = NumericProperty(1) + y_hover_pos = NumericProperty(1) + xmin_line = NumericProperty(1) + xmax_line = NumericProperty(1) + ymin_line = NumericProperty(1) + ymax_line = NumericProperty(1) hover_outside_bound = BooleanProperty(False) show_cursor = BooleanProperty(False) - label_x = StringProperty('x') - label_y = StringProperty('y') - label_x_value = StringProperty('') - label_y_value = StringProperty('') - custom_label = StringProperty('',allownone=True) #futur used for dynamic label - custom_color=ColorProperty([0,0,0,1],allownone=True) #futur used for dynamic color - figwidth = NumericProperty(2) - figheight = NumericProperty(2) + label_x = StringProperty('x') + label_y = StringProperty('y') + label_x_value = StringProperty('') + label_y_value = StringProperty('') + # futur used for dynamic label + custom_label = StringProperty('', allownone=True) + # futur used for dynamic color + custom_color = ColorProperty([0, 0, 0, 1], allownone=True) + figwidth = NumericProperty(2) + figheight = NumericProperty(2) figx = NumericProperty(0) figy = NumericProperty(0) - + def __init__(self, **kwargs): """ init class """ super().__init__(**kwargs) @@ -118,110 +127,114 @@ def reset_hover(self): """ reset hover attribute """ self.x_hover_pos = 1 self.y_hover_pos = 1 - self.ymin_line = 1 - self.ymax_line = 1 + self.ymin_line = 1 + self.ymax_line = 1 + class GeneralHover(BaseHoverFloatLayout): - """ GeneralHover """ - text_color=ColorProperty([0,0,0,1]) - text_font=StringProperty("Roboto") + """ GeneralHover """ + text_color = ColorProperty([0, 0, 0, 1]) + text_font = StringProperty("Roboto") text_size = NumericProperty(dp(14)) - background_color=ColorProperty([0,0,1,0.3]) + background_color = ColorProperty([0, 0, 1, 0.3]) hover_height = NumericProperty(dp(24)) - + def __init__(self, **kwargs): """ init class """ - super().__init__(**kwargs) + super().__init__(**kwargs) + class HoverVerticalText(BaseHoverFloatLayout): - """ Hover with vertical text""" - text_color=ColorProperty([0,0,0,1]) - text_font=StringProperty("Roboto") + """ Hover with vertical text""" + text_color = ColorProperty([0, 0, 0, 1]) + text_font = StringProperty("Roboto") text_size = NumericProperty(dp(14)) - background_color=ColorProperty([1,1,0,1]) + background_color = ColorProperty([1, 1, 0, 1]) hover_height = NumericProperty(dp(48)) - + def __init__(self, **kwargs): """ init class """ super().__init__(**kwargs) + class GeneralCompareHover(BaseHoverFloatLayout): - """ GeneralCompareHover""" - text_color=ColorProperty([0,0,0,1]) - text_font=StringProperty("Roboto") + """ GeneralCompareHover""" + text_color = ColorProperty([0, 0, 0, 1]) + text_font = StringProperty("Roboto") text_size = NumericProperty(dp(14)) - background_color=ColorProperty([1,1,1,1]) + background_color = ColorProperty([1, 1, 1, 1]) hover_height = NumericProperty(dp(48)) - y_touch_pos = NumericProperty(1) - + y_touch_pos = NumericProperty(1) + def __init__(self, **kwargs): """ init class """ - super().__init__(**kwargs) - self.children_names=[] - self.children_list=[] - - def create_child(self,lines): - if len(self.ids.main_box.children)>2: - #clear all added childrens - for i in range(len(self.ids.main_box.children)-2): + super().__init__(**kwargs) + self.children_names = [] + self.children_list = [] + + def create_child(self, lines): + if len(self.ids.main_box.children) > 2: + # clear all added childrens + for i in range(len(self.ids.main_box.children) - 2): self.ids.main_box.remove_widget(self.ids.main_box.children[0]) - self.children_names=[] - self.children_list=[] - for i,line in enumerate(lines): - label=line.get_label() - if i==0: - self.ids.comparehoverbox.label_y=label - mywidget = self.ids.comparehoverbox + self.children_names = [] + self.children_list = [] + for i, line in enumerate(lines): + label = line.get_label() + if i == 0: + self.ids.comparehoverbox.label_y = label + mywidget = self.ids.comparehoverbox else: - mywidget=CompareHoverBox() - mywidget.label_y=label + mywidget = CompareHoverBox() + mywidget.label_y = label self.ids.main_box.add_widget(mywidget) self.children_names.append(label) self.children_list.append(mywidget) - + + class BoxShadowCompareHover(BaseHoverFloatLayout): - """ GeneralCompareHover with a box shadow""" - text_color=ColorProperty([0,0,0,1]) - text_font=StringProperty("Roboto") + """ GeneralCompareHover with a box shadow""" + text_color = ColorProperty([0, 0, 0, 1]) + text_font = StringProperty("Roboto") text_size = NumericProperty(dp(14)) - background_color=ColorProperty([1,1,1,1]) + background_color = ColorProperty([1, 1, 1, 1]) hover_height = NumericProperty(dp(48)) - y_touch_pos = NumericProperty(1) + y_touch_pos = NumericProperty(1) reorder_data = BooleanProperty(True) - + def __init__(self, **kwargs): """ init class """ super(BoxShadowCompareHover, self).__init__(**kwargs) - self.children_names=[] - self.children_list=[] + self.children_names = [] + self.children_list = [] - def create_child(self,lines): - if len(self.ids.main_box.children)>2: - #clear all added childrens - for i in range(len(self.ids.main_box.children)-2): + def create_child(self, lines): + if len(self.ids.main_box.children) > 2: + # clear all added childrens + for i in range(len(self.ids.main_box.children) - 2): self.ids.main_box.remove_widget(self.ids.main_box.children[0]) - self.children_names=[] - self.children_list=[] - for i,line in enumerate(lines): - label=line.get_label() - if i==0: - self.ids.comparehoverbox.label_y=label - mywidget = self.ids.comparehoverbox + self.children_names = [] + self.children_list = [] + for i, line in enumerate(lines): + label = line.get_label() + if i == 0: + self.ids.comparehoverbox.label_y = label + mywidget = self.ids.comparehoverbox else: - mywidget=DotCompareHoverBox() - mywidget.label_y=label + mywidget = DotCompareHoverBox() + mywidget.label_y = label self.ids.main_box.add_widget(mywidget) self.children_names.append(label) self.children_list.append(mywidget) - + def overlap_check(self): """ reorder label base on y_hover_pos of data""" - if self.reorder_data and len(self.ids.main_box.children)>2: - y_hover_pos_list=[] - child_list=[] - y_pos_list=[] + if self.reorder_data and len(self.ids.main_box.children) > 2: + y_hover_pos_list = [] + child_list = [] + y_pos_list = [] for child in self.ids.main_box.children[:-1]: # child.hover_offset=0 if child.show_widget: @@ -229,190 +242,221 @@ def overlap_check(self): child_list.append(child.ids.label) y_pos_list.append(child.y) # heigh_child = child.ids.label.texture_size[1]+dp(6) - sorting_args= np.argsort(y_hover_pos_list) + sorting_args = np.argsort(y_hover_pos_list) - for index in range(len(sorting_args)): + for index in range(len(sorting_args)): child_list[sorting_args[index]].y = y_pos_list[index] + class CompareHoverBox(BoxLayout): - """ Hover with vertical text""" - text_color=ColorProperty([0,0,0,1]) - text_font=StringProperty("Roboto") - text_size = NumericProperty(dp(14)) + """ Hover with vertical text""" + text_color = ColorProperty([0, 0, 0, 1]) + text_font = StringProperty("Roboto") + text_size = NumericProperty(dp(14)) label_y = StringProperty('y') label_y_value = StringProperty('y') x_hover_pos = NumericProperty(1) y_hover_pos = NumericProperty(1) show_widget = BooleanProperty(False) - custom_color=ColorProperty([0,0,0,1],allownone=True) + custom_color = ColorProperty([0, 0, 0, 1], allownone=True) def __init__(self, **kwargs): """ init class """ - super().__init__(**kwargs) + super().__init__(**kwargs) + class DotCompareHoverBox(FloatLayout): - """ Hover with vertical text""" - text_color=ColorProperty([0,0,0,1]) - text_font=StringProperty("Roboto") - text_size = NumericProperty(dp(14)) + """ Hover with vertical text""" + text_color = ColorProperty([0, 0, 0, 1]) + text_font = StringProperty("Roboto") + text_size = NumericProperty(dp(14)) label_y = StringProperty('y') label_y_value = StringProperty('y') x_hover_pos = NumericProperty(1) y_hover_pos = NumericProperty(1) show_widget = BooleanProperty(False) - custom_color=ColorProperty([0,0,0,1],allownone=True) + custom_color = ColorProperty([0, 0, 0, 1], allownone=True) def __init__(self, **kwargs): """ init class """ - super().__init__(**kwargs) + super().__init__(**kwargs) + class TagCompareHover(BaseHoverFloatLayout): - """ TagCompareHover""" - text_color=ColorProperty([0,0,0,1]) - text_font=StringProperty("Roboto") + """ TagCompareHover""" + text_color = ColorProperty([0, 0, 0, 1]) + text_font = StringProperty("Roboto") text_size = NumericProperty(dp(14)) - background_color=ColorProperty([1,1,1,1]) + background_color = ColorProperty([1, 1, 1, 1]) hover_height = NumericProperty(dp(48)) - y_touch_pos = NumericProperty(1) - + y_touch_pos = NumericProperty(1) + def __init__(self, **kwargs): """ init class """ - super().__init__(**kwargs) - self.children_names=[] - self.children_list=[] - - def create_child(self,lines): - if len(self.ids.main_box.children)>2: - #clear all added childrens - for i in range(len(self.ids.main_box.children)-2): + super().__init__(**kwargs) + self.children_names = [] + self.children_list = [] + + def create_child(self, lines): + if len(self.ids.main_box.children) > 2: + # clear all added childrens + for i in range(len(self.ids.main_box.children) - 2): self.ids.main_box.remove_widget(self.ids.main_box.children[0]) - self.children_names=[] - self.children_list=[] - for i,line in enumerate(lines): - label=line.get_label() - if i==0: - self.ids.ycomparehoverbox.label_y=label - mywidget = self.ids.ycomparehoverbox + self.children_names = [] + self.children_list = [] + for i, line in enumerate(lines): + label = line.get_label() + if i == 0: + self.ids.ycomparehoverbox.label_y = label + mywidget = self.ids.ycomparehoverbox else: - mywidget=TagCompareHoverBox() - mywidget.label_y=label + mywidget = TagCompareHoverBox() + mywidget.label_y = label self.ids.main_box.add_widget(mywidget) self.children_names.append(label) self.children_list.append(mywidget) - + def overlap_check(self): - if len(self.ids.main_box.children)>2: - y_pos_list=[] - child_list=[] + if len(self.ids.main_box.children) > 2: + y_pos_list = [] + child_list = [] for child in self.ids.main_box.children[:-1]: - child.hover_offset=0 + child.hover_offset = 0 y_pos_list.append(child.y_hover_pos) child_list.append(child) - heigh_child = child.ids.label.texture_size[1]+dp(6) - sorting_args= np.argsort(y_pos_list) + heigh_child = child.ids.label.texture_size[1] + dp(6) + sorting_args = np.argsort(y_pos_list) + + for index in range(len(sorting_args) - 1): + # chneck overlap + if y_pos_list[sorting_args[index + 1]] - heigh_child / 2 <= y_pos_list[sorting_args[index] + ] + heigh_child / 2 and child_list[sorting_args[index + 1]].show_widget: + offset = -((y_pos_list[sorting_args[index + 1]] - heigh_child / 2) - ( + y_pos_list[sorting_args[index]] + heigh_child / 2)) - for index in range(len(sorting_args)-1): - #chneck overlap - if y_pos_list[sorting_args[index+1]]-heigh_child/2 <= y_pos_list[sorting_args[index]]+heigh_child/2 and \ - child_list[sorting_args[index+1]].show_widget: - offset = -((y_pos_list[sorting_args[index+1]]-heigh_child/2) - (y_pos_list[sorting_args[index]]+heigh_child/2)) + y_pos_list[sorting_args[index + 1]] += offset + child_list[sorting_args[index + 1]].hover_offset = offset - y_pos_list[sorting_args[index+1]] +=offset - child_list[sorting_args[index+1]].hover_offset = offset class TagCompareHoverBox(FloatLayout): - """ Hover with vertical text""" - text_color=ColorProperty([0,0,0,1]) - text_font=StringProperty("Roboto") - text_size = NumericProperty(dp(14)) + """ Hover with vertical text""" + text_color = ColorProperty([0, 0, 0, 1]) + text_font = StringProperty("Roboto") + text_size = NumericProperty(dp(14)) label_y = StringProperty('y') label_y_value = StringProperty('y') x_hover_pos = NumericProperty(1) y_hover_pos = NumericProperty(1) show_widget = BooleanProperty(False) - custom_color=ColorProperty([0,0,0,1],allownone=True) + custom_color = ColorProperty([0, 0, 0, 1], allownone=True) hover_offset = NumericProperty(0) def __init__(self, **kwargs): """ init class """ - super().__init__(**kwargs) + super().__init__(**kwargs) class InfoHover(BaseHoverFloatLayout): - """ InfoHover adapt the background and the font color with the line or scatter color""" - text_color=ColorProperty([0,0,0,1]) - text_font=StringProperty("Roboto") + """ InfoHover adapt the background and the font color with the line or scatter color""" + text_color = ColorProperty([0, 0, 0, 1]) + text_font = StringProperty("Roboto") text_size = NumericProperty(dp(14)) hover_height = NumericProperty(dp(48)) - + def __init__(self, **kwargs): """ init class """ - super().__init__(**kwargs) + super().__init__(**kwargs) + class MatplotlibStyleHover(BaseHoverFloatLayout): """MatplotlibStyleHover look like matplotlib cursor but do not use matplotlib draw. Usefull in live blit drawing - - """ - text_color=ColorProperty([0,0,0,1]) - text_font=StringProperty("Roboto") + + """ + text_color = ColorProperty([0, 0, 0, 1]) + text_font = StringProperty("Roboto") text_size = NumericProperty(dp(14)) hover_height = NumericProperty(dp(48)) - background_color=ColorProperty([1,1,1,1]) - + background_color = ColorProperty([1, 1, 1, 1]) + def __init__(self, **kwargs): """ init class """ - super().__init__(**kwargs) + super().__init__(**kwargs) + class HightChartHover(BaseHoverFloatLayout): - """ PlotlyHover adapt the background and the font color with the line or scatter color""" - text_color=ColorProperty([0,0,0,1]) - text_font=StringProperty("Roboto") + """ PlotlyHover adapt the background and the font color with the line or scatter color""" + text_color = ColorProperty([0, 0, 0, 1]) + text_font = StringProperty("Roboto") text_size = NumericProperty(dp(14)) hover_height = NumericProperty(dp(24)) label_format = StringProperty("") label_padding_width = NumericProperty(dp(16)) label_padding_height = NumericProperty(dp(8)) - position = OptionProperty('top', - options=('top', 'bottom', 'left', 'right', - 'top_start','top_end','bottom_start','bottom_end', - 'left_start','left_end','right_start','right_end')) - - + position = OptionProperty( + 'top', + options=( + 'top', + 'bottom', + 'left', + 'right', + 'top_start', + 'top_end', + 'bottom_start', + 'bottom_end', + 'left_start', + 'left_end', + 'right_start', + 'right_end')) + def __init__(self, **kwargs): """ init class """ - super().__init__(**kwargs) + super().__init__(**kwargs) + class RichTooltip(BoxLayout): - position = OptionProperty('top', - options=('top', 'bottom', 'left', 'right', - 'top_start','top_end','bottom_start','bottom_end', - 'left_start','left_end','right_start','right_end')) + position = OptionProperty( + 'top', + options=( + 'top', + 'bottom', + 'left', + 'right', + 'top_start', + 'top_end', + 'bottom_start', + 'bottom_end', + 'left_start', + 'left_end', + 'right_start', + 'right_end')) + class PlotlyHover(BaseHoverFloatLayout): - """ PlotlyHover adapt the background and the font color with the line or scatter color""" - text_color=ColorProperty([0,0,0,1]) - text_font=StringProperty("Roboto") + """ PlotlyHover adapt the background and the font color with the line or scatter color""" + text_color = ColorProperty([0, 0, 0, 1]) + text_font = StringProperty("Roboto") text_size = NumericProperty(dp(14)) hover_height = NumericProperty(dp(24)) use_position = StringProperty('right') position = OptionProperty('right', - options=('right', 'left')) - + options=('right', 'left')) + def __init__(self, **kwargs): """ init class """ super().__init__(**kwargs) + Builder.load_string(''' size_hint: None,None - width: dp(0.01) + width: dp(0.01) height: dp(0.01) opacity:1 if root.show_cursor and not root.hover_outside_bound else 0 - + BoxLayout: x: @@ -423,39 +467,39 @@ def __init__(self, **kwargs): size_hint: None, None padding: dp(4),0,dp(4),0 height: root.hover_height - width: + width: label.texture_size[0] + self.padding[0] * 2 if root.show_cursor \ - else dp(0.0001) - - canvas: + else dp(0.0001) + + canvas: Color: rgba: root.background_color RoundedRectangle: pos: self.pos size: self.size - radius: [dp(7)] - canvas.after: + radius: [dp(7)] + canvas.after: Color: rgba: 0,0,1,1 Ellipse: size: (dp(8),dp(8)) - pos: + pos: (root.x_hover_pos-dp(8/2), \ root.y_hover_pos-dp(8/2)) Color: rgba: 0,0,1,1 Line: width: dp(1) - points: + points: + root.x_hover_pos, \ + root.ymin_line, \ root.x_hover_pos, \ - root.ymin_line, \ - root.x_hover_pos, \ - root.ymax_line + root.ymax_line Label: id:label - text: + text: root.label_x + ': ' + root.label_x_value + ', ' + \ - root.label_y + ': ' + root.label_y_value + root.label_y + ': ' + root.label_y_value font_size:root.text_size color: root.text_color font_name : root.text_font @@ -469,61 +513,61 @@ def __init__(self, **kwargs): root.y_hover_pos + dp(4) size_hint: None, None height: root.hover_height - width: + width: max(label.texture_size[0],label2.texture_size[0]) + dp(12) if root.show_cursor \ - else dp(0.0001) + else dp(0.0001) orientation:'vertical' padding: 0,dp(4),0,dp(4) - - canvas: + + canvas: Color: rgba: root.background_color RoundedRectangle: pos: self.pos size: self.size - radius: [dp(4)] + radius: [dp(4)] Color: rgba: 0,0,0,1 - + Line: - width: 1 + width: 1 rounded_rectangle: (self.x, self.y, self.width, self.height,\ dp(4), dp(4), dp(4), dp(4),\ - self.height) - - - canvas.after: + self.height) + + + canvas.after: Color: rgba: 0,0,0,1 Rectangle: size: (dp(8),dp(8)) - pos: + pos: (root.x_hover_pos-dp(8/2), \ root.y_hover_pos-dp(8/2)) - + BoxLayout: size_hint_x:None width:label.texture_size[0] padding: dp(12),0,0,0 Label: id:label - text: - root.label_x + ': ' + root.label_x_value + text: + root.label_x + ': ' + root.label_x_value font_size:root.text_size color: root.text_color font_name : root.text_font BoxLayout: size_hint_x:None - width:label2.texture_size[0] + width:label2.texture_size[0] padding: dp(12),0,0,0 Label: id:label2 text: - root.label_y + ': ' + root.label_y_value + root.label_y + ': ' + root.label_y_value font_size:root.text_size - color: root.text_color + color: root.text_color font_name : root.text_font @@ -537,13 +581,13 @@ def __init__(self, **kwargs): root.y_hover_pos + dp(4) size_hint: None, None height: root.hover_height - width: + width: max(label.texture_size[0],label2.texture_size[0]) + dp(12) if root.show_cursor \ - else dp(0.0001) + else dp(0.0001) orientation:'vertical' padding: 0,dp(4),0,dp(4) - - canvas: + + canvas: Color: rgba: root.custom_color if root.custom_color else [0,0,0,1] Rectangle: @@ -551,32 +595,32 @@ def __init__(self, **kwargs): size: self.size Color: rgba: 0,0,0,1 - + Line: - width: 1 + width: 1 rounded_rectangle: (self.x, self.y, self.width, self.height,\ dp(4), dp(4), dp(4), dp(4),\ - self.height) - - - canvas.after: + self.height) + + + canvas.after: Color: rgba: 0,0,0,1 Rectangle: size: (dp(8),dp(8)) - pos: + pos: (root.x_hover_pos-dp(8/2), \ root.y_hover_pos-dp(8/2)) - + BoxLayout: size_hint_x:None width:label.texture_size[0] padding: dp(12),0,0,0 Label: id:label - text: - root.label_x + ': ' + root.label_x_value + text: + root.label_x + ': ' + root.label_x_value font_size:root.text_size color: [0,0,0,1] if (root.custom_color[0]*0.299 + \ @@ -586,12 +630,12 @@ def __init__(self, **kwargs): BoxLayout: size_hint_x:None - width:label2.texture_size[0] + width:label2.texture_size[0] padding: dp(12),0,0,0 Label: id:label2 text: - root.label_y + ': ' + root.label_y_value + root.label_y + ': ' + root.label_y_value font_size:root.text_size color: [0,0,0,1] if (root.custom_color[0]*0.299 + \ @@ -600,8 +644,8 @@ def __init__(self, **kwargs): font_name : root.text_font FloatLayout: size_hint: None,None - width: dp(0.01) - height: dp(0.01) + width: dp(0.01) + height: dp(0.01) BoxLayout: size_hint:None,None x:main_box.x + main_box.width + dp(4) @@ -610,15 +654,15 @@ def __init__(self, **kwargs): height:label3.texture_size[1] Label: id:label3 - text: - root.custom_label if root.custom_label else '' + text: + root.custom_label if root.custom_label else '' font_size:root.text_size color: root.text_color - font_name : root.text_font - + font_name : root.text_font + - custom_color: [0,0,0,1] - + custom_color: [0,0,0,1] + BoxLayout: id:main_box x: @@ -628,41 +672,41 @@ def __init__(self, **kwargs): root.ymin_line + dp(4) if abs(root.y_touch_pos -root.ymin_line) > abs(root.y_touch_pos -root.ymax_line) else root.ymax_line - dp(4) - self.height size_hint: None, None height: self.minimum_height - width: + width: self.minimum_width + dp(12) if root.show_cursor \ else dp(0.0001) orientation:'vertical' padding: 0,dp(4),0,dp(4) - - canvas: + + canvas: Color: rgba: root.background_color RoundedRectangle: pos: self.pos size: self.size - radius: [dp(4)] + radius: [dp(4)] Color: rgba: 0,0,0,1 - + Line: - width: 1 + width: 1 rounded_rectangle: (self.x, self.y, self.width, self.height,\ dp(4), dp(4), dp(4), dp(4),\ - self.height) - - canvas.after: + self.height) + + canvas.after: Color: rgba: 0,0,1,1 Line: width: dp(1) - points: + points: + root.x_hover_pos, \ + root.ymin_line, \ root.x_hover_pos, \ - root.ymin_line, \ - root.x_hover_pos, \ - root.ymax_line - - + root.ymax_line + + BoxLayout: size_hint:None,None width:label.texture_size[0] + dp(12) @@ -670,15 +714,15 @@ def __init__(self, **kwargs): padding: dp(12),0,0,0 Label: id:label - text: - root.label_x + ': ' + root.label_x_value + text: + root.label_x + ': ' + root.label_x_value font_size:root.text_size font_name : root.text_font color: root.text_color - - CompareHoverBox: - id:comparehoverbox - + + CompareHoverBox: + id:comparehoverbox + size_hint:None,None width:label.texture_size[0] + dp(12) + dp(24) if self.show_widget else dp(12) @@ -688,22 +732,22 @@ def __init__(self, **kwargs): Widget: size_hint_x:None width:dp(16) - canvas: + canvas: Color: rgba: root.custom_color if root.custom_color else [0,0,0,1] Rectangle: pos: self.pos[0],self.pos[1]+self.height/2-dp(6) - size: dp(12),dp(12) + size: dp(12),dp(12) Label: id:label - text: root.label_y + ': ' + root.label_y_value + text: root.label_y + ': ' + root.label_y_value color: root.text_color font_size:root.text_size - font_name : root.text_font + font_name : root.text_font - custom_color: [0,0,0,1] - + custom_color: [0,0,0,1] + BoxLayout: id:main_box x: @@ -713,40 +757,40 @@ def __init__(self, **kwargs): root.ymin_line if abs(root.y_touch_pos -root.ymin_line) > abs(root.y_touch_pos -root.ymax_line) else root.ymax_line - self.height size_hint: None, None height: root.hover_height - width: + width: self.minimum_width + dp(12) if root.show_cursor \ else dp(0.0001) orientation:'vertical' padding: 0,dp(4),0,dp(4) - - canvas.after: + + canvas.after: Color: rgba: 0,0,1,0 Line: width: dp(1) - points: + points: root.x_hover_pos, \ - root.ymin_line, \ - root.x_hover_pos, \ - root.ymax_line - - + root.ymin_line, \ + root.x_hover_pos, \ + root.ymax_line + + FloatLayout: size_hint:None,None width:dp(0.01) height:dp(0.01) - BoxLayout: + BoxLayout: size_hint:None,None width:label.texture_size[0] + dp(8) height:label.texture_size[1] + dp(6) x: root.x_hover_pos - label.texture_size[0]/2 - dp(4) y: root.ymin_line - label.texture_size[1] - dp(10) - canvas.before: + canvas.before: Color: rgba: [0,0,0,1] Rectangle: pos: self.pos - size: self.size + size: self.size Triangle: points: [ \ @@ -754,19 +798,19 @@ def __init__(self, **kwargs): root.x_hover_pos, root.ymin_line, \ root.x_hover_pos +dp(4), root.ymin_line-dp(4) \ ] - + Label: id:label - text: - root.label_x_value + text: + root.label_x_value font_size:root.text_size font_name : root.text_font color: [1,1,1,1] - - TagCompareHoverBox: - id:ycomparehoverbox - + TagCompareHoverBox: + id:ycomparehoverbox + + size_hint:None,None width:dp(0.01) @@ -780,13 +824,13 @@ def __init__(self, **kwargs): padding: dp(2),0,0,0 x: root.x_hover_pos + dp(4) y: root.y_hover_pos - label.texture_size[1]/2-dp(3) + root.hover_offset - - canvas.before: + + canvas.before: Color: rgba: root.custom_color if root.custom_color else [0,0,0,1] Rectangle: pos: self.pos - size: self.size + size: self.size Triangle: points: @@ -801,93 +845,93 @@ def __init__(self, **kwargs): [ \ root.x_hover_pos, root.y_hover_pos, \ main_box.x, root.y_hover_pos + root.hover_offset \ - ] + ] Label: id:label - text: root.label_y_value + text: root.label_y_value color: [0,0,0,1] if (root.custom_color[0]*0.299 + \ root.custom_color[1]*0.587 + root.custom_color[2]*0.114) > 186/255 \ else [1,1,1,1] font_size:root.text_size - font_name : root.text_font - + font_name : root.text_font + FloatLayout: size_hint: None,None - width: dp(0.01) - height: dp(0.01) + width: dp(0.01) + height: dp(0.01) BoxLayout: size_hint:None,None x:main_box.x + main_box.width + dp(2) y:main_box.y + main_box.height/2-label2.texture_size[1]/2 width:label2.texture_size[0] height:label2.texture_size[1] - canvas.before: + canvas.before: Color: rgba: [1,1,1,0.7] Rectangle: pos: self.pos - size: self.size + size: self.size Label: id:label2 - text: root.label_y + text: root.label_y font_size:root.text_size color: [0,0,0,1] - font_name : root.text_font - + font_name : root.text_font + - custom_color: [0,0,0,1] - + custom_color: [0,0,0,1] + BoxLayout: id:main_box x:root.xmin_line - y: root.ymax_line + dp(4) + y: root.ymax_line + dp(4) size_hint: None, None height: self.minimum_height - width: + width: self.minimum_width + dp(12) if root.show_cursor \ else dp(0.0001) orientation:'vertical' padding: 0,dp(4),0,dp(4) - - canvas: + + canvas: Color: rgba: root.background_color Rectangle: pos: self.pos size: self.size - - canvas.after: + + canvas.after: Color: rgba: 0,0,0,1 Line: width: dp(1) - points: + points: root.x_hover_pos, \ - root.ymin_line, \ - root.x_hover_pos, \ - root.ymax_line + root.ymin_line, \ + root.x_hover_pos, \ + root.ymax_line dash_offset: 2 dash_length: 10 - + Line: width: dp(1) - points: + points: root.xmin_line, \ - root.y_hover_pos, \ - root.xmax_line, \ - root.y_hover_pos + root.y_hover_pos, \ + root.xmax_line, \ + root.y_hover_pos dash_offset: 2 - dash_length: 10 - + dash_length: 10 + BoxLayout: size_hint:None,None width:label.texture_size[0] + dp(12) height:label.texture_size[1] + dp(12) Label: id:label - text: - root.label_x + ': ' + root.label_x_value + ' ' + root.label_y + ': ' + root.label_y_value + text: + root.label_x + ': ' + root.label_x_value + ' ' + root.label_y + ': ' + root.label_y_value font_size:root.text_size font_name : root.text_font color: root.text_color @@ -901,20 +945,20 @@ def __init__(self, **kwargs): '[/font][/color][/size]' + root.extra_text + "\\n" + 'x:' + \ root.label_x_value +"\\n"+ "y:" + root.label_y_value - canvas.before: + canvas.before: Color: rgb: root.custom_color if root.custom_color else [0,0,0,1] a:0.5 Ellipse: size: (dp(12),dp(12)) - pos: (root.x_hover_pos - dp(6),root.y_hover_pos-dp(6)) - - use_position: + pos: (root.x_hover_pos - dp(6),root.y_hover_pos-dp(6)) + + use_position: 'left_' + root.position.split('_')[1] if ('right_' in root.position and root.x_hover_pos + main_box.width + dp(14) > root.figwidth + root.figx) else \ 'left' if ('right'==root.position and root.x_hover_pos + main_box.width + dp(14) > root.figwidth + root.figx) else \ 'right' if ('left'==root.position and root.x_hover_pos - main_box.width -dp(14) < root.figx) else \ - 'right_' + root.position.split('_')[1] if ('left_' in root.position and root.x_hover_pos - main_box.width -dp(14) < root.figx) else root.position - + 'right_' + root.position.split('_')[1] if ('left_' in root.position and root.x_hover_pos - main_box.width -dp(14) < root.figx) else root.position + RichTooltip: id:main_box x: @@ -925,16 +969,16 @@ def __init__(self, **kwargs): root.y_hover_pos + dp(14) if 'top' in root.position else \ root.y_hover_pos - self.height - dp(14) if 'bottom' in root.position else \ root.y_hover_pos - self.height/2 - main_box.offset2 - + size_hint: None, None height: label.texture_size[1]+ root.label_padding_height*2 - width: + width: label.texture_size[0] + root.label_padding_width*2 if root.show_cursor \ - else dp(0.0001) + else dp(0.0001) orientation:'vertical' padding: 0,-dp(1),0,0 position:root.use_position - + Label: id:label text: root.label_format @@ -948,10 +992,10 @@ def __init__(self, **kwargs): size_hint: None,None width:dp(200) height:dp(60) - offset1: + offset1: 0 if not '_' in root.position else -mainbox.width//2 + dp(20) \ if 'start' in root.position else mainbox.width//2 - dp(20) - offset2: + offset2: 0 if not '_' in root.position else mainbox.height//2 - dp(20) \ if 'start' in root.position else - mainbox.height//2 + dp(20) canvas.before: @@ -959,18 +1003,18 @@ def __init__(self, **kwargs): rgba: 0, 0, 0, .65 BoxShadow: pos: self.pos - size: self.size + size: self.size offset: 0, -2 spread_radius: -4, -4 border_radius: 4, 4, 4, 4 - blur_radius: 14 + blur_radius: 14 Color: rgba: 1, 1, 1, 1 SmoothRoundedRectangle: pos: self.pos size: self.size - radius: [7,] - + radius: [7,] + FloatLayout: size_hint: None,None size:dp(0.01),dp(0.01) @@ -979,14 +1023,14 @@ def __init__(self, **kwargs): mainbox.x+mainbox.width//2 - self.width//2 + root.offset1 \ if ('top' in root.position or 'bottom' in root.position) \ else mainbox.x +mainbox.width - self.width//2 - dp(1) \ - if 'left' in root.position else mainbox.x - self.width//2 + dp(1) + if 'left' in root.position else mainbox.x - self.width//2 + dp(1) y: mainbox.y - self.height//2 +dp(1) if 'top' in root.position \ else mainbox.y + mainbox.height - self.height//2 - dp(1) \ - if 'bottom' in root.position else mainbox.y + mainbox.height//2 - self.height//2 + root.offset2 + if 'bottom' in root.position else mainbox.y + mainbox.height//2 - self.height//2 + root.offset2 size_hint: None,None width:dp(12) - height:dp(12) + height:dp(12) canvas.before: StencilPush Rectangle: @@ -999,7 +1043,7 @@ def __init__(self, **kwargs): (self.size[0]+self.width,self.size[1]+dp(5)) \ if ('top' in root.position or 'bottom' in root.position ) \ else (self.size[0]+dp(5) ,self.size[1] + dp(40)) - StencilUse + StencilUse Color: rgba: 0, 0, 0, .65 PushMatrix @@ -1017,35 +1061,35 @@ def __init__(self, **kwargs): rgba: 1, 1, 1, 1 SmoothRectangle: pos: self.pos - size: self.size - PopMatrix - StencilPop + size: self.size + PopMatrix + StencilPop custom_color: [0,0,0,1] - - use_position: + + use_position: 'left' if ('right'==root.position and root.x_hover_pos + main_box.width + label3.texture_size[0] + dp(6) > root.figwidth + root.figx) else \ 'right' if ('left'==root.position and root.x_hover_pos - main_box.width - label3.texture_size[0] - dp(6) < root.figx) else \ root.position - + BoxLayout: id:main_box x: root.x_hover_pos + dp(4) if 'right' in root.use_position else \ - root.x_hover_pos - self.width - dp(4) + root.x_hover_pos - self.width - dp(4) y: root.y_hover_pos - root.hover_height/2 - + size_hint: None, None height: label.texture_size[1]+ dp(4) - width: + width: self.minimum_width + dp(12) if root.show_cursor \ - else dp(0.0001) + else dp(0.0001) orientation:'vertical' padding: 0,-dp(1),0,0 - - canvas: + + canvas: Color: rgba: root.custom_color if root.custom_color else [0,0,0,1] Rectangle: @@ -1062,7 +1106,7 @@ def __init__(self, **kwargs): root.x_hover_pos, root.y_hover_pos, \ main_box.x + main_box.width, root.y_hover_pos+ dp(4), \ main_box.x + main_box.width, root.y_hover_pos- dp(4) \ - ] + ] SmoothLine: width:dp(1) points: @@ -1074,15 +1118,15 @@ def __init__(self, **kwargs): root.x_hover_pos, root.y_hover_pos, \ main_box.x + main_box.width, root.y_hover_pos \ ] - - + + BoxLayout: size_hint_x:None width:label.texture_size[0] padding: dp(12),0,0,0 Label: id:label - text: + text: '(' + root.label_x_value +','+ root.label_y_value +')' font_size:root.text_size color: @@ -1092,11 +1136,11 @@ def __init__(self, **kwargs): font_name : root.text_font font_name : root.text_font - + FloatLayout: size_hint: None,None - width: dp(0.01) - height: dp(0.01) + width: dp(0.01) + height: dp(0.01) BoxLayout: size_hint:None,None x: @@ -1107,14 +1151,14 @@ def __init__(self, **kwargs): height:label3.texture_size[1] Label: id:label3 - text: - root.custom_label if root.custom_label and not '_child' in root.custom_label else '' + text: + root.custom_label if root.custom_label and not '_child' in root.custom_label else '' font_size:root.text_size color: root.text_color - font_name : root.text_font + font_name : root.text_font - custom_color: [0,0,0,1] + custom_color: [0,0,0,1] BoxLayout: id:main_box @@ -1125,41 +1169,41 @@ def __init__(self, **kwargs): root.ymin_line + dp(4) if abs(root.y_touch_pos -root.ymin_line) > abs(root.y_touch_pos -root.ymax_line) else root.ymax_line - dp(4) - self.height size_hint: None, None height: self.minimum_height - width: + width: self.minimum_width + dp(12) if root.show_cursor \ else dp(0.0001) orientation:'vertical' padding: 0,dp(4),0,dp(4) - - canvas: + + canvas: Color: rgba: 0, 0, 0, .65 BoxShadow: pos: self.pos - size: self.size + size: self.size offset: 0, -2 spread_radius: -4, -4 border_radius: 4, 4, 4, 4 - blur_radius: 14 + blur_radius: 14 Color: rgba: root.background_color SmoothRoundedRectangle: pos: self.pos size: self.size radius: [7,] - - canvas.after: + + canvas.after: Color: rgba: 0,0,1,0.1 SmoothLine: width: dp(8) - points: + points: + root.x_hover_pos, \ + root.ymin_line, \ root.x_hover_pos, \ - root.ymin_line, \ - root.x_hover_pos, \ - root.ymax_line - - + root.ymax_line + + BoxLayout: size_hint:None,None width:label.texture_size[0] + dp(12) @@ -1167,14 +1211,14 @@ def __init__(self, **kwargs): padding: dp(6),0,0,0 Label: id:label - text: + text: root.label_x + ': ' + root.label_x_value if root.label_x else root.label_x_value font_size:root.text_size font_name : root.text_font color: root.text_color - - DotCompareHoverBox: - id:comparehoverbox + + DotCompareHoverBox: + id:comparehoverbox size_hint:None,None @@ -1185,22 +1229,22 @@ def __init__(self, **kwargs): extra_text:root.label_y if root.label_y and not '_child' in root.label_y else '' label_format:'[size={}]'.format(int(root.text_size + dp(6))) + '[color={}]'.format(get_hex_from_color(root.custom_color)) + \ '[font=NavigationIcons]' + u"{}".format("\U00000EB1") + \ - '[/font][/color][/size]' + root.extra_text + ": " + root.label_y_value - - canvas.before: + '[/font][/color][/size]' + root.extra_text + ": " + root.label_y_value + + canvas.before: Color: rgb: root.custom_color if root.custom_color else [0,0,0,1] a:0.7 SmoothEllipse: size: (dp(12),dp(12)) - pos: (root.x_hover_pos - dp(6),root.y_hover_pos-dp(6)) + pos: (root.x_hover_pos - dp(6),root.y_hover_pos-dp(6)) Color: rgb: [1,1,1] a:1.0 SmoothLine: width: 1. - ellipse: (root.x_hover_pos - dp(7), root.y_hover_pos-dp(7), dp(14), dp(14)) - + ellipse: (root.x_hover_pos - dp(7), root.y_hover_pos-dp(7), dp(14), dp(14)) + Label: id:label @@ -1210,5 +1254,5 @@ def __init__(self, **kwargs): font_size:root.text_size color:[0,0,0,1] font_name : root.text_font - markup:True + markup:True ''') diff --git a/kivy_matplotlib_widget/uix/legend_widget.py b/kivy_matplotlib_widget/uix/legend_widget.py index e421adf..8c69fae 100644 --- a/kivy_matplotlib_widget/uix/legend_widget.py +++ b/kivy_matplotlib_widget/uix/legend_widget.py @@ -1,3 +1,4 @@ +from kivy.factory import Factory from kivy.uix.boxlayout import BoxLayout from kivy.uix.floatlayout import FloatLayout from kivy.utils import get_color_from_hex @@ -13,7 +14,7 @@ ListProperty, BooleanProperty, ColorProperty - ) +) from kivy.lang import Builder from kivy.uix.widget import Widget @@ -29,7 +30,7 @@ class LegendGestures(Widget): """ This widget is based on CommonGestures from gestures4kivy project https://github.com/Android-for-Python/gestures4kivy - + For more gesture features like long press or swipe, replace LegendGestures by CommonGestures (available on gestures4kivy project). """ @@ -38,21 +39,20 @@ def __init__(self, **kwargs): super().__init__(**kwargs) self.mobile = platform == 'android' or platform == 'ios' self._new_gesture() - #### Sensitivity - self._DOUBLE_TAP_TIME = Config.getint('postproc', - 'double_tap_time') / 1000 + # Sensitivity + self._DOUBLE_TAP_TIME = Config.getint('postproc', + 'double_tap_time') / 1000 self._DOUBLE_TAP_DISTANCE = Config.getint('postproc', 'double_tap_distance') - self._persistent_pos = [(0,0),(0,0)] - + self._persistent_pos = [(0, 0), (0, 0)] ##################### # Kivy Touch Events ##################### # In the case of a RelativeLayout, the touch.pos value is not persistent. # Because the same Touch is called twice, once with Window relative and - # once with the RelativeLayout relative values. + # once with the RelativeLayout relative values. # The on_touch_* callbacks have the required value because of collide_point # but only within the scope of that touch callback. # @@ -60,9 +60,10 @@ def __init__(self, **kwargs): # So if we have a RelativeLayout we can't rely on the value in touch.pos . # So regardless of there being a RelativeLayout, we save each touch.pos # in self._persistent_pos[] and use that when the current value is - # required. - + # required. + ### touch down ### + def on_touch_down(self, touch): if self.collide_point(touch.x, touch.y): if len(self._touches) == 1 and touch.id == self._touches[0].id: @@ -79,18 +80,18 @@ def on_touch_down(self, touch): # Two finger tap or right click pass else: - self._gesture_state = 'Dont Know' - # schedule a posssible tap + self._gesture_state = 'Dont Know' + # schedule a posssible tap if not self._single_tap_schedule: self._single_tap_schedule =\ Clock.schedule_once(partial(self._single_tap_event, - touch , + touch, touch.x, touch.y), self._DOUBLE_TAP_TIME) self._persistent_pos[0] = tuple(touch.pos) elif len(self._touches) == 2: - # two fingers + # two fingers self._not_single_tap() self._persistent_pos[1] = tuple(touch.pos) @@ -112,7 +113,7 @@ def on_touch_up(self, touch): else: self._new_gesture() - return super().on_touch_up(touch) + return super().on_touch_up(touch) ############################################ # gesture utilities @@ -124,7 +125,7 @@ def _single_tap_event(self, touch, x, y, dt): if self._gesture_state == 'Dont Know': if not self._long_press_schedule: x, y = self._pos_to_widget(x, y) - self.cg_tap(touch,x,y) + self.cg_tap(touch, x, y) self._new_gesture() def _not_single_tap(self): @@ -132,7 +133,6 @@ def _not_single_tap(self): Clock.unschedule(self._single_tap_schedule) self._single_tap_schedule = None - ### Every result is in the self frame ### def _pos_to_widget(self, x, y): @@ -144,7 +144,7 @@ def _remove_gesture(self, touch): if touch and len(self._touches): if touch in self._touches: self._touches.remove(touch) - + def _new_gesture(self): self._touches = [] self._long_press_schedule = None @@ -154,63 +154,65 @@ def _new_gesture(self): self._finger_distance = 0 self._velocity = 0 - ############################################ # User Events # define some subset in the derived class ############################################ - ############# Tap, Double Tap, and Long Press + # Tap, Double Tap, and Long Press + def cg_tap(self, touch, x, y): pass def cg_double_tap(self, touch, x, y): pass + class LegendRv(BoxLayout): """Legend class - - """ - figure_wgt = ObjectProperty(None) - data=ListProperty() - text_color=ColorProperty([0,0,0,1]) - text_font=StringProperty("Roboto") - text_font_size=NumericProperty(dp(18.5)) - background_color=ColorProperty([1,1,1,1]) + + """ + figure_wgt = ObjectProperty(None) + data = ListProperty() + text_color = ColorProperty([0, 0, 0, 1]) + text_font = StringProperty("Roboto") + text_font_size = NumericProperty(dp(18.5)) + background_color = ColorProperty([1, 1, 1, 1]) box_height = NumericProperty(dp(48)) - autoscale=BooleanProperty(False) + autoscale = BooleanProperty(False) def __init__(self, **kwargs): """init class - - """ + + """ super(LegendRv, self).__init__(**kwargs) - self.data=[] + self.data = [] + + def set_data(self, content: list) -> None: + """set legend data - def set_data(self,content:list) -> None: - """set legend data - Args: - content (list):list of dictionary with matplotlib line(s) - + content (list):list of dictionary with matplotlib line(s) + Returns: None - """ - self.data=[] - - #reset scroll + """ + self.data = [] + + # reset scroll self.ids.view.scroll_y = 1 - for i,row_content in enumerate(content): - + for i, row_content in enumerate(content): + r_data = { - "row_index":int(i), + "row_index": int(i), "viewclass": "CellLegend" - } + } r_data["text"] = str(row_content.get_label()) - r_data["mycolor"] = get_color_from_hex(to_hex(row_content.get_color())) + r_data["mycolor"] = get_color_from_hex( + to_hex(row_content.get_color())) r_data["line_type"] = row_content.get_linestyle() - r_data["legend_rv"] = self + r_data["legend_rv"] = self r_data["selected"] = False r_data["matplotlib_line"] = row_content r_data["text_color"] = self.text_color @@ -219,25 +221,25 @@ def set_data(self,content:list) -> None: r_data["box_height"] = self.box_height self.data.append(r_data) - - def add_data(self,line) -> None: + + def add_data(self, line) -> None: """add line method - + Args: line:matplotlib line - + Returns: None - """ - nb_data=len(self.data) + """ + nb_data = len(self.data) r_data = { - "row_index":int(nb_data), + "row_index": int(nb_data), "viewclass": "CellLegend" - } + } r_data["text"] = str(line.get_label()) r_data["mycolor"] = get_color_from_hex(to_hex(line.get_color())) r_data["line_type"] = line.get_linestyle() - r_data["legend_rv"] = self + r_data["legend_rv"] = self r_data["selected"] = False r_data["matplotlib_line"] = line r_data["text_color"] = self.text_color @@ -247,141 +249,144 @@ def add_data(self,line) -> None: self.data.append(r_data) self.figure_wgt.figure.canvas.draw_idle() - self.figure_wgt.figure.canvas.flush_events() - - def remove_data(self,remove_line) -> None: + self.figure_wgt.figure.canvas.flush_events() + + def remove_data(self, remove_line) -> None: """add line method - + Args: remove_line: matplotlib line - + Returns: None - """ - remove_idx=None - for idx,current_data in enumerate(self.data): + """ + remove_idx = None + for idx, current_data in enumerate(self.data): if current_data["matplotlib_line"] == remove_line: - remove_idx=idx - break + remove_idx = idx + break if remove_idx: del self.data[remove_idx] remove_line.remove() self.figure_wgt.figure.canvas.draw_idle() - self.figure_wgt.figure.canvas.flush_events() - - def show_hide_wgt(self,row_index) -> None: + self.figure_wgt.figure.canvas.flush_events() + + def show_hide_wgt(self, row_index) -> None: if self.data[row_index]["selected"]: - #show line + # show line self.data[row_index]["selected"] = False self.data[row_index]["matplotlib_line"].set_visible(True) - if self.autoscale and hasattr(self.figure_wgt,'autoscale_axis'): + if self.autoscale and hasattr(self.figure_wgt, 'autoscale_axis'): self.figure_wgt.autoscale() - else: + else: self.figure_wgt.figure.canvas.draw_idle() - self.figure_wgt.figure.canvas.flush_events() + self.figure_wgt.figure.canvas.flush_events() else: - #hide line + # hide line self.data[row_index]["selected"] = True self.data[row_index]["matplotlib_line"].set_visible(False) - if self.autoscale and hasattr(self.figure_wgt,'autoscale_axis'): + if self.autoscale and hasattr(self.figure_wgt, 'autoscale_axis'): self.figure_wgt.autoscale() else: self.figure_wgt.figure.canvas.draw_idle() - self.figure_wgt.figure.canvas.flush_events() + self.figure_wgt.figure.canvas.flush_events() self.ids.view.refresh_from_layout() - - def doubletap(self,row_index) -> None: + + def doubletap(self, row_index) -> None: """ double tap behavior is based on plotly behavior """ if not self.data[row_index]["selected"]: current_line = self.data[row_index]["matplotlib_line"] if current_line.get_visible(): - #check if we isolate line or show all lines + # check if we isolate line or show all lines need_isolate = False - if len(self.figure_wgt.figure.axes)>1: - figure_lines = self.figure_wgt.figure.axes[0].get_lines() + \ - self.figure_wgt.figure.axes[1].get_lines() + if len(self.figure_wgt.figure.axes) > 1: + figure_lines = self.figure_wgt.figure.axes[0].get_lines( + ) + self.figure_wgt.figure.axes[1].get_lines() else: - figure_lines = list(self.figure_wgt.figure.axes[0].get_lines()) + figure_lines = list( + self.figure_wgt.figure.axes[0].get_lines()) for line in figure_lines: if line != current_line and line.get_visible(): - need_isolate=True - break - + need_isolate = True + break + if need_isolate: - #isolate line' - for idx,line in enumerate(figure_lines): - if line != current_line: - line.set_visible(False) - self.data[idx]["selected"] = True - else: - self.data[idx]["selected"] = False + # isolate line' + for idx, line in enumerate(figure_lines): + if line != current_line: + line.set_visible(False) + self.data[idx]["selected"] = True + else: + self.data[idx]["selected"] = False else: - #show all lines' - for idx,line in enumerate(figure_lines): + # show all lines' + for idx, line in enumerate(figure_lines): line.set_visible(True) - self.data[idx]["selected"] = False + self.data[idx]["selected"] = False else: - #show all lines - if len(self.figure_wgt.figure.axes)>1: + # show all lines + if len(self.figure_wgt.figure.axes) > 1: figure_lines = self.figure_wgt.figure.axes[0].get_lines() + \ - self.figure_wgt.figure.axes[1].get_lines() + self.figure_wgt.figure.axes[1].get_lines() else: - figure_lines = list(self.figure_wgt.figure.axes[0].get_lines()) - for idx,line in enumerate(figure_lines): - line.set_visible(True) - self.data[idx]["selected"] = False - + figure_lines = list(self.figure_wgt.figure.axes[0].get_lines()) + for idx, line in enumerate(figure_lines): + line.set_visible(True) + self.data[idx]["selected"] = False + self.ids.view.refresh_from_layout() - if self.autoscale and hasattr(self.figure_wgt,'autoscale_axis'): - self.figure_wgt.autoscale() + if self.autoscale and hasattr(self.figure_wgt, 'autoscale_axis'): + self.figure_wgt.autoscale() else: self.figure_wgt.figure.canvas.draw_idle() self.figure_wgt.figure.canvas.flush_events() - + + class LegendRvHorizontal(BoxLayout): """Legend Horizontal class - - """ - figure_wgt = ObjectProperty(None) + + """ + figure_wgt = ObjectProperty(None) data = ListProperty() - text_color = ColorProperty([0,0,0,1]) + text_color = ColorProperty([0, 0, 0, 1]) text_font = StringProperty("Roboto") text_font_size = NumericProperty(dp(18.5)) - background_color = ColorProperty([1,1,1,1]) + background_color = ColorProperty([1, 1, 1, 1]) box_height = NumericProperty(dp(48)) - autoscale=BooleanProperty(False) - + autoscale = BooleanProperty(False) + def __init__(self, **kwargs): """init class - - """ + + """ super(LegendRvHorizontal, self).__init__(**kwargs) - self.data=[] + self.data = [] + + def set_data(self, content: list) -> None: + """set legend data - def set_data(self,content:list) -> None: - """set legend data - Args: - content (list):list of dictionary with matplotlib line(s) - + content (list):list of dictionary with matplotlib line(s) + Returns: None - """ - self.data=[] - - #reset scroll + """ + self.data = [] + + # reset scroll self.ids.view.scroll_y = 1 - for i,row_content in enumerate(content): - + for i, row_content in enumerate(content): + r_data = { - "row_index":int(i), + "row_index": int(i), "viewclass": "CellLegend" - } + } r_data["text"] = str(row_content.get_label()) - r_data["mycolor"] = get_color_from_hex(to_hex(row_content.get_color())) + r_data["mycolor"] = get_color_from_hex( + to_hex(row_content.get_color())) r_data["line_type"] = row_content.get_linestyle() - r_data["legend_rv"] = self + r_data["legend_rv"] = self r_data["selected"] = False r_data["matplotlib_line"] = row_content r_data["text_color"] = self.text_color @@ -390,25 +395,25 @@ def set_data(self,content:list) -> None: r_data['box_height'] = self.box_height self.data.append(r_data) - - def add_data(self,line) -> None: + + def add_data(self, line) -> None: """add line method - + Args: line:matplotlib line - + Returns: None - """ - nb_data=len(self.data) + """ + nb_data = len(self.data) r_data = { - "row_index":int(nb_data), + "row_index": int(nb_data), "viewclass": "CellLegend" - } + } r_data["text"] = str(line.get_label()) r_data["mycolor"] = get_color_from_hex(to_hex(line.get_color())) r_data["line_type"] = line.get_linestyle() - r_data["legend_rv"] = self + r_data["legend_rv"] = self r_data["selected"] = False r_data["matplotlib_line"] = line r_data["text_color"] = self.text_color @@ -418,170 +423,175 @@ def add_data(self,line) -> None: self.data.append(r_data) self.figure_wgt.figure.canvas.draw_idle() - self.figure_wgt.figure.canvas.flush_events() - - def remove_data(self,remove_line) -> None: + self.figure_wgt.figure.canvas.flush_events() + + def remove_data(self, remove_line) -> None: """add line method - + Args: remove_line: matplotlib line - + Returns: None - """ - remove_idx=None - for idx,current_data in enumerate(self.data): + """ + remove_idx = None + for idx, current_data in enumerate(self.data): if current_data["matplotlib_line"] == remove_line: - remove_idx=idx - break - if remove_idx: + remove_idx = idx + break + if remove_idx: del self.data[remove_idx] remove_line.remove() self.figure_wgt.figure.canvas.draw_idle() - self.figure_wgt.figure.canvas.flush_events() - - def show_hide_wgt(self,row_index) -> None: + self.figure_wgt.figure.canvas.flush_events() + + def show_hide_wgt(self, row_index) -> None: if self.data[row_index]["selected"]: - #show line + # show line self.data[row_index]["selected"] = False self.data[row_index]["matplotlib_line"].set_visible(True) - if self.autoscale and hasattr(self.figure_wgt,'autoscale_axis'): + if self.autoscale and hasattr(self.figure_wgt, 'autoscale_axis'): self.figure_wgt.autoscale() else: self.figure_wgt.figure.canvas.draw_idle() - self.figure_wgt.figure.canvas.flush_events() + self.figure_wgt.figure.canvas.flush_events() else: - #hide line + # hide line self.data[row_index]["selected"] = True self.data[row_index]["matplotlib_line"].set_visible(False) - if self.autoscale and hasattr(self.figure_wgt,'autoscale_axis'): + if self.autoscale and hasattr(self.figure_wgt, 'autoscale_axis'): self.figure_wgt.autoscale() else: self.figure_wgt.figure.canvas.draw_idle() - self.figure_wgt.figure.canvas.flush_events() + self.figure_wgt.figure.canvas.flush_events() self.ids.view.refresh_from_layout() - - def doubletap(self,row_index) -> None: + + def doubletap(self, row_index) -> None: """ double tap behavior is based on plotly behavior """ if not self.data[row_index]["selected"]: current_line = self.data[row_index]["matplotlib_line"] if current_line.get_visible(): - #check if we isolate line or show all lines + # check if we isolate line or show all lines need_isolate = False - if len(self.figure_wgt.figure.axes)>1: - figure_lines = self.figure_wgt.figure.axes[0].get_lines() + \ - self.figure_wgt.figure.axes[1].get_lines() + if len(self.figure_wgt.figure.axes) > 1: + figure_lines = self.figure_wgt.figure.axes[0].get_lines( + ) + self.figure_wgt.figure.axes[1].get_lines() else: - figure_lines = list(self.figure_wgt.figure.axes[0].get_lines()) + figure_lines = list( + self.figure_wgt.figure.axes[0].get_lines()) for line in figure_lines: if line != current_line and line.get_visible(): - need_isolate=True - break - + need_isolate = True + break + if need_isolate: - #isolate line' - for idx,line in enumerate(figure_lines): - if line != current_line: - line.set_visible(False) - self.data[idx]["selected"] = True - else: - self.data[idx]["selected"] = False + # isolate line' + for idx, line in enumerate(figure_lines): + if line != current_line: + line.set_visible(False) + self.data[idx]["selected"] = True + else: + self.data[idx]["selected"] = False else: - #show all lines' - for idx,line in enumerate(figure_lines): + # show all lines' + for idx, line in enumerate(figure_lines): line.set_visible(True) - self.data[idx]["selected"] = False + self.data[idx]["selected"] = False else: - #show all lines - if len(self.figure_wgt.figure.axes)>1: + # show all lines + if len(self.figure_wgt.figure.axes) > 1: figure_lines = self.figure_wgt.figure.axes[0].get_lines() + \ - self.figure_wgt.figure.axes[1].get_lines() + self.figure_wgt.figure.axes[1].get_lines() else: - figure_lines = list(self.figure_wgt.figure.axes[0].get_lines()) - for idx,line in enumerate(figure_lines): - line.set_visible(True) - self.data[idx]["selected"] = False - + figure_lines = list(self.figure_wgt.figure.axes[0].get_lines()) + for idx, line in enumerate(figure_lines): + line.set_visible(True) + self.data[idx]["selected"] = False + self.ids.view.refresh_from_layout() - if self.autoscale and hasattr(self.figure_wgt,'autoscale_axis'): - self.figure_wgt.autoscale() + if self.autoscale and hasattr(self.figure_wgt, 'autoscale_axis'): + self.figure_wgt.autoscale() else: self.figure_wgt.figure.canvas.draw_idle() self.figure_wgt.figure.canvas.flush_events() + class CellLegendMatplotlib(LegendGestures, BoxLayout): - """ Touch legend kivy class""" + """ Touch legend kivy class""" selected = BooleanProperty(False) row_index = NumericProperty(0) matplotlib_legend_box = ObjectProperty(None) matplotlib_line = ObjectProperty(None) matplotlib_text = ObjectProperty(None) - + def __init__(self, **kwargs): """ init class """ - super().__init__(**kwargs) + super().__init__(**kwargs) def cg_tap(self, touch, x, y): - #single tap - if self.matplotlib_legend_box.figure_wgt.touch_mode!='drag_legend': + # single tap + if self.matplotlib_legend_box.figure_wgt.touch_mode != 'drag_legend': self.matplotlib_legend_box.show_hide_wgt(self.row_index) def cg_double_tap(self, touch, x, y): - #double tap + # double tap self.matplotlib_legend_box.doubletap(self.row_index) - + + class CellLegend(LegendGestures, BoxLayout): - """ Touch legend kivy class""" + """ Touch legend kivy class""" selected = BooleanProperty(False) text = StringProperty("") row_index = NumericProperty(0) legend_rv = ObjectProperty(None) matplotlib_line = ObjectProperty(None) - line_type = StringProperty('-') - mycolor= ListProperty([0,0,1]) - text_color=ColorProperty([0,0,0,1]) - text_font=StringProperty("Roboto") - text_font_size=NumericProperty(dp(18.5)) - box_height = NumericProperty(dp(48)) - + line_type = StringProperty('-') + mycolor = ListProperty([0, 0, 1]) + text_color = ColorProperty([0, 0, 0, 1]) + text_font = StringProperty("Roboto") + text_font_size = NumericProperty(dp(18.5)) + box_height = NumericProperty(dp(48)) + def __init__(self, **kwargs): """ init class """ - super().__init__(**kwargs) + super().__init__(**kwargs) def cg_tap(self, touch, x, y): - #single tap + # single tap self.legend_rv.show_hide_wgt(self.row_index) def cg_double_tap(self, touch, x, y): - #double tap + # double tap self.legend_rv.doubletap(self.row_index) - + + class CellLegendHorizontal(LegendGestures, BoxLayout): - """ Touch legend kivy class""" + """ Touch legend kivy class""" selected = BooleanProperty(False) text = StringProperty("") row_index = NumericProperty(0) legend_rv = ObjectProperty(None) matplotlib_line = ObjectProperty(None) - line_type = StringProperty('-') - mycolor = ListProperty([0,0,1]) - text_color = ColorProperty([0,0,0,1]) + line_type = StringProperty('-') + mycolor = ListProperty([0, 0, 1]) + text_color = ColorProperty([0, 0, 0, 1]) text_font = StringProperty("Roboto") text_font_size = NumericProperty(dp(18.5)) - box_height = NumericProperty(dp(48)) + box_height = NumericProperty(dp(48)) def __init__(self, **kwargs): """ init class """ - super().__init__(**kwargs) + super().__init__(**kwargs) def cg_tap(self, touch, x, y): - #single tap + # single tap self.legend_rv.show_hide_wgt(self.row_index) def cg_double_tap(self, touch, x, y): - #double tap + # double tap self.legend_rv.doubletap(self.row_index) - + + def MatplotlibInteractiveLegend(figure_wgt, legend_handles='auto', delay=None, @@ -593,7 +603,7 @@ def MatplotlibInteractiveLegend(figure_wgt, scatter=None, autoscale=False): """ transform matplotlib legend to interactive legend - + Args: figure_wgt: figure widget from kivy_matplotlib_widget package legend_handles : 'auto' (general purpose) or variante (ex: for seaborn legend) @@ -604,39 +614,39 @@ def MatplotlibInteractiveLegend(figure_wgt, multi_legend (bool): Set it True if you have multiple legend in graph """ - - #check if the figure has a legend + + # check if the figure has a legend if legend_instance is None: leg = figure_wgt.figure.axes[0].get_legend() if leg is None: - #create a defaut legend if no figure exist - ax=figure_wgt.figure.axes[0] + # create a defaut legend if no figure exist + ax = figure_wgt.figure.axes[0] ax.legend() - leg = figure_wgt.figure.axes[0].get_legend() + leg = figure_wgt.figure.axes[0].get_legend() else: leg = legend_instance - - #put the legend on top (useful for drag behavior) + + # put the legend on top (useful for drag behavior) leg.set_zorder(20) figure_wgt.figcanvas.draw() - - #detect is the legend use column (ex: horizontal legend) - if hasattr(leg,'_ncols'): - #matplotlib version >3.6 + + # detect is the legend use column (ex: horizontal legend) + if hasattr(leg, '_ncols'): + # matplotlib version >3.6 legend_ncol = leg._ncols else: legend_ncol = leg._ncol - - if legend_ncol>1: - ncol=legend_ncol + + if legend_ncol > 1: + ncol = legend_ncol else: - ncol=None - + ncol = None + # create_touch_legend(figure_wgt,leg,ncol,legend_handles,0) if delay is None: - #no delay case + # no delay case create_touch_legend(figure_wgt, - leg,ncol, + leg, ncol, legend_handles, legend_instance, custom_handlers, @@ -647,9 +657,10 @@ def MatplotlibInteractiveLegend(figure_wgt, autoscale, 0) else: - #get legend bbox position atfer delay (sometime needed if 'best position' was used) + # get legend bbox position atfer delay (sometime needed if 'best + # position' was used) Clock.schedule_once(partial(create_touch_legend, - figure_wgt,leg,ncol, + figure_wgt, leg, ncol, legend_handles, legend_instance, custom_handlers, @@ -659,10 +670,10 @@ def MatplotlibInteractiveLegend(figure_wgt, scatter, autoscale), delay) - + def create_touch_legend(figure_wgt, - leg,ncol, + leg, ncol, legend_handles, legend_instance, custom_handlers, @@ -671,219 +682,237 @@ def create_touch_legend(figure_wgt, current_handles_text, scatter, autoscale, - _): + _): """ create touch legend """ - + bbox = leg.get_window_extent() - + if legend_instance is None: - ax=figure_wgt.figure.axes[0] + ax = figure_wgt.figure.axes[0] else: - ax=figure_wgt.figure.axes - legend_x0 = bbox.x0 - legend_y0 = bbox.y0 - legend_x1 = bbox.x1 - legend_y1 = bbox.y1 + ax = figure_wgt.figure.axes + legend_x0 = bbox.x0 + legend_y0 = bbox.y0 + legend_x1 = bbox.x1 + legend_y1 = bbox.y1 pos_x, pos_y = figure_wgt.pos if leg._get_loc() == 0: - #location best. Need to fix the legend location + # location best. Need to fix the legend location loc_in_canvas = bbox.xmin, bbox.ymin loc_in_norm_axes = leg.parent.transAxes.inverted().transform_point(loc_in_canvas) - leg._loc = tuple(loc_in_norm_axes) + leg._loc = tuple(loc_in_norm_axes) - #position for kivy widget - x0_pos=int(legend_x0)+pos_x - y0_pos=int(legend_y0)+pos_y - x1_pos=int(legend_x1)+pos_x - y1_pos=int(legend_y1)+pos_y + # position for kivy widget + x0_pos = int(legend_x0) + pos_x + y0_pos = int(legend_y0) + pos_y + x1_pos = int(legend_x1) + pos_x + y1_pos = int(legend_y1) + pos_y instance_dict = dict() - + if custom_handlers: - current_handles=custom_handlers + current_handles = custom_handlers else: - #get legend handles and labels + # get legend handles and labels if legend_instance is None: current_handles, current_labels = ax.get_legend_handles_labels() else: - current_handles=[] - current_labels=[] + current_handles = [] + current_labels = [] for current_ax in ax: current_handles0, current_labels0 = current_ax.get_legend_handles_labels() - current_handles+=current_handles0 - current_labels+=current_labels0 - - nb_group=len(current_handles) - - if nb_group==0: + current_handles += current_handles0 + current_labels += current_labels0 + + nb_group = len(current_handles) + + if nb_group == 0: print('no legend available') return - #check if a title exist - have_title=False + # check if a title exist + have_title = False if leg.get_title().get_text(): - have_title=True - - figure_wgt_as_legend=False + have_title = True + + figure_wgt_as_legend = False if figure_wgt.legend_instance and not multi_legend: for current_legend in figure_wgt.legend_instance: current_legend.reset_legend() matplotlib_legend_box = current_legend - figure_wgt_as_legend=True + figure_wgt_as_legend = True else: matplotlib_legend_box = MatplotlibLegendGrid() - - matplotlib_legend_box.prop=prop - matplotlib_legend_box.autoscale=autoscale - + + matplotlib_legend_box.prop = prop + matplotlib_legend_box.autoscale = autoscale + if prop: matplotlib_legend_box.facecolor = scatter.get_facecolor() - matplotlib_legend_box.mysize=float(scatter.get_sizes()[0]) + matplotlib_legend_box.mysize = float(scatter.get_sizes()[0]) matplotlib_legend_box.scatter = scatter - matplotlib_legend_box.current_handles_text=current_handles_text - matplotlib_legend_box.original_size=scatter.get_sizes() + matplotlib_legend_box.current_handles_text = current_handles_text + matplotlib_legend_box.original_size = scatter.get_sizes() if ncol: - #reorder legend handles to fit with grid layout orientation - #note: all kivy grid layout orientation don't fit with matpotlib grid position - - matplotlib_legend_box.ids.box.cols=ncol - - m, n = ceil(nb_group/ncol), ncol - index_arr = np.pad(np.arange(nb_group).astype(float), (0, m*n - np.arange(nb_group).size), - mode='constant', constant_values=np.nan) - index_arr2 = np.pad(np.arange(nb_group).astype(float), (0, m*n - np.arange(nb_group).size), - mode='constant', constant_values=np.nan).reshape(m,n) - - i=0 + # reorder legend handles to fit with grid layout orientation + # note: all kivy grid layout orientation don't fit with matpotlib grid + # position + + matplotlib_legend_box.ids.box.cols = ncol + + m, n = ceil(nb_group / ncol), ncol + index_arr = np.pad( + np.arange(nb_group).astype(float), + (0, + m * n - np.arange(nb_group).size), + mode='constant', + constant_values=np.nan) + index_arr2 = np.pad( + np.arange(nb_group).astype(float), + (0, + m * n - np.arange(nb_group).size), + mode='constant', + constant_values=np.nan).reshape( + m, + n) + + i = 0 for col in range(ncol): for row in range(m): - value=index_arr2[int(row),int(col)] + value = index_arr2[int(row), int(col)] if not np.isnan(value): - index_arr2[int(row),int(col)]=index_arr[i] - i+=1 + index_arr2[int(row), int(col)] = index_arr[i] + i += 1 myorder = index_arr2.flatten() myorder = myorder[~np.isnan(myorder)] current_handles = [current_handles[int(i)] for i in myorder] else: - matplotlib_legend_box.ids.box.cols=1 - - #update kivy widget attributes + matplotlib_legend_box.ids.box.cols = 1 + + # update kivy widget attributes matplotlib_legend_box.x_pos = x0_pos matplotlib_legend_box.y_pos = y0_pos - matplotlib_legend_box.legend_height = y1_pos-y0_pos + matplotlib_legend_box.legend_height = y1_pos - y0_pos if legend_instance: matplotlib_legend_box.legend_instance = legend_instance - + if have_title: - - #we create an offset for the matplotlib position + + # we create an offset for the matplotlib position if ncol: - title_padding = (y1_pos-y0_pos)/(ceil(nb_group/ncol)+1) + title_padding = (y1_pos - y0_pos) / (ceil(nb_group / ncol) + 1) else: - title_padding = (y1_pos-y0_pos)/(nb_group+1) - + title_padding = (y1_pos - y0_pos) / (nb_group + 1) + matplotlib_legend_box.title_padding = title_padding matplotlib_legend_box.legend_height -= title_padding - - matplotlib_legend_box.legend_width = x1_pos-x0_pos - - if leg.get_patches()[:] and legend_handles=='variante': - #sometime the legend handles not perfectly match with graph - #in this case, this experimenta section try to fix this - ax_instances = ax.get_children() - hist_instance=[] + + matplotlib_legend_box.legend_width = x1_pos - x0_pos + + if leg.get_patches()[:] and legend_handles == 'variante': + # sometime the legend handles not perfectly match with graph + # in this case, this experimenta section try to fix this + ax_instances = ax.get_children() + hist_instance = [] for current_inst in ax_instances: if isinstance(current_inst, mpl.spines.Spine): break hist_instance.append(current_inst) - - nb_instance_by_hist = len(hist_instance)//nb_group - for i,leg_instance in enumerate(leg.get_patches()[:]): - instance_dict[leg_instance] = hist_instance[int(i*nb_instance_by_hist):int((i+1)*nb_instance_by_hist)] + nb_instance_by_hist = len(hist_instance) // nb_group + + for i, leg_instance in enumerate(leg.get_patches()[:]): + instance_dict[leg_instance] = hist_instance[int( + i * nb_instance_by_hist):int((i + 1) * nb_instance_by_hist)] current_legend_cell = CellLegendMatplotlib() current_legend_cell.matplotlib_line = leg_instance current_legend_cell.matplotlib_text = leg.get_texts()[:][i] - current_legend_cell.matplotlib_legend_box=matplotlib_legend_box - current_legend_cell.row_index=i - current_legend_cell.height=int(matplotlib_legend_box.legend_height/nb_group) + current_legend_cell.matplotlib_legend_box = matplotlib_legend_box + current_legend_cell.row_index = i + current_legend_cell.height = int( + matplotlib_legend_box.legend_height / nb_group) matplotlib_legend_box.box.add_widget(current_legend_cell) else: - ##general purpose interactive position - #get matplotlib text object - if hasattr(leg,'legend_handles'): - #matplotlib>3.7 + # general purpose interactive position + # get matplotlib text object + if hasattr(leg, 'legend_handles'): + # matplotlib>3.7 legeng_marker = leg.legend_handles else: legeng_marker = leg.legendHandles - label_text_list=leg.get_texts()[:] + label_text_list = leg.get_texts()[:] if ncol: - #reorder legend marker and text position to fit with grid layout orientation + # reorder legend marker and text position to fit with grid layout + # orientation legeng_marker = [legeng_marker[int(i)] for i in myorder] label_text_list = [label_text_list[int(i)] for i in myorder] - #create all legend cell in kivy - for i,leg_instance in enumerate(label_text_list): + # create all legend cell in kivy + for i, leg_instance in enumerate(label_text_list): current_legend_cell = CellLegendMatplotlib() if ncol: - current_legend_cell.height=int(matplotlib_legend_box.legend_height/(ceil((nb_group-1)/ncol))) - current_legend_cell.width=int(matplotlib_legend_box.legend_width/ncol) + current_legend_cell.height = int( + matplotlib_legend_box.legend_height / + (ceil((nb_group - 1) / ncol))) + current_legend_cell.width = int( + matplotlib_legend_box.legend_width / ncol) else: - current_legend_cell.height=int(matplotlib_legend_box.legend_height/max(nb_group,1)) - current_legend_cell.width=matplotlib_legend_box.legend_width - - if isinstance(current_handles[i],list): - instance_dict[legeng_marker[i]] = current_handles[i] + current_legend_cell.height = int( + matplotlib_legend_box.legend_height / max(nb_group, 1)) + current_legend_cell.width = matplotlib_legend_box.legend_width + + if isinstance(current_handles[i], list): + instance_dict[legeng_marker[i]] = current_handles[i] else: instance_dict[legeng_marker[i]] = [current_handles[i]] current_legend_cell.matplotlib_line = legeng_marker[i] - + current_legend_cell.matplotlib_text = leg_instance - current_legend_cell.matplotlib_legend_box=matplotlib_legend_box - current_legend_cell.row_index=i - - #add cell in kivy widget - matplotlib_legend_box.box.add_widget(current_legend_cell) + current_legend_cell.matplotlib_legend_box = matplotlib_legend_box + current_legend_cell.row_index = i + + # add cell in kivy widget + matplotlib_legend_box.box.add_widget(current_legend_cell) - #update some attributes - matplotlib_legend_box.figure_wgt=figure_wgt - matplotlib_legend_box.instance_dict=instance_dict + # update some attributes + matplotlib_legend_box.figure_wgt = figure_wgt + matplotlib_legend_box.instance_dict = instance_dict - #add kivy legend widget in figure if needed + # add kivy legend widget in figure if needed if not figure_wgt_as_legend: figure_wgt.parent.add_widget(matplotlib_legend_box) figure_wgt.legend_instance.append(matplotlib_legend_box) - - + + class MatplotlibLegendGrid(FloatLayout): """ Touch egend kivy class""" - figure_wgt= ObjectProperty(None) + figure_wgt = ObjectProperty(None) x_pos = NumericProperty(1) y_pos = NumericProperty(1) legend_height = NumericProperty(1) - legend_width = NumericProperty(1) - title_padding = NumericProperty(0) - legend_instance = ObjectProperty(None,allow_none=True) - prop = StringProperty(None,allow_none=True) - autoscale=BooleanProperty(False) - - instance_dict=dict() - + legend_width = NumericProperty(1) + title_padding = NumericProperty(0) + legend_instance = ObjectProperty(None, allow_none=True) + prop = StringProperty(None, allow_none=True) + autoscale = BooleanProperty(False) + + instance_dict = dict() + def __init__(self, **kwargs): """ init class """ - self.facecolor=[] - self.mysize=None - self.scatter=None - self.mysize_list=[] - self.original_size=None - self.current_handles_text=None - - super().__init__(**kwargs) + self.facecolor = [] + self.mysize = None + self.scatter = None + self.mysize_list = [] + self.original_size = None + self.current_handles_text = None + + super().__init__(**kwargs) def update_size(self): """ update size """ @@ -894,382 +923,408 @@ def update_size(self): leg = self.figure_wgt.figure.axes[0].get_legend() if leg: bbox = leg.get_window_extent() - - legend_x0 = bbox.x0 - legend_y0 = bbox.y0 - legend_x1 = bbox.x1 - legend_y1 = bbox.y1 + + legend_x0 = bbox.x0 + legend_y0 = bbox.y0 + legend_x1 = bbox.x1 + legend_y1 = bbox.y1 pos_x, pos_y = self.figure_wgt.pos - x0_pos=int(legend_x0)+pos_x - y0_pos=int(legend_y0)+pos_y - x1_pos=int(legend_x1)+pos_x - y1_pos=int(legend_y1)+pos_y - + x0_pos = int(legend_x0) + pos_x + y0_pos = int(legend_y0) + pos_y + x1_pos = int(legend_x1) + pos_x + y1_pos = int(legend_y1) + pos_y + self.x_pos = x0_pos self.y_pos = y0_pos - self.legend_height = y1_pos-y0_pos - have_title=False + self.legend_height = y1_pos - y0_pos + have_title = False if leg.get_title().get_text(): - have_title=True + have_title = True if have_title: - if hasattr(leg,'legend_handles'): - #matplotlib>3.7 + if hasattr(leg, 'legend_handles'): + # matplotlib>3.7 current_handles = leg.legend_handles else: current_handles = leg.legendHandles - - nb_group=len(current_handles) - - if hasattr(leg,'_ncols'): - #matplotlib version >3.6 + + nb_group = len(current_handles) + + if hasattr(leg, '_ncols'): + # matplotlib version >3.6 legend_ncol = leg._ncols else: legend_ncol = leg._ncol - - if legend_ncol>1: - ncol=legend_ncol + + if legend_ncol > 1: + ncol = legend_ncol else: - ncol=None + ncol = None if ncol: - title_padding = (y1_pos-y0_pos)/(ceil(nb_group/ncol)+1) + title_padding = (y1_pos - y0_pos) / \ + (ceil(nb_group / ncol) + 1) else: - title_padding = (y1_pos-y0_pos)/(nb_group+1) - + title_padding = (y1_pos - y0_pos) / (nb_group + 1) + self.title_padding = title_padding self.legend_height -= title_padding - self.legend_width = x1_pos-x0_pos + self.legend_width = x1_pos - x0_pos def reset_legend(self): """ reset_legend and clear all children """ self.x_pos = 1 self.y_pos = 1 - self.legend_height = 1 - self.legend_width = 1 - self.title_padding = 0 - self.box.clear_widgets() + self.legend_height = 1 + self.legend_width = 1 + self.title_padding = 0 + self.box.clear_widgets() - def show_hide_wgt(self,row_index) -> None: + def show_hide_wgt(self, row_index) -> None: if self.box.children[::-1][row_index].selected: - #show line + # show line self.box.children[::-1][row_index].selected = False self.box.children[::-1][row_index].matplotlib_line.set_alpha(1) self.box.children[::-1][row_index].matplotlib_text.set_alpha(1) - - hist = self.instance_dict[self.box.children[::-1][row_index].matplotlib_line] + + hist = self.instance_dict[self.box.children[::-1] + [row_index].matplotlib_line] for current_hist in hist: - self.set_visible(current_hist,True,row_index) - if self.autoscale and hasattr(self.figure_wgt,'autoscale_axis'): + self.set_visible(current_hist, True, row_index) + if self.autoscale and hasattr(self.figure_wgt, 'autoscale_axis'): if self.prop: self.prop_autoscale() else: self.figure_wgt.autoscale() else: self.figure_wgt.figure.canvas.draw_idle() - self.figure_wgt.figure.canvas.flush_events() + self.figure_wgt.figure.canvas.flush_events() else: - #hide line + # hide line self.box.children[::-1][row_index].selected = True self.box.children[::-1][row_index].matplotlib_line.set_alpha(0.5) self.box.children[::-1][row_index].matplotlib_text.set_alpha(0.5) - - hist = self.instance_dict[self.box.children[::-1][row_index].matplotlib_line] + + hist = self.instance_dict[self.box.children[::-1] + [row_index].matplotlib_line] for current_hist in hist: - self.set_visible(current_hist,False,row_index) - if self.autoscale and hasattr(self.figure_wgt,'autoscale_axis'): + self.set_visible(current_hist, False, row_index) + if self.autoscale and hasattr(self.figure_wgt, 'autoscale_axis'): if self.prop: self.prop_autoscale() else: - self.figure_wgt.autoscale() + self.figure_wgt.autoscale() else: self.figure_wgt.figure.canvas.draw_idle() self.figure_wgt.figure.canvas.flush_events() - - def doubletap(self,row_index) -> None: + + def doubletap(self, row_index) -> None: """ double tap behavior is based on plotly behavior """ if not self.box.children[::-1][row_index].selected: hist = self.instance_dict - current_line = hist[self.box.children[::-1][row_index].matplotlib_line][0] - hist_keys= list(self.instance_dict.keys()) - - #check if we isolate line or show all lines + current_line = hist[self.box.children[::-1] + [row_index].matplotlib_line][0] + hist_keys = list(self.instance_dict.keys()) + + # check if we isolate line or show all lines need_isolate = False # for line in hist_keys: - for idx,line in enumerate(hist_keys): + for idx, line in enumerate(hist_keys): if hist[line][0] != current_line: if not self.box.children[::-1][idx].selected: - need_isolate=True - break - + need_isolate = True + break + if need_isolate: - #isolate line' - for idx,line in enumerate(hist_keys): + # isolate line' + for idx, line in enumerate(hist_keys): if hist[line][0] != current_line: for current_hist in hist[line]: - self.set_visible(current_hist,False,idx) + self.set_visible(current_hist, False, idx) self.box.children[::-1][idx].selected = True - self.box.children[::-1][idx].matplotlib_line.set_alpha(0.5) - self.box.children[::-1][idx].matplotlib_text.set_alpha(0.5) + self.box.children[::- + 1][idx].matplotlib_line.set_alpha(0.5) + self.box.children[::- + 1][idx].matplotlib_text.set_alpha(0.5) else: self.box.children[::-1][idx].selected = False - self.box.children[::-1][idx].matplotlib_line.set_alpha(1) - self.box.children[::-1][idx].matplotlib_text.set_alpha(1) + self.box.children[::- + 1][idx].matplotlib_line.set_alpha(1) + self.box.children[::- + 1][idx].matplotlib_text.set_alpha(1) else: - #show all lines' - for idx,line in enumerate(hist_keys): + # show all lines' + for idx, line in enumerate(hist_keys): for current_hist in hist[line]: - self.set_visible(current_hist,True,idx) - self.box.children[::-1][idx].selected = False - self.box.children[::-1][idx].matplotlib_line.set_alpha(1) - self.box.children[::-1][idx].matplotlib_text.set_alpha(1) + self.set_visible(current_hist, True, idx) + self.box.children[::-1][idx].selected = False + self.box.children[::-1][idx].matplotlib_line.set_alpha(1) + self.box.children[::-1][idx].matplotlib_text.set_alpha(1) else: hist = self.instance_dict - #show all lines - for idx,line in enumerate(hist): + # show all lines + for idx, line in enumerate(hist): for current_hist in hist[line]: - self.set_visible(current_hist,True,idx) - self.box.children[::-1][idx].selected = False - self.box.children[::-1][idx].matplotlib_line.set_alpha(1) - self.box.children[::-1][idx].matplotlib_text.set_alpha(1) + self.set_visible(current_hist, True, idx) + self.box.children[::-1][idx].selected = False + self.box.children[::-1][idx].matplotlib_line.set_alpha(1) + self.box.children[::-1][idx].matplotlib_text.set_alpha(1) - if self.autoscale and hasattr(self.figure_wgt,'autoscale_axis'): + if self.autoscale and hasattr(self.figure_wgt, 'autoscale_axis'): if self.prop: self.prop_autoscale() - self.figure_wgt.autoscale() - else: - self.figure_wgt.autoscale() - else: + self.figure_wgt.autoscale() + else: + self.figure_wgt.autoscale() + else: self.figure_wgt.figure.canvas.draw_idle() - self.figure_wgt.figure.canvas.flush_events() + self.figure_wgt.figure.canvas.flush_events() def prop_autoscale(self) -> None: """set autoscale hen use prop - + Args: None - + Returns: None - """ - ax=self.scatter.axes - if self.prop=='colors': + """ + ax = self.scatter.axes + if self.prop == 'colors': if not self.facecolor.any(): ax.figure.canvas.draw_idle() ax.figure.canvas.flush_events() - return - - if isinstance(self.mysize_list,list): + return + + if isinstance(self.mysize_list, list): mysize_list = self.mysize_list else: mysize_list = self.mysize_list.tolist() if mysize_list and not all(x == 0.0 for x in mysize_list): - - x,y = self.scatter.get_offsets().T + x, y = self.scatter.get_offsets().T indices_nozero = [i for i, v in enumerate(mysize_list) if v != 0.0] - if len(indices_nozero)==0: + if len(indices_nozero) == 0: ax.set_autoscale_on(False) - + ax.figure.canvas.draw_idle() ax.figure.canvas.flush_events() return - else: + else: xmin = np.min(x[indices_nozero]) xmax = np.max(x[indices_nozero]) ymin = np.min(y[indices_nozero]) ymax = np.max(y[indices_nozero]) - + autoscale_axis = self.figure_wgt.autoscale_axis - no_visible = self.figure_wgt.myrelim(ax,visible_only=self.figure_wgt.autoscale_visible_only) + no_visible = self.figure_wgt.myrelim( + ax, visible_only=self.figure_wgt.autoscale_visible_only) ax.autoscale_view(tight=self.figure_wgt.autoscale_tight, - scalex=True if autoscale_axis!="y" else False, - scaley=True if autoscale_axis!="x" else False) - ax.autoscale(axis=autoscale_axis,tight=self.figure_wgt.autoscale_tight) - + scalex=True if autoscale_axis != "y" else False, + scaley=True if autoscale_axis != "x" else False) + ax.autoscale( + axis=autoscale_axis, + tight=self.figure_wgt.autoscale_tight) + current_xlim = ax.get_xlim() - current_ylim = ax.get_ylim() + current_ylim = ax.get_ylim() invert_xaxis = False invert_yaxis = False if ax.xaxis_inverted(): - invert_xaxis=True + invert_xaxis = True xleft = xmax xright = xmin else: xleft = xmin xright = xmax - + if ax.yaxis_inverted(): - invert_yaxis=True + invert_yaxis = True ybottom = ymax ytop = ymin else: ybottom = ymin - ytop = ymax + ytop = ymax - lim_collection = [xleft,ybottom,xright,ytop] + lim_collection = [xleft, ybottom, xright, ytop] if lim_collection: - xchanged=False + xchanged = False if self.figure_wgt.autoscale_tight: - current_margins = (0,0) + current_margins = (0, 0) else: current_margins = ax.margins() - - if self.figure_wgt.autoscale_axis!="y": + + if self.figure_wgt.autoscale_axis != "y": if invert_xaxis: - if lim_collection[0]>current_xlim[0] or no_visible: + if lim_collection[0] > current_xlim[0] or no_visible: ax.set_xlim(left=lim_collection[0]) - xchanged=True - if lim_collection[2]current_xlim[1] or no_visible: + xchanged = True + if lim_collection[2] > current_xlim[1] or no_visible: ax.set_xlim(right=lim_collection[2]) - xchanged=True - - #recalculed margin + xchanged = True + + # recalculed margin if xchanged: - xlim = ax.get_xlim() - ax.set_xlim(left=xlim[0] - current_margins[0]*(xlim[1]-xlim[0])) - ax.set_xlim(right=xlim[1] + current_margins[0]*(xlim[1]-xlim[0])) - - ychanged=False - - if self.figure_wgt.autoscale_axis!="x": + xlim = ax.get_xlim() + ax.set_xlim( + left=xlim[0] - current_margins[0] * (xlim[1] - xlim[0])) + ax.set_xlim( + right=xlim[1] + current_margins[0] * + (xlim[1] - xlim[0])) + + ychanged = False + + if self.figure_wgt.autoscale_axis != "x": if invert_yaxis: - if lim_collection[1]>current_ylim[0] or no_visible: + if lim_collection[1] > current_ylim[0] or no_visible: ax.set_ylim(bottom=lim_collection[1]) - ychanged=True - if lim_collection[3]current_ylim[1] or no_visible: - ax.set_ylim(top=lim_collection[3]) - ychanged=True - + ychanged = True + if lim_collection[3] > current_ylim[1] or no_visible: + ax.set_ylim(top=lim_collection[3]) + ychanged = True + if ychanged: - ylim = ax.get_ylim() - ax.set_ylim(bottom=ylim[0] - current_margins[1]*(ylim[1]-ylim[0])) - ax.set_ylim(top=ylim[1] + current_margins[1]*(ylim[1]-ylim[0])) - + ylim = ax.get_ylim() + ax.set_ylim( + bottom=ylim[0] - current_margins[1] * + (ylim[1] - ylim[0])) + ax.set_ylim( + top=ylim[1] + current_margins[1] * (ylim[1] - ylim[0])) + index = self.figure_wgt.figure.axes.index(ax) - self.figure_wgt.xmin[index],self.figure_wgt.xmax[index] = ax.get_xlim() - self.figure_wgt.ymin[index],self.figure_wgt.ymax[index] = ax.get_ylim() + self.figure_wgt.xmin[index], self.figure_wgt.xmax[index] = ax.get_xlim( + ) + self.figure_wgt.ymin[index], self.figure_wgt.ymax[index] = ax.get_ylim( + ) ax.set_autoscale_on(False) - + ax.figure.canvas.draw_idle() ax.figure.canvas.flush_events() - - def set_visible(self,instance,value,row_index=None) -> None: + + def set_visible(self, instance, value, row_index=None) -> None: """set visible method - + Args: instance: matplotlib instance value: True or False - + Returns: None - """ + """ if self.prop: - if self.prop=='colors': + if self.prop == 'colors': if self.facecolor.any(): - unique_color = np.unique(self.facecolor,axis=0) + unique_color = np.unique(self.facecolor, axis=0) ncol = np.shape(unique_color)[0] - + if not self.mysize_list: - - self.mysize_list =[self.mysize] * np.shape(self.facecolor)[0] + + self.mysize_list = [self.mysize] * \ + np.shape(self.facecolor)[0] for i in range(np.shape(self.facecolor)[0]): - if (self.facecolor[i,:] == unique_color[row_index,:]).all(): + if (self.facecolor[i, :] == + unique_color[row_index, :]).all(): if value: self.mysize_list[i] = self.mysize else: - self.mysize_list[i] = 0.0 + self.mysize_list[i] = 0.0 # else: # if value: # mysize_list.append(0.0) # else: # mysize_list.append(self.mysize) - + # self.figure_wgt.figure.axes[0].get_children()[0].set_facecolor(newfacecolor) self.scatter.set_sizes(self.mysize_list) - if self.prop=='sizes': + if self.prop == 'sizes': if self.mysize: legend_size = len(self.current_handles_text) legend_value = self.current_handles_text[row_index] - - float_legend_value_before=None - if row_index!=0: - legend_value_before = self.current_handles_text[row_index-1] - match_legend_value_before = re.search(r"\{(.+?)\}", legend_value_before) - if match_legend_value_before: - float_legend_value_before = float(match_legend_value_before.group(1)) + + float_legend_value_before = None + if row_index != 0: + legend_value_before = self.current_handles_text[row_index - 1] + match_legend_value_before = re.search( + r"\{(.+?)\}", legend_value_before) + if match_legend_value_before: + float_legend_value_before = float( + match_legend_value_before.group(1)) else: - return - + return + match_legend_value = re.search(r"\{(.+?)\}", legend_value) - if match_legend_value: + if match_legend_value: float_legend_value = float(match_legend_value.group(1)) else: return - - if isinstance(self.mysize_list,list) and len(self.mysize_list)==0: - + + if isinstance( + self.mysize_list, list) and len( + self.mysize_list) == 0: + self.mysize_list = copy.copy(self.original_size) - - if row_index==0: - index_match=np.where(float_legend_value>=self.original_size)[0] - elif row_index==legend_size-1: - index_match=np.where(float_legend_value_before<=self.original_size)[0] + + if row_index == 0: + index_match = np.where( + float_legend_value >= self.original_size)[0] + elif row_index == legend_size - 1: + index_match = np.where( + float_legend_value_before <= self.original_size)[0] else: - index_match=np.where((float_legend_value>=self.original_size) & - (float_legend_value_before<=self.original_size))[0] + index_match = np.where( + (float_legend_value >= self.original_size) & ( + float_legend_value_before <= self.original_size))[0] if value: self.mysize_list[index_match] = self.original_size[index_match] else: - self.mysize_list[index_match] = 0.0 + self.mysize_list[index_match] = 0.0 self.scatter.set_sizes(self.mysize_list) - + else: - if hasattr(instance,'set_visible'): + if hasattr(instance, 'set_visible'): instance.set_visible(value) - elif hasattr(instance,'get_children'): + elif hasattr(instance, 'get_children'): all_child = instance.get_children() for child in all_child: child.set_visible(value) - def get_visible(self,instance) -> bool: + def get_visible(self, instance) -> bool: """get visible method - + Args: instance: matplotlib instance - + Returns: bool - """ - if hasattr(instance,'get_visible'): + """ + if hasattr(instance, 'get_visible'): return instance.get_visible() - elif hasattr(instance,'get_children'): - return_value=False + elif hasattr(instance, 'get_children'): + return_value = False all_child = instance.get_children() for child in all_child[:1]: return_value = child.get_visible() return return_value else: return False - -from kivy.factory import Factory + Factory.register('LegendRv', LegendRv) @@ -1277,60 +1332,60 @@ def get_visible(self,instance) -> bool: canvas.before: Color: - rgba: root.background_color + rgba: root.background_color Rectangle: pos: self.pos - size: self.size - + size: self.size + RecycleView: id:view viewclass: "CellLegend" - size_hint_y:1 + size_hint_y:1 data: root.data scroll_timeout:5000 effect_cls: "ScrollEffect" - + RecycleBoxLayout: default_size_hint: 1, None default_size: None, None - id:body + id:body orientation: 'vertical' size_hint_y:None height:self.minimum_height - + canvas.before: Color: - rgba: root.background_color + rgba: root.background_color Rectangle: pos: self.pos - size: self.size - + size: self.size + RecycleView: id:view viewclass: "CellLegendHorizontal" - size_hint_y:1 + size_hint_y:1 data: root.data scroll_timeout:5000 effect_cls: "ScrollEffect" - + RecycleBoxLayout: default_size_hint: None, 1 default_size: None, None - id:body + id:body orientation: 'horizontal' size_hint_y:1 size_hint_x:None width: self.minimum_width - + box:box size_hint: None,None - width: dp(0.01) - height: dp(0.01) - - GridLayout: - id:box + width: dp(0.01) + height: dp(0.01) + + GridLayout: + id:box x:root.x_pos y:root.y_pos padding:0,root.title_padding,0,0 @@ -1341,30 +1396,30 @@ def get_visible(self,instance) -> bool: opacity: 0.5 if self.selected else 1 size_hint_y: None - height: dp(48) + height: dp(48) size_hint_x: None - width: dp(48) - - + width: dp(48) + + mycolor: [1,0,0] line_type:'-' opacity: 0.5 if self.selected else 1 size_hint_y: None - height: root.box_height + height: root.box_height size_hint_x:None width:dp(78) + line_label.texture_size[0] - + Widget: - size_hint_x:None - width:dp(18) + size_hint_x:None + width:dp(18) BoxLayout: - size_hint_x:None - width:dp(38) - canvas: - Color: - rgb:root.mycolor - a: + size_hint_x:None + width:dp(38) + canvas: + Color: + rgb:root.mycolor + a: self.opacity if root.line_type=='-' or \ root.line_type!='--' or \ root.line_type!='-.' or \ @@ -1372,31 +1427,31 @@ def get_visible(self,instance) -> bool: Line: width: dp(2.5) cap:'square' - points: + points: self.pos[0], \ self.pos[1]+self.height/2, \ self.pos[0]+self. \ width,self.pos[1]+self.height/2 - Color: - rgb:root.mycolor - a:self.opacity if root.line_type=='--' else 0 + Color: + rgb:root.mycolor + a:self.opacity if root.line_type=='--' else 0 Line: width: dp(2.5) - cap:'square' - points: - self.pos[0], \ - self.pos[1]+self.height/2, \ - self.pos[0]+self.width*0.4, \ - self.pos[1]+self.height/2 - Line: - width: dp(2.5) - cap:'square' + cap:'square' + points: + self.pos[0], \ + self.pos[1]+self.height/2, \ + self.pos[0]+self.width*0.4, \ + self.pos[1]+self.height/2 + Line: + width: dp(2.5) + cap:'square' points: self.pos[0]+self.width*0.6, \ self.pos[1]+self.height/2, \ self.pos[0]+self.width, \ - self.pos[1]+self.height/2 + self.pos[1]+self.height/2 Color: rgb:root.mycolor @@ -1404,53 +1459,53 @@ def get_visible(self,instance) -> bool: Line: width: dp(2.5) cap:'square' - points: + points: self.pos[0], \ self.pos[1]+self.height/2, \ self.pos[0]+self.width*0.3, \ self.pos[1]+self.height/2 Ellipse: size: (dp(5),dp(5)) - pos: + pos: (self.pos[0]+self.width*0.5-dp(5/2), \ self.pos[1]+self.height/2-dp(5/2)) Line: width: dp(2.5) cap:'square' - points: + points: self.pos[0]+self.width*0.7, \ self.pos[1]+self.height/2, \ self.pos[0]+self.width, \ - self.pos[1]+self.height/2 + self.pos[1]+self.height/2 Color: - rgb:root.mycolor + rgb:root.mycolor a:self.opacity if root.line_type==':' else 0 Ellipse: - size: (dp(5),dp(5)) - pos: + size: (dp(5),dp(5)) + pos: self.pos[0]-dp(5/2), \ self.pos[1]+self.height/2-dp(5/2) Ellipse: - size: (dp(5),dp(5)) - pos: - (self.pos[0]+self.width*0.5-dp(5/2), \ - self.pos[1]+self.height/2-dp(5/2)) + size: (dp(5),dp(5)) + pos: + (self.pos[0]+self.width*0.5-dp(5/2), \ + self.pos[1]+self.height/2-dp(5/2)) Ellipse: - size: (dp(5),dp(5)) - pos: - self.pos[0]+self.width-dp(5/2), \ - self.pos[1]+self.height/2-dp(5/2) + size: (dp(5),dp(5)) + pos: + self.pos[0]+self.width-dp(5/2), \ + self.pos[1]+self.height/2-dp(5/2) Widget: - size_hint_x:None - width:dp(18) + size_hint_x:None + width:dp(18) - BoxLayout: - size_hint_x:None - width:line_label.texture_size[0] + BoxLayout: + size_hint_x:None + width:line_label.texture_size[0] Label: id:line_label text:root.text @@ -1459,29 +1514,29 @@ def get_visible(self,instance) -> bool: color:root.text_color font_size: root.text_font_size font_name:root.text_font - + Widget: - size_hint_x:None - width:dp(4) - - + size_hint_x:None + width:dp(4) + + mycolor: [1,0,0] line_type:'-' opacity: 0.5 if self.selected else 1 size_hint_y: None height: root.box_height - + Widget: - size_hint_x:None - width:dp(4) + size_hint_x:None + width:dp(4) BoxLayout: - size_hint_x:None - width:dp(38) - canvas: - Color: - rgb:root.mycolor - a: + size_hint_x:None + width:dp(38) + canvas: + Color: + rgb:root.mycolor + a: self.opacity if root.line_type=='-' or \ root.line_type!='--' or \ root.line_type!='-.' or \ @@ -1489,31 +1544,31 @@ def get_visible(self,instance) -> bool: Line: width: dp(2.5) cap:'square' - points: + points: self.pos[0], \ self.pos[1]+self.height/2, \ self.pos[0]+self. \ width,self.pos[1]+self.height/2 - Color: - rgb:root.mycolor - a:self.opacity if root.line_type=='--' else 0 + Color: + rgb:root.mycolor + a:self.opacity if root.line_type=='--' else 0 + Line: + width: dp(2.5) + cap:'square' + points: + self.pos[0], \ + self.pos[1]+self.height/2, \ + self.pos[0]+self.width*0.4, \ + self.pos[1]+self.height/2 Line: width: dp(2.5) - cap:'square' - points: - self.pos[0], \ - self.pos[1]+self.height/2, \ - self.pos[0]+self.width*0.4, \ - self.pos[1]+self.height/2 - Line: - width: dp(2.5) - cap:'square' + cap:'square' points: self.pos[0]+self.width*0.6, \ self.pos[1]+self.height/2, \ self.pos[0]+self.width, \ - self.pos[1]+self.height/2 + self.pos[1]+self.height/2 Color: rgb:root.mycolor @@ -1521,50 +1576,50 @@ def get_visible(self,instance) -> bool: Line: width: dp(2.5) cap:'square' - points: + points: self.pos[0], \ self.pos[1]+self.height/2, \ self.pos[0]+self.width*0.3, \ self.pos[1]+self.height/2 Ellipse: size: (dp(5),dp(5)) - pos: + pos: (self.pos[0]+self.width*0.5-dp(5/2), \ self.pos[1]+self.height/2-dp(5/2)) Line: width: dp(2.5) cap:'square' - points: + points: self.pos[0]+self.width*0.7, \ self.pos[1]+self.height/2, \ self.pos[0]+self.width, \ - self.pos[1]+self.height/2 + self.pos[1]+self.height/2 Color: - rgb:root.mycolor + rgb:root.mycolor a:self.opacity if root.line_type==':' else 0 Ellipse: - size: (dp(5),dp(5)) - pos: + size: (dp(5),dp(5)) + pos: self.pos[0]-dp(5/2), \ self.pos[1]+self.height/2-dp(5/2) Ellipse: - size: (dp(5),dp(5)) - pos: - (self.pos[0]+self.width*0.5-dp(5/2), \ - self.pos[1]+self.height/2-dp(5/2)) + size: (dp(5),dp(5)) + pos: + (self.pos[0]+self.width*0.5-dp(5/2), \ + self.pos[1]+self.height/2-dp(5/2)) Ellipse: - size: (dp(5),dp(5)) - pos: - self.pos[0]+self.width-dp(5/2), \ - self.pos[1]+self.height/2-dp(5/2) + size: (dp(5),dp(5)) + pos: + self.pos[0]+self.width-dp(5/2), \ + self.pos[1]+self.height/2-dp(5/2) Widget: - size_hint_x:None - width:dp(18) - + size_hint_x:None + width:dp(18) + Label: text:root.text text_size:self.size diff --git a/kivy_matplotlib_widget/uix/minmax_widget.py b/kivy_matplotlib_widget/uix/minmax_widget.py index da05f6a..0f4d588 100644 --- a/kivy_matplotlib_widget/uix/minmax_widget.py +++ b/kivy_matplotlib_widget/uix/minmax_widget.py @@ -8,49 +8,49 @@ BooleanProperty, ColorProperty, DictProperty - ) +) -from kivy.lang import Builder +from kivy.lang import Builder from kivy.metrics import dp from kivy.clock import Clock from kivy.core import text as coretext - + def add_minmax(figure_wgt, - xaxis_formatter = None, - invert_xaxis_formatter = None, - yaxis_formatter = None, - invert_yaxis_formatter = None): + xaxis_formatter=None, + invert_xaxis_formatter=None, + yaxis_formatter=None, + invert_yaxis_formatter=None): - if hasattr(figure_wgt,'text_instance'): - if figure_wgt.text_instance is None: + if hasattr(figure_wgt, 'text_instance'): + if figure_wgt.text_instance is None: text_widget = TextBox() else: text_widget = figure_wgt.text_instance - + text_widget.figure_wgt = figure_wgt text_widget.xaxis_formatter = xaxis_formatter text_widget.invert_xaxis_formatter = invert_xaxis_formatter text_widget.yaxis_formatter = yaxis_formatter - text_widget.invert_yaxis_formatter = invert_yaxis_formatter - - text_widget.x_text_pos=figure_wgt.x - text_widget.y_text_pos=figure_wgt.y - + text_widget.invert_yaxis_formatter = invert_yaxis_formatter + + text_widget.x_text_pos = figure_wgt.x + text_widget.y_text_pos = figure_wgt.y + if figure_wgt.text_instance is None: figure_wgt.parent.add_widget(text_widget) - figure_wgt.text_instance=text_widget - - + figure_wgt.text_instance = text_widget + + class BaseTextFloatLayout(FloatLayout): """ Touch egend kivy class""" figure_wgt = ObjectProperty(None) current_axis = ObjectProperty(None) - kind = DictProperty({'axis':'x','anchor':'right'}) + kind = DictProperty({'axis': 'x', 'anchor': 'right'}) x_text_pos = NumericProperty(10) - y_text_pos = NumericProperty(10) + y_text_pos = NumericProperty(10) show_text = BooleanProperty(False) - + def __init__(self, **kwargs): """ init class """ super().__init__(**kwargs) @@ -60,12 +60,13 @@ def reset_text(self): self.x_text_pos = 1 self.y_text_pos = 1 + class TextBox(BaseTextFloatLayout): - """ text box widget """ - text_color=ColorProperty([0,0,0,1]) - text_font=StringProperty("Roboto") + """ text box widget """ + text_color = ColorProperty([0, 0, 0, 1]) + text_font = StringProperty("Roboto") text_size = NumericProperty(dp(14)) - background_color=ColorProperty([1,1,1,0.9]) + background_color = ColorProperty([1, 1, 1, 0.9]) text_height = NumericProperty(dp(36)) text_width = NumericProperty(dp(40)) offset_text = BooleanProperty(False) @@ -74,12 +75,12 @@ class TextBox(BaseTextFloatLayout): yaxis_formatter = None invert_yaxis_formatter = None current_text = "" - + def __init__(self, **kwargs): """ init class """ - super().__init__(**kwargs) - - def on_axis_validation(self,instance) -> bool: + super().__init__(**kwargs) + + def on_axis_validation(self, instance) -> bool: """Called to validate axis value. Args: @@ -96,98 +97,117 @@ def on_axis_validation(self,instance) -> bool: if self.kind.get('axis') == 'x' and self.invert_xaxis_formatter: number = float(self.invert_xaxis_formatter(instance.text)) elif self.kind.get('axis') == 'y' and self.invert_yaxis_formatter: - number = float(self.invert_yaxis_formatter(instance.text)) + number = float(self.invert_yaxis_formatter(instance.text)) else: - number = float(instance.text) + number = float(instance.text) except ValueError: return False return True - def on_set_axis(self,instance): + def on_set_axis(self, instance): """On set axis (in textinput), validate the input Args: instance (widget) : kivy widget object - """ + """ if not instance.focused and \ self.on_axis_validation(instance) and \ - self.current_text!=instance.text: + self.current_text != instance.text: kind = self.kind if self.kind.get('axis') == 'x' and self.invert_xaxis_formatter: number = float(self.invert_xaxis_formatter(instance.text)) elif self.kind.get('axis') == 'y' and self.invert_yaxis_formatter: - number = float(self.invert_yaxis_formatter(instance.text)) + number = float(self.invert_yaxis_formatter(instance.text)) else: - number = float(instance.text) + number = float(instance.text) if kind.get('axis') == 'x': if kind.get('anchor') == 'left': - self.current_axis.set_xlim((number,None)) + self.current_axis.set_xlim((number, None)) elif kind.get('anchor') == 'right': - self.current_axis.set_xlim((None,number)) + self.current_axis.set_xlim((None, number)) elif kind.get('axis') == 'y': if kind.get('anchor') == 'bottom': - self.current_axis.set_ylim((number,None)) + self.current_axis.set_ylim((number, None)) elif kind.get('anchor') == 'top': - self.current_axis.set_ylim((None,number)) + self.current_axis.set_ylim((None, number)) self.current_axis.figure.canvas.draw_idle() - self.current_axis.figure.canvas.flush_events() + self.current_axis.figure.canvas.flush_events() - self.show_text=False + self.show_text = False - def autofocus_text(self,*args) -> None: + def autofocus_text(self, *args) -> None: """ auto focus text input - + Returns: None """ - Clock.schedule_once(self.scheduled_autofocus_text,0.2) - - def scheduled_autofocus_text(self,*args): - self.ids.text_input.focus = True - Clock.schedule_once(self.select_all_text,0.2) - def select_all_text(self,*args): + Clock.schedule_once(self.scheduled_autofocus_text, 0.2) + + def scheduled_autofocus_text(self, *args): + self.ids.text_input.focus = True + Clock.schedule_once(self.select_all_text, 0.2) + + def select_all_text(self, *args): self.ids.text_input.select_all() - - def on_show_text(self,instance,val): + + def on_show_text(self, instance, val): if val: kind = self.kind if kind.get('axis') == 'x': - xlim=self.current_axis.get_xlim() + xlim = self.current_axis.get_xlim() if self.xaxis_formatter is None: - axis_formatter = (self.current_axis.fmt_xdata if self.current_axis.fmt_xdata is not None - else self.current_axis.xaxis.get_major_formatter().format_data_short) + axis_formatter = ( + self.current_axis.fmt_xdata + if self.current_axis.fmt_xdata is + not None else + self.current_axis.xaxis.get_major_formatter().format_data_short) else: axis_formatter = self.xaxis_formatter - + if kind.get('anchor') == 'left': - #u"\u2212" is to manage unicode minus - self.ids.text_input.text=f"{axis_formatter(xlim[0])}".replace(u"\u2212","-") - + # u"\u2212" is to manage unicode minus + self.ids.text_input.text = f"{ + axis_formatter( + xlim[0])}".replace( + u"\u2212", "-") + elif kind.get('anchor') == 'right': - #u"\u2212" is to manage unicode minus - self.ids.text_input.text=f"{axis_formatter(xlim[1])}".replace(u"\u2212","-") + # u"\u2212" is to manage unicode minus + self.ids.text_input.text = f"{ + axis_formatter( + xlim[1])}".replace( + u"\u2212", "-") self.current_value = self.ids.text_input.text - + elif kind.get('axis') == 'y': - ylim=self.current_axis.get_ylim() + ylim = self.current_axis.get_ylim() if self.yaxis_formatter is None: - axis_formatter = (self.current_axis.fmt_ydata if self.current_axis.fmt_ydata is not None - else self.current_axis.yaxis.get_major_formatter().format_data_short) + axis_formatter = ( + self.current_axis.fmt_ydata + if self.current_axis.fmt_ydata is + not None else + self.current_axis.yaxis.get_major_formatter().format_data_short) else: axis_formatter = self.xaxis_formatter if kind.get('anchor') == 'bottom': - #u"\u2212" is to manage unicode minus - self.ids.text_input.text=f"{axis_formatter(ylim[0])}".replace(u"\u2212","-") + # u"\u2212" is to manage unicode minus + self.ids.text_input.text = f"{ + axis_formatter( + ylim[0])}".replace( + u"\u2212", "-") elif kind.get('anchor') == 'top': - #u"\u2212" is to manage unicode minus - self.ids.text_input.text=f"{axis_formatter(ylim[1])}".replace(u"\u2212","-") + # u"\u2212" is to manage unicode minus + self.ids.text_input.text = f"{ + axis_formatter( + ylim[1])}".replace( + u"\u2212", "-") self.current_value = self.ids.text_input.text self.autofocus_text() @@ -197,55 +217,58 @@ class CustomTextInput(TextInput): """ text_width = NumericProperty(100) - + '''The text width ''' - + text_box_instance = figure_wgt = ObjectProperty(None) - textcenter=BooleanProperty(True) - + textcenter = BooleanProperty(True) + def __init__(self, *args, **kwargs): super(CustomTextInput, self).__init__(*args, **kwargs) self.bind(focus=self.on_focus_text) - + def update_textbox(self, *args): ''' Update the text box from text width ''' if self.text: - + try: string = self.text - text_texture_width = coretext.Label(font_size=self.font_size).get_extents(string)[0] - - except: + text_texture_width = coretext.Label( + font_size=self.font_size).get_extents(string)[0] + + except BaseException: print('get text width failed') else: if self.text_box_instance: - if text_texture_width>dp(20): - self.text_box_instance.text_width=text_texture_width + self.padding[0] + self.padding[2] + if text_texture_width > dp(20): + self.text_box_instance.text_width = text_texture_width + \ + self.padding[0] + self.padding[2] else: - self.text_box_instance.text_width = dp(20) + self.padding[0] + self.padding[2] - def on_focus_text(self, instance,value): + self.text_box_instance.text_width = dp( + 20) + self.padding[0] + self.padding[2] + + def on_focus_text(self, instance, value): """ on focus operation""" if value: - #User focused + # User focused pass else: - #User defocused - self.text_box_instance.show_text=False - - + # User defocused + self.text_box_instance.show_text = False + Builder.load_string(''' size_hint: None,None - width: dp(0.01) + width: dp(0.01) height: dp(0.01) opacity:1 if root.show_text else 0 - + BoxLayout: x: @@ -254,32 +277,32 @@ def on_focus_text(self, instance,value): root.y_text_pos - dp(3) size_hint: None, None height: root.text_height - width: + width: root.text_width if root.show_text \ - else dp(0.0001) - - canvas: + else dp(0.0001) + + canvas: Color: rgba: root.background_color Rectangle: pos: self.pos - size: self.size + size: self.size CustomTextInput: - id:text_input + id:text_input text_box_instance:root font_size:root.text_size color: root.text_color font_name : root.text_font last_good_entry:None - on_text_validate: - root.on_axis_validation(self) - on_focus: + on_text_validate: + root.on_axis_validation(self) + on_focus: root.on_set_axis(self) foreground_color: (0,0,0,1) background_color: (0,0,0,0) font_size:dp(14) - on_text: root.update_textbox() - multiline:False + on_text: root.update_textbox() + multiline:False ''') diff --git a/kivy_matplotlib_widget/uix/navigation_bar_widget.py b/kivy_matplotlib_widget/uix/navigation_bar_widget.py index 5cb7247..5bc6c89 100644 --- a/kivy_matplotlib_widget/uix/navigation_bar_widget.py +++ b/kivy_matplotlib_widget/uix/navigation_bar_widget.py @@ -1,5 +1,5 @@ from matplotlib.backend_bases import NavigationToolbar2 -from kivy.properties import ObjectProperty, OptionProperty,ListProperty,BooleanProperty,NumericProperty,StringProperty +from kivy.properties import ObjectProperty, OptionProperty, ListProperty, BooleanProperty, NumericProperty, StringProperty from kivy.lang import Builder from kivy.uix.boxlayout import BoxLayout from kivy.uix.relativelayout import RelativeLayout @@ -7,7 +7,8 @@ from kivy.factory import Factory from kivy_matplotlib_widget.uix.hover_widget import add_hover from kivy.core.window import Window -from kivy.metrics import dp +from kivy.metrics import dp + class MatplotNavToolbar(BoxLayout): @@ -39,7 +40,7 @@ class _NavigationToolbar(NavigationToolbar2): def __init__(self, canvas, widget): self.widget = widget super(_NavigationToolbar, self).__init__(canvas) - + def on_kv_post(self): if self.figure_widget: self._init_toolbar() @@ -48,7 +49,7 @@ def _init_toolbar(self): print('init toolbar') self.widget.home_btn.bind(on_press=self.home) self.widget.pan_btn.bind(on_press=self.pan) - self.widget.zoom_btn.bind(on_press=self.zoom) + self.widget.zoom_btn.bind(on_press=self.zoom) self.widget.back_btn.bind(on_press=self.back) self.widget.forward_btn.bind(on_press=self.forward) @@ -61,6 +62,7 @@ def draw_rubberband(self, event, x0, y0, x1, y1): def set_message(self, s): self.widget.info_lbl.text = s + class KivyMatplotNavToolbar(RelativeLayout): """Figure Toolbar""" @@ -72,129 +74,216 @@ class KivyMatplotNavToolbar(RelativeLayout): info_lbl = ObjectProperty(None) _navtoolbar = None # Internal NavToolbar logic figure_wgt = ObjectProperty(None) - figure_wgt_layout = ObjectProperty(None) ##3D graph only + figure_wgt_layout = ObjectProperty(None) # 3D graph only orientation_type = OptionProperty( "actionbar", options=["rail", "actionbar"]) nav_icon = OptionProperty( - "normal", options=["minimal", "normal","all","3D","custom"]) + "normal", options=["minimal", "normal", "all", "3D", "custom"]) hover_mode = OptionProperty( - "touch", options=["touch", "desktop"]) - + "touch", options=["touch", "desktop"]) + custom_icon = ListProperty([]) show_cursor_data = BooleanProperty(False) cursor_data_font_size = NumericProperty(dp(12)) - cursor_data_font=StringProperty("Roboto") + cursor_data_font = StringProperty("Roboto") icon_font_size = NumericProperty(dp(36)) nav_btn_size = NumericProperty(dp(80)) compare_hover = BooleanProperty(False) - drag_legend = BooleanProperty(False) #only if nav_icon is 'all' + drag_legend = BooleanProperty(False) # only if nav_icon is 'all' def __init__(self, figure_wgt=None, *args, **kwargs): super(KivyMatplotNavToolbar, self).__init__(*args, **kwargs) self.figure_wgt = figure_wgt - - def on_kv_post(self,_): - if self.nav_icon=="minimal" and not self.custom_icon: - - #pan button - self.add_nav_btn("pan",self.set_touch_mode,mode='pan',btn_type='group') - - #zoombox button - self.add_nav_btn("zoom",self.set_touch_mode,mode='zoombox',btn_type='group') - - elif self.nav_icon=="normal" and not self.custom_icon: - - #home button - self.add_nav_btn("home",self.home) - #pan button - self.add_nav_btn("pan",self.set_touch_mode,mode='pan',btn_type='group') + def on_kv_post(self, _): + if self.nav_icon == "minimal" and not self.custom_icon: + + # pan button + self.add_nav_btn( + "pan", + self.set_touch_mode, + mode='pan', + btn_type='group') + + # zoombox button + self.add_nav_btn( + "zoom", + self.set_touch_mode, + mode='zoombox', + btn_type='group') + + elif self.nav_icon == "normal" and not self.custom_icon: + + # home button + self.add_nav_btn("home", self.home) + + # pan button + self.add_nav_btn( + "pan", + self.set_touch_mode, + mode='pan', + btn_type='group') + + # zoombox button + self.add_nav_btn( + "zoom", + self.set_touch_mode, + mode='zoombox', + btn_type='group') + + if self.hover_mode == "touch" and not self.compare_hover: + # cursor button + self.add_nav_btn( + "cursor", + self.set_touch_mode, + mode='cursor', + btn_type='group') + elif self.hover_mode == "touch": + # nearest hover button + self.add_nav_btn( + "hover", + self.change_hover_type, + mode='nearest', + btn_type='hover_type') + # compare hover button + self.add_nav_btn( + "hover_compare", + self.change_hover_type, + mode='compare', + btn_type='hover_type') - #zoombox button - self.add_nav_btn("zoom",self.set_touch_mode,mode='zoombox',btn_type='group') - - if self.hover_mode=="touch" and not self.compare_hover: - #cursor button - self.add_nav_btn("cursor",self.set_touch_mode,mode='cursor',btn_type='group') - elif self.hover_mode=="touch": - #nearest hover button - self.add_nav_btn("hover",self.change_hover_type,mode='nearest',btn_type='hover_type') - #compare hover button - self.add_nav_btn("hover_compare",self.change_hover_type,mode='compare',btn_type='hover_type') - elif self.compare_hover: - #nearest hover button - self.add_nav_btn("hover",self.change_hover_type,mode='nearest',btn_type='hover_type') - #compare hover button - self.add_nav_btn("hover_compare",self.change_hover_type,mode='compare',btn_type='hover_type') - - elif self.nav_icon=="all" and not self.custom_icon: - - #home button - self.add_nav_btn("home",self.home) - - #home button - self.add_nav_btn("back",self.back) - - #home button - self.add_nav_btn("forward",self.forward) - - #pan button - self.add_nav_btn("pan",self.set_touch_mode,mode='pan',btn_type='group') - - #zoombox button - self.add_nav_btn("zoom",self.set_touch_mode,mode='zoombox',btn_type='group') - - if self.hover_mode=="touch" and not self.compare_hover: - #cursor button - self.add_nav_btn("cursor",self.set_touch_mode,mode='cursor',btn_type='group') - - elif self.hover_mode=="touch": - #nearest hover button - self.add_nav_btn("hover",self.change_hover_type,mode='nearest',btn_type='group') - #compare hover button - self.add_nav_btn("hover_compare",self.change_hover_type,mode='compare',btn_type='group') - + # nearest hover button + self.add_nav_btn( + "hover", + self.change_hover_type, + mode='nearest', + btn_type='hover_type') + # compare hover button + self.add_nav_btn( + "hover_compare", + self.change_hover_type, + mode='compare', + btn_type='hover_type') + + elif self.nav_icon == "all" and not self.custom_icon: + + # home button + self.add_nav_btn("home", self.home) + + # home button + self.add_nav_btn("back", self.back) + + # home button + self.add_nav_btn("forward", self.forward) + + # pan button + self.add_nav_btn( + "pan", + self.set_touch_mode, + mode='pan', + btn_type='group') + + # zoombox button + self.add_nav_btn( + "zoom", + self.set_touch_mode, + mode='zoombox', + btn_type='group') + + if self.hover_mode == "touch" and not self.compare_hover: + # cursor button + self.add_nav_btn( + "cursor", + self.set_touch_mode, + mode='cursor', + btn_type='group') + + elif self.hover_mode == "touch": + # nearest hover button + self.add_nav_btn( + "hover", + self.change_hover_type, + mode='nearest', + btn_type='group') + # compare hover button + self.add_nav_btn( + "hover_compare", + self.change_hover_type, + mode='compare', + btn_type='group') + elif self.compare_hover: - #nearest hover button - self.add_nav_btn("hover",self.change_hover_type,mode='nearest',btn_type='group') - #compare hover button - self.add_nav_btn("hover_compare",self.change_hover_type,mode='compare',btn_type='group') - - #minmax button - self.add_nav_btn("minmax",self.set_touch_mode,mode='minmax',btn_type='group') - - #home button - self.add_nav_btn("autoscale",self.autoscale) - - #add_drag_legend + # nearest hover button + self.add_nav_btn( + "hover", + self.change_hover_type, + mode='nearest', + btn_type='group') + # compare hover button + self.add_nav_btn( + "hover_compare", + self.change_hover_type, + mode='compare', + btn_type='group') + + # minmax button + self.add_nav_btn( + "minmax", + self.set_touch_mode, + mode='minmax', + btn_type='group') + + # home button + self.add_nav_btn("autoscale", self.autoscale) + + # add_drag_legend if self.drag_legend: - self.add_nav_btn("drag_legend",self.set_touch_mode,mode='drag_legend',btn_type='group') + self.add_nav_btn( + "drag_legend", + self.set_touch_mode, + mode='drag_legend', + btn_type='group') elif self.custom_icon: pass - - if self.nav_icon=="3D" and not self.custom_icon: - #home button - self.add_nav_btn("home",self.home_3D) - - #data pan/zoom button - self.add_nav_btn("axis-z-rotate-clockwise",self.set_touch_mode_3D,mode='rotate',btn_type='group') - - #data pan/zoom button - self.add_nav_btn("pan",self.set_touch_mode_3D,mode='pan',btn_type='group') - - #figure pan/zoom button - self.add_nav_btn("magnify",self.set_touch_mode_3D,mode='figure_zoom_pan',btn_type='group') - - #cursor button - self.add_nav_btn("cursor",self.set_touch_mode_3D,mode='cursor',btn_type='group') - - + + if self.nav_icon == "3D" and not self.custom_icon: + # home button + self.add_nav_btn("home", self.home_3D) + + # data pan/zoom button + self.add_nav_btn( + "axis-z-rotate-clockwise", + self.set_touch_mode_3D, + mode='rotate', + btn_type='group') + + # data pan/zoom button + self.add_nav_btn( + "pan", + self.set_touch_mode_3D, + mode='pan', + btn_type='group') + + # figure pan/zoom button + self.add_nav_btn( + "magnify", + self.set_touch_mode_3D, + mode='figure_zoom_pan', + btn_type='group') + + # cursor button + self.add_nav_btn( + "cursor", + self.set_touch_mode_3D, + mode='cursor', + btn_type='group') + if self.show_cursor_data: - Window.bind(mouse_pos=self.on_motion) + Window.bind(mouse_pos=self.on_motion) - def on_motion(self,*args): + def on_motion(self, *args): '''Kivy Event to trigger mouse event on motion `enter_notify_event`. ''' @@ -204,84 +293,90 @@ def on_motion(self,*args): newcoord = self.figure_wgt.to_widget(pos[0], pos[1]) x = newcoord[0] y = newcoord[1] - inside = self.figure_wgt.collide_point(x,y) - if inside: + inside = self.figure_wgt.collide_point(x, y) + if inside: # will receive all motion events. if self.figure_wgt.figcanvas: - #avoid in motion if touch is detected - if not len(self.figure_wgt._touches)==0: + # avoid in motion if touch is detected + if not len(self.figure_wgt._touches) == 0: return - - #transform kivy x,y touch event to x,y data - x_format,y_format=self.figure_wgt.get_data_xy(x,y) + + # transform kivy x,y touch event to x,y data + x_format, y_format = self.figure_wgt.get_data_xy(x, y) if x_format and y_format: - self.current_label.text=f"{x_format}\n{y_format}" + self.current_label.text = f"{x_format}\n{y_format}" else: - self.current_label.text="" - - def add_nav_btn(self,icon,fct,mode=None,btn_type=None): - if btn_type=='group': + self.current_label.text = "" + + def add_nav_btn(self, icon, fct, mode=None, btn_type=None): + if btn_type == 'group': if 'hover' in icon: - btn = Factory.NavToggleButton(group= "hover_type") - if self.hover_mode=="touch": - btn.bind(on_release=lambda x:self.set_touch_mode('cursor')) - + btn = Factory.NavToggleButton(group="hover_type") + if self.hover_mode == "touch": + btn.bind( + on_release=lambda x: self.set_touch_mode('cursor')) + elif mode == 'nearest': - btn.state='down' - - + btn.state = 'down' + else: - btn = Factory.NavToggleButton(group= "toolbar_btn") - btn.orientation_type=self.orientation_type + btn = Factory.NavToggleButton(group="toolbar_btn") + btn.orientation_type = self.orientation_type else: - btn = Factory.NavButton() - btn.orientation_type=self.orientation_type + btn = Factory.NavButton() + btn.orientation_type = self.orientation_type btn.icon = icon - btn.icon_font_size=self.icon_font_size - btn.nav_btn_size=self.nav_btn_size + btn.icon_font_size = self.icon_font_size + btn.nav_btn_size = self.nav_btn_size if mode: - btn.bind(on_release=lambda x:fct(mode)) + btn.bind(on_release=lambda x: fct(mode)) else: - btn.bind(on_release=lambda x:fct()) - - if self.nav_icon!="3D" and mode == 'pan': - #by default pan button is press down - btn.state='down' - - if self.nav_icon=="3D" and mode =='rotate': - btn.state='down' - - self.ids.container.add_widget(btn) - - def set_touch_mode(self,mode): - self.figure_wgt.touch_mode=mode + btn.bind(on_release=lambda x: fct()) + + if self.nav_icon != "3D" and mode == 'pan': + # by default pan button is press down + btn.state = 'down' + + if self.nav_icon == "3D" and mode == 'rotate': + btn.state = 'down' + + self.ids.container.add_widget(btn) + + def set_touch_mode(self, mode): + self.figure_wgt.touch_mode = mode + def home(self): - if hasattr(self.figure_wgt,'main_home'): + if hasattr(self.figure_wgt, 'main_home'): self.figure_wgt.main_home() else: self.figure_wgt.home() + def back(self): - self.figure_wgt.back() + self.figure_wgt.back() + def forward(self): - self.figure_wgt.forward() + self.figure_wgt.forward() + def autoscale(self): - self.figure_wgt.autoscale() - - def change_hover_type(self,hover_type): - add_hover(self.figure_wgt,mode=self.hover_mode,hover_type=hover_type) + self.figure_wgt.autoscale() + + def change_hover_type(self, hover_type): + add_hover(self.figure_wgt, mode=self.hover_mode, hover_type=hover_type) + + def set_touch_mode_3D(self, mode): + self.figure_wgt_layout.figure_wgt.touch_mode = mode - def set_touch_mode_3D(self,mode): - self.figure_wgt_layout.figure_wgt.touch_mode=mode def home_3D(self): self.figure_wgt_layout.figure_wgt.home() + Factory.register('MatplotNavToolbar', MatplotNavToolbar) Builder.load_string(''' -#:import nav_icons kivy_matplotlib_widget.icon_definitions.nav_icons - +#:import nav_icons kivy_matplotlib_widget.icon_definitions.nav_icons + : orientation: 'vertical' home_btn: home_btn @@ -292,7 +387,7 @@ def home_3D(self): forward_btn: forward_btn Label: id: info_lbl - size_hint: 1, 0.3 + size_hint: 1, 0.3 BoxLayout: size_hint: 1, 0.7 Button: @@ -304,7 +399,7 @@ def home_3D(self): font_name:"NavigationIcons" Button: id: forward_btn - text: "Forward" + text: "Forward" font_name:"NavigationIcons" ToggleButton: id: pan_btn @@ -316,7 +411,7 @@ def home_3D(self): text: "Zoom" group: "toolbar_btn" font_name:"NavigationIcons" - + : current_label: info_lbl2 if root.orientation_type=='rail' else info_lbl info_lbl: info_lbl @@ -333,12 +428,12 @@ def home_3D(self): rgba: (1, 1, 1, 1) Rectangle: pos: self.pos - size: self.size + size: self.size BoxLayout: size_hint_y: None size_hint_x: None if root.orientation_type=='rail' else 1 - height: "0.01dp" if root.orientation_type=='rail'else root.nav_btn_size - width:root.nav_btn_size + height: "0.01dp" if root.orientation_type=='rail'else root.nav_btn_size + width:root.nav_btn_size Label: id: info_lbl color: 0,0,0,1 @@ -352,7 +447,7 @@ def home_3D(self): height: root.nav_btn_size*len(self.children) if root.orientation_type=='rail'else root.nav_btn_size width: root.nav_btn_size if root.orientation_type=='rail'else root.nav_btn_size*len(self.children) - BoxLayout: + BoxLayout: size_hint_y: 1 if root.orientation_type=='rail' else None size_hint_x: None height:root.nav_btn_size @@ -362,7 +457,7 @@ def home_3D(self): color: 0,0,0,1 font_size: root.cursor_data_font_size font_name:root.cursor_data_font - + icon:"pan" @@ -376,12 +471,12 @@ def home_3D(self): color: 0,0,0,1 if self.state=='down' else 0.38 size_hint:1 if root.orientation_type=='rail' else None,None if root.orientation_type=='rail' else 1 height: root.nav_btn_size - width:root.nav_btn_size + width:root.nav_btn_size background_normal: '' background_down: '' - background_color :1,1,1,1 + background_color :1,1,1,1 on_release: - self.state='down' + self.state='down' icon:"home" @@ -394,9 +489,9 @@ def home_3D(self): font_size:root.icon_font_size color: 0,0,0,1 height: root.nav_btn_size - width:root.nav_btn_size + width:root.nav_btn_size background_normal: '' background_color :1,1,1,1 - ''') \ No newline at end of file + ''') diff --git a/kivy_matplotlib_widget/uix/selector_widget.py b/kivy_matplotlib_widget/uix/selector_widget.py index 46f1f2e..6ca98f2 100644 --- a/kivy_matplotlib_widget/uix/selector_widget.py +++ b/kivy_matplotlib_widget/uix/selector_widget.py @@ -29,14 +29,14 @@ kv = ''' -: +: canvas.before: # TOP LINE Color: rgba: root.top_color Line: width: dp(1) - points: + points: (self.x + dp(7), self.top, self.right - dp(7), self.top) if root.alpha \ else (self.x, self.top, self.right, self.top)# Top line cap: 'round' @@ -50,7 +50,7 @@ rgba: root.bottom_color Line: width: dp(1) - points: + points: (self.x + dp(7), self.y, self.right - dp(7), self.y) if root.alpha \ else (self.x, self.y, self.right, self.y)# Bottom cap: 'round' @@ -64,8 +64,8 @@ rgba: root.left_color Line: width: dp(1) - points: - (self.x, self.y+dp(7), self.x, self.top - dp(7)) if root.alpha \ + points: + (self.x, self.y+dp(7), self.x, self.top - dp(7)) if root.alpha \ else (self.x, self.y, self.x, self.top)# Left cap: 'round' joint: 'round' @@ -78,7 +78,7 @@ rgba: root.right_color Line: width: dp(1) - points: + points: (self.right, self.y + dp(7), self.right, self.top - dp(7)) if root.alpha \ else (self.right, self.y, self.right, self.top)# Right cap: 'round' @@ -97,7 +97,7 @@ rgba: [0,0,0,root.alpha] if (root.bg_color[0]*0.299 + \ root.bg_color[1]*0.587 + root.bg_color[2]*0.114) > 186/255 \ - else [1,1,1,root.alpha] + else [1,1,1,root.alpha] Line: width: dp(1) points: ( \ @@ -119,7 +119,7 @@ rgba: [0,0,0,root.alpha] if (root.bg_color[0]*0.299 + \ root.bg_color[1]*0.587 + root.bg_color[2]*0.114) > 186/255 \ - else [1,1,1,root.alpha] + else [1,1,1,root.alpha] Line: width: dp(1) points: ( \ @@ -141,7 +141,7 @@ rgba: [0,0,0,root.alpha] if (root.bg_color[0]*0.299 + \ root.bg_color[1]*0.587 + root.bg_color[2]*0.114) > 186/255 \ - else [1,1,1,root.alpha] + else [1,1,1,root.alpha] Line: width: dp(1) points: ( \ @@ -152,7 +152,7 @@ cap: 'round' joint: 'round' close: True - + # Upper right rectangle Color: rgba: 62/255, 254/255, 1, root.alpha @@ -163,7 +163,7 @@ rgba: [0,0,0,root.alpha] if (root.bg_color[0]*0.299 + \ root.bg_color[1]*0.587 + root.bg_color[2]*0.114) > 186/255 \ - else [1,1,1,root.alpha] + else [1,1,1,root.alpha] Line: width: dp(1) points: ( \ @@ -185,7 +185,7 @@ rgba: [0,0,0,root.alpha] if (root.bg_color[0]*0.299 + \ root.bg_color[1]*0.587 + root.bg_color[2]*0.114) > 186/255 \ - else [1,1,1,root.alpha] + else [1,1,1,root.alpha] Line: width: dp(1) points: ( \ @@ -207,7 +207,7 @@ rgba: [0,0,0,root.alpha] if (root.bg_color[0]*0.299 + \ root.bg_color[1]*0.587 + root.bg_color[2]*0.114) > 186/255 \ - else [1,1,1,root.alpha] + else [1,1,1,root.alpha] Line: width: dp(1) points: ( \ @@ -230,7 +230,7 @@ rgba: [0,0,0,root.alpha] if (root.bg_color[0]*0.299 + \ root.bg_color[1]*0.587 + root.bg_color[2]*0.114) > 186/255 \ - else [1,1,1,root.alpha] + else [1,1,1,root.alpha] Line: width: dp(1) points: ( \ @@ -253,7 +253,7 @@ rgba: [0,0,0,root.alpha] if (root.bg_color[0]*0.299 + \ root.bg_color[1]*0.587 + root.bg_color[2]*0.114) > 186/255 \ - else [1,1,1,root.alpha] + else [1,1,1,root.alpha] Line: width: dp(1) points: ( \ @@ -265,16 +265,16 @@ cap: 'round' joint: 'round' close: True - - -: + + +: canvas.before: # TOP LINE Color: rgba: root.top_color Line: width: dp(1) - points: + points: (self.x, self.top, self.right, self.top) if root.alpha \ else (self.x, self.top, self.right, self.top)# Top line @@ -283,7 +283,7 @@ rgba: root.bottom_color Line: width: dp(1) - points: + points: (self.x, self.y, self.right, self.y) if root.alpha \ else (self.x, self.y, self.right, self.y)# Bottom @@ -292,8 +292,8 @@ rgba: root.left_color Line: width: dp(1) - points: - (self.x, self.y, self.x, self.top) if root.alpha \ + points: + (self.x, self.y, self.x, self.top) if root.alpha \ else (self.x, self.y, self.x, self.top)# Left @@ -302,24 +302,24 @@ rgba: root.right_color Line: width: dp(1) - points: + points: (self.right, self.y, self.right, self.top) if root.alpha \ else (self.right, self.y, self.right, self.top)# Right - + Color: rgba: root.span_color[0], root.span_color[1], root.span_color[2], self.span_alpha Rectangle: pos: self.pos size: self.size - - - + + + resize_wgt:resize_wgt size_hint: None,None height: dp(0.001) - width:dp(0.001) + width:dp(0.001) ResizeSelect: id:resize_wgt desktop_mode:root.desktop_mode @@ -328,12 +328,12 @@ size: dp(0.001),dp(0.001) pos: 0, 0 opacity:0 - - + + resize_wgt:resize_wgt size_hint: None,None height: dp(0.001) - width:dp(0.001) + width:dp(0.001) PainterWidget: id:resize_wgt pointsize:dp(0.01) @@ -346,12 +346,12 @@ opacity:1 line_color : 0, 0, 0, 1 selection_point_color : 62/255, 254/255, 1,1 - - + + resize_wgt:resize_wgt size_hint: None,None height: dp(0.001) - width:dp(0.001) + width:dp(0.001) PainterWidget2: id:resize_wgt pointsize:dp(0.01) @@ -364,12 +364,12 @@ opacity:1 line_color : 0, 0, 0, 1 selection_point_color : 62/255, 254/255, 1,1 - - + + resize_wgt:resize_wgt size_hint: None,None height: dp(0.001) - width:dp(0.001) + width:dp(0.001) SpanSelect: id:resize_wgt desktop_mode:root.desktop_mode @@ -384,42 +384,51 @@ MINIMUM_HEIGHT = 20.0 MINIMUM_WIDTH = 20.0 + class ResizeRelativeLayout(RelativeLayout): figure_wgt = ObjectProperty() resize_wgt = ObjectProperty() desktop_mode = BooleanProperty(True) - def __init__(self,figure_wgt=None,desktop_mode=True, **kwargs): - self.figure_wgt=figure_wgt - self.desktop_mode=desktop_mode + + def __init__(self, figure_wgt=None, desktop_mode=True, **kwargs): + self.figure_wgt = figure_wgt + self.desktop_mode = desktop_mode super().__init__(**kwargs) - + + class LassoRelativeLayout(RelativeLayout): figure_wgt = ObjectProperty() resize_wgt = ObjectProperty() desktop_mode = BooleanProperty(True) - def __init__(self,figure_wgt=None,desktop_mode=True, **kwargs): - self.figure_wgt=figure_wgt - self.desktop_mode=desktop_mode + + def __init__(self, figure_wgt=None, desktop_mode=True, **kwargs): + self.figure_wgt = figure_wgt + self.desktop_mode = desktop_mode super().__init__(**kwargs) - + + class EllipseRelativeLayout(RelativeLayout): figure_wgt = ObjectProperty() resize_wgt = ObjectProperty() desktop_mode = BooleanProperty(True) - def __init__(self,figure_wgt=None,desktop_mode=True, **kwargs): - self.figure_wgt=figure_wgt - self.desktop_mode=desktop_mode + + def __init__(self, figure_wgt=None, desktop_mode=True, **kwargs): + self.figure_wgt = figure_wgt + self.desktop_mode = desktop_mode super().__init__(**kwargs) - + + class SpanRelativeLayout(RelativeLayout): figure_wgt = ObjectProperty() resize_wgt = ObjectProperty() desktop_mode = BooleanProperty(True) - def __init__(self,figure_wgt=None,desktop_mode=True, **kwargs): - self.figure_wgt=figure_wgt - self.desktop_mode=desktop_mode + + def __init__(self, figure_wgt=None, desktop_mode=True, **kwargs): + self.figure_wgt = figure_wgt + self.desktop_mode = desktop_mode super().__init__(**kwargs) - + + def line_dists(points, start, end): if np.all(start == end): return np.linalg.norm(points - start, axis=1) @@ -447,25 +456,26 @@ def rdp(M, epsilon=0): return result + class ResizeSelect(FloatLayout): top_color = ColorProperty("black") bottom_color = ColorProperty("black") left_color = ColorProperty("black") right_color = ColorProperty("black") highlight_color = ColorProperty("red") - bg_color = ColorProperty([1,1,1,1]) + bg_color = ColorProperty([1, 1, 1, 1]) figure_wgt = ObjectProperty() desktop_mode = BooleanProperty(True) alpha = NumericProperty(1) - dynamic_callback = BooleanProperty(False) + dynamic_callback = BooleanProperty(False) - def __init__(self,**kwargs): + def __init__(self, **kwargs): super().__init__(**kwargs) self.selected_side = None self.verts = [] self.ax = None self.callback = None - + self.alpha_other = 0.3 self.ind = [] @@ -473,32 +483,35 @@ def __init__(self,**kwargs): self.xys = None self.Npts = None self.fc = None - - self.line = None - self.ind_line=[] - - self.first_touch=None - - def on_kv_post(self,_): - if platform != 'android' and self.desktop_mode: #only bind mouse position if not android or if the user set desktop mode to false + + self.line = None + self.ind_line = [] + + self.first_touch = None + + def on_kv_post(self, _): + # only bind mouse position if not android or if the user set desktop + # mode to false + if platform != 'android' and self.desktop_mode: Window.bind(mouse_pos=self.on_mouse_pos) - + def update_bg_color(self): fig_bg_color = self.figure_wgt.figure.get_facecolor() rgb_fig_bg_color = mcolors.to_rgb(fig_bg_color) - if (rgb_fig_bg_color[0]*0.299 + rgb_fig_bg_color[1]*0.587 + rgb_fig_bg_color[2]*0.114) > 186/255: - self.bg_color = [1,1,1,1] + if (rgb_fig_bg_color[0] * 0.299 + rgb_fig_bg_color[1] + * 0.587 + rgb_fig_bg_color[2] * 0.114) > 186 / 255: + self.bg_color = [1, 1, 1, 1] self.bottom_color = ( self.top_colors - ) = self.left_color = self.right_color = [1,1,1,1] - + ) = self.left_color = self.right_color = [1, 1, 1, 1] + else: - self.bg_color = [0,0,0,1] + self.bg_color = [0, 0, 0, 1] self.bottom_color = ( self.top_colors - ) = self.left_color = self.right_color = [0,0,0,1] - - def set_collection(self,collection): + ) = self.left_color = self.right_color = [0, 0, 0, 1] + + def set_collection(self, collection): self.collection = collection self.xys = collection.get_offsets() self.Npts = len(self.xys) @@ -509,18 +522,20 @@ def set_collection(self,collection): raise ValueError('Collection must have a facecolor') elif len(self.fc) == 1: self.fc = np.tile(self.fc, (self.Npts, 1)) - - def set_line(self,line): - self.line = line + + def set_line(self, line): + self.line = line def on_mouse_pos(self, something, touch): """ When the mouse moves, we check the position of the mouse and update the cursor accordingly. """ - if self.opacity and self.figure_wgt.touch_mode=='selector' and self.collide_point(*self.to_widget(*touch)): - - collision = self.collides_with_control_points(something, self.to_widget(*touch)) + if self.opacity and self.figure_wgt.touch_mode == 'selector' and self.collide_point( + *self.to_widget(*touch)): + + collision = self.collides_with_control_points( + something, self.to_widget(*touch)) if collision in ["top left", "bottom right"]: Window.set_system_cursor("size_nwse") elif collision in ["top right", "bottom left"]: @@ -531,7 +546,7 @@ def on_mouse_pos(self, something, touch): Window.set_system_cursor("size_we") else: Window.set_system_cursor("size_all") - + elif self.figure_wgt.collide_point(*touch): Window.set_system_cursor("arrow") @@ -571,7 +586,7 @@ def collides_with_control_points(self, _, touch): def on_touch_down(self, touch): if self.figure_wgt.touch_mode != 'selector': return - + if self.collide_point(*touch.pos) and self.opacity: touch.grab(self) x, y = touch.pos[0], touch.pos[1] @@ -614,37 +629,36 @@ def on_touch_down(self, touch): self.left_color = self.highlight_color self.right_color = self.highlight_color elif self.figure_wgt.collide_point(*self.to_window(*touch.pos)): - if self.figure_wgt.touch_mode=='selector': + if self.figure_wgt.touch_mode == 'selector': if touch.is_double_tap and self.callback_clear: self.callback_clear() return - + touch.grab(self) x, y = touch.pos[0], touch.pos[1] - self.pos = (x,y-5) - self.size = (5,5) - self.opacity=1 - self.first_touch = (x,y-5) + self.pos = (x, y - 5) + self.size = (5, 5) + self.opacity = 1 + self.first_touch = (x, y - 5) self.selected_side = "new select" - - + return super().on_touch_down(touch) def on_touch_move(self, touch): if self.figure_wgt.touch_mode != 'selector': return - + if touch.grab_current is self: x, y = self.to_window(*self.pos) top = y + self.height # top of our widget right = x + self.width # right of our widget - + if self.selected_side == "top": if self.height + touch.dy <= MINIMUM_HEIGHT: return False self.height += touch.dy - + if self.dynamic_callback and self.verts is not None: self.verts = self._get_box_data() self.onselect(self.verts) @@ -658,7 +672,7 @@ def on_touch_move(self, touch): if self.dynamic_callback and self.verts is not None: self.verts = self._get_box_data() self.onselect(self.verts) - + elif self.selected_side == "left": if self.width - touch.dx <= MINIMUM_WIDTH: return False @@ -668,7 +682,7 @@ def on_touch_move(self, touch): if self.dynamic_callback and self.verts is not None: self.verts = self._get_box_data() self.onselect(self.verts) - + elif self.selected_side == "right": if self.width + touch.dx <= MINIMUM_WIDTH: return False @@ -677,7 +691,7 @@ def on_touch_move(self, touch): if self.dynamic_callback and self.verts is not None: self.verts = self._get_box_data() self.onselect(self.verts) - + elif self.selected_side == "top left": if touch.dx > 0: if self.width - touch.dx <= MINIMUM_WIDTH: @@ -692,7 +706,7 @@ def on_touch_move(self, touch): if self.dynamic_callback and self.verts is not None: self.verts = self._get_box_data() - self.onselect(self.verts) + self.onselect(self.verts) elif self.selected_side == "top right": if touch.dx < 0: @@ -704,10 +718,10 @@ def on_touch_move(self, touch): self.width += touch.dx self.height += touch.dy - + if self.dynamic_callback and self.verts is not None: self.verts = self._get_box_data() - self.onselect(self.verts) + self.onselect(self.verts) elif self.selected_side == "bottom left": if touch.dx > 0: @@ -724,7 +738,7 @@ def on_touch_move(self, touch): if self.dynamic_callback and self.verts is not None: self.verts = self._get_box_data() - self.onselect(self.verts) + self.onselect(self.verts) elif self.selected_side == "bottom right": if touch.dx < 0: @@ -737,26 +751,39 @@ def on_touch_move(self, touch): self.width += touch.dx self.height -= touch.dy self.y += touch.dy - + if self.dynamic_callback and self.verts is not None: self.verts = self._get_box_data() - self.onselect(self.verts) - + self.onselect(self.verts) + elif self.selected_side == "new select": self.width += touch.dx self.height -= touch.dy - self.y += touch.dy + self.y += touch.dy elif not self.selected_side: - if self.figure_wgt.collide_point(*self.to_window(self.pos[0]+touch.dx,self.pos[1]+touch.dy )) and \ - self.figure_wgt.collide_point(*self.to_window(self.pos[0] + self.size[0]+touch.dx,self.pos[1]+ self.size[1]+touch.dy )): + if self.figure_wgt.collide_point( + * + self.to_window( + self.pos[0] + + touch.dx, + self.pos[1] + + touch.dy)) and self.figure_wgt.collide_point( + * + self.to_window( + self.pos[0] + + self.size[0] + + touch.dx, + self.pos[1] + + self.size[1] + + touch.dy)): self.x += touch.dx self.y += touch.dy if self.dynamic_callback and self.verts is not None: self.verts = self._get_box_data() - self.onselect(self.verts) - + self.onselect(self.verts) + if self.selected_side == "new select": self.alpha = 0 else: @@ -767,24 +794,26 @@ def on_touch_move(self, touch): def on_touch_up(self, touch): if self.figure_wgt.touch_mode != 'selector': return - + if touch.grab_current is self: touch.ungrab(self) self.alpha = 1 - if (self.bg_color[0]*0.299 + \ - self.bg_color[1]*0.587 + self.bg_color[2]*0.114) > 186/255: + if (self.bg_color[0] * 0.299 + self.bg_color[1] + * 0.587 + self.bg_color[2] * 0.114) > 186 / 255: self.bottom_color = ( self.top_colors - ) = self.left_color = self.right_color = [0,0,0,1] + ) = self.left_color = self.right_color = [0, 0, 0, 1] else: self.bottom_color = ( self.top_colors - ) = self.left_color = self.right_color = [1,1,1,1] - + ) = self.left_color = self.right_color = [1, 1, 1, 1] + if self.first_touch and self.selected_side == "new select": self.check_if_reverse_selection(touch) - - if abs(self.size[0]) self.first_touch[0] and \ - last_touch.y < self.first_touch[1]: + last_touch.y < self.first_touch[1]: + + return - return - else: - #reverse selection' + # reverse selection' if last_touch.x < self.first_touch[0]: self.pos[0] = last_touch.x self.size[0] = self.first_touch[0] - last_touch.x + 5 - + if last_touch.y > self.first_touch[1]: - self.size[1] = last_touch.y - self.first_touch[1] + self.size[1] = last_touch.y - self.first_touch[1] self.pos[1] = last_touch.y - self.size[1] - - return + return def reset_selection(self): - self.pos = (0,0) - self.size = (dp(0.01),dp(0.01)) - self.opacity=0 + self.pos = (0, 0) + self.size = (dp(0.01), dp(0.01)) + self.opacity = 0 - def _get_box_data(self): - trans = self.ax.transData.inverted() - #get box 4points xis data - x0 = self.to_window(*self.pos)[0]-self.figure_wgt.pos[0] - y0 = self.to_window(*self.pos)[1]-self.figure_wgt.pos[1] - x1 = self.to_window(*self.pos)[0]-self.figure_wgt.pos[0] - y1 = self.to_window(*self.pos)[1] + self.height-self.figure_wgt.pos[1] - x3 = self.to_window(*self.pos)[0] + self.width-self.figure_wgt.pos[0] - y3 = self.to_window(*self.pos)[1]-self.figure_wgt.pos[1] - x2 = self.to_window(*self.pos)[0] + self.width -self.figure_wgt.pos[0] - y2 = self.to_window(*self.pos)[1] + self.height -self.figure_wgt.pos[1] - - x0_box, y0_box = trans.transform_point((x0, y0)) + trans = self.ax.transData.inverted() + # get box 4points xis data + x0 = self.to_window(*self.pos)[0] - self.figure_wgt.pos[0] + y0 = self.to_window(*self.pos)[1] - self.figure_wgt.pos[1] + x1 = self.to_window(*self.pos)[0] - self.figure_wgt.pos[0] + y1 = self.to_window(*self.pos)[1] + \ + self.height - self.figure_wgt.pos[1] + x3 = self.to_window(*self.pos)[0] + self.width - self.figure_wgt.pos[0] + y3 = self.to_window(*self.pos)[1] - self.figure_wgt.pos[1] + x2 = self.to_window(*self.pos)[0] + self.width - self.figure_wgt.pos[0] + y2 = self.to_window(*self.pos)[1] + \ + self.height - self.figure_wgt.pos[1] + + x0_box, y0_box = trans.transform_point((x0, y0)) x1_box, y1_box = trans.transform_point((x1, y1)) x2_box, y2_box = trans.transform_point((x2, y2)) x3_box, y3_box = trans.transform_point((x3, y3)) - verts=[] + verts = [] verts.append((x0_box, y0_box)) verts.append((x1_box, y1_box)) verts.append((x2_box, y2_box)) @@ -847,24 +876,26 @@ def _get_box_data(self): def onselect(self, verts): path = Path(verts) if self.collection: - self.ind = np.nonzero(path.contains_points(self.xys))[0] #xys collection.get_offsets() + self.ind = np.nonzero(path.contains_points(self.xys))[ + 0] # xys collection.get_offsets() self.fc[:, -1] = self.alpha_other self.fc[self.ind, -1] = 1 self.collection.set_facecolors(self.fc) if self.line: - xdata,ydata = self.line.get_xydata().T - self.ind_line = np.nonzero(path.contains_points(np.array([xdata,ydata]).T))[0] + xdata, ydata = self.line.get_xydata().T + self.ind_line = np.nonzero( + path.contains_points(np.array([xdata, ydata]).T))[0] self.figure_wgt.figure.canvas.draw_idle() if self.callback: self.callback(self) - - def set_callback(self,callback): - self.callback=callback - def set_callback_clear(self,callback): - self.callback_clear=callback - + def set_callback(self, callback): + self.callback = callback + + def set_callback_clear(self, callback): + self.callback_clear = callback + def clear_selection(self): if self.collection: @@ -872,10 +903,10 @@ def clear_selection(self): self.fc[:, -1] = 1 self.collection.set_facecolors(self.fc) if self.line: - self.ind_line=[] - + self.ind_line = [] + self.reset_selection() - self.figure_wgt.figure.canvas.draw_idle() + self.figure_wgt.figure.canvas.draw_idle() def _rotate_pos(x, y, cx, cy, angle, base_angle=0.): @@ -1307,7 +1338,7 @@ def on_touch_down(self, touch): if super(PaintCanvasBehaviorBase, self).on_touch_down(touch): return True - + if not self.collide_point(touch.x, touch.y): return False @@ -1558,6 +1589,7 @@ def keyboard_on_key_up(self, window, keycode): return False + class PaintShape(EventDispatcher): """Base class for shapes used by :attr:`PaintCanvasBehavior` when creating new shapes when drawing. @@ -1702,7 +1734,7 @@ class PaintShape(EventDispatcher): def __init__( self, line_color=(0, 0, 0, 0.65), line_color_locked=(.4, .56, .36, 1), - selection_point_color=(62/255, 254/255, 1,1), **kwargs): + selection_point_color=(62 / 255, 254 / 255, 1, 1), **kwargs): self.graphics_name = '{}-{}'.format(self.__class__.__name__, id(self)) super(PaintShape, self).__init__(**kwargs) @@ -2101,6 +2133,7 @@ def rescale(self, scale): """ raise NotImplementedError + class PaintEllipse(PaintShape): """A shape that represents an ellipse. @@ -2172,7 +2205,7 @@ class PaintEllipse(PaintShape): ready_to_finish = True is_valid = True - + during_creation = False def __init__(self, **kwargs): @@ -2220,7 +2253,7 @@ def add_area_graphics_to_canvas(self, name, canvas): x, y = self.center rx, ry = self.radius_x, self.radius_y angle = self.angle - + PushMatrix(group=name) Rotate(angle=angle / pi * 180., origin=(x, y), group=name) Ellipse(size=(rx * 2., ry * 2.), pos=(x - rx, y - ry), group=name) @@ -2231,7 +2264,7 @@ def add_shape_to_canvas(self, paint_widget): return False colors = self.color_instructions = [] - + x, y = self.center rx, ry = self.radius_x, self.radius_y angle = self.angle @@ -2247,11 +2280,11 @@ def add_shape_to_canvas(self, paint_widget): i4 = self.perim_ellipse_inst = Line( ellipse=(x - rx, y - ry, 2 * rx, 2 * ry), width=self.line_width, group=self.graphics_name) - + i66 = self.perim_color_inst2 = Color( *self.selection_point_color, group=self.graphics_name) colors.append(i66) - + i6 = self.selection_point_inst2 = Point( points=[x, y + ry], pointsize=self.pointsize, group=self.graphics_name) @@ -2281,14 +2314,14 @@ def _update_from_line_width(self, *args): super()._update_from_line_width() if self.perim_ellipse_inst is not None: # w = 2 if self.selected else 1 - w=1 + w = 1 self.perim_ellipse_inst.width = w * self.line_width def _update_from_pointsize(self, *args): super()._update_from_pointsize() if self.selection_point_inst is not None: # w = 2 if self.interacting else 1 - w=1 + w = 1 self.selection_point_inst.pointsize = w * self.pointsize self.selection_point_inst2.pointsize = w * self.pointsize @@ -2440,27 +2473,28 @@ def rescale(self, scale): def get_widget_verts(self): cx, cy = self.center - x0,y0 = self._get_coord_from_angle(self.selection_point_inst.points) - x1,y1 = self._get_coord_from_angle(self.selection_point_inst2.points,base_angle = pi / 2.) - - return np.array([[cx,cy], - [x0,y0], - [x1,y1]]) - - def _get_coord_from_angle(self,pos,base_angle=0.): + x0, y0 = self._get_coord_from_angle(self.selection_point_inst.points) + x1, y1 = self._get_coord_from_angle( + self.selection_point_inst2.points, base_angle=pi / 2.) + + return np.array([[cx, cy], + [x0, y0], + [x1, y1]]) + + def _get_coord_from_angle(self, pos, base_angle=0.): x1, y1 = pos x2, y2 = self.center x, y = _rotate_pos(x1, y1, x2, y2, self.angle, base_angle=base_angle) - return x,y - + return x, y + def get_min_max(self): """ Calculates the min/max x and y coordinates for a rotated ellipse. - + Args: None - + Returns: dict: A dictionary containing min_x, max_x, min_y, max_y. """ @@ -2468,30 +2502,30 @@ def get_min_max(self): a = self.radius_x # semi-major axis b = self.radius_y # semi-minor axis theta = self.angle # convert angle to radians - + # Parametric equations for the ellipse t = np.linspace(0, 2 * pi, 1000) # Parameter t from 0 to 2*pi - + # Parametric points on the unrotated ellipse x_ellipse = a * np.cos(t) y_ellipse = b * np.sin(t) - + # Rotation matrix applied to the points x_rotated = x_ellipse * cos(theta) - y_ellipse * sin(theta) y_rotated = x_ellipse * sin(theta) + y_ellipse * cos(theta) - + # Translating the rotated points to the ellipse's center x_final = cx + x_rotated y_final = cy + y_rotated - + # Finding the min and max values of x and y min_x = np.min(x_final) max_x = np.max(x_final) min_y = np.min(y_final) max_y = np.max(y_final) - - return min_x, max_x, min_y, max_y - + + return min_x, max_x, min_y, max_y + class PaintPolygon(PaintShape): """A shape that represents a polygon. @@ -2634,16 +2668,16 @@ def add_shape_to_canvas(self, paint_widget): i2 = self.perim_line_inst = Line( points=self.points, width=self.line_width, close=self.finished, group=self.graphics_name) - + i33 = self.perim_color_inst2 = Color( *self.selection_point_color, group=self.graphics_name) colors.append(i33) - + i3 = self.perim_points_inst = Point( points=self.points, pointsize=self.pointsize, group=self.graphics_name) - insts = [i1, i2,i33, i3] + insts = [i1, i2, i33, i3] if not self.finished: points = self.points[-2:] + self.points[:2] line = self.perim_close_inst = Line( @@ -2678,7 +2712,7 @@ def remove_shape_from_canvas(self): def _update_from_line_width(self, *args): super()._update_from_line_width() w = 2 if self.selected else 1 - w=1 + w = 1 if self.perim_line_inst is not None: self.perim_line_inst.width = w * self.line_width if self.perim_close_inst is not None: @@ -2736,8 +2770,8 @@ def handle_touch_up(self, touch, outside=False): def start_interaction(self, pos): if super(PaintPolygon, self).start_interaction(pos): if self.selection_point_inst is not None: - self.selection_point_inst.pointsize = 2* self.pointsize - self.perim_points_inst.pointsize = 2*self.pointsize + self.selection_point_inst.pointsize = 2 * self.pointsize + self.perim_points_inst.pointsize = 2 * self.pointsize return True return False @@ -2858,7 +2892,8 @@ def rescale(self, scale): points = [val for point in zip(x_vals, y_vals) for val in point] self.points = points - self.selection_point = points[:2] + self.selection_point = points[:2] + class PaintFreeformPolygon(PaintPolygon): """A shape that represents a polygon. @@ -2901,6 +2936,7 @@ def handle_touch_up(self, touch, outside=False): return super(PaintFreeformPolygon, self).handle_touch_up(touch) self.ready_to_finish = True + class PaintCanvasBehavior(PaintCanvasBehaviorBase): """Implements the :class:`PaintCanvasBehaviorBase` to be able to draw any of the following shapes: `'circle', 'ellipse', 'polygon', 'freeform'`, @@ -2922,8 +2958,8 @@ class PaintCanvasBehavior(PaintCanvasBehaviorBase): """ shape_cls_map = {'ellipse': PaintEllipse, - 'polygon': PaintPolygon, 'freeform': PaintFreeformPolygon - } + 'polygon': PaintPolygon, 'freeform': PaintFreeformPolygon + } """Maps :attr:`draw_mode` to the actual :attr:`PaintShape` subclass to be used for drawing when in this mode. @@ -3007,23 +3043,24 @@ def create_shape_from_state(self, state, add=True): if add: self.add_shape(shape) return shape - + + class PainterWidget(PaintCanvasBehavior, FloatLayout): figure_wgt = ObjectProperty() desktop_mode = BooleanProperty(True) alpha = NumericProperty(1) - max_rate = NumericProperty(2/60) + max_rate = NumericProperty(2 / 60) - def __init__(self,**kwargs): - super().__init__(**kwargs) + def __init__(self, **kwargs): + super().__init__(**kwargs) self.selected_side = None - self.last_touch_time=None + self.last_touch_time = None self.verts = [] self.widget_verts = [] self.ax = None self.callback = None self.callback_clear = None - + self.alpha_other = 0.3 self.ind = [] @@ -3031,18 +3068,18 @@ def __init__(self,**kwargs): self.xys = None self.Npts = None self.fc = None - - self.line = None - self.ind_line=[] - - self.first_touch=None - self.current_shape_close=None - - #TODO manage mouse system cursor - # def on_kv_post(self,_): + + self.line = None + self.ind_line = [] + + self.first_touch = None + self.current_shape_close = None + + # TODO manage mouse system cursor + # def on_kv_post(self,_): # if platform != 'android' and self.desktop_mode: #only bind mouse position if not android or if the user set desktop mode to false # Window.bind(mouse_pos=self.on_mouse_pos) - + def on_mouse_pos(self, something, touch): """ TODO @@ -3050,8 +3087,9 @@ def on_mouse_pos(self, something, touch): and update the cursor accordingly. """ if self.opacity and self.collide_point(*self.to_widget(*touch)): - - collision = self.collides_with_control_points(something, self.to_widget(*touch)) + + collision = self.collides_with_control_points( + something, self.to_widget(*touch)) if collision in ["top left", "bottom right"]: Window.set_system_cursor("size_nwse") elif collision in ["top right", "bottom left"]: @@ -3062,10 +3100,9 @@ def on_mouse_pos(self, something, touch): Window.set_system_cursor("size_we") else: Window.set_system_cursor("size_all") - + elif self.figure_wgt.collide_point(*touch): Window.set_system_cursor("arrow") - def create_shape_with_touch(self, touch): shape = super(PainterWidget, self).create_shape_with_touch(touch) @@ -3078,9 +3115,9 @@ def add_shape(self, shape): shape.add_shape_to_canvas(self) return True return False - + def on_touch_down(self, touch): - if self.figure_wgt.touch_mode!='selector': + if self.figure_wgt.touch_mode != 'selector': return False ud = touch.ud # whether the touch was used by the painter for any purpose whatsoever @@ -3105,65 +3142,76 @@ def on_touch_down(self, touch): return True if self.figure_wgt.collide_point(*self.to_window(*touch.pos)): - if self.figure_wgt.touch_mode=='selector': + if self.figure_wgt.touch_mode == 'selector': if touch.is_double_tap and self.callback_clear: self.callback_clear() return - + x, y = touch.pos[0], touch.pos[1] - result=False - if self.shapes and self.opacity==1: - result = self.check_if_inside_path(self.shapes[0].points,x,y) + result = False + if self.shapes and self.opacity == 1: + result = self.check_if_inside_path( + self.shapes[0].points, x, y) - self.opacity=1 + self.opacity = 1 if result: - shape = self.get_closest_selection_point_shape(touch.x, touch.y) + shape = self.get_closest_selection_point_shape( + touch.x, touch.y) shape2 = self.get_closest_shape(touch.x, touch.y) if shape is not None or shape2 is not None: self.selected_side = "modify" if shape: - self.start_shape_interaction(shape, (touch.x, touch.y)) + self.start_shape_interaction( + shape, (touch.x, touch.y)) shape.handle_touch_up( - touch, outside=not self.collide_point(touch.x, touch.y)) + touch, outside=not self.collide_point( + touch.x, touch.y)) if self.check_new_shape_done(shape, 'up'): self.finish_current_shape() else: - self.start_shape_interaction(shape2, (touch.x, touch.y)) + self.start_shape_interaction( + shape2, (touch.x, touch.y)) shape2.handle_touch_up( - touch, outside=not self.collide_point(touch.x, touch.y)) + touch, outside=not self.collide_point( + touch.x, touch.y)) if self.check_new_shape_done(shape2, 'up'): self.finish_current_shape() - + else: self.selected_side = "pan" else: - shape = self.get_closest_selection_point_shape(touch.x, touch.y) + shape = self.get_closest_selection_point_shape( + touch.x, touch.y) shape2 = self.get_closest_shape(touch.x, touch.y) if shape is not None or shape2 is not None: self.selected_side = "modify" if shape: ud['paint_interaction'] = 'current' - self.start_shape_interaction(shape, (touch.x, touch.y)) + self.start_shape_interaction( + shape, (touch.x, touch.y)) shape.handle_touch_up( - touch, outside=not self.collide_point(touch.x, touch.y)) + touch, outside=not self.collide_point( + touch.x, touch.y)) if self.check_new_shape_done(shape, 'up'): self.finish_current_shape() else: ud['paint_interaction'] = 'current' - self.start_shape_interaction(shape2, (touch.x, touch.y)) + self.start_shape_interaction( + shape2, (touch.x, touch.y)) shape2.handle_touch_up( - touch, outside=not self.collide_point(touch.x, touch.y)) + touch, outside=not self.collide_point( + touch.x, touch.y)) if self.check_new_shape_done(shape2, 'up'): self.finish_current_shape() - + else: self.selected_side = "new select" self.delete_all_shapes() - + ud['paint_interacted'] = True self._processing_touch = touch touch.grab(self) - + # if we have a current shape, all touch will go to it current_shape = self.current_shape if current_shape is not None: @@ -3172,21 +3220,23 @@ def on_touch_down(self, touch): >= dp(self.min_touch_dist) if ud['paint_cleared_selection']: self.finish_current_shape() - + else: ud['paint_interaction'] = 'current' current_shape.handle_touch_down(touch) return True - - # next try to interact by selecting or interacting with selected shapes - shape = self.get_closest_selection_point_shape(touch.x, touch.y) + + # next try to interact by selecting or interacting with + # selected shapes + shape = self.get_closest_selection_point_shape( + touch.x, touch.y) if shape is not None: ud['paint_interaction'] = 'selected' ud['paint_selected_shape'] = shape ud['paint_was_selected'] = shape not in self.selected_shapes return True - + if self._ctrl_down: ud['paint_interaction'] = 'done' return True @@ -3195,39 +3245,39 @@ def on_touch_down(self, touch): for shape in self.shapes: shape.pointsize = dp(0.01) - - return True - + + return True + elif not self.collide_point(touch.x, touch.y): return False - def on_touch_move(self, touch): - if self.figure_wgt.touch_mode!='selector': - return False + if self.figure_wgt.touch_mode != 'selector': + return False ud = touch.ud if touch.grab_current is self: # for move, only use normal touch, not touch outside range x, y = self.to_window(*self.pos) - + if self.selected_side == "new select" or self.selected_side == "modify": if ud['paint_interaction'] == 'done': return True ud['paint_touch_moved'] = True - + if self.figure_wgt.collide_point(*self.to_window(*touch.pos)): if not ud['paint_interaction']: if ud['paint_cleared_selection'] or self.clear_selected_shapes(): ud['paint_interaction'] = 'done' return True - + # finally try creating a new shape - # touch must have originally collided otherwise we wouldn't be here + # touch must have originally collided otherwise we + # wouldn't be here shape = self.create_shape_with_touch(touch) if shape is not None: shape.handle_touch_down(touch, opos=touch.opos) @@ -3237,43 +3287,54 @@ def on_touch_move(self, touch): self.finish_current_shape() ud['paint_interaction'] = 'done' return True - + ud['paint_interaction'] = 'current_new' else: ud['paint_interaction'] = 'done' return True - + if ud['paint_interaction'] in ('current', 'current_new'): if self.current_shape is None: ud['paint_interaction'] = 'done' else: self.current_shape.handle_touch_move(touch) return True - + if self.selected_side == "pan": - - shape = ud['paint_selected_shape'] - dataxy = np.array(self.shapes[0].points).reshape(-1,2) - xmax, ymax = dataxy.max(axis=0) - xmin, ymin = dataxy.min(axis=0) - if self.figure_wgt.collide_point(*self.to_window(xmin +touch.dx,ymin+touch.dy)) and \ - self.figure_wgt.collide_point(*self.to_window(xmax+touch.dx,ymax+touch.dy)): + shape = ud['paint_selected_shape'] + + dataxy = np.array(self.shapes[0].points).reshape(-1, 2) + xmax, ymax = dataxy.max(axis=0) + xmin, ymin = dataxy.min(axis=0) + if self.figure_wgt.collide_point( + * + self.to_window( + xmin + + touch.dx, + ymin + + touch.dy)) and self.figure_wgt.collide_point( + * + self.to_window( + xmax + + touch.dx, + ymax + + touch.dy)): - for s in self.shapes: - s.translate(dpos=(touch.dx, touch.dy)) - return True + for s in self.shapes: + s.translate(dpos=(touch.dx, touch.dy)) + return True return False return super().on_touch_move(touch) def on_touch_up(self, touch): - if self.figure_wgt.touch_mode!='selector': - return False + if self.figure_wgt.touch_mode != 'selector': + return False ud = touch.ud if touch.grab_current is self: - touch.ungrab(self) + touch.ungrab(self) # don't process the same touch up again paint_interaction = ud['paint_interaction'] ud['paint_interaction'] = 'done' @@ -3281,12 +3342,12 @@ def on_touch_up(self, touch): self.finish_current_shape() self.alpha = 1 - if self.selected_side != "modify": if self.selected_side == "new select" and self.shapes: self.filter_path(self.shapes[0]) - elif self.selected_side=='pan' and self.shapes: - self.widget_verts = np.array(self.shapes[0].points).reshape(-1, 2) + elif self.selected_side == 'pan' and self.shapes: + self.widget_verts = np.array( + self.shapes[0].points).reshape(-1, 2) # self.clear_selected_shapes() shape = self.get_closest_shape(touch.x, touch.y) @@ -3298,15 +3359,15 @@ def on_touch_up(self, touch): else: for shape in self.shapes: shape.pointsize = dp(7) - + if self.widget_verts is not None: self.verts = self._get_lasso_data(self.widget_verts) self.onselect(self.verts) - + if self.selected_side == "modify": paint_interaction = ud['paint_interaction'] - ud['paint_interaction'] = 'done' + ud['paint_interaction'] = 'done' self._processing_touch = None if not paint_interaction: @@ -3314,7 +3375,8 @@ def on_touch_up(self, touch): return True # finally try creating a new shape - # touch must have originally collided otherwise we wouldn't be here + # touch must have originally collided otherwise we wouldn't + # be here shape = self.create_shape_with_touch(touch) if shape is not None: shape.handle_touch_down(touch, opos=touch.opos) @@ -3329,19 +3391,20 @@ def on_touch_up(self, touch): if self.current_shape is not None: self.current_shape.handle_touch_up( - touch, outside=not self.collide_point(touch.x, touch.y)) + touch, outside=not self.collide_point( + touch.x, touch.y)) if self.check_new_shape_done(self.current_shape, 'up'): self.finish_current_shape() self.finish_current_shape() - if self.selected_side=='modify' and self.shapes: - self.widget_verts = np.array(self.shapes[0].points).reshape(-1, 2)[1:,:] + if self.selected_side == 'modify' and self.shapes: + self.widget_verts = np.array( + self.shapes[0].points).reshape(-1, 2)[1:, :] if self.widget_verts is not None: self.verts = self._get_lasso_data(self.widget_verts) self.onselect(self.verts) return super().on_touch_up(touch) - shape = ud['paint_selected_shape'] if shape not in self.shapes: return True @@ -3356,30 +3419,27 @@ def on_touch_up(self, touch): self.selected_shapes[0] != shape: self.clear_selected_shapes() self.select_shape(shape) - return True - - return True - - if self.figure_wgt.collide_point(*self.to_window(*touch.pos)): + + return True + + if self.figure_wgt.collide_point(*self.to_window(*touch.pos)): self._processing_touch = None - return True - + return True return super().on_touch_up(touch) - - def check_if_inside_path(self,pts,x,y): + def check_if_inside_path(self, pts, x, y): verts = np.array(pts).reshape(-1, 2) path = Path(verts) - ind = np.nonzero(path.contains_points(np.array([[x,y]])))[0] - if len(ind)!=0: + ind = np.nonzero(path.contains_points(np.array([[x, y]])))[0] + if len(ind) != 0: return True else: return False - + def get_closest_selection_point_shape(self, x, y): """Given a position, it returns the shape whose selection point is the closest to this position among all the shapes. @@ -3405,7 +3465,7 @@ def get_closest_selection_point_shape(self, x, y): min_dist = dist return closest_shape - + def do_long_touch(self, touch, *largs): """Handles a long touch by the user. """ @@ -3432,44 +3492,44 @@ def do_long_touch(self, touch, *largs): self.start_shape_interaction(shape, (touch.x, touch.y)) else: ud['paint_interaction'] = 'done' - touch.pop() - - def filter_path(self,shape): + touch.pop() + + def filter_path(self, shape): pts = shape.points verts = np.array(pts).reshape(-1, 2) - filter_pts = rdp(verts,5.0) - - if len(filter_pts) < 6 : + filter_pts = rdp(verts, 5.0) + + if len(filter_pts) < 6: self.widget_verts = None return - - - #delete old shape + + # delete old shape self.delete_all_shapes() - + shape = self.create_shape_with_touch(self.first_touch) if shape is not None: - shape.handle_touch_down(self.first_touch, opos=self.first_touch.opos) + shape.handle_touch_down( + self.first_touch, opos=self.first_touch.opos) self.current_shape = shape - - - #crete new filter shape + + # crete new filter shape for data_xy in filter_pts: - + if not self.current_shape.selection_point: self.current_shape.selection_point = data_xy - + self.current_shape.points.extend(data_xy) if self.current_shape.perim_close_inst is not None: - self.current_shape.perim_close_inst.points = self.current_shape.points[-2:] + self.current_shape.points[:2] + self.current_shape.perim_close_inst.points = self.current_shape.points[ + -2:] + self.current_shape.points[:2] if len(self.current_shape.points) >= 6: self.current_shape.is_valid = True - + self.finish_current_shape() - + self.widget_verts = filter_pts - def set_collection(self,collection): + def set_collection(self, collection): self.collection = collection self.xys = collection.get_offsets() self.Npts = len(self.xys) @@ -3480,32 +3540,34 @@ def set_collection(self,collection): raise ValueError('Collection must have a facecolor') elif len(self.fc) == 1: self.fc = np.tile(self.fc, (self.Npts, 1)) - - def set_line(self,line): - self.line = line - + + def set_line(self, line): + self.line = line + def onselect(self, verts): if verts: path = Path(verts) if self.collection: - self.ind = np.nonzero(path.contains_points(self.xys))[0] #xys collection.get_offsets() + self.ind = np.nonzero(path.contains_points(self.xys))[ + 0] # xys collection.get_offsets() self.fc[:, -1] = self.alpha_other self.fc[self.ind, -1] = 1 self.collection.set_facecolors(self.fc) if self.line: - xdata,ydata = self.line.get_xydata().T - self.ind_line = np.nonzero(path.contains_points(np.array([xdata,ydata]).T))[0] - + xdata, ydata = self.line.get_xydata().T + self.ind_line = np.nonzero( + path.contains_points(np.array([xdata, ydata]).T))[0] + self.figure_wgt.figure.canvas.draw_idle() if self.callback: self.callback(self) - - def set_callback(self,callback): - self.callback=callback - - def set_callback_clear(self,callback): - self.callback_clear=callback - + + def set_callback(self, callback): + self.callback = callback + + def set_callback_clear(self, callback): + self.callback_clear = callback + def clear_selection(self): if self.collection: @@ -3513,38 +3575,44 @@ def clear_selection(self): self.fc[:, -1] = 1 self.collection.set_facecolors(self.fc) if self.line: - self.ind_line=[] + self.ind_line = [] self.delete_all_shapes() - self.figure_wgt.figure.canvas.draw_idle() - - def _get_lasso_data(self,widget_verts): - trans = self.ax.transData.inverted() - - verts=[] + self.figure_wgt.figure.canvas.draw_idle() + + def _get_lasso_data(self, widget_verts): + trans = self.ax.transData.inverted() + + verts = [] for data_xy in widget_verts: - x, y = trans.transform_point((data_xy[0] + self.to_window(*self.pos)[0]-self.figure_wgt.pos[0], - data_xy[1] + self.to_window(*self.pos)[1]-self.figure_wgt.pos[1])) + x, y = trans.transform_point((data_xy[0] + + self.to_window(* + self.pos)[0] - + self.figure_wgt.pos[0], data_xy[1] + + self.to_window(* + self.pos)[1] - + self.figure_wgt.pos[1])) verts.append((x, y)) return verts - + + class PainterWidget2(PaintCanvasBehavior, FloatLayout): figure_wgt = ObjectProperty() desktop_mode = BooleanProperty(True) alpha = NumericProperty(1) - max_rate = NumericProperty(2/60) + max_rate = NumericProperty(2 / 60) - def __init__(self,**kwargs): - super().__init__(**kwargs) + def __init__(self, **kwargs): + super().__init__(**kwargs) self.selected_side = None - self.last_touch_time=None + self.last_touch_time = None self.verts = [] self.widget_verts = [] self.ax = None self.callback = None self.callback_clear = None - + self.alpha_other = 0.3 self.ind = [] @@ -3552,18 +3620,18 @@ def __init__(self,**kwargs): self.xys = None self.Npts = None self.fc = None - - self.line = None - self.ind_line=[] - - self.first_touch=None - self.current_shape_close=None - - #TODO manage mouse system cursor - # def on_kv_post(self,_): + + self.line = None + self.ind_line = [] + + self.first_touch = None + self.current_shape_close = None + + # TODO manage mouse system cursor + # def on_kv_post(self,_): # if platform != 'android' and self.desktop_mode: #only bind mouse position if not android or if the user set desktop mode to false # Window.bind(mouse_pos=self.on_mouse_pos) - + def on_mouse_pos(self, something, touch): """ TODO @@ -3571,8 +3639,9 @@ def on_mouse_pos(self, something, touch): and update the cursor accordingly. """ if self.opacity and self.collide_point(*self.to_widget(*touch)): - - collision = self.collides_with_control_points(something, self.to_widget(*touch)) + + collision = self.collides_with_control_points( + something, self.to_widget(*touch)) if collision in ["top left", "bottom right"]: Window.set_system_cursor("size_nwse") elif collision in ["top right", "bottom left"]: @@ -3583,16 +3652,15 @@ def on_mouse_pos(self, something, touch): Window.set_system_cursor("size_we") else: Window.set_system_cursor("size_all") - + elif self.figure_wgt.collide_point(*touch): Window.set_system_cursor("arrow") - def create_shape_with_touch(self, touch, check=True): shape = super(PainterWidget2, self).create_shape_with_touch(touch) - if check and shape.radius_x==dp(10) and shape.radius_y==dp(15): - return None - + if check and shape.radius_x == dp(10) and shape.radius_y == dp(15): + return None + if shape is not None: shape.add_shape_to_canvas(self) return shape @@ -3602,9 +3670,9 @@ def add_shape(self, shape): shape.add_shape_to_canvas(self) return True return False - + def on_touch_down(self, touch): - if self.figure_wgt.touch_mode!='selector': + if self.figure_wgt.touch_mode != 'selector': return False ud = touch.ud @@ -3627,68 +3695,77 @@ def on_touch_down(self, touch): if super(PaintCanvasBehaviorBase, self).on_touch_down(touch): return True - + if self.figure_wgt.collide_point(*self.to_window(*touch.pos)): - - - if self.figure_wgt.touch_mode=='selector': - + + if self.figure_wgt.touch_mode == 'selector': + if touch.is_double_tap and self.callback_clear: self.callback_clear() return x, y = touch.pos[0], touch.pos[1] - result=False - if self.shapes and self.opacity==1: - result = self.check_if_inside_path(self.shapes[0],x,y) - - self.opacity=1 + result = False + if self.shapes and self.opacity == 1: + result = self.check_if_inside_path(self.shapes[0], x, y) + + self.opacity = 1 if result: - shape = self.get_closest_selection_point_shape(touch.x, touch.y) + shape = self.get_closest_selection_point_shape( + touch.x, touch.y) shape2 = self.get_closest_shape(touch.x, touch.y) if shape is not None or shape2 is not None: self.selected_side = "modify" if shape: - self.start_shape_interaction(shape, (touch.x, touch.y)) + self.start_shape_interaction( + shape, (touch.x, touch.y)) shape.handle_touch_up( - touch, outside=not self.collide_point(touch.x, touch.y)) + touch, outside=not self.collide_point( + touch.x, touch.y)) if self.check_new_shape_done(shape, 'up'): self.finish_current_shape() else: - self.start_shape_interaction(shape2, (touch.x, touch.y)) + self.start_shape_interaction( + shape2, (touch.x, touch.y)) shape2.handle_touch_up( - touch, outside=not self.collide_point(touch.x, touch.y)) + touch, outside=not self.collide_point( + touch.x, touch.y)) if self.check_new_shape_done(shape2, 'up'): self.finish_current_shape() - + else: self.selected_side = "pan" touch.grab(self) return True else: - shape = self.get_closest_selection_point_shape(touch.x, touch.y) + shape = self.get_closest_selection_point_shape( + touch.x, touch.y) shape2 = self.get_closest_shape(touch.x, touch.y) if shape is not None or shape2 is not None: self.selected_side = "modify" if shape: ud['paint_interaction'] = 'current' - self.start_shape_interaction(shape, (touch.x, touch.y)) + self.start_shape_interaction( + shape, (touch.x, touch.y)) shape.handle_touch_up( - touch, outside=not self.collide_point(touch.x, touch.y)) + touch, outside=not self.collide_point( + touch.x, touch.y)) if self.check_new_shape_done(shape, 'up'): self.finish_current_shape() else: ud['paint_interaction'] = 'current' - self.start_shape_interaction(shape2, (touch.x, touch.y)) + self.start_shape_interaction( + shape2, (touch.x, touch.y)) shape2.handle_touch_up( - touch, outside=not self.collide_point(touch.x, touch.y)) + touch, outside=not self.collide_point( + touch.x, touch.y)) if self.check_new_shape_done(shape2, 'up'): self.finish_current_shape() - + else: self.selected_side = "new select" self.delete_all_shapes() - + ud['paint_interacted'] = True self._processing_touch = touch touch.grab(self) @@ -3700,22 +3777,23 @@ def on_touch_down(self, touch): >= dp(self.min_touch_dist) if ud['paint_cleared_selection']: self.finish_current_shape() - + else: ud['paint_interaction'] = 'current' current_shape.handle_touch_down(touch) - return True - - # next try to interact by selecting or interacting with selected shapes - shape = self.get_closest_selection_point_shape(touch.x, touch.y) + + # next try to interact by selecting or interacting with + # selected shapes + shape = self.get_closest_selection_point_shape( + touch.x, touch.y) if shape is not None: ud['paint_interaction'] = 'selected' ud['paint_selected_shape'] = shape ud['paint_was_selected'] = shape not in self.selected_shapes return True - + if self._ctrl_down: ud['paint_interaction'] = 'done' return True @@ -3724,77 +3802,89 @@ def on_touch_down(self, touch): for shape in self.shapes: shape.pointsize = dp(7) - - return True - - + + return True + elif not self.collide_point(touch.x, touch.y): if self.figure_wgt.collide_point(*self.to_window(*touch.pos)): self.delete_all_shapes() return False - def on_touch_move(self, touch): - if self.figure_wgt.touch_mode!='selector': - return False + if self.figure_wgt.touch_mode != 'selector': + return False ud = touch.ud if touch.grab_current is self: # for move, only use normal touch, not touch outside range x, y = self.to_window(*self.pos) - + if self.selected_side == "new select" or self.selected_side == "modify": # if ud['paint_interaction'] == 'done': # return True ud['paint_touch_moved'] = True - + if self.figure_wgt.collide_point(*self.to_window(*touch.pos)): if not ud['paint_interaction']: if ud['paint_cleared_selection'] or self.clear_selected_shapes(): ud['paint_interaction'] = 'done' return True - + # finally try creating a new shape - # touch must have originally collided otherwise we wouldn't be here - shape = self.create_shape_with_touch(touch,check=False) + # touch must have originally collided otherwise we + # wouldn't be here + shape = self.create_shape_with_touch( + touch, check=False) if shape is not None: shape.handle_touch_down(touch, opos=touch.opos) self.first_touch = touch self.current_shape = shape self.current_shape.during_creation = True - + if self.check_new_shape_done(shape, 'down'): - + self.finish_current_shape() self.current_shape = self.shapes[0] ud['paint_interaction'] = 'done' return True - + ud['paint_interaction'] = 'current_new' else: ud['paint_interaction'] = 'done' shape.handle_touch_move(touch) return True - + # if ud['paint_interaction'] in ('current', 'current_new'): else: if self.current_shape is None: ud['paint_interaction'] = 'done' else: - self.current_shape.start_interaction((touch.x, touch.y)) + self.current_shape.start_interaction( + (touch.x, touch.y)) self.current_shape.handle_touch_move(touch) return True - + if self.selected_side == "pan": shape = ud['paint_selected_shape'] - xmin,xmax,ymin,ymax = self.shapes[0].get_min_max() - if self.figure_wgt.collide_point(*self.to_window(xmin +touch.dx,ymin+touch.dy)) and \ - self.figure_wgt.collide_point(*self.to_window(xmax+touch.dx,ymax+touch.dy)): + xmin, xmax, ymin, ymax = self.shapes[0].get_min_max() + if self.figure_wgt.collide_point( + * + self.to_window( + xmin + + touch.dx, + ymin + + touch.dy)) and self.figure_wgt.collide_point( + * + self.to_window( + xmax + + touch.dx, + ymax + + touch.dy)): for s in self.shapes: s.translate(dpos=(touch.dx, touch.dy)) @@ -3804,28 +3894,26 @@ def on_touch_move(self, touch): return super().on_touch_move(touch) def on_touch_up(self, touch): - - if self.figure_wgt.touch_mode!='selector': - return False - + + if self.figure_wgt.touch_mode != 'selector': + return False + ud = touch.ud - + if touch.grab_current is self: - touch.ungrab(self) + touch.ungrab(self) # don't process the same touch up again paint_interaction = ud['paint_interaction'] ud['paint_interaction'] = 'done' self._processing_touch = None self.finish_current_shape() self.alpha = 1 - - if self.selected_side != "modify": if self.selected_side == "new select" and self.shapes: self.shapes[0].during_creation = False self.widget_verts = self.shapes[0].get_widget_verts() - elif self.selected_side=='pan' and self.shapes: + elif self.selected_side == 'pan' and self.shapes: self.widget_verts = self.shapes[0].get_widget_verts() # self.clear_selected_shapes() @@ -3838,16 +3926,15 @@ def on_touch_up(self, touch): else: for shape in self.shapes: shape.pointsize = dp(7) - + if self.widget_verts is not None: self.verts = self._get_ellipse_data(self.widget_verts) self.onselect(self.verts) - - + if self.selected_side == "modify": paint_interaction = ud['paint_interaction'] - ud['paint_interaction'] = 'done' + ud['paint_interaction'] = 'done' self._processing_touch = None if not paint_interaction: @@ -3855,7 +3942,8 @@ def on_touch_up(self, touch): return True # finally try creating a new shape - # touch must have originally collided otherwise we wouldn't be here + # touch must have originally collided otherwise we wouldn't + # be here shape = self.create_shape_with_touch(touch) if shape is not None: shape.handle_touch_down(touch, opos=touch.opos) @@ -3870,12 +3958,13 @@ def on_touch_up(self, touch): if self.current_shape is not None: self.current_shape.handle_touch_up( - touch, outside=not self.collide_point(touch.x, touch.y)) + touch, outside=not self.collide_point( + touch.x, touch.y)) if self.check_new_shape_done(self.current_shape, 'up'): self.finish_current_shape() self.finish_current_shape() - if self.selected_side=='modify' and self.shapes: + if self.selected_side == 'modify' and self.shapes: self.widget_verts = self.shapes[0].get_widget_verts() if self.widget_verts is not None: @@ -3883,7 +3972,6 @@ def on_touch_up(self, touch): self.onselect(self.verts) return super().on_touch_up(touch) - shape = ud['paint_selected_shape'] if shape not in self.shapes: return True @@ -3898,35 +3986,33 @@ def on_touch_up(self, touch): self.selected_shapes[0] != shape: self.clear_selected_shapes() self.select_shape(shape) - return True - - return True - - if self.figure_wgt.collide_point(*self.to_window(*touch.pos)): + + return True + + if self.figure_wgt.collide_point(*self.to_window(*touch.pos)): self._processing_touch = None - return True - + return True + self.finish_current_shape() return super().on_touch_up(touch) - - def check_if_inside_path(self,shape,x,y): + def check_if_inside_path(self, shape, x, y): cx, cy = shape.center - width= shape.radius_x * 2 - height= shape.radius_y * 2 - angle= shape.rotate_inst.angle - - path = Ellipse_mpl((cx,cy), width, height,angle=angle) - ind = np.nonzero(path.contains_points(np.array([[x,y]])))[0] - if len(ind)!=0: + width = shape.radius_x * 2 + height = shape.radius_y * 2 + angle = shape.rotate_inst.angle + + path = Ellipse_mpl((cx, cy), width, height, angle=angle) + ind = np.nonzero(path.contains_points(np.array([[x, y]])))[0] + if len(ind) != 0: return True else: return False - + def get_closest_selection_point_shape(self, x, y): """Given a position, it returns the shape whose selection point is the closest to this position among all the shapes. @@ -3952,7 +4038,7 @@ def get_closest_selection_point_shape(self, x, y): min_dist = dist return closest_shape - + def do_long_touch(self, touch, *largs): """Handles a long touch by the user. """ @@ -3979,10 +4065,9 @@ def do_long_touch(self, touch, *largs): self.start_shape_interaction(shape, (touch.x, touch.y)) else: ud['paint_interaction'] = 'done' - touch.pop() - + touch.pop() - def set_collection(self,collection): + def set_collection(self, collection): self.collection = collection self.xys = collection.get_offsets() self.Npts = len(self.xys) @@ -3993,42 +4078,44 @@ def set_collection(self,collection): raise ValueError('Collection must have a facecolor') elif len(self.fc) == 1: self.fc = np.tile(self.fc, (self.Npts, 1)) - - def set_line(self,line): - self.line = line - + + def set_line(self, line): + self.line = line + def onselect(self, verts): - if verts and self.shapes: + if verts and self.shapes: cx, cy = self.shapes[0].center - width= self.shapes[0].radius_x * 2 - height= self.shapes[0].radius_y * 2 - angle= self.shapes[0].rotate_inst.angle - - path = Ellipse_mpl((cx,cy), width, height,angle=angle) - + width = self.shapes[0].radius_x * 2 + height = self.shapes[0].radius_y * 2 + angle = self.shapes[0].rotate_inst.angle + + path = Ellipse_mpl((cx, cy), width, height, angle=angle) + newverts = self._get_ellipse_data(path.get_verts()) - + path = Path(newverts) if self.collection: - self.ind = np.nonzero(path.contains_points(self.xys))[0] #xys collection.get_offsets() + self.ind = np.nonzero(path.contains_points(self.xys))[ + 0] # xys collection.get_offsets() self.fc[:, -1] = self.alpha_other self.fc[self.ind, -1] = 1 self.collection.set_facecolors(self.fc) if self.line: - xdata,ydata = self.line.get_xydata().T - self.ind_line = np.nonzero(path.contains_points(np.array([xdata,ydata]).T))[0] - + xdata, ydata = self.line.get_xydata().T + self.ind_line = np.nonzero( + path.contains_points(np.array([xdata, ydata]).T))[0] + self.figure_wgt.figure.canvas.draw_idle() if self.callback: self.callback(self) - - def set_callback(self,callback): - self.callback=callback - - def set_callback_clear(self,callback): - self.callback_clear=callback - + + def set_callback(self, callback): + self.callback = callback + + def set_callback_clear(self, callback): + self.callback_clear = callback + def clear_selection(self): if self.collection: @@ -4036,22 +4123,27 @@ def clear_selection(self): self.fc[:, -1] = 1 self.collection.set_facecolors(self.fc) if self.line: - self.ind_line=[] + self.ind_line = [] self.delete_all_shapes() - self.figure_wgt.figure.canvas.draw_idle() - - def _get_ellipse_data(self,widget_verts): - trans = self.ax.transData.inverted() - - verts=[] + self.figure_wgt.figure.canvas.draw_idle() + + def _get_ellipse_data(self, widget_verts): + trans = self.ax.transData.inverted() + + verts = [] for data_xy in widget_verts: - x, y = trans.transform_point((data_xy[0] + self.to_window(*self.pos)[0]-self.figure_wgt.pos[0], - data_xy[1] + self.to_window(*self.pos)[1]-self.figure_wgt.pos[1])) + x, y = trans.transform_point((data_xy[0] + + self.to_window(* + self.pos)[0] - + self.figure_wgt.pos[0], data_xy[1] + + self.to_window(* + self.pos)[1] - + self.figure_wgt.pos[1])) verts.append((x, y)) return verts - + class SpanSelect(FloatLayout): top_color = ColorProperty("black") @@ -4059,25 +4151,27 @@ class SpanSelect(FloatLayout): left_color = ColorProperty("black") right_color = ColorProperty("black") highlight_color = ColorProperty("red") - bg_color = ColorProperty([1,1,1,1]) + bg_color = ColorProperty([1, 1, 1, 1]) figure_wgt = ObjectProperty() desktop_mode = BooleanProperty(True) - span_orientation = OptionProperty('vertical', options=['vertical','horizontal']) + span_orientation = OptionProperty( + 'vertical', options=[ + 'vertical', 'horizontal']) span_color = ColorProperty("red") span_alpha = NumericProperty(0.3) span_hide_on_release = BooleanProperty(False) alpha = NumericProperty(1) invert_rect_ver = BooleanProperty(False) - invert_rect_hor = BooleanProperty(False) - dynamic_callback = BooleanProperty(False) + invert_rect_hor = BooleanProperty(False) + dynamic_callback = BooleanProperty(False) stay_in_axis_limit = BooleanProperty(False) - - def __init__(self,**kwargs): + + def __init__(self, **kwargs): super().__init__(**kwargs) self.verts = [] self.ax = None self.callback = None - + self.alpha_other = 0.3 self.ind = [] @@ -4085,32 +4179,35 @@ def __init__(self,**kwargs): self.xys = None self.Npts = None self.fc = None - - self.line = None - self.ind_line=[] - - self.first_touch=None - - def on_kv_post(self,_): - if platform != 'android' and self.desktop_mode: #only bind mouse position if not android or if the user set desktop mode to false + + self.line = None + self.ind_line = [] + + self.first_touch = None + + def on_kv_post(self, _): + # only bind mouse position if not android or if the user set desktop + # mode to false + if platform != 'android' and self.desktop_mode: Window.bind(mouse_pos=self.on_mouse_pos) - + def update_bg_color(self): fig_bg_color = self.figure_wgt.figure.get_facecolor() rgb_fig_bg_color = mcolors.to_rgb(fig_bg_color) - if (rgb_fig_bg_color[0]*0.299 + rgb_fig_bg_color[1]*0.587 + rgb_fig_bg_color[2]*0.114) > 186/255: - self.bg_color = [1,1,1,1] + if (rgb_fig_bg_color[0] * 0.299 + rgb_fig_bg_color[1] + * 0.587 + rgb_fig_bg_color[2] * 0.114) > 186 / 255: + self.bg_color = [1, 1, 1, 1] self.bottom_color = ( self.top_colors - ) = self.left_color = self.right_color = [1,1,1,1] - + ) = self.left_color = self.right_color = [1, 1, 1, 1] + else: - self.bg_color = [0,0,0,1] + self.bg_color = [0, 0, 0, 1] self.bottom_color = ( self.top_colors - ) = self.left_color = self.right_color = [0,0,0,1] - - def set_collection(self,collection): + ) = self.left_color = self.right_color = [0, 0, 0, 1] + + def set_collection(self, collection): self.collection = collection self.xys = collection.get_offsets() self.Npts = len(self.xys) @@ -4121,18 +4218,20 @@ def set_collection(self,collection): raise ValueError('Collection must have a facecolor') elif len(self.fc) == 1: self.fc = np.tile(self.fc, (self.Npts, 1)) - - def set_line(self,line): - self.line = line + + def set_line(self, line): + self.line = line def on_mouse_pos(self, something, touch): """ When the mouse moves, we check the position of the mouse and update the cursor accordingly. """ - if self.opacity and self.figure_wgt.touch_mode=='selector' and self.collide_point(*self.to_widget(*touch)): - - collision = self.collides_with_control_points(something, self.to_widget(*touch)) + if self.opacity and self.figure_wgt.touch_mode == 'selector' and self.collide_point( + *self.to_widget(*touch)): + + collision = self.collides_with_control_points( + something, self.to_widget(*touch)) if collision in ["top left", "bottom right"]: Window.set_system_cursor("size_nwse") elif collision in ["top right", "bottom left"]: @@ -4143,7 +4242,7 @@ def on_mouse_pos(self, something, touch): Window.set_system_cursor("size_we") else: Window.set_system_cursor("size_all") - + # elif self.figure_wgt.collide_point(*touch): else: Window.set_system_cursor("arrow") @@ -4154,7 +4253,6 @@ def collides_with_control_points(self, _, touch): """ x, y = touch[0], touch[1] - if self.span_orientation == 'vertical': # Checking mouse is on left edge if self.x - dp(7) <= x <= self.x + dp(7): @@ -4165,10 +4263,10 @@ def collides_with_control_points(self, _, touch): # Checking mouse is on right edge elif self.x + self.width - dp(7) <= x <= self.x + self.width + dp(7): - if self.y<= y <= self.y + self.height: + if self.y <= y <= self.y + self.height: return "right" else: - return False + return False else: return False @@ -4177,14 +4275,13 @@ def collides_with_control_points(self, _, touch): if self.x <= x <= self.x + self.width: if self.y <= y <= self.y + dp(7): return "bottom" - elif self.y + self.height - dp(7)<= y <= self.y + self.height: + elif self.y + self.height - dp(7) <= y <= self.y + self.height: return "top" else: return False else: return False - def on_touch_down(self, touch): if self.figure_wgt.touch_mode != 'selector': return @@ -4213,66 +4310,81 @@ def on_touch_down(self, touch): self.left_color = self.highlight_color self.right_color = self.highlight_color elif self.figure_wgt.collide_point(*self.to_window(*touch.pos)): - if self.figure_wgt.touch_mode=='selector': + if self.figure_wgt.touch_mode == 'selector': if touch.is_double_tap and self.callback_clear: self.callback_clear() return - + touch.grab(self) - #get figure boundaries (in pixels) - - left_bound = float(self.figure_wgt.x +self.ax.bbox.bounds[0]) - right_bound = float(self.figure_wgt.x +self.ax.bbox.bounds[2] +self.ax.bbox.bounds[0] ) - top_bound = float(self.figure_wgt.y +self.ax.bbox.bounds[3] + self.ax.bbox.bounds[1]) - bottom_bound = float(self.figure_wgt.y +self.ax.bbox.bounds[1]) - - width = right_bound-left_bound - - left_bound,right_bound = self.to_widget(left_bound,right_bound) + # get figure boundaries (in pixels) + + left_bound = float(self.figure_wgt.x + self.ax.bbox.bounds[0]) + right_bound = float( + self.figure_wgt.x + + self.ax.bbox.bounds[2] + + self.ax.bbox.bounds[0]) + top_bound = float( + self.figure_wgt.y + + self.ax.bbox.bounds[3] + + self.ax.bbox.bounds[1]) + bottom_bound = float( + self.figure_wgt.y + self.ax.bbox.bounds[1]) + + width = right_bound - left_bound + + left_bound, right_bound = self.to_widget( + left_bound, right_bound) x, y = touch.pos[0], touch.pos[1] - - self.opacity=1 - + + self.opacity = 1 + if self.span_orientation == 'vertical': - self.pos = (x,bottom_bound - self.figure_wgt.y) - self.size = (5,top_bound-bottom_bound) + self.pos = (x, bottom_bound - self.figure_wgt.y) + self.size = (5, top_bound - bottom_bound) elif self.span_orientation == 'horizontal': - self.pos = (left_bound,y) - self.size = (width,-5) - + self.pos = (left_bound, y) + self.size = (width, -5) + # self.size = (5,5) - self.opacity=1 + self.opacity = 1 if self.span_orientation == 'vertical': - self.first_touch = (x,bottom_bound - self.figure_wgt.y) + self.first_touch = (x, bottom_bound - self.figure_wgt.y) elif self.span_orientation == 'horizontal': - self.first_touch = (left_bound,y) + self.first_touch = (left_bound, y) self.selected_side = "new select" - - + return super().on_touch_down(touch) def on_touch_move(self, touch): if self.figure_wgt.touch_mode != 'selector': return - + if touch.grab_current is self: x, y = self.to_window(*self.pos) top = y + self.height # top of our widget right = x + self.width # right of our widget - + if self.stay_in_axis_limit: - #get figure boundaries (in pixels) - left_bound = float(self.figure_wgt.x +self.ax.bbox.bounds[0]) - right_bound = float(self.figure_wgt.x +self.ax.bbox.bounds[2] +self.ax.bbox.bounds[0] ) - top_bound = float(self.figure_wgt.y +self.ax.bbox.bounds[3] + self.ax.bbox.bounds[1]) - bottom_bound = float(self.figure_wgt.y +self.ax.bbox.bounds[1]) - - width = right_bound-left_bound - - left_bound,right_bound = self.to_widget(left_bound,right_bound) - + # get figure boundaries (in pixels) + left_bound = float(self.figure_wgt.x + self.ax.bbox.bounds[0]) + right_bound = float( + self.figure_wgt.x + + self.ax.bbox.bounds[2] + + self.ax.bbox.bounds[0]) + top_bound = float( + self.figure_wgt.y + + self.ax.bbox.bounds[3] + + self.ax.bbox.bounds[1]) + bottom_bound = float( + self.figure_wgt.y + self.ax.bbox.bounds[1]) + + width = right_bound - left_bound + + left_bound, right_bound = self.to_widget( + left_bound, right_bound) + if self.selected_side == "top": if self.height + touch.dy <= MINIMUM_HEIGHT: return False @@ -4287,19 +4399,19 @@ def on_touch_move(self, touch): elif self.selected_side == "left": if self.width - touch.dx <= MINIMUM_WIDTH: return False - + if self.stay_in_axis_limit and self.x + touch.dx <= left_bound: - self.width -= (left_bound-self.x) + self.width -= (left_bound - self.x) self.x = left_bound - + if self.dynamic_callback and self.verts is not None: self.verts = self._get_box_data() self.onselect(self.verts) - + else: self.width -= touch.dx self.x += touch.dx - + if self.dynamic_callback and self.verts is not None: self.verts = self._get_box_data() self.onselect(self.verts) @@ -4308,20 +4420,21 @@ def on_touch_move(self, touch): if self.width + touch.dx <= MINIMUM_WIDTH: return False - if self.stay_in_axis_limit and self.width + touch.dx+self.x >= left_bound+width: - self.width = left_bound+width-self.x + if self.stay_in_axis_limit and self.width + \ + touch.dx + self.x >= left_bound + width: + self.width = left_bound + width - self.x if self.dynamic_callback and self.verts is not None: self.verts = self._get_box_data() self.onselect(self.verts) - + else: - + self.width += touch.dx - + if self.dynamic_callback and self.verts is not None: self.verts = self._get_box_data() self.onselect(self.verts) - + elif self.selected_side == "new select": if self.span_orientation == 'vertical': self.width += touch.dx @@ -4329,20 +4442,33 @@ def on_touch_move(self, touch): self.height += touch.dy elif not self.selected_side: - if self.figure_wgt.collide_point(*self.to_window(self.pos[0]+touch.dx,self.pos[1]+touch.dy )) and \ - self.figure_wgt.collide_point(*self.to_window(self.pos[0] + self.size[0]+touch.dx,self.pos[1]+ self.size[1]+touch.dy )): - if self.span_orientation == 'vertical': + if self.figure_wgt.collide_point( + * + self.to_window( + self.pos[0] + + touch.dx, + self.pos[1] + + touch.dy)) and self.figure_wgt.collide_point( + * + self.to_window( + self.pos[0] + + self.size[0] + + touch.dx, + self.pos[1] + + self.size[1] + + touch.dy)): + if self.span_orientation == 'vertical': # print(touch.dx) if self.stay_in_axis_limit and self.x + touch.dx < left_bound: self.x = left_bound if self.dynamic_callback and self.verts is not None: self.verts = self._get_box_data() self.onselect(self.verts) - elif self.stay_in_axis_limit and self.width + touch.dx+self.x > left_bound+width: - self.x = left_bound+width-self.width + elif self.stay_in_axis_limit and self.width + touch.dx + self.x > left_bound + width: + self.x = left_bound + width - self.width if self.dynamic_callback and self.verts is not None: self.verts = self._get_box_data() - self.onselect(self.verts) + self.onselect(self.verts) else: self.x += touch.dx if self.dynamic_callback and self.verts is not None: @@ -4356,152 +4482,156 @@ def on_touch_move(self, touch): def on_touch_up(self, touch): if self.figure_wgt.touch_mode != 'selector': return - + if touch.grab_current is self: touch.ungrab(self) self.alpha = 1 - if (self.bg_color[0]*0.299 + \ - self.bg_color[1]*0.587 + self.bg_color[2]*0.114) > 186/255: + if (self.bg_color[0] * 0.299 + self.bg_color[1] + * 0.587 + self.bg_color[2] * 0.114) > 186 / 255: self.bottom_color = ( self.top_colors - ) = self.left_color = self.right_color = [0,0,0,1] + ) = self.left_color = self.right_color = [0, 0, 0, 1] else: self.bottom_color = ( self.top_colors - ) = self.left_color = self.right_color = [1,1,1,1] - + ) = self.left_color = self.right_color = [1, 1, 1, 1] + if self.first_touch and self.selected_side == "new select": self.check_if_reverse_selection(touch) - - if abs(self.size[0])xmax: - out_of_bound=True + if self.span_orientation == 'vertical': + box_xmin = min(np.array(box_data)[:, 0]) + box_xmax = max(np.array(box_data)[:, 0]) + + xmin, xmax = self.ax.get_xlim() + if box_xmin < xmin or box_xmax > xmax: + out_of_bound = True self.reset_selection() - + if self.verts is not None and not out_of_bound: self.verts = self._get_box_data() self.onselect(self.verts) return True return super().on_touch_up(touch) - - def check_if_reverse_selection(self,last_touch): + + def check_if_reverse_selection(self, last_touch): if self.span_orientation == 'vertical': if last_touch.x > self.first_touch[0]: return else: - self.pos[0] = last_touch.x + 5 - self.size[0] = abs(self.size[0]) #self.first_touch[0] - last_touch.x - + self.pos[0] = last_touch.x + 5 + # self.first_touch[0] - last_touch.x + self.size[0] = abs(self.size[0]) + elif self.span_orientation == 'horizontal': if last_touch.y > self.first_touch[1]: return else: - self.pos[1] = last_touch.y - 5 + self.pos[1] = last_touch.y - 5 self.size[1] = abs(self.size[1]) - + else: return def reset_selection(self): - self.pos = (0,0) - self.size = (dp(0.01),dp(0.01)) - self.opacity=0 + self.pos = (0, 0) + self.size = (dp(0.01), dp(0.01)) + self.opacity = 0 - def _get_box_data(self): - trans = self.ax.transData.inverted() - #get box 4points xis data - x0 = self.to_window(*self.pos)[0]-self.figure_wgt.pos[0] - y0 = self.to_window(*self.pos)[1]-self.figure_wgt.pos[1] - x1 = self.to_window(*self.pos)[0]-self.figure_wgt.pos[0] - y1 = self.to_window(*self.pos)[1] + self.height-self.figure_wgt.pos[1] - x3 = self.to_window(*self.pos)[0] + self.width-self.figure_wgt.pos[0] - y3 = self.to_window(*self.pos)[1]-self.figure_wgt.pos[1] - x2 = self.to_window(*self.pos)[0] + self.width -self.figure_wgt.pos[0] - y2 = self.to_window(*self.pos)[1] + self.height -self.figure_wgt.pos[1] - - - x0_box, y0_box = trans.transform_point((x0, y0)) + trans = self.ax.transData.inverted() + # get box 4points xis data + x0 = self.to_window(*self.pos)[0] - self.figure_wgt.pos[0] + y0 = self.to_window(*self.pos)[1] - self.figure_wgt.pos[1] + x1 = self.to_window(*self.pos)[0] - self.figure_wgt.pos[0] + y1 = self.to_window(*self.pos)[1] + \ + self.height - self.figure_wgt.pos[1] + x3 = self.to_window(*self.pos)[0] + self.width - self.figure_wgt.pos[0] + y3 = self.to_window(*self.pos)[1] - self.figure_wgt.pos[1] + x2 = self.to_window(*self.pos)[0] + self.width - self.figure_wgt.pos[0] + y2 = self.to_window(*self.pos)[1] + \ + self.height - self.figure_wgt.pos[1] + + x0_box, y0_box = trans.transform_point((x0, y0)) x1_box, y1_box = trans.transform_point((x1, y1)) x2_box, y2_box = trans.transform_point((x2, y2)) x3_box, y3_box = trans.transform_point((x3, y3)) - verts=[] + verts = [] verts.append((x0_box, y0_box)) verts.append((x1_box, y1_box)) verts.append((x2_box, y2_box)) verts.append((x3_box, y3_box)) - + if self.span_orientation == 'vertical': if self.collection: - ymin = np.nanmin(self.xys[:,1]) - ymax = np.nanmax(self.xys[:,1]) + ymin = np.nanmin(self.xys[:, 1]) + ymax = np.nanmax(self.xys[:, 1]) if self.line: ydata = self.line.get_ydata() ymin = np.nanmin(ydata) - ymax = np.nanmax(ydata) - - verts[0] = (verts[0][0],ymin-1) - verts[3] = (verts[3][0],ymin-1) - verts[1] = (verts[1][0],ymax+1) - verts[2] = (verts[2][0],ymax+1) - + ymax = np.nanmax(ydata) + + verts[0] = (verts[0][0], ymin - 1) + verts[3] = (verts[3][0], ymin - 1) + verts[1] = (verts[1][0], ymax + 1) + verts[2] = (verts[2][0], ymax + 1) + elif self.span_orientation == 'horizontal': if self.collection: - xmin = np.nanmin(self.xys[:,1]) - xmax = np.nanmax(self.xys[:,1]) + xmin = np.nanmin(self.xys[:, 1]) + xmax = np.nanmax(self.xys[:, 1]) if self.line: - xdata = self.line.get_xdata() + xdata = self.line.get_xdata() xmin = np.nanmin(xdata) xmax = np.nanmax(xdata) - - verts[0] = (xmin-1,verts[0][1]) - verts[1] = (xmin-1,verts[1][1]) - verts[2] = (xmax+1,verts[2][1]) - verts[3] = (xmax+1 ,verts[3][1]) - + + verts[0] = (xmin - 1, verts[0][1]) + verts[1] = (xmin - 1, verts[1][1]) + verts[2] = (xmax + 1, verts[2][1]) + verts[3] = (xmax + 1, verts[3][1]) return verts def onselect(self, verts): path = Path(verts) if self.collection: - self.ind = np.nonzero(path.contains_points(self.xys))[0] #xys collection.get_offsets() + self.ind = np.nonzero(path.contains_points(self.xys))[ + 0] # xys collection.get_offsets() self.fc[:, -1] = self.alpha_other self.fc[self.ind, -1] = 1 self.collection.set_facecolors(self.fc) if self.line: - xdata,ydata = self.line.get_xydata().T - - #matplotlib method if data is sorted + xdata, ydata = self.line.get_xydata().T + + # matplotlib method if data is sorted # indmin, indmax = np.searchsorted(x, (xmin, xmax)) # indmax = min(len(x) - 1, indmax) - + # region_x = x[indmin:indmax] # region_y = y[indmin:indmax] - - self.ind_line = np.nonzero(path.contains_points(np.array([xdata,ydata]).T))[0] + + self.ind_line = np.nonzero( + path.contains_points(np.array([xdata, ydata]).T))[0] self.figure_wgt.figure.canvas.draw_idle() if self.callback: self.callback(self) - - def set_callback(self,callback): - self.callback=callback - def set_callback_clear(self,callback): - self.callback_clear=callback - + def set_callback(self, callback): + self.callback = callback + + def set_callback_clear(self, callback): + self.callback_clear = callback + def clear_selection(self): if self.collection: @@ -4509,9 +4639,10 @@ def clear_selection(self): self.fc[:, -1] = 1 self.collection.set_facecolors(self.fc) if self.line: - self.ind_line=[] - + self.ind_line = [] + self.reset_selection() self.figure_wgt.figure.canvas.draw_idle() - -Builder.load_string(kv) \ No newline at end of file + + +Builder.load_string(kv) From eddc3d178944f3e444b26561d918bcd5038d1987 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 22 Oct 2025 02:54:22 +0000 Subject: [PATCH 07/14] Fix continuation line and comment formatting issues Co-authored-by: mp-007 <19823482+mp-007@users.noreply.github.com> --- kivy_matplotlib_widget/uix/graph_widget.py | 8 ++++---- kivy_matplotlib_widget/uix/graph_widget_crop_factor.py | 8 ++++---- kivy_matplotlib_widget/uix/graph_widget_scatter.py | 8 ++++---- kivy_matplotlib_widget/uix/graph_widget_twinx.py | 8 ++++---- kivy_matplotlib_widget/uix/legend_widget.py | 10 +++++----- 5 files changed, 21 insertions(+), 21 deletions(-) diff --git a/kivy_matplotlib_widget/uix/graph_widget.py b/kivy_matplotlib_widget/uix/graph_widget.py index 6c63a0f..726b5c2 100644 --- a/kivy_matplotlib_widget/uix/graph_widget.py +++ b/kivy_matplotlib_widget/uix/graph_widget.py @@ -1190,10 +1190,10 @@ def on_touch_up(self, event): event.ungrab(self) del self._last_touch_pos[event] self._touches.remove(event) - if self.touch_mode == 'pan' or self.touch_mode == 'zoombox' or \ - self.touch_mode == 'pan_x' or self.touch_mode == 'pan_y' \ - or self.touch_mode == 'adjust_x' or self.touch_mode == 'adjust_y' \ - or self.touch_mode == 'minmax': + if (self.touch_mode == 'pan' or self.touch_mode == 'zoombox' or + self.touch_mode == 'pan_x' or self.touch_mode == 'pan_y' or + self.touch_mode == 'adjust_x' or self.touch_mode == 'adjust_y' or + self.touch_mode == 'minmax'): self.push_current() if self.interactive_axis: diff --git a/kivy_matplotlib_widget/uix/graph_widget_crop_factor.py b/kivy_matplotlib_widget/uix/graph_widget_crop_factor.py index 6e54ab8..ddb7e2e 100644 --- a/kivy_matplotlib_widget/uix/graph_widget_crop_factor.py +++ b/kivy_matplotlib_widget/uix/graph_widget_crop_factor.py @@ -468,10 +468,10 @@ def on_touch_up(self, event): event.ungrab(self) del self._last_touch_pos[event] self._touches.remove(event) - if self.touch_mode == 'pan' or self.touch_mode == 'zoombox' or \ - self.touch_mode == 'pan_x' or self.touch_mode == 'pan_y' \ - or self.touch_mode == 'adjust_x' or self.touch_mode == 'adjust_y' \ - or self.touch_mode == 'minmax': + if (self.touch_mode == 'pan' or self.touch_mode == 'zoombox' or + self.touch_mode == 'pan_x' or self.touch_mode == 'pan_y' or + self.touch_mode == 'adjust_x' or self.touch_mode == 'adjust_y' or + self.touch_mode == 'minmax'): self.push_current() if self.interactive_axis: diff --git a/kivy_matplotlib_widget/uix/graph_widget_scatter.py b/kivy_matplotlib_widget/uix/graph_widget_scatter.py index ac1d794..789460b 100644 --- a/kivy_matplotlib_widget/uix/graph_widget_scatter.py +++ b/kivy_matplotlib_widget/uix/graph_widget_scatter.py @@ -1217,10 +1217,10 @@ def on_touch_up(self, event): event.ungrab(self) del self._last_touch_pos[event] self._touches.remove(event) - if self.touch_mode == 'pan' or self.touch_mode == 'zoombox' or \ - self.touch_mode == 'pan_x' or self.touch_mode == 'pan_y' \ - or self.touch_mode == 'adjust_x' or self.touch_mode == 'adjust_y' \ - or self.touch_mode == 'minmax': + if (self.touch_mode == 'pan' or self.touch_mode == 'zoombox' or + self.touch_mode == 'pan_x' or self.touch_mode == 'pan_y' or + self.touch_mode == 'adjust_x' or self.touch_mode == 'adjust_y' or + self.touch_mode == 'minmax'): self.push_current() if self.interactive_axis: if self.touch_mode == 'pan_x' or self.touch_mode == 'pan_y' \ diff --git a/kivy_matplotlib_widget/uix/graph_widget_twinx.py b/kivy_matplotlib_widget/uix/graph_widget_twinx.py index 73f7009..6e07e72 100644 --- a/kivy_matplotlib_widget/uix/graph_widget_twinx.py +++ b/kivy_matplotlib_widget/uix/graph_widget_twinx.py @@ -1370,10 +1370,10 @@ def on_touch_up(self, event): event.ungrab(self) del self._last_touch_pos[event] self._touches.remove(event) - if self.touch_mode == 'pan' or self.touch_mode == 'zoombox' or \ - self.touch_mode == 'pan_x' or self.touch_mode == 'pan_y' \ - or self.touch_mode == 'adjust_x' or self.touch_mode == 'adjust_y' \ - or self.touch_mode == 'minmax': + if (self.touch_mode == 'pan' or self.touch_mode == 'zoombox' or + self.touch_mode == 'pan_x' or self.touch_mode == 'pan_y' or + self.touch_mode == 'adjust_x' or self.touch_mode == 'adjust_y' or + self.touch_mode == 'minmax'): self.push_current() if self.interactive_axis: if self.touch_mode == 'pan_x' or self.touch_mode == 'pan_y' \ diff --git a/kivy_matplotlib_widget/uix/legend_widget.py b/kivy_matplotlib_widget/uix/legend_widget.py index 8c69fae..7f12505 100644 --- a/kivy_matplotlib_widget/uix/legend_widget.py +++ b/kivy_matplotlib_widget/uix/legend_widget.py @@ -62,7 +62,7 @@ def __init__(self, **kwargs): # in self._persistent_pos[] and use that when the current value is # required. - ### touch down ### + # touch down def on_touch_down(self, touch): if self.collide_point(touch.x, touch.y): @@ -97,7 +97,7 @@ def on_touch_down(self, touch): return super().on_touch_down(touch) - ### touch up ### + # touch up def on_touch_up(self, touch): if touch in self._touches: @@ -120,7 +120,7 @@ def on_touch_up(self, touch): ############################################ # - ### single tap clock ### + # single tap clock def _single_tap_event(self, touch, x, y, dt): if self._gesture_state == 'Dont Know': if not self._long_press_schedule: @@ -133,12 +133,12 @@ def _not_single_tap(self): Clock.unschedule(self._single_tap_schedule) self._single_tap_schedule = None - ### Every result is in the self frame ### + # Every result is in the self frame def _pos_to_widget(self, x, y): return (x - self.x, y - self.y) - ### gesture utilities ### + # gesture utilities def _remove_gesture(self, touch): if touch and len(self._touches): From acc3c4c565bf84a2aff33e99fe9027d9efa8753e Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 22 Oct 2025 02:56:52 +0000 Subject: [PATCH 08/14] Fix line length violations in imports and registrations Co-authored-by: mp-007 <19823482+mp-007@users.noreply.github.com> --- kivy_matplotlib_widget/factory_registers.py | 18 ++++++++++++------ .../tools/interactive_converter.py | 12 +++++++++--- 2 files changed, 21 insertions(+), 9 deletions(-) diff --git a/kivy_matplotlib_widget/factory_registers.py b/kivy_matplotlib_widget/factory_registers.py index a0cc750..374e443 100644 --- a/kivy_matplotlib_widget/factory_registers.py +++ b/kivy_matplotlib_widget/factory_registers.py @@ -3,14 +3,20 @@ r = Factory.register r("MatplotFigure", module="kivy_matplotlib_widget.uix.graph_widget") -r("MatplotFigureScatter", module="kivy_matplotlib_widget.uix.graph_widget_scatter") +r("MatplotFigureScatter", + module="kivy_matplotlib_widget.uix.graph_widget_scatter") r("MatplotFigure3D", module="kivy_matplotlib_widget.uix.graph_widget_3d") r("MatplotFigure3DLayout", module="kivy_matplotlib_widget.uix.graph_widget_3d") -r("MatplotFigureGeneral", module="kivy_matplotlib_widget.uix.graph_widget_general") +r("MatplotFigureGeneral", + module="kivy_matplotlib_widget.uix.graph_widget_general") r("MatplotFigureTwinx", module="kivy_matplotlib_widget.uix.graph_widget_twinx") -r("MatplotFigureSubplot", module="kivy_matplotlib_widget.uix.graph_subplot_widget") -r("MatplotFigureCropFactor", module="kivy_matplotlib_widget.uix.graph_widget_crop_factor") -r("MatplotNavToolbar", module="kivy_matplotlib_widget.uix.navigation_bar_widget") -r("KivyMatplotNavToolbar", module="kivy_matplotlib_widget.uix.navigation_bar_widget") +r("MatplotFigureSubplot", + module="kivy_matplotlib_widget.uix.graph_subplot_widget") +r("MatplotFigureCropFactor", + module="kivy_matplotlib_widget.uix.graph_widget_crop_factor") +r("MatplotNavToolbar", + module="kivy_matplotlib_widget.uix.navigation_bar_widget") +r("KivyMatplotNavToolbar", + module="kivy_matplotlib_widget.uix.navigation_bar_widget") r("LegendRv", module="kivy_matplotlib_widget.uix.legend_widget") r("LegendRvHorizontal", module="kivy_matplotlib_widget.uix.legend_widget") diff --git a/kivy_matplotlib_widget/tools/interactive_converter.py b/kivy_matplotlib_widget/tools/interactive_converter.py index d34557d..1a93c85 100644 --- a/kivy_matplotlib_widget/tools/interactive_converter.py +++ b/kivy_matplotlib_widget/tools/interactive_converter.py @@ -1,9 +1,15 @@ -from kivy_matplotlib_widget.uix.hover_widget import add_hover, BaseHoverFloatLayout, TagCompareHover, PlotlyHover -from kivy.properties import ColorProperty, NumericProperty, StringProperty, BooleanProperty +from kivy_matplotlib_widget.uix.hover_widget import ( + add_hover, BaseHoverFloatLayout, TagCompareHover, PlotlyHover +) +from kivy.properties import ( + ColorProperty, NumericProperty, StringProperty, BooleanProperty +) import multiprocessing as mp from kivy_matplotlib_widget.uix.minmax_widget import add_minmax -from kivy_matplotlib_widget.uix.legend_widget import MatplotlibInteractiveLegend +from kivy_matplotlib_widget.uix.legend_widget import ( + MatplotlibInteractiveLegend +) import matplotlib.pyplot as plt from kivy.core.window import Window from kivy.metrics import dp From 867bd7f9994f9ba6ba9d2b69c6ee8b629c305ecf Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 22 Oct 2025 02:59:17 +0000 Subject: [PATCH 09/14] Apply black formatter to fix majority of PEP8 violations Co-authored-by: mp-007 <19823482+mp-007@users.noreply.github.com> --- kivy_matplotlib_widget/__init__.py | 5 +- kivy_matplotlib_widget/factory_registers.py | 36 +- .../tools/clipboard_tool.py | 51 +- kivy_matplotlib_widget/tools/cursors.py | 100 +- .../tools/interactive_converter.py | 132 +- kivy_matplotlib_widget/tools/pick_info.py | 318 ++- kivy_matplotlib_widget/uix/__init__.py | 3 +- .../uix/graph_subplot_widget.py | 1386 +++++++---- kivy_matplotlib_widget/uix/graph_widget.py | 1377 +++++++---- kivy_matplotlib_widget/uix/graph_widget_3d.py | 287 ++- .../uix/graph_widget_crop_factor.py | 728 +++--- .../uix/graph_widget_general.py | 78 +- .../uix/graph_widget_scatter.py | 1238 ++++++---- .../uix/graph_widget_twinx.py | 2070 +++++++++++------ kivy_matplotlib_widget/uix/hover_widget.py | 200 +- kivy_matplotlib_widget/uix/legend_widget.py | 475 ++-- kivy_matplotlib_widget/uix/minmax_widget.py | 132 +- .../uix/navigation_bar_widget.py | 181 +- kivy_matplotlib_widget/uix/selector_widget.py | 1272 +++++----- 19 files changed, 6273 insertions(+), 3796 deletions(-) diff --git a/kivy_matplotlib_widget/__init__.py b/kivy_matplotlib_widget/__init__.py index b510726..0a90c23 100644 --- a/kivy_matplotlib_widget/__init__.py +++ b/kivy_matplotlib_widget/__init__.py @@ -12,9 +12,8 @@ fonts_path = os.path.join(path, f"fonts{os.sep}") """Path to fonts directory.""" LabelBase.register( - name="NavigationIcons", - fn_regular=fonts_path + - "NavigationIcons.ttf") + name="NavigationIcons", fn_regular=fonts_path + "NavigationIcons.ttf" +) import kivy_matplotlib_widget.factory_registers # NOQA diff --git a/kivy_matplotlib_widget/factory_registers.py b/kivy_matplotlib_widget/factory_registers.py index 374e443..05db097 100644 --- a/kivy_matplotlib_widget/factory_registers.py +++ b/kivy_matplotlib_widget/factory_registers.py @@ -3,20 +3,32 @@ r = Factory.register r("MatplotFigure", module="kivy_matplotlib_widget.uix.graph_widget") -r("MatplotFigureScatter", - module="kivy_matplotlib_widget.uix.graph_widget_scatter") +r( + "MatplotFigureScatter", + module="kivy_matplotlib_widget.uix.graph_widget_scatter", +) r("MatplotFigure3D", module="kivy_matplotlib_widget.uix.graph_widget_3d") r("MatplotFigure3DLayout", module="kivy_matplotlib_widget.uix.graph_widget_3d") -r("MatplotFigureGeneral", - module="kivy_matplotlib_widget.uix.graph_widget_general") +r( + "MatplotFigureGeneral", + module="kivy_matplotlib_widget.uix.graph_widget_general", +) r("MatplotFigureTwinx", module="kivy_matplotlib_widget.uix.graph_widget_twinx") -r("MatplotFigureSubplot", - module="kivy_matplotlib_widget.uix.graph_subplot_widget") -r("MatplotFigureCropFactor", - module="kivy_matplotlib_widget.uix.graph_widget_crop_factor") -r("MatplotNavToolbar", - module="kivy_matplotlib_widget.uix.navigation_bar_widget") -r("KivyMatplotNavToolbar", - module="kivy_matplotlib_widget.uix.navigation_bar_widget") +r( + "MatplotFigureSubplot", + module="kivy_matplotlib_widget.uix.graph_subplot_widget", +) +r( + "MatplotFigureCropFactor", + module="kivy_matplotlib_widget.uix.graph_widget_crop_factor", +) +r( + "MatplotNavToolbar", + module="kivy_matplotlib_widget.uix.navigation_bar_widget", +) +r( + "KivyMatplotNavToolbar", + module="kivy_matplotlib_widget.uix.navigation_bar_widget", +) r("LegendRv", module="kivy_matplotlib_widget.uix.legend_widget") r("LegendRvHorizontal", module="kivy_matplotlib_widget.uix.legend_widget") diff --git a/kivy_matplotlib_widget/tools/clipboard_tool.py b/kivy_matplotlib_widget/tools/clipboard_tool.py index 2086834..14e84c8 100644 --- a/kivy_matplotlib_widget/tools/clipboard_tool.py +++ b/kivy_matplotlib_widget/tools/clipboard_tool.py @@ -16,16 +16,16 @@ from io import BytesIO from PIL import Image as PILImage -if platform == 'win': +if platform == "win": import win32clipboard -elif platform == 'linux': +elif platform == "linux": """ used xclip to copy to clipboard """ import subprocess import tempfile -elif platform == 'macosx': +elif platform == "macosx": """ Appkit come with pyobjc """ @@ -38,7 +38,8 @@ def image2clipboard(widget): - if platform == 'win': + if platform == "win": + def send_to_clipboard(clip_type, data): win32clipboard.OpenClipboard() win32clipboard.EmptyClipboard() @@ -46,9 +47,9 @@ def send_to_clipboard(clip_type, data): win32clipboard.CloseClipboard() img = widget.export_as_image() # export widget as image - pil_img = PILImage.frombytes('RGBA', - img.texture.size, - img.texture.pixels) + pil_img = PILImage.frombytes( + "RGBA", img.texture.size, img.texture.pixels + ) output = BytesIO() pil_img.convert("RGB").save(output, "BMP") @@ -56,7 +57,7 @@ def send_to_clipboard(clip_type, data): output.close() send_to_clipboard(win32clipboard.CF_DIB, data) - elif platform == 'linux': + elif platform == "linux": def _copy_linux_xclip(image): """On Linux, copy the `image` to the clipboard. The `image` arg can either be @@ -64,26 +65,30 @@ def _copy_linux_xclip(image): """ with tempfile.NamedTemporaryFile() as temp_file_obj: - image.save(temp_file_obj.name, format='png') - subprocess.run(['xclip', - '-selection', - 'clipboard', - '-t', - 'image/png', - '-i', - temp_file_obj.name]) + image.save(temp_file_obj.name, format="png") + subprocess.run( + [ + "xclip", + "-selection", + "clipboard", + "-t", + "image/png", + "-i", + temp_file_obj.name, + ] + ) img = widget.export_as_image() # export widget as image - pil_img = PILImage.frombytes('RGBA', - img.texture.size, - img.texture.pixels) + pil_img = PILImage.frombytes( + "RGBA", img.texture.size, img.texture.pixels + ) _copy_linux_xclip(pil_img) - elif platform == 'macosx': + elif platform == "macosx": img = widget.export_as_image() # export widget as image - pil_img = PILImage.frombytes('RGBA', - img.texture.size, - img.texture.pixels) + pil_img = PILImage.frombytes( + "RGBA", img.texture.size, img.texture.pixels + ) output = BytesIO() pil_img.save(output, format="PNG") data = output.getvalue() diff --git a/kivy_matplotlib_widget/tools/cursors.py b/kivy_matplotlib_widget/tools/cursors.py index 83faeb6..2842381 100644 --- a/kivy_matplotlib_widget/tools/cursors.py +++ b/kivy_matplotlib_widget/tools/cursors.py @@ -42,17 +42,23 @@ def _is_alive(artist): and artist.axes # `cla()` clears `.axes` since matplotlib/matplotlib#24627 (3.7); # iterating over subartists can be very slow. - and (getattr(mpl, "__version_info__", ()) >= (3, 7) - or (artist.container in artist.axes.containers - if isinstance(artist, pick_info.ContainerArtist) else - artist in _iter_axes_subartists(artist.axes)))) + and ( + getattr(mpl, "__version_info__", ()) >= (3, 7) + or ( + artist.container in artist.axes.containers + if isinstance(artist, pick_info.ContainerArtist) + else artist in _iter_axes_subartists(artist.axes) + ) + ) + ) def _reassigned_axes_event(event, ax): """Reassign *event* to *ax*.""" event = copy.copy(event) - event.xdata, event.ydata = ( - ax.transData.inverted().transform((event.x, event.y))) + event.xdata, event.ydata = ax.transData.inverted().transform( + (event.x, event.y) + ) return event @@ -68,11 +74,7 @@ class Cursor: _keep_alive = WeakKeyDictionary() - def __init__(self, - artists, - *, - multiple=False, - highlight=False): + def __init__(self, artists, *, multiple=False, highlight=False): """ Construct a cursor. @@ -125,17 +127,22 @@ def selections(self): r"""The tuple of current `Selection`\s.""" for sel in self._selections: if sel.annotation.axes is None: - raise RuntimeError("Annotation unexpectedly removed; " - "use 'cursor.remove_selection' instead") + raise RuntimeError( + "Annotation unexpectedly removed; " + "use 'cursor.remove_selection' instead" + ) return tuple(self._selections) def _get_figure(self, aoc): """Return the parent figure of artist-or-container *aoc*.""" if isinstance(aoc, Container): try: - ca, = {artist for artist in (ref() for ref in self._artists) - if isinstance(artist, pick_info.ContainerArtist) - and artist.container is aoc} + (ca,) = { + artist + for artist in (ref() for ref in self._artists) + if isinstance(artist, pick_info.ContainerArtist) + and artist.container is aoc + } except ValueError: raise ValueError(f"Cannot find parent figure of {aoc}") return ca.figure @@ -146,9 +153,12 @@ def _get_axes(self, aoc): """Return the parent axes of artist-or-container *aoc*.""" if isinstance(aoc, Container): try: - ca, = {artist for artist in (ref() for ref in self._artists) - if isinstance(artist, pick_info.ContainerArtist) - and artist.container is aoc} + (ca,) = { + artist + for artist in (ref() for ref in self._artists) + if isinstance(artist, pick_info.ContainerArtist) + and artist.container is aoc + } except ValueError: raise ValueError(f"Cannot find parent axes of {aoc}") return ca.axes @@ -167,8 +177,10 @@ def add_highlight(self, artist, *args, **kwargs): method) in order to ensure cleanup upon deselection. """ hl = pick_info.make_highlight( - artist, *args, - **{"highlight_kwargs": self.highlight_kwargs, **kwargs}) + artist, + *args, + **{"highlight_kwargs": self.highlight_kwargs, **kwargs}, + ) if hl: artist.axes.add_artist(hl) return hl @@ -210,12 +222,16 @@ def add_highlight(self, artist, *args, **kwargs): def xy_event(self, event): # Work around lack of support for twinned axes. - per_axes_event = {ax: _reassigned_axes_event(event, ax) - for ax in {artist.axes for artist in self.artists}} + per_axes_event = { + ax: _reassigned_axes_event(event, ax) + for ax in {artist.axes for artist in self.artists} + } pis = [] for artist in self.artists: - if (artist.axes is None # Removed or figure-level artist. - or not artist.get_visible()): + if ( + artist.axes is None # Removed or figure-level artist. + or not artist.get_visible() + ): continue pi = pick_info.compute_pick(artist, per_axes_event[artist.axes]) if pi: @@ -225,11 +241,19 @@ def xy_event(self, event): # rather than not adding the pick_info to pis at all, because in # transient hover mode, selections should be cleared out only when no # candidate picks (including such duplicates) exist at all. - pi = min((pi for pi in pis - if not any((pi.artist, tuple(pi.target)) - == (other.artist, tuple(other.target)) - for other in self._selections)), - key=lambda pi: pi.dist, default=None) + pi = min( + ( + pi + for pi in pis + if not any( + (pi.artist, tuple(pi.target)) + == (other.artist, tuple(other.target)) + for other in self._selections + ) + ), + key=lambda pi: pi.dist, + default=None, + ) if pi: if event.compare_xdata: @@ -237,9 +261,11 @@ def xy_event(self, event): # print(pi) pi_list = [] for pi in pis: - if not any((pi.artist, tuple(pi.target)) - == (other.artist, tuple(other.target)) - for other in self._selections): + if not any( + (pi.artist, tuple(pi.target)) + == (other.artist, tuple(other.target)) + for other in self._selections + ): if pi.dist == min_distance: pi_list.append(pi) return pi_list @@ -273,12 +299,14 @@ def cursor(pltfig, pickables=None, remove_artists=[], **kwargs): # "TypeError: Cursor.__init__() got multiple values for argument 'artists'" if "artists" in kwargs: raise TypeError( - "cursor() got an unexpected keyword argument 'artists'") + "cursor() got an unexpected keyword argument 'artists'" + ) if pickables is None: pickables = [pltfig] - elif (isinstance(pickables, Container) - or not isinstance(pickables, Iterable)): + elif isinstance(pickables, Container) or not isinstance( + pickables, Iterable + ): pickables = [pickables] def iter_unpack_figures(pickables): diff --git a/kivy_matplotlib_widget/tools/interactive_converter.py b/kivy_matplotlib_widget/tools/interactive_converter.py index 1a93c85..709ab75 100644 --- a/kivy_matplotlib_widget/tools/interactive_converter.py +++ b/kivy_matplotlib_widget/tools/interactive_converter.py @@ -1,14 +1,19 @@ - from kivy_matplotlib_widget.uix.hover_widget import ( - add_hover, BaseHoverFloatLayout, TagCompareHover, PlotlyHover + add_hover, + BaseHoverFloatLayout, + TagCompareHover, + PlotlyHover, ) from kivy.properties import ( - ColorProperty, NumericProperty, StringProperty, BooleanProperty + ColorProperty, + NumericProperty, + StringProperty, + BooleanProperty, ) import multiprocessing as mp from kivy_matplotlib_widget.uix.minmax_widget import add_minmax from kivy_matplotlib_widget.uix.legend_widget import ( - MatplotlibInteractiveLegend + MatplotlibInteractiveLegend, ) import matplotlib.pyplot as plt from kivy.core.window import Window @@ -16,14 +21,15 @@ from kivy.app import App from kivy.lang import Builder from kivy.config import Config + # avoid double-click on touch device -Config.set('input', 'mouse', 'mouse,disable_on_activity') +Config.set("input", "mouse", "mouse,disable_on_activity") # disable red dot (user use mouse scroll for zooming) -Config.set('input', 'mouse', 'mouse,multitouch_on_demand') -Config.set('kivy', 'keyboard_mode', '') # disable keyboard mode +Config.set("input", "mouse", "mouse,multitouch_on_demand") +Config.set("kivy", "keyboard_mode", "") # disable keyboard mode -KV = ''' +KV = """ Screen figure_wgt:figure_wgt @@ -55,9 +61,9 @@ hist_range:app.hist_range autoscale_tight:app.autoscale_tight -''' +""" -KV3D = ''' +KV3D = """ Screen figure_wgt_layout:figure_wgt_layout @@ -77,7 +83,7 @@ MatplotFigure3DLayout: id:figure_wgt_layout -''' +""" class GraphApp(App): @@ -92,26 +98,28 @@ class GraphApp(App): hist_range = BooleanProperty(True) autoscale_tight = BooleanProperty(False) - def __init__(self, - figure, - show_cursor_data=True, - hover_widget=PlotlyHover, - compare_hover_widget=TagCompareHover, - compare_hover=False, - legend_instance=None, - custom_handlers=None, - multi_legend=False, - drag_legend=False, - legend_do_scroll_x=True, - disable_interactive_legend=False, - max_hover_rate=5 / 60, - disable_hover=False, - fast_draw=True, - hist_range=True, - autoscale_tight=False, - register_cursor=None, - figsize=None, - **kwargs): + def __init__( + self, + figure, + show_cursor_data=True, + hover_widget=PlotlyHover, + compare_hover_widget=TagCompareHover, + compare_hover=False, + legend_instance=None, + custom_handlers=None, + multi_legend=False, + drag_legend=False, + legend_do_scroll_x=True, + disable_interactive_legend=False, + max_hover_rate=5 / 60, + disable_hover=False, + fast_draw=True, + hist_range=True, + autoscale_tight=False, + register_cursor=None, + figsize=None, + **kwargs, + ): """__init__ function class""" self.figure = figure self.hover_widget = hover_widget @@ -147,7 +155,7 @@ def build(self): return self.screen def on_start(self, *args): - if hasattr(self.figure, 'get'): + if hasattr(self.figure, "get"): figure = self.figure.get()[0] else: figure = self.figure @@ -160,56 +168,65 @@ def on_start(self, *args): if self.register_cursor: self.screen.figure_wgt.register_cursor( - pickables=self.register_cursor) + pickables=self.register_cursor + ) if self.compare_hover: if self.compare_hover_widget: add_hover( self.screen.figure_wgt, - mode='desktop', - hover_type='compare', - hover_widget=self.compare_hover_widget()) + mode="desktop", + hover_type="compare", + hover_widget=self.compare_hover_widget(), + ) else: add_hover( self.screen.figure_wgt, - mode='desktop', - hover_type='compare') + mode="desktop", + hover_type="compare", + ) if not self.disable_hover: if self.hover_widget: add_hover( self.screen.figure_wgt, - mode='desktop', - hover_widget=self.hover_widget()) + mode="desktop", + hover_widget=self.hover_widget(), + ) else: - add_hover(self.screen.figure_wgt, mode='desktop') + add_hover(self.screen.figure_wgt, mode="desktop") add_minmax(self.screen.figure_wgt) if not self.disable_interactive_legend: - if len(self.screen.figure_wgt.figure.axes) > 0 and \ - (self.screen.figure_wgt.figure.axes[0].get_legend() or - self.legend_instance): + if len(self.screen.figure_wgt.figure.axes) > 0 and ( + self.screen.figure_wgt.figure.axes[0].get_legend() + or self.legend_instance + ): if self.multi_legend: for i, current_legend_instance in enumerate( - self.legend_instance): + self.legend_instance + ): if i == 0: MatplotlibInteractiveLegend( self.screen.figure_wgt, legend_instance=current_legend_instance, - custom_handlers=self.custom_handlers[i]) + custom_handlers=self.custom_handlers[i], + ) else: MatplotlibInteractiveLegend( self.screen.figure_wgt, legend_instance=current_legend_instance, custom_handlers=self.custom_handlers[i], - multi_legend=True) + multi_legend=True, + ) else: MatplotlibInteractiveLegend( self.screen.figure_wgt, legend_instance=self.legend_instance, - custom_handlers=self.custom_handlers) + custom_handlers=self.custom_handlers, + ) def app_window(plot_queue, **kwargs): @@ -221,10 +238,7 @@ class GraphApp3D(App): figure = None figsize = None # figure size in pixel. inpu is a tuple ex: (1200,400) - def __init__(self, - figure, - figsize=None, - **kwargs): + def __init__(self, figure, figsize=None, **kwargs): """__init__ function class""" self.figure = figure self.figsize = figsize @@ -242,7 +256,7 @@ def build(self): return self.screen def on_start(self, *args): - if hasattr(self.figure, 'get'): + if hasattr(self.figure, "get"): figure = self.figure.get()[0] else: figure = self.figure @@ -260,14 +274,14 @@ def app_window_3D(plot_queue, **kwargs): def interactive_graph(fig, **kwargs): - """ Interactive grpah using multiprocessing method. + """Interactive grpah using multiprocessing method. function need to be call in if __name__ == "__main__": method """ # Create a queue to pass the Matplotlib instance object plot_queue = mp.Queue() # switch to agg backend - plt.switch_backend('Agg') + plt.switch_backend("Agg") # Put the Matplotlib instance object into the queue plot_queue.put((fig,)) @@ -286,14 +300,14 @@ def interactive_graph3D_ipython(fig, **kwargs): def interactive_graph3D(fig, **kwargs): - """ Interactive grpah using multiprocessing method. + """Interactive grpah using multiprocessing method. function need to be call in if __name__ == "__main__": method """ # Create a queue to pass the Matplotlib instance object plot_queue = mp.Queue() # switch to agg backend - plt.switch_backend('Agg') + plt.switch_backend("Agg") # Put the Matplotlib instance object into the queue plot_queue.put((fig,)) @@ -306,8 +320,8 @@ def interactive_graph3D(fig, **kwargs): if __name__ == "__main__": fig, ax1 = plt.subplots(1, 1) - line1, = ax1.plot([0, 1, 2, 3, 4], [1, 2, 8, 9, 4], label='line1') - line2, = ax1.plot([2, 8, 10, 15], [15, 0, 2, 4], label='line2') + (line1,) = ax1.plot([0, 1, 2, 3, 4], [1, 2, 8, 9, 4], label="line1") + (line2,) = ax1.plot([2, 8, 10, 15], [15, 0, 2, 4], label="line2") ax1.legend() diff --git a/kivy_matplotlib_widget/tools/pick_info.py b/kivy_matplotlib_widget/tools/pick_info.py index 52ff6b7..d9f20c3 100644 --- a/kivy_matplotlib_widget/tools/pick_info.py +++ b/kivy_matplotlib_widget/tools/pick_info.py @@ -25,7 +25,10 @@ from matplotlib.axes import Axes from matplotlib.backend_bases import RendererBase from matplotlib.collections import ( - LineCollection, PatchCollection, PathCollection) + LineCollection, + PatchCollection, + PathCollection, +) from matplotlib.container import BarContainer, ErrorbarContainer, StemContainer from matplotlib.figure import Figure from matplotlib.image import AxesImage @@ -52,6 +55,7 @@ def _register_scatter(): def __init__(self, *args, **kwargs): _nonscatter_pathcollections.add(self) return __init__.__wrapped__(self, *args, **kwargs) + PathCollection.__init__ = __init__ @functools.wraps(Axes.scatter) @@ -60,6 +64,7 @@ def scatter(*args, **kwargs): with suppress(KeyError): _nonscatter_pathcollections.remove(paths) return paths + Axes.scatter = scatter @@ -68,8 +73,10 @@ def scatter(*args, **kwargs): def _is_scatter(artist): - return (isinstance(artist, PathCollection) - and artist not in _nonscatter_pathcollections) + return ( + isinstance(artist, PathCollection) + and artist not in _nonscatter_pathcollections + ) def _artist_in_container(container): @@ -86,7 +93,8 @@ def __init__(self, container): # the ContainerArtist; as no one else strongly references the # ContainerArtist, it will get GC'd whenever the Container is. vars(container).setdefault( - f"_{__class__.__name__}__keep_alive", []).append(self) + f"_{__class__.__name__}__keep_alive", [] + ).append(self) def __str__(self): return f"<{type(self).__name__}({self.container})>" @@ -102,7 +110,8 @@ def get_visible(self): Selection = namedtuple( - "Selection", "artist target_ index dist annotation extras") + "Selection", "artist target_ index dist annotation extras" +) Selection.__doc__ = """ A selection. @@ -117,22 +126,23 @@ def get_visible(self): # artists are already non-comparable. Selection.__eq__ = lambda self, other: self is other Selection.__ne__ = lambda self, other: self is not other -Selection.artist.__doc__ = ( - "The selected artist.") +Selection.artist.__doc__ = "The selected artist." Selection.target_.__doc__ = """ The point picked within the artist, in data coordinates. :meta private: """ Selection.index.__doc__ = ( - "The index of the selected point, within the artist data.") + "The index of the selected point, within the artist data." +) Selection.dist.__doc__ = ( - "The distance from the click to the target, in pixels.") -Selection.annotation.__doc__ = ( - "The instantiated `matplotlib.text.Annotation`.") + "The distance from the click to the target, in pixels." +) +Selection.annotation.__doc__ = "The instantiated `matplotlib.text.Annotation`." Selection.extras.__doc__ = ( "An additional list of artists (e.g., highlighters) that will be cleared " - "at the same time as the annotation.") + "at the same time as the annotation." +) class _Target(np.ndarray): @@ -145,12 +155,14 @@ def __new__(cls, sel): def index(self): warnings.warn( "Selection.target.index is deprecated and will be removed in the " - "future; use Selection.index instead.") + "future; use Selection.index instead." + ) return self._sel.index Selection.target = property( - _Target, doc="The point picked within the artist, in data coordinates.") + _Target, doc="The point picked within the artist, in data coordinates." +) @functools.singledispatch @@ -206,20 +218,20 @@ def post_index(cls, n_pts, index): def mid_index(cls, n_pts, index): i, frac = divmod(index, 1) if i == 0: - frac = .5 + frac / 2 + frac = 0.5 + frac / 2 elif i == 2 * n_pts - 2: # One less line than points. frac = frac / 2 quot, odd = divmod(i, 2) if not odd: - if frac < .5: + if frac < 0.5: i = quot - 1 - x, y = frac + .5, 1 + x, y = frac + 0.5, 1 else: i = quot - x, y = frac - .5, 0 + x, y = frac - 0.5, 0 else: i = quot - x, y = .5, frac + x, y = 0.5, frac return cls(i, x, y) @@ -237,9 +249,12 @@ def _compute_projection_pick(artist, path, xy): needed. """ transform = artist.get_transform().frozen() - tpath = (path.cleaned(transform) if transform.is_affine - # `cleaned` only handles affine transforms. - else transform.transform_path(path).cleaned()) + tpath = ( + path.cleaned(transform) + if transform.is_affine + # `cleaned` only handles affine transforms. + else transform.transform_path(path).cleaned() + ) # `cleaned` should return a path where the first element is `MOVETO`, the # following are `LINETO` or `CLOSEPOLY`, and the last one is `STOP`, i.e. # codes = path.codes @@ -247,8 +262,8 @@ def _compute_projection_pick(artist, path, xy): # assert np.in1d(codes[1:-1], [path.LINETO, path.CLOSEPOLY]).all() vertices = tpath.vertices[:-1] codes = tpath.codes[:-1] - mt_idxs, = (codes == tpath.MOVETO).nonzero() - cp_idxs, = (codes == tpath.CLOSEPOLY).nonzero() + (mt_idxs,) = (codes == tpath.MOVETO).nonzero() + (cp_idxs,) = (codes == tpath.CLOSEPOLY).nonzero() vertices[cp_idxs] = vertices[mt_idxs[mt_idxs.searchsorted(cp_idxs) - 1]] # Unit vectors for each segment. us = vertices[1:] - vertices[:-1] @@ -271,8 +286,9 @@ def _compute_projection_pick(artist, path, xy): return else: target = artist.axes.transData.inverted().transform(projs[argmin]) - index = ((argmin + dot[argmin] / ls[argmin]) - / (path._interpolation_steps / tpath._interpolation_steps)) + index = (argmin + dot[argmin] / ls[argmin]) / ( + path._interpolation_steps / tpath._interpolation_steps + ) return Selection(artist, target, index, ds[argmin], None, None) @@ -291,7 +307,8 @@ def _untransform(orig_xy, screen_xy, ax): return ( orig_xy if ((tr_xy == screen_xy) | np.isnan(tr_xy) & np.isnan(screen_xy)).all() - else ax.transData.inverted().transform(screen_xy)) + else ax.transData.inverted().transform(screen_xy) + ) @compute_pick.register(Line2D) @@ -311,11 +328,11 @@ def _(artist, event): if event.compare_xdata: ds = abs(xy[0] - data_screen_xy[:, 0]) else: - if event.pick_radius_axis == 'both': + if event.pick_radius_axis == "both": ds = np.hypot(*(xy - data_screen_xy).T) - elif event.pick_radius_axis == 'x': + elif event.pick_radius_axis == "x": ds = abs(xy[0] - data_screen_xy[:, 0]) - elif event.pick_radius_axis == 'y': + elif event.pick_radius_axis == "y": ds = abs(xy[1] - data_screen_xy[:, 1]) try: argmin = np.nanargmin(ds) @@ -323,22 +340,29 @@ def _(artist, event): pass else: target = _untransform( # More precise than transforming back. - data_xy[argmin], data_screen_xy[argmin], artist.axes) + data_xy[argmin], data_screen_xy[argmin], artist.axes + ) sels.append( - Selection(artist, target, argmin, ds[argmin], None, None)) + Selection(artist, target, argmin, ds[argmin], None, None) + ) # If lines are visible, find the closest projection. - if (artist.get_linestyle() not in ["None", "none", " ", "", None] - and len(artist.get_xydata()) > 1) and event.projection: + if ( + artist.get_linestyle() not in ["None", "none", " ", "", None] + and len(artist.get_xydata()) > 1 + ) and event.projection: sel = _compute_projection_pick(artist, artist.get_path(), xy) if sel is not None: - sel = sel._replace(index={ - "_draw_lines": lambda _, index: index, - "_draw_steps_pre": Index.pre_index, - "_draw_steps_mid": Index.mid_index, - "_draw_steps_post": Index.post_index}[ - Line2D.drawStyles[artist.get_drawstyle()]]( - len(data_xy), sel.index)) + sel = sel._replace( + index={ + "_draw_lines": lambda _, index: index, + "_draw_steps_pre": Index.pre_index, + "_draw_steps_mid": Index.mid_index, + "_draw_steps_post": Index.post_index, + }[Line2D.drawStyles[artist.get_drawstyle()]]( + len(data_xy), sel.index + ) + ) sels.append(sel) sel = min(sels, key=lambda sel: sel.dist, default=None) return sel if sel and sel.dist < event.pickradius else None @@ -349,7 +373,8 @@ def _(artist, event): @compute_pick.register(Rectangle) def _(artist, event): sel = _compute_projection_pick( - artist, artist.get_path(), (event.x, event.y)) + artist, artist.get_path(), (event.x, event.y) + ) if sel and sel.dist < event.pickradius: return sel @@ -378,7 +403,8 @@ def _(artist, event): argmin = ds.argmin() target = _untransform( - offsets[argmin], offsets_screen[argmin], artist.axes) + offsets[argmin], offsets_screen[argmin], artist.axes + ) # return Selection(artist, target, inds[argmin], ds[argmin], None, # None) sel = Selection(artist, target, argmin, ds[argmin], None, None) @@ -387,13 +413,21 @@ def _(artist, event): return None elif len(paths) and len(offsets): # Note that this won't select implicitly closed paths. - sels = [*filter(None, [ - _compute_projection_pick( - artist, - Affine2D().translate(*offsets[ind % len(offsets)]) - .transform_path(paths[ind % len(paths)]), - (event.x, event.y)) - for ind in range(max(len(offsets), len(paths)))])] + sels = [ + *filter( + None, + [ + _compute_projection_pick( + artist, + Affine2D() + .translate(*offsets[ind % len(offsets)]) + .transform_path(paths[ind % len(paths)]), + (event.x, event.y), + ) + for ind in range(max(len(offsets), len(paths))) + ], + ) + ] if not sels: return None idx = min(range(len(sels)), key=lambda idx: sels[idx].dist) @@ -435,7 +469,8 @@ def _(artist, event): argmin = np.nanargmin(ds) if ds[argmin] < event.pickradius: target = _untransform( - offsets[argmin], offsets_screen[argmin], artist.axes) + offsets[argmin], offsets_screen[argmin], artist.axes + ) return Selection(artist, target, argmin, ds[argmin], None, None) else: return None @@ -454,22 +489,28 @@ def _(artist, event): @compute_pick.register(BarContainer) def _(container, event): try: - (idx, patch), = { - (idx, patch) for idx, patch in enumerate(container.patches) - if patch.contains(event)[0]} + ((idx, patch),) = { + (idx, patch) + for idx, patch in enumerate(container.patches) + if patch.contains(event)[0] + } except ValueError: return if event.projection: target = [event.xdata, event.ydata] if patch.sticky_edges.x: - target[0], = ( - x for x in [patch.get_x(), patch.get_x() + patch.get_width()] - if x not in patch.sticky_edges.x) + (target[0],) = ( + x + for x in [patch.get_x(), patch.get_x() + patch.get_width()] + if x not in patch.sticky_edges.x + ) if patch.sticky_edges.y: - target[1], = ( - y for y in [patch.get_y(), patch.get_y() + patch.get_height()] - if y not in patch.sticky_edges.y) + (target[1],) = ( + y + for y in [patch.get_y(), patch.get_y() + patch.get_height()] + if y not in patch.sticky_edges.y + ) else: x, y, width, height = container[idx].get_bbox().bounds @@ -480,11 +521,11 @@ def _(container, event): @compute_pick.register(Wedge) def _(container, event): try: - ang = (container.theta2 - container.theta1) / 2. + container.theta1 + ang = (container.theta2 - container.theta1) / 2.0 + container.theta1 radius = container.r center_x, center_y = container.center - y = np.sin(np.deg2rad(ang)) * radius * .95 + center_x - x = np.cos(np.deg2rad(ang)) * radius * .95 + center_y + y = np.sin(np.deg2rad(ang)) * radius * 0.95 + center_x + x = np.cos(np.deg2rad(ang)) * radius * 0.95 + center_y except ValueError: return @@ -503,8 +544,10 @@ def _(container, event): sel_data = compute_pick(data_line, event) if data_line else None sel_err = min( filter(None, (compute_pick(err_lc, event) for err_lc in err_lcs)), - key=lambda sel: sel.dist, default=None) - if (sel_data and sel_data.dist < getattr(sel_err, "dist", np.inf)): + key=lambda sel: sel.dist, + default=None, + ) + if sel_data and sel_data.dist < getattr(sel_err, "dist", np.inf): return sel_data elif sel_err: idx, _ = sel_err.index @@ -523,8 +566,10 @@ def _(container, event): if sel: return sel if not isinstance(container.stemlines, LineCollection): - warnings.warn("Only stem plots created with use_line_collection=True " - "are supported.") + warnings.warn( + "Only stem plots created with use_line_collection=True " + "are supported." + ) return sel = compute_pick(container.stemlines, event) if sel: @@ -540,18 +585,29 @@ def _call_with_selection(func=None, *, argname="artist"): return functools.partial(_call_with_selection, argname=argname) wrapped_kwonly_params = [ - param for param in inspect.signature(func).parameters.values() - if param.kind == param.KEYWORD_ONLY] + param + for param in inspect.signature(func).parameters.values() + if param.kind == param.KEYWORD_ONLY + ] sel_sig = inspect.signature(Selection) default_sel_sig = sel_sig.replace( - parameters=[param.replace(default=None) if param.default is param.empty - else param - for param in sel_sig.parameters.values()]) + parameters=[ + ( + param.replace(default=None) + if param.default is param.empty + else param + ) + for param in sel_sig.parameters.values() + ] + ) @functools.wraps(func) def wrapper(*args, **kwargs): - extra_kw = {param.name: kwargs.pop(param.name) - for param in wrapped_kwonly_params if param.name in kwargs} + extra_kw = { + param.name: kwargs.pop(param.name) + for param in wrapped_kwonly_params + if param.name in kwargs + } ba = default_sel_sig.bind(*args, **kwargs) ba.apply_defaults() sel = Selection(*ba.args, **ba.kwargs) @@ -567,10 +623,13 @@ def _format_coord_unspaced(ax, xy): # Un-space-pad, remove empty coordinates from the output of # `format_{x,y}data`, and rejoin with newlines. return "\n".join( - line for line, empty in zip( + line + for line, empty in zip( re.split(",? +", ax.format_coord(*xy)), - itertools.chain(["x=", "y=", "z="], itertools.repeat(None))) - if line != empty).rstrip() + itertools.chain(["x=", "y=", "z="], itertools.repeat(None)), + ) + if line != empty + ).rstrip() @functools.singledispatch @@ -583,7 +642,8 @@ def get_ann_text(sel): classes follow. """ warnings.warn( - f"Annotation support for {type(sel.artist).__name__} is missing.") + f"Annotation support for {type(sel.artist).__name__} is missing." + ) return "" @@ -620,12 +680,14 @@ def _(sel): artist = sel.artist label = artist.get_label() or "" text = _format_coord_unspaced(artist.axes, sel.target) - if (_is_scatter(artist) - # Heuristic: is the artist colormapped? - # Note that this doesn't handle size-mapping (which is more likely - # to involve an arbitrary scaling). - and artist.get_array() is not None - and len(artist.get_array()) == len(artist.get_offsets())): + if ( + _is_scatter(artist) + # Heuristic: is the artist colormapped? + # Note that this doesn't handle size-mapping (which is more likely + # to involve an arbitrary scaling). + and artist.get_array() is not None + and len(artist.get_array()) == len(artist.get_offsets()) + ): value = _format_scalarmappable_value(artist, sel.index) text = f"{text}\n{value}" if re.match("[^_]", label): @@ -651,7 +713,8 @@ def _(sel): artist = sel.artist text = "{}\n{}".format( _format_coord_unspaced(artist.axes, sel.target), - (artist.u[sel.index], artist.v[sel.index])) + (artist.u[sel.index], artist.v[sel.index]), + ) return text @@ -661,7 +724,8 @@ def _(sel): artist = sel.artist text = "{}\n{}".format( _format_coord_unspaced(artist.axes, sel.target), - (artist.U[sel.index], artist.V[sel.index])) + (artist.U[sel.index], artist.V[sel.index]), + ) return text @@ -675,7 +739,8 @@ def _(sel): @_call_with_selection(argname="container") def _(sel): return _format_coord_unspaced( - _artist_in_container(sel.artist).axes, sel.target) + _artist_in_container(sel.artist).axes, sel.target + ) @get_ann_text.register(ErrorbarContainer) @@ -686,13 +751,20 @@ def _(sel): if isinstance(sel.index, Integral): err_lcs = iter(err_lcs) for idx, (dir, has) in enumerate( - zip("xy", [sel.artist.has_xerr, sel.artist.has_yerr])): + zip("xy", [sel.artist.has_xerr, sel.artist.has_yerr]) + ): if has: - err = (next(err_lcs).get_paths()[sel.index].vertices - - data_line.get_xydata()[sel.index])[:, idx] - err_s = [getattr(_artist_in_container(sel.artist).axes, - f"format_{dir}data")(e).rstrip() - for e in err] + err = ( + next(err_lcs).get_paths()[sel.index].vertices + - data_line.get_xydata()[sel.index] + )[:, idx] + err_s = [ + getattr( + _artist_in_container(sel.artist).axes, + f"format_{dir}data", + )(e).rstrip() + for e in err + ] # We'd normally want to check err.sum() == 0, but that can run # into fp inaccuracies. signs = "+-\N{MINUS SIGN}" @@ -703,9 +775,11 @@ def _(sel): # rendering as the string is mathtext, but allows keeping # the same tests across Matplotlib versions that use # unicode minus and those that don't. - err_s = [("+" if not s.startswith(tuple(signs)) else "") - + s.replace("\N{MINUS SIGN}", "-") - for s in err_s] + err_s = [ + ("+" if not s.startswith(tuple(signs)) else "") + + s.replace("\N{MINUS SIGN}", "-") + for s in err_s + ] repl = r"\1=$\2_{%s}^{%s}$\3" % tuple(err_s) ann_text = re.sub(f"({dir})=(.*)(\n?)", repl, ann_text) return ann_text @@ -752,9 +826,13 @@ def _(sel, *, key): data_xy = sel.artist.get_xydata() return _move_within_points( sel, - _untransform(data_xy, sel.artist.get_transform().transform(data_xy), - sel.artist.axes), - key=key) + _untransform( + data_xy, + sel.artist.get_transform().transform(data_xy), + sel.artist.axes, + ), + key=key, + ) @move.register(PathCollection) @@ -765,9 +843,12 @@ def _(sel, *, key): return _move_within_points( sel, _untransform( - offsets, sel.artist.get_offset_transform().transform(offsets), - sel.artist.axes), - key=key) + offsets, + sel.artist.get_offset_transform().transform(offsets), + sel.artist.axes, + ), + key=key, + ) else: return sel @@ -776,25 +857,32 @@ def _(sel, *, key): @_call_with_selection def _(sel, *, key): ns = sel.artist.get_array().shape[:2] - delta = ( - {"left": [0, -1], "right": [0, +1], "down": [-1, 0], "up": [+1, 0]}[ - key] - * np.array([-1 if sel.artist.axes.yaxis.get_inverted() else +1, - -1 if sel.artist.axes.xaxis.get_inverted() else +1])) + delta = { + "left": [0, -1], + "right": [0, +1], + "down": [-1, 0], + "up": [+1, 0], + }[key] * np.array( + [ + -1 if sel.artist.axes.yaxis.get_inverted() else +1, + -1 if sel.artist.axes.xaxis.get_inverted() else +1, + ] + ) idxs = (sel.index + delta) % ns xmin, xmax, ymin, ymax = sel.artist.get_extent() if sel.artist.origin == "upper": ymin, ymax = ymax, ymin low, high = np.array([[xmin, ymin], [xmax, ymax]]) - target = ((idxs + .5) / ns)[::-1] * (high - low) + low + target = ((idxs + 0.5) / ns)[::-1] * (high - low) + low return sel._replace(target_=target, index=tuple(idxs)) @move.register(ContainerArtist) @_call_with_selection def _(sel, *, key): - return (move(*sel._replace(artist=sel.artist.container), key=key) - ._replace(artist=sel.artist)) + return move(*sel._replace(artist=sel.artist.container), key=key)._replace( + artist=sel.artist + ) @move.register(ErrorbarContainer) @@ -814,7 +902,8 @@ def make_highlight(sel, *, highlight_kwargs): classes follow. """ warnings.warn( - f"Highlight support for {type(sel.artist).__name__} is missing.") + f"Highlight support for {type(sel.artist).__name__} is missing." + ) def _set_valid_props(artist, kwargs): @@ -836,7 +925,10 @@ def _(sel, *, highlight_kwargs): def _(sel, *, highlight_kwargs): hl = copy.copy(sel.artist) offsets = hl.get_offsets() - hl.set_offsets(np.where( - np.arange(len(offsets))[:, None] == sel.index, offsets, np.nan)) + hl.set_offsets( + np.where( + np.arange(len(offsets))[:, None] == sel.index, offsets, np.nan + ) + ) _set_valid_props(hl, highlight_kwargs) return hl diff --git a/kivy_matplotlib_widget/uix/__init__.py b/kivy_matplotlib_widget/uix/__init__.py index bc97c7e..92349c4 100644 --- a/kivy_matplotlib_widget/uix/__init__.py +++ b/kivy_matplotlib_widget/uix/__init__.py @@ -1,3 +1,4 @@ # remove font_manager "debug" from matplotib import logging -logging.getLogger('matplotlib.font_manager').disabled = True + +logging.getLogger("matplotlib.font_manager").disabled = True diff --git a/kivy_matplotlib_widget/uix/graph_subplot_widget.py b/kivy_matplotlib_widget/uix/graph_subplot_widget.py index 2faf98c..3130bac 100644 --- a/kivy_matplotlib_widget/uix/graph_subplot_widget.py +++ b/kivy_matplotlib_widget/uix/graph_subplot_widget.py @@ -1,7 +1,10 @@ -""" Custom MatplotFigure -""" +"""Custom MatplotFigure""" -from kivy_matplotlib_widget.uix.graph_widget import _FigureCanvas, MatplotFigure, MatplotlibEvent +from kivy_matplotlib_widget.uix.graph_widget import ( + _FigureCanvas, + MatplotFigure, + MatplotlibEvent, +) from kivy.properties import NumericProperty, BooleanProperty, OptionProperty from matplotlib.container import BarContainer import matplotlib.transforms as mtransforms @@ -18,7 +21,8 @@ import time import matplotlib -matplotlib.use('Agg') + +matplotlib.use("Agg") def _iter_axes_subartists(ax): @@ -27,8 +31,8 @@ def _iter_axes_subartists(ax): class MatplotFigureSubplot(MatplotFigure): - """Custom MatplotFigure - """ + """Custom MatplotFigure""" + cursor_cls = None pickradius = NumericProperty(dp(50)) projection = BooleanProperty(False) @@ -60,38 +64,54 @@ def my_in_axes(self, ax, mouseevent): ylabelsize = self.interactive_axis_pad # get label left/right information - xlabelbottom = ax.xaxis._major_tick_kw.get('tick1On') - xlabeltop = ax.xaxis._major_tick_kw.get('tick2On') - ylabelleft = ax.yaxis._major_tick_kw.get('tick1On') - ylabelright = ax.yaxis._major_tick_kw.get('tick2On') + xlabelbottom = ax.xaxis._major_tick_kw.get("tick1On") + xlabeltop = ax.xaxis._major_tick_kw.get("tick2On") + ylabelleft = ax.yaxis._major_tick_kw.get("tick1On") + ylabelright = ax.yaxis._major_tick_kw.get("tick2On") # check if tick zone # y left axis - if ylabelleft and mouseevent.x > ax.bbox.bounds[0] - ylabelsize and \ - mouseevent.x < ax.bbox.bounds[0] and \ - mouseevent.y < ax.bbox.bounds[1] + ax.bbox.bounds[3] and \ - mouseevent.y > ax.bbox.bounds[1]: + if ( + ylabelleft + and mouseevent.x > ax.bbox.bounds[0] - ylabelsize + and mouseevent.x < ax.bbox.bounds[0] + and mouseevent.y < ax.bbox.bounds[1] + ax.bbox.bounds[3] + and mouseevent.y > ax.bbox.bounds[1] + ): result2 = True # x bottom axis - elif xlabelbottom and mouseevent.x > ax.bbox.bounds[0] and \ - mouseevent.x < ax.bbox.bounds[2] + ax.bbox.bounds[0] and \ - mouseevent.y > ax.bbox.bounds[1] - xlabelsize and \ - mouseevent.y < ax.bbox.bounds[1]: + elif ( + xlabelbottom + and mouseevent.x > ax.bbox.bounds[0] + and mouseevent.x < ax.bbox.bounds[2] + ax.bbox.bounds[0] + and mouseevent.y > ax.bbox.bounds[1] - xlabelsize + and mouseevent.y < ax.bbox.bounds[1] + ): result2 = True # y right axis - elif ylabelright and mouseevent.x < ax.bbox.bounds[2] + ax.bbox.bounds[0] + self.interactive_axis_pad and \ - mouseevent.x > ax.bbox.bounds[2] + ax.bbox.bounds[0] and \ - mouseevent.y < ax.bbox.bounds[1] + ax.bbox.bounds[3] and \ - mouseevent.y > ax.bbox.bounds[1]: + elif ( + ylabelright + and mouseevent.x + < ax.bbox.bounds[2] + + ax.bbox.bounds[0] + + self.interactive_axis_pad + and mouseevent.x > ax.bbox.bounds[2] + ax.bbox.bounds[0] + and mouseevent.y < ax.bbox.bounds[1] + ax.bbox.bounds[3] + and mouseevent.y > ax.bbox.bounds[1] + ): result2 = True # #x top axis - elif xlabeltop and mouseevent.x > ax.bbox.bounds[0] and \ - mouseevent.x < ax.bbox.bounds[2] + ax.bbox.bounds[0] and \ - mouseevent.y > ax.bbox.bounds[1] + ax.bbox.bounds[3] and \ - mouseevent.y < ax.bbox.bounds[1] + ax.bbox.bounds[3] + xlabelsize: + elif ( + xlabeltop + and mouseevent.x > ax.bbox.bounds[0] + and mouseevent.x < ax.bbox.bounds[2] + ax.bbox.bounds[0] + and mouseevent.y > ax.bbox.bounds[1] + ax.bbox.bounds[3] + and mouseevent.y + < ax.bbox.bounds[1] + ax.bbox.bounds[3] + xlabelsize + ): result2 = True if result1 == result2: @@ -116,14 +136,14 @@ def on_figure(self, obj, value): # ax=self.figure.axes[0] patch_cpy = copy.copy(ax.patch) patch_cpy.set_visible(False) - if hasattr(ax, 'PolarTransform'): + if hasattr(ax, "PolarTransform"): for pos in list(ax.spines._dict.keys()): ax.spines[pos].set_zorder(10) # note: make an other widget if you need polar graph with # other type of graph self.disabled = True # polar graph do not handle pan/zoom else: - for pos in ['right', 'top', 'bottom', 'left']: + for pos in ["right", "top", "bottom", "left"]: ax.spines[pos].set_zorder(10) patch_cpy.set_zorder(9) self.background_patch_copy.append(ax.add_patch(patch_cpy)) @@ -178,28 +198,29 @@ def __init__(self, **kwargs): def register_cursor(self, pickables=None): remove_artists = [] - if hasattr(self, 'horizontal_line'): + if hasattr(self, "horizontal_line"): remove_artists.append(self.horizontal_line) - if hasattr(self, 'vertical_line'): + if hasattr(self, "vertical_line"): remove_artists.append(self.vertical_line) - if hasattr(self, 'text'): + if hasattr(self, "text"): remove_artists.append(self.text) self.cursor_cls = cursor( - self.figure, - pickables=pickables, - remove_artists=remove_artists) + self.figure, pickables=pickables, remove_artists=remove_artists + ) def autoscale(self): if self.disabled: return axes = self.figure.axes for i, ax in enumerate(axes): - twinx = any(ax.get_shared_x_axes().joined(ax, prev) - for prev in axes[:i]) + twinx = any( + ax.get_shared_x_axes().joined(ax, prev) for prev in axes[:i] + ) - twiny = any(ax.get_shared_y_axes().joined(ax, prev) - for prev in axes[:i]) + twiny = any( + ax.get_shared_y_axes().joined(ax, prev) for prev in axes[:i] + ) autoscale_axis = self.autoscale_axis if twinx: @@ -207,16 +228,22 @@ def autoscale(self): if twiny: autoscale_axis = "x" no_visible = self.myrelim( - ax, visible_only=self.autoscale_visible_only) - ax.autoscale_view(tight=self.autoscale_tight, - scalex=True if autoscale_axis != "y" else False, - scaley=True if autoscale_axis != "x" else False) + ax, visible_only=self.autoscale_visible_only + ) + ax.autoscale_view( + tight=self.autoscale_tight, + scalex=True if autoscale_axis != "y" else False, + scaley=True if autoscale_axis != "x" else False, + ) ax.autoscale(axis=autoscale_axis, tight=self.autoscale_tight) current_xlim = ax.get_xlim() current_ylim = ax.get_ylim() - lim_collection, invert_xaxis, invert_yaxis = self.data_limit_collection( - ax, visible_only=self.autoscale_visible_only) + lim_collection, invert_xaxis, invert_yaxis = ( + self.data_limit_collection( + ax, visible_only=self.autoscale_visible_only + ) + ) if lim_collection: xchanged = False if self.autoscale_tight: @@ -244,10 +271,12 @@ def autoscale(self): if xchanged: xlim = ax.get_xlim() ax.set_xlim( - left=xlim[0] - current_margins[0] * (xlim[1] - xlim[0])) + left=xlim[0] - current_margins[0] * (xlim[1] - xlim[0]) + ) ax.set_xlim( - right=xlim[1] + current_margins[0] * - (xlim[1] - xlim[0])) + right=xlim[1] + + current_margins[0] * (xlim[1] - xlim[0]) + ) ychanged = False @@ -270,10 +299,12 @@ def autoscale(self): if ychanged: ylim = ax.get_ylim() ax.set_ylim( - bottom=ylim[0] - current_margins[1] * - (ylim[1] - ylim[0])) + bottom=ylim[0] + - current_margins[1] * (ylim[1] - ylim[0]) + ) ax.set_ylim( - top=ylim[1] + current_margins[1] * (ylim[1] - ylim[0])) + top=ylim[1] + current_margins[1] * (ylim[1] - ylim[0]) + ) index = self.figure.axes.index(ax) self.xmin[index], self.xmax[index] = ax.get_xlim() @@ -388,19 +419,25 @@ def main_home(self, *args): def home(self, event=None): - if self.xmin is not None and \ - self.xmax is not None and \ - self.ymin is not None and \ - self.ymax is not None: + if ( + self.xmin is not None + and self.xmax is not None + and self.ymin is not None + and self.ymax is not None + ): if event: self.myevent.x = event.x - self.pos[0] self.myevent.y = event.y - self.pos[1] self.myevent.inaxes = self.figure.canvas.inaxes( - (event.x - self.pos[0], event.y - self.pos[1])) + (event.x - self.pos[0], event.y - self.pos[1]) + ) - axes = [a for a in self.figure.canvas.figure.get_axes() - if a.in_axes(self.myevent)] + axes = [ + a + for a in self.figure.canvas.figure.get_axes() + if a.in_axes(self.myevent) + ] if not axes: return @@ -468,36 +505,49 @@ def home(self, event=None): ax.figure.canvas.flush_events() def get_data_xy(self, x, y): - """ manage x y data in navigation bar """ + """manage x y data in navigation bar""" self.myevent_cursor.x = x - self.pos[0] self.myevent_cursor.y = y - self.pos[1] self.myevent_cursor.inaxes = self.figure.canvas.inaxes( - (x - self.pos[0], y - self.pos[1])) + (x - self.pos[0], y - self.pos[1]) + ) # find all axis - axes = [a for a in self.figure.canvas.figure.get_axes() - if a.in_axes(self.myevent_cursor)] + axes = [ + a + for a in self.figure.canvas.figure.get_axes() + if a.in_axes(self.myevent_cursor) + ] if axes: trans = axes[0].transData.inverted() # always use first axis - xdata, ydata = trans.transform_point((x - self.pos[0], - y - self.pos[1])) + xdata, ydata = trans.transform_point( + (x - self.pos[0], y - self.pos[1]) + ) if self.cursor_xaxis_formatter: x_format = self.cursor_xaxis_formatter.format_data(xdata) else: - x_format = self.axes.xaxis.get_major_formatter().format_data_short(xdata) + x_format = ( + self.axes.xaxis.get_major_formatter().format_data_short( + xdata + ) + ) if self.cursor_yaxis_formatter: y_format = self.cursor_yaxis_formatter.format_data(ydata) else: - y_format = self.axes.yaxis.get_major_formatter().format_data_short(ydata) + y_format = ( + self.axes.yaxis.get_major_formatter().format_data_short( + ydata + ) + ) return x_format, y_format else: return None, None def on_touch_down(self, event): - """ Manage Mouse/touch press """ + """Manage Mouse/touch press""" if self.disabled: return x, y = event.x, event.y @@ -513,7 +563,7 @@ def on_touch_down(self, event): self.current_legend = current_legend break if select_legend: - if self.touch_mode != 'drag_legend': + if self.touch_mode != "drag_legend": return False else: event.grab(self) @@ -534,22 +584,22 @@ def on_touch_down(self, event): elif event.is_double_tap: if not self.disable_double_tap: - if self.touch_mode != 'selector': + if self.touch_mode != "selector": self.home(event) return True else: - if self.touch_mode == 'cursor': + if self.touch_mode == "cursor": self.hover_on = True self.hover(event) - elif self.touch_mode == 'zoombox': + elif self.touch_mode == "zoombox": real_x, real_y = x - self.pos[0], y - self.pos[1] self.x_init = x self.y_init = real_y self.draw_box(event, x, real_y, x, real_y, onpress=True) - elif self.touch_mode == 'minmax': + elif self.touch_mode == "minmax": self.min_max(event) - elif self.touch_mode == 'selector': + elif self.touch_mode == "selector": pass event.grab(self) @@ -565,7 +615,7 @@ def on_touch_down(self, event): return False def hover(self, event) -> None: - """ hover cursor method (cursor to nearest value) + """hover cursor method (cursor to nearest value) Args: event: touch kivy event @@ -592,7 +642,8 @@ def hover(self, event) -> None: self.myevent.x = event.x - self.pos[0] self.myevent.y = event.y - self.pos[1] self.myevent.inaxes = self.figure.canvas.inaxes( - (event.x - self.pos[0], event.y - self.pos[1])) + (event.x - self.pos[0], event.y - self.pos[1]) + ) self.myevent.pickradius = self.pickradius self.myevent.projection = self.projection self.myevent.compare_xdata = self.compare_xdata @@ -602,7 +653,7 @@ def hover(self, event) -> None: # case if no good result if not sel: - if hasattr(self, 'horizontal_line'): + if hasattr(self, "horizontal_line"): self.set_cross_hair_visible(False) if self.hover_instance: self.hover_instance.x_hover_pos = self.x @@ -611,8 +662,11 @@ def hover(self, event) -> None: self.x_hover_data = None self.y_hover_data = None if self.highlight_hover: - axes = [a for a in self.figure.canvas.figure.get_axes() - if a.in_axes(self.myevent)] + axes = [ + a + for a in self.figure.canvas.figure.get_axes() + if a.in_axes(self.myevent) + ] if not axes: @@ -621,7 +675,8 @@ def hover(self, event) -> None: ax = self.axes if self.background: ax.figure.canvas.restore_region( - self.background) + self.background + ) # draw (blit method) ax.figure.canvas.blit(ax.bbox) ax.figure.canvas.flush_events() @@ -630,17 +685,19 @@ def hover(self, event) -> None: if self.compare_xdata: if not self.hover_instance or not hasattr( - self.hover_instance, 'children_list'): + self.hover_instance, "children_list" + ): return ax = None line = sel[0].artist custom_x = None - if not hasattr(line, 'axes'): - if hasattr(line, '_ContainerArtist__keep_alive'): + if not hasattr(line, "axes"): + if hasattr(line, "_ContainerArtist__keep_alive"): if self.hist_range and isinstance(line, BarContainer): - x_hist, y_hist, width_hist, height_hist = line[sel.index].get_bbox( - ).bounds + x_hist, y_hist, width_hist, height_hist = ( + line[sel.index].get_bbox().bounds + ) if self.cursor_xaxis_formatter: custom_x = f"{ self.cursor_xaxis_formatter.format_data(x_hist)}-{ @@ -687,32 +744,45 @@ def hover(self, event) -> None: if line_label in self.hover_instance.children_names: # index= self.hover_instance.children_names.index(line_label) index = [ - ii for ii, - x - in - enumerate( - self.hover_instance.children_names) - if x == line_label and ii in index_list][0] + ii + for ii, x in enumerate( + self.hover_instance.children_names + ) + if x == line_label and ii in index_list + ][0] y = sel[i].target[1] xy_pos = ax.transData.transform([(x, y)]) pos_y = float(xy_pos[0][1]) + self.y - if pos_y < self.y + ax.bbox.bounds[1] + ax.bbox.bounds[3] and \ - pos_y > self.y + ax.bbox.bounds[1]: - available_widget[index].x_hover_pos = float( - xy_pos[0][0]) + self.x - available_widget[index].y_hover_pos = float( - xy_pos[0][1]) + self.y - available_widget[index].custom_color = get_color_from_hex( - to_hex(line.get_color())) + if ( + pos_y + < self.y + + ax.bbox.bounds[1] + + ax.bbox.bounds[3] + and pos_y > self.y + ax.bbox.bounds[1] + ): + available_widget[index].x_hover_pos = ( + float(xy_pos[0][0]) + self.x + ) + available_widget[index].y_hover_pos = ( + float(xy_pos[0][1]) + self.y + ) + available_widget[index].custom_color = ( + get_color_from_hex( + to_hex(line.get_color()) + ) + ) if self.cursor_yaxis_formatter: y = self.cursor_yaxis_formatter.format_data( - y) + y + ) else: - y = ax.yaxis.get_major_formatter().format_data_short(y) + y = ax.yaxis.get_major_formatter().format_data_short( + y + ) available_widget[index].label_y_value = f"{y}" available_widget[index].show_widget = True index_list.remove(index) @@ -727,20 +797,29 @@ def hover(self, event) -> None: x = ax.xaxis.get_major_formatter().format_data_short(x) self.hover_instance.label_x_value = f"{x}" - if hasattr(self.hover_instance, 'overlap_check'): + if hasattr(self.hover_instance, "overlap_check"): self.hover_instance.overlap_check() - self.hover_instance.xmin_line = float( - ax.bbox.bounds[0]) + self.x - self.hover_instance.xmax_line = float( - ax.bbox.bounds[0] + ax.bbox.bounds[2]) + self.x - self.hover_instance.ymin_line = float( - ax.bbox.bounds[1]) + self.y - self.hover_instance.ymax_line = float( - ax.bbox.bounds[1] + ax.bbox.bounds[3]) + self.y - - if self.hover_instance.x_hover_pos > self.x + ax.bbox.bounds[2] + ax.bbox.bounds[0] or \ - self.hover_instance.x_hover_pos < self.x + ax.bbox.bounds[0] or len(index_list) == nb_widget: + self.hover_instance.xmin_line = ( + float(ax.bbox.bounds[0]) + self.x + ) + self.hover_instance.xmax_line = ( + float(ax.bbox.bounds[0] + ax.bbox.bounds[2]) + self.x + ) + self.hover_instance.ymin_line = ( + float(ax.bbox.bounds[1]) + self.y + ) + self.hover_instance.ymax_line = ( + float(ax.bbox.bounds[1] + ax.bbox.bounds[3]) + self.y + ) + + if ( + self.hover_instance.x_hover_pos + > self.x + ax.bbox.bounds[2] + ax.bbox.bounds[0] + or self.hover_instance.x_hover_pos + < self.x + ax.bbox.bounds[0] + or len(index_list) == nb_widget + ): self.hover_instance.hover_outside_bound = True else: self.hover_instance.hover_outside_bound = False @@ -759,17 +838,25 @@ def hover(self, event) -> None: line = sel.artist custom_x = None invert_xy = False - if not hasattr(line, 'axes'): - if hasattr(line, '_ContainerArtist__keep_alive'): + if not hasattr(line, "axes"): + if hasattr(line, "_ContainerArtist__keep_alive"): if self.hist_range and isinstance(line, BarContainer): - x_hist, y_hist, width_hist, height_hist = line[sel.index].get_bbox( - ).bounds - if line._ContainerArtist__keep_alive[0].container.orientation == 'horizontal': + x_hist, y_hist, width_hist, height_hist = ( + line[sel.index].get_bbox().bounds + ) + if ( + line._ContainerArtist__keep_alive[ + 0 + ].container.orientation + == "horizontal" + ): x_hist = y_hist width_hist = height_hist invert_xy = True - x = line._ContainerArtist__keep_alive[0].container.datavalues[sel.index] + x = line._ContainerArtist__keep_alive[ + 0 + ].container.datavalues[sel.index] if self.cursor_xaxis_formatter: custom_x = f"{ self.cursor_xaxis_formatter.format_data(x_hist)}-{ @@ -780,9 +867,11 @@ def hover(self, event) -> None: line = line._ContainerArtist__keep_alive[0] extra_data = None - if hasattr(self.myevent.inaxes, 'patch'): - if sel.artist is not self.myevent.inaxes.patch and hasattr( - sel.artist, 'get_cursor_data'): + if hasattr(self.myevent.inaxes, "patch"): + if ( + sel.artist is not self.myevent.inaxes.patch + and hasattr(sel.artist, "get_cursor_data") + ): extra_data = sel.artist.get_cursor_data(self.myevent) ax = line.axes @@ -792,21 +881,31 @@ def hover(self, event) -> None: self.cursor_last_axis = ax - if hasattr(self, 'horizontal_line'): - self.horizontal_line.set_ydata([y,]) + if hasattr(self, "horizontal_line"): + self.horizontal_line.set_ydata( + [ + y, + ] + ) - if hasattr(self, 'vertical_line'): - self.vertical_line.set_xdata([x,]) + if hasattr(self, "vertical_line"): + self.vertical_line.set_xdata( + [ + x, + ] + ) # x y label if self.hover_instance: xy_pos = ax.transData.transform([(x, y)]) self.x_hover_data = x self.y_hover_data = y - self.hover_instance.x_hover_pos = float( - xy_pos[0][0]) + self.x - self.hover_instance.y_hover_pos = float( - xy_pos[0][1]) + self.y + self.hover_instance.x_hover_pos = ( + float(xy_pos[0][0]) + self.x + ) + self.hover_instance.y_hover_pos = ( + float(xy_pos[0][1]) + self.y + ) self.hover_instance.show_cursor = True if self.cursor_xaxis_formatter: @@ -828,35 +927,50 @@ def hover(self, event) -> None: self.hover_instance.label_y_value = f"{y}" if extra_data is not None: - self.hover_instance.label_y_value += ' [' + str( - extra_data) + ']' - - self.hover_instance.xmin_line = float( - ax.bbox.bounds[0]) + self.x - self.hover_instance.xmax_line = float( - ax.bbox.bounds[0] + ax.bbox.bounds[2]) + self.x - self.hover_instance.ymin_line = float( - ax.bbox.bounds[1]) + self.y - self.hover_instance.ymax_line = float( - ax.bbox.bounds[1] + ax.bbox.bounds[3]) + self.y - - if hasattr(line, 'get_label'): + self.hover_instance.label_y_value += ( + " [" + str(extra_data) + "]" + ) + + self.hover_instance.xmin_line = ( + float(ax.bbox.bounds[0]) + self.x + ) + self.hover_instance.xmax_line = ( + float(ax.bbox.bounds[0] + ax.bbox.bounds[2]) + self.x + ) + self.hover_instance.ymin_line = ( + float(ax.bbox.bounds[1]) + self.y + ) + self.hover_instance.ymax_line = ( + float(ax.bbox.bounds[1] + ax.bbox.bounds[3]) + self.y + ) + + if hasattr(line, "get_label"): self.hover_instance.custom_label = line.get_label() - if hasattr(line, 'get_color'): + if hasattr(line, "get_color"): self.hover_instance.custom_color = get_color_from_hex( - to_hex(line.get_color())) - - if self.hover_instance.x_hover_pos > self.x + ax.bbox.bounds[2] + ax.bbox.bounds[0] or \ - self.hover_instance.x_hover_pos < self.x + ax.bbox.bounds[0] or \ - self.hover_instance.y_hover_pos > self.y + ax.bbox.bounds[1] + ax.bbox.bounds[3] or \ - self.hover_instance.y_hover_pos < self.y + ax.bbox.bounds[1]: + to_hex(line.get_color()) + ) + + if ( + self.hover_instance.x_hover_pos + > self.x + ax.bbox.bounds[2] + ax.bbox.bounds[0] + or self.hover_instance.x_hover_pos + < self.x + ax.bbox.bounds[0] + or self.hover_instance.y_hover_pos + > self.y + ax.bbox.bounds[1] + ax.bbox.bounds[3] + or self.hover_instance.y_hover_pos + < self.y + ax.bbox.bounds[1] + ): self.hover_instance.hover_outside_bound = True else: self.hover_instance.hover_outside_bound = False if self.highlight_hover: - axes = [a for a in self.figure.canvas.figure.get_axes() - if a.in_axes(self.myevent)] + axes = [ + a + for a in self.figure.canvas.figure.get_axes() + if a.in_axes(self.myevent) + ] if not axes or not isinstance(line, mlines.Line2D): @@ -864,7 +978,8 @@ def hover(self, event) -> None: self.clear_line_prop() if self.background: ax.figure.canvas.restore_region( - self.background) + self.background + ) # draw (blit method) ax.figure.canvas.blit(ax.bbox) ax.figure.canvas.flush_events() @@ -876,7 +991,8 @@ def hover(self, event) -> None: # draw) if self.background is None: self.background = ax.figure.canvas.copy_from_bbox( - ax.figure.bbox) + ax.figure.bbox + ) if self.last_line is None: default_alpha = [] lines_list = ax.lines @@ -886,8 +1002,9 @@ def hover(self, event) -> None: ax.figure.canvas.draw_idle() ax.figure.canvas.flush_events() - self.background_highlight = ax.figure.canvas.copy_from_bbox( - ax.figure.bbox) + self.background_highlight = ( + ax.figure.canvas.copy_from_bbox(ax.figure.bbox) + ) self.last_line = line for i, current_line in enumerate(lines_list): current_line.set_alpha(default_alpha[i]) @@ -897,28 +1014,32 @@ def hover(self, event) -> None: self.last_line_prop = {} for key in self.highlight_prop: # if hasattr(line,key): - line_attr = getattr(line, 'get_' + key) + line_attr = getattr(line, "get_" + key) self.last_line_prop.update( - {key: line_attr()}) - set_line_attr = getattr(line, 'set_' + key) + {key: line_attr()} + ) + set_line_attr = getattr(line, "set_" + key) set_line_attr(self.highlight_prop[key]) elif self.last_line_prop: for key in self.last_line_prop: set_line_attr = getattr( - self.last_line, 'set_' + key) + self.last_line, "set_" + key + ) set_line_attr(self.last_line_prop[key]) - self.hover_instance.custom_color = get_color_from_hex( - to_hex(line.get_color())) + self.hover_instance.custom_color = ( + get_color_from_hex(to_hex(line.get_color())) + ) self.last_line_prop = {} for key in self.highlight_prop: - line_attr = getattr(line, 'get_' + key) + line_attr = getattr(line, "get_" + key) self.last_line_prop.update({key: line_attr()}) - set_line_attr = getattr(line, 'set_' + key) + set_line_attr = getattr(line, "set_" + key) set_line_attr(self.highlight_prop[key]) self.last_line = line ax.figure.canvas.restore_region( - self.background_highlight) + self.background_highlight + ) ax.draw_artist(line) # draw (blit method) @@ -943,7 +1064,8 @@ def hover(self, event) -> None: self.figcanvas.draw_idle() self.figcanvas.flush_events() self.background = self.figcanvas.copy_from_bbox( - ax.figure.bbox) + ax.figure.bbox + ) self.set_cross_hair_visible(True) if self.last_line is not None: self.clear_line_prop() @@ -959,7 +1081,7 @@ def hover(self, event) -> None: self.figcanvas.flush_events() def zoom_factory(self, event, base_scale=1.1): - """ zoom with scrolling mouse method """ + """zoom with scrolling mouse method""" x = event.x - self.pos[0] y = event.y - self.pos[1] @@ -969,8 +1091,11 @@ def zoom_factory(self, event, base_scale=1.1): self.myevent.y = event.y - self.pos[1] self.myevent.inaxes = self.figure.canvas.inaxes((x, y)) # press event - axes = [a for a in self.figure.canvas.figure.get_axes() - if a.in_axes(self.myevent)] + axes = [ + a + for a in self.figure.canvas.figure.get_axes() + if a.in_axes(self.myevent) + ] self._pick_info = axes @@ -980,11 +1105,15 @@ def zoom_factory(self, event, base_scale=1.1): for i, ax in enumerate(self._pick_info): self.axes = ax - twinx = any(ax.get_shared_x_axes().joined(ax, prev) - for prev in self._pick_info[:i]) + twinx = any( + ax.get_shared_x_axes().joined(ax, prev) + for prev in self._pick_info[:i] + ) - twiny = any(ax.get_shared_y_axes().joined(ax, prev) - for prev in self._pick_info[:i]) + twiny = any( + ax.get_shared_y_axes().joined(ax, prev) + for prev in self._pick_info[:i] + ) trans = ax.transData.inverted() xdata, ydata = trans.transform_point((x, y)) @@ -995,7 +1124,7 @@ def zoom_factory(self, event, base_scale=1.1): scale = ax.get_xscale() yscale = ax.get_yscale() - if scale == 'linear': + if scale == "linear": old_min = cur_xlim[0] old_max = cur_xlim[1] @@ -1006,7 +1135,7 @@ def zoom_factory(self, event, base_scale=1.1): xdata = self.transform_eval(xdata, ax.yaxis) old_max = self.transform_eval(max_, ax.yaxis) - if yscale == 'linear': + if yscale == "linear": yold_min = cur_ylim[0] yold_max = cur_ylim[1] @@ -1017,10 +1146,10 @@ def zoom_factory(self, event, base_scale=1.1): ydata = self.transform_eval(ydata, ax.yaxis) yold_max = self.transform_eval(ymax_, ax.yaxis) - if event.button == 'scrolldown': + if event.button == "scrolldown": # deal with zoom in scale_factor = 1 / base_scale - elif event.button == 'scrollup': + elif event.button == "scrollup": # deal with zoom out scale_factor = base_scale else: @@ -1035,36 +1164,44 @@ def zoom_factory(self, event, base_scale=1.1): rely = (yold_max - ydata) / (yold_max - yold_min) if self.do_zoom_x and not twinx: - if scale == 'linear': - ax.set_xlim([xdata - new_width * (1 - relx), - xdata + new_width * (relx)]) + if scale == "linear": + ax.set_xlim( + [ + xdata - new_width * (1 - relx), + xdata + new_width * (relx), + ] + ) else: new_min = xdata - new_width * (1 - relx) new_max = xdata + new_width * (relx) try: new_min, new_max = self.inv_transform_eval( - new_min, ax.yaxis), self.inv_transform_eval( - new_max, ax.yaxis) + new_min, ax.yaxis + ), self.inv_transform_eval(new_max, ax.yaxis) except OverflowError: # Limit case new_min, new_max = min_, max_ - if new_min <= 0. or new_max <= 0.: # Limit case + if new_min <= 0.0 or new_max <= 0.0: # Limit case new_min, new_max = min_, max_ ax.set_xlim([new_min, new_max]) if self.do_zoom_y and not twiny: - if yscale == 'linear': - ax.set_ylim([ydata - new_height * (1 - rely), - ydata + new_height * (rely)]) + if yscale == "linear": + ax.set_ylim( + [ + ydata - new_height * (1 - rely), + ydata + new_height * (rely), + ] + ) else: new_ymin = ydata - new_height * (1 - rely) new_ymax = ydata + new_height * (rely) try: new_ymin, new_ymax = self.inv_transform_eval( - new_ymin, ax.yaxis), self.inv_transform_eval( - new_ymax, ax.yaxis) + new_ymin, ax.yaxis + ), self.inv_transform_eval(new_ymax, ax.yaxis) except OverflowError: # Limit case new_ymin, new_ymax = ymin_, ymax_ - if new_ymin <= 0. or new_ymax <= 0.: # Limit case + if new_ymin <= 0.0 or new_ymax <= 0.0: # Limit case new_ymin, new_ymax = ymin_, ymax_ ax.set_ylim([new_ymin, new_ymax]) @@ -1073,8 +1210,8 @@ def zoom_factory(self, event, base_scale=1.1): ax.figure.canvas.flush_events() def apply_zoom(self, scale_factor, ax, anchor=(0, 0), new_line=None): - """ zoom touch method """ - if self.touch_mode == 'selector': + """zoom touch method""" + if self.touch_mode == "selector": return x = anchor[0] - self.pos[0] @@ -1086,8 +1223,11 @@ def apply_zoom(self, scale_factor, ax, anchor=(0, 0), new_line=None): self.myevent.y = y self.myevent.inaxes = self.figure.canvas.inaxes((x, y)) # press event - axes = [a for a in self.figure.canvas.figure.get_axes() - if a.in_axes(self.myevent)] + axes = [ + a + for a in self.figure.canvas.figure.get_axes() + if a.in_axes(self.myevent) + ] self._pick_info = axes @@ -1099,17 +1239,22 @@ def apply_zoom(self, scale_factor, ax, anchor=(0, 0), new_line=None): self.axes = ax - twinx = any(ax.get_shared_x_axes().joined(ax, prev) - for prev in self._pick_info[:i]) + twinx = any( + ax.get_shared_x_axes().joined(ax, prev) + for prev in self._pick_info[:i] + ) - twiny = any(ax.get_shared_y_axes().joined(ax, prev) - for prev in self._pick_info[:i]) + twiny = any( + ax.get_shared_y_axes().joined(ax, prev) + for prev in self._pick_info[:i] + ) artists.extend(_iter_axes_subartists(ax)) trans = ax.transData.inverted() xdata, ydata = trans.transform_point( - (x + new_line.x, y + new_line.y)) + (x + new_line.x, y + new_line.y) + ) cur_xlim = ax.get_xlim() cur_ylim = ax.get_ylim() @@ -1117,7 +1262,7 @@ def apply_zoom(self, scale_factor, ax, anchor=(0, 0), new_line=None): scale = ax.get_xscale() yscale = ax.get_yscale() - if scale == 'linear': + if scale == "linear": old_min = cur_xlim[0] old_max = cur_xlim[1] @@ -1128,7 +1273,7 @@ def apply_zoom(self, scale_factor, ax, anchor=(0, 0), new_line=None): xdata = self.transform_eval(xdata, ax.yaxis) old_max = self.transform_eval(max_, ax.yaxis) - if yscale == 'linear': + if yscale == "linear": yold_min = cur_ylim[0] yold_max = cur_ylim[1] @@ -1146,36 +1291,44 @@ def apply_zoom(self, scale_factor, ax, anchor=(0, 0), new_line=None): rely = (yold_max - ydata) / (yold_max - yold_min) if self.do_zoom_x and not twinx: - if scale == 'linear': - ax.set_xlim([xdata - new_width * (1 - relx), - xdata + new_width * (relx)]) + if scale == "linear": + ax.set_xlim( + [ + xdata - new_width * (1 - relx), + xdata + new_width * (relx), + ] + ) else: new_min = xdata - new_width * (1 - relx) new_max = xdata + new_width * (relx) try: new_min, new_max = self.inv_transform_eval( - new_min, ax.yaxis), self.inv_transform_eval( - new_max, ax.yaxis) + new_min, ax.yaxis + ), self.inv_transform_eval(new_max, ax.yaxis) except OverflowError: # Limit case new_min, new_max = min_, max_ - if new_min <= 0. or new_max <= 0.: # Limit case + if new_min <= 0.0 or new_max <= 0.0: # Limit case new_min, new_max = min_, max_ ax.set_xlim([new_min, new_max]) if self.do_zoom_y and not twiny: - if yscale == 'linear': - ax.set_ylim([ydata - new_height * (1 - rely), - ydata + new_height * (rely)]) + if yscale == "linear": + ax.set_ylim( + [ + ydata - new_height * (1 - rely), + ydata + new_height * (rely), + ] + ) else: new_ymin = ydata - new_height * (1 - rely) new_ymax = ydata + new_height * (rely) try: new_ymin, new_ymax = self.inv_transform_eval( - new_ymin, ax.yaxis), self.inv_transform_eval( - new_ymax, ax.yaxis) + new_ymin, ax.yaxis + ), self.inv_transform_eval(new_ymax, ax.yaxis) except OverflowError: # Limit case new_ymin, new_ymax = ymin_, ymax_ - if new_ymin <= 0. or new_ymax <= 0.: # Limit case + if new_ymin <= 0.0 or new_ymax <= 0.0: # Limit case new_ymin, new_ymax = ymin_, ymax_ ax.set_ylim([new_ymin, new_ymax]) @@ -1221,22 +1374,27 @@ def apply_zoom(self, scale_factor, ax, anchor=(0, 0), new_line=None): self.figcanvas.draw_idle() self.figcanvas.flush_events() - def apply_pan(self, my_ax, event, mode='pan'): - """ pan method """ + def apply_pan(self, my_ax, event, mode="pan"): + """pan method""" # manage press and drag if not self._pick_info: self.myevent.x = event.x - self.pos[0] self.myevent.y = event.y - self.pos[1] - self.myevent.inaxes = self.figure.canvas.inaxes((event.x, - event.y)) + self.myevent.inaxes = self.figure.canvas.inaxes((event.x, event.y)) # press event - axes = [a for a in self.figure.canvas.figure.get_axes() - if a.in_axes(self.myevent)] + axes = [ + a + for a in self.figure.canvas.figure.get_axes() + if a.in_axes(self.myevent) + ] if not axes: - if self.first_touch_pan != 'pan' and self.interactive_axis: - axes = [a for a in self.figure.canvas.figure.get_axes() - if self.my_in_axes(a, self.myevent)] + if self.first_touch_pan != "pan" and self.interactive_axis: + axes = [ + a + for a in self.figure.canvas.figure.get_axes() + if self.my_in_axes(a, self.myevent) + ] self._pick_info = axes @@ -1250,33 +1408,43 @@ def apply_pan(self, my_ax, event, mode='pan'): artists.extend(_iter_axes_subartists(ax)) - twinx = any(ax.get_shared_x_axes().joined(ax, prev) - for prev in self._pick_info[:i]) + twinx = any( + ax.get_shared_x_axes().joined(ax, prev) + for prev in self._pick_info[:i] + ) - twiny = any(ax.get_shared_y_axes().joined(ax, prev) - for prev in self._pick_info[:i]) + twiny = any( + ax.get_shared_y_axes().joined(ax, prev) + for prev in self._pick_info[:i] + ) trans = ax.transData.inverted() xdata, ydata = trans.transform_point( - (event.x - self.pos[0], event.y - self.pos[1])) + (event.x - self.pos[0], event.y - self.pos[1]) + ) xpress, ypress = trans.transform_point( - (self._last_touch_pos[event][0] - self.pos[0], - self._last_touch_pos[event][1] - self.pos[1])) + ( + self._last_touch_pos[event][0] - self.pos[0], + self._last_touch_pos[event][1] - self.pos[1], + ) + ) scale = ax.get_xscale() yscale = ax.get_yscale() - if scale == 'linear': + if scale == "linear": dx = xdata - xpress else: - dx = self.transform_eval(xdata, ax.xaxis) - \ - self.transform_eval(xpress, ax.xaxis) + dx = self.transform_eval( + xdata, ax.xaxis + ) - self.transform_eval(xpress, ax.xaxis) - if yscale == 'linear': + if yscale == "linear": dy = ydata - ypress else: - dy = self.transform_eval(ydata, ax.yaxis) - \ - self.transform_eval(ypress, ax.yaxis) + dy = self.transform_eval( + ydata, ax.yaxis + ) - self.transform_eval(ypress, ax.yaxis) xleft, xright = ax.get_xlim() ybottom, ytop = ax.get_ylim() @@ -1295,169 +1463,249 @@ def apply_pan(self, my_ax, event, mode='pan'): else: cur_ylim = (ybottom, ytop) - if self.interactive_axis and self.touch_mode == 'pan' and not self.first_touch_pan == 'pan': + if ( + self.interactive_axis + and self.touch_mode == "pan" + and not self.first_touch_pan == "pan" + ): xlabelsize = self.interactive_axis_pad ylabelsize = self.interactive_axis_pad - xlabelbottom = ax.xaxis._major_tick_kw.get('tick1On') - xlabeltop = ax.xaxis._major_tick_kw.get('tick2On') - ylabelleft = ax.yaxis._major_tick_kw.get('tick1On') - ylabelright = ax.yaxis._major_tick_kw.get('tick2On') + xlabelbottom = ax.xaxis._major_tick_kw.get("tick1On") + xlabeltop = ax.xaxis._major_tick_kw.get("tick2On") + ylabelleft = ax.yaxis._major_tick_kw.get("tick1On") + ylabelright = ax.yaxis._major_tick_kw.get("tick2On") # if (ydata < cur_ylim[0] and not inverted_y) or (ydata > # cur_ylim[1] and inverted_y): - if xlabelbottom and event.x > self.x + ax.bbox.bounds[0] and \ - event.x < self.x + ax.bbox.bounds[2] + ax.bbox.bounds[0] and \ - event.y > self.y + ax.bbox.bounds[1] - xlabelsize and \ - event.y < self.y + ax.bbox.bounds[1]: + if ( + xlabelbottom + and event.x > self.x + ax.bbox.bounds[0] + and event.x + < self.x + ax.bbox.bounds[2] + ax.bbox.bounds[0] + and event.y > self.y + ax.bbox.bounds[1] - xlabelsize + and event.y < self.y + ax.bbox.bounds[1] + ): right_lim = self.x + ax.bbox.bounds[2] + ax.bbox.bounds[0] left_lim = self.x + ax.bbox.bounds[0] - left_anchor_zone = (right_lim - left_lim) * .2 + left_lim - right_anchor_zone = (right_lim - left_lim) * .8 + left_lim - - if event.x < left_anchor_zone or event.x > right_anchor_zone: - mode = 'adjust_x' + left_anchor_zone = (right_lim - left_lim) * 0.2 + left_lim + right_anchor_zone = (right_lim - left_lim) * 0.8 + left_lim + + if ( + event.x < left_anchor_zone + or event.x > right_anchor_zone + ): + mode = "adjust_x" self.current_anchor_axis = ax else: - mode = 'pan_x' + mode = "pan_x" self.touch_mode = mode # elif (xdata < cur_xlim[0] and not inverted_x) or (xdata > # cur_xlim[1] and inverted_x): - elif ylabelleft and event.x > self.x + ax.bbox.bounds[0] - ylabelsize and \ - event.x < self.x + ax.bbox.bounds[0] and \ - event.y < self.y + ax.bbox.bounds[1] + ax.bbox.bounds[3] and \ - event.y > self.y + ax.bbox.bounds[1]: + elif ( + ylabelleft + and event.x > self.x + ax.bbox.bounds[0] - ylabelsize + and event.x < self.x + ax.bbox.bounds[0] + and event.y + < self.y + ax.bbox.bounds[1] + ax.bbox.bounds[3] + and event.y > self.y + ax.bbox.bounds[1] + ): top_lim = self.y + ax.bbox.bounds[3] + ax.bbox.bounds[1] bottom_lim = self.y + ax.bbox.bounds[1] bottom_anchor_zone = ( - top_lim - bottom_lim) * .2 + bottom_lim - top_anchor_zone = (top_lim - bottom_lim) * .8 + bottom_lim - - if event.y < bottom_anchor_zone or event.y > top_anchor_zone: - mode = 'adjust_y' + top_lim - bottom_lim + ) * 0.2 + bottom_lim + top_anchor_zone = (top_lim - bottom_lim) * 0.8 + bottom_lim + + if ( + event.y < bottom_anchor_zone + or event.y > top_anchor_zone + ): + mode = "adjust_y" self.current_anchor_axis = ax else: - mode = 'pan_y' + mode = "pan_y" self.touch_mode = mode - elif ylabelright and event.x < self.x + ax.bbox.bounds[2] + ax.bbox.bounds[0] + self.interactive_axis_pad and \ - event.x > self.x + ax.bbox.bounds[2] + ax.bbox.bounds[0] and \ - event.y < self.y + ax.bbox.bounds[1] + ax.bbox.bounds[3] and \ - event.y > self.y + ax.bbox.bounds[1]: + elif ( + ylabelright + and event.x + < self.x + + ax.bbox.bounds[2] + + ax.bbox.bounds[0] + + self.interactive_axis_pad + and event.x + > self.x + ax.bbox.bounds[2] + ax.bbox.bounds[0] + and event.y + < self.y + ax.bbox.bounds[1] + ax.bbox.bounds[3] + and event.y > self.y + ax.bbox.bounds[1] + ): top_lim = self.y + ax.bbox.bounds[3] + ax.bbox.bounds[1] bottom_lim = self.y + ax.bbox.bounds[1] bottom_anchor_zone = ( - top_lim - bottom_lim) * .2 + bottom_lim - top_anchor_zone = (top_lim - bottom_lim) * .8 + bottom_lim - - if event.y < bottom_anchor_zone or event.y > top_anchor_zone: - mode = 'adjust_y' + top_lim - bottom_lim + ) * 0.2 + bottom_lim + top_anchor_zone = (top_lim - bottom_lim) * 0.8 + bottom_lim + + if ( + event.y < bottom_anchor_zone + or event.y > top_anchor_zone + ): + mode = "adjust_y" self.current_anchor_axis = ax else: - mode = 'pan_y' + mode = "pan_y" self.touch_mode = mode - elif xlabeltop and event.x > self.x + ax.bbox.bounds[0] and \ - event.x < self.x + ax.bbox.bounds[2] + ax.bbox.bounds[0] and \ - event.y > self.y + ax.bbox.bounds[1] + ax.bbox.bounds[3] and \ - event.y < self.y + ax.bbox.bounds[1] + ax.bbox.bounds[3] + xlabelsize: + elif ( + xlabeltop + and event.x > self.x + ax.bbox.bounds[0] + and event.x + < self.x + ax.bbox.bounds[2] + ax.bbox.bounds[0] + and event.y + > self.y + ax.bbox.bounds[1] + ax.bbox.bounds[3] + and event.y + < self.y + + ax.bbox.bounds[1] + + ax.bbox.bounds[3] + + xlabelsize + ): right_lim = self.x + ax.bbox.bounds[2] + ax.bbox.bounds[0] left_lim = self.x + ax.bbox.bounds[0] - left_anchor_zone = (right_lim - left_lim) * .2 + left_lim - right_anchor_zone = (right_lim - left_lim) * .8 + left_lim - - if event.x < left_anchor_zone or event.x > right_anchor_zone: - mode = 'adjust_x' + left_anchor_zone = (right_lim - left_lim) * 0.2 + left_lim + right_anchor_zone = (right_lim - left_lim) * 0.8 + left_lim + + if ( + event.x < left_anchor_zone + or event.x > right_anchor_zone + ): + mode = "adjust_x" self.current_anchor_axis = ax else: - mode = 'pan_x' + mode = "pan_x" self.touch_mode = mode else: - self.touch_mode = 'pan' + self.touch_mode = "pan" - if not mode == 'pan_y' and not mode == 'adjust_y': - if mode == 'adjust_x': + if not mode == "pan_y" and not mode == "adjust_y": + if mode == "adjust_x": if self.current_anchor_axis == ax: if self.anchor_x is None: midpoint = ( - (self.x + ax.bbox.bounds[2] + ax.bbox.bounds - [0]) + (self.x + ax.bbox.bounds[0])) / 2 + ( + self.x + + ax.bbox.bounds[2] + + ax.bbox.bounds[0] + ) + + (self.x + ax.bbox.bounds[0]) + ) / 2 if event.x > midpoint: if inverted_x: - self.anchor_x = 'right' + self.anchor_x = "right" else: - self.anchor_x = 'left' + self.anchor_x = "left" else: if inverted_x: - self.anchor_x = 'left' + self.anchor_x = "left" else: - self.anchor_x = 'right' - if self.anchor_x == 'left': + self.anchor_x = "right" + if self.anchor_x == "left": if xdata > cur_xlim[0]: - if scale == 'linear': + if scale == "linear": cur_xlim -= dx else: try: cur_xlim = [ self.inv_transform_eval( - (self.transform_eval( - cur_xlim[0], - ax.xaxis) - dx), - ax.xaxis), + ( + self.transform_eval( + cur_xlim[0], ax.xaxis + ) + - dx + ), + ax.xaxis, + ), self.inv_transform_eval( - (self.transform_eval( - cur_xlim[1], - ax.xaxis) - dx), - ax.xaxis)] + ( + self.transform_eval( + cur_xlim[1], ax.xaxis + ) + - dx + ), + ax.xaxis, + ), + ] except (ValueError, OverflowError): - cur_xlim = cur_xlim # Keep previous limits + cur_xlim = ( + cur_xlim # Keep previous limits + ) if inverted_x: ax.set_xlim(cur_xlim[1], None) else: ax.set_xlim(None, cur_xlim[1]) else: if xdata < cur_xlim[1]: - if scale == 'linear': + if scale == "linear": cur_xlim -= dx else: try: cur_xlim = [ self.inv_transform_eval( - (self.transform_eval( - cur_xlim[0], - ax.xaxis) - dx), - ax.xaxis), + ( + self.transform_eval( + cur_xlim[0], ax.xaxis + ) + - dx + ), + ax.xaxis, + ), self.inv_transform_eval( - (self.transform_eval( - cur_xlim[1], - ax.xaxis) - dx), - ax.xaxis)] + ( + self.transform_eval( + cur_xlim[1], ax.xaxis + ) + - dx + ), + ax.xaxis, + ), + ] except (ValueError, OverflowError): - cur_xlim = cur_xlim # Keep previous limits + cur_xlim = ( + cur_xlim # Keep previous limits + ) if inverted_x: ax.set_xlim(None, cur_xlim[0]) else: ax.set_xlim(cur_xlim[0], None) else: if not twinx: - if scale == 'linear': + if scale == "linear": cur_xlim -= dx else: try: cur_xlim = [ self.inv_transform_eval( - (self.transform_eval( - cur_xlim[0], - ax.xaxis) - dx), - ax.xaxis), + ( + self.transform_eval( + cur_xlim[0], ax.xaxis + ) + - dx + ), + ax.xaxis, + ), self.inv_transform_eval( - (self.transform_eval( - cur_xlim[1], - ax.xaxis) - dx), - ax.xaxis)] + ( + self.transform_eval( + cur_xlim[1], ax.xaxis + ) + - dx + ), + ax.xaxis, + ), + ] except (ValueError, OverflowError): cur_xlim = cur_xlim # Keep previous limits if inverted_x: @@ -1465,44 +1713,60 @@ def apply_pan(self, my_ax, event, mode='pan'): else: ax.set_xlim(cur_xlim) - if not mode == 'pan_x' and not mode == 'adjust_x': - if mode == 'adjust_y': + if not mode == "pan_x" and not mode == "adjust_x": + if mode == "adjust_y": if self.current_anchor_axis == ax: if self.anchor_y is None: midpoint = ( - (self.y + ax.bbox.bounds[3] + ax.bbox.bounds - [1]) + (self.y + ax.bbox.bounds[1])) / 2 + ( + self.y + + ax.bbox.bounds[3] + + ax.bbox.bounds[1] + ) + + (self.y + ax.bbox.bounds[1]) + ) / 2 if event.y > midpoint: if inverted_y: - self.anchor_y = 'bottom' + self.anchor_y = "bottom" else: - self.anchor_y = 'top' + self.anchor_y = "top" else: if inverted_y: - self.anchor_y = 'top' + self.anchor_y = "top" else: - self.anchor_y = 'bottom' + self.anchor_y = "bottom" - if self.anchor_y == 'top': + if self.anchor_y == "top": if ydata > cur_ylim[0]: - if yscale == 'linear': + if yscale == "linear": cur_ylim -= dy else: try: cur_ylim = [ self.inv_transform_eval( - (self.transform_eval( - cur_ylim[0], - ax.yaxis) - dy), - ax.yaxis), + ( + self.transform_eval( + cur_ylim[0], ax.yaxis + ) + - dy + ), + ax.yaxis, + ), self.inv_transform_eval( - (self.transform_eval( - cur_ylim[1], - ax.yaxis) - dy), - ax.yaxis)] + ( + self.transform_eval( + cur_ylim[1], ax.yaxis + ) + - dy + ), + ax.yaxis, + ), + ] except (ValueError, OverflowError): - cur_ylim = cur_ylim # Keep previous limits + cur_ylim = ( + cur_ylim # Keep previous limits + ) if inverted_y: ax.set_ylim(cur_ylim[1], None) @@ -1510,46 +1774,66 @@ def apply_pan(self, my_ax, event, mode='pan'): ax.set_ylim(None, cur_ylim[1]) else: if ydata < cur_ylim[1]: - if yscale == 'linear': + if yscale == "linear": cur_ylim -= dy else: try: cur_ylim = [ self.inv_transform_eval( - (self.transform_eval( - cur_ylim[0], - ax.yaxis) - dy), - ax.yaxis), + ( + self.transform_eval( + cur_ylim[0], ax.yaxis + ) + - dy + ), + ax.yaxis, + ), self.inv_transform_eval( - (self.transform_eval( - cur_ylim[1], - ax.yaxis) - dy), - ax.yaxis)] + ( + self.transform_eval( + cur_ylim[1], ax.yaxis + ) + - dy + ), + ax.yaxis, + ), + ] except (ValueError, OverflowError): - cur_ylim = cur_ylim # Keep previous limits + cur_ylim = ( + cur_ylim # Keep previous limits + ) if inverted_y: ax.set_ylim(None, cur_ylim[0]) else: ax.set_ylim(cur_ylim[0], None) else: if not twiny: - if yscale == 'linear': + if yscale == "linear": cur_ylim -= dy else: try: cur_ylim = [ self.inv_transform_eval( - (self.transform_eval( - cur_ylim[0], - ax.yaxis) - dy), - ax.yaxis), + ( + self.transform_eval( + cur_ylim[0], ax.yaxis + ) + - dy + ), + ax.yaxis, + ), self.inv_transform_eval( - (self.transform_eval( - cur_ylim[1], - ax.yaxis) - dy), - ax.yaxis)] + ( + self.transform_eval( + cur_ylim[1], ax.yaxis + ) + - dy + ), + ax.yaxis, + ), + ] except (ValueError, OverflowError): cur_ylim = cur_ylim # Keep previous limits if inverted_y: @@ -1603,206 +1887,309 @@ def apply_pan(self, my_ax, event, mode='pan'): self.figcanvas.flush_events() def update_hover(self): - """ update hover on fast draw (if exist)""" + """update hover on fast draw (if exist)""" if self.hover_instance: # update hover pos if needed - if self.hover_instance.show_cursor and self.x_hover_data is not None and self.y_hover_data is not None: + if ( + self.hover_instance.show_cursor + and self.x_hover_data is not None + and self.y_hover_data is not None + ): # if self.cursor_last_axis.axes==self.axes: xy_pos = self.cursor_last_axis.transData.transform( - [(self.x_hover_data, self.y_hover_data)]) + [(self.x_hover_data, self.y_hover_data)] + ) self.hover_instance.x_hover_pos = float(xy_pos[0][0]) + self.x self.hover_instance.y_hover_pos = float(xy_pos[0][1]) + self.y - self.hover_instance.xmin_line = float( - self.axes.bbox.bounds[0]) + self.x - self.hover_instance.xmax_line = float( - self.axes.bbox.bounds[0] + self.axes.bbox.bounds[2]) + self.x - self.hover_instance.ymin_line = float( - self.axes.bbox.bounds[1]) + self.y - self.hover_instance.ymax_line = float( - self.axes.bbox.bounds[1] + self.axes.bbox.bounds[3]) + self.y - - if self.hover_instance.x_hover_pos > self.x + self.axes.bbox.bounds[2] + self.axes.bbox.bounds[0] or \ - self.hover_instance.x_hover_pos < self.x + self.axes.bbox.bounds[0] or \ - self.hover_instance.y_hover_pos > self.y + self.axes.bbox.bounds[1] + self.axes.bbox.bounds[3] or \ - self.hover_instance.y_hover_pos < self.y + self.axes.bbox.bounds[1]: + self.hover_instance.xmin_line = ( + float(self.axes.bbox.bounds[0]) + self.x + ) + self.hover_instance.xmax_line = ( + float(self.axes.bbox.bounds[0] + self.axes.bbox.bounds[2]) + + self.x + ) + self.hover_instance.ymin_line = ( + float(self.axes.bbox.bounds[1]) + self.y + ) + self.hover_instance.ymax_line = ( + float(self.axes.bbox.bounds[1] + self.axes.bbox.bounds[3]) + + self.y + ) + + if ( + self.hover_instance.x_hover_pos + > self.x + + self.axes.bbox.bounds[2] + + self.axes.bbox.bounds[0] + or self.hover_instance.x_hover_pos + < self.x + self.axes.bbox.bounds[0] + or self.hover_instance.y_hover_pos + > self.y + + self.axes.bbox.bounds[1] + + self.axes.bbox.bounds[3] + or self.hover_instance.y_hover_pos + < self.y + self.axes.bbox.bounds[1] + ): self.hover_instance.hover_outside_bound = True else: self.hover_instance.hover_outside_bound = False def min_max(self, event): - """ manage min/max touch mode """ + """manage min/max touch mode""" self.myevent.x = event.x - self.pos[0] self.myevent.y = event.y - self.pos[1] - self.myevent.inaxes = self.figure.canvas.inaxes((event.x, - event.y)) - axes = [a for a in self.figure.canvas.figure.get_axes() - if self.my_in_axes(a, self.myevent)] + self.myevent.inaxes = self.figure.canvas.inaxes((event.x, event.y)) + axes = [ + a + for a in self.figure.canvas.figure.get_axes() + if self.my_in_axes(a, self.myevent) + ] for ax in axes: xlabelsize = self.interactive_axis_pad ylabelsize = self.interactive_axis_pad - xlabelbottom = ax.xaxis._major_tick_kw.get('tick1On') - xlabeltop = ax.xaxis._major_tick_kw.get('tick2On') - ylabelleft = ax.yaxis._major_tick_kw.get('tick1On') - ylabelright = ax.yaxis._major_tick_kw.get('tick2On') - - if xlabelbottom and event.x > self.x + ax.bbox.bounds[0] and \ - event.x < self.x + ax.bbox.bounds[2] + ax.bbox.bounds[0] and \ - event.y > self.y + ax.bbox.bounds[1] - xlabelsize and \ - event.y < self.y + ax.bbox.bounds[1]: + xlabelbottom = ax.xaxis._major_tick_kw.get("tick1On") + xlabeltop = ax.xaxis._major_tick_kw.get("tick2On") + ylabelleft = ax.yaxis._major_tick_kw.get("tick1On") + ylabelright = ax.yaxis._major_tick_kw.get("tick2On") + + if ( + xlabelbottom + and event.x > self.x + ax.bbox.bounds[0] + and event.x < self.x + ax.bbox.bounds[2] + ax.bbox.bounds[0] + and event.y > self.y + ax.bbox.bounds[1] - xlabelsize + and event.y < self.y + ax.bbox.bounds[1] + ): right_lim = self.x + ax.bbox.bounds[2] + ax.bbox.bounds[0] left_lim = self.x + ax.bbox.bounds[0] - left_anchor_zone = (right_lim - left_lim) * .2 + left_lim - right_anchor_zone = (right_lim - left_lim) * .8 + left_lim + left_anchor_zone = (right_lim - left_lim) * 0.2 + left_lim + right_anchor_zone = (right_lim - left_lim) * 0.8 + left_lim if event.x < left_anchor_zone or event.x > right_anchor_zone: if self.text_instance: if not self.text_instance.show_text: midpoint = ( - (self.x + ax.bbox.bounds[2] + ax.bbox.bounds - [0]) + (self.x + ax.bbox.bounds[0])) / 2 + ( + self.x + + ax.bbox.bounds[2] + + ax.bbox.bounds[0] + ) + + (self.x + ax.bbox.bounds[0]) + ) / 2 if event.x < midpoint: - anchor = 'left' + anchor = "left" self.text_instance.x_text_pos = float( - self.x + ax.bbox.bounds[0]) - self.text_instance.y_text_pos = float( - self.y + ax.bbox.bounds[1]) - self.text_instance.text_height + self.x + ax.bbox.bounds[0] + ) + self.text_instance.y_text_pos = ( + float(self.y + ax.bbox.bounds[1]) + - self.text_instance.text_height + ) self.text_instance.offset_text = False else: - anchor = 'right' + anchor = "right" self.text_instance.x_text_pos = float( - self.x + ax.bbox.bounds[2] + ax.bbox.bounds[0]) - self.text_instance.y_text_pos = float( - self.y + ax.bbox.bounds[1]) - self.text_instance.text_height + self.x + + ax.bbox.bounds[2] + + ax.bbox.bounds[0] + ) + self.text_instance.y_text_pos = ( + float(self.y + ax.bbox.bounds[1]) + - self.text_instance.text_height + ) self.text_instance.offset_text = True self.text_instance.current_axis = ax self.text_instance.kind = { - 'axis': 'x', 'anchor': anchor} + "axis": "x", + "anchor": anchor, + } self.text_instance.show_text = True return - elif ylabelleft and event.x > self.x + ax.bbox.bounds[0] - ylabelsize and \ - event.x < self.x + ax.bbox.bounds[0] and \ - event.y < self.y + ax.bbox.bounds[1] + ax.bbox.bounds[3] and \ - event.y > self.y + ax.bbox.bounds[1]: + elif ( + ylabelleft + and event.x > self.x + ax.bbox.bounds[0] - ylabelsize + and event.x < self.x + ax.bbox.bounds[0] + and event.y < self.y + ax.bbox.bounds[1] + ax.bbox.bounds[3] + and event.y > self.y + ax.bbox.bounds[1] + ): top_lim = self.y + ax.bbox.bounds[3] + ax.bbox.bounds[1] bottom_lim = self.y + ax.bbox.bounds[1] - bottom_anchor_zone = (top_lim - bottom_lim) * .2 + bottom_lim - top_anchor_zone = (top_lim - bottom_lim) * .8 + bottom_lim + bottom_anchor_zone = (top_lim - bottom_lim) * 0.2 + bottom_lim + top_anchor_zone = (top_lim - bottom_lim) * 0.8 + bottom_lim if event.y < bottom_anchor_zone or event.y > top_anchor_zone: if self.text_instance: if not self.text_instance.show_text: midpoint = ( - (self.y + ax.bbox.bounds[3] + ax.bbox.bounds - [1]) + (self.y + ax.bbox.bounds[1])) / 2 + ( + self.y + + ax.bbox.bounds[3] + + ax.bbox.bounds[1] + ) + + (self.y + ax.bbox.bounds[1]) + ) / 2 if event.y > midpoint: - anchor = 'top' + anchor = "top" self.text_instance.x_text_pos = float( - self.x + ax.bbox.bounds[0]) - dp(40) - self.text_instance.y_text_pos = float( - self.y + ax.bbox.bounds[1] + ax.bbox.bounds - [3]) - self.text_instance.text_height + self.x + ax.bbox.bounds[0] + ) - dp(40) + self.text_instance.y_text_pos = ( + float( + self.y + + ax.bbox.bounds[1] + + ax.bbox.bounds[3] + ) + - self.text_instance.text_height + ) self.text_instance.offset_text = False else: - anchor = 'bottom' + anchor = "bottom" self.text_instance.x_text_pos = float( - self.x + ax.bbox.bounds[0]) - dp(40) + self.x + ax.bbox.bounds[0] + ) - dp(40) self.text_instance.y_text_pos = float( - self.y + ax.bbox.bounds[1]) - dp(6) + self.y + ax.bbox.bounds[1] + ) - dp(6) self.text_instance.offset_text = False self.text_instance.current_axis = ax self.text_instance.kind = { - 'axis': 'y', 'anchor': anchor} + "axis": "y", + "anchor": anchor, + } self.text_instance.show_text = True return - elif ylabelright and event.x < self.x + ax.bbox.bounds[2] + ax.bbox.bounds[0] + ylabelsize and \ - event.x > self.x + ax.bbox.bounds[2] + ax.bbox.bounds[0] and \ - event.y < self.y + ax.bbox.bounds[1] + ax.bbox.bounds[3] and \ - event.y > self.y + ax.bbox.bounds[1]: + elif ( + ylabelright + and event.x + < self.x + ax.bbox.bounds[2] + ax.bbox.bounds[0] + ylabelsize + and event.x > self.x + ax.bbox.bounds[2] + ax.bbox.bounds[0] + and event.y < self.y + ax.bbox.bounds[1] + ax.bbox.bounds[3] + and event.y > self.y + ax.bbox.bounds[1] + ): top_lim = self.y + ax.bbox.bounds[3] + ax.bbox.bounds[1] bottom_lim = self.y + ax.bbox.bounds[1] - bottom_anchor_zone = (top_lim - bottom_lim) * .2 + bottom_lim - top_anchor_zone = (top_lim - bottom_lim) * .8 + bottom_lim + bottom_anchor_zone = (top_lim - bottom_lim) * 0.2 + bottom_lim + top_anchor_zone = (top_lim - bottom_lim) * 0.8 + bottom_lim if event.y < bottom_anchor_zone or event.y > top_anchor_zone: if self.text_instance: if not self.text_instance.show_text: midpoint = ( - (self.y + ax.bbox.bounds[3] + ax.bbox.bounds - [1]) + (self.y + ax.bbox.bounds[1])) / 2 + ( + self.y + + ax.bbox.bounds[3] + + ax.bbox.bounds[1] + ) + + (self.y + ax.bbox.bounds[1]) + ) / 2 if event.y > midpoint: - anchor = 'top' + anchor = "top" self.text_instance.x_text_pos = float( - self.x + ax.bbox.bounds[0] + ax.bbox.bounds[2]) + dp(40) - self.text_instance.y_text_pos = float( - self.y + ax.bbox.bounds[1] + ax.bbox.bounds - [3]) - self.text_instance.text_height + self.x + + ax.bbox.bounds[0] + + ax.bbox.bounds[2] + ) + dp(40) + self.text_instance.y_text_pos = ( + float( + self.y + + ax.bbox.bounds[1] + + ax.bbox.bounds[3] + ) + - self.text_instance.text_height + ) self.text_instance.offset_text = True else: - anchor = 'bottom' + anchor = "bottom" self.text_instance.x_text_pos = float( - self.x + ax.bbox.bounds[0] + ax.bbox.bounds[2]) + dp(40) + self.x + + ax.bbox.bounds[0] + + ax.bbox.bounds[2] + ) + dp(40) self.text_instance.y_text_pos = float( - self.y + ax.bbox.bounds[1]) - dp(6) + self.y + ax.bbox.bounds[1] + ) - dp(6) self.text_instance.offset_text = True self.text_instance.current_axis = ax self.text_instance.kind = { - 'axis': 'y', 'anchor': anchor} + "axis": "y", + "anchor": anchor, + } self.text_instance.show_text = True return - elif xlabeltop and event.x > self.x + ax.bbox.bounds[0] and \ - event.x < self.x + ax.bbox.bounds[2] + ax.bbox.bounds[0] and \ - event.y > self.y + ax.bbox.bounds[1] + ax.bbox.bounds[3] and \ - event.y < self.y + ax.bbox.bounds[1] + ax.bbox.bounds[3] + xlabelsize: + elif ( + xlabeltop + and event.x > self.x + ax.bbox.bounds[0] + and event.x < self.x + ax.bbox.bounds[2] + ax.bbox.bounds[0] + and event.y > self.y + ax.bbox.bounds[1] + ax.bbox.bounds[3] + and event.y + < self.y + ax.bbox.bounds[1] + ax.bbox.bounds[3] + xlabelsize + ): right_lim = self.x + ax.bbox.bounds[2] + ax.bbox.bounds[0] left_lim = self.x + ax.bbox.bounds[0] - left_anchor_zone = (right_lim - left_lim) * .2 + left_lim - right_anchor_zone = (right_lim - left_lim) * .8 + left_lim + left_anchor_zone = (right_lim - left_lim) * 0.2 + left_lim + right_anchor_zone = (right_lim - left_lim) * 0.8 + left_lim if event.x < left_anchor_zone or event.x > right_anchor_zone: if self.text_instance: if not self.text_instance.show_text: midpoint = ( - (self.x + ax.bbox.bounds[2] + ax.bbox.bounds - [0]) + (self.x + ax.bbox.bounds[0])) / 2 + ( + self.x + + ax.bbox.bounds[2] + + ax.bbox.bounds[0] + ) + + (self.x + ax.bbox.bounds[0]) + ) / 2 if event.x < midpoint: - anchor = 'left' + anchor = "left" self.text_instance.x_text_pos = float( - self.x + ax.bbox.bounds[0]) + self.x + ax.bbox.bounds[0] + ) self.text_instance.y_text_pos = float( - self.y + ax.bbox.bounds[1] + ax.bbox.bounds[3]) + dp(2) + self.y + + ax.bbox.bounds[1] + + ax.bbox.bounds[3] + ) + dp(2) self.text_instance.offset_text = False else: - anchor = 'right' + anchor = "right" self.text_instance.x_text_pos = float( - self.x + ax.bbox.bounds[2] + ax.bbox.bounds[0]) + self.x + + ax.bbox.bounds[2] + + ax.bbox.bounds[0] + ) self.text_instance.y_text_pos = float( - self.y + ax.bbox.bounds[1] + ax.bbox.bounds[3]) + dp(2) + self.y + + ax.bbox.bounds[1] + + ax.bbox.bounds[3] + ) + dp(2) self.text_instance.offset_text = True self.text_instance.current_axis = ax self.text_instance.kind = { - 'axis': 'x', 'anchor': anchor} + "axis": "x", + "anchor": anchor, + } self.text_instance.show_text = True return def on_touch_up(self, event): - """ Manage Mouse/touch release """ + """Manage Mouse/touch release""" if self.disabled: return # remove it from our saved touches @@ -1810,25 +2197,36 @@ def on_touch_up(self, event): event.ungrab(self) del self._last_touch_pos[event] self._touches.remove(event) - if self.touch_mode == 'pan' or self.touch_mode == 'zoombox' or \ - self.touch_mode == 'pan_x' or self.touch_mode == 'pan_y' \ - or self.touch_mode == 'adjust_x' or self.touch_mode == 'adjust_y' \ - or self.touch_mode == 'minmax': + if ( + self.touch_mode == "pan" + or self.touch_mode == "zoombox" + or self.touch_mode == "pan_x" + or self.touch_mode == "pan_y" + or self.touch_mode == "adjust_x" + or self.touch_mode == "adjust_y" + or self.touch_mode == "minmax" + ): self.push_current() self._pick_info = None if self.interactive_axis: - if self.touch_mode == 'pan_x' or self.touch_mode == 'pan_y' \ - or self.touch_mode == 'adjust_x' or self.touch_mode == 'adjust_y': - self.touch_mode = 'pan' + if ( + self.touch_mode == "pan_x" + or self.touch_mode == "pan_y" + or self.touch_mode == "adjust_x" + or self.touch_mode == "adjust_y" + ): + self.touch_mode = "pan" self.first_touch_pan = None if self.last_line is not None: self.clear_line_prop() x, y = event.x, event.y - if abs( - self._box_size[0]) > 1 or abs( - self._box_size[1]) > 1 or self.touch_mode == 'zoombox': + if ( + abs(self._box_size[0]) > 1 + or abs(self._box_size[1]) > 1 + or self.touch_mode == "zoombox" + ): self.reset_box() if not self.collide_point(x, y) and self.do_update: # update axis lim if zoombox is used and touch outside widget @@ -1851,14 +2249,14 @@ def on_touch_up(self, event): ax = self.axes self.background = None self.show_compare_cursor = True - if self.last_line is None or self.touch_mode != 'cursor': + if self.last_line is None or self.touch_mode != "cursor": ax.figure.canvas.draw_idle() ax.figure.canvas.flush_events() return True def draw_box(self, event, x0, y0, x1, y1, onpress=False) -> None: - """ Draw zoombox method + """Draw zoombox method Args: event: touch kivy event @@ -1880,8 +2278,9 @@ def draw_box(self, event, x0, y0, x1, y1, onpress=False) -> None: self._alpha_rect = 0 if onpress: - ax = self.figure.canvas.inaxes((event.x - self.pos[0], - event.y - self.pos[1])) + ax = self.figure.canvas.inaxes( + (event.x - self.pos[0], event.y - self.pos[1]) + ) if ax is None: return self.axes = ax @@ -1889,17 +2288,22 @@ def draw_box(self, event, x0, y0, x1, y1, onpress=False) -> None: self.myevent.x = event.x - self.pos[0] self.myevent.y = event.y - self.pos[1] self.myevent.inaxes = self.figure.canvas.inaxes( - (event.x - self.pos[0], event.y - self.pos[1])) + (event.x - self.pos[0], event.y - self.pos[1]) + ) - self.box_axes = [a for a in self.figure.canvas.figure.get_axes() - if a.in_axes(self.myevent)] + self.box_axes = [ + a + for a in self.figure.canvas.figure.get_axes() + if a.in_axes(self.myevent) + ] else: ax = self.axes trans = ax.transData.inverted() xdata, ydata = trans.transform_point( - (event.x - pos_x, event.y - pos_y)) + (event.x - pos_x, event.y - pos_y) + ) xleft, xright = ax.get_xlim() ybottom, ytop = ax.get_ylim() @@ -1993,7 +2397,7 @@ def draw_box(self, event, x0, y0, x1, y1, onpress=False) -> None: self._box_size = x1 - x0, y1 - y0 def update_lim(self): - """ update axis lim if zoombox is used""" + """update axis lim if zoombox is used""" ax = self.axes self.do_update = False @@ -2009,43 +2413,44 @@ def update_lim(self): if xright > xleft: ax.set_xlim( - left=min( - self.x0_box[index], - self.x1_box[index]), - right=max( - self.x0_box[index], - self.x1_box[index])) + left=min(self.x0_box[index], self.x1_box[index]), + right=max(self.x0_box[index], self.x1_box[index]), + ) else: ax.set_xlim( - right=min( - self.x0_box[index], self.x1_box[index]), left=max( - self.x0_box[index], self.x1_box[index])) + right=min(self.x0_box[index], self.x1_box[index]), + left=max(self.x0_box[index], self.x1_box[index]), + ) if ytop > ybottom: ax.set_ylim( - bottom=min( - self.y0_box[index], self.y1_box[index]), top=max( - self.y0_box[index], self.y1_box[index])) + bottom=min(self.y0_box[index], self.y1_box[index]), + top=max(self.y0_box[index], self.y1_box[index]), + ) else: ax.set_ylim( - top=min( - self.y0_box[index], - self.y1_box[index]), - bottom=max( - self.y0_box[index], - self.y1_box[index])) + top=min(self.y0_box[index], self.y1_box[index]), + bottom=max(self.y0_box[index], self.y1_box[index]), + ) def reset_box(self): - """ reset zoombox and apply zoombox limit if zoombox option if selected""" + """reset zoombox and apply zoombox limit if zoombox option if selected""" if min(abs(self._box_size[0]), abs(self._box_size[1])) > self.minzoom: self.x0_box, self.y0_box = [], [] self.x1_box, self.y1_box = [], [] for ax in self.box_axes: trans = ax.transData.inverted() x0_box, y0_box = trans.transform_point( - (self._box_pos[0] - self.pos[0], self._box_pos[1] - self.pos[1])) + ( + self._box_pos[0] - self.pos[0], + self._box_pos[1] - self.pos[1], + ) + ) x1_box, y1_box = trans.transform_point( - (self._box_size[0] + self._box_pos[0] - self.pos[0], - self._box_size[1] + self._box_pos[1] - self.pos[1])) + ( + self._box_size[0] + self._box_pos[0] - self.pos[0], + self._box_size[1] + self._box_pos[1] - self.pos[1], + ) + ) self.x0_box.append(x0_box) self.y0_box.append(y0_box) self.x1_box.append(x1_box) @@ -2066,13 +2471,14 @@ def reset_box(self): self.invert_rect_ver = False def _draw_bitmap(self): - """ draw bitmap method. based on kivy scatter method""" + """draw bitmap method. based on kivy scatter method""" if self._bitmap is None: print("No bitmap!") return self._img_texture = Texture.create(size=(self.bt_w, self.bt_h)) self._img_texture.blit_buffer( - bytes(self._bitmap), colorfmt="rgba", bufferfmt='ubyte') + bytes(self._bitmap), colorfmt="rgba", bufferfmt="ubyte" + ) self._img_texture.flip_vertical() self.update_hover() diff --git a/kivy_matplotlib_widget/uix/graph_widget.py b/kivy_matplotlib_widget/uix/graph_widget.py index 726b5c2..525de87 100644 --- a/kivy_matplotlib_widget/uix/graph_widget.py +++ b/kivy_matplotlib_widget/uix/graph_widget.py @@ -1,4 +1,4 @@ -""" MatplotFigure is based on https://github.com/jeysonmc/kivy_matplotlib +"""MatplotFigure is based on https://github.com/jeysonmc/kivy_matplotlib and kivy scatter """ @@ -15,8 +15,16 @@ from matplotlib.backends.backend_agg import FigureCanvasAgg from kivy.vector import Vector from kivy.uix.widget import Widget -from kivy.properties import ObjectProperty, ListProperty, BooleanProperty, BoundedNumericProperty, AliasProperty, \ - NumericProperty, OptionProperty, DictProperty +from kivy.properties import ( + ObjectProperty, + ListProperty, + BooleanProperty, + BoundedNumericProperty, + AliasProperty, + NumericProperty, + OptionProperty, + DictProperty, +) from kivy.lang import Builder from kivy.graphics.transformation import Matrix from kivy.graphics.texture import Texture @@ -24,13 +32,19 @@ import copy import matplotlib -matplotlib.use('Agg') + +matplotlib.use("Agg") selector_widgets_available = False try: selector_widgets_available = True - from kivy_matplotlib_widget.uix.selector_widget import ResizeRelativeLayout, LassoRelativeLayout, EllipseRelativeLayout, SpanRelativeLayout + from kivy_matplotlib_widget.uix.selector_widget import ( + ResizeRelativeLayout, + LassoRelativeLayout, + EllipseRelativeLayout, + SpanRelativeLayout, + ) except ImportError: - print('Selector widgets are not available') + print("Selector widgets are not available") class MatplotlibEvent: @@ -40,7 +54,7 @@ class MatplotlibEvent: inaxes = None projection = False compare_xdata = False - pick_radius_axis = 'both' + pick_radius_axis = "both" class MatplotFigure(Widget): @@ -98,13 +112,8 @@ class MatplotFigure(Widget): desktop_mode = BooleanProperty(True) current_selector = OptionProperty( "None", - options=[ - "None", - 'rectangle', - 'lasso', - 'ellipse', - 'span', - 'custom']) + options=["None", "rectangle", "lasso", "ellipse", "span", "custom"], + ) highlight_hover = BooleanProperty(False) highlight_prop = DictProperty({}) highlight_alpha = NumericProperty(0.2) @@ -128,12 +137,12 @@ def on_figure(self, obj, value): patch_cpy = copy.copy(ax.patch) patch_cpy.set_visible(False) - if hasattr(ax, 'PolarTransform'): + if hasattr(ax, "PolarTransform"): for pos in list(ax.spines._dict.keys()): ax.spines[pos].set_zorder(10) self.disabled = True # polar graph do not handle pan/zoom else: - for pos in ['right', 'top', 'bottom', 'left']: + for pos in ["right", "top", "bottom", "left"]: ax.spines[pos].set_zorder(10) patch_cpy.set_zorder(9) self.background_patch_copy = ax.add_patch(patch_cpy) @@ -175,7 +184,7 @@ def __init__(self, **kwargs): self.lines = [] # option - self.touch_mode = 'pan' + self.touch_mode = "pan" self.hover_on = False # used matplotlib formatter to display x cursor value self.cursor_xaxis_formatter = None @@ -214,7 +223,7 @@ def __init__(self, **kwargs): self.show_compare_cursor = False # manage back and next event - if hasattr(cbook, '_Stack'): + if hasattr(cbook, "_Stack"): # manage matplotlib version with no Stack (replace by _Stack) self._nav_stack = cbook._Stack() else: @@ -238,13 +247,13 @@ def __init__(self, **kwargs): def on_kv_post(self, _): # if not self.selector: if self.current_selector != "None" and selector_widgets_available: - if self.current_selector == 'rectangle': + if self.current_selector == "rectangle": self.set_selector(ResizeRelativeLayout) - elif self.current_selector == 'lasso': + elif self.current_selector == "lasso": self.set_selector(LassoRelativeLayout) - elif self.current_selector == 'ellipse': + elif self.current_selector == "ellipse": self.set_selector(EllipseRelativeLayout) - elif self.current_selector == 'span': + elif self.current_selector == "span": self.set_selector(SpanRelativeLayout) self.kv_post_done = True @@ -260,18 +269,19 @@ def on_current_selector(self, instance, value, *args): if self.kv_post_done and selector_widgets_available: - if value == 'rectangle': + if value == "rectangle": self.set_selector(ResizeRelativeLayout) - elif value == 'lasso': + elif value == "lasso": self.set_selector(LassoRelativeLayout) - elif value == 'ellipse': + elif value == "ellipse": self.set_selector(EllipseRelativeLayout) - elif self.current_selector == 'span': + elif self.current_selector == "span": self.set_selector(SpanRelativeLayout) elif value == "None": if self.selector: Window.unbind( - mouse_pos=self.selector.resize_wgt.on_mouse_pos) + mouse_pos=self.selector.resize_wgt.on_mouse_pos + ) self.parent.remove_widget(self.selector) self.selector = None @@ -289,8 +299,8 @@ def set_selector(self, selector, *args): self.parent.remove_widget(self.selector) self.selector = selector( - figure_wgt=self, - desktop_mode=self.desktop_mode) + figure_wgt=self, desktop_mode=self.desktop_mode + ) self.selector.resize_wgt.ax = self.axes if selector_collection: self.set_collection() @@ -321,7 +331,7 @@ def set_callback_clear(self, callback): self.selector.resize_wgt.set_callback_clear(callback) def register_lines(self, lines: list) -> None: - """ register lines method + """register lines method Args: lines (list): list of matplolib line class @@ -334,20 +344,22 @@ def register_lines(self, lines: list) -> None: ymin, ymax = self.axes.get_ylim() # create cross hair cusor self.horizontal_line = self.axes.axhline( - y=self.ymin, color='k', lw=0.8, ls='--', visible=False) + y=self.ymin, color="k", lw=0.8, ls="--", visible=False + ) self.vertical_line = self.axes.axvline( - x=self.xmin, color='k', lw=0.8, ls='--', visible=False) + x=self.xmin, color="k", lw=0.8, ls="--", visible=False + ) # register lines self.lines = lines # cursor text - self.text = self.axes.text(1.0, 1.01, '', - transform=self.axes.transAxes, - ha='right') + self.text = self.axes.text( + 1.0, 1.01, "", transform=self.axes.transAxes, ha="right" + ) def set_cross_hair_visible(self, visible: bool) -> None: - """ set curcor visibility + """set curcor visibility Args: visible (bool): make cursor visble or not @@ -361,7 +373,7 @@ def set_cross_hair_visible(self, visible: bool) -> None: self.text.set_visible(visible) def clear_line_prop(self) -> None: - """ clear attribute line_prop method + """clear attribute line_prop method Args: None @@ -372,13 +384,13 @@ def clear_line_prop(self) -> None: """ if self.last_line_prop: for key in self.last_line_prop: - set_line_attr = getattr(self.last_line, 'set_' + key) + set_line_attr = getattr(self.last_line, "set_" + key) set_line_attr(self.last_line_prop[key]) self.last_line_prop = {} self.last_line = None def hover(self, event) -> None: - """ hover cursor method (cursor to nearest value) + """hover cursor method (cursor to nearest value) Args: event: touch kivy event @@ -394,7 +406,8 @@ def hover(self, event) -> None: # transform kivy x,y touch event to x,y data trans = self.axes.transData.inverted() xdata, ydata = trans.transform_point( - (event.x - self.pos[0], event.y - self.pos[1])) + (event.x - self.pos[0], event.y - self.pos[1]) + ) # loop all register lines and find closest x,y data for each valid # line @@ -413,9 +426,9 @@ def hover(self, event) -> None: # find closest data index from touch (x axis) if self.xsorted: index = min( - np.searchsorted( - self.x_cursor, xdata), len( - self.y_cursor) - 1) + np.searchsorted(self.x_cursor, xdata), + len(self.y_cursor) - 1, + ) else: index = np.argsort(abs(self.x_cursor - xdata))[0] @@ -430,14 +443,20 @@ def hover(self, event) -> None: ax = line.axes # left axis xy_pixels_mouse = ax.transData.transform( - [(xdata, ydata)]) - if np.ma.is_masked(x) or np.ma.is_masked( - y) or np.isnan(x) or np.isnan(y): + [(xdata, ydata)] + ) + if ( + np.ma.is_masked(x) + or np.ma.is_masked(y) + or np.isnan(x) + or np.isnan(y) + ): distance.append(np.nan) else: xy_pixels = ax.transData.transform( - [(x, ydata)]) - dx2 = (xy_pixels_mouse[0][0] - xy_pixels[0][0]) + [(x, ydata)] + ) + dx2 = xy_pixels_mouse[0][0] - xy_pixels[0][0] distance.append(abs(dx2)) else: @@ -448,22 +467,25 @@ def hover(self, event) -> None: ax = line.axes # left axis xy_pixels_mouse = ax.transData.transform( - [(xdata, ydata)]) + [(xdata, ydata)] + ) if np.ma.is_masked(x) or np.ma.is_masked(y): distance.append(np.nan) else: xy_pixels = ax.transData.transform([(x, y)]) dx2 = ( - xy_pixels_mouse[0][0] - xy_pixels[0][0])**2 + xy_pixels_mouse[0][0] - xy_pixels[0][0] + ) ** 2 dy2 = ( - xy_pixels_mouse[0][1] - xy_pixels[0][1])**2 + xy_pixels_mouse[0][1] - xy_pixels[0][1] + ) ** 2 # store distance - if self.pick_radius_axis == 'both': - distance.append((dx2 + dy2)**0.5) - if self.pick_radius_axis == 'x': + if self.pick_radius_axis == "both": + distance.append((dx2 + dy2) ** 0.5) + if self.pick_radius_axis == "x": distance.append(abs(dx2)) - if self.pick_radius_axis == 'y': + if self.pick_radius_axis == "y": distance.append(abs(dy2)) # store all best lines and index good_line.append(line) @@ -479,11 +501,13 @@ def hover(self, event) -> None: # index of minimum distance if self.compare_xdata: if not self.hover_instance or not hasattr( - self.hover_instance, 'children_list'): + self.hover_instance, "children_list" + ): return idx_best_list = np.flatnonzero( - np.array(distance) == np.nanmin(distance)) + np.array(distance) == np.nanmin(distance) + ) # get datas from closest line line = good_line[idx_best_list[0]] @@ -494,12 +518,15 @@ def hover(self, event) -> None: xy_pos = ax.transData.transform([(x, y)]) self.x_hover_data = x self.y_hover_data = y - self.hover_instance.x_hover_pos = float( - xy_pos[0][0]) + self.x - self.hover_instance.y_hover_pos = float( - xy_pos[0][1]) + self.y - self.hover_instance.y_touch_pos = float( - xy_pixels[0][1]) + self.y + self.hover_instance.x_hover_pos = ( + float(xy_pos[0][0]) + self.x + ) + self.hover_instance.y_hover_pos = ( + float(xy_pos[0][1]) + self.y + ) + self.hover_instance.y_touch_pos = ( + float(xy_pixels[0][1]) + self.y + ) if self.first_call_compare_hover: self.hover_instance.show_cursor = True @@ -516,31 +543,53 @@ def hover(self, event) -> None: else: line = good_line[idx_best_list[i]] line_label = line.get_label() - if line_label in self.hover_instance.children_names: + if ( + line_label + in self.hover_instance.children_names + ): index = self.hover_instance.children_names.index( - line_label) + line_label + ) y_cursor = line.get_ydata() y = y_cursor[good_index[idx_best_list[i]]] xy_pos = ax.transData.transform([(x, y)]) pos_y = float(xy_pos[0][1]) + self.y - if pos_y < self.y + self.axes.bbox.bounds[1] + self.axes.bbox.bounds[3] and \ - pos_y > self.y + self.axes.bbox.bounds[1]: - available_widget[index].x_hover_pos = float( - xy_pos[0][0]) + self.x - available_widget[index].y_hover_pos = float( - xy_pos[0][1]) + self.y - available_widget[index].custom_color = get_color_from_hex( - to_hex(line.get_color())) + if ( + pos_y + < self.y + + self.axes.bbox.bounds[1] + + self.axes.bbox.bounds[3] + and pos_y + > self.y + self.axes.bbox.bounds[1] + ): + available_widget[index].x_hover_pos = ( + float(xy_pos[0][0]) + self.x + ) + available_widget[index].y_hover_pos = ( + float(xy_pos[0][1]) + self.y + ) + available_widget[ + index + ].custom_color = get_color_from_hex( + to_hex(line.get_color()) + ) if self.cursor_yaxis_formatter: y = self.cursor_yaxis_formatter.format_data( - y) + y + ) else: - y = ax.yaxis.get_major_formatter().format_data_short(y) - available_widget[index].label_y_value = f"{y}" - available_widget[index].show_widget = True + y = ax.yaxis.get_major_formatter().format_data_short( + y + ) + available_widget[ + index + ].label_y_value = f"{y}" + available_widget[index].show_widget = ( + True + ) index_list.remove(index) for ii in index_list: @@ -549,24 +598,39 @@ def hover(self, event) -> None: if self.cursor_xaxis_formatter: x = self.cursor_xaxis_formatter.format_data(x) else: - x = ax.xaxis.get_major_formatter().format_data_short(x) + x = ax.xaxis.get_major_formatter().format_data_short( + x + ) self.hover_instance.label_x_value = f"{x}" - if hasattr(self.hover_instance, 'overlap_check'): + if hasattr(self.hover_instance, "overlap_check"): self.hover_instance.overlap_check() - self.hover_instance.xmin_line = float( - ax.bbox.bounds[0]) + self.x - self.hover_instance.xmax_line = float( - ax.bbox.bounds[0] + ax.bbox.bounds[2]) + self.x - self.hover_instance.ymin_line = float( - ax.bbox.bounds[1]) + self.y - self.hover_instance.ymax_line = float( - ax.bbox.bounds[1] + ax.bbox.bounds[3]) + self.y - - if self.hover_instance.x_hover_pos > self.x + self.axes.bbox.bounds[2] + self.axes.bbox.bounds[0] or \ - self.hover_instance.x_hover_pos < self.x + self.axes.bbox.bounds[0] or len(index_list) == nb_widget: + self.hover_instance.xmin_line = ( + float(ax.bbox.bounds[0]) + self.x + ) + self.hover_instance.xmax_line = ( + float(ax.bbox.bounds[0] + ax.bbox.bounds[2]) + + self.x + ) + self.hover_instance.ymin_line = ( + float(ax.bbox.bounds[1]) + self.y + ) + self.hover_instance.ymax_line = ( + float(ax.bbox.bounds[1] + ax.bbox.bounds[3]) + + self.y + ) + + if ( + self.hover_instance.x_hover_pos + > self.x + + self.axes.bbox.bounds[2] + + self.axes.bbox.bounds[0] + or self.hover_instance.x_hover_pos + < self.x + self.axes.bbox.bounds[0] + or len(index_list) == nb_widget + ): self.hover_instance.hover_outside_bound = True else: self.hover_instance.hover_outside_bound = False @@ -587,48 +651,79 @@ def hover(self, event) -> None: # update the cursor x,y data ax = line.axes - self.horizontal_line.set_ydata([y,]) - self.vertical_line.set_xdata([x,]) + self.horizontal_line.set_ydata( + [ + y, + ] + ) + self.vertical_line.set_xdata( + [ + x, + ] + ) # x y label if self.hover_instance: xy_pos = ax.transData.transform([(x, y)]) self.x_hover_data = x self.y_hover_data = y - self.hover_instance.x_hover_pos = float( - xy_pos[0][0]) + self.x - self.hover_instance.y_hover_pos = float( - xy_pos[0][1]) + self.y + self.hover_instance.x_hover_pos = ( + float(xy_pos[0][0]) + self.x + ) + self.hover_instance.y_hover_pos = ( + float(xy_pos[0][1]) + self.y + ) self.hover_instance.show_cursor = True if self.cursor_xaxis_formatter: x = self.cursor_xaxis_formatter.format_data(x) else: - x = ax.xaxis.get_major_formatter().format_data_short(x) + x = ax.xaxis.get_major_formatter().format_data_short( + x + ) if self.cursor_yaxis_formatter: y = self.cursor_yaxis_formatter.format_data(y) else: - y = ax.yaxis.get_major_formatter().format_data_short(y) + y = ax.yaxis.get_major_formatter().format_data_short( + y + ) self.hover_instance.label_x_value = f"{x}" self.hover_instance.label_y_value = f"{y}" - self.hover_instance.xmin_line = float( - ax.bbox.bounds[0]) + self.x - self.hover_instance.xmax_line = float( - ax.bbox.bounds[0] + ax.bbox.bounds[2]) + self.x - self.hover_instance.ymin_line = float( - ax.bbox.bounds[1]) + self.y - self.hover_instance.ymax_line = float( - ax.bbox.bounds[1] + ax.bbox.bounds[3]) + self.y + self.hover_instance.xmin_line = ( + float(ax.bbox.bounds[0]) + self.x + ) + self.hover_instance.xmax_line = ( + float(ax.bbox.bounds[0] + ax.bbox.bounds[2]) + + self.x + ) + self.hover_instance.ymin_line = ( + float(ax.bbox.bounds[1]) + self.y + ) + self.hover_instance.ymax_line = ( + float(ax.bbox.bounds[1] + ax.bbox.bounds[3]) + + self.y + ) self.hover_instance.custom_label = line.get_label() self.hover_instance.custom_color = get_color_from_hex( - to_hex(line.get_color())) - - if self.hover_instance.x_hover_pos > self.x + self.axes.bbox.bounds[2] + self.axes.bbox.bounds[0] or \ - self.hover_instance.x_hover_pos < self.x + self.axes.bbox.bounds[0] or \ - self.hover_instance.y_hover_pos > self.y + self.axes.bbox.bounds[1] + self.axes.bbox.bounds[3] or \ - self.hover_instance.y_hover_pos < self.y + self.axes.bbox.bounds[1]: + to_hex(line.get_color()) + ) + + if ( + self.hover_instance.x_hover_pos + > self.x + + self.axes.bbox.bounds[2] + + self.axes.bbox.bounds[0] + or self.hover_instance.x_hover_pos + < self.x + self.axes.bbox.bounds[0] + or self.hover_instance.y_hover_pos + > self.y + + self.axes.bbox.bounds[1] + + self.axes.bbox.bounds[3] + or self.hover_instance.y_hover_pos + < self.y + self.axes.bbox.bounds[1] + ): self.hover_instance.hover_outside_bound = True else: self.hover_instance.hover_outside_bound = False @@ -637,12 +732,14 @@ def hover(self, event) -> None: self.myevent.x = event.x - self.pos[0] self.myevent.y = event.y - self.pos[1] self.myevent.inaxes = self.figure.canvas.inaxes( - (event.x - self.pos[0], event.y - self.pos[1])) + (event.x - self.pos[0], event.y - self.pos[1]) + ) - axes = [a - for a in - self.figure.canvas.figure.get_axes() - if a.in_axes(self.myevent)] + axes = [ + a + for a in self.figure.canvas.figure.get_axes() + if a.in_axes(self.myevent) + ] if not axes: @@ -650,10 +747,12 @@ def hover(self, event) -> None: self.clear_line_prop() if self.background: self.axes.figure.canvas.restore_region( - self.background) + self.background + ) # draw (blit method) self.axes.figure.canvas.blit( - self.axes.bbox) + self.axes.bbox + ) self.axes.figure.canvas.flush_events() self.background = None @@ -662,21 +761,29 @@ def hover(self, event) -> None: # blit method (always use because same visual # effect as draw) if self.background is None: - self.background = self.axes.figure.canvas.copy_from_bbox( - self.axes.figure.bbox) + self.background = ( + self.axes.figure.canvas.copy_from_bbox( + self.axes.figure.bbox + ) + ) if self.last_line is None: default_alpha = [] lines_list = self.axes.lines for current_line in lines_list: default_alpha.append( - current_line.get_alpha()) + current_line.get_alpha() + ) current_line.set_alpha( - self.highlight_alpha) + self.highlight_alpha + ) self.axes.figure.canvas.draw_idle() self.axes.figure.canvas.flush_events() - self.background_highlight = self.axes.figure.canvas.copy_from_bbox( - self.axes.figure.bbox) + self.background_highlight = ( + self.axes.figure.canvas.copy_from_bbox( + self.axes.figure.bbox + ) + ) self.last_line = line for i, current_line in enumerate(lines_list): current_line.set_alpha(default_alpha[i]) @@ -686,30 +793,38 @@ def hover(self, event) -> None: self.last_line_prop = {} for key in self.highlight_prop: # if hasattr(line,key): - line_attr = getattr(line, 'get_' + key) + line_attr = getattr(line, "get_" + key) self.last_line_prop.update( - {key: line_attr()}) + {key: line_attr()} + ) set_line_attr = getattr( - line, 'set_' + key) + line, "set_" + key + ) set_line_attr(self.highlight_prop[key]) elif self.last_line_prop: for key in self.last_line_prop: set_line_attr = getattr( - self.last_line, 'set_' + key) + self.last_line, "set_" + key + ) set_line_attr(self.last_line_prop[key]) - self.hover_instance.custom_color = get_color_from_hex( - to_hex(line.get_color())) + self.hover_instance.custom_color = ( + get_color_from_hex( + to_hex(line.get_color()) + ) + ) self.last_line_prop = {} for key in self.highlight_prop: - line_attr = getattr(line, 'get_' + key) + line_attr = getattr(line, "get_" + key) self.last_line_prop.update( - {key: line_attr()}) - set_line_attr = getattr(line, 'set_' + key) + {key: line_attr()} + ) + set_line_attr = getattr(line, "set_" + key) set_line_attr(self.highlight_prop[key]) self.last_line = line self.axes.figure.canvas.restore_region( - self.background_highlight) + self.background_highlight + ) self.axes.draw_artist(line) # draw (blit method) @@ -720,11 +835,15 @@ def hover(self, event) -> None: if self.cursor_xaxis_formatter: x = self.cursor_xaxis_formatter.format_data(x) else: - x = ax.xaxis.get_major_formatter().format_data_short(x) + x = ax.xaxis.get_major_formatter().format_data_short( + x + ) if self.cursor_yaxis_formatter: y = self.cursor_yaxis_formatter.format_data(y) else: - y = ax.yaxis.get_major_formatter().format_data_short(y) + y = ax.yaxis.get_major_formatter().format_data_short( + y + ) self.text.set_text(f"x={x}, y={y}") # blit method (always use because same visual effect as @@ -733,8 +852,11 @@ def hover(self, event) -> None: self.set_cross_hair_visible(False) self.axes.figure.canvas.draw_idle() self.axes.figure.canvas.flush_events() - self.background = self.axes.figure.canvas.copy_from_bbox( - self.axes.figure.bbox) + self.background = ( + self.axes.figure.canvas.copy_from_bbox( + self.axes.figure.bbox + ) + ) self.set_cross_hair_visible(True) if self.last_line is not None: self.clear_line_prop() @@ -762,10 +884,14 @@ def hover(self, event) -> None: self.myevent.x = event.x - self.pos[0] self.myevent.y = event.y - self.pos[1] self.myevent.inaxes = self.figure.canvas.inaxes( - (event.x - self.pos[0], event.y - self.pos[1])) + (event.x - self.pos[0], event.y - self.pos[1]) + ) - axes = [a for a in self.figure.canvas.figure.get_axes() - if a.in_axes(self.myevent)] + axes = [ + a + for a in self.figure.canvas.figure.get_axes() + if a.in_axes(self.myevent) + ] if not axes: @@ -773,10 +899,12 @@ def hover(self, event) -> None: self.clear_line_prop() if self.background: self.axes.figure.canvas.restore_region( - self.background) + self.background + ) # draw (blit method) self.axes.figure.canvas.blit( - self.axes.bbox) + self.axes.bbox + ) self.axes.figure.canvas.flush_events() self.background = None @@ -787,9 +915,11 @@ def autoscale(self): return ax = self.axes ax.relim(visible_only=self.autoscale_visible_only) - ax.autoscale_view(tight=self.autoscale_tight, - scalex=True if self.autoscale_axis != "y" else False, - scaley=True if self.autoscale_axis != "x" else False) + ax.autoscale_view( + tight=self.autoscale_tight, + scalex=True if self.autoscale_axis != "y" else False, + scaley=True if self.autoscale_axis != "x" else False, + ) ax.autoscale(axis=self.autoscale_axis, tight=self.autoscale_tight) ax.figure.canvas.draw_idle() ax.figure.canvas.flush_events() @@ -799,16 +929,18 @@ def autoscale(self): self.ymin, self.ymax = ax.get_ylim() def home(self) -> None: - """ reset data axis + """reset data axis Return: None """ # do nothing is all min/max are not set - if self.xmin is not None and \ - self.xmax is not None and \ - self.ymin is not None and \ - self.ymax is not None: + if ( + self.xmin is not None + and self.xmax is not None + and self.ymin is not None + and self.ymax is not None + ): ax = self.axes xleft, xright = ax.get_xlim() @@ -863,11 +995,19 @@ def push_current(self): """Push the current view limits and position onto the stack.""" self._nav_stack.push( WeakKeyDictionary( - {ax: (ax._get_view(), - # Store both the original and modified positions. - (ax.get_position(True).frozen(), - ax.get_position().frozen())) - for ax in self.figure.axes})) + { + ax: ( + ax._get_view(), + # Store both the original and modified positions. + ( + ax.get_position(True).frozen(), + ax.get_position().frozen(), + ), + ) + for ax in self.figure.axes + } + ) + ) self.set_history_buttons() def update(self): @@ -889,8 +1029,8 @@ def _update_view(self): for ax, (view, (pos_orig, pos_active)) in items: ax._set_view(view) # Restore both the original and modified positions - ax._set_position(pos_orig, 'original') - ax._set_position(pos_active, 'active') + ax._set_position(pos_orig, "original") + ax._set_position(pos_active, "active") self.figure.canvas.draw_idle() self.figure.canvas.flush_events() @@ -898,7 +1038,7 @@ def set_history_buttons(self): """Enable or disable the back/forward button.""" def reset_touch(self) -> None: - """ reset touch + """reset touch Return: None @@ -907,7 +1047,7 @@ def reset_touch(self) -> None: self._last_touch_pos = {} def _get_scale(self): - """ kivy scatter _get_scale method """ + """kivy scatter _get_scale method""" p1 = Vector(*self.to_parent(0, 0)) p2 = Vector(*self.to_parent(1, 0)) scale = p1.distance(p2) @@ -917,7 +1057,7 @@ def _get_scale(self): # prevent anything wrong with scale, just avoid to dispatch it # if the scale "visually" didn't change. #947 # Remove this ugly hack when we'll be Python 3 only. - if hasattr(self, '_scale_p'): + if hasattr(self, "_scale_p"): if str(scale) == str(self._scale_p): return self._scale_p @@ -925,66 +1065,73 @@ def _get_scale(self): return scale def _set_scale(self, scale): - """ kivy scatter _set_scale method """ + """kivy scatter _set_scale method""" rescale = scale * 1.0 / self.scale - self.apply_transform(Matrix().scale(rescale, rescale, rescale), - post_multiply=True, - anchor=self.to_local(*self.center)) + self.apply_transform( + Matrix().scale(rescale, rescale, rescale), + post_multiply=True, + anchor=self.to_local(*self.center), + ) - scale = AliasProperty(_get_scale, _set_scale, bind=('x', 'y', 'transform')) - '''Scale value of the scatter. + scale = AliasProperty(_get_scale, _set_scale, bind=("x", "y", "transform")) + """Scale value of the scatter. :attr:`scale` is an :class:`~kivy.properties.AliasProperty` and defaults to 1.0. - ''' + """ def _draw_bitmap(self): - """ draw bitmap method. based on kivy scatter method""" + """draw bitmap method. based on kivy scatter method""" if self._bitmap is None: print("No bitmap!") return self._img_texture = Texture.create(size=(self.bt_w, self.bt_h)) self._img_texture.blit_buffer( - bytes(self._bitmap), colorfmt="rgba", bufferfmt='ubyte') + bytes(self._bitmap), colorfmt="rgba", bufferfmt="ubyte" + ) self._img_texture.flip_vertical() self.update_hover() self.update_selector() def transform_with_touch(self, event): - """ manage touch behaviour. based on kivy scatter method""" + """manage touch behaviour. based on kivy scatter method""" # just do a simple one finger drag changed = False if len(self._touches) == self.translation_touches: - if self.touch_mode == 'pan': + if self.touch_mode == "pan": if self._nav_stack() is None: self.push_current() self.apply_pan(self.axes, event) - if self.touch_mode == 'pan_x' or self.touch_mode == 'pan_y' \ - or self.touch_mode == 'adjust_x' or self.touch_mode == 'adjust_y': + if ( + self.touch_mode == "pan_x" + or self.touch_mode == "pan_y" + or self.touch_mode == "adjust_x" + or self.touch_mode == "adjust_y" + ): if self._nav_stack() is None: self.push_current() self.apply_pan(self.axes, event, mode=self.touch_mode) - elif self.touch_mode == 'drag_legend': + elif self.touch_mode == "drag_legend": if self.legend_instance: self.apply_drag_legend(self.axes, event) - elif self.touch_mode == 'zoombox': + elif self.touch_mode == "zoombox": if self._nav_stack() is None: self.push_current() real_x, real_y = event.x - self.pos[0], event.y - self.pos[1] # in case x_init is not create - if not hasattr(self, 'x_init'): + if not hasattr(self, "x_init"): self.x_init = event.x self.y_init = real_y self.draw_box(event, self.x_init, self.y_init, event.x, real_y) # mode cursor - elif self.touch_mode == 'cursor': + elif self.touch_mode == "cursor": self.hover_on = True self.hover(event) @@ -995,8 +1142,11 @@ def transform_with_touch(self, event): return changed # we have more than one touch... list of last known pos - points = [Vector(self._last_touch_pos[t]) for t in self._touches - if t is not event] + points = [ + Vector(self._last_touch_pos[t]) + for t in self._touches + if t is not event + ] # add current touch last points.append(Vector(event.pos)) @@ -1021,16 +1171,28 @@ def transform_with_touch(self, event): if self.auto_zoom: v1 = Vector(0, 10) angle = v1.angle(new_line) + 180 - if angle < 0 + self.zoom_angle_detection or angle > 360 - self.zoom_angle_detection: + if ( + angle < 0 + self.zoom_angle_detection + or angle > 360 - self.zoom_angle_detection + ): self.do_zoom_x = False self.do_zoom_y = True - elif angle > 90 - self.zoom_angle_detection and angle < 90 + self.zoom_angle_detection: + elif ( + angle > 90 - self.zoom_angle_detection + and angle < 90 + self.zoom_angle_detection + ): self.do_zoom_x = True self.do_zoom_y = False - elif angle > 180 - self.zoom_angle_detection and angle < 180 + self.zoom_angle_detection: + elif ( + angle > 180 - self.zoom_angle_detection + and angle < 180 + self.zoom_angle_detection + ): self.do_zoom_x = False self.do_zoom_y = True - elif angle > 270 - self.zoom_angle_detection and angle < 270 + self.zoom_angle_detection: + elif ( + angle > 270 - self.zoom_angle_detection + and angle < 270 + self.zoom_angle_detection + ): self.do_zoom_x = True self.do_zoom_y = False else: @@ -1052,10 +1214,12 @@ def transform_with_touch(self, event): return changed def on_motion(self, *args): - '''Kivy Event to trigger mouse event on motion - `enter_notify_event`. - ''' - if self._pressed or self.disabled: # Do not process this event if there's a touch_move + """Kivy Event to trigger mouse event on motion + `enter_notify_event`. + """ + if ( + self._pressed or self.disabled + ): # Do not process this event if there's a touch_move return pos = args[1] newcoord = self.to_widget(pos[0], pos[1]) @@ -1074,24 +1238,29 @@ def on_motion(self, *args): self.hover(FakeEvent) def get_data_xy(self, x, y): - """ manage x y data in navigation bar """ + """manage x y data in navigation bar""" trans = self.axes.transData.inverted() - xdata, ydata = trans.transform_point((x - self.pos[0], - y - self.pos[1])) + xdata, ydata = trans.transform_point( + (x - self.pos[0], y - self.pos[1]) + ) if self.cursor_xaxis_formatter: x_format = self.cursor_xaxis_formatter.format_data(xdata) else: - x_format = self.axes.xaxis.get_major_formatter().format_data_short(xdata) + x_format = self.axes.xaxis.get_major_formatter().format_data_short( + xdata + ) if self.cursor_yaxis_formatter: y_format = self.cursor_yaxis_formatter.format_data(ydata) else: - y_format = self.axes.yaxis.get_major_formatter().format_data_short(ydata) + y_format = self.axes.yaxis.get_major_formatter().format_data_short( + ydata + ) return x_format, y_format def on_touch_down(self, event): - """ Manage Mouse/touch press """ + """Manage Mouse/touch press""" if self.disabled: return x, y = event.x, event.y @@ -1107,7 +1276,7 @@ def on_touch_down(self, event): self.current_legend = current_legend break if select_legend: - if self.touch_mode != 'drag_legend': + if self.touch_mode != "drag_legend": return False else: event.grab(self) @@ -1129,23 +1298,23 @@ def on_touch_down(self, event): elif event.is_double_tap: if not self.disable_double_tap: - if self.touch_mode != 'selector': + if self.touch_mode != "selector": self.home() return True else: - if self.touch_mode == 'cursor': + if self.touch_mode == "cursor": self.hover_on = True self.hover(event) - elif self.touch_mode == 'zoombox': + elif self.touch_mode == "zoombox": real_x, real_y = x - self.pos[0], y - self.pos[1] self.x_init = x self.y_init = real_y self.draw_box(event, x, real_y, x, real_y) - elif self.touch_mode == 'minmax': + elif self.touch_mode == "minmax": self.min_max(event) - elif self.touch_mode == 'selector': + elif self.touch_mode == "selector": pass event.grab(self) @@ -1161,14 +1330,14 @@ def on_touch_down(self, event): return False def on_touch_move(self, event): - """ Manage Mouse/touch move while pressed """ + """Manage Mouse/touch move while pressed""" if self.disabled: return x, y = event.x, event.y if event.is_double_tap: if not self.disable_double_tap: - if self.touch_mode != 'selector': + if self.touch_mode != "selector": self.home() return True @@ -1182,7 +1351,7 @@ def on_touch_move(self, event): return True def on_touch_up(self, event): - """ Manage Mouse/touch release """ + """Manage Mouse/touch release""" if self.disabled: return # remove it from our saved touches @@ -1190,25 +1359,36 @@ def on_touch_up(self, event): event.ungrab(self) del self._last_touch_pos[event] self._touches.remove(event) - if (self.touch_mode == 'pan' or self.touch_mode == 'zoombox' or - self.touch_mode == 'pan_x' or self.touch_mode == 'pan_y' or - self.touch_mode == 'adjust_x' or self.touch_mode == 'adjust_y' or - self.touch_mode == 'minmax'): + if ( + self.touch_mode == "pan" + or self.touch_mode == "zoombox" + or self.touch_mode == "pan_x" + or self.touch_mode == "pan_y" + or self.touch_mode == "adjust_x" + or self.touch_mode == "adjust_y" + or self.touch_mode == "minmax" + ): self.push_current() if self.interactive_axis: - if self.touch_mode == 'pan_x' or self.touch_mode == 'pan_y' \ - or self.touch_mode == 'adjust_x' or self.touch_mode == 'adjust_y': - self.touch_mode = 'pan' + if ( + self.touch_mode == "pan_x" + or self.touch_mode == "pan_y" + or self.touch_mode == "adjust_x" + or self.touch_mode == "adjust_y" + ): + self.touch_mode = "pan" self.first_touch_pan = None if self.last_line is not None: self.clear_line_prop() x, y = event.x, event.y - if abs( - self._box_size[0]) > 1 or abs( - self._box_size[1]) > 1 or self.touch_mode == 'zoombox': + if ( + abs(self._box_size[0]) > 1 + or abs(self._box_size[1]) > 1 + or self.touch_mode == "zoombox" + ): self.reset_box() if not self.collide_point(x, y) and self.do_update: # update axis lim if zoombox is used and touch outside widget @@ -1231,15 +1411,15 @@ def on_touch_up(self, event): ax = self.axes self.background = None self.show_compare_cursor = True - if self.last_line is None or self.touch_mode != 'cursor': + if self.last_line is None or self.touch_mode != "cursor": ax.figure.canvas.draw_idle() ax.figure.canvas.flush_events() return True def apply_zoom(self, scale_factor, ax, anchor=(0, 0), new_line=None): - """ zoom touch method """ - if self.touch_mode == 'selector': + """zoom touch method""" + if self.touch_mode == "selector": return x = anchor[0] - self.pos[0] @@ -1254,7 +1434,7 @@ def apply_zoom(self, scale_factor, ax, anchor=(0, 0), new_line=None): scale = ax.get_xscale() yscale = ax.get_yscale() - if scale == 'linear': + if scale == "linear": old_min = cur_xlim[0] old_max = cur_xlim[1] @@ -1265,7 +1445,7 @@ def apply_zoom(self, scale_factor, ax, anchor=(0, 0), new_line=None): xdata = self.transform_eval(xdata, ax.yaxis) old_max = self.transform_eval(max_, ax.yaxis) - if yscale == 'linear': + if yscale == "linear": yold_min = cur_ylim[0] yold_max = cur_ylim[1] @@ -1283,36 +1463,44 @@ def apply_zoom(self, scale_factor, ax, anchor=(0, 0), new_line=None): rely = (yold_max - ydata) / (yold_max - yold_min) if self.do_zoom_x: - if scale == 'linear': - ax.set_xlim([xdata - new_width * (1 - relx), - xdata + new_width * (relx)]) + if scale == "linear": + ax.set_xlim( + [ + xdata - new_width * (1 - relx), + xdata + new_width * (relx), + ] + ) else: new_min = xdata - new_width * (1 - relx) new_max = xdata + new_width * (relx) try: new_min, new_max = self.inv_transform_eval( - new_min, ax.yaxis), self.inv_transform_eval( - new_max, ax.yaxis) + new_min, ax.yaxis + ), self.inv_transform_eval(new_max, ax.yaxis) except OverflowError: # Limit case new_min, new_max = min_, max_ - if new_min <= 0. or new_max <= 0.: # Limit case + if new_min <= 0.0 or new_max <= 0.0: # Limit case new_min, new_max = min_, max_ ax.set_xlim([new_min, new_max]) if self.do_zoom_y: - if yscale == 'linear': - ax.set_ylim([ydata - new_height * (1 - rely), - ydata + new_height * (rely)]) + if yscale == "linear": + ax.set_ylim( + [ + ydata - new_height * (1 - rely), + ydata + new_height * (rely), + ] + ) else: new_ymin = ydata - new_height * (1 - rely) new_ymax = ydata + new_height * (rely) try: new_ymin, new_ymax = self.inv_transform_eval( - new_ymin, ax.yaxis), self.inv_transform_eval( - new_ymax, ax.yaxis) + new_ymin, ax.yaxis + ), self.inv_transform_eval(new_ymax, ax.yaxis) except OverflowError: # Limit case new_ymin, new_ymax = ymin_, ymax_ - if new_ymin <= 0. or new_ymax <= 0.: # Limit case + if new_ymin <= 0.0 or new_ymax <= 0.0: # Limit case new_ymin, new_ymax = ymin_, ymax_ ax.set_ylim([new_ymin, new_ymax]) @@ -1323,7 +1511,8 @@ def apply_zoom(self, scale_factor, ax, anchor=(0, 0), new_line=None): ax.figure.canvas.draw_idle() ax.figure.canvas.flush_events() self.background = ax.figure.canvas.copy_from_bbox( - ax.figure.bbox) + ax.figure.bbox + ) self.background_patch_copy.set_visible(False) if self.last_line is not None: self.clear_line_prop() @@ -1339,30 +1528,36 @@ def apply_zoom(self, scale_factor, ax, anchor=(0, 0), new_line=None): ax.figure.canvas.draw_idle() ax.figure.canvas.flush_events() - def apply_pan(self, ax, event, mode='pan'): - """ pan method """ + def apply_pan(self, ax, event, mode="pan"): + """pan method""" trans = ax.transData.inverted() xdata, ydata = trans.transform_point( - (event.x - self.pos[0], event.y - self.pos[1])) + (event.x - self.pos[0], event.y - self.pos[1]) + ) xpress, ypress = trans.transform_point( - (self._last_touch_pos[event][0] - self.pos[0], - self._last_touch_pos[event][1] - self.pos[1])) + ( + self._last_touch_pos[event][0] - self.pos[0], + self._last_touch_pos[event][1] - self.pos[1], + ) + ) scale = ax.get_xscale() yscale = ax.get_yscale() - if scale == 'linear': + if scale == "linear": dx = xdata - xpress else: - dx = self.transform_eval(xdata, ax.xaxis) - \ - self.transform_eval(xpress, ax.xaxis) + dx = self.transform_eval(xdata, ax.xaxis) - self.transform_eval( + xpress, ax.xaxis + ) - if yscale == 'linear': + if yscale == "linear": dy = ydata - ypress else: - dy = self.transform_eval(ydata, ax.yaxis) - \ - self.transform_eval(ypress, ax.yaxis) + dy = self.transform_eval(ydata, ax.yaxis) - self.transform_eval( + ypress, ax.yaxis + ) xleft, xright = self.axes.get_xlim() ybottom, ytop = self.axes.get_ylim() @@ -1381,56 +1576,76 @@ def apply_pan(self, ax, event, mode='pan'): else: cur_ylim = (ybottom, ytop) - if self.interactive_axis and self.touch_mode == 'pan' and not self.first_touch_pan == 'pan': + if ( + self.interactive_axis + and self.touch_mode == "pan" + and not self.first_touch_pan == "pan" + ): if (ydata < cur_ylim[0] and not inverted_y) or ( - ydata > cur_ylim[1] and inverted_y): + ydata > cur_ylim[1] and inverted_y + ): left_anchor_zone = ( - cur_xlim[1] - cur_xlim[0]) * .2 + cur_xlim[0] + cur_xlim[1] - cur_xlim[0] + ) * 0.2 + cur_xlim[0] right_anchor_zone = ( - cur_xlim[1] - cur_xlim[0]) * .8 + cur_xlim[0] + cur_xlim[1] - cur_xlim[0] + ) * 0.8 + cur_xlim[0] if xdata < left_anchor_zone or xdata > right_anchor_zone: - mode = 'adjust_x' + mode = "adjust_x" else: - mode = 'pan_x' + mode = "pan_x" self.touch_mode = mode - elif (xdata < cur_xlim[0] and not inverted_x) or (xdata > cur_xlim[1] and inverted_x): + elif (xdata < cur_xlim[0] and not inverted_x) or ( + xdata > cur_xlim[1] and inverted_x + ): bottom_anchor_zone = ( - cur_ylim[1] - cur_ylim[0]) * .2 + cur_ylim[0] - top_anchor_zone = ( - cur_ylim[1] - cur_ylim[0]) * .8 + cur_ylim[0] + cur_ylim[1] - cur_ylim[0] + ) * 0.2 + cur_ylim[0] + top_anchor_zone = (cur_ylim[1] - cur_ylim[0]) * 0.8 + cur_ylim[ + 0 + ] if ydata < bottom_anchor_zone or ydata > top_anchor_zone: - mode = 'adjust_y' + mode = "adjust_y" else: - mode = 'pan_y' + mode = "pan_y" self.touch_mode = mode else: - self.touch_mode = 'pan' + self.touch_mode = "pan" - if not mode == 'pan_y' and not mode == 'adjust_y': - if mode == 'adjust_x': + if not mode == "pan_y" and not mode == "adjust_y": + if mode == "adjust_x": if self.anchor_x is None: midpoint = (cur_xlim[1] + cur_xlim[0]) / 2 if xdata > midpoint: - self.anchor_x = 'left' + self.anchor_x = "left" else: - self.anchor_x = 'right' - if self.anchor_x == 'left': + self.anchor_x = "right" + if self.anchor_x == "left": if xdata > cur_xlim[0]: - if scale == 'linear': + if scale == "linear": cur_xlim -= dx else: try: cur_xlim = [ self.inv_transform_eval( - (self.transform_eval( - cur_xlim[0], - ax.xaxis) - dx), - ax.xaxis), + ( + self.transform_eval( + cur_xlim[0], ax.xaxis + ) + - dx + ), + ax.xaxis, + ), self.inv_transform_eval( - (self.transform_eval( - cur_xlim[1], - ax.xaxis) - dx), - ax.xaxis)] + ( + self.transform_eval( + cur_xlim[1], ax.xaxis + ) + - dx + ), + ax.xaxis, + ), + ] except (ValueError, OverflowError): cur_xlim = cur_xlim # Keep previous limits if inverted_x: @@ -1439,21 +1654,30 @@ def apply_pan(self, ax, event, mode='pan'): ax.set_xlim(None, cur_xlim[1]) else: if xdata < cur_xlim[1]: - if scale == 'linear': + if scale == "linear": cur_xlim -= dx else: try: cur_xlim = [ self.inv_transform_eval( - (self.transform_eval( - cur_xlim[0], - ax.xaxis) - dx), - ax.xaxis), + ( + self.transform_eval( + cur_xlim[0], ax.xaxis + ) + - dx + ), + ax.xaxis, + ), self.inv_transform_eval( - (self.transform_eval( - cur_xlim[1], - ax.xaxis) - dx), - ax.xaxis)] + ( + self.transform_eval( + cur_xlim[1], ax.xaxis + ) + - dx + ), + ax.xaxis, + ), + ] except (ValueError, OverflowError): cur_xlim = cur_xlim # Keep previous limits if inverted_x: @@ -1461,21 +1685,26 @@ def apply_pan(self, ax, event, mode='pan'): else: ax.set_xlim(cur_xlim[0], None) else: - if scale == 'linear': + if scale == "linear": cur_xlim -= dx else: try: cur_xlim = [ self.inv_transform_eval( - (self.transform_eval( - cur_xlim[0], - ax.xaxis) - dx), - ax.xaxis), + ( + self.transform_eval(cur_xlim[0], ax.xaxis) + - dx + ), + ax.xaxis, + ), self.inv_transform_eval( - (self.transform_eval( - cur_xlim[1], - ax.xaxis) - dx), - ax.xaxis)] + ( + self.transform_eval(cur_xlim[1], ax.xaxis) + - dx + ), + ax.xaxis, + ), + ] except (ValueError, OverflowError): cur_xlim = cur_xlim # Keep previous limits if inverted_x: @@ -1483,33 +1712,42 @@ def apply_pan(self, ax, event, mode='pan'): else: ax.set_xlim(cur_xlim) - if not mode == 'pan_x' and not mode == 'adjust_x': - if mode == 'adjust_y': + if not mode == "pan_x" and not mode == "adjust_x": + if mode == "adjust_y": if self.anchor_y is None: midpoint = (cur_ylim[1] + cur_ylim[0]) / 2 if ydata > midpoint: - self.anchor_y = 'top' + self.anchor_y = "top" else: - self.anchor_y = 'bottom' + self.anchor_y = "bottom" - if self.anchor_y == 'top': + if self.anchor_y == "top": if ydata > cur_ylim[0]: - if yscale == 'linear': + if yscale == "linear": cur_ylim -= dy else: try: cur_ylim = [ self.inv_transform_eval( - (self.transform_eval( - cur_ylim[0], - ax.yaxis) - dy), - ax.yaxis), + ( + self.transform_eval( + cur_ylim[0], ax.yaxis + ) + - dy + ), + ax.yaxis, + ), self.inv_transform_eval( - (self.transform_eval( - cur_ylim[1], - ax.yaxis) - dy), - ax.yaxis)] + ( + self.transform_eval( + cur_ylim[1], ax.yaxis + ) + - dy + ), + ax.yaxis, + ), + ] except (ValueError, OverflowError): cur_ylim = cur_ylim # Keep previous limits @@ -1519,22 +1757,31 @@ def apply_pan(self, ax, event, mode='pan'): ax.set_ylim(None, cur_ylim[1]) else: if ydata < cur_ylim[1]: - if yscale == 'linear': + if yscale == "linear": cur_ylim -= dy else: try: cur_ylim = [ self.inv_transform_eval( - (self.transform_eval( - cur_ylim[0], - ax.yaxis) - dy), - ax.yaxis), + ( + self.transform_eval( + cur_ylim[0], ax.yaxis + ) + - dy + ), + ax.yaxis, + ), self.inv_transform_eval( - (self.transform_eval( - cur_ylim[1], - ax.yaxis) - dy), - ax.yaxis)] + ( + self.transform_eval( + cur_ylim[1], ax.yaxis + ) + - dy + ), + ax.yaxis, + ), + ] except (ValueError, OverflowError): cur_ylim = cur_ylim # Keep previous limits if inverted_y: @@ -1542,22 +1789,27 @@ def apply_pan(self, ax, event, mode='pan'): else: ax.set_ylim(cur_ylim[0], None) else: - if yscale == 'linear': + if yscale == "linear": cur_ylim -= dy else: try: cur_ylim = [ self.inv_transform_eval( - (self.transform_eval( - cur_ylim[0], - ax.yaxis) - dy), - ax.yaxis), + ( + self.transform_eval(cur_ylim[0], ax.yaxis) + - dy + ), + ax.yaxis, + ), self.inv_transform_eval( - (self.transform_eval( - cur_ylim[1], - ax.yaxis) - dy), - ax.yaxis)] + ( + self.transform_eval(cur_ylim[1], ax.yaxis) + - dy + ), + ax.yaxis, + ), + ] except (ValueError, OverflowError): cur_ylim = cur_ylim # Keep previous limits if inverted_y: @@ -1575,7 +1827,8 @@ def apply_pan(self, ax, event, mode='pan'): ax.figure.canvas.draw_idle() ax.figure.canvas.flush_events() self.background = ax.figure.canvas.copy_from_bbox( - ax.figure.bbox) + ax.figure.bbox + ) self.background_patch_copy.set_visible(False) if self.last_line is not None: self.clear_line_prop() @@ -1594,72 +1847,111 @@ def apply_pan(self, ax, event, mode='pan'): ax.figure.canvas.flush_events() def update_hover(self): - """ update hover on fast draw (if exist)""" + """update hover on fast draw (if exist)""" if self.hover_instance: if self.compare_xdata: - if (self.touch_mode != 'cursor' or len(self._touches) - > 1) and not self.show_compare_cursor: + if ( + self.touch_mode != "cursor" or len(self._touches) > 1 + ) and not self.show_compare_cursor: self.hover_instance.hover_outside_bound = True - elif self.show_compare_cursor and self.touch_mode == 'cursor': + elif self.show_compare_cursor and self.touch_mode == "cursor": self.show_compare_cursor = False else: self.hover_instance.hover_outside_bound = True # update hover pos if needed - elif self.hover_instance.show_cursor and self.x_hover_data is not None and self.y_hover_data is not None: + elif ( + self.hover_instance.show_cursor + and self.x_hover_data is not None + and self.y_hover_data is not None + ): xy_pos = self.axes.transData.transform( - [(self.x_hover_data, self.y_hover_data)]) + [(self.x_hover_data, self.y_hover_data)] + ) self.hover_instance.x_hover_pos = float(xy_pos[0][0]) + self.x self.hover_instance.y_hover_pos = float(xy_pos[0][1]) + self.y - self.hover_instance.xmin_line = float( - self.axes.bbox.bounds[0]) + self.x - self.hover_instance.xmax_line = float( - self.axes.bbox.bounds[0] + self.axes.bbox.bounds[2]) + self.x - self.hover_instance.ymin_line = float( - self.axes.bbox.bounds[1]) + self.y - self.hover_instance.ymax_line = float( - self.axes.bbox.bounds[1] + self.axes.bbox.bounds[3]) + self.y - - if self.hover_instance.x_hover_pos > self.x + self.axes.bbox.bounds[2] + self.axes.bbox.bounds[0] or \ - self.hover_instance.x_hover_pos < self.x + self.axes.bbox.bounds[0] or \ - self.hover_instance.y_hover_pos > self.y + self.axes.bbox.bounds[1] + self.axes.bbox.bounds[3] or \ - self.hover_instance.y_hover_pos < self.y + self.axes.bbox.bounds[1]: + self.hover_instance.xmin_line = ( + float(self.axes.bbox.bounds[0]) + self.x + ) + self.hover_instance.xmax_line = ( + float(self.axes.bbox.bounds[0] + self.axes.bbox.bounds[2]) + + self.x + ) + self.hover_instance.ymin_line = ( + float(self.axes.bbox.bounds[1]) + self.y + ) + self.hover_instance.ymax_line = ( + float(self.axes.bbox.bounds[1] + self.axes.bbox.bounds[3]) + + self.y + ) + + if ( + self.hover_instance.x_hover_pos + > self.x + + self.axes.bbox.bounds[2] + + self.axes.bbox.bounds[0] + or self.hover_instance.x_hover_pos + < self.x + self.axes.bbox.bounds[0] + or self.hover_instance.y_hover_pos + > self.y + + self.axes.bbox.bounds[1] + + self.axes.bbox.bounds[3] + or self.hover_instance.y_hover_pos + < self.y + self.axes.bbox.bounds[1] + ): self.hover_instance.hover_outside_bound = True else: self.hover_instance.hover_outside_bound = False def update_selector(self, *args): - """ update selector on fast draw (if exist)""" + """update selector on fast draw (if exist)""" if self.selector: # update selector pos if needed if self.selector.resize_wgt.verts and ( - len(args) != 0 or self.touch_mode != 'selector'): + len(args) != 0 or self.touch_mode != "selector" + ): resize_wgt = self.selector.resize_wgt - if hasattr(resize_wgt, 'shapes'): + if hasattr(resize_wgt, "shapes"): # lasso widget or ellipse if resize_wgt.shapes: - if hasattr(resize_wgt.shapes[0], 'radius_x'): + if hasattr(resize_wgt.shapes[0], "radius_x"): # ellipse widget xy_pos = resize_wgt.ax.transData.transform( - [(resize_wgt.verts[1][0], resize_wgt.verts[1][1])]) + [ + ( + resize_wgt.verts[1][0], + resize_wgt.verts[1][1], + ) + ] + ) new_pos = resize_wgt.to_widget( - *(float(xy_pos[0][0]), float(xy_pos[0][1]))) + *(float(xy_pos[0][0]), float(xy_pos[0][1])) + ) pos0 = new_pos[0] + self.x pos1 = new_pos[1] + self.y xy_pos2 = resize_wgt.ax.transData.transform( - [(resize_wgt.verts[2][0], resize_wgt.verts[2][1])]) + [ + ( + resize_wgt.verts[2][0], + resize_wgt.verts[2][1], + ) + ] + ) new_pos2 = resize_wgt.to_widget( - *(float(xy_pos2[0][0]), float(xy_pos2[0][1]))) + *(float(xy_pos2[0][0]), float(xy_pos2[0][1])) + ) pos0_2 = new_pos2[0] + self.x pos1_2 = new_pos2[1] + self.y current_shape = resize_wgt.shapes[0] dataxy1 = current_shape.selection_point_inst.points - dataxy2 = current_shape.selection_point_inst2.points + dataxy2 = ( + current_shape.selection_point_inst2.points + ) # note: the 2 first points are the same in # current_shape.points @@ -1670,17 +1962,26 @@ def update_selector(self, *args): pos1_2_old = dataxy2[1] old_length = np.sqrt( - (pos0_2_old - pos0_old) ** 2 + - (pos1_2_old - pos1_old) ** 2) + (pos0_2_old - pos0_old) ** 2 + + (pos1_2_old - pos1_old) ** 2 + ) new_length = np.sqrt( - (pos0_2 - pos0)**2 + (pos1_2 - pos1)**2) + (pos0_2 - pos0) ** 2 + (pos1_2 - pos1) ** 2 + ) scale = float(new_length / old_length) xy_pos3 = resize_wgt.ax.transData.transform( - [(resize_wgt.verts[0][0], resize_wgt.verts[0][1])]) + [ + ( + resize_wgt.verts[0][0], + resize_wgt.verts[0][1], + ) + ] + ) new_pos3 = resize_wgt.to_widget( - *(float(xy_pos3[0][0]), float(xy_pos3[0][1]))) + *(float(xy_pos3[0][0]), float(xy_pos3[0][1])) + ) pos0_c = new_pos3[0] + self.x pos1_c = new_pos3[1] + self.y @@ -1690,27 +1991,43 @@ def update_selector(self, *args): for s in resize_wgt.shapes: s.translate(pos=(pos0_c, pos1_c)) - xmin, xmax, ymin, ymax = resize_wgt.shapes[0].get_min_max( - ) + xmin, xmax, ymin, ymax = resize_wgt.shapes[ + 0 + ].get_min_max() else: # lasso widget xy_pos = resize_wgt.ax.transData.transform( - [(resize_wgt.verts[0][0], resize_wgt.verts[0][1])]) + [ + ( + resize_wgt.verts[0][0], + resize_wgt.verts[0][1], + ) + ] + ) new_pos = resize_wgt.to_widget( - *(float(xy_pos[0][0]), float(xy_pos[0][1]))) + *(float(xy_pos[0][0]), float(xy_pos[0][1])) + ) pos0 = new_pos[0] + self.x pos1 = new_pos[1] + self.y xy_pos2 = resize_wgt.ax.transData.transform( - [(resize_wgt.verts[1][0], resize_wgt.verts[1][1])]) + [ + ( + resize_wgt.verts[1][0], + resize_wgt.verts[1][1], + ) + ] + ) new_pos2 = resize_wgt.to_widget( - *(float(xy_pos2[0][0]), float(xy_pos2[0][1]))) + *(float(xy_pos2[0][0]), float(xy_pos2[0][1])) + ) pos0_2 = new_pos2[0] + self.x pos1_2 = new_pos2[1] + self.y current_shape = resize_wgt.shapes[0] - dataxy = np.array( - current_shape.points).reshape(-1, 2) + dataxy = np.array(current_shape.points).reshape( + -1, 2 + ) # note: the 2 first points are the same in # current_shape.points @@ -1721,10 +2038,12 @@ def update_selector(self, *args): pos1_2_old = dataxy[2][1] old_length = np.sqrt( - (pos0_2_old - pos0_old) ** 2 + - (pos1_2_old - pos1_old) ** 2) + (pos0_2_old - pos0_old) ** 2 + + (pos1_2_old - pos1_old) ** 2 + ) new_length = np.sqrt( - (pos0_2 - pos0)**2 + (pos1_2 - pos1)**2) + (pos0_2 - pos0) ** 2 + (pos1_2 - pos1) ** 2 + ) scale = new_length / old_length @@ -1738,73 +2057,105 @@ def update_selector(self, *args): xmin, ymin = dataxy.min(axis=0) if self.collide_point( - * - resize_wgt.to_window( - xmin, - ymin)) and self.collide_point( - * - resize_wgt.to_window( - xmax, - ymax)): + *resize_wgt.to_window(xmin, ymin) + ) and self.collide_point( + *resize_wgt.to_window(xmax, ymax) + ): resize_wgt.opacity = 1 else: resize_wgt.opacity = 0 - elif self.selector.resize_wgt.verts and (len(args) != 0 or self.touch_mode != 'selector'): + elif self.selector.resize_wgt.verts and ( + len(args) != 0 or self.touch_mode != "selector" + ): resize_wgt = self.selector.resize_wgt if not (resize_wgt.size[0] > 1 and resize_wgt.size[1] > 1): return # rectangle or spann selector - if hasattr(resize_wgt, 'span_orientation'): + if hasattr(resize_wgt, "span_orientation"): # span selector - if resize_wgt.span_orientation == 'vertical': + if resize_wgt.span_orientation == "vertical": xy_pos = resize_wgt.ax.transData.transform( - [(resize_wgt.verts[0][0], resize_wgt.verts[0][1])]) + [ + ( + resize_wgt.verts[0][0], + resize_wgt.verts[0][1], + ) + ] + ) new_pos = resize_wgt.to_widget( - *(float(xy_pos[0][0]), float(xy_pos[0][1]))) + *(float(xy_pos[0][0]), float(xy_pos[0][1])) + ) resize_wgt.pos[0] = new_pos[0] + self.x top_bound = float( - self.y + - resize_wgt.ax.bbox.bounds[3] + - resize_wgt.ax.bbox.bounds[1]) + self.y + + resize_wgt.ax.bbox.bounds[3] + + resize_wgt.ax.bbox.bounds[1] + ) bottom_bound = float( - self.y + resize_wgt.ax.bbox.bounds[1]) + self.y + resize_wgt.ax.bbox.bounds[1] + ) resize_wgt.pos[1] = bottom_bound - self.y # recalcul size xy_pos2 = resize_wgt.ax.transData.transform( - [(resize_wgt.verts[3][0], resize_wgt.verts[3][1])]) + [ + ( + resize_wgt.verts[3][0], + resize_wgt.verts[3][1], + ) + ] + ) resize_wgt.size[0] = float( - xy_pos2[0][0] - xy_pos[0][0]) + xy_pos2[0][0] - xy_pos[0][0] + ) resize_wgt.size[1] = top_bound - bottom_bound - elif resize_wgt.span_orientation == 'horizontal': + elif resize_wgt.span_orientation == "horizontal": xy_pos = resize_wgt.ax.transData.transform( - [(resize_wgt.verts[0][0], resize_wgt.verts[0][1])]) + [ + ( + resize_wgt.verts[0][0], + resize_wgt.verts[0][1], + ) + ] + ) new_pos = resize_wgt.to_widget( - *(float(xy_pos[0][0]), float(xy_pos[0][1]))) + *(float(xy_pos[0][0]), float(xy_pos[0][1])) + ) left_bound = float( - self.x + resize_wgt.ax.bbox.bounds[0]) + self.x + resize_wgt.ax.bbox.bounds[0] + ) right_bound = float( - self.x + resize_wgt.ax.bbox.bounds[2] + - resize_wgt.ax.bbox.bounds[0]) + self.x + + resize_wgt.ax.bbox.bounds[2] + + resize_wgt.ax.bbox.bounds[0] + ) width = right_bound - left_bound left_bound, right_bound = resize_wgt.to_widget( - left_bound, right_bound) + left_bound, right_bound + ) resize_wgt.pos[0] = left_bound resize_wgt.pos[1] = new_pos[1] + self.y # recalcul size xy_pos2 = resize_wgt.ax.transData.transform( - [(resize_wgt.verts[0][1], resize_wgt.verts[1][1])]) + [ + ( + resize_wgt.verts[0][1], + resize_wgt.verts[1][1], + ) + ] + ) resize_wgt.size[0] = width resize_wgt.size[1] = float( - xy_pos2[0][1] - xy_pos[0][1]) + xy_pos2[0][1] - xy_pos[0][1] + ) else: # rectangle selector @@ -1812,117 +2163,148 @@ def update_selector(self, *args): # update all selector pts # recalcul pos xy_pos = resize_wgt.ax.transData.transform( - [(resize_wgt.verts[0][0], resize_wgt.verts[0][1])]) + [(resize_wgt.verts[0][0], resize_wgt.verts[0][1])] + ) new_pos = resize_wgt.to_widget( - *(float(xy_pos[0][0]), float(xy_pos[0][1]))) + *(float(xy_pos[0][0]), float(xy_pos[0][1])) + ) resize_wgt.pos[0] = new_pos[0] + self.x resize_wgt.pos[1] = new_pos[1] + self.y # recalcul size xy_pos2 = resize_wgt.ax.transData.transform( - [(resize_wgt.verts[2][0], resize_wgt.verts[2][1])]) + [(resize_wgt.verts[2][0], resize_wgt.verts[2][1])] + ) resize_wgt.size[0] = float( - xy_pos2[0][0] - xy_pos[0][0]) + xy_pos2[0][0] - xy_pos[0][0] + ) resize_wgt.size[1] = float( - xy_pos2[0][1] - xy_pos[0][1]) + xy_pos2[0][1] - xy_pos[0][1] + ) if self.collide_point( - * - resize_wgt.to_window( - resize_wgt.pos[0], - resize_wgt.pos[1])) and self.collide_point( - * - resize_wgt.to_window( - resize_wgt.pos[0] + - resize_wgt.size[0], - resize_wgt.pos[1] + - resize_wgt.size[1])): + *resize_wgt.to_window( + resize_wgt.pos[0], resize_wgt.pos[1] + ) + ) and self.collide_point( + *resize_wgt.to_window( + resize_wgt.pos[0] + resize_wgt.size[0], + resize_wgt.pos[1] + resize_wgt.size[1], + ) + ): resize_wgt.opacity = 1 else: resize_wgt.opacity = 0 def min_max(self, event): - """ manage min/max touch mode """ + """manage min/max touch mode""" ax = self.axes - xlabelbottom = ax.xaxis._major_tick_kw.get('tick1On') - ylabelleft = ax.yaxis._major_tick_kw.get('tick1On') + xlabelbottom = ax.xaxis._major_tick_kw.get("tick1On") + ylabelleft = ax.yaxis._major_tick_kw.get("tick1On") - if xlabelbottom and event.x > self.x + ax.bbox.bounds[0] and \ - event.x < self.x + ax.bbox.bounds[2] + ax.bbox.bounds[0] and \ - event.y < self.y + ax.bbox.bounds[1]: + if ( + xlabelbottom + and event.x > self.x + ax.bbox.bounds[0] + and event.x < self.x + ax.bbox.bounds[2] + ax.bbox.bounds[0] + and event.y < self.y + ax.bbox.bounds[1] + ): right_lim = self.x + ax.bbox.bounds[2] + ax.bbox.bounds[0] left_lim = self.x + ax.bbox.bounds[0] - left_anchor_zone = (right_lim - left_lim) * .2 + left_lim - right_anchor_zone = (right_lim - left_lim) * .8 + left_lim + left_anchor_zone = (right_lim - left_lim) * 0.2 + left_lim + right_anchor_zone = (right_lim - left_lim) * 0.8 + left_lim if event.x < left_anchor_zone or event.x > right_anchor_zone: if self.text_instance: if not self.text_instance.show_text: midpoint = ( - (self.x + ax.bbox.bounds[2] + ax.bbox.bounds[0]) + - (self.x + ax.bbox.bounds[0])) / 2 + (self.x + ax.bbox.bounds[2] + ax.bbox.bounds[0]) + + (self.x + ax.bbox.bounds[0]) + ) / 2 if event.x < midpoint: - anchor = 'left' + anchor = "left" self.text_instance.x_text_pos = float( - self.x + ax.bbox.bounds[0]) - self.text_instance.y_text_pos = float( - self.y + ax.bbox.bounds[1]) - self.text_instance.text_height + self.x + ax.bbox.bounds[0] + ) + self.text_instance.y_text_pos = ( + float(self.y + ax.bbox.bounds[1]) + - self.text_instance.text_height + ) self.text_instance.offset_text = False else: - anchor = 'right' + anchor = "right" self.text_instance.x_text_pos = float( - self.x + ax.bbox.bounds[2] + ax.bbox.bounds[0]) - self.text_instance.y_text_pos = float( - self.y + ax.bbox.bounds[1]) - self.text_instance.text_height + self.x + ax.bbox.bounds[2] + ax.bbox.bounds[0] + ) + self.text_instance.y_text_pos = ( + float(self.y + ax.bbox.bounds[1]) + - self.text_instance.text_height + ) self.text_instance.offset_text = True self.text_instance.current_axis = ax self.text_instance.kind = { - 'axis': 'x', 'anchor': anchor} + "axis": "x", + "anchor": anchor, + } self.text_instance.show_text = True return - elif ylabelleft and event.x < self.x + ax.bbox.bounds[0] and \ - event.y < self.y + ax.bbox.bounds[1] + ax.bbox.bounds[3] and \ - event.y > self.y + ax.bbox.bounds[1]: + elif ( + ylabelleft + and event.x < self.x + ax.bbox.bounds[0] + and event.y < self.y + ax.bbox.bounds[1] + ax.bbox.bounds[3] + and event.y > self.y + ax.bbox.bounds[1] + ): top_lim = self.y + ax.bbox.bounds[3] + ax.bbox.bounds[1] bottom_lim = self.y + ax.bbox.bounds[1] - bottom_anchor_zone = (top_lim - bottom_lim) * .2 + bottom_lim - top_anchor_zone = (top_lim - bottom_lim) * .8 + bottom_lim + bottom_anchor_zone = (top_lim - bottom_lim) * 0.2 + bottom_lim + top_anchor_zone = (top_lim - bottom_lim) * 0.8 + bottom_lim if event.y < bottom_anchor_zone or event.y > top_anchor_zone: if self.text_instance: if not self.text_instance.show_text: midpoint = ( - (self.y + ax.bbox.bounds[3] + ax.bbox.bounds[1]) + - (self.y + ax.bbox.bounds[1])) / 2 + (self.y + ax.bbox.bounds[3] + ax.bbox.bounds[1]) + + (self.y + ax.bbox.bounds[1]) + ) / 2 if event.y > midpoint: - anchor = 'top' + anchor = "top" self.text_instance.x_text_pos = float( - self.x + ax.bbox.bounds[0]) - dp(40) - self.text_instance.y_text_pos = float( - self.y + ax.bbox.bounds[1] + ax.bbox.bounds[3]) - self.text_instance.text_height + self.x + ax.bbox.bounds[0] + ) - dp(40) + self.text_instance.y_text_pos = ( + float( + self.y + + ax.bbox.bounds[1] + + ax.bbox.bounds[3] + ) + - self.text_instance.text_height + ) self.text_instance.offset_text = False else: - anchor = 'bottom' + anchor = "bottom" self.text_instance.x_text_pos = float( - self.x + ax.bbox.bounds[0]) - dp(40) + self.x + ax.bbox.bounds[0] + ) - dp(40) self.text_instance.y_text_pos = float( - self.y + ax.bbox.bounds[1]) - dp(6) + self.y + ax.bbox.bounds[1] + ) - dp(6) self.text_instance.offset_text = False self.text_instance.current_axis = ax self.text_instance.kind = { - 'axis': 'y', 'anchor': anchor} + "axis": "y", + "anchor": anchor, + } self.text_instance.show_text = True return def apply_drag_legend(self, ax, event): - """ drag legend method """ + """drag legend method""" dx = event.x - self._last_touch_pos[event][0] if not self.legend_do_scroll_x: @@ -1945,7 +2327,11 @@ def apply_drag_legend(self, ax, event): legend_y = bbox.ymin loc_in_canvas = legend_x + dx, legend_y + dy - loc_in_norm_axes = legend.parent.transAxes.inverted().transform_point(loc_in_canvas) + loc_in_norm_axes = ( + legend.parent.transAxes.inverted().transform_point( + loc_in_canvas + ) + ) legend._loc = tuple(loc_in_norm_axes) # use blit method @@ -1954,7 +2340,8 @@ def apply_drag_legend(self, ax, event): ax.figure.canvas.draw_idle() ax.figure.canvas.flush_events() self.background = ax.figure.canvas.copy_from_bbox( - ax.figure.bbox) + ax.figure.bbox + ) legend.set_visible(True) if self.last_line is not None: self.clear_line_prop() @@ -1968,7 +2355,7 @@ def apply_drag_legend(self, ax, event): self.current_legend.update_size() def zoom_factory(self, event, ax, base_scale=1.1): - """ zoom with scrolling mouse method """ + """zoom with scrolling mouse method""" newcoord = self.to_widget(event.x, event.y, relative=True) x = newcoord[0] @@ -1983,7 +2370,7 @@ def zoom_factory(self, event, ax, base_scale=1.1): scale = ax.get_xscale() yscale = ax.get_yscale() - if scale == 'linear': + if scale == "linear": old_min = cur_xlim[0] old_max = cur_xlim[1] @@ -1994,7 +2381,7 @@ def zoom_factory(self, event, ax, base_scale=1.1): xdata = self.transform_eval(xdata, ax.yaxis) old_max = self.transform_eval(max_, ax.yaxis) - if yscale == 'linear': + if yscale == "linear": yold_min = cur_ylim[0] yold_max = cur_ylim[1] @@ -2005,10 +2392,10 @@ def zoom_factory(self, event, ax, base_scale=1.1): ydata = self.transform_eval(ydata, ax.yaxis) yold_max = self.transform_eval(ymax_, ax.yaxis) - if event.button == 'scrolldown': + if event.button == "scrolldown": # deal with zoom in scale_factor = 1 / base_scale - elif event.button == 'scrollup': + elif event.button == "scrollup": # deal with zoom out scale_factor = base_scale else: @@ -2023,36 +2410,44 @@ def zoom_factory(self, event, ax, base_scale=1.1): rely = (yold_max - ydata) / (yold_max - yold_min) if self.do_zoom_x: - if scale == 'linear': - ax.set_xlim([xdata - new_width * (1 - relx), - xdata + new_width * (relx)]) + if scale == "linear": + ax.set_xlim( + [ + xdata - new_width * (1 - relx), + xdata + new_width * (relx), + ] + ) else: new_min = xdata - new_width * (1 - relx) new_max = xdata + new_width * (relx) try: new_min, new_max = self.inv_transform_eval( - new_min, ax.yaxis), self.inv_transform_eval( - new_max, ax.yaxis) + new_min, ax.yaxis + ), self.inv_transform_eval(new_max, ax.yaxis) except OverflowError: # Limit case new_min, new_max = min_, max_ - if new_min <= 0. or new_max <= 0.: # Limit case + if new_min <= 0.0 or new_max <= 0.0: # Limit case new_min, new_max = min_, max_ ax.set_xlim([new_min, new_max]) if self.do_zoom_y: - if yscale == 'linear': - ax.set_ylim([ydata - new_height * (1 - rely), - ydata + new_height * (rely)]) + if yscale == "linear": + ax.set_ylim( + [ + ydata - new_height * (1 - rely), + ydata + new_height * (rely), + ] + ) else: new_ymin = ydata - new_height * (1 - rely) new_ymax = ydata + new_height * (rely) try: new_ymin, new_ymax = self.inv_transform_eval( - new_ymin, ax.yaxis), self.inv_transform_eval( - new_ymax, ax.yaxis) + new_ymin, ax.yaxis + ), self.inv_transform_eval(new_ymax, ax.yaxis) except OverflowError: # Limit case new_ymin, new_ymax = ymin_, ymax_ - if new_ymin <= 0. or new_ymax <= 0.: # Limit case + if new_ymin <= 0.0 or new_ymax <= 0.0: # Limit case new_ymin, new_ymax = ymin_, ymax_ ax.set_ylim([new_ymin, new_ymax]) @@ -2063,7 +2458,7 @@ def zoom_factory(self, event, ax, base_scale=1.1): ax.figure.canvas.flush_events() def _onSize(self, o, size): - """ _onsize method """ + """_onsize method""" if self.figure is None: return # Create a new, correctly sized bitmap @@ -2078,7 +2473,7 @@ def _onSize(self, o, size): hinch = self._height / dpival self.figure.set_size_inches(winch, hinch) - s = 'resize_event' + s = "resize_event" event = ResizeEvent(s, self.figcanvas) self.figcanvas.callbacks.process(s, event) self.figcanvas.draw_idle() @@ -2097,7 +2492,7 @@ def _onSize(self, o, size): Clock.schedule_once(self.update_selector) def update_lim(self): - """ update axis lim if zoombox is used""" + """update axis lim if zoombox is used""" ax = self.axes self.do_update = False @@ -2108,34 +2503,41 @@ def update_lim(self): if xright > xleft: ax.set_xlim( - left=min( - self.x0_box, self.x1_box), right=max( - self.x0_box, self.x1_box)) + left=min(self.x0_box, self.x1_box), + right=max(self.x0_box, self.x1_box), + ) else: ax.set_xlim( - right=min( - self.x0_box, self.x1_box), left=max( - self.x0_box, self.x1_box)) + right=min(self.x0_box, self.x1_box), + left=max(self.x0_box, self.x1_box), + ) if ytop > ybottom: ax.set_ylim( - bottom=min( - self.y0_box, self.y1_box), top=max( - self.y0_box, self.y1_box)) + bottom=min(self.y0_box, self.y1_box), + top=max(self.y0_box, self.y1_box), + ) else: ax.set_ylim( - top=min( - self.y0_box, self.y1_box), bottom=max( - self.y0_box, self.y1_box)) + top=min(self.y0_box, self.y1_box), + bottom=max(self.y0_box, self.y1_box), + ) def reset_box(self): - """ reset zoombox and apply zoombox limit if zoombox option if selected""" + """reset zoombox and apply zoombox limit if zoombox option if selected""" if min(abs(self._box_size[0]), abs(self._box_size[1])) > self.minzoom: trans = self.axes.transData.inverted() self.x0_box, self.y0_box = trans.transform_point( - (self._box_pos[0] - self.pos[0], self._box_pos[1] - self.pos[1])) + ( + self._box_pos[0] - self.pos[0], + self._box_pos[1] - self.pos[1], + ) + ) self.x1_box, self.y1_box = trans.transform_point( - (self._box_size[0] + self._box_pos[0] - self.pos[0], - self._box_size[1] + self._box_pos[1] - self.pos[1])) + ( + self._box_size[0] + self._box_pos[0] - self.pos[0], + self._box_size[1] + self._box_pos[1] - self.pos[1], + ) + ) self.do_update = True self._box_size = 0, 0 @@ -2152,7 +2554,7 @@ def reset_box(self): self.invert_rect_ver = False def draw_box(self, event, x0, y0, x1, y1) -> None: - """ Draw zoombox method + """Draw zoombox method Args: event: touch kivy event @@ -2175,7 +2577,8 @@ def draw_box(self, event, x0, y0, x1, y1) -> None: trans = self.axes.transData.inverted() xdata, ydata = trans.transform_point( - (event.x - pos_x, event.y - pos_y)) + (event.x - pos_x, event.y - pos_y) + ) xleft, xright = self.axes.get_xlim() ybottom, ytop = self.axes.get_ylim() @@ -2307,9 +2710,10 @@ class FakeEvent: y: None -Factory.register('MatplotFigure', MatplotFigure) +Factory.register("MatplotFigure", MatplotFigure) -Builder.load_string(''' +Builder.load_string( + """ canvas: Color: @@ -2367,4 +2771,5 @@ class FakeEvent: (self.pos_x_rect_ver-dp(20),self.pos_y_rect_ver-dp(4)+self._box_size[1] \ if root.invert_rect_hor else self.pos_y_rect_ver+dp(1)+self._box_size[1], \ dp(40),dp(4)) - ''') + """ +) diff --git a/kivy_matplotlib_widget/uix/graph_widget_3d.py b/kivy_matplotlib_widget/uix/graph_widget_3d.py index 8f64d68..5e69302 100644 --- a/kivy_matplotlib_widget/uix/graph_widget_3d.py +++ b/kivy_matplotlib_widget/uix/graph_widget_3d.py @@ -1,5 +1,4 @@ -""" MatplotFigure3D for matplotlib 3D graph -""" +"""MatplotFigure3D for matplotlib 3D graph""" from kivy.factory import Factory import time @@ -18,15 +17,24 @@ from matplotlib.backends.backend_agg import FigureCanvasAgg from kivy.vector import Vector from kivy.uix.boxlayout import BoxLayout -from kivy.properties import ObjectProperty, ListProperty, BooleanProperty, BoundedNumericProperty, AliasProperty, \ - NumericProperty, StringProperty, ColorProperty +from kivy.properties import ( + ObjectProperty, + ListProperty, + BooleanProperty, + BoundedNumericProperty, + AliasProperty, + NumericProperty, + StringProperty, + ColorProperty, +) from kivy.lang import Builder from kivy.graphics.transformation import Matrix from kivy.graphics.texture import Texture import math import matplotlib -matplotlib.use('Agg') + +matplotlib.use("Agg") def line2d_seg_dist(p1, p2, p0): @@ -61,8 +69,9 @@ def get_xyz_mouse_click(event, ax): xd, yd = event.xdata, event.ydata p = (xd, yd) edges = ax.tunit_edges() - ldists = [(line2d_seg_dist(p0, p1, p), i) for - i, (p0, p1) in enumerate(edges)] + ldists = [ + (line2d_seg_dist(p0, p1, p), i) for i, (p0, p1) in enumerate(edges) + ] ldists.sort() # nearest edge @@ -155,7 +164,8 @@ def on_figure(self, obj, value): if self.matplot_figure_layout: self.cursor = self.figure.axes[0].scatter( - [0], [0], [0], marker="s", color="k", s=100, alpha=0) + [0], [0], [0], marker="s", color="k", s=100, alpha=0 + ) self.cursor_cls = cursor(self.figure, remove_artists=[self.cursor]) self.cursor_label = CursorInfo() @@ -179,7 +189,7 @@ def __init__(self, **kwargs): self.zoompan = None self.fast_draw = True self.draw_left_spline = False # available only when fast_draw is True - self.touch_mode = 'rotate' + self.touch_mode = "rotate" self.hover_on = False self.xsorted = True # to manage x sorted data (if numpy is used) @@ -189,7 +199,7 @@ def __init__(self, **kwargs): self.x1_box = None self.y1_box = None - if hasattr(cbook, '_Stack'): + if hasattr(cbook, "_Stack"): # manage matplotlib version with no Stack (replace by _Stack) self._nav_stack = cbook._Stack() else: @@ -223,7 +233,7 @@ def _get_scale(self): # prevent anything wrong with scale, just avoid to dispatch it # if the scale "visually" didn't change. #947 # Remove this ugly hack when we'll be Python 3 only. - if hasattr(self, '_scale_p'): + if hasattr(self, "_scale_p"): if str(scale) == str(self._scale_p): return self._scale_p @@ -236,20 +246,12 @@ def _set_scale(self, scale): # update center new_center = self.parent.to_local(*self.parent.center) self.apply_transform( - Matrix().scale( - rescale, - rescale, - rescale), + Matrix().scale(rescale, rescale, rescale), post_multiply=True, - anchor=( - new_center[0] / - 2 / - scale, - new_center[1] / - 2 / - scale)) + anchor=(new_center[0] / 2 / scale, new_center[1] / 2 / scale), + ) - scale = AliasProperty(_get_scale, _set_scale, bind=('x', 'y', 'transform')) + scale = AliasProperty(_get_scale, _set_scale, bind=("x", "y", "transform")) def set_custom_label_widget(self, custom_widget): self.cursor_label = custom_widget @@ -281,11 +283,19 @@ def push_current(self): """Push the current view limits and position onto the stack.""" self._nav_stack.push( WeakKeyDictionary( - {ax: (ax._get_view(), - # Store both the original and modified positions. - (ax.get_position(True).frozen(), - ax.get_position().frozen())) - for ax in self.figure.axes})) + { + ax: ( + ax._get_view(), + # Store both the original and modified positions. + ( + ax.get_position(True).frozen(), + ax.get_position().frozen(), + ), + ) + for ax in self.figure.axes + } + ) + ) self.set_history_buttons() def update(self): @@ -307,15 +317,15 @@ def _update_view(self): for ax, (view, (pos_orig, pos_active)) in items: ax._set_view(view) # Restore both the original and modified positions - ax._set_position(pos_orig, 'original') - ax._set_position(pos_active, 'active') + ax._set_position(pos_orig, "original") + ax._set_position(pos_active, "active") self.figcanvas.draw() def set_history_buttons(self): """Enable or disable the back/forward button.""" def reset_touch(self) -> None: - """ reset touch + """reset touch Return: None @@ -330,10 +340,12 @@ def transform_with_touch(self, touch): if len(self._touches) == self.translation_touches: # _last_touch_pos has last pos in correct parent space, # just like incoming touch - dx = (touch.x - self._last_touch_pos[touch][0]) \ - * self.do_translation_x - dy = (touch.y - self._last_touch_pos[touch][1]) \ - * self.do_translation_y + dx = ( + touch.x - self._last_touch_pos[touch][0] + ) * self.do_translation_x + dy = ( + touch.y - self._last_touch_pos[touch][1] + ) * self.do_translation_y dx = dx / self.translation_touches dy = dy / self.translation_touches @@ -347,10 +359,8 @@ def transform_with_touch(self, touch): scale_y = scale_y**0.5 self.apply_transform( - Matrix().translate( - dx / 2 / scale_x, - dy / 2 / scale_y, - 0)) + Matrix().translate(dx / 2 / scale_x, dy / 2 / scale_y, 0) + ) changed = True if self.cursor_alpha == 1.0: @@ -360,8 +370,11 @@ def transform_with_touch(self, touch): return changed # we have more than one touch... list of last known pos - points = [Vector(self._last_touch_pos[t]) for t in self._touches - if t is not touch] + points = [ + Vector(self._last_touch_pos[t]) + for t in self._touches + if t is not touch + ] # add current touch last points.append(Vector(touch.pos)) @@ -412,22 +425,23 @@ def transform_with_touch(self, touch): return changed def _draw_bitmap(self): - """ draw bitmap method. based on kivy scatter method""" + """draw bitmap method. based on kivy scatter method""" if self._bitmap is None: print("No bitmap!") return self._img_texture = Texture.create(size=(self.bt_w, self.bt_h)) self._img_texture.blit_buffer( - bytes(self._bitmap), colorfmt="rgba", bufferfmt='ubyte') + bytes(self._bitmap), colorfmt="rgba", bufferfmt="ubyte" + ) self._img_texture.flip_vertical() def transform_with_touch2(self, event): - """ manage touch behaviour. based on kivy scatter method""" + """manage touch behaviour. based on kivy scatter method""" # just do a simple one finger drag changed = False if len(self._touches) == self.translation_touches: - if self.touch_mode == 'pan': + if self.touch_mode == "pan": ax = self.figure.axes[0] # Start the pan event with pixel coordinates px, py = ax.transData.transform([ax._sx, ax._sy]) @@ -454,8 +468,11 @@ def transform_with_touch2(self, event): return changed # we have more than one touch... list of last known pos - points = [Vector(self._last_touch_pos[t]) for t in self._touches - if t is not event] + points = [ + Vector(self._last_touch_pos[t]) + for t in self._touches + if t is not event + ] # add current touch last points.append(Vector(event.pos)) @@ -495,7 +512,7 @@ def transform_with_touch2(self, event): return changed def on_touch_down(self, touch): - """ Manage Mouse/touch press """ + """Manage Mouse/touch press""" x, y = touch.x, touch.y self.prev_x = touch.x self.prev_y = touch.y @@ -505,16 +522,16 @@ def on_touch_down(self, touch): # self.figcanvas.button_press_event(x, real_y, 1, guiEvent=event) if touch.is_mouse_scrolling: - if self.touch_mode == 'figure_zoom_pan': + if self.touch_mode == "figure_zoom_pan": - if touch.button == 'scrolldown': + if touch.button == "scrolldown": # zoom in if self.scale < 5: self.scale = self.scale * 1.1 self.crop_factor = 1 / self.scale - elif touch.button == 'scrollup': + elif touch.button == "scrollup": # zoom out if self.scale > 0.6: self.scale = self.scale * 0.8 @@ -525,16 +542,17 @@ def on_touch_down(self, touch): self.recalcul_cursor() else: - if touch.button == 'scrollup': + if touch.button == "scrollup": # zoom in scale_factor = 1.1 - elif touch.button == 'scrolldown': + elif touch.button == "scrolldown": # zoom out scale_factor = 0.8 ax = self.figure.axes[0] ax._scale_axis_limits( - scale_factor, scale_factor, scale_factor) + scale_factor, scale_factor, scale_factor + ) self.figcanvas.draw() if self.cursor_alpha == 1.0: self.recalcul_cursor() @@ -544,7 +562,7 @@ def on_touch_down(self, touch): self.home() return True - elif self.touch_mode == 'pan': + elif self.touch_mode == "pan": ax = self.figure.axes[0] # transform kivy x,y touch event to x,y data trans = ax.transData.inverted() @@ -557,7 +575,7 @@ def on_touch_down(self, touch): real_y = (touch.y) / self.crop_factor self.figcanvas._button = 1 - s = 'button_press_event' + s = "button_press_event" mouseevent = MouseEvent( s, self.figcanvas, @@ -566,7 +584,8 @@ def on_touch_down(self, touch): 1, self.figcanvas._key, dblclick=False, - guiEvent=touch) + guiEvent=touch, + ) self.figcanvas.callbacks.process(s, mouseevent) # if the touch isnt on the widget we do nothing @@ -579,7 +598,7 @@ def on_touch_down(self, touch): touch.apply_transform_2d(self.to_local) if super(Scatter, self).on_touch_down(touch): # ensure children don't have to do it themselves - if 'multitouch_sim' in touch.profile: + if "multitouch_sim" in touch.profile: touch.multitouch_sim = True touch.pop() self._bring_to_front(touch) @@ -590,30 +609,31 @@ def on_touch_down(self, touch): # if our child didn't do anything, and if we don't have any active # interaction control, then don't accept the touch. - if not self.do_translation_x and \ - not self.do_translation_y and \ - not self.do_rotation and \ - not self.do_scale: + if ( + not self.do_translation_x + and not self.do_translation_y + and not self.do_rotation + and not self.do_scale + ): return False if self.do_collide_after_children: if not self.collide_point(x, y): return False - if 'multitouch_sim' in touch.profile: + if "multitouch_sim" in touch.profile: touch.multitouch_sim = True # grab the touch so we get all it later move events for sure self._bring_to_front(touch) touch.grab(self) self._touches.append(touch) - self._last_touch_pos[touch] = (touch.pos[0], - touch.pos[1]) + self._last_touch_pos[touch] = (touch.pos[0], touch.pos[1]) # return True return False def on_touch_move(self, event): - """ Mouse move while pressed """ + """Mouse move while pressed""" x, y = event.x, event.y if self.collide_point(x, y): @@ -628,7 +648,7 @@ def on_touch_move(self, event): self.home() return True - elif self.touch_mode == 'figure_zoom_pan': + elif self.touch_mode == "figure_zoom_pan": touch = event # let the child widgets handle the event if they want @@ -643,14 +663,13 @@ def on_touch_move(self, event): # rotate/scale/translate if touch in self._touches and touch.grab_current == self: self.transform_with_touch(touch) - self._last_touch_pos[touch] = (touch.pos[0], - touch.pos[1]) + self._last_touch_pos[touch] = (touch.pos[0], touch.pos[1]) # stop propagating if its within our bounds if self.collide_point(x, y): return True - elif self.touch_mode == 'cursor': + elif self.touch_mode == "cursor": if self.cursor_label is None: return # trick to improve app fps (use for big data) @@ -658,7 +677,10 @@ def on_touch_move(self, event): if self.last_hover_time is None: self.last_hover_time = time.time() - elif time.time() - self.last_hover_time < self.max_hover_rate: + elif ( + time.time() - self.last_hover_time + < self.max_hover_rate + ): return else: self.last_hover_time = None @@ -668,15 +690,22 @@ def on_touch_move(self, event): if self.scale != 1.0: - self.myevent.x = event.x / self.scale - \ - self.pos[0] / self.scale - self.bbox[0][0] - self.myevent.y = event.y / self.scale - \ - self.pos[1] / self.scale - self.bbox[0][1] + self.myevent.x = ( + event.x / self.scale + - self.pos[0] / self.scale + - self.bbox[0][0] + ) + self.myevent.y = ( + event.y / self.scale + - self.pos[1] / self.scale + - self.bbox[0][1] + ) self.myevent.x = self.myevent.x / self.crop_factor self.myevent.y = self.myevent.y / self.crop_factor self.myevent.inaxes = self.figure.canvas.inaxes( - (self.myevent.x, self.myevent.y)) + (self.myevent.x, self.myevent.y) + ) self.myevent.pickradius = self.pickradius self.myevent.projection = self.projection self.myevent.compare_xdata = False @@ -691,21 +720,27 @@ def on_touch_move(self, event): self.myevent.xdata = sel.target[0] self.myevent.ydata = sel.target[1] try: - if hasattr(sel.artist, 'get_data_3d'): # 3d line - result = [sel.artist.get_data_3d()[0][sel.index], - sel.artist.get_data_3d()[1][sel.index], - sel.artist.get_data_3d()[2][sel.index]] - - elif hasattr(sel.artist, '_offsets3d'): # scatter - result = [sel.artist._offsets3d[0].data[sel.index], - sel.artist._offsets3d[1].data[sel.index], - sel.artist._offsets3d[2][sel.index]] + if hasattr(sel.artist, "get_data_3d"): # 3d line + result = [ + sel.artist.get_data_3d()[0][sel.index], + sel.artist.get_data_3d()[1][sel.index], + sel.artist.get_data_3d()[2][sel.index], + ] + + elif hasattr(sel.artist, "_offsets3d"): # scatter + result = [ + sel.artist._offsets3d[0].data[sel.index], + sel.artist._offsets3d[1].data[sel.index], + sel.artist._offsets3d[2][sel.index], + ] else: # other z is a projectio. so it can not reflrct a real value result = get_xyz_mouse_click( - self.myevent, self.figure.axes[0]) + self.myevent, self.figure.axes[0] + ) except BaseException: result = get_xyz_mouse_click( - self.myevent, self.figure.axes[0]) + self.myevent, self.figure.axes[0] + ) self.cursor_alpha = 1.0 @@ -715,12 +750,15 @@ def on_touch_move(self, event): self.last_y = result[1] self.last_z = result[2] - x = ax.xaxis.get_major_formatter( - ).format_data_short(result[0]) - y = ax.yaxis.get_major_formatter( - ).format_data_short(result[1]) - z = ax.yaxis.get_major_formatter( - ).format_data_short(result[2]) + x = ax.xaxis.get_major_formatter().format_data_short( + result[0] + ) + y = ax.yaxis.get_major_formatter().format_data_short( + result[1] + ) + z = ax.yaxis.get_major_formatter().format_data_short( + result[2] + ) # self.text.set_text(f"x={x}, y={y}, z={z}") self.cursor_label.label_x_value = f"{x}" self.cursor_label.label_y_value = f"{y}" @@ -728,7 +766,7 @@ def on_touch_move(self, event): self.recalcul_cursor() - elif self.touch_mode == 'pan': + elif self.touch_mode == "pan": touch = event # let the child widgets handle the event if they want if self.collide_point(x, y) and not touch.grab_current == self: @@ -742,14 +780,13 @@ def on_touch_move(self, event): # rotate/scale/translate if touch in self._touches and touch.grab_current == self: self.transform_with_touch2(touch) - self._last_touch_pos[touch] = (touch.pos[0], - touch.pos[1]) + self._last_touch_pos[touch] = (touch.pos[0], touch.pos[1]) # stop propagating if its within our bounds if self.collide_point(x, y): return True - elif self.touch_mode == 'zoom': + elif self.touch_mode == "zoom": ax = self.figure.axes[0] else: @@ -759,7 +796,7 @@ def on_touch_move(self, event): self.figcanvas._lastx, self.figcanvas._lasty = x, real_y - s = 'motion_notify_event' + s = "motion_notify_event" event = MouseEvent( s, self.figcanvas, @@ -767,7 +804,8 @@ def on_touch_move(self, event): y, self.figcanvas._button, self.figcanvas._key, - guiEvent=None) + guiEvent=None, + ) event.inaxes = self.figure.axes[0] self.figcanvas.callbacks.process(s, event) @@ -777,20 +815,16 @@ def on_touch_move(self, event): def recalcul_cursor(self): ax = self.figure.axes[0] x, y, z = mplot3d.proj3d.transform( - self.last_x, self.last_y, self.last_z, ax.M) + self.last_x, self.last_y, self.last_z, ax.M + ) xy_pos = ax.transData.transform([(x, y)]) self.center_graph = ( - float( - xy_pos[0][0]) * - self.crop_factor + - self.x, - float( - xy_pos[0][1]) * - self.crop_factor + - self.y) + float(xy_pos[0][0]) * self.crop_factor + self.x, + float(xy_pos[0][1]) * self.crop_factor + self.y, + ) def _onSize(self, o, size): - """ _onsize method """ + """_onsize method""" if self.figure is None: return # Create a new, correctly sized bitmap @@ -804,10 +838,10 @@ def _onSize(self, o, size): winch = self._width / dpival hinch = self._height / dpival self.figure.set_size_inches( - winch / self.crop_factor, - hinch / self.crop_factor) + winch / self.crop_factor, hinch / self.crop_factor + ) - s = 'resize_event' + s = "resize_event" event = ResizeEvent(s, self.figcanvas) self.figcanvas.callbacks.process(s, event) self.figcanvas.draw_idle() @@ -818,8 +852,9 @@ def _onSize(self, o, size): if self.cursor_label and self.figure and self.cursor_label is not None: self.cursor_label.xmin_line = self.parent.x + dp(20) - self.cursor_label.ymax_line = self.parent.y + \ - int(math.ceil(h)) * self.crop_factor - dp(48) + self.cursor_label.ymax_line = ( + self.parent.y + int(math.ceil(h)) * self.crop_factor - dp(48) + ) class _FigureCanvas(FigureCanvasAgg): @@ -869,7 +904,7 @@ class MatplotFigure3DLayout(BoxLayout): figure_background = ColorProperty([1, 1, 1, 1]) def __init__(self, **kwargs): - """ init class """ + """init class""" super().__init__(**kwargs) def set_figure_background(self, color): @@ -895,25 +930,26 @@ class CursorInfo(FloatLayout): xmin_line = NumericProperty(1) ymax_line = NumericProperty(1) show_cursor = BooleanProperty(False) - label_x = StringProperty('x') - label_y = StringProperty('y') - label_z = StringProperty('z') - label_x_value = StringProperty('') - label_y_value = StringProperty('') - label_z_value = StringProperty('') + label_x = StringProperty("x") + label_y = StringProperty("y") + label_z = StringProperty("z") + label_x_value = StringProperty("") + label_y_value = StringProperty("") + label_z_value = StringProperty("") text_color = ColorProperty([0, 0, 0, 1]) text_font = StringProperty("Roboto") text_size = NumericProperty(dp(14)) background_color = ColorProperty([1, 1, 1, 1]) def __init__(self, **kwargs): - """ init class """ + """init class""" super().__init__(**kwargs) -Factory.register('MatplotFigure3D', MatplotFigure3D) +Factory.register("MatplotFigure3D", MatplotFigure3D) -Builder.load_string(''' +Builder.load_string( + """ figure_wgt : figure_wgt.__self__ canvas.before: @@ -994,4 +1030,5 @@ def __init__(self, **kwargs): font_name : root.text_font color: root.text_color -''') +""" +) diff --git a/kivy_matplotlib_widget/uix/graph_widget_crop_factor.py b/kivy_matplotlib_widget/uix/graph_widget_crop_factor.py index ddb7e2e..dacef6c 100644 --- a/kivy_matplotlib_widget/uix/graph_widget_crop_factor.py +++ b/kivy_matplotlib_widget/uix/graph_widget_crop_factor.py @@ -1,4 +1,5 @@ -""" MatplotFigure is based on https://github.com/mp-007/kivy_matplotlib_widget """ +"""MatplotFigure is based on https://github.com/mp-007/kivy_matplotlib_widget""" + import numpy as np from kivy.metrics import dp from weakref import WeakKeyDictionary @@ -7,20 +8,28 @@ from matplotlib.backends.backend_agg import FigureCanvasAgg from kivy.vector import Vector from kivy.uix.widget import Widget -from kivy.properties import ObjectProperty, ListProperty, BooleanProperty, AliasProperty, \ - NumericProperty, OptionProperty, BoundedNumericProperty, StringProperty +from kivy.properties import ( + ObjectProperty, + ListProperty, + BooleanProperty, + AliasProperty, + NumericProperty, + OptionProperty, + BoundedNumericProperty, + StringProperty, +) from kivy.lang import Builder from kivy.graphics.transformation import Matrix from kivy.graphics.texture import Texture -__all__ = ( - 'MatplotFigureCropFactor', -) + +__all__ = ("MatplotFigureCropFactor",) import math import copy import matplotlib -matplotlib.use('Agg') + +matplotlib.use("Agg") class MatplotFigureCropFactor(Widget): @@ -106,7 +115,7 @@ def on_figure(self, obj, value): ax = self.figure.axes[0] patch_cpy = copy.copy(ax.patch) patch_cpy.set_visible(False) - for pos in ['right', 'top', 'bottom', 'left']: + for pos in ["right", "top", "bottom", "left"]: ax.spines[pos].set_zorder(10) patch_cpy.set_zorder(9) self.background_patch_copy = ax.add_patch(patch_cpy) @@ -129,7 +138,7 @@ def __init__(self, **kwargs): self.ymax = None # option - self.touch_mode = 'pan' # pan, pan_x, pan_y, adjust_x, adjust_y + self.touch_mode = "pan" # pan, pan_x, pan_y, adjust_x, adjust_y self.text = None # used matplotlib formatter to display x cursor value self.cursor_xaxis_formatter = None @@ -152,7 +161,7 @@ def __init__(self, **kwargs): self.anchor_y = None # manage back and next event - if hasattr(cbook, '_Stack'): + if hasattr(cbook, "_Stack"): # manage matplotlib version with no Stack (replace by _Stack) self._nav_stack = cbook._Stack() else: @@ -180,20 +189,23 @@ def inv_transform_eval(self, x, axis): def register_lines(self) -> None: self.horizontal_line = self.axes.axhline( - color='orange', lw=0.8, ls='--', visible=False, animated=True) + color="orange", lw=0.8, ls="--", visible=False, animated=True + ) self.vertical_line = self.axes.axvline( - color='orange', lw=0.8, ls='--', visible=False, animated=True) + color="orange", lw=0.8, ls="--", visible=False, animated=True + ) self.text = self.axes.text( 0.0, 1.01, - ' ', + " ", transform=self.axes.transAxes, - ha='left', - color='orange', + ha="left", + color="orange", fontsize=14, - animated=True) - self._text_data = ' ' + animated=True, + ) + self._text_data = " " self.horizontal_line.set_zorder(1000) self.vertical_line.set_zorder(1000) @@ -205,7 +217,7 @@ def set_cross_hair_visible(self, visible: bool) -> None: self.text.set_text(self._text_data) else: self._text_data = self.text._text - self.text.set_text(' ') + self.text.set_text(" ") def home(self) -> None: if ( @@ -246,10 +258,18 @@ def push_current(self): """Push the current view limits and position onto the stack.""" self._nav_stack.push( WeakKeyDictionary( - {ax: ( - ax._get_view(), - (ax.get_position(True).frozen(), ax.get_position().frozen()) - ) for ax in self.figure.axes})) + { + ax: ( + ax._get_view(), + ( + ax.get_position(True).frozen(), + ax.get_position().frozen(), + ), + ) + for ax in self.figure.axes + } + ) + ) def _update_view(self): """ @@ -265,14 +285,14 @@ def _update_view(self): for ax, (view, (pos_orig, pos_active)) in items: ax._set_view(view) # Restore both the original and modified positions - ax._set_position(pos_orig, 'original') - ax._set_position(pos_active, 'active') + ax._set_position(pos_orig, "original") + ax._set_position(pos_active, "active") self.figure.canvas.draw_idle() self.figure.canvas.flush_events() def reset_touch(self) -> None: - """ reset touch + """reset touch Return: None @@ -281,12 +301,12 @@ def reset_touch(self) -> None: self._last_touch_pos = {} def _get_scale(self): - """ kivy scatter _get_scale method """ + """kivy scatter _get_scale method""" p1 = Vector(*self.to_parent(0, 0)) p2 = Vector(*self.to_parent(1, 0)) scale = p1.distance(p2) - if hasattr(self, '_scale_p'): + if hasattr(self, "_scale_p"): if str(scale) == str(self._scale_p): return self._scale_p @@ -294,13 +314,15 @@ def _get_scale(self): return scale def _set_scale(self, scale): - """ kivy scatter _set_scale method """ + """kivy scatter _set_scale method""" rescale = scale * 1.0 / self.scale - self.apply_transform(Matrix().scale(rescale, rescale, rescale), - post_multiply=True, - anchor=self.to_local(*self.center)) + self.apply_transform( + Matrix().scale(rescale, rescale, rescale), + post_multiply=True, + anchor=self.to_local(*self.center), + ) - scale = AliasProperty(_get_scale, _set_scale, bind=('x', 'y', 'transform')) + scale = AliasProperty(_get_scale, _set_scale, bind=("x", "y", "transform")) def normalize_axis_stops(self, stop_limits, xlim): if stop_limits is not None: @@ -312,7 +334,7 @@ def normalize_axis_stops(self, stop_limits, xlim): return xlim def _draw_bitmap(self): - """ draw bitmap method. based on kivy scatter method""" + """draw bitmap method. based on kivy scatter method""" if self._bitmap is None: return @@ -320,42 +342,48 @@ def _draw_bitmap(self): self._img_texture = Texture.create(size=(self.bt_w, self.bt_h)) self._img_texture.flip_vertical() self._img_texture.blit_buffer( - bytes( - self._bitmap), - size=( - self.bt_w, - self.bt_h), - colorfmt='rgba', - bufferfmt='ubyte') + bytes(self._bitmap), + size=(self.bt_w, self.bt_h), + colorfmt="rgba", + bufferfmt="ubyte", + ) self.update_hover() def transform_with_touch(self, event): - """ manage touch behaviour. based on kivy scatter method""" + """manage touch behaviour. based on kivy scatter method""" # just do a simple one finger drag - event_ppos = event.ppos[0] / \ - self.crop_factor, event.ppos[1] / self.crop_factor - event_pos = event.pos[0] / \ - self.crop_factor, event.pos[1] / self.crop_factor + event_ppos = ( + event.ppos[0] / self.crop_factor, + event.ppos[1] / self.crop_factor, + ) + event_pos = ( + event.pos[0] / self.crop_factor, + event.pos[1] / self.crop_factor, + ) if len(self._touches) == self.translation_touches: - if self.touch_mode == 'pan': + if self.touch_mode == "pan": if self._nav_stack() is None: self.push_current() self.apply_pan(self.axes, event) - if self.touch_mode == 'pan_x' or self.touch_mode == 'pan_y' \ - or self.touch_mode == 'adjust_x' or self.touch_mode == 'adjust_y': + if ( + self.touch_mode == "pan_x" + or self.touch_mode == "pan_y" + or self.touch_mode == "adjust_x" + or self.touch_mode == "adjust_y" + ): if self._nav_stack() is None: self.push_current() self.apply_pan(self.axes, event, mode=self.touch_mode) - elif self.touch_mode == 'zoombox': + elif self.touch_mode == "zoombox": if self._nav_stack() is None: self.push_current() real_x, real_y = event.x - self.pos[0], event.y - self.pos[1] # in case x_init is not create - if not hasattr(self, 'x_init'): + if not hasattr(self, "x_init"): self.x_init = event.x self.y_init = real_y self.draw_box(event, self.x_init, self.y_init, event.x, real_y) @@ -367,8 +395,11 @@ def transform_with_touch(self, event): return changed # we have more than one touch... list of last known pos - points = [Vector(self._last_touch_pos[t]) - for t in self._touches if t is not event] + points = [ + Vector(self._last_touch_pos[t]) + for t in self._touches + if t is not event + ] points.append(Vector(event_pos)) # we only want to transform if the touch is part of the two touches @@ -404,7 +435,7 @@ def transform_with_touch(self, event): return False def on_touch_down(self, event): - """ Manage Mouse/touch press """ + """Manage Mouse/touch press""" if self.disabled: return @@ -423,7 +454,7 @@ def on_touch_down(self, event): return True else: - if self.touch_mode == 'zoombox': + if self.touch_mode == "zoombox": x, y = event.x, event.y real_x, real_y = x - self.pos[0], y - self.pos[1] self.x_init = x @@ -431,8 +462,10 @@ def on_touch_down(self, event): self.draw_box(event, x, real_y, x, real_y) event.grab(self) self._touches.append(event) - self._last_touch_pos[event] = event.pos[0] / \ - self.crop_factor, event.pos[1] / self.crop_factor + self._last_touch_pos[event] = ( + event.pos[0] / self.crop_factor, + event.pos[1] / self.crop_factor, + ) if len(self._touches) > 1: self.background = None @@ -442,7 +475,7 @@ def on_touch_down(self, event): return False def on_touch_move(self, event): - """ Manage Mouse/touch move while pressed """ + """Manage Mouse/touch move while pressed""" if self.disabled: return @@ -454,36 +487,49 @@ def on_touch_move(self, event): # scale/translate if event in self._touches and event.grab_current == self: self.transform_with_touch(event) - self._last_touch_pos[event] = event.pos[0] / \ - self.crop_factor, event.pos[1] / self.crop_factor + self._last_touch_pos[event] = ( + event.pos[0] / self.crop_factor, + event.pos[1] / self.crop_factor, + ) # stop propagating if its within our bounds if self.collide_point(*event.pos): return True def on_touch_up(self, event): - """ Manage Mouse/touch release """ + """Manage Mouse/touch release""" # remove it from our saved touches if event in self._touches and event.grab_state: event.ungrab(self) del self._last_touch_pos[event] self._touches.remove(event) - if (self.touch_mode == 'pan' or self.touch_mode == 'zoombox' or - self.touch_mode == 'pan_x' or self.touch_mode == 'pan_y' or - self.touch_mode == 'adjust_x' or self.touch_mode == 'adjust_y' or - self.touch_mode == 'minmax'): + if ( + self.touch_mode == "pan" + or self.touch_mode == "zoombox" + or self.touch_mode == "pan_x" + or self.touch_mode == "pan_y" + or self.touch_mode == "adjust_x" + or self.touch_mode == "adjust_y" + or self.touch_mode == "minmax" + ): self.push_current() if self.interactive_axis: - if self.touch_mode == 'pan_x' or self.touch_mode == 'pan_y' \ - or self.touch_mode == 'adjust_x' or self.touch_mode == 'adjust_y': - self.touch_mode = 'pan' + if ( + self.touch_mode == "pan_x" + or self.touch_mode == "pan_y" + or self.touch_mode == "adjust_x" + or self.touch_mode == "adjust_y" + ): + self.touch_mode = "pan" self.first_touch_pan = None x, y = event.x, event.y - if abs( - self._box_size[0]) > 1 or abs( - self._box_size[1]) > 1 or self.touch_mode == 'zoombox': + if ( + abs(self._box_size[0]) > 1 + or abs(self._box_size[1]) > 1 + or self.touch_mode == "zoombox" + ): self.reset_box() if not self.collide_point(x, y) and self.do_update: # update axis lim if zoombox is used and touch outside widget @@ -512,7 +558,7 @@ def on_touch_up(self, event): return True def apply_zoom(self, scale_factor, ax, anchor=(0, 0), new_line=None): - """ zoom touch method """ + """zoom touch method""" self.background = None x = anchor[0] - self.pos[0] / self.crop_factor @@ -520,7 +566,8 @@ def apply_zoom(self, scale_factor, ax, anchor=(0, 0), new_line=None): trans = ax.transData.inverted() xdata, ydata = trans.transform_point( - (x + new_line.x / 2, y + new_line.y / 2)) + (x + new_line.x / 2, y + new_line.y / 2) + ) cur_xlim = ax.get_xlim() cur_ylim = ax.get_ylim() @@ -528,7 +575,7 @@ def apply_zoom(self, scale_factor, ax, anchor=(0, 0), new_line=None): scale = ax.get_xscale() yscale = ax.get_yscale() - if scale == 'linear': + if scale == "linear": old_min = cur_xlim[0] old_max = cur_xlim[1] else: @@ -538,7 +585,7 @@ def apply_zoom(self, scale_factor, ax, anchor=(0, 0), new_line=None): xdata = self.transform_eval(xdata, ax.yaxis) old_max = self.transform_eval(max_, ax.yaxis) - if yscale == 'linear': + if yscale == "linear": yold_min = cur_ylim[0] yold_max = cur_ylim[1] else: @@ -557,49 +604,63 @@ def apply_zoom(self, scale_factor, ax, anchor=(0, 0), new_line=None): if self.do_zoom_x: x_left = xdata - new_width * (1 - relx) x_right = xdata + new_width * (relx) - if scale != 'linear': + if scale != "linear": try: x_left, x_right = self.inv_transform_eval( - x_left, ax.xaxis), self.inv_transform_eval( - x_right, ax.xaxis) + x_left, ax.xaxis + ), self.inv_transform_eval(x_right, ax.xaxis) except OverflowError: # Limit case x_left, x_right = min_, max_ - if x_left <= 0. or x_right <= 0.: # Limit case + if x_left <= 0.0 or x_right <= 0.0: # Limit case x_left, x_right = min_, max_ x_left, x_right = self.normalize_axis_stops( - self.stop_xlimits, (x_left, x_right)) + self.stop_xlimits, (x_left, x_right) + ) ax.set_xlim([x_left, x_right]) if self.do_zoom_y: y_left = ydata - new_height * (1 - rely) y_right = ydata + new_height * (rely) - if yscale != 'linear': + if yscale != "linear": try: y_left, y_right = self.inv_transform_eval( - y_left, ax.yaxis), self.inv_transform_eval( - y_right, ax.yaxis) + y_left, ax.yaxis + ), self.inv_transform_eval(y_right, ax.yaxis) except OverflowError: # Limit case y_left, y_right = ymin_, ymax_ - if y_left <= 0. or y_right <= 0.: # Limit case + if y_left <= 0.0 or y_right <= 0.0: # Limit case y_left, y_right = ymin_, ymax_ y_left, y_right = self.normalize_axis_stops( - self.stop_ylimits, (y_left, y_right)) + self.stop_ylimits, (y_left, y_right) + ) ax.set_ylim([y_left, y_right]) self._draw(ax) - def apply_pan(self, ax, event, mode='pan'): - """ pan method """ + def apply_pan(self, ax, event, mode="pan"): + """pan method""" # self.background = None trans = ax.transData.inverted() xdata, ydata = trans.transform_point( - ((event.x - self.pos[0]) / self.crop_factor, - (event.y - self.pos[1]) / self.crop_factor)) + ( + (event.x - self.pos[0]) / self.crop_factor, + (event.y - self.pos[1]) / self.crop_factor, + ) + ) xpress, ypress = trans.transform_point( - ((self._last_touch_pos[event][0] - self.pos[0] / self.crop_factor), - (self._last_touch_pos[event][1] - self.pos[1] / self.crop_factor))) + ( + ( + self._last_touch_pos[event][0] + - self.pos[0] / self.crop_factor + ), + ( + self._last_touch_pos[event][1] + - self.pos[1] / self.crop_factor + ), + ) + ) # trans = ax.transData.inverted() # xdata, ydata = trans.transform_point((event.x-self.pos[0], event.y-self.pos[1])) @@ -608,17 +669,19 @@ def apply_pan(self, ax, event, mode='pan'): scale = ax.get_xscale() yscale = ax.get_yscale() - if scale == 'linear': + if scale == "linear": dx = xdata - xpress else: - dx = self.transform_eval(xdata, ax.xaxis) - \ - self.transform_eval(xpress, ax.xaxis) + dx = self.transform_eval(xdata, ax.xaxis) - self.transform_eval( + xpress, ax.xaxis + ) - if yscale == 'linear': + if yscale == "linear": dy = ydata - ypress else: - dy = self.transform_eval(ydata, ax.yaxis) - \ - self.transform_eval(ypress, ax.yaxis) + dy = self.transform_eval(ydata, ax.yaxis) - self.transform_eval( + ypress, ax.yaxis + ) xleft, xright = self.axes.get_xlim() ybottom, ytop = self.axes.get_ylim() @@ -637,61 +700,84 @@ def apply_pan(self, ax, event, mode='pan'): else: cur_ylim = (ybottom, ytop) - if self.interactive_axis and self.touch_mode == 'pan' and not self.first_touch_pan == 'pan': + if ( + self.interactive_axis + and self.touch_mode == "pan" + and not self.first_touch_pan == "pan" + ): if (ydata < cur_ylim[0] and not inverted_y) or ( - ydata > cur_ylim[1] and inverted_y): + ydata > cur_ylim[1] and inverted_y + ): left_anchor_zone = ( - cur_xlim[1] - cur_xlim[0]) * .2 + cur_xlim[0] + cur_xlim[1] - cur_xlim[0] + ) * 0.2 + cur_xlim[0] right_anchor_zone = ( - cur_xlim[1] - cur_xlim[0]) * .8 + cur_xlim[0] + cur_xlim[1] - cur_xlim[0] + ) * 0.8 + cur_xlim[0] if xdata < left_anchor_zone or xdata > right_anchor_zone: - mode = 'adjust_x' + mode = "adjust_x" else: - mode = 'pan_x' + mode = "pan_x" self.touch_mode = mode - elif (xdata < cur_xlim[0] and not inverted_x) or (xdata > cur_xlim[1] and inverted_x): + elif (xdata < cur_xlim[0] and not inverted_x) or ( + xdata > cur_xlim[1] and inverted_x + ): bottom_anchor_zone = ( - cur_ylim[1] - cur_ylim[0]) * .2 + cur_ylim[0] - top_anchor_zone = ( - cur_ylim[1] - cur_ylim[0]) * .8 + cur_ylim[0] + cur_ylim[1] - cur_ylim[0] + ) * 0.2 + cur_ylim[0] + top_anchor_zone = (cur_ylim[1] - cur_ylim[0]) * 0.8 + cur_ylim[ + 0 + ] if ydata < bottom_anchor_zone or ydata > top_anchor_zone: - mode = 'adjust_y' + mode = "adjust_y" else: - mode = 'pan_y' + mode = "pan_y" self.touch_mode = mode else: - self.touch_mode = 'pan' + self.touch_mode = "pan" - if not mode == 'pan_y' and not mode == 'adjust_y': - if mode == 'adjust_x': + if not mode == "pan_y" and not mode == "adjust_y": + if mode == "adjust_x": if self.anchor_x is None: midpoint = (cur_xlim[1] + cur_xlim[0]) / 2 if xdata > midpoint: - self.anchor_x = 'left' + self.anchor_x = "left" else: - self.anchor_x = 'right' - if self.anchor_x == 'left': + self.anchor_x = "right" + if self.anchor_x == "left": if xdata > cur_xlim[0]: - if scale == 'linear': + if scale == "linear": cur_xlim -= dx else: try: cur_xlim = [ self.inv_transform_eval( - (self.transform_eval( - cur_xlim[0], - ax.xaxis) - dx), - ax.xaxis), + ( + self.transform_eval( + cur_xlim[0], ax.xaxis + ) + - dx + ), + ax.xaxis, + ), self.inv_transform_eval( - (self.transform_eval( - cur_xlim[1], - ax.xaxis) - dx), - ax.xaxis)] + ( + self.transform_eval( + cur_xlim[1], ax.xaxis + ) + - dx + ), + ax.xaxis, + ), + ] except (ValueError, OverflowError): cur_xlim = cur_xlim # Keep previous limits if self.stop_xlimits is not None: - if cur_xlim[0] > self.stop_xlimits[0] and cur_xlim[1] < self.stop_xlimits[1]: + if ( + cur_xlim[0] > self.stop_xlimits[0] + and cur_xlim[1] < self.stop_xlimits[1] + ): if inverted_x: ax.set_xlim(cur_xlim[1], None) else: @@ -703,26 +789,38 @@ def apply_pan(self, ax, event, mode='pan'): ax.set_xlim(None, cur_xlim[1]) else: if xdata < cur_xlim[1]: - if scale == 'linear': + if scale == "linear": cur_xlim -= dx else: try: cur_xlim = [ self.inv_transform_eval( - (self.transform_eval( - cur_xlim[0], - ax.xaxis) - dx), - ax.xaxis), + ( + self.transform_eval( + cur_xlim[0], ax.xaxis + ) + - dx + ), + ax.xaxis, + ), self.inv_transform_eval( - (self.transform_eval( - cur_xlim[1], - ax.xaxis) - dx), - ax.xaxis)] + ( + self.transform_eval( + cur_xlim[1], ax.xaxis + ) + - dx + ), + ax.xaxis, + ), + ] except (ValueError, OverflowError): cur_xlim = cur_xlim # Keep previous limits if self.stop_xlimits is not None: - if cur_xlim[0] > self.stop_xlimits[0] and cur_xlim[1] < self.stop_xlimits[1]: + if ( + cur_xlim[0] > self.stop_xlimits[0] + and cur_xlim[1] < self.stop_xlimits[1] + ): if inverted_x: ax.set_xlim(None, cur_xlim[0]) else: @@ -733,31 +831,40 @@ def apply_pan(self, ax, event, mode='pan'): else: ax.set_xlim(cur_xlim[0], None) else: - if scale == 'linear': + if scale == "linear": cur_xlim -= dx else: try: cur_xlim = [ self.inv_transform_eval( - (self.transform_eval( - cur_xlim[0], - ax.xaxis) - dx), - ax.xaxis), + ( + self.transform_eval(cur_xlim[0], ax.xaxis) + - dx + ), + ax.xaxis, + ), self.inv_transform_eval( - (self.transform_eval( - cur_xlim[1], - ax.xaxis) - dx), - ax.xaxis)] + ( + self.transform_eval(cur_xlim[1], ax.xaxis) + - dx + ), + ax.xaxis, + ), + ] except (ValueError, OverflowError): cur_xlim = cur_xlim # Keep previous limits if self.stop_xlimits is not None: - if cur_xlim[0] < self.stop_xlimits[0] or cur_xlim[1] > self.stop_xlimits[1]: - side = 'left' if (xleft - cur_xlim[0]) > 0 else 'right' + if ( + cur_xlim[0] < self.stop_xlimits[0] + or cur_xlim[1] > self.stop_xlimits[1] + ): + side = "left" if (xleft - cur_xlim[0]) > 0 else "right" diff = min( abs(cur_xlim[0] - self.stop_xlimits[0]), - abs(cur_xlim[1] - self.stop_xlimits[1])) - if side == 'left': + abs(cur_xlim[1] - self.stop_xlimits[1]), + ) + if side == "left": cur_xlim = cur_xlim[0] + diff, cur_xlim[1] + diff else: cur_xlim = cur_xlim[0] - diff, cur_xlim[1] - diff @@ -771,33 +878,42 @@ def apply_pan(self, ax, event, mode='pan'): else: ax.set_xlim(cur_xlim) - if not mode == 'pan_x' and not mode == 'adjust_x': - if mode == 'adjust_y': + if not mode == "pan_x" and not mode == "adjust_x": + if mode == "adjust_y": if self.anchor_y is None: midpoint = (cur_ylim[1] + cur_ylim[0]) / 2 if ydata > midpoint: - self.anchor_y = 'top' + self.anchor_y = "top" else: - self.anchor_y = 'bottom' + self.anchor_y = "bottom" - if self.anchor_y == 'top': + if self.anchor_y == "top": if ydata > cur_ylim[0]: - if yscale == 'linear': + if yscale == "linear": cur_ylim -= dy else: try: cur_ylim = [ self.inv_transform_eval( - (self.transform_eval( - cur_ylim[0], - ax.yaxis) - dy), - ax.yaxis), + ( + self.transform_eval( + cur_ylim[0], ax.yaxis + ) + - dy + ), + ax.yaxis, + ), self.inv_transform_eval( - (self.transform_eval( - cur_ylim[1], - ax.yaxis) - dy), - ax.yaxis)] + ( + self.transform_eval( + cur_ylim[1], ax.yaxis + ) + - dy + ), + ax.yaxis, + ), + ] except (ValueError, OverflowError): cur_ylim = cur_ylim # Keep previous limits @@ -807,22 +923,31 @@ def apply_pan(self, ax, event, mode='pan'): ax.set_ylim(None, cur_ylim[1]) else: if ydata < cur_ylim[1]: - if yscale == 'linear': + if yscale == "linear": cur_ylim -= dy else: try: cur_ylim = [ self.inv_transform_eval( - (self.transform_eval( - cur_ylim[0], - ax.yaxis) - dy), - ax.yaxis), + ( + self.transform_eval( + cur_ylim[0], ax.yaxis + ) + - dy + ), + ax.yaxis, + ), self.inv_transform_eval( - (self.transform_eval( - cur_ylim[1], - ax.yaxis) - dy), - ax.yaxis)] + ( + self.transform_eval( + cur_ylim[1], ax.yaxis + ) + - dy + ), + ax.yaxis, + ), + ] except (ValueError, OverflowError): cur_ylim = cur_ylim # Keep previous limits if inverted_y: @@ -830,22 +955,27 @@ def apply_pan(self, ax, event, mode='pan'): else: ax.set_ylim(cur_ylim[0], None) else: - if yscale == 'linear': + if yscale == "linear": cur_ylim -= dy else: try: cur_ylim = [ self.inv_transform_eval( - (self.transform_eval( - cur_ylim[0], - ax.yaxis) - dy), - ax.yaxis), + ( + self.transform_eval(cur_ylim[0], ax.yaxis) + - dy + ), + ax.yaxis, + ), self.inv_transform_eval( - (self.transform_eval( - cur_ylim[1], - ax.yaxis) - dy), - ax.yaxis)] + ( + self.transform_eval(cur_ylim[1], ax.yaxis) + - dy + ), + ax.yaxis, + ), + ] except (ValueError, OverflowError): cur_ylim = cur_ylim # Keep previous limits if inverted_y: @@ -864,7 +994,8 @@ def apply_pan(self, ax, event, mode='pan'): ax.figure.canvas.draw_idle() ax.figure.canvas.flush_events() self.background = ax.figure.canvas.copy_from_bbox( - ax.figure.bbox) + ax.figure.bbox + ) self.background_patch_copy.set_visible(False) ax.figure.canvas.restore_region(self.background) @@ -881,10 +1012,11 @@ def apply_pan(self, ax, event, mode='pan'): ax.figure.canvas.flush_events() def zoom_factory(self, event, ax, base_scale=1.1): - """ zoom with scrolling mouse method """ + """zoom with scrolling mouse method""" self.background = None xdata, ydata = self._calculate_mouse_position( - self.to_local(*event.pos)) + self.to_local(*event.pos) + ) cur_xlim = ax.get_xlim() cur_ylim = ax.get_ylim() @@ -892,7 +1024,7 @@ def zoom_factory(self, event, ax, base_scale=1.1): scale = ax.get_xscale() yscale = ax.get_yscale() - if scale == 'linear': + if scale == "linear": old_min = cur_xlim[0] old_max = cur_xlim[1] @@ -903,7 +1035,7 @@ def zoom_factory(self, event, ax, base_scale=1.1): xdata = self.transform_eval(xdata, ax.yaxis) old_max = self.transform_eval(max_, ax.yaxis) - if yscale == 'linear': + if yscale == "linear": yold_min = cur_ylim[0] yold_max = cur_ylim[1] @@ -914,10 +1046,10 @@ def zoom_factory(self, event, ax, base_scale=1.1): ydata = self.transform_eval(ydata, ax.yaxis) yold_max = self.transform_eval(ymax_, ax.yaxis) - if event.button == 'scrolldown': + if event.button == "scrolldown": # deal with zoom in scale_factor = 1 / base_scale - elif event.button == 'scrollup': + elif event.button == "scrollup": # deal with zoom out scale_factor = base_scale else: @@ -933,42 +1065,44 @@ def zoom_factory(self, event, ax, base_scale=1.1): x_left = xdata - new_width * (1 - relx) x_right = xdata + new_width * (relx) - if scale != 'linear': + if scale != "linear": try: x_left, x_right = self.inv_transform_eval( - x_left, ax.yaxis), self.inv_transform_eval( - x_right, ax.yaxis) + x_left, ax.yaxis + ), self.inv_transform_eval(x_right, ax.yaxis) except OverflowError: # Limit case x_left, x_right = min_, max_ - if x_left <= 0. or x_right <= 0.: # Limit case + if x_left <= 0.0 or x_right <= 0.0: # Limit case x_left, x_right = min_, max_ x_left, x_right = self.normalize_axis_stops( - self.stop_xlimits, (x_left, x_right)) + self.stop_xlimits, (x_left, x_right) + ) ax.set_xlim([x_left, x_right]) if self.do_zoom_y: y_left = ydata - new_height * (1 - rely) y_right = ydata + new_height * (rely) - if yscale != 'linear': + if yscale != "linear": try: y_left, y_right = self.inv_transform_eval( - y_left, ax.yaxis), self.inv_transform_eval( - y_right, ax.yaxis) + y_left, ax.yaxis + ), self.inv_transform_eval(y_right, ax.yaxis) except OverflowError: # Limit case y_left, y_right = ymin_, ymax_ - if y_left <= 0. or y_right <= 0.: # Limit case + if y_left <= 0.0 or y_right <= 0.0: # Limit case y_left, y_right = ymin_, ymax_ y_left, y_right = self.normalize_axis_stops( - self.stop_ylimits, (y_left, y_right)) + self.stop_ylimits, (y_left, y_right) + ) ax.set_ylim([y_left, y_right]) self._draw(ax) def _onSize(self, o, size): - """ _onsize method """ + """_onsize method""" self.background = None if self.figure is None or self.axes is None: return @@ -984,11 +1118,11 @@ def _onSize(self, o, size): winch = self._width / dpival hinch = self._height / dpival self.figure.set_size_inches( - winch / self.crop_factor, - hinch / self.crop_factor) + winch / self.crop_factor, hinch / self.crop_factor + ) - event = ResizeEvent('resize_event', self.figcanvas) - self.figcanvas.callbacks.process('resize_event', event) + event = ResizeEvent("resize_event", self.figcanvas) + self.figcanvas.callbacks.process("resize_event", event) def _update_background(self): if self.text: @@ -996,7 +1130,8 @@ def _update_background(self): self.axes.figure.canvas.draw_idle() self.axes.figure.canvas.flush_events() self.background = self.axes.figure.canvas.copy_from_bbox( - self.axes.figure.bbox) + self.axes.figure.bbox + ) if self.text: self.set_cross_hair_visible(True) @@ -1032,8 +1167,11 @@ def _draw(self, ax): def _calculate_mouse_position(self, pos): trans = self.axes.transData.inverted() x, y = trans.transform_point( - ((pos[0] - self.pos[0]) / self.crop_factor, (pos[1] - self.pos[1]) / - self.crop_factor)) + ( + (pos[0] - self.pos[0]) / self.crop_factor, + (pos[1] - self.pos[1]) / self.crop_factor, + ) + ) return x, y def on_motion(self, window, pos): @@ -1042,21 +1180,30 @@ def on_motion(self, window, pos): x, y = self._calculate_mouse_position(self.to_local(*pos)) if self.hover_instance: ax = self.axes - self.hover_instance.xmin_line = float( - ax.bbox.bounds[0]) * self.crop_factor + self.x - self.hover_instance.xmax_line = float( - ax.bbox.bounds[0] + ax.bbox.bounds[2]) * self.crop_factor + self.x - self.hover_instance.ymin_line = float( - ax.bbox.bounds[1]) * self.crop_factor + self.y - self.hover_instance.ymax_line = float( - ax.bbox.bounds[1] + ax.bbox.bounds[3]) * self.crop_factor + self.hover_instance.xmin_line = ( + float(ax.bbox.bounds[0]) * self.crop_factor + self.x + ) + self.hover_instance.xmax_line = ( + float(ax.bbox.bounds[0] + ax.bbox.bounds[2]) + * self.crop_factor + + self.x + ) + self.hover_instance.ymin_line = ( + float(ax.bbox.bounds[1]) * self.crop_factor + self.y + ) + self.hover_instance.ymax_line = ( + float(ax.bbox.bounds[1] + ax.bbox.bounds[3]) + * self.crop_factor + ) xy_pos = ax.transData.transform([(x, y)]) self.x_hover_data = x self.y_hover_data = y - self.hover_instance.x_hover_pos = float( - xy_pos[0][0]) * self.crop_factor + self.x - self.hover_instance.y_hover_pos = float( - xy_pos[0][1]) * self.crop_factor + self.y + self.hover_instance.x_hover_pos = ( + float(xy_pos[0][0]) * self.crop_factor + self.x + ) + self.hover_instance.y_hover_pos = ( + float(xy_pos[0][1]) * self.crop_factor + self.y + ) self.hover_instance.show_cursor = True if self.cursor_xaxis_formatter: @@ -1070,10 +1217,20 @@ def on_motion(self, window, pos): self.hover_instance.label_x_value = f"{x}" self.hover_instance.label_y_value = f"{y}" - if self.hover_instance.x_hover_pos > self.x + (self.axes.bbox.bounds[2] + self.axes.bbox.bounds[0]) * self.crop_factor or \ - self.hover_instance.x_hover_pos < self.x + self.axes.bbox.bounds[0] * self.crop_factor or \ - self.hover_instance.y_hover_pos > self.y + (self.axes.bbox.bounds[1] + self.axes.bbox.bounds[3]) * self.crop_factor or \ - self.hover_instance.y_hover_pos < self.y + self.axes.bbox.bounds[1] * self.crop_factor: + if ( + self.hover_instance.x_hover_pos + > self.x + + (self.axes.bbox.bounds[2] + self.axes.bbox.bounds[0]) + * self.crop_factor + or self.hover_instance.x_hover_pos + < self.x + self.axes.bbox.bounds[0] * self.crop_factor + or self.hover_instance.y_hover_pos + > self.y + + (self.axes.bbox.bounds[1] + self.axes.bbox.bounds[3]) + * self.crop_factor + or self.hover_instance.y_hover_pos + < self.y + self.axes.bbox.bounds[1] * self.crop_factor + ): self.hover_instance.hover_outside_bound = True else: self.hover_instance.hover_outside_bound = False @@ -1083,7 +1240,8 @@ def on_motion(self, window, pos): self.horizontal_line.set_ydata([y]) self.vertical_line.set_xdata([x]) self.text.set_text( - f'{self.x_cursor_label}: {x:.3f} {self.y_cursor_label}: {y:.3f}') + f"{self.x_cursor_label}: {x:.3f} {self.y_cursor_label}: {y:.3f}" + ) self._draw(self.axes) elif self.show_cursor: @@ -1094,41 +1252,65 @@ def on_motion(self, window, pos): self.x_hover_data = None self.y_hover_data = None elif self.text: - if self.text._text != ' ': + if self.text._text != " ": self.set_cross_hair_visible(False) self._draw(self.axes) def update_hover(self): - """ update hover on fast draw (if exist)""" + """update hover on fast draw (if exist)""" if self.hover_instance: # update hover pos if needed - if self.hover_instance.show_cursor and self.x_hover_data and self.y_hover_data: + if ( + self.hover_instance.show_cursor + and self.x_hover_data + and self.y_hover_data + ): xy_pos = self.axes.transData.transform( - [(self.x_hover_data, self.y_hover_data)]) - self.hover_instance.x_hover_pos = float( - xy_pos[0][0]) * self.crop_factor + self.x - self.hover_instance.y_hover_pos = float( - xy_pos[0][1]) * self.crop_factor + self.y - - self.hover_instance.xmin_line = float( - self.axes.bbox.bounds[0]) * self.crop_factor + self.x - self.hover_instance.xmax_line = float( - self.axes.bbox.bounds[0] + self.axes.bbox.bounds[2]) * self.crop_factor + self.x - self.hover_instance.ymin_line = float( - self.axes.bbox.bounds[1]) * self.crop_factor + self.y - self.hover_instance.ymax_line = float( - self.axes.bbox.bounds[1] + self.axes.bbox.bounds[3]) * self.crop_factor - - if self.hover_instance.x_hover_pos > self.x + (self.axes.bbox.bounds[2] + self.axes.bbox.bounds[0]) * self.crop_factor or \ - self.hover_instance.x_hover_pos < self.x + self.axes.bbox.bounds[0] * self.crop_factor or \ - self.hover_instance.y_hover_pos > self.y + (self.axes.bbox.bounds[1] + self.axes.bbox.bounds[3]) * self.crop_factor or \ - self.hover_instance.y_hover_pos < self.y + self.axes.bbox.bounds[1] * self.crop_factor: + [(self.x_hover_data, self.y_hover_data)] + ) + self.hover_instance.x_hover_pos = ( + float(xy_pos[0][0]) * self.crop_factor + self.x + ) + self.hover_instance.y_hover_pos = ( + float(xy_pos[0][1]) * self.crop_factor + self.y + ) + + self.hover_instance.xmin_line = ( + float(self.axes.bbox.bounds[0]) * self.crop_factor + self.x + ) + self.hover_instance.xmax_line = ( + float(self.axes.bbox.bounds[0] + self.axes.bbox.bounds[2]) + * self.crop_factor + + self.x + ) + self.hover_instance.ymin_line = ( + float(self.axes.bbox.bounds[1]) * self.crop_factor + self.y + ) + self.hover_instance.ymax_line = ( + float(self.axes.bbox.bounds[1] + self.axes.bbox.bounds[3]) + * self.crop_factor + ) + + if ( + self.hover_instance.x_hover_pos + > self.x + + (self.axes.bbox.bounds[2] + self.axes.bbox.bounds[0]) + * self.crop_factor + or self.hover_instance.x_hover_pos + < self.x + self.axes.bbox.bounds[0] * self.crop_factor + or self.hover_instance.y_hover_pos + > self.y + + (self.axes.bbox.bounds[1] + self.axes.bbox.bounds[3]) + * self.crop_factor + or self.hover_instance.y_hover_pos + < self.y + self.axes.bbox.bounds[1] * self.crop_factor + ): self.hover_instance.hover_outside_bound = True else: self.hover_instance.hover_outside_bound = False def draw_box(self, event, x0, y0, x1, y1) -> None: - """ Draw zoombox method + """Draw zoombox method Args: event: touch kivy event @@ -1152,7 +1334,11 @@ def draw_box(self, event, x0, y0, x1, y1) -> None: trans = self.axes.transData.inverted() # xdata, ydata = trans.transform_point((event.x-pos_x, event.y-pos_y)) xdata, ydata = trans.transform_point( - ((event.x - pos_x) / self.crop_factor, (event.y - pos_y) / self.crop_factor)) + ( + (event.x - pos_x) / self.crop_factor, + (event.y - pos_y) / self.crop_factor, + ) + ) xleft, xright = self.axes.get_xlim() ybottom, ytop = self.axes.get_ylim() @@ -1172,7 +1358,8 @@ def draw_box(self, event, x0, y0, x1, y1) -> None: # x0data, y0data = trans.transform_point((x0-pos_x, y0-pos_y)) x0data, y0data = trans.transform_point( - ((x0 - pos_x) / self.crop_factor, (y0 - pos_y) / self.crop_factor)) + ((x0 - pos_x) / self.crop_factor, (y0 - pos_y) / self.crop_factor) + ) if x0data > xmax or x0data < xmin or y0data > ymax or y0data < ymin: return @@ -1248,7 +1435,7 @@ def draw_box(self, event, x0, y0, x1, y1) -> None: self._box_size = x1 - x0, y1 - y0 def update_lim(self): - """ update axis lim if zoombox is used""" + """update axis lim if zoombox is used""" ax = self.axes self.do_update = False @@ -1259,34 +1446,43 @@ def update_lim(self): if xright > xleft: ax.set_xlim( - left=min( - self.x0_box, self.x1_box), right=max( - self.x0_box, self.x1_box)) + left=min(self.x0_box, self.x1_box), + right=max(self.x0_box, self.x1_box), + ) else: ax.set_xlim( - right=min( - self.x0_box, self.x1_box), left=max( - self.x0_box, self.x1_box)) + right=min(self.x0_box, self.x1_box), + left=max(self.x0_box, self.x1_box), + ) if ytop > ybottom: ax.set_ylim( - bottom=min( - self.y0_box, self.y1_box), top=max( - self.y0_box, self.y1_box)) + bottom=min(self.y0_box, self.y1_box), + top=max(self.y0_box, self.y1_box), + ) else: ax.set_ylim( - top=min( - self.y0_box, self.y1_box), bottom=max( - self.y0_box, self.y1_box)) + top=min(self.y0_box, self.y1_box), + bottom=max(self.y0_box, self.y1_box), + ) def reset_box(self): - """ reset zoombox and apply zoombox limit if zoombox option if selected""" + """reset zoombox and apply zoombox limit if zoombox option if selected""" if min(abs(self._box_size[0]), abs(self._box_size[1])) > self.minzoom: trans = self.axes.transData.inverted() self.x0_box, self.y0_box = trans.transform_point( - ((self._box_pos[0] - self.pos[0]) / self.crop_factor, - (self._box_pos[1] - self.pos[1]) / self.crop_factor)) + ( + (self._box_pos[0] - self.pos[0]) / self.crop_factor, + (self._box_pos[1] - self.pos[1]) / self.crop_factor, + ) + ) self.x1_box, self.y1_box = trans.transform_point( - ((self._box_size[0] + self._box_pos[0] - self.pos[0]) / self.crop_factor, (self._box_size[1] + self._box_pos[1] - self.pos[1]) / self.crop_factor)) + ( + (self._box_size[0] + self._box_pos[0] - self.pos[0]) + / self.crop_factor, + (self._box_size[1] + self._box_pos[1] - self.pos[1]) + / self.crop_factor, + ) + ) self.do_update = True self._box_size = 0, 0 @@ -1339,7 +1535,8 @@ def clear(self): self.get_renderer().clear() -Builder.load_string(''' +Builder.load_string( + """ canvas: @@ -1398,5 +1595,6 @@ def clear(self): (self.pos_x_rect_ver-dp(20),self.pos_y_rect_ver-dp(4)+self._box_size[1] \ if root.invert_rect_hor else self.pos_y_rect_ver+dp(1)+self._box_size[1], \ dp(40),dp(4)) -''') +""" +) FigureCanvas = _FigureCanvas diff --git a/kivy_matplotlib_widget/uix/graph_widget_general.py b/kivy_matplotlib_widget/uix/graph_widget_general.py index 33adc52..d0ea152 100644 --- a/kivy_matplotlib_widget/uix/graph_widget_general.py +++ b/kivy_matplotlib_widget/uix/graph_widget_general.py @@ -1,4 +1,4 @@ -""" MatplotFigure is based on https://github.com/jeysonmc/kivy_matplotlib +"""MatplotFigure is based on https://github.com/jeysonmc/kivy_matplotlib and kivy scatter """ @@ -12,8 +12,14 @@ from matplotlib.backends.backend_agg import FigureCanvasAgg from kivy.vector import Vector from kivy.uix.widget import Widget -from kivy.properties import ObjectProperty, ListProperty, BooleanProperty, BoundedNumericProperty, AliasProperty, \ - NumericProperty +from kivy.properties import ( + ObjectProperty, + ListProperty, + BooleanProperty, + BoundedNumericProperty, + AliasProperty, + NumericProperty, +) from kivy.lang import Builder from kivy.graphics.transformation import Matrix from kivy.graphics.texture import Texture @@ -21,7 +27,8 @@ import copy import matplotlib -matplotlib.use('Agg') + +matplotlib.use("Agg") class MatplotFigureGeneral(Widget): @@ -80,7 +87,7 @@ def __init__(self, **kwargs): self.zoompan = None self.fast_draw = True self.draw_left_spline = False # available only when fast_draw is True - self.touch_mode = 'pan' + self.touch_mode = "pan" self.hover_on = False self.xsorted = True # to manage x sorted data (if numpy is used) self.minzoom = dp(20) # minimum pixel distance to apply zoom @@ -103,7 +110,7 @@ def __init__(self, **kwargs): self.bind(size=self._onSize) def reset_touch(self) -> None: - """ reset touch + """reset touch Return: None @@ -112,17 +119,18 @@ def reset_touch(self) -> None: self._last_touch_pos = {} def _draw_bitmap(self): - """ draw bitmap method. based on kivy scatter method""" + """draw bitmap method. based on kivy scatter method""" if self._bitmap is None: print("No bitmap!") return self._img_texture = Texture.create(size=(self.bt_w, self.bt_h)) self._img_texture.blit_buffer( - bytes(self._bitmap), colorfmt="rgba", bufferfmt='ubyte') + bytes(self._bitmap), colorfmt="rgba", bufferfmt="ubyte" + ) self._img_texture.flip_vertical() def on_mouse_move(self, window, mouse_pos): - """ Mouse move """ + """Mouse move""" if self._pressed: # Do not process this event if there's a touch_move return x, y = mouse_pos @@ -132,7 +140,7 @@ def on_mouse_move(self, window, mouse_pos): # self.figcanvas.motion_notify_event(x, real_y, guiEvent=None) self.figcanvas._lastx, self.figcanvas._lasty = x, real_y - s = 'motion_notify_event' + s = "motion_notify_event" event = MouseEvent( s, self.figcanvas, @@ -140,7 +148,8 @@ def on_mouse_move(self, window, mouse_pos): y, self.figcanvas._button, self.figcanvas._key, - guiEvent=None) + guiEvent=None, + ) self.figcanvas.callbacks.process(s, event) def on_touch_down(self, event): @@ -152,7 +161,7 @@ def on_touch_down(self, event): # self.figcanvas.button_press_event(x, real_y, 1, guiEvent=event) self.figcanvas._button = 1 - s = 'button_press_event' + s = "button_press_event" mouseevent = MouseEvent( s, self.figcanvas, @@ -161,18 +170,19 @@ def on_touch_down(self, event): 1, self.figcanvas._key, dblclick=False, - guiEvent=event) + guiEvent=event, + ) self.figcanvas.callbacks.process(s, mouseevent) def on_touch_move(self, event): - """ Mouse move while pressed """ + """Mouse move while pressed""" x, y = event.x, event.y if self.collide_point(x, y): real_x, real_y = x - self.pos[0], y - self.pos[1] # self.figcanvas.motion_notify_event(x, real_y, guiEvent=event) self.figcanvas._lastx, self.figcanvas._lasty = x, real_y - s = 'motion_notify_event' + s = "motion_notify_event" event = MouseEvent( s, self.figcanvas, @@ -180,7 +190,8 @@ def on_touch_move(self, event): y, self.figcanvas._button, self.figcanvas._key, - guiEvent=event) + guiEvent=event, + ) self.figcanvas.callbacks.process(s, event) def on_touch_up(self, event): @@ -192,7 +203,7 @@ def on_touch_up(self, event): real_x, real_y = x - pos_x, y - pos_y # self.figcanvas.button_release_event(x, real_y, 1, guiEvent=event) - s = 'button_release_event' + s = "button_release_event" event = MouseEvent( s, self.figcanvas, @@ -200,14 +211,15 @@ def on_touch_up(self, event): real_y, 1, self.figcanvas._key, - guiEvent=event) + guiEvent=event, + ) self.figcanvas.callbacks.process(s, event) self.figcanvas._button = None self._pressed = False def _onSize(self, o, size): - """ _onsize method """ + """_onsize method""" if self.figure is None: return # Create a new, correctly sized bitmap @@ -222,7 +234,7 @@ def _onSize(self, o, size): hinch = self._height / dpival self.figure.set_size_inches(winch, hinch) - s = 'resize_event' + s = "resize_event" event = ResizeEvent(s, self.figcanvas) self.figcanvas.callbacks.process(s, event) self.figcanvas.draw_idle() @@ -230,22 +242,22 @@ def _onSize(self, o, size): self.figcanvas.draw() def update_lim(self): - """ update axis lim if zoombox is used""" + """update axis lim if zoombox is used""" ax = self.axes self.do_update = False ax.set_xlim( - left=min( - self.x0_box, self.x1_box), right=max( - self.x0_box, self.x1_box)) + left=min(self.x0_box, self.x1_box), + right=max(self.x0_box, self.x1_box), + ) ax.set_ylim( - bottom=min( - self.y0_box, self.y1_box), top=max( - self.y0_box, self.y1_box)) + bottom=min(self.y0_box, self.y1_box), + top=max(self.y0_box, self.y1_box), + ) def reset_box(self): - """ reset zoombox and apply zoombox limit if zoombox option if selected""" + """reset zoombox and apply zoombox limit if zoombox option if selected""" # if min(abs(self._box_size[0]),abs(self._box_size[1]))>self.minzoom: # trans = self.axes.transData.inverted() # self.x0_box, self.y0_box = trans.transform_point((self._box_pos[0]-self.pos[0], self._box_pos[1]-self.pos[1])) @@ -262,7 +274,7 @@ def reset_box(self): self.invert_rect_ver = False def draw_box(self, event, x0, y0, x1, y1) -> None: - """ Draw zoombox method + """Draw zoombox method Args: event: touch kivy event @@ -329,9 +341,10 @@ def blit(self, bbox=None): self.widget._draw_bitmap() -Factory.register('MatplotFigureGeneral', MatplotFigureGeneral) +Factory.register("MatplotFigureGeneral", MatplotFigureGeneral) -Builder.load_string(''' +Builder.load_string( + """ canvas: Color: @@ -351,4 +364,5 @@ def blit(self, bbox=None): dp(1) if root.invert_rect_ver else -dp(1), \ dp(1) if root.invert_rect_hor else -dp(1), \ dp(1) if root.invert_rect_ver else -dp(1) - ''') + """ +) diff --git a/kivy_matplotlib_widget/uix/graph_widget_scatter.py b/kivy_matplotlib_widget/uix/graph_widget_scatter.py index 789460b..96e22a7 100644 --- a/kivy_matplotlib_widget/uix/graph_widget_scatter.py +++ b/kivy_matplotlib_widget/uix/graph_widget_scatter.py @@ -1,4 +1,4 @@ -""" MatplotFigure is based on https://github.com/jeysonmc/kivy_matplotlib +"""MatplotFigure is based on https://github.com/jeysonmc/kivy_matplotlib and kivy scatter """ @@ -19,8 +19,15 @@ from matplotlib.colors import to_hex from kivy.vector import Vector from kivy.uix.widget import Widget -from kivy.properties import ObjectProperty, ListProperty, BooleanProperty, BoundedNumericProperty, AliasProperty, \ - NumericProperty, OptionProperty +from kivy.properties import ( + ObjectProperty, + ListProperty, + BooleanProperty, + BoundedNumericProperty, + AliasProperty, + NumericProperty, + OptionProperty, +) from kivy.lang import Builder from kivy.graphics.transformation import Matrix from kivy.graphics.texture import Texture @@ -28,13 +35,19 @@ import copy import matplotlib -matplotlib.use('Agg') + +matplotlib.use("Agg") selector_widgets_available = False try: selector_widgets_available = True - from kivy_matplotlib_widget.uix.selector_widget import ResizeRelativeLayout, LassoRelativeLayout, EllipseRelativeLayout, SpanRelativeLayout + from kivy_matplotlib_widget.uix.selector_widget import ( + ResizeRelativeLayout, + LassoRelativeLayout, + EllipseRelativeLayout, + SpanRelativeLayout, + ) except ImportError: - print('Selector widgets are not available') + print("Selector widgets are not available") class MatplotFigureScatter(Widget): @@ -95,13 +108,8 @@ class MatplotFigureScatter(Widget): desktop_mode = BooleanProperty(True) current_selector = OptionProperty( "None", - options=[ - "None", - 'rectangle', - 'lasso', - 'ellipse', - 'span', - 'custom']) + options=["None", "rectangle", "lasso", "ellipse", "span", "custom"], + ) pick_minimum_radius = NumericProperty(dp(50)) pick_radius_axis = OptionProperty("both", options=["both", "x", "y"]) @@ -119,7 +127,7 @@ def on_figure(self, obj, value): ax = self.figure.axes[0] patch_cpy = copy.copy(ax.patch) patch_cpy.set_visible(False) - for pos in ['right', 'top', 'bottom', 'left']: + for pos in ["right", "top", "bottom", "left"]: ax.spines[pos].set_zorder(10) patch_cpy.set_zorder(9) self.background_patch_copy = ax.add_patch(patch_cpy) @@ -164,7 +172,7 @@ def __init__(self, **kwargs): self.scatters = [] # option - self.touch_mode = 'pan' + self.touch_mode = "pan" self.hover_on = False # used matplotlib formatter to display x cursor value self.cursor_xaxis_formatter = None @@ -201,7 +209,7 @@ def __init__(self, **kwargs): self.first_touch_pan = None # manage back and next event - if hasattr(cbook, '_Stack'): + if hasattr(cbook, "_Stack"): # manage matplotlib version with no Stack (replace by _Stack) self._nav_stack = cbook._Stack() else: @@ -221,13 +229,13 @@ def __init__(self, **kwargs): def on_kv_post(self, _): # if not self.selector: if self.current_selector != "None" and selector_widgets_available: - if self.current_selector == 'rectangle': + if self.current_selector == "rectangle": self.set_selector(ResizeRelativeLayout) - elif self.current_selector == 'lasso': + elif self.current_selector == "lasso": self.set_selector(LassoRelativeLayout) - elif self.current_selector == 'ellipse': + elif self.current_selector == "ellipse": self.set_selector(EllipseRelativeLayout) - elif self.current_selector == 'span': + elif self.current_selector == "span": self.set_selector(SpanRelativeLayout) self.kv_post_done = True @@ -243,18 +251,19 @@ def on_current_selector(self, instance, value, *args): if self.kv_post_done and selector_widgets_available: - if value == 'rectangle': + if value == "rectangle": self.set_selector(ResizeRelativeLayout) - elif value == 'lasso': + elif value == "lasso": self.set_selector(LassoRelativeLayout) - elif value == 'ellipse': + elif value == "ellipse": self.set_selector(EllipseRelativeLayout) - elif self.current_selector == 'span': + elif self.current_selector == "span": self.set_selector(SpanRelativeLayout) elif value == "None": if self.selector: Window.unbind( - mouse_pos=self.selector.resize_wgt.on_mouse_pos) + mouse_pos=self.selector.resize_wgt.on_mouse_pos + ) self.parent.remove_widget(self.selector) self.selector = None @@ -272,8 +281,8 @@ def set_selector(self, selector, *args): self.parent.remove_widget(self.selector) self.selector = selector( - figure_wgt=self, - desktop_mode=self.desktop_mode) + figure_wgt=self, desktop_mode=self.desktop_mode + ) self.selector.resize_wgt.ax = self.axes if selector_collection: self.set_collection() @@ -304,7 +313,7 @@ def set_callback_clear(self, callback): self.selector.resize_wgt.set_callback_clear(callback) def register_lines(self, lines: list) -> None: - """ register lines method + """register lines method Args: lines (list): list of matplolib line class @@ -317,20 +326,22 @@ def register_lines(self, lines: list) -> None: ymin, ymax = self.axes.get_ylim() # create cross hair cusor self.horizontal_line = self.axes.axhline( - y=self.ymin, color='k', lw=0.8, ls='--', visible=False) + y=self.ymin, color="k", lw=0.8, ls="--", visible=False + ) self.vertical_line = self.axes.axvline( - x=self.xmin, color='k', lw=0.8, ls='--', visible=False) + x=self.xmin, color="k", lw=0.8, ls="--", visible=False + ) # register lines self.lines = lines # cursor text - self.text = self.axes.text(1.0, 1.01, '', - transform=self.axes.transAxes, - ha='right') + self.text = self.axes.text( + 1.0, 1.01, "", transform=self.axes.transAxes, ha="right" + ) def register_scatters(self, scatters: list) -> None: - """ register scatters method + """register scatters method Args: scatters (list): list of matplolib scatters class @@ -342,7 +353,7 @@ def register_scatters(self, scatters: list) -> None: self.scatters = scatters def set_cross_hair_visible(self, visible: bool) -> None: - """ set curcor visibility + """set curcor visibility Args: visible (bool): make cursor visble or not @@ -356,7 +367,7 @@ def set_cross_hair_visible(self, visible: bool) -> None: self.text.set_visible(visible) def hover(self, event) -> None: - """ hover cursor method (cursor to nearest value) + """hover cursor method (cursor to nearest value) Args: event: touch kivy event @@ -372,7 +383,8 @@ def hover(self, event) -> None: # transform kivy x,y touch event to x,y data trans = self.axes.transData.inverted() xdata, ydata = trans.transform_point( - (event.x - self.pos[0], event.y - self.pos[1])) + (event.x - self.pos[0], event.y - self.pos[1]) + ) # loop all register lines and find closest x,y data for each valid # line @@ -385,7 +397,11 @@ def hover(self, event) -> None: good_index_scatter = [] if self.multi_xdata: xdata_res, ydata_dump = trans.transform_point( - (event.x - self.pos[0] + self.multi_xdata_res, event.y - self.pos[1])) + ( + event.x - self.pos[0] + self.multi_xdata_res, + event.y - self.pos[1], + ) + ) delta = abs(xdata_res - xdata) for line in self.lines: @@ -400,9 +416,9 @@ def hover(self, event) -> None: # find closest data index from touch (x axis) if self.xsorted: index = min( - np.searchsorted( - self.x_cursor, xdata), len( - self.y_cursor) - 1) + np.searchsorted(self.x_cursor, xdata), + len(self.y_cursor) - 1, + ) else: index = np.argsort(abs(self.x_cursor - xdata))[0] @@ -414,9 +430,16 @@ def hover(self, event) -> None: # find closest ydata from lines idx_good_y = np.where( - abs(np.array(self.x_cursor) - x) < delta)[0] - index2_best = idx_good_y[np.argsort( - abs(np.array(self.y_cursor)[idx_good_y] - ydata))[0]] + abs(np.array(self.x_cursor) - x) < delta + )[0] + index2_best = idx_good_y[ + np.argsort( + abs( + np.array(self.y_cursor)[idx_good_y] + - ydata + ) + )[0] + ] y = self.y_cursor[index2_best] x = self.x_cursor[index2_best] good_index2.append(index2_best) @@ -428,18 +451,19 @@ def hover(self, event) -> None: # left axis # xy_pixels_mouse = ax.transData.transform(np.vstack([xdata,ydata]).T) xy_pixels_mouse = ax.transData.transform( - [(xdata, ydata)]) + [(xdata, ydata)] + ) # xy_pixels = ax.transData.transform(np.vstack([x,y]).T) xy_pixels = ax.transData.transform([(x, y)]) - dx2 = (xy_pixels_mouse[0][0] - xy_pixels[0][0])**2 - dy2 = (xy_pixels_mouse[0][1] - xy_pixels[0][1])**2 + dx2 = (xy_pixels_mouse[0][0] - xy_pixels[0][0]) ** 2 + dy2 = (xy_pixels_mouse[0][1] - xy_pixels[0][1]) ** 2 # store distance - if self.pick_radius_axis == 'both': - distance.append((dx2 + dy2)**0.5) - if self.pick_radius_axis == 'x': + if self.pick_radius_axis == "both": + distance.append((dx2 + dy2) ** 0.5) + if self.pick_radius_axis == "x": distance.append(abs(dx2)) - if self.pick_radius_axis == 'y': + if self.pick_radius_axis == "y": distance.append(abs(dy2)) # store all best lines and index @@ -458,9 +482,9 @@ def hover(self, event) -> None: # find closest data index from touch (x axis) if self.xsorted: index = min( - np.searchsorted( - self.x_cursor, xdata), len( - self.y_cursor) - 1) + np.searchsorted(self.x_cursor, xdata), + len(self.y_cursor) - 1, + ) else: index = np.argsort(abs(self.x_cursor - xdata))[0] @@ -472,9 +496,16 @@ def hover(self, event) -> None: if self.multi_xdata: # find closest ydata from lines idx_good_y = np.where( - abs(np.array(self.x_cursor) - x) < delta)[0] - index2_best = idx_good_y[np.argsort( - abs(np.array(self.y_cursor)[idx_good_y] - ydata))[0]] + abs(np.array(self.x_cursor) - x) < delta + )[0] + index2_best = idx_good_y[ + np.argsort( + abs( + np.array(self.y_cursor)[idx_good_y] + - ydata + ) + )[0] + ] y = self.y_cursor[index2_best] x = self.x_cursor[index2_best] good_index2_scatter.append(index2_best) @@ -485,16 +516,21 @@ def hover(self, event) -> None: ax = scatter.axes # left axis xy_pixels_mouse = ax.transData.transform( - [(xdata, ydata)]) + [(xdata, ydata)] + ) if np.ma.is_masked(x) or np.ma.is_masked(y): distance.append(np.nan) else: xy_pixels = ax.transData.transform([(x, y)]) - dx2 = (xy_pixels_mouse[0][0] - xy_pixels[0][0])**2 - dy2 = (xy_pixels_mouse[0][1] - xy_pixels[0][1])**2 + dx2 = ( + xy_pixels_mouse[0][0] - xy_pixels[0][0] + ) ** 2 + dy2 = ( + xy_pixels_mouse[0][1] - xy_pixels[0][1] + ) ** 2 # store distance - distance.append((dx2 + dy2)**0.5) + distance.append((dx2 + dy2) ** 0.5) # store all best scatters and index good_scatter.append(scatter) @@ -546,18 +582,28 @@ def hover(self, event) -> None: self.set_cross_hair_visible(True) # update the cursor x,y data - self.horizontal_line.set_ydata([y,]) - self.vertical_line.set_xdata([x,]) + self.horizontal_line.set_ydata( + [ + y, + ] + ) + self.vertical_line.set_xdata( + [ + x, + ] + ) # x y label if self.hover_instance: xy_pos = ax.transData.transform([(x, y)]) self.x_hover_data = x self.y_hover_data = y - self.hover_instance.x_hover_pos = float( - xy_pos[0][0]) + self.x - self.hover_instance.y_hover_pos = float( - xy_pos[0][1]) + self.y + self.hover_instance.x_hover_pos = ( + float(xy_pos[0][0]) + self.x + ) + self.hover_instance.y_hover_pos = ( + float(xy_pos[0][1]) + self.y + ) self.hover_instance.show_cursor = True if self.cursor_xaxis_formatter: @@ -571,51 +617,76 @@ def hover(self, event) -> None: self.hover_instance.label_x_value = f"{x}" self.hover_instance.label_y_value = f"{y}" - self.hover_instance.xmin_line = float( - ax.bbox.bounds[0]) + self.x - self.hover_instance.xmax_line = float( - ax.bbox.bounds[0] + ax.bbox.bounds[2]) + self.x - self.hover_instance.ymin_line = float( - ax.bbox.bounds[1]) + self.y - self.hover_instance.ymax_line = float( - ax.bbox.bounds[1] + ax.bbox.bounds[3]) + self.y + self.hover_instance.xmin_line = ( + float(ax.bbox.bounds[0]) + self.x + ) + self.hover_instance.xmax_line = ( + float(ax.bbox.bounds[0] + ax.bbox.bounds[2]) + self.x + ) + self.hover_instance.ymin_line = ( + float(ax.bbox.bounds[1]) + self.y + ) + self.hover_instance.ymax_line = ( + float(ax.bbox.bounds[1] + ax.bbox.bounds[3]) + self.y + ) if self.scatter_label and idx_best > len(good_line) - 1: if self.multi_xdata: - self.hover_instance.custom_label = self.scatter_label[ - good_index2_scatter[idx_best]] + self.hover_instance.custom_label = ( + self.scatter_label[ + good_index2_scatter[idx_best] + ] + ) else: - self.hover_instance.custom_label = self.scatter_label[ - good_index_scatter[idx_best]] + self.hover_instance.custom_label = ( + self.scatter_label[ + good_index_scatter[idx_best] + ] + ) if line: self.hover_instance.custom_color = get_color_from_hex( - to_hex(line.get_color())) + to_hex(line.get_color()) + ) elif scatter: if self.multi_xdata: - if len( - self.x_cursor) == len( - scatter.get_facecolors()): - color = scatter.get_facecolors( - )[good_index2_scatter[idx_best]] + if len(self.x_cursor) == len( + scatter.get_facecolors() + ): + color = scatter.get_facecolors()[ + good_index2_scatter[idx_best] + ] else: color = scatter.get_facecolors() - self.hover_instance.custom_color = get_color_from_hex( - to_hex(color)) + self.hover_instance.custom_color = ( + get_color_from_hex(to_hex(color)) + ) else: - if len( - self.x_cursor) == len( - scatter.get_facecolors()): - color = scatter.get_facecolors( - )[good_index_scatter[idx_best]] + if len(self.x_cursor) == len( + scatter.get_facecolors() + ): + color = scatter.get_facecolors()[ + good_index_scatter[idx_best] + ] else: color = scatter.get_facecolors() - self.hover_instance.custom_color = get_color_from_hex( - to_hex(color)) + self.hover_instance.custom_color = ( + get_color_from_hex(to_hex(color)) + ) - if self.hover_instance.x_hover_pos > self.x + self.axes.bbox.bounds[2] + self.axes.bbox.bounds[0] or \ - self.hover_instance.x_hover_pos < self.x + self.axes.bbox.bounds[0] or \ - self.hover_instance.y_hover_pos > self.y + self.axes.bbox.bounds[1] + self.axes.bbox.bounds[3] or \ - self.hover_instance.y_hover_pos < self.y + self.axes.bbox.bounds[1]: + if ( + self.hover_instance.x_hover_pos + > self.x + + self.axes.bbox.bounds[2] + + self.axes.bbox.bounds[0] + or self.hover_instance.x_hover_pos + < self.x + self.axes.bbox.bounds[0] + or self.hover_instance.y_hover_pos + > self.y + + self.axes.bbox.bounds[1] + + self.axes.bbox.bounds[3] + or self.hover_instance.y_hover_pos + < self.y + self.axes.bbox.bounds[1] + ): self.hover_instance.hover_outside_bound = True else: self.hover_instance.hover_outside_bound = False @@ -635,10 +706,12 @@ def hover(self, event) -> None: if self.scatter_label and idx_best > len(good_line) - 1: if self.multi_xdata: self.text.set_text( - f"{self.scatter_label[good_index2_scatter[idx_best]]} x={x}, y={y}") + f"{self.scatter_label[good_index2_scatter[idx_best]]} x={x}, y={y}" + ) else: self.text.set_text( - f"{self.scatter_label[good_index_scatter[idx_best]]} x={x}, y={y}") + f"{self.scatter_label[good_index_scatter[idx_best]]} x={x}, y={y}" + ) else: self.text.set_text(f"x={x}, y={y}") @@ -648,7 +721,8 @@ def hover(self, event) -> None: self.axes.figure.canvas.draw_idle() self.axes.figure.canvas.flush_events() self.background = self.axes.figure.canvas.copy_from_bbox( - self.axes.figure.bbox) + self.axes.figure.bbox + ) self.set_cross_hair_visible(True) self.axes.figure.canvas.restore_region(self.background) @@ -676,15 +750,20 @@ def autoscale(self): return ax = self.axes no_visible = self.myrelim(ax, visible_only=self.autoscale_visible_only) - ax.autoscale_view(tight=self.autoscale_tight, - scalex=True if self.autoscale_axis != "y" else False, - scaley=True if self.autoscale_axis != "x" else False) + ax.autoscale_view( + tight=self.autoscale_tight, + scalex=True if self.autoscale_axis != "y" else False, + scaley=True if self.autoscale_axis != "x" else False, + ) ax.autoscale(axis=self.autoscale_axis, tight=self.autoscale_tight) current_xlim = ax.get_xlim() current_ylim = ax.get_ylim() - lim_collection, invert_xaxis, invert_yaxis = self.data_limit_collection( - ax, visible_only=self.autoscale_visible_only) + lim_collection, invert_xaxis, invert_yaxis = ( + self.data_limit_collection( + ax, visible_only=self.autoscale_visible_only + ) + ) if lim_collection: xchanged = False if self.autoscale_tight: @@ -711,10 +790,12 @@ def autoscale(self): # recalculed margin if xchanged: xlim = ax.get_xlim() - ax.set_xlim(left=xlim[0] - - current_margins[0] * (xlim[1] - xlim[0])) ax.set_xlim( - right=xlim[1] + current_margins[0] * (xlim[1] - xlim[0])) + left=xlim[0] - current_margins[0] * (xlim[1] - xlim[0]) + ) + ax.set_xlim( + right=xlim[1] + current_margins[0] * (xlim[1] - xlim[0]) + ) ychanged = False @@ -737,9 +818,11 @@ def autoscale(self): if ychanged: ylim = ax.get_ylim() ax.set_ylim( - bottom=ylim[0] - current_margins[1] * (ylim[1] - ylim[0])) - ax.set_ylim(top=ylim[1] + current_margins[1] - * (ylim[1] - ylim[0])) + bottom=ylim[0] - current_margins[1] * (ylim[1] - ylim[0]) + ) + ax.set_ylim( + top=ylim[1] + current_margins[1] * (ylim[1] - ylim[0]) + ) ax.figure.canvas.draw_idle() ax.figure.canvas.flush_events() @@ -840,16 +923,18 @@ def data_limit_collection(self, ax, visible_only=False): return datalim, invert_xaxis, invert_yaxis def home(self) -> None: - """ reset data axis + """reset data axis Return: None """ # do nothing is all min/max are not set - if self.xmin is not None and \ - self.xmax is not None and \ - self.ymin is not None and \ - self.ymax is not None: + if ( + self.xmin is not None + and self.xmax is not None + and self.ymin is not None + and self.ymax is not None + ): ax = self.axes xleft, xright = ax.get_xlim() @@ -901,11 +986,19 @@ def push_current(self): """Push the current view limits and position onto the stack.""" self._nav_stack.push( WeakKeyDictionary( - {ax: (ax._get_view(), - # Store both the original and modified positions. - (ax.get_position(True).frozen(), - ax.get_position().frozen())) - for ax in self.figure.axes})) + { + ax: ( + ax._get_view(), + # Store both the original and modified positions. + ( + ax.get_position(True).frozen(), + ax.get_position().frozen(), + ), + ) + for ax in self.figure.axes + } + ) + ) self.set_history_buttons() def update(self): @@ -927,8 +1020,8 @@ def _update_view(self): for ax, (view, (pos_orig, pos_active)) in items: ax._set_view(view) # Restore both the original and modified positions - ax._set_position(pos_orig, 'original') - ax._set_position(pos_active, 'active') + ax._set_position(pos_orig, "original") + ax._set_position(pos_active, "active") self.figure.canvas.draw_idle() self.figure.canvas.flush_events() @@ -936,7 +1029,7 @@ def set_history_buttons(self): """Enable or disable the back/forward button.""" def reset_touch(self) -> None: - """ reset touch + """reset touch Return: None @@ -945,7 +1038,7 @@ def reset_touch(self) -> None: self._last_touch_pos = {} def _get_scale(self): - """ kivy scatter _get_scale method """ + """kivy scatter _get_scale method""" p1 = Vector(*self.to_parent(0, 0)) p2 = Vector(*self.to_parent(1, 0)) scale = p1.distance(p2) @@ -955,7 +1048,7 @@ def _get_scale(self): # prevent anything wrong with scale, just avoid to dispatch it # if the scale "visually" didn't change. #947 # Remove this ugly hack when we'll be Python 3 only. - if hasattr(self, '_scale_p'): + if hasattr(self, "_scale_p"): if str(scale) == str(self._scale_p): return self._scale_p @@ -963,65 +1056,72 @@ def _get_scale(self): return scale def _set_scale(self, scale): - """ kivy scatter _set_scale method """ + """kivy scatter _set_scale method""" rescale = scale * 1.0 / self.scale - self.apply_transform(Matrix().scale(rescale, rescale, rescale), - post_multiply=True, - anchor=self.to_local(*self.center)) + self.apply_transform( + Matrix().scale(rescale, rescale, rescale), + post_multiply=True, + anchor=self.to_local(*self.center), + ) - scale = AliasProperty(_get_scale, _set_scale, bind=('x', 'y', 'transform')) - '''Scale value of the scatter. + scale = AliasProperty(_get_scale, _set_scale, bind=("x", "y", "transform")) + """Scale value of the scatter. :attr:`scale` is an :class:`~kivy.properties.AliasProperty` and defaults to 1.0. - ''' + """ def _draw_bitmap(self): - """ draw bitmap method. based on kivy scatter method""" + """draw bitmap method. based on kivy scatter method""" if self._bitmap is None: print("No bitmap!") return self._img_texture = Texture.create(size=(self.bt_w, self.bt_h)) self._img_texture.blit_buffer( - bytes(self._bitmap), colorfmt="rgba", bufferfmt='ubyte') + bytes(self._bitmap), colorfmt="rgba", bufferfmt="ubyte" + ) self._img_texture.flip_vertical() self.update_hover() self.update_selector() def transform_with_touch(self, event): - """ manage touch behaviour. based on kivy scatter method""" + """manage touch behaviour. based on kivy scatter method""" # just do a simple one finger drag changed = False if len(self._touches) == self.translation_touches: - if self.touch_mode == 'pan': + if self.touch_mode == "pan": if self._nav_stack() is None: self.push_current() self.apply_pan(self.axes, event) - if self.touch_mode == 'pan_x' or self.touch_mode == 'pan_y' \ - or self.touch_mode == 'adjust_x' or self.touch_mode == 'adjust_y': + if ( + self.touch_mode == "pan_x" + or self.touch_mode == "pan_y" + or self.touch_mode == "adjust_x" + or self.touch_mode == "adjust_y" + ): if self._nav_stack() is None: self.push_current() self.apply_pan(self.axes, event, mode=self.touch_mode) - elif self.touch_mode == 'drag_legend': + elif self.touch_mode == "drag_legend": if self.legend_instance: self.apply_drag_legend(self.axes, event) - elif self.touch_mode == 'zoombox': + elif self.touch_mode == "zoombox": if self._nav_stack() is None: self.push_current() real_x, real_y = event.x - self.pos[0], event.y - self.pos[1] # in case x_init is not create - if not hasattr(self, 'x_init'): + if not hasattr(self, "x_init"): self.x_init = event.x self.y_init = real_y self.draw_box(event, self.x_init, self.y_init, event.x, real_y) # mode cursor - elif self.touch_mode == 'cursor': + elif self.touch_mode == "cursor": self.hover_on = True self.hover(event) @@ -1032,8 +1132,11 @@ def transform_with_touch(self, event): return changed # we have more than one touch... list of last known pos - points = [Vector(self._last_touch_pos[t]) for t in self._touches - if t is not event] + points = [ + Vector(self._last_touch_pos[t]) + for t in self._touches + if t is not event + ] # add current touch last points.append(Vector(event.pos)) @@ -1058,16 +1161,28 @@ def transform_with_touch(self, event): if self.auto_zoom: v1 = Vector(0, 10) angle = v1.angle(new_line) + 180 - if angle < 0 + self.zoom_angle_detection or angle > 360 - self.zoom_angle_detection: + if ( + angle < 0 + self.zoom_angle_detection + or angle > 360 - self.zoom_angle_detection + ): self.do_zoom_x = False self.do_zoom_y = True - elif angle > 90 - self.zoom_angle_detection and angle < 90 + self.zoom_angle_detection: + elif ( + angle > 90 - self.zoom_angle_detection + and angle < 90 + self.zoom_angle_detection + ): self.do_zoom_x = True self.do_zoom_y = False - elif angle > 180 - self.zoom_angle_detection and angle < 180 + self.zoom_angle_detection: + elif ( + angle > 180 - self.zoom_angle_detection + and angle < 180 + self.zoom_angle_detection + ): self.do_zoom_x = False self.do_zoom_y = True - elif angle > 270 - self.zoom_angle_detection and angle < 270 + self.zoom_angle_detection: + elif ( + angle > 270 - self.zoom_angle_detection + and angle < 270 + self.zoom_angle_detection + ): self.do_zoom_x = True self.do_zoom_y = False else: @@ -1089,10 +1204,12 @@ def transform_with_touch(self, event): return changed def on_motion(self, *args): - '''Kivy Event to trigger mouse event on motion - `enter_notify_event`. - ''' - if self._pressed or self.disabled: # Do not process this event if there's a touch_move + """Kivy Event to trigger mouse event on motion + `enter_notify_event`. + """ + if ( + self._pressed or self.disabled + ): # Do not process this event if there's a touch_move return pos = args[1] @@ -1112,11 +1229,11 @@ def on_motion(self, *args): self.hover(FakeEventScatter) def get_data_xy(self, x, y): - """ manage x y data in navigation bar TODO""" + """manage x y data in navigation bar TODO""" return None, None def on_touch_down(self, event): - """ Manage Mouse/touch press """ + """Manage Mouse/touch press""" if self.disabled: return @@ -1132,7 +1249,7 @@ def on_touch_down(self, event): self.current_legend = current_legend break if select_legend: - if self.touch_mode != 'drag_legend': + if self.touch_mode != "drag_legend": return False else: event.grab(self) @@ -1154,23 +1271,23 @@ def on_touch_down(self, event): elif event.is_double_tap: if not self.disable_double_tap: - if self.touch_mode != 'selector': + if self.touch_mode != "selector": self.home() return True else: - if self.touch_mode == 'cursor': + if self.touch_mode == "cursor": self.hover_on = True self.hover(event) - elif self.touch_mode == 'zoombox': + elif self.touch_mode == "zoombox": real_x, real_y = x - self.pos[0], y - self.pos[1] self.x_init = x self.y_init = real_y self.draw_box(event, x, real_y, x, real_y) - elif self.touch_mode == 'minmax': + elif self.touch_mode == "minmax": self.min_max(event) - elif self.touch_mode == 'selector': + elif self.touch_mode == "selector": pass event.grab(self) @@ -1186,7 +1303,7 @@ def on_touch_down(self, event): return False def on_touch_move(self, event): - """ Manage Mouse/touch move while pressed """ + """Manage Mouse/touch move while pressed""" if self.disabled: return @@ -1194,7 +1311,7 @@ def on_touch_move(self, event): if event.is_double_tap: if not self.disable_double_tap: - if self.touch_mode != 'selector': + if self.touch_mode != "selector": self.home() return True @@ -1208,7 +1325,7 @@ def on_touch_move(self, event): return True def on_touch_up(self, event): - """ Manage Mouse/touch release """ + """Manage Mouse/touch release""" if self.disabled: return @@ -1217,21 +1334,32 @@ def on_touch_up(self, event): event.ungrab(self) del self._last_touch_pos[event] self._touches.remove(event) - if (self.touch_mode == 'pan' or self.touch_mode == 'zoombox' or - self.touch_mode == 'pan_x' or self.touch_mode == 'pan_y' or - self.touch_mode == 'adjust_x' or self.touch_mode == 'adjust_y' or - self.touch_mode == 'minmax'): + if ( + self.touch_mode == "pan" + or self.touch_mode == "zoombox" + or self.touch_mode == "pan_x" + or self.touch_mode == "pan_y" + or self.touch_mode == "adjust_x" + or self.touch_mode == "adjust_y" + or self.touch_mode == "minmax" + ): self.push_current() if self.interactive_axis: - if self.touch_mode == 'pan_x' or self.touch_mode == 'pan_y' \ - or self.touch_mode == 'adjust_x' or self.touch_mode == 'adjust_y': - self.touch_mode = 'pan' + if ( + self.touch_mode == "pan_x" + or self.touch_mode == "pan_y" + or self.touch_mode == "adjust_x" + or self.touch_mode == "adjust_y" + ): + self.touch_mode = "pan" self.first_touch_pan = None x, y = event.x, event.y - if abs( - self._box_size[0]) > 1 or abs( - self._box_size[1]) > 1 or self.touch_mode == 'zoombox': + if ( + abs(self._box_size[0]) > 1 + or abs(self._box_size[1]) > 1 + or self.touch_mode == "zoombox" + ): self.reset_box() if not self.collide_point(x, y) and self.do_update: # update axis lim if zoombox is used and touch outside widget @@ -1259,8 +1387,8 @@ def on_touch_up(self, event): return True def apply_zoom(self, scale_factor, ax, anchor=(0, 0), new_line=None): - """ zoom touch method """ - if self.touch_mode == 'selector': + """zoom touch method""" + if self.touch_mode == "selector": return x = anchor[0] @@ -1275,7 +1403,7 @@ def apply_zoom(self, scale_factor, ax, anchor=(0, 0), new_line=None): scale = ax.get_xscale() yscale = ax.get_yscale() - if scale == 'linear': + if scale == "linear": old_min = cur_xlim[0] old_max = cur_xlim[1] @@ -1286,7 +1414,7 @@ def apply_zoom(self, scale_factor, ax, anchor=(0, 0), new_line=None): xdata = self.transform_eval(xdata, ax.yaxis) old_max = self.transform_eval(max_, ax.yaxis) - if yscale == 'linear': + if yscale == "linear": yold_min = cur_ylim[0] yold_max = cur_ylim[1] @@ -1304,36 +1432,44 @@ def apply_zoom(self, scale_factor, ax, anchor=(0, 0), new_line=None): rely = (yold_max - ydata) / (yold_max - yold_min) if self.do_zoom_x: - if scale == 'linear': - ax.set_xlim([xdata - new_width * (1 - relx), - xdata + new_width * (relx)]) + if scale == "linear": + ax.set_xlim( + [ + xdata - new_width * (1 - relx), + xdata + new_width * (relx), + ] + ) else: new_min = xdata - new_width * (1 - relx) new_max = xdata + new_width * (relx) try: new_min, new_max = self.inv_transform_eval( - new_min, ax.yaxis), self.inv_transform_eval( - new_max, ax.yaxis) + new_min, ax.yaxis + ), self.inv_transform_eval(new_max, ax.yaxis) except OverflowError: # Limit case new_min, new_max = min_, max_ - if new_min <= 0. or new_max <= 0.: # Limit case + if new_min <= 0.0 or new_max <= 0.0: # Limit case new_min, new_max = min_, max_ ax.set_xlim([new_min, new_max]) if self.do_zoom_y: - if yscale == 'linear': - ax.set_ylim([ydata - new_height * (1 - rely), - ydata + new_height * (rely)]) + if yscale == "linear": + ax.set_ylim( + [ + ydata - new_height * (1 - rely), + ydata + new_height * (rely), + ] + ) else: new_ymin = ydata - new_height * (1 - rely) new_ymax = ydata + new_height * (rely) try: new_ymin, new_ymax = self.inv_transform_eval( - new_ymin, ax.yaxis), self.inv_transform_eval( - new_ymax, ax.yaxis) + new_ymin, ax.yaxis + ), self.inv_transform_eval(new_ymax, ax.yaxis) except OverflowError: # Limit case new_ymin, new_ymax = ymin_, ymax_ - if new_ymin <= 0. or new_ymax <= 0.: # Limit case + if new_ymin <= 0.0 or new_ymax <= 0.0: # Limit case new_ymin, new_ymax = ymin_, ymax_ ax.set_ylim([new_ymin, new_ymax]) @@ -1344,7 +1480,8 @@ def apply_zoom(self, scale_factor, ax, anchor=(0, 0), new_line=None): ax.figure.canvas.draw_idle() ax.figure.canvas.flush_events() self.background = ax.figure.canvas.copy_from_bbox( - ax.figure.bbox) + ax.figure.bbox + ) self.background_patch_copy.set_visible(False) ax.figure.canvas.restore_region(self.background) @@ -1368,30 +1505,36 @@ def apply_zoom(self, scale_factor, ax, anchor=(0, 0), new_line=None): ax.figure.canvas.draw_idle() ax.figure.canvas.flush_events() - def apply_pan(self, ax, event, mode='pan'): - """ pan method """ + def apply_pan(self, ax, event, mode="pan"): + """pan method""" trans = ax.transData.inverted() xdata, ydata = trans.transform_point( - (event.x - self.pos[0], event.y - self.pos[1])) + (event.x - self.pos[0], event.y - self.pos[1]) + ) xpress, ypress = trans.transform_point( - (self._last_touch_pos[event][0] - self.pos[0], - self._last_touch_pos[event][1] - self.pos[1])) + ( + self._last_touch_pos[event][0] - self.pos[0], + self._last_touch_pos[event][1] - self.pos[1], + ) + ) scale = ax.get_xscale() yscale = ax.get_yscale() - if scale == 'linear': + if scale == "linear": dx = xdata - xpress else: - dx = self.transform_eval(xdata, ax.xaxis) - \ - self.transform_eval(xpress, ax.xaxis) + dx = self.transform_eval(xdata, ax.xaxis) - self.transform_eval( + xpress, ax.xaxis + ) - if yscale == 'linear': + if yscale == "linear": dy = ydata - ypress else: - dy = self.transform_eval(ydata, ax.yaxis) - \ - self.transform_eval(ypress, ax.yaxis) + dy = self.transform_eval(ydata, ax.yaxis) - self.transform_eval( + ypress, ax.yaxis + ) xleft, xright = self.axes.get_xlim() ybottom, ytop = self.axes.get_ylim() @@ -1410,56 +1553,76 @@ def apply_pan(self, ax, event, mode='pan'): else: cur_ylim = (ybottom, ytop) - if self.interactive_axis and self.touch_mode == 'pan' and not self.first_touch_pan == 'pan': + if ( + self.interactive_axis + and self.touch_mode == "pan" + and not self.first_touch_pan == "pan" + ): if (ydata < cur_ylim[0] and not inverted_y) or ( - ydata > cur_ylim[1] and inverted_y): + ydata > cur_ylim[1] and inverted_y + ): left_anchor_zone = ( - cur_xlim[1] - cur_xlim[0]) * .2 + cur_xlim[0] + cur_xlim[1] - cur_xlim[0] + ) * 0.2 + cur_xlim[0] right_anchor_zone = ( - cur_xlim[1] - cur_xlim[0]) * .8 + cur_xlim[0] + cur_xlim[1] - cur_xlim[0] + ) * 0.8 + cur_xlim[0] if xdata < left_anchor_zone or xdata > right_anchor_zone: - mode = 'adjust_x' + mode = "adjust_x" else: - mode = 'pan_x' + mode = "pan_x" self.touch_mode = mode - elif (xdata < cur_xlim[0] and not inverted_x) or (xdata > cur_xlim[1] and inverted_x): + elif (xdata < cur_xlim[0] and not inverted_x) or ( + xdata > cur_xlim[1] and inverted_x + ): bottom_anchor_zone = ( - cur_ylim[1] - cur_ylim[0]) * .2 + cur_ylim[0] - top_anchor_zone = ( - cur_ylim[1] - cur_ylim[0]) * .8 + cur_ylim[0] + cur_ylim[1] - cur_ylim[0] + ) * 0.2 + cur_ylim[0] + top_anchor_zone = (cur_ylim[1] - cur_ylim[0]) * 0.8 + cur_ylim[ + 0 + ] if ydata < bottom_anchor_zone or ydata > top_anchor_zone: - mode = 'adjust_y' + mode = "adjust_y" else: - mode = 'pan_y' + mode = "pan_y" self.touch_mode = mode else: - self.touch_mode = 'pan' + self.touch_mode = "pan" - if not mode == 'pan_y' and not mode == 'adjust_y': - if mode == 'adjust_x': + if not mode == "pan_y" and not mode == "adjust_y": + if mode == "adjust_x": if self.anchor_x is None: midpoint = (cur_xlim[1] + cur_xlim[0]) / 2 if xdata > midpoint: - self.anchor_x = 'left' + self.anchor_x = "left" else: - self.anchor_x = 'right' - if self.anchor_x == 'left': + self.anchor_x = "right" + if self.anchor_x == "left": if xdata > cur_xlim[0]: - if scale == 'linear': + if scale == "linear": cur_xlim -= dx else: try: cur_xlim = [ self.inv_transform_eval( - (self.transform_eval( - cur_xlim[0], - ax.xaxis) - dx), - ax.xaxis), + ( + self.transform_eval( + cur_xlim[0], ax.xaxis + ) + - dx + ), + ax.xaxis, + ), self.inv_transform_eval( - (self.transform_eval( - cur_xlim[1], - ax.xaxis) - dx), - ax.xaxis)] + ( + self.transform_eval( + cur_xlim[1], ax.xaxis + ) + - dx + ), + ax.xaxis, + ), + ] except (ValueError, OverflowError): cur_xlim = cur_xlim # Keep previous limits if inverted_x: @@ -1468,21 +1631,30 @@ def apply_pan(self, ax, event, mode='pan'): ax.set_xlim(None, cur_xlim[1]) else: if xdata < cur_xlim[1]: - if scale == 'linear': + if scale == "linear": cur_xlim -= dx else: try: cur_xlim = [ self.inv_transform_eval( - (self.transform_eval( - cur_xlim[0], - ax.xaxis) - dx), - ax.xaxis), + ( + self.transform_eval( + cur_xlim[0], ax.xaxis + ) + - dx + ), + ax.xaxis, + ), self.inv_transform_eval( - (self.transform_eval( - cur_xlim[1], - ax.xaxis) - dx), - ax.xaxis)] + ( + self.transform_eval( + cur_xlim[1], ax.xaxis + ) + - dx + ), + ax.xaxis, + ), + ] except (ValueError, OverflowError): cur_xlim = cur_xlim # Keep previous limits if inverted_x: @@ -1490,21 +1662,26 @@ def apply_pan(self, ax, event, mode='pan'): else: ax.set_xlim(cur_xlim[0], None) else: - if scale == 'linear': + if scale == "linear": cur_xlim -= dx else: try: cur_xlim = [ self.inv_transform_eval( - (self.transform_eval( - cur_xlim[0], - ax.xaxis) - dx), - ax.xaxis), + ( + self.transform_eval(cur_xlim[0], ax.xaxis) + - dx + ), + ax.xaxis, + ), self.inv_transform_eval( - (self.transform_eval( - cur_xlim[1], - ax.xaxis) - dx), - ax.xaxis)] + ( + self.transform_eval(cur_xlim[1], ax.xaxis) + - dx + ), + ax.xaxis, + ), + ] except (ValueError, OverflowError): cur_xlim = cur_xlim # Keep previous limits if inverted_x: @@ -1512,33 +1689,42 @@ def apply_pan(self, ax, event, mode='pan'): else: ax.set_xlim(cur_xlim) - if not mode == 'pan_x' and not mode == 'adjust_x': - if mode == 'adjust_y': + if not mode == "pan_x" and not mode == "adjust_x": + if mode == "adjust_y": if self.anchor_y is None: midpoint = (cur_ylim[1] + cur_ylim[0]) / 2 if ydata > midpoint: - self.anchor_y = 'top' + self.anchor_y = "top" else: - self.anchor_y = 'bottom' + self.anchor_y = "bottom" - if self.anchor_y == 'top': + if self.anchor_y == "top": if ydata > cur_ylim[0]: - if yscale == 'linear': + if yscale == "linear": cur_ylim -= dy else: try: cur_ylim = [ self.inv_transform_eval( - (self.transform_eval( - cur_ylim[0], - ax.yaxis) - dy), - ax.yaxis), + ( + self.transform_eval( + cur_ylim[0], ax.yaxis + ) + - dy + ), + ax.yaxis, + ), self.inv_transform_eval( - (self.transform_eval( - cur_ylim[1], - ax.yaxis) - dy), - ax.yaxis)] + ( + self.transform_eval( + cur_ylim[1], ax.yaxis + ) + - dy + ), + ax.yaxis, + ), + ] except (ValueError, OverflowError): cur_ylim = cur_ylim # Keep previous limits if inverted_y: @@ -1547,22 +1733,31 @@ def apply_pan(self, ax, event, mode='pan'): ax.set_ylim(None, cur_ylim[1]) else: if ydata < cur_ylim[1]: - if yscale == 'linear': + if yscale == "linear": cur_ylim -= dy else: try: cur_ylim = [ self.inv_transform_eval( - (self.transform_eval( - cur_ylim[0], - ax.yaxis) - dy), - ax.yaxis), + ( + self.transform_eval( + cur_ylim[0], ax.yaxis + ) + - dy + ), + ax.yaxis, + ), self.inv_transform_eval( - (self.transform_eval( - cur_ylim[1], - ax.yaxis) - dy), - ax.yaxis)] + ( + self.transform_eval( + cur_ylim[1], ax.yaxis + ) + - dy + ), + ax.yaxis, + ), + ] except (ValueError, OverflowError): cur_ylim = cur_ylim # Keep previous limits if inverted_y: @@ -1570,22 +1765,27 @@ def apply_pan(self, ax, event, mode='pan'): else: ax.set_ylim(cur_ylim[0], None) else: - if yscale == 'linear': + if yscale == "linear": cur_ylim -= dy else: try: cur_ylim = [ self.inv_transform_eval( - (self.transform_eval( - cur_ylim[0], - ax.yaxis) - dy), - ax.yaxis), + ( + self.transform_eval(cur_ylim[0], ax.yaxis) + - dy + ), + ax.yaxis, + ), self.inv_transform_eval( - (self.transform_eval( - cur_ylim[1], - ax.yaxis) - dy), - ax.yaxis)] + ( + self.transform_eval(cur_ylim[1], ax.yaxis) + - dy + ), + ax.yaxis, + ), + ] except (ValueError, OverflowError): cur_ylim = cur_ylim # Keep previous limits if inverted_y: @@ -1603,7 +1803,8 @@ def apply_pan(self, ax, event, mode='pan'): ax.figure.canvas.draw_idle() ax.figure.canvas.flush_events() self.background = ax.figure.canvas.copy_from_bbox( - ax.figure.bbox) + ax.figure.bbox + ) self.background_patch_copy.set_visible(False) ax.figure.canvas.restore_region(self.background) @@ -1629,62 +1830,100 @@ def apply_pan(self, ax, event, mode='pan'): ax.figure.canvas.flush_events() def update_hover(self): - """ update hover on fast draw (if exist)""" + """update hover on fast draw (if exist)""" if self.hover_instance: # update hover pos if needed - if self.hover_instance.show_cursor and self.x_hover_data is not None and self.y_hover_data is not None: + if ( + self.hover_instance.show_cursor + and self.x_hover_data is not None + and self.y_hover_data is not None + ): xy_pos = self.axes.transData.transform( - [(self.x_hover_data, self.y_hover_data)]) + [(self.x_hover_data, self.y_hover_data)] + ) self.hover_instance.x_hover_pos = float(xy_pos[0][0]) + self.x self.hover_instance.y_hover_pos = float(xy_pos[0][1]) + self.y - self.hover_instance.xmin_line = float( - self.axes.bbox.bounds[0]) + self.x - self.hover_instance.xmax_line = float( - self.axes.bbox.bounds[0] + self.axes.bbox.bounds[2]) + self.x - self.hover_instance.ymin_line = float( - self.axes.bbox.bounds[1]) + self.y - self.hover_instance.ymax_line = float( - self.axes.bbox.bounds[1] + self.axes.bbox.bounds[3]) + self.y - - if self.hover_instance.x_hover_pos > self.x + self.axes.bbox.bounds[2] + self.axes.bbox.bounds[0] or \ - self.hover_instance.x_hover_pos < self.x + self.axes.bbox.bounds[0] or \ - self.hover_instance.y_hover_pos > self.y + self.axes.bbox.bounds[1] + self.axes.bbox.bounds[3] or \ - self.hover_instance.y_hover_pos < self.y + self.axes.bbox.bounds[1]: + self.hover_instance.xmin_line = ( + float(self.axes.bbox.bounds[0]) + self.x + ) + self.hover_instance.xmax_line = ( + float(self.axes.bbox.bounds[0] + self.axes.bbox.bounds[2]) + + self.x + ) + self.hover_instance.ymin_line = ( + float(self.axes.bbox.bounds[1]) + self.y + ) + self.hover_instance.ymax_line = ( + float(self.axes.bbox.bounds[1] + self.axes.bbox.bounds[3]) + + self.y + ) + + if ( + self.hover_instance.x_hover_pos + > self.x + + self.axes.bbox.bounds[2] + + self.axes.bbox.bounds[0] + or self.hover_instance.x_hover_pos + < self.x + self.axes.bbox.bounds[0] + or self.hover_instance.y_hover_pos + > self.y + + self.axes.bbox.bounds[1] + + self.axes.bbox.bounds[3] + or self.hover_instance.y_hover_pos + < self.y + self.axes.bbox.bounds[1] + ): self.hover_instance.hover_outside_bound = True else: self.hover_instance.hover_outside_bound = False def update_selector(self, *args): - """ update selector on fast draw (if exist)""" + """update selector on fast draw (if exist)""" if self.selector: # update selector pos if needed if self.selector.resize_wgt.verts and ( - len(args) != 0 or self.touch_mode != 'selector'): + len(args) != 0 or self.touch_mode != "selector" + ): resize_wgt = self.selector.resize_wgt - if hasattr(resize_wgt, 'shapes'): + if hasattr(resize_wgt, "shapes"): # lasso widget or ellipse if resize_wgt.shapes: - if hasattr(resize_wgt.shapes[0], 'radius_x'): + if hasattr(resize_wgt.shapes[0], "radius_x"): # ellipse widget xy_pos = resize_wgt.ax.transData.transform( - [(resize_wgt.verts[1][0], resize_wgt.verts[1][1])]) + [ + ( + resize_wgt.verts[1][0], + resize_wgt.verts[1][1], + ) + ] + ) new_pos = resize_wgt.to_widget( - *(float(xy_pos[0][0]), float(xy_pos[0][1]))) + *(float(xy_pos[0][0]), float(xy_pos[0][1])) + ) pos0 = new_pos[0] + self.x pos1 = new_pos[1] + self.y xy_pos2 = resize_wgt.ax.transData.transform( - [(resize_wgt.verts[2][0], resize_wgt.verts[2][1])]) + [ + ( + resize_wgt.verts[2][0], + resize_wgt.verts[2][1], + ) + ] + ) new_pos2 = resize_wgt.to_widget( - *(float(xy_pos2[0][0]), float(xy_pos2[0][1]))) + *(float(xy_pos2[0][0]), float(xy_pos2[0][1])) + ) pos0_2 = new_pos2[0] + self.x pos1_2 = new_pos2[1] + self.y current_shape = resize_wgt.shapes[0] dataxy1 = current_shape.selection_point_inst.points - dataxy2 = current_shape.selection_point_inst2.points + dataxy2 = ( + current_shape.selection_point_inst2.points + ) # note: the 2 first points are the same in # current_shape.points @@ -1695,17 +1934,26 @@ def update_selector(self, *args): pos1_2_old = dataxy2[1] old_length = np.sqrt( - (pos0_2_old - pos0_old) ** 2 + - (pos1_2_old - pos1_old) ** 2) + (pos0_2_old - pos0_old) ** 2 + + (pos1_2_old - pos1_old) ** 2 + ) new_length = np.sqrt( - (pos0_2 - pos0)**2 + (pos1_2 - pos1)**2) + (pos0_2 - pos0) ** 2 + (pos1_2 - pos1) ** 2 + ) scale = float(new_length / old_length) xy_pos3 = resize_wgt.ax.transData.transform( - [(resize_wgt.verts[0][0], resize_wgt.verts[0][1])]) + [ + ( + resize_wgt.verts[0][0], + resize_wgt.verts[0][1], + ) + ] + ) new_pos3 = resize_wgt.to_widget( - *(float(xy_pos3[0][0]), float(xy_pos3[0][1]))) + *(float(xy_pos3[0][0]), float(xy_pos3[0][1])) + ) pos0_c = new_pos3[0] + self.x pos1_c = new_pos3[1] + self.y @@ -1715,27 +1963,43 @@ def update_selector(self, *args): for s in resize_wgt.shapes: s.translate(pos=(pos0_c, pos1_c)) - xmin, xmax, ymin, ymax = resize_wgt.shapes[0].get_min_max( - ) + xmin, xmax, ymin, ymax = resize_wgt.shapes[ + 0 + ].get_min_max() else: # lasso widget xy_pos = resize_wgt.ax.transData.transform( - [(resize_wgt.verts[0][0], resize_wgt.verts[0][1])]) + [ + ( + resize_wgt.verts[0][0], + resize_wgt.verts[0][1], + ) + ] + ) new_pos = resize_wgt.to_widget( - *(float(xy_pos[0][0]), float(xy_pos[0][1]))) + *(float(xy_pos[0][0]), float(xy_pos[0][1])) + ) pos0 = new_pos[0] + self.x pos1 = new_pos[1] + self.y xy_pos2 = resize_wgt.ax.transData.transform( - [(resize_wgt.verts[1][0], resize_wgt.verts[1][1])]) + [ + ( + resize_wgt.verts[1][0], + resize_wgt.verts[1][1], + ) + ] + ) new_pos2 = resize_wgt.to_widget( - *(float(xy_pos2[0][0]), float(xy_pos2[0][1]))) + *(float(xy_pos2[0][0]), float(xy_pos2[0][1])) + ) pos0_2 = new_pos2[0] + self.x pos1_2 = new_pos2[1] + self.y current_shape = resize_wgt.shapes[0] - dataxy = np.array( - current_shape.points).reshape(-1, 2) + dataxy = np.array(current_shape.points).reshape( + -1, 2 + ) # note: the 2 first points are the same in # current_shape.points @@ -1746,10 +2010,12 @@ def update_selector(self, *args): pos1_2_old = dataxy[2][1] old_length = np.sqrt( - (pos0_2_old - pos0_old) ** 2 + - (pos1_2_old - pos1_old) ** 2) + (pos0_2_old - pos0_old) ** 2 + + (pos1_2_old - pos1_old) ** 2 + ) new_length = np.sqrt( - (pos0_2 - pos0)**2 + (pos1_2 - pos1)**2) + (pos0_2 - pos0) ** 2 + (pos1_2 - pos1) ** 2 + ) scale = new_length / old_length @@ -1763,73 +2029,105 @@ def update_selector(self, *args): xmin, ymin = dataxy.min(axis=0) if self.collide_point( - * - resize_wgt.to_window( - xmin, - ymin)) and self.collide_point( - * - resize_wgt.to_window( - xmax, - ymax)): + *resize_wgt.to_window(xmin, ymin) + ) and self.collide_point( + *resize_wgt.to_window(xmax, ymax) + ): resize_wgt.opacity = 1 else: resize_wgt.opacity = 0 - elif self.selector.resize_wgt.verts and (len(args) != 0 or self.touch_mode != 'selector'): + elif self.selector.resize_wgt.verts and ( + len(args) != 0 or self.touch_mode != "selector" + ): resize_wgt = self.selector.resize_wgt if not (resize_wgt.size[0] > 1 and resize_wgt.size[1] > 1): return # rectangle or spann selector - if hasattr(resize_wgt, 'span_orientation'): + if hasattr(resize_wgt, "span_orientation"): # span selector - if resize_wgt.span_orientation == 'vertical': + if resize_wgt.span_orientation == "vertical": xy_pos = resize_wgt.ax.transData.transform( - [(resize_wgt.verts[0][0], resize_wgt.verts[0][1])]) + [ + ( + resize_wgt.verts[0][0], + resize_wgt.verts[0][1], + ) + ] + ) new_pos = resize_wgt.to_widget( - *(float(xy_pos[0][0]), float(xy_pos[0][1]))) + *(float(xy_pos[0][0]), float(xy_pos[0][1])) + ) resize_wgt.pos[0] = new_pos[0] + self.x top_bound = float( - self.y + - resize_wgt.ax.bbox.bounds[3] + - resize_wgt.ax.bbox.bounds[1]) + self.y + + resize_wgt.ax.bbox.bounds[3] + + resize_wgt.ax.bbox.bounds[1] + ) bottom_bound = float( - self.y + resize_wgt.ax.bbox.bounds[1]) + self.y + resize_wgt.ax.bbox.bounds[1] + ) resize_wgt.pos[1] = bottom_bound - self.y # recalcul size xy_pos2 = resize_wgt.ax.transData.transform( - [(resize_wgt.verts[3][0], resize_wgt.verts[3][1])]) + [ + ( + resize_wgt.verts[3][0], + resize_wgt.verts[3][1], + ) + ] + ) resize_wgt.size[0] = float( - xy_pos2[0][0] - xy_pos[0][0]) + xy_pos2[0][0] - xy_pos[0][0] + ) resize_wgt.size[1] = top_bound - bottom_bound - elif resize_wgt.span_orientation == 'horizontal': + elif resize_wgt.span_orientation == "horizontal": xy_pos = resize_wgt.ax.transData.transform( - [(resize_wgt.verts[0][0], resize_wgt.verts[0][1])]) + [ + ( + resize_wgt.verts[0][0], + resize_wgt.verts[0][1], + ) + ] + ) new_pos = resize_wgt.to_widget( - *(float(xy_pos[0][0]), float(xy_pos[0][1]))) + *(float(xy_pos[0][0]), float(xy_pos[0][1])) + ) left_bound = float( - self.x + resize_wgt.ax.bbox.bounds[0]) + self.x + resize_wgt.ax.bbox.bounds[0] + ) right_bound = float( - self.x + resize_wgt.ax.bbox.bounds[2] + - resize_wgt.ax.bbox.bounds[0]) + self.x + + resize_wgt.ax.bbox.bounds[2] + + resize_wgt.ax.bbox.bounds[0] + ) width = right_bound - left_bound left_bound, right_bound = resize_wgt.to_widget( - left_bound, right_bound) + left_bound, right_bound + ) resize_wgt.pos[0] = left_bound resize_wgt.pos[1] = new_pos[1] + self.y # recalcul size xy_pos2 = resize_wgt.ax.transData.transform( - [(resize_wgt.verts[0][1], resize_wgt.verts[1][1])]) + [ + ( + resize_wgt.verts[0][1], + resize_wgt.verts[1][1], + ) + ] + ) resize_wgt.size[0] = width resize_wgt.size[1] = float( - xy_pos2[0][1] - xy_pos[0][1]) + xy_pos2[0][1] - xy_pos[0][1] + ) else: # rectangle selector @@ -1837,117 +2135,148 @@ def update_selector(self, *args): # update all selector pts # recalcul pos xy_pos = resize_wgt.ax.transData.transform( - [(resize_wgt.verts[0][0], resize_wgt.verts[0][1])]) + [(resize_wgt.verts[0][0], resize_wgt.verts[0][1])] + ) new_pos = resize_wgt.to_widget( - *(float(xy_pos[0][0]), float(xy_pos[0][1]))) + *(float(xy_pos[0][0]), float(xy_pos[0][1])) + ) resize_wgt.pos[0] = new_pos[0] + self.x resize_wgt.pos[1] = new_pos[1] + self.y # recalcul size xy_pos2 = resize_wgt.ax.transData.transform( - [(resize_wgt.verts[2][0], resize_wgt.verts[2][1])]) + [(resize_wgt.verts[2][0], resize_wgt.verts[2][1])] + ) resize_wgt.size[0] = float( - xy_pos2[0][0] - xy_pos[0][0]) + xy_pos2[0][0] - xy_pos[0][0] + ) resize_wgt.size[1] = float( - xy_pos2[0][1] - xy_pos[0][1]) + xy_pos2[0][1] - xy_pos[0][1] + ) if self.collide_point( - * - resize_wgt.to_window( - resize_wgt.pos[0], - resize_wgt.pos[1])) and self.collide_point( - * - resize_wgt.to_window( - resize_wgt.pos[0] + - resize_wgt.size[0], - resize_wgt.pos[1] + - resize_wgt.size[1])): + *resize_wgt.to_window( + resize_wgt.pos[0], resize_wgt.pos[1] + ) + ) and self.collide_point( + *resize_wgt.to_window( + resize_wgt.pos[0] + resize_wgt.size[0], + resize_wgt.pos[1] + resize_wgt.size[1], + ) + ): resize_wgt.opacity = 1 else: resize_wgt.opacity = 0 def min_max(self, event): - """ manage min/max touch mode """ + """manage min/max touch mode""" ax = self.axes - xlabelbottom = ax.xaxis._major_tick_kw.get('tick1On') - ylabelleft = ax.yaxis._major_tick_kw.get('tick1On') + xlabelbottom = ax.xaxis._major_tick_kw.get("tick1On") + ylabelleft = ax.yaxis._major_tick_kw.get("tick1On") - if xlabelbottom and event.x > self.x + ax.bbox.bounds[0] and \ - event.x < self.x + ax.bbox.bounds[2] + ax.bbox.bounds[0] and \ - event.y < self.y + ax.bbox.bounds[1]: + if ( + xlabelbottom + and event.x > self.x + ax.bbox.bounds[0] + and event.x < self.x + ax.bbox.bounds[2] + ax.bbox.bounds[0] + and event.y < self.y + ax.bbox.bounds[1] + ): right_lim = self.x + ax.bbox.bounds[2] + ax.bbox.bounds[0] left_lim = self.x + ax.bbox.bounds[0] - left_anchor_zone = (right_lim - left_lim) * .2 + left_lim - right_anchor_zone = (right_lim - left_lim) * .8 + left_lim + left_anchor_zone = (right_lim - left_lim) * 0.2 + left_lim + right_anchor_zone = (right_lim - left_lim) * 0.8 + left_lim if event.x < left_anchor_zone or event.x > right_anchor_zone: if self.text_instance: if not self.text_instance.show_text: midpoint = ( - (self.x + ax.bbox.bounds[2] + ax.bbox.bounds[0]) + - (self.x + ax.bbox.bounds[0])) / 2 + (self.x + ax.bbox.bounds[2] + ax.bbox.bounds[0]) + + (self.x + ax.bbox.bounds[0]) + ) / 2 if event.x < midpoint: - anchor = 'left' + anchor = "left" self.text_instance.x_text_pos = float( - self.x + ax.bbox.bounds[0]) - self.text_instance.y_text_pos = float( - self.y + ax.bbox.bounds[1]) - self.text_instance.text_height + self.x + ax.bbox.bounds[0] + ) + self.text_instance.y_text_pos = ( + float(self.y + ax.bbox.bounds[1]) + - self.text_instance.text_height + ) self.text_instance.offset_text = False else: - anchor = 'right' + anchor = "right" self.text_instance.x_text_pos = float( - self.x + ax.bbox.bounds[2] + ax.bbox.bounds[0]) - self.text_instance.y_text_pos = float( - self.y + ax.bbox.bounds[1]) - self.text_instance.text_height + self.x + ax.bbox.bounds[2] + ax.bbox.bounds[0] + ) + self.text_instance.y_text_pos = ( + float(self.y + ax.bbox.bounds[1]) + - self.text_instance.text_height + ) self.text_instance.offset_text = True self.text_instance.current_axis = ax self.text_instance.kind = { - 'axis': 'x', 'anchor': anchor} + "axis": "x", + "anchor": anchor, + } self.text_instance.show_text = True return - elif ylabelleft and event.x < self.x + ax.bbox.bounds[0] and \ - event.y < self.y + ax.bbox.bounds[1] + ax.bbox.bounds[3] and \ - event.y > self.y + ax.bbox.bounds[1]: + elif ( + ylabelleft + and event.x < self.x + ax.bbox.bounds[0] + and event.y < self.y + ax.bbox.bounds[1] + ax.bbox.bounds[3] + and event.y > self.y + ax.bbox.bounds[1] + ): top_lim = self.y + ax.bbox.bounds[3] + ax.bbox.bounds[1] bottom_lim = self.y + ax.bbox.bounds[1] - bottom_anchor_zone = (top_lim - bottom_lim) * .2 + bottom_lim - top_anchor_zone = (top_lim - bottom_lim) * .8 + bottom_lim + bottom_anchor_zone = (top_lim - bottom_lim) * 0.2 + bottom_lim + top_anchor_zone = (top_lim - bottom_lim) * 0.8 + bottom_lim if event.y < bottom_anchor_zone or event.y > top_anchor_zone: if self.text_instance: if not self.text_instance.show_text: midpoint = ( - (self.y + ax.bbox.bounds[3] + ax.bbox.bounds[1]) + - (self.y + ax.bbox.bounds[1])) / 2 + (self.y + ax.bbox.bounds[3] + ax.bbox.bounds[1]) + + (self.y + ax.bbox.bounds[1]) + ) / 2 if event.y > midpoint: - anchor = 'top' + anchor = "top" self.text_instance.x_text_pos = float( - self.x + ax.bbox.bounds[0]) - dp(40) - self.text_instance.y_text_pos = float( - self.y + ax.bbox.bounds[1] + ax.bbox.bounds[3]) - self.text_instance.text_height + self.x + ax.bbox.bounds[0] + ) - dp(40) + self.text_instance.y_text_pos = ( + float( + self.y + + ax.bbox.bounds[1] + + ax.bbox.bounds[3] + ) + - self.text_instance.text_height + ) self.text_instance.offset_text = False else: - anchor = 'bottom' + anchor = "bottom" self.text_instance.x_text_pos = float( - self.x + ax.bbox.bounds[0]) - dp(40) + self.x + ax.bbox.bounds[0] + ) - dp(40) self.text_instance.y_text_pos = float( - self.y + ax.bbox.bounds[1]) - dp(6) + self.y + ax.bbox.bounds[1] + ) - dp(6) self.text_instance.offset_text = False self.text_instance.current_axis = ax self.text_instance.kind = { - 'axis': 'y', 'anchor': anchor} + "axis": "y", + "anchor": anchor, + } self.text_instance.show_text = True return def apply_drag_legend(self, ax, event): - """ drag legend method """ + """drag legend method""" dx = event.x - self._last_touch_pos[event][0] if not self.legend_do_scroll_x: @@ -1969,7 +2298,11 @@ def apply_drag_legend(self, ax, event): legend_y = bbox.ymin loc_in_canvas = legend_x + dx, legend_y + dy - loc_in_norm_axes = legend.parent.transAxes.inverted().transform_point(loc_in_canvas) + loc_in_norm_axes = ( + legend.parent.transAxes.inverted().transform_point( + loc_in_canvas + ) + ) legend._loc = tuple(loc_in_norm_axes) # use blit method @@ -1978,7 +2311,8 @@ def apply_drag_legend(self, ax, event): ax.figure.canvas.draw_idle() ax.figure.canvas.flush_events() self.background = ax.figure.canvas.copy_from_bbox( - ax.figure.bbox) + ax.figure.bbox + ) legend.set_visible(True) ax.figure.canvas.restore_region(self.background) @@ -1990,7 +2324,7 @@ def apply_drag_legend(self, ax, event): self.current_legend.update_size() def zoom_factory(self, event, ax, base_scale=1.1): - """ zoom with scrolling mouse method """ + """zoom with scrolling mouse method""" newcoord = self.to_widget(event.x, event.y, relative=True) x = newcoord[0] @@ -2005,7 +2339,7 @@ def zoom_factory(self, event, ax, base_scale=1.1): scale = ax.get_xscale() yscale = ax.get_yscale() - if scale == 'linear': + if scale == "linear": old_min = cur_xlim[0] old_max = cur_xlim[1] @@ -2016,7 +2350,7 @@ def zoom_factory(self, event, ax, base_scale=1.1): xdata = self.transform_eval(xdata, ax.yaxis) old_max = self.transform_eval(max_, ax.yaxis) - if yscale == 'linear': + if yscale == "linear": yold_min = cur_ylim[0] yold_max = cur_ylim[1] @@ -2027,10 +2361,10 @@ def zoom_factory(self, event, ax, base_scale=1.1): ydata = self.transform_eval(ydata, ax.yaxis) yold_max = self.transform_eval(ymax_, ax.yaxis) - if event.button == 'scrolldown': + if event.button == "scrolldown": # deal with zoom in scale_factor = 1 / base_scale - elif event.button == 'scrollup': + elif event.button == "scrollup": # deal with zoom out scale_factor = base_scale else: @@ -2045,36 +2379,44 @@ def zoom_factory(self, event, ax, base_scale=1.1): rely = (yold_max - ydata) / (yold_max - yold_min) if self.do_zoom_x: - if scale == 'linear': - ax.set_xlim([xdata - new_width * (1 - relx), - xdata + new_width * (relx)]) + if scale == "linear": + ax.set_xlim( + [ + xdata - new_width * (1 - relx), + xdata + new_width * (relx), + ] + ) else: new_min = xdata - new_width * (1 - relx) new_max = xdata + new_width * (relx) try: new_min, new_max = self.inv_transform_eval( - new_min, ax.yaxis), self.inv_transform_eval( - new_max, ax.yaxis) + new_min, ax.yaxis + ), self.inv_transform_eval(new_max, ax.yaxis) except OverflowError: # Limit case new_min, new_max = min_, max_ - if new_min <= 0. or new_max <= 0.: # Limit case + if new_min <= 0.0 or new_max <= 0.0: # Limit case new_min, new_max = min_, max_ ax.set_xlim([new_min, new_max]) if self.do_zoom_y: - if yscale == 'linear': - ax.set_ylim([ydata - new_height * (1 - rely), - ydata + new_height * (rely)]) + if yscale == "linear": + ax.set_ylim( + [ + ydata - new_height * (1 - rely), + ydata + new_height * (rely), + ] + ) else: new_ymin = ydata - new_height * (1 - rely) new_ymax = ydata + new_height * (rely) try: new_ymin, new_ymax = self.inv_transform_eval( - new_ymin, ax.yaxis), self.inv_transform_eval( - new_ymax, ax.yaxis) + new_ymin, ax.yaxis + ), self.inv_transform_eval(new_ymax, ax.yaxis) except OverflowError: # Limit case new_ymin, new_ymax = ymin_, ymax_ - if new_ymin <= 0. or new_ymax <= 0.: # Limit case + if new_ymin <= 0.0 or new_ymax <= 0.0: # Limit case new_ymin, new_ymax = ymin_, ymax_ ax.set_ylim([new_ymin, new_ymax]) @@ -2082,7 +2424,7 @@ def zoom_factory(self, event, ax, base_scale=1.1): ax.figure.canvas.flush_events() def _onSize(self, o, size): - """ _onsize method """ + """_onsize method""" if self.figure is None: return # Create a new, correctly sized bitmap @@ -2097,7 +2439,7 @@ def _onSize(self, o, size): hinch = self._height / dpival self.figure.set_size_inches(winch, hinch) - s = 'resize_event' + s = "resize_event" event = ResizeEvent(s, self.figcanvas) self.figcanvas.callbacks.process(s, event) self.figcanvas.draw_idle() @@ -2116,7 +2458,7 @@ def _onSize(self, o, size): Clock.schedule_once(self.update_selector) def update_lim(self): - """ update axis lim if zoombox is used""" + """update axis lim if zoombox is used""" ax = self.axes self.do_update = False @@ -2127,34 +2469,41 @@ def update_lim(self): if xright > xleft: ax.set_xlim( - left=min( - self.x0_box, self.x1_box), right=max( - self.x0_box, self.x1_box)) + left=min(self.x0_box, self.x1_box), + right=max(self.x0_box, self.x1_box), + ) else: ax.set_xlim( - right=min( - self.x0_box, self.x1_box), left=max( - self.x0_box, self.x1_box)) + right=min(self.x0_box, self.x1_box), + left=max(self.x0_box, self.x1_box), + ) if ytop > ybottom: ax.set_ylim( - bottom=min( - self.y0_box, self.y1_box), top=max( - self.y0_box, self.y1_box)) + bottom=min(self.y0_box, self.y1_box), + top=max(self.y0_box, self.y1_box), + ) else: ax.set_ylim( - top=min( - self.y0_box, self.y1_box), bottom=max( - self.y0_box, self.y1_box)) + top=min(self.y0_box, self.y1_box), + bottom=max(self.y0_box, self.y1_box), + ) def reset_box(self): - """ reset zoombox and apply zoombox limit if zoombox option if selected""" + """reset zoombox and apply zoombox limit if zoombox option if selected""" if min(abs(self._box_size[0]), abs(self._box_size[1])) > self.minzoom: trans = self.axes.transData.inverted() self.x0_box, self.y0_box = trans.transform_point( - (self._box_pos[0] - self.pos[0], self._box_pos[1] - self.pos[1])) + ( + self._box_pos[0] - self.pos[0], + self._box_pos[1] - self.pos[1], + ) + ) self.x1_box, self.y1_box = trans.transform_point( - (self._box_size[0] + self._box_pos[0] - self.pos[0], - self._box_size[1] + self._box_pos[1] - self.pos[1])) + ( + self._box_size[0] + self._box_pos[0] - self.pos[0], + self._box_size[1] + self._box_pos[1] - self.pos[1], + ) + ) self.do_update = True self._box_size = 0, 0 @@ -2169,7 +2518,7 @@ def reset_box(self): self._alpha_ver = 0 def draw_box(self, event, x0, y0, x1, y1) -> None: - """ Draw zoombox method + """Draw zoombox method Args: event: touch kivy event @@ -2192,7 +2541,8 @@ def draw_box(self, event, x0, y0, x1, y1) -> None: trans = self.axes.transData.inverted() xdata, ydata = trans.transform_point( - (event.x - pos_x, event.y - pos_y)) + (event.x - pos_x, event.y - pos_y) + ) xleft, xright = self.axes.get_xlim() ybottom, ytop = self.axes.get_ylim() @@ -2324,9 +2674,10 @@ class FakeEventScatter: y: None -Factory.register('MatplotFigureScatter', MatplotFigureScatter) +Factory.register("MatplotFigureScatter", MatplotFigureScatter) -Builder.load_string(''' +Builder.load_string( + """ canvas: Color: @@ -2384,4 +2735,5 @@ class FakeEventScatter: (self.pos_x_rect_ver-dp(20),self.pos_y_rect_ver-dp(4)+self._box_size[1] \ if root.invert_rect_hor else self.pos_y_rect_ver+dp(1)+self._box_size[1], \ dp(40),dp(4)) - ''') + """ +) diff --git a/kivy_matplotlib_widget/uix/graph_widget_twinx.py b/kivy_matplotlib_widget/uix/graph_widget_twinx.py index 6e07e72..778551d 100644 --- a/kivy_matplotlib_widget/uix/graph_widget_twinx.py +++ b/kivy_matplotlib_widget/uix/graph_widget_twinx.py @@ -1,4 +1,4 @@ -""" MatplotFigure is based on https://github.com/jeysonmc/kivy_matplotlib +"""MatplotFigure is based on https://github.com/jeysonmc/kivy_matplotlib and kivy scatter """ @@ -16,8 +16,16 @@ from matplotlib.backends.backend_agg import FigureCanvasAgg from kivy.vector import Vector from kivy.uix.widget import Widget -from kivy.properties import ObjectProperty, ListProperty, BooleanProperty, BoundedNumericProperty, AliasProperty, \ - NumericProperty, OptionProperty, DictProperty +from kivy.properties import ( + ObjectProperty, + ListProperty, + BooleanProperty, + BoundedNumericProperty, + AliasProperty, + NumericProperty, + OptionProperty, + DictProperty, +) from kivy.lang import Builder from kivy.graphics.transformation import Matrix from kivy.graphics.texture import Texture @@ -25,13 +33,19 @@ import copy import matplotlib -matplotlib.use('Agg') + +matplotlib.use("Agg") selector_widgets_available = False try: selector_widgets_available = True - from kivy_matplotlib_widget.uix.selector_widget import ResizeRelativeLayout, LassoRelativeLayout, EllipseRelativeLayout, SpanRelativeLayout + from kivy_matplotlib_widget.uix.selector_widget import ( + ResizeRelativeLayout, + LassoRelativeLayout, + EllipseRelativeLayout, + SpanRelativeLayout, + ) except ImportError: - print('Selector widgets are not available') + print("Selector widgets are not available") class MatplotlibEvent: @@ -41,7 +55,7 @@ class MatplotlibEvent: inaxes = None projection = False compare_xdata = False - pick_radius_axis = 'both' + pick_radius_axis = "both" class MatplotFigureTwinx(Widget): @@ -101,13 +115,8 @@ class MatplotFigureTwinx(Widget): desktop_mode = BooleanProperty(True) current_selector = OptionProperty( "None", - options=[ - "None", - 'rectangle', - 'lasso', - 'ellipse', - 'span', - 'custom']) + options=["None", "rectangle", "lasso", "ellipse", "span", "custom"], + ) highlight_hover = BooleanProperty(False) highlight_prop = DictProperty({}) highlight_alpha = NumericProperty(0.2) @@ -130,7 +139,7 @@ def on_figure(self, obj, value): self.axes = ax patch_cpy = copy.copy(ax.patch) patch_cpy.set_visible(False) - for pos in ['right', 'top', 'bottom', 'left']: + for pos in ["right", "top", "bottom", "left"]: ax.spines[pos].set_zorder(10) patch_cpy.set_zorder(9) self.background_patch_copy = ax.add_patch(patch_cpy) @@ -144,7 +153,7 @@ def on_figure(self, obj, value): ax2 = self.figure.axes[1] patch_cpy_ax2 = copy.copy(ax2.patch) patch_cpy_ax2.set_visible(False) - for pos in ['right', 'top', 'bottom', 'left']: + for pos in ["right", "top", "bottom", "left"]: ax2.spines[pos].set_zorder(10) patch_cpy_ax2.set_zorder(9) self.background_ax2_patch_copy = ax2.add_patch(patch_cpy_ax2) @@ -164,9 +173,8 @@ def on_figure(self, obj, value): if self.auto_cursor: if len(self.figure.axes) == 2: self.register_lines( - list( - self.figure.axes[0].lines + - self.figure.axes[1].lines)) + list(self.figure.axes[0].lines + self.figure.axes[1].lines) + ) elif len(self.figure.axes) > 0: self.register_lines(list(self.figure.axes[0].lines)) @@ -194,7 +202,7 @@ def __init__(self, **kwargs): self.lines = [] # option - self.touch_mode = 'pan' + self.touch_mode = "pan" self.hover_on = False # used matplotlib formatter to display x cursor value self.cursor_xaxis_formatter = None @@ -247,7 +255,7 @@ def __init__(self, **kwargs): self.show_compare_cursor = False # manage back and next event - if hasattr(cbook, '_Stack'): + if hasattr(cbook, "_Stack"): # manage matplotlib version with no Stack (replace by _Stack) self._nav_stack = cbook._Stack() else: @@ -271,13 +279,13 @@ def __init__(self, **kwargs): def on_kv_post(self, _): # if not self.selector: if self.current_selector != "None" and selector_widgets_available: - if self.current_selector == 'rectangle': + if self.current_selector == "rectangle": self.set_selector(ResizeRelativeLayout) - elif self.current_selector == 'lasso': + elif self.current_selector == "lasso": self.set_selector(LassoRelativeLayout) - elif self.current_selector == 'ellipse': + elif self.current_selector == "ellipse": self.set_selector(EllipseRelativeLayout) - elif self.current_selector == 'span': + elif self.current_selector == "span": self.set_selector(SpanRelativeLayout) self.kv_post_done = True @@ -293,18 +301,19 @@ def on_current_selector(self, instance, value, *args): if self.kv_post_done and selector_widgets_available: - if value == 'rectangle': + if value == "rectangle": self.set_selector(ResizeRelativeLayout) - elif value == 'lasso': + elif value == "lasso": self.set_selector(LassoRelativeLayout) - elif value == 'ellipse': + elif value == "ellipse": self.set_selector(EllipseRelativeLayout) - elif self.current_selector == 'span': + elif self.current_selector == "span": self.set_selector(SpanRelativeLayout) elif value == "None": if self.selector: Window.unbind( - mouse_pos=self.selector.resize_wgt.on_mouse_pos) + mouse_pos=self.selector.resize_wgt.on_mouse_pos + ) self.parent.remove_widget(self.selector) self.selector = None @@ -322,8 +331,8 @@ def set_selector(self, selector, *args): self.parent.remove_widget(self.selector) self.selector = selector( - figure_wgt=self, - desktop_mode=self.desktop_mode) + figure_wgt=self, desktop_mode=self.desktop_mode + ) self.selector.resize_wgt.ax = self.axes if selector_collection: self.set_collection() @@ -354,7 +363,7 @@ def set_callback_clear(self, callback): self.selector.resize_wgt.set_callback_clear(callback) def register_lines(self, lines: list) -> None: - """ register lines method + """register lines method Args: lines (list): list of matplolib line class @@ -369,20 +378,20 @@ def register_lines(self, lines: list) -> None: # create cross hair cursor self.horizontal_line = ax.axhline( - y=self.ymin, color='k', lw=0.8, ls='--', visible=False) + y=self.ymin, color="k", lw=0.8, ls="--", visible=False + ) self.vertical_line = ax.axvline( - x=self.xmin, color='k', lw=0.8, ls='--', visible=False) + x=self.xmin, color="k", lw=0.8, ls="--", visible=False + ) # register lines self.lines = lines # cursor text - self.text = ax.text(1.0, 1.01, '', - transform=ax.transAxes, - ha='right') + self.text = ax.text(1.0, 1.01, "", transform=ax.transAxes, ha="right") def set_cross_hair_visible(self, visible: bool) -> None: - """ set curcor visibility + """set curcor visibility Args: visible (bool): make cursor visble or not @@ -404,25 +413,37 @@ def update_cursor(self): if self.hover_instance: - if self.hover_instance.show_cursor and self.x_hover_data is not None and self.y_hover_data is not None: + if ( + self.hover_instance.show_cursor + and self.x_hover_data is not None + and self.y_hover_data is not None + ): self.y_hover_data = self.cursor_last_y xy_pos = self.figure.axes[1].transData.transform( - [(self.x_hover_data, self.y_hover_data)]) - self.hover_instance.y_hover_pos = float( - xy_pos[0][1]) + self.y + [(self.x_hover_data, self.y_hover_data)] + ) + self.hover_instance.y_hover_pos = ( + float(xy_pos[0][1]) + self.y + ) else: new_y = self.cursor_last_y x = self.vertical_line.get_xdata() trans = self.figure.axes[0].transData.inverted() xy_pos = self.figure.axes[1].transData.transform( - [(x, new_y)]) + [(x, new_y)] + ) xdata, ydata = trans.transform_point( - (xy_pos[0][0], xy_pos[0][1])) - self.horizontal_line.set_ydata([ydata,]) + (xy_pos[0][0], xy_pos[0][1]) + ) + self.horizontal_line.set_ydata( + [ + ydata, + ] + ) def clear_line_prop(self) -> None: - """ clear attribute line_prop method + """clear attribute line_prop method Args: None @@ -433,13 +454,13 @@ def clear_line_prop(self) -> None: """ if self.last_line_prop: for key in self.last_line_prop: - set_line_attr = getattr(self.last_line, 'set_' + key) + set_line_attr = getattr(self.last_line, "set_" + key) set_line_attr(self.last_line_prop[key]) self.last_line_prop = {} self.last_line = None def hover(self, event) -> None: - """ hover cursor method (cursor to nearest value) + """hover cursor method (cursor to nearest value) Args: event: touch kivy event @@ -455,7 +476,8 @@ def hover(self, event) -> None: # transform kivy x,y touch event to x,y data trans = self.figure.axes[0].transData.inverted() xdata, ydata = trans.transform_point( - (event.x - self.pos[0], event.y - self.pos[1])) + (event.x - self.pos[0], event.y - self.pos[1]) + ) # loop all register lines and find closest x,y data for each valid # line @@ -475,9 +497,9 @@ def hover(self, event) -> None: # find closest data index from touch (x axis) if self.xsorted: index = min( - np.searchsorted( - self.x_cursor, xdata), len( - self.y_cursor) - 1) + np.searchsorted(self.x_cursor, xdata), + len(self.y_cursor) - 1, + ) else: index = np.argsort(abs(self.x_cursor - xdata))[0] @@ -494,27 +516,40 @@ def hover(self, event) -> None: if self.twinx: if ax == self.figure.axes[1]: # right axis - trans = self.figure.axes[1].transData.inverted( - ) + trans = self.figure.axes[ + 1 + ].transData.inverted() xdata2, ydata2 = trans.transform_point( - (event.x - self.pos[0], event.y - self.pos[1])) + ( + event.x - self.pos[0], + event.y - self.pos[1], + ) + ) xy_pixels_mouse = ax.transData.transform( - np.vstack([xdata2, ydata2]).T) + np.vstack([xdata2, ydata2]).T + ) else: # left axis xy_pixels_mouse = ax.transData.transform( - [(xdata, ydata)]) + [(xdata, ydata)] + ) else: # left axis xy_pixels_mouse = ax.transData.transform( - [(xdata, ydata)]) - if np.ma.is_masked(x) or np.ma.is_masked( - y) or np.isnan(x) or np.isnan(y): + [(xdata, ydata)] + ) + if ( + np.ma.is_masked(x) + or np.ma.is_masked(y) + or np.isnan(x) + or np.isnan(y) + ): distance.append(np.nan) else: xy_pixels = ax.transData.transform( - [(x, ydata)]) - dx2 = (xy_pixels_mouse[0][0] - xy_pixels[0][0]) + [(x, ydata)] + ) + dx2 = xy_pixels_mouse[0][0] - xy_pixels[0][0] distance.append(abs(dx2)) else: # find ydata corresponding to xdata @@ -525,35 +560,45 @@ def hover(self, event) -> None: if self.twinx: if ax == self.figure.axes[1]: # right axis - trans = self.figure.axes[1].transData.inverted( - ) + trans = self.figure.axes[ + 1 + ].transData.inverted() xdata2, ydata2 = trans.transform_point( - (event.x - self.pos[0], event.y - self.pos[1])) + ( + event.x - self.pos[0], + event.y - self.pos[1], + ) + ) xy_pixels_mouse = ax.transData.transform( - np.vstack([xdata2, ydata2]).T) + np.vstack([xdata2, ydata2]).T + ) else: # left axis xy_pixels_mouse = ax.transData.transform( - [(xdata, ydata)]) + [(xdata, ydata)] + ) else: # left axis xy_pixels_mouse = ax.transData.transform( - [(xdata, ydata)]) + [(xdata, ydata)] + ) if np.ma.is_masked(x) or np.ma.is_masked(y): distance.append(np.nan) else: xy_pixels = ax.transData.transform([(x, y)]) dx2 = ( - xy_pixels_mouse[0][0] - xy_pixels[0][0])**2 + xy_pixels_mouse[0][0] - xy_pixels[0][0] + ) ** 2 dy2 = ( - xy_pixels_mouse[0][1] - xy_pixels[0][1])**2 + xy_pixels_mouse[0][1] - xy_pixels[0][1] + ) ** 2 # store distance - if self.pick_radius_axis == 'both': - distance.append((dx2 + dy2)**0.5) - if self.pick_radius_axis == 'x': + if self.pick_radius_axis == "both": + distance.append((dx2 + dy2) ** 0.5) + if self.pick_radius_axis == "x": distance.append(abs(dx2)) - if self.pick_radius_axis == 'y': + if self.pick_radius_axis == "y": distance.append(abs(dy2)) # store all best lines and index @@ -570,11 +615,13 @@ def hover(self, event) -> None: # index of minimum distance if self.compare_xdata: if not self.hover_instance or not hasattr( - self.hover_instance, 'children_list'): + self.hover_instance, "children_list" + ): return idx_best_list = np.flatnonzero( - np.array(distance) == np.nanmin(distance)) + np.array(distance) == np.nanmin(distance) + ) # get datas from closest line line = good_line[idx_best_list[0]] self.x_cursor, self.y_cursor = line.get_xydata().T @@ -585,12 +632,15 @@ def hover(self, event) -> None: xy_pos = ax.transData.transform([(x, y)]) self.x_hover_data = x self.y_hover_data = y - self.hover_instance.x_hover_pos = float( - xy_pos[0][0]) + self.x - self.hover_instance.y_hover_pos = float( - xy_pos[0][1]) + self.y - self.hover_instance.y_touch_pos = float( - xy_pixels[0][1]) + self.y + self.hover_instance.x_hover_pos = ( + float(xy_pos[0][0]) + self.x + ) + self.hover_instance.y_hover_pos = ( + float(xy_pos[0][1]) + self.y + ) + self.hover_instance.y_touch_pos = ( + float(xy_pixels[0][1]) + self.y + ) if self.first_call_compare_hover: self.hover_instance.show_cursor = True @@ -607,9 +657,13 @@ def hover(self, event) -> None: else: line = good_line[idx_best_list[i]] line_label = line.get_label() - if line_label in self.hover_instance.children_names: + if ( + line_label + in self.hover_instance.children_names + ): index = self.hover_instance.children_names.index( - line_label) + line_label + ) y_cursor = line.get_ydata() y = y_cursor[good_index[idx_best_list[i]]] ax = line.axes @@ -617,36 +671,59 @@ def hover(self, event) -> None: xy_pos = ax.transData.transform([(x, y)]) pos_y = float(xy_pos[0][1]) + self.y - if pos_y < self.y + ax.bbox.bounds[1] + ax.bbox.bounds[3] and \ - pos_y > self.y + ax.bbox.bounds[1]: - - available_widget[index].x_hover_pos = float( - xy_pos[0][0]) + self.x - available_widget[index].y_hover_pos = float( - xy_pos[0][1]) + self.y - available_widget[index].custom_color = get_color_from_hex( - to_hex(line.get_color())) + if ( + pos_y + < self.y + + ax.bbox.bounds[1] + + ax.bbox.bounds[3] + and pos_y > self.y + ax.bbox.bounds[1] + ): + + available_widget[index].x_hover_pos = ( + float(xy_pos[0][0]) + self.x + ) + available_widget[index].y_hover_pos = ( + float(xy_pos[0][1]) + self.y + ) + available_widget[ + index + ].custom_color = get_color_from_hex( + to_hex(line.get_color()) + ) if self.twinx: if ax == self.figure.axes[1]: - if self.cursor_yaxis2_formatter: + if ( + self.cursor_yaxis2_formatter + ): y = self.cursor_yaxis2_formatter.format_data( - y) + y + ) else: - y = ax.yaxis.get_major_formatter().format_data_short(y) + y = ax.yaxis.get_major_formatter().format_data_short( + y + ) else: if self.cursor_yaxis_formatter: y = self.cursor_yaxis_formatter.format_data( - y) + y + ) else: if self.cursor_yaxis_formatter: y = self.cursor_yaxis_formatter.format_data( - y) + y + ) else: - y = ax.yaxis.get_major_formatter().format_data_short(y) - available_widget[index].label_y_value = f"{y}" - available_widget[index].show_widget = True + y = ax.yaxis.get_major_formatter().format_data_short( + y + ) + available_widget[ + index + ].label_y_value = f"{y}" + available_widget[index].show_widget = ( + True + ) index_list.remove(index) for ii in index_list: @@ -655,24 +732,37 @@ def hover(self, event) -> None: if self.cursor_xaxis_formatter: x = self.cursor_xaxis_formatter.format_data(x) else: - x = ax.xaxis.get_major_formatter().format_data_short(x) + x = ax.xaxis.get_major_formatter().format_data_short( + x + ) self.hover_instance.label_x_value = f"{x}" - if hasattr(self.hover_instance, 'overlap_check'): + if hasattr(self.hover_instance, "overlap_check"): self.hover_instance.overlap_check() - self.hover_instance.xmin_line = float( - ax.bbox.bounds[0]) + self.x - self.hover_instance.xmax_line = float( - ax.bbox.bounds[0] + ax.bbox.bounds[2]) + self.x - self.hover_instance.ymin_line = float( - ax.bbox.bounds[1]) + self.y - self.hover_instance.ymax_line = float( - ax.bbox.bounds[1] + ax.bbox.bounds[3]) + self.y - - if self.hover_instance.x_hover_pos > self.x + ax.bbox.bounds[2] + ax.bbox.bounds[0] or \ - self.hover_instance.x_hover_pos < self.x + ax.bbox.bounds[0] or len(index_list) == nb_widget: + self.hover_instance.xmin_line = ( + float(ax.bbox.bounds[0]) + self.x + ) + self.hover_instance.xmax_line = ( + float(ax.bbox.bounds[0] + ax.bbox.bounds[2]) + + self.x + ) + self.hover_instance.ymin_line = ( + float(ax.bbox.bounds[1]) + self.y + ) + self.hover_instance.ymax_line = ( + float(ax.bbox.bounds[1] + ax.bbox.bounds[3]) + + self.y + ) + + if ( + self.hover_instance.x_hover_pos + > self.x + ax.bbox.bounds[2] + ax.bbox.bounds[0] + or self.hover_instance.x_hover_pos + < self.x + ax.bbox.bounds[0] + or len(index_list) == nb_widget + ): self.hover_instance.hover_outside_bound = True else: self.hover_instance.hover_outside_bound = False @@ -699,29 +789,61 @@ def hover(self, event) -> None: cur_ylim = self.figure.axes[0].get_ylim() cur_ylim2 = self.figure.axes[1].get_ylim() - ratio = (cur_ylim2[1] - cur_ylim2[0] - ) / (cur_ylim[1] - cur_ylim[0]) + ratio = (cur_ylim2[1] - cur_ylim2[0]) / ( + cur_ylim[1] - cur_ylim[0] + ) new_y = (y - cur_ylim2[0]) / ratio + cur_ylim[0] - self.horizontal_line.set_ydata([new_y,]) + self.horizontal_line.set_ydata( + [ + new_y, + ] + ) self.cursor_last_y = new_y - if self.cursor_yaxis2_formatter and not self.hover_instance: + if ( + self.cursor_yaxis2_formatter + and not self.hover_instance + ): y = self.cursor_yaxis2_formatter.format_data(y) elif not self.hover_instance: - y = ax.yaxis.get_major_formatter().format_data_short(y) + y = ax.yaxis.get_major_formatter().format_data_short( + y + ) else: - self.horizontal_line.set_ydata([y,]) - if self.cursor_yaxis_formatter and not self.hover_instance: + self.horizontal_line.set_ydata( + [ + y, + ] + ) + if ( + self.cursor_yaxis_formatter + and not self.hover_instance + ): y = self.cursor_yaxis_formatter.format_data(y) elif not self.hover_instance: - y = ax.yaxis.get_major_formatter().format_data_short(y) + y = ax.yaxis.get_major_formatter().format_data_short( + y + ) else: - self.horizontal_line.set_ydata([y,]) - if self.cursor_yaxis_formatter and not self.hover_instance: + self.horizontal_line.set_ydata( + [ + y, + ] + ) + if ( + self.cursor_yaxis_formatter + and not self.hover_instance + ): y = self.cursor_yaxis_formatter.format_data(y) elif not self.hover_instance: - y = ax.yaxis.get_major_formatter().format_data_short(y) - self.vertical_line.set_xdata([x,]) + y = ax.yaxis.get_major_formatter().format_data_short( + y + ) + self.vertical_line.set_xdata( + [ + x, + ] + ) if self.cursor_xaxis_formatter and not self.hover_instance: x = self.cursor_xaxis_formatter.format_data(x) elif not self.hover_instance: @@ -732,57 +854,86 @@ def hover(self, event) -> None: xy_pos = ax.transData.transform([(x, y)]) self.x_hover_data = x self.y_hover_data = y - self.hover_instance.x_hover_pos = float( - xy_pos[0][0]) + self.x - self.hover_instance.y_hover_pos = float( - xy_pos[0][1]) + self.y + self.hover_instance.x_hover_pos = ( + float(xy_pos[0][0]) + self.x + ) + self.hover_instance.y_hover_pos = ( + float(xy_pos[0][1]) + self.y + ) self.hover_instance.show_cursor = True if self.twinx: if ax == self.figure.axes[1]: if self.cursor_yaxis2_formatter: y = self.cursor_yaxis2_formatter.format_data( - y) + y + ) else: - y = ax.yaxis.get_major_formatter().format_data_short(y) + y = ax.yaxis.get_major_formatter().format_data_short( + y + ) else: if self.cursor_yaxis_formatter: y = self.cursor_yaxis_formatter.format_data( - y) + y + ) else: - y = ax.yaxis.get_major_formatter().format_data_short(y) + y = ax.yaxis.get_major_formatter().format_data_short( + y + ) else: if self.cursor_yaxis_formatter: y = self.cursor_yaxis_formatter.format_data(y) else: - y = ax.yaxis.get_major_formatter().format_data_short(y) + y = ax.yaxis.get_major_formatter().format_data_short( + y + ) if self.cursor_xaxis_formatter: x = self.cursor_xaxis_formatter.format_data(x) else: - x = ax.xaxis.get_major_formatter().format_data_short(x) + x = ax.xaxis.get_major_formatter().format_data_short( + x + ) self.hover_instance.label_x_value = f"{x}" self.hover_instance.label_y_value = f"{y}" - self.hover_instance.xmin_line = float( - ax.bbox.bounds[0]) + self.x - self.hover_instance.xmax_line = float( - ax.bbox.bounds[0] + ax.bbox.bounds[2]) + self.x - self.hover_instance.ymin_line = float( - ax.bbox.bounds[1]) + self.y - self.hover_instance.ymax_line = float( - ax.bbox.bounds[1] + ax.bbox.bounds[3]) + self.y + self.hover_instance.xmin_line = ( + float(ax.bbox.bounds[0]) + self.x + ) + self.hover_instance.xmax_line = ( + float(ax.bbox.bounds[0] + ax.bbox.bounds[2]) + + self.x + ) + self.hover_instance.ymin_line = ( + float(ax.bbox.bounds[1]) + self.y + ) + self.hover_instance.ymax_line = ( + float(ax.bbox.bounds[1] + ax.bbox.bounds[3]) + + self.y + ) self.hover_instance.custom_label = line.get_label() self.hover_instance.custom_color = get_color_from_hex( - to_hex(line.get_color())) - - if self.hover_instance.x_hover_pos > self.x + self.figure.axes[0].bbox.bounds[2] + self.figure.axes[0].bbox.bounds[0] or \ - self.hover_instance.x_hover_pos < self.x + self.figure.axes[0].bbox.bounds[0] or \ - self.hover_instance.y_hover_pos > self.y + self.figure.axes[0].bbox.bounds[1] + self.figure.axes[0].bbox.bounds[3] or \ - self.hover_instance.y_hover_pos < self.y + self.figure.axes[0].bbox.bounds[1]: + to_hex(line.get_color()) + ) + + if ( + self.hover_instance.x_hover_pos + > self.x + + self.figure.axes[0].bbox.bounds[2] + + self.figure.axes[0].bbox.bounds[0] + or self.hover_instance.x_hover_pos + < self.x + self.figure.axes[0].bbox.bounds[0] + or self.hover_instance.y_hover_pos + > self.y + + self.figure.axes[0].bbox.bounds[1] + + self.figure.axes[0].bbox.bounds[3] + or self.hover_instance.y_hover_pos + < self.y + self.figure.axes[0].bbox.bounds[1] + ): self.hover_instance.hover_outside_bound = True else: self.hover_instance.hover_outside_bound = False @@ -791,12 +942,14 @@ def hover(self, event) -> None: self.myevent.x = event.x - self.pos[0] self.myevent.y = event.y - self.pos[1] self.myevent.inaxes = self.figure.canvas.inaxes( - (event.x - self.pos[0], event.y - self.pos[1])) + (event.x - self.pos[0], event.y - self.pos[1]) + ) - axes = [a - for a in - self.figure.canvas.figure.get_axes() - if a.in_axes(self.myevent)] + axes = [ + a + for a in self.figure.canvas.figure.get_axes() + if a.in_axes(self.myevent) + ] if not axes or not isinstance(line, mlines.Line2D): @@ -804,7 +957,8 @@ def hover(self, event) -> None: self.clear_line_prop() if self.background: ax.figure.canvas.restore_region( - self.background) + self.background + ) # draw (blit method) ax.figure.canvas.blit(ax.bbox) ax.figure.canvas.flush_events() @@ -815,21 +969,31 @@ def hover(self, event) -> None: # blit method (always use because same visual # effect as draw) if self.background is None: - self.background = ax.figure.canvas.copy_from_bbox( - ax.figure.bbox) + self.background = ( + ax.figure.canvas.copy_from_bbox( + ax.figure.bbox + ) + ) if self.last_line is None: default_alpha = [] - lines_list = self.lines # get all register lines + lines_list = ( + self.lines + ) # get all register lines for current_line in lines_list: default_alpha.append( - current_line.get_alpha()) + current_line.get_alpha() + ) current_line.set_alpha( - self.highlight_alpha) + self.highlight_alpha + ) ax.figure.canvas.draw_idle() ax.figure.canvas.flush_events() - self.background_highlight = ax.figure.canvas.copy_from_bbox( - ax.figure.bbox) + self.background_highlight = ( + ax.figure.canvas.copy_from_bbox( + ax.figure.bbox + ) + ) self.last_line = line for i, current_line in enumerate(lines_list): current_line.set_alpha(default_alpha[i]) @@ -839,30 +1003,38 @@ def hover(self, event) -> None: self.last_line_prop = {} for key in self.highlight_prop: # if hasattr(line,key): - line_attr = getattr(line, 'get_' + key) + line_attr = getattr(line, "get_" + key) self.last_line_prop.update( - {key: line_attr()}) + {key: line_attr()} + ) set_line_attr = getattr( - line, 'set_' + key) + line, "set_" + key + ) set_line_attr(self.highlight_prop[key]) elif self.last_line_prop: for key in self.last_line_prop: set_line_attr = getattr( - self.last_line, 'set_' + key) + self.last_line, "set_" + key + ) set_line_attr(self.last_line_prop[key]) - self.hover_instance.custom_color = get_color_from_hex( - to_hex(line.get_color())) + self.hover_instance.custom_color = ( + get_color_from_hex( + to_hex(line.get_color()) + ) + ) self.last_line_prop = {} for key in self.highlight_prop: - line_attr = getattr(line, 'get_' + key) + line_attr = getattr(line, "get_" + key) self.last_line_prop.update( - {key: line_attr()}) - set_line_attr = getattr(line, 'set_' + key) + {key: line_attr()} + ) + set_line_attr = getattr(line, "set_" + key) set_line_attr(self.highlight_prop[key]) self.last_line = line ax.figure.canvas.restore_region( - self.background_highlight) + self.background_highlight + ) ax.draw_artist(line) # draw (blit method) @@ -880,7 +1052,8 @@ def hover(self, event) -> None: self.figure.canvas.draw_idle() self.figure.canvas.flush_events() self.background = self.figure.canvas.copy_from_bbox( - self.figure.bbox) + self.figure.bbox + ) self.set_cross_hair_visible(True) if self.last_line is not None: self.clear_line_prop() @@ -909,10 +1082,14 @@ def hover(self, event) -> None: self.myevent.x = event.x - self.pos[0] self.myevent.y = event.y - self.pos[1] self.myevent.inaxes = self.figure.canvas.inaxes( - (event.x - self.pos[0], event.y - self.pos[1])) + (event.x - self.pos[0], event.y - self.pos[1]) + ) - axes = [a for a in self.figure.canvas.figure.get_axes() - if a.in_axes(self.myevent)] + axes = [ + a + for a in self.figure.canvas.figure.get_axes() + if a.in_axes(self.myevent) + ] if not axes: @@ -920,10 +1097,12 @@ def hover(self, event) -> None: self.clear_line_prop() if self.background: self.figure.canvas.restore_region( - self.background) + self.background + ) # draw (blit method) self.figure.canvas.blit( - self.figure.axes[0].bbox) + self.figure.axes[0].bbox + ) self.figure.canvas.flush_events() self.background = None return @@ -933,17 +1112,19 @@ def autoscale(self): return ax = self.figure.axes[0] ax.relim(visible_only=self.autoscale_visible_only) - ax.autoscale_view(tight=self.autoscale_tight, - scalex=True if self.autoscale_axis != "y" else False, - scaley=True if self.autoscale_axis != "x" else False) + ax.autoscale_view( + tight=self.autoscale_tight, + scalex=True if self.autoscale_axis != "y" else False, + scaley=True if self.autoscale_axis != "x" else False, + ) ax.autoscale(axis=self.autoscale_axis, tight=self.autoscale_tight) if self.twinx: ax2 = self.figure.axes[1] if self.autoscale_axis != "x": ax2.relim(visible_only=self.autoscale_visible_only) - ax2.autoscale_view(tight=self.autoscale_tight, - scalex=False, - scaley=True) + ax2.autoscale_view( + tight=self.autoscale_tight, scalex=False, scaley=True + ) ax2.autoscale(axis="y", tight=self.autoscale_tight) self.ymin2, self.ymax2 = ax2.get_ylim() @@ -955,7 +1136,7 @@ def autoscale(self): self.ymin, self.ymax = ax.get_ylim() def home(self) -> None: - """ reset data axis + """reset data axis Return: None @@ -1026,11 +1207,19 @@ def push_current(self): """Push the current view limits and position onto the stack.""" self._nav_stack.push( WeakKeyDictionary( - {ax: (ax._get_view(), - # Store both the original and modified positions. - (ax.get_position(True).frozen(), - ax.get_position().frozen())) - for ax in self.figure.axes})) + { + ax: ( + ax._get_view(), + # Store both the original and modified positions. + ( + ax.get_position(True).frozen(), + ax.get_position().frozen(), + ), + ) + for ax in self.figure.axes + } + ) + ) self.set_history_buttons() def update(self): @@ -1052,8 +1241,8 @@ def _update_view(self): for ax, (view, (pos_orig, pos_active)) in items: ax._set_view(view) # Restore both the original and modified positions - ax._set_position(pos_orig, 'original') - ax._set_position(pos_active, 'active') + ax._set_position(pos_orig, "original") + ax._set_position(pos_active, "active") self.figure.canvas.draw_idle() self.figure.canvas.flush_events() @@ -1061,7 +1250,7 @@ def set_history_buttons(self): """Enable or disable the back/forward button.""" def reset_touch(self) -> None: - """ reset touch + """reset touch Return: None @@ -1070,7 +1259,7 @@ def reset_touch(self) -> None: self._last_touch_pos = {} def _get_scale(self): - """ kivy scatter _get_scale method """ + """kivy scatter _get_scale method""" p1 = Vector(*self.to_parent(0, 0)) p2 = Vector(*self.to_parent(1, 0)) scale = p1.distance(p2) @@ -1080,7 +1269,7 @@ def _get_scale(self): # prevent anything wrong with scale, just avoid to dispatch it # if the scale "visually" didn't change. #947 # Remove this ugly hack when we'll be Python 3 only. - if hasattr(self, '_scale_p'): + if hasattr(self, "_scale_p"): if str(scale) == str(self._scale_p): return self._scale_p @@ -1088,49 +1277,57 @@ def _get_scale(self): return scale def _set_scale(self, scale): - """ kivy scatter _set_scale method """ + """kivy scatter _set_scale method""" rescale = scale * 1.0 / self.scale - self.apply_transform(Matrix().scale(rescale, rescale, rescale), - post_multiply=True, - anchor=self.to_local(*self.center)) + self.apply_transform( + Matrix().scale(rescale, rescale, rescale), + post_multiply=True, + anchor=self.to_local(*self.center), + ) - scale = AliasProperty(_get_scale, _set_scale, bind=('x', 'y', 'transform')) - '''Scale value of the scatter. + scale = AliasProperty(_get_scale, _set_scale, bind=("x", "y", "transform")) + """Scale value of the scatter. :attr:`scale` is an :class:`~kivy.properties.AliasProperty` and defaults to 1.0. - ''' + """ def _draw_bitmap(self): - """ draw bitmap method. based on kivy scatter method""" + """draw bitmap method. based on kivy scatter method""" if self._bitmap is None: print("No bitmap!") return self._img_texture = Texture.create(size=(self.bt_w, self.bt_h)) self._img_texture.blit_buffer( - bytes(self._bitmap), colorfmt="rgba", bufferfmt='ubyte') + bytes(self._bitmap), colorfmt="rgba", bufferfmt="ubyte" + ) self._img_texture.flip_vertical() self.update_hover() self.update_selector() def transform_with_touch(self, event): - """ manage touch behaviour. based on kivy scatter method""" + """manage touch behaviour. based on kivy scatter method""" # just do a simple one finger drag changed = False if len(self._touches) == self.translation_touches: - if self.touch_mode == 'pan': + if self.touch_mode == "pan": if self._nav_stack() is None: self.push_current() if self.twinx: self.apply_pan_twinx( - self.figure.axes[0], self.figure.axes[1], event) + self.figure.axes[0], self.figure.axes[1], event + ) else: self.apply_pan(self.figure.axes[0], event) - if self.touch_mode == 'pan_x' or self.touch_mode == 'pan_y' \ - or self.touch_mode == 'adjust_x' or self.touch_mode == 'adjust_y': + if ( + self.touch_mode == "pan_x" + or self.touch_mode == "pan_y" + or self.touch_mode == "adjust_x" + or self.touch_mode == "adjust_y" + ): if self._nav_stack() is None: self.push_current() if self.twinx: @@ -1138,27 +1335,29 @@ def transform_with_touch(self, event): self.figure.axes[0], self.figure.axes[1], event, - mode=self.touch_mode) + mode=self.touch_mode, + ) else: self.apply_pan( - self.figure.axes[0], event, mode=self.touch_mode) + self.figure.axes[0], event, mode=self.touch_mode + ) - elif self.touch_mode == 'drag_legend': + elif self.touch_mode == "drag_legend": if self.legend_instance: self.apply_drag_legend(self.figure.axes[0], event) - elif self.touch_mode == 'zoombox': + elif self.touch_mode == "zoombox": if self._nav_stack() is None: self.push_current() real_x, real_y = event.x - self.pos[0], event.y - self.pos[1] # in case x_init is not create - if not hasattr(self, 'x_init'): + if not hasattr(self, "x_init"): self.x_init = event.x self.y_init = real_y self.draw_box(event, self.x_init, self.y_init, event.x, real_y) # mode cursor - elif self.touch_mode == 'cursor': + elif self.touch_mode == "cursor": self.hover_on = True self.hover(event) @@ -1169,8 +1368,11 @@ def transform_with_touch(self, event): return changed # we have more than one touch... list of last known pos - points = [Vector(self._last_touch_pos[t]) for t in self._touches - if t is not event] + points = [ + Vector(self._last_touch_pos[t]) + for t in self._touches + if t is not event + ] # add current touch last points.append(Vector(event.pos)) @@ -1195,16 +1397,28 @@ def transform_with_touch(self, event): if self.auto_zoom: v1 = Vector(0, 10) angle = v1.angle(new_line) + 180 - if angle < 0 + self.zoom_angle_detection or angle > 360 - self.zoom_angle_detection: + if ( + angle < 0 + self.zoom_angle_detection + or angle > 360 - self.zoom_angle_detection + ): self.do_zoom_x = False self.do_zoom_y = True - elif angle > 90 - self.zoom_angle_detection and angle < 90 + self.zoom_angle_detection: + elif ( + angle > 90 - self.zoom_angle_detection + and angle < 90 + self.zoom_angle_detection + ): self.do_zoom_x = True self.do_zoom_y = False - elif angle > 180 - self.zoom_angle_detection and angle < 180 + self.zoom_angle_detection: + elif ( + angle > 180 - self.zoom_angle_detection + and angle < 180 + self.zoom_angle_detection + ): self.do_zoom_x = False self.do_zoom_y = True - elif angle > 270 - self.zoom_angle_detection and angle < 270 + self.zoom_angle_detection: + elif ( + angle > 270 - self.zoom_angle_detection + and angle < 270 + self.zoom_angle_detection + ): self.do_zoom_x = True self.do_zoom_y = False else: @@ -1226,22 +1440,26 @@ def transform_with_touch(self, event): self.figure.axes[0], self.figure.axes[1], anchor=anchor, - new_line=new_line) + new_line=new_line, + ) else: self.apply_zoom( scale, self.figure.axes[0], anchor=anchor, - new_line=new_line) + new_line=new_line, + ) changed = True return changed def on_motion(self, *args): - '''Kivy Event to trigger mouse event on motion - `enter_notify_event`. - ''' - if self._pressed or self.disabled: # Do not process this event if there's a touch_move + """Kivy Event to trigger mouse event on motion + `enter_notify_event`. + """ + if ( + self._pressed or self.disabled + ): # Do not process this event if there's a touch_move return pos = args[1] newcoord = self.to_widget(pos[0], pos[1]) @@ -1260,11 +1478,11 @@ def on_motion(self, *args): self.hover(FakeEventTwinx) def get_data_xy(self, x, y): - """ manage x y data in navigation bar TODO""" + """manage x y data in navigation bar TODO""" return None, None def on_touch_down(self, event): - """ Manage Mouse/touch press """ + """Manage Mouse/touch press""" if self.disabled: return @@ -1281,7 +1499,7 @@ def on_touch_down(self, event): self.current_legend = current_legend break if select_legend: - if self.touch_mode != 'drag_legend': + if self.touch_mode != "drag_legend": return False else: event.grab(self) @@ -1307,23 +1525,23 @@ def on_touch_down(self, event): elif event.is_double_tap: if not self.disable_double_tap: - if self.touch_mode != 'selector': + if self.touch_mode != "selector": self.home() return True else: - if self.touch_mode == 'cursor': + if self.touch_mode == "cursor": self.hover_on = True self.hover(event) - elif self.touch_mode == 'zoombox': + elif self.touch_mode == "zoombox": real_x, real_y = x - self.pos[0], y - self.pos[1] self.x_init = x self.y_init = real_y self.draw_box(event, x, real_y, x, real_y) - elif self.touch_mode == 'minmax': + elif self.touch_mode == "minmax": self.min_max(event) - elif self.touch_mode == 'selector': + elif self.touch_mode == "selector": pass event.grab(self) @@ -1339,7 +1557,7 @@ def on_touch_down(self, event): return False def on_touch_move(self, event): - """ Manage Mouse/touch move while pressed """ + """Manage Mouse/touch move while pressed""" if self.disabled: return @@ -1347,7 +1565,7 @@ def on_touch_move(self, event): if event.is_double_tap: if not self.disable_double_tap: - if self.touch_mode != 'selector': + if self.touch_mode != "selector": self.home() return True @@ -1361,7 +1579,7 @@ def on_touch_move(self, event): return True def on_touch_up(self, event): - """ Manage Mouse/touch release """ + """Manage Mouse/touch release""" if self.disabled: return @@ -1370,24 +1588,35 @@ def on_touch_up(self, event): event.ungrab(self) del self._last_touch_pos[event] self._touches.remove(event) - if (self.touch_mode == 'pan' or self.touch_mode == 'zoombox' or - self.touch_mode == 'pan_x' or self.touch_mode == 'pan_y' or - self.touch_mode == 'adjust_x' or self.touch_mode == 'adjust_y' or - self.touch_mode == 'minmax'): + if ( + self.touch_mode == "pan" + or self.touch_mode == "zoombox" + or self.touch_mode == "pan_x" + or self.touch_mode == "pan_y" + or self.touch_mode == "adjust_x" + or self.touch_mode == "adjust_y" + or self.touch_mode == "minmax" + ): self.push_current() if self.interactive_axis: - if self.touch_mode == 'pan_x' or self.touch_mode == 'pan_y' \ - or self.touch_mode == 'adjust_x' or self.touch_mode == 'adjust_y': - self.touch_mode = 'pan' + if ( + self.touch_mode == "pan_x" + or self.touch_mode == "pan_y" + or self.touch_mode == "adjust_x" + or self.touch_mode == "adjust_y" + ): + self.touch_mode = "pan" self.first_touch_pan = None if self.last_line is not None: self.clear_line_prop() x, y = event.x, event.y - if abs( - self._box_size[0]) > 1 or abs( - self._box_size[1]) > 1 or self.touch_mode == 'zoombox': + if ( + abs(self._box_size[0]) > 1 + or abs(self._box_size[1]) > 1 + or self.touch_mode == "zoombox" + ): self.reset_box() if not self.collide_point(x, y) and self.do_update: # update axis lim if zoombox is used and touch outside widget @@ -1410,15 +1639,15 @@ def on_touch_up(self, event): self.show_compare_cursor = True self.figure.canvas.draw_idle() self.figure.canvas.flush_events() - if self.last_line is None or self.touch_mode != 'cursor': + if self.last_line is None or self.touch_mode != "cursor": self.figure.canvas.draw_idle() self.figure.canvas.flush_events() return True def apply_zoom(self, scale_factor, ax, anchor=(0, 0), new_line=None): - """ zoom touch method """ - if self.touch_mode == 'selector': + """zoom touch method""" + if self.touch_mode == "selector": return x = anchor[0] - self.pos[0] @@ -1433,7 +1662,7 @@ def apply_zoom(self, scale_factor, ax, anchor=(0, 0), new_line=None): scale = ax.get_xscale() yscale = ax.get_yscale() - if scale == 'linear': + if scale == "linear": old_min = cur_xlim[0] old_max = cur_xlim[1] @@ -1444,7 +1673,7 @@ def apply_zoom(self, scale_factor, ax, anchor=(0, 0), new_line=None): xdata = self.transform_eval(xdata, ax.yaxis) old_max = self.transform_eval(max_, ax.yaxis) - if yscale == 'linear': + if yscale == "linear": yold_min = cur_ylim[0] yold_max = cur_ylim[1] @@ -1462,36 +1691,44 @@ def apply_zoom(self, scale_factor, ax, anchor=(0, 0), new_line=None): rely = (yold_max - ydata) / (yold_max - yold_min) if self.do_zoom_x: - if scale == 'linear': - ax.set_xlim([xdata - new_width * (1 - relx), - xdata + new_width * (relx)]) + if scale == "linear": + ax.set_xlim( + [ + xdata - new_width * (1 - relx), + xdata + new_width * (relx), + ] + ) else: new_min = xdata - new_width * (1 - relx) new_max = xdata + new_width * (relx) try: new_min, new_max = self.inv_transform_eval( - new_min, ax.yaxis), self.inv_transform_eval( - new_max, ax.yaxis) + new_min, ax.yaxis + ), self.inv_transform_eval(new_max, ax.yaxis) except OverflowError: # Limit case new_min, new_max = min_, max_ - if new_min <= 0. or new_max <= 0.: # Limit case + if new_min <= 0.0 or new_max <= 0.0: # Limit case new_min, new_max = min_, max_ ax.set_xlim([new_min, new_max]) if self.do_zoom_y: - if yscale == 'linear': - ax.set_ylim([ydata - new_height * (1 - rely), - ydata + new_height * (rely)]) + if yscale == "linear": + ax.set_ylim( + [ + ydata - new_height * (1 - rely), + ydata + new_height * (rely), + ] + ) else: new_ymin = ydata - new_height * (1 - rely) new_ymax = ydata + new_height * (rely) try: new_ymin, new_ymax = self.inv_transform_eval( - new_ymin, ax.yaxis), self.inv_transform_eval( - new_ymax, ax.yaxis) + new_ymin, ax.yaxis + ), self.inv_transform_eval(new_ymax, ax.yaxis) except OverflowError: # Limit case new_ymin, new_ymax = ymin_, ymax_ - if new_ymin <= 0. or new_ymax <= 0.: # Limit case + if new_ymin <= 0.0 or new_ymax <= 0.0: # Limit case new_ymin, new_ymax = ymin_, ymax_ ax.set_ylim([new_ymin, new_ymax]) @@ -1502,7 +1739,8 @@ def apply_zoom(self, scale_factor, ax, anchor=(0, 0), new_line=None): ax.figure.canvas.draw_idle() ax.figure.canvas.flush_events() self.background = ax.figure.canvas.copy_from_bbox( - ax.figure.bbox) + ax.figure.bbox + ) self.background_patch_copy.set_visible(False) if self.last_line is not None: self.clear_line_prop() @@ -1519,16 +1757,10 @@ def apply_zoom(self, scale_factor, ax, anchor=(0, 0), new_line=None): ax.figure.canvas.flush_events() def apply_zoom_twinx( - self, - scale_factor, - ax, - ax2, - anchor=( - 0, - 0), - new_line=None): + self, scale_factor, ax, ax2, anchor=(0, 0), new_line=None + ): """twin axis zoom method""" - if self.touch_mode == 'selector': + if self.touch_mode == "selector": return x = anchor[0] - self.pos[0] y = anchor[1] - self.pos[1] @@ -1537,7 +1769,8 @@ def apply_zoom_twinx( xdata, ydata = trans.transform_point((x + new_line.x, y + new_line.y)) trans2 = ax2.transData.inverted() xdata2, ydata2 = trans2.transform_point( - (x + new_line.x, y + new_line.y)) + (x + new_line.x, y + new_line.y) + ) cur_xlim = ax.get_xlim() cur_ylim = ax.get_ylim() @@ -1547,7 +1780,7 @@ def apply_zoom_twinx( yscale = ax.get_yscale() yscale2 = ax2.get_yscale() - if scale == 'linear': + if scale == "linear": old_min = cur_xlim[0] old_max = cur_xlim[1] @@ -1558,7 +1791,7 @@ def apply_zoom_twinx( xdata = self.transform_eval(xdata, ax.yaxis) old_max = self.transform_eval(max_, ax.yaxis) - if yscale == 'linear': + if yscale == "linear": yold_min = cur_ylim[0] yold_max = cur_ylim[1] @@ -1576,40 +1809,48 @@ def apply_zoom_twinx( rely = (yold_max - ydata) / (yold_max - yold_min) if self.do_zoom_x: - if scale == 'linear': - ax.set_xlim([xdata - new_width * (1 - relx), - xdata + new_width * (relx)]) + if scale == "linear": + ax.set_xlim( + [ + xdata - new_width * (1 - relx), + xdata + new_width * (relx), + ] + ) else: new_min = xdata - new_width * (1 - relx) new_max = xdata + new_width * (relx) try: new_min, new_max = self.inv_transform_eval( - new_min, ax.yaxis), self.inv_transform_eval( - new_max, ax.yaxis) + new_min, ax.yaxis + ), self.inv_transform_eval(new_max, ax.yaxis) except OverflowError: # Limit case new_min, new_max = min_, max_ - if new_min <= 0. or new_max <= 0.: # Limit case + if new_min <= 0.0 or new_max <= 0.0: # Limit case new_min, new_max = min_, max_ ax.set_xlim([new_min, new_max]) if self.do_zoom_y: - if yscale == 'linear': - ax.set_ylim([ydata - new_height * (1 - rely), - ydata + new_height * (rely)]) + if yscale == "linear": + ax.set_ylim( + [ + ydata - new_height * (1 - rely), + ydata + new_height * (rely), + ] + ) else: new_ymin = ydata - new_height * (1 - rely) new_ymax = ydata + new_height * (rely) try: new_ymin, new_ymax = self.inv_transform_eval( - new_ymin, ax.yaxis), self.inv_transform_eval( - new_ymax, ax.yaxis) + new_ymin, ax.yaxis + ), self.inv_transform_eval(new_ymax, ax.yaxis) except OverflowError: # Limit case new_ymin, new_ymax = ymin_, ymax_ - if new_ymin <= 0. or new_ymax <= 0.: # Limit case + if new_ymin <= 0.0 or new_ymax <= 0.0: # Limit case new_ymin, new_ymax = ymin_, ymax_ ax.set_ylim([new_ymin, new_ymax]) - if yscale2 == 'linear': + if yscale2 == "linear": yold2_min = cur_ylim2[0] yold2_max = cur_ylim2[1] @@ -1624,22 +1865,30 @@ def apply_zoom_twinx( rely2 = (yold2_max - ydata2) / (yold2_max - yold2_min) if self.do_zoom_y: - ax2.set_ylim([ydata2 - new_height2 * (1 - rely2), - ydata2 + new_height2 * (rely2)]) - - if yscale2 == 'linear': - ax2.set_ylim([ydata2 - new_height2 * (1 - rely2), - ydata2 + new_height2 * (rely2)]) + ax2.set_ylim( + [ + ydata2 - new_height2 * (1 - rely2), + ydata2 + new_height2 * (rely2), + ] + ) + + if yscale2 == "linear": + ax2.set_ylim( + [ + ydata2 - new_height2 * (1 - rely2), + ydata2 + new_height2 * (rely2), + ] + ) else: new_ymin2 = ydata2 - new_height2 * (1 - rely2) new_ymax2 = ydata2 + new_height2 * (rely2) try: new_ymin2, new_ymax2 = self.inv_transform_eval( - new_ymin2, ax2.yaxis), self.inv_transform_eval( - new_ymax2, ax2.yaxis) + new_ymin2, ax2.yaxis + ), self.inv_transform_eval(new_ymax2, ax2.yaxis) except OverflowError: # Limit case new_ymin2, new_ymax2 = ymin2_, ymax2_ - if new_ymin2 <= 0. or new_ymax2 <= 0.: # Limit case + if new_ymin2 <= 0.0 or new_ymax2 <= 0.0: # Limit case new_ymin2, new_ymax2 = ymin2_, ymax2_ ax2.set_ylim([new_ymin2, new_ymax2]) @@ -1651,7 +1900,8 @@ def apply_zoom_twinx( ax.figure.canvas.draw_idle() ax.figure.canvas.flush_events() self.background = ax.figure.canvas.copy_from_bbox( - ax.figure.bbox) + ax.figure.bbox + ) self.background_patch_copy.set_visible(False) self.background_ax2_patch_copy.set_visible(False) if self.last_line is not None: @@ -1674,30 +1924,36 @@ def apply_zoom_twinx( ax.figure.canvas.draw_idle() ax.figure.canvas.flush_events() - def apply_pan(self, ax, event, mode='pan'): - """ pan method """ + def apply_pan(self, ax, event, mode="pan"): + """pan method""" trans = ax.transData.inverted() xdata, ydata = trans.transform_point( - (event.x - self.pos[0], event.y - self.pos[1])) + (event.x - self.pos[0], event.y - self.pos[1]) + ) xpress, ypress = trans.transform_point( - (self._last_touch_pos[event][0] - self.pos[0], - self._last_touch_pos[event][1] - self.pos[1])) + ( + self._last_touch_pos[event][0] - self.pos[0], + self._last_touch_pos[event][1] - self.pos[1], + ) + ) scale = ax.get_xscale() yscale = ax.get_yscale() - if scale == 'linear': + if scale == "linear": dx = xdata - xpress else: - dx = self.transform_eval(xdata, ax.xaxis) - \ - self.transform_eval(xpress, ax.xaxis) + dx = self.transform_eval(xdata, ax.xaxis) - self.transform_eval( + xpress, ax.xaxis + ) - if yscale == 'linear': + if yscale == "linear": dy = ydata - ypress else: - dy = self.transform_eval(ydata, ax.yaxis) - \ - self.transform_eval(ypress, ax.yaxis) + dy = self.transform_eval(ydata, ax.yaxis) - self.transform_eval( + ypress, ax.yaxis + ) xleft, xright = self.figure.axes[0].get_xlim() ybottom, ytop = self.figure.axes[0].get_ylim() @@ -1716,56 +1972,76 @@ def apply_pan(self, ax, event, mode='pan'): else: cur_ylim = (ybottom, ytop) - if self.interactive_axis and self.touch_mode == 'pan' and not self.first_touch_pan == 'pan': + if ( + self.interactive_axis + and self.touch_mode == "pan" + and not self.first_touch_pan == "pan" + ): if (ydata < cur_ylim[0] and not inverted_y) or ( - ydata > cur_ylim[1] and inverted_y): + ydata > cur_ylim[1] and inverted_y + ): left_anchor_zone = ( - cur_xlim[1] - cur_xlim[0]) * .2 + cur_xlim[0] + cur_xlim[1] - cur_xlim[0] + ) * 0.2 + cur_xlim[0] right_anchor_zone = ( - cur_xlim[1] - cur_xlim[0]) * .8 + cur_xlim[0] + cur_xlim[1] - cur_xlim[0] + ) * 0.8 + cur_xlim[0] if xdata < left_anchor_zone or xdata > right_anchor_zone: - mode = 'adjust_x' + mode = "adjust_x" else: - mode = 'pan_x' + mode = "pan_x" self.touch_mode = mode - elif (xdata < cur_xlim[0] and not inverted_x) or (xdata > cur_xlim[1] and inverted_x): + elif (xdata < cur_xlim[0] and not inverted_x) or ( + xdata > cur_xlim[1] and inverted_x + ): bottom_anchor_zone = ( - cur_ylim[1] - cur_ylim[0]) * .2 + cur_ylim[0] - top_anchor_zone = ( - cur_ylim[1] - cur_ylim[0]) * .8 + cur_ylim[0] + cur_ylim[1] - cur_ylim[0] + ) * 0.2 + cur_ylim[0] + top_anchor_zone = (cur_ylim[1] - cur_ylim[0]) * 0.8 + cur_ylim[ + 0 + ] if ydata < bottom_anchor_zone or ydata > top_anchor_zone: - mode = 'adjust_y' + mode = "adjust_y" else: - mode = 'pan_y' + mode = "pan_y" self.touch_mode = mode else: - self.touch_mode = 'pan' + self.touch_mode = "pan" - if not mode == 'pan_y' and not mode == 'adjust_y': - if mode == 'adjust_x': + if not mode == "pan_y" and not mode == "adjust_y": + if mode == "adjust_x": if self.anchor_x is None: midpoint = (cur_xlim[1] + cur_xlim[0]) / 2 if xdata > midpoint: - self.anchor_x = 'left' + self.anchor_x = "left" else: - self.anchor_x = 'right' - if self.anchor_x == 'left': + self.anchor_x = "right" + if self.anchor_x == "left": if xdata > cur_xlim[0]: - if scale == 'linear': + if scale == "linear": cur_xlim -= dx else: try: cur_xlim = [ self.inv_transform_eval( - (self.transform_eval( - cur_xlim[0], - ax.xaxis) - dx), - ax.xaxis), + ( + self.transform_eval( + cur_xlim[0], ax.xaxis + ) + - dx + ), + ax.xaxis, + ), self.inv_transform_eval( - (self.transform_eval( - cur_xlim[1], - ax.xaxis) - dx), - ax.xaxis)] + ( + self.transform_eval( + cur_xlim[1], ax.xaxis + ) + - dx + ), + ax.xaxis, + ), + ] except (ValueError, OverflowError): cur_xlim = cur_xlim # Keep previous limits if inverted_x: @@ -1774,21 +2050,30 @@ def apply_pan(self, ax, event, mode='pan'): ax.set_xlim(None, cur_xlim[1]) else: if xdata < cur_xlim[1]: - if scale == 'linear': + if scale == "linear": cur_xlim -= dx else: try: cur_xlim = [ self.inv_transform_eval( - (self.transform_eval( - cur_xlim[0], - ax.xaxis) - dx), - ax.xaxis), + ( + self.transform_eval( + cur_xlim[0], ax.xaxis + ) + - dx + ), + ax.xaxis, + ), self.inv_transform_eval( - (self.transform_eval( - cur_xlim[1], - ax.xaxis) - dx), - ax.xaxis)] + ( + self.transform_eval( + cur_xlim[1], ax.xaxis + ) + - dx + ), + ax.xaxis, + ), + ] except (ValueError, OverflowError): cur_xlim = cur_xlim # Keep previous limits if inverted_x: @@ -1796,21 +2081,26 @@ def apply_pan(self, ax, event, mode='pan'): else: ax.set_xlim(cur_xlim[0], None) else: - if scale == 'linear': + if scale == "linear": cur_xlim -= dx else: try: cur_xlim = [ self.inv_transform_eval( - (self.transform_eval( - cur_xlim[0], - ax.xaxis) - dx), - ax.xaxis), + ( + self.transform_eval(cur_xlim[0], ax.xaxis) + - dx + ), + ax.xaxis, + ), self.inv_transform_eval( - (self.transform_eval( - cur_xlim[1], - ax.xaxis) - dx), - ax.xaxis)] + ( + self.transform_eval(cur_xlim[1], ax.xaxis) + - dx + ), + ax.xaxis, + ), + ] except (ValueError, OverflowError): cur_xlim = cur_xlim # Keep previous limits if inverted_x: @@ -1818,33 +2108,42 @@ def apply_pan(self, ax, event, mode='pan'): else: ax.set_xlim(cur_xlim) - if not mode == 'pan_x' and not mode == 'adjust_x': - if mode == 'adjust_y': + if not mode == "pan_x" and not mode == "adjust_x": + if mode == "adjust_y": if self.anchor_y is None: midpoint = (cur_ylim[1] + cur_ylim[0]) / 2 if ydata > midpoint: - self.anchor_y = 'top' + self.anchor_y = "top" else: - self.anchor_y = 'bottom' + self.anchor_y = "bottom" - if self.anchor_y == 'top': + if self.anchor_y == "top": if ydata > cur_ylim[0]: - if yscale == 'linear': + if yscale == "linear": cur_ylim -= dy else: try: cur_ylim = [ self.inv_transform_eval( - (self.transform_eval( - cur_ylim[0], - ax.yaxis) - dy), - ax.yaxis), + ( + self.transform_eval( + cur_ylim[0], ax.yaxis + ) + - dy + ), + ax.yaxis, + ), self.inv_transform_eval( - (self.transform_eval( - cur_ylim[1], - ax.yaxis) - dy), - ax.yaxis)] + ( + self.transform_eval( + cur_ylim[1], ax.yaxis + ) + - dy + ), + ax.yaxis, + ), + ] except (ValueError, OverflowError): cur_ylim = cur_ylim # Keep previous limits @@ -1854,22 +2153,31 @@ def apply_pan(self, ax, event, mode='pan'): ax.set_ylim(None, cur_ylim[1]) else: if ydata < cur_ylim[1]: - if yscale == 'linear': + if yscale == "linear": cur_ylim -= dy else: try: cur_ylim = [ self.inv_transform_eval( - (self.transform_eval( - cur_ylim[0], - ax.yaxis) - dy), - ax.yaxis), + ( + self.transform_eval( + cur_ylim[0], ax.yaxis + ) + - dy + ), + ax.yaxis, + ), self.inv_transform_eval( - (self.transform_eval( - cur_ylim[1], - ax.yaxis) - dy), - ax.yaxis)] + ( + self.transform_eval( + cur_ylim[1], ax.yaxis + ) + - dy + ), + ax.yaxis, + ), + ] except (ValueError, OverflowError): cur_ylim = cur_ylim # Keep previous limits if inverted_y: @@ -1877,22 +2185,27 @@ def apply_pan(self, ax, event, mode='pan'): else: ax.set_ylim(cur_ylim[0], None) else: - if yscale == 'linear': + if yscale == "linear": cur_ylim -= dy else: try: cur_ylim = [ self.inv_transform_eval( - (self.transform_eval( - cur_ylim[0], - ax.yaxis) - dy), - ax.yaxis), + ( + self.transform_eval(cur_ylim[0], ax.yaxis) + - dy + ), + ax.yaxis, + ), self.inv_transform_eval( - (self.transform_eval( - cur_ylim[1], - ax.yaxis) - dy), - ax.yaxis)] + ( + self.transform_eval(cur_ylim[1], ax.yaxis) + - dy + ), + ax.yaxis, + ), + ] except (ValueError, OverflowError): cur_ylim = cur_ylim # Keep previous limits if inverted_y: @@ -1910,7 +2223,8 @@ def apply_pan(self, ax, event, mode='pan'): ax.figure.canvas.draw_idle() ax.figure.canvas.flush_events() self.background = ax.figure.canvas.copy_from_bbox( - ax.figure.bbox) + ax.figure.bbox + ) self.background_patch_copy.set_visible(False) if self.last_line is not None: self.clear_line_prop() @@ -1928,14 +2242,18 @@ def apply_pan(self, ax, event, mode='pan'): ax.figure.canvas.draw_idle() ax.figure.canvas.flush_events() - def apply_pan_twinx(self, ax, ax2, event, mode='pan'): + def apply_pan_twinx(self, ax, ax2, event, mode="pan"): """twin axis pan method""" trans = ax.transData.inverted() xdata, ydata = trans.transform_point( - (event.x - self.pos[0], event.y - self.pos[1])) + (event.x - self.pos[0], event.y - self.pos[1]) + ) xpress, ypress = trans.transform_point( - (self._last_touch_pos[event][0] - self.pos[0], - self._last_touch_pos[event][1] - self.pos[1])) + ( + self._last_touch_pos[event][0] - self.pos[0], + self._last_touch_pos[event][1] - self.pos[1], + ) + ) scale = ax.get_xscale() yscale = ax.get_yscale() @@ -1943,17 +2261,19 @@ def apply_pan_twinx(self, ax, ax2, event, mode='pan'): update_cursor = False - if scale == 'linear': + if scale == "linear": dx = xdata - xpress else: - dx = self.transform_eval(xdata, ax.xaxis) - \ - self.transform_eval(xpress, ax.xaxis) + dx = self.transform_eval(xdata, ax.xaxis) - self.transform_eval( + xpress, ax.xaxis + ) - if yscale == 'linear': + if yscale == "linear": dy = ydata - ypress else: - dy = self.transform_eval(ydata, ax.yaxis) - \ - self.transform_eval(ypress, ax.yaxis) + dy = self.transform_eval(ydata, ax.yaxis) - self.transform_eval( + ypress, ax.yaxis + ) xleft, xright = ax.get_xlim() ybottom, ytop = ax.get_ylim() @@ -1983,64 +2303,87 @@ def apply_pan_twinx(self, ax, ax2, event, mode='pan'): ydata2 = ydata * ratio + cur_ylim2[0] ypress2 = ypress * ratio + cur_ylim2[0] - if yscale2 == 'linear': + if yscale2 == "linear": dy2 = ydata2 - ypress2 else: - dy2 = self.transform_eval(ydata2, ax2.yaxis) - \ - self.transform_eval(ypress2, ax2.yaxis) - - if self.interactive_axis and self.touch_mode == 'pan' and not self.first_touch_pan == 'pan': + dy2 = self.transform_eval(ydata2, ax2.yaxis) - self.transform_eval( + ypress2, ax2.yaxis + ) + + if ( + self.interactive_axis + and self.touch_mode == "pan" + and not self.first_touch_pan == "pan" + ): if (ydata < cur_ylim[0] and not inverted_y) or ( - ydata > cur_ylim[1] and inverted_y): + ydata > cur_ylim[1] and inverted_y + ): left_anchor_zone = ( - cur_xlim[1] - cur_xlim[0]) * .2 + cur_xlim[0] + cur_xlim[1] - cur_xlim[0] + ) * 0.2 + cur_xlim[0] right_anchor_zone = ( - cur_xlim[1] - cur_xlim[0]) * .8 + cur_xlim[0] + cur_xlim[1] - cur_xlim[0] + ) * 0.8 + cur_xlim[0] if xdata < left_anchor_zone or xdata > right_anchor_zone: - mode = 'adjust_x' + mode = "adjust_x" else: - mode = 'pan_x' + mode = "pan_x" self.touch_mode = mode - elif (xdata < cur_xlim[0] and not inverted_y) or (xdata > cur_xlim[1] and inverted_y) \ - or (xdata > cur_xlim[1] and not inverted_y) or (xdata < cur_xlim[0] and inverted_y): + elif ( + (xdata < cur_xlim[0] and not inverted_y) + or (xdata > cur_xlim[1] and inverted_y) + or (xdata > cur_xlim[1] and not inverted_y) + or (xdata < cur_xlim[0] and inverted_y) + ): bottom_anchor_zone = ( - cur_ylim[1] - cur_ylim[0]) * .2 + cur_ylim[0] - top_anchor_zone = ( - cur_ylim[1] - cur_ylim[0]) * .8 + cur_ylim[0] + cur_ylim[1] - cur_ylim[0] + ) * 0.2 + cur_ylim[0] + top_anchor_zone = (cur_ylim[1] - cur_ylim[0]) * 0.8 + cur_ylim[ + 0 + ] if ydata < bottom_anchor_zone or ydata > top_anchor_zone: - mode = 'adjust_y' + mode = "adjust_y" else: - mode = 'pan_y' + mode = "pan_y" self.touch_mode = mode else: - self.touch_mode = 'pan' + self.touch_mode = "pan" - if not mode == 'pan_y' and not mode == 'adjust_y': - if mode == 'adjust_x': + if not mode == "pan_y" and not mode == "adjust_y": + if mode == "adjust_x": if self.anchor_x is None: midpoint = (cur_xlim[1] + cur_xlim[0]) / 2 if xdata > midpoint: - self.anchor_x = 'left' + self.anchor_x = "left" else: - self.anchor_x = 'right' - if self.anchor_x == 'left': + self.anchor_x = "right" + if self.anchor_x == "left": if xdata > cur_xlim[0]: - if scale == 'linear': + if scale == "linear": cur_xlim -= dx else: try: cur_xlim = [ self.inv_transform_eval( - (self.transform_eval( - cur_xlim[0], - ax.xaxis) - dx), - ax.xaxis), + ( + self.transform_eval( + cur_xlim[0], ax.xaxis + ) + - dx + ), + ax.xaxis, + ), self.inv_transform_eval( - (self.transform_eval( - cur_xlim[1], - ax.xaxis) - dx), - ax.xaxis)] + ( + self.transform_eval( + cur_xlim[1], ax.xaxis + ) + - dx + ), + ax.xaxis, + ), + ] except (ValueError, OverflowError): cur_xlim = cur_xlim # Keep previous limits if inverted_x: @@ -2050,21 +2393,30 @@ def apply_pan_twinx(self, ax, ax2, event, mode='pan'): else: if xdata < cur_xlim[1]: - if scale == 'linear': + if scale == "linear": cur_xlim -= dx else: try: cur_xlim = [ self.inv_transform_eval( - (self.transform_eval( - cur_xlim[0], - ax.xaxis) - dx), - ax.xaxis), + ( + self.transform_eval( + cur_xlim[0], ax.xaxis + ) + - dx + ), + ax.xaxis, + ), self.inv_transform_eval( - (self.transform_eval( - cur_xlim[1], - ax.xaxis) - dx), - ax.xaxis)] + ( + self.transform_eval( + cur_xlim[1], ax.xaxis + ) + - dx + ), + ax.xaxis, + ), + ] except (ValueError, OverflowError): cur_xlim = cur_xlim # Keep previous limits if inverted_x: @@ -2073,21 +2425,26 @@ def apply_pan_twinx(self, ax, ax2, event, mode='pan'): ax.set_xlim(cur_xlim[0], None) else: - if scale == 'linear': + if scale == "linear": cur_xlim -= dx else: try: cur_xlim = [ self.inv_transform_eval( - (self.transform_eval( - cur_xlim[0], - ax.xaxis) - dx), - ax.xaxis), + ( + self.transform_eval(cur_xlim[0], ax.xaxis) + - dx + ), + ax.xaxis, + ), self.inv_transform_eval( - (self.transform_eval( - cur_xlim[1], - ax.xaxis) - dx), - ax.xaxis)] + ( + self.transform_eval(cur_xlim[1], ax.xaxis) + - dx + ), + ax.xaxis, + ), + ] except (ValueError, OverflowError): cur_xlim = cur_xlim # Keep previous limits if inverted_x: @@ -2095,51 +2452,62 @@ def apply_pan_twinx(self, ax, ax2, event, mode='pan'): else: ax.set_xlim(cur_xlim) - if not mode == 'pan_x' and not mode == 'adjust_x': - if mode == 'adjust_y': + if not mode == "pan_x" and not mode == "adjust_x": + if mode == "adjust_y": trans_ax2 = ax2.transData.inverted() xdata_ax2, ydata_ax2 = trans_ax2.transform_point( - (event.x - self.pos[0], event.y - self.pos[1])) + (event.x - self.pos[0], event.y - self.pos[1]) + ) if self.anchor_y is None: midpoint_x = (cur_xlim[1] + cur_xlim[0]) / 2 midpoint_ax1 = (cur_ylim[1] + cur_ylim[0]) / 2 midpoint_ax2 = (cur_ylim2[1] + cur_ylim2[0]) / 2 if (xdata > midpoint_x and not inverted_x) or ( - xdata < midpoint_x and inverted_x): - ax_anchor = 'right' + xdata < midpoint_x and inverted_x + ): + ax_anchor = "right" else: - ax_anchor = 'left' + ax_anchor = "left" - if ax_anchor == 'left': + if ax_anchor == "left": if ydata > midpoint_ax1: - self.anchor_y = 'top_left' + self.anchor_y = "top_left" else: - self.anchor_y = 'bottom_left' + self.anchor_y = "bottom_left" else: if ydata_ax2 > midpoint_ax2: - self.anchor_y = 'top_right' + self.anchor_y = "top_right" else: - self.anchor_y = 'bottom_right' + self.anchor_y = "bottom_right" # print(self.anchor_y) - if self.anchor_y == 'top_left': + if self.anchor_y == "top_left": if ydata > cur_ylim[0]: - if yscale == 'linear': + if yscale == "linear": cur_ylim -= dy else: try: cur_ylim = [ self.inv_transform_eval( - (self.transform_eval( - cur_ylim[0], - ax.yaxis) - dy), - ax.yaxis), + ( + self.transform_eval( + cur_ylim[0], ax.yaxis + ) + - dy + ), + ax.yaxis, + ), self.inv_transform_eval( - (self.transform_eval( - cur_ylim[1], - ax.yaxis) - dy), - ax.yaxis)] + ( + self.transform_eval( + cur_ylim[1], ax.yaxis + ) + - dy + ), + ax.yaxis, + ), + ] except (ValueError, OverflowError): cur_ylim = cur_ylim # Keep previous limits if inverted_y: @@ -2149,24 +2517,33 @@ def apply_pan_twinx(self, ax, ax2, event, mode='pan'): update_cursor = True - elif self.anchor_y == 'top_right': + elif self.anchor_y == "top_right": if ydata_ax2 > cur_ylim2[0]: - if yscale2 == 'linear': + if yscale2 == "linear": cur_ylim2 -= dy2 else: try: cur_ylim2 = [ self.inv_transform_eval( - (self.transform_eval( - cur_ylim2[0], - ax2.yaxis) - dy2), - ax2.yaxis), + ( + self.transform_eval( + cur_ylim2[0], ax2.yaxis + ) + - dy2 + ), + ax2.yaxis, + ), self.inv_transform_eval( - (self.transform_eval( - cur_ylim2[1], - ax2.yaxis) - dy2), - ax2.yaxis)] + ( + self.transform_eval( + cur_ylim2[1], ax2.yaxis + ) + - dy2 + ), + ax2.yaxis, + ), + ] except (ValueError, OverflowError): cur_ylim2 = cur_ylim2 # Keep previous limits @@ -2177,24 +2554,33 @@ def apply_pan_twinx(self, ax, ax2, event, mode='pan'): update_cursor = True - elif self.anchor_y == 'bottom_left': + elif self.anchor_y == "bottom_left": if ydata < cur_ylim[1]: - if yscale == 'linear': + if yscale == "linear": cur_ylim -= dy else: try: cur_ylim = [ self.inv_transform_eval( - (self.transform_eval( - cur_ylim[0], - ax.yaxis) - dy), - ax.yaxis), + ( + self.transform_eval( + cur_ylim[0], ax.yaxis + ) + - dy + ), + ax.yaxis, + ), self.inv_transform_eval( - (self.transform_eval( - cur_ylim[1], - ax.yaxis) - dy), - ax.yaxis)] + ( + self.transform_eval( + cur_ylim[1], ax.yaxis + ) + - dy + ), + ax.yaxis, + ), + ] except (ValueError, OverflowError): cur_ylim = cur_ylim # Keep previous limits if inverted_y: @@ -2205,22 +2591,31 @@ def apply_pan_twinx(self, ax, ax2, event, mode='pan'): update_cursor = True else: if ydata_ax2 < cur_ylim2[1]: - if yscale2 == 'linear': + if yscale2 == "linear": cur_ylim2 -= dy2 else: try: cur_ylim2 = [ self.inv_transform_eval( - (self.transform_eval( - cur_ylim2[0], - ax2.yaxis) - dy2), - ax2.yaxis), + ( + self.transform_eval( + cur_ylim2[0], ax2.yaxis + ) + - dy2 + ), + ax2.yaxis, + ), self.inv_transform_eval( - (self.transform_eval( - cur_ylim2[1], - ax2.yaxis) - dy2), - ax2.yaxis)] + ( + self.transform_eval( + cur_ylim2[1], ax2.yaxis + ) + - dy2 + ), + ax2.yaxis, + ), + ] except (ValueError, OverflowError): cur_ylim2 = cur_ylim2 # Keep previous limits # ax2.set_ylim(cur_ylim2[0],None) @@ -2231,41 +2626,55 @@ def apply_pan_twinx(self, ax, ax2, event, mode='pan'): update_cursor = True else: - if yscale == 'linear': + if yscale == "linear": cur_ylim -= dy else: try: cur_ylim = [ self.inv_transform_eval( - (self.transform_eval( - cur_ylim[0], - ax.yaxis) - dy), - ax.yaxis), + ( + self.transform_eval(cur_ylim[0], ax.yaxis) + - dy + ), + ax.yaxis, + ), self.inv_transform_eval( - (self.transform_eval( - cur_ylim[1], - ax.yaxis) - dy), - ax.yaxis)] + ( + self.transform_eval(cur_ylim[1], ax.yaxis) + - dy + ), + ax.yaxis, + ), + ] except (ValueError, OverflowError): cur_ylim = cur_ylim # Keep previous limits - if yscale2 == 'linear': + if yscale2 == "linear": cur_ylim2 -= dy2 else: try: cur_ylim2 = [ self.inv_transform_eval( - (self.transform_eval( - cur_ylim2[0], - ax2.yaxis) - dy2), - ax2.yaxis), + ( + self.transform_eval( + cur_ylim2[0], ax2.yaxis + ) + - dy2 + ), + ax2.yaxis, + ), self.inv_transform_eval( - (self.transform_eval( - cur_ylim2[1], - ax2.yaxis) - dy2), - ax2.yaxis)] + ( + self.transform_eval( + cur_ylim2[1], ax2.yaxis + ) + - dy2 + ), + ax2.yaxis, + ), + ] except (ValueError, OverflowError): cur_ylim2 = cur_ylim2 # Keep previous limits # ax.set_ylim(cur_ylim) @@ -2293,7 +2702,8 @@ def apply_pan_twinx(self, ax, ax2, event, mode='pan'): ax.figure.canvas.draw_idle() ax.figure.canvas.flush_events() self.background = ax.figure.canvas.copy_from_bbox( - ax.figure.bbox) + ax.figure.bbox + ) self.background_patch_copy.set_visible(False) self.background_ax2_patch_copy.set_visible(False) if self.last_line is not None: @@ -2317,76 +2727,122 @@ def apply_pan_twinx(self, ax, ax2, event, mode='pan'): ax.figure.canvas.flush_events() def update_hover(self): - """ update hover on fast draw (if exist)""" + """update hover on fast draw (if exist)""" if self.hover_instance: if self.compare_xdata and self.hover_instance: - if (self.touch_mode != 'cursor' or len(self._touches) - > 1) and not self.show_compare_cursor: + if ( + self.touch_mode != "cursor" or len(self._touches) > 1 + ) and not self.show_compare_cursor: self.hover_instance.hover_outside_bound = True - elif self.show_compare_cursor and self.touch_mode == 'cursor': + elif self.show_compare_cursor and self.touch_mode == "cursor": self.show_compare_cursor = False else: self.hover_instance.hover_outside_bound = True # update hover pos if needed - elif self.hover_instance.show_cursor and self.x_hover_data is not None and self.y_hover_data is not None: + elif ( + self.hover_instance.show_cursor + and self.x_hover_data is not None + and self.y_hover_data is not None + ): if self.cursor_last_axis: xy_pos = self.cursor_last_axis.transData.transform( - [(self.x_hover_data, self.y_hover_data)]) + [(self.x_hover_data, self.y_hover_data)] + ) else: xy_pos = self.figure.axes[0].transData.transform( - [(self.x_hover_data, self.y_hover_data)]) + [(self.x_hover_data, self.y_hover_data)] + ) self.hover_instance.x_hover_pos = float(xy_pos[0][0]) + self.x self.hover_instance.y_hover_pos = float(xy_pos[0][1]) + self.y - self.hover_instance.xmin_line = float( - self.figure.axes[0].bbox.bounds[0]) + self.x - self.hover_instance.xmax_line = float( - self.figure.axes[0].bbox.bounds[0] + self.figure.axes[0].bbox.bounds[2]) + self.x - self.hover_instance.ymin_line = float( - self.figure.axes[0].bbox.bounds[1]) + self.y - self.hover_instance.ymax_line = float( - self.figure.axes[0].bbox.bounds[1] + self.figure.axes[0].bbox.bounds[3]) + self.y - - if self.hover_instance.x_hover_pos > self.x + self.figure.axes[0].bbox.bounds[2] + self.figure.axes[0].bbox.bounds[0] or \ - self.hover_instance.x_hover_pos < self.x + self.figure.axes[0].bbox.bounds[0] or \ - self.hover_instance.y_hover_pos > self.y + self.figure.axes[0].bbox.bounds[1] + self.figure.axes[0].bbox.bounds[3] or \ - self.hover_instance.y_hover_pos < self.y + self.figure.axes[0].bbox.bounds[1]: + self.hover_instance.xmin_line = ( + float(self.figure.axes[0].bbox.bounds[0]) + self.x + ) + self.hover_instance.xmax_line = ( + float( + self.figure.axes[0].bbox.bounds[0] + + self.figure.axes[0].bbox.bounds[2] + ) + + self.x + ) + self.hover_instance.ymin_line = ( + float(self.figure.axes[0].bbox.bounds[1]) + self.y + ) + self.hover_instance.ymax_line = ( + float( + self.figure.axes[0].bbox.bounds[1] + + self.figure.axes[0].bbox.bounds[3] + ) + + self.y + ) + + if ( + self.hover_instance.x_hover_pos + > self.x + + self.figure.axes[0].bbox.bounds[2] + + self.figure.axes[0].bbox.bounds[0] + or self.hover_instance.x_hover_pos + < self.x + self.figure.axes[0].bbox.bounds[0] + or self.hover_instance.y_hover_pos + > self.y + + self.figure.axes[0].bbox.bounds[1] + + self.figure.axes[0].bbox.bounds[3] + or self.hover_instance.y_hover_pos + < self.y + self.figure.axes[0].bbox.bounds[1] + ): self.hover_instance.hover_outside_bound = True else: self.hover_instance.hover_outside_bound = False def update_selector(self, *args): - """ update selector on fast draw (if exist)""" + """update selector on fast draw (if exist)""" if self.selector: # update selector pos if needed if self.selector.resize_wgt.verts and ( - len(args) != 0 or self.touch_mode != 'selector'): + len(args) != 0 or self.touch_mode != "selector" + ): resize_wgt = self.selector.resize_wgt - if hasattr(resize_wgt, 'shapes'): + if hasattr(resize_wgt, "shapes"): # lasso widget or ellipse if resize_wgt.shapes: - if hasattr(resize_wgt.shapes[0], 'radius_x'): + if hasattr(resize_wgt.shapes[0], "radius_x"): # ellipse widget xy_pos = resize_wgt.ax.transData.transform( - [(resize_wgt.verts[1][0], resize_wgt.verts[1][1])]) + [ + ( + resize_wgt.verts[1][0], + resize_wgt.verts[1][1], + ) + ] + ) new_pos = resize_wgt.to_widget( - *(float(xy_pos[0][0]), float(xy_pos[0][1]))) + *(float(xy_pos[0][0]), float(xy_pos[0][1])) + ) pos0 = new_pos[0] + self.x pos1 = new_pos[1] + self.y xy_pos2 = resize_wgt.ax.transData.transform( - [(resize_wgt.verts[2][0], resize_wgt.verts[2][1])]) + [ + ( + resize_wgt.verts[2][0], + resize_wgt.verts[2][1], + ) + ] + ) new_pos2 = resize_wgt.to_widget( - *(float(xy_pos2[0][0]), float(xy_pos2[0][1]))) + *(float(xy_pos2[0][0]), float(xy_pos2[0][1])) + ) pos0_2 = new_pos2[0] + self.x pos1_2 = new_pos2[1] + self.y current_shape = resize_wgt.shapes[0] dataxy1 = current_shape.selection_point_inst.points - dataxy2 = current_shape.selection_point_inst2.points + dataxy2 = ( + current_shape.selection_point_inst2.points + ) # note: the 2 first points are the same in # current_shape.points @@ -2397,17 +2853,26 @@ def update_selector(self, *args): pos1_2_old = dataxy2[1] old_length = np.sqrt( - (pos0_2_old - pos0_old) ** 2 + - (pos1_2_old - pos1_old) ** 2) + (pos0_2_old - pos0_old) ** 2 + + (pos1_2_old - pos1_old) ** 2 + ) new_length = np.sqrt( - (pos0_2 - pos0)**2 + (pos1_2 - pos1)**2) + (pos0_2 - pos0) ** 2 + (pos1_2 - pos1) ** 2 + ) scale = float(new_length / old_length) xy_pos3 = resize_wgt.ax.transData.transform( - [(resize_wgt.verts[0][0], resize_wgt.verts[0][1])]) + [ + ( + resize_wgt.verts[0][0], + resize_wgt.verts[0][1], + ) + ] + ) new_pos3 = resize_wgt.to_widget( - *(float(xy_pos3[0][0]), float(xy_pos3[0][1]))) + *(float(xy_pos3[0][0]), float(xy_pos3[0][1])) + ) pos0_c = new_pos3[0] + self.x pos1_c = new_pos3[1] + self.y @@ -2417,27 +2882,43 @@ def update_selector(self, *args): for s in resize_wgt.shapes: s.translate(pos=(pos0_c, pos1_c)) - xmin, xmax, ymin, ymax = resize_wgt.shapes[0].get_min_max( - ) + xmin, xmax, ymin, ymax = resize_wgt.shapes[ + 0 + ].get_min_max() else: # lasso widget xy_pos = resize_wgt.ax.transData.transform( - [(resize_wgt.verts[0][0], resize_wgt.verts[0][1])]) + [ + ( + resize_wgt.verts[0][0], + resize_wgt.verts[0][1], + ) + ] + ) new_pos = resize_wgt.to_widget( - *(float(xy_pos[0][0]), float(xy_pos[0][1]))) + *(float(xy_pos[0][0]), float(xy_pos[0][1])) + ) pos0 = new_pos[0] + self.x pos1 = new_pos[1] + self.y xy_pos2 = resize_wgt.ax.transData.transform( - [(resize_wgt.verts[1][0], resize_wgt.verts[1][1])]) + [ + ( + resize_wgt.verts[1][0], + resize_wgt.verts[1][1], + ) + ] + ) new_pos2 = resize_wgt.to_widget( - *(float(xy_pos2[0][0]), float(xy_pos2[0][1]))) + *(float(xy_pos2[0][0]), float(xy_pos2[0][1])) + ) pos0_2 = new_pos2[0] + self.x pos1_2 = new_pos2[1] + self.y current_shape = resize_wgt.shapes[0] - dataxy = np.array( - current_shape.points).reshape(-1, 2) + dataxy = np.array(current_shape.points).reshape( + -1, 2 + ) # note: the 2 first points are the same in # current_shape.points @@ -2448,10 +2929,12 @@ def update_selector(self, *args): pos1_2_old = dataxy[2][1] old_length = np.sqrt( - (pos0_2_old - pos0_old) ** 2 + - (pos1_2_old - pos1_old) ** 2) + (pos0_2_old - pos0_old) ** 2 + + (pos1_2_old - pos1_old) ** 2 + ) new_length = np.sqrt( - (pos0_2 - pos0)**2 + (pos1_2 - pos1)**2) + (pos0_2 - pos0) ** 2 + (pos1_2 - pos1) ** 2 + ) scale = new_length / old_length @@ -2465,73 +2948,105 @@ def update_selector(self, *args): xmin, ymin = dataxy.min(axis=0) if self.collide_point( - * - resize_wgt.to_window( - xmin, - ymin)) and self.collide_point( - * - resize_wgt.to_window( - xmax, - ymax)): + *resize_wgt.to_window(xmin, ymin) + ) and self.collide_point( + *resize_wgt.to_window(xmax, ymax) + ): resize_wgt.opacity = 1 else: resize_wgt.opacity = 0 - elif self.selector.resize_wgt.verts and (len(args) != 0 or self.touch_mode != 'selector'): + elif self.selector.resize_wgt.verts and ( + len(args) != 0 or self.touch_mode != "selector" + ): resize_wgt = self.selector.resize_wgt if not (resize_wgt.size[0] > 1 and resize_wgt.size[1] > 1): return # rectangle or spann selector - if hasattr(resize_wgt, 'span_orientation'): + if hasattr(resize_wgt, "span_orientation"): # span selector - if resize_wgt.span_orientation == 'vertical': + if resize_wgt.span_orientation == "vertical": xy_pos = resize_wgt.ax.transData.transform( - [(resize_wgt.verts[0][0], resize_wgt.verts[0][1])]) + [ + ( + resize_wgt.verts[0][0], + resize_wgt.verts[0][1], + ) + ] + ) new_pos = resize_wgt.to_widget( - *(float(xy_pos[0][0]), float(xy_pos[0][1]))) + *(float(xy_pos[0][0]), float(xy_pos[0][1])) + ) resize_wgt.pos[0] = new_pos[0] + self.x top_bound = float( - self.y + - resize_wgt.ax.bbox.bounds[3] + - resize_wgt.ax.bbox.bounds[1]) + self.y + + resize_wgt.ax.bbox.bounds[3] + + resize_wgt.ax.bbox.bounds[1] + ) bottom_bound = float( - self.y + resize_wgt.ax.bbox.bounds[1]) + self.y + resize_wgt.ax.bbox.bounds[1] + ) resize_wgt.pos[1] = bottom_bound - self.y # recalcul size xy_pos2 = resize_wgt.ax.transData.transform( - [(resize_wgt.verts[3][0], resize_wgt.verts[3][1])]) + [ + ( + resize_wgt.verts[3][0], + resize_wgt.verts[3][1], + ) + ] + ) resize_wgt.size[0] = float( - xy_pos2[0][0] - xy_pos[0][0]) + xy_pos2[0][0] - xy_pos[0][0] + ) resize_wgt.size[1] = top_bound - bottom_bound - elif resize_wgt.span_orientation == 'horizontal': + elif resize_wgt.span_orientation == "horizontal": xy_pos = resize_wgt.ax.transData.transform( - [(resize_wgt.verts[0][0], resize_wgt.verts[0][1])]) + [ + ( + resize_wgt.verts[0][0], + resize_wgt.verts[0][1], + ) + ] + ) new_pos = resize_wgt.to_widget( - *(float(xy_pos[0][0]), float(xy_pos[0][1]))) + *(float(xy_pos[0][0]), float(xy_pos[0][1])) + ) left_bound = float( - self.x + resize_wgt.ax.bbox.bounds[0]) + self.x + resize_wgt.ax.bbox.bounds[0] + ) right_bound = float( - self.x + resize_wgt.ax.bbox.bounds[2] + - resize_wgt.ax.bbox.bounds[0]) + self.x + + resize_wgt.ax.bbox.bounds[2] + + resize_wgt.ax.bbox.bounds[0] + ) width = right_bound - left_bound left_bound, right_bound = resize_wgt.to_widget( - left_bound, right_bound) + left_bound, right_bound + ) resize_wgt.pos[0] = left_bound resize_wgt.pos[1] = new_pos[1] + self.y # recalcul size xy_pos2 = resize_wgt.ax.transData.transform( - [(resize_wgt.verts[0][1], resize_wgt.verts[1][1])]) + [ + ( + resize_wgt.verts[0][1], + resize_wgt.verts[1][1], + ) + ] + ) resize_wgt.size[0] = width resize_wgt.size[1] = float( - xy_pos2[0][1] - xy_pos[0][1]) + xy_pos2[0][1] - xy_pos[0][1] + ) else: # rectangle selector @@ -2539,156 +3054,202 @@ def update_selector(self, *args): # update all selector pts # recalcul pos xy_pos = resize_wgt.ax.transData.transform( - [(resize_wgt.verts[0][0], resize_wgt.verts[0][1])]) + [(resize_wgt.verts[0][0], resize_wgt.verts[0][1])] + ) new_pos = resize_wgt.to_widget( - *(float(xy_pos[0][0]), float(xy_pos[0][1]))) + *(float(xy_pos[0][0]), float(xy_pos[0][1])) + ) resize_wgt.pos[0] = new_pos[0] + self.x resize_wgt.pos[1] = new_pos[1] + self.y # recalcul size xy_pos2 = resize_wgt.ax.transData.transform( - [(resize_wgt.verts[2][0], resize_wgt.verts[2][1])]) + [(resize_wgt.verts[2][0], resize_wgt.verts[2][1])] + ) resize_wgt.size[0] = float( - xy_pos2[0][0] - xy_pos[0][0]) + xy_pos2[0][0] - xy_pos[0][0] + ) resize_wgt.size[1] = float( - xy_pos2[0][1] - xy_pos[0][1]) + xy_pos2[0][1] - xy_pos[0][1] + ) if self.collide_point( - * - resize_wgt.to_window( - resize_wgt.pos[0], - resize_wgt.pos[1])) and self.collide_point( - * - resize_wgt.to_window( - resize_wgt.pos[0] + - resize_wgt.size[0], - resize_wgt.pos[1] + - resize_wgt.size[1])): + *resize_wgt.to_window( + resize_wgt.pos[0], resize_wgt.pos[1] + ) + ) and self.collide_point( + *resize_wgt.to_window( + resize_wgt.pos[0] + resize_wgt.size[0], + resize_wgt.pos[1] + resize_wgt.size[1], + ) + ): resize_wgt.opacity = 1 else: resize_wgt.opacity = 0 def min_max(self, event): - """ manage min/max touch mode """ + """manage min/max touch mode""" ax = self.figure.axes[0] # left axis - xlabelbottom = ax.xaxis._major_tick_kw.get('tick1On') - ylabelleft = ax.yaxis._major_tick_kw.get('tick1On') - ylabelright = ax.yaxis._major_tick_kw.get('tick2On') + xlabelbottom = ax.xaxis._major_tick_kw.get("tick1On") + ylabelleft = ax.yaxis._major_tick_kw.get("tick1On") + ylabelright = ax.yaxis._major_tick_kw.get("tick2On") - if xlabelbottom and event.x > self.x + ax.bbox.bounds[0] and \ - event.x < self.x + ax.bbox.bounds[2] + ax.bbox.bounds[0] and \ - event.y < self.y + ax.bbox.bounds[1]: + if ( + xlabelbottom + and event.x > self.x + ax.bbox.bounds[0] + and event.x < self.x + ax.bbox.bounds[2] + ax.bbox.bounds[0] + and event.y < self.y + ax.bbox.bounds[1] + ): right_lim = self.x + ax.bbox.bounds[2] + ax.bbox.bounds[0] left_lim = self.x + ax.bbox.bounds[0] - left_anchor_zone = (right_lim - left_lim) * .2 + left_lim - right_anchor_zone = (right_lim - left_lim) * .8 + left_lim + left_anchor_zone = (right_lim - left_lim) * 0.2 + left_lim + right_anchor_zone = (right_lim - left_lim) * 0.8 + left_lim if event.x < left_anchor_zone or event.x > right_anchor_zone: if self.text_instance: if not self.text_instance.show_text: midpoint = ( - (self.x + ax.bbox.bounds[2] + ax.bbox.bounds[0]) + - (self.x + ax.bbox.bounds[0])) / 2 + (self.x + ax.bbox.bounds[2] + ax.bbox.bounds[0]) + + (self.x + ax.bbox.bounds[0]) + ) / 2 if event.x < midpoint: - anchor = 'left' + anchor = "left" self.text_instance.x_text_pos = float( - self.x + ax.bbox.bounds[0]) - self.text_instance.y_text_pos = float( - self.y + ax.bbox.bounds[1]) - self.text_instance.text_height + self.x + ax.bbox.bounds[0] + ) + self.text_instance.y_text_pos = ( + float(self.y + ax.bbox.bounds[1]) + - self.text_instance.text_height + ) self.text_instance.offset_text = False else: - anchor = 'right' + anchor = "right" self.text_instance.x_text_pos = float( - self.x + ax.bbox.bounds[2] + ax.bbox.bounds[0]) - self.text_instance.y_text_pos = float( - self.y + ax.bbox.bounds[1]) - self.text_instance.text_height + self.x + ax.bbox.bounds[2] + ax.bbox.bounds[0] + ) + self.text_instance.y_text_pos = ( + float(self.y + ax.bbox.bounds[1]) + - self.text_instance.text_height + ) self.text_instance.offset_text = True self.text_instance.current_axis = ax self.text_instance.kind = { - 'axis': 'x', 'anchor': anchor} + "axis": "x", + "anchor": anchor, + } self.text_instance.show_text = True return - elif ylabelleft and event.x < self.x + ax.bbox.bounds[0] and \ - event.y < self.y + ax.bbox.bounds[1] + ax.bbox.bounds[3] and \ - event.y > self.y + ax.bbox.bounds[1]: + elif ( + ylabelleft + and event.x < self.x + ax.bbox.bounds[0] + and event.y < self.y + ax.bbox.bounds[1] + ax.bbox.bounds[3] + and event.y > self.y + ax.bbox.bounds[1] + ): top_lim = self.y + ax.bbox.bounds[3] + ax.bbox.bounds[1] bottom_lim = self.y + ax.bbox.bounds[1] - bottom_anchor_zone = (top_lim - bottom_lim) * .2 + bottom_lim - top_anchor_zone = (top_lim - bottom_lim) * .8 + bottom_lim + bottom_anchor_zone = (top_lim - bottom_lim) * 0.2 + bottom_lim + top_anchor_zone = (top_lim - bottom_lim) * 0.8 + bottom_lim if event.y < bottom_anchor_zone or event.y > top_anchor_zone: if self.text_instance: if not self.text_instance.show_text: midpoint = ( - (self.y + ax.bbox.bounds[3] + ax.bbox.bounds[1]) + - (self.y + ax.bbox.bounds[1])) / 2 + (self.y + ax.bbox.bounds[3] + ax.bbox.bounds[1]) + + (self.y + ax.bbox.bounds[1]) + ) / 2 if event.y > midpoint: - anchor = 'top' + anchor = "top" self.text_instance.x_text_pos = float( - self.x + ax.bbox.bounds[0]) - dp(40) - self.text_instance.y_text_pos = float( - self.y + ax.bbox.bounds[1] + ax.bbox.bounds[3]) - self.text_instance.text_height + self.x + ax.bbox.bounds[0] + ) - dp(40) + self.text_instance.y_text_pos = ( + float( + self.y + + ax.bbox.bounds[1] + + ax.bbox.bounds[3] + ) + - self.text_instance.text_height + ) self.text_instance.offset_text = False else: - anchor = 'bottom' + anchor = "bottom" self.text_instance.x_text_pos = float( - self.x + ax.bbox.bounds[0]) - dp(40) + self.x + ax.bbox.bounds[0] + ) - dp(40) self.text_instance.y_text_pos = float( - self.y + ax.bbox.bounds[1]) - dp(6) + self.y + ax.bbox.bounds[1] + ) - dp(6) self.text_instance.offset_text = False self.text_instance.current_axis = ax self.text_instance.kind = { - 'axis': 'y', 'anchor': anchor} + "axis": "y", + "anchor": anchor, + } self.text_instance.show_text = True return - elif ylabelright and event.x > self.x + ax.bbox.bounds[2] + ax.bbox.bounds[0] and \ - event.y < self.y + ax.bbox.bounds[1] + ax.bbox.bounds[3] and \ - event.y > self.y + ax.bbox.bounds[1]: + elif ( + ylabelright + and event.x > self.x + ax.bbox.bounds[2] + ax.bbox.bounds[0] + and event.y < self.y + ax.bbox.bounds[1] + ax.bbox.bounds[3] + and event.y > self.y + ax.bbox.bounds[1] + ): top_lim = self.y + ax.bbox.bounds[3] + ax.bbox.bounds[1] bottom_lim = self.y + ax.bbox.bounds[1] - bottom_anchor_zone = (top_lim - bottom_lim) * .2 + bottom_lim - top_anchor_zone = (top_lim - bottom_lim) * .8 + bottom_lim + bottom_anchor_zone = (top_lim - bottom_lim) * 0.2 + bottom_lim + top_anchor_zone = (top_lim - bottom_lim) * 0.8 + bottom_lim if event.y < bottom_anchor_zone or event.y > top_anchor_zone: if self.text_instance: if not self.text_instance.show_text: midpoint = ( - (self.y + ax.bbox.bounds[3] + ax.bbox.bounds[1]) + - (self.y + ax.bbox.bounds[1])) / 2 + (self.y + ax.bbox.bounds[3] + ax.bbox.bounds[1]) + + (self.y + ax.bbox.bounds[1]) + ) / 2 if event.y > midpoint: - anchor = 'top' + anchor = "top" self.text_instance.x_text_pos = float( - self.x + ax.bbox.bounds[0] + ax.bbox.bounds[2]) + dp(40) - self.text_instance.y_text_pos = float( - self.y + ax.bbox.bounds[1] + ax.bbox.bounds[3]) - self.text_instance.text_height + self.x + ax.bbox.bounds[0] + ax.bbox.bounds[2] + ) + dp(40) + self.text_instance.y_text_pos = ( + float( + self.y + + ax.bbox.bounds[1] + + ax.bbox.bounds[3] + ) + - self.text_instance.text_height + ) self.text_instance.offset_text = True else: - anchor = 'bottom' + anchor = "bottom" self.text_instance.x_text_pos = float( - self.x + ax.bbox.bounds[0] + ax.bbox.bounds[2]) + dp(40) + self.x + ax.bbox.bounds[0] + ax.bbox.bounds[2] + ) + dp(40) self.text_instance.y_text_pos = float( - self.y + ax.bbox.bounds[1]) - dp(6) + self.y + ax.bbox.bounds[1] + ) - dp(6) self.text_instance.offset_text = True # left axis self.text_instance.current_axis = self.figure.axes[1] self.text_instance.kind = { - 'axis': 'y', 'anchor': anchor} + "axis": "y", + "anchor": anchor, + } self.text_instance.show_text = True return def apply_drag_legend(self, ax, event): - """ drag legend method """ + """drag legend method""" dx = event.x - self._last_touch_pos[event][0] if not self.legend_do_scroll_x: @@ -2709,7 +3270,11 @@ def apply_drag_legend(self, ax, event): legend_y = bbox.ymin loc_in_canvas = legend_x + dx, legend_y + dy - loc_in_norm_axes = legend.parent.transAxes.inverted().transform_point(loc_in_canvas) + loc_in_norm_axes = ( + legend.parent.transAxes.inverted().transform_point( + loc_in_canvas + ) + ) legend._loc = tuple(loc_in_norm_axes) # use blit method @@ -2718,7 +3283,8 @@ def apply_drag_legend(self, ax, event): ax.figure.canvas.draw_idle() ax.figure.canvas.flush_events() self.background = ax.figure.canvas.copy_from_bbox( - ax.figure.bbox) + ax.figure.bbox + ) legend.set_visible(True) if self.last_line is not None: self.clear_line_prop() @@ -2732,7 +3298,7 @@ def apply_drag_legend(self, ax, event): self.current_legend.update_size() def zoom_factory(self, event, ax, base_scale=1.1): - """ zoom with scrolling mouse method """ + """zoom with scrolling mouse method""" newcoord = self.to_widget(event.x, event.y, relative=True) x = newcoord[0] @@ -2747,7 +3313,7 @@ def zoom_factory(self, event, ax, base_scale=1.1): scale = ax.get_xscale() yscale = ax.get_yscale() - if scale == 'linear': + if scale == "linear": old_min = cur_xlim[0] old_max = cur_xlim[1] @@ -2758,7 +3324,7 @@ def zoom_factory(self, event, ax, base_scale=1.1): xdata = self.transform_eval(xdata, ax.yaxis) old_max = self.transform_eval(max_, ax.yaxis) - if yscale == 'linear': + if yscale == "linear": yold_min = cur_ylim[0] yold_max = cur_ylim[1] @@ -2769,10 +3335,10 @@ def zoom_factory(self, event, ax, base_scale=1.1): ydata = self.transform_eval(ydata, ax.yaxis) yold_max = self.transform_eval(ymax_, ax.yaxis) - if event.button == 'scrolldown': + if event.button == "scrolldown": # deal with zoom in scale_factor = 1 / base_scale - elif event.button == 'scrollup': + elif event.button == "scrollup": # deal with zoom out scale_factor = base_scale else: @@ -2787,36 +3353,44 @@ def zoom_factory(self, event, ax, base_scale=1.1): rely = (yold_max - ydata) / (yold_max - yold_min) if self.do_zoom_x: - if scale == 'linear': - ax.set_xlim([xdata - new_width * (1 - relx), - xdata + new_width * (relx)]) + if scale == "linear": + ax.set_xlim( + [ + xdata - new_width * (1 - relx), + xdata + new_width * (relx), + ] + ) else: new_min = xdata - new_width * (1 - relx) new_max = xdata + new_width * (relx) try: new_min, new_max = self.inv_transform_eval( - new_min, ax.yaxis), self.inv_transform_eval( - new_max, ax.yaxis) + new_min, ax.yaxis + ), self.inv_transform_eval(new_max, ax.yaxis) except OverflowError: # Limit case new_min, new_max = min_, max_ - if new_min <= 0. or new_max <= 0.: # Limit case + if new_min <= 0.0 or new_max <= 0.0: # Limit case new_min, new_max = min_, max_ ax.set_xlim([new_min, new_max]) if self.do_zoom_y: - if yscale == 'linear': - ax.set_ylim([ydata - new_height * (1 - rely), - ydata + new_height * (rely)]) + if yscale == "linear": + ax.set_ylim( + [ + ydata - new_height * (1 - rely), + ydata + new_height * (rely), + ] + ) else: new_ymin = ydata - new_height * (1 - rely) new_ymax = ydata + new_height * (rely) try: new_ymin, new_ymax = self.inv_transform_eval( - new_ymin, ax.yaxis), self.inv_transform_eval( - new_ymax, ax.yaxis) + new_ymin, ax.yaxis + ), self.inv_transform_eval(new_ymax, ax.yaxis) except OverflowError: # Limit case new_ymin, new_ymax = ymin_, ymax_ - if new_ymin <= 0. or new_ymax <= 0.: # Limit case + if new_ymin <= 0.0 or new_ymax <= 0.0: # Limit case new_ymin, new_ymax = ymin_, ymax_ ax.set_ylim([new_ymin, new_ymax]) @@ -2826,7 +3400,7 @@ def zoom_factory(self, event, ax, base_scale=1.1): ax.figure.canvas.draw_idle() ax.figure.canvas.flush_events() - def zoom_factory_twin(self, event, ax, ax2, base_scale=2.): + def zoom_factory_twin(self, event, ax, ax2, base_scale=2.0): """twin axis zoom method from scroll mouse""" x, y = event.x, event.y cur_xlim = ax.get_xlim() @@ -2843,7 +3417,7 @@ def zoom_factory_twin(self, event, ax, ax2, base_scale=2.): yscale = ax.get_yscale() yscale2 = ax2.get_yscale() - if scale == 'linear': + if scale == "linear": old_min = cur_xlim[0] old_max = cur_xlim[1] @@ -2854,7 +3428,7 @@ def zoom_factory_twin(self, event, ax, ax2, base_scale=2.): xdata = self.transform_eval(xdata, ax.yaxis) old_max = self.transform_eval(max_, ax.yaxis) - if yscale == 'linear': + if yscale == "linear": yold_min = cur_ylim[0] yold_max = cur_ylim[1] @@ -2865,10 +3439,10 @@ def zoom_factory_twin(self, event, ax, ax2, base_scale=2.): ydata = self.transform_eval(ydata, ax.yaxis) yold_max = self.transform_eval(ymax_, ax.yaxis) - if event.button == 'scrolldown': + if event.button == "scrolldown": # deal with zoom in scale_factor = 1 / base_scale - elif event.button == 'scrollup': + elif event.button == "scrollup": # deal with zoom out scale_factor = base_scale else: @@ -2883,40 +3457,48 @@ def zoom_factory_twin(self, event, ax, ax2, base_scale=2.): rely = (yold_max - ydata) / (yold_max - yold_min) if self.do_zoom_x: - if scale == 'linear': - ax.set_xlim([xdata - new_width * (1 - relx), - xdata + new_width * (relx)]) + if scale == "linear": + ax.set_xlim( + [ + xdata - new_width * (1 - relx), + xdata + new_width * (relx), + ] + ) else: new_min = xdata - new_width * (1 - relx) new_max = xdata + new_width * (relx) try: new_min, new_max = self.inv_transform_eval( - new_min, ax.yaxis), self.inv_transform_eval( - new_max, ax.yaxis) + new_min, ax.yaxis + ), self.inv_transform_eval(new_max, ax.yaxis) except OverflowError: # Limit case new_min, new_max = min_, max_ - if new_min <= 0. or new_max <= 0.: # Limit case + if new_min <= 0.0 or new_max <= 0.0: # Limit case new_min, new_max = min_, max_ ax.set_xlim([new_min, new_max]) if self.do_zoom_y: - if yscale == 'linear': - ax.set_ylim([ydata - new_height * (1 - rely), - ydata + new_height * (rely)]) + if yscale == "linear": + ax.set_ylim( + [ + ydata - new_height * (1 - rely), + ydata + new_height * (rely), + ] + ) else: new_ymin = ydata - new_height * (1 - rely) new_ymax = ydata + new_height * (rely) try: new_ymin, new_ymax = self.inv_transform_eval( - new_ymin, ax.yaxis), self.inv_transform_eval( - new_ymax, ax.yaxis) + new_ymin, ax.yaxis + ), self.inv_transform_eval(new_ymax, ax.yaxis) except OverflowError: # Limit case new_ymin, new_ymax = ymin_, ymax_ - if new_ymin <= 0. or new_ymax <= 0.: # Limit case + if new_ymin <= 0.0 or new_ymax <= 0.0: # Limit case new_ymin, new_ymax = ymin_, ymax_ ax.set_ylim([new_ymin, new_ymax]) - if yscale2 == 'linear': + if yscale2 == "linear": yold2_min = cur_ylim2[0] yold2_max = cur_ylim2[1] @@ -2931,22 +3513,30 @@ def zoom_factory_twin(self, event, ax, ax2, base_scale=2.): rely2 = (yold2_max - ydata2) / (yold2_max - yold2_min) if self.do_zoom_y: - ax2.set_ylim([ydata2 - new_height2 * (1 - rely2), - ydata2 + new_height2 * (rely2)]) - - if yscale2 == 'linear': - ax2.set_ylim([ydata2 - new_height2 * (1 - rely2), - ydata2 + new_height2 * (rely2)]) + ax2.set_ylim( + [ + ydata2 - new_height2 * (1 - rely2), + ydata2 + new_height2 * (rely2), + ] + ) + + if yscale2 == "linear": + ax2.set_ylim( + [ + ydata2 - new_height2 * (1 - rely2), + ydata2 + new_height2 * (rely2), + ] + ) else: new_ymin2 = ydata2 - new_height2 * (1 - rely2) new_ymax2 = ydata2 + new_height2 * (rely2) try: new_ymin2, new_ymax2 = self.inv_transform_eval( - new_ymin2, ax2.yaxis), self.inv_transform_eval( - new_ymax2, ax2.yaxis) + new_ymin2, ax2.yaxis + ), self.inv_transform_eval(new_ymax2, ax2.yaxis) except OverflowError: # Limit case new_ymin2, new_ymax2 = ymin2_, ymax2_ - if new_ymin2 <= 0. or new_ymax2 <= 0.: # Limit case + if new_ymin2 <= 0.0 or new_ymax2 <= 0.0: # Limit case new_ymin2, new_ymax2 = ymin2_, ymax2_ ax2.set_ylim([new_ymin2, new_ymax2]) @@ -2957,7 +3547,7 @@ def zoom_factory_twin(self, event, ax, ax2, base_scale=2.): ax.figure.canvas.flush_events() def _onSize(self, o, size): - """ _onsize method """ + """_onsize method""" if self.figure is None: return # Create a new, correctly sized bitmap @@ -2972,7 +3562,7 @@ def _onSize(self, o, size): hinch = self._height / dpival self.figure.set_size_inches(winch, hinch) - s = 'resize_event' + s = "resize_event" event = ResizeEvent(s, self.figcanvas) self.figcanvas.callbacks.process(s, event) self.figcanvas.draw_idle() @@ -2991,7 +3581,7 @@ def _onSize(self, o, size): Clock.schedule_once(self.update_selector) def update_lim(self): - """ update axis lim if zoombox is used""" + """update axis lim if zoombox is used""" ax = self.figure.axes[0] ax2 = self.figure.axes[1] self.do_update = False @@ -3024,45 +3614,54 @@ def update_lim(self): range_old = cur_ylim[1] - cur_ylim[0] range_old2 = cur_ylim2[1] - cur_ylim2[0] - ymin2 = (min(self.y0_box, self.y1_box) - - cur_ylim[0]) / range_old * range_old2 + cur_ylim2[0] - ymax2 = (max(self.y0_box, self.y1_box) - - cur_ylim[0]) / range_old * range_old2 + cur_ylim2[0] + ymin2 = ( + min(self.y0_box, self.y1_box) - cur_ylim[0] + ) / range_old * range_old2 + cur_ylim2[0] + ymax2 = ( + max(self.y0_box, self.y1_box) - cur_ylim[0] + ) / range_old * range_old2 + cur_ylim2[0] if inverted_x: ax.set_xlim( - right=min( - self.x0_box, self.x1_box), left=max( - self.x0_box, self.x1_box)) + right=min(self.x0_box, self.x1_box), + left=max(self.x0_box, self.x1_box), + ) else: ax.set_xlim( - left=min( - self.x0_box, self.x1_box), right=max( - self.x0_box, self.x1_box)) + left=min(self.x0_box, self.x1_box), + right=max(self.x0_box, self.x1_box), + ) if inverted_y: ax.set_ylim( - top=min( - self.y0_box, self.y1_box), bottom=max( - self.y0_box, self.y1_box)) + top=min(self.y0_box, self.y1_box), + bottom=max(self.y0_box, self.y1_box), + ) else: ax.set_ylim( - bottom=min( - self.y0_box, self.y1_box), top=max( - self.y0_box, self.y1_box)) + bottom=min(self.y0_box, self.y1_box), + top=max(self.y0_box, self.y1_box), + ) if inverted_y2: ax2.set_ylim(top=ymin2, bottom=ymax2) else: ax2.set_ylim(bottom=ymin2, top=ymax2) def reset_box(self): - """ reset zoombox and apply zoombox limit if zoombox option if selected""" + """reset zoombox and apply zoombox limit if zoombox option if selected""" if min(abs(self._box_size[0]), abs(self._box_size[1])) > self.minzoom: trans = self.figure.axes[0].transData.inverted() self.x0_box, self.y0_box = trans.transform_point( - (self._box_pos[0] - self.pos[0], self._box_pos[1] - self.pos[1])) + ( + self._box_pos[0] - self.pos[0], + self._box_pos[1] - self.pos[1], + ) + ) self.x1_box, self.y1_box = trans.transform_point( - (self._box_size[0] + self._box_pos[0] - self.pos[0], - self._box_size[1] + self._box_pos[1] - self.pos[1])) + ( + self._box_size[0] + self._box_pos[0] - self.pos[0], + self._box_size[1] + self._box_pos[1] - self.pos[1], + ) + ) self.do_update = True self._box_size = 0, 0 @@ -3079,7 +3678,7 @@ def reset_box(self): self.invert_rect_ver = False def draw_box(self, event, x0, y0, x1, y1) -> None: - """ Draw zoombox method + """Draw zoombox method Args: event: touch kivy event @@ -3102,7 +3701,8 @@ def draw_box(self, event, x0, y0, x1, y1) -> None: trans = self.figure.axes[0].transData.inverted() xdata, ydata = trans.transform_point( - (event.x - pos_x, event.y - pos_y)) + (event.x - pos_x, event.y - pos_y) + ) # xmin,xmax=self.figure.axes[0].get_xlim() # ymin,ymax=self.figure.axes[0].get_ylim() @@ -3236,9 +3836,10 @@ class FakeEventTwinx: y: None -Factory.register('MatplotFigureTwinx', MatplotFigureTwinx) +Factory.register("MatplotFigureTwinx", MatplotFigureTwinx) -Builder.load_string(''' +Builder.load_string( + """ canvas: Color: @@ -3296,4 +3897,5 @@ class FakeEventTwinx: (self.pos_x_rect_ver-dp(20),self.pos_y_rect_ver-dp(4)+self._box_size[1] \ if root.invert_rect_hor else self.pos_y_rect_ver+dp(1)+self._box_size[1], \ dp(40),dp(4)) - ''') + """ +) diff --git a/kivy_matplotlib_widget/uix/hover_widget.py b/kivy_matplotlib_widget/uix/hover_widget.py index f2245f8..b19f8ec 100644 --- a/kivy_matplotlib_widget/uix/hover_widget.py +++ b/kivy_matplotlib_widget/uix/hover_widget.py @@ -7,7 +7,7 @@ NumericProperty, StringProperty, BooleanProperty, - ColorProperty + ColorProperty, ) from kivy.lang import Builder @@ -17,13 +17,14 @@ def add_hover( - figure_wgt, - mode='touch', - label_x='x', - label_y='y', - hover_widget=None, - hover_type='nearest'): - """ add hover to matpotlib figure + figure_wgt, + mode="touch", + label_x="x", + label_y="y", + hover_widget=None, + hover_type="nearest", +): + """add hover to matpotlib figure Args: figure_wgt: figure widget from kivy_matplotlib_widget package @@ -33,7 +34,7 @@ def add_hover( if figure_wgt.hover_instance: - if hover_type == 'compare': + if hover_type == "compare": if not figure_wgt.compare_hover_instance: if hover_widget is None: hover_widget = GeneralCompareHover() @@ -70,12 +71,12 @@ def add_hover( figure_wgt.compare_hover_instance.show_cursor = False else: if hover_widget is None: - if hover_type == 'compare': + if hover_type == "compare": hover_widget = GeneralCompareHover() else: hover_widget = GeneralHover() - if hover_type == 'compare': + if hover_type == "compare": hover_widget.create_child(figure_wgt.lines) figure_wgt.compare_hover_instance = hover_widget figure_wgt.compare_xdata = True @@ -90,13 +91,14 @@ def add_hover( figure_wgt.parent.add_widget(hover_widget) figure_wgt.hover_instance = hover_widget - if mode == 'desktop': + if mode == "desktop": figure_wgt.hover_on = True Window.bind(mouse_pos=figure_wgt.on_motion) class BaseHoverFloatLayout(FloatLayout): - """ Touch egend kivy class""" + """Touch egend kivy class""" + figure_wgt = ObjectProperty(None) x_hover_pos = NumericProperty(1) y_hover_pos = NumericProperty(1) @@ -106,12 +108,12 @@ class BaseHoverFloatLayout(FloatLayout): ymax_line = NumericProperty(1) hover_outside_bound = BooleanProperty(False) show_cursor = BooleanProperty(False) - label_x = StringProperty('x') - label_y = StringProperty('y') - label_x_value = StringProperty('') - label_y_value = StringProperty('') + label_x = StringProperty("x") + label_y = StringProperty("y") + label_x_value = StringProperty("") + label_y_value = StringProperty("") # futur used for dynamic label - custom_label = StringProperty('', allownone=True) + custom_label = StringProperty("", allownone=True) # futur used for dynamic color custom_color = ColorProperty([0, 0, 0, 1], allownone=True) figwidth = NumericProperty(2) @@ -120,11 +122,11 @@ class BaseHoverFloatLayout(FloatLayout): figy = NumericProperty(0) def __init__(self, **kwargs): - """ init class """ + """init class""" super().__init__(**kwargs) def reset_hover(self): - """ reset hover attribute """ + """reset hover attribute""" self.x_hover_pos = 1 self.y_hover_pos = 1 self.ymin_line = 1 @@ -132,7 +134,8 @@ def reset_hover(self): class GeneralHover(BaseHoverFloatLayout): - """ GeneralHover """ + """GeneralHover""" + text_color = ColorProperty([0, 0, 0, 1]) text_font = StringProperty("Roboto") text_size = NumericProperty(dp(14)) @@ -140,12 +143,13 @@ class GeneralHover(BaseHoverFloatLayout): hover_height = NumericProperty(dp(24)) def __init__(self, **kwargs): - """ init class """ + """init class""" super().__init__(**kwargs) class HoverVerticalText(BaseHoverFloatLayout): - """ Hover with vertical text""" + """Hover with vertical text""" + text_color = ColorProperty([0, 0, 0, 1]) text_font = StringProperty("Roboto") text_size = NumericProperty(dp(14)) @@ -153,12 +157,13 @@ class HoverVerticalText(BaseHoverFloatLayout): hover_height = NumericProperty(dp(48)) def __init__(self, **kwargs): - """ init class """ + """init class""" super().__init__(**kwargs) class GeneralCompareHover(BaseHoverFloatLayout): - """ GeneralCompareHover""" + """GeneralCompareHover""" + text_color = ColorProperty([0, 0, 0, 1]) text_font = StringProperty("Roboto") text_size = NumericProperty(dp(14)) @@ -167,7 +172,7 @@ class GeneralCompareHover(BaseHoverFloatLayout): y_touch_pos = NumericProperty(1) def __init__(self, **kwargs): - """ init class """ + """init class""" super().__init__(**kwargs) self.children_names = [] self.children_list = [] @@ -194,7 +199,8 @@ def create_child(self, lines): class BoxShadowCompareHover(BaseHoverFloatLayout): - """ GeneralCompareHover with a box shadow""" + """GeneralCompareHover with a box shadow""" + text_color = ColorProperty([0, 0, 0, 1]) text_font = StringProperty("Roboto") text_size = NumericProperty(dp(14)) @@ -204,7 +210,7 @@ class BoxShadowCompareHover(BaseHoverFloatLayout): reorder_data = BooleanProperty(True) def __init__(self, **kwargs): - """ init class """ + """init class""" super(BoxShadowCompareHover, self).__init__(**kwargs) self.children_names = [] self.children_list = [] @@ -230,7 +236,7 @@ def create_child(self, lines): self.children_list.append(mywidget) def overlap_check(self): - """ reorder label base on y_hover_pos of data""" + """reorder label base on y_hover_pos of data""" if self.reorder_data and len(self.ids.main_box.children) > 2: y_hover_pos_list = [] child_list = [] @@ -249,41 +255,44 @@ def overlap_check(self): class CompareHoverBox(BoxLayout): - """ Hover with vertical text""" + """Hover with vertical text""" + text_color = ColorProperty([0, 0, 0, 1]) text_font = StringProperty("Roboto") text_size = NumericProperty(dp(14)) - label_y = StringProperty('y') - label_y_value = StringProperty('y') + label_y = StringProperty("y") + label_y_value = StringProperty("y") x_hover_pos = NumericProperty(1) y_hover_pos = NumericProperty(1) show_widget = BooleanProperty(False) custom_color = ColorProperty([0, 0, 0, 1], allownone=True) def __init__(self, **kwargs): - """ init class """ + """init class""" super().__init__(**kwargs) class DotCompareHoverBox(FloatLayout): - """ Hover with vertical text""" + """Hover with vertical text""" + text_color = ColorProperty([0, 0, 0, 1]) text_font = StringProperty("Roboto") text_size = NumericProperty(dp(14)) - label_y = StringProperty('y') - label_y_value = StringProperty('y') + label_y = StringProperty("y") + label_y_value = StringProperty("y") x_hover_pos = NumericProperty(1) y_hover_pos = NumericProperty(1) show_widget = BooleanProperty(False) custom_color = ColorProperty([0, 0, 0, 1], allownone=True) def __init__(self, **kwargs): - """ init class """ + """init class""" super().__init__(**kwargs) class TagCompareHover(BaseHoverFloatLayout): - """ TagCompareHover""" + """TagCompareHover""" + text_color = ColorProperty([0, 0, 0, 1]) text_font = StringProperty("Roboto") text_size = NumericProperty(dp(14)) @@ -292,7 +301,7 @@ class TagCompareHover(BaseHoverFloatLayout): y_touch_pos = NumericProperty(1) def __init__(self, **kwargs): - """ init class """ + """init class""" super().__init__(**kwargs) self.children_names = [] self.children_list = [] @@ -330,22 +339,28 @@ def overlap_check(self): for index in range(len(sorting_args) - 1): # chneck overlap - if y_pos_list[sorting_args[index + 1]] - heigh_child / 2 <= y_pos_list[sorting_args[index] - ] + heigh_child / 2 and child_list[sorting_args[index + 1]].show_widget: - offset = -((y_pos_list[sorting_args[index + 1]] - heigh_child / 2) - ( - y_pos_list[sorting_args[index]] + heigh_child / 2)) + if ( + y_pos_list[sorting_args[index + 1]] - heigh_child / 2 + <= y_pos_list[sorting_args[index]] + heigh_child / 2 + and child_list[sorting_args[index + 1]].show_widget + ): + offset = -( + (y_pos_list[sorting_args[index + 1]] - heigh_child / 2) + - (y_pos_list[sorting_args[index]] + heigh_child / 2) + ) y_pos_list[sorting_args[index + 1]] += offset child_list[sorting_args[index + 1]].hover_offset = offset class TagCompareHoverBox(FloatLayout): - """ Hover with vertical text""" + """Hover with vertical text""" + text_color = ColorProperty([0, 0, 0, 1]) text_font = StringProperty("Roboto") text_size = NumericProperty(dp(14)) - label_y = StringProperty('y') - label_y_value = StringProperty('y') + label_y = StringProperty("y") + label_y_value = StringProperty("y") x_hover_pos = NumericProperty(1) y_hover_pos = NumericProperty(1) show_widget = BooleanProperty(False) @@ -353,27 +368,29 @@ class TagCompareHoverBox(FloatLayout): hover_offset = NumericProperty(0) def __init__(self, **kwargs): - """ init class """ + """init class""" super().__init__(**kwargs) class InfoHover(BaseHoverFloatLayout): - """ InfoHover adapt the background and the font color with the line or scatter color""" + """InfoHover adapt the background and the font color with the line or scatter color""" + text_color = ColorProperty([0, 0, 0, 1]) text_font = StringProperty("Roboto") text_size = NumericProperty(dp(14)) hover_height = NumericProperty(dp(48)) def __init__(self, **kwargs): - """ init class """ + """init class""" super().__init__(**kwargs) class MatplotlibStyleHover(BaseHoverFloatLayout): """MatplotlibStyleHover look like matplotlib cursor but do not use matplotlib draw. - Usefull in live blit drawing + Usefull in live blit drawing """ + text_color = ColorProperty([0, 0, 0, 1]) text_font = StringProperty("Roboto") text_size = NumericProperty(dp(14)) @@ -381,12 +398,13 @@ class MatplotlibStyleHover(BaseHoverFloatLayout): background_color = ColorProperty([1, 1, 1, 1]) def __init__(self, **kwargs): - """ init class """ + """init class""" super().__init__(**kwargs) class HightChartHover(BaseHoverFloatLayout): - """ PlotlyHover adapt the background and the font color with the line or scatter color""" + """PlotlyHover adapt the background and the font color with the line or scatter color""" + text_color = ColorProperty([0, 0, 0, 1]) text_font = StringProperty("Roboto") text_size = NumericProperty(dp(14)) @@ -396,60 +414,65 @@ class HightChartHover(BaseHoverFloatLayout): label_padding_height = NumericProperty(dp(8)) position = OptionProperty( - 'top', + "top", options=( - 'top', - 'bottom', - 'left', - 'right', - 'top_start', - 'top_end', - 'bottom_start', - 'bottom_end', - 'left_start', - 'left_end', - 'right_start', - 'right_end')) + "top", + "bottom", + "left", + "right", + "top_start", + "top_end", + "bottom_start", + "bottom_end", + "left_start", + "left_end", + "right_start", + "right_end", + ), + ) def __init__(self, **kwargs): - """ init class """ + """init class""" super().__init__(**kwargs) class RichTooltip(BoxLayout): position = OptionProperty( - 'top', + "top", options=( - 'top', - 'bottom', - 'left', - 'right', - 'top_start', - 'top_end', - 'bottom_start', - 'bottom_end', - 'left_start', - 'left_end', - 'right_start', - 'right_end')) + "top", + "bottom", + "left", + "right", + "top_start", + "top_end", + "bottom_start", + "bottom_end", + "left_start", + "left_end", + "right_start", + "right_end", + ), + ) class PlotlyHover(BaseHoverFloatLayout): - """ PlotlyHover adapt the background and the font color with the line or scatter color""" + """PlotlyHover adapt the background and the font color with the line or scatter color""" + text_color = ColorProperty([0, 0, 0, 1]) text_font = StringProperty("Roboto") text_size = NumericProperty(dp(14)) hover_height = NumericProperty(dp(24)) - use_position = StringProperty('right') - position = OptionProperty('right', - options=('right', 'left')) + use_position = StringProperty("right") + position = OptionProperty("right", options=("right", "left")) def __init__(self, **kwargs): - """ init class """ + """init class""" super().__init__(**kwargs) -Builder.load_string(''' +Builder.load_string( + """ size_hint: None,None @@ -941,7 +964,7 @@ def __init__(self, **kwargs): custom_color: [0,0,0,1] extra_text:root.custom_label if root.custom_label and not '_child' in root.custom_label else '' label_format:'[size={}]'.format(int(root.text_size + dp(6))) + '[color={}]'.format(get_hex_from_color(root.custom_color)) + \ - '[font=NavigationIcons]' + u"{}".format("\U00000EB1") + \ + '[font=NavigationIcons]' + u"{}".format("\U00000eb1") + \ '[/font][/color][/size]' + root.extra_text + "\\n" + 'x:' + \ root.label_x_value +"\\n"+ "y:" + root.label_y_value @@ -1228,7 +1251,7 @@ def __init__(self, **kwargs): padding: dp(6),0,0,0 extra_text:root.label_y if root.label_y and not '_child' in root.label_y else '' label_format:'[size={}]'.format(int(root.text_size + dp(6))) + '[color={}]'.format(get_hex_from_color(root.custom_color)) + \ - '[font=NavigationIcons]' + u"{}".format("\U00000EB1") + \ + '[font=NavigationIcons]' + u"{}".format("\U00000eb1") + \ '[/font][/color][/size]' + root.extra_text + ": " + root.label_y_value canvas.before: @@ -1255,4 +1278,5 @@ def __init__(self, **kwargs): color:[0,0,0,1] font_name : root.text_font markup:True - ''') + """ +) diff --git a/kivy_matplotlib_widget/uix/legend_widget.py b/kivy_matplotlib_widget/uix/legend_widget.py index 7f12505..9fcae54 100644 --- a/kivy_matplotlib_widget/uix/legend_widget.py +++ b/kivy_matplotlib_widget/uix/legend_widget.py @@ -13,7 +13,7 @@ NumericProperty, ListProperty, BooleanProperty, - ColorProperty + ColorProperty, ) from kivy.lang import Builder @@ -28,7 +28,7 @@ class LegendGestures(Widget): - """ This widget is based on CommonGestures from gestures4kivy project + """This widget is based on CommonGestures from gestures4kivy project https://github.com/Android-for-Python/gestures4kivy For more gesture features like long press or swipe, replace LegendGestures @@ -37,13 +37,15 @@ class LegendGestures(Widget): def __init__(self, **kwargs): super().__init__(**kwargs) - self.mobile = platform == 'android' or platform == 'ios' + self.mobile = platform == "android" or platform == "ios" self._new_gesture() # Sensitivity - self._DOUBLE_TAP_TIME = Config.getint('postproc', - 'double_tap_time') / 1000 - self._DOUBLE_TAP_DISTANCE = Config.getint('postproc', - 'double_tap_distance') + self._DOUBLE_TAP_TIME = ( + Config.getint("postproc", "double_tap_time") / 1000 + ) + self._DOUBLE_TAP_DISTANCE = Config.getint( + "postproc", "double_tap_distance" + ) self._persistent_pos = [(0, 0), (0, 0)] @@ -69,25 +71,26 @@ def on_touch_down(self, touch): if len(self._touches) == 1 and touch.id == self._touches[0].id: # Filter noise from Kivy, one touch.id touches down twice pass - elif platform == 'ios' and 'mouse' in str(touch.id): + elif platform == "ios" and "mouse" in str(touch.id): # Filter more noise from Kivy, extra mouse events return super().on_touch_down(touch) else: self._touches.append(touch) if len(self._touches) == 1: - if 'button' in touch.profile and touch.button == 'right': + if "button" in touch.profile and touch.button == "right": # Two finger tap or right click pass else: - self._gesture_state = 'Dont Know' + self._gesture_state = "Dont Know" # schedule a posssible tap if not self._single_tap_schedule: - self._single_tap_schedule =\ - Clock.schedule_once(partial(self._single_tap_event, - touch, - touch.x, touch.y), - self._DOUBLE_TAP_TIME) + self._single_tap_schedule = Clock.schedule_once( + partial( + self._single_tap_event, touch, touch.x, touch.y + ), + self._DOUBLE_TAP_TIME, + ) self._persistent_pos[0] = tuple(touch.pos) elif len(self._touches) == 2: @@ -103,7 +106,7 @@ def on_touch_up(self, touch): x, y = self._pos_to_widget(touch.x, touch.y) - if self._gesture_state == 'Dont Know': + if self._gesture_state == "Dont Know": if touch.is_double_tap: self._not_single_tap() self.cg_double_tap(touch, x, y) @@ -122,7 +125,7 @@ def on_touch_up(self, touch): # single tap clock def _single_tap_event(self, touch, x, y, dt): - if self._gesture_state == 'Dont Know': + if self._gesture_state == "Dont Know": if not self._long_press_schedule: x, y = self._pos_to_widget(x, y) self.cg_tap(touch, x, y) @@ -150,7 +153,7 @@ def _new_gesture(self): self._long_press_schedule = None self._single_tap_schedule = None self._velocity_schedule = None - self._gesture_state = 'None' + self._gesture_state = "None" self._finger_distance = 0 self._velocity = 0 @@ -169,9 +172,8 @@ def cg_double_tap(self, touch, x, y): class LegendRv(BoxLayout): - """Legend class + """Legend class""" - """ figure_wgt = ObjectProperty(None) data = ListProperty() text_color = ColorProperty([0, 0, 0, 1]) @@ -182,9 +184,7 @@ class LegendRv(BoxLayout): autoscale = BooleanProperty(False) def __init__(self, **kwargs): - """init class - - """ + """init class""" super(LegendRv, self).__init__(**kwargs) self.data = [] @@ -204,13 +204,11 @@ def set_data(self, content: list) -> None: for i, row_content in enumerate(content): - r_data = { - "row_index": int(i), - "viewclass": "CellLegend" - } + r_data = {"row_index": int(i), "viewclass": "CellLegend"} r_data["text"] = str(row_content.get_label()) r_data["mycolor"] = get_color_from_hex( - to_hex(row_content.get_color())) + to_hex(row_content.get_color()) + ) r_data["line_type"] = row_content.get_linestyle() r_data["legend_rv"] = self r_data["selected"] = False @@ -232,10 +230,7 @@ def add_data(self, line) -> None: None """ nb_data = len(self.data) - r_data = { - "row_index": int(nb_data), - "viewclass": "CellLegend" - } + r_data = {"row_index": int(nb_data), "viewclass": "CellLegend"} r_data["text"] = str(line.get_label()) r_data["mycolor"] = get_color_from_hex(to_hex(line.get_color())) r_data["line_type"] = line.get_linestyle() @@ -276,7 +271,7 @@ def show_hide_wgt(self, row_index) -> None: # show line self.data[row_index]["selected"] = False self.data[row_index]["matplotlib_line"].set_visible(True) - if self.autoscale and hasattr(self.figure_wgt, 'autoscale_axis'): + if self.autoscale and hasattr(self.figure_wgt, "autoscale_axis"): self.figure_wgt.autoscale() else: self.figure_wgt.figure.canvas.draw_idle() @@ -285,7 +280,7 @@ def show_hide_wgt(self, row_index) -> None: # hide line self.data[row_index]["selected"] = True self.data[row_index]["matplotlib_line"].set_visible(False) - if self.autoscale and hasattr(self.figure_wgt, 'autoscale_axis'): + if self.autoscale and hasattr(self.figure_wgt, "autoscale_axis"): self.figure_wgt.autoscale() else: self.figure_wgt.figure.canvas.draw_idle() @@ -293,18 +288,21 @@ def show_hide_wgt(self, row_index) -> None: self.ids.view.refresh_from_layout() def doubletap(self, row_index) -> None: - """ double tap behavior is based on plotly behavior """ + """double tap behavior is based on plotly behavior""" if not self.data[row_index]["selected"]: current_line = self.data[row_index]["matplotlib_line"] if current_line.get_visible(): # check if we isolate line or show all lines need_isolate = False if len(self.figure_wgt.figure.axes) > 1: - figure_lines = self.figure_wgt.figure.axes[0].get_lines( - ) + self.figure_wgt.figure.axes[1].get_lines() + figure_lines = ( + self.figure_wgt.figure.axes[0].get_lines() + + self.figure_wgt.figure.axes[1].get_lines() + ) else: figure_lines = list( - self.figure_wgt.figure.axes[0].get_lines()) + self.figure_wgt.figure.axes[0].get_lines() + ) for line in figure_lines: if line != current_line and line.get_visible(): need_isolate = True @@ -326,8 +324,10 @@ def doubletap(self, row_index) -> None: else: # show all lines if len(self.figure_wgt.figure.axes) > 1: - figure_lines = self.figure_wgt.figure.axes[0].get_lines() + \ - self.figure_wgt.figure.axes[1].get_lines() + figure_lines = ( + self.figure_wgt.figure.axes[0].get_lines() + + self.figure_wgt.figure.axes[1].get_lines() + ) else: figure_lines = list(self.figure_wgt.figure.axes[0].get_lines()) for idx, line in enumerate(figure_lines): @@ -335,7 +335,7 @@ def doubletap(self, row_index) -> None: self.data[idx]["selected"] = False self.ids.view.refresh_from_layout() - if self.autoscale and hasattr(self.figure_wgt, 'autoscale_axis'): + if self.autoscale and hasattr(self.figure_wgt, "autoscale_axis"): self.figure_wgt.autoscale() else: self.figure_wgt.figure.canvas.draw_idle() @@ -343,9 +343,8 @@ def doubletap(self, row_index) -> None: class LegendRvHorizontal(BoxLayout): - """Legend Horizontal class + """Legend Horizontal class""" - """ figure_wgt = ObjectProperty(None) data = ListProperty() text_color = ColorProperty([0, 0, 0, 1]) @@ -356,9 +355,7 @@ class LegendRvHorizontal(BoxLayout): autoscale = BooleanProperty(False) def __init__(self, **kwargs): - """init class - - """ + """init class""" super(LegendRvHorizontal, self).__init__(**kwargs) self.data = [] @@ -378,13 +375,11 @@ def set_data(self, content: list) -> None: for i, row_content in enumerate(content): - r_data = { - "row_index": int(i), - "viewclass": "CellLegend" - } + r_data = {"row_index": int(i), "viewclass": "CellLegend"} r_data["text"] = str(row_content.get_label()) r_data["mycolor"] = get_color_from_hex( - to_hex(row_content.get_color())) + to_hex(row_content.get_color()) + ) r_data["line_type"] = row_content.get_linestyle() r_data["legend_rv"] = self r_data["selected"] = False @@ -392,7 +387,7 @@ def set_data(self, content: list) -> None: r_data["text_color"] = self.text_color r_data["text_font"] = self.text_font r_data["text_font_size"] = self.text_font_size - r_data['box_height'] = self.box_height + r_data["box_height"] = self.box_height self.data.append(r_data) @@ -406,10 +401,7 @@ def add_data(self, line) -> None: None """ nb_data = len(self.data) - r_data = { - "row_index": int(nb_data), - "viewclass": "CellLegend" - } + r_data = {"row_index": int(nb_data), "viewclass": "CellLegend"} r_data["text"] = str(line.get_label()) r_data["mycolor"] = get_color_from_hex(to_hex(line.get_color())) r_data["line_type"] = line.get_linestyle() @@ -419,7 +411,7 @@ def add_data(self, line) -> None: r_data["text_color"] = self.text_color r_data["text_font"] = self.text_font r_data["text_font_size"] = self.text_font_size - r_data['box_height'] = self.box_height + r_data["box_height"] = self.box_height self.data.append(r_data) self.figure_wgt.figure.canvas.draw_idle() @@ -450,7 +442,7 @@ def show_hide_wgt(self, row_index) -> None: # show line self.data[row_index]["selected"] = False self.data[row_index]["matplotlib_line"].set_visible(True) - if self.autoscale and hasattr(self.figure_wgt, 'autoscale_axis'): + if self.autoscale and hasattr(self.figure_wgt, "autoscale_axis"): self.figure_wgt.autoscale() else: self.figure_wgt.figure.canvas.draw_idle() @@ -459,7 +451,7 @@ def show_hide_wgt(self, row_index) -> None: # hide line self.data[row_index]["selected"] = True self.data[row_index]["matplotlib_line"].set_visible(False) - if self.autoscale and hasattr(self.figure_wgt, 'autoscale_axis'): + if self.autoscale and hasattr(self.figure_wgt, "autoscale_axis"): self.figure_wgt.autoscale() else: self.figure_wgt.figure.canvas.draw_idle() @@ -467,18 +459,21 @@ def show_hide_wgt(self, row_index) -> None: self.ids.view.refresh_from_layout() def doubletap(self, row_index) -> None: - """ double tap behavior is based on plotly behavior """ + """double tap behavior is based on plotly behavior""" if not self.data[row_index]["selected"]: current_line = self.data[row_index]["matplotlib_line"] if current_line.get_visible(): # check if we isolate line or show all lines need_isolate = False if len(self.figure_wgt.figure.axes) > 1: - figure_lines = self.figure_wgt.figure.axes[0].get_lines( - ) + self.figure_wgt.figure.axes[1].get_lines() + figure_lines = ( + self.figure_wgt.figure.axes[0].get_lines() + + self.figure_wgt.figure.axes[1].get_lines() + ) else: figure_lines = list( - self.figure_wgt.figure.axes[0].get_lines()) + self.figure_wgt.figure.axes[0].get_lines() + ) for line in figure_lines: if line != current_line and line.get_visible(): need_isolate = True @@ -500,8 +495,10 @@ def doubletap(self, row_index) -> None: else: # show all lines if len(self.figure_wgt.figure.axes) > 1: - figure_lines = self.figure_wgt.figure.axes[0].get_lines() + \ - self.figure_wgt.figure.axes[1].get_lines() + figure_lines = ( + self.figure_wgt.figure.axes[0].get_lines() + + self.figure_wgt.figure.axes[1].get_lines() + ) else: figure_lines = list(self.figure_wgt.figure.axes[0].get_lines()) for idx, line in enumerate(figure_lines): @@ -509,7 +506,7 @@ def doubletap(self, row_index) -> None: self.data[idx]["selected"] = False self.ids.view.refresh_from_layout() - if self.autoscale and hasattr(self.figure_wgt, 'autoscale_axis'): + if self.autoscale and hasattr(self.figure_wgt, "autoscale_axis"): self.figure_wgt.autoscale() else: self.figure_wgt.figure.canvas.draw_idle() @@ -517,7 +514,8 @@ def doubletap(self, row_index) -> None: class CellLegendMatplotlib(LegendGestures, BoxLayout): - """ Touch legend kivy class""" + """Touch legend kivy class""" + selected = BooleanProperty(False) row_index = NumericProperty(0) matplotlib_legend_box = ObjectProperty(None) @@ -525,12 +523,12 @@ class CellLegendMatplotlib(LegendGestures, BoxLayout): matplotlib_text = ObjectProperty(None) def __init__(self, **kwargs): - """ init class """ + """init class""" super().__init__(**kwargs) def cg_tap(self, touch, x, y): # single tap - if self.matplotlib_legend_box.figure_wgt.touch_mode != 'drag_legend': + if self.matplotlib_legend_box.figure_wgt.touch_mode != "drag_legend": self.matplotlib_legend_box.show_hide_wgt(self.row_index) def cg_double_tap(self, touch, x, y): @@ -539,13 +537,14 @@ def cg_double_tap(self, touch, x, y): class CellLegend(LegendGestures, BoxLayout): - """ Touch legend kivy class""" + """Touch legend kivy class""" + selected = BooleanProperty(False) text = StringProperty("") row_index = NumericProperty(0) legend_rv = ObjectProperty(None) matplotlib_line = ObjectProperty(None) - line_type = StringProperty('-') + line_type = StringProperty("-") mycolor = ListProperty([0, 0, 1]) text_color = ColorProperty([0, 0, 0, 1]) text_font = StringProperty("Roboto") @@ -553,7 +552,7 @@ class CellLegend(LegendGestures, BoxLayout): box_height = NumericProperty(dp(48)) def __init__(self, **kwargs): - """ init class """ + """init class""" super().__init__(**kwargs) def cg_tap(self, touch, x, y): @@ -566,13 +565,14 @@ def cg_double_tap(self, touch, x, y): class CellLegendHorizontal(LegendGestures, BoxLayout): - """ Touch legend kivy class""" + """Touch legend kivy class""" + selected = BooleanProperty(False) text = StringProperty("") row_index = NumericProperty(0) legend_rv = ObjectProperty(None) matplotlib_line = ObjectProperty(None) - line_type = StringProperty('-') + line_type = StringProperty("-") mycolor = ListProperty([0, 0, 1]) text_color = ColorProperty([0, 0, 0, 1]) text_font = StringProperty("Roboto") @@ -580,7 +580,7 @@ class CellLegendHorizontal(LegendGestures, BoxLayout): box_height = NumericProperty(dp(48)) def __init__(self, **kwargs): - """ init class """ + """init class""" super().__init__(**kwargs) def cg_tap(self, touch, x, y): @@ -592,17 +592,19 @@ def cg_double_tap(self, touch, x, y): self.legend_rv.doubletap(self.row_index) -def MatplotlibInteractiveLegend(figure_wgt, - legend_handles='auto', - delay=None, - legend_instance=None, - custom_handlers=None, - multi_legend=False, - prop=None, - current_handles_text=None, - scatter=None, - autoscale=False): - """ transform matplotlib legend to interactive legend +def MatplotlibInteractiveLegend( + figure_wgt, + legend_handles="auto", + delay=None, + legend_instance=None, + custom_handlers=None, + multi_legend=False, + prop=None, + current_handles_text=None, + scatter=None, + autoscale=False, +): + """transform matplotlib legend to interactive legend Args: figure_wgt: figure widget from kivy_matplotlib_widget package @@ -631,7 +633,7 @@ def MatplotlibInteractiveLegend(figure_wgt, figure_wgt.figcanvas.draw() # detect is the legend use column (ex: horizontal legend) - if hasattr(leg, '_ncols'): + if hasattr(leg, "_ncols"): # matplotlib version >3.6 legend_ncol = leg._ncols else: @@ -645,45 +647,57 @@ def MatplotlibInteractiveLegend(figure_wgt, # create_touch_legend(figure_wgt,leg,ncol,legend_handles,0) if delay is None: # no delay case - create_touch_legend(figure_wgt, - leg, ncol, - legend_handles, - legend_instance, - custom_handlers, - multi_legend, - prop, - current_handles_text, - scatter, - autoscale, - 0) + create_touch_legend( + figure_wgt, + leg, + ncol, + legend_handles, + legend_instance, + custom_handlers, + multi_legend, + prop, + current_handles_text, + scatter, + autoscale, + 0, + ) else: # get legend bbox position atfer delay (sometime needed if 'best # position' was used) - Clock.schedule_once(partial(create_touch_legend, - figure_wgt, leg, ncol, - legend_handles, - legend_instance, - custom_handlers, - multi_legend, - prop, - current_handles_text, - scatter, - autoscale), - delay) - - -def create_touch_legend(figure_wgt, - leg, ncol, - legend_handles, - legend_instance, - custom_handlers, - multi_legend, - prop, - current_handles_text, - scatter, - autoscale, - _): - """ create touch legend """ + Clock.schedule_once( + partial( + create_touch_legend, + figure_wgt, + leg, + ncol, + legend_handles, + legend_instance, + custom_handlers, + multi_legend, + prop, + current_handles_text, + scatter, + autoscale, + ), + delay, + ) + + +def create_touch_legend( + figure_wgt, + leg, + ncol, + legend_handles, + legend_instance, + custom_handlers, + multi_legend, + prop, + current_handles_text, + scatter, + autoscale, + _, +): + """create touch legend""" bbox = leg.get_window_extent() @@ -700,7 +714,9 @@ def create_touch_legend(figure_wgt, if leg._get_loc() == 0: # location best. Need to fix the legend location loc_in_canvas = bbox.xmin, bbox.ymin - loc_in_norm_axes = leg.parent.transAxes.inverted().transform_point(loc_in_canvas) + loc_in_norm_axes = leg.parent.transAxes.inverted().transform_point( + loc_in_canvas + ) leg._loc = tuple(loc_in_norm_axes) # position for kivy widget @@ -720,14 +736,16 @@ def create_touch_legend(figure_wgt, current_handles = [] current_labels = [] for current_ax in ax: - current_handles0, current_labels0 = current_ax.get_legend_handles_labels() + current_handles0, current_labels0 = ( + current_ax.get_legend_handles_labels() + ) current_handles += current_handles0 current_labels += current_labels0 nb_group = len(current_handles) if nb_group == 0: - print('no legend available') + print("no legend available") return # check if a title exist @@ -764,18 +782,16 @@ def create_touch_legend(figure_wgt, m, n = ceil(nb_group / ncol), ncol index_arr = np.pad( np.arange(nb_group).astype(float), - (0, - m * n - np.arange(nb_group).size), - mode='constant', - constant_values=np.nan) + (0, m * n - np.arange(nb_group).size), + mode="constant", + constant_values=np.nan, + ) index_arr2 = np.pad( np.arange(nb_group).astype(float), - (0, - m * n - np.arange(nb_group).size), - mode='constant', - constant_values=np.nan).reshape( - m, - n) + (0, m * n - np.arange(nb_group).size), + mode="constant", + constant_values=np.nan, + ).reshape(m, n) i = 0 for col in range(ncol): @@ -812,7 +828,7 @@ def create_touch_legend(figure_wgt, matplotlib_legend_box.legend_width = x1_pos - x0_pos - if leg.get_patches()[:] and legend_handles == 'variante': + if leg.get_patches()[:] and legend_handles == "variante": # sometime the legend handles not perfectly match with graph # in this case, this experimenta section try to fix this ax_instances = ax.get_children() @@ -825,21 +841,25 @@ def create_touch_legend(figure_wgt, nb_instance_by_hist = len(hist_instance) // nb_group for i, leg_instance in enumerate(leg.get_patches()[:]): - instance_dict[leg_instance] = hist_instance[int( - i * nb_instance_by_hist):int((i + 1) * nb_instance_by_hist)] + instance_dict[leg_instance] = hist_instance[ + int(i * nb_instance_by_hist) : int( + (i + 1) * nb_instance_by_hist + ) + ] current_legend_cell = CellLegendMatplotlib() current_legend_cell.matplotlib_line = leg_instance current_legend_cell.matplotlib_text = leg.get_texts()[:][i] current_legend_cell.matplotlib_legend_box = matplotlib_legend_box current_legend_cell.row_index = i current_legend_cell.height = int( - matplotlib_legend_box.legend_height / nb_group) + matplotlib_legend_box.legend_height / nb_group + ) matplotlib_legend_box.box.add_widget(current_legend_cell) else: # general purpose interactive position # get matplotlib text object - if hasattr(leg, 'legend_handles'): + if hasattr(leg, "legend_handles"): # matplotlib>3.7 legeng_marker = leg.legend_handles else: @@ -857,13 +877,16 @@ def create_touch_legend(figure_wgt, current_legend_cell = CellLegendMatplotlib() if ncol: current_legend_cell.height = int( - matplotlib_legend_box.legend_height / - (ceil((nb_group - 1) / ncol))) + matplotlib_legend_box.legend_height + / (ceil((nb_group - 1) / ncol)) + ) current_legend_cell.width = int( - matplotlib_legend_box.legend_width / ncol) + matplotlib_legend_box.legend_width / ncol + ) else: current_legend_cell.height = int( - matplotlib_legend_box.legend_height / max(nb_group, 1)) + matplotlib_legend_box.legend_height / max(nb_group, 1) + ) current_legend_cell.width = matplotlib_legend_box.legend_width if isinstance(current_handles[i], list): @@ -890,7 +913,8 @@ def create_touch_legend(figure_wgt, class MatplotlibLegendGrid(FloatLayout): - """ Touch egend kivy class""" + """Touch egend kivy class""" + figure_wgt = ObjectProperty(None) x_pos = NumericProperty(1) y_pos = NumericProperty(1) @@ -904,7 +928,7 @@ class MatplotlibLegendGrid(FloatLayout): instance_dict = dict() def __init__(self, **kwargs): - """ init class """ + """init class""" self.facecolor = [] self.mysize = None self.scatter = None @@ -915,7 +939,7 @@ def __init__(self, **kwargs): super().__init__(**kwargs) def update_size(self): - """ update size """ + """update size""" if self.figure_wgt: if self.legend_instance: leg = self.legend_instance @@ -941,7 +965,7 @@ def update_size(self): if leg.get_title().get_text(): have_title = True if have_title: - if hasattr(leg, 'legend_handles'): + if hasattr(leg, "legend_handles"): # matplotlib>3.7 current_handles = leg.legend_handles else: @@ -949,7 +973,7 @@ def update_size(self): nb_group = len(current_handles) - if hasattr(leg, '_ncols'): + if hasattr(leg, "_ncols"): # matplotlib version >3.6 legend_ncol = leg._ncols else: @@ -960,8 +984,9 @@ def update_size(self): else: ncol = None if ncol: - title_padding = (y1_pos - y0_pos) / \ - (ceil(nb_group / ncol) + 1) + title_padding = (y1_pos - y0_pos) / ( + ceil(nb_group / ncol) + 1 + ) else: title_padding = (y1_pos - y0_pos) / (nb_group + 1) @@ -970,7 +995,7 @@ def update_size(self): self.legend_width = x1_pos - x0_pos def reset_legend(self): - """ reset_legend and clear all children """ + """reset_legend and clear all children""" self.x_pos = 1 self.y_pos = 1 self.legend_height = 1 @@ -985,11 +1010,12 @@ def show_hide_wgt(self, row_index) -> None: self.box.children[::-1][row_index].matplotlib_line.set_alpha(1) self.box.children[::-1][row_index].matplotlib_text.set_alpha(1) - hist = self.instance_dict[self.box.children[::-1] - [row_index].matplotlib_line] + hist = self.instance_dict[ + self.box.children[::-1][row_index].matplotlib_line + ] for current_hist in hist: self.set_visible(current_hist, True, row_index) - if self.autoscale and hasattr(self.figure_wgt, 'autoscale_axis'): + if self.autoscale and hasattr(self.figure_wgt, "autoscale_axis"): if self.prop: self.prop_autoscale() else: @@ -1003,11 +1029,12 @@ def show_hide_wgt(self, row_index) -> None: self.box.children[::-1][row_index].matplotlib_line.set_alpha(0.5) self.box.children[::-1][row_index].matplotlib_text.set_alpha(0.5) - hist = self.instance_dict[self.box.children[::-1] - [row_index].matplotlib_line] + hist = self.instance_dict[ + self.box.children[::-1][row_index].matplotlib_line + ] for current_hist in hist: self.set_visible(current_hist, False, row_index) - if self.autoscale and hasattr(self.figure_wgt, 'autoscale_axis'): + if self.autoscale and hasattr(self.figure_wgt, "autoscale_axis"): if self.prop: self.prop_autoscale() else: @@ -1017,11 +1044,12 @@ def show_hide_wgt(self, row_index) -> None: self.figure_wgt.figure.canvas.flush_events() def doubletap(self, row_index) -> None: - """ double tap behavior is based on plotly behavior """ + """double tap behavior is based on plotly behavior""" if not self.box.children[::-1][row_index].selected: hist = self.instance_dict - current_line = hist[self.box.children[::-1] - [row_index].matplotlib_line][0] + current_line = hist[ + self.box.children[::-1][row_index].matplotlib_line + ][0] hist_keys = list(self.instance_dict.keys()) # check if we isolate line or show all lines @@ -1040,16 +1068,20 @@ def doubletap(self, row_index) -> None: for current_hist in hist[line]: self.set_visible(current_hist, False, idx) self.box.children[::-1][idx].selected = True - self.box.children[::- - 1][idx].matplotlib_line.set_alpha(0.5) - self.box.children[::- - 1][idx].matplotlib_text.set_alpha(0.5) + self.box.children[::-1][idx].matplotlib_line.set_alpha( + 0.5 + ) + self.box.children[::-1][idx].matplotlib_text.set_alpha( + 0.5 + ) else: self.box.children[::-1][idx].selected = False - self.box.children[::- - 1][idx].matplotlib_line.set_alpha(1) - self.box.children[::- - 1][idx].matplotlib_text.set_alpha(1) + self.box.children[::-1][idx].matplotlib_line.set_alpha( + 1 + ) + self.box.children[::-1][idx].matplotlib_text.set_alpha( + 1 + ) else: # show all lines' for idx, line in enumerate(hist_keys): @@ -1068,7 +1100,7 @@ def doubletap(self, row_index) -> None: self.box.children[::-1][idx].matplotlib_line.set_alpha(1) self.box.children[::-1][idx].matplotlib_text.set_alpha(1) - if self.autoscale and hasattr(self.figure_wgt, 'autoscale_axis'): + if self.autoscale and hasattr(self.figure_wgt, "autoscale_axis"): if self.prop: self.prop_autoscale() self.figure_wgt.autoscale() @@ -1088,7 +1120,7 @@ def prop_autoscale(self) -> None: None """ ax = self.scatter.axes - if self.prop == 'colors': + if self.prop == "colors": if not self.facecolor.any(): ax.figure.canvas.draw_idle() ax.figure.canvas.flush_events() @@ -1118,13 +1150,16 @@ def prop_autoscale(self) -> None: autoscale_axis = self.figure_wgt.autoscale_axis no_visible = self.figure_wgt.myrelim( - ax, visible_only=self.figure_wgt.autoscale_visible_only) - ax.autoscale_view(tight=self.figure_wgt.autoscale_tight, - scalex=True if autoscale_axis != "y" else False, - scaley=True if autoscale_axis != "x" else False) + ax, visible_only=self.figure_wgt.autoscale_visible_only + ) + ax.autoscale_view( + tight=self.figure_wgt.autoscale_tight, + scalex=True if autoscale_axis != "y" else False, + scaley=True if autoscale_axis != "x" else False, + ) ax.autoscale( - axis=autoscale_axis, - tight=self.figure_wgt.autoscale_tight) + axis=autoscale_axis, tight=self.figure_wgt.autoscale_tight + ) current_xlim = ax.get_xlim() current_ylim = ax.get_ylim() @@ -1176,10 +1211,12 @@ def prop_autoscale(self) -> None: if xchanged: xlim = ax.get_xlim() ax.set_xlim( - left=xlim[0] - current_margins[0] * (xlim[1] - xlim[0])) + left=xlim[0] - current_margins[0] * (xlim[1] - xlim[0]) + ) ax.set_xlim( - right=xlim[1] + current_margins[0] * - (xlim[1] - xlim[0])) + right=xlim[1] + + current_margins[0] * (xlim[1] - xlim[0]) + ) ychanged = False @@ -1202,15 +1239,19 @@ def prop_autoscale(self) -> None: if ychanged: ylim = ax.get_ylim() ax.set_ylim( - bottom=ylim[0] - current_margins[1] * - (ylim[1] - ylim[0])) + bottom=ylim[0] + - current_margins[1] * (ylim[1] - ylim[0]) + ) ax.set_ylim( - top=ylim[1] + current_margins[1] * (ylim[1] - ylim[0])) + top=ylim[1] + current_margins[1] * (ylim[1] - ylim[0]) + ) index = self.figure_wgt.figure.axes.index(ax) - self.figure_wgt.xmin[index], self.figure_wgt.xmax[index] = ax.get_xlim( + self.figure_wgt.xmin[index], self.figure_wgt.xmax[index] = ( + ax.get_xlim() ) - self.figure_wgt.ymin[index], self.figure_wgt.ymax[index] = ax.get_ylim( + self.figure_wgt.ymin[index], self.figure_wgt.ymax[index] = ( + ax.get_ylim() ) ax.set_autoscale_on(False) @@ -1228,18 +1269,20 @@ def set_visible(self, instance, value, row_index=None) -> None: None """ if self.prop: - if self.prop == 'colors': + if self.prop == "colors": if self.facecolor.any(): unique_color = np.unique(self.facecolor, axis=0) ncol = np.shape(unique_color)[0] if not self.mysize_list: - self.mysize_list = [self.mysize] * \ - np.shape(self.facecolor)[0] + self.mysize_list = [self.mysize] * np.shape( + self.facecolor + )[0] for i in range(np.shape(self.facecolor)[0]): - if (self.facecolor[i, :] == - unique_color[row_index, :]).all(): + if ( + self.facecolor[i, :] == unique_color[row_index, :] + ).all(): if value: self.mysize_list[i] = self.mysize else: @@ -1252,19 +1295,23 @@ def set_visible(self, instance, value, row_index=None) -> None: # self.figure_wgt.figure.axes[0].get_children()[0].set_facecolor(newfacecolor) self.scatter.set_sizes(self.mysize_list) - if self.prop == 'sizes': + if self.prop == "sizes": if self.mysize: legend_size = len(self.current_handles_text) legend_value = self.current_handles_text[row_index] float_legend_value_before = None if row_index != 0: - legend_value_before = self.current_handles_text[row_index - 1] + legend_value_before = self.current_handles_text[ + row_index - 1 + ] match_legend_value_before = re.search( - r"\{(.+?)\}", legend_value_before) + r"\{(.+?)\}", legend_value_before + ) if match_legend_value_before: float_legend_value_before = float( - match_legend_value_before.group(1)) + match_legend_value_before.group(1) + ) else: return @@ -1274,33 +1321,39 @@ def set_visible(self, instance, value, row_index=None) -> None: else: return - if isinstance( - self.mysize_list, list) and len( - self.mysize_list) == 0: + if ( + isinstance(self.mysize_list, list) + and len(self.mysize_list) == 0 + ): self.mysize_list = copy.copy(self.original_size) if row_index == 0: index_match = np.where( - float_legend_value >= self.original_size)[0] + float_legend_value >= self.original_size + )[0] elif row_index == legend_size - 1: index_match = np.where( - float_legend_value_before <= self.original_size)[0] + float_legend_value_before <= self.original_size + )[0] else: index_match = np.where( - (float_legend_value >= self.original_size) & ( - float_legend_value_before <= self.original_size))[0] + (float_legend_value >= self.original_size) + & (float_legend_value_before <= self.original_size) + )[0] if value: - self.mysize_list[index_match] = self.original_size[index_match] + self.mysize_list[index_match] = self.original_size[ + index_match + ] else: self.mysize_list[index_match] = 0.0 self.scatter.set_sizes(self.mysize_list) else: - if hasattr(instance, 'set_visible'): + if hasattr(instance, "set_visible"): instance.set_visible(value) - elif hasattr(instance, 'get_children'): + elif hasattr(instance, "get_children"): all_child = instance.get_children() for child in all_child: child.set_visible(value) @@ -1314,9 +1367,9 @@ def get_visible(self, instance) -> bool: Returns: bool """ - if hasattr(instance, 'get_visible'): + if hasattr(instance, "get_visible"): return instance.get_visible() - elif hasattr(instance, 'get_children'): + elif hasattr(instance, "get_children"): return_value = False all_child = instance.get_children() for child in all_child[:1]: @@ -1326,9 +1379,10 @@ def get_visible(self, instance) -> bool: return False -Factory.register('LegendRv', LegendRv) +Factory.register("LegendRv", LegendRv) -Builder.load_string(''' +Builder.load_string( + """ canvas.before: Color: @@ -1630,4 +1684,5 @@ def get_visible(self, instance) -> bool: font_name:root.text_font shorten: True shorten_from: 'center' - ''') + """ +) diff --git a/kivy_matplotlib_widget/uix/minmax_widget.py b/kivy_matplotlib_widget/uix/minmax_widget.py index 0f4d588..a17a0e1 100644 --- a/kivy_matplotlib_widget/uix/minmax_widget.py +++ b/kivy_matplotlib_widget/uix/minmax_widget.py @@ -7,7 +7,7 @@ StringProperty, BooleanProperty, ColorProperty, - DictProperty + DictProperty, ) from kivy.lang import Builder @@ -16,13 +16,15 @@ from kivy.core import text as coretext -def add_minmax(figure_wgt, - xaxis_formatter=None, - invert_xaxis_formatter=None, - yaxis_formatter=None, - invert_yaxis_formatter=None): +def add_minmax( + figure_wgt, + xaxis_formatter=None, + invert_xaxis_formatter=None, + yaxis_formatter=None, + invert_yaxis_formatter=None, +): - if hasattr(figure_wgt, 'text_instance'): + if hasattr(figure_wgt, "text_instance"): if figure_wgt.text_instance is None: text_widget = TextBox() else: @@ -43,26 +45,28 @@ def add_minmax(figure_wgt, class BaseTextFloatLayout(FloatLayout): - """ Touch egend kivy class""" + """Touch egend kivy class""" + figure_wgt = ObjectProperty(None) current_axis = ObjectProperty(None) - kind = DictProperty({'axis': 'x', 'anchor': 'right'}) + kind = DictProperty({"axis": "x", "anchor": "right"}) x_text_pos = NumericProperty(10) y_text_pos = NumericProperty(10) show_text = BooleanProperty(False) def __init__(self, **kwargs): - """ init class """ + """init class""" super().__init__(**kwargs) def reset_text(self): - """ reset text attribute """ + """reset text attribute""" self.x_text_pos = 1 self.y_text_pos = 1 class TextBox(BaseTextFloatLayout): - """ text box widget """ + """text box widget""" + text_color = ColorProperty([0, 0, 0, 1]) text_font = StringProperty("Roboto") text_size = NumericProperty(dp(14)) @@ -77,7 +81,7 @@ class TextBox(BaseTextFloatLayout): current_text = "" def __init__(self, **kwargs): - """ init class """ + """init class""" super().__init__(**kwargs) def on_axis_validation(self, instance) -> bool: @@ -94,9 +98,9 @@ def on_axis_validation(self, instance) -> bool: return False try: - if self.kind.get('axis') == 'x' and self.invert_xaxis_formatter: + if self.kind.get("axis") == "x" and self.invert_xaxis_formatter: number = float(self.invert_xaxis_formatter(instance.text)) - elif self.kind.get('axis') == 'y' and self.invert_yaxis_formatter: + elif self.kind.get("axis") == "y" and self.invert_yaxis_formatter: number = float(self.invert_yaxis_formatter(instance.text)) else: number = float(instance.text) @@ -111,29 +115,31 @@ def on_set_axis(self, instance): instance (widget) : kivy widget object """ - if not instance.focused and \ - self.on_axis_validation(instance) and \ - self.current_text != instance.text: + if ( + not instance.focused + and self.on_axis_validation(instance) + and self.current_text != instance.text + ): kind = self.kind - if self.kind.get('axis') == 'x' and self.invert_xaxis_formatter: + if self.kind.get("axis") == "x" and self.invert_xaxis_formatter: number = float(self.invert_xaxis_formatter(instance.text)) - elif self.kind.get('axis') == 'y' and self.invert_yaxis_formatter: + elif self.kind.get("axis") == "y" and self.invert_yaxis_formatter: number = float(self.invert_yaxis_formatter(instance.text)) else: number = float(instance.text) - if kind.get('axis') == 'x': - if kind.get('anchor') == 'left': + if kind.get("axis") == "x": + if kind.get("anchor") == "left": self.current_axis.set_xlim((number, None)) - elif kind.get('anchor') == 'right': + elif kind.get("anchor") == "right": self.current_axis.set_xlim((None, number)) - elif kind.get('axis') == 'y': - if kind.get('anchor') == 'bottom': + elif kind.get("axis") == "y": + if kind.get("anchor") == "bottom": self.current_axis.set_ylim((number, None)) - elif kind.get('anchor') == 'top': + elif kind.get("anchor") == "top": self.current_axis.set_ylim((None, number)) self.current_axis.figure.canvas.draw_idle() self.current_axis.figure.canvas.flush_events() @@ -141,7 +147,7 @@ def on_set_axis(self, instance): self.show_text = False def autofocus_text(self, *args) -> None: - """ auto focus text input + """auto focus text input Returns: None @@ -159,67 +165,70 @@ def on_show_text(self, instance, val): if val: kind = self.kind - if kind.get('axis') == 'x': + if kind.get("axis") == "x": xlim = self.current_axis.get_xlim() if self.xaxis_formatter is None: axis_formatter = ( self.current_axis.fmt_xdata - if self.current_axis.fmt_xdata is - not None else - self.current_axis.xaxis.get_major_formatter().format_data_short) + if self.current_axis.fmt_xdata is not None + else self.current_axis.xaxis.get_major_formatter().format_data_short + ) else: axis_formatter = self.xaxis_formatter - if kind.get('anchor') == 'left': + if kind.get("anchor") == "left": # u"\u2212" is to manage unicode minus self.ids.text_input.text = f"{ axis_formatter( xlim[0])}".replace( - u"\u2212", "-") + "\u2212", "-" + ) - elif kind.get('anchor') == 'right': + elif kind.get("anchor") == "right": # u"\u2212" is to manage unicode minus self.ids.text_input.text = f"{ axis_formatter( xlim[1])}".replace( - u"\u2212", "-") + "\u2212", "-" + ) self.current_value = self.ids.text_input.text - elif kind.get('axis') == 'y': + elif kind.get("axis") == "y": ylim = self.current_axis.get_ylim() if self.yaxis_formatter is None: axis_formatter = ( self.current_axis.fmt_ydata - if self.current_axis.fmt_ydata is - not None else - self.current_axis.yaxis.get_major_formatter().format_data_short) + if self.current_axis.fmt_ydata is not None + else self.current_axis.yaxis.get_major_formatter().format_data_short + ) else: axis_formatter = self.xaxis_formatter - if kind.get('anchor') == 'bottom': + if kind.get("anchor") == "bottom": # u"\u2212" is to manage unicode minus self.ids.text_input.text = f"{ axis_formatter( ylim[0])}".replace( - u"\u2212", "-") + "\u2212", "-" + ) - elif kind.get('anchor') == 'top': + elif kind.get("anchor") == "top": # u"\u2212" is to manage unicode minus self.ids.text_input.text = f"{ axis_formatter( ylim[1])}".replace( - u"\u2212", "-") + "\u2212", "-" + ) self.current_value = self.ids.text_input.text self.autofocus_text() class CustomTextInput(TextInput): - """ Variation of kivy TextInput + """Variation of kivy TextInput""" - """ text_width = NumericProperty(100) - '''The text width - ''' + """The text width + """ text_box_instance = figure_wgt = ObjectProperty(None) textcenter = BooleanProperty(True) @@ -229,30 +238,35 @@ def __init__(self, *args, **kwargs): self.bind(focus=self.on_focus_text) def update_textbox(self, *args): - ''' + """ Update the text box from text width - ''' + """ if self.text: try: string = self.text text_texture_width = coretext.Label( - font_size=self.font_size).get_extents(string)[0] + font_size=self.font_size + ).get_extents(string)[0] except BaseException: - print('get text width failed') + print("get text width failed") else: if self.text_box_instance: if text_texture_width > dp(20): - self.text_box_instance.text_width = text_texture_width + \ - self.padding[0] + self.padding[2] + self.text_box_instance.text_width = ( + text_texture_width + + self.padding[0] + + self.padding[2] + ) else: - self.text_box_instance.text_width = dp( - 20) + self.padding[0] + self.padding[2] + self.text_box_instance.text_width = ( + dp(20) + self.padding[0] + self.padding[2] + ) def on_focus_text(self, instance, value): - """ on focus operation""" + """on focus operation""" if value: # User focused pass @@ -261,7 +275,8 @@ def on_focus_text(self, instance, value): self.text_box_instance.show_text = False -Builder.load_string(''' +Builder.load_string( + """ size_hint: None,None @@ -305,4 +320,5 @@ def on_focus_text(self, instance, value): font_size:dp(14) on_text: root.update_textbox() multiline:False - ''') + """ +) diff --git a/kivy_matplotlib_widget/uix/navigation_bar_widget.py b/kivy_matplotlib_widget/uix/navigation_bar_widget.py index 5bc6c89..46a1d49 100644 --- a/kivy_matplotlib_widget/uix/navigation_bar_widget.py +++ b/kivy_matplotlib_widget/uix/navigation_bar_widget.py @@ -1,5 +1,12 @@ from matplotlib.backend_bases import NavigationToolbar2 -from kivy.properties import ObjectProperty, OptionProperty, ListProperty, BooleanProperty, NumericProperty, StringProperty +from kivy.properties import ( + ObjectProperty, + OptionProperty, + ListProperty, + BooleanProperty, + NumericProperty, + StringProperty, +) from kivy.lang import Builder from kivy.uix.boxlayout import BoxLayout from kivy.uix.relativelayout import RelativeLayout @@ -11,8 +18,8 @@ class MatplotNavToolbar(BoxLayout): - """Figure Toolbar""" + pan_btn = ObjectProperty(None) zoom_btn = ObjectProperty(None) home_btn = ObjectProperty(None) @@ -46,7 +53,7 @@ def on_kv_post(self): self._init_toolbar() def _init_toolbar(self): - print('init toolbar') + print("init toolbar") self.widget.home_btn.bind(on_press=self.home) self.widget.pan_btn.bind(on_press=self.pan) self.widget.zoom_btn.bind(on_press=self.zoom) @@ -64,8 +71,8 @@ def set_message(self, s): class KivyMatplotNavToolbar(RelativeLayout): - """Figure Toolbar""" + pan_btn = ObjectProperty(None) zoom_btn = ObjectProperty(None) home_btn = ObjectProperty(None) @@ -76,11 +83,12 @@ class KivyMatplotNavToolbar(RelativeLayout): figure_wgt = ObjectProperty(None) figure_wgt_layout = ObjectProperty(None) # 3D graph only orientation_type = OptionProperty( - "actionbar", options=["rail", "actionbar"]) + "actionbar", options=["rail", "actionbar"] + ) nav_icon = OptionProperty( - "normal", options=["minimal", "normal", "all", "3D", "custom"]) - hover_mode = OptionProperty( - "touch", options=["touch", "desktop"]) + "normal", options=["minimal", "normal", "all", "3D", "custom"] + ) + hover_mode = OptionProperty("touch", options=["touch", "desktop"]) custom_icon = ListProperty([]) show_cursor_data = BooleanProperty(False) @@ -100,17 +108,13 @@ def on_kv_post(self, _): # pan button self.add_nav_btn( - "pan", - self.set_touch_mode, - mode='pan', - btn_type='group') + "pan", self.set_touch_mode, mode="pan", btn_type="group" + ) # zoombox button self.add_nav_btn( - "zoom", - self.set_touch_mode, - mode='zoombox', - btn_type='group') + "zoom", self.set_touch_mode, mode="zoombox", btn_type="group" + ) elif self.nav_icon == "normal" and not self.custom_icon: @@ -119,52 +123,53 @@ def on_kv_post(self, _): # pan button self.add_nav_btn( - "pan", - self.set_touch_mode, - mode='pan', - btn_type='group') + "pan", self.set_touch_mode, mode="pan", btn_type="group" + ) # zoombox button self.add_nav_btn( - "zoom", - self.set_touch_mode, - mode='zoombox', - btn_type='group') + "zoom", self.set_touch_mode, mode="zoombox", btn_type="group" + ) if self.hover_mode == "touch" and not self.compare_hover: # cursor button self.add_nav_btn( "cursor", self.set_touch_mode, - mode='cursor', - btn_type='group') + mode="cursor", + btn_type="group", + ) elif self.hover_mode == "touch": # nearest hover button self.add_nav_btn( "hover", self.change_hover_type, - mode='nearest', - btn_type='hover_type') + mode="nearest", + btn_type="hover_type", + ) # compare hover button self.add_nav_btn( "hover_compare", self.change_hover_type, - mode='compare', - btn_type='hover_type') + mode="compare", + btn_type="hover_type", + ) elif self.compare_hover: # nearest hover button self.add_nav_btn( "hover", self.change_hover_type, - mode='nearest', - btn_type='hover_type') + mode="nearest", + btn_type="hover_type", + ) # compare hover button self.add_nav_btn( "hover_compare", self.change_hover_type, - mode='compare', - btn_type='hover_type') + mode="compare", + btn_type="hover_type", + ) elif self.nav_icon == "all" and not self.custom_icon: @@ -179,60 +184,59 @@ def on_kv_post(self, _): # pan button self.add_nav_btn( - "pan", - self.set_touch_mode, - mode='pan', - btn_type='group') + "pan", self.set_touch_mode, mode="pan", btn_type="group" + ) # zoombox button self.add_nav_btn( - "zoom", - self.set_touch_mode, - mode='zoombox', - btn_type='group') + "zoom", self.set_touch_mode, mode="zoombox", btn_type="group" + ) if self.hover_mode == "touch" and not self.compare_hover: # cursor button self.add_nav_btn( "cursor", self.set_touch_mode, - mode='cursor', - btn_type='group') + mode="cursor", + btn_type="group", + ) elif self.hover_mode == "touch": # nearest hover button self.add_nav_btn( "hover", self.change_hover_type, - mode='nearest', - btn_type='group') + mode="nearest", + btn_type="group", + ) # compare hover button self.add_nav_btn( "hover_compare", self.change_hover_type, - mode='compare', - btn_type='group') + mode="compare", + btn_type="group", + ) elif self.compare_hover: # nearest hover button self.add_nav_btn( "hover", self.change_hover_type, - mode='nearest', - btn_type='group') + mode="nearest", + btn_type="group", + ) # compare hover button self.add_nav_btn( "hover_compare", self.change_hover_type, - mode='compare', - btn_type='group') + mode="compare", + btn_type="group", + ) # minmax button self.add_nav_btn( - "minmax", - self.set_touch_mode, - mode='minmax', - btn_type='group') + "minmax", self.set_touch_mode, mode="minmax", btn_type="group" + ) # home button self.add_nav_btn("autoscale", self.autoscale) @@ -242,8 +246,9 @@ def on_kv_post(self, _): self.add_nav_btn( "drag_legend", self.set_touch_mode, - mode='drag_legend', - btn_type='group') + mode="drag_legend", + btn_type="group", + ) elif self.custom_icon: pass @@ -256,38 +261,41 @@ def on_kv_post(self, _): self.add_nav_btn( "axis-z-rotate-clockwise", self.set_touch_mode_3D, - mode='rotate', - btn_type='group') + mode="rotate", + btn_type="group", + ) # data pan/zoom button self.add_nav_btn( - "pan", - self.set_touch_mode_3D, - mode='pan', - btn_type='group') + "pan", self.set_touch_mode_3D, mode="pan", btn_type="group" + ) # figure pan/zoom button self.add_nav_btn( "magnify", self.set_touch_mode_3D, - mode='figure_zoom_pan', - btn_type='group') + mode="figure_zoom_pan", + btn_type="group", + ) # cursor button self.add_nav_btn( "cursor", self.set_touch_mode_3D, - mode='cursor', - btn_type='group') + mode="cursor", + btn_type="group", + ) if self.show_cursor_data: Window.bind(mouse_pos=self.on_motion) def on_motion(self, *args): - '''Kivy Event to trigger mouse event on motion - `enter_notify_event`. - ''' - if self.figure_wgt._pressed: # Do not process this event if there's a touch_move + """Kivy Event to trigger mouse event on motion + `enter_notify_event`. + """ + if ( + self.figure_wgt._pressed + ): # Do not process this event if there's a touch_move return pos = args[1] newcoord = self.figure_wgt.to_widget(pos[0], pos[1]) @@ -311,15 +319,16 @@ def on_motion(self, *args): self.current_label.text = "" def add_nav_btn(self, icon, fct, mode=None, btn_type=None): - if btn_type == 'group': - if 'hover' in icon: + if btn_type == "group": + if "hover" in icon: btn = Factory.NavToggleButton(group="hover_type") if self.hover_mode == "touch": btn.bind( - on_release=lambda x: self.set_touch_mode('cursor')) + on_release=lambda x: self.set_touch_mode("cursor") + ) - elif mode == 'nearest': - btn.state = 'down' + elif mode == "nearest": + btn.state = "down" else: btn = Factory.NavToggleButton(group="toolbar_btn") @@ -335,12 +344,12 @@ def add_nav_btn(self, icon, fct, mode=None, btn_type=None): else: btn.bind(on_release=lambda x: fct()) - if self.nav_icon != "3D" and mode == 'pan': + if self.nav_icon != "3D" and mode == "pan": # by default pan button is press down - btn.state = 'down' + btn.state = "down" - if self.nav_icon == "3D" and mode == 'rotate': - btn.state = 'down' + if self.nav_icon == "3D" and mode == "rotate": + btn.state = "down" self.ids.container.add_widget(btn) @@ -348,7 +357,7 @@ def set_touch_mode(self, mode): self.figure_wgt.touch_mode = mode def home(self): - if hasattr(self.figure_wgt, 'main_home'): + if hasattr(self.figure_wgt, "main_home"): self.figure_wgt.main_home() else: self.figure_wgt.home() @@ -372,9 +381,10 @@ def home_3D(self): self.figure_wgt_layout.figure_wgt.home() -Factory.register('MatplotNavToolbar', MatplotNavToolbar) +Factory.register("MatplotNavToolbar", MatplotNavToolbar) -Builder.load_string(''' +Builder.load_string( + """ #:import nav_icons kivy_matplotlib_widget.icon_definitions.nav_icons : @@ -494,4 +504,5 @@ def home_3D(self): background_color :1,1,1,1 - ''') + """ +) diff --git a/kivy_matplotlib_widget/uix/selector_widget.py b/kivy_matplotlib_widget/uix/selector_widget.py index 6ca98f2..cf59f5c 100644 --- a/kivy_matplotlib_widget/uix/selector_widget.py +++ b/kivy_matplotlib_widget/uix/selector_widget.py @@ -1,12 +1,14 @@ from kivy.lang import Builder from kivy.uix.relativelayout import RelativeLayout from kivy.uix.floatlayout import FloatLayout -from kivy.properties import (ColorProperty, - ObjectProperty, - OptionProperty, - BooleanProperty, - ListProperty, - NumericProperty) +from kivy.properties import ( + ColorProperty, + ObjectProperty, + OptionProperty, + BooleanProperty, + ListProperty, + NumericProperty, +) from kivy.metrics import dp from kivy.core.window import Window from kivy.utils import platform @@ -21,14 +23,23 @@ from typing import List, Optional from kivy.clock import Clock -from kivy.graphics import Ellipse, Line, Color, Point, Mesh, PushMatrix, \ - PopMatrix, Rotate, InstructionGroup +from kivy.graphics import ( + Ellipse, + Line, + Color, + Point, + Mesh, + PushMatrix, + PopMatrix, + Rotate, + InstructionGroup, +) from kivy.graphics.tesselator import Tesselator from kivy.event import EventDispatcher import copy -kv = ''' +kv = """ : canvas.before: # TOP LINE @@ -379,7 +390,7 @@ pos: 0, 0 opacity:0 -''' +""" MINIMUM_HEIGHT = 20.0 MINIMUM_WIDTH = 20.0 @@ -447,7 +458,7 @@ def rdp(M, epsilon=0): dmax = dists[index] if dmax > epsilon: - result1 = rdp(M[:index + 1], epsilon) + result1 = rdp(M[: index + 1], epsilon) result2 = rdp(M[index:], epsilon) result = np.vstack((result1[:-1], result2)) @@ -492,24 +503,27 @@ def __init__(self, **kwargs): def on_kv_post(self, _): # only bind mouse position if not android or if the user set desktop # mode to false - if platform != 'android' and self.desktop_mode: + if platform != "android" and self.desktop_mode: Window.bind(mouse_pos=self.on_mouse_pos) def update_bg_color(self): fig_bg_color = self.figure_wgt.figure.get_facecolor() rgb_fig_bg_color = mcolors.to_rgb(fig_bg_color) - if (rgb_fig_bg_color[0] * 0.299 + rgb_fig_bg_color[1] - * 0.587 + rgb_fig_bg_color[2] * 0.114) > 186 / 255: + if ( + rgb_fig_bg_color[0] * 0.299 + + rgb_fig_bg_color[1] * 0.587 + + rgb_fig_bg_color[2] * 0.114 + ) > 186 / 255: self.bg_color = [1, 1, 1, 1] - self.bottom_color = ( - self.top_colors - ) = self.left_color = self.right_color = [1, 1, 1, 1] + self.bottom_color = self.top_colors = self.left_color = ( + self.right_color + ) = [1, 1, 1, 1] else: self.bg_color = [0, 0, 0, 1] - self.bottom_color = ( - self.top_colors - ) = self.left_color = self.right_color = [0, 0, 0, 1] + self.bottom_color = self.top_colors = self.left_color = ( + self.right_color + ) = [0, 0, 0, 1] def set_collection(self, collection): self.collection = collection @@ -519,7 +533,7 @@ def set_collection(self, collection): # Ensure that we have separate colors for each object self.fc = collection.get_facecolors() if len(self.fc) == 0: - raise ValueError('Collection must have a facecolor') + raise ValueError("Collection must have a facecolor") elif len(self.fc) == 1: self.fc = np.tile(self.fc, (self.Npts, 1)) @@ -531,11 +545,15 @@ def on_mouse_pos(self, something, touch): When the mouse moves, we check the position of the mouse and update the cursor accordingly. """ - if self.opacity and self.figure_wgt.touch_mode == 'selector' and self.collide_point( - *self.to_widget(*touch)): + if ( + self.opacity + and self.figure_wgt.touch_mode == "selector" + and self.collide_point(*self.to_widget(*touch)) + ): collision = self.collides_with_control_points( - something, self.to_widget(*touch)) + something, self.to_widget(*touch) + ) if collision in ["top left", "bottom right"]: Window.set_system_cursor("size_nwse") elif collision in ["top right", "bottom left"]: @@ -584,7 +602,7 @@ def collides_with_control_points(self, _, touch): return "top right" def on_touch_down(self, touch): - if self.figure_wgt.touch_mode != 'selector': + if self.figure_wgt.touch_mode != "selector": return if self.collide_point(*touch.pos) and self.opacity: @@ -629,7 +647,7 @@ def on_touch_down(self, touch): self.left_color = self.highlight_color self.right_color = self.highlight_color elif self.figure_wgt.collide_point(*self.to_window(*touch.pos)): - if self.figure_wgt.touch_mode == 'selector': + if self.figure_wgt.touch_mode == "selector": if touch.is_double_tap and self.callback_clear: self.callback_clear() return @@ -645,7 +663,7 @@ def on_touch_down(self, touch): return super().on_touch_down(touch) def on_touch_move(self, touch): - if self.figure_wgt.touch_mode != 'selector': + if self.figure_wgt.touch_mode != "selector": return if touch.grab_current is self: @@ -763,20 +781,15 @@ def on_touch_move(self, touch): elif not self.selected_side: if self.figure_wgt.collide_point( - * - self.to_window( - self.pos[0] + - touch.dx, - self.pos[1] + - touch.dy)) and self.figure_wgt.collide_point( - * - self.to_window( - self.pos[0] + - self.size[0] + - touch.dx, - self.pos[1] + - self.size[1] + - touch.dy)): + *self.to_window( + self.pos[0] + touch.dx, self.pos[1] + touch.dy + ) + ) and self.figure_wgt.collide_point( + *self.to_window( + self.pos[0] + self.size[0] + touch.dx, + self.pos[1] + self.size[1] + touch.dy, + ) + ): self.x += touch.dx self.y += touch.dy @@ -792,28 +805,32 @@ def on_touch_move(self, touch): return super().on_touch_move(touch) def on_touch_up(self, touch): - if self.figure_wgt.touch_mode != 'selector': + if self.figure_wgt.touch_mode != "selector": return if touch.grab_current is self: touch.ungrab(self) self.alpha = 1 - if (self.bg_color[0] * 0.299 + self.bg_color[1] - * 0.587 + self.bg_color[2] * 0.114) > 186 / 255: - self.bottom_color = ( - self.top_colors - ) = self.left_color = self.right_color = [0, 0, 0, 1] + if ( + self.bg_color[0] * 0.299 + + self.bg_color[1] * 0.587 + + self.bg_color[2] * 0.114 + ) > 186 / 255: + self.bottom_color = self.top_colors = self.left_color = ( + self.right_color + ) = [0, 0, 0, 1] else: - self.bottom_color = ( - self.top_colors - ) = self.left_color = self.right_color = [1, 1, 1, 1] + self.bottom_color = self.top_colors = self.left_color = ( + self.right_color + ) = [1, 1, 1, 1] if self.first_touch and self.selected_side == "new select": self.check_if_reverse_selection(touch) - if abs( - self.size[0]) < MINIMUM_WIDTH or abs( - self.size[1]) < MINIMUM_HEIGHT: + if ( + abs(self.size[0]) < MINIMUM_WIDTH + or abs(self.size[1]) < MINIMUM_HEIGHT + ): self.reset_selection() else: if self.verts is not None: @@ -825,8 +842,10 @@ def on_touch_up(self, touch): def check_if_reverse_selection(self, last_touch): - if last_touch.x > self.first_touch[0] and \ - last_touch.y < self.first_touch[1]: + if ( + last_touch.x > self.first_touch[0] + and last_touch.y < self.first_touch[1] + ): return @@ -853,13 +872,15 @@ def _get_box_data(self): x0 = self.to_window(*self.pos)[0] - self.figure_wgt.pos[0] y0 = self.to_window(*self.pos)[1] - self.figure_wgt.pos[1] x1 = self.to_window(*self.pos)[0] - self.figure_wgt.pos[0] - y1 = self.to_window(*self.pos)[1] + \ - self.height - self.figure_wgt.pos[1] + y1 = ( + self.to_window(*self.pos)[1] + self.height - self.figure_wgt.pos[1] + ) x3 = self.to_window(*self.pos)[0] + self.width - self.figure_wgt.pos[0] y3 = self.to_window(*self.pos)[1] - self.figure_wgt.pos[1] x2 = self.to_window(*self.pos)[0] + self.width - self.figure_wgt.pos[0] - y2 = self.to_window(*self.pos)[1] + \ - self.height - self.figure_wgt.pos[1] + y2 = ( + self.to_window(*self.pos)[1] + self.height - self.figure_wgt.pos[1] + ) x0_box, y0_box = trans.transform_point((x0, y0)) x1_box, y1_box = trans.transform_point((x1, y1)) @@ -877,14 +898,16 @@ def onselect(self, verts): path = Path(verts) if self.collection: self.ind = np.nonzero(path.contains_points(self.xys))[ - 0] # xys collection.get_offsets() + 0 + ] # xys collection.get_offsets() self.fc[:, -1] = self.alpha_other self.fc[self.ind, -1] = 1 self.collection.set_facecolors(self.fc) if self.line: xdata, ydata = self.line.get_xydata().T self.ind_line = np.nonzero( - path.contains_points(np.array([xdata, ydata]).T))[0] + path.contains_points(np.array([xdata, ydata]).T) + )[0] self.figure_wgt.figure.canvas.draw_idle() if self.callback: @@ -909,7 +932,7 @@ def clear_selection(self): self.figure_wgt.figure.canvas.draw_idle() -def _rotate_pos(x, y, cx, cy, angle, base_angle=0.): +def _rotate_pos(x, y, cx, cy, angle, base_angle=0.0): """Rotates ``(x, y)`` by angle ``angle`` along a circle centered at ``(cx, cy)``. """ @@ -920,7 +943,7 @@ def _rotate_pos(x, y, cx, cy, angle, base_angle=0.): class PaintCanvasBehaviorBase(EventDispatcher): - '''Abstract base class that can paint on a widget canvas. See + """Abstract base class that can paint on a widget canvas. See :class:`PaintCanvasBehavior` for a the implementation that can be used with touch to draw upon. @@ -957,30 +980,30 @@ class PaintCanvasBehaviorBase(EventDispatcher): Finally, if no shape is selected or active, we create a new one on up or if the mouse moves. - ''' + """ - shapes: List['PaintShape'] = ListProperty([]) + shapes: List["PaintShape"] = ListProperty([]) """A list of :class:`PaintShape` instances currently added to the painting widget. """ - selected_shapes: List['PaintShape'] = ListProperty([]) + selected_shapes: List["PaintShape"] = ListProperty([]) """A list of :class:`PaintShape` instances currently selected in the painting widget. """ - current_shape: Optional['PaintShape'] = None - '''Holds shape currently being edited. Can be a finished shape, e.g. if + current_shape: Optional["PaintShape"] = None + """Holds shape currently being edited. Can be a finished shape, e.g. if a point is selected. Read only. - ''' + """ locked: bool = BooleanProperty(False) - '''It locks all added shapes so they cannot be interacted with. + """It locks all added shapes so they cannot be interacted with. Setting it to `True` will finish any shapes being drawn and unselect them. - ''' + """ multiselect: bool = BooleanProperty(False) """Whether multiple shapes can be selected by holding down control. @@ -994,7 +1017,7 @@ class PaintCanvasBehaviorBase(EventDispatcher): able to select that point. It's in :func:`kivy.metrics.dp` units. """ - long_touch_delay: float = .7 + long_touch_delay: float = 0.7 """Minimum delay after a touch down before a touch up, for the touch to be considered a long touch. """ @@ -1008,13 +1031,14 @@ class PaintCanvasBehaviorBase(EventDispatcher): def __init__(self, **kwargs): super(PaintCanvasBehaviorBase, self).__init__(**kwargs) self._ctrl_down = set() - self.fbind('locked', self._handle_locked) + self.fbind("locked", self._handle_locked) def set_focus(*largs): if not self.focus: self.finish_current_shape() - if hasattr(self, 'focus'): - self.fbind('focus', set_focus) + + if hasattr(self, "focus"): + self.fbind("focus", set_focus) def _handle_locked(self, *largs): if not self.locked: @@ -1319,19 +1343,19 @@ def get_closest_shape(self, x, y): def on_touch_down(self, touch): ud = touch.ud # whether the touch was used by the painter for any purpose whatsoever - ud['paint_interacted'] = False + ud["paint_interacted"] = False # can be one of current, selected, done indicating how the touch was # used, if it was used. done means the touch is done and don't do # anything with anymore. selected means a shape was selected. - ud['paint_interaction'] = '' + ud["paint_interaction"] = "" # if this touch experienced a move - ud['paint_touch_moved'] = False + ud["paint_touch_moved"] = False # the shape that was selected if paint_interaction is selected - ud['paint_selected_shape'] = None + ud["paint_selected_shape"] = None # whether the selected_shapes contained the shape this touch was # used to select a shape in touch_down. - ud['paint_was_selected'] = False - ud['paint_cleared_selection'] = False + ud["paint_was_selected"] = False + ud["paint_cleared_selection"] = False if self.locked or self._processing_touch is not None: return super(PaintCanvasBehaviorBase, self).on_touch_down(touch) @@ -1342,68 +1366,71 @@ def on_touch_down(self, touch): if not self.collide_point(touch.x, touch.y): return False - ud['paint_interacted'] = True + ud["paint_interacted"] = True self._processing_touch = touch touch.grab(self) # if we have a current shape, all touch will go to it current_shape = self.current_shape if current_shape is not None: - ud['paint_cleared_selection'] = current_shape.finished and \ - current_shape.get_interaction_point_dist(touch.pos) \ + ud["paint_cleared_selection"] = ( + current_shape.finished + and current_shape.get_interaction_point_dist(touch.pos) >= dp(self.min_touch_dist) - if ud['paint_cleared_selection']: + ) + if ud["paint_cleared_selection"]: self.finish_current_shape() else: - ud['paint_interaction'] = 'current' + ud["paint_interaction"] = "current" current_shape.handle_touch_down(touch) return True # next try to interact by selecting or interacting with selected shapes shape = self.get_closest_selection_point_shape(touch.x, touch.y) if shape is not None: - ud['paint_interaction'] = 'selected' - ud['paint_selected_shape'] = shape - ud['paint_was_selected'] = shape not in self.selected_shapes + ud["paint_interaction"] = "selected" + ud["paint_selected_shape"] = shape + ud["paint_was_selected"] = shape not in self.selected_shapes self._long_touch_trigger = Clock.schedule_once( - partial(self.do_long_touch, touch), self.long_touch_delay) + partial(self.do_long_touch, touch), self.long_touch_delay + ) return True if self._ctrl_down: - ud['paint_interaction'] = 'done' + ud["paint_interaction"] = "done" return True self._long_touch_trigger = Clock.schedule_once( - partial(self.do_long_touch, touch), self.long_touch_delay) + partial(self.do_long_touch, touch), self.long_touch_delay + ) return True def do_long_touch(self, touch, *largs): - """Handles a long touch by the user. - """ + """Handles a long touch by the user.""" assert self._processing_touch touch.push() touch.apply_transform_2d(self.to_widget) self._long_touch_trigger = None ud = touch.ud - if ud['paint_interaction'] == 'selected': + if ud["paint_interaction"] == "selected": if self._ctrl_down: - ud['paint_interaction'] = 'done' + ud["paint_interaction"] = "done" touch.pop() return - ud['paint_interaction'] = '' + ud["paint_interaction"] = "" - assert ud['paint_interacted'] - assert not ud['paint_interaction'] + assert ud["paint_interacted"] + assert not ud["paint_interaction"] self.clear_selected_shapes() shape = self.get_closest_shape(touch.x, touch.y) if shape is not None: - ud['paint_interaction'] = 'current' + ud["paint_interaction"] = "current" self.start_shape_interaction(shape, (touch.x, touch.y)) else: - ud['paint_interaction'] = 'done' + ud["paint_interaction"] = "done" touch.pop() def on_touch_move(self, touch): @@ -1411,7 +1438,7 @@ def on_touch_move(self, touch): # return False ud = touch.ud - if 'paint_interacted' not in ud or not ud['paint_interacted']: + if "paint_interacted" not in ud or not ud["paint_interacted"]: return super(PaintCanvasBehaviorBase, self).on_touch_move(touch) if self._long_touch_trigger is not None: @@ -1422,16 +1449,16 @@ def on_touch_move(self, touch): # for move, only use normal touch, not touch outside range return False - if ud['paint_interaction'] == 'done': + if ud["paint_interaction"] == "done": return True - ud['paint_touch_moved'] = True + ud["paint_touch_moved"] = True if not self.collide_point(touch.x, touch.y): return True - if not ud['paint_interaction']: - if ud['paint_cleared_selection'] or self.clear_selected_shapes(): - ud['paint_interaction'] = 'done' + if not ud["paint_interaction"]: + if ud["paint_cleared_selection"] or self.clear_selected_shapes(): + ud["paint_interaction"] = "done" return True # finally try creating a new shape @@ -1440,36 +1467,38 @@ def on_touch_move(self, touch): if shape is not None: shape.handle_touch_down(touch, opos=touch.opos) self.current_shape = shape - if self.check_new_shape_done(shape, 'down'): + if self.check_new_shape_done(shape, "down"): self.finish_current_shape() - ud['paint_interaction'] = 'done' + ud["paint_interaction"] = "done" return True - ud['paint_interaction'] = 'current_new' + ud["paint_interaction"] = "current_new" else: - ud['paint_interaction'] = 'done' + ud["paint_interaction"] = "done" return True - if ud['paint_interaction'] in ('current', 'current_new'): + if ud["paint_interaction"] in ("current", "current_new"): if self.current_shape is None: - ud['paint_interaction'] = 'done' + ud["paint_interaction"] = "done" else: self.current_shape.handle_touch_move(touch) return True - assert ud['paint_interaction'] == 'selected' + assert ud["paint_interaction"] == "selected" - shape = ud['paint_selected_shape'] + shape = ud["paint_selected_shape"] if shape not in self.shapes: - ud['paint_interaction'] = 'done' + ud["paint_interaction"] = "done" return True if self._ctrl_down or self.multiselect: if shape not in self.selected_shapes: self.select_shape(shape) else: - if len(self.selected_shapes) != 1 or \ - self.selected_shapes[0] != shape: + if ( + len(self.selected_shapes) != 1 + or self.selected_shapes[0] != shape + ): self.clear_selected_shapes() self.select_shape(shape) @@ -1479,7 +1508,7 @@ def on_touch_move(self, touch): def on_touch_up(self, touch): ud = touch.ud - if 'paint_interacted' not in ud or not ud['paint_interacted']: + if "paint_interacted" not in ud or not ud["paint_interacted"]: return super(PaintCanvasBehaviorBase, self).on_touch_up(touch) if self._long_touch_trigger is not None: @@ -1488,15 +1517,15 @@ def on_touch_up(self, touch): touch.ungrab(self) # don't process the same touch up again - paint_interaction = ud['paint_interaction'] - ud['paint_interaction'] = 'done' + paint_interaction = ud["paint_interaction"] + ud["paint_interaction"] = "done" self._processing_touch = None - if paint_interaction == 'done': + if paint_interaction == "done": return True if not paint_interaction: - if ud['paint_cleared_selection'] or self.clear_selected_shapes(): + if ud["paint_cleared_selection"] or self.clear_selected_shapes(): return True # finally try creating a new shape @@ -1505,61 +1534,70 @@ def on_touch_up(self, touch): if shape is not None: shape.handle_touch_down(touch, opos=touch.opos) self.current_shape = shape - if self.check_new_shape_done(shape, 'down'): + if self.check_new_shape_done(shape, "down"): self.finish_current_shape() return True - paint_interaction = 'current_new' + paint_interaction = "current_new" else: return True - if paint_interaction in ('current', 'current_new'): + if paint_interaction in ("current", "current_new"): if self.current_shape is not None: self.current_shape.handle_touch_up( - touch, outside=not self.collide_point(touch.x, touch.y)) - if self.check_new_shape_done(self.current_shape, 'up'): + touch, outside=not self.collide_point(touch.x, touch.y) + ) + if self.check_new_shape_done(self.current_shape, "up"): self.finish_current_shape() return True if not self.collide_point(touch.x, touch.y): return True - assert paint_interaction == 'selected' - if ud['paint_touch_moved']: + assert paint_interaction == "selected" + if ud["paint_touch_moved"]: # moving normally doesn't change the selection state # this is a quick selection mode where someone dragged a object but # nothing was selected so don't keep the object that was dragged # selected - if ud['paint_was_selected'] and \ - len(self.selected_shapes) == 1 and \ - self.selected_shapes[0] == ud['paint_selected_shape']: + if ( + ud["paint_was_selected"] + and len(self.selected_shapes) == 1 + and self.selected_shapes[0] == ud["paint_selected_shape"] + ): self.clear_selected_shapes() return True - shape = ud['paint_selected_shape'] + shape = ud["paint_selected_shape"] if shape not in self.shapes: return True if self._ctrl_down or self.multiselect: - if not ud['paint_was_selected'] and shape in self.selected_shapes: + if not ud["paint_was_selected"] and shape in self.selected_shapes: self.deselect_shape(shape) - elif ud['paint_was_selected']: + elif ud["paint_was_selected"]: self.select_shape(shape) else: - if len(self.selected_shapes) != 1 or \ - self.selected_shapes[0] != shape: + if ( + len(self.selected_shapes) != 1 + or self.selected_shapes[0] != shape + ): self.clear_selected_shapes() self.select_shape(shape) return True def keyboard_on_key_down(self, window, keycode, text, modifiers): - if keycode[1] in ('lctrl', 'ctrl', 'rctrl'): + if keycode[1] in ("lctrl", "ctrl", "rctrl"): self._ctrl_down.add(keycode[1]) arrows = { - 'left': (-1, 0), 'right': (1, 0), 'up': (0, 1), 'down': (0, -1)} + "left": (-1, 0), + "right": (1, 0), + "up": (0, 1), + "down": (0, -1), + } if keycode[1] in arrows and self.selected_shapes: dpos = arrows[keycode[1]] for shape in self.selected_shapes: @@ -1569,21 +1607,21 @@ def keyboard_on_key_down(self, window, keycode, text, modifiers): return False def keyboard_on_key_up(self, window, keycode): - if keycode[1] in ('lctrl', 'ctrl', 'rctrl'): + if keycode[1] in ("lctrl", "ctrl", "rctrl"): self._ctrl_down.remove(keycode[1]) - if keycode[1] == 'escape': + if keycode[1] == "escape": if self.finish_current_shape() or self.clear_selected_shapes(): return True - elif keycode[1] == 'delete': + elif keycode[1] == "delete": if self.delete_selected_shapes(): return True - elif keycode[1] == 'a' and self._ctrl_down: + elif keycode[1] == "a" and self._ctrl_down: for shape in self.shapes: if not shape.locked: self.select_shape(shape) return True - elif keycode[1] == 'd' and self._ctrl_down: + elif keycode[1] == "d" and self._ctrl_down: if self.duplicate_selected_shapes(): return True @@ -1635,11 +1673,11 @@ class PaintShape(EventDispatcher): translated etc. This is only dispatched once the shape is finished. """ - line_width = NumericProperty('1dp') + line_width = NumericProperty("1dp") """The line width of lines shown, in :func:`~kivy.metrics.dp`. """ - pointsize = NumericProperty('0.01dp') + pointsize = NumericProperty("0.01dp") """The point size of points shown, in :func:`~kivy.metrics.dp`. """ @@ -1647,11 +1685,11 @@ class PaintShape(EventDispatcher): """The line color of lines and/or points shown. """ - selection_point_color = 1, .5, .31, 1 + selection_point_color = 1, 0.5, 0.31, 1 """The color of the point by which the shape is selected/dragged. """ - line_color_locked = .4, .56, .36, 1 + line_color_locked = 0.4, 0.56, 0.36, 1 """The line color of lines and/or points shown when the shape is :attr:`locked`. """ @@ -1706,7 +1744,7 @@ class PaintShape(EventDispatcher): Read only. """ - graphics_name = '' + graphics_name = "" """The group name given to all the canvas instructions added to the :attr:`instruction_group`. These are the lines, points etc. @@ -1729,13 +1767,16 @@ class PaintShape(EventDispatcher): Read only. """ - __events__ = ('on_update', ) + __events__ = ("on_update",) def __init__( - self, line_color=(0, 0, 0, 0.65), - line_color_locked=(.4, .56, .36, 1), - selection_point_color=(62 / 255, 254 / 255, 1, 1), **kwargs): - self.graphics_name = '{}-{}'.format(self.__class__.__name__, id(self)) + self, + line_color=(0, 0, 0, 0.65), + line_color_locked=(0.4, 0.56, 0.36, 1), + selection_point_color=(62 / 255, 254 / 255, 1, 1), + **kwargs, + ): + self.graphics_name = "{}-{}".format(self.__class__.__name__, id(self)) super(PaintShape, self).__init__(**kwargs) self.line_color = line_color @@ -1743,8 +1784,8 @@ def __init__( self.selection_point_color = selection_point_color self.color_instructions = [] - self.fbind('line_width', self._update_from_line_width) - self.fbind('pointsize', self._update_from_pointsize) + self.fbind("line_width", self._update_from_line_width) + self.fbind("pointsize", self._update_from_pointsize) def _update_from_line_width(self, *args): pass @@ -2040,7 +2081,7 @@ def create_shape_from_state(cls, state): shape.set_valid() shape.finish() if not shape.is_valid: - raise ValueError('Shape {} is not valid'.format(shape)) + raise ValueError("Shape {} is not valid".format(shape)) return shape @@ -2054,10 +2095,15 @@ def get_state(self, state=None): :return: A dict with all the config data of the shape. """ d = {} if state is None else state - for k in ['line_color', 'line_width', 'is_valid', 'locked', - 'line_color_locked']: + for k in [ + "line_color", + "line_width", + "is_valid", + "locked", + "line_color_locked", + ]: d[k] = getattr(self, k) - d['cls'] = self.__class__.__name__ + d["cls"] = self.__class__.__name__ return d @@ -2070,10 +2116,10 @@ def set_state(self, state): state = dict(state) lock = None for k, v in state.items(): - if k == 'locked': + if k == "locked": lock = bool(v) continue - elif k == 'cls': + elif k == "cls": continue setattr(self, k, v) @@ -2083,7 +2129,7 @@ def set_state(self, state): self.lock() elif lock is False: self.unlock() - self.dispatch('on_update') + self.dispatch("on_update") def __deepcopy__(self, memo): obj = self.__class__() @@ -2112,14 +2158,14 @@ def show_shape_in_canvas(self): :meth:`hide_shape_in_canvas` to display the shape again. """ for color in self.color_instructions: - color.rgba = [color.r, color.g, color.b, 1.] + color.rgba = [color.r, color.g, color.b, 1.0] def hide_shape_in_canvas(self): """Hides the shape so that it is not visible in the widget to which it was added with :meth:`add_shape_to_canvas`. """ for color in self.color_instructions: - color.rgba = [color.r, color.g, color.b, 0.] + color.rgba = [color.r, color.g, color.b, 0.0] def rescale(self, scale): """Rescales the all the perimeter points/lines distance from the center @@ -2140,7 +2186,8 @@ class PaintEllipse(PaintShape): The shape has a single point by which it can be dragged or the radius expanded. """ - pointsize = NumericProperty('7dp') + + pointsize = NumericProperty("7dp") center = ListProperty([0, 0]) """A 2-tuple containing the center position of the ellipse. @@ -2151,7 +2198,7 @@ class PaintEllipse(PaintShape): or if the shape is not :attr:`finished`. """ - radius_x = NumericProperty('10dp') + radius_x = NumericProperty("10dp") """The x-radius of the circle. This can be set, and the shape will resize itself to the new size. @@ -2160,7 +2207,7 @@ class PaintEllipse(PaintShape): or if the shape is not :attr:`finished`. """ - radius_y = NumericProperty('15dp') + radius_y = NumericProperty("15dp") """The y-radius of the circle. This can be set, and the shape will resize itself to the new size. @@ -2170,7 +2217,7 @@ class PaintEllipse(PaintShape): """ angle = NumericProperty(0) - '''The angle in radians by which the x-axis is rotated counter clockwise. + """The angle in radians by which the x-axis is rotated counter clockwise. This allows the ellipse to be rotated rather than just be aligned to the default axes. @@ -2178,7 +2225,7 @@ class PaintEllipse(PaintShape): This is read only while a user is interacting with the shape with touch, or if the shape is not :attr:`finished`. - ''' + """ perim_ellipse_inst = None """(internal) The graphics instruction representing the perimeter. @@ -2213,15 +2260,21 @@ def __init__(self, **kwargs): def update(*largs): self.translate() - self.fbind('radius_x', update) - self.fbind('radius_y', update) - self.fbind('angle', update) - self.fbind('center', update) + + self.fbind("radius_x", update) + self.fbind("radius_y", update) + self.fbind("angle", update) + self.fbind("center", update) @classmethod def create_shape( - cls, center=(0, 0), radius_x=dp(10), radius_y=dp(15), angle=0, - **inst_kwargs): + cls, + center=(0, 0), + radius_x=dp(10), + radius_y=dp(15), + angle=0, + **inst_kwargs, + ): """Creates a new ellipse instance from the given arguments. E.g.: @@ -2239,13 +2292,16 @@ def create_shape( :return: The newly created ellipse instance. """ shape = cls( - center=center, radius_x=radius_x, radius_y=radius_y, angle=angle, - **inst_kwargs) + center=center, + radius_x=radius_x, + radius_y=radius_y, + angle=angle, + **inst_kwargs, + ) shape.set_valid() shape.finish() if not shape.is_valid: - raise ValueError( - 'Shape {} is not valid'.format(shape)) + raise ValueError("Shape {} is not valid".format(shape)) return shape def add_area_graphics_to_canvas(self, name, canvas): @@ -2255,8 +2311,10 @@ def add_area_graphics_to_canvas(self, name, canvas): angle = self.angle PushMatrix(group=name) - Rotate(angle=angle / pi * 180., origin=(x, y), group=name) - Ellipse(size=(rx * 2., ry * 2.), pos=(x - rx, y - ry), group=name) + Rotate(angle=angle / pi * 180.0, origin=(x, y), group=name) + Ellipse( + size=(rx * 2.0, ry * 2.0), pos=(x - rx, y - ry), group=name + ) PopMatrix(group=name) def add_shape_to_canvas(self, paint_widget): @@ -2270,30 +2328,39 @@ def add_shape_to_canvas(self, paint_widget): angle = self.angle i1 = self.ellipse_color_inst = Color( - *self.line_color, group=self.graphics_name) + *self.line_color, group=self.graphics_name + ) colors.append(i1) i2 = PushMatrix(group=self.graphics_name) i3 = self.rotate_inst = Rotate( - angle=angle / pi * 180., origin=(x, y), group=self.graphics_name) + angle=angle / pi * 180.0, origin=(x, y), group=self.graphics_name + ) i4 = self.perim_ellipse_inst = Line( ellipse=(x - rx, y - ry, 2 * rx, 2 * ry), - width=self.line_width, group=self.graphics_name) + width=self.line_width, + group=self.graphics_name, + ) i66 = self.perim_color_inst2 = Color( - *self.selection_point_color, group=self.graphics_name) + *self.selection_point_color, group=self.graphics_name + ) colors.append(i66) i6 = self.selection_point_inst2 = Point( - points=[x, y + ry], pointsize=self.pointsize, - group=self.graphics_name) + points=[x, y + ry], + pointsize=self.pointsize, + group=self.graphics_name, + ) i8 = Color(*self.selection_point_color, group=self.graphics_name) colors.append(i8) i5 = self.selection_point_inst = Point( - points=[x + rx, y], pointsize=self.pointsize, - group=self.graphics_name) + points=[x + rx, y], + pointsize=self.pointsize, + group=self.graphics_name, + ) i7 = PopMatrix(group=self.graphics_name) for inst in (i1, i2, i3, i4, i66, i6, i8, i5, i7): @@ -2405,7 +2472,8 @@ def _get_interaction_points_dist(self, pos): d1 = ((x1 - x_) ** 2 + (y1 - y_) ** 2) ** 0.5 x_, y_ = _rotate_pos( - x2, y2 + self.radius_y, x2, y2, self.angle, base_angle=pi / 2.0) + x2, y2 + self.radius_y, x2, y2, self.angle, base_angle=pi / 2.0 + ) d2 = ((x1 - x_) ** 2 + (y1 - y_) ** 2) ** 0.5 return d1, d2 @@ -2452,18 +2520,18 @@ def translate(self, dpos=None, pos=None): angle = self.angle self.center = x, y if self.rotate_inst is not None: - self.rotate_inst.angle = angle / pi * 180. + self.rotate_inst.angle = angle / pi * 180.0 self.rotate_inst.origin = x, y self.perim_ellipse_inst.ellipse = x - rx, y - ry, 2 * rx, 2 * ry self.selection_point_inst.points = [x + rx, y] self.selection_point_inst2.points = [x, y + ry] - self.dispatch('on_update') + self.dispatch("on_update") return True def get_state(self, state=None): d = super(PaintEllipse, self).get_state(state) - for k in ['center', 'radius_x', 'radius_y', 'angle']: + for k in ["center", "radius_x", "radius_y", "angle"]: d[k] = getattr(self, k) return d @@ -2475,13 +2543,12 @@ def get_widget_verts(self): cx, cy = self.center x0, y0 = self._get_coord_from_angle(self.selection_point_inst.points) x1, y1 = self._get_coord_from_angle( - self.selection_point_inst2.points, base_angle=pi / 2.) + self.selection_point_inst2.points, base_angle=pi / 2.0 + ) - return np.array([[cx, cy], - [x0, y0], - [x1, y1]]) + return np.array([[cx, cy], [x0, y0], [x1, y1]]) - def _get_coord_from_angle(self, pos, base_angle=0.): + def _get_coord_from_angle(self, pos, base_angle=0.0): x1, y1 = pos x2, y2 = self.center @@ -2605,10 +2672,10 @@ def update(*largs): self.perim_line_inst.points = self.points self.perim_points_inst.points = self.points self.selection_point_inst.points = self.selection_point - self.dispatch('on_update') + self.dispatch("on_update") - self.fbind('points', update) - self.fbind('selection_point', update) + self.fbind("points", update) + self.fbind("selection_point", update) update() @classmethod @@ -2632,12 +2699,12 @@ def create_shape(cls, points=(), selection_point=(), **inst_kwargs): selection_point = points[:2] shape = cls( - points=points, selection_point=selection_point, **inst_kwargs) + points=points, selection_point=selection_point, **inst_kwargs + ) shape.set_valid() shape.finish() if not shape.is_valid: - raise ValueError( - 'Shape {} is not valid'.format(shape)) + raise ValueError("Shape {} is not valid".format(shape)) return shape def add_area_graphics_to_canvas(self, name, canvas): @@ -2652,8 +2719,11 @@ def add_area_graphics_to_canvas(self, name, canvas): if tess.tesselate(): for vertices, indices in tess.meshes: Mesh( - vertices=vertices, indices=indices, - mode='triangle_fan', group=name) + vertices=vertices, + indices=indices, + mode="triangle_fan", + group=name, + ) def add_shape_to_canvas(self, paint_widget): if not super(PaintPolygon, self).add_shape_to_canvas(paint_widget): @@ -2662,27 +2732,37 @@ def add_shape_to_canvas(self, paint_widget): colors = self.color_instructions = [] i1 = self.perim_color_inst = Color( - *self.line_color, group=self.graphics_name) + *self.line_color, group=self.graphics_name + ) colors.append(i1) i2 = self.perim_line_inst = Line( - points=self.points, width=self.line_width, - close=self.finished, group=self.graphics_name) + points=self.points, + width=self.line_width, + close=self.finished, + group=self.graphics_name, + ) i33 = self.perim_color_inst2 = Color( - *self.selection_point_color, group=self.graphics_name) + *self.selection_point_color, group=self.graphics_name + ) colors.append(i33) i3 = self.perim_points_inst = Point( - points=self.points, pointsize=self.pointsize, - group=self.graphics_name) + points=self.points, + pointsize=self.pointsize, + group=self.graphics_name, + ) insts = [i1, i2, i33, i3] if not self.finished: points = self.points[-2:] + self.points[:2] line = self.perim_close_inst = Line( - points=points, width=self.line_width, - close=False, group=self.graphics_name) + points=points, + width=self.line_width, + close=False, + group=self.graphics_name, + ) line.dash_offset = 4 line.dash_length = 4 insts.append(line) @@ -2691,8 +2771,10 @@ def add_shape_to_canvas(self, paint_widget): colors.append(i4) i5 = self.selection_point_inst = Point( - points=self.selection_point, pointsize=self.pointsize, - group=self.graphics_name) + points=self.selection_point, + pointsize=self.pointsize, + group=self.graphics_name, + ) for inst in insts + [i4, i5]: self.instruction_group.add(inst) @@ -2742,10 +2824,10 @@ def handle_touch_move(self, touch): return self._last_point_moved = i - x, y = self.points[2 * i: 2 * i + 2] + x, y = self.points[2 * i : 2 * i + 2] x += touch.dx y += touch.dy - self.points[2 * i: 2 * i + 2] = x, y + self.points[2 * i : 2 * i + 2] = x, y if not i: self.selection_point = [x, y] @@ -2760,8 +2842,9 @@ def handle_touch_up(self, touch, outside=False): self.selection_point = touch.pos[:] self.points.extend(touch.pos) if self.perim_close_inst is not None: - self.perim_close_inst.points = \ + self.perim_close_inst.points = ( self.points[-2:] + self.points[:2] + ) if len(self.points) >= 6: self.is_valid = True else: @@ -2863,19 +2946,21 @@ def translate(self, dpos=None, pos=None): assert False points = self.points - new_points = [None, ] * len(points) + new_points = [ + None, + ] * len(points) for i in range(len(points) // 2): new_points[2 * i] = points[2 * i] + dx new_points[2 * i + 1] = points[2 * i + 1] + dy self.selection_point = new_points[:2] self.points = new_points - self.dispatch('on_update') + self.dispatch("on_update") return True def get_state(self, state=None): d = super(PaintPolygon, self).get_state(state) - for k in ['points', 'selection_point']: + for k in ["points", "selection_point"]: d[k] = getattr(self, k) return d @@ -2913,8 +2998,9 @@ def handle_touch_down(self, touch, opos=None): self.points.extend(pos) if self.perim_close_inst is not None: - self.perim_close_inst.points = \ + self.perim_close_inst.points = ( self.points[-2:] + self.points[:2] + ) if len(self.points) >= 6: self.is_valid = True @@ -2948,8 +3034,10 @@ class PaintCanvasBehavior(PaintCanvasBehaviorBase): This is a demo class to be used as a guide for your own usage. """ - draw_mode = OptionProperty('freeform', options=[ - 'circle', 'ellipse', 'polygon', 'freeform', 'point', 'none']) + draw_mode = OptionProperty( + "freeform", + options=["circle", "ellipse", "polygon", "freeform", "point", "none"], + ) """The shape to create when a user starts drawing with a touch. It can be one of ``'circle', 'ellipse', 'polygon', 'freeform', 'point', 'none'`` and it starts drawing the corresponding shape in the painter widget. @@ -2957,9 +3045,11 @@ class PaintCanvasBehavior(PaintCanvasBehaviorBase): When ``'none'``, not shape will be drawn and only selection is possible. """ - shape_cls_map = {'ellipse': PaintEllipse, - 'polygon': PaintPolygon, 'freeform': PaintFreeformPolygon - } + shape_cls_map = { + "ellipse": PaintEllipse, + "polygon": PaintPolygon, + "freeform": PaintFreeformPolygon, + } """Maps :attr:`draw_mode` to the actual :attr:`PaintShape` subclass to be used for drawing when in this mode. @@ -2976,10 +3066,12 @@ class PaintCanvasBehavior(PaintCanvasBehaviorBase): def __init__(self, **kwargs): self.shape_cls_name_map = { - cls.__name__: cls for cls in self.shape_cls_map.values() - if cls is not None} + cls.__name__: cls + for cls in self.shape_cls_map.values() + if cls is not None + } super(PaintCanvasBehavior, self).__init__(**kwargs) - self.fbind('draw_mode', self._handle_draw_mode) + self.fbind("draw_mode", self._handle_draw_mode) def _handle_draw_mode(self, *largs): self.finish_current_shape() @@ -2987,7 +3079,7 @@ def _handle_draw_mode(self, *largs): def create_shape_with_touch(self, touch): draw_mode = self.draw_mode if draw_mode is None: - raise TypeError('Cannot create a shape when the draw mode is none') + raise TypeError("Cannot create a shape when the draw mode is none") shape_cls = self.shape_cls_map[draw_mode] @@ -3037,7 +3129,7 @@ def create_shape_from_state(self, state, add=True): :param add: Whether to add to the painter. :return: The newly created shape instance. """ - cls = self.shape_cls_name_map[state['cls']] + cls = self.shape_cls_name_map[state["cls"]] shape = cls.create_shape_from_state(state) if add: @@ -3089,7 +3181,8 @@ def on_mouse_pos(self, something, touch): if self.opacity and self.collide_point(*self.to_widget(*touch)): collision = self.collides_with_control_points( - something, self.to_widget(*touch)) + something, self.to_widget(*touch) + ) if collision in ["top left", "bottom right"]: Window.set_system_cursor("size_nwse") elif collision in ["top right", "bottom left"]: @@ -3117,23 +3210,23 @@ def add_shape(self, shape): return False def on_touch_down(self, touch): - if self.figure_wgt.touch_mode != 'selector': + if self.figure_wgt.touch_mode != "selector": return False ud = touch.ud # whether the touch was used by the painter for any purpose whatsoever - ud['paint_interacted'] = False + ud["paint_interacted"] = False # can be one of current, selected, done indicating how the touch was # used, if it was used. done means the touch is done and don't do # anything with anymore. selected means a shape was selected. - ud['paint_interaction'] = '' + ud["paint_interaction"] = "" # if this touch experienced a move - ud['paint_touch_moved'] = False + ud["paint_touch_moved"] = False # the shape that was selected if paint_interaction is selected - ud['paint_selected_shape'] = None + ud["paint_selected_shape"] = None # whether the selected_shapes contained the shape this touch was # used to select a shape in touch_down. - ud['paint_was_selected'] = False - ud['paint_cleared_selection'] = False + ud["paint_was_selected"] = False + ud["paint_cleared_selection"] = False if self.locked or self._processing_touch is not None: return super(PaintCanvasBehaviorBase, self).on_touch_down(touch) @@ -3142,7 +3235,7 @@ def on_touch_down(self, touch): return True if self.figure_wgt.collide_point(*self.to_window(*touch.pos)): - if self.figure_wgt.touch_mode == 'selector': + if self.figure_wgt.touch_mode == "selector": if touch.is_double_tap and self.callback_clear: self.callback_clear() return @@ -3151,78 +3244,99 @@ def on_touch_down(self, touch): result = False if self.shapes and self.opacity == 1: result = self.check_if_inside_path( - self.shapes[0].points, x, y) + self.shapes[0].points, x, y + ) self.opacity = 1 if result: shape = self.get_closest_selection_point_shape( - touch.x, touch.y) + touch.x, touch.y + ) shape2 = self.get_closest_shape(touch.x, touch.y) if shape is not None or shape2 is not None: self.selected_side = "modify" if shape: self.start_shape_interaction( - shape, (touch.x, touch.y)) + shape, (touch.x, touch.y) + ) shape.handle_touch_up( - touch, outside=not self.collide_point( - touch.x, touch.y)) - if self.check_new_shape_done(shape, 'up'): + touch, + outside=not self.collide_point( + touch.x, touch.y + ), + ) + if self.check_new_shape_done(shape, "up"): self.finish_current_shape() else: self.start_shape_interaction( - shape2, (touch.x, touch.y)) + shape2, (touch.x, touch.y) + ) shape2.handle_touch_up( - touch, outside=not self.collide_point( - touch.x, touch.y)) - if self.check_new_shape_done(shape2, 'up'): + touch, + outside=not self.collide_point( + touch.x, touch.y + ), + ) + if self.check_new_shape_done(shape2, "up"): self.finish_current_shape() else: self.selected_side = "pan" else: shape = self.get_closest_selection_point_shape( - touch.x, touch.y) + touch.x, touch.y + ) shape2 = self.get_closest_shape(touch.x, touch.y) if shape is not None or shape2 is not None: self.selected_side = "modify" if shape: - ud['paint_interaction'] = 'current' + ud["paint_interaction"] = "current" self.start_shape_interaction( - shape, (touch.x, touch.y)) + shape, (touch.x, touch.y) + ) shape.handle_touch_up( - touch, outside=not self.collide_point( - touch.x, touch.y)) - if self.check_new_shape_done(shape, 'up'): + touch, + outside=not self.collide_point( + touch.x, touch.y + ), + ) + if self.check_new_shape_done(shape, "up"): self.finish_current_shape() else: - ud['paint_interaction'] = 'current' + ud["paint_interaction"] = "current" self.start_shape_interaction( - shape2, (touch.x, touch.y)) + shape2, (touch.x, touch.y) + ) shape2.handle_touch_up( - touch, outside=not self.collide_point( - touch.x, touch.y)) - if self.check_new_shape_done(shape2, 'up'): + touch, + outside=not self.collide_point( + touch.x, touch.y + ), + ) + if self.check_new_shape_done(shape2, "up"): self.finish_current_shape() else: self.selected_side = "new select" self.delete_all_shapes() - ud['paint_interacted'] = True + ud["paint_interacted"] = True self._processing_touch = touch touch.grab(self) # if we have a current shape, all touch will go to it current_shape = self.current_shape if current_shape is not None: - ud['paint_cleared_selection'] = current_shape.finished and \ - current_shape.get_interaction_point_dist(touch.pos) \ + ud["paint_cleared_selection"] = ( + current_shape.finished + and current_shape.get_interaction_point_dist(touch.pos) >= dp(self.min_touch_dist) - if ud['paint_cleared_selection']: + ) + if ud["paint_cleared_selection"]: self.finish_current_shape() else: - ud['paint_interaction'] = 'current' + ud["paint_interaction"] = "current" current_shape.handle_touch_down(touch) return True @@ -3230,15 +3344,18 @@ def on_touch_down(self, touch): # next try to interact by selecting or interacting with # selected shapes shape = self.get_closest_selection_point_shape( - touch.x, touch.y) + touch.x, touch.y + ) if shape is not None: - ud['paint_interaction'] = 'selected' - ud['paint_selected_shape'] = shape - ud['paint_was_selected'] = shape not in self.selected_shapes + ud["paint_interaction"] = "selected" + ud["paint_selected_shape"] = shape + ud["paint_was_selected"] = ( + shape not in self.selected_shapes + ) return True if self._ctrl_down: - ud['paint_interaction'] = 'done' + ud["paint_interaction"] = "done" return True if self.selected_side == "new select": @@ -3252,7 +3369,7 @@ def on_touch_down(self, touch): return False def on_touch_move(self, touch): - if self.figure_wgt.touch_mode != 'selector': + if self.figure_wgt.touch_mode != "selector": return False ud = touch.ud @@ -3261,18 +3378,24 @@ def on_touch_move(self, touch): # for move, only use normal touch, not touch outside range x, y = self.to_window(*self.pos) - if self.selected_side == "new select" or self.selected_side == "modify": + if ( + self.selected_side == "new select" + or self.selected_side == "modify" + ): - if ud['paint_interaction'] == 'done': + if ud["paint_interaction"] == "done": return True - ud['paint_touch_moved'] = True + ud["paint_touch_moved"] = True if self.figure_wgt.collide_point(*self.to_window(*touch.pos)): - if not ud['paint_interaction']: - if ud['paint_cleared_selection'] or self.clear_selected_shapes(): - ud['paint_interaction'] = 'done' + if not ud["paint_interaction"]: + if ( + ud["paint_cleared_selection"] + or self.clear_selected_shapes() + ): + ud["paint_interaction"] = "done" return True # finally try creating a new shape @@ -3283,43 +3406,35 @@ def on_touch_move(self, touch): shape.handle_touch_down(touch, opos=touch.opos) self.first_touch = touch self.current_shape = shape - if self.check_new_shape_done(shape, 'down'): + if self.check_new_shape_done(shape, "down"): self.finish_current_shape() - ud['paint_interaction'] = 'done' + ud["paint_interaction"] = "done" return True - ud['paint_interaction'] = 'current_new' + ud["paint_interaction"] = "current_new" else: - ud['paint_interaction'] = 'done' + ud["paint_interaction"] = "done" return True - if ud['paint_interaction'] in ('current', 'current_new'): + if ud["paint_interaction"] in ("current", "current_new"): if self.current_shape is None: - ud['paint_interaction'] = 'done' + ud["paint_interaction"] = "done" else: self.current_shape.handle_touch_move(touch) return True if self.selected_side == "pan": - shape = ud['paint_selected_shape'] + shape = ud["paint_selected_shape"] dataxy = np.array(self.shapes[0].points).reshape(-1, 2) xmax, ymax = dataxy.max(axis=0) xmin, ymin = dataxy.min(axis=0) if self.figure_wgt.collide_point( - * - self.to_window( - xmin + - touch.dx, - ymin + - touch.dy)) and self.figure_wgt.collide_point( - * - self.to_window( - xmax + - touch.dx, - ymax + - touch.dy)): + *self.to_window(xmin + touch.dx, ymin + touch.dy) + ) and self.figure_wgt.collide_point( + *self.to_window(xmax + touch.dx, ymax + touch.dy) + ): for s in self.shapes: s.translate(dpos=(touch.dx, touch.dy)) @@ -3329,15 +3444,15 @@ def on_touch_move(self, touch): return super().on_touch_move(touch) def on_touch_up(self, touch): - if self.figure_wgt.touch_mode != 'selector': + if self.figure_wgt.touch_mode != "selector": return False ud = touch.ud if touch.grab_current is self: touch.ungrab(self) # don't process the same touch up again - paint_interaction = ud['paint_interaction'] - ud['paint_interaction'] = 'done' + paint_interaction = ud["paint_interaction"] + ud["paint_interaction"] = "done" self._processing_touch = None self.finish_current_shape() self.alpha = 1 @@ -3345,14 +3460,15 @@ def on_touch_up(self, touch): if self.selected_side != "modify": if self.selected_side == "new select" and self.shapes: self.filter_path(self.shapes[0]) - elif self.selected_side == 'pan' and self.shapes: + elif self.selected_side == "pan" and self.shapes: self.widget_verts = np.array( - self.shapes[0].points).reshape(-1, 2) + self.shapes[0].points + ).reshape(-1, 2) # self.clear_selected_shapes() shape = self.get_closest_shape(touch.x, touch.y) if shape is not None: - ud['paint_interaction'] = 'current' + ud["paint_interaction"] = "current" for shape in self.shapes: shape.pointsize = dp(7) @@ -3366,12 +3482,15 @@ def on_touch_up(self, touch): if self.selected_side == "modify": - paint_interaction = ud['paint_interaction'] - ud['paint_interaction'] = 'done' + paint_interaction = ud["paint_interaction"] + ud["paint_interaction"] = "done" self._processing_touch = None if not paint_interaction: - if ud['paint_cleared_selection'] or self.clear_selected_shapes(): + if ( + ud["paint_cleared_selection"] + or self.clear_selected_shapes() + ): return True # finally try creating a new shape @@ -3381,42 +3500,48 @@ def on_touch_up(self, touch): if shape is not None: shape.handle_touch_down(touch, opos=touch.opos) self.current_shape = shape - if self.check_new_shape_done(shape, 'down'): + if self.check_new_shape_done(shape, "down"): self.finish_current_shape() return True - paint_interaction = 'current_new' + paint_interaction = "current_new" else: return True if self.current_shape is not None: self.current_shape.handle_touch_up( - touch, outside=not self.collide_point( - touch.x, touch.y)) - if self.check_new_shape_done(self.current_shape, 'up'): + touch, outside=not self.collide_point(touch.x, touch.y) + ) + if self.check_new_shape_done(self.current_shape, "up"): self.finish_current_shape() self.finish_current_shape() - if self.selected_side == 'modify' and self.shapes: + if self.selected_side == "modify" and self.shapes: self.widget_verts = np.array( - self.shapes[0].points).reshape(-1, 2)[1:, :] + self.shapes[0].points + ).reshape(-1, 2)[1:, :] if self.widget_verts is not None: self.verts = self._get_lasso_data(self.widget_verts) self.onselect(self.verts) return super().on_touch_up(touch) - shape = ud['paint_selected_shape'] + shape = ud["paint_selected_shape"] if shape not in self.shapes: return True if self._ctrl_down or self.multiselect: - if not ud['paint_was_selected'] and shape in self.selected_shapes: + if ( + not ud["paint_was_selected"] + and shape in self.selected_shapes + ): self.deselect_shape(shape) - elif ud['paint_was_selected']: + elif ud["paint_was_selected"]: self.select_shape(shape) else: - if len(self.selected_shapes) != 1 or \ - self.selected_shapes[0] != shape: + if ( + len(self.selected_shapes) != 1 + or self.selected_shapes[0] != shape + ): self.clear_selected_shapes() self.select_shape(shape) @@ -3467,20 +3592,19 @@ def get_closest_selection_point_shape(self, x, y): return closest_shape def do_long_touch(self, touch, *largs): - """Handles a long touch by the user. - """ + """Handles a long touch by the user.""" # assert self._processing_touch touch.push() touch.apply_transform_2d(self.to_widget) self._long_touch_trigger = None ud = touch.ud - if ud['paint_interaction'] == 'selected': + if ud["paint_interaction"] == "selected": if self._ctrl_down: - ud['paint_interaction'] = 'done' + ud["paint_interaction"] = "done" touch.pop() return - ud['paint_interaction'] = '' + ud["paint_interaction"] = "" # assert ud['paint_interacted'] # assert not ud['paint_interaction'] @@ -3488,10 +3612,10 @@ def do_long_touch(self, touch, *largs): self.clear_selected_shapes() shape = self.get_closest_shape(touch.x, touch.y) if shape is not None: - ud['paint_interaction'] = 'current' + ud["paint_interaction"] = "current" self.start_shape_interaction(shape, (touch.x, touch.y)) else: - ud['paint_interaction'] = 'done' + ud["paint_interaction"] = "done" touch.pop() def filter_path(self, shape): @@ -3509,7 +3633,8 @@ def filter_path(self, shape): shape = self.create_shape_with_touch(self.first_touch) if shape is not None: shape.handle_touch_down( - self.first_touch, opos=self.first_touch.opos) + self.first_touch, opos=self.first_touch.opos + ) self.current_shape = shape # crete new filter shape @@ -3520,8 +3645,10 @@ def filter_path(self, shape): self.current_shape.points.extend(data_xy) if self.current_shape.perim_close_inst is not None: - self.current_shape.perim_close_inst.points = self.current_shape.points[ - -2:] + self.current_shape.points[:2] + self.current_shape.perim_close_inst.points = ( + self.current_shape.points[-2:] + + self.current_shape.points[:2] + ) if len(self.current_shape.points) >= 6: self.current_shape.is_valid = True @@ -3537,7 +3664,7 @@ def set_collection(self, collection): # Ensure that we have separate colors for each object self.fc = collection.get_facecolors() if len(self.fc) == 0: - raise ValueError('Collection must have a facecolor') + raise ValueError("Collection must have a facecolor") elif len(self.fc) == 1: self.fc = np.tile(self.fc, (self.Npts, 1)) @@ -3549,14 +3676,16 @@ def onselect(self, verts): path = Path(verts) if self.collection: self.ind = np.nonzero(path.contains_points(self.xys))[ - 0] # xys collection.get_offsets() + 0 + ] # xys collection.get_offsets() self.fc[:, -1] = self.alpha_other self.fc[self.ind, -1] = 1 self.collection.set_facecolors(self.fc) if self.line: xdata, ydata = self.line.get_xydata().T self.ind_line = np.nonzero( - path.contains_points(np.array([xdata, ydata]).T))[0] + path.contains_points(np.array([xdata, ydata]).T) + )[0] self.figure_wgt.figure.canvas.draw_idle() if self.callback: @@ -3585,13 +3714,16 @@ def _get_lasso_data(self, widget_verts): verts = [] for data_xy in widget_verts: - x, y = trans.transform_point((data_xy[0] + - self.to_window(* - self.pos)[0] - - self.figure_wgt.pos[0], data_xy[1] + - self.to_window(* - self.pos)[1] - - self.figure_wgt.pos[1])) + x, y = trans.transform_point( + ( + data_xy[0] + + self.to_window(*self.pos)[0] + - self.figure_wgt.pos[0], + data_xy[1] + + self.to_window(*self.pos)[1] + - self.figure_wgt.pos[1], + ) + ) verts.append((x, y)) return verts @@ -3641,7 +3773,8 @@ def on_mouse_pos(self, something, touch): if self.opacity and self.collide_point(*self.to_widget(*touch)): collision = self.collides_with_control_points( - something, self.to_widget(*touch)) + something, self.to_widget(*touch) + ) if collision in ["top left", "bottom right"]: Window.set_system_cursor("size_nwse") elif collision in ["top right", "bottom left"]: @@ -3672,24 +3805,24 @@ def add_shape(self, shape): return False def on_touch_down(self, touch): - if self.figure_wgt.touch_mode != 'selector': + if self.figure_wgt.touch_mode != "selector": return False ud = touch.ud # whether the touch was used by the painter for any purpose whatsoever - ud['paint_interacted'] = False + ud["paint_interacted"] = False # can be one of current, selected, done indicating how the touch was # used, if it was used. done means the touch is done and don't do # anything with anymore. selected means a shape was selected. - ud['paint_interaction'] = '' + ud["paint_interaction"] = "" # if this touch experienced a move - ud['paint_touch_moved'] = False + ud["paint_touch_moved"] = False # the shape that was selected if paint_interaction is selected - ud['paint_selected_shape'] = None + ud["paint_selected_shape"] = None # whether the selected_shapes contained the shape this touch was # used to select a shape in touch_down. - ud['paint_was_selected'] = False - ud['paint_cleared_selection'] = False + ud["paint_was_selected"] = False + ud["paint_cleared_selection"] = False if self.locked or self._processing_touch is not None: return super(PaintCanvasBehaviorBase, self).on_touch_down(touch) @@ -3698,7 +3831,7 @@ def on_touch_down(self, touch): if self.figure_wgt.collide_point(*self.to_window(*touch.pos)): - if self.figure_wgt.touch_mode == 'selector': + if self.figure_wgt.touch_mode == "selector": if touch.is_double_tap and self.callback_clear: self.callback_clear() @@ -3712,25 +3845,34 @@ def on_touch_down(self, touch): self.opacity = 1 if result: shape = self.get_closest_selection_point_shape( - touch.x, touch.y) + touch.x, touch.y + ) shape2 = self.get_closest_shape(touch.x, touch.y) if shape is not None or shape2 is not None: self.selected_side = "modify" if shape: self.start_shape_interaction( - shape, (touch.x, touch.y)) + shape, (touch.x, touch.y) + ) shape.handle_touch_up( - touch, outside=not self.collide_point( - touch.x, touch.y)) - if self.check_new_shape_done(shape, 'up'): + touch, + outside=not self.collide_point( + touch.x, touch.y + ), + ) + if self.check_new_shape_done(shape, "up"): self.finish_current_shape() else: self.start_shape_interaction( - shape2, (touch.x, touch.y)) + shape2, (touch.x, touch.y) + ) shape2.handle_touch_up( - touch, outside=not self.collide_point( - touch.x, touch.y)) - if self.check_new_shape_done(shape2, 'up'): + touch, + outside=not self.collide_point( + touch.x, touch.y + ), + ) + if self.check_new_shape_done(shape2, "up"): self.finish_current_shape() else: @@ -3739,47 +3881,58 @@ def on_touch_down(self, touch): return True else: shape = self.get_closest_selection_point_shape( - touch.x, touch.y) + touch.x, touch.y + ) shape2 = self.get_closest_shape(touch.x, touch.y) if shape is not None or shape2 is not None: self.selected_side = "modify" if shape: - ud['paint_interaction'] = 'current' + ud["paint_interaction"] = "current" self.start_shape_interaction( - shape, (touch.x, touch.y)) + shape, (touch.x, touch.y) + ) shape.handle_touch_up( - touch, outside=not self.collide_point( - touch.x, touch.y)) - if self.check_new_shape_done(shape, 'up'): + touch, + outside=not self.collide_point( + touch.x, touch.y + ), + ) + if self.check_new_shape_done(shape, "up"): self.finish_current_shape() else: - ud['paint_interaction'] = 'current' + ud["paint_interaction"] = "current" self.start_shape_interaction( - shape2, (touch.x, touch.y)) + shape2, (touch.x, touch.y) + ) shape2.handle_touch_up( - touch, outside=not self.collide_point( - touch.x, touch.y)) - if self.check_new_shape_done(shape2, 'up'): + touch, + outside=not self.collide_point( + touch.x, touch.y + ), + ) + if self.check_new_shape_done(shape2, "up"): self.finish_current_shape() else: self.selected_side = "new select" self.delete_all_shapes() - ud['paint_interacted'] = True + ud["paint_interacted"] = True self._processing_touch = touch touch.grab(self) # if we have a current shape, all touch will go to it current_shape = self.current_shape if current_shape is not None: - ud['paint_cleared_selection'] = current_shape.finished and \ - current_shape.get_interaction_point_dist(touch.pos) \ + ud["paint_cleared_selection"] = ( + current_shape.finished + and current_shape.get_interaction_point_dist(touch.pos) >= dp(self.min_touch_dist) - if ud['paint_cleared_selection']: + ) + if ud["paint_cleared_selection"]: self.finish_current_shape() else: - ud['paint_interaction'] = 'current' + ud["paint_interaction"] = "current" current_shape.handle_touch_down(touch) return True @@ -3787,15 +3940,18 @@ def on_touch_down(self, touch): # next try to interact by selecting or interacting with # selected shapes shape = self.get_closest_selection_point_shape( - touch.x, touch.y) + touch.x, touch.y + ) if shape is not None: - ud['paint_interaction'] = 'selected' - ud['paint_selected_shape'] = shape - ud['paint_was_selected'] = shape not in self.selected_shapes + ud["paint_interaction"] = "selected" + ud["paint_selected_shape"] = shape + ud["paint_was_selected"] = ( + shape not in self.selected_shapes + ) return True if self._ctrl_down: - ud['paint_interaction'] = 'done' + ud["paint_interaction"] = "done" return True if self.selected_side == "new select": @@ -3811,7 +3967,7 @@ def on_touch_down(self, touch): return False def on_touch_move(self, touch): - if self.figure_wgt.touch_mode != 'selector': + if self.figure_wgt.touch_mode != "selector": return False ud = touch.ud @@ -3820,71 +3976,71 @@ def on_touch_move(self, touch): # for move, only use normal touch, not touch outside range x, y = self.to_window(*self.pos) - if self.selected_side == "new select" or self.selected_side == "modify": + if ( + self.selected_side == "new select" + or self.selected_side == "modify" + ): # if ud['paint_interaction'] == 'done': # return True - ud['paint_touch_moved'] = True + ud["paint_touch_moved"] = True if self.figure_wgt.collide_point(*self.to_window(*touch.pos)): - if not ud['paint_interaction']: - if ud['paint_cleared_selection'] or self.clear_selected_shapes(): - ud['paint_interaction'] = 'done' + if not ud["paint_interaction"]: + if ( + ud["paint_cleared_selection"] + or self.clear_selected_shapes() + ): + ud["paint_interaction"] = "done" return True # finally try creating a new shape # touch must have originally collided otherwise we # wouldn't be here shape = self.create_shape_with_touch( - touch, check=False) + touch, check=False + ) if shape is not None: shape.handle_touch_down(touch, opos=touch.opos) self.first_touch = touch self.current_shape = shape self.current_shape.during_creation = True - if self.check_new_shape_done(shape, 'down'): + if self.check_new_shape_done(shape, "down"): self.finish_current_shape() self.current_shape = self.shapes[0] - ud['paint_interaction'] = 'done' + ud["paint_interaction"] = "done" return True - ud['paint_interaction'] = 'current_new' + ud["paint_interaction"] = "current_new" else: - ud['paint_interaction'] = 'done' + ud["paint_interaction"] = "done" shape.handle_touch_move(touch) return True # if ud['paint_interaction'] in ('current', 'current_new'): else: if self.current_shape is None: - ud['paint_interaction'] = 'done' + ud["paint_interaction"] = "done" else: self.current_shape.start_interaction( - (touch.x, touch.y)) + (touch.x, touch.y) + ) self.current_shape.handle_touch_move(touch) return True if self.selected_side == "pan": - shape = ud['paint_selected_shape'] + shape = ud["paint_selected_shape"] xmin, xmax, ymin, ymax = self.shapes[0].get_min_max() if self.figure_wgt.collide_point( - * - self.to_window( - xmin + - touch.dx, - ymin + - touch.dy)) and self.figure_wgt.collide_point( - * - self.to_window( - xmax + - touch.dx, - ymax + - touch.dy)): + *self.to_window(xmin + touch.dx, ymin + touch.dy) + ) and self.figure_wgt.collide_point( + *self.to_window(xmax + touch.dx, ymax + touch.dy) + ): for s in self.shapes: s.translate(dpos=(touch.dx, touch.dy)) @@ -3895,7 +4051,7 @@ def on_touch_move(self, touch): def on_touch_up(self, touch): - if self.figure_wgt.touch_mode != 'selector': + if self.figure_wgt.touch_mode != "selector": return False ud = touch.ud @@ -3903,8 +4059,8 @@ def on_touch_up(self, touch): if touch.grab_current is self: touch.ungrab(self) # don't process the same touch up again - paint_interaction = ud['paint_interaction'] - ud['paint_interaction'] = 'done' + paint_interaction = ud["paint_interaction"] + ud["paint_interaction"] = "done" self._processing_touch = None self.finish_current_shape() self.alpha = 1 @@ -3913,13 +4069,13 @@ def on_touch_up(self, touch): if self.selected_side == "new select" and self.shapes: self.shapes[0].during_creation = False self.widget_verts = self.shapes[0].get_widget_verts() - elif self.selected_side == 'pan' and self.shapes: + elif self.selected_side == "pan" and self.shapes: self.widget_verts = self.shapes[0].get_widget_verts() # self.clear_selected_shapes() shape = self.get_closest_shape(touch.x, touch.y) if shape is not None: - ud['paint_interaction'] = 'current' + ud["paint_interaction"] = "current" for shape in self.shapes: shape.pointsize = dp(7) @@ -3933,12 +4089,15 @@ def on_touch_up(self, touch): if self.selected_side == "modify": - paint_interaction = ud['paint_interaction'] - ud['paint_interaction'] = 'done' + paint_interaction = ud["paint_interaction"] + ud["paint_interaction"] = "done" self._processing_touch = None if not paint_interaction: - if ud['paint_cleared_selection'] or self.clear_selected_shapes(): + if ( + ud["paint_cleared_selection"] + or self.clear_selected_shapes() + ): return True # finally try creating a new shape @@ -3948,23 +4107,23 @@ def on_touch_up(self, touch): if shape is not None: shape.handle_touch_down(touch, opos=touch.opos) self.current_shape = shape - if self.check_new_shape_done(shape, 'down'): + if self.check_new_shape_done(shape, "down"): self.finish_current_shape() return True - paint_interaction = 'current_new' + paint_interaction = "current_new" else: return True if self.current_shape is not None: self.current_shape.handle_touch_up( - touch, outside=not self.collide_point( - touch.x, touch.y)) - if self.check_new_shape_done(self.current_shape, 'up'): + touch, outside=not self.collide_point(touch.x, touch.y) + ) + if self.check_new_shape_done(self.current_shape, "up"): self.finish_current_shape() self.finish_current_shape() - if self.selected_side == 'modify' and self.shapes: + if self.selected_side == "modify" and self.shapes: self.widget_verts = self.shapes[0].get_widget_verts() if self.widget_verts is not None: @@ -3972,18 +4131,23 @@ def on_touch_up(self, touch): self.onselect(self.verts) return super().on_touch_up(touch) - shape = ud['paint_selected_shape'] + shape = ud["paint_selected_shape"] if shape not in self.shapes: return True if self._ctrl_down or self.multiselect: - if not ud['paint_was_selected'] and shape in self.selected_shapes: + if ( + not ud["paint_was_selected"] + and shape in self.selected_shapes + ): self.deselect_shape(shape) - elif ud['paint_was_selected']: + elif ud["paint_was_selected"]: self.select_shape(shape) else: - if len(self.selected_shapes) != 1 or \ - self.selected_shapes[0] != shape: + if ( + len(self.selected_shapes) != 1 + or self.selected_shapes[0] != shape + ): self.clear_selected_shapes() self.select_shape(shape) @@ -4040,20 +4204,19 @@ def get_closest_selection_point_shape(self, x, y): return closest_shape def do_long_touch(self, touch, *largs): - """Handles a long touch by the user. - """ + """Handles a long touch by the user.""" # assert self._processing_touch touch.push() touch.apply_transform_2d(self.to_widget) self._long_touch_trigger = None ud = touch.ud - if ud['paint_interaction'] == 'selected': + if ud["paint_interaction"] == "selected": if self._ctrl_down: - ud['paint_interaction'] = 'done' + ud["paint_interaction"] = "done" touch.pop() return - ud['paint_interaction'] = '' + ud["paint_interaction"] = "" # assert ud['paint_interacted'] # assert not ud['paint_interaction'] @@ -4061,10 +4224,10 @@ def do_long_touch(self, touch, *largs): self.clear_selected_shapes() shape = self.get_closest_shape(touch.x, touch.y) if shape is not None: - ud['paint_interaction'] = 'current' + ud["paint_interaction"] = "current" self.start_shape_interaction(shape, (touch.x, touch.y)) else: - ud['paint_interaction'] = 'done' + ud["paint_interaction"] = "done" touch.pop() def set_collection(self, collection): @@ -4075,7 +4238,7 @@ def set_collection(self, collection): # Ensure that we have separate colors for each object self.fc = collection.get_facecolors() if len(self.fc) == 0: - raise ValueError('Collection must have a facecolor') + raise ValueError("Collection must have a facecolor") elif len(self.fc) == 1: self.fc = np.tile(self.fc, (self.Npts, 1)) @@ -4097,14 +4260,16 @@ def onselect(self, verts): if self.collection: self.ind = np.nonzero(path.contains_points(self.xys))[ - 0] # xys collection.get_offsets() + 0 + ] # xys collection.get_offsets() self.fc[:, -1] = self.alpha_other self.fc[self.ind, -1] = 1 self.collection.set_facecolors(self.fc) if self.line: xdata, ydata = self.line.get_xydata().T self.ind_line = np.nonzero( - path.contains_points(np.array([xdata, ydata]).T))[0] + path.contains_points(np.array([xdata, ydata]).T) + )[0] self.figure_wgt.figure.canvas.draw_idle() if self.callback: @@ -4133,13 +4298,16 @@ def _get_ellipse_data(self, widget_verts): verts = [] for data_xy in widget_verts: - x, y = trans.transform_point((data_xy[0] + - self.to_window(* - self.pos)[0] - - self.figure_wgt.pos[0], data_xy[1] + - self.to_window(* - self.pos)[1] - - self.figure_wgt.pos[1])) + x, y = trans.transform_point( + ( + data_xy[0] + + self.to_window(*self.pos)[0] + - self.figure_wgt.pos[0], + data_xy[1] + + self.to_window(*self.pos)[1] + - self.figure_wgt.pos[1], + ) + ) verts.append((x, y)) return verts @@ -4155,8 +4323,8 @@ class SpanSelect(FloatLayout): figure_wgt = ObjectProperty() desktop_mode = BooleanProperty(True) span_orientation = OptionProperty( - 'vertical', options=[ - 'vertical', 'horizontal']) + "vertical", options=["vertical", "horizontal"] + ) span_color = ColorProperty("red") span_alpha = NumericProperty(0.3) span_hide_on_release = BooleanProperty(False) @@ -4188,24 +4356,27 @@ def __init__(self, **kwargs): def on_kv_post(self, _): # only bind mouse position if not android or if the user set desktop # mode to false - if platform != 'android' and self.desktop_mode: + if platform != "android" and self.desktop_mode: Window.bind(mouse_pos=self.on_mouse_pos) def update_bg_color(self): fig_bg_color = self.figure_wgt.figure.get_facecolor() rgb_fig_bg_color = mcolors.to_rgb(fig_bg_color) - if (rgb_fig_bg_color[0] * 0.299 + rgb_fig_bg_color[1] - * 0.587 + rgb_fig_bg_color[2] * 0.114) > 186 / 255: + if ( + rgb_fig_bg_color[0] * 0.299 + + rgb_fig_bg_color[1] * 0.587 + + rgb_fig_bg_color[2] * 0.114 + ) > 186 / 255: self.bg_color = [1, 1, 1, 1] - self.bottom_color = ( - self.top_colors - ) = self.left_color = self.right_color = [1, 1, 1, 1] + self.bottom_color = self.top_colors = self.left_color = ( + self.right_color + ) = [1, 1, 1, 1] else: self.bg_color = [0, 0, 0, 1] - self.bottom_color = ( - self.top_colors - ) = self.left_color = self.right_color = [0, 0, 0, 1] + self.bottom_color = self.top_colors = self.left_color = ( + self.right_color + ) = [0, 0, 0, 1] def set_collection(self, collection): self.collection = collection @@ -4215,7 +4386,7 @@ def set_collection(self, collection): # Ensure that we have separate colors for each object self.fc = collection.get_facecolors() if len(self.fc) == 0: - raise ValueError('Collection must have a facecolor') + raise ValueError("Collection must have a facecolor") elif len(self.fc) == 1: self.fc = np.tile(self.fc, (self.Npts, 1)) @@ -4227,11 +4398,15 @@ def on_mouse_pos(self, something, touch): When the mouse moves, we check the position of the mouse and update the cursor accordingly. """ - if self.opacity and self.figure_wgt.touch_mode == 'selector' and self.collide_point( - *self.to_widget(*touch)): + if ( + self.opacity + and self.figure_wgt.touch_mode == "selector" + and self.collide_point(*self.to_widget(*touch)) + ): collision = self.collides_with_control_points( - something, self.to_widget(*touch)) + something, self.to_widget(*touch) + ) if collision in ["top left", "bottom right"]: Window.set_system_cursor("size_nwse") elif collision in ["top right", "bottom left"]: @@ -4253,7 +4428,7 @@ def collides_with_control_points(self, _, touch): """ x, y = touch[0], touch[1] - if self.span_orientation == 'vertical': + if self.span_orientation == "vertical": # Checking mouse is on left edge if self.x - dp(7) <= x <= self.x + dp(7): if self.y <= y <= self.y + self.height: @@ -4262,7 +4437,9 @@ def collides_with_control_points(self, _, touch): return False # Checking mouse is on right edge - elif self.x + self.width - dp(7) <= x <= self.x + self.width + dp(7): + elif ( + self.x + self.width - dp(7) <= x <= self.x + self.width + dp(7) + ): if self.y <= y <= self.y + self.height: return "right" else: @@ -4270,7 +4447,7 @@ def collides_with_control_points(self, _, touch): else: return False - elif self.span_orientation == 'horizontal': + elif self.span_orientation == "horizontal": # Checking mouse is on top edge if self.x <= x <= self.x + self.width: if self.y <= y <= self.y + dp(7): @@ -4283,7 +4460,7 @@ def collides_with_control_points(self, _, touch): return False def on_touch_down(self, touch): - if self.figure_wgt.touch_mode != 'selector': + if self.figure_wgt.touch_mode != "selector": return if self.collide_point(*touch.pos) and self.opacity: touch.grab(self) @@ -4310,7 +4487,7 @@ def on_touch_down(self, touch): self.left_color = self.highlight_color self.right_color = self.highlight_color elif self.figure_wgt.collide_point(*self.to_window(*touch.pos)): - if self.figure_wgt.touch_mode == 'selector': + if self.figure_wgt.touch_mode == "selector": if touch.is_double_tap and self.callback_clear: self.callback_clear() return @@ -4320,44 +4497,48 @@ def on_touch_down(self, touch): left_bound = float(self.figure_wgt.x + self.ax.bbox.bounds[0]) right_bound = float( - self.figure_wgt.x + - self.ax.bbox.bounds[2] + - self.ax.bbox.bounds[0]) + self.figure_wgt.x + + self.ax.bbox.bounds[2] + + self.ax.bbox.bounds[0] + ) top_bound = float( - self.figure_wgt.y + - self.ax.bbox.bounds[3] + - self.ax.bbox.bounds[1]) + self.figure_wgt.y + + self.ax.bbox.bounds[3] + + self.ax.bbox.bounds[1] + ) bottom_bound = float( - self.figure_wgt.y + self.ax.bbox.bounds[1]) + self.figure_wgt.y + self.ax.bbox.bounds[1] + ) width = right_bound - left_bound left_bound, right_bound = self.to_widget( - left_bound, right_bound) + left_bound, right_bound + ) x, y = touch.pos[0], touch.pos[1] self.opacity = 1 - if self.span_orientation == 'vertical': + if self.span_orientation == "vertical": self.pos = (x, bottom_bound - self.figure_wgt.y) self.size = (5, top_bound - bottom_bound) - elif self.span_orientation == 'horizontal': + elif self.span_orientation == "horizontal": self.pos = (left_bound, y) self.size = (width, -5) # self.size = (5,5) self.opacity = 1 - if self.span_orientation == 'vertical': + if self.span_orientation == "vertical": self.first_touch = (x, bottom_bound - self.figure_wgt.y) - elif self.span_orientation == 'horizontal': + elif self.span_orientation == "horizontal": self.first_touch = (left_bound, y) self.selected_side = "new select" return super().on_touch_down(touch) def on_touch_move(self, touch): - if self.figure_wgt.touch_mode != 'selector': + if self.figure_wgt.touch_mode != "selector": return if touch.grab_current is self: @@ -4370,20 +4551,24 @@ def on_touch_move(self, touch): # get figure boundaries (in pixels) left_bound = float(self.figure_wgt.x + self.ax.bbox.bounds[0]) right_bound = float( - self.figure_wgt.x + - self.ax.bbox.bounds[2] + - self.ax.bbox.bounds[0]) + self.figure_wgt.x + + self.ax.bbox.bounds[2] + + self.ax.bbox.bounds[0] + ) top_bound = float( - self.figure_wgt.y + - self.ax.bbox.bounds[3] + - self.ax.bbox.bounds[1]) + self.figure_wgt.y + + self.ax.bbox.bounds[3] + + self.ax.bbox.bounds[1] + ) bottom_bound = float( - self.figure_wgt.y + self.ax.bbox.bounds[1]) + self.figure_wgt.y + self.ax.bbox.bounds[1] + ) width = right_bound - left_bound left_bound, right_bound = self.to_widget( - left_bound, right_bound) + left_bound, right_bound + ) if self.selected_side == "top": if self.height + touch.dy <= MINIMUM_HEIGHT: @@ -4401,7 +4586,7 @@ def on_touch_move(self, touch): return False if self.stay_in_axis_limit and self.x + touch.dx <= left_bound: - self.width -= (left_bound - self.x) + self.width -= left_bound - self.x self.x = left_bound if self.dynamic_callback and self.verts is not None: @@ -4420,8 +4605,10 @@ def on_touch_move(self, touch): if self.width + touch.dx <= MINIMUM_WIDTH: return False - if self.stay_in_axis_limit and self.width + \ - touch.dx + self.x >= left_bound + width: + if ( + self.stay_in_axis_limit + and self.width + touch.dx + self.x >= left_bound + width + ): self.width = left_bound + width - self.x if self.dynamic_callback and self.verts is not None: self.verts = self._get_box_data() @@ -4436,79 +4623,94 @@ def on_touch_move(self, touch): self.onselect(self.verts) elif self.selected_side == "new select": - if self.span_orientation == 'vertical': + if self.span_orientation == "vertical": self.width += touch.dx - elif self.span_orientation == 'horizontal': + elif self.span_orientation == "horizontal": self.height += touch.dy elif not self.selected_side: if self.figure_wgt.collide_point( - * - self.to_window( - self.pos[0] + - touch.dx, - self.pos[1] + - touch.dy)) and self.figure_wgt.collide_point( - * - self.to_window( - self.pos[0] + - self.size[0] + - touch.dx, - self.pos[1] + - self.size[1] + - touch.dy)): - if self.span_orientation == 'vertical': + *self.to_window( + self.pos[0] + touch.dx, self.pos[1] + touch.dy + ) + ) and self.figure_wgt.collide_point( + *self.to_window( + self.pos[0] + self.size[0] + touch.dx, + self.pos[1] + self.size[1] + touch.dy, + ) + ): + if self.span_orientation == "vertical": # print(touch.dx) - if self.stay_in_axis_limit and self.x + touch.dx < left_bound: + if ( + self.stay_in_axis_limit + and self.x + touch.dx < left_bound + ): self.x = left_bound - if self.dynamic_callback and self.verts is not None: + if ( + self.dynamic_callback + and self.verts is not None + ): self.verts = self._get_box_data() self.onselect(self.verts) - elif self.stay_in_axis_limit and self.width + touch.dx + self.x > left_bound + width: + elif ( + self.stay_in_axis_limit + and self.width + touch.dx + self.x + > left_bound + width + ): self.x = left_bound + width - self.width - if self.dynamic_callback and self.verts is not None: + if ( + self.dynamic_callback + and self.verts is not None + ): self.verts = self._get_box_data() self.onselect(self.verts) else: self.x += touch.dx - if self.dynamic_callback and self.verts is not None: + if ( + self.dynamic_callback + and self.verts is not None + ): self.verts = self._get_box_data() self.onselect(self.verts) - elif self.span_orientation == 'horizontal': + elif self.span_orientation == "horizontal": self.y += touch.dy return super().on_touch_move(touch) def on_touch_up(self, touch): - if self.figure_wgt.touch_mode != 'selector': + if self.figure_wgt.touch_mode != "selector": return if touch.grab_current is self: touch.ungrab(self) self.alpha = 1 - if (self.bg_color[0] * 0.299 + self.bg_color[1] - * 0.587 + self.bg_color[2] * 0.114) > 186 / 255: - self.bottom_color = ( - self.top_colors - ) = self.left_color = self.right_color = [0, 0, 0, 1] + if ( + self.bg_color[0] * 0.299 + + self.bg_color[1] * 0.587 + + self.bg_color[2] * 0.114 + ) > 186 / 255: + self.bottom_color = self.top_colors = self.left_color = ( + self.right_color + ) = [0, 0, 0, 1] else: - self.bottom_color = ( - self.top_colors - ) = self.left_color = self.right_color = [1, 1, 1, 1] + self.bottom_color = self.top_colors = self.left_color = ( + self.right_color + ) = [1, 1, 1, 1] if self.first_touch and self.selected_side == "new select": self.check_if_reverse_selection(touch) - if abs( - self.size[0]) < MINIMUM_WIDTH or abs( - self.size[1]) < MINIMUM_HEIGHT: + if ( + abs(self.size[0]) < MINIMUM_WIDTH + or abs(self.size[1]) < MINIMUM_HEIGHT + ): self.reset_selection() else: out_of_bound = False if self.stay_in_axis_limit: if self.first_touch and self.selected_side == "new select": box_data = self._get_box_data() - if self.span_orientation == 'vertical': + if self.span_orientation == "vertical": box_xmin = min(np.array(box_data)[:, 0]) box_xmax = max(np.array(box_data)[:, 0]) @@ -4524,7 +4726,7 @@ def on_touch_up(self, touch): return super().on_touch_up(touch) def check_if_reverse_selection(self, last_touch): - if self.span_orientation == 'vertical': + if self.span_orientation == "vertical": if last_touch.x > self.first_touch[0]: return else: @@ -4532,7 +4734,7 @@ def check_if_reverse_selection(self, last_touch): # self.first_touch[0] - last_touch.x self.size[0] = abs(self.size[0]) - elif self.span_orientation == 'horizontal': + elif self.span_orientation == "horizontal": if last_touch.y > self.first_touch[1]: return else: @@ -4553,13 +4755,15 @@ def _get_box_data(self): x0 = self.to_window(*self.pos)[0] - self.figure_wgt.pos[0] y0 = self.to_window(*self.pos)[1] - self.figure_wgt.pos[1] x1 = self.to_window(*self.pos)[0] - self.figure_wgt.pos[0] - y1 = self.to_window(*self.pos)[1] + \ - self.height - self.figure_wgt.pos[1] + y1 = ( + self.to_window(*self.pos)[1] + self.height - self.figure_wgt.pos[1] + ) x3 = self.to_window(*self.pos)[0] + self.width - self.figure_wgt.pos[0] y3 = self.to_window(*self.pos)[1] - self.figure_wgt.pos[1] x2 = self.to_window(*self.pos)[0] + self.width - self.figure_wgt.pos[0] - y2 = self.to_window(*self.pos)[1] + \ - self.height - self.figure_wgt.pos[1] + y2 = ( + self.to_window(*self.pos)[1] + self.height - self.figure_wgt.pos[1] + ) x0_box, y0_box = trans.transform_point((x0, y0)) x1_box, y1_box = trans.transform_point((x1, y1)) @@ -4571,7 +4775,7 @@ def _get_box_data(self): verts.append((x2_box, y2_box)) verts.append((x3_box, y3_box)) - if self.span_orientation == 'vertical': + if self.span_orientation == "vertical": if self.collection: ymin = np.nanmin(self.xys[:, 1]) ymax = np.nanmax(self.xys[:, 1]) @@ -4585,7 +4789,7 @@ def _get_box_data(self): verts[1] = (verts[1][0], ymax + 1) verts[2] = (verts[2][0], ymax + 1) - elif self.span_orientation == 'horizontal': + elif self.span_orientation == "horizontal": if self.collection: xmin = np.nanmin(self.xys[:, 1]) xmax = np.nanmax(self.xys[:, 1]) @@ -4605,7 +4809,8 @@ def onselect(self, verts): path = Path(verts) if self.collection: self.ind = np.nonzero(path.contains_points(self.xys))[ - 0] # xys collection.get_offsets() + 0 + ] # xys collection.get_offsets() self.fc[:, -1] = self.alpha_other self.fc[self.ind, -1] = 1 self.collection.set_facecolors(self.fc) @@ -4620,7 +4825,8 @@ def onselect(self, verts): # region_y = y[indmin:indmax] self.ind_line = np.nonzero( - path.contains_points(np.array([xdata, ydata]).T))[0] + path.contains_points(np.array([xdata, ydata]).T) + )[0] self.figure_wgt.figure.canvas.draw_idle() if self.callback: From 523ba7cde72beea306dbf4b8888f9a9420075de1 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 22 Oct 2025 03:02:03 +0000 Subject: [PATCH 10/14] Add flake8 configuration and complete PEP8 compliance Co-authored-by: mp-007 <19823482+mp-007@users.noreply.github.com> --- .flake8 | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .flake8 diff --git a/.flake8 b/.flake8 new file mode 100644 index 0000000..9aada51 --- /dev/null +++ b/.flake8 @@ -0,0 +1,5 @@ +[flake8] +# E203: whitespace before ':' - Black formatter adds this for slices, which is PEP8 compliant +# This is a known flake8 false positive when using Black +extend-ignore = E203 +max-line-length = 79 From dcce09d0c5a94e5a395659ee0474e50e48093409 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 22 Oct 2025 16:10:30 +0000 Subject: [PATCH 11/14] Initial plan From 455234dade481a638d09397fbc9154cf4f5ca8ec Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 22 Oct 2025 16:17:21 +0000 Subject: [PATCH 12/14] Apply isort to organize imports with black profile Applied isort with --profile black to ensure import ordering matches PEP8 standards while maintaining compatibility with black formatter. This resolves conflicts with cleanup branch which also applied isort. --- kivy_matplotlib_widget/__init__.py | 3 +- .../tools/clipboard_tool.py | 3 +- kivy_matplotlib_widget/tools/cursors.py | 4 +- .../tools/interactive_converter.py | 32 +++++------ kivy_matplotlib_widget/tools/pick_info.py | 10 ++-- .../uix/graph_subplot_widget.py | 33 ++++++------ kivy_matplotlib_widget/uix/graph_widget.py | 46 ++++++++-------- kivy_matplotlib_widget/uix/graph_widget_3d.py | 44 +++++++-------- .../uix/graph_widget_crop_factor.py | 29 +++++----- .../uix/graph_widget_general.py | 35 ++++++------ .../uix/graph_widget_scatter.py | 52 +++++++++--------- .../uix/graph_widget_twinx.py | 48 ++++++++--------- kivy_matplotlib_widget/uix/hover_widget.py | 20 ++++--- kivy_matplotlib_widget/uix/legend_widget.py | 36 ++++++------- kivy_matplotlib_widget/uix/minmax_widget.py | 20 ++++--- .../uix/navigation_bar_widget.py | 19 +++---- kivy_matplotlib_widget/uix/selector_widget.py | 53 +++++++++---------- 17 files changed, 241 insertions(+), 246 deletions(-) diff --git a/kivy_matplotlib_widget/__init__.py b/kivy_matplotlib_widget/__init__.py index 0a90c23..f549df2 100644 --- a/kivy_matplotlib_widget/__init__.py +++ b/kivy_matplotlib_widget/__init__.py @@ -1,8 +1,7 @@ -from kivy.core.text import LabelBase import os import kivy - +from kivy.core.text import LabelBase kivy.require("2.3.0") diff --git a/kivy_matplotlib_widget/tools/clipboard_tool.py b/kivy_matplotlib_widget/tools/clipboard_tool.py index 14e84c8..b7a3a1d 100644 --- a/kivy_matplotlib_widget/tools/clipboard_tool.py +++ b/kivy_matplotlib_widget/tools/clipboard_tool.py @@ -12,8 +12,9 @@ So this tool will certaintly be removed or modified in the futur """ -from kivy.utils import platform from io import BytesIO + +from kivy.utils import platform from PIL import Image as PILImage if platform == "win": diff --git a/kivy_matplotlib_widget/tools/cursors.py b/kivy_matplotlib_widget/tools/cursors.py index 2842381..af30fdb 100644 --- a/kivy_matplotlib_widget/tools/cursors.py +++ b/kivy_matplotlib_widget/tools/cursors.py @@ -5,10 +5,10 @@ https://github.com/anntzer/mplcursors """ -from collections.abc import Iterable -from contextlib import suppress import copy import weakref +from collections.abc import Iterable +from contextlib import suppress from weakref import WeakKeyDictionary import matplotlib as mpl diff --git a/kivy_matplotlib_widget/tools/interactive_converter.py b/kivy_matplotlib_widget/tools/interactive_converter.py index 709ab75..34ea64d 100644 --- a/kivy_matplotlib_widget/tools/interactive_converter.py +++ b/kivy_matplotlib_widget/tools/interactive_converter.py @@ -1,26 +1,28 @@ -from kivy_matplotlib_widget.uix.hover_widget import ( - add_hover, - BaseHoverFloatLayout, - TagCompareHover, - PlotlyHover, -) +import multiprocessing as mp + +import matplotlib.pyplot as plt +from kivy.app import App +from kivy.config import Config +from kivy.core.window import Window +from kivy.lang import Builder +from kivy.metrics import dp from kivy.properties import ( + BooleanProperty, ColorProperty, NumericProperty, StringProperty, - BooleanProperty, ) -import multiprocessing as mp -from kivy_matplotlib_widget.uix.minmax_widget import add_minmax + +from kivy_matplotlib_widget.uix.hover_widget import ( + BaseHoverFloatLayout, + PlotlyHover, + TagCompareHover, + add_hover, +) from kivy_matplotlib_widget.uix.legend_widget import ( MatplotlibInteractiveLegend, ) -import matplotlib.pyplot as plt -from kivy.core.window import Window -from kivy.metrics import dp -from kivy.app import App -from kivy.lang import Builder -from kivy.config import Config +from kivy_matplotlib_widget.uix.minmax_widget import add_minmax # avoid double-click on touch device Config.set("input", "mouse", "mouse,disable_on_activity") diff --git a/kivy_matplotlib_widget/tools/pick_info.py b/kivy_matplotlib_widget/tools/pick_info.py index d9f20c3..e10c589 100644 --- a/kivy_matplotlib_widget/tools/pick_info.py +++ b/kivy_matplotlib_widget/tools/pick_info.py @@ -9,18 +9,19 @@ # have a `format_coord`-like method); PolyCollection (picking is not well # defined). -from collections import namedtuple -from contextlib import suppress import copy import functools import inspect -from inspect import Signature import itertools -from numbers import Integral import re import warnings +from collections import namedtuple +from contextlib import suppress +from inspect import Signature +from numbers import Integral from weakref import WeakSet +import numpy as np from matplotlib import cbook from matplotlib.axes import Axes from matplotlib.backend_bases import RendererBase @@ -37,7 +38,6 @@ from matplotlib.quiver import Barbs, Quiver from matplotlib.text import Text from matplotlib.transforms import Affine2D -import numpy as np def _register_scatter(): diff --git a/kivy_matplotlib_widget/uix/graph_subplot_widget.py b/kivy_matplotlib_widget/uix/graph_subplot_widget.py index 3130bac..86f6791 100644 --- a/kivy_matplotlib_widget/uix/graph_subplot_widget.py +++ b/kivy_matplotlib_widget/uix/graph_subplot_widget.py @@ -1,26 +1,27 @@ """Custom MatplotFigure""" -from kivy_matplotlib_widget.uix.graph_widget import ( - _FigureCanvas, - MatplotFigure, - MatplotlibEvent, -) -from kivy.properties import NumericProperty, BooleanProperty, OptionProperty -from matplotlib.container import BarContainer -import matplotlib.transforms as mtransforms -import matplotlib.patches as mpatches -import matplotlib.lines as mlines +import copy +import math +import time + +import matplotlib import matplotlib.image as mimage +import matplotlib.lines as mlines +import matplotlib.patches as mpatches +import matplotlib.transforms as mtransforms from kivy.graphics.texture import Texture -from kivy_matplotlib_widget.tools.cursors import cursor from kivy.metrics import dp -from matplotlib.colors import to_hex +from kivy.properties import BooleanProperty, NumericProperty, OptionProperty from kivy.utils import get_color_from_hex -import math -import copy -import time +from matplotlib.colors import to_hex +from matplotlib.container import BarContainer -import matplotlib +from kivy_matplotlib_widget.tools.cursors import cursor +from kivy_matplotlib_widget.uix.graph_widget import ( + MatplotFigure, + MatplotlibEvent, + _FigureCanvas, +) matplotlib.use("Agg") diff --git a/kivy_matplotlib_widget/uix/graph_widget.py b/kivy_matplotlib_widget/uix/graph_widget.py index 525de87..de8d378 100644 --- a/kivy_matplotlib_widget/uix/graph_widget.py +++ b/kivy_matplotlib_widget/uix/graph_widget.py @@ -2,45 +2,45 @@ and kivy scatter """ -from kivy.factory import Factory +import copy +import math +from weakref import WeakKeyDictionary + +import matplotlib +import numpy as np from kivy.clock import Clock from kivy.core.window import Window -from kivy.utils import get_color_from_hex -import numpy as np +from kivy.factory import Factory +from kivy.graphics.texture import Texture +from kivy.graphics.transformation import Matrix +from kivy.lang import Builder from kivy.metrics import dp -from weakref import WeakKeyDictionary -from matplotlib.backend_bases import ResizeEvent -from matplotlib.colors import to_hex -from matplotlib import cbook -from matplotlib.backends.backend_agg import FigureCanvasAgg -from kivy.vector import Vector -from kivy.uix.widget import Widget from kivy.properties import ( - ObjectProperty, - ListProperty, + AliasProperty, BooleanProperty, BoundedNumericProperty, - AliasProperty, + DictProperty, + ListProperty, NumericProperty, + ObjectProperty, OptionProperty, - DictProperty, ) -from kivy.lang import Builder -from kivy.graphics.transformation import Matrix -from kivy.graphics.texture import Texture -import math -import copy - -import matplotlib +from kivy.uix.widget import Widget +from kivy.utils import get_color_from_hex +from kivy.vector import Vector +from matplotlib import cbook +from matplotlib.backend_bases import ResizeEvent +from matplotlib.backends.backend_agg import FigureCanvasAgg +from matplotlib.colors import to_hex matplotlib.use("Agg") selector_widgets_available = False try: selector_widgets_available = True from kivy_matplotlib_widget.uix.selector_widget import ( - ResizeRelativeLayout, - LassoRelativeLayout, EllipseRelativeLayout, + LassoRelativeLayout, + ResizeRelativeLayout, SpanRelativeLayout, ) except ImportError: diff --git a/kivy_matplotlib_widget/uix/graph_widget_3d.py b/kivy_matplotlib_widget/uix/graph_widget_3d.py index 5e69302..48a8d93 100644 --- a/kivy_matplotlib_widget/uix/graph_widget_3d.py +++ b/kivy_matplotlib_widget/uix/graph_widget_3d.py @@ -1,38 +1,38 @@ """MatplotFigure3D for matplotlib 3D graph""" -from kivy.factory import Factory +import math import time -from kivy.uix.floatlayout import FloatLayout -from kivy.uix.scatterlayout import ScatterLayout -from kivy.uix.scatter import Scatter -from matplotlib import cbook from weakref import WeakKeyDictionary -from mpl_toolkits import mplot3d + +import matplotlib import numpy as np -from kivy_matplotlib_widget.tools.cursors import cursor +from kivy.factory import Factory +from kivy.graphics.texture import Texture +from kivy.graphics.transformation import Matrix +from kivy.lang import Builder from kivy.metrics import dp -from matplotlib.colors import to_hex -from matplotlib.backend_bases import MouseEvent -from matplotlib.backend_bases import ResizeEvent -from matplotlib.backends.backend_agg import FigureCanvasAgg -from kivy.vector import Vector -from kivy.uix.boxlayout import BoxLayout from kivy.properties import ( - ObjectProperty, - ListProperty, + AliasProperty, BooleanProperty, BoundedNumericProperty, - AliasProperty, + ColorProperty, + ListProperty, NumericProperty, + ObjectProperty, StringProperty, - ColorProperty, ) -from kivy.lang import Builder -from kivy.graphics.transformation import Matrix -from kivy.graphics.texture import Texture -import math +from kivy.uix.boxlayout import BoxLayout +from kivy.uix.floatlayout import FloatLayout +from kivy.uix.scatter import Scatter +from kivy.uix.scatterlayout import ScatterLayout +from kivy.vector import Vector +from matplotlib import cbook +from matplotlib.backend_bases import MouseEvent, ResizeEvent +from matplotlib.backends.backend_agg import FigureCanvasAgg +from matplotlib.colors import to_hex +from mpl_toolkits import mplot3d -import matplotlib +from kivy_matplotlib_widget.tools.cursors import cursor matplotlib.use("Agg") diff --git a/kivy_matplotlib_widget/uix/graph_widget_crop_factor.py b/kivy_matplotlib_widget/uix/graph_widget_crop_factor.py index dacef6c..3af5119 100644 --- a/kivy_matplotlib_widget/uix/graph_widget_crop_factor.py +++ b/kivy_matplotlib_widget/uix/graph_widget_crop_factor.py @@ -1,31 +1,32 @@ """MatplotFigure is based on https://github.com/mp-007/kivy_matplotlib_widget""" +from weakref import WeakKeyDictionary + import numpy as np +from kivy.graphics.texture import Texture +from kivy.graphics.transformation import Matrix +from kivy.lang import Builder from kivy.metrics import dp -from weakref import WeakKeyDictionary -from matplotlib.backend_bases import ResizeEvent -from matplotlib import cbook -from matplotlib.backends.backend_agg import FigureCanvasAgg -from kivy.vector import Vector -from kivy.uix.widget import Widget from kivy.properties import ( - ObjectProperty, - ListProperty, - BooleanProperty, AliasProperty, + BooleanProperty, + BoundedNumericProperty, + ListProperty, NumericProperty, + ObjectProperty, OptionProperty, - BoundedNumericProperty, StringProperty, ) -from kivy.lang import Builder -from kivy.graphics.transformation import Matrix -from kivy.graphics.texture import Texture +from kivy.uix.widget import Widget +from kivy.vector import Vector +from matplotlib import cbook +from matplotlib.backend_bases import ResizeEvent +from matplotlib.backends.backend_agg import FigureCanvasAgg __all__ = ("MatplotFigureCropFactor",) -import math import copy +import math import matplotlib diff --git a/kivy_matplotlib_widget/uix/graph_widget_general.py b/kivy_matplotlib_widget/uix/graph_widget_general.py index d0ea152..d946a9b 100644 --- a/kivy_matplotlib_widget/uix/graph_widget_general.py +++ b/kivy_matplotlib_widget/uix/graph_widget_general.py @@ -2,31 +2,30 @@ and kivy scatter """ -from kivy.factory import Factory -from kivy.base import EventLoop +import copy +import math + +import matplotlib import numpy as np +from kivy.base import EventLoop +from kivy.factory import Factory +from kivy.graphics.texture import Texture +from kivy.graphics.transformation import Matrix +from kivy.lang import Builder from kivy.metrics import dp -from matplotlib.backend_bases import MouseEvent -from matplotlib.backend_bases import ResizeEvent -from matplotlib.transforms import Bbox -from matplotlib.backends.backend_agg import FigureCanvasAgg -from kivy.vector import Vector -from kivy.uix.widget import Widget from kivy.properties import ( - ObjectProperty, - ListProperty, + AliasProperty, BooleanProperty, BoundedNumericProperty, - AliasProperty, + ListProperty, NumericProperty, + ObjectProperty, ) -from kivy.lang import Builder -from kivy.graphics.transformation import Matrix -from kivy.graphics.texture import Texture -import math -import copy - -import matplotlib +from kivy.uix.widget import Widget +from kivy.vector import Vector +from matplotlib.backend_bases import MouseEvent, ResizeEvent +from matplotlib.backends.backend_agg import FigureCanvasAgg +from matplotlib.transforms import Bbox matplotlib.use("Agg") diff --git a/kivy_matplotlib_widget/uix/graph_widget_scatter.py b/kivy_matplotlib_widget/uix/graph_widget_scatter.py index 96e22a7..6325c83 100644 --- a/kivy_matplotlib_widget/uix/graph_widget_scatter.py +++ b/kivy_matplotlib_widget/uix/graph_widget_scatter.py @@ -2,48 +2,48 @@ and kivy scatter """ -from kivy.factory import Factory -from kivy.clock import Clock -from kivy.core.window import Window -import matplotlib.transforms as mtransforms -import matplotlib.patches as mpatches -import matplotlib.lines as mlines +import copy +import math +from weakref import WeakKeyDictionary + +import matplotlib import matplotlib.image as mimage -from kivy.utils import get_color_from_hex +import matplotlib.lines as mlines +import matplotlib.patches as mpatches +import matplotlib.transforms as mtransforms import numpy as np +from kivy.clock import Clock +from kivy.core.window import Window +from kivy.factory import Factory +from kivy.graphics.texture import Texture +from kivy.graphics.transformation import Matrix +from kivy.lang import Builder from kivy.metrics import dp -from weakref import WeakKeyDictionary -from matplotlib.backend_bases import ResizeEvent -from matplotlib import cbook -from matplotlib.backends.backend_agg import FigureCanvasAgg -from matplotlib.colors import to_hex -from kivy.vector import Vector -from kivy.uix.widget import Widget from kivy.properties import ( - ObjectProperty, - ListProperty, + AliasProperty, BooleanProperty, BoundedNumericProperty, - AliasProperty, + ListProperty, NumericProperty, + ObjectProperty, OptionProperty, ) -from kivy.lang import Builder -from kivy.graphics.transformation import Matrix -from kivy.graphics.texture import Texture -import math -import copy - -import matplotlib +from kivy.uix.widget import Widget +from kivy.utils import get_color_from_hex +from kivy.vector import Vector +from matplotlib import cbook +from matplotlib.backend_bases import ResizeEvent +from matplotlib.backends.backend_agg import FigureCanvasAgg +from matplotlib.colors import to_hex matplotlib.use("Agg") selector_widgets_available = False try: selector_widgets_available = True from kivy_matplotlib_widget.uix.selector_widget import ( - ResizeRelativeLayout, - LassoRelativeLayout, EllipseRelativeLayout, + LassoRelativeLayout, + ResizeRelativeLayout, SpanRelativeLayout, ) except ImportError: diff --git a/kivy_matplotlib_widget/uix/graph_widget_twinx.py b/kivy_matplotlib_widget/uix/graph_widget_twinx.py index 778551d..c76df81 100644 --- a/kivy_matplotlib_widget/uix/graph_widget_twinx.py +++ b/kivy_matplotlib_widget/uix/graph_widget_twinx.py @@ -2,46 +2,46 @@ and kivy scatter """ -from kivy.factory import Factory +import copy +import math +from weakref import WeakKeyDictionary + +import matplotlib +import matplotlib.lines as mlines +import numpy as np from kivy.clock import Clock from kivy.core.window import Window -from kivy.utils import get_color_from_hex -import numpy as np +from kivy.factory import Factory +from kivy.graphics.texture import Texture +from kivy.graphics.transformation import Matrix +from kivy.lang import Builder from kivy.metrics import dp -from matplotlib.backend_bases import ResizeEvent -from weakref import WeakKeyDictionary -import matplotlib.lines as mlines -from matplotlib import cbook -from matplotlib.colors import to_hex -from matplotlib.backends.backend_agg import FigureCanvasAgg -from kivy.vector import Vector -from kivy.uix.widget import Widget from kivy.properties import ( - ObjectProperty, - ListProperty, + AliasProperty, BooleanProperty, BoundedNumericProperty, - AliasProperty, + DictProperty, + ListProperty, NumericProperty, + ObjectProperty, OptionProperty, - DictProperty, ) -from kivy.lang import Builder -from kivy.graphics.transformation import Matrix -from kivy.graphics.texture import Texture -import math -import copy - -import matplotlib +from kivy.uix.widget import Widget +from kivy.utils import get_color_from_hex +from kivy.vector import Vector +from matplotlib import cbook +from matplotlib.backend_bases import ResizeEvent +from matplotlib.backends.backend_agg import FigureCanvasAgg +from matplotlib.colors import to_hex matplotlib.use("Agg") selector_widgets_available = False try: selector_widgets_available = True from kivy_matplotlib_widget.uix.selector_widget import ( - ResizeRelativeLayout, - LassoRelativeLayout, EllipseRelativeLayout, + LassoRelativeLayout, + ResizeRelativeLayout, SpanRelativeLayout, ) except ImportError: diff --git a/kivy_matplotlib_widget/uix/hover_widget.py b/kivy_matplotlib_widget/uix/hover_widget.py index b19f8ec..5f171e4 100644 --- a/kivy_matplotlib_widget/uix/hover_widget.py +++ b/kivy_matplotlib_widget/uix/hover_widget.py @@ -1,19 +1,17 @@ -from kivy.uix.floatlayout import FloatLayout -from kivy.uix.boxlayout import BoxLayout - +import numpy as np +from kivy.core.window import Window +from kivy.lang import Builder +from kivy.metrics import dp from kivy.properties import ( + BooleanProperty, + ColorProperty, + NumericProperty, ObjectProperty, OptionProperty, - NumericProperty, StringProperty, - BooleanProperty, - ColorProperty, ) - -from kivy.lang import Builder -from kivy.core.window import Window -from kivy.metrics import dp -import numpy as np +from kivy.uix.boxlayout import BoxLayout +from kivy.uix.floatlayout import FloatLayout def add_hover( diff --git a/kivy_matplotlib_widget/uix/legend_widget.py b/kivy_matplotlib_widget/uix/legend_widget.py index 9fcae54..42026c7 100644 --- a/kivy_matplotlib_widget/uix/legend_widget.py +++ b/kivy_matplotlib_widget/uix/legend_widget.py @@ -1,30 +1,28 @@ -from kivy.factory import Factory -from kivy.uix.boxlayout import BoxLayout -from kivy.uix.floatlayout import FloatLayout -from kivy.utils import get_color_from_hex -from kivy.clock import Clock -from kivy.config import Config -from kivy.utils import platform +import copy +import re from functools import partial +from math import ceil +import matplotlib as mpl +import numpy as np +from kivy.clock import Clock +from kivy.config import Config +from kivy.factory import Factory +from kivy.lang import Builder +from kivy.metrics import dp from kivy.properties import ( - StringProperty, - ObjectProperty, - NumericProperty, - ListProperty, BooleanProperty, ColorProperty, + ListProperty, + NumericProperty, + ObjectProperty, + StringProperty, ) - -from kivy.lang import Builder +from kivy.uix.boxlayout import BoxLayout +from kivy.uix.floatlayout import FloatLayout from kivy.uix.widget import Widget -from kivy.metrics import dp +from kivy.utils import get_color_from_hex, platform from matplotlib.colors import to_hex -import matplotlib as mpl -from math import ceil -import numpy as np -import copy -import re class LegendGestures(Widget): diff --git a/kivy_matplotlib_widget/uix/minmax_widget.py b/kivy_matplotlib_widget/uix/minmax_widget.py index a17a0e1..6b41fda 100644 --- a/kivy_matplotlib_widget/uix/minmax_widget.py +++ b/kivy_matplotlib_widget/uix/minmax_widget.py @@ -1,19 +1,17 @@ -from kivy.uix.floatlayout import FloatLayout -from kivy.uix.textinput import TextInput - +from kivy.clock import Clock +from kivy.core import text as coretext +from kivy.lang import Builder +from kivy.metrics import dp from kivy.properties import ( - ObjectProperty, - NumericProperty, - StringProperty, BooleanProperty, ColorProperty, DictProperty, + NumericProperty, + ObjectProperty, + StringProperty, ) - -from kivy.lang import Builder -from kivy.metrics import dp -from kivy.clock import Clock -from kivy.core import text as coretext +from kivy.uix.floatlayout import FloatLayout +from kivy.uix.textinput import TextInput def add_minmax( diff --git a/kivy_matplotlib_widget/uix/navigation_bar_widget.py b/kivy_matplotlib_widget/uix/navigation_bar_widget.py index 46a1d49..b9086bf 100644 --- a/kivy_matplotlib_widget/uix/navigation_bar_widget.py +++ b/kivy_matplotlib_widget/uix/navigation_bar_widget.py @@ -1,20 +1,21 @@ -from matplotlib.backend_bases import NavigationToolbar2 +from kivy.clock import Clock +from kivy.core.window import Window +from kivy.factory import Factory +from kivy.lang import Builder +from kivy.metrics import dp from kivy.properties import ( - ObjectProperty, - OptionProperty, - ListProperty, BooleanProperty, + ListProperty, NumericProperty, + ObjectProperty, + OptionProperty, StringProperty, ) -from kivy.lang import Builder from kivy.uix.boxlayout import BoxLayout from kivy.uix.relativelayout import RelativeLayout -from kivy.clock import Clock -from kivy.factory import Factory +from matplotlib.backend_bases import NavigationToolbar2 + from kivy_matplotlib_widget.uix.hover_widget import add_hover -from kivy.core.window import Window -from kivy.metrics import dp class MatplotNavToolbar(BoxLayout): diff --git a/kivy_matplotlib_widget/uix/selector_widget.py b/kivy_matplotlib_widget/uix/selector_widget.py index cf59f5c..41bf06c 100644 --- a/kivy_matplotlib_widget/uix/selector_widget.py +++ b/kivy_matplotlib_widget/uix/selector_widget.py @@ -1,43 +1,40 @@ -from kivy.lang import Builder -from kivy.uix.relativelayout import RelativeLayout -from kivy.uix.floatlayout import FloatLayout -from kivy.properties import ( - ColorProperty, - ObjectProperty, - OptionProperty, - BooleanProperty, - ListProperty, - NumericProperty, -) -from kivy.metrics import dp -from kivy.core.window import Window -from kivy.utils import platform - -import numpy as np -from matplotlib.path import Path -from matplotlib.patches import Ellipse as Ellipse_mpl -import matplotlib.colors as mcolors - +import copy from functools import partial -from math import cos, sin, atan2, pi +from math import atan2, cos, pi, sin from typing import List, Optional +import matplotlib.colors as mcolors +import numpy as np from kivy.clock import Clock +from kivy.core.window import Window +from kivy.event import EventDispatcher from kivy.graphics import ( + Color, Ellipse, + InstructionGroup, Line, - Color, - Point, Mesh, - PushMatrix, + Point, PopMatrix, + PushMatrix, Rotate, - InstructionGroup, ) from kivy.graphics.tesselator import Tesselator -from kivy.event import EventDispatcher -import copy - +from kivy.lang import Builder +from kivy.metrics import dp +from kivy.properties import ( + BooleanProperty, + ColorProperty, + ListProperty, + NumericProperty, + ObjectProperty, + OptionProperty, +) +from kivy.uix.floatlayout import FloatLayout +from kivy.uix.relativelayout import RelativeLayout +from kivy.utils import platform +from matplotlib.patches import Ellipse as Ellipse_mpl +from matplotlib.path import Path kv = """ : From b695b805093027b740fcd88474d46985aec431c4 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 22 Oct 2025 16:26:41 +0000 Subject: [PATCH 13/14] Add summary documentation for PR #34 update Co-authored-by: mp-007 <19823482+mp-007@users.noreply.github.com> --- PR34_UPDATE_SUMMARY.md | 46 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 46 insertions(+) create mode 100644 PR34_UPDATE_SUMMARY.md diff --git a/PR34_UPDATE_SUMMARY.md b/PR34_UPDATE_SUMMARY.md new file mode 100644 index 0000000..26edbe2 --- /dev/null +++ b/PR34_UPDATE_SUMMARY.md @@ -0,0 +1,46 @@ +# PR #34 Update Summary + +## Problem +PR #34 (https://github.com/mp-007/kivy_matplotlib_widget/pull/34) was not mergeable into the `cleanup` branch because: +- The cleanup branch received additional changes from PR #32 (isort) and PR #33 (trailing whitespace removal) +- PR #34's source branch `copilot/format-src-files-to-pep8` was outdated and had merge conflicts + +## Solution Applied +1. **Applied isort** to `copilot/format-src-files-to-pep8` with `--profile black` to match cleanup's import ordering +2. **Merged cleanup branch** into `copilot/format-src-files-to-pep8` to incorporate the latest changes +3. **Resolved conflicts** by keeping the PEP8-formatted versions (black, autopep8) which include better formatting than cleanup's changes +4. **Merged the updated format branch** into `copilot/cleanup-branch` (the working branch for this task) + +## Current State +- `copilot/cleanup-branch` (pushed to remote) has all changes from PR #34 plus the updates to make it compatible with cleanup +- `copilot/format-src-files-to-pep8` (local only) also has these updates but hasn't been pushed to remote +- Both branches have identical content and can merge cleanly into cleanup + +## Verification +- ✅ All Python files compile successfully +- ✅ Flake8 reports 188 violations (as expected per PR #34 description): + - 153 E501 (line too long) - intentional for readability + - 24 F841 (unused variables) - debugging placeholders + - 11 F401 (unused imports) - may be part of public API + +## Next Steps +To complete the update of PR #34, someone with write access needs to either: + +### Option 1: Update PR #34's source branch (Recommended) +```bash +git checkout copilot/format-src-files-to-pep8 +git pull origin copilot/cleanup-branch +git push origin copilot/format-src-files-to-pep8 +``` + +### Option 2: Change PR #34's source branch +- Edit PR #34 to use `copilot/cleanup-branch` instead of `copilot/format-src-files-to-pep8` as the source branch + +### Option 3: Create new PR +- Close PR #34 +- Create a new PR from `copilot/cleanup-branch` to `cleanup` + +## Commits Created +1. **455234d** - Apply isort to organize imports with black profile +2. **aadf8dc** - Merge cleanup branch into copilot/format-src-files-to-pep8 +3. **326bd8e** - Merge copilot/format-src-files-to-pep8 into copilot/cleanup-branch (pushed) From 32e5dd81337f708fb2aa289827bc67ae189537f3 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 22 Oct 2025 16:28:38 +0000 Subject: [PATCH 14/14] Update PR #34 summary with final recommendations Co-authored-by: mp-007 <19823482+mp-007@users.noreply.github.com> --- PR34_UPDATE_SUMMARY.md | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/PR34_UPDATE_SUMMARY.md b/PR34_UPDATE_SUMMARY.md index 26edbe2..776f383 100644 --- a/PR34_UPDATE_SUMMARY.md +++ b/PR34_UPDATE_SUMMARY.md @@ -27,18 +27,24 @@ PR #34 (https://github.com/mp-007/kivy_matplotlib_widget/pull/34) was not mergea To complete the update of PR #34, someone with write access needs to either: ### Option 1: Update PR #34's source branch (Recommended) +The local repository contains the necessary commits on `copilot/format-src-files-to-pep8` branch. +These commits need to be pushed to the remote: ```bash +# From a local clone with push access: +git fetch origin copilot/cleanup-branch git checkout copilot/format-src-files-to-pep8 -git pull origin copilot/cleanup-branch -git push origin copilot/format-src-files-to-pep8 +git cherry-pick 455234d aadf8dc # Or merge copilot/cleanup-branch +git push origin copilot/format-src-files-to-pep8 --force-with-lease ``` ### Option 2: Change PR #34's source branch - Edit PR #34 to use `copilot/cleanup-branch` instead of `copilot/format-src-files-to-pep8` as the source branch +- This branch is already pushed and ready to merge ### Option 3: Create new PR - Close PR #34 - Create a new PR from `copilot/cleanup-branch` to `cleanup` +- This is the simplest option since `copilot/cleanup-branch` is already pushed and verified ## Commits Created 1. **455234d** - Apply isort to organize imports with black profile