#!/usr/bin/env python3 __doc__ = \ """ Generate a figure showing the alignment of an external clock referenced to a WR clock as could be seen on an oscilloscope. """ import matplotlib.pyplot as plt import numpy as np import scipy.signal as sig ### Functions def plot_signal( ax, t, t_edge, s_edge, name=None, box_kw={'hatch': '/'}, line_kw={}, annotate_t_edge=True, divide_s_edge=False,**plot_kw): """ Plot a signal directly on an axis. Uses t_edge to trigger height """ if divide_s_edge: lower = (t > t_edge - s_edge/2) upper = (t > t_edge + s_edge/2) else: lower = (t > t_edge) upper = (t > t_edge + s_edge) # merge dictionaries correctly line_kw = { **plot_kw, **line_kw } # plot lower and upper lines l = ax.plot(t, lower, **line_kw) ## update colour when plot_kw was empty plot_kw = { **plot_kw, **{'color': l[0].get_color()} } line_kw = { **plot_kw, **line_kw } box_kw = { **plot_kw, **box_kw } l2 = ax.plot(t, upper, **line_kw) # plot the shaded box of width t_sigma l3 = ax.fill_between(t, upper, lower, **box_kw) # annotations if name is not None: if annotate_t_edge: # annotate t_edge y = 1 ax.annotate("$t_\mathrm{{{}}}$".format(name), xy=(t_edge, y), xytext=(t_edge-3, y), va='bottom', ha='center' ) # annotate s_edge y = 0.3 if divide_s_edge: annotate_width(ax, "$\sigma_\mathrm{{{}}}$".format(name), t_edge-s_edge/2, t_edge+s_edge/2, y) else: annotate_width(ax, "$\sigma_\mathrm{{{}}}$".format(name), t_edge, t_edge+s_edge, y) return [l, l2, l3] def plot_diff_time(ax, name, t_1, t_2, y, vline_kw={}, va='bottom'): ax.axvline(t_1, **vline_kw) ax.axvline(t_2, **vline_kw) arrow_kw = { 'va':va, } annotate_width(ax, name, t_1, t_2, y) def annotate_width(ax, name, x1, x2, y, text_kw={}, arrow_kw={}): default_arrow_kw = dict( xy = (x1, y), xytext = (x2,y), arrowprops = dict( arrowstyle="<->", shrinkA=False, shrinkB=False ), ) default_text_kw = dict( va='bottom', ha='center', xy=((x1+x2)/2, y) ) an1 = ax.annotate("", **{**default_arrow_kw, **arrow_kw}) an2 = ax.annotate(name, **{**default_text_kw, **text_kw}) return [an1, an2] ## Main def main(ref=(10,5), A=(40,10), B=(70,12) ): """ Create a figure with two signals at times t1 and t2 (accuracy s1, s2) as compared to a reference timer tr (sr), with annotations. """ t = np.linspace(0, 100, 1e3) t_A, s_A = A t_B, s_B = B t_ref, s_ref = ref box_kw = { "alpha": 0.6, "hatch": '\\', } line_kw = { } vline_kw = { "linestyle": (0,(4,2)), "color": "k", "alpha":0.8, } fig, axs = plt.subplots(3,1,sharex=True, gridspec_kw={'hspace': 0}); # Overall styling axs[-1].set_xticks([]) axs[-1].set_xticklabels([]) for ax in axs: ax.set_ylim(-0.25, 1.25) ax.set_yticks([]) ax.set_yticklabels([]) ax.grid() ax.spines['top'].set_visible(False) ax.spines['right'].set_visible(False) ax.spines['bottom'].set_visible(False) ax.spines['left'].set_visible(False) # Create the plots i = -1 # Signal A i+=1 y = 0.6 axs[i].set_ylabel("Signal A", rotation=0) plot_diff_time(axs[i], "$t_\\mathrm{A}}$", t_ref, t_A, y, vline_kw=vline_kw) plot_signal(axs[i], t, t_A, s_A, name="A", box_kw=box_kw, line_kw=line_kw, annotate_t_edge=False) # Reference i+=1 axs[i].set_ylabel("Reference", rotation=0) axs[i].axvline(t_ref, **vline_kw) plot_signal(axs[i], t, t_ref, s_ref, name="ref", box_kw=box_kw, line_kw=line_kw, color='g') plot_diff_time(axs[i], "$t_\\mathrm{C}$", t_A, t_B, 0.3, vline_kw=vline_kw) # Signal B i+=1 axs[i].set_ylabel("Signal B", rotation=0) plot_diff_time(axs[i], "$t_\\mathrm{B}}$", t_ref, t_B, y, vline_kw=vline_kw) plot_signal(axs[i], t, t_B, s_B, name="B", box_kw=box_kw, line_kw=line_kw, color='purple', annotate_t_edge=False) return fig, 0 if __name__ == "__main__": from argparse import ArgumentParser import os.path as path parser = ArgumentParser(description=__doc__) parser.add_argument("fname", metavar="path/to/figure[/]", nargs="?", help="Location for generated figure, will append __file__ if a directory. If not supplied, figure is shown.") args = parser.parse_args() if args.fname is not None and path.isdir(args.fname): args.fname = path.join(args.fname, path.splitext(path.basename(__file__))[0] + ".pdf") ### fig, _ = main() if args.fname is not None: plt.tight_layout() plt.savefig(args.fname, transparent=True) else: plt.show()