graph_plotter_rewrite.py
7.73 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
#!/usr/bin/env python2
# Tool to draw live graphs of data coming in from serial port
# Written as a telemetry tool by:
# The UoN Robot Wars Project, 2018
# This code is incomplete, and is missing core features
import pyglet
#import math
import time
import serial
import numpy
import os
import platform
import sys
import easygui
import logging
from serialselect import selectserial
from colours import *
logging.basicConfig(level=logging.DEBUG)
logging.info("Logging system active")
datafeed = selectserial()
if type(datafeed) is str:
message = "Failed to open serial port:\n" + datafeed
easygui.msgbox(message, "Fatal error")
logging.critical(message)
sys.exit()
elif datafeed == None:
message = "Failed to open serial port for unknown reason!"
easygui.msgbox(message, "Fatal error")
logging.critical(message)
sys.exit()
if platform.system()=='Windows': #easier than passing vars
os='Windows'
else:
os='Other'
class Series:
def __init__(self, points=100, title="Series title", xname="x-axis name", yname="y-axis name"):
"""Set up an object to store a 2D data series"""
# Proposal:
# In order to neatly handle multiple lines on the same graph
# a series is not so much a set of 2D data points, but a number
# of series sharing an x axis. For example
# (time, temperature)
# (time, xaccel, yaccel, zaccel)
# The latter seta of points much be of like nature and share an axis
# (time, temperature, xaccel)
# would be a meaningless thing to plot on a single graph anyway
self.title = title
self.xname = xname
self.yname = yname
self.xlimits = (100, 0)
self.ylimits = (0, 255)
self.data = []
self.points = points
def addpoint(self, point):
"""Add a point to the dataset, and remove the oldest, if necessary"""
self.data.append(point)
if len(self.data) > self.points:
del self.data[0]
class Plot(pyglet.window.Window):
def __init__(self, series):
"""Setup a the details of a plot, and create a corresponding window"""
pyglet.window.Window.__init__(self, resizable=True)
self.series = series
self.font = 'Arkhip'
self.margins = (0.09, 0.08) # Fractions of window size
self.lines = (10, 8)
#self.resizable = True
self.set_caption(self.series.title)
def on_resize(self, width, height):
"""Handle a resize event from the pyglet event loop"""
self.bounds = ((int(self.width * self.margins[0]), int(self.width * (1 - self.margins[0]))),
(int(self.height * self.margins[1]), int(self.height * (1 - self.margins[1]))))
self.tag_size = min(self.height*self.margins[1]*0.3,self.width*self.margins[0]*0.3)
# This sometimes seems to throw an error ('AttributeError: 'Plot' object has no attribute 'margins') when started for a second time from the same instance. Interesting. Causes the plot windows to freeze
pyglet.window.Window.on_resize(self, width, height)
def on_draw(self):
"""Draw all the components of the graph"""
self.drawBackground()
self.drawHeading()
self.drawAxis(0)
self.drawAxis(1)
def drawBackground(self):
"""Draw the graph background, currently a plain colour"""
pyglet.image.SolidColorImagePattern(WHITE).create_image(self.width, self.height).blit(0, 0)
def drawHeading(self):
"""Draw a title for the graph (duplicated in the window titlebar, if present"""
heading = pyglet.text.Label(self.series.title, color=BLACK,
font_name=self.font, font_size=self.height*self.margins[0]*0.5,
x=self.width/2, y=self.height-(self.margins[1]),
anchor_x='center', anchor_y='top')
heading.draw()
def drawLine(self):
for n in range(len(data) - 1):
a, b = data[n], data[n+1]
pass
def drawAxis(self, axis): # axis=0 is x, 1 is y
"""Draw the gridlines and labels for one axis, specified in the last argument"""
limita = self.bounds[1-axis][1]
limitb = self.bounds[1-axis][0]
start = self.bounds[axis][0]
stop = self.bounds[axis][1]
increment = float(stop-start)/self.lines[axis]
for pos in numpy.arange(start, stop+1, increment):
# Using fp arithmetic to avoid intermittent fencepost errors
pos = int(pos)
if axis==0: # x axis, vertical lines
scale = float(self.series.xlimits[1]-self.series.xlimits[0])/(stop-start)
tagvalue = ((pos-start) * scale) + self.series.xlimits[0]
tagtext = str(int(tagvalue))
pyglet.graphics.draw(2, pyglet.gl.GL_LINES, ('v2i', (pos, limita, pos, limitb)),
('c3B', (0, 0, 0, 0, 0, 0)))
tag = pyglet.text.Label(tagtext, color=BLACK,
font_name=self.font, font_size=self.tag_size,
x=pos, y=self.height*self.margins[1],
anchor_x='left', anchor_y='top')
axistitle = pyglet.text.Label(self.series.xname, color=BLACK,
font_name=self.font, font_size=self.tag_size,
x=self.width/2, y=0,
anchor_x='center', anchor_y='bottom')
axistitle.draw()
if axis==1: # y axis, horizontal lines
scale = float(self.series.ylimits[1]-self.series.ylimits[0])/(stop-start)
tagvalue = ((pos-start) * scale) + self.series.ylimits[0]
tagtext = str(int(tagvalue))
pyglet.graphics.draw(2, pyglet.gl.GL_LINES, ('v2i', (limita, pos, limitb, pos)),
('c3B', (0, 0, 0, 0, 0, 0)))
tag = pyglet.text.Label(tagtext, color=BLACK,
font_name=self.font, font_size=self.tag_size,
x=self.width*self.margins[0]*0.9, y=pos,
anchor_x='right', anchor_y='center')
axistitle = pyglet.text.Label(self.series.yname, color=BLACK,
font_name=self.font, font_size=self.tag_size,
x=0, y=self.height/2,
anchor_x='center', anchor_y='top')
pyglet.gl.glPushMatrix() # Set up a new context to avoid confusing the main one
# Tranformation to rotate label, and ensure it ends up in the right place
pyglet.gl.glTranslatef(self.height//2, self.height//2, 0.0)
pyglet.gl.glRotatef(90.0, 0.0, 0.0, 1.0)
# Draw the axis title using the rotated coordinate system
axistitle.draw()
# Return everything to its previous state
pyglet.gl.glPopMatrix()
tag.draw()
testseries = Series()
plots = []
plots.append(Plot(testseries))
def pollSerial(elapsed):
"""Check serial port for incoming data"""
# Note: 'elapsed' is time since last call of this function
# This works, but it might be better for performance to have two seperate functions, only one of which is run.
if os=='Windows':
values = datafeed.readline().strip().split(b", ")
else:
values = datafeed.readline().strip().split(", ")
testseries.addpoint(values)
# Pyglet looks after the main event loop, but this ensures that data keeps being read in
pyglet.clock.schedule_interval(pollSerial, 0.1)
pyglet.app.run()