python-3.xnumpymatplotlibplotmplcursors

Moving the plot regrading to the offset


Dears,

I wrote this code to calculate the distance between two mouse clicks on the plot. Now I am trying to move the plot to the left or to the right with regards to the calculated offset so that the two plots are exactly match. any idea how to achieve that? I tried to add and subtract normally but it did not work.

import matplotlib.pyplot as plt
from matplotlib.widgets import Cursor
import mplcursors
from math import sqrt
import numpy as np 
import tkinter as tk
from tkinter import simpledialog

class DistancePlot:
    def __init__(self):
        
        ROOT = tk.Tk()
        ROOT.withdraw()
        ROOT.geometry("500x200")
        #my_frame = Frame(ROOT)
        #my_frame.pack(fill="both", expand=True)

        USER_INP = (simpledialog.askinteger(title="Plot dialig", 
                                  prompt="Enter the number of the plots "))
        
        if USER_INP is not None: 
            def f(x):
                return np.sin(x) + np.random.normal(scale=0.1, size=len(x))
            self.x = np.linspace(1, 10)
            self.fig, self.ax= plt.subplots()
            for i in range(USER_INP):
                plt.plot(self.x, f(self.x))

       
            self.ax.set_xlabel('X-axis')
            self.ax.set_ylabel('Y-axis')

            self.d1 = (0.0, 0.0)
            self.d2 = (0.0, 0.0)

            self.first_click = True

            self.cursor=Cursor(self.ax, horizOn=True, vertOn=True, color='black', linewidth=1.0)

            self.fig.canvas.mpl_connect('button_press_event', self.onclick)

            mplcursors.cursor(hover=True)
            plt.show()
            
        else: 
            def quit(self):
                self.ROOT.destroy()
        
    def onclick(self, event):
        z1, r1 = event.xdata, event.ydata
        print(z1, r1)

        if self.first_click:
            self.first_click = False
            self.d1 = (z1, r1)
        else:
            self.first_click = True
            self.d2 = (z1, r1)
            distance = sqrt((((self.d1[0]) - (self.d2[0])) ** 2) + (((self.d1[1]) - (self.d2[1])) ** 2))
            print("The shift between ", self.d1, "and", self.d2, "is", distance)
    
            
dp = DistancePlot()

the answer in the comment was helpful but this is not exactly what I want, I tried to use the same logic to get to my solution but it didn't work and I will share it with you.

import matplotlib.pyplot as plt
from matplotlib.widgets import Cursor
import mplcursors
import numpy as np
import tkinter as tk
#from tkinter import simpledialog

class DistancePlot:
    def __init__(self):
        ROOT = tk.Tk()
        ROOT.withdraw()
        ROOT.geometry("500x200")
#        USER_INP = 1
        
        #random sine wave
        x = np.linspace(1, 10)
        def f(x):
            return np.sin(x) + np.random.normal(scale=0.1, size=len(x)) 
        #fixed sine wave 
        time= np.arange(0, 10, 0.1)
        amplitude   = np.sin(time)
        
        self.fig, self.ax = plt.subplots()
        self.ax.plot(x, f(x))
        self.ax.plot(time, amplitude)
        self.ax.set_xlabel('X-axis')
        self.ax.set_ylabel('Y-axis')
        self.d1 = np.zeros(2)
        self.d2 = np.zeros(2)

        self.first_click = True
        self.cursor = Cursor(self.ax, horizOn=True, vertOn=True, color='black', linewidth=1)
        self.fig.canvas.mpl_connect('button_press_event', self.onclick)
        mplcursors.cursor(hover=True)
        plt.show()
            

    def onclick(self, event):
        z1, r1 = event.xdata, event.ydata
        print(z1, r1)
        if self.first_click:
            self.first_click = False
            self.d1 = np.array((z1, r1))
        else:
            self.first_click = True
            self.d2 = np.array((z1, r1))
            #distance = sqrt((((self.d1[0]) - (self.d2[0])) ** 2) + (((self.d1[1]) - (self.d2[1])) ** 2))
            #print("The distance between ", self.d1, "and", self.d2, "is", distance)
            delta = self.d2 - self.d1
            print("The delta between ", self.d1, "and", self.d2, "is", delta)
            if (abs(self.d2[0]) > abs(self.d1[0])).all():
                self.ax.lines[0].set_data(self.ax.lines[0].get_data() - delta.reshape(2,1))
                self.ax.relim()
                self.ax.autoscale_view()
                plt.draw()
                
            else: 
                self.ax.lines[0].set_data(self.ax.lines[0].get_data() + delta.reshape(2,1))
                self.ax.relim()
                self.ax.autoscale_view()
                plt.draw()

dp = DistancePlot()

what I want is use a reference graph and match it with another graph, if the graph I am adding is leading I want it to be subtracted and if it lagging I want it to move forward so adding it to the delta.


Solution

  • Here is an approach, moving the first curve over the given distance. Numpy arrays are used to simplify the loops. relim() and autoscale_view() recalculate the x and y limits to fit everything again inside a margin (this step can be skipped if the expected displacement is small).

    import matplotlib.pyplot as plt
    from matplotlib.widgets import Cursor
    import mplcursors
    from math import sqrt
    import numpy as np
    import tkinter as tk
    from tkinter import simpledialog
    
    class DistancePlot:
        def __init__(self):
            ROOT = tk.Tk()
            ROOT.withdraw()
            ROOT.geometry("500x200")
            USER_INP = 2
            # USER_INP = (simpledialog.askinteger(title="Plot dialig", prompt="Enter the number of the plots "))
            if USER_INP is not None:
                def f(x):
                    return np.sin(x) + np.random.normal(scale=0.1, size=len(x))
    
                self.x = np.linspace(1, 10)
                self.fig, self.ax = plt.subplots()
                for i in range(USER_INP):
                    self.ax.plot(self.x, f(self.x))
                self.ax.set_xlabel('X-axis')
                self.ax.set_ylabel('Y-axis')
                self.d1 = np.zeros(2)
                self.d2 = np.zeros(2)
    
                self.first_click = True
                self.cursor = Cursor(self.ax, horizOn=True, vertOn=True, color='black', linewidth=1)
                self.fig.canvas.mpl_connect('button_press_event', self.onclick)
                mplcursors.cursor(hover=True)
                plt.show()
            else:
                def quit(self):
                    self.ROOT.destroy()
    
        def onclick(self, event):
            z1, r1 = event.xdata, event.ydata
            if self.first_click:
                self.first_click = False
                self.d1 = np.array((z1, r1))
            else:
                self.first_click = True
                self.d2 = np.array((z1, r1))
                distance = sqrt((((self.d1[0]) - (self.d2[0])) ** 2) + (((self.d1[1]) - (self.d2[1])) ** 2))
                delta = self.d2 - self.d1
                self.ax.lines[0].set_data(self.ax.lines[0].get_data() + delta.reshape(2,1))
                self.ax.relim()
                self.ax.autoscale_view()
                plt.draw()
    
    dp = DistancePlot()
    

    If you only want to move left-right, you can use set_xdata to only change the x-positions. The following example code moves the second curve with the given displacement. If you want to move to the left, the second click should be to the left of the first.

        def onclick(self, event):
            z1, r1 = event.xdata, event.ydata
            if self.first_click:
                self.first_click = False
                self.d1 = np.array((z1, r1))
            else:
                self.first_click = True
                self.d2 = np.array((z1, r1))
                delta_x = self.d1[0] - self.d2[0]
                self.ax.lines[1].set_xdata(self.ax.lines[1].get_xdata() + delta_x)
                self.ax.relim()
                self.ax.autoscale_view()
                plt.draw()