Построение графика на Python с использованием Tkinter Canvas

Я пытаюсь построить график с помощью python, используя виджет холста, в настоящее время я отправляю данные из скетча датчика arduino. Кто-нибудь знает, как я могу построить этот график в режиме реального времени на Python, используя холст. Я читал об этом, но у меня проблемы с распаковкой данных по осям x, y и z и их циклическим обновлением в режиме реального времени. Ниже представлен скетч arduino. А также скелет скетча холста Tkinter. Спасибо!!

AcceleroMMA7361 ski;
int x;
int y;
int z;

void setup(){
    Serial.begin(115200);
    ski.begin(13, 12,11,10, A0, A1, A2);
    ski.setARefVoltage(3.3);             
    ski.setSensitivity(HIGH);
    ski.calibrate();
}

void loop(){
    x = ski.getXRaw();
    y = ski.getYRaw();
    z = ski.getZRaw();
    Serial.print("\nx: ");
    Serial.println(x);
    Serial.print("\ty: ");
    Serial.print(y);
    Serial.print("\tz: ");
    Serial.print(z);
    delay(10);                                     //(сделать читаемым)
}//Конец скетча Arduino



  from Tkinter import *
  import serial #import Serial Library
  import numpy as np # Import numpy

  ardoData = serial.Serial('COM4', 115200) 
  accelX1 = []     #to hold the incoming axis data from the arduino
  accelY1 = []
  accelZ1 = []


  class GraphException(Exception):
      def __init__(self, string):
         Exception.__init__(self, string)

  class Smoking(Canvas):
     def __init__(self, master, FrameTitle, Col, Row, Height):
        Canvas.__init__(self, master)
          self.FrameTitle = FrameTitle
          self.x1Axis = range(1001)
          self.y1Axis = range(1001)
          self.z1Axis = range(1001)
          self.Line1 = range(1001)
          self.Line2 = range(1001)
          self.Line3 = range(1001)
          self.configure(height=Height, width=750, bg='grey', bd=3, relief=GROOVE)
         self.grid()
         self.grid_propagate(0)
         self.Col = Col
         self.Row = Row
         self.place(y=Col, x=Row)
         self.create_text(380, 20, text=self.FrameTitle, fill = 'black')
         for i in range(0, 1001):
            self.Line1[i] = self.create_line(-10+(i*10), 90, 0+(i*10), 90, fill='blue', width=0)
            self.Line2[i] = self.create_line(-10+(i*10), 90, 0+(i*10), 90, fill='red', width=0)
            self.Line3[i] = self.create_line(-10+(i*10), 90, 0+(i*10), 90, fill='yellow', width=0)

     def LiveValues(self,accelX1, accelY1, accelZ1, Centerline = False, Dritter = False):
          for x in range(1, 59):
             for i in np.arange(1, 1001, 1): 
                  while(ardoData.inWaiting() == 0):  
                      pass #do nothing
                  ardoString = ardoData.readline()              
                  dataArray = ardoString.strip().strip('\n')   #Split into an array called dataArray and strip off any spaces
                # Ensure that you are not working on empty line
               if ardoString:
                   dataArray = ardoString.split(",")
               if len(dataArray) > 1:
                   self.x1Axis = int(dataArray[0])
                   self.y1Axis = int(dataArray[1])
                   self.z1Axis = int(dataArray[2])
                   accelX1.append(self.x1Axis)
                   accelY1.append(self.y1Axis)
                   accelZ1.append(self.z1Axis)
              for x in range(0, 1001):
                 self.coords(self.Line1[x], -10+(x*10), self.accelX1[x], (x*10), self.accelX1[x+1])
                 self.coords(self.Line2[x], -10+(x*10), self.accelY1[x], (x*10), self.accelY1[x+1])
                 self.coords(self.Line3[x], -10+(x*10), self.accelZ1[x], (x*10), self.accelZ1[x+1])


   # Create and run the GUI
   root = Tk()
   app = Smoking(root, "Smooth Sailing", 1000, 1000, 1000)
   app.mainloop()

