diff --git a/.gitignore b/.gitignore index ad8168d4..42b4c42b 100644 --- a/.gitignore +++ b/.gitignore @@ -14,3 +14,5 @@ __pycache__/ # files that are generated when package is installed build/ metplotpy.egg-info/ + +.DS_Store diff --git a/docs/Users_Guide/release-notes.rst b/docs/Users_Guide/release-notes.rst index 787730f0..1a2ca934 100644 --- a/docs/Users_Guide/release-notes.rst +++ b/docs/Users_Guide/release-notes.rst @@ -10,7 +10,6 @@ describes the bugfix, enhancement, or new feature: METplotpy Version 4.0.0-beta1 release notes (20260205) ====================================================== - .. dropdown:: New Plots None diff --git a/docs/copyright.txt b/docs/copyright.txt index 75eec2d4..19a595bd 100644 --- a/docs/copyright.txt +++ b/docs/copyright.txt @@ -1,5 +1,5 @@ # ============================* -# ** Copyright UCAR (c) 1992 - 2024 +# ** Copyright UCAR (c) 1992 - 2026 # ** University Corporation for Atmospheric Research (UCAR) # ** National Center for Atmospheric Research (NCAR) # ** Research Applications Lab (RAL) diff --git a/internal/scripts/installation/modulefiles/3.1.0_derecho b/internal/scripts/installation/modulefiles/3.1.0_derecho deleted file mode 100644 index 8d271645..00000000 --- a/internal/scripts/installation/modulefiles/3.1.0_derecho +++ /dev/null @@ -1,28 +0,0 @@ -#%Module###################################################################### -## -## METplotpy -## -proc ModulesHelp { } { - puts stderr "Sets up the paths and environment variables to use the METplotpy-3.1.0 - *** For help see the official MET webpage at -#http://www.dtcenter.org/met/users ***" -} - -module load ncarenv/23.09 -module load intel/2023.2.1 - -prepend-path PATH /glade/work/dtcrt/METplus/derecho/miniconda/miniconda3/envs/metplus_v5.1_py3.10/bin - -setenv METPLOTPY_SOURCE /glade/work/dtcrt/METplus/derecho/components/METplotpy/installations/METplotpy-3.1.0 -setenv METPLOTPY_BASE /glade/work/dtcrt/METplus/derecho/components/METplotpy/installations/METplotpy-3.1.0 - -prepend-path PATH /glade/work/dtcrt/METplus/derecho/components/METplotpy/installations/METplotpy-3.1.0/metplotpy/contributed -prepend-path PATH /glade/work/dtcrt/METplus/derecho/components/METplotpy/installations/METplotpy-3.1.0/metplotpy/plots/performance_diagram -prepend-path PATH /glade/work/dtcrt/METplus/derecho/components/METplotpy/installations/METplotpy-3.1.0/metplotpy/plots -prepend-path PATH /glade/work/dtcrt/METplus/derecho/components/METplotpy/installations/METplotpy-3.1.0/metplotpy -prepend-path PATH /glade/work/dtcrt/METplus/derecho/components/METplotpy/installations/METplotpy-3.1.0 -prepend-path PYTHONPATH /glade/work/dtcrt/METplus/derecho/components/METplotpy/installations/METplotpy-3.1.0/metplotpy/contributed -prepend-path PYTHONPATH /glade/work/dtcrt/METplus/derecho/components/METplotpy/installations/METplotpy-3.1.0/metplotpy/plots/performance_diagram -prepend-path PYTHONPATH /glade/work/dtcrt/METplus/derecho/components/METplotpy/installations/METplotpy-3.1.0/metplotpy/plots -prepend-path PYTHONPATH /glade/work/dtcrt/METplus/derecho/components/METplotpy/installations/METplotpy-3.1.0/metplotpy -prepend-path PYTHONPATH /glade/work/dtcrt/METplus/derecho/components/METplotpy/installations/METplotpy-3.1.0 diff --git a/internal/scripts/installation/modulefiles/3.1.0_gaea b/internal/scripts/installation/modulefiles/3.1.0_gaea deleted file mode 100644 index cc26d653..00000000 --- a/internal/scripts/installation/modulefiles/3.1.0_gaea +++ /dev/null @@ -1,25 +0,0 @@ -#%Module###################################################################### -## -## METplotpy -## -proc ModulesHelp { } { - puts stderr "Sets up the paths and environment variables to use the METplotpy-3.1.0. - *** For help see the official MET webpage at http://www.dtcenter.org/met/users ***" -} - -module load intel-oneapi/intel/2023.2.0 - -setenv METPLOTPY_SOURCE /usw/met/METplotpy/METplotpy-3.1.0 -setenv METPLOTPY_BASE /usw/met/METplotpy/METplotpy-3.1.0 - -prepend-path PATH /ncrc/proj/nggps_psd/Julie.Prestopnik/projects/miniconda/miniconda3/envs/metplus_v5.1_py3.10/bin -prepend-path PATH /usw/met/METplotpy/METplotpy-3.1.0/metplotpy/contributed -prepend-path PATH /usw/met/METplotpy/METplotpy-3.1.0/metplotpy/plots/performance_diagram -prepend-path PATH /usw/met/METplotpy/METplotpy-3.1.0/metplotpy/plots -prepend-path PATH /usw/met/METplotpy/METplotpy-3.1.0/metplotpy -prepend-path PATH /usw/met/METplotpy/METplotpy-3.1.0 -prepend-path PYTHONPATH /usw/met/METplotpy/METplotpy-3.1.0/metplotpy/contributed -prepend-path PYTHONPATH /usw/met/METplotpy/METplotpy-3.1.0/metplotpy/plots/performance_diagram -prepend-path PYTHONPATH /usw/met/METplotpy/METplotpy-3.1.0/metplotpy/plots -prepend-path PYTHONPATH /usw/met/METplotpy/METplotpy-3.1.0/metplotpy -prepend-path PYTHONPATH /usw/met/METplotpy/METplotpy-3.1.0 diff --git a/internal/scripts/installation/modulefiles/3.1.0_hera b/internal/scripts/installation/modulefiles/3.1.0_hera deleted file mode 100644 index 07f1c960..00000000 --- a/internal/scripts/installation/modulefiles/3.1.0_hera +++ /dev/null @@ -1,25 +0,0 @@ -#%Module###################################################################### -## -## METplotpy -## -proc ModulesHelp { } { - puts stderr "Sets up the paths and environment variables to use the METplotpy-3.1.0. - *** For help see the official MET webpage at http://www.dtcenter.org/met/users ***" -} - -prereq intel/2024.2.1 - -setenv METPLOTPY_SOURCE /contrib/METplotpy/METplotpy-3.1.0 -setenv METPLOTPY_BASE /contrib/METplotpy/METplotpy-3.1.0 - -prepend-path PATH /scratch1/BMC/dtc/miniconda/miniconda3/envs/metplus_v6.1_py3.12/bin -prepend-path PATH /contrib/METplotpy/METplotpy-3.1.0/metplotpy/contributed -prepend-path PATH /contrib/METplotpy/METplotpy-3.1.0/metplotpy/plots/performance_diagram -prepend-path PATH /contrib/METplotpy/METplotpy-3.1.0/metplotpy/plots -prepend-path PATH /contrib/METplotpy/METplotpy-3.1.0/metplotpy -prepend-path PATH /contrib/METplotpy/METplotpy-3.1.0 -prepend-path PYTHONPATH /contrib/METplotpy/METplotpy-3.1.0/metplotpy/contributed -prepend-path PYTHONPATH /contrib/METplotpy/METplotpy-3.1.0/metplotpy/plots/performance_diagram -prepend-path PYTHONPATH /contrib/METplotpy/METplotpy-3.1.0/metplotpy/plots -prepend-path PYTHONPATH /contrib/METplotpy/METplotpy-3.1.0/metplotpy -prepend-path PYTHONPATH /contrib/METplotpy/METplotpy-3.1.0 diff --git a/internal/scripts/installation/modulefiles/3.1.0_jet b/internal/scripts/installation/modulefiles/3.1.0_jet deleted file mode 100644 index 85bf61c7..00000000 --- a/internal/scripts/installation/modulefiles/3.1.0_jet +++ /dev/null @@ -1,25 +0,0 @@ -#%Module###################################################################### -## -## METplotpy -## -proc ModulesHelp { } { - puts stderr "Sets up the paths and environment variables to use the METplotpy-3.1.0 - *** For help see the official MET webpage at http://www.dtcenter.org/met/users ***" -} - -prereq intel/2024.2.1 - -setenv METPLOTPY_SOURCE /contrib/met/METplotpy/METplotpy-3.1.0 -setenv METPLOTPY_BASE /contrib/met/METplotpy/METplotpy-3.1.0 - -prepend-path PATH /lfs6/HFIP/dtc-hurr/METplus/miniconda/miniconda3/envs/metplus_v6.1_py3.12/bin -prepend-path PATH /contrib/met/METplotpy/METplotpy-3.1.0/metplotpy/contributed -prepend-path PATH /contrib/met/METplotpy/METplotpy-3.1.0/metplotpy/plots/performance_diagram -prepend-path PATH /contrib/met/METplotpy/METplotpy-3.1.0/metplotpy/plots -prepend-path PATH /contrib/met/METplotpy/METplotpy-3.1.0/metplotpy -prepend-path PATH /contrib/met/METplotpy/METplotpy-3.1.0 -prepend-path PYTHONPATH /contrib/met/METplotpy/METplotpy-3.1.0/metplotpy/contributed -prepend-path PYTHONPATH /contrib/met/METplotpy/METplotpy-3.1.0/metplotpy/plots/performance_diagram -prepend-path PYTHONPATH /contrib/met/METplotpy/METplotpy-3.1.0/metplotpy/plots -prepend-path PYTHONPATH /contrib/met/METplotpy/METplotpy-3.1.0/metplotpy -prepend-path PYTHONPATH /contrib/met/METplotpy/METplotpy-3.1.0 diff --git a/internal/scripts/installation/modulefiles/3.1.0_ursa b/internal/scripts/installation/modulefiles/3.1.0_ursa deleted file mode 100644 index 9ffb356e..00000000 --- a/internal/scripts/installation/modulefiles/3.1.0_ursa +++ /dev/null @@ -1,25 +0,0 @@ -#%Module###################################################################### -## -## METplotpy -## -proc ModulesHelp { } { - puts stderr "Sets up the paths and environment variables to use the METplotpy-3.1.0. - *** For help see the official MET webpage at http://www.dtcenter.org/met/users ***" -} - -prereq intel-oneapi-compilers/2025.1.1 - -setenv METPLOTPY_SOURCE /contrib/METplotpy/METplotpy-3.1.0 -setenv METPLOTPY_BASE /contrib/METplotpy/METplotpy-3.1.0 - -prepend-path PATH /scratch3/BMC/dtc/METplus/miniconda/miniconda3/envs/metplus_v6.1_py3.12/bin -prepend-path PATH /contrib/METplotpy/METplotpy-3.1.0/metplotpy/contributed -prepend-path PATH /contrib/METplotpy/METplotpy-3.1.0/metplotpy/plots/performance_diagram -prepend-path PATH /contrib/METplotpy/METplotpy-3.1.0/metplotpy/plots -prepend-path PATH /contrib/METplotpy/METplotpy-3.1.0/metplotpy -prepend-path PATH /contrib/METplotpy/METplotpy-3.1.0 -prepend-path PYTHONPATH /contrib/METplotpy/METplotpy-3.1.0/metplotpy/contributed -prepend-path PYTHONPATH /contrib/METplotpy/METplotpy-3.1.0/metplotpy/plots/performance_diagram -prepend-path PYTHONPATH /contrib/METplotpy/METplotpy-3.1.0/metplotpy/plots -prepend-path PYTHONPATH /contrib/METplotpy/METplotpy-3.1.0/metplotpy -prepend-path PYTHONPATH /contrib/METplotpy/METplotpy-3.1.0 diff --git a/internal/scripts/installation/modulefiles/3.1.0_wcoss2 b/internal/scripts/installation/modulefiles/3.1.0_wcoss2 deleted file mode 100644 index ea6ea5b4..00000000 --- a/internal/scripts/installation/modulefiles/3.1.0_wcoss2 +++ /dev/null @@ -1,30 +0,0 @@ -#%Module###################################################################### -## -## METplotpy -## -proc ModulesHelp { } { - puts stderr "Sets up the paths and environment variables to use the METplotpy-3.1.0. - *** For help see the official MET webpage at http://www.dtcenter.org/met/users ***" -} - -module reset -module use /apps/ops/para/libs/modulefiles/compiler/intel/19.1.3.304 -module load intel -module use /apps/dev/modulefiles/ -module load ve/evs/2.0 -module use /apps/sw_review/emc/METcalcpy/modulefiles -module load metcalcpy/3.1.0 - -setenv METPLOTPY_SOURCE /apps/sw_review/emc/METplotpy/3.1.0 -setenv METPLOTPY_BASE /apps/sw_review/emc/METplotpy/3.1.0 - -prepend-path PATH /apps/sw_review/emc/METplotpy/3.1.0/metplotpy/contributed -prepend-path PATH /apps/sw_review/emc/METplotpy/3.1.0/metplotpy/plots/performance_diagram -prepend-path PATH /apps/sw_review/emc/METplotpy/3.1.0/metplotpy/plots -prepend-path PATH /apps/sw_review/emc/METplotpy/3.1.0/metplotpy -prepend-path PATH /apps/sw_review/emc/METplotpy/3.1.0 -prepend-path PYTHONPATH /apps/sw_review/emc/METplotpy/3.1.0/metplotpy/contributed -prepend-path PYTHONPATH /apps/sw_review/emc/METplotpy/3.1.0/metplotpy/plots/performance_diagram -prepend-path PYTHONPATH /apps/sw_review/emc/METplotpy/3.1.0/metplotpy/plots -prepend-path PYTHONPATH /apps/sw_review/emc/METplotpy/3.1.0/metplotpy -prepend-path PYTHONPATH /apps/sw_review/emc/METplotpy/3.1.0 diff --git a/internal/scripts/installation/modulefiles/3.1.0_casper b/internal/scripts/installation/modulefiles/3.2.0_casper similarity index 65% rename from internal/scripts/installation/modulefiles/3.1.0_casper rename to internal/scripts/installation/modulefiles/3.2.0_casper index 2d786c42..ea67c53d 100644 --- a/internal/scripts/installation/modulefiles/3.1.0_casper +++ b/internal/scripts/installation/modulefiles/3.2.0_casper @@ -3,7 +3,7 @@ ## METplotpy ## proc ModulesHelp { } { - puts stderr "Sets up the paths and environment variables to use the METplotpy-3.1.0 + puts stderr "Sets up the paths and environment variables to use the METplotpy-3.2.0 *** For help see the official MET webpage at http://www.dtcenter.org/met/users ***" } @@ -12,16 +12,16 @@ module load intel/2024.2.1 prepend-path PATH /glade/work/dtcrt/METplus/casper/miniconda/miniconda3/envs/metplus_v6.1_py3.12/bin -setenv METPLOTPY_SOURCE /glade/work/dtcrt/METplus/casper/components/METplotpy/installations/METplotpy-3.1.0 -setenv METPLOTPY_BASE /glade/work/dtcrt/METplus/casper/components/METplotpy/installations/METplotpy-3.1.0 +setenv METPLOTPY_SOURCE /glade/work/dtcrt/METplus/casper/components/METplotpy/installations/METplotpy-3.2.0 +setenv METPLOTPY_BASE /glade/work/dtcrt/METplus/casper/components/METplotpy/installations/METplotpy-3.2.0 -prepend-path PATH /glade/work/dtcrt/METplus/casper/components/METplotpy/installations/METplotpy-3.1.0/metplotpy/contributed -prepend-path PATH /glade/work/dtcrt/METplus/casper/components/METplotpy/installations/METplotpy-3.1.0/metplotpy/plots/performance_diagram -prepend-path PATH /glade/work/dtcrt/METplus/casper/components/METplotpy/installations/METplotpy-3.1.0/metplotpy/plots -prepend-path PATH /glade/work/dtcrt/METplus/casper/components/METplotpy/installations/METplotpy-3.1.0/metplotpy -prepend-path PATH /glade/work/dtcrt/METplus/casper/components/METplotpy/installations/METplotpy-3.1.0 -prepend-path PYTHONPATH /glade/work/dtcrt/METplus/casper/components/METplotpy/installations/METplotpy-3.1.0/metplotpy/contributed -prepend-path PYTHONPATH /glade/work/dtcrt/METplus/casper/components/METplotpy/installations/METplotpy-3.1.0/metplotpy/plots/performance_diagram -prepend-path PYTHONPATH /glade/work/dtcrt/METplus/casper/components/METplotpy/installations/METplotpy-3.1.0/metplotpy/plots -prepend-path PYTHONPATH /glade/work/dtcrt/METplus/casper/components/METplotpy/installations/METplotpy-3.1.0/metplotpy -prepend-path PYTHONPATH /glade/work/dtcrt/METplus/casper/components/METplotpy/installations/METplotpy-3.1.0 +prepend-path PATH /glade/work/dtcrt/METplus/casper/components/METplotpy/installations/METplotpy-3.2.0/metplotpy/contributed +prepend-path PATH /glade/work/dtcrt/METplus/casper/components/METplotpy/installations/METplotpy-3.2.0/metplotpy/plots/performance_diagram +prepend-path PATH /glade/work/dtcrt/METplus/casper/components/METplotpy/installations/METplotpy-3.2.0/metplotpy/plots +prepend-path PATH /glade/work/dtcrt/METplus/casper/components/METplotpy/installations/METplotpy-3.2.0/metplotpy +prepend-path PATH /glade/work/dtcrt/METplus/casper/components/METplotpy/installations/METplotpy-3.2.0 +prepend-path PYTHONPATH /glade/work/dtcrt/METplus/casper/components/METplotpy/installations/METplotpy-3.2.0/metplotpy/contributed +prepend-path PYTHONPATH /glade/work/dtcrt/METplus/casper/components/METplotpy/installations/METplotpy-3.2.0/metplotpy/plots/performance_diagram +prepend-path PYTHONPATH /glade/work/dtcrt/METplus/casper/components/METplotpy/installations/METplotpy-3.2.0/metplotpy/plots +prepend-path PYTHONPATH /glade/work/dtcrt/METplus/casper/components/METplotpy/installations/METplotpy-3.2.0/metplotpy +prepend-path PYTHONPATH /glade/work/dtcrt/METplus/casper/components/METplotpy/installations/METplotpy-3.2.0 diff --git a/internal/scripts/installation/modulefiles/3.2.0_gaea b/internal/scripts/installation/modulefiles/3.2.0_gaea new file mode 100644 index 00000000..16ec5975 --- /dev/null +++ b/internal/scripts/installation/modulefiles/3.2.0_gaea @@ -0,0 +1,25 @@ +#%Module###################################################################### +## +## METplotpy +## +proc ModulesHelp { } { + puts stderr "Sets up the paths and environment variables to use the METplotpy-3.2.0. + *** For help see the official MET webpage at http://www.dtcenter.org/met/users ***" +} + +module load intel-oneapi/intel/2023.2.0 + +setenv METPLOTPY_SOURCE /usw/met/METplotpy/METplotpy-3.2.0 +setenv METPLOTPY_BASE /usw/met/METplotpy/METplotpy-3.2.0 + +prepend-path PATH /ncrc/proj/nggps_psd/Julie.Prestopnik/projects/miniconda/miniconda3/envs/metplus_v6.1_py3.12/bin +prepend-path PATH /usw/met/METplotpy/METplotpy-3.2.0/metplotpy/contributed +prepend-path PATH /usw/met/METplotpy/METplotpy-3.2.0/metplotpy/plots/performance_diagram +prepend-path PATH /usw/met/METplotpy/METplotpy-3.2.0/metplotpy/plots +prepend-path PATH /usw/met/METplotpy/METplotpy-3.2.0/metplotpy +prepend-path PATH /usw/met/METplotpy/METplotpy-3.2.0 +prepend-path PYTHONPATH /usw/met/METplotpy/METplotpy-3.2.0/metplotpy/contributed +prepend-path PYTHONPATH /usw/met/METplotpy/METplotpy-3.2.0/metplotpy/plots/performance_diagram +prepend-path PYTHONPATH /usw/met/METplotpy/METplotpy-3.2.0/metplotpy/plots +prepend-path PYTHONPATH /usw/met/METplotpy/METplotpy-3.2.0/metplotpy +prepend-path PYTHONPATH /usw/met/METplotpy/METplotpy-3.2.0 diff --git a/internal/scripts/installation/modulefiles/3.2.0_hera b/internal/scripts/installation/modulefiles/3.2.0_hera new file mode 100644 index 00000000..b94c8609 --- /dev/null +++ b/internal/scripts/installation/modulefiles/3.2.0_hera @@ -0,0 +1,25 @@ +#%Module###################################################################### +## +## METplotpy +## +proc ModulesHelp { } { + puts stderr "Sets up the paths and environment variables to use the METplotpy-3.2.0. + *** For help see the official MET webpage at http://www.dtcenter.org/met/users ***" +} + +prereq intel/2024.2.1 + +setenv METPLOTPY_SOURCE /contrib/METplotpy/METplotpy-3.2.0 +setenv METPLOTPY_BASE /contrib/METplotpy/METplotpy-3.2.0 + +prepend-path PATH /scratch1/BMC/dtc/miniconda/miniconda3/envs/metplus_v6.1_py3.12/bin +prepend-path PATH /contrib/METplotpy/METplotpy-3.2.0/metplotpy/contributed +prepend-path PATH /contrib/METplotpy/METplotpy-3.2.0/metplotpy/plots/performance_diagram +prepend-path PATH /contrib/METplotpy/METplotpy-3.2.0/metplotpy/plots +prepend-path PATH /contrib/METplotpy/METplotpy-3.2.0/metplotpy +prepend-path PATH /contrib/METplotpy/METplotpy-3.2.0 +prepend-path PYTHONPATH /contrib/METplotpy/METplotpy-3.2.0/metplotpy/contributed +prepend-path PYTHONPATH /contrib/METplotpy/METplotpy-3.2.0/metplotpy/plots/performance_diagram +prepend-path PYTHONPATH /contrib/METplotpy/METplotpy-3.2.0/metplotpy/plots +prepend-path PYTHONPATH /contrib/METplotpy/METplotpy-3.2.0/metplotpy +prepend-path PYTHONPATH /contrib/METplotpy/METplotpy-3.2.0 diff --git a/internal/scripts/installation/modulefiles/3.1.0_hercules b/internal/scripts/installation/modulefiles/3.2.0_hercules similarity index 59% rename from internal/scripts/installation/modulefiles/3.1.0_hercules rename to internal/scripts/installation/modulefiles/3.2.0_hercules index 2a424ebe..26d40f34 100644 --- a/internal/scripts/installation/modulefiles/3.1.0_hercules +++ b/internal/scripts/installation/modulefiles/3.2.0_hercules @@ -3,25 +3,25 @@ ## METplotpy ## proc ModulesHelp { } { - puts stderr "Sets up the paths and environment variables to use the METplotpy-3.1.0. + puts stderr "Sets up the paths and environment variables to use the METplotpy-3.2.0. *** For help see the official MET webpage at http://www.dtcenter.org/met/users ***" } module load contrib module load intel-oneapi-compilers/2022.2.1 -setenv METPLOTPY_SOURCE /apps/contrib/MET/METplotpy/METplotpy-3.1.0 -setenv METPLOTPY_BASE /apps/contrib/MET/METplotpy/METplotpy-3.1.0 +setenv METPLOTPY_SOURCE /apps/contrib/MET/METplotpy/METplotpy-3.2.0 +setenv METPLOTPY_BASE /apps/contrib/MET/METplotpy/METplotpy-3.2.0 -#setenv METPLOTPY_PATH /apps/contrib/MET/METplotpy/METplotpy-3.1.0/ -prepend-path PATH /work/noaa/ovp/miniconda/miniconda3/envs/metplus_v5.1_py3.10/bin -prepend-path PATH /apps/contrib/MET/METplotpy/METplotpy-3.1.0/metplotpy/contributed -prepend-path PATH /apps/contrib/MET/METplotpy/METplotpy-3.1.0/metplotpy/plots/performance_diagram -prepend-path PATH /apps/contrib/MET/METplotpy/METplotpy-3.1.0/metplotpy/plots -prepend-path PATH /apps/contrib/MET/METplotpy/METplotpy-3.1.0/metplotpy -prepend-path PATH /apps/contrib/MET/METplotpy/METplotpy-3.1.0 -prepend-path PYTHONPATH /apps/contrib/MET/METplotpy/METplotpy-3.1.0/metplotpy/contributed -prepend-path PYTHONPATH /apps/contrib/MET/METplotpy/METplotpy-3.1.0/metplotpy/plots/performance_diagram -prepend-path PYTHONPATH /apps/contrib/MET/METplotpy/METplotpy-3.1.0/metplotpy/plots -prepend-path PYTHONPATH /apps/contrib/MET/METplotpy/METplotpy-3.1.0/metplotpy -prepend-path PYTHONPATH /apps/contrib/MET/METplotpy/METplotpy-3.1.0 +#setenv METPLOTPY_PATH /apps/contrib/MET/METplotpy/METplotpy-3.2.0/ +prepend-path PATH /work/noaa/ovp/miniconda/miniconda3/envs/metplus_v6.1_py3.12/bin +prepend-path PATH /apps/contrib/MET/METplotpy/METplotpy-3.2.0/metplotpy/contributed +prepend-path PATH /apps/contrib/MET/METplotpy/METplotpy-3.2.0/metplotpy/plots/performance_diagram +prepend-path PATH /apps/contrib/MET/METplotpy/METplotpy-3.2.0/metplotpy/plots +prepend-path PATH /apps/contrib/MET/METplotpy/METplotpy-3.2.0/metplotpy +prepend-path PATH /apps/contrib/MET/METplotpy/METplotpy-3.2.0 +prepend-path PYTHONPATH /apps/contrib/MET/METplotpy/METplotpy-3.2.0/metplotpy/contributed +prepend-path PYTHONPATH /apps/contrib/MET/METplotpy/METplotpy-3.2.0/metplotpy/plots/performance_diagram +prepend-path PYTHONPATH /apps/contrib/MET/METplotpy/METplotpy-3.2.0/metplotpy/plots +prepend-path PYTHONPATH /apps/contrib/MET/METplotpy/METplotpy-3.2.0/metplotpy +prepend-path PYTHONPATH /apps/contrib/MET/METplotpy/METplotpy-3.2.0 diff --git a/internal/scripts/installation/modulefiles/3.2.0_jet b/internal/scripts/installation/modulefiles/3.2.0_jet new file mode 100644 index 00000000..40f620f9 --- /dev/null +++ b/internal/scripts/installation/modulefiles/3.2.0_jet @@ -0,0 +1,25 @@ +#%Module###################################################################### +## +## METplotpy +## +proc ModulesHelp { } { + puts stderr "Sets up the paths and environment variables to use the METplotpy-3.2.0 + *** For help see the official MET webpage at http://www.dtcenter.org/met/users ***" +} + +prereq intel/2024.2.1 + +setenv METPLOTPY_SOURCE /contrib/met/METplotpy/METplotpy-3.2.0 +setenv METPLOTPY_BASE /contrib/met/METplotpy/METplotpy-3.2.0 + +prepend-path PATH /lfs6/HFIP/dtc-hurr/METplus/miniconda/miniconda3/envs/metplus_v6.1_py3.12/bin +prepend-path PATH /contrib/met/METplotpy/METplotpy-3.2.0/metplotpy/contributed +prepend-path PATH /contrib/met/METplotpy/METplotpy-3.2.0/metplotpy/plots/performance_diagram +prepend-path PATH /contrib/met/METplotpy/METplotpy-3.2.0/metplotpy/plots +prepend-path PATH /contrib/met/METplotpy/METplotpy-3.2.0/metplotpy +prepend-path PATH /contrib/met/METplotpy/METplotpy-3.2.0 +prepend-path PYTHONPATH /contrib/met/METplotpy/METplotpy-3.2.0/metplotpy/contributed +prepend-path PYTHONPATH /contrib/met/METplotpy/METplotpy-3.2.0/metplotpy/plots/performance_diagram +prepend-path PYTHONPATH /contrib/met/METplotpy/METplotpy-3.2.0/metplotpy/plots +prepend-path PYTHONPATH /contrib/met/METplotpy/METplotpy-3.2.0/metplotpy +prepend-path PYTHONPATH /contrib/met/METplotpy/METplotpy-3.2.0 diff --git a/internal/scripts/installation/modulefiles/3.1.0_orion b/internal/scripts/installation/modulefiles/3.2.0_orion similarity index 60% rename from internal/scripts/installation/modulefiles/3.1.0_orion rename to internal/scripts/installation/modulefiles/3.2.0_orion index b233f0a9..98302409 100644 --- a/internal/scripts/installation/modulefiles/3.1.0_orion +++ b/internal/scripts/installation/modulefiles/3.2.0_orion @@ -3,25 +3,25 @@ ## METplotpy ## proc ModulesHelp { } { - puts stderr "Sets up the paths and environment variables to use the METplotpy-3.1.0. + puts stderr "Sets up the paths and environment variables to use the METplotpy-3.2.0. *** For help see the official MET webpage at http://www.dtcenter.org/met/users ***" } module load contrib module load intel-oneapi-compilers/2024.1.0 -setenv METPLOTPY_SOURCE /apps/contrib/MET/METplotpy/METplotpy-3.1.0 -setenv METPLOTPY_BASE /apps/contrib/MET/METplotpy/METplotpy-3.1.0 +setenv METPLOTPY_SOURCE /apps/contrib/MET/METplotpy/METplotpy-3.2.0 +setenv METPLOTPY_BASE /apps/contrib/MET/METplotpy/METplotpy-3.2.0 -#setenv METPLOTPY_PATH /apps/contrib/MET/METplotpy/METplotpy-3.1.0/ +#setenv METPLOTPY_PATH /apps/contrib/MET/METplotpy/METplotpy-3.2.0/ prepend-path PATH /work/noaa/ovp/miniconda/miniconda3/envs/metplus_v6.1_py3.12/bin -prepend-path PATH /apps/contrib/MET/METplotpy/METplotpy-3.1.0/metplotpy/contributed -prepend-path PATH /apps/contrib/MET/METplotpy/METplotpy-3.1.0/metplotpy/plots/performance_diagram -prepend-path PATH /apps/contrib/MET/METplotpy/METplotpy-3.1.0/metplotpy/plots -prepend-path PATH /apps/contrib/MET/METplotpy/METplotpy-3.1.0/metplotpy -prepend-path PATH /apps/contrib/MET/METplotpy/METplotpy-3.1.0 -prepend-path PYTHONPATH /apps/contrib/MET/METplotpy/METplotpy-3.1.0/metplotpy/contributed -prepend-path PYTHONPATH /apps/contrib/MET/METplotpy/METplotpy-3.1.0/metplotpy/plots/performance_diagram -prepend-path PYTHONPATH /apps/contrib/MET/METplotpy/METplotpy-3.1.0/metplotpy/plots -prepend-path PYTHONPATH /apps/contrib/MET/METplotpy/METplotpy-3.1.0/metplotpy -prepend-path PYTHONPATH /apps/contrib/MET/METplotpy/METplotpy-3.1.0 +prepend-path PATH /apps/contrib/MET/METplotpy/METplotpy-3.2.0/metplotpy/contributed +prepend-path PATH /apps/contrib/MET/METplotpy/METplotpy-3.2.0/metplotpy/plots/performance_diagram +prepend-path PATH /apps/contrib/MET/METplotpy/METplotpy-3.2.0/metplotpy/plots +prepend-path PATH /apps/contrib/MET/METplotpy/METplotpy-3.2.0/metplotpy +prepend-path PATH /apps/contrib/MET/METplotpy/METplotpy-3.2.0 +prepend-path PYTHONPATH /apps/contrib/MET/METplotpy/METplotpy-3.2.0/metplotpy/contributed +prepend-path PYTHONPATH /apps/contrib/MET/METplotpy/METplotpy-3.2.0/metplotpy/plots/performance_diagram +prepend-path PYTHONPATH /apps/contrib/MET/METplotpy/METplotpy-3.2.0/metplotpy/plots +prepend-path PYTHONPATH /apps/contrib/MET/METplotpy/METplotpy-3.2.0/metplotpy +prepend-path PYTHONPATH /apps/contrib/MET/METplotpy/METplotpy-3.2.0 diff --git a/internal/scripts/installation/modulefiles/3.2.0_smac-c5 b/internal/scripts/installation/modulefiles/3.2.0_smac-c5 new file mode 100644 index 00000000..f6cc6acf --- /dev/null +++ b/internal/scripts/installation/modulefiles/3.2.0_smac-c5 @@ -0,0 +1,31 @@ +#%Module###################################################################### +## +## METplotpy +## +proc ModulesHelp { } { + puts stderr "Sets up the paths and environment variables to use the METplotpy-3.2.0. + *** For help see the official MET webpage at http://www.dtcenter.org/met/users ***" +} + +module load compiler-rt/2024.0.1 +module load tbb/2021.11 +module load oclfpga/2024.0.0 +module load compiler/2024.0.1 +module load mkl/2024.0 +module load mpi/2021.11 + + +setenv METPLOTPY_SOURCE /atec/opt/atectest/METplotpy/METplotpy-3.2.0 +setenv METPLOTPY_BASE /atec/opt/atectest/METplotpy/METplotpy-3.2.0 + +prepend-path PATH /atec/opt/atectest/METplus/miniconda/miniconda3/envs/metplus_v6.1_py3.12/bin +prepend-path PATH /atec/opt/atectest/METplotpy/METplotpy-3.2.0/metplotpy/contributed +prepend-path PATH /atec/opt/atectest/METplotpy/METplotpy-3.2.0/metplotpy/plots/performance_diagram +prepend-path PATH /atec/opt/atectest/METplotpy/METplotpy-3.2.0/metplotpy/plots +prepend-path PATH /atec/opt/atectest/METplotpy/METplotpy-3.2.0/metplotpy +prepend-path PATH /atec/opt/atectest/METplotpy/METplotpy-3.2.0 +prepend-path PYTHONPATH /atec/opt/atectest/METplotpy/METplotpy-3.2.0/metplotpy/contributed +prepend-path PYTHONPATH /atec/opt/atectest/METplotpy/METplotpy-3.2.0/metplotpy/plots/performance_diagram +prepend-path PYTHONPATH /atec/opt/atectest/METplotpy/METplotpy-3.2.0/metplotpy/plots +prepend-path PYTHONPATH /atec/opt/atectest/METplotpy/METplotpy-3.2.0/metplotpy +prepend-path PYTHONPATH /atec/opt/atectest/METplotpy/METplotpy-3.2.0 diff --git a/internal/scripts/installation/modulefiles/3.2.0_wcoss2 b/internal/scripts/installation/modulefiles/3.2.0_wcoss2 new file mode 100644 index 00000000..d20fed4b --- /dev/null +++ b/internal/scripts/installation/modulefiles/3.2.0_wcoss2 @@ -0,0 +1,30 @@ +#%Module###################################################################### +## +## METplotpy +## +proc ModulesHelp { } { + puts stderr "Sets up the paths and environment variables to use the METplotpy-3.2.0. + *** For help see the official MET webpage at http://www.dtcenter.org/met/users ***" +} + +module reset +module use /apps/ops/para/libs/modulefiles/compiler/intel/19.1.3.304 +module load intel +module use /apps/dev/modulefiles/ +module load ve/evs/2.0 +module use /apps/sw_review/emc/METcalcpy/modulefiles +module load metcalcpy/3.2.0 + +setenv METPLOTPY_SOURCE /apps/sw_review/emc/METplotpy/3.2.0 +setenv METPLOTPY_BASE /apps/sw_review/emc/METplotpy/3.2.0 + +prepend-path PATH /apps/sw_review/emc/METplotpy/3.2.0/metplotpy/contributed +prepend-path PATH /apps/sw_review/emc/METplotpy/3.2.0/metplotpy/plots/performance_diagram +prepend-path PATH /apps/sw_review/emc/METplotpy/3.2.0/metplotpy/plots +prepend-path PATH /apps/sw_review/emc/METplotpy/3.2.0/metplotpy +prepend-path PATH /apps/sw_review/emc/METplotpy/3.2.0 +prepend-path PYTHONPATH /apps/sw_review/emc/METplotpy/3.2.0/metplotpy/contributed +prepend-path PYTHONPATH /apps/sw_review/emc/METplotpy/3.2.0/metplotpy/plots/performance_diagram +prepend-path PYTHONPATH /apps/sw_review/emc/METplotpy/3.2.0/metplotpy/plots +prepend-path PYTHONPATH /apps/sw_review/emc/METplotpy/3.2.0/metplotpy +prepend-path PYTHONPATH /apps/sw_review/emc/METplotpy/3.2.0 diff --git a/metplotpy/plots/bar/bar.py b/metplotpy/plots/bar/bar.py index a90da4c8..9d5ffa86 100644 --- a/metplotpy/plots/bar/bar.py +++ b/metplotpy/plots/bar/bar.py @@ -228,12 +228,7 @@ def _draw_series(self, ax: plt.Axes, series: BarSeries, idx: int) -> None: else: x_points = sorted(series.series_data[self.config_obj.indy_var].unique()) - base = np.arange(len(x_points)) - n_visible_series = sum(1 for s in self.series_list if s.plot_disp) - n = max(n_visible_series, 1) - width = constants.MPL_DEFAULT_BAR_WIDTH / n - offset = (idx - (n - 1) / 2.0) * width - x_locs = base + offset + x_locs, width = self._get_x_locs_and_width(x_points, idx) # add the plot ax.bar(x=x_locs, height=y_points, width=width, align='center', color=self.config_obj.colors_list[series.idx], diff --git a/metplotpy/plots/bar/bar_config.py b/metplotpy/plots/bar/bar_config.py index cd012ec8..844564f5 100644 --- a/metplotpy/plots/bar/bar_config.py +++ b/metplotpy/plots/bar/bar_config.py @@ -109,7 +109,6 @@ def __init__(self, parameters: dict) -> None: self.legend_orientation = 'v' else: self.legend_orientation = 'h' - self.legend_border_color = "black" self.show_legend = self._get_show_legend() diff --git a/metplotpy/plots/base_plot.py b/metplotpy/plots/base_plot.py index 75ec2ff6..2253e321 100644 --- a/metplotpy/plots/base_plot.py +++ b/metplotpy/plots/base_plot.py @@ -348,7 +348,7 @@ def get_array_dimensions(data): def _add_title(self, ax, font_properties): ax.set_title( - self.config_obj.title, + self.config_obj.title.replace('
', '\n'), fontproperties=font_properties, color=constants.DEFAULT_TITLE_COLOR, pad=28, @@ -390,7 +390,7 @@ def _add_legend(self, ax: plt.Axes, handles_and_labels=None) -> None: bbox_to_anchor=(self.config_obj.bbox_x, self.config_obj.bbox_y), loc='upper center', edgecolor=self.config_obj.legend_border_color, - frameon=True, + frameon=self.config_obj.draw_box, ncol=max(1, len(handles)) if orientation == "horizontal" else 1, fontsize=self.config_obj.legend_size, labelcolor="black" @@ -399,16 +399,34 @@ def _add_legend(self, ax: plt.Axes, handles_and_labels=None) -> None: frame = legend.get_frame() frame.set_linewidth(self.config_obj.legend_border_width) - def _add_xaxis(self, ax: plt.Axes, fontproperties: FontProperties) -> None: + def _add_xaxis(self, ax: plt.Axes, fontproperties: FontProperties, label=None, grid_on=None) -> None: """ Configures and adds x-axis to the plot """ - ax.set_xlabel(self.config_obj.xaxis, fontproperties=fontproperties, + if label is None: + label = self.config_obj.xaxis + + if grid_on is None: + grid_on = self.config_obj.grid_on + + ax.set_xlabel(label, fontproperties=fontproperties, labelpad=abs(self.config_obj.parameters['xlab_offset']) * constants.PIXELS_TO_POINTS) - xtick_locs = np.arange(len(self.config_obj.indy_label)) - ax.set_xticks(xtick_locs, self.config_obj.indy_label) + + if self.config_obj.indy_label: + # use the indices as tick locations + xtick_locs = np.arange(len(self.config_obj.indy_label)) + if self.config_obj.indy_vals: + # Use the actual numeric values from indy_vals as tick locations + try: + xtick_locs = [float(i) for i in self.config_obj.indy_vals] + # if they are not numeric, revert to using the indices + except ValueError: + pass + + ax.set_xticks(xtick_locs, self.config_obj.indy_label) + ax.tick_params(axis="x", direction="in", which="both", labelrotation=self.config_obj.x_tickangle) - if self.config_obj.grid_on: + if grid_on: ax.grid(True, which='major', axis='x', color=self.config_obj.blended_grid_col, linestyle='-', linewidth=self.config_obj.parameters['grid_lwd']) ax.set_axisbelow(True) @@ -416,11 +434,15 @@ def _add_xaxis(self, ax: plt.Axes, fontproperties: FontProperties) -> None: if self.config_obj.xaxis_reverse: ax.invert_xaxis() - def _add_yaxis(self, ax: plt.Axes, fontproperties: FontProperties) -> None: + def _add_yaxis(self, ax: plt.Axes, fontproperties: FontProperties, label=None, grid_on=None) -> None: """ Configures and adds y-axis to the plot """ - ax.set_ylabel(self.config_obj.yaxis_1, fontproperties=fontproperties, + if label is None: + label = self.config_obj.yaxis_1 + if grid_on is None: + grid_on = self.config_obj.grid_on + ax.set_ylabel(label, fontproperties=fontproperties, labelpad=abs(self.config_obj.parameters['ylab_offset']) * constants.PIXELS_TO_POINTS) ax.tick_params(axis="y", direction="in", which="both", labelrotation=self.config_obj.y_tickangle) @@ -429,7 +451,7 @@ def _add_yaxis(self, ax: plt.Axes, fontproperties: FontProperties) -> None: ax.set_ylim(self.config_obj.parameters['ylim']) # add grid lines if requested - if self.config_obj.grid_on: + if grid_on: ax.grid(True, which='major', axis='y', color=self.config_obj.blended_grid_col, linestyle='-', linewidth=self.config_obj.parameters['grid_lwd']) ax.set_axisbelow(True) @@ -459,13 +481,10 @@ def _add_x2axis(self, ax, n_stats, fontproperties: FontProperties) -> None: # this doesn't appear to be working to add ticks at the top ax_top.tick_params(axis="x", direction="in", labelrotation=self.config_obj.x2_tickangle) - def _add_y2axis(self, ax: plt.Axes, fontproperties: FontProperties): + def _add_y2axis(self, ax: plt.Axes, fontproperties: Union[FontProperties, None]): """ Adds y2-axis if needed """ - if not self.config_obj.parameters['list_stat_2']: - return None - ax_right = ax.twinx() ax_right.set_ylabel(self.config_obj.yaxis_2, fontproperties=fontproperties, labelpad=abs(self.config_obj.parameters['y2lab_offset']) * constants.PIXELS_TO_POINTS) @@ -519,3 +538,34 @@ def _add_lines(self, ax: plt.Axes, config_obj: Config, x_points_index: Union[lis msg = f"Vertical line with position {x_position} cannot be created." self.logger.warning(msg) print(f"WARNING: {msg}") + + def _get_x_locs_and_width(self, x_points, index): + try: + # Attempt to convert x_points to floats (handles numeric indy_vals) + # Threshold values (e.g., ">5.0") will raise a ValueError/TypeError + base = np.array([float(x) for x in x_points]) + + if len(base) > 1: + # Calculate the minimum spacing between numeric x-points + # to determine an appropriate bar width. + sorted_base = np.sort(base) + spacing = np.diff(sorted_base) + min_spacing = np.min(spacing) + # Ensure spacing is positive to avoid zero-width bars + if min_spacing <= 0: + min_spacing = 1.0 + else: + min_spacing = 1.0 + except (ValueError, TypeError): + # Fallback to integer indices for non-numeric data (e.g., thresholds) + base = np.arange(len(x_points)) + min_spacing = 1.0 + + n_visible_series = sum(1 for s in self.series_list if s.plot_disp) + n = max(n_visible_series, 1) + + # Scale width and offset by min_spacing to ensure bars fit within the numeric gaps + width = (min_spacing * constants.MPL_DEFAULT_BAR_WIDTH) / n + offset = (index - (n - 1) / 2.0) * width + x_locs = base + offset + return x_locs, width diff --git a/metplotpy/plots/box/box.py b/metplotpy/plots/box/box.py index fb359f25..0968938e 100644 --- a/metplotpy/plots/box/box.py +++ b/metplotpy/plots/box/box.py @@ -174,7 +174,7 @@ def _create_figure(self): self._add_caption(plt, wts_size_styles['caption']) ax_y2 = None - if wts_size_styles.get('y2lab'): + if wts_size_styles.get('y2lab') and self.config_obj.parameters['list_stat_2']: ax_y2 = self._add_y2axis(ax, wts_size_styles['y2lab']) n_stats, handles_and_labels, yaxis_min, yaxis_max = self._add_series(ax, ax_y2) @@ -223,7 +223,8 @@ def _draw_series(self, ax: plt.Axes, ax2, series: BoxSeries, idx: int): self.logger.info(f"Begin drawing the boxes on the plot for {series.series_name}: {datetime.now()}") # Group your 'stat_value' data by 'indy_var' categories first - data_to_plot, x_locs, width = self._get_data_to_plot_and_x_locs(series, idx) + data_to_plot = self._get_data_to_plot(series) + x_locs, width = self._get_x_locs_and_width(self.config_obj.indy_vals, idx) plot_ax = ax if ax2 and ax2.get_ylabel() in series.series_data.stat_name.values: @@ -257,13 +258,13 @@ def _draw_series(self, ax: plt.Axes, ax2, series: BoxSeries, idx: int): return boxplot['boxes'][0] + def _get_data_to_plot(self, series): + data_to_plot = [group_data for name, group_data in + series.series_data.groupby(self.config_obj.indy_var)['stat_value']] + return data_to_plot + def _get_data_to_plot_and_x_locs(self, series, idx): - base = np.arange(len(self.config_obj.indy_vals)) - n_visible_series = sum(1 for s in self.series_list if s.plot_disp) - n = max(n_visible_series, 1) - width = MPL_DEFAULT_BOX_WIDTH / n - offset = (idx - (n - 1) / 2.0) * width - x_locs = base + offset + x_locs, width = self._get_x_locs_and_width(self.config_obj.indy_vals, idx) data_to_plot = [group_data for name, group_data in series.series_data.groupby(self.config_obj.indy_var)['stat_value']] diff --git a/metplotpy/plots/box/box_config.py b/metplotpy/plots/box/box_config.py index dc788ceb..6591de0c 100644 --- a/metplotpy/plots/box/box_config.py +++ b/metplotpy/plots/box/box_config.py @@ -135,7 +135,6 @@ def __init__(self, parameters: dict) -> None: self.legend_orientation = 'v' else: self.legend_orientation = 'h' - self.legend_border_color = "black" # Default Matplotlib values for whiskers self.whis = 1.5 diff --git a/metplotpy/plots/config.py b/metplotpy/plots/config.py index fc3a8f08..67c84816 100644 --- a/metplotpy/plots/config.py +++ b/metplotpy/plots/config.py @@ -237,6 +237,7 @@ def __init__(self, parameters): # Don't draw a box around legend labels unless an 'o' is set legend_box = self.get_config_value('legend_box').lower() self.draw_box = legend_box == 'o' + self.legend_border_color = "black" # These are the inner keys to the series_val setting, and # they represent the series variables of @@ -562,12 +563,43 @@ def _get_markers(self): # markers is the matplotlib symbol: .,o, ^, d, H, or s markers_list.append(marker) else: - # markers are indicated by name: small circle, circle, triangle, - # diamond, hexagon, square + # markers are indicated by name or PCH number markers_list.append(constants.PCH_TO_MATPLOTLIB_MARKER[marker.lower()]) markers_list_ordered = self.create_list_by_series_ordering(list(markers_list)) return markers_list_ordered + def _get_markers_size(self) -> list: + """Convert marker names from the config file into matplotlib marker sizes. + Use the default marker size if the marker size is not a supported value. + + Args: + + Returns: + markers_size: a list of the integers that define the size of the markers + or None if the marker size is not a supported value. + """ + markers = self.get_config_value('series_symbols') + markers_size = [] + for marker in markers: + markers_size.append(constants.PCH_TO_MATPLOTLIB_MARKER_SIZE.get(marker)) + + return self.create_list_by_series_ordering(markers_size) + + def _get_markers_open(self) -> list: + """Parse info from markers to determine if they should be open or filled. + + Args: + + Returns: + a list of the boolean values to indicate if the marker should be open or filled. + """ + markers = self.get_config_value('series_symbols') + markers_open = [] + for marker in markers: + markers_open.append('open' in marker.lower() or 'small circle' in marker.lower()) + + return self.create_list_by_series_ordering(markers_open) + def _get_linewidths(self) -> Union[list, None]: """ Retrieve all the linewidths from the configuration file, if not specified in any config file, use the default values of 2 @@ -968,3 +1000,21 @@ def _config_compare_lists_to_num_series(self, lists_to_check: dict) -> list: raise ValueError(msg) self.logger.info(f"Config consistency check completed successfully: {datetime.now()}") + + def _get_mode(self) -> list: + """Retrieve all the modes. Convert mode names from the config file into + strings that will determine which matplotlib settings to use. + 'both' - use both lines and markers + 'points' - use linestyle='None' to show only markers + 'lines' - use marker=None to show only lines + + Args: + + Returns: + modes: a list of strings to determine matplotlib settings to use + """ + modes = self.get_config_value('series_type') + mode_list = [] + for mode in modes: + mode_list.append(constants.SERIES_TYPE_TO_PLOT_MODE.get(mode, 'lines+markers')) + return self.create_list_by_series_ordering(mode_list) diff --git a/metplotpy/plots/constants.py b/metplotpy/plots/constants.py index 5b91b679..fcf716ef 100644 --- a/metplotpy/plots/constants.py +++ b/metplotpy/plots/constants.py @@ -66,29 +66,46 @@ AVAILABLE_MARKERS_LIST = ["o", "^", "s", "d", "H", ".", "h"] -PCH_TO_MATPLOTLIB_MARKER = {'20': '.', '19': 'o', '17': '^', '1': 'H', - '18': 'd', '15': 's', 'small circle': '.', - 'circle': 'o', 'square': 's', - 'triangle': '^', 'rhombus': 'd', 'ring': 'h'} +PCH_TO_MATPLOTLIB_MARKER = { + # R plotting characters + '20': '.', + '19': 'o', + '17': '^', + '1': 'H', + '18': 'd', + '15': 's', + 'small circle': 'o', # changed from . + 'circle': 'o', + 'square': 's', + 'triangle': '^', + 'rhombus': 'd', + 'ring': 'h', + # plotly marker strings + 'circle-open': 'o', # H? + 'triangle-up': '^', + 'diamond': 'd', + 'hexagon': 'h', + 'asterisk-open': '*', # .? +} # approximated from plotly marker size to matplotlib marker size PCH_TO_MATPLOTLIB_MARKER_SIZE = {'.': 14, 'o': 36, 's': 20, '^': 36, 'd': 20, 'H': 28} -XAXIS_ORIENTATION = {0: 0, 1: 0, 2: 270, 3: 270} -YAXIS_ORIENTATION = {0: -90, 1: 0, 2: 0, 3: -90} - -# approximated from plotly marker size to matplotlib marker size -PCH_TO_MATPLOTLIB_MARKER_SIZE = {'.': 14, 'o': 36, 's': 20, '^': 36, 'd': 20, 'H': 28} +SERIES_TYPE_TO_PLOT_MODE = {'b': 'lines+markers', 'p': 'markers', 'l': 'lines'} -# used for tick angles XAXIS_ORIENTATION = {0: 0, 1: 0, 2: 270, 3: 270} YAXIS_ORIENTATION = {0: -90, 1: 0, 2: 0, 3: -90} # Caption weights supported in Matplotlib are normal, italic and oblique. # Map these onto the MetViewer requested values of 1 (normal), 2 (bold), # 3 (italic), 4 (bold italic), and 5 (symbol) using a dictionary -MV_TO_MPL_CAPTION_STYLE = {1:('normal', 'normal'), 2:('normal','bold'), 3:('italic', 'normal') - , 4:('italic', 'bold'),5:('oblique','normal')} +MV_TO_MPL_CAPTION_STYLE = { + 1: ('normal', 'normal'), + 2: ('normal','bold'), + 3: ('italic', 'normal'), + 4: ('italic', 'bold'), + 5: ('oblique','normal'), +} # Matplotlib constants MPL_FONT_SIZE_DEFAULT = 11 diff --git a/metplotpy/plots/ens_ss/ens_ss.py b/metplotpy/plots/ens_ss/ens_ss.py index a037ab29..8ae2e960 100644 --- a/metplotpy/plots/ens_ss/ens_ss.py +++ b/metplotpy/plots/ens_ss/ens_ss.py @@ -21,16 +21,14 @@ import numpy as np import pandas as pd -import plotly.graph_objects as go -from plotly.subplots import make_subplots -from plotly.graph_objects import Figure +from matplotlib import pyplot as plt +from matplotlib import ticker from metcalcpy.event_equalize import event_equalize -from metplotpy.plots.constants_plotly import PLOTLY_AXIS_LINE_COLOR, PLOTLY_AXIS_LINE_WIDTH, PLOTLY_PAPER_BGCOOR from metplotpy.plots.ens_ss.ens_ss_config import EnsSsConfig from metplotpy.plots.ens_ss.ens_ss_series import EnsSsSeries -from metplotpy.plots.base_plot_plotly import BasePlot -import metplotpy.plots.util_plotly as util +from metplotpy.plots.base_plot import BasePlot +import metplotpy.plots.util as util import metcalcpy.util.utils as utils @@ -62,21 +60,13 @@ def __init__(self, parameters: dict) -> None: self.logger.info(f"Start Ens_ss plot: {datetime.now()}") # Check that we have all the necessary settings for each series - self.logger.info(f"Consistency checking of config settings for colors, " - f"legends, etc.{datetime.now()}") - is_config_consistent = self.config_obj._config_consistency_check() - self.logger.info(f"Finished consistency checking of config settings for colors, " - f"legends, etc.{datetime.now()}") - if not is_config_consistent: - value_error_msg = ("ValueError: The number of series defined by " - "series_val_1 and " - "derived curves is inconsistent with the number of " - "settings required for describing each series. Please " - "check the number of your configuration file's " - "plot_i, plot_disp, series_order, user_legend, show_legend and " - "colors settings.") - self.logger.error(value_error_msg) - raise ValueError(value_error_msg) + self.config_obj.config_consistency_check() + + # if plotting points, add show_legend True in between each show legend value + # do this after the consistency check to ensure the number of + # show_legend values matches the number of series before adding to the list + if self.config_obj.ensss_pts_disp: + self.config_obj.show_legend = [val for item in self.config_obj.show_legend for val in (item, 1)] # Read in input data, location specified in config file self.logger.info(f"Begin reading input data: {datetime.now()}") @@ -94,11 +84,6 @@ def __init__(self, parameters: dict) -> None: # line width, and criteria needed to subset the input dataframe. self.series_list = self._create_series(self.input_df) - # create figure - # pylint:disable=assignment-from-no-return - # Need to have a self.figure that we can pass along to - # the methods in base_plot.py (BasePlot class methods) to - # create binary versions of the plot. self._create_figure() def _perform_event_equalization(self): @@ -204,14 +189,13 @@ def _create_series(self, input_data): for i, name in enumerate(self.config_obj.get_series_y(1)): series_obj = EnsSsSeries(self.config_obj, i, input_data, series_list, name) series_list.append(series_obj) - if self.config_obj.ensss_pts_disp is True: + if self.config_obj.ensss_pts_disp: series_list.append(series_obj) # reorder series series_list = self.config_obj.create_list_by_series_ordering(series_list) - self.logger.info(f"Finished creating series objects:" - f" {datetime.now()}") + self.logger.info(f"Finished creating series objects: {datetime.now()}") return series_list @@ -223,276 +207,94 @@ def _create_figure(self): self.logger.info(f"Begin creating the figure: {datetime.now()}") # create and draw the plot - self.figure = self._create_layout() - self._add_xaxis() - self._add_yaxis() + fig, ax = plt.subplots(figsize=(self.config_obj.plot_width, self.config_obj.plot_height)) - self._add_y2axis() - self._add_legend() + wts_size_styles = self.get_weights_size_styles() - # add series lines - i = 0 - counter = 1 - if self.config_obj.ensss_pts_disp is True: - counter = 2 - # for series in self.series_list: - while i < len(self.series_list): + self._add_title(ax, wts_size_styles['title']) + self._add_caption(plt, wts_size_styles['caption']) - # Don't generate the plot for this series if - # it isn't requested (as set in the config file) - if self.series_list[i].plot_disp: - self._draw_series(self.series_list[i]) - i = i + counter + ax_y2 = None + if self.config_obj.ensss_pts_disp: + ax_y2 = self._add_y2axis(ax, wts_size_styles['y2lab']) - # add custom lines - if len(self.series_list) > 0: - self._add_lines(self.config_obj) - - # apply y axis limits - self._yaxis_limits() - self._y2axis_limits() + # format large numbers like 3 million as 3M + ax_y2.yaxis.set_major_formatter(ticker.EngFormatter()) - self.logger.info(f"Finished creating the figure: {datetime.now()}") + handles_and_labels = self._add_series(ax, ax_y2) - def _add_y2axis(self) -> None: - """ - Adds y2-axis if needed - """ - if self.config_obj.ensss_pts_disp is True: - self.figure.update_yaxes(title_text= - util.apply_weight_style(self.config_obj.yaxis_2, - self.config_obj.parameters['y2lab_weight'] - ), - secondary_y=True, - linecolor=PLOTLY_AXIS_LINE_COLOR, - linewidth=PLOTLY_AXIS_LINE_WIDTH, - showgrid=False, - zeroline=False, - ticks="inside", - title_font={ - 'size': self.config_obj.y2_title_font_size - }, - title_standoff=abs(self.config_obj.parameters['y2lab_offset']), - tickangle=self.config_obj.y2_tickangle, - tickfont={'size': self.config_obj.y2_tickfont_size} - ) - - def _y2axis_limits(self) -> None: - """ - Apply limits on y2 axis if needed - """ - if len(self.config_obj.parameters['y2lim']) > 0: - self.figure.update_layout(yaxis2={'range': [self.config_obj.parameters['y2lim'][0], - self.config_obj.parameters['y2lim'][1]], - 'autorange': False}) + self._add_xaxis(ax, wts_size_styles['xlab']) + self._add_yaxis(ax, wts_size_styles['ylab']) - def _draw_series(self, series: EnsSsSeries) -> None: - """ - Draws the formatted line on the plot + self._add_legend(ax, handles_and_labels) - :param series: EnsSs series object with data and parameters - """ - self.logger.info(f"Begin drawing the series on the plot:" - f" {datetime.now()}") + self._add_custom_lines(ax) - # add the plot - self.figure.add_trace( - go.Scatter(x=series.series_points['spread_skill'], - y=series.series_points['mse'], - showlegend=self.config_obj.show_legend[series.idx] == 1, - mode=self.config_obj.mode[series.idx], - textposition="top right", - name=self.config_obj.user_legends[series.idx], - line={'color': self.config_obj.colors_list[series.idx], - 'width': self.config_obj.linewidth_list[series.idx], - 'dash': self.config_obj.linestyles_list[series.idx]}, - marker_symbol=self.config_obj.marker_list[series.idx], - marker_color=self.config_obj.colors_list[series.idx], - marker_line_color=self.config_obj.colors_list[series.idx], - marker_size=self.config_obj.marker_size[series.idx] - ), - secondary_y=series.y_axis != 1 - ) - - # add PTS - if self.config_obj.ensss_pts_disp is True: - self.figure.add_trace( - go.Scatter(x=series.series_points['spread_skill'], - y=series.series_points['pts'], - showlegend=True, - mode=self.config_obj.mode[series.idx + 1], - textposition="top right", - name=self.config_obj.user_legends[series.idx + 1], - line={'color': self.config_obj.colors_list[series.idx + 1], - 'width': self.config_obj.linewidth_list[series.idx + 1], - 'dash': self.config_obj.linestyles_list[series.idx + 1]}, - marker_symbol=self.config_obj.marker_list[series.idx + 1], - marker_color=self.config_obj.colors_list[series.idx + 1], - marker_line_color=self.config_obj.colors_list[series.idx + 1], - marker_size=self.config_obj.marker_size[series.idx + 1] - ), - secondary_y=True - ) - - self.logger.info(f"Finished drawing the series on the plot:" - f" {datetime.now()}") - - def _create_layout(self) -> Figure: - """ - Creates a new layout based on the properties from the config file - including plots size, annotation and title + plt.tight_layout() - :return: Figure object - """ - # create annotation - annotation = [ - {'text': util.apply_weight_style(self.config_obj.parameters['plot_caption'], - self.config_obj.parameters['caption_weight']), - 'align': 'left', - 'showarrow': False, - 'xref': 'paper', - 'yref': 'paper', - 'x': self.config_obj.parameters['caption_align'], - 'y': self.config_obj.caption_offset, - 'font': { - 'size': self.config_obj.caption_size, - 'color': self.config_obj.parameters['caption_col'] - } - }] - # create title - title = {'text': util.apply_weight_style(self.config_obj.title, - self.config_obj.parameters['title_weight']), - 'font': { - 'size': self.config_obj.title_font_size, - }, - 'y': self.config_obj.title_offset, - 'x': self.config_obj.parameters['title_align'], - 'xanchor': 'center', - 'xref': 'paper' - } - - # create a layout and allow y2 axis - fig = make_subplots(specs=[[{"secondary_y": True}]]) - - # add size, annotation, title - fig.update_layout( - width=self.config_obj.plot_width, - height=self.config_obj.plot_height, - margin=self.config_obj.plot_margins, - paper_bgcolor=PLOTLY_PAPER_BGCOOR, - annotations=annotation, - title=title, - plot_bgcolor=PLOTLY_PAPER_BGCOOR - ) - return fig - - def _add_xaxis(self) -> None: - """ - Configures and adds x-axis to the plot - """ - self.figure.update_xaxes(title_text=self.config_obj.xaxis, - linecolor=PLOTLY_AXIS_LINE_COLOR, - linewidth=PLOTLY_AXIS_LINE_WIDTH, - showgrid=self.config_obj.grid_on, - ticks="inside", - zeroline=False, - gridwidth=self.config_obj.parameters['grid_lwd'], - gridcolor=self.config_obj.blended_grid_col, - automargin=True, - title_font={ - 'size': self.config_obj.x_title_font_size - }, - title_standoff=abs(self.config_obj.parameters['xlab_offset']), - tickangle=self.config_obj.x_tickangle, - tickfont={'size': self.config_obj.x_tickfont_size} - ) - # reverse xaxis if needed - if self.config_obj.xaxis_reverse is True: - self.figure.update_xaxes(autorange="reversed") - - def _add_yaxis(self) -> None: - """ - Configures and adds y-axis to the plot - """ - self.figure.update_yaxes(title_text= - util.apply_weight_style(self.config_obj.yaxis_1, - self.config_obj.parameters['ylab_weight']), - secondary_y=False, - linecolor=PLOTLY_AXIS_LINE_COLOR, - linewidth=PLOTLY_AXIS_LINE_WIDTH, - showgrid=self.config_obj.grid_on, - zeroline=False, - ticks="inside", - gridwidth=self.config_obj.parameters['grid_lwd'], - gridcolor=self.config_obj.blended_grid_col, - automargin=True, - title_font={ - 'size': self.config_obj.y_title_font_size - }, - title_standoff=abs(self.config_obj.parameters['ylab_offset']), - tickangle=self.config_obj.y_tickangle, - tickfont={'size': self.config_obj.y_tickfont_size} - ) - - def _add_legend(self) -> None: - """ - Creates a plot legend based on the properties from the config file - and attaches it to the initial Figure - """ - self.figure.update_layout(legend={'x': self.config_obj.bbox_x, - 'y': self.config_obj.bbox_y, - 'xanchor': 'center', - 'yanchor': 'top', - 'bordercolor': self.config_obj.legend_border_color, - 'borderwidth': self.config_obj.legend_border_width, - 'orientation': self.config_obj.legend_orientation, - 'font': { - 'size': self.config_obj.legend_size, - 'color': "black" - } - }) - - def _yaxis_limits(self) -> None: - """ - Apply limits on y2 axis if needed - """ - if len(self.config_obj.parameters['ylim']) > 0: - self.figure.update_layout(yaxis={'range': [self.config_obj.parameters['ylim'][0], - self.config_obj.parameters['ylim'][1]], - 'autorange': False}) + self.logger.info(f"Finished creating the figure: {datetime.now()}") - def remove_file(self): - """ - Removes previously made image file . Invoked by the parent class before self.output_file - attribute can be created, but overridden here. - """ + def _add_custom_lines(self, ax): + # add custom lines if lines are defined in config + if len(self.series_list) > 0: + self._add_lines(ax, self.config_obj) - super().remove_file() - self._remove_html() + def _add_series(self, ax, ax2): + handles_and_labels = [] + i = 0 + counter = 1 + if self.config_obj.ensss_pts_disp: + counter = 2 - def _remove_html(self) -> None: - """ - Removes previously made HTML file. - """ + # for series in self.series_list: + for idx, series in enumerate(self.series_list): + if not series.plot_disp: + continue - base_name, _ = os.path.splitext(self.get_config_value('plot_filename')) - html_name = f"{base_name}.html" + is_points_plot = self.config_obj.ensss_pts_disp and idx % 2 == 1 + plot_ax = ax2 if is_points_plot else ax + handle = self._draw_series(plot_ax, series, idx, is_points_plot) + handles_and_labels.append((handle, handle.get_label())) - # remove the old file if it exist - if os.path.exists(html_name): - os.remove(html_name) + return handles_and_labels - def write_html(self) -> None: + def _draw_series(self, ax, series: EnsSsSeries, index: int, is_points_plot: bool) -> None: """ - Is needed - creates and saves the html representation of the plot WITHOUT Plotly.js + Draws the formatted line on the plot + + :param series: EnsSs series object with data and parameters """ - if self.config_obj.create_html is True: - # construct the file name from plot_filename - base_name, _ = os.path.splitext(self.get_config_value('plot_filename')) - html_name = f"{base_name}.html" + self.logger.info(f"Begin drawing the series on the plot: {datetime.now()}") - # save html - self.figure.write_html(html_name, include_plotlyjs=False) + # add the plot + x = series.series_points['spread_skill'] + y = series.series_points['pts'] if is_points_plot else series.series_points['mse'] + + # set arguments for the plot + plot_args = self._get_plot_args(index) + plot_obj = ax.plot(x, y, **plot_args) + + self.logger.info(f"Finished drawing the series on the plot: {datetime.now()}") + return plot_obj[0] + + def _get_plot_args(self, idx): + plot_mode = self.config_obj.mode[idx] + marker = self.config_obj.marker_list[idx] if 'markers' in plot_mode else None + line_style = self.config_obj.linestyles_list[idx] if 'lines' in plot_mode else 'None' + + plot_args = { + 'marker': marker, + 'markersize': self.config_obj.marker_size[idx], + 'label': self.config_obj.user_legends[idx], + 'color': self.config_obj.colors_list[idx], + 'linewidth': self.config_obj.linewidth_list[idx], + 'linestyle': line_style, + } + if self.config_obj.marker_open_list[idx]: + plot_args['markerfacecolor'] = 'none' + plot_args['markeredgecolor'] = self.config_obj.colors_list[idx] + + return plot_args def write_output_file(self) -> None: """ @@ -504,48 +306,49 @@ def write_output_file(self) -> None: # (the input data file) except replace the .data # extension with .points1 extension # otherwise use points_path path + if not self.config_obj.dump_points_1: + return match = re.match(r'(.*)(.data)', self.config_obj.parameters['stat_input']) - if self.config_obj.dump_points_1 is True and match: - i = 0 - counter = 1 - if self.config_obj.ensss_pts_disp is True: - counter = 2 - - filename = match.group(1) - if self.config_obj.points_path is not None: - # get the file name - path = filename.split(os.path.sep) - if len(path) > 0: - filename = path[-1] - else: - filename = '.' + os.path.sep - filename = self.config_obj.points_path + os.path.sep + filename - # else: - # filename = 'points' + if not match: + return - filename = filename + '.points1' - os.makedirs(os.path.dirname(filename), exist_ok=True) + i = 0 + counter = 1 + if self.config_obj.ensss_pts_disp is True: + counter = 2 - with open(filename, 'w') as file: + filename = match.group(1) + if self.config_obj.points_path is not None: + # get the file name + path = filename.split(os.path.sep) + if len(path) > 0: + filename = path[-1] + else: + filename = '.' + os.path.sep + filename = self.config_obj.points_path + os.path.sep + filename + + filename = filename + '.points1' + os.makedirs(os.path.dirname(filename), exist_ok=True) + + with open(filename, 'w') as file: + while i < len(self.series_list): + file.writelines( + map("{}\t{}\n".format, + [round(num, 6) for num in self.series_list[i].series_points['spread_skill']], + [round(num, 6) for num in self.series_list[i].series_points['mse']])) + i = i + counter + # print PTS values + if self.config_obj.ensss_pts_disp is True: + i = 0 + file.write('#PTS\n') while i < len(self.series_list): file.writelines( map("{}\t{}\n".format, [round(num, 6) for num in self.series_list[i].series_points['spread_skill']], - [round(num, 6) for num in self.series_list[i].series_points['mse']])) + [round(num, 6) for num in self.series_list[i].series_points['pts']]) + ) i = i + counter - # print PTS values - if self.config_obj.ensss_pts_disp is True: - i = 0 - file.write('#PTS\n') - while i < len(self.series_list): - file.writelines( - map("{}\t{}\n".format, - [round(num, 6) for num in self.series_list[i].series_points['spread_skill']], - [round(num, 6) for num in self.series_list[i].series_points['pts']]) - ) - i = i + counter - file.close() def main(config_filename=None): diff --git a/metplotpy/plots/ens_ss/ens_ss_config.py b/metplotpy/plots/ens_ss/ens_ss_config.py index 298428c5..ce776c8b 100644 --- a/metplotpy/plots/ens_ss/ens_ss_config.py +++ b/metplotpy/plots/ens_ss/ens_ss_config.py @@ -17,9 +17,8 @@ import itertools -from ..config_plotly import Config -from .. import constants_plotly as constants -from .. import util_plotly as util +from ..config import Config +from .. import constants import metcalcpy.util.utils as utils @@ -59,7 +58,7 @@ def __init__(self, parameters: dict) -> None: ############################################## # title parameters self.title_font_size = self.parameters['title_size'] * constants.DEFAULT_TITLE_FONT_SIZE - self.title_offset = self.parameters['title_offset'] * constants.DEFAULT_TITLE_OFFSET + self.title_offset = 1.0 + abs(self.parameters['title_offset']) * constants.DEFAULT_TITLE_OFFSET self.y_title_font_size = self.parameters['ylab_size'] + constants.DEFAULT_TITLE_FONTSIZE ############################################## @@ -86,7 +85,6 @@ def __init__(self, parameters: dict) -> None: if self.x_tickangle in constants.XAXIS_ORIENTATION.keys(): self.x_tickangle = constants.XAXIS_ORIENTATION[self.x_tickangle] self.x_tickfont_size = self.parameters['xtlab_size'] + constants.DEFAULT_TITLE_FONTSIZE - self.xaxis = util.apply_weight_style(self.xaxis, self.parameters['xlab_weight']) ############################################## # x2-axis parameters @@ -104,6 +102,7 @@ def __init__(self, parameters: dict) -> None: self.plot_disp = self._get_plot_disp() self.colors_list = self._get_colors() self.marker_list = self._get_markers() + self.marker_open_list = self._get_markers_open() self.marker_size = self._get_markers_size() self.mode = self._get_mode() self.linewidth_list = self._get_linewidths() @@ -178,104 +177,26 @@ def _get_plot_disp(self) -> list: return self.create_list_by_series_ordering(plot_display_bools) - def _get_mode(self) -> list: - """ - Retrieve all the modes. Convert mode names from - the config file into plotly python's mode names. - - Args: - - Returns: - markers: a list of the plotly markers - """ - modes = self.get_config_value('series_type') - mode_list = [] - for mode in modes: - if mode in constants.TYPE_TO_PLOTLY_MODE.keys(): - # the recognized plotly marker names: - # circle-open (for small circle), circle, triangle-up, - # square, diamond, or hexagon - mode_list.append(constants.TYPE_TO_PLOTLY_MODE[mode]) - else: - mode_list.append('ens_sss+markers') - return self.create_list_by_series_ordering(mode_list) + def config_consistency_check(self) -> None: + """Checks that the number of settings defined for + plot_disp, series_ordering, colors_list, user_legends, and show_legend + are consistent with number of series. - def _get_markers(self) -> list: + @raises ValueError if any of settings are inconsistent with the + number of series (as defined by the cross product of the model + and vx_mask defined in the series_val_1 setting) """ - Retrieve all the markers. Convert marker names from - the config file into plotly python's marker names. - - Args: - - Returns: - markers: a list of the plotly markers - """ - markers = self.get_config_value('series_symbols') - markers_list = [] - for marker in markers: - if marker in constants.AVAILABLE_PLOTLY_MARKERS_LIST: - # the recognized plotly marker names: - # circle-open (for small circle), circle, triangle-up, - # square, diamond, or hexagon - markers_list.append(marker) - else: - markers_list.append(constants.PCH_TO_PLOTLY_MARKER[marker]) - return self.create_list_by_series_ordering(markers_list) - - def _get_markers_size(self) -> list: - """ - Retrieve all the markers. Convert marker names from - the config file into plotly python's marker names. - - Args: - - Returns: - markers: a list of the plotly markers - """ - markers = self.get_config_value('series_symbols') - markers_size = [] - for marker in markers: - if marker in constants.AVAILABLE_PLOTLY_MARKERS_LIST: - markers_size.append(marker) - else: - markers_size.append(constants.PCH_TO_PLOTLY_MARKER_SIZE[marker]) - - return self.create_list_by_series_ordering(markers_size) - - def _config_consistency_check(self) -> bool: - """ - Checks that the number of settings defined for plot_ci, - plot_disp, series_order, user_legend colors, and series_symbols - are consistent. - - Args: - - Returns: - True if the number of settings for each of the above - settings is consistent with the number of - series (as defined by the cross product of the model - and vx_mask defined in the series_val_1 setting) - - """ - # Determine the number of series based on the number of - # permutations from the series_var setting in the - # config file - - # Numbers of values for other settings for series - num_plot_disp = len(self.plot_disp) - num_markers = len(self.marker_list) - num_series_ord = len(self.series_ordering) - num_colors = len(self.colors_list) - num_legends = len(self.user_legends) - num_line_widths = len(self.linewidth_list) - num_linestyles = len(self.linestyles_list) - status = False - - if self.num_series == num_plot_disp == \ - num_markers == num_series_ord == num_colors \ - == num_legends == num_line_widths == num_linestyles: - status = True - return status + lists_to_check = { + "plot_disp": self.plot_disp, + "marker_list": self.marker_list, + "series_ordering": self.series_ordering, + "colors_list": self.colors_list, + "user_legends": self.user_legends, + "linewidth_list": self.linewidth_list, + "linestyles_list": self.linestyles_list, + "show_legend": self.show_legend, + } + self._config_compare_lists_to_num_series(lists_to_check) def _get_user_legends(self, legend_label_type: str = '') -> list: """ @@ -302,7 +223,7 @@ def _get_user_legends(self, legend_label_type: str = '') -> list: ser_components_copy = ser_components.copy() ser_components_copy.append('MSE') legend_list.append(' '.join(map(str, ser_components_copy))) - if self.ensss_pts_disp is True: + if self.ensss_pts_disp: ser_components.append('#PTS') legend_list.append(' '.join(map(str, ser_components))) else: @@ -322,8 +243,8 @@ def get_series_y(self, axis: int) -> list: for field in reversed(list(all_fields_values_orig.keys())): all_fields_values[field] = all_fields_values_orig.get(field) - if self._get_fcst_vars(axis): - all_fields_values['fcst_var'] = list(self._get_fcst_vars(axis).keys()) + if self.get_fcst_vars_keys(axis): + all_fields_values['fcst_var'] = self.get_fcst_vars_keys(axis) return utils.create_permutations_mv(all_fields_values, 0) @@ -355,10 +276,7 @@ def calculate_number_of_series(self) -> int: """ # Retrieve the lists from the series_val_1 dictionary series_vals_list = self.series_vals_1.copy() - if isinstance(self.fcst_var_val_1, list) is True: - fcst_vals = self.fcst_var_val_1 - elif isinstance(self.fcst_var_val_1, dict) is True: - fcst_vals = list(self.fcst_var_val_1.values()) + fcst_vals = list(self.fcst_var_val_1.values()) fcst_vals_flat = [item for sublist in fcst_vals for item in sublist] series_vals_list.append(fcst_vals_flat) @@ -367,26 +285,7 @@ def calculate_number_of_series(self) -> int: # fcst_var_val values. permutations = list(itertools.product(*series_vals_list)) total = len(permutations) - if self.ensss_pts_disp is True: + if self.ensss_pts_disp: total = total * 2 return total - - def _get_linestyles(self) -> list: - """ - Retrieve all the line styles. Convert line style names from - the config file into plotly python's line style names. - - Args: - - Returns: - line_styles: a list of the plotly line styles - """ - line_styles = self.get_config_value('series_line_style') - line_style_list = [] - for line_style in line_styles: - if line_style in constants.LINE_STYLE_TO_PLOTLY_DASH.keys(): - line_style_list.append(constants.LINE_STYLE_TO_PLOTLY_DASH[line_style]) - else: - line_style_list.append(None) - return self.create_list_by_series_ordering(line_style_list) diff --git a/metplotpy/plots/ens_ss/ens_ss_series.py b/metplotpy/plots/ens_ss/ens_ss_series.py index 28d599c6..348e744f 100644 --- a/metplotpy/plots/ens_ss/ens_ss_series.py +++ b/metplotpy/plots/ens_ss/ens_ss_series.py @@ -19,7 +19,7 @@ import numpy as np import metcalcpy.util.utils as utils -import metplotpy.plots.util_plotly as util +import metplotpy.plots.util as util from .. import GROUP_SEPARATOR from ..series import Series diff --git a/metplotpy/plots/histogram/hist.py b/metplotpy/plots/histogram/hist.py index 8e4e9e88..c38d6ae5 100644 --- a/metplotpy/plots/histogram/hist.py +++ b/metplotpy/plots/histogram/hist.py @@ -256,6 +256,8 @@ def _create_figure(self): # use x points from first series if indy label is not set if not self.config_obj.indy_label: self.config_obj.indy_label = self._get_x_points(self.series_list[0]) + if not self.config_obj.indy_vals: + self.config_obj.indy_vals = self.config_obj.indy_label self._add_xaxis(ax, wts_size_styles['xlab']) #if self._get_dtick(): @@ -279,12 +281,7 @@ def _draw_series(self, ax: plt.Axes, series: HistSeries, idx: int) -> None: x_points = self._get_x_points(series) y_points = series.series_points - base = np.arange(len(x_points)) - n_visible_series = sum(1 for s in self.series_list if s.plot_disp) - n = max(n_visible_series, 1) - width = MPL_DEFAULT_BAR_WIDTH / n - offset = (idx - (n - 1) / 2.0) * width - x_locs = base + offset + x_locs, width = self._get_x_locs_and_width(x_points, idx) ax.bar( x=x_locs, height=y_points, width=width, align='center', diff --git a/metplotpy/plots/histogram/hist_config.py b/metplotpy/plots/histogram/hist_config.py index 932fe4ee..fd1fecdb 100644 --- a/metplotpy/plots/histogram/hist_config.py +++ b/metplotpy/plots/histogram/hist_config.py @@ -97,7 +97,6 @@ def __init__(self, parameters: dict) -> None: self.legend_orientation = 'v' else: self.legend_orientation = 'h' - self.legend_border_color = "black" self.normalized_histogram = self._get_bool('normalized_histogram') self.fixed_vars_vals_input = self.parameters['fixed_vars_vals_input'] diff --git a/metplotpy/plots/reliability_diagram/reliability.py b/metplotpy/plots/reliability_diagram/reliability.py index aad7eab9..022cf420 100644 --- a/metplotpy/plots/reliability_diagram/reliability.py +++ b/metplotpy/plots/reliability_diagram/reliability.py @@ -22,13 +22,12 @@ import numpy as np import pandas as pd -import plotly.graph_objects as go -from plotly.subplots import make_subplots -from plotly.graph_objects import Figure +from matplotlib import pyplot as plt +from matplotlib import ticker -from metplotpy.plots.constants_plotly import PLOTLY_AXIS_LINE_COLOR, PLOTLY_AXIS_LINE_WIDTH, PLOTLY_PAPER_BGCOOR -from metplotpy.plots.base_plot_plotly import BasePlot -from metplotpy.plots import util_plotly as util +from metplotpy.plots.base_plot import BasePlot +from metplotpy.plots import util +from metplotpy.plots.constants import MPL_DEFAULT_BAR_WIDTH from metplotpy.plots.reliability_diagram.reliability_config import ReliabilityConfig from metplotpy.plots.reliability_diagram.reliability_series import ReliabilitySeries @@ -58,16 +57,7 @@ def __init__(self, parameters: dict) -> None: self.logger.info(f"Begin reliability diagram: {datetime.now()}") # Check that we have all the necessary settings for each series - is_config_consistent = self.config_obj._config_consistency_check() - if not is_config_consistent: - value_error_msg = ("The number of series defined by series_val_1 " - " inconsistent with the number of settings" - " required for describing each series. Please check" - " the number of your configuration file's plot_i," - " plot_disp, series_order, user_legend," - " colors, show_legend and series_symbols settings.") - self.logger.error(f"ValueError:{value_error_msg}") - raise ValueError(value_error_msg) + self.config_obj.config_consistency_check() # Read in input data, location specified in config file self.input_df = self._read_input_data() @@ -78,11 +68,6 @@ def __init__(self, parameters: dict) -> None: # line width, and criteria needed to subset the input dataframe. self.series_list = self._create_series(self.input_df) - # create figure - # pylint:disable=assignment-from-no-return - # Need to have a self.figure that we can pass along to - # the methods in base_plot.py (BasePlot class methods) to - # create binary versions of the plot. self._create_figure() def __repr__(self): @@ -151,23 +136,60 @@ def _create_figure(self): """ # create and draw the plot - self.logger.info(f"Begin creating the lines on the reliability plot: " - f"{datetime.now()}") + self.logger.info(f"Begin creating the lines on the reliability plot: {datetime.now()}") - self.figure = self._create_layout() - self._add_xaxis() - self._add_yaxis() - self._add_y2axis() + fig, ax = plt.subplots(figsize=(self.config_obj.plot_width, self.config_obj.plot_height)) - self._add_legend() + wts_size_styles = self.get_weights_size_styles() + self._add_title(ax, wts_size_styles['title']) + self._add_caption(plt, wts_size_styles['caption']) + + ax2 = None + if self.config_obj.rely_event_hist: + # create inset or create 2nd y-axis + if self.config_obj.inset_hist: + ax2 = ax.inset_axes((0.08, 0.7, 0.47, 0.28)) + else: + ax2 = self._add_y2axis(ax, None) + + self._add_xaxis(ax2, wts_size_styles['xlab']) + ax2.set_xlim(0, 1) + self._add_yaxis(ax2, wts_size_styles['ylab'], label="# Forecasts", grid_on=True) + + # format large numbers like 3 million as 3M + ax2.yaxis.set_major_formatter(ticker.EngFormatter()) + + self._add_series(ax, ax2) + + self._add_xaxis(ax, wts_size_styles['xlab']) + ax.set_xlim(0, 1) + self._add_yaxis(ax, wts_size_styles['ylab']) + ax.set_ylim(0, 1) + ax.set_yticks(np.linspace(0, 1, 11)) + + self._add_legend(ax) + + self._add_custom_lines(ax) + + plt.tight_layout() + + self.logger.info(f"Finished drawing lines on reliability diagram {datetime.now()}") + + def _add_custom_lines(self, ax): + # add custom lines if lines are defined in config + # TODO: move to base_plot? + if len(self.series_list) > 0: + self._add_lines(ax, self.config_obj, self.config_obj.indy_vals) + + def _add_series(self, ax, ax2): # calculate stag adjustments stag_adjustments = self._calc_stag_adjustments() x_points_index = self.series_list[-1].series_points['thresh_i'].tolist() # add series lines - for series in self.series_list: + for index, series in enumerate(self.series_list): # apply staggering offset if applicable if stag_adjustments[series.idx] == 0: x_points_index_adj = x_points_index @@ -177,15 +199,9 @@ def _create_figure(self): # Don't generate the plot for this series if # it isn't requested (as set in the config file) if series.plot_disp: - self._draw_series(series, x_points_index_adj) - # add custom lines - if len(self.series_list) > 0: - self._add_lines(self.config_obj) + self._draw_series(ax, ax2, series, x_points_index_adj, index) - self.logger.info(f"Finished drawing lines on reliability diagram" - f" {datetime.now()}") - - def _draw_series(self, series: ReliabilitySeries, x_points_index_adj: list) -> None: + def _draw_series(self, ax, ax2, series: ReliabilitySeries, x_points_index_adj: list, idx) -> None: """ Draws the formatted line with CIs if needed on the plot @@ -195,32 +211,29 @@ def _draw_series(self, series: ReliabilitySeries, x_points_index_adj: list) -> N self.logger.info(f"Draw the bar plot and skill lines: {datetime.now()}") if series.idx == 0: - self._add_noskill_polygon(series.series_points['stat_value'][0]) - - if self.config_obj.rely_event_hist is True and 'n_i' in series.series_points: - x_axis = 'x1' - if self.config_obj.inset_hist is True: - x_axis = 'x2' - - bar_trace = go.Bar( - x=x_points_index_adj, - y=series.series_points['n_i'].tolist(), - name="Absolute_cases", - marker_color=self.config_obj.colors_list[series.idx], - marker_line_color=self.config_obj.colors_list[series.idx], - opacity=1, - showlegend=False, - xaxis=x_axis, - yaxis='y2' - ) - if self.config_obj.inset_hist is True: - self.figure.add_trace(bar_trace) - else: - self.figure.add_trace(bar_trace, secondary_y=True) + self._add_noskill_polygon(ax, series.series_points['stat_value'][0]) - self._add_noskill_line(series.series_points['stat_value'][0]) - self._add_perfect_reliability_line() - self._add_noresolution_line(series.series_points['stat_value'][0]) + # determine whether to add to the inset plot or the main plot + plot_ax = ax + if self.config_obj.inset_hist: + plot_ax = ax2 + + if self.config_obj.rely_event_hist and 'n_i' in series.series_points: + + n_visible_series = sum(1 for s in self.series_list if s.plot_disp) + n = max(n_visible_series, 1) + width = MPL_DEFAULT_BAR_WIDTH / 40 + offset = (idx - (n - 1) / 2.0) * width + x_locs = [item + offset for item in x_points_index_adj] + + plot_ax.bar(x=x_locs, height=series.series_points['n_i'].tolist(), align='center', + width=width, + color=self.config_obj.colors_list[series.idx], + label="Absolute_cases") + + self._add_noskill_line(ax, series.series_points['stat_value'][0]) + self._add_perfect_reliability_line(ax) + self._add_noresolution_line(ax, series.series_points['stat_value'][0]) y_points = series.series_points['stat_value'].tolist() stat_bcu = all(v == 0 for v in series.series_points['stat_btcu']) @@ -232,393 +245,132 @@ def _draw_series(self, series: ReliabilitySeries, x_points_index_adj: list) -> N error_y_visible = False # add the plot - line_trace = go.Scatter(x=x_points_index_adj, - y=y_points, - showlegend=self.config_obj.show_legend[series.idx] == 1, - mode=self.config_obj.mode[series.idx], - textposition="top right", - name=self.config_obj.user_legends[series.idx], - connectgaps=self.config_obj.con_series[series.idx] == 1, - line={'color': self.config_obj.colors_list[series.idx], - 'width': self.config_obj.linewidth_list[series.idx], - 'dash': self.config_obj.linestyles_list[series.idx]}, - marker_symbol=self.config_obj.marker_list[series.idx], - marker_color=self.config_obj.colors_list[series.idx], - marker_line_color=self.config_obj.colors_list[series.idx], - marker_size=self.config_obj.marker_size[series.idx], - error_y={'type': 'data', - 'symmetric': False, - 'array': series.series_points['stat_btcu'], - 'arrayminus': series.series_points['stat_btcl'], - 'visible': error_y_visible, - 'thickness': self.config_obj.linewidth_list[series.idx]} - - ) - - if self.config_obj.inset_hist is True: - self.figure.add_trace(line_trace) - else: - self.figure.add_trace(line_trace, secondary_y=False) + y_errors = [series.series_points['stat_btcl'], series.series_points['stat_btcu']] + plot_mode = self.config_obj.mode[series.idx] + marker = self.config_obj.marker_list[series.idx] if 'markers' in plot_mode else None + line_style = self.config_obj.linestyles_list[series.idx] if 'lines' in plot_mode else 'None' + + ax.errorbar( + x=x_points_index_adj, + y=y_points, + label=self.config_obj.user_legends[series.idx], + # line style + color=self.config_obj.colors_list[series.idx], + linestyle=line_style, + linewidth=self.config_obj.linewidth_list[series.idx], + # marker style + marker=marker, + markersize=self.config_obj.marker_size[series.idx], + markeredgecolor=self.config_obj.colors_list[series.idx], + markerfacecolor=self.config_obj.colors_list[series.idx], + # error bar + yerr=y_errors if error_y_visible else None, + elinewidth=self.config_obj.linewidth_list[series.idx], + ) self.logger.info(f"Finished with bar plot and skill lines :{datetime.now()}") - def _add_y2axis(self) -> None: - """ - Adds y2-axis if needed - """ - - if self.config_obj.rely_event_hist is True and self.config_obj.inset_hist is False: - self.figure.update_yaxes(title_text='', - secondary_y=True, - linecolor=PLOTLY_AXIS_LINE_COLOR, - linewidth=PLOTLY_AXIS_LINE_WIDTH, - showgrid=False, - zeroline=False, - ticks="inside", - tickangle=self.config_obj.y_tickangle, - tickfont={'size': self.config_obj.y_tickfont_size} - ) - - def _add_noskill_polygon(self, o_bar: Union[float, None]) -> None: + def _add_noskill_polygon(self, ax, o_bar: Union[float, None]) -> None: """ Adds no-skill polygon to the graph if needed and o_bar is not None :param o_bar: o_bar value or None """ + if not self.config_obj.add_noskill_line: + return + + if not o_bar: + print(" WARNING: no-skill polygon can't be created for the series") + return self.logger.info("Adding no-skill polygon") - if self.config_obj.add_noskill_line is True: - if o_bar and o_bar is not None: - self.figure.add_trace( - go.Scatter(x=[o_bar, o_bar, 1, 1, o_bar, 0, 0], - y=[0, 1, 1, (1 - o_bar) / 2 + o_bar, o_bar, o_bar, 0], - fill='toself', - fillcolor='#ededed', - line={'color': '#ededed'}, - showlegend=False, - name='No-Skill poly', - hoverinfo='skip', - opacity=0.5 - ) - ) - else: - print(' WARNING: no-skill polygon can\'t be created for the series') - def _add_noskill_line(self, o_bar: Union[float, None]) -> None: + x = [o_bar, o_bar, 1, 1, o_bar, 0, 0] + y = [0, 1, 1, (1 - o_bar) / 2 + o_bar, o_bar, o_bar, 0] + ax.fill(x, y, + facecolor='#ededed', + edgecolor='#ededed', + alpha=0.5, + label='_no-skill-poly_') + + def _add_noskill_line(self, ax, o_bar: Union[float, None]) -> None: """ Adds no-skill line to the graph if needed and o_bar is not None :param o_bar: o_bar value or None """ - + if not self.config_obj.add_noskill_line: + return self.logger.info("Adding no-skill line") - if self.config_obj.add_noskill_line is True: - if o_bar and o_bar is not None: - # create a line - intercept = 0.5 * o_bar - self.figure.add_trace( - go.Scatter(x=[0, 1], - y=[util.abline(0, intercept, 0.5), util.abline(1, intercept, 0.5)], - line={'color': self.config_obj.noskill_line_col, - 'dash': 'dash', - 'width': 1}, - showlegend=False, - mode='lines', - name='No-Skill' - ) - ) - # create annotation - self.figure.add_annotation( - x=1, - y=util.abline(1, intercept, 0.5), - xref="x", - yref="y", - text="No-Skill", - showarrow=True, - font={ - 'color': '#636363', - 'size': self.config_obj.x_tickfont_size - }, - align="left", - ax=10, - ay=0, - textangle=90 - ) - else: - print(' WARNING: no-skill line can\'t be created for the series') - - def _add_perfect_reliability_line(self) -> None: - """ - Adds perfect reliability line to the graph if needed - :return: - """ - - self.logger.info("Adding perfect reliability line") - if self.config_obj.add_skill_line is True: - self.figure.add_trace( - go.Scatter(x=[0, 1], - y=[util.abline(0, 0, 1), util.abline(1, 0, 1)], - line={'color': 'grey', - 'width': 1}, - showlegend=False, - mode='lines', - name='Perfect reliability' - ) - ) - self.figure.add_annotation( - x=1, - y=util.abline(1, 0, 1), - xref="x", - yref="y", - text="Perfect reliability", - font={ - 'color': '#636363', - 'size': self.config_obj.x_tickfont_size - }, - showarrow=True, - align="left", - ax=10, - ay=0, - textangle=90 - ) - - def _add_noresolution_line(self, o_bar: Union[float, None]) -> None: - """ - Adds no-resolution line to the graph if needed and o_bar is not None - :param o_bar: o_bar value or None - """ + if not o_bar: + print(" WARNING: no-skill line can't be created for the series") + return - self.logger.info("Adding no-resolution line") - if self.config_obj.add_reference_line is True: - if o_bar and o_bar is not None: - self.figure.add_trace( - go.Scatter(x=[0, 1], - y=[util.abline(0, o_bar, 0), util.abline(1, o_bar, 0)], - line={'color': self.config_obj.reference_line_col, - 'dash': 'dash', - 'width': 1}, - showlegend=False, - mode='lines', - name='No-resolution' - ) - ) - self.figure.add_trace( - go.Scatter(x=[util.abline(0, o_bar, 0), util.abline(1, o_bar, 0)], - y=[0, 1], - line={'color': 'red', - 'dash': 'dash', - 'width': 1}, - showlegend=False, - mode='lines', - name='No-resolution' - ) - ) - self.figure.add_annotation( - x=1, - y=util.abline(1, o_bar, 0), - xref="x", - yref="y", - text="No-resolution", - showarrow=True, - font={ - 'color': '#636363', - 'size': self.config_obj.x_tickfont_size - }, - align="left", - ax=10, - ay=0, - textangle=90 - ) - else: - print(' WARNING: no-resolution line can\'t be created for the series') - - def _create_layout(self) -> Figure: - """ - Creates a new layout based on the properties from the config file - including plots size, annotation and title + # create a line + intercept = 0.5 * o_bar + x = [0, 1] + y = [util.abline(0, intercept, 0.5), util.abline(1, intercept, 0.5)] + ax.plot(x, y, label='_No-Skill_', color=self.config_obj.noskill_line_col, linewidth=1, linestyle='--') - :return: Figure object - """ # create annotation - annotation = [ - {'text': util.apply_weight_style(self.config_obj.parameters['plot_caption'], - self.config_obj.parameters['caption_weight']), - 'align': 'left', - 'showarrow': False, - 'xref': 'paper', - 'yref': 'paper', - 'x': self.config_obj.parameters['caption_align'], - 'y': self.config_obj.caption_offset, - 'font': { - 'size': self.config_obj.caption_size, - 'color': self.config_obj.parameters['caption_col'] - } - }] - # create title - title = {'text': util.apply_weight_style(self.config_obj.title, - self.config_obj.parameters['title_weight']), - 'font': { - 'size': self.config_obj.title_font_size, - }, - 'y': self.config_obj.title_offset, - 'x': self.config_obj.parameters['title_align'], - 'xanchor': 'center', - 'xref': 'paper' - } - - # create a layout and allow y2 axis - if self.config_obj.rely_event_hist is True and self.config_obj.inset_hist is True: - # us go.Layout and go.Figure to create a figure because of the inset - layout = go.Layout( - yaxis=dict( - range=[0, 1], - tickvals=[x / 10.0 for x in range(0, 11, 1)], - ticktext=[x / 10.0 for x in range(0, 11, 1)], - showgrid=False, - title_text= - util.apply_weight_style(self.config_obj.yaxis_1, - self.config_obj.parameters['ylab_weight']), - title_standoff=abs(self.config_obj.parameters['ylab_offset']) + 10, - - ), - xaxis2=dict( - domain=[0.08, 0.55], - anchor='y2' - ), - yaxis2=dict( - domain=[0.7, 0.98], - anchor='x2', - title_text='# Forecasts', - showgrid=True, - title_standoff=0 - ) - ) - fig = go.Figure(layout=layout) - else: - fig = make_subplots(specs=[[{"secondary_y": True}]]) - - # add size, annotation, title - fig.update_layout( - width=self.config_obj.plot_width, - height=self.config_obj.plot_height, - margin=self.config_obj.plot_margins, - paper_bgcolor=PLOTLY_PAPER_BGCOOR, - annotations=annotation, - title=title, - plot_bgcolor=PLOTLY_PAPER_BGCOOR + ax.text( + 1, util.abline(1, intercept, 0.5), + "No-Skill", + size=self.config_obj.x_tickfont_size, + color='#636363', + rotation=270, + transform=ax.transAxes, ) - return fig - - def _add_xaxis(self) -> None: - """ - Configures and adds x-axis to the plot - """ - self.figure.update_xaxes(title_text=self.config_obj.xaxis, - linecolor=PLOTLY_AXIS_LINE_COLOR, - linewidth=PLOTLY_AXIS_LINE_WIDTH, - showgrid=False, - ticks="inside", - zeroline=False, - gridwidth=self.config_obj.parameters['grid_lwd'], - gridcolor=self.config_obj.blended_grid_col, - automargin=True, - title_font={ - 'size': self.config_obj.x_title_font_size - }, - title_standoff=abs(self.config_obj.parameters['xlab_offset']), - tickangle=self.config_obj.x_tickangle, - tickfont={'size': self.config_obj.x_tickfont_size}, - tickmode='array', - tickvals=[x / 10.0 for x in range(0, 11, 1)], - ticktext=[x / 10.0 for x in range(0, 11, 1)], - range=[0, 1] - ) - - def _add_yaxis(self) -> None: + def _add_perfect_reliability_line(self, ax) -> None: """ - Configures and adds y-axis to the plot + Adds perfect reliability line to the graph if needed """ + if not self.config_obj.add_skill_line: + return - self.figure.update_yaxes( - linecolor=PLOTLY_AXIS_LINE_COLOR, - linewidth=PLOTLY_AXIS_LINE_WIDTH, - - zeroline=False, - ticks="inside", - gridwidth=self.config_obj.parameters['grid_lwd'], - gridcolor=self.config_obj.blended_grid_col, - automargin=True, - title_font={ - 'size': self.config_obj.y_title_font_size - }, - tickangle=self.config_obj.y_tickangle, - tickfont={'size': self.config_obj.y_tickfont_size}, - + self.logger.info("Adding perfect reliability line") + x = [0., 1.] + y = [util.abline(0, 0, 1), util.abline(1, 0, 1)] + ax.plot(x, y, label='_Perfect reliability_', color='grey', zorder=0, linewidth=1) + + ax.text( + 1, util.abline(1, 0, 1), + "Perfect reliability", + size=self.config_obj.x_tickfont_size, + color='#636363', + rotation=270, + transform=ax.transAxes, ) - # adjustments for the inset - if self.config_obj.rely_event_hist is False or self.config_obj.inset_hist is False: - self.figure.update_yaxes(secondary_y=False, - showgrid=False, - range=[0, 1], - tickvals=[x / 10.0 for x in range(0, 11, 1)], - ticktext=[x / 10.0 for x in range(0, 11, 1)], - title_standoff= - abs(self.config_obj.parameters['ylab_offset']) + 10, - title_text= - util.apply_weight_style(self.config_obj.yaxis_1, - self.config_obj.parameters['ylab_weight']) - ) - - def _add_legend(self) -> None: - """ - Creates a plot legend based on the properties from the config file - and attaches it to the initial Figure - """ - self.figure.update_layout(legend={'x': self.config_obj.bbox_x, - 'y': self.config_obj.bbox_y, - 'xanchor': 'center', - 'yanchor': 'top', - 'bordercolor': self.config_obj.legend_border_color, - 'borderwidth': self.config_obj.legend_border_width, - 'orientation': self.config_obj.legend_orientation, - 'font': { - 'size': self.config_obj.legend_size, - 'color': "black" - } - }) - - def remove_file(self): - """ - Removes previously made image file . Invoked by the parent class before self.output_file - attribute can be created, but overridden here. - """ - - super().remove_file() - self._remove_html() - def _remove_html(self) -> None: + def _add_noresolution_line(self, ax, o_bar: Union[float, None]) -> None: """ - Removes previously made HTML file. + Adds no-resolution line to the graph if needed and o_bar is not None + :param o_bar: o_bar value or None """ + if not self.config_obj.add_reference_line: + return - base_name, _ = os.path.splitext(self.get_config_value('plot_filename')) - html_name = f"{base_name}.html" - - # remove the old file if it exist - if os.path.exists(html_name): - os.remove(html_name) + self.logger.info("Adding no-resolution line") - def write_html(self) -> None: - """ - Is needed - creates and saves the html representation of the plot WITHOUT Plotly.js - """ - self.logger.info("Writing html file.") - if self.config_obj.create_html is True: - # construct the fle name from plot_filename - base_name, _ = os.path.splitext(self.get_config_value('plot_filename')) - html_name = f"{base_name}.html" - - # save html - self.figure.write_html(html_name, include_plotlyjs=False) + if not o_bar: + print(" WARNING: no-resolution line can't be created for the series") + return + + end_to_end = [0, 1] + ab_line = [util.abline(0, o_bar, 0), util.abline(1, o_bar, 0)] + ax.plot(end_to_end, ab_line, label='_No-resolution_', + color=self.config_obj.reference_line_col, linestyle='--', zorder=0, linewidth=1) + + ax.plot(ab_line, end_to_end, label='_No-resolution_', + color=self.config_obj.reference_line_col, linestyle='--', zorder=0, linewidth=1) + + ax.text( + 1, util.abline(1, o_bar, 0), + "No-resolution", + size=self.config_obj.x_tickfont_size, + color='#636363', + rotation=270, + transform=ax.transAxes, + ) def write_output_file(self) -> None: """ diff --git a/metplotpy/plots/reliability_diagram/reliability_config.py b/metplotpy/plots/reliability_diagram/reliability_config.py index 3f1175d1..ad5dafdd 100644 --- a/metplotpy/plots/reliability_diagram/reliability_config.py +++ b/metplotpy/plots/reliability_diagram/reliability_config.py @@ -16,11 +16,10 @@ __author__ = 'Tatiana Burek' import itertools -from datetime import datetime -from ..config_plotly import Config -from .. import constants_plotly as constants -from .. import util_plotly as util +from ..config import Config +from .. import constants +from .. import util class ReliabilityConfig(Config): @@ -41,14 +40,16 @@ def __init__(self, parameters: dict) -> None: # plot parameters self.grid_on = self._get_bool('grid_on') - self.plot_width = self.calculate_plot_dimension('plot_width', 'pixels') - self.plot_height = self.calculate_plot_dimension('plot_height', 'pixels') - self.plot_margins = dict(l=0, - r=self.parameters['mar'][3] + 20, - t=self.parameters['mar'][2] + 80, - b=self.parameters['mar'][0] + 80, - pad=5 - ) + self.plot_width = self.calculate_plot_dimension('plot_width') + self.plot_height = self.calculate_plot_dimension('plot_height') + + self.plot_margins = { + 'l': 0, + 'r': self.parameters['mar'][3] + 20, + 't': self.parameters['mar'][2] + 80, + 'b': self.parameters['mar'][0] + 80, + 'pad': 5, + } self.indy_stagger = self._get_bool('indy_stagger_1') self.blended_grid_col = util.alpha_blending(self.parameters['grid_col'], 0.5) self.variance_inflation_factor = self._get_bool('variance_inflation_factor') @@ -75,7 +76,7 @@ def __init__(self, parameters: dict) -> None: ############################################## # title parameters self.title_font_size = self.parameters['title_size'] * constants.DEFAULT_TITLE_FONT_SIZE - self.title_offset = self.parameters['title_offset'] * constants.DEFAULT_TITLE_OFFSET + self.title_offset = 1.0 + abs(self.parameters['title_offset']) * constants.DEFAULT_TITLE_OFFSET self.y_title_font_size = self.parameters['ylab_size'] + constants.DEFAULT_TITLE_FONTSIZE ############################################## @@ -92,7 +93,6 @@ def __init__(self, parameters: dict) -> None: if self.x_tickangle in constants.XAXIS_ORIENTATION.keys(): self.x_tickangle = constants.XAXIS_ORIENTATION[self.x_tickangle] self.x_tickfont_size = self.parameters['xtlab_size'] + constants.DEFAULT_TITLE_FONTSIZE - self.xaxis = util.apply_weight_style(self.xaxis, self.parameters['xlab_weight']) ############################################## # series parameters @@ -111,6 +111,8 @@ def __init__(self, parameters: dict) -> None: self.con_series = self._get_con_series() self.num_series = self.calculate_number_of_series() self.show_legend = self._get_show_legend() + if not self.indy_label: + self.indy_label = self.indy_vals ############################################## # legend parameters @@ -171,127 +173,27 @@ def _get_fcst_vars(self, index): return fcst_var_val_dict - def _get_mode(self) -> list: - """ - Retrieve all the modes. Convert mode names from - the config file into plotly python's mode names. - - Args: - - Returns: - markers: a list of the plotly markers - """ - modes = self.get_config_value('series_type') - mode_list = [] - for mode in modes: - if mode in constants.TYPE_TO_PLOTLY_MODE.keys(): - # the recognized plotly marker names: - # circle-open (for small circle), circle, triangle-up, - # square, diamond, or hexagon - mode_list.append(constants.TYPE_TO_PLOTLY_MODE[mode]) - else: - mode_list.append('lines+markers') - return self.create_list_by_series_ordering(mode_list) - - def _get_linestyles(self) -> list: - """ - Retrieve all the line styles. Convert line style names from - the config file into plotly python's line style names. - - Args: - - Returns: - line_styles: a list of the plotly line styles - """ - line_styles = self.get_config_value('series_line_style') - line_style_list = [] - for line_style in line_styles: - if line_style in constants.LINE_STYLE_TO_PLOTLY_DASH.keys(): - line_style_list.append(constants.LINE_STYLE_TO_PLOTLY_DASH[line_style]) - else: - line_style_list.append(None) - return self.create_list_by_series_ordering(line_style_list) - - def _get_markers(self) -> list: - """ - Retrieve all the markers. Convert marker names from - the config file into plotly python's marker names. - - Args: - - Returns: - markers: a list of the plotly markers - """ - markers = self.get_config_value('series_symbols') - markers_list = [] - for marker in markers: - if marker in constants.AVAILABLE_PLOTLY_MARKERS_LIST: - # the recognized plotly marker names: - # circle-open (for small circle), circle, triangle-up, - # square, diamond, or hexagon - markers_list.append(marker) - else: - markers_list.append(constants.PCH_TO_PLOTLY_MARKER[marker]) - return self.create_list_by_series_ordering(markers_list) - - def _get_markers_size(self) -> list: - """ - Retrieve all the markers. Convert marker names from - the config file into plotly python's marker names. - - Args: - - Returns: - markers: a list of the plotly markers - """ - markers = self.get_config_value('series_symbols') - markers_size = [] - for marker in markers: - if marker in constants.AVAILABLE_PLOTLY_MARKERS_LIST: - markers_size.append(marker) - else: - markers_size.append(constants.PCH_TO_PLOTLY_MARKER_SIZE[marker]) - - return self.create_list_by_series_ordering(markers_size) - - def _config_consistency_check(self) -> bool: - """ - Checks that the number of settings defined for plot_ci, - plot_disp, series_order, user_legend colors, and series_symbols - are consistent. - - Args: - - Returns: - True if the number of settings for each of the above - settings is consistent with the number of - series (as defined by the cross product of the model - and vx_mask defined in the series_val_1 setting) + def config_consistency_check(self) -> None: + """Checks that the number of settings defined for + plot_disp, series_ordering, colors_list, user_legends, and show_legend + are consistent with number of series. + @raises ValueError if any of settings are inconsistent with the + number of series (as defined by the cross product of the model + and vx_mask defined in the series_val_1 setting) """ - self.logger.info(f"Begin consistency check: {datetime.now()}") - # Determine the number of series based on the number of - # permutations from the series_var setting in the - # config file - - # Numbers of values for other settings for series - num_ci_settings = len(self.plot_ci) - num_plot_disp = len(self.plot_disp) - num_markers = len(self.marker_list) - num_series_ord = len(self.series_ordering) - num_colors = len(self.colors_list) - num_legends = len(self.user_legends) - num_line_widths = len(self.linewidth_list) - num_linestyles = len(self.linestyles_list) - num_show_legend = len(self.show_legend) - status = False - - if self.num_series == num_plot_disp == \ - num_markers == num_series_ord == num_colors \ - == num_legends == num_line_widths == num_linestyles == num_ci_settings == num_show_legend: - status = True - self.logger.info(f"Finished consistency check :{datetime.now()}") - return status + lists_to_check = { + "plot_ci": self.plot_ci, + "plot_disp": self.plot_disp, + "marker_list": self.marker_list, + "series_ordering": self.series_ordering, + "colors_list": self.colors_list, + "user_legends": self.user_legends, + "linewidth_list": self.linewidth_list, + "linestyles_list": self.linestyles_list, + "show_legend": self.show_legend, + } + self._config_compare_lists_to_num_series(lists_to_check) def _get_plot_ci(self) -> list: """ diff --git a/metplotpy/plots/reliability_diagram/reliability_series.py b/metplotpy/plots/reliability_diagram/reliability_series.py index 92be7495..1c09eb3f 100644 --- a/metplotpy/plots/reliability_diagram/reliability_series.py +++ b/metplotpy/plots/reliability_diagram/reliability_series.py @@ -44,14 +44,14 @@ def _create_all_fields_values_no_indy(self) -> dict: """ all_fields_values_no_indy = {} all_fields_values = self.config.get_config_value('series_val_1').copy() - if self.config._get_fcst_vars(1): - all_fields_values['fcst_var'] = list(self.config._get_fcst_vars(1).keys()) + if self.config.get_fcst_vars_keys(1): + all_fields_values['fcst_var'] = self.config.get_fcst_vars_keys(1) all_fields_values['stat_name'] = self.config.get_config_value('list_stat_1') all_fields_values_no_indy[1] = all_fields_values all_fields_values = self.config.get_config_value('series_val_2').copy() - if self.config._get_fcst_vars(2): - all_fields_values['fcst_var'] = list(self.config._get_fcst_vars(2).keys()) + if self.config.get_fcst_vars_keys(2): + all_fields_values['fcst_var'] = self.config.get_fcst_vars_keys(2) all_fields_values['stat_name'] = self.config.get_config_value('list_stat_2') all_fields_values_no_indy[2] = all_fields_values diff --git a/metplotpy/plots/revision_box/revision_box.py b/metplotpy/plots/revision_box/revision_box.py index 61cbfc96..9a2d3a61 100644 --- a/metplotpy/plots/revision_box/revision_box.py +++ b/metplotpy/plots/revision_box/revision_box.py @@ -169,10 +169,13 @@ def _add_caption(self, plt, font_properties): def _add_custom_lines(self, ax): return - def _get_data_to_plot_and_x_locs(self, series, idx): + def _get_data_to_plot(self, series): + return series.series_points['points']['stat_value'].dropna().values + + def _get_x_locs_and_width(self, x_points, index): base = np.arange(len(self.config_obj.indy_label)) - x_locs = [base[idx]] - return series.series_points['points']['stat_value'].dropna().values, x_locs, MPL_DEFAULT_BOX_WIDTH + x_locs = [base[index]] + return x_locs, MPL_DEFAULT_BOX_WIDTH def write_output_file(self) -> None: """ diff --git a/metplotpy/plots/roc_diagram/roc_diagram.py b/metplotpy/plots/roc_diagram/roc_diagram.py index dfd84035..f7fd2ce8 100644 --- a/metplotpy/plots/roc_diagram/roc_diagram.py +++ b/metplotpy/plots/roc_diagram/roc_diagram.py @@ -19,12 +19,12 @@ import warnings import pandas as pd -import plotly.graph_objects as go -from plotly.subplots import make_subplots -from metplotpy.plots import util_plotly as util -from metplotpy.plots import constants_plotly as constants -from metplotpy.plots.base_plot_plotly import BasePlot +from matplotlib import pyplot as plt + +from metplotpy.plots import util +from metplotpy.plots import constants +from metplotpy.plots.base_plot import BasePlot from metplotpy.plots.roc_diagram.roc_diagram_config import ROCDiagramConfig from metplotpy.plots.roc_diagram.roc_diagram_series import ROCDiagramSeries @@ -94,16 +94,11 @@ def __init__(self, parameters): # line width, and criteria needed to subset the input dataframe. self.series_list = self._create_series(self.input_df) - # create figure - # pylint:disable=assignment-from-no-return - # Need to have a self.figure that we can pass along to - # the methods in base_plot.py (BasePlot class methods) to - # create binary versions of the plot. - self.figure = self._create_figure() + self._create_figure() # add custom lines - if len(self.series_list) > 0: - self._add_lines(self.config_obj) + # if len(self.series_list) > 0: + # self._add_lines(self.config_obj) def _read_input_data(self) -> pd.DataFrame: """ @@ -199,76 +194,83 @@ def _create_series(self, input_data): series_obj = ROCDiagramSeries(self.config_obj, i, input_data) series_list.append(series_obj) - if self.config_obj.summary_curve != 'none': - # add Summary Curve bassd on teh summary dataframes of each ROCDiagramSeries - df_sum_main = None - for idx, series in enumerate(series_list): - # create a main summary frame from series summary frames + if self.config_obj.summary_curve == 'none': + return series_list + + # add Summary Curve based on teh summary dataframes of each ROCDiagramSeries + df_sum_main = None + for idx, series in enumerate(series_list): + # create a main summary frame from series summary frames + if df_sum_main is None: if self.config_obj.linetype_ctc: - if df_sum_main is None: - df_sum_main = pd.DataFrame(columns=['fcst_thresh', 'fy_oy', 'fy_on', 'fn_oy', 'fn_on']) + df_sum_main = pd.DataFrame(columns=['fcst_thresh', 'fy_oy', 'fy_on', 'fn_oy', 'fn_on']) elif self.config_obj.linetype_pct: - if df_sum_main is None: - df_sum_main = pd.DataFrame(columns=['thresh_i', 'i_value', 'on_i', 'oy_i']) - - df_sum_main = pd.concat([df_sum_main, series.series_points[3]], axis=0) - - if self.config_obj.linetype_ctc: - df_summary_curve = pd.DataFrame(columns=['fcst_thresh', 'fy_oy', 'fy_on', 'fn_oy', 'fn_on']) - fcst_thresh_list = df_sum_main['fcst_thresh'].unique() - for thresh in fcst_thresh_list: - if self.config_obj.summary_curve == 'median': - group_stats_fy_oy = df_sum_main['fy_oy'][df_sum_main['fcst_thresh'] == thresh].median() - group_stats_fn_oy = df_sum_main['fn_oy'][df_sum_main['fcst_thresh'] == thresh].median() - group_stats_fy_on = df_sum_main['fy_on'][df_sum_main['fcst_thresh'] == thresh].median() - group_stats_fn_on = df_sum_main['fn_on'][df_sum_main['fcst_thresh'] == thresh].median() - else: - group_stats_fy_oy = df_sum_main['fy_oy'][df_sum_main['fcst_thresh'] == thresh].mean() - group_stats_fn_oy = df_sum_main['fn_oy'][df_sum_main['fcst_thresh'] == thresh].mean() - group_stats_fy_on = df_sum_main['fy_on'][df_sum_main['fcst_thresh'] == thresh].mean() - group_stats_fn_on = df_sum_main['fn_on'][df_sum_main['fcst_thresh'] == thresh].mean() - df_summary_curve.loc[len(df_summary_curve)] = {'fcst_thresh': thresh, - 'fy_oy': group_stats_fy_oy, - 'fn_oy': group_stats_fn_oy, - 'fy_on': group_stats_fy_on, - 'fn_on': group_stats_fn_on, - } - df_summary_curve.reset_index() - pody, pofd, thresh = util.prepare_ctc_roc(df_summary_curve,self.config_obj.ctc_ascending) - else: - df_summary_curve = pd.DataFrame(columns=['thresh_i', 'on_i', 'oy_i']) - thresh_i_list = df_sum_main['thresh_i'].unique() - for index, thresh in enumerate(thresh_i_list): - if self.config_obj.summary_curve == 'median': - on_i_sum = df_sum_main['on_i'][df_sum_main['thresh_i'] == thresh].median() - oy_i_sum = df_sum_main['oy_i'][df_sum_main['thresh_i'] == thresh].median() - else: - on_i_sum = df_sum_main['on_i'][df_sum_main['thresh_i'] == thresh].mean() - oy_i_sum = df_sum_main['oy_i'][df_sum_main['thresh_i'] == thresh].mean() - df_summary_curve.loc[len(df_summary_curve)] = {'thresh_i': thresh, 'on_i': on_i_sum, - 'oy_i': oy_i_sum, } - df_summary_curve.reset_index() - pody, pofd, thresh = util.prepare_pct_roc(df_summary_curve) - - series_obj = ROCDiagramSeries(self.config_obj, num_series -1, None) - series_obj.series_points = (pofd, pody, thresh, None) + df_sum_main = pd.DataFrame(columns=['thresh_i', 'i_value', 'on_i', 'oy_i']) - series_list.append(series_obj) + df_sum_main = pd.concat([df_sum_main, series.series_points[3]], axis=0) - return series_list + if self.config_obj.linetype_ctc: + pofd, pody, thresh = self._handle_ctc(df_sum_main) + else: + pofd, pody, thresh = self._handle_pct(df_sum_main) - def remove_file(self): - """ - Removes previously made image file . Invoked by the parent class before self.output_file - attribute can be created, but overridden here. - """ + series_obj = ROCDiagramSeries(self.config_obj, num_series -1, None) + series_obj.series_points = (pofd, pody, thresh, None) - image_name = self.get_config_value('plot_filename') - warnings.filterwarnings("ignore", category=DeprecationWarning) + series_list.append(series_obj) - # remove the old file if it exist - if os.path.exists(image_name): - os.remove(image_name) + return series_list + + def _handle_ctc(self, df_sum_main): + df_summary_curve = pd.DataFrame(columns=['fcst_thresh', 'fy_oy', 'fy_on', 'fn_oy', 'fn_on']) + fcst_thresh_list = df_sum_main['fcst_thresh'].unique() + for thresh in fcst_thresh_list: + if self.config_obj.summary_curve == 'median': + group_stats_fy_oy = df_sum_main['fy_oy'][ + df_sum_main['fcst_thresh'] == thresh].median() + group_stats_fn_oy = df_sum_main['fn_oy'][ + df_sum_main['fcst_thresh'] == thresh].median() + group_stats_fy_on = df_sum_main['fy_on'][ + df_sum_main['fcst_thresh'] == thresh].median() + group_stats_fn_on = df_sum_main['fn_on'][ + df_sum_main['fcst_thresh'] == thresh].median() + else: + group_stats_fy_oy = df_sum_main['fy_oy'][ + df_sum_main['fcst_thresh'] == thresh].mean() + group_stats_fn_oy = df_sum_main['fn_oy'][ + df_sum_main['fcst_thresh'] == thresh].mean() + group_stats_fy_on = df_sum_main['fy_on'][ + df_sum_main['fcst_thresh'] == thresh].mean() + group_stats_fn_on = df_sum_main['fn_on'][ + df_sum_main['fcst_thresh'] == thresh].mean() + df_summary_curve.loc[len(df_summary_curve)] = {'fcst_thresh': thresh, + 'fy_oy': group_stats_fy_oy, + 'fn_oy': group_stats_fn_oy, + 'fy_on': group_stats_fy_on, + 'fn_on': group_stats_fn_on, + } + df_summary_curve.reset_index() + pody, pofd, thresh = util.prepare_ctc_roc(df_summary_curve, self.config_obj.ctc_ascending) + return pofd, pody, thresh + + def _handle_pct(self, df_sum_main): + df_summary_curve = pd.DataFrame(columns=['thresh_i', 'on_i', 'oy_i']) + thresh_i_list = df_sum_main['thresh_i'].unique() + for index, thresh in enumerate(thresh_i_list): + if self.config_obj.summary_curve == 'median': + on_i_sum = df_sum_main['on_i'][df_sum_main['thresh_i'] == thresh].median() + oy_i_sum = df_sum_main['oy_i'][df_sum_main['thresh_i'] == thresh].median() + else: + on_i_sum = df_sum_main['on_i'][df_sum_main['thresh_i'] == thresh].mean() + oy_i_sum = df_sum_main['oy_i'][df_sum_main['thresh_i'] == thresh].mean() + df_summary_curve.loc[len(df_summary_curve)] = { + 'thresh_i': thresh, + 'on_i': on_i_sum, + 'oy_i': oy_i_sum, + } + df_summary_curve.reset_index() + pody, pofd, thresh = util.prepare_pct_roc(df_summary_curve) + return pofd, pody, thresh def _create_figure(self): """ @@ -276,207 +278,80 @@ def _create_figure(self): (Success Rate) values. Hard-coding of labels for CSI lines and bias lines, and contour colors for the CSI curves. - Args: - Returns: ROC diagram """ - self.logger.info(f"Begin creating figure: {datetime.now()}") - fig = make_subplots(specs=[[{"secondary_y": True}]]) - - # Set plot height and width in pixel value - width = self.config_obj.plot_width - height = self.config_obj.plot_height - # fig.update_layout(width=width, height=height, paper_bgcolor="white") - fig.update_layout(width=width, height=height) - - # Add figure title - # fig.update_layout( - # title={'text': self.config_obj.title, - # 'y': 0.95, - # 'x': 0.5, - # 'xanchor': "center", - # 'yanchor': "top"}, - # plot_bgcolor="#FFF" - # - # ) - - # create title - title = {'text': util.apply_weight_style(self.config_obj.title, - self.config_obj.parameters['title_weight']), - 'font': { - 'size': self.config_obj.title_font_size, - }, - 'y': self.config_obj.title_offset, - 'x': self.config_obj.parameters['title_align'], - 'xanchor': 'center', - 'yanchor': 'top', - 'xref': 'paper' - } - fig.update_layout(title=title, plot_bgcolor="#FFF") - - # fig.update_xaxes(title_text=self.config_obj.xaxis, linecolor="black", linewidth=2, showgrid=False, - # range=[0.0, 1.0], dtick=0.1) - - # Set y-axes titles - # fig.update_yaxes(title_text="primary yaxis title", secondary_y=False) - fig.update_yaxes(title_text=self.config_obj.yaxis_1, secondary_y=False, linecolor="black", linewidth=2, - showgrid=False, zeroline=False, range=[0.0, 1.0], dtick=0.1) - # fig.update_yaxes(title_text=self.config_obj.yaxis_2, secondary_y=True, linecolor="black", linewidth=2, - # showgrid=False, zeroline=False, range=[0.0, 1.0], dtick=0.1) - - # set the range of the x-axis and y-axis to range from 0 to 1 - fig.update_layout(xaxis=dict(range=[0., 1.])) - fig.update_layout(yaxis=dict(range=[0., 1.])) - # plot the no-skill line - x = [0., 1.] - y = [0., 1.] - fig.add_trace(go.Scatter(x=x, y=y, line=dict(color='grey', - width=1.2, - dash='dash' - ), - name='no skill line', - showlegend=False - )) - - # style the legend box - if self.config_obj.draw_box: - fig.update_layout(legend=dict(x=self.config_obj.bbox_x, - y=self.config_obj.bbox_y, - bordercolor="black", - borderwidth=2 - )) + # create and draw the plot + _, ax = plt.subplots(figsize=(self.config_obj.plot_width, self.config_obj.plot_height)) - else: - fig.update_layout(legend=dict(x=self.config_obj.bbox_x, - y=self.config_obj.bbox_y - )) - - # can't support number of columns in legend, can only choose - # between horizontal or vertical alignment of legend labels - # so only support vertical legends (ie num columns = 1) - fig.update_layout(legend=dict(x=self.config_obj.bbox_x, - y=self.config_obj.bbox_y, - bordercolor="black", - borderwidth=2 - )) - - # caption styling - annotation = [ - {'text': util.apply_weight_style(self.config_obj.parameters['plot_caption'], - self.config_obj.parameters['caption_weight']), - 'align': 'left', - 'showarrow': False, - 'xref': 'paper', - 'yref': 'paper', - 'x': self.config_obj.parameters['caption_align'], - 'y': self.config_obj.caption_offset, - 'font': { - 'size': self.config_obj.caption_size, - 'color': self.config_obj.parameters['caption_col'] - } - }] - - # Set x-axis title - # fig.update_xaxes(title_text=self.config_obj.xaxis, linecolor="black", linewidth=2, showgrid=False, - # dtick=0.1, tickmode='linear', tick0=0.0) - fig.update_xaxes(title_text=self.config_obj.xaxis, - linecolor=constants.PLOTLY_AXIS_LINE_COLOR, - linewidth=constants.PLOTLY_AXIS_LINE_WIDTH, - showgrid=False, - dtick=0.1, - tick0=0.0, - tickmode='linear', - zeroline=False, - title_font={ - 'size': self.config_obj.x_title_font_size - }, - ticks="inside", - title_standoff=abs(self.config_obj.parameters['xlab_offset']), - tickangle=self.config_obj.x_tickangle, - tickfont={'size': self.config_obj.x_tickfont_size} - ) - fig.update_yaxes(title_text= - util.apply_weight_style(self.config_obj.yaxis_1, - self.config_obj.parameters['ylab_weight']), - secondary_y=False, - linecolor=constants.PLOTLY_AXIS_LINE_COLOR, - linewidth=constants.PLOTLY_AXIS_LINE_WIDTH, - zeroline=False, - title_font={ - 'size': self.config_obj.y_title_font_size - }, - ticks="inside", - title_standoff=abs(self.config_obj.parameters['ylab_offset']), - tickangle=self.config_obj.y_tickangle, - tickfont={'size': self.config_obj.y_tickfont_size} - ) - - fig.update_layout(annotations=annotation) - - thresh_list = [] - - - - - # "Dump" False Detection Rate (POFD) and PODY points to an output - # file based on the output image filename (useful in debugging) - # This output file is used by METviewer and not necessary for other uses. - if self.config_obj.dump_points_1 == True : - self.write_output_file() + wts_size_styles = self.get_weights_size_styles() - for idx, series in enumerate(self.series_list): - for i, thresh_val in enumerate(series.series_points[2]): - thresh_list.append(str(thresh_val)) - - # Don't generate the plot for this series if - # it isn't requested (as set in the config file) - if series.plot_disp: - pofd_points = series.series_points[0] - pody_points = series.series_points[1] - legend_label = self.config_obj.user_legends[idx] - - # add the plot - self.logger.info("Adding traces for markers and legend.") - fig.add_trace( - go.Scatter(mode="lines+markers", x=pofd_points, y=pody_points, - showlegend=self.config_obj.show_legend[series.idx] == 1, - text=thresh_list, textposition="top right", name=legend_label, - line=dict(color=self.config_obj.colors_list[idx], - width=self.config_obj.linewidth_list[idx]), - marker_symbol=self.config_obj.marker_list[idx]), - secondary_y=False - ) + self._add_title(ax, wts_size_styles['title']) + self._add_caption(plt, wts_size_styles['caption']) + self._add_series(ax) - def add_trace_copy(trace): - """Adds separate traces for markers and a legend. - This is a fix for not printing 'Aa' in the legend - Args: - Returns: - """ + self._add_xaxis(ax, wts_size_styles['xlab']) + ax.set_xlim(0, 1) + self._add_yaxis(ax, wts_size_styles['ylab']) + ax.set_ylim(0, 1) - fig.add_traces(trace) - new_trace = fig.data[-1] - # if self.config_obj.add_point_thresholds: - # new_trace.update(textfont_color=trace.marker.color, textposition='top center', - # mode="text", showlegend=False) - new_trace.update(textfont_color=trace.marker.color, textposition='top center', - mode="text", showlegend=False) - trace.update(mode="lines+markers") + self._add_legend(ax) - if self.config_obj.add_point_thresholds: - fig.for_each_trace(add_trace_copy) + # add custom lines if lines are defined in config + if len(self.series_list) > 0: + self._add_lines(ax, self.config_obj, self.config_obj.indy_vals) + plt.tight_layout() self.logger.info(f"Finished creating figure: {datetime.now()}") - return fig - + def _add_series(self, ax): + # plot the no-skill line + ax.plot([0., 1.], [0., 1.], color='grey', zorder=0, linewidth=1.2, linestyle='--') + for idx, series in enumerate(self.series_list): + # Don't generate the plot for this series if + # it isn't requested (as set in the config file) + if not series.plot_disp: + continue + + pofd_points = series.series_points[0] + pody_points = series.series_points[1] + + # set arguments for the plot + plot_args = { + 'marker': self.config_obj.marker_list[idx], + 'label': self.config_obj.user_legends[idx], + 'color': self.config_obj.colors_list[idx], + 'linewidth': self.config_obj.linewidth_list[idx], + } + if self.config_obj.marker_open_list[idx]: + plot_args['markerfacecolor'] = 'none' + plot_args['markeredgecolor'] = self.config_obj.colors_list[idx] + + ax.plot(pofd_points, pody_points, **plot_args) + + # add thresholds if defined and requested + if not self.config_obj.add_point_thresholds: + continue + + for pofd_point, pody_point, thresh_val in zip(pofd_points, pody_points, series.series_points[2]): + + if not thresh_val or pofd_point is None or pody_point is None: + continue + + ax.annotate( + str(thresh_val), + (pofd_point, pody_point), + xytext=(-10, 2), + textcoords="offset points", + ha='left', + va='bottom', + ) def write_output_file(self): """ @@ -484,61 +359,50 @@ def write_output_file(self): being plotted """ + if not self.config_obj.dump_points_1: + return - self.logger.info("Writing output file") # if points_path parameter doesn't exist, # open file, name it based on the stat_input config setting, # (the input data file) except replace the .data # extension with .points1 extension # otherwise use points_path path match = re.match(r'(.*)(.data)', self.config_obj.parameters['stat_input']) - if self.config_obj.dump_points_1 is True and match: - filename = match.group(1) - # replace the default path with the custom - if self.config_obj.points_path is not None: - # get the file name - path = filename.split(os.path.sep) - if len(path) > 0: - filename = path[-1] - else: - filename = '.' + os.path.sep - filename = self.config_obj.points_path + os.path.sep + filename - - output_file = filename + '.points1' - os.makedirs(os.path.dirname(output_file), exist_ok=True) - if os.path.exists(output_file): - os.remove(output_file) - - with open(output_file, 'a') as fileobj: - header_str = "pofd\t pody\n" - fileobj.write(header_str) - all_pody = [] - all_pofd = [] - for series in self.series_list: - pody_points = series.series_points[1] - pofd_points = series.series_points[0] - all_pody.extend(pody_points) - all_pofd.extend(pofd_points) - - all_points = zip(all_pofd, all_pody) - for idx, pts in enumerate(all_points): - data_str = str(pts[0]) + "\t" + str(pts[1]) + "\n" - fileobj.write(data_str) - - - def write_html(self) -> None: - """ - Is needed - creates and saves the html representation of the plot WITHOUT Plotly.js - """ + if not match: + return - self.logger.info("Writing HTML file") - if self.config_obj.create_html is True: - # construct the fle name from plot_filename - base_name, _ = os.path.splitext(self.get_config_value('plot_filename')) - html_name = f"{base_name}.html" + self.logger.info("Writing output file") + filename = match.group(1) + # replace the default path with the custom + if self.config_obj.points_path is not None: + # get the file name + path = filename.split(os.path.sep) + if len(path) > 0: + filename = path[-1] + else: + filename = '.' + os.path.sep + filename = self.config_obj.points_path + os.path.sep + filename + + output_file = filename + '.points1' + os.makedirs(os.path.dirname(output_file), exist_ok=True) + if os.path.exists(output_file): + os.remove(output_file) + + with open(output_file, 'a') as fileobj: + header_str = "pofd\t pody\n" + fileobj.write(header_str) + all_pody = [] + all_pofd = [] + for series in self.series_list: + pody_points = series.series_points[1] + pofd_points = series.series_points[0] + all_pody.extend(pody_points) + all_pofd.extend(pofd_points) - # save html - self.figure.write_html(html_name, include_plotlyjs=False) + all_points = zip(all_pofd, all_pody) + for idx, pts in enumerate(all_points): + data_str = str(pts[0]) + "\t" + str(pts[1]) + "\n" + fileobj.write(data_str) def main(config_filename=None): @@ -552,17 +416,7 @@ def main(config_filename=None): @param config_filename: default is None, the name of the custom config file to apply Returns: """ - params = util.get_params(config_filename) - try: - r = ROCDiagram(params) - r.save_to_file() - - r.write_html() - r.logger.info(f"Finished ROC diagram: {datetime.now()}") - - #r.show_in_browser() - except ValueError as ve: - print(ve) + util.make_plot(config_filename, ROCDiagram) if __name__ == "__main__": diff --git a/metplotpy/plots/roc_diagram/roc_diagram_config.py b/metplotpy/plots/roc_diagram/roc_diagram_config.py index a2ca8a01..06855e27 100644 --- a/metplotpy/plots/roc_diagram/roc_diagram_config.py +++ b/metplotpy/plots/roc_diagram/roc_diagram_config.py @@ -15,9 +15,9 @@ """ __author__ = 'Minna Win' -from ..config_plotly import Config -from .. import util_plotly as util -from .. import constants_plotly as constants +from ..config import Config +from .. import util +from .. import constants class ROCDiagramConfig(Config): def __init__(self, parameters): @@ -61,10 +61,11 @@ def __init__(self, parameters): self.linetype_ctc = self.get_config_value('roc_ctc') # Probability contingency table count line type self.linetype_pct = self.get_config_value('roc_pct') + # TODO: it looks like both roc_ctc and roc_pct cannot be set - add error check? # Supported values for stat_curve are none, mean, and median self.plot_stat = self.get_config_value('stat_curve') - self.plot_width = self.calculate_plot_dimension('plot_width', 'pixels') - self.plot_height = self.calculate_plot_dimension('plot_height', 'pixels') + self.plot_width = self.calculate_plot_dimension('plot_width') + self.plot_height = self.calculate_plot_dimension('plot_height') self.plot_resolution = self._get_plot_resolution() reverse_ctc_connection = str(self.get_config_value('reverse_connection_order')) if reverse_ctc_connection.upper() == "FALSE": @@ -74,21 +75,21 @@ def __init__(self, parameters): # title parameters self.title_font_size = self.parameters['title_size'] * constants.DEFAULT_TITLE_FONT_SIZE - self.title_offset = self.parameters['title_offset'] * constants.DEFAULT_TITLE_OFFSET + self.title_offset = 1.0 + abs(self.parameters['title_offset']) * constants.DEFAULT_TITLE_OFFSET self.y_title_font_size = self.parameters['ylab_size'] + constants.DEFAULT_TITLE_FONTSIZE # Caption settings self.caption = self.get_config_value('plot_caption') - self.caption_weight = self.get_config_value('caption_weight') self.caption_color = self.get_config_value('caption_col') # caption size is a magnification value self.caption_size = float(self.get_config_value('caption_size')) * constants.DEFAULT_CAPTION_FONTSIZE - self.caption_offset = self.get_config_value('caption_offset') - 3.1 + self.caption_offset = self.get_config_value('caption_offset') * constants.DEFAULT_CAPTION_Y_OFFSET self.caption_align = self.get_config_value('caption_align') self.caption = self.get_config_value('plot_caption') self.colors_list = self._get_colors() self.marker_list = self._get_markers() + self.marker_open_list = self._get_markers_open() self.linewidth_list = self._get_linewidths() self.linestyles_list = self._get_linestyles() self.user_legends = self._get_user_legends("ROC Curve") @@ -101,20 +102,21 @@ def __init__(self, parameters): # the location of the bounding box which defines # the legend. self.bbox_x = float(user_settings['bbox_x']) - # set legend box lower by .18 pixels of the default value - # set in METviewer to prevent obstructing the x-axis. - self.bbox_y = float(user_settings['bbox_y']) - 0.18 + self.bbox_y = float(user_settings['bbox_y']) legend_magnification = user_settings['legend_size'] self.legend_size = int(constants.DEFAULT_LEGEND_FONTSIZE * legend_magnification) self.legend_ncol = self.get_config_value('legend_ncol') + self.legend_orientation = 'v' # TODO: should this be always vertical? legend_box = self.get_config_value('legend_box').lower() if legend_box == 'n': # Don't draw a box around legend labels self.draw_box = False + self.legend_border_width = 0 else: # Other choice is 'o' # Enclose legend labels in a box self.draw_box = True + self.legend_border_width = 2 # x-axis parameters self.x_title_font_size = self.parameters['xlab_size'] * constants.DEFAULT_TITLE_FONT_SIZE @@ -122,7 +124,6 @@ def __init__(self, parameters): if self.x_tickangle in constants.XAXIS_ORIENTATION.keys(): self.x_tickangle = constants.XAXIS_ORIENTATION[self.x_tickangle] self.x_tickfont_size = self.parameters['xtlab_size'] * constants.DEFAULT_TITLE_FONT_SIZE - self.xaxis = util.apply_weight_style(self.xaxis, self.parameters['xlab_weight']) # y-axis parameters self.y_tickangle = self.parameters['ytlab_orient'] @@ -131,8 +132,8 @@ def __init__(self, parameters): self.y_tickfont_size = self.parameters['ytlab_size'] * constants.DEFAULT_TITLE_FONT_SIZE - self.plot_width = self.calculate_plot_dimension('plot_width', 'pixels') - self.plot_height = self.calculate_plot_dimension('plot_height', 'pixels') + self.plot_width = self.calculate_plot_dimension('plot_width') + self.plot_height = self.calculate_plot_dimension('plot_height') self.show_legend = self._get_show_legend() if 'summary_curve' in self.parameters.keys(): @@ -230,7 +231,7 @@ def _get_series_order(self): """ ordinals = self.get_config_value('series_order') - series_order_list = [ord for ord in ordinals] + series_order_list = list(ordinals) return series_order_list @@ -278,34 +279,3 @@ def _get_point_thresh(self): return True else: return False - - - def _get_markers(self): - """ - Retrieve all the markers. Convert marker names from - the config file into plotly python's marker names. - - Args: - - Returns: - markers: a list of the plotly markers - """ - markers = self.get_config_value('series_symbols') - markers_list = [] - for marker in markers: - if marker in constants.AVAILABLE_PLOTLY_MARKERS_LIST: - # the recognized plotly marker names: - # circle-open (for small circle), circle, triangle-up, - # square, diamond, or hexagon - markers_list.append(marker) - else: - # markers are indicated by name: circle-open (for small circle), - # circle, triangle-up, - # diamond, hexagon, square - m = marker.lower() - markers_list.append(constants.PCH_TO_PLOTLY_MARKER[m]) - markers_list_ordered = self.create_list_by_series_ordering(markers_list) - return markers_list_ordered - - - diff --git a/metplotpy/plots/roc_diagram/roc_diagram_series.py b/metplotpy/plots/roc_diagram/roc_diagram_series.py index 81be29fe..9d3f59c2 100644 --- a/metplotpy/plots/roc_diagram/roc_diagram_series.py +++ b/metplotpy/plots/roc_diagram/roc_diagram_series.py @@ -17,7 +17,7 @@ import pandas as pd import metcalcpy.util.utils as utils from ..series import Series -from ..util_plotly import prepare_pct_roc, prepare_ctc_roc +from ..util import prepare_pct_roc, prepare_ctc_roc class ROCDiagramSeries(Series): diff --git a/metplotpy/plots/taylor_diagram/taylor_diagram_config.py b/metplotpy/plots/taylor_diagram/taylor_diagram_config.py index c7696cfa..dc182b6c 100644 --- a/metplotpy/plots/taylor_diagram/taylor_diagram_config.py +++ b/metplotpy/plots/taylor_diagram/taylor_diagram_config.py @@ -259,28 +259,6 @@ def _get_plot_disp(self) -> list: return plot_display_bools_ordered - def _get_markers(self) -> list: - """ - Retrieve all the markers. - - Args: - - Returns: - markers: a list of the markers - """ - markers = self.get_config_value('series_symbols') - markers_list = [] - for marker in markers: - if marker in constants.AVAILABLE_MARKERS_LIST: - # markers is the matplotlib symbol: .,o, ^, d, H, or s - markers_list.append(marker) - else: - # markers are indicated by name: small circle, circle, triangle, - # diamond, hexagon, square - markers_list.append(constants.PCH_TO_MATPLOTLIB_MARKER[marker.lower()]) - markers_list_ordered = self.create_list_by_series_ordering(list(markers_list)) - return markers_list_ordered - def _config_consistency_check(self) -> bool: """ Checks that the number of settings defined for diff --git a/metplotpy/plots/util.py b/metplotpy/plots/util.py index 5c0a41ee..04971e9f 100644 --- a/metplotpy/plots/util.py +++ b/metplotpy/plots/util.py @@ -99,7 +99,6 @@ def make_plot(config_filename, plot_class): try: plot = plot_class(params) plot.save_to_file() - plot.write_output_file() name = plot_class.__name__ if not hasattr(plot_class, 'LONG_NAME') else plot_class.LONG_NAME plot.logger.info(f"Finished {name} plot at {datetime.now()}") diff --git a/pyproject.toml b/pyproject.toml index 0bafdc06..e4933891 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -38,13 +38,14 @@ testpaths = ["test"] filterwarnings = ["ignore::RuntimeWarning"] [tool.coverage.run] -source = ["metplotpy/plots"] +source = ["metplotpy/plots" + ] omit = [ "config.py", - "config-3.py", + "config-3.py", ] relative_files = true - + [tool.coverage.report] exclude_also = [ "def __repr__", diff --git a/test/ens_ss/custom_ens_ss.yaml b/test/ens_ss/custom_ens_ss.yaml index 5d4dd003..0d04e1f5 100644 --- a/test/ens_ss/custom_ens_ss.yaml +++ b/test/ens_ss/custom_ens_ss.yaml @@ -1,3 +1,8 @@ +stat_input: !ENV '${TEST_DIR}/ens_ss.data' +plot_filename: !ENV '${TEST_OUTPUT}/ens_ss.png' + +points_path: !ENV '${TEST_OUTPUT}/intermed_files' + caption_align: 0.0 caption_col: '#333333' caption_offset: 3.0 @@ -54,8 +59,6 @@ plot_type: png16m plot_units: in plot_width: 11.0 -points_path: !ENV '${TEST_OUTPUT}/intermed_files' - series_line_style: - '-' - '-' @@ -115,9 +118,6 @@ y2tlab_orient: 1 y2tlab_perp: 1 y2tlab_size: 1.0 -stat_input: !ENV '${TEST_DIR}/ens_ss.data' -plot_filename: !ENV '${TEST_OUTPUT}/ens_ss.png' - # To save your log output to a file, specify a path and filename and uncomment the line below. Make sure you have # permissions to the directory you specify. The default, as specified in the default config file is stdout. #log_filename: ./ens_ss.log @@ -126,5 +126,5 @@ plot_filename: !ENV '${TEST_OUTPUT}/ens_ss.png' # Debug and info log level will produce more log output. #log_level: WARNING show_legend: - -True - -True \ No newline at end of file +- True +- True \ No newline at end of file diff --git a/test/reliability_diagram/custom_reliability_points1.yaml b/test/reliability_diagram/custom_reliability_points1.yaml index 8ea99682..657093ba 100644 --- a/test/reliability_diagram/custom_reliability_points1.yaml +++ b/test/reliability_diagram/custom_reliability_points1.yaml @@ -42,7 +42,7 @@ fixed_vars_vals_input: {} grid_col: '#cccccc' grid_lty: 3 grid_lwd: 1 -grid_on: 'True' +grid_on: 'False' grid_x: listX indy_label: [] indy_stagger_1: 'True' @@ -58,6 +58,7 @@ indy_vals: - '0.7' - '0.8' - '0.9' +- '1.0' indy_var: thresh_i inset_hist: 'True' legend_box: o diff --git a/test/roc_diagram/CTC_ROC_thresh.yaml b/test/roc_diagram/CTC_ROC_thresh.yaml index 9a1c5bb6..3f4bc376 100644 --- a/test/roc_diagram/CTC_ROC_thresh.yaml +++ b/test/roc_diagram/CTC_ROC_thresh.yaml @@ -32,7 +32,7 @@ fixed_vars_vals_input: {} grid_col: '#cccccc' grid_lty: 3 grid_lwd: 1 -grid_on: 'True' +grid_on: 'False' grid_x: listX indy_label: [] indy_stagger_1: 'False' diff --git a/test/roc_diagram/custom_roc_diagram.yaml b/test/roc_diagram/custom_roc_diagram.yaml index 3a1e463d..6831bad8 100644 --- a/test/roc_diagram/custom_roc_diagram.yaml +++ b/test/roc_diagram/custom_roc_diagram.yaml @@ -1,4 +1,6 @@ ---- +stat_input: !ENV '${TEST_DIR}/plot_20200507_074426.data' +plot_filename: !ENV '${TEST_OUTPUT}/roc_diagram_custom.png' + # Write points file. Set to True for METviewer use, # False otherwise dump_points_1: 'False' @@ -98,8 +100,6 @@ reverse_connection_order: False # Make the plot generated in METviewer interactive create_html: 'True' -stat_input: !ENV '${TEST_DIR}/plot_20200507_074426.data' -plot_filename: !ENV '${TEST_DIR}/roc_diagram_custom.png' # To save your log output to a file, specify a path and filename and uncomment the line below. Make sure you have # permissions to the directory you specify. The default, as specified in the default config file is stdout. diff --git a/test/roc_diagram/test_roc_diagram.py b/test/roc_diagram/test_roc_diagram.py index b44ea918..4569d356 100644 --- a/test/roc_diagram/test_roc_diagram.py +++ b/test/roc_diagram/test_roc_diagram.py @@ -16,8 +16,9 @@ ("CTC_ROC_thresh_dump_pts.yaml", ["CTC_ROC_thresh_dump_pts.png", "intermed_files/CTC_ROC_thresh.points1"]), ("CTC_ROC_summary.yaml", ["CTC_ROC_summary.png", "intermed_files/CTC_ROC_summary.points1"]), ("CTC_ROC_thresh_reverse_pts.yaml", ["CTC_ROC_thresh_reverse_pts.png", "intermed_files_reverse_pts/CTC_ROC_thresh.points1"]), - ("PCT_ROC.yaml", ["PCT_ROC.png", "PCT_ROC.html"]), - ("CTC_wind_reformatted.yaml", ["CTC_wind_reformatted.png", "CTC_wind_reformatted.html"]), + ("PCT_ROC.yaml", ["PCT_ROC.png"]), + ("CTC_wind_reformatted.yaml", ["CTC_wind_reformatted.png"]), + ("custom_roc_diagram.yaml", ["roc_diagram_custom.png"]), ]) def test_roc_diagram(module_setup_env, remove_files, input_yaml, expected_files): """Checking that the plot file is getting created but the points1 file is NOT""" @@ -115,7 +116,7 @@ def test_ee_returns_empty_df(module_setup_env, capsys, remove_files): "INFO: No resulting data after performing event equalization of axis 1 INFO: No points to plot (most likely as a result of event equalization). " """ - expected_files = ['CTC_ROC_ee.png', 'CTC_ROC_ee.html'] + expected_files = ['CTC_ROC_ee.png'] remove_files(os.environ['TEST_OUTPUT'], expected_files) custom_config_filename = f"{cwd}/CTC_ROC_ee.yaml" @@ -136,7 +137,7 @@ def test_pct_no_warnings(module_setup_env, remove_files): Verify that the ROC diagram is generated without FutureWarnings ''' - remove_files(os.environ['TEST_OUTPUT'], ['PCT_ROC.png', 'PCT_ROC.html']) + remove_files(os.environ['TEST_OUTPUT'], ['PCT_ROC.png']) custom_config_filename = f"{cwd}/PCT_ROC.yaml" print("\n Testing for FutureWarning..")