Source code for pygpsclient.graphview_frame

"""
graphview_frame.py

Graphview frame class for PyGPSClient application.

This handles a frame containing a graph of current satellite reception.

Created on 14 Sep 2020

:author: semuadmin
:copyright: 2020 SEMU Consulting
:license: BSD 3-Clause
"""

from tkinter import ALL, BOTH, YES, Canvas, E, Frame

from pygpsclient.globals import (
    AXISCOL,
    BGCOL,
    FGCOL,
    GNSS_LIST,
    GRIDCOL,
    MAX_SNR,
    WIDGETU2,
)
from pygpsclient.helpers import fontheight, scale_font, snr2col

# Relative offsets of graph axes and legend
AXIS_XL = 19
AXIS_XR = 10
AXIS_Y = 22
OL_WID = 2
LEG_XOFF = AXIS_XL + 10
LEG_YOFF = 5
LEG_GAP = 5


[docs] class GraphviewFrame(Frame): """ Graphview frame class. """
[docs] def __init__(self, app, *args, **kwargs): """ Constructor. :param Frame app: reference to main tkinter application :param args: optional args to pass to Frame parent class :param kwargs: optional kwargs to pass to Frame parent class """ self.__app = app # Reference to main application class self.__master = self.__app.appmaster # Reference to root class (Tk) Frame.__init__(self, self.__master, *args, **kwargs) def_w, def_h = WIDGETU2 self.width = kwargs.get("width", def_w) self.height = kwargs.get("height", def_h) self._font = self.__app.font_vsm self._fonth = fontheight(self._font) self._body() self._attach_events()
def _body(self): """ Set up frame and widgets. """ self.grid_columnconfigure(0, weight=1) self.grid_rowconfigure(0, weight=1) self.can_graphview = Canvas( self, width=self.width, height=self.height, bg=BGCOL ) self.can_graphview.pack(fill=BOTH, expand=YES) def _attach_events(self): """ Bind events to frame. """ self.bind("<Configure>", self._on_resize) self.can_graphview.bind("<Double-Button-1>", self._on_legend) def _on_legend(self, event): # pylint: disable=unused-argument """ On double-click - toggle legend on/off. :param event: event """ self.__app.frm_settings.show_legend.set( not self.__app.frm_settings.show_legend.get() )
[docs] def init_frame(self): """ Initialise graph view """ w, h = self.width, self.height ticks = int(MAX_SNR / 10) self.can_graphview.delete(ALL) for i in range(ticks, 0, -1): y = (h - AXIS_Y) * i / ticks self.can_graphview.create_line( AXIS_XL, y, w - AXIS_XR + 2, y, fill=AXISCOL if i == ticks else GRIDCOL ) self.can_graphview.create_text( 10, y, text=str(MAX_SNR - (i * 10)), angle=90, fill=FGCOL, font=self._font, ) self.can_graphview.create_line(AXIS_XL, 5, AXIS_XL, h - AXIS_Y, fill=AXISCOL) self.can_graphview.create_line( w - AXIS_XR + 2, 5, w - AXIS_XR + 2, h - AXIS_Y, fill=AXISCOL ) self.can_graphview.create_text( AXIS_XR, 5, text="CNâ‚€ dB", angle=90, fill=FGCOL, anchor=E, font=self._font, ) if self.__app.frm_settings.config.get("legend_b", 1): self._draw_legend()
def _draw_legend(self): """ Draw GNSS color code legend """ w = self.width / 10 h = self.height / 15 for i, (_, (gnssName, gnssCol)) in enumerate(GNSS_LIST.items()): x = LEG_XOFF + w * i self.can_graphview.create_rectangle( x, LEG_YOFF, x + w - LEG_GAP, LEG_YOFF + h, outline=gnssCol, fill=BGCOL, width=OL_WID, ) self.can_graphview.create_text( (x + x + w - LEG_GAP) / 2, LEG_YOFF + h / 2, text=gnssName, fill=FGCOL, font=self._font, )
[docs] def update_frame(self): """ Plot satellites' signal-to-noise ratio (cno). Automatically adjust y axis according to number of satellites in view. """ data = self.__app.gnss_status.gsv_data siv = len(self.__app.gnss_status.gsv_data) if siv == 0: return w, h = self.width, self.height self.init_frame() offset = AXIS_XL + 2 colwidth = (w - AXIS_XL - AXIS_XR + 1) / siv # scale x axis label according to siv svfont, _ = scale_font(self.width, 6, siv, 14) for d in sorted(data.values()): # sort by ascending gnssid, svid gnssId, prn, _, _, snr = d if snr in ("", "0", 0): snr = 1 # show 'place marker' in graph else: snr = int(snr) snr_y = int(snr) * (h - AXIS_Y - 1) / MAX_SNR (_, ol_col) = GNSS_LIST[gnssId] prn = f"{int(prn):02}" self.can_graphview.create_rectangle( offset, h - AXIS_Y - 1, offset + colwidth - OL_WID, h - AXIS_Y - 1 - snr_y, outline=ol_col, fill=snr2col(snr), width=OL_WID, ) self.can_graphview.create_text( offset + colwidth / 2, h - 10, text=prn, fill=FGCOL, font=svfont, angle=35, ) offset += colwidth self.can_graphview.update_idletasks()
def _on_resize(self, event): # pylint: disable=unused-argument """ Resize frame :param event event: resize event """ self.width, self.height = self.get_size() self._font, self._fonth = scale_font(self.width, 8, 25, 16)
[docs] def get_size(self): """ Get current canvas size. :return: window size (width, height) :rtype: tuple """ self.update_idletasks() # Make sure we know about any resizing return self.can_graphview.winfo_width(), self.can_graphview.winfo_height()