Итак, после работы с ответом, данным в сообщении, я получил линейный график, который не является реактивным, я понимаю, что мне нужно настроить код, чтобы он соответствовал диапазону значений, которые я получаю, как показано ниже. Я увеличил лимит, как указано @patthoyts, но я по-прежнему не мог заставить ни одну из линий отображаться на холсте и даже быть реактивным (я имею в виду, что он должен реагировать на движение датчика). Кроме того, потерпите меня, редакторы!, если я собираюсь неправильно отредактировать этот вопрос, просто я очень отчаянно хочу решить эту проблему. Кто-нибудь знает, как я могу решить эту проблему. Спасибо PS: я добавил оба изображения, изображение моего потока данных и изображение на холсте.

, 👍3

Обсуждение

Вы должны показать хотя бы часть своего кода Python. На самом деле речь идет не об ардуино, а о построении графика из потока из 3-х десятичных чисел, по 1 в строке, выводимых каждые 10 мс. Создайте холст и нанесите на него линию. Прочитайте 3 строки и проанализируйте числа, затем преобразуйте их в координаты холста и обновите свойство coords конфигурации строки., @patthoyts

@patthoyts.....пожалуйста, имейте в виду, что ЭТО предупреждение для НОВИЧКОВ!!!!! У меня есть ограниченное представление о том, как это сделать ... Идея, которую я собирался, была просто внедрена в этот скетч ниже., @dada


1 ответ


Лучший ответ:

1

Чтобы создать полезный пример, я настроил Arduino Nano с магнитным датчиком ориентации и заставил его выводить 3 значения в строке для напряженности поля X, Y и Z. Он печатает 1 строку каждые 50 мс, и следующий скрипт tkinter python отображает это. Он начинался как ваш код Python выше, но был переработан, чтобы стать более питоническим. Результат выглядит следующим образом: скриншот графика tkinter

Вызов скрипта с компортом и скоростью. например: python magplot.py COM21 19200

#!/usr/bin/python
#
# Чтение потока строк с Arduino с помощью магнитного датчика. Этот
# производит 3 значения в строке каждые 50 мс, которые относятся к ориентации
№ датчика. Каждая строка выглядит так:
# МАГ 1,00 -2,00 0,00
# с каждой строкой данных, начинающейся с «MAG», и каждым полем, разделенным
# символы табуляции. Значения представляют собой числа с плавающей запятой в кодировке ASCII.
#
# Этот скрипт поддерживает как Python 2.7, так и Python 3.

from __future__ import print_function, division, absolute_import
import sys
if sys.hexversion > 0x02ffffff:
    import tkinter as tk
else:
    import Tkinter as tk
from serial import Serial

