#!/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()