blob: 5e6fcb55c78dc4e67321ac25fed8304aebc769de [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
Ravago Jonesfa8da562022-07-02 18:10:22 -07008from multispline import Multispline
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 """
Ravago Jonesfa8da562022-07-02 18:10:22 -070092
93 # TODO: Draw all multisplines
94 points = points[0]
95
Ravago Jones0a1d4092022-06-03 12:47:32 -070096 if not points.getLibsplines(): return
Ravago Jones56941a52021-07-31 14:42:38 -070097 new_copy = copy.deepcopy(points)
98
99 # empty the queue
100 try:
101 self.queue.get_nowait()
102 except queue.Empty:
Ravago Jones8da89c42022-07-17 19:34:06 -0700103 pass # was already empty
Ravago Jones56941a52021-07-31 14:42:38 -0700104
105 # replace with new request
106 self.queue.put_nowait(new_copy)
107
108 def worker(self):
109 while True:
110 self.recalculate_graph(self.queue.get())
John Park91e69732019-03-03 13:12:43 -0800111
Ravago Jones6d460fe2021-07-03 16:59:55 -0700112 def recalculate_graph(self, points):
113 if not points.getLibsplines(): return
John Park91e69732019-03-03 13:12:43 -0800114
Ravago Jones6d460fe2021-07-03 16:59:55 -0700115 # call C++ wrappers to calculate the trajectory
Ravago Jones0a1d4092022-06-03 12:47:32 -0700116 distance_spline = DistanceSpline(points.getLibsplines())
117 traj = Trajectory(distance_spline)
Ravago Jones6d460fe2021-07-03 16:59:55 -0700118 points.addConstraintsToTrajectory(traj)
119 traj.Plan()
Ravago Jones0a1d4092022-06-03 12:47:32 -0700120 self.data = traj.GetPlanXVA(self.dt)
121 if self.data is None: return
John Park91e69732019-03-03 13:12:43 -0800122
Ravago Jones6d460fe2021-07-03 16:59:55 -0700123 # extract values to be graphed
Ravago Jones0a1d4092022-06-03 12:47:32 -0700124 total_steps_taken = self.data.shape[1]
125 total_time = self.dt * total_steps_taken
126 times = np.linspace(0, total_time, num=total_steps_taken)
127 position, velocity, acceleration = self.data
Ravago Jones6d460fe2021-07-03 16:59:55 -0700128 left_voltage, right_voltage = zip(*(traj.Voltage(x) for x in position))
John Park91e69732019-03-03 13:12:43 -0800129
Ravago Jones6d460fe2021-07-03 16:59:55 -0700130 # update graph
131 self.axis.clear()
Ravago Jones0a1d4092022-06-03 12:47:32 -0700132 self.axis.plot(times, velocity)
133 self.axis.plot(times, acceleration)
134 self.axis.plot(times, left_voltage)
135 self.axis.plot(times, right_voltage)
Ravago Jones6d460fe2021-07-03 16:59:55 -0700136 self.axis.legend(
137 ["Velocity", "Acceleration", "Left Voltage", "Right Voltage"])
138 self.axis.xaxis.set_label_text("Time (sec)")
John Park91e69732019-03-03 13:12:43 -0800139
Ravago Jones6d460fe2021-07-03 16:59:55 -0700140 # renumber the x-axis to include the last point,
141 # the total time to drive the spline
142 self.axis.xaxis.set_ticks(np.linspace(0, total_time, num=8))
Ravago Jones128fb992021-07-31 13:56:58 -0700143
Ravago Jonese9584792022-05-28 16:16:46 -0700144 # redraw
Ravago Jones0a1d4092022-06-03 12:47:32 -0700145 self.canvas.draw_idle()