11import copy
22import math
3+ import logging
34
45from PySide6 .QtCore import QThreadPool , QTimer , Signal , Qt
56from PySide6 .QtWidgets import QFileDialog , QMessageBox
89from matplotlib .figure import Figure
910from matplotlib .lines import Line2D
1011from matplotlib .patches import Circle
11- from matplotlib .ticker import AutoLocator , FuncFormatter
12+ from matplotlib .ticker import AutoLocator , AutoMinorLocator , FuncFormatter
1213
14+ import matplotlib as mpl
1315import matplotlib .pyplot as plt
1416import matplotlib .transforms as tx
1517
3537)
3638from hexrdgui .utils .tth_distortion import apply_tth_distortion_if_needed
3739
40+ # Increase these font sizes (compared to the global font) by the specified
41+ # amounts.
42+ FONTSIZE_LABEL_INCREASE = 4
43+ FONTSIZE_TICKS_INCREASE = 4
44+
3845
3946class ImageCanvas (FigureCanvas ):
4047
@@ -114,6 +121,42 @@ def setup_connections(self):
114121 def thread_pool (self ):
115122 return QThreadPool .globalInstance ()
116123
124+ @property
125+ def fontsize_label (self ):
126+ return HexrdConfig ().font_size + FONTSIZE_LABEL_INCREASE
127+
128+ @property
129+ def fontsize_ticks (self ):
130+ return HexrdConfig ().font_size + FONTSIZE_TICKS_INCREASE
131+
132+ @property
133+ def label_kwargs (self ):
134+ return {
135+ 'fontsize' : self .fontsize_label ,
136+ 'family' : 'serif' ,
137+ }
138+
139+ @property
140+ def major_tick_kwargs (self ):
141+ return {
142+ 'left' : True ,
143+ 'right' : True ,
144+ 'bottom' : True ,
145+ 'top' : True ,
146+ 'which' : 'major' ,
147+ 'length' : 10 ,
148+ 'labelfontfamily' : 'serif' ,
149+ 'labelsize' : self .fontsize_ticks ,
150+ }
151+
152+ @property
153+ def minor_tick_kwargs (self ):
154+ return {
155+ ** self .major_tick_kwargs ,
156+ 'which' : 'minor' ,
157+ 'length' : 2 ,
158+ }
159+
117160 def __del__ (self ):
118161 # This is so that the figure can be cleaned up
119162 plt .close (self .figure )
@@ -1004,6 +1047,7 @@ def show_polar(self):
10041047 worker .signals .error .connect (self .async_worker_error )
10051048
10061049 def finish_show_polar (self , iviewer ):
1050+
10071051 if self .mode != ViewType .polar :
10081052 # Image mode was switched during generation. Ignore this.
10091053 return
@@ -1034,10 +1078,21 @@ def finish_show_polar(self, iviewer):
10341078 }
10351079 self .axes_images .append (self .axis .imshow (** kwargs ))
10361080 self .axis .axis ('auto' )
1081+
1082+ self .axis .yaxis .set_major_locator (AutoLocator ())
1083+ self .axis .yaxis .set_minor_locator (AutoMinorLocator ())
1084+
1085+ self .axis .xaxis .set_major_locator (PolarXAxisTickLocator (self ))
1086+ self .axis .xaxis .set_minor_locator (
1087+ PolarXAxisMinorTickLocator (self )
1088+ )
1089+ self .axis .tick_params (** self .major_tick_kwargs )
1090+ self .axis .tick_params (** self .minor_tick_kwargs )
1091+
10371092 # Do not allow the axis to autoscale, which could happen if
10381093 # overlays are drawn out-of-bounds
10391094 self .axis .autoscale (False )
1040- self .axis .set_ylabel (r'$\eta$ [deg]' )
1095+ self .axis .set_ylabel (r'$\eta$ [deg]' , ** self . label_kwargs )
10411096 self .axis .label_outer ()
10421097 else :
10431098 rescale_image = False
@@ -1053,11 +1108,11 @@ def finish_show_polar(self, iviewer):
10531108 axis = self .figure .add_subplot (grid [3 , 0 ], sharex = self .axis )
10541109 data = (tth , self .compute_azimuthal_integral_sum ())
10551110 unscaled = (tth , self .compute_azimuthal_integral_sum (False ))
1056- self .azimuthal_line_artist , = axis .plot (* data )
1111+ self .azimuthal_line_artist , = axis .plot (* data , '-k' , lw = 2.5 )
10571112 HexrdConfig ().last_unscaled_azimuthal_integral_data = unscaled
10581113
10591114 self .azimuthal_integral_axis = axis
1060- axis .set_ylabel (r'Azimuthal Average' )
1115+ axis .set_ylabel (r'Azimuthal Average' , ** self . label_kwargs )
10611116 self .update_azimuthal_plot_overlays ()
10621117 self .update_wppf_plot ()
10631118
@@ -1067,15 +1122,51 @@ def finish_show_polar(self, iviewer):
10671122 formatter = PolarXAxisFormatter (default_formatter , f )
10681123 axis .xaxis .set_major_formatter (formatter )
10691124
1070- # Set our custom tick locators as well
1071- self .axis .xaxis .set_major_locator (PolarXAxisTickLocator (self ))
1125+ axis .yaxis .set_major_locator (AutoLocator ())
1126+ axis .yaxis .set_minor_locator (AutoMinorLocator ())
1127+
10721128 axis .xaxis .set_major_locator (PolarXAxisTickLocator (self ))
1129+ self .axis .xaxis .set_minor_locator (
1130+ PolarXAxisMinorTickLocator (self )
1131+ )
1132+
1133+ # change property of ticks
1134+ axis .tick_params (** self .major_tick_kwargs )
1135+ axis .tick_params (** self .minor_tick_kwargs )
1136+
1137+ # add grid lines parallel to x-axis in azimuthal average
1138+ kwargs = {
1139+ 'visible' : True ,
1140+ 'which' : 'major' ,
1141+ 'axis' : 'y' ,
1142+ 'linewidth' : 0.25 ,
1143+ 'linestyle' : '-' ,
1144+ 'color' : 'k' ,
1145+ 'alpha' : 0.75 ,
1146+ }
1147+ axis .grid (** kwargs )
1148+
1149+ kwargs = {
1150+ 'visible' : True ,
1151+ 'which' : 'minor' ,
1152+ 'axis' : 'y' ,
1153+ 'linewidth' : 0.075 ,
1154+ 'linestyle' : '--' ,
1155+ 'color' : 'k' ,
1156+ 'alpha' : 0.9 ,
1157+ }
1158+ axis .grid (** kwargs )
1159+
1160+ # add grid lines parallel to y-axis
1161+ kwargs ['which' ] = 'both'
1162+ kwargs ['axis' ] = 'x'
1163+ axis .grid (** kwargs )
10731164 else :
10741165 self .update_azimuthal_integral_plot ()
10751166 axis = self .azimuthal_integral_axis
10761167
10771168 # Update the xlabel in case it was modified (via tth distortion)
1078- axis .set_xlabel (self .polar_xlabel )
1169+ axis .set_xlabel (self .polar_xlabel , ** self . label_kwargs )
10791170 else :
10801171 if len (self .axes_images ) == 0 :
10811172 self .axis = self .figure .add_subplot (111 )
@@ -1087,13 +1178,13 @@ def finish_show_polar(self, iviewer):
10871178 'interpolation' : 'none' ,
10881179 }
10891180 self .axes_images .append (self .axis .imshow (** kwargs ))
1090- self .axis .set_ylabel (r'$\eta$ [deg]' )
1181+ self .axis .set_ylabel (r'$\eta$ [deg]' , ** self . label_kwargs )
10911182 else :
10921183 rescale_image = False
10931184 self .axes_images [0 ].set_data (img )
10941185
10951186 # Update the xlabel in case it was modified (via tth distortion)
1096- self .axis .set_xlabel (self .polar_xlabel )
1187+ self .axis .set_xlabel (self .polar_xlabel , ** self . label_kwargs )
10971188
10981189 if rescale_image :
10991190 self .axis .relim ()
@@ -1193,7 +1284,8 @@ def polar_x_axis_type(self):
11931284
11941285 def on_polar_x_axis_type_changed (self ):
11951286 # Update the x-label
1196- self .azimuthal_integral_axis .set_xlabel (self .polar_xlabel )
1287+ self .azimuthal_integral_axis .set_xlabel (
1288+ self .polar_xlabel , ** self .label_kwargs )
11971289
11981290 # Still need to draw if the x-label was modified
11991291 self .draw_idle ()
@@ -1701,7 +1793,12 @@ def draw_mask_boundaries(self, axis, det=None):
17011793 delta_eta_est = np .nanmedian (eta_diff )
17021794 tolerance = delta_eta_est * 10
17031795 big_gaps , = np .nonzero (eta_diff > tolerance )
1704- verts [i ] = np .insert (vert , big_gaps + 1 , np .nan , axis = 0 )
1796+ verts [i ] = np .insert (
1797+ vert ,
1798+ big_gaps + 1 ,
1799+ np .nan ,
1800+ axis = 0 ,
1801+ )
17051802
17061803 if self .mode == ViewType .stereo :
17071804 # Now convert from polar to stereo
@@ -1756,6 +1853,67 @@ def tick_values(self, vmin, vmax):
17561853 return canvas .polar_x_type_to_tth (values )
17571854
17581855
1856+ class PolarXAxisMinorTickLocator (AutoMinorLocator ):
1857+ """Subclass the tick locator so we can modify its behavior
1858+
1859+ We will modify any value ranges provided so that the current x-axis type
1860+ provides nice looking ticks.
1861+
1862+ For instance, for Q, we want to space minor ticks non-linearly between
1863+ major ticks
1864+ """
1865+ def __init__ (self , canvas , * args , ** kwargs ):
1866+ super ().__init__ (* args , ** kwargs )
1867+ self ._hexrdgui_canvas = canvas
1868+
1869+ def __call__ (self ):
1870+ canvas = self ._hexrdgui_canvas
1871+ if self .axis .get_scale () == 'log' :
1872+ logging .warning (
1873+ 'PolarXAxisMinorTickLocator does not work on logarithmic '
1874+ 'scales'
1875+ )
1876+ return []
1877+
1878+ majorlocs = np .unique (self .axis .get_majorticklocs ())
1879+ if len (majorlocs ) < 2 :
1880+ # Need at least two major ticks to find minor tick locations.
1881+ return []
1882+
1883+ # Convert to our current x type
1884+ majorlocs = canvas .polar_tth_to_x_type (majorlocs )
1885+ majorstep = majorlocs [1 ] - majorlocs [0 ]
1886+
1887+ if self .ndivs is None :
1888+ self .ndivs = mpl .rcParams [
1889+ 'ytick.minor.ndivs' if self .axis .axis_name == 'y'
1890+ else 'xtick.minor.ndivs' ] # for x and z axis
1891+
1892+ if self .ndivs == 'auto' :
1893+ majorstep_mantissa = 10 ** (np .log10 (majorstep ) % 1 )
1894+ if np .isclose (majorstep_mantissa , [1 , 2.5 , 5 , 10 ]).any ():
1895+ ndivs = 5
1896+ else :
1897+ ndivs = 4
1898+ else :
1899+ ndivs = self .ndivs
1900+
1901+ minorstep = majorstep / ndivs
1902+
1903+ vmin , vmax = sorted (self .axis .get_view_interval ())
1904+ # Convert to our current x type
1905+ vmin , vmax = canvas .polar_tth_to_x_type ([vmin , vmax ])
1906+ t0 = majorlocs [0 ]
1907+ tmin = round ((vmin - t0 ) / minorstep )
1908+ tmax = round ((vmax - t0 ) / minorstep ) + 1
1909+ locs = (np .arange (tmin , tmax ) * minorstep ) + t0
1910+
1911+ # Convert back to tth
1912+ locs = canvas .polar_x_type_to_tth (locs )
1913+
1914+ return self .raise_if_exceeds (locs )
1915+
1916+
17591917class PolarXAxisFormatter (FuncFormatter ):
17601918 """Subclass the func formatter so we can keep the default formatter in sync
17611919
0 commit comments