Source code for ncvue.ncvue

#!/usr/bin/env python3
"""
Calling routine of ncvue.

The calling routine sets up the toplevel root window, opens the netcdf file
and gets an instance of the ncvMain class.

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 functions are provided:

.. autosummary::
   ncvue

History
   * Written Nov-Dec 2020 by Matthias Cuntz (mc (at) macu (dot) de)
   * Separate Tk() and Toplevel() widgets to communicate via Tk() between
     windows, Jan 2021, Matthias Cuntz
   * Set titlebar and taskbar icon only if "standalone" not in ipython or
     jupyter, May 2021, Matthias Cuntz
   * Different themes for different OS, May 2021, Matthias Cuntz
   * Font size 13 on Windows for plots, Jun 2021, Matthias Cuntz
   * Allow groups in netcdf files, Jan 2024, Matthias Cuntz
   * Allow multiple netcdf files, Jan 2024, Matthias Cuntz
   * Move themes/ and images/ directories from src/ncvue/ to src/ directory,
     Jan 2024, Matthias Cuntz
   * Move themes/ and images/ back to src/ncvue/, Feb 2024, Matthias Cuntz
   * Change formatting of file string for multiple files,
     Jul 2024, Matthias Cuntz
   * Use CustomTkinter if installed, Nov 2024, Matthias Cuntz
   * Use own ncvue-blue theme for customtkinter, Dec 2024, Matthias Cuntz
   * Include xarray to read input files, Feb 2025, Matthias Cuntz

"""
import os
import platform
import sys
import tkinter as tk
import tkinter.ttk as ttk
try:
    import customtkinter
    from customtkinter import CTk as Tk
    from customtkinter import CTkToplevel as Toplevel
    ihavectk = True
except ModuleNotFoundError:
    from tkinter import Tk
    from tkinter import Toplevel
    ihavectk = False
try:
    import xarray as xr
    ihavex = True
except ModuleNotFoundError:
    ihavex = False
from matplotlib import pyplot as plt
import netCDF4 as nc
import numpy as np
from .ncvmethods import analyse_netcdf
from .ncvmain import ncvMain


__all__ = ['ncvue']


