Source code for ncvue.ncvscatter

#!/usr/bin/env python
"""
Scatter/Line panel of ncvue.

The panel allows plotting variables against time or two variables against
each other. A second variable can be plotted in the same graph using the
right-hand-side y-axis.

This module was written by Matthias Cuntz while at Institut National de
Recherche pour l'Agriculture, l'Alimentation et l'Environnement (INRAE), Nancy,
France.

:copyright: Copyright 2020-2021 Matthias Cuntz - mc (at) macu (dot) de
:license: MIT License, see LICENSE for details.

.. moduleauthor:: Matthias Cuntz

The following classes are provided:

.. autosummary::
   ncvScatter

History
   * Written Nov-Dec 2020 by Matthias Cuntz (mc (at) macu (dot) de)
   * Open new netcdf file, communicate via top widget,
     Jan 2021, Matthias Cuntz
   * Write left-hand side and right-hand side values on bottom of plotting
     canvas, May 2021, Matthias Cuntz
   * Address fi.variables[name] directly by fi[name], Jan 2024, Matthias Cuntz
   * Allow groups in netcdf files, Jan 2024, Matthias Cuntz
   * Allow multiple netcdf files, Jan 2024, Matthias Cuntz
   * Move themes/ and images/ back to src/ncvue/, Feb 2024, Matthias Cuntz
   * Add Quit button, Nov 2024, Matthias Cuntz
   * Use CustomTkinter if installed, Nov 2024, Matthias Cuntz

"""
import tkinter as tk
try:
    from customtkinter import CTkFrame as Frame
    from customtkinter import CTkButton as Button
    from customtkinter import CTkLabel as Label
    from customtkinter import CTkComboBox as Combobox
    ihavectk = True
except ModuleNotFoundError:
    from tkinter.ttk import Frame
    from tkinter.ttk import Button
    from tkinter.ttk import Label
    from tkinter.ttk import Combobox
    ihavectk = False
import numpy as np
import netCDF4 as nc
from .ncvutils import clone_ncvmain, format_coord_scatter, selvar
from .ncvutils import set_axis_label, vardim2var
from .ncvmethods import analyse_netcdf, get_slice_miss
from .ncvmethods import set_dim_x, set_dim_y, set_dim_y2
from .ncvwidgets import add_checkbutton, add_combobox, add_entry
from .ncvwidgets import add_spinbox, add_tooltip
# import matplotlib
# matplotlib.use('TkAgg')
from matplotlib import pyplot as plt
try:
    # plt.style.use('seaborn-v0_8-darkgrid')
    plt.style.use('seaborn-v0_8-dark')
except OSError:
    # plt.style.use('seaborn-darkgrid')
    plt.style.use('seaborn-dark')
# plt.style.use('fast')


__all__ = ['ncvScatter']