class App(tk.Frame):
    def __init__(self, parent, title, serialPort):
        tk.Frame.__init__(self, parent)
        self.serialPort = serialPort
        self.npoints = 100
        self.Line1 = [0 for x in range(self.npoints)]
        self.Line2 = [0 for x in range(self.npoints)]
        self.Line3 = [0 for x in range(self.npoints)]
        parent.wm_title(title)
        parent.wm_geometry("800x400")
        self.canvas = tk.Canvas(self, background="white")
        self.canvas.bind("<Configure>", self.on_resize)
        self.canvas.create_line((0, 0, 0, 0), tag='X', fill='darkblue', width=1)
        self.canvas.create_line((0, 0, 0, 0), tag='Y', fill='darkred', width=1)
        self.canvas.create_line((0, 0, 0, 0), tag='Z', fill='darkgreen', width=1)
        self.canvas.grid(sticky="news")
        self.grid_rowconfigure(0, weight=1)
        self.grid_columnconfigure(0, weight=1)
        self.grid(sticky="news")
        parent.grid_rowconfigure(0, weight=1)
        parent.grid_columnconfigure(0, weight=1)

    def on_resize(self, event):
        self.replot()

    def read_serial(self):
        """
        Check for input from the serial port. On fetching a line, parse
        the sensor values and append to the stored data and post a replot
        request.
        """
        if self.serialPort.inWaiting() != 0:
            line = self.serialPort.readline()
            line = line.decode('ascii').strip("\r\n")
            if line[0:3] != "MAG":
                print(line) # line not a valid sensor result.
            else:
                try:
                    data = line.split("\t")
                    x, y, z = data[1], data[2], data[3]
                    self.append_values(x, y, z)
                    self.after_idle(self.replot)
                except Exception as e:
                    print(e)
        self.after(10, self.read_serial)

    def append_values(self, x, y, z):
        """
        Update the cached data lists with new sensor values.
        """
        self.Line1.append(float(x))
        self.Line1 = self.Line1[-1 * self.npoints:]
        self.Line2.append(float(y))
        self.Line2 = self.Line2[-1 * self.npoints:]
        self.Line3.append(float(z))
        self.Line3 = self.Line3[-1 * self.npoints:]
        return

    def replot(self):
        """
        Update the canvas graph lines from the cached data lists.
        The lines are scaled to match the canvas size as the window may
        be resized by the user.
        """
        w = self.winfo_width()
        h = self.winfo_height()
        max_X = max(self.Line1) + 1e-5
        max_Y = max(self.Line2) + 1e-5
        max_Z = max(self.Line3) + 1e-5
        max_all = 200.0
        coordsX, coordsY, coordsZ = [], [], []
        for n in range(0, self.npoints):
            x = (w * n) / self.npoints
            coordsX.append(x)
            coordsX.append(h - ((h * (self.Line1[n]+100)) / max_all))
            coordsY.append(x)
            coordsY.append(h - ((h * (self.Line2[n]+100)) / max_all))
            coordsZ.append(x)
            coordsZ.append(h - ((h * (self.Line3[n] + 100)) / max_all))
        self.canvas.coords('X', *coordsX)
        self.canvas.coords('Y', *coordsY)
        self.canvas.coords('Z', *coordsZ)

def main(args = None):
    if args is None:
        args = sys.argv
    port,baudrate = 'COM4', 115200
    if len(args) > 1:
        port = args[1]
    if len(args) > 2:
        baudrate = int(args[2])
    root = tk.Tk()
    app = App(root, "Smooth Sailing", Serial(port, baudrate))
    app.read_serial()
    app.mainloop()
    return 0

if __name__ == '__main__':
    sys.exit(main())

Обновить

Глядя на обновленный вопрос, становится ясно, что новый код Tkinter не обновляет отображение. Во всех оконных системах важно, чтобы поток приложения мог регулярно обрабатывать события. Вот почему приведенный выше пример кода использует метод Tk after() для добавления вызовов в очередь событий. Функция read_serial() проверяет, доступны ли какие-либо данные, и, если нет, планирует повторную попытку позже. Это делается с помощью after(), чтобы запросить повторный вызов того же метода через 10 мс. В течение этой 10-миллисекундной задержки приложение Tk обрабатывает все другие события оконной системы, такие как движение мыши, ввод с клавиатуры, а также важные события экспонирования и рисования.

Тот же метод используется для вызова метода replot() после обновления списка элементов данных. В данном случае используется after_idle(), что означает планирование вызова этой функции, как только ничего не происходит.

Изменение координат линии холста приведет к тому, что Tk вызовет дальнейшие события, которые повлияют на отображение только после того, как они будут обработаны циклом обработки событий. Если мы никогда не обработаем события, на экране ничего не появится.

,

Я предлагаю добавить несколько операторов print() в read_serial, чтобы проверить, правильно ли вы читаете значения и разбиваете их. Затем проверьте масштабирование в «replot». Я сместил на 100 и масштабировал до 200, поэтому значения должны быть -100 < x < 100. Если ваши значения лежат за пределами этого диапазона, они будут за кадром. Вы можете использовать max_all = max([max_X, max_Y, max_Z]) для динамического диапазона., @patthoyts

@pattyhoyts из вашего обновления я понял, что, хотя структура скетча верна, проблема заключается в его компиляции/выполнении. Не могли бы вы посоветовать мне продолжить поиск способов его работы в моей системе (Windows) или отказаться от него для чего-то другого?, @dada