import time, math import tkinter as tk from PIL import Image, ImageDraw, ImageTk, ImageFont class Graph(tk.Canvas): def __init__(self, root, scale=(-50, 450), **kwargs): self.root = root tk.Canvas.__init__(self, root, **kwargs) self.image = self.create_image(0, 0, image=None, anchor='nw') self.height = self.winfo_reqheight() self.width = self.winfo_reqwidth() # store last point of plot to connect the lines self.lastPoints = [(0, 0)] * 3 # scale contains the (min, max) values of both axes self.scale = scale # appearance of the plots self.colors = [(100, 255, 100, 255), (255, 100, 100, 255) ,(100, 100, 255, 255)] self.font = ImageFont.truetype("gui/SourceSansPro-Semibold.otf", 12) self.lineWidth = 1 # the background contains all static elements self.drawBackground() # the plots will be drawn on a separate canvas self.canvas = self.bg.copy() self.bind("", self.on_resize) def drawBackground(self): self.bg = Image.new('RGB', (self.width, self.height), (0,10,0)) draw = ImageDraw.Draw(self.bg) # draw x and y axis axes = self.pointToCoord((0, 0)) draw.line([(0, axes[1]), (self.width, axes[1])], (60,127,127), self.lineWidth) draw.line([(axes[0], 0), (axes[0], self.height)], (60,127,127), self.lineWidth) step = 10**int(math.log10(self.scale[1]-self.scale[0])) begin = math.floor(self.scale[0] / step) * step end = math.ceil(self.scale[1] / step) * step halfStep = int(step / 2) for p in range(begin, end, halfStep): tickPosX = self.pointToCoord((p, 0)) tickPosY = self.pointToCoord((0, p)) #draw grid draw.line([(tickPosX[0], self.height), (tickPosX[0], 0)], (60,127,60), 1) draw.line([(self.width, tickPosY[1]), (0, tickPosY[1])], (60,127,60), 1) for p in range(begin, end, step): tickPosX = self.pointToCoord((p, 0)) tickPosY = self.pointToCoord((0, p)) # draw ticks draw.line([(tickPosX[0], tickPosX[1]+self.lineWidth*3), (tickPosX[0], tickPosX[1]-self.lineWidth*3)], (60,127,100), int(self.lineWidth/2)) draw.line([(tickPosY[0]+self.lineWidth*3, tickPosY[1]), (tickPosY[0]-self.lineWidth*3, tickPosY[1])], (60,127,100), int(self.lineWidth/2)) # draw tick labels draw.text((tickPosX[0]+3, tickPosX[1]+self.lineWidth*4), str(p) + " mm", font=self.font) if p != 0: draw.text((tickPosY[0]-self.lineWidth*4-35, tickPosY[1]), str(p) + " mm", font=self.font) def on_resize(self,event): self.width = max(100, event.width-4) self.height = max(100, event.height-4) self.lineWidth = int(max(min(self.width,self.height) / 100, 1)) # resize the canvas self.canvas = self.canvas.resize((self.width, self.height)) self.drawBackground() self.canvas = Image.blend(self.canvas, self.bg, 1) # convert physical space to screen space def pointToCoord(self, point): return ((point[0] - self.scale[0]) / (self.scale[1]-self.scale[0]) * self.width, self.height - (point[1] - self.scale[0]) / (self.scale[1]-self.scale[0]) * self.height - 1) def update(self, data): # load first point of line coord = [[self.pointToCoord(p)] for p in self.lastPoints] # append new points for i in range(len(data)): for point in data[i]: coord[i].append(self.pointToCoord(point)) self.canvas = Image.blend(self.canvas, self.bg, 1/100) if len(data[0]) > 0: self.lastPoints = [line[-1] for line in data] #fade out old lines by mixing with background draw = ImageDraw.Draw(self.canvas) for i in range(len(coord)): draw.line(coord[i], fill=self.colors[i], width=self.lineWidth+2, joint='curve') # draw to tk.Canvas self.photo = ImageTk.PhotoImage(self.canvas) self.itemconfig(self.image, image=self.photo) def clear(self): self.canvas = self.bg.copy()