blob: f21b2ea13ea14c8c1c1be53e31427f00ec234c6e [file] [log] [blame]
Ravago Jones6d460fe2021-07-03 16:59:55 -07001import gi
2gi.require_version('Gtk', '3.0')
3from gi.repository import Gtk
4import numpy as np
Ravago Jones56941a52021-07-31 14:42:38 -07005import queue
6import threading
7import copy
John Park91e69732019-03-03 13:12:43 -08008from points import Points
John Park91e69732019-03-03 13:12:43 -08009from libspline import Spline, DistanceSpline, Trajectory
10
Ravago Jones6d460fe2021-07-03 16:59:55 -070011from matplotlib.backends.backend_gtk3agg import (FigureCanvasGTK3Agg as
12 FigureCanvas)
13from matplotlib.figure import Figure
John Park91e69732019-03-03 13:12:43 -080014
Ravago Jones8da89c42022-07-17 19:34:06 -070015
Ravago Jones6d460fe2021-07-03 16:59:55 -070016class Graph(Gtk.Bin):
17 def __init__(self):
18 super(Graph, self).__init__()
19 fig = Figure(figsize=(5, 4), dpi=100)
20 self.axis = fig.add_subplot(111)
Ravago Jonese9584792022-05-28 16:16:46 -070021 self.canvas = FigureCanvas(fig) # a Gtk.DrawingArea
22 self.canvas.set_vexpand(True)
23 self.canvas.set_size_request(800, 250)
Ravago Jones0a1d4092022-06-03 12:47:32 -070024 self.callback_id = self.canvas.mpl_connect('motion_notify_event',
25 self.on_mouse_move)
Ravago Jonese9584792022-05-28 16:16:46 -070026 self.add(self.canvas)
Ravago Jones56941a52021-07-31 14:42:38 -070027
Ravago Jones0a1d4092022-06-03 12:47:32 -070028 # The current graph data
29 self.data = None
30 # The size of a timestep
31 self.dt = 0.00505
32 # The position of the cursor in seconds
33 self.cursor = 0
34
35 # Reference to the parent gtk Widget that wants to get redrawn
36 # when user moves the cursor
37 self.cursor_watcher = None
38 self.cursor_line = None
39
40 self.queue = queue.Queue(maxsize=1)
Ravago Jones56941a52021-07-31 14:42:38 -070041 thread = threading.Thread(target=self.worker)
42 thread.daemon = True
43 thread.start()
44
Ravago Jones0a1d4092022-06-03 12:47:32 -070045 def find_cursor(self):
46 """Gets the cursor position as a distance along the spline"""
47 if self.data is None:
48 return None
49 cursor_index = int(self.cursor / self.dt)
50 # use the time to index into the position data
51 distance_at_cursor = self.data[0][cursor_index - 1]
52 return distance_at_cursor
53
54 def place_cursor(self, distance):
55 """Places the cursor at a certain distance along the spline"""
56 if self.data is None:
57 return
58 # convert distance along spline to time along trajectory
59 index = np.searchsorted(self.data[0], distance, side='left')
60 time = index * self.dt
61 self.cursor = time
62 self.redraw_cursor()
63
64 def on_mouse_move(self, event):
65 """Updates the cursor and all the canvases that watch it on mouse move"""
66 if self.data is None:
67 return
68 total_steps_taken = self.data.shape[1]
69 total_time = self.dt * total_steps_taken
70 if event.xdata is not None:
71 # clip the position if still on the canvas, but off the graph
72 self.cursor = np.clip(event.xdata, 0, total_time)
73
74 self.redraw_cursor()
75
76 # tell the field to update too
77 if self.cursor_watcher is not None:
78 self.cursor_watcher.queue_draw()
79
80 def redraw_cursor(self):
81 """Redraws the cursor line"""
82 # TODO: This redraws the entire graph and isn't very snappy
83 if self.cursor_line: self.cursor_line.remove()
84 self.cursor_line = self.axis.axvline(self.cursor)
85 self.canvas.draw_idle()
86
Ravago Jones56941a52021-07-31 14:42:38 -070087 def schedule_recalculate(self, points):
Ravago Jones0a1d4092022-06-03 12:47:32 -070088 """Submits points to be graphed
89
90 Can be superseded by newer points if an old one isn't finished processing.
91 """
92 if not points.getLibsplines(): return
Ravago Jones56941a52021-07-31 14:42:38 -070093 new_copy = copy.deepcopy(points)
94
95 # empty the queue
96 try:
97 self.queue.get_nowait()
98 except queue.Empty:
Ravago Jones8da89c42022-07-17 19:34:06 -070099 pass # was already empty
Ravago Jones56941a52021-07-31 14:42:38 -0700100
101 # replace with new request
102 self.queue.put_nowait(new_copy)
103
104 def worker(self):
105 while True:
106 self.recalculate_graph(self.queue.get())
John Park91e69732019-03-03 13:12:43 -0800107
Ravago Jones6d460fe2021-07-03 16:59:55 -0700108 def recalculate_graph(self, points):
109 if not points.getLibsplines(): return
John Park91e69732019-03-03 13:12:43 -0800110
Ravago Jones6d460fe2021-07-03 16:59:55 -0700111 # call C++ wrappers to calculate the trajectory
Ravago Jones0a1d4092022-06-03 12:47:32 -0700112 distance_spline = DistanceSpline(points.getLibsplines())
113 traj = Trajectory(distance_spline)
Ravago Jones6d460fe2021-07-03 16:59:55 -0700114 points.addConstraintsToTrajectory(traj)
115 traj.Plan()
Ravago Jones0a1d4092022-06-03 12:47:32 -0700116 self.data = traj.GetPlanXVA(self.dt)
117 if self.data is None: return
John Park91e69732019-03-03 13:12:43 -0800118
Ravago Jones6d460fe2021-07-03 16:59:55 -0700119 # extract values to be graphed
Ravago Jones0a1d4092022-06-03 12:47:32 -0700120 total_steps_taken = self.data.shape[1]
121 total_time = self.dt * total_steps_taken
122 times = np.linspace(0, total_time, num=total_steps_taken)
123 position, velocity, acceleration = self.data
Ravago Jones6d460fe2021-07-03 16:59:55 -0700124 left_voltage, right_voltage = zip(*(traj.Voltage(x) for x in position))
John Park91e69732019-03-03 13:12:43 -0800125
Ravago Jones6d460fe2021-07-03 16:59:55 -0700126 # update graph
127 self.axis.clear()
Ravago Jones0a1d4092022-06-03 12:47:32 -0700128 self.axis.plot(times, velocity)
129 self.axis.plot(times, acceleration)
130 self.axis.plot(times, left_voltage)
131 self.axis.plot(times, right_voltage)
Ravago Jones6d460fe2021-07-03 16:59:55 -0700132 self.axis.legend(
133 ["Velocity", "Acceleration", "Left Voltage", "Right Voltage"])
134 self.axis.xaxis.set_label_text("Time (sec)")
John Park91e69732019-03-03 13:12:43 -0800135
Ravago Jones6d460fe2021-07-03 16:59:55 -0700136 # renumber the x-axis to include the last point,
137 # the total time to drive the spline
138 self.axis.xaxis.set_ticks(np.linspace(0, total_time, num=8))
Ravago Jones128fb992021-07-31 13:56:58 -0700139
Ravago Jonese9584792022-05-28 16:16:46 -0700140 # redraw
Ravago Jones0a1d4092022-06-03 12:47:32 -0700141 self.canvas.draw_idle()