diff --git a/docs/source/plotting_scripts/maps.py b/docs/source/plotting_scripts/maps.py index 7a8f0d1c3..9de2f0ab4 100644 --- a/docs/source/plotting_scripts/maps.py +++ b/docs/source/plotting_scripts/maps.py @@ -3,6 +3,7 @@ #%% Imports import os import sys +from adjustText import adjust_text import shapely import datetime import numpy as np @@ -230,6 +231,7 @@ dfcounty.plot(ax=ax, facecolor='none', edgecolor='C7', lw=0.02, zorder=1e6) if draw_lakes: greatlakes.plot(ax=ax, edgecolor='#2CA8E7', facecolor='#D3EFFA', lw=0.2, zorder=-1) + text_artists = [] for r, row in dfregion.iterrows(): dfregion.loc[[r]].plot(ax=ax, color=colors[r], alpha=alpha_region, lw=0, zorder=1) if label_regions.get(level, True): @@ -237,32 +239,39 @@ np.array([row.geometry.centroid.x, row.geometry.centroid.y]) + np.array(offset.get(level, {}).get(r, (0,0))) ) - for i, (c, a) in enumerate([('k',1), (colors[r], 0.6)]): + ## Shade the r labels with the zone color + for i, (_color, _alpha) in enumerate([('k', 1), (colors[r], 0.6)]): if i == 1 and level != 'r': continue - ax.annotate( + text_artists.append(ax.annotate( (r if level == 'r' else r.replace('_','\n')), (x, y), ha='center', va='center', weight='bold', size={'r':7, 'hurdlereg':7, 'st':10}.get(level,11), - color=c, zorder=1e11+i, alpha=a, + color=_color, zorder=1e11+i, alpha=_alpha, path_effects=( [pe.withStroke(linewidth=1.5, foreground='w', alpha=(1 if i == 0 else 0))] ), - ) + )) + adjust_text(text_artists, ax=ax, avoid_self=False, ensure_inside_axes=True) + + text_artists = [] if label_zones.get(level, True): for r, row in dfmap['r'].iterrows(): x, y = ( np.array([row.geometry.centroid.x, row.geometry.centroid.y]) + np.array(offset.get('r', {}).get(r, (0,0))) ) - ax.annotate( - r, - (x, y), - ha='center', va='center', size=6, weight='normal', - color='C7', zorder=1e10, - path_effects=[pe.withStroke(linewidth=0.7, foreground='w', alpha=1)] + text_artists.append( + ax.annotate( + r, + (x, y), + ha='center', va='center', size=6, weight='normal', + color='C7', zorder=1e10, + path_effects=[pe.withStroke(linewidth=0.7, foreground='w', alpha=1)] + ) ) + adjust_text(text_artists, ax=ax, avoid_self=False, ensure_inside_axes=True) ax.axis('off') savename = ( diff --git a/environment.yml b/environment.yml index 8df757e15..bbe8d8b05 100644 --- a/environment.yml +++ b/environment.yml @@ -27,6 +27,7 @@ dependencies: - tqdm=4.68 ## vvv The next packages are optional for default settings and may ## vvv be excluded if you're having trouble building the environment. + - adjustText=1.4 # for avoiding map label overlaps - fiona=1.10 # for interactive maps - folium=0.20 # for interactive maps - ipykernel=7.3 # for interactive python in VS Code diff --git a/postprocessing/compare_cases.py b/postprocessing/compare_cases.py index 3ab6c8dbe..0d964b430 100644 --- a/postprocessing/compare_cases.py +++ b/postprocessing/compare_cases.py @@ -130,7 +130,7 @@ #%%### Fixed inputs -cmap = cmocean.cm.rain +cmap = cmocean.cm.tempo cmap_diff = plt.cm.RdBu_r ## https://www.whitehouse.gov/wp-content/uploads/2023/11/CircularA-4.pdf discountrate_social = DEFAULT_DISCOUNT_RATE @@ -2595,7 +2595,7 @@ def two_bars(dfplot, basecase, colors, ax, col=0, ypad=0.02): year=lastyear, casebase=casebase, casecomp=casecomp, level=level, plot='base', f=f, ax=ax[0], - cmap=cmocean.cm.rain, + cmap=cmap, ) ax[0].annotate( casebase_name, @@ -2606,7 +2606,7 @@ def two_bars(dfplot, basecase, colors, ax, col=0, ypad=0.02): year=lastyear, casebase=casebase, casecomp=casecomp, level=level, plot='comp', f=f, ax=ax[1], - cmap=cmocean.cm.rain, + cmap=cmap, ) ax[1].annotate( casecomp_name, @@ -2682,6 +2682,7 @@ def two_bars(dfplot, basecase, colors, ax, col=0, ypad=0.02): ax=ax[coords[case]], column='GW', cmap=cmap, vmin=vmin, vmax=vmax, legend=False, ) + reedsplots.label_region_value(dfplot, ax=ax[coords[case]], column="GW", fontsize=5) ## Legend if coords[case] == legendcoords: plots.addcolorbarhist( @@ -2753,6 +2754,7 @@ def two_bars(dfplot, basecase, colors, ax, col=0, ypad=0.02): vmax=(absmax if case == basecase else diffmax), legend=False, ) + reedsplots.label_region_value(dfplot, ax=ax[coords[case]], column="GW", fontsize=5) ## Difference legend if coords[case] == legendcoords: plots.addcolorbarhist( diff --git a/postprocessing/single_case_plots.py b/postprocessing/single_case_plots.py index e35683d88..7c44145cb 100644 --- a/postprocessing/single_case_plots.py +++ b/postprocessing/single_case_plots.py @@ -29,7 +29,7 @@ ## the position of the annotations crs = 'ESRI:102008' ### General purpose -cmap = cmocean.cm.rain +cmap = cmocean.cm.tempo ### For VRE siting & transmission maps transalpha = 0.25 transcolor = 'k' @@ -267,7 +267,7 @@ plt.close() f,ax = reedsplots.plot_trans_vsc( case=case, year=year, wscale=wscale_straight*1e3, - alpha=1.0, miles=300, + alpha=1.0, miles=300, cmap=cmap, ) savename = f'map_translines_vsc-{year}.png' if write and (f is not None): @@ -288,7 +288,10 @@ for vmax in ['each', 'shared']: try: f,ax = reedsplots.map_capacity_techs( - case, year=year, ncols=ncols, vmax=vmax, + case, year=year, ncols=ncols, vmax=vmax, cmap=cmap, + label_regions = ( + True if len(reeds.inputs.parse_regions(case))<=100 else False + ), ) savename = f'map_capacity-{year}-{vmax}.png' if write: @@ -517,7 +520,11 @@ if int(sw.GSw_H2): plt.close() f,ax = reedsplots.map_h2_capacity( - case=case, year=year, cmap=cmap, wscale_h2=wscale_h2) + case=case, year=year, cmap=cmap, wscale_h2=wscale_h2, + label_regions = ( + True if len(reeds.inputs.parse_regions(case))<=100 else False + ), + ) savename = f'map_h2_capacity-{sw.endyear}.png' if write: plt.savefig(os.path.join(savepath, savename)) @@ -646,7 +653,7 @@ try: for y in [y for y in years if y >= 2025]: plt.close() - f, ax, neue, _iteration = reedsplots.map_neue(case=case, year=y) + f, ax, neue, _iteration = reedsplots.map_neue(case=case, year=y, cmap=cmap) savename = f"map_PRAS_neue-{y}i{_iteration}.png" if write: plt.savefig(os.path.join(savepath, savename)) @@ -860,7 +867,7 @@ #%% PRM if iterating if int(sw.GSw_PRM_StressIterateMax) and int(sw.GSw_PRM_UpdateMethod): try: - f, ax, prm_final = reedsplots.map_prm(case) + f, ax, prm_final = reedsplots.map_prm(case, cmap=cmap) savename = 'map_prm.png' if write: plt.savefig(os.path.join(savepath, savename)) @@ -928,6 +935,7 @@ years=[year], vscale=1e-3, vmin=0, + cmap=cmap, title='Sited demand [GW]', ) savename = f'map_loadsite-{year}.png' diff --git a/reeds/input_processing/hourly_plots.py b/reeds/input_processing/hourly_plots.py index d477d72ce..5fc00045f 100644 --- a/reeds/input_processing/hourly_plots.py +++ b/reeds/input_processing/hourly_plots.py @@ -14,6 +14,7 @@ from pathlib import Path sys.path.append(str(Path(__file__).parent.parent.parent)) import reeds +from reeds import reedsplots from reeds import plots plots.plotparams() @@ -230,7 +231,7 @@ def plot_maps(sw, inputs_case, reeds_path, figpath, periodtype='rep', crs='EPSG: ### Settings cmaps = { 'cf_actual':plt.cm.turbo, 'cf_rep':plt.cm.turbo, 'cf_diff':plt.cm.RdBu_r, - 'GW_actual':cmocean.cm.rain, 'GW_rep':cmocean.cm.rain, + 'GW_actual':cmocean.cm.tempo, 'GW_rep':cmocean.cm.tempo, 'GW_diff':plt.cm.RdBu_r, 'GW_frac':plt.cm.RdBu_r, 'pct_diff':plt.cm.RdBu_r, } vm = { @@ -351,17 +352,16 @@ def plot_maps(sw, inputs_case, reeds_path, figpath, periodtype='rep', crs='EPSG: dfdiffs[level].plot( ax=ax[coords[level]], column='cf_diff', cmap=cmaps['cf_diff'], vmin=vm[tech]['cf_diff'][0], vmax=vm[tech]['cf_diff'][1], - lw=0, legend=False, + lw=0, legend=False, missing_kwds={"color": "white"}, ) dfmap[level].plot(ax=ax[coords[level]], facecolor='none', edgecolor='k', lw=0.2) - ## Text differences - for r, row in (dfdiffs[level].assign(val=dfdiffs[level].cf_diff.abs()).sort_values('val')).iterrows(): - decimals = 0 if abs(row.cf_diff) >= 1 else 1 - ax[coords[level]].annotate( - f"{row.cf_diff*100:+.{decimals}f}", - [row.centroid_x, row.centroid_y], - ha='center', va='center', c='k', fontsize={'r':5}.get(level,7), - path_effects=[pe.withStroke(linewidth=1.5, foreground='w', alpha=0.5)], + dfmap[level]['cf_diff_pct'] = (dfdiffs[level].cf_diff * 100).values + reedsplots.label_region_value( + dfmap[level], + ax[coords[level]], + column='cf_diff_pct', + text_kwargs={'fontsize': {'r':5}.get(level,7)}, + pe_kwargs={'linewidth': 1.5, 'foreground': 'w', 'alpha': 0.5}, ) ## Colorbar plots.addcolorbarhist( @@ -485,14 +485,12 @@ def plot_maps(sw, inputs_case, reeds_path, figpath, periodtype='rep', crs='EPSG: lw=0, legend=False, ) dfmap[level].plot(ax=ax[coords[level]], facecolor='none', edgecolor='k', lw=0.2) - ## Text differences - for r, row in (dfdiffs[level].assign(val=dfdiffs[level][val].abs()).sort_values('val')).iterrows(): - decimals = 0 if abs(row[val]) >= 1 else 1 - ax[coords[level]].annotate( - f"{row[val]:+.{decimals}f}", - [row.centroid_x, row.centroid_y], - ha='center', va='center', c='k', fontsize={'r':5}.get(level,7), - path_effects=[pe.withStroke(linewidth=1.5, foreground='w', alpha=0.5)], + reedsplots.label_region_value( + dfdiffs[level], + ax[coords[level]], + column=val, + text_kwargs={'fontsize': {'r':5}.get(level,7)}, + pe_kwargs={'linewidth': 1.5, 'foreground': 'w', 'alpha': 0.5}, ) ## Colorbar plots.addcolorbarhist( diff --git a/reeds/input_processing/outage_rates.py b/reeds/input_processing/outage_rates.py index 6d36110a7..ae52ff758 100644 --- a/reeds/input_processing/outage_rates.py +++ b/reeds/input_processing/outage_rates.py @@ -7,6 +7,7 @@ import argparse import h5py from pathlib import Path +import cmocean sys.path.append(str(Path(__file__).parent.parent.parent)) import reeds reeds_path = reeds.io.reeds_path @@ -19,6 +20,7 @@ tz_out = 'Etc/GMT+6' temp_min = -50 temp_max = 60 +cmap = cmocean.cm.tempo ## Only use during_quarters for techs without a monthly scheduled outage rate during_quarters = ['spring', 'fall'] ## Cap the extrapolation of forced outage rates at high/low temperatures to 0.4 because @@ -383,7 +385,7 @@ def plot_outage_forced( plt.close() f, ax = reeds.plots.map_years_months( - dfzones=dfzones, dfdata=dfdata, aggfunc=aggfunc, + dfzones=dfzones, dfdata=dfdata, cmap=cmap, aggfunc=aggfunc, title=f"Monthly {aggfunc}\nforced outage rate,\n{nicelabels.get(pm,pm)} [%]", ) plt.savefig(os.path.join(figpath, f'FOR_monthly-{aggfunc}-{pm}.png')) diff --git a/reeds/reedsplots.py b/reeds/reedsplots.py index 5b525a61b..ae0758946 100644 --- a/reeds/reedsplots.py +++ b/reeds/reedsplots.py @@ -981,6 +981,31 @@ def plot_diff_maps( dfbase[valcol] *= unitscaler dfcomp[valcol] *= unitscaler + ### Aggregate selected technologies to one value per region for the target year + dfbase_selected = ( + dfbase.loc[(dfbase.i.isin(titles)) & (dfbase.t == year)] + .groupby('r', as_index=False)[valcol].sum() + .rename(columns={valcol: f'{valcol}_base'}) + ) + dfcomp_selected = ( + dfcomp.loc[(dfcomp.i.isin(titles)) & (dfcomp.t == year)] + .groupby('r', as_index=False)[valcol].sum() + .rename(columns={valcol: f'{valcol}_comp'}) + ) + dfdiff_selected = ( + dfbase_selected + .merge(dfcomp_selected, on='r', how='outer') + .fillna(0) + ) + dfdiff_selected[f'{valcol}_diff'] = ( + dfdiff_selected[f'{valcol}_comp'] - dfdiff_selected[f'{valcol}_base'] + ) + dfdiff_selected[f'{valcol}_pctdiff'] = np.where( + dfdiff_selected[f'{valcol}_base'] != 0, + dfdiff_selected[f'{valcol}_diff'] / dfdiff_selected[f'{valcol}_base'] * 100, + np.nan, + ) + ### Start the plot # plt.close() if (f is None) and (ax is None): @@ -988,52 +1013,41 @@ def plot_diff_maps( else: pass - ###### Calculate the diff - dfdiff = dfbase.merge( - dfcomp, on=['i','r','t'], how='outer', suffixes=('_base','_comp')).fillna(0) - if plot in ['diff','pctdiff','pct_diff','diffpct','diff_pct','pct']: - ### Percent difference - dfdiff['{}_diff'.format(valcol)] = ( - (dfdiff['{}_comp'.format(valcol)] - dfdiff['{}_base'.format(valcol)]) - / dfdiff['{}_base'.format(valcol)] * 100 - ).replace(np.inf,np.nan) - elif plot in ['absdiff', 'abs_diff', 'diffabs', 'diff_abs']: - ### Difference - dfdiff['{}_diff'.format(valcol)] = ( - dfdiff['{}_comp'.format(valcol)] - dfdiff['{}_base'.format(valcol)]) - if zmax is None: zmax = max( - dfdiff.loc[(dfdiff.i.isin(titles))&(dfdiff.t==year),valcol+'_base'].max(), - dfdiff.loc[(dfdiff.i.isin(titles))&(dfdiff.t==year),valcol+'_comp'].max(), + dfdiff_selected[f'{valcol}_base'].max(), + dfdiff_selected[f'{valcol}_comp'].max(), ) ###### Plot the base if plot == 'base': dfplot = dfba.merge( - dfbase.loc[(dfbase.i.isin(titles))&(dfbase.t==year),['r',valcol]], + dfdiff_selected[['r', f'{valcol}_base']], left_index=True, right_on='r', how='left' - ).fillna(0).reset_index(drop=True) + ).fillna(0).reset_index(drop=True).rename(columns={f'{valcol}_base': valcol}) dfplot.plot(ax=ax, column=valcol, cmap=cmap, legend=True, legend_kwds=legend_kwds, vmax=zmax) + label_region_value(dfplot, ax=ax, column=valcol, text_kwargs={'fontsize':5}) ###### Plot the comp elif plot == 'comp': dfplot = dfba.merge( - dfcomp.loc[(dfcomp.i.isin(titles))&(dfcomp.t==year),['r',valcol]], + dfdiff_selected[['r', f'{valcol}_comp']], left_index=True, right_on='r', how='left' - ).fillna(0).reset_index(drop=True) + ).fillna(0).reset_index(drop=True).rename(columns={f'{valcol}_comp': valcol}) dfplot.plot(ax=ax, column=valcol, cmap=cmap, legend=True, legend_kwds=legend_kwds, vmax=zmax) + label_region_value(dfplot, ax=ax, column=valcol, text_kwargs={'fontsize':5}) + ###### Plot the pct diff elif plot in ['diff','pctdiff','pct_diff','diffpct','diff_pct','pct']: legend_kwds['label'] = '{} {} {}\n[% diff]'.format(valcol,i_plot,year) dfplot = dfba.merge( - dfdiff.loc[(dfdiff.i.isin(titles))&(dfdiff.t==year),['r',valcol+'_diff']], + dfdiff_selected[['r', f'{valcol}_pctdiff']], left_index=True, right_on='r', how='left' ).reset_index(drop=True) @@ -1046,13 +1060,14 @@ def plot_diff_maps( dfplot.plot(ax=ax, column=valcol+'_pctdiff', cmap=cmap, legend=True, vmin=-zlim, vmax=+zlim, legend_kwds=legend_kwds) + label_region_value(dfplot, ax=ax, column=valcol+'_pctdiff', text_kwargs={'fontsize':5}) ###### Plot the absolute diff elif plot in ['absdiff', 'abs_diff', 'diffabs', 'diff_abs']: legend_kwds['label'] = '{} diff {} [{}]'.format(valcol,i_plot,units) dfplot = dfba.merge( - dfdiff.loc[(dfdiff.i.isin(titles))&(dfdiff.t==year),['r',valcol+'_diff']], + dfdiff_selected[['r', f'{valcol}_diff']], left_index=True, right_on='r', how='left' ).reset_index(drop=True) @@ -1065,9 +1080,11 @@ def plot_diff_maps( dfplot.plot(ax=ax, column=valcol+'_diff', cmap=plt.cm.RdBu_r, legend=True, vmin=-zlim, vmax=+zlim, legend_kwds=legend_kwds) + label_region_value(dfplot, ax=ax, column=valcol+'_diff', text_kwargs={'fontsize':5}) ### Finish and return # ax.set_title(title, y=0.95) + dfba.plot(ax=ax, edgecolor='k', facecolor='none', lw=0.1) dfstates.plot(ax=ax, edgecolor='k', facecolor='none', lw=0.25) ax.axis('off') @@ -1358,6 +1375,10 @@ def map_net_imports( legend=False, vmin=-vmax[year], vmax=vmax[year], ) + label_region_value( + df, ax=ax[coords[year]], column='net_import', + fmt='{:.0f}', text_kwargs={'fontsize':5}, + ) ## Formatting ax[coords[year]].set_title(year, y=0.9) if vlim != 'shared': @@ -2698,6 +2719,7 @@ def map_capacity_techs( ncols=4, vmax='shared', cmap=cmocean.cm.rain, + label_regions=True, ): """ techs: list of technologies to plot, or 'aggregation' to plot all aggregated technologies @@ -2751,6 +2773,8 @@ def map_capacity_techs( 'label': '{} [GW]'.format(tech), } ) + if label_regions: + label_region_value(dfplot, ax=ax[coords[tech]], column='GW', text_kwargs={'fontsize':5}) ax[coords[tech]].axis('off') ax[0,0].set_title( '{} ({})'.format(os.path.basename(case), year), @@ -4373,6 +4397,7 @@ def map_neue( ## Labels # decimals = (0 if df.NEUE_ppm.max() >= 10 else 1) decimals = (0 if level in ['st','r'] else 1) + # label_region_value can't handle conditional formating, so keep as-is for r, row in df.sort_values('NEUE_ppm').iterrows(): if highlight_over_threshold: over_threshold = row.NEUE_ppm > neue_threshold @@ -4408,7 +4433,7 @@ def map_neue( def map_h2_capacity( case, year=2050, wscale_h2=10, figheight=6, pipescale=0.1, legend_kwds={'shrink':0.6, 'pad':0, 'orientation':'horizontal', 'aspect':12}, - cmap=cmocean.cm.rain, + cmap=cmocean.cm.rain, label_regions=True, ): """ H2 turbines, production (Electrolyzer/SMR), pipelines, and storage @@ -4494,16 +4519,22 @@ def map_h2_capacity( cap_h2turbine.plot( ax=ax[0,0], column='kTperday', cmap=cmap, lw=0, vmin=0, legend=True, legend_kwds={**legend_kwds, **{'label':'Turbines [kT/day]'}}) + if label_regions: + label_region_value(cap_h2turbine, ax=ax[0,0], column='kTperday', text_kwargs={'fontsize':5}) ### Electrolyzers if not cap_h2prod.empty: cap_h2prod.plot( ax=ax[0,1], column='kTperday', cmap=cmap, lw=0, vmin=0, legend=True, legend_kwds={**legend_kwds, **{'label':'Production [kT/day]'}}) + if label_regions: + label_region_value(cap_h2prod, ax=ax[0,1], column='kTperday', text_kwargs={'fontsize':5}) ### Storage if not cap_h2prod.empty: cap_storage.plot( ax=ax[1,0], column='h2_storage', cmap=cmap, lw=0, vmin=0, legend=True, legend_kwds={**legend_kwds, **{'label':'Storage [kT]'}}) + if label_regions: + label_region_value(cap_storage, ax=ax[1,0], column='h2_storage', text_kwargs={'fontsize':5}) ### Pipelines if not h2_trans_cap.empty: for i,row in h2_trans_cap.iterrows(): @@ -5105,12 +5136,13 @@ def plot_seed_stressperiods( ax=ax[row,col], column='val', edgecolor='none', lw=0, cmap=cmap, alpha=alpha, vmin=0, vmax=1, ) - for i, _row in df.iterrows(): - ax[row,col].annotate( - _row.date, (_row.centroid_x, _row.centroid_y), - ha='center', va='center', color='k', fontsize=fontsize, - path_effects=[pe.withStroke(linewidth=pelinewidth, foreground='w', alpha=pealpha)], - ) + label_region_value( + df, + ax=ax[row,col], + column='val', + text_kwargs={'fontsize': fontsize}, + pe_kwargs={'linewidth': pelinewidth, 'foreground': 'w', 'alpha': pealpha}, + ) ### Max load @@ -5141,12 +5173,13 @@ def plot_seed_stressperiods( ax=ax[row,col], column='val', edgecolor='none', lw=0, cmap=cmap, alpha=alpha, vmin=0, vmax=1, ) - for i, _row in df.iterrows(): - ax[row,col].annotate( - _row.date, (_row.centroid_x, _row.centroid_y), - ha='center', va='center', color='k', fontsize=fontsize, - path_effects=[pe.withStroke(linewidth=pelinewidth, foreground='w', alpha=pealpha)], - ) + label_region_value( + df, + ax=ax[row,col], + column='date', + text_kwargs={'fontsize': fontsize}, + pe_kwargs={'linewidth': pelinewidth, 'foreground': 'w', 'alpha': pealpha}, + ) ### Colorbar row, col = 0, 0 @@ -6142,14 +6175,13 @@ def map_outage_days( dfplot.plot(ax=_ax, column='outage_pct', cmap=cmap, vmin=vmin, vmax=vmax) ## Data values if fontsize: - for r, row in dfplot.sort_values('outage_pct').iterrows(): - _ax.annotate( - f"{row.outage_pct:.0f}", - [row.centroid_x, row.centroid_y], - ha='center', va='center', c='k', - fontsize=fontsize, - path_effects=[pe.withStroke(linewidth=1.4, foreground='w', alpha=0.7)], - ) + label_region_value( + df=dfplot, + ax=_ax, + column='outage_pct', + text_kwargs={'fontsize': fontsize}, + pe_kwargs={'linewidth': 1.4}, + ) _ax.axis('off') ## Formatting if date == dates[0]: @@ -6216,7 +6248,8 @@ def get_cf_map(case, tech='wind-ons', timestamp=None, recf=None, crs='EPSG:5070' dfsc['i'] = tech + '_' + dfsc['class'].astype(str) sitemap = reeds.io.get_sitemap(offshore=(True if tech == 'wind-ofs' else False)) dfsc['geometry'] = dfsc.index.map(sitemap.geometry) - dfsc = gpd.GeoDataFrame(dfsc).to_crs(crs) + dfsc = gpd.GeoDataFrame(dfsc, geometry='geometry', crs=sitemap.crs) + dfsc = dfsc.to_crs(crs) dfsc['cf'] = dfsc[['i','r']].merge(cf.rename('cf'), on=['i','r'], how='left').cf.values ## Convert to polygons @@ -6225,18 +6258,49 @@ def get_cf_map(case, tech='wind-ons', timestamp=None, recf=None, crs='EPSG:5070' return dfsc -def label_region_value(df, ax, column, fmt='{:.2f}', color='k', fontsize=8, **kwargs): - """kwargs are passed to patheffects.withStroke()""" - pe_kwargs = {**{'linewidth':1.5, 'foreground':'w', 'alpha':1}, **kwargs} +def label_region_value( + df, + ax, + column, + fmt='{auto}', + text_kwargs:None|dict=None, + pe_kwargs:None|dict=None, +): + """pe_kwargs are passed to patheffects.withStroke()""" + text_kwargs = { + 'ha':'center', 'va':'center', 'color':'k', 'fontsize':8, + **(text_kwargs if isinstance(text_kwargs, dict) else {}) + } + pe_kwargs = { + 'linewidth':1.5, 'foreground':'w', 'alpha':0.7, + **(pe_kwargs if isinstance(pe_kwargs, dict) else {}) + } + text_artists = [] for r, row in df.iterrows(): - ax.annotate( - fmt.format(row[column]), - (row.geometry.centroid.x, row.geometry.centroid.y), - ha='center', va='center', fontsize=fontsize, - color=color, - path_effects=[pe.withStroke(**pe_kwargs)], - zorder=1e9, + value = row.get(column, np.nan) + if isinstance(value, (int, float, complex)): + if not np.isfinite(value): + continue + if '{auto}' in fmt: + decimals = 0 if ((abs(value) >= 1) or abs(value) < 0.05) else 1 + fmt = fmt.replace('{auto}', f"{{:.{decimals}f}}") + text_artists.append( + ax.annotate( + (fmt.format(value) if not isinstance(value, str) else value), + (row.geometry.centroid.x, row.geometry.centroid.y), + **text_kwargs, + path_effects=[pe.withStroke(**pe_kwargs)], + zorder=1e9, + ) ) + try: + from adjustText import adjust_text + except ImportError: + return + + adjust_text(text_artists, ax=ax, + avoid_self=False, ensure_inside_axes=True, + ) def map_stressors( @@ -6276,7 +6340,7 @@ def map_stressors( """ ### Plot setup cmaps = { - 'load': cmocean.cm.rain, + 'load': cmocean.cm.tempo, # 'load': plt.cm.turbo, # 'wind-ons': cmocean.cm.ice_r, 'wind-ons': cmocean.cm.ice, @@ -6555,8 +6619,8 @@ def map_stressors( ax=ax[1,0], column='load_rank', cmap=cmaps['rank'], vmin=0, vmax=100, ) label_region_value( - df=dflevel, ax=ax[1,0], column='load_rank', fmt='{:.0f}%', - linewidth=2.0, alpha=0.8, + df=dflevel, ax=ax[1,0], column='load_rank', + fmt='{:.0%}', pe_kwargs={'linewidth':2.0, 'alpha':0.8}, ) ax[1,0].set_title('Demand', y=0.9) plots.addcolorbarhist( @@ -6570,8 +6634,8 @@ def map_stressors( ax=ax[1,col], column=f'{tech}_rank', cmap=cmaps['rank'], vmin=0, vmax=100, ) label_region_value( - df=dflevel, ax=ax[1,col], column=f'{tech}_rank', fmt='{:.0f}%', - linewidth=2.0, alpha=0.8, + df=dflevel, ax=ax[1,col], column=f'{tech}_rank', + fmt='{:.0%}', pe_kwargs={'linewidth':2.0, 'alpha':0.8}, ) ax[1,col].set_title(labels.get(tech,tech), y=0.9) plots.addcolorbarhist( @@ -6584,8 +6648,8 @@ def map_stressors( ax=ax[1,3], column='temperature_rank', cmap=cmaps['rank'], vmin=0, vmax=100, ) label_region_value( - df=dflevel, ax=ax[1,3], column='temperature_rank', fmt='{:.0f}%', - linewidth=2.0, alpha=0.8, + df=dflevel, ax=ax[1,3], column='temperature_rank', + fmt='{:.0%}', pe_kwargs={'linewidth':2.0, 'alpha':0.8}, ) ax[1,3].set_title('Temperature', y=0.9) plots.addcolorbarhist( @@ -6602,8 +6666,9 @@ def map_stressors( ) dflevel[f'outage_{tech}'] = outage_region.loc[(y,m,d), tech] label_region_value( - df=dflevel, ax=ax[2,col], column=f'outage_{tech}', fmt='{:.0f}%', - linewidth=2.0, alpha=0.8, + df=dflevel, ax=ax[2,col], column=f'outage_{tech}', + fmt='{:.0%}', + pe_kwargs={'linewidth':2.0, 'alpha':0.8}, ) ax[2,col].set_title(label, y=0.9) plots.addcolorbarhist( diff --git a/reeds/resource_adequacy/diagnostic_plots.py b/reeds/resource_adequacy/diagnostic_plots.py index 3e8f87e19..a7eb691ac 100644 --- a/reeds/resource_adequacy/diagnostic_plots.py +++ b/reeds/resource_adequacy/diagnostic_plots.py @@ -14,6 +14,7 @@ from pathlib import Path sys.path.append(str(Path(__file__).parent.parent.parent)) import reeds +from reeds import reedsplots from reeds import plots plots.plotparams() @@ -23,7 +24,7 @@ dpi = None interactive = False savefig = True - +cmap = cmocean.cm.tempo #%%### Functions def delete_temporary_files(sw): @@ -426,18 +427,22 @@ def map_dropped_load(sw, dfs, level='r'): ### Background dfba.plot(ax=ax, facecolor='none', edgecolor='k', lw=0.2) ### Data - dfplot.plot(ax=ax, column='val', cmap=cmocean.cm.rain) - for r, row in dfplot.iterrows(): - if row.val > 0: - ax.annotate( - f'{row.val:,.0f} {units[metric,agg][0]}', - (row.centroid_x, row.centroid_y), - color='r', ha='center', va='top', fontsize=6, weight='bold') + dfplot.plot(ax=ax, column='val', cmap=cmap) + reedsplots.label_region_value( + dfplot, + ax=ax, + column='val', + fmt=f'{{:,.0f}} {units[metric,agg][0]}', + text_kwargs={'color':'r', 'ha':'center', 'va':'top', 'fontsize':6, 'weight':'bold'} + ) ### Formatting if level in ['r','rb','ba']: - for r, row in dfba.iterrows(): - ax.annotate(r, (row.centroid_x, row.centroid_y), - ha='center', va='bottom', fontsize=6, color='C7') + reedsplots.label_region_value( + dfba, + ax=ax, + column=dfba.index, + text_kwargs={'ha':'center', 'va':'bottom', 'fontsize':6, 'color':'C7'} + ) ax.axis('off') if savefig: plt.savefig(os.path.join(sw['savepath'],savename)) @@ -987,10 +992,10 @@ def map_pras_failure_rate(sw, dfs, aggfunc='mean', repair=False): plottechs = failsum.loc[failsum != 0].index.get_level_values('i').unique() for tech in plottechs: - savename = f"hourly_failure_rate-year,month-{aggfunc}-{tech.replace('-','')}-{sw['t']}" + savename = f"hourly_failure_rate-year,month-{aggfunc}-{tech.replace('-','').replace('/','')}-{sw['t']}" plt.close() f, ax = plots.map_years_months( - dfzones=dfzones, dfdata=failrate[tech], + dfzones=dfzones, dfdata=failrate[tech], cmap=cmap, title=f"Monthly {aggfunc}\nhourly failure rate,\n{tech} [%]", ) ## Save it @@ -1008,10 +1013,10 @@ def map_pras_failure_rate(sw, dfs, aggfunc='mean', repair=False): ) repairrate.index = dfs['pras_system']['genrepairrate'].index for tech in plottechs: - savename = f"hourly_repair_rate-year,month-{aggfunc}-{tech.replace('-','')}-{sw['t']}" + savename = f"hourly_repair_rate-year,month-{aggfunc}-{tech.replace('-','').replace('/','')}-{sw['t']}" plt.close() f, ax = plots.map_years_months( - dfzones=dfzones, dfdata=repairrate[tech], + dfzones=dfzones, dfdata=repairrate[tech], cmap=cmap, title=f"Monthly {aggfunc}\nhourly repair rate,\n{tech} [%]", ) ## Save it @@ -1043,6 +1048,7 @@ def map_outagerate_new_stressperiods(sw, dfs): dates=dates, outage_type=outage_type, aggfunc=aggfunc, + cmap=cmap, vmax=vmax[outage_type], ) ## Save it