2022-02-24 18:19:40 +01:00
#!/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
2023-03-07 11:29:40 +01:00
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 ) :
2022-02-24 18:19:40 +01:00
"""
Plot a signal directly on an axis .
Uses t_edge to trigger height
"""
2023-03-07 11:29:40 +01:00
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 )
2022-02-24 18:19:40 +01:00
# 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 ) ,
2023-03-07 11:29:40 +01:00
va = ' bottom ' , ha = ' center '
2022-02-24 18:19:40 +01:00
)
# annotate s_edge
y = 0.3
2023-03-07 11:29:40 +01:00
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 )
2022-02-24 18:19:40 +01:00
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
2023-03-07 11:29:40 +01:00
def main ( ref = ( 10 , 5 ) , A = ( 40 , 10 ) , B = ( 70 , 12 ) ) :
2022-02-24 18:19:40 +01:00
"""
Create a figure with two signals at times t1 and t2 ( accuracy s1 , s2 )
as compared to a reference timer tr ( sr ) , with annotations .
"""
2023-06-12 11:14:53 +02:00
t = np . linspace ( 0 , 100 , int ( 1e3 ) )
2022-02-24 18:19:40 +01:00
2023-03-07 11:29:40 +01:00
t_A , s_A = A
t_B , s_B = B
t_ref , s_ref = ref
2022-02-24 18:19:40 +01:00
box_kw = {
2023-03-07 11:29:40 +01:00
" alpha " : 0.6 ,
2022-02-24 18:19:40 +01:00
" hatch " : ' \\ ' ,
}
line_kw = {
}
vline_kw = {
2023-03-07 11:29:40 +01:00
" linestyle " : ( 0 , ( 4 , 2 ) ) ,
2022-02-24 18:19:40 +01:00
" color " : " k " ,
2023-03-07 11:29:40 +01:00
" alpha " : 0.8 ,
2022-02-24 18:19:40 +01:00
}
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 :
2023-03-07 11:29:40 +01:00
ax . set_ylim ( - 0.25 , 1.25 )
2022-02-24 18:19:40 +01:00
ax . set_yticks ( [ ] )
ax . set_yticklabels ( [ ] )
ax . grid ( )
2023-03-07 11:29:40 +01:00
ax . spines [ ' top ' ] . set_visible ( False )
ax . spines [ ' right ' ] . set_visible ( False )
ax . spines [ ' bottom ' ] . set_visible ( False )
ax . spines [ ' left ' ] . set_visible ( False )
2022-02-24 18:19:40 +01:00
# Create the plots
i = - 1
# Signal A
i + = 1
y = 0.6
2023-03-07 11:29:40 +01:00
axs [ i ] . set_ylabel ( " Signal A " , rotation = 0 )
2022-02-24 18:19:40 +01:00
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
2023-03-07 11:29:40 +01:00
axs [ i ] . set_ylabel ( " Reference " , rotation = 0 )
2022-02-24 18:19:40 +01:00
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
2023-03-07 11:29:40 +01:00
axs [ i ] . set_ylabel ( " Signal B " , rotation = 0 )
2022-02-24 18:19:40 +01:00
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 :
2023-03-07 11:29:40 +01:00
plt . tight_layout ( )
plt . savefig ( args . fname , transparent = True )
2022-02-24 18:19:40 +01:00
else :
plt . show ( )