From 1c37ac5dde962863916e486900b1e8678c5b5222 Mon Sep 17 00:00:00 2001 From: sarahclaude Date: Mon, 29 Jan 2024 14:25:26 -0500 Subject: [PATCH 01/15] docs changes --- docs/notebooks/figanos_multiplots.ipynb | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/docs/notebooks/figanos_multiplots.ipynb b/docs/notebooks/figanos_multiplots.ipynb index 0d6f69e4..a4c076cb 100644 --- a/docs/notebooks/figanos_multiplots.ipynb +++ b/docs/notebooks/figanos_multiplots.ipynb @@ -41,14 +41,6 @@ "opened = xr.open_dataset(url, decode_timedelta=False)" ] }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Timeseries\n", - "Create multiple timeseries plot with matplotlib subplots and figanos as shown below." - ] - }, { "cell_type": "markdown", "metadata": {}, @@ -74,7 +66,8 @@ " plot_kw = {\"col\": \"time\"},\n", " features = ['coastline','ocean'],\n", " frame = False,\n", - " use_attrs={\"suptitle\": \"description\"}\n", + " use_attrs={\"suptitle\": \"description\"},\n", + "\n", " )\n" ] }, @@ -121,6 +114,10 @@ " \"coastline\": {\"edgecolor\": \"black\"},\n", " },\n", " fig_kw={\"figsize\": (7, 4)},\n", + " legend_kw={\n", + " 'ncol':4,\n", + " 'bbox_to_anchor':(0.15, 0.05)\n", + " },\n", " )\n" ] }, From 28d7cb2d25627288754cdd02b3eeec4b5668a29b Mon Sep 17 00:00:00 2001 From: juliettelavoie Date: Mon, 5 Feb 2024 12:21:28 -0500 Subject: [PATCH 02/15] env req --- environment-dev.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/environment-dev.yml b/environment-dev.yml index 7f467817..731cfb68 100644 --- a/environment-dev.yml +++ b/environment-dev.yml @@ -17,7 +17,7 @@ dependencies: - pyyaml - scikit-image - xarray - - xclim >=0.38 + - xclim >=0.47 # To make the package and notebooks usable - dask - h5py From 13fcf4a6ebef3ec6f7323d06852cf550969758d2 Mon Sep 17 00:00:00 2001 From: juliettelavoie Date: Mon, 5 Feb 2024 12:22:20 -0500 Subject: [PATCH 03/15] i was in the wrong branch sorry --- environment-dev.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/environment-dev.yml b/environment-dev.yml index 731cfb68..7f467817 100644 --- a/environment-dev.yml +++ b/environment-dev.yml @@ -17,7 +17,7 @@ dependencies: - pyyaml - scikit-image - xarray - - xclim >=0.47 + - xclim >=0.38 # To make the package and notebooks usable - dask - h5py From 6b1957ae837cd233b82c84e2f054832e313bcdf5 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 6 Feb 2024 13:03:44 +0000 Subject: [PATCH 04/15] Bump actions/upload-artifact from 4.3.0 to 4.3.1 Bumps [actions/upload-artifact](https://github.com/actions/upload-artifact) from 4.3.0 to 4.3.1. - [Release notes](https://github.com/actions/upload-artifact/releases) - [Commits](https://github.com/actions/upload-artifact/compare/26f96dfa697d77e81fd5907df203aa23a56210a8...5d5d22a31266ced268874388b861e4b58bb5c2f3) --- updated-dependencies: - dependency-name: actions/upload-artifact dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- .github/workflows/scorecard.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/scorecard.yml b/.github/workflows/scorecard.yml index 6e8c77fa..ccd791ae 100644 --- a/.github/workflows/scorecard.yml +++ b/.github/workflows/scorecard.yml @@ -69,7 +69,7 @@ jobs: # Upload the results as artifacts (optional). Commenting out will disable uploads of run results in SARIF # format to the repository Actions tab. - name: Upload artifact - uses: actions/upload-artifact@26f96dfa697d77e81fd5907df203aa23a56210a8 + uses: actions/upload-artifact@5d5d22a31266ced268874388b861e4b58bb5c2f3 with: name: SARIF file path: results.sarif From 503b0e6e06a80a0110204016581b4ed1ac64ac96 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 13 Feb 2024 12:21:26 +0000 Subject: [PATCH 05/15] Bump black from 24.1.1 to 24.2.0 Bumps [black](https://github.com/psf/black) from 24.1.1 to 24.2.0. - [Release notes](https://github.com/psf/black/releases) - [Changelog](https://github.com/psf/black/blob/main/CHANGES.md) - [Commits](https://github.com/psf/black/compare/24.1.1...24.2.0) --- updated-dependencies: - dependency-name: black dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index ebdab8f3..5af32af1 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -64,7 +64,7 @@ dev = [ "coveralls>=3.3.1", "pytest>=7.3.1", "pytest-cov>=4.0.0", - "black==24.1.1", + "black==24.2.0", "blackdoc==0.3.9", "isort==5.13.2", "pre-commit>=3.3.2" From 3507d4bab283281809d4a065c8be4e079e7f3712 Mon Sep 17 00:00:00 2001 From: Trevor James Smith <10819524+Zeitsperre@users.noreply.github.com> Date: Tue, 13 Feb 2024 11:37:05 -0500 Subject: [PATCH 06/15] Update .pre-commit-config.yaml --- .pre-commit-config.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 53ee2ba0..50a32d14 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -29,7 +29,7 @@ repos: hooks: - id: rst-inline-touching-normal - repo: https://github.com/psf/black-pre-commit-mirror - rev: 24.1.1 + rev: 24.2.0 hooks: - id: black exclude: ^docs/ @@ -53,7 +53,7 @@ repos: rev: v0.3.9 hooks: - id: blackdoc - additional_dependencies: [ 'black==24.1.1' ] + additional_dependencies: [ 'black==24.2.0' ] - repo: https://github.com/kynan/nbstripout rev: 0.6.1 hooks: From a7807a74f09587a3d53a3e60babcc27d238ee7a5 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 14 Feb 2024 13:00:24 +0000 Subject: [PATCH 07/15] Bump github/codeql-action from 3.24.0 to 3.24.1 Bumps [github/codeql-action](https://github.com/github/codeql-action) from 3.24.0 to 3.24.1. - [Release notes](https://github.com/github/codeql-action/releases) - [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md) - [Commits](https://github.com/github/codeql-action/compare/e8893c57a1f3a2b659b6b55564fdfdbbd2982911...e675ced7a7522a761fc9c8eb26682c8b27c42b2b) --- updated-dependencies: - dependency-name: github/codeql-action dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- .github/workflows/scorecard.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/scorecard.yml b/.github/workflows/scorecard.yml index ccd791ae..748e0927 100644 --- a/.github/workflows/scorecard.yml +++ b/.github/workflows/scorecard.yml @@ -77,6 +77,6 @@ jobs: # Upload the results to GitHub's code scanning dashboard. - name: Upload to code-scanning - uses: github/codeql-action/upload-sarif@e8893c57a1f3a2b659b6b55564fdfdbbd2982911 # 3.24.0 + uses: github/codeql-action/upload-sarif@e675ced7a7522a761fc9c8eb26682c8b27c42b2b # 3.24.1 with: sarif_file: results.sarif From 3a7c162c5c6c9ecc63280b43049a37142089b91d Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 14 Feb 2024 13:00:31 +0000 Subject: [PATCH 08/15] Bump actions/dependency-review-action from 4.0.0 to 4.1.0 Bumps [actions/dependency-review-action](https://github.com/actions/dependency-review-action) from 4.0.0 to 4.1.0. - [Release notes](https://github.com/actions/dependency-review-action/releases) - [Commits](https://github.com/actions/dependency-review-action/compare/4901385134134e04cec5fbe5ddfe3b2c5bd5d976...80f10bf419f34980065523f5efca7ebed17576aa) --- updated-dependencies: - dependency-name: actions/dependency-review-action dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- .github/workflows/dependency-review.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/dependency-review.yml b/.github/workflows/dependency-review.yml index 761056d6..c4e9e943 100644 --- a/.github/workflows/dependency-review.yml +++ b/.github/workflows/dependency-review.yml @@ -28,4 +28,4 @@ jobs: uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 - name: 'Dependency Review' - uses: actions/dependency-review-action@4901385134134e04cec5fbe5ddfe3b2c5bd5d976 + uses: actions/dependency-review-action@80f10bf419f34980065523f5efca7ebed17576aa From b56810030d6d7bbc51f14c9b219e9592e52e12da Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 15 Feb 2024 13:01:37 +0000 Subject: [PATCH 09/15] Bump github/codeql-action from 3.24.1 to 3.24.3 Bumps [github/codeql-action](https://github.com/github/codeql-action) from 3.24.1 to 3.24.3. - [Release notes](https://github.com/github/codeql-action/releases) - [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md) - [Commits](https://github.com/github/codeql-action/compare/e675ced7a7522a761fc9c8eb26682c8b27c42b2b...379614612a29c9e28f31f39a59013eb8012a51f0) --- updated-dependencies: - dependency-name: github/codeql-action dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- .github/workflows/scorecard.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/scorecard.yml b/.github/workflows/scorecard.yml index 748e0927..bd9c1b68 100644 --- a/.github/workflows/scorecard.yml +++ b/.github/workflows/scorecard.yml @@ -77,6 +77,6 @@ jobs: # Upload the results to GitHub's code scanning dashboard. - name: Upload to code-scanning - uses: github/codeql-action/upload-sarif@e675ced7a7522a761fc9c8eb26682c8b27c42b2b # 3.24.1 + uses: github/codeql-action/upload-sarif@379614612a29c9e28f31f39a59013eb8012a51f0 # 3.24.3 with: sarif_file: results.sarif From e4a45d892bb2272b16fbe6082b0af40c9ec33e5a Mon Sep 17 00:00:00 2001 From: sarahclaude Date: Thu, 15 Feb 2024 11:14:57 -0500 Subject: [PATCH 10/15] correct commits, .squeeze() dataarray --- CHANGES.rst | 2 +- docs/notebooks/figanos_multiplots.ipynb | 42 +++ figanos/matplotlib/plot.py | 470 +++++++++++++++--------- 3 files changed, 344 insertions(+), 170 deletions(-) diff --git a/CHANGES.rst b/CHANGES.rst index 280bfba0..f78c0e3d 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -20,7 +20,7 @@ New features and enhancements * `figanos` now uses `Semantic Versioning v2.0 `_. (:pull:`143`). * Figanos now adheres to PEPs 517/518/621 using the `flit` backend for building and packaging. (:pull:`135`). * New function ``fg.partition`` (:pull:`134`). -* Add wrapper around ``xarray.plot.facetgrid`` for map functions (``fg.gridmap``, ``fg.scattermap``, ``fg.hatchmap``). (:issue:`51`, :pull:`136`). +* Add wrapper around ``xarray.plot.facetgrid`` for multiple functions (``fg.gridmap``, ``fg.scattermap``, ``fg.hatchmap``, ``fg.timeseries``). (:issue:`51`, :pull:`136`). Bug fixes ^^^^^^^^^ diff --git a/docs/notebooks/figanos_multiplots.ipynb b/docs/notebooks/figanos_multiplots.ipynb index a4c076cb..a1c49ca2 100644 --- a/docs/notebooks/figanos_multiplots.ipynb +++ b/docs/notebooks/figanos_multiplots.ipynb @@ -30,6 +30,13 @@ "fg.utils.set_mpl_style('ouranos')" ] }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Timeseries" + ] + }, { "cell_type": "code", "execution_count": null, @@ -41,6 +48,41 @@ "opened = xr.open_dataset(url, decode_timedelta=False)" ] }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "ds_time = opened.isel(lon=[500], lat=[150, 250])\n", + "im = fg.timeseries({'p50': ds_time.tx_max_p50, 'p90': ds_time.tx_max_p90},\n", + " plot_kw={'p50': {\"col\": \"lat\"}, 'p90': {\"col\": \"lat\"}},\n", + " fig_kw={'figsize':(10,4)},\n", + " legend=\"edge\",\n", + " show_lat_lon=True)\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "#creating fake scenarios\n", + "ds_time = ds_time[['tx_max_p10', 'tx_max_p50', 'tx_max_p90']]\n", + "data = {'tasmax_ssp434': ds_time,\n", + " 'tasmax_ssp245': ds_time.copy()-10,\n", + " 'tasmax_ssp585': ds_time.copy()+10}\n", + "\n", + "fg.timeseries(data=data,\n", + " legend='facetgrid',\n", + " show_lat_lon=False,\n", + " fig_kw = {'figsize':(9,4)},\n", + " plot_kw={'tasmax_ssp434': {\"col\": \"lat\"}, 'tasmax_ssp245': {\"col\": \"lat\"}, \"tasmax_ssp585\": {\"col\": \"lat\"}}\n", + " )" + ] + }, { "cell_type": "markdown", "metadata": {}, diff --git a/figanos/matplotlib/plot.py b/figanos/matplotlib/plot.py index 4f211c36..9f99d29f 100644 --- a/figanos/matplotlib/plot.py +++ b/figanos/matplotlib/plot.py @@ -1,6 +1,7 @@ # noqa: D100 from __future__ import annotations +import copy import math import warnings from pathlib import Path @@ -101,6 +102,159 @@ def _plot_realizations( return ax +def _plot_timeseries( + ax: matplotlib.axes.Axes, + name: str, + arr: xr.DataArray | xr.Dataset, + plot_kw: dict[str, Any], + non_dict_data: bool, + array_categ: dict[str, Any], + legend: str, +) -> matplotlib.axes.Axes: + """Plot figanos timeseries. + + Parameters + ---------- + ax: matplotlib.axes.Axes + Axe to be used for plotting. + name : str + Dictionnary key of the plotted data. + arr : Dataset/DataArray + Data to be plotted. + plot_kw : dict + Dictionary of kwargs coming from the timeseries() input. + non_dic_data : bool + If True, plot_kw is not a dictionary. + array_categ: dict + Categories of data. + legend: str + Legend type. + + Returns + ------- + matplotlib.axes.Axes + """ + lines_dict = {} # created to facilitate accessing line properties later + # look for SSP, RCP, CMIP model color + cat_colors = Path(__file__).parents[1] / "data/ipcc_colors/categorical_colors.json" + if get_scen_color(name, cat_colors): + plot_kw[name].setdefault("color", get_scen_color(name, cat_colors)) + + # remove 'label' to avoid error due to double 'label' args + if "label" in plot_kw[name]: + del plot_kw[name]["label"] + warnings.warn(f'"label" entry in plot_kw[{name}] will be ignored.') + + if array_categ[name] == "ENS_REALS_DA": + _plot_realizations(ax, arr, name, plot_kw, non_dict_data) + + elif array_categ[name] == "ENS_REALS_DS": + if len(arr.data_vars) >= 2: + raise TypeError( + "To plot multiple ensembles containing realizations, use DataArrays outside a Dataset" + ) + for k, sub_arr in arr.data_vars.items(): + _plot_realizations(ax, sub_arr, name, plot_kw, non_dict_data) + + elif array_categ[name] == "ENS_PCT_DIM_DS": + for k, sub_arr in arr.data_vars.items(): + sub_name = ( + sub_arr.name if non_dict_data is True else (name + "_" + sub_arr.name) + ) + + # extract each percentile array from the dims + array_data = {} + for pct in sub_arr.percentiles.values: + array_data[str(pct)] = sub_arr.sel(percentiles=pct) + + # create a dictionary labeling the middle, upper and lower line + sorted_lines = sort_lines(array_data) + + # plot + lines_dict[sub_name] = ax.plot( + array_data[sorted_lines["middle"]]["time"], + array_data[sorted_lines["middle"]].values, + label=sub_name, + **plot_kw[name], + ) + + ax.fill_between( + array_data[sorted_lines["lower"]]["time"], + array_data[sorted_lines["lower"]].values, + array_data[sorted_lines["upper"]].values, + color=lines_dict[sub_name][0].get_color(), + linewidth=0.0, + alpha=0.2, + label=fill_between_label(sorted_lines, name, array_categ, legend), + ) + + # other ensembles + elif array_categ[name] in [ + "ENS_PCT_VAR_DS", + "ENS_STATS_VAR_DS", + "ENS_PCT_DIM_DA", + ]: + # extract each array from the datasets + array_data = {} + if array_categ[name] == "ENS_PCT_DIM_DA": + for pct in arr.percentiles: + array_data[str(int(pct))] = arr.sel(percentiles=int(pct)) + else: + for k, v in arr.data_vars.items(): + array_data[k] = v + + # create a dictionary labeling the middle, upper and lower line + sorted_lines = sort_lines(array_data) + + # plot + lines_dict[name] = ax.plot( + array_data[sorted_lines["middle"]]["time"], + array_data[sorted_lines["middle"]].values, + label=name, + **plot_kw[name], + ) + + ax.fill_between( + array_data[sorted_lines["lower"]]["time"], + array_data[sorted_lines["lower"]].values, + array_data[sorted_lines["upper"]].values, + color=lines_dict[name][0].get_color(), + linewidth=0.0, + alpha=0.2, + label=fill_between_label(sorted_lines, name, array_categ, legend), + ) + + # non-ensemble Datasets + elif array_categ[name] == "DS": + ignore_label = False + for k, sub_arr in arr.data_vars.items(): + sub_name = ( + sub_arr.name if non_dict_data is True else (name + "_" + sub_arr.name) + ) + + # if kwargs are specified by user, all lines are the same and we want one legend entry + if plot_kw[name]: + label = name if not ignore_label else "" + ignore_label = True + else: + label = sub_name + + lines_dict[sub_name] = ax.plot( + sub_arr["time"], sub_arr.values, label=label, **plot_kw[name] + ) + + # non-ensemble DataArrays + elif array_categ[name] in ["DA"]: + lines_dict[name] = ax.plot(arr["time"], arr.values, label=name, **plot_kw[name]) + + else: + raise ValueError( + "Data structure not supported" + ) # can probably be removed along with elif logic above, + # given that get_array_categ() also does this check + return ax + + def timeseries( data: dict[str, Any] | xr.DataArray | xr.Dataset, ax: matplotlib.axes.Axes | None = None, @@ -127,9 +281,9 @@ def timeseries( plot_kw : dict, optional Arguments to pass to the `plot()` function. Changes how the line looks. If 'data' is a dictionary, must be a nested dictionary with the same keys as 'data'. - legend : str (default 'lines') + legend : str (default 'lines') or dict 'full' (lines and shading), 'lines' (lines only), 'in_plot' (end of lines), - 'edge' (out of plot), 'none' (no legend). + 'edge' (out of plot), 'facetgrid' under figure, 'none' (no legend). If dict, arguments to pass to ax.legend(). show_lat_lon : bool, tuple, str or int If True, show latitude and longitude at the bottom right of the figure. Can be a tuple of axis coordinates (from 0 to 1, as a fraction of the axis length) representing @@ -194,192 +348,170 @@ def timeseries( # check: 'time' dimension and calendar format data = check_timeindex(data) + # set fig, ax if not provided + if ax is None and ( + "row" not in list(plot_kw.values())[0].keys() + and "col" not in list(plot_kw.values())[0].keys() + ): + fig, ax = plt.subplots(**fig_kw) + elif ax is not None and ( + "col" in list(plot_kw.values())[0].keys() + or "row" in list(plot_kw.values())[0].keys() + ): + raise ValueError("Cannot use 'ax' and 'col'/'row' at the same time.") + elif ax is None: + cfig_kw = fig_kw.copy() + if "figsize" in fig_kw: # add figsize to plot_kw for facetgrid + list(plot_kw.values())[0].setdefault("figsize", fig_kw["figsize"]) + cfig_kw.pop("figsize") + if cfig_kw: + for v in plot_kw.values(): + {"subplots_kws": cfig_kw} | v + warnings.warn( + "Only figsize and figure.add_subplot() arguments can be passed to fig_kw when using facetgrid." + ) + # set default use_attrs values - use_attrs.setdefault("title", "description") + if ax: + use_attrs.setdefault("title", "description") + else: + use_attrs.setdefault("suptitle", "description") use_attrs.setdefault("ylabel", "long_name") use_attrs.setdefault("yunits", "units") - # set fig, ax if not provided - if ax is None: - fig, ax = plt.subplots(**fig_kw) - # dict of array 'categories' array_categ = {name: get_array_categ(array) for name, array in data.items()} - - lines_dict = {} # created to facilitate accessing line properties later - + cp_plot_kw = copy.deepcopy(plot_kw) # get data and plot for name, arr in data.items(): - # look for SSP, RCP, CMIP model color - cat_colors = ( - Path(__file__).parents[1] / "data/ipcc_colors/categorical_colors.json" - ) - if get_scen_color(name, cat_colors): - plot_kw[name].setdefault("color", get_scen_color(name, cat_colors)) - - # remove 'label' to avoid error due to double 'label' args - if "label" in plot_kw[name]: - del plot_kw[name]["label"] - warnings.warn(f'"label" entry in plot_kw[{name}] will be ignored.') - - if array_categ[name] == "ENS_REALS_DA": - _plot_realizations(ax, arr, name, plot_kw, non_dict_data) - - elif array_categ[name] == "ENS_REALS_DS": - if len(arr.data_vars) >= 2: - raise TypeError( - "To plot multiple ensembles containing realizations, use DataArrays outside a Dataset" - ) - for k, sub_arr in arr.data_vars.items(): - _plot_realizations(ax, sub_arr, name, plot_kw, non_dict_data) - - elif array_categ[name] == "ENS_PCT_DIM_DS": - for k, sub_arr in arr.data_vars.items(): - sub_name = ( - sub_arr.name - if non_dict_data is True - else (name + "_" + sub_arr.name) - ) - - # extract each percentile array from the dims - array_data = {} - for pct in sub_arr.percentiles.values: - array_data[str(pct)] = sub_arr.sel(percentiles=pct) + if ax: + _plot_timeseries(ax, name, arr, plot_kw, non_dict_data, array_categ, legend) + else: + if name == list(data.keys())[0]: + # create empty DataArray with same dimensions as data first entry to create an empty xr.plot.FacetGrid + if isinstance(arr, xr.Dataset): + da = arr[list(arr.keys())[0]] + else: + da = arr + da = da.where(da == np.nan) + im = da.plot(**plot_kw[name], color="white") + + [ + cp_plot_kw[name].pop(key) + for key in ["row", "col", "figsize"] + if key in cp_plot_kw[name].keys() + ] + + # plot data in every axis of the facetgrid + for i in range(0, im.axs.shape[0]): + for j in range(0, im.axs.shape[1]): + sel_arr = {} + + if "row" in plot_kw[name]: + sel_arr[plot_kw[name]["row"]] = i + if "col" in plot_kw[name]: + sel_arr[plot_kw[name]["col"]] = j + + _plot_timeseries( + im.axs[i, j], + name, + arr.isel(**sel_arr).squeeze(), + cp_plot_kw, + non_dict_data, + array_categ, + legend, + ) - # create a dictionary labeling the middle, upper and lower line - sorted_lines = sort_lines(array_data) + # add/modify plot elements according to the first entry. + if ax: + set_plot_attrs( + use_attrs, + list(data.values())[0], + ax, + title_loc="left", + wrap_kw={"min_line_len": 35, "max_line_len": 48}, + ) + ax.set_xlabel( + get_localized_term("time").capitalize() + ) # check_timeindex() already checks for 'time' - # plot - lines_dict[sub_name] = ax.plot( - array_data[sorted_lines["middle"]]["time"], - array_data[sorted_lines["middle"]].values, - label=sub_name, - **plot_kw[name], + # other plot elements + if show_lat_lon: + if show_lat_lon is True: + plot_coords( + ax, + list(data.values())[0], + param="location", + loc="lower right", + backgroundalpha=1, ) - - ax.fill_between( - array_data[sorted_lines["lower"]]["time"], - array_data[sorted_lines["lower"]].values, - array_data[sorted_lines["upper"]].values, - color=lines_dict[sub_name][0].get_color(), - linewidth=0.0, - alpha=0.2, - label=fill_between_label(sorted_lines, name, array_categ, legend), + elif isinstance(show_lat_lon, (str, tuple, int)): + plot_coords( + ax, + list(data.values())[0], + param="location", + loc=show_lat_lon, + backgroundalpha=1, ) - - # other ensembles - elif array_categ[name] in [ - "ENS_PCT_VAR_DS", - "ENS_STATS_VAR_DS", - "ENS_PCT_DIM_DA", - ]: - # extract each array from the datasets - array_data = {} - if array_categ[name] == "ENS_PCT_DIM_DA": - for pct in arr.percentiles: - array_data[str(int(pct))] = arr.sel(percentiles=int(pct)) else: - for k, v in arr.data_vars.items(): - array_data[k] = v + raise TypeError(" show_lat_lon must be a bool, string, int, or tuple") - # create a dictionary labeling the middle, upper and lower line - sorted_lines = sort_lines(array_data) - - # plot - lines_dict[name] = ax.plot( - array_data[sorted_lines["middle"]]["time"], - array_data[sorted_lines["middle"]].values, - label=name, - **plot_kw[name], - ) + if legend is not None: + if not ax.get_legend_handles_labels()[0]: # check if legend is empty + pass + elif legend == "in_plot": + split_legend(ax, in_plot=True) + elif legend == "edge": + split_legend(ax, in_plot=False) + elif isinstance(legend, dict): + ax.legend(**legend) + else: + ax.legend() - ax.fill_between( - array_data[sorted_lines["lower"]]["time"], - array_data[sorted_lines["lower"]].values, - array_data[sorted_lines["upper"]].values, - color=lines_dict[name][0].get_color(), - linewidth=0.0, - alpha=0.2, - label=fill_between_label(sorted_lines, name, array_categ, legend), - ) + return ax + else: - # non-ensemble Datasets - elif array_categ[name] == "DS": - ignore_label = False - for k, sub_arr in arr.data_vars.items(): - sub_name = ( - sub_arr.name - if non_dict_data is True - else (name + "_" + sub_arr.name) + if legend is not None: + if not im.axs[-1, -1].get_legend_handles_labels()[ + 0 + ]: # check if legend is empty + pass + elif legend == "in_plot": + split_legend(im.axs[-1, -1], in_plot=True) + elif legend == "edge": + split_legend(im.axs[-1, -1], in_plot=False) + elif isinstance(legend, dict): + handles, labels = im.axs[-1, -1].get_legend_handles_labels() + legend = {"handles": handles, "labels": labels} | legend + im.fig.legend(**legend) + elif legend == "facetgrid": + handles, labels = im.axs[-1, -1].get_legend_handles_labels() + im.fig.legend( + handles, + labels, + loc="lower center", + ncol=len(im.axs[-1, -1].lines), + bbox_to_anchor=(0.5, -0.05), ) - # if kwargs are specified by user, all lines are the same and we want one legend entry - if plot_kw[name]: - label = name if not ignore_label else "" - ignore_label = True - else: - label = sub_name - - lines_dict[sub_name] = ax.plot( - sub_arr["time"], sub_arr.values, label=label, **plot_kw[name] + if show_lat_lon: + if show_lat_lon is True: + plot_coords( + None, + list(data.values())[0].isel(lat=0, lon=0), + param="location", + loc="lower right", + backgroundalpha=1, ) - - # non-ensemble DataArrays - elif array_categ[name] in ["DA"]: - lines_dict[name] = ax.plot( - arr["time"], arr.values, label=name, **plot_kw[name] - ) - - else: - raise ValueError( - "Data structure not supported" - ) # can probably be removed along with elif logic above, - # given that get_array_categ() also does this check - - # add/modify plot elements according to the first entry. - set_plot_attrs( - use_attrs, - list(data.values())[0], - ax, - title_loc="left", - wrap_kw={"min_line_len": 35, "max_line_len": 48}, - ) - ax.set_xlabel( - get_localized_term("time").capitalize() - ) # check_timeindex() already checks for 'time' - - # other plot elements - if show_lat_lon: - if show_lat_lon is True: - plot_coords( - ax, - list(data.values())[0], - param="location", - loc="lower right", - backgroundalpha=1, - ) - elif isinstance(show_lat_lon, (str, tuple, int)): - plot_coords( - ax, - list(data.values())[0], - param="location", - loc=show_lat_lon, - backgroundalpha=1, - ) - else: - raise TypeError(" show_lat_lon must be a bool, string, int, or tuple") - - if legend is not None: - if not ax.get_legend_handles_labels()[0]: # check if legend is empty - pass - elif legend == "in_plot": - split_legend(ax, in_plot=True) - elif legend == "edge": - split_legend(ax, in_plot=False) - else: - ax.legend() - - return ax + elif isinstance(show_lat_lon, (str, tuple, int)): + plot_coords( + None, + list(data.values())[0].isel(lat=0, lon=0), + param="location", + loc=show_lat_lon, + backgroundalpha=1, + ) + return im def gridmap( From 1311e6e90a6e87d6f770a1903c62abde23e7b5fe Mon Sep 17 00:00:00 2001 From: sarahclaude Date: Thu, 15 Feb 2024 11:31:08 -0500 Subject: [PATCH 11/15] tas exemples for hatchmap --- docs/notebooks/figanos_multiplots.ipynb | 26 +++++++------------------ 1 file changed, 7 insertions(+), 19 deletions(-) diff --git a/docs/notebooks/figanos_multiplots.ipynb b/docs/notebooks/figanos_multiplots.ipynb index a1c49ca2..b0b81dd9 100644 --- a/docs/notebooks/figanos_multiplots.ipynb +++ b/docs/notebooks/figanos_multiplots.ipynb @@ -170,32 +170,20 @@ "outputs": [], "source": [ "from xclim import ensembles\n", - "urls = ['https://pavics.ouranos.ca/twitcher/ows/proxy/thredds/dodsC/birdhouse/ouranos/portraits-clim-1.1/NorESM1-M_rcp85_prcptot_monthly.nc',\n", - " 'https://pavics.ouranos.ca/twitcher/ows/proxy/thredds/dodsC/birdhouse/ouranos/portraits-clim-1.1/MPI-ESM-LR_rcp85_prcptot_monthly.nc',\n", - " 'https://pavics.ouranos.ca/twitcher/ows/proxy/thredds/dodsC/birdhouse/ouranos/portraits-clim-1.1/IPSL-CM5B-LR_rcp85_prcptot_monthly.nc',\n", - " ]\n", - "ens = ensembles.create_ensemble(urls)\n", - "mean_hor = xr.concat([ens.sel(time=slice(\"2020\", \"2050\")).mean(\"time\", keep_attrs='True').assign_coords(horizon=\"Delta 2020-2050\"),\n", - " ens.sel(time=slice(\"2050\", \"2080\")).mean(\"time\", keep_attrs='True').assign_coords(horizon=\"Delta 2050-2080\")],\n", - " dim=\"horizon\")\n", - "delta = mean_hor - ens.sel(time=slice(\"1990\", \"2020\")).mean(\"time\")\n", - "chng_f, pos_f = ensembles.change_significance(\n", - " delta, test=\"threshold\", abs_thresh=2\n", - ")\n", - "sup_8 = chng_f.where(chng_f.prcptot>0.8)\n", - "inf_5 = chng_f.where(chng_f.prcptot<0.5)\n", + "sup_305k = ds_space.where(ds_space.tx_max_p50>305)\n", + "inf_300k = ds_space.where(ds_space.tx_max_p50<300)\n", "\n", - "im = fg.hatchmap({'sup_8': sup_8, 'inf_5': inf_5},\n", + "im = fg.hatchmap({'sup_305k': sup_305k, 'inf_300k': inf_300k},\n", " plot_kw={\n", - " 'sup_8': {\n", + " 'sup_305k': {\n", " 'hatches': '*',\n", - " 'col': 'horizon',\n", + " 'col': 'time',\n", " \"x\": \"lon\",\n", " \"y\": \"lat\"\n", " },\n", - " 'inf_5': {\n", + " 'inf_300k': {\n", " 'hatches': 'x',\n", - " 'col': 'horizon',\n", + " 'col': 'time',\n", " \"x\": \"lon\",\n", " \"y\": \"lat\"\n", " },\n", From 6bd521d6d688a2539c6d76d2dc7550f2a1a167a4 Mon Sep 17 00:00:00 2001 From: sarahclaude Date: Thu, 15 Feb 2024 17:02:04 -0500 Subject: [PATCH 12/15] changed title --- docs/notebooks/figanos_multiplots.ipynb | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/docs/notebooks/figanos_multiplots.ipynb b/docs/notebooks/figanos_multiplots.ipynb index b0b81dd9..09a94acf 100644 --- a/docs/notebooks/figanos_multiplots.ipynb +++ b/docs/notebooks/figanos_multiplots.ipynb @@ -4,7 +4,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "# Figanos multiple plots" + "# Multiple plots" ] }, { @@ -17,7 +17,11 @@ { "cell_type": "code", "execution_count": null, - "metadata": {}, + "metadata": { + "pycharm": { + "is_executing": true + } + }, "outputs": [], "source": [ "# import necessary libraries\n", From bca0320a0c0a0a1e1057b85925ed423f47b3ee61 Mon Sep 17 00:00:00 2001 From: sarahclaude Date: Fri, 16 Feb 2024 13:48:09 -0500 Subject: [PATCH 13/15] Prepare for v0.3.0 release --- .cruft.json | 2 +- CHANGES.rst | 2 +- figanos/__init__.py | 2 +- pyproject.toml | 2 +- tests/test_figanos.py | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/.cruft.json b/.cruft.json index 097e5472..2860861a 100644 --- a/.cruft.json +++ b/.cruft.json @@ -11,7 +11,7 @@ "project_slug": "figanos", "project_short_description": "Outils pour produire des graphiques informatifs sur les impacts des changements climatiques.", "pypi_username": "Sarahclaude", - "version": "0.2.2", + "version": "0.3.0", "use_pytest": "y", "use_black": "y", "use_conda": "y", diff --git a/CHANGES.rst b/CHANGES.rst index f13dd180..0c1c0dca 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -2,7 +2,7 @@ Changelog ========= -0.3.0 (Unreleased) +0.3.0 (2024-02-16) ------------------ Contributors to this version: Sarah-Claude Bourdeau-Goulet (:user:`Sarahclaude`), Pascal Bourgault (:user:`aulemahal`), Trevor James Smith (:user:`Zeitsperre`), Juliette Lavoie (:user:`juliettelavoie`), Gabriel Rondeau-Genesse (:user:`RondeauG`). diff --git a/figanos/__init__.py b/figanos/__init__.py index 6057226f..59a3e243 100644 --- a/figanos/__init__.py +++ b/figanos/__init__.py @@ -2,7 +2,7 @@ __author__ = """Sarah-Claude Bourdeau-Goulet""" __email__ = "bourdeau-goulet.sarah-claude@ouranos.ca" -__version__ = "0.2.2" +__version__ = "0.3.0" from . import matplotlib from ._logo import Logos diff --git a/pyproject.toml b/pyproject.toml index 5af32af1..9fb10b01 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -108,7 +108,7 @@ target-version = [ ] [tool.bumpversion] -current_version = "0.2.2" +current_version = "0.3.0" commit = true commit_args = "--no-verify" tag = false diff --git a/tests/test_figanos.py b/tests/test_figanos.py index b7f529e9..414396db 100644 --- a/tests/test_figanos.py +++ b/tests/test_figanos.py @@ -35,4 +35,4 @@ def test_package_metadata(): contents = f.read() assert """Sarah-Claude Bourdeau-Goulet""" in contents assert '__email__ = "bourdeau-goulet.sarah-claude@ouranos.ca"' in contents - assert '__version__ = "0.2.2"' in contents + assert '__version__ = "0.3.0"' in contents From 57d585184f2b968595525fe328bf88b70b8dc1de Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 19 Feb 2024 12:56:08 +0000 Subject: [PATCH 14/15] Bump actions/dependency-review-action from 4.1.0 to 4.1.1 Bumps [actions/dependency-review-action](https://github.com/actions/dependency-review-action) from 4.1.0 to 4.1.1. - [Release notes](https://github.com/actions/dependency-review-action/releases) - [Commits](https://github.com/actions/dependency-review-action/compare/80f10bf419f34980065523f5efca7ebed17576aa...fd07d42ce87ab09f10c61a2d1a5e59e6c655620a) --- updated-dependencies: - dependency-name: actions/dependency-review-action dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- .github/workflows/dependency-review.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/dependency-review.yml b/.github/workflows/dependency-review.yml index c4e9e943..236b8b9b 100644 --- a/.github/workflows/dependency-review.yml +++ b/.github/workflows/dependency-review.yml @@ -28,4 +28,4 @@ jobs: uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 - name: 'Dependency Review' - uses: actions/dependency-review-action@80f10bf419f34980065523f5efca7ebed17576aa + uses: actions/dependency-review-action@fd07d42ce87ab09f10c61a2d1a5e59e6c655620a From 297d7b9114faaada310ec37a2a36d2964ab48516 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 20 Feb 2024 12:56:41 +0000 Subject: [PATCH 15/15] Bump actions/dependency-review-action from 4.1.1 to 4.1.2 Bumps [actions/dependency-review-action](https://github.com/actions/dependency-review-action) from 4.1.1 to 4.1.2. - [Release notes](https://github.com/actions/dependency-review-action/releases) - [Commits](https://github.com/actions/dependency-review-action/compare/fd07d42ce87ab09f10c61a2d1a5e59e6c655620a...be8bc500ee15e96754d2a6f2d34be14e945a46f3) --- updated-dependencies: - dependency-name: actions/dependency-review-action dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- .github/workflows/dependency-review.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/dependency-review.yml b/.github/workflows/dependency-review.yml index 236b8b9b..7f40588c 100644 --- a/.github/workflows/dependency-review.yml +++ b/.github/workflows/dependency-review.yml @@ -28,4 +28,4 @@ jobs: uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 - name: 'Dependency Review' - uses: actions/dependency-review-action@fd07d42ce87ab09f10c61a2d1a5e59e6c655620a + uses: actions/dependency-review-action@be8bc500ee15e96754d2a6f2d34be14e945a46f3