graph_plotter_rewrite.py
6.29 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
#!/usr/bin/env python
# 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
from colours import *
datafeed = serial.Serial(
port='/dev/ttyUSB0',
baudrate = 9600,
parity=serial.PARITY_NONE,
stopbits=serial.STOPBITS_ONE,
bytesize=serial.EIGHTBITS,
timeout=1
)
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"""
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.1, 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]))))
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 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(round(tagvalue, 1))
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.height*self.margins[1-axis]*0.28,
x=pos, y=self.height*self.margins[1-axis],
anchor_x='left', anchor_y='top')
axistitle = pyglet.text.Label(self.series.xname, color=BLACK,
font_name=self.font, font_size=self.height*self.margins[axis]*0.3,
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(round(tagvalue, 1))
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.width*self.margins[axis]*0.22,
x=self.width*self.margins[1-axis]*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.height*self.margins[axis]*0.3,
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
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()