def annotation(ax, text, loc='upper right',fontsize=8): """Put a general annotation in the plot.""" at = AnchoredText('%s'% text, prop=dict(size=fontsize), frameon=True, loc=loc) at.patch.set_boxstyle("round,pad=0.,rounding_size=0.1") at.zorder = 10 ax.add_artist(at) return(at)
def annotation_run(ax, time, loc='upper right',fontsize=8): """Put annotation of the run obtaining it from the time array passed to the function.""" at = AnchoredText('Run %s'% time.strftime('%Y%m%d %H UTC'), prop=dict(size=fontsize), frameon=True, loc=loc) at.patch.set_boxstyle("round,pad=0.,rounding_size=0.1") at.zorder = 10 ax.add_artist(at) return(at)
def annotation_forecast(ax, time, loc='upper left',fontsize=8, local=True): """Put annotation of the forecast time.""" if local: # convert to local time time = convert_timezone(time) at = AnchoredText('Valid %s' % time.strftime('%A %d %b %Y at %H (Berlin)'), prop=dict(size=fontsize), frameon=True, loc=loc) else: at = AnchoredText('Forecast for %s' % time.strftime('%A %d %b %Y at %H UTC'), prop=dict(size=fontsize), frameon=True, loc=loc) at.patch.set_boxstyle("round,pad=0.,rounding_size=0.1") at.zorder = 10 ax.add_artist(at) return(at)
def plastic_measurements(plastic_type='count', output_path=None, plot_style='plot_tools/plot.mplstyle'): time_cki, lon_cki, lat_cki, n_plastic_cki, kg_plastic_cki = get_cki_plastic_measurements( ) time_ci, lon_ci, lat_ci, n_plastic_ci, kg_plastic_ci = get_christmas_plastic_measurements( ) n_plastic_month_cki = np.zeros(12) n_months_cki = np.zeros(12) n_plastic_month_ci = np.zeros(12) n_months_ci = np.zeros(12) if plastic_type == 'count': samples_cki = n_plastic_cki samples_ci = n_plastic_ci ylabel = 'Plastic items [#]' ylim = [0, 140000] yticks = np.arange(0, 140000, 20000) ylim_studies = [0, 14] yticks_studies = np.arange(0, 14, 2) elif plastic_type == 'mass': samples_cki = kg_plastic_cki samples_ci = kg_plastic_ci ylabel = 'Plastic weight [kg]' ylim = [0, 4000] yticks = np.arange(0, 4000, 500) ylim_studies = [0, 16] yticks_studies = np.arange(0, 16, 2) else: raise ValueError( f'Unknown plastic type requested: {plastic_type}. Valid values are: count and mass.' ) for i, t in enumerate(time_cki): n_months_cki[t.month - 1] += 1 if plastic_type == 'count': n_plastic_month_cki[t.month - 1] = np.nansum( [n_plastic_month_cki[t.month - 1], n_plastic_cki[i]]) elif plastic_type == 'mass': n_plastic_month_cki[t.month - 1] = np.nansum( [n_plastic_month_cki[t.month - 1], kg_plastic_cki[i]]) for i, t in enumerate(time_ci): n_months_ci[t.month - 1] += 1 if plastic_type == 'count': n_plastic_month_ci[t.month - 1] = np.nansum( [n_plastic_month_ci[t.month - 1], n_plastic_ci[i]]) elif plastic_type == 'mass': n_plastic_month_ci[t.month - 1] = np.nansum( [n_plastic_month_ci[t.month - 1], kg_plastic_ci[i]]) (main_colors_cki, main_sizes_cki, main_edgewidths_cki) = _get_marker_colors_sizes_for_samples( samples_cki, plastic_type) (main_colors_ci, main_sizes_ci, main_edgewidths_ci) = _get_marker_colors_sizes_for_samples( samples_ci, plastic_type) plt.style.use(plot_style) fig = plt.figure(figsize=(8, 6)) months = np.arange(1, 13, 1) colors = get_months_colors() color = '#adadad' land_color = '#cfcfcf' # (a) seasonal distribution measured plastic CKI ax1 = plt.subplot(2, 2, 1) ax1.bar(months - 0.2, n_plastic_month_cki, width=0.4, color=colors, edgecolor='k', zorder=5) ax1.set_xticks(months) ax1.set_xticklabels([ 'Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec' ]) ax1.set_ylim(ylim) ax1.set_yticks(yticks) ax1.set_ylabel(ylabel) anchored_text1 = AnchoredText( f'(a) Plastic samples from Cocos Keeling Islands (CKI)', loc='upper left', borderpad=0.0) ax1.add_artist(anchored_text1) # number of sampling studies per month ax2 = ax1.twinx() ax2.grid(False) ax2.bar(months + 0.2, n_months_cki, width=0.4, color=color, edgecolor='k', zorder=6) ax2.set_ylim(ylim_studies) ax2.set_yticks(yticks_studies) ax2.set_ylabel('Sampling studies [#]') ax2.spines['right'].set_color(color) ax2.tick_params(axis='y', colors=color) ax2.yaxis.label.set_color(color) # (b) seasonal distribution measured plastic CI ax3 = plt.subplot(2, 2, 3) ax3.bar(months - 0.2, n_plastic_month_ci, width=0.4, color=colors, edgecolor='k', zorder=5) ax3.set_xticks(months) ax3.set_xticklabels([ 'Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec' ]) ax3.set_ylim(ylim) ax3.set_yticks(yticks) ax3.set_ylabel(ylabel) anchored_text2 = AnchoredText( f'(b) Plastic samples from Christmas Island (CI)', loc='upper left', borderpad=0.0) ax3.add_artist(anchored_text2) # number of sampling studies per month ax4 = ax3.twinx() ax4.grid(False) ax4.bar(months + 0.2, n_months_ci, width=0.4, color=color, edgecolor='k', zorder=6) ax4.set_ylim(ylim_studies) ax4.set_yticks(yticks_studies) ax4.set_ylabel('Sampling studies [#]') ax4.spines['right'].set_color(color) ax4.tick_params(axis='y', colors=color) ax4.yaxis.label.set_color(color) # (c) map of measured plastic CKI ax5 = plt.subplot(2, 2, 2, projection=ccrs.PlateCarree()) lon_range_cki, lat_range_cki = get_cki_box_lon_lat_range() mplot1 = _iot_basic_map(ax5, lon_range=lon_range_cki, lat_range=lat_range_cki, dlon=0.2, dlat=0.1) l_nonan_cki = ~np.isnan(samples_cki) mplot1.points(lon_cki[l_nonan_cki], lat_cki[l_nonan_cki], facecolor=main_colors_cki, edgecolor='k', marker='o', markersize=np.array(main_sizes_cki) * 5, edgewidth=main_edgewidths_cki) anchored_text3 = AnchoredText(f'(c) Sampled plastics on CKI', loc='upper left', borderpad=0.0) anchored_text3.zorder = 8 ax5.add_artist(anchored_text3) # (d) map of measured plastic CI ax6 = plt.subplot(2, 2, 4, projection=ccrs.PlateCarree()) lon_range_ci, lat_range_ci = get_christmas_box_lon_lat_range() mplot2 = _iot_basic_map(ax6, lon_range=lon_range_ci, lat_range=lat_range_ci, dlon=0.2, dlat=0.1) l_nonan_ci = ~np.isnan(samples_ci) mplot2.points(lon_ci[l_nonan_ci], lat_ci[l_nonan_ci], facecolor=main_colors_ci, edgecolor='k', marker='o', markersize=np.array(main_sizes_ci) * 5, edgewidth=main_edgewidths_ci) anchored_text4 = AnchoredText(f'(d) Sampled plastics on CI', loc='upper left', borderpad=0.0) ax6.add_artist(anchored_text4) # legend legend_entries = _get_legend_entries_samples(plastic_type) ax5.legend(handles=legend_entries, loc='upper left', title=ylabel, bbox_to_anchor=(1.1, 1.0)) # save if output_path: plt.savefig(output_path, bbox_inches='tight', dpi=300) plt.show()
def _spectrogram( tr, starttime, endtime, is_infrasound, rescale, spec_win_dur, db_lim, freq_lim, log, is_local_time, resolution, ): """ Make a combination waveform and spectrogram plot for an infrasound or seismic signal. Args: tr (:class:`~obspy.core.trace.Trace`): Input data, usually starts before `starttime` and ends after `endtime` (this function expects the response to be removed!) starttime (:class:`~obspy.core.utcdatetime.UTCDateTime`): Start time endtime (:class:`~obspy.core.utcdatetime.UTCDateTime`): End time is_infrasound (bool): `True` if infrasound, `False` if seismic rescale (int or float): Scale waveforms by this factor for plotting spec_win_dur (int or float): See docstring for :func:`~sonify.sonify` db_lim (tuple or str): See docstring for :func:`~sonify.sonify` freq_lim (tuple): Tuple defining frequency limits for spectrogram plot log (bool): See docstring for :func:`~sonify.sonify` is_local_time (bool): Passed to :class:`_UTCDateFormatter` resolution (str): See docstring for :func:`~sonify.sonify` Returns: Tuple of (`fig`, `spec_line`, `wf_line`, `time_box`, `wf_progress`) """ if is_infrasound: ylab = 'Pressure (Pa)' clab = f'Power (dB rel. [{REFERENCE_PRESSURE * 1e6:g} µPa]$^2$ Hz$^{{-1}}$)' ref_val = REFERENCE_PRESSURE else: ylab = 'Velocity (µm s$^{-1}$)' if REFERENCE_VELOCITY == 1: clab = ( f'Power (dB rel. {REFERENCE_VELOCITY:g} [m s$^{{-1}}$]$^2$ Hz$^{{-1}}$)' ) else: clab = ( f'Power (dB rel. [{REFERENCE_VELOCITY:g} m s$^{{-1}}$]$^2$ Hz$^{{-1}}$)' ) ref_val = REFERENCE_VELOCITY fs = tr.stats.sampling_rate nperseg = int(spec_win_dur * fs) # Samples nfft = np.power(2, int(np.ceil(np.log2(nperseg))) + 1) # Pad fft with zeroes f, t, sxx = signal.spectrogram( tr.data, fs, window='hann', nperseg=nperseg, noverlap=nperseg // 2, nfft=nfft ) # [dB rel. (ref_val <ref_val_unit>)^2 Hz^-1] sxx_db = 10 * np.log10(sxx / (ref_val**2)) t_mpl = tr.stats.starttime.matplotlib_date + (t / mdates.SEC_PER_DAY) # Ensure a 16:9 aspect ratio fig = Figure(figsize=(FIGURE_WIDTH, (9 / 16) * FIGURE_WIDTH)) # width_ratios effectively controls the colorbar width gs = GridSpec(2, 2, figure=fig, height_ratios=[2, 1], width_ratios=[40, 1]) spec_ax = fig.add_subplot(gs[0, 0]) wf_ax = fig.add_subplot(gs[1, 0], sharex=spec_ax) # Share x-axis with spec cax = fig.add_subplot(gs[0, 1]) wf_lw = 0.5 wf_ax.plot(tr.times('matplotlib'), tr.data * rescale, '#b0b0b0', linewidth=wf_lw) wf_progress = wf_ax.plot(np.nan, np.nan, 'black', linewidth=wf_lw)[0] wf_ax.set_ylabel(ylab) wf_ax.grid(linestyle=':') max_value = np.abs(tr.copy().trim(starttime, endtime).data).max() * rescale wf_ax.set_ylim(-max_value, max_value) im = spec_ax.pcolormesh( t_mpl, f, sxx_db, cmap='inferno', shading='nearest', rasterized=True ) spec_ax.set_ylabel('Frequency (Hz)') spec_ax.grid(linestyle=':') spec_ax.set_ylim(freq_lim) if log: spec_ax.set_yscale('log') # Tick locating and formatting locator = mdates.AutoDateLocator() wf_ax.xaxis.set_major_locator(locator) wf_ax.xaxis.set_major_formatter(_UTCDateFormatter(locator, is_local_time)) fig.autofmt_xdate() # "Crop" x-axis! wf_ax.set_xlim(starttime.matplotlib_date, endtime.matplotlib_date) # Initialize animated stuff line_kwargs = dict(x=starttime.matplotlib_date, color='forestgreen', linewidth=1) spec_line = spec_ax.axvline(**line_kwargs) wf_line = wf_ax.axvline(ymin=0.01, clip_on=False, zorder=10, **line_kwargs) time_box = AnchoredText( s=starttime.strftime('%H:%M:%S'), pad=0.2, loc='lower right', bbox_to_anchor=[1, 1], bbox_transform=wf_ax.transAxes, borderpad=0, prop=dict(color='forestgreen'), ) offset_px = -0.0025 * RESOLUTIONS[resolution][1] # Resolution-independent! time_box.txt._text.set_y(offset_px) # [pixels] Vertically center text time_box.zorder = 12 # This should place it on the very top; see below time_box.patch.set_linewidth(matplotlib.rcParams['axes.linewidth']) wf_ax.add_artist(time_box) # Adjustments to ensure time marker line is zordered properly # 9 is below marker; 11 is above marker spec_ax.spines['bottom'].set_zorder(9) wf_ax.spines['top'].set_zorder(9) for side in 'bottom', 'left', 'right': wf_ax.spines[side].set_zorder(11) # Pick smart limits rounded to nearest 10 if db_lim == 'smart': db_min = np.percentile(sxx_db, 20) db_max = sxx_db.max() db_lim = (np.ceil(db_min / 10) * 10, np.floor(db_max / 10) * 10) # Clip image to db_lim if provided (doesn't clip if db_lim=None) im.set_clim(db_lim) # Automatically determine whether to show triangle extensions on colorbar # (kind of adopted from xarray) if db_lim: min_extend = sxx_db.min() < db_lim[0] max_extend = sxx_db.max() > db_lim[1] else: min_extend = False max_extend = False if min_extend and max_extend: extend = 'both' elif min_extend: extend = 'min' elif max_extend: extend = 'max' else: extend = 'neither' fig.colorbar(im, cax, extend=extend, extendfrac=EXTENDFRAC, label=clab) spec_ax.set_title(tr.id) fig.tight_layout() fig.subplots_adjust(hspace=0, wspace=0.05) # Finnicky formatting to get extension triangles (if they exist) to extend # above and below the vertical extent of the spectrogram axes pos = cax.get_position() triangle_height = EXTENDFRAC * pos.height ymin = pos.ymin height = pos.height if min_extend and max_extend: ymin -= triangle_height height += 2 * triangle_height elif min_extend and not max_extend: ymin -= triangle_height height += triangle_height elif max_extend and not min_extend: height += triangle_height else: pass cax.set_position([pos.xmin, ymin, pos.width, height]) # Move offset text around and format it more nicely, see # https://github.com/matplotlib/matplotlib/blob/710fce3df95e22701bd68bf6af2c8adbc9d67a79/lib/matplotlib/ticker.py#L677 magnitude = wf_ax.yaxis.get_major_formatter().orderOfMagnitude if magnitude: # I.e., if offset text is present wf_ax.yaxis.get_offset_text().set_visible(False) # Remove original text sf = ScalarFormatter(useMathText=True) sf.orderOfMagnitude = magnitude # Formatter needs to know this! sf.locs = [47] # Can't be an empty list wf_ax.text( 0.002, 0.95, sf.get_offset(), # Let the ScalarFormatter do the formatting work transform=wf_ax.transAxes, ha='left', va='top', ) return fig, spec_line, wf_line, time_box, wf_progress
def add_subtitle(self, subtitle, location='upper left'): anchored_text = AnchoredText(subtitle, loc=location, borderpad=0.0) anchored_text.zorder = 15 self.ax.add_artist(anchored_text)