[docs] def ncvue(ncfile=[], miss=np.nan, usex=False): """ The main function to start the data frame GUI. Parameters ---------- ncfile : list of str, optional Name of netcdf file (default: []). miss : float, optional Add value to list of missing values: _FillValue, missing_value, and the standard netCDF missing value for current datatype from netcdf4.default_fillvals (default: np.nan). usex : bool, optional If True, use xarray to read input files. Multiple input files will be read using xarray.open_mfdataset. """ # print(mpl.get_backend()) ios = platform.system() # Windows, Darwin, Linux if ios == 'Windows': # make Windows aware of high resolution displays # https://stackoverflow.com/questions/41315873/attempting-to-resolve-blurred-tkinter-text-scaling-on-windows-10-high-dpi-disp from ctypes import windll windll.shcore.SetProcessDpiAwareness(1) # Pyinstaller sets _MEIPASS if macOS app bundle_dir = getattr(sys, '_MEIPASS', os.path.abspath(os.path.dirname(__file__))) top = Tk() top.withdraw() # top.option_add("*Font", "Helvetica 10") # Check light/dark mode # https://stackoverflow.com/questions/65294987/detect-os-dark-mode-in-python # style = ttk.Style() # print(style.theme_names(), style.theme_use()) if ios == 'Darwin': theme = 'aqua' style = ttk.Style() try: style.theme_use(theme) except: pass # top.tk.call('source', bundle_dir + '/themes/azure-2.0/azure.tcl') # theme = 'light' # light, dark # top.tk.call("set_theme", theme) elif ios == 'Windows': top.option_add("*Font", "Helvetica 10") plt.rc('font', size=13) # standard Windows themes # ('winnative', 'clam', 'alt', 'default', 'classic', 'vista', # 'xpnative') # theme = 'vista' # style = ttk.Style() # style.theme_use(theme) # style packages # Download from https://sourceforge.net/projects/tcl-awthemes/ # top.tk.call('lappend', 'auto_path', # bundle_dir + '/themes/awthemes-10.3.2') # theme = 'awdark' # 'awlight', 'awdark' # top.tk.call('package', 'require', theme) # style = ttk.Style() # style.theme_use(theme) # single file styles # 'azure' and 'azure-dark' v1.x, 'Breeze' # top.tk.call('source', bundle_dir + '/themes/breeze/breeze.tcl') # theme = 'Breeze' # top.tk.call('source', bundle_dir + '/themes/azure-1.3/azure.tcl') # theme = 'azure' # top.tk.call('source', bundle_dir + # '/themes/azure-1.3/azure-dark.tcl') # theme = 'azure-dark' # style = ttk.Style() # style.theme_use(theme) # 'azure' v2.x, 'sun-valley', 'forest' of rdbende top.tk.call('source', bundle_dir + '/themes/azure-2.0/azure.tcl') # top.tk.call('source', bundle_dir + # '/themes/sun-valley-1.0/sun-valley.tcl') theme = 'light' # light, dark top.tk.call("set_theme", theme) elif ios == 'Linux': # standard Linux schemes # theme = 'clam' # 'clam', 'alt', 'default', 'classic' # style = ttk.Style() # style.theme_use(theme) # 'azure' v2.x, 'sun-valley', 'forest' of rdbende top.tk.call('source', bundle_dir + '/themes/azure-2.0/azure.tcl') theme = 'light' # light, dark top.tk.call("set_theme", theme) if ihavectk: # customtkinter.set_default_color_theme("blue") # customtkinter.set_default_color_theme("dark-blue") # customtkinter.set_default_color_theme("green") # customtkinter.set_default_color_theme( # f'{bundle_dir}/themes/customtkinter/dark-blue.json') customtkinter.set_default_color_theme( f'{bundle_dir}/themes/customtkinter/ncvue-blue.json') # set titlebar and taskbar icon only if "standalone", # i.e. not ipython or jupyter try: whichpy = get_ipython().__class__.__name__ except NameError: whichpy = '' if not whichpy: icon = tk.PhotoImage(file=bundle_dir + '/images/ncvue_icon.png') top.iconphoto(True, icon) # True: apply to all future toplevels else: icon = None root = Toplevel() root.name = 'ncvOne' if len(ncfile) == 1: root.title("ncvue " + ncfile[0]) else: root.title("ncvue") root.geometry('1000x800+100+100') # Connect netcdf file and extracted information to top top.os = ios # operating system top.theme = theme # current theme top.icon = icon # app icon if ihavex: top.usex = usex # use xarray else: top.usex = False # use xarray top.fi = [] # file name or file handle top.groups = [] # filename with increasing index or group names top.miss = miss # extra missing value top.dunlim = [] # name of unlimited dimension top.time = [] # datetime variable top.tname = [] # datetime variable name top.tvar = [] # datetime variable name in netcdf file top.dtime = [] # decimal year top.latvar = [] # name of latitude variable top.lonvar = [] # name of longitude variable top.latdim = [] # name of latitude dimension top.londim = [] # name of longitude dimension top.maxdim = 1 # maximum number of dimensions of all variables # > 0 so that dimension spinboxes present top.cols = [] # variable list if len(ncfile) > 0: if top.usex: if len(ncfile) > 1: top.fi = xr.open_mfdataset(ncfile) else: top.fi = xr.open_dataset(ncfile[0]) else: for ii, nn in enumerate(ncfile): top.fi.append(nc.Dataset(nn, 'r')) if len(ncfile) > 1: nnc = np.ceil(np.log10(len(ncfile))).astype(int) top.groups.append(f'file{ii:0{nnc}d}') # Check groups if len(ncfile) == 1: top.groups = list(top.fi[0].groups.keys()) else: for ii, nn in enumerate(ncfile): if len(list(top.fi[ii].groups.keys())) > 0: print(f'Either multiple files or one file with' f' groups allowed as input. Multiple files' f' given but file {nn} has groups.') for fi in top.fi: fi.close() top.quit() top.destroy() # Analyse netcdf file analyse_netcdf(top) def on_closing(): if top.usex: if top.fi: top.fi.close() else: if len(top.fi) > 0: for fi in top.fi: fi.close() top.quit() top.destroy() root.top = top root.protocol("WM_DELETE_WINDOW", on_closing) # 1st plotting window main_frame = ncvMain(root) main_frame.pack(fill=tk.BOTH, expand=1) top.mainloop()