graph.py 3.8 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394
  1. import time, math
  2. import tkinter as tk
  3. from PIL import Image, ImageDraw, ImageTk, ImageFont
  4. class Graph(tk.Canvas):
  5. def __init__(self, root, scale=(-50, 450), **kwargs):
  6. self.root = root
  7. tk.Canvas.__init__(self, root, **kwargs)
  8. self.image = self.create_image(0, 0, image=None, anchor='nw')
  9. self.height = self.winfo_reqheight()
  10. self.width = self.winfo_reqwidth()
  11. # store last point of plot to connect the lines
  12. self.lastPoints = [(0, 0)] * 3
  13. # scale contains the (min, max) values of both axes
  14. self.scale = scale
  15. # appearance of the plots
  16. self.colors = [(100, 255, 100, 255), (255, 100, 100, 255) ,(100, 100, 255, 255)]
  17. self.font = ImageFont.truetype("gui/SourceSansPro-Semibold.otf", 12)
  18. self.lineWidth = 1
  19. # the background contains all static elements
  20. self.drawBackground()
  21. # the plots will be drawn on a separate canvas
  22. self.canvas = self.bg.copy()
  23. self.bind("<Configure>", self.on_resize)
  24. def drawBackground(self):
  25. self.bg = Image.new('RGB', (self.width, self.height), (0,10,0))
  26. draw = ImageDraw.Draw(self.bg)
  27. # draw x and y axis
  28. axes = self.pointToCoord((0, 0))
  29. draw.line([(0, axes[1]), (self.width, axes[1])], (60,127,127), self.lineWidth)
  30. draw.line([(axes[0], 0), (axes[0], self.height)], (60,127,127), self.lineWidth)
  31. step = 10**int(math.log10(self.scale[1]-self.scale[0]))
  32. begin = math.floor(self.scale[0] / step) * step
  33. end = math.ceil(self.scale[1] / step) * step
  34. halfStep = int(step / 2)
  35. for p in range(begin, end, halfStep):
  36. tickPosX = self.pointToCoord((p, 0))
  37. tickPosY = self.pointToCoord((0, p))
  38. #draw grid
  39. draw.line([(tickPosX[0], self.height), (tickPosX[0], 0)], (60,127,60), 1)
  40. draw.line([(self.width, tickPosY[1]), (0, tickPosY[1])], (60,127,60), 1)
  41. for p in range(begin, end, step):
  42. tickPosX = self.pointToCoord((p, 0))
  43. tickPosY = self.pointToCoord((0, p))
  44. # draw ticks
  45. draw.line([(tickPosX[0], tickPosX[1]+self.lineWidth*3), (tickPosX[0], tickPosX[1]-self.lineWidth*3)], (60,127,100), int(self.lineWidth/2))
  46. draw.line([(tickPosY[0]+self.lineWidth*3, tickPosY[1]), (tickPosY[0]-self.lineWidth*3, tickPosY[1])], (60,127,100), int(self.lineWidth/2))
  47. # draw tick labels
  48. draw.text((tickPosX[0]+3, tickPosX[1]+self.lineWidth*4), str(p) + " mm", font=self.font)
  49. if p != 0:
  50. draw.text((tickPosY[0]-self.lineWidth*4-35, tickPosY[1]), str(p) + " mm", font=self.font)
  51. def on_resize(self,event):
  52. self.width = max(100, event.width-4)
  53. self.height = max(100, event.height-4)
  54. self.lineWidth = int(max(min(self.width,self.height) / 100, 1))
  55. # resize the canvas
  56. self.canvas = self.canvas.resize((self.width, self.height))
  57. self.drawBackground()
  58. self.canvas = Image.blend(self.canvas, self.bg, 1)
  59. # convert physical space to screen space
  60. def pointToCoord(self, point):
  61. return ((point[0] - self.scale[0]) / (self.scale[1]-self.scale[0]) * self.width,
  62. self.height - (point[1] - self.scale[0]) / (self.scale[1]-self.scale[0]) * self.height - 1)
  63. def update(self, data):
  64. # load first point of line
  65. coord = [[self.pointToCoord(p)] for p in self.lastPoints]
  66. # append new points
  67. for i in range(len(data)):
  68. for point in data[i]:
  69. coord[i].append(self.pointToCoord(point))
  70. self.canvas = Image.blend(self.canvas, self.bg, 1/100)
  71. if len(data[0]) > 0:
  72. self.lastPoints = [line[-1] for line in data]
  73. #fade out old lines by mixing with background
  74. draw = ImageDraw.Draw(self.canvas)
  75. for i in range(len(coord)):
  76. draw.line(coord[i], fill=self.colors[i], width=self.lineWidth+2, joint='curve')
  77. # draw to tk.Canvas
  78. self.photo = ImageTk.PhotoImage(self.canvas)
  79. self.itemconfig(self.image, image=self.photo)
  80. def clear(self):
  81. self.canvas = self.bg.copy()