def _fixup_dates(coord, values): if coord.units.calendar is not None and values.ndim == 1: # Convert coordinate values into tuples of # (year, month, day, hour, min, sec) dates = [coord.units.num2date(val).timetuple()[0:6] for val in values] if coord.units.calendar == 'gregorian': r = [datetime.datetime(*date) for date in dates] else: try: import nc_time_axis except ImportError: msg = ('Cannot plot against time in a non-gregorian ' 'calendar, because "nc_time_axis" is not available : ' 'Install the package from ' 'https://github.com/SciTools/nc-time-axis to enable ' 'this usage.') raise IrisError(msg) r = [ nc_time_axis.CalendarDateTime(netcdftime.datetime(*date), coord.units.calendar) for date in dates ] values = np.empty(len(r), dtype=object) values[:] = r return values
def test_360_day_calendar_CalendarDateTime(self): datetimes = [cftime.datetime(1986, month, 30) for month in range(1, 6)] cal_datetimes = [ nc_time_axis.CalendarDateTime(dt, '360_day') for dt in datetimes ] line1, = plt.plot(cal_datetimes) result_ydata = line1.get_ydata() np.testing.assert_array_equal(result_ydata, cal_datetimes)
def _fixup_dates(coord, values): if coord.units.calendar is not None and values.ndim == 1: # Convert coordinate values into tuples of # (year, month, day, hour, min, sec) dates = [coord.units.num2date(val).timetuple()[0:6] for val in values] if coord.units.calendar == 'gregorian': r = [datetime.datetime(*date) for date in dates] else: r = [ nc_time_axis.CalendarDateTime(netcdftime.datetime(*date), coord.units.calendar) for date in dates ] values = np.empty(len(r), dtype=object) values[:] = r return values
def plot_timeseries( dsets, var, trend=False, align_times=False, smooth=None, nyears=None, labels=None, legend=True, means=True, ): """Standardized function to make a timeseries plot Parameters ---------- dsets : gfdlvitals.VitalsDataFrame or list Dataframe or list of dataframes to plot var : str Variable name to plot trend : bool, optional Plot linear trend line if True, by default False align_times : bool, optional H, by default False smooth : int, optional Integer number of years to apply smoothing, by default None nyears : int, optional Limit the x-axis to nyears number of points, by default None labels : str, optional Comma-separated list of dataset labels, by default None legend : bool, optional Display a legend for the plot, by default True means : bool, optional Add variable means to the legend, by default True Returns ------- matplotlib.pyplot.figure, dict Matplotlib figure handle and dictionary of axes/dataset mappings """ set_font() # Ensure "dsets" is a list dsets = [dsets] if not isinstance(dsets, list) else dsets # Text Labels if labels is None: labels = [f"Dataset {x}" for x in range(0, len(dsets))] legend = False else: labels = labels.split(",") # Determine max length of time values maxlen = max([len(x.index) for x in dsets]) # Compute trends if asked if trend: trends = [x.trend()[0:nyears] for x in dsets] if align_times: dsets = [x.extend(maxlen) for x in dsets] # Setup the figure. The top half will have a 16:9 aspect # ratio. The bottom half will be used for legend info and # the whole figure will be cropped at the end fig = plt.figure(figsize=(12, 6.75)) ax1 = plt.subplot(1, 1, 1) # Establish a list of axes in the figure axes_dict = {} # If smoothing is requested, still plot a faint copy of the full timeseries for x, dset in enumerate(dsets): dset.attrs["alpha"] = 0.3 if smooth is not None else 1.0 dset.attrs["color"] = f"C{x}" _lines = [] for i, dset in enumerate(dsets): label = labels[i] axes_dict[label] = {} axes_dict[label]["data"] = dset # Determine if we need a twin time axis _ax = ax1.twiny() if align_times and i > 0 else ax1 axes_dict[label]["axis"] = _ax axes_list = [axes_dict[x]["axis"] for x in list(axes_dict.keys())] # Keep a list of the axes and move new ones to the bottom of the figure if _ax not in list(axes_dict.keys()) and align_times: # Move twinned axis ticks and label from top to bottom _ax.xaxis.set_ticks_position("bottom") _ax.xaxis.set_label_position("bottom") # Offset the twin axis below the host _ax.spines["bottom"].set_position( ("axes", -0.10 * (len(axes_dict) - 1))) # Make sure frame is displayed if i > 0: _ax.set_frame_on(True) # Turn on the frame for the twin axis, but then hide all # but the bottom spine _ax.patch.set_visible(False) _ = [sp.set_visible(False) for sp in _ax.spines.values()] _ax.spines["bottom"].set_visible(True) # Convert times to nc_time_axis times = [ nc_time_axis.CalendarDateTime(item, "noleap") for item in dset.index.values ] # Add means to the labels _label = labels[i] if means: _mean = float(dset[var].values[0:nyears].mean()) _mean = round(_mean, 4) _label = f"{_label} (mean={_mean})" labels[i] = _label # Make the first plot (axes_dict[label]["line"], ) = _ax.plot( times[0:nyears], dset[var].values[0:nyears], color=dset.attrs["color"], alpha=dset.attrs["alpha"], label=_label, ) _lines.append(axes_dict[label]["line"]) # After the first plot is established, align time axes if asked if align_times: _time_index = dset.index[0:nyears] _ax.set_xlim( cftime.date2num(_time_index[0], calendar="noleap", units="days since 2000-01-01"), cftime.date2num(_time_index[-1], calendar="noleap", units="days since 2000-01-01"), ) if trend: (axes_dict[label]["trendline"], ) = _ax.plot( times[0:len(trends[i])], trends[i][var].values[0:nyears], linestyle="dashed", color=dset.attrs["color"], alpha=1.0, linewidth=1, ) axes_dict[label]["trend"] = trends[i] if smooth: (axes_dict[label]["smoothline"], ) = _ax.plot( times[0:nyears], dset.smooth(smooth)[var].values[0:nyears], color=dset.attrs["color"], alpha=1.0, linewidth=2, ) # Text annotations if i == 0: axes_dict[label]["topline_label"] = _ax.text( 0.01, 1.08, var, ha="left", transform=ax1.transAxes, fontsize=22) axes_dict[label]["longname_label"] = _ax.text( 0.01, 1.03, dset[var].attrs["long_name"], ha="left", transform=ax1.transAxes, style="italic", fontsize=14, fontfamily="Roboto Condensed", ) axes_dict[label]["units_label"] = _ax.set_ylabel( dset[var].attrs["units"]) axes_list = [axes_dict[x]["axis"] for x in list(axes_dict.keys())] axes_list = list(set(axes_list)) maxlim = max([x.get_xlim()[1] - x.get_xlim()[0] for x in axes_list]) _ = [ x.set_xlim(x.get_xlim()[0], x.get_xlim()[0] + maxlim) for x in axes_list ] # Add grid _ = [ x.grid(color="gray", linestyle="--", linewidth=0.3) for x in axes_list ] if legend: plt.legend( _lines, labels, fancybox=True, framealpha=1, shadow=True, borderpad=0.5, loc="upper center", bbox_to_anchor=(0.5, (-0.10 * (len(axes_dict)) + 0.02)), ) return fig, axes_dict
def fix_time(ds): ds = ds.copy() ds.time.data = [ nc_time_axis.CalendarDateTime(item, "360_day") for item in ds.time.data ] return ds