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..")