[docs] class ncvScatter(Frame): """ Panel for scatter and line plots. Sets up the layout with the figure canvas, variable selectors, dimension spinboxes, and options in __init__. Contains various commands that manage what will be drawn or redrawn if something is selected, changed, checked, etc. Contains three drawing routines. `redraw_y` and `redraw_y2` redraw the y-axes without changing zoom level, etc. `redraw` is called if a new x-variable was selected or the `Redraw`-button was pressed. It resets all axes, resetting zoom, etc. """ # # Setup panel # def __init__(self, master, **kwargs): from functools import partial from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg from matplotlib.backends.backend_tkagg import NavigationToolbar2Tk from matplotlib.figure import Figure super().__init__(master, **kwargs) self.name = 'Scatter/Line' self.master = master self.top = master.top # copy for ease of use self.fi = self.top.fi self.groups = self.top.groups self.miss = self.top.miss self.dunlim = self.top.dunlim self.time = self.top.time self.tname = self.top.tname self.tvar = self.top.tvar self.dtime = self.top.dtime self.latvar = self.top.latvar self.lonvar = self.top.lonvar self.latdim = self.top.latdim self.londim = self.top.londim self.maxdim = self.top.maxdim self.cols = self.top.cols # selections and options columns = [''] + self.cols # colors c = list(plt.rcParams['axes.prop_cycle']) col1 = c[0]['color'] # blue col2 = c[3]['color'] # red # color tooltip ctstr = ('- color names: red, green, blue, yellow, ...\n' '- single characters: b (blue), g (green), r (red), c (cyan),' ' m (magenta), y (yellow), k (black), w (white)\n' '- hex RGB: #rrggbb such such as #ff9300 (orange)\n' '- gray level: float between 0 and 1\n' '- RGA (red, green, blue) or RGBA (red, green, blue, alpha)' ' tuples between 0 and 1, e.g. (1, 0.57, 0) for orange\n' '- name from xkcd color survey, e.g. xkcd:sky blue') # marker tooltip mtstr = ('. (point), "," (pixel), o (circle),\n' 'v (triangle_down), ^ (triangle_up),\n' '< (triangle_left), > (triangle_right),\n' '1 (tri_down), 2 (tri_up), 3 (tri_left), 4 (tri_right),' ' 8 (octagon),\n' 's (square), p (pentagon), P (plus (filled)),\n' '* (star), h (hexagon1), H (hexagon2),\n' '+ (plus), x (x), X (x (filled)),\n' 'D (diamond), d (thin_diamond),\n' '| (vline), _ (hline), or None') if ihavectk: # width of combo boxes in px combowidth = 288 # widths of entry widgets in px ewsmall = 20 ewmed = 45 ewbig = 70 # pad between label and entry padx = 5 # width of animation and variables buttons bwidth = 35 # width of projections menu mwidth = 70 else: # width of combo boxes in characters combowidth = 28 # widths of entry widgets in characters ewsmall = 3 ewmed = 4 ewbig = 7 # pad between label and entry (not used) padx = 5 # width of animation and variables buttons bwidth = 1 # width of projections menu mwidth = 13 # new window self.rowwin = Frame(self) self.rowwin.pack(side=tk.TOP, fill=tk.X) self.newfile = Button(self.rowwin, text='Open File', command=self.newnetcdf) self.newfiletip = add_tooltip(self.newfile, 'Open a new netcdf file') self.newfile.pack(side=tk.LEFT) self.newwin = Button( self.rowwin, text='New Window', command=partial(clone_ncvmain, self.master)) self.newwintip = add_tooltip( self.newwin, 'Open secondary ncvue window') self.newwin.pack(side=tk.RIGHT) # plotting canvas self.figure = Figure(facecolor='white', figsize=(1, 1)) self.axes = self.figure.add_subplot(111) self.axes2 = self.axes.twinx() self.axes2.yaxis.set_label_position('right') self.axes2.yaxis.tick_right() self.canvas = FigureCanvasTkAgg(self.figure, master=self) self.canvas.draw() self.tkcanvas = self.canvas.get_tk_widget() self.tkcanvas.pack(side=tk.TOP, fill=tk.BOTH, expand=1) # matplotlib toolbar # toolbar uses pack internally -> put into frame self.toolbar = NavigationToolbar2Tk(self.canvas, self, pack_toolbar=True) self.toolbar.update() self.toolbar.pack(side=tk.TOP, fill=tk.X) # 1. row : x- and lhs y-axis selection self.rowxy = Frame(self) self.rowxy.pack(side=tk.TOP, fill=tk.X) # block with x and its dimensions self.blockx = Frame(self.rowxy) self.blockx.pack(side=tk.LEFT) # x self.rowx = Frame(self.blockx) self.rowx.pack(side=tk.TOP, fill=tk.X) self.xframe, self.xlbl, self.x, self.xtip = add_combobox( self.rowx, label='x', values=columns, command=self.selected_x, width=combowidth, padx=padx, tooltip=('Choose variable of x-axis.\nTakes index if "None",' ' which is much faster.')) self.xframe.pack(side=tk.LEFT) # invert x self.inv_xframe, self.inv_xlbl, self.inv_x, self.inv_xtip = ( add_checkbutton(self.rowx, label='invert x', value=False, command=self.checked_x, tooltip='Invert x-axis')) self.inv_xframe.pack(side=tk.LEFT) # x dimensions self.rowxd = Frame(self.blockx) self.rowxd.pack(side=tk.TOP, fill=tk.X) self.xdframe = [] self.xdlblval = [] self.xdlbl = [] self.xdval = [] self.xd = [] self.xdtip = [] for i in range(self.maxdim): xdframe, xdlblval, xdlbl, xdval, xd, xdtip = add_spinbox( self.rowxd, label=str(i), values=(0,), wrap=True, command=self.spinned_x, state=tk.DISABLED, tooltip='None') self.xdframe.append(xdframe) self.xdlblval.append(xdlblval) self.xdlbl.append(xdlbl) self.xdval.append(xdval) self.xd.append(xd) self.xdtip.append(xdtip) xdframe.pack(side=tk.LEFT) # space between x and y blocks spacex_label = tk.StringVar() spacex_label.set(' ') spacex = Label(self.rowxy, textvariable=spacex_label) spacex.pack(side=tk.LEFT) # block with y and its dimensions self.blocky = Frame(self.rowxy) self.blocky.pack(side=tk.LEFT) # y self.rowy = Frame(self.blocky) self.rowy.pack(side=tk.TOP, fill=tk.X) self.ylbl = tk.StringVar() self.ylbl.set('y') lkwargs = {'textvariable': self.ylbl} if ihavectk: lkwargs.update({'padx': padx}) ylab = Label(self.rowy, **lkwargs) ylab.pack(side=tk.LEFT) self.bprev_y = Button(self.rowy, text='<', width=bwidth, command=self.prev_y) self.bprev_y.pack(side=tk.LEFT) self.bprev_ytip = add_tooltip(self.bprev_y, 'Previous variable') self.bnext_y = Button(self.rowy, text='>', width=bwidth, command=self.next_y) self.bnext_y.pack(side=tk.LEFT) self.bnext_ytip = add_tooltip(self.bnext_y, 'Next variable') if ihavectk: self.y = Combobox(self.rowy, values=columns, width=combowidth, command=self.selected_y) else: self.y = Combobox(self.rowy, values=columns, width=combowidth) # long = len(max(columns, key=len)) # self.y.configure(width=(max(20, long//2))) self.y.bind('<<ComboboxSelected>>', self.selected_y) self.y.pack(side=tk.LEFT) self.ytip = add_tooltip(self.y, 'Choose variable of y-axis') self.line_y = [] self.inv_yframe, self.inv_ylbl, self.inv_y, self.inv_ytip = ( add_checkbutton(self.rowy, label='invert y', value=False, command=self.checked_y, tooltip='Inert y-axis')) self.inv_yframe.pack(side=tk.LEFT) # y dimensions self.rowyd = Frame(self.blocky) self.rowyd.pack(side=tk.TOP, fill=tk.X) self.ydframe = [] self.ydlblval = [] self.ydlbl = [] self.ydval = [] self.yd = [] self.ydtip = [] for i in range(self.maxdim): ydframe, ydlblval, ydlbl, ydval, yd, ydtip = add_spinbox( self.rowyd, label=str(i), values=(0,), wrap=True, command=self.spinned_y, state=tk.DISABLED, tooltip='None') self.ydframe.append(ydframe) self.ydlblval.append(ydlblval) self.ydlbl.append(ydlbl) self.ydval.append(ydval) self.yd.append(yd) self.ydtip.append(ydtip) ydframe.pack(side=tk.LEFT) # redraw button self.bredraw = Button(self.rowxy, text='Redraw', command=self.redraw) self.bredraw.pack(side=tk.RIGHT) self.bredrawtip = add_tooltip(self.bredraw, 'Redraw, resetting zoom') # 2. row # options for lhs y-axis self.rowxyopt = Frame(self) self.rowxyopt.pack(side=tk.TOP, fill=tk.X) self.lsframe, self.lslbl, self.ls, self.lstip = add_entry( self.rowxyopt, label='ls', text='-', width=ewmed, command=self.entered_y, padx=padx, tooltip='Line style: -, --, -., :, or None') self.lsframe.pack(side=tk.LEFT) self.lwframe, self.lwlbl, self.lw, self.lwtip = add_entry( self.rowxyopt, label='lw', text='1', width=ewsmall, command=self.entered_y, tooltip='Line width', padx=padx) self.lwframe.pack(side=tk.LEFT) self.lcframe, self.lclbl, self.lc, self.lctip = add_entry( self.rowxyopt, label='c', text=col1, width=ewbig, command=self.entered_y, padx=padx, tooltip='Line color:\n' + ctstr) self.lcframe.pack(side=tk.LEFT) self.markerframe, self.markerlbl, self.marker, self.markertip = ( add_entry(self.rowxyopt, label='marker', text='None', width=ewmed, command=self.entered_y, padx=padx, tooltip='Marker symbol:\n' + mtstr)) self.markerframe.pack(side=tk.LEFT) self.msframe, self.mslbl, self.ms, self.mstip = add_entry( self.rowxyopt, label='ms', text='1', width=ewsmall, command=self.entered_y, tooltip='Marker size', padx=padx) self.msframe.pack(side=tk.LEFT) self.mfcframe, self.mfclbl, self.mfc, self.mfctip = add_entry( self.rowxyopt, label='mfc', text=col1, width=ewbig, command=self.entered_y, padx=padx, tooltip='Marker fill color:\n' + ctstr) self.mfcframe.pack(side=tk.LEFT) self.mecframe, self.meclbl, self.mec, self.mectip = add_entry( self.rowxyopt, label='mec', text=col1, width=ewbig, command=self.entered_y, padx=padx, tooltip='Marker edge color:\n' + ctstr) self.mecframe.pack(side=tk.LEFT) self.mewframe, self.mewlbl, self.mew, self.mewtip = add_entry( self.rowxyopt, label='mew', text='1', width=ewsmall, padx=padx, command=self.entered_y, tooltip='Marker edge width') self.mewframe.pack(side=tk.LEFT) # space self.rowspace = Frame(self) self.rowspace.pack(side=tk.TOP, fill=tk.X) rowspace_label = tk.StringVar() rowspace_label.set(' ') rowspace = Label(self.rowspace, textvariable=spacex_label) rowspace.pack(side=tk.LEFT) # 3. row # rhs y-axis 2 selection self.rowyy2 = Frame(self) self.rowyy2.pack(side=tk.TOP, fill=tk.X) self.blocky2 = Frame(self.rowyy2) self.blocky2.pack(side=tk.LEFT) self.rowy2 = Frame(self.blocky2) self.rowy2.pack(side=tk.TOP, fill=tk.X) self.y2frame, self.y2lbl, self.y2, self.y2tip = add_combobox( self.rowy2, label='y2', values=columns, command=self.selected_y2, width=combowidth, padx=padx, tooltip='Choose variable for right-hand-side y-axis') self.y2frame.pack(side=tk.LEFT) self.line_y2 = [] self.inv_y2frame, self.inv_y2lbl, self.inv_y2, self.inv_y2tip = ( add_checkbutton(self.rowy2, label='invert y2', value=False, command=self.checked_y2, tooltip='Invert right-hand-side y-axis')) self.inv_y2frame.pack(side=tk.LEFT) spacey2_label = tk.StringVar() spacey2_label.set(' ') spacey2 = Label(self.rowy2, textvariable=spacey2_label) spacey2.pack(side=tk.LEFT) tstr = 'Same limits for left-hand-side and right-hand-side y-axes' self.same_yframe, self.same_ylbl, self.same_y, self.same_ytip = ( add_checkbutton(self.rowy2, label='same y-axes', value=False, command=self.checked_yy2, tooltip=tstr)) self.same_yframe.pack(side=tk.LEFT) self.rowy2d = Frame(self.blocky2) self.rowy2d.pack(side=tk.TOP, fill=tk.X) self.y2dframe = [] self.y2dlblval = [] self.y2dlbl = [] self.y2dval = [] self.y2d = [] self.y2dtip = [] for i in range(self.maxdim): y2dframe, y2dlblval, y2dlbl, y2dval, y2d, y2dtip = add_spinbox( self.rowy2d, label=str(i), values=(0,), wrap=True, command=self.spinned_y2, state=tk.DISABLED, tooltip='None') self.y2dframe.append(y2dframe) self.y2dlblval.append(y2dlblval) self.y2dlbl.append(y2dlbl) self.y2dval.append(y2dval) self.y2d.append(y2d) self.y2dtip.append(y2dtip) y2dframe.pack(side=tk.LEFT) # 4. row # options for rhs y-axis 2 self.rowy2opt = Frame(self) self.rowy2opt.pack(side=tk.TOP, fill=tk.X) self.ls2frame, self.ls2lbl, self.ls2, self.ls2tip = add_entry( self.rowy2opt, label='ls', text='-', width=ewmed, command=self.entered_y2, padx=padx, tooltip='Line style: -, --, -., :, or None') self.ls2frame.pack(side=tk.LEFT) self.lw2frame, self.lw2lbl, self.lw2, self.lw2tip = add_entry( self.rowy2opt, label='lw', text='1', width=ewsmall, padx=padx, command=self.entered_y2, tooltip='Line width') self.lw2frame.pack(side=tk.LEFT) self.lc2frame, self.lc2lbl, self.lc2, self.lc2tip = add_entry( self.rowy2opt, label='c', text=col2, width=ewbig, command=self.entered_y2, padx=padx, tooltip='Line color:\n' + ctstr) self.lc2frame.pack(side=tk.LEFT) self.marker2frame, self.marker2lbl, self.marker2, self.marker2tip = ( add_entry(self.rowy2opt, label='marker', text='None', width=ewmed, command=self.entered_y2, padx=padx, tooltip='Marker symbol:\n' + mtstr)) self.markerframe.pack(side=tk.LEFT) self.ms2frame, self.ms2lbl, self.ms2, self.ms2tip = add_entry( self.rowy2opt, label='ms', text='1', width=ewsmall, padx=padx, command=self.entered_y2, tooltip='Marker size') self.ms2frame.pack(side=tk.LEFT) self.mfc2frame, self.mfc2lbl, self.mfc2, self.mfc2tip = add_entry( self.rowy2opt, label='mfc', text=col2, width=ewbig, padx=padx, command=self.entered_y2, tooltip='Marker fill color:\n' + ctstr) self.mfc2frame.pack(side=tk.LEFT) self.mec2frame, self.mec2lbl, self.mec2, self.mec2tip = add_entry( self.rowy2opt, label='mec', text=col2, width=ewbig, padx=padx, command=self.entered_y2, tooltip='Marker edge color:\n' + ctstr) self.mec2frame.pack(side=tk.LEFT) self.mew2frame, self.mew2lbl, self.mew2, self.mew2tip = add_entry( self.rowy2opt, label='mew', text='1', width=ewsmall, padx=padx, command=self.entered_y2, tooltip='Marker edge width') self.mew2frame.pack(side=tk.LEFT) # Quit button self.bquit = Button(self.rowy2opt, text='Quit', command=self.master.top.destroy) self.bquit.pack(side=tk.RIGHT) self.bquittip = add_tooltip(self.bquit, 'Quit ncvue') # # Event bindings #
[docs] def checked_x(self): """ Command called if any checkbutton for x-axis was checked or unchecked. Redraws left-hand-side and right-hand-side y-axes. """ self.redraw_y() self.redraw_y2()
[docs] def checked_y(self): """ Command called if any checkbutton for left-hand-side y-axis was checked or unchecked. Redraws left-hand-side y-axis. """ self.redraw_y()
[docs] def checked_y2(self): """ Command called if any checkbutton for right-hand-side y-axis was checked or unchecked. Redraws right-hand-side y-axis. """ self.redraw_y2()
[docs] def checked_yy2(self): """ Command called if any checkbutton was checked or unchecked that concerns both, the left-hand-side and right-hand-side y-axes. Redraws left-hand-side and right-hand-side y-axes. """ self.redraw_y() self.redraw_y2()
[docs] def entered_y(self, event): """ Command called if option was entered for left-hand-side y-axis. Redraws left-hand-side y-axis. """ self.redraw_y()
[docs] def entered_y2(self, event): """ Command called if option was entered for right-hand-side y-axis. Redraws right-hand-side y-axis. """ self.redraw_y2()
[docs] def next_y(self): """ Command called if next button for the left-hand-side y-variable was pressed. Resets dimensions of left-hand-side y-variable. Redraws plot. """ y = self.y.get() if ihavectk: cols = self.y.cget('values') else: cols = self.y['values'] idx = cols.index(y) idx += 1 if idx < len(cols): self.y.set(cols[idx]) set_dim_y(self) self.redraw()
# def onpick(self, event): # print('in pick') # print('you pressed', event.button, event.xdata, event.ydata) # thisline = event.artist # xdata = thisline.get_xdata() # ydata = thisline.get_ydata() # ind = event.ind # points = tuple(zip(xdata[ind], ydata[ind])) # print('onpick points:', points)
[docs] def prev_y(self): """ Command called if previous button for the left-hand-side y-variable was pressed. Resets dimensions of left-hand-side y-variable. Redraws plot. """ y = self.y.get() if ihavectk: cols = self.y.cget('values') else: cols = self.y['values'] idx = cols.index(y) idx -= 1 if idx > 0: self.y.set(cols[idx]) set_dim_y(self) self.redraw()
[docs] def newnetcdf(self): """ Open a new netcdf file and connect it to top. """ # get new netcdf file name ncfile = tk.filedialog.askopenfilename( parent=self, title='Choose netcdf file', multiple=True) if len(ncfile) > 0: # close old netcdf file if len(self.top.fi) > 0: for fi in self.top.fi: fi.close() # reset empty defaults of top self.top.fi = [] # file name or file handle self.top.groups = [] # filename with incr. index or group names self.top.dunlim = [] # name of unlimited dimension self.top.time = [] # datetime variable self.top.tname = [] # datetime variable name self.top.tvar = [] # datetime variable name in netcdf self.top.dtime = [] # decimal year self.top.latvar = [] # name of latitude variable self.top.lonvar = [] # name of longitude variable self.top.latdim = [] # name of latitude dimension self.top.londim = [] # name of longitude dimension self.top.maxdim = 0 # maximum num of dims of all variables self.top.cols = [] # variable list # open new netcdf file for ii, nn in enumerate(ncfile): self.top.fi.append(nc.Dataset(nn, 'r')) if len(ncfile) > 1: self.top.groups.append(f'file{ii:03d}') # Check groups ianalyse = True if len(ncfile) == 1: self.top.groups = list(self.top.fi[0].groups.keys()) else: for ii, nn in enumerate(ncfile): if len(list(self.top.fi[ii].groups.keys())) > 0: print(f'Either multiple files or one file with groups' f' allowed as input. Multiple files and file' f' {nn} has groups.') for fi in self.top.fi: fi.close() ianalyse = False if ianalyse: analyse_netcdf(self.top) # reset panel self.reinit() self.redraw()
[docs] def selected_x(self, event): """ Command called if x-variable was selected with combobox. Triggering `event` was bound to the combobox. Resets `x` dimensions. Redraws plot. """ set_dim_x(self) self.redraw()
[docs] def selected_y(self, event): """ Command called if left-hand-side y-variable was selected with combobox. Triggering `event` was bound to the combobox. Resets left-hand-side `y` dimensions. Redraws plot. """ set_dim_y(self) self.redraw()
[docs] def selected_y2(self, event): """ Command called if right-hand-side y-variable was selected with combobox. Triggering `event` was bound to the combobox. Resets right-hand-side `y` dimensions. Redraws plot. """ set_dim_y2(self) self.redraw()
[docs] def spinned_x(self, event=None): """ Command called if spinbox of x-dimensions was changed. Triggering `event` was bound to the spinbox. Redraws plot. """ self.redraw()
[docs] def spinned_y(self, event=None): """ Command called if spinbox of any dimension of left-hand-side y-variable was changed. Triggering `event` was bound to the spinbox. Redraws plot. """ self.redraw()
[docs] def spinned_y2(self, event=None): """ Command called if spinbox of any dimension of right-hand-side y-variable was changed. Triggering `event` was bound to the spinbox. Redraws plot. """ self.redraw()
# # Methods #
[docs] def minmax_ylim(self, ylim, ylim2): """ Get minimum of first elements of lists `ylim` and `ylim2` and maximum of second element of the two lists. Returns minimum, maximum. """ if (ylim[0] is not None) and (ylim2[0] is not None): ymin = min(ylim[0], ylim2[0]) else: if (ylim[0] is not None): ymin = ylim[0] else: ymin = ylim2[0] if (ylim[1] is not None) and (ylim2[1] is not None): ymax = max(ylim[1], ylim2[1]) else: if (ylim[1] is not None): ymax = ylim[1] else: ymax = ylim2[1] return ymin, ymax
[docs] def reinit(self): """ Reinitialise the panel from top. """ # reinit from top self.fi = self.top.fi self.groups = self.top.groups self.miss = self.top.miss self.dunlim = self.top.dunlim self.time = self.top.time self.tname = self.top.tname self.tvar = self.top.tvar self.dtime = self.top.dtime self.latvar = self.top.latvar self.lonvar = self.top.lonvar self.latdim = self.top.latdim self.londim = self.top.londim self.maxdim = self.top.maxdim self.cols = self.top.cols # reset dimensions for ll in self.xdlbl: ll.destroy() for ll in self.xd: ll.destroy() self.xdframe = [] self.xdlblval = [] self.xdlbl = [] self.xdval = [] self.xd = [] self.xdtip = [] for i in range(self.maxdim): xdframe, xdlblval, xdlbl, xdval, xd, xdtip = add_spinbox( self.rowxd, label=str(i), values=(0,), wrap=True, command=self.spinned_x, state=tk.DISABLED, tooltip='None') self.xdframe.append(xdframe) self.xdlblval.append(xdlblval) self.xdlbl.append(xdlbl) self.xdval.append(xdval) self.xd.append(xd) self.xdtip.append(xdtip) xdframe.pack(side=tk.LEFT) for ll in self.ydlbl: ll.destroy() for ll in self.yd: ll.destroy() self.ydframe = [] self.ydlblval = [] self.ydlbl = [] self.ydval = [] self.yd = [] self.ydtip = [] for i in range(self.maxdim): ydframe, ydlblval, ydlbl, ydval, yd, ydtip = add_spinbox( self.rowyd, label=str(i), values=(0,), wrap=True, command=self.spinned_y, state=tk.DISABLED, tooltip='None') self.ydframe.append(ydframe) self.ydlblval.append(ydlblval) self.ydlbl.append(ydlbl) self.ydval.append(ydval) self.yd.append(yd) self.ydtip.append(ydtip) ydframe.pack(side=tk.LEFT) for ll in self.y2dlbl: ll.destroy() for ll in self.y2d: ll.destroy() self.y2dframe = [] self.y2dlblval = [] self.y2dlbl = [] self.y2dval = [] self.y2d = [] self.y2dtip = [] for i in range(self.maxdim): y2dframe, y2dlblval, y2dlbl, y2dval, y2d, y2dtip = add_spinbox( self.rowy2d, label=str(i), values=(0,), wrap=True, command=self.spinned_y2, state=tk.DISABLED, tooltip='None') self.y2dframe.append(y2dframe) self.y2dlblval.append(y2dlblval) self.y2dlbl.append(y2dlbl) self.y2dval.append(y2dval) self.y2d.append(y2d) self.y2dtip.append(y2dtip) y2dframe.pack(side=tk.LEFT) # set variables columns = [''] + self.cols if ihavectk: self.x.configure(values=columns) else: self.x['values'] = columns self.x.set(columns[0]) if ihavectk: self.y.configure(values=columns) else: self.y['values'] = columns self.y.set(columns[0]) if ihavectk: self.y2.configure(values=columns) else: self.y2['values'] = columns self.y2.set(columns[0])
# # Plot #
[docs] def redraw_y(self): """ Redraw the left-hand-side y-axis. Reads left-hand-side `y` variable name, the current settings of its dimension spinboxes, as well as all other plotting options. Then redraws the left-hand-side y-axis. """ # get all states # rowxy y = self.y.get() if y != '': inv_y = self.inv_y.get() # rowxyopt ls = self.ls.get() lw = float(self.lw.get()) c = str(self.lc.get()) try: if isinstance(eval(c), tuple): c = eval(c) except: # several different exceptions possible pass m = self.marker.get() ms = float(self.ms.get()) mfc = self.mfc.get() try: if isinstance(eval(mfc), tuple): mfc = eval(mfc) except: pass mec = self.mec.get() try: if isinstance(eval(mec), tuple): mec = eval(mec) except: pass mew = float(self.mew.get()) # rowy2 y2 = self.y2.get() same_y = self.same_y.get() # y plotting styles pargs = {'linestyle': ls, 'linewidth': lw, 'marker': m, 'markersize': ms, 'markerfacecolor': mfc, 'markeredgecolor': mec, 'markeredgewidth': mew} gy, vy = vardim2var(y, self.groups) if vy == self.tname[gy]: ylab = 'Date' pargs['color'] = c else: vvy = selvar(self, vy) ylab = set_axis_label(vvy) # ToDo with dimensions if len(self.line_y) == 1: # set color only if single line, # None and 'None' do not work for multiple lines pargs['color'] = c # set style for ll in self.line_y: plt.setp(ll, **pargs) if 'color' in pargs: ic = pargs['color'] if (ic != 'None'): self.axes.spines['left'].set_color(ic) self.axes.tick_params(axis='y', colors=ic) self.axes.yaxis.label.set_color(ic) self.axes.yaxis.set_label_text(ylab) # same y-axes ylim = self.axes.get_ylim() ylim2 = self.axes2.get_ylim() if same_y and (y2 != ''): ymin, ymax = self.minmax_ylim(ylim, ylim2) if (ymin is not None) and (ymax is not None): ylim = [ymin, ymax] ylim2 = [ymin, ymax] self.axes.set_ylim(ylim) self.axes2.set_ylim(ylim2) # invert y-axis if inv_y and (ylim[0] is not None): if ylim[0] < ylim[1]: ylim = ylim[::-1] self.axes.set_ylim(ylim) else: if ylim[1] < ylim[0]: ylim = ylim[::-1] self.axes.set_ylim(ylim) # invert x-axis inv_x = self.inv_x.get() xlim = self.axes.get_xlim() if inv_x and (xlim[0] is not None): if xlim[0] < xlim[1]: xlim = xlim[::-1] self.axes.set_xlim(xlim) else: if xlim[1] < xlim[0]: xlim = xlim[::-1] self.axes.set_xlim(xlim) # redraw self.canvas.draw() self.toolbar.update()
[docs] def redraw_y2(self): """ Redraw the right-hand-side y-axis. Reads right-hand-side `y` variable name, the current settings of its dimension spinboxes, as well as all other plotting options. Then redraws the right-hand-side y-axis. """ # get all states # rowy2 y2 = self.y2.get() if y2 != '': # rowxy y = self.y.get() # rowy2 inv_y2 = self.inv_y2.get() same_y = self.same_y.get() # rowy2opt ls = self.ls2.get() lw = float(self.lw2.get()) c = self.lc2.get() try: if isinstance(eval(c), tuple): c = eval(c) except: pass m = self.marker2.get() ms = float(self.ms2.get()) mfc = self.mfc2.get() try: if isinstance(eval(mfc), tuple): mfc = eval(mfc) except: pass mec = self.mec2.get() try: if isinstance(eval(mec), tuple): mec = eval(mec) except: pass mew = float(self.mew2.get()) # y plotting styles pargs = {'linestyle': ls, 'linewidth': lw, 'marker': m, 'markersize': ms, 'markerfacecolor': mfc, 'markeredgecolor': mec, 'markeredgewidth': mew} gy, vy = vardim2var(y2, self.groups) if vy == self.tname[gy]: ylab = 'Date' pargs['color'] = c else: vvy = selvar(self, vy) ylab = set_axis_label(vvy) if len(self.line_y2) == 1: # set color only if single line, # None and 'None' do not work for multiple lines pargs['color'] = c # set style for ll in self.line_y2: plt.setp(ll, **pargs) if 'color' in pargs: ic = pargs['color'] if (ic != 'None'): self.axes2.spines['left'].set_color(ic) self.axes2.tick_params(axis='y', colors=ic) self.axes2.yaxis.label.set_color(ic) self.axes2.yaxis.set_label_text(ylab) # same y-axes ylim = self.axes.get_ylim() ylim2 = self.axes2.get_ylim() if same_y and (y2 != ''): ymin, ymax = self.minmax_ylim(ylim, ylim2) if (ymin is not None) and (ymax is not None): ylim = [ymin, ymax] ylim2 = [ymin, ymax] self.axes.set_ylim(ylim) self.axes2.set_ylim(ylim2) # invert y-axis ylim = ylim2 if inv_y2 and (ylim[0] is not None): if ylim[0] < ylim[1]: ylim = ylim[::-1] self.axes2.set_ylim(ylim) else: if ylim[1] < ylim[0]: ylim = ylim[::-1] self.axes2.set_ylim(ylim) # invert x-axis inv_x = self.inv_x.get() xlim = self.axes.get_xlim() if inv_x and (xlim[0] is not None): if xlim[0] < xlim[1]: xlim = xlim[::-1] self.axes.set_xlim(xlim) else: if xlim[1] < xlim[0]: xlim = xlim[::-1] self.axes.set_xlim(xlim) # redraw self.canvas.draw() self.toolbar.update()
[docs] def redraw(self, event=None): """ Redraw the left-hand-side and right-hand-side y-axis. Reads the two `y` variable names, the current settings of their dimension spinboxes, as well as all other plotting options. Then redraws the both y-axes. """ # get all states # rowxy x = self.x.get() y = self.y.get() # rowy2 y2 = self.y2.get() # Clear both axes first, otherwise x-axis only shows # if line2 is chosen. self.axes.clear() self.axes2.clear() self.axes2.yaxis.set_label_position('right') self.axes2.yaxis.tick_right() # ylim = [None, None] # ylim2 = [None, None] # set x, y, axes labels vx = 'None' vy = 'None' vy2 = 'None' if (y != '') or (y2 != ''): # y axis if y != '': gy, vy = vardim2var(y, self.groups) if vy == self.tname[gy]: yy = self.time[gy] ylab = 'Date' else: yy = selvar(self, vy) ylab = set_axis_label(yy) yy = get_slice_miss(self, self.yd, yy) # y2 axis if y2 != '': gy2, vy2 = vardim2var(y2, self.groups) if vy2 == self.tname[gy2]: yy2 = self.time[gy2] ylab2 = 'Date' else: yy2 = selvar(self, vy2) ylab2 = set_axis_label(yy2) yy2 = get_slice_miss(self, self.y2d, yy2) if (x != ''): # x axis gx, vx = vardim2var(x, self.groups) if vx == self.tname[gx]: xx = self.time[gx] xlab = 'Date' else: xx = selvar(self, vx) xlab = set_axis_label(xx) xx = get_slice_miss(self, self.xd, xx) else: # set x to index if not selected if (y != ''): nx = yy.shape[0] elif (y2 != ''): nx = yy2.shape[0] else: nx = 0 xx = np.arange(nx) xlab = '' # set y-axes to nan if not selected if (y == ''): yy = np.ones_like(xx, dtype='float') * np.nan ylab = '' if (y2 == ''): yy2 = np.ones_like(xx, dtype='float') * np.nan ylab2 = '' # plot # y-axis try: # , picker=True, pickradius=5) self.line_y = self.axes.plot(xx, yy) except Exception: estr = ('Scatter: x (' + vx + ') and y (' + vy + ')' ' shapes do not match for plot:') print(estr, xx.shape, yy.shape) return self.axes.xaxis.set_label_text(xlab) self.axes.yaxis.set_label_text(ylab) # y2-axis try: # , picker=True, pickradius=5) self.line_y2 = self.axes2.plot(xx, yy2) except Exception: estr = ('Scatter: x (' + vx + ') and y2 (' + vy2 + ')' ' shapes do not match for plot:') print(estr, xx.shape, yy2.shape) return self.axes2.format_coord = lambda x, y: format_coord_scatter( x, y, self.axes, self.axes2, xx.dtype, yy.dtype, yy2.dtype) self.axes2.xaxis.set_label_text(xlab) self.axes2.yaxis.set_label_text(ylab2) # styles, invert, same axes, etc. self.redraw_y() self.redraw_y2() # redraw self.canvas.draw() self.toolbar.update()