diff --git a/kivy_matplotlib_widget/tools/clipboard_tool.py b/kivy_matplotlib_widget/tools/clipboard_tool.py index 8f70a3b..9490b0f 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 @@ -25,37 +25,37 @@ """ import subprocess import tempfile - + elif platform == 'macosx': """ Appkit come with pyobjc """ from AppKit import NSPasteboard, 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. @@ -71,7 +71,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, @@ -79,10 +79,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 0761b2c..b3651b3 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 @@ -175,8 +175,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): @@ -210,7 +210,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}} @@ -232,7 +232,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 77b65fd..1c24088 100644 --- a/kivy_matplotlib_widget/tools/interactive_converter.py +++ b/kivy_matplotlib_widget/tools/interactive_converter.py @@ -8,6 +8,7 @@ import multiprocessing as mp import matplotlib.pyplot as plt + from kivy.app import App from kivy.core.window import Window from kivy.lang import Builder @@ -26,16 +27,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' @@ -53,17 +54,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) @@ -86,18 +87,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, @@ -120,10 +121,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 @@ -134,50 +135,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: @@ -188,9 +189,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) @@ -198,37 +199,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] @@ -237,15 +238,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') @@ -255,20 +256,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') @@ -281,14 +282,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 9e099e0..5eb7dc5 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 99ac617..9f1b6a5 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 copy @@ -41,33 +41,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 \ @@ -83,11 +83,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 @@ -96,12 +96,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): @@ -129,11 +129,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 = [],[] @@ -141,18 +141,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: @@ -163,29 +163,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): @@ -195,10 +195,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" @@ -206,10 +206,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) @@ -219,7 +219,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: @@ -227,7 +227,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): """ @@ -300,11 +300,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 = [] @@ -313,7 +313,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)) @@ -323,7 +323,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: @@ -331,20 +331,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): """ @@ -376,44 +376,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: @@ -421,28 +421,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: @@ -450,39 +450,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 @@ -505,7 +505,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) @@ -513,11 +513,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) @@ -525,55 +525,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: @@ -582,38 +582,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 @@ -626,17 +626,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 @@ -645,60 +645,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: @@ -859,53 +859,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): @@ -917,56 +917,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 @@ -977,13 +977,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)]) @@ -995,9 +995,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)]) @@ -1009,8 +1009,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() @@ -1020,7 +1020,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] @@ -1028,66 +1028,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)]) @@ -1099,9 +1099,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)]) @@ -1113,51 +1113,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'): @@ -1167,56 +1167,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: @@ -1226,26 +1226,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: @@ -1325,16 +1325,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: @@ -1346,9 +1346,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: @@ -1360,14 +1360,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: @@ -1375,27 +1375,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: @@ -1404,28 +1404,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: @@ -1433,87 +1433,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: @@ -1544,10 +1544,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 @@ -1555,11 +1555,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: @@ -1577,18 +1577,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: @@ -1600,21 +1600,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 """ @@ -1712,50 +1712,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 @@ -1799,22 +1799,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: @@ -1825,25 +1825,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: @@ -1851,23 +1851,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 @@ -1875,12 +1875,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: @@ -1890,6 +1890,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 f2b6094..3f70bfd 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 """ @@ -46,7 +46,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 @@ -57,7 +57,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 @@ -72,25 +72,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 @@ -98,9 +98,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) @@ -108,7 +108,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 @@ -123,7 +123,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) @@ -133,34 +133,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 @@ -169,7 +169,7 @@ def __init__(self, **kwargs): self.ymin = None self.ymax = None self.lines = [] - + #option self.touch_mode='pan' self.hover_on = False @@ -181,52 +181,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,_): @@ -235,39 +235,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 @@ -280,7 +280,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: @@ -291,92 +291,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: @@ -390,28 +390,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): @@ -420,13 +420,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): @@ -434,51 +434,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) @@ -490,130 +490,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: @@ -641,22 +641,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: @@ -664,60 +664,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: @@ -725,19 +725,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 """ @@ -746,19 +746,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: @@ -766,13 +766,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): """ @@ -795,7 +795,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( @@ -805,13 +805,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 @@ -828,7 +828,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): @@ -836,13 +836,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)) @@ -884,10 +884,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""" @@ -895,43 +895,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] @@ -964,13 +964,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 @@ -983,7 +983,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 @@ -1000,7 +1000,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: @@ -1010,22 +1010,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): @@ -1046,7 +1046,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) @@ -1054,11 +1054,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 @@ -1067,32 +1067,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: @@ -1106,8 +1106,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 @@ -1131,75 +1131,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] @@ -1207,7 +1207,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) @@ -1229,9 +1229,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)]) @@ -1243,21 +1243,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) @@ -1266,33 +1266,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: @@ -1302,11 +1302,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] @@ -1318,16 +1318,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 @@ -1335,16 +1335,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: @@ -1356,9 +1356,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: @@ -1369,14 +1369,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: @@ -1384,20 +1384,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: @@ -1406,27 +1406,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: @@ -1435,26 +1435,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() @@ -1465,15 +1465,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 @@ -1481,20 +1481,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 """ @@ -1651,14 +1651,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: @@ -1685,9 +1685,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: @@ -1708,49 +1708,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() @@ -1762,24 +1762,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] @@ -1787,7 +1787,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) @@ -1820,9 +1820,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)]) @@ -1834,14 +1834,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 """ @@ -1862,9 +1862,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() @@ -1875,18 +1875,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: @@ -1900,10 +1900,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 @@ -1911,22 +1911,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 """ @@ -1934,32 +1934,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 @@ -2003,22 +2003,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: @@ -2029,7 +2029,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 @@ -2057,7 +2057,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() @@ -2068,7 +2068,7 @@ def blit(self, bbox=None): class FakeEvent: x:None y:None - + from kivy.factory import Factory Factory.register('MatplotFigure', MatplotFigure) @@ -2093,8 +2093,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 @@ -2102,7 +2102,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: @@ -2111,7 +2111,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: @@ -2120,7 +2120,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 18ed6a1..360d253 100644 --- a/kivy_matplotlib_widget/uix/graph_widget_3d.py +++ b/kivy_matplotlib_widget/uix/graph_widget_3d.py @@ -105,7 +105,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) @@ -119,7 +119,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 @@ -127,15 +127,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: @@ -151,9 +151,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]) @@ -162,11 +162,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 @@ -174,7 +174,7 @@ def __init__(self, **kwargs): self.xmax = None self.ymin = None self.ymax = None - + #option self.zoompan = None self.fast_draw=True @@ -193,13 +193,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): @@ -208,12 +208,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)) @@ -233,7 +233,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), @@ -244,8 +244,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. @@ -268,7 +268,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( @@ -278,13 +278,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 @@ -304,18 +304,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 @@ -329,7 +329,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: @@ -339,17 +339,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] @@ -381,27 +381,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): @@ -428,7 +428,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() @@ -439,13 +439,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] @@ -477,7 +477,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() @@ -492,26 +492,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: @@ -521,7 +521,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 @@ -535,7 +535,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 @@ -547,14 +547,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): @@ -595,8 +595,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 """ @@ -613,9 +613,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: @@ -625,18 +625,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 @@ -666,21 +666,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], @@ -691,13 +691,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]) @@ -705,7 +705,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': @@ -718,42 +718,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: @@ -769,14 +769,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: @@ -798,7 +798,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 @@ -808,31 +808,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): """ @@ -849,32 +849,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: @@ -883,21 +883,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: @@ -906,53 +906,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 c450b96..f50af8e 100644 --- a/kivy_matplotlib_widget/uix/graph_widget_crop_factor.py +++ b/kivy_matplotlib_widget/uix/graph_widget_crop_factor.py @@ -38,7 +38,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 @@ -53,32 +53,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) @@ -87,7 +87,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) @@ -95,7 +95,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 @@ -138,13 +138,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 @@ -159,7 +159,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) @@ -168,11 +168,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] @@ -325,28 +325,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 @@ -414,7 +414,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 @@ -455,8 +455,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' \ @@ -466,31 +466,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): @@ -573,29 +573,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: @@ -605,11 +605,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] @@ -621,16 +621,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 @@ -638,16 +638,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]: @@ -655,7 +655,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: @@ -667,17 +667,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: @@ -688,10 +688,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' @@ -704,12 +704,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: @@ -717,20 +717,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: @@ -739,27 +739,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: @@ -769,27 +769,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 """ @@ -896,10 +896,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: @@ -942,35 +942,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 """ @@ -1028,34 +1028,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 @@ -1099,22 +1099,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: @@ -1125,20 +1125,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: @@ -1152,10 +1152,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 @@ -1163,8 +1163,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 @@ -1207,7 +1207,7 @@ def clear(self): Builder.load_string(''' - + canvas: Color: rgba: (1, 1, 1, 1) @@ -1226,8 +1226,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 @@ -1235,7 +1235,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: @@ -1244,7 +1244,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: @@ -1253,7 +1253,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: @@ -1263,6 +1263,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 99bae1d..207d263 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 """ @@ -34,7 +34,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 @@ -49,7 +49,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) @@ -61,13 +61,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 @@ -75,7 +75,7 @@ def __init__(self, **kwargs): self.xmax = None self.ymin = None self.ymax = None - + #option self.zoompan = None self.fast_draw=True @@ -90,22 +90,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 """ @@ -131,13 +131,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 @@ -173,13 +173,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 """ @@ -196,20 +196,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)) @@ -217,29 +217,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 """ @@ -247,9 +247,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: @@ -260,7 +260,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 @@ -288,7 +288,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 14355a8..7d94ea2 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 """ @@ -51,7 +51,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 @@ -66,14 +66,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 @@ -81,26 +81,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 @@ -122,7 +122,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() @@ -132,14 +132,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)) @@ -148,7 +148,7 @@ def on_figure(self, obj, value): def __init__(self, **kwargs): super(MatplotFigureScatter, self).__init__(**kwargs) - + #figure info self.figure = None self.axes = None @@ -158,7 +158,7 @@ def __init__(self, **kwargs): self.ymax = None self.lines = [] self.scatters =[] - + #option self.touch_mode='pan' self.hover_on = False @@ -170,22 +170,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 @@ -193,64 +193,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 @@ -263,7 +263,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: @@ -274,87 +274,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: @@ -371,74 +371,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())) @@ -567,31 +567,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}") @@ -602,29 +602,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: @@ -632,9 +632,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() @@ -645,7 +645,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: @@ -653,7 +653,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() @@ -725,11 +725,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 = [] @@ -738,7 +738,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)) @@ -748,7 +748,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: @@ -756,20 +756,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 """ @@ -801,19 +801,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: @@ -821,10 +821,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): """ @@ -857,7 +857,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.""" @@ -880,7 +880,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): @@ -888,13 +888,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)) @@ -948,40 +948,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] @@ -1014,13 +1014,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 @@ -1033,7 +1033,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 @@ -1045,13 +1045,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: @@ -1065,12 +1065,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: @@ -1084,7 +1084,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) @@ -1092,45 +1092,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: @@ -1140,13 +1140,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 @@ -1162,7 +1162,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) @@ -1171,69 +1171,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] @@ -1241,7 +1241,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) @@ -1263,9 +1263,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)]) @@ -1277,55 +1277,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: @@ -1334,7 +1334,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: @@ -1344,11 +1344,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] @@ -1360,16 +1360,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 @@ -1377,16 +1377,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: @@ -1398,9 +1398,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: @@ -1411,14 +1411,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: @@ -1426,19 +1426,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: @@ -1447,27 +1447,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: @@ -1475,34 +1475,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() @@ -1511,29 +1511,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 """ @@ -1690,14 +1690,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: @@ -1724,9 +1724,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: @@ -1751,43 +1751,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() @@ -1799,24 +1799,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] @@ -1824,7 +1824,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) @@ -1857,9 +1857,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)]) @@ -1871,11 +1871,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 """ @@ -1892,35 +1892,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: @@ -1934,10 +1934,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 @@ -1945,20 +1945,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 """ @@ -1966,32 +1966,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: @@ -2061,7 +2061,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 @@ -2089,14 +2089,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 @@ -2125,8 +2125,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 @@ -2134,7 +2134,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: @@ -2143,7 +2143,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: @@ -2152,7 +2152,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 35b1099..1a0b278 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 """ @@ -58,7 +58,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 @@ -73,44 +73,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 @@ -130,11 +130,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] @@ -143,38 +143,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 @@ -185,54 +185,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 @@ -242,20 +242,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,_): @@ -264,39 +264,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 @@ -309,7 +309,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: @@ -320,35 +320,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() @@ -357,46 +357,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])) @@ -404,32 +404,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: @@ -444,116 +444,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) @@ -565,188 +565,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: @@ -774,77 +774,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: @@ -852,43 +852,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) @@ -899,24 +899,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): """ @@ -949,7 +949,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.""" @@ -972,7 +972,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): @@ -980,13 +980,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)) @@ -1028,9 +1028,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""" @@ -1040,7 +1040,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: @@ -1050,37 +1050,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] @@ -1113,13 +1113,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 @@ -1132,7 +1132,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: @@ -1146,13 +1146,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: @@ -1166,12 +1166,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: @@ -1186,7 +1186,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) @@ -1194,41 +1194,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) @@ -1236,7 +1236,7 @@ def on_touch_down(self, event): if len(self._touches)>1: #new touch, reset background self.background=None - + return True else: @@ -1246,13 +1246,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 @@ -1268,7 +1268,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) @@ -1277,7 +1277,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' \ @@ -1286,24 +1286,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 @@ -1311,40 +1311,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] @@ -1352,7 +1352,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) @@ -1374,9 +1374,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)]) @@ -1388,35 +1388,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] @@ -1427,22 +1427,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] @@ -1450,7 +1450,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) @@ -1472,9 +1472,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)]) @@ -1486,7 +1486,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': @@ -1495,13 +1495,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)]) @@ -1517,64 +1517,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: @@ -1584,11 +1584,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] @@ -1600,16 +1600,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 @@ -1617,16 +1617,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: @@ -1638,9 +1638,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: @@ -1651,14 +1651,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: @@ -1666,20 +1666,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: @@ -1687,28 +1687,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: @@ -1717,26 +1717,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() @@ -1750,25 +1750,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: @@ -1778,27 +1778,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] @@ -1811,17 +1811,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 @@ -1829,21 +1829,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': @@ -1851,32 +1851,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 @@ -1885,105 +1885,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), @@ -2000,42 +2000,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)""" @@ -2043,39 +2043,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 """ @@ -2233,14 +2233,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: @@ -2267,9 +2267,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: @@ -2290,15 +2290,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: @@ -2310,26 +2310,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: @@ -2337,33 +2337,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 """ @@ -2372,24 +2372,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] @@ -2397,7 +2397,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) @@ -2430,9 +2430,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)]) @@ -2444,14 +2444,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""" @@ -2464,7 +2464,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() @@ -2476,10 +2476,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] @@ -2487,7 +2487,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) @@ -2520,9 +2520,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)]) @@ -2534,7 +2534,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': @@ -2543,13 +2543,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)]) @@ -2565,15 +2565,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: @@ -2593,20 +2593,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""" @@ -2616,9 +2616,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: @@ -2628,23 +2628,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: @@ -2657,15 +2657,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 @@ -2673,22 +2673,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 """ @@ -2696,34 +2696,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 @@ -2767,22 +2767,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: @@ -2793,7 +2793,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 @@ -2821,7 +2821,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() @@ -2857,8 +2857,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 @@ -2866,7 +2866,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: @@ -2875,7 +2875,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: @@ -2884,7 +2884,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 55acf54..6194723 100644 --- a/kivy_matplotlib_widget/uix/hover_widget.py +++ b/kivy_matplotlib_widget/uix/hover_widget.py @@ -10,7 +10,7 @@ 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) @@ -18,91 +18,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) @@ -111,45 +111,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=[] @@ -160,7 +160,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: @@ -172,17 +172,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) @@ -196,7 +196,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: @@ -208,7 +208,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: @@ -224,14 +224,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) @@ -241,13 +241,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) @@ -257,20 +257,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=[] @@ -281,7 +281,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: @@ -293,7 +293,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=[] @@ -315,10 +315,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) @@ -329,37 +329,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)) @@ -372,11 +372,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', @@ -385,7 +385,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)) @@ -393,7 +393,7 @@ class PlotlyHover(BaseHoverFloatLayout): use_position = StringProperty('right') position = OptionProperty('right', options=('right', 'left')) - + def __init__(self, **kwargs): """ init class """ super().__init__(**kwargs) @@ -402,10 +402,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: @@ -416,39 +416,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 @@ -462,61 +462,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 @@ -530,13 +530,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: @@ -544,32 +544,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 + \ @@ -579,12 +579,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 + \ @@ -593,8 +593,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) @@ -603,15 +603,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: @@ -621,41 +621,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) @@ -663,15 +663,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) @@ -681,22 +681,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: @@ -706,40 +706,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: [ \ @@ -747,19 +747,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) @@ -773,13 +773,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: @@ -794,93 +794,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 @@ -894,20 +894,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: @@ -918,16 +918,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 @@ -941,10 +941,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: @@ -952,18 +952,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) @@ -972,14 +972,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: @@ -992,7 +992,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 @@ -1010,35 +1010,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: @@ -1055,7 +1055,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: @@ -1067,15 +1067,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: @@ -1085,11 +1085,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: @@ -1100,14 +1100,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 @@ -1118,41 +1118,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) @@ -1160,14 +1160,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 @@ -1178,22 +1178,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 @@ -1203,5 +1203,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 d865bc3..ced76f8 100644 --- a/kivy_matplotlib_widget/uix/legend_widget.py +++ b/kivy_matplotlib_widget/uix/legend_widget.py @@ -21,7 +21,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). """ @@ -44,7 +44,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. # @@ -52,8 +52,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): @@ -71,8 +71,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, @@ -82,7 +82,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) @@ -104,7 +104,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 @@ -136,7 +136,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 @@ -161,9 +161,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") @@ -174,27 +174,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" @@ -202,7 +202,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 @@ -211,16 +211,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), @@ -229,7 +229,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 @@ -239,28 +239,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 @@ -268,9 +268,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 @@ -279,10 +279,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"] @@ -297,44 +297,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") @@ -342,30 +342,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" @@ -373,7 +373,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 @@ -382,16 +382,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), @@ -400,7 +400,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 @@ -410,28 +410,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 @@ -441,7 +441,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 @@ -450,10 +450,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"] @@ -464,54 +464,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 @@ -521,24 +521,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 @@ -547,24 +547,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 @@ -573,7 +573,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, @@ -585,7 +585,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) @@ -596,7 +596,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() @@ -604,26 +604,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 @@ -651,7 +651,7 @@ def MatplotlibInteractiveLegend(figure_wgt, scatter, autoscale), delay) - + def create_touch_legend(figure_wgt, leg,ncol, @@ -663,26 +663,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 @@ -690,7 +690,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: @@ -704,9 +704,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 @@ -715,7 +715,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: @@ -724,10 +724,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]) @@ -738,15 +738,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): @@ -760,42 +760,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] @@ -805,8 +805,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 @@ -828,19 +828,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 @@ -850,22 +850,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=[] @@ -874,8 +874,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 """ @@ -886,47 +886,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 @@ -935,10 +935,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: @@ -946,7 +946,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) @@ -957,13 +957,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) @@ -971,18 +971,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: @@ -990,14 +990,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) @@ -1007,48 +1007,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: @@ -1056,31 +1056,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 @@ -1091,14 +1091,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] @@ -1108,7 +1108,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: @@ -1116,7 +1116,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) @@ -1243,13 +1243,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'): @@ -1260,7 +1260,7 @@ def get_visible(self,instance) -> bool: return return_value else: return False - + from kivy.factory import Factory Factory.register('LegendRv', LegendRv) @@ -1269,60 +1269,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 @@ -1333,22 +1333,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 @@ -1364,7 +1364,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. \ @@ -1376,7 +1376,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, \ @@ -1388,7 +1388,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 @@ -1396,51 +1396,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: @@ -1451,21 +1451,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 @@ -1481,7 +1481,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. \ @@ -1493,7 +1493,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, \ @@ -1505,7 +1505,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 @@ -1513,50 +1513,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 8ff05a8..35cd6cb 100644 --- a/kivy_matplotlib_widget/uix/minmax_widget.py +++ b/kivy_matplotlib_widget/uix/minmax_widget.py @@ -15,34 +15,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) @@ -53,7 +53,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)) @@ -66,11 +66,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. @@ -88,9 +88,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 @@ -101,7 +101,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: @@ -110,41 +110,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 @@ -156,16 +156,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: @@ -189,28 +189,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: @@ -226,18 +226,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: @@ -246,32 +246,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 d4d5b0b..1eef3b7 100644 --- a/kivy_matplotlib_widget/uix/navigation_bar_widget.py +++ b/kivy_matplotlib_widget/uix/navigation_bar_widget.py @@ -42,7 +42,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() @@ -51,7 +51,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) @@ -81,8 +81,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)) @@ -95,7 +95,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: @@ -123,13 +123,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: @@ -140,8 +140,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') @@ -156,46 +156,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 @@ -208,14 +208,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) @@ -223,23 +223,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 @@ -248,16 +248,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): @@ -266,12 +266,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) @@ -283,8 +283,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 @@ -295,7 +295,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: @@ -307,7 +307,7 @@ def home_3D(self): font_name:"NavigationIcons" Button: id: forward_btn - text: "Forward" + text: "Forward" font_name:"NavigationIcons" ToggleButton: id: pan_btn @@ -319,7 +319,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 @@ -336,12 +336,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 @@ -355,7 +355,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 @@ -365,7 +365,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" @@ -379,12 +379,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" @@ -397,7 +397,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 52f9348..0fee3f2 100644 --- a/kivy_matplotlib_widget/uix/selector_widget.py +++ b/kivy_matplotlib_widget/uix/selector_widget.py @@ -22,14 +22,14 @@ from matplotlib.path import Path 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' @@ -43,7 +43,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' @@ -57,8 +57,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' @@ -71,7 +71,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' @@ -90,7 +90,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: ( \ @@ -112,7 +112,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: ( \ @@ -134,7 +134,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: ( \ @@ -145,7 +145,7 @@ cap: 'round' joint: 'round' close: True - + # Upper right rectangle Color: rgba: 62/255, 254/255, 1, root.alpha @@ -156,7 +156,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: ( \ @@ -178,7 +178,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: ( \ @@ -200,7 +200,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: ( \ @@ -223,7 +223,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: ( \ @@ -246,7 +246,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: ( \ @@ -258,16 +258,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 @@ -276,7 +276,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 @@ -285,8 +285,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 @@ -295,24 +295,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 @@ -321,12 +321,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) @@ -339,12 +339,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) @@ -357,12 +357,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 @@ -385,7 +385,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() @@ -394,7 +394,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() @@ -403,7 +403,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() @@ -412,7 +412,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) @@ -450,7 +450,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) @@ -458,7 +458,7 @@ def __init__(self,**kwargs): self.verts = [] self.ax = None self.callback = None - + self.alpha_other = 0.3 self.ind = [] @@ -466,16 +466,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) @@ -484,13 +484,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() @@ -502,9 +502,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): """ @@ -512,7 +512,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") @@ -524,7 +524,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") @@ -564,7 +564,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] @@ -611,7 +611,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) @@ -619,25 +619,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) @@ -651,7 +651,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 @@ -661,7 +661,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 @@ -670,7 +670,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: @@ -685,7 +685,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: @@ -697,10 +697,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: @@ -717,7 +717,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: @@ -730,15 +730,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 \ @@ -748,8 +748,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: @@ -760,7 +760,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 @@ -772,11 +772,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 @@ -812,9 +812,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] @@ -824,8 +824,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)) @@ -846,18 +846,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: @@ -866,9 +866,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.): @@ -1300,7 +1300,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 @@ -2165,7 +2165,7 @@ class PaintEllipse(PaintShape): ready_to_finish = True is_valid = True - + during_creation = False def __init__(self, **kwargs): @@ -2213,7 +2213,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) @@ -2224,7 +2224,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 @@ -2240,11 +2240,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) @@ -2435,25 +2435,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. """ @@ -2461,30 +2461,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. @@ -2627,11 +2627,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) @@ -2851,7 +2851,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. @@ -3000,7 +3000,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) @@ -3008,7 +3008,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 = [] @@ -3016,7 +3016,7 @@ def __init__(self,**kwargs): self.ax = None self.callback = None self.callback_clear = None - + self.alpha_other = 0.3 self.ind = [] @@ -3024,18 +3024,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 @@ -3043,7 +3043,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") @@ -3055,10 +3055,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) @@ -3071,7 +3071,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 @@ -3102,7 +3102,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: @@ -3126,7 +3126,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: @@ -3148,15 +3148,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: @@ -3165,13 +3165,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: @@ -3179,7 +3179,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 @@ -3188,37 +3188,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) @@ -3230,21 +3230,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) @@ -3262,11 +3262,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' @@ -3274,7 +3274,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]) @@ -3291,15 +3291,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: @@ -3349,20 +3349,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) @@ -3372,7 +3372,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. @@ -3398,7 +3398,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. """ @@ -3425,41 +3425,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): @@ -3473,10 +3473,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) @@ -3487,18 +3487,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: @@ -3509,11 +3509,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], @@ -3521,7 +3521,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) @@ -3529,7 +3529,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 = [] @@ -3537,7 +3537,7 @@ def __init__(self,**kwargs): self.ax = None self.callback = None self.callback_clear = None - + self.alpha_other = 0.3 self.ind = [] @@ -3545,18 +3545,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 @@ -3564,7 +3564,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") @@ -3576,16 +3576,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 @@ -3595,7 +3595,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 @@ -3620,12 +3620,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 @@ -3634,7 +3634,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) @@ -3653,7 +3653,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) @@ -3677,11 +3677,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) @@ -3693,14 +3693,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: @@ -3708,7 +3708,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 @@ -3717,10 +3717,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() @@ -3729,28 +3729,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) @@ -3759,20 +3759,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: @@ -3781,7 +3781,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'] @@ -3797,23 +3797,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 @@ -3831,16 +3831,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: @@ -3891,35 +3891,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. @@ -3945,7 +3945,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. """ @@ -3972,8 +3972,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 @@ -3986,21 +3986,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: @@ -4010,18 +4010,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: @@ -4032,11 +4032,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], @@ -4044,7 +4044,7 @@ def _get_ellipse_data(self,widget_verts): verts.append((x, y)) return verts - + class SpanSelect(FloatLayout): top_color = ColorProperty("black") @@ -4052,7 +4052,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']) @@ -4061,16 +4061,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 = [] @@ -4078,16 +4078,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) @@ -4096,13 +4096,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() @@ -4114,9 +4114,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): """ @@ -4124,7 +4124,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") @@ -4136,7 +4136,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") @@ -4147,7 +4147,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): @@ -4161,7 +4161,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 @@ -4210,62 +4210,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 @@ -4280,19 +4280,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) @@ -4306,15 +4306,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 @@ -4324,7 +4324,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 @@ -4332,10 +4332,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: @@ -4349,7 +4349,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 @@ -4361,11 +4361,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 @@ -4411,9 +4411,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] @@ -4423,9 +4423,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)) @@ -4434,7 +4434,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]) @@ -4442,27 +4442,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 @@ -4475,26 +4475,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: @@ -4503,8 +4503,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