# cPy - show dynamic data in a oscilloscope like window
# Copyright by Anthony Vogelaar, Perth Scotland
# rev 0.10  2024-10-03  initial release

import  time
from    collections         import  deque
import  matplotlib.pyplot   as  plt

# ============================== Oscilloscope =================================

# single channel data oscilloscope ~ 35 fps
class Oscilloscope1:

    def __init__(self, x_width, y_range):
        self.isRunning = True
        self.data      = deque([], x_width)
        plt.ion()                                       # enable interactive mode = redraw on change
        self.fig, ax   = plt.subplots()
        ax.set_facecolor('0')
        ax.grid(True, linestyle='-.', color='green')
        ax.tick_params(labelcolor='r', labelsize='medium')
        ax.set_xlim(0, x_width)
        ax.set_ylim(*y_range)
        self.line = ax.plot([],[], color='red')[0]    # plot returns list of Line2D
        self.fig.canvas.mpl_connect('close_event', self._onClose)


    # y is int/float, block for approx 30 mSec
    def append(self, y):
        self.data.append(y)
        y_data = [y for y in self.data]
        x_data = [i for i in range(len(self.data))]
        self.line.set_data(x_data, y_data)
        self.fig.canvas.draw()
        self.fig.canvas.flush_events()


    def _onClose(self, ev):
        self.isRunning = False


# dual channel data oscilloscope ~ 35 fps
class Oscilloscope2:

    def __init__(self, x_width, y_range):
        self.isRunning = True
        self.data1     = deque([], x_width)
        self.data2     = deque([], x_width)
        plt.ion()                                       # enable interactive mode = redraw on change
        self.fig, ax1  = plt.subplots()
        ax2 = ax1.twinx()
        ax1.set_facecolor('0')
        ax1.grid(True, linestyle='-.', color='green')
        ax1.tick_params(labelcolor='r', labelsize='medium')
        ax2.tick_params(labelcolor='b', labelsize='medium')
        ax1.set_xlim(0, x_width)
        ax1.set_ylim(*y_range[0])
        ax2.set_ylim(*y_range[1])
        self.line1 = ax1.plot([],[], color='red')[0]    # plot returns list of Line2D
        self.line2 = ax2.plot([],[], color='cyan')[0] 
        self.fig.canvas.mpl_connect('close_event', self._onClose)


    # y is 2-tuple int/float, block for approx 30 mSec
    def append(self, y):
        self.data1.append(y[0])
        self.data2.append(y[1])
        y_data1 = [y for y in self.data1]
        y_data2 = [y for y in self.data2]
        x_data  = [i for i in range(len(self.data1))]
        self.line1.set_data(x_data, y_data1)
        self.line2.set_data(x_data, y_data2)
        self.fig.canvas.draw()
        self.fig.canvas.flush_events()


    def _onClose(self, ev):
        self.isRunning = False

# =============================== Test =======================================

if __name__ == '__main__':

    import  os;     os.system('clear')
    import  time
    import  math

    def _getData():
        t = 0
        while True:
            t += 0.05
            yield(math.sin(t))
    getData = _getData()


    # single channel, run at 20 fps = 50 mS, horiz = 10 seconds
    def test1():
        osc  = Oscilloscope1(200, (-1, +1))
        t1 = time.time()
        while osc.isRunning:                        # 50 mS loop
            osc.append(next(getData))               # block for approx 30 mSec
            t1, dt = t1+0.05, t1+0.05-time.time()   # x speed is 200 * 0.05 = 10 sec / screen 
            if dt > 0: time.sleep(dt)


    # dual channel run at 20 fps = 50 mS, horiz = 10 seconds
    def test2():
        osc  = Oscilloscope2(200, ((-1, +1), (-10, 10)))
        t1 = time.time()
        while osc.isRunning:                        # 50 mS loop
            y  = next(getData)                      # block for approx 30 mSec
            osc.append((y, y*5))            
            t1, dt = t1+0.05, t1+0.05-time.time()   # x speed is 200 * 0.05 = 10 sec / screen 
            if dt > 0: time.sleep(dt)


    # test1()
    test2()

# ================================ End ==================================
