From a74cacf5ee19292941dcace2a1f700b139425f0e Mon Sep 17 00:00:00 2001 From: Wei Ji Date: Sat, 11 Apr 2020 12:35:34 +1200 Subject: [PATCH 01/12] Wrap subplot Wrapping the `subplot` function! Original GMT `subplot` function can be found at https://docs.generic-mapping-tools.org/latest/subplot.html. Current implementation has a 'directive' statement to tell subplot whether to 'begin', 'set' or 'end' a subplot, and the position/number of row(s) and column(s) can be set too. Also created an alias for dimensions (F). --- doc/api/index.rst | 1 + pygmt/base_plotting.py | 52 ++++++++++++++++++++ pygmt/tests/baseline/test_subplot_basic.png | Bin 0 -> 9291 bytes pygmt/tests/test_subplot.py | 31 ++++++++++++ 4 files changed, 84 insertions(+) create mode 100644 pygmt/tests/baseline/test_subplot_basic.png create mode 100644 pygmt/tests/test_subplot.py diff --git a/doc/api/index.rst b/doc/api/index.rst index c4f199b3dbc..d8c39a315c4 100644 --- a/doc/api/index.rst +++ b/doc/api/index.rst @@ -34,6 +34,7 @@ Plotting data and laying out the map: Figure.logo Figure.image Figure.shift_origin + Figure.subplot Figure.text Figure.meca diff --git a/pygmt/base_plotting.py b/pygmt/base_plotting.py index 950e1b089de..e2ec43459c7 100644 --- a/pygmt/base_plotting.py +++ b/pygmt/base_plotting.py @@ -884,6 +884,58 @@ def legend(self, spec=None, position="JTR+jTR+o0.2c", box="+gwhite+p1p", **kwarg lib.call_module("legend", arg_str) @fmt_docstring + @use_alias(F="dimensions") + def subplot(self, directive: str, row: int = None, col: int = None, **kwargs): + """ + Manage modern mode figure subplot configuration and selection. + + The subplot module is used to split the current figure into a + rectangular layout of subplots that each may contain a single + self-contained figure. A subplot setup is started with the begin + directive that defines the layout of the subplots, while positioning to + a particular subplot for plotting is done via the set directive. The + subplot process is completed via the end directive. + + Full option list at :gmt-docs:`subplot.html` + + {aliases} + + Parameters + ---------- + directive : str + Either 'begin', 'set' or 'end'. + row : int + The number of rows if using the 'begin' directive, or the row + number if using the 'set' directive. First row is 0, not 1. + col : int + The number of columns if using the 'begin' directive, or the column + number if using the 'set' directive. First column is 0, not 1. + dimensions : str + Specify the dimensions of the figure when using the 'begin' + directive. There are two different ways to do this: (f) Specify + overall figure dimensions or (s) specify the dimensions of a single + subplot. + """ + if directive not in ("begin", "set", "end"): + raise GMTInvalidInput( + f"Unrecognized subplot directive '{directive}',\ + should be either 'begin', 'set', or 'end'" + ) + + with Session() as lib: + rowcol = "" # default is blank, e.g. when directive == "end" + if row is not None and col is not None: + if directive == "begin": + rowcol = f"{row}x{col}" + elif directive == "set": + rowcol = f"{row},{col}" + arg_str = " ".join( + a for a in [directive, rowcol, build_arg_string(kwargs)] if a + ) + lib.call_module(module="subplot", args=arg_str) + + @fmt_docstring + @use_alias(R="region", J="projection", B="frame") @use_alias( R="region", J="projection", diff --git a/pygmt/tests/baseline/test_subplot_basic.png b/pygmt/tests/baseline/test_subplot_basic.png new file mode 100644 index 0000000000000000000000000000000000000000..6a9a6a6b5e53aaa933b0dcba60c690138978a726 GIT binary patch literal 9291 zcmds7cTiJXw-0bd=?Wq!5Tq+8ARVOGXi7j-q=q8>h9)FHkO0aRq=t@km3A*BC?H6v ziW)jXt^^1WkY)@;N<>QF9SB~(`Mx*vzBk{Uc{A@1hRiwp?6cQiYyZ~ox7LZeZmiG2 zdV&=M0&y4`T)6=PF&TkC3_FJyfh!d44sPHdi;sb&9|$BXw)@8rSg7L*=~ivN zW4%;Tve85;g1nW0%8;$z`=bTRi#<@D;f4@A99oMIuoG_?u*s)jr{lnZ^`Z5jW)UQ8 z#xEk!W&^??Q7gFGMcs$Mj;jh3TOM{c_ZUr+vZ{N>)YYE25>MUWm2>D?(d?%t!8x)q z53j(|m;z`Kf3O-nF2m%S=hL*-d3N0Y6Cl}J4mZyzV850u?%<8|fPotS1NY1@{Y&z>n)%~yvIeyw)$7jSt!6=Jqf z<<(OjYAQ-QfVD~YHg`gKycQ=1C1JRrA+^!b1KG?A5LC`)M4d{;`3c8U4q_4N?Y?!# zVB7H#mUnGUuvZDn-cd&sbag3c2`-UBKRW z)c;JmSk!y@;zKsh$ku7}kWT+|&3uT;@DmvlZwO~AtpMg;028U)c^`8ie~EyA4v6{6 zBAFKT(DAZPjq3}EUt^g9rOBD+$yTXH%7>03^F?0y)`}(7p{B7IyAf{Nao^wg)P|7~ zWNB!psa3Vh&8fJuk$JaF$*&lTKzBtSB^3pN51wKfMN=AJH#_rLt*T@lQ^FB6uS zegW(}+cRX5(zF=oQ*&p|Ss#l@xmP^{pF9u@Qxd5nQm*3HXwxA`ypsBY1Jk|R_E_de z182}vsq!@jiW&!xP?KUhp`s6QCyDkBfQtXC?E zJ_BLC&Z`yPgE9Xa{I1J9(&>a=3;1XjMkN0u*-F7rJ6ygl2C0#=oEd`l?aAp2c%MGR zdqIJ$!F|%I>h$u+poy8;EZY^m<9{ka2nZyk!IdAXp@j}Cs*nsxae0ppaVVdYP`TwXWXjA2c)KKw?FtAm>HY2NiL)tJnlO2- z$M5m^t$sFj@9Z%o1oNh14(P#DbZtp%=_dKM$jEE9k!Bf68tCh9WM`&nHeWsZUs@Cv z!tVEFcE|Ct=!GleymKZuWL`MBez0Vr7z`A^KnIFo-+3F-ZYHW?*N`?<(IaK+f62pkmsSkpqJmAd> ztJA!Aq-VD`)N5fXr#M)ZHl0>JqrYis>X_@AfFTr6DveZ8mVH4T-EL~R&0f>}RBbl` zB1?&?TsfB6*&#S#he=)wss@k@L&2oSYRw?I0dI&IN~UIGbai1a`pYHjQmP@e&5akM z86QVgSsNn*!$!v zJ4$Lz+wxGpq5Av7n<7v-LxQvAnxRNvCO(eOZn!2#GhX8pEVU|a5*`%Mv}kU7&Tm`# zF73Tm0gR>KikVTh3sQWsb6w^truXEnA=|^OemzJ08YxKd*Za*;k01u-tVhmUt;jBe zK#x&@TmTOrS#si3dT46GXW{Cr|2vK^HDWYl6bUjv|3f@^243 zaC#ZQb_y0lDHo``zY<;RS&z7^a(xb)t~ri;k#sQYw;`;Ez@eJH@ZEWBe!ly&=K|j> zPk*a?aAUtx_qs5Oq*HHsq38-ufyPu|Xn>9qt?(pvqQ5Uw??4FV5=9~)wAL>Y@`yF%@{H1bOG3b7b&6Ws+DU{p+JukAj&R;ir+n>kCU^Rjbvk?Z z5TkD7B6s7RRm8V7%B1*_qsH(vc%2KGoA}8J(Tc^*zQW*QJb5GKB55su$Csbk_1F`O zXO0=$4E>toTHDggvgsC0%e9`wJ{;6M)AI9XlP59DKfSk;l+sY!B=OXY#r8u90lrx7 zUoR?Wu_kp|Uc>8#%g&slv|R#5LaIyKd`da=M&8sp@?Gr_)s0c|`TW*!lI)I$-8}M( zb<@TM(whY@EmK*`i%c5HpB!RbHFjT~)*HZ4W8_Ut+)n5$3Nno9lQkh`Ia^!tJ z9-+=vu7nv=@`QjK;M)^?$oy6m2=*x6dE=mgpGi=InHggJXj)co8)J$Q-BkTToHezX zXqKe&1}vRo_~W8}hcYtXwH8z+qLD@xKG|&()BtT>oj(W#tB-6^s&^K5wks%Do$c?F zTa&#yJ2qw?aX4J9wa>%CZjalH?O8Ju9efs;rJ{LzW2IL|m@{%`YgR|~r6&&?*%Y1o&Ud~NhM~O0 zP-;-JEx-%6*4OJCnC$&XJ)a2`RMUu@s+%;&;xI`qJH)|2%%94z#>*eIKqqyg-VcL)L)zH>Brj+>7YpHriNe% z^3{ou4KhG{)+Hw5&R->1qj~(Hsv9wt^XutrYim+ev8g+)EU(7)ccMS_O=EMj_xXe7 zRtD(BMNAHm1D%&%O~ly?3k&bi7DM&ru*?sCkH<|?IneG^fjpa0@u*?59Ru@dUzEWd zX=i(F+Rx12OF_#k#CU9I!e`KsJ4bj_>Bh4A$vXm&$+r=K9C zebCnPP!{pOZl7zC?9K7Iz}WY~Xb0rJV2Bnr`{IX|MN0(vavwGL5m&hH24nKe1%L!LLhL2rOoRr{LR z1fextn-k6jyueP!ZaZk99Pntts@~7SXxU2%R#bq9b{fL2B1;yqi9|T@`~fO?PqGWbW`P@fWnv?k}pK9q=&cju3&8@j8JV>|L#DSkJrq;w}X6uC>< z>44FBd3nI7xsH)JH9<}}FBbJAj69h}qfJgaQf`kF_Dm;CX4iN|I`B6>-n{x+kSRC) z+~BDWYQ_-Ayg?1h_MhgZ|G_YCYgPO58c(U+l>&>K>)ul|w~E z^}OJF$6aS<76Fd8R`o3R0D=NEuV3<2^;y^8l?M zsThxeKQzQm-u3cdMeTfgU+{-XF@C_)q7Xx&vdvcB@G0GOg+-kYUa3y}h(HqN`hlE7 z#so&dqmaaa%s!>=0Z4dU_g@iMC9OdTK-NLZkk+|5 zdYwTt^kkHr1SX^dkhnHat=Uy_ssR^2$v{*P6us9~bI6uiUZIL~=*}GS`Ri6l53XE* znm)D4-#eJ)RXp6f5hD^^No2@_!W%5&X;sA6%)JQ&BbyBH&MV2qqS>u;@S^rzn#b%!RxdX+S6 zB}v;u6ZYY=b>R7|JWM0N7c`Pohn;PF98zPXjaO_6d(Jg<6)L|Si(LC-wJBTu|t3vUZv833w;&+c9#y>X+K;qlDp0H z0}q{o#E&8KnWEfry!ZR*9#G5xtMVa%p*jzTPP5o&&?mS?zKdT;*I!$`&4mK5GMRrj zg*<3Pj<467Yj@H9=sVJKb945^Y_RZ0>A|dn#>un-3bEtZu4o^fCXaoo_rAeIPY5oM zpfYKI3<{w-s0T;oJOfq&HA6x|w4WbO$f>Sdc|d8jS)xY>h4S_433NVYYR7zq=2hbZL=#Tv&wi(ij#%z z+V68g7Vt9XJmMNW0*R9R|^VpR}uqg7ZnbDPotZ-OBr{`9i{XDRD$EoK4gWt--76c6vmBH$k3j~|^;3$uUaRa5f05Mx`sl6&5?(l0+q z*}eYB@{*Fy%MS>>H<=&r$60>A{(iKWMQQSjtyguv}T{viiZ zx!xOX8^fYSp-)*gj$~sFfDWC?5?xfvch4-(Viw5C65^bE5dnY+1mD#iU!GNSTLq8~ zAj;1-|6R)BpJx2*NntKCBTxq*VL6#uLJ<5OkR=EKC>!DMI7+uMKnHJR_CP>24d z!0ap`6sh^uIrlIB8i5Y{w`E6^AhL#UBMM}0U6gvT=LUf6`lasJMzIPg_{iP?rsI{S z39rFPI@#-3^?~e04~Y+{s^uU&zAu-k^mza*^7a!ptrO*cl}HB%_g!@9 z_1yZ42pe&!+Jg2I{b%7~SXNPnO&hOx%MYS#gbqClz_5wz)B|q&`}9cy{^bP!JVzhm zqZ>4$Tn}Fu9#Ic9O+NwpeY=(D)E2nY)n?-3L10B}||AIj#|)w-;k|4RI}k^N@u1PeNe5JvKY z&%N&1l&W~m98gjLmK#y(9;teyTC1S7hXj9D@CR8}eIUbw{LhC4(zBXn26&G@{)5dW zu;p)0{v9UcSGQdIchi`kWs1McdH&yU4VqV~61*yuSL_VNpM49oKTre771Dw-DU zM))hmfU_>o?pHMl*JuRJx?np{aykXbbO_$4DMS;jqoV_i8cf|wO|~Q4zT&|6&CN}q ztT0gi>RL{x2u|IT$k{3%bZ#y6fjYCMoa1xT6pCUM119h;_Sk<=wE8Cm{%?J}r+Pez z#=9&1nL%el4%q$3p!tiNe5#TVltxaCSF1DvT4AS_p8CN0rRRC}R)De7ChgYC$HScW@h2P1LLjODlx1|}<0Cnhp+Cz74)j*8FyxuSJwz+Eht}=Gahcgk z)~a8;-O1;)p~N*>E!U?L5feUYukLbv->+|IkK*ca*i7uGb0m{C{&QeP@TO~II7jYm zP_?%<$0DGx(Nl5Su!Ufth?Xv_${w!Bc3oj#^P2z~{+dnh60|AG5ialZ=b~wL?U%q-eqVj8mQ=gE$wLs-{H23l+k*H8iRWU3?%tRGL7@DCH1|@7 z`Iw%LYe3O>pr&oJ1%XPjW|ZiZ)v!H-+pRTu`LZ^QX_1%mef2;F`}+t`H2q>2MI4a3 z)~bRkX(|D?ZQHLc4A-P$62@0n6xZORra+aoB|o;Jop^oNa@H=CMg^)_!GVq^#oYaH zjzrePhNp!x4>!rhc_Hi5>A$HiNI?T`O%Tz3VTxPqnRIY6I#{%6WO;(&Wo4MNO;{ zfvOy>mJ}ku5f~UT1wZRAyI5t$;(gjrw`Z+CH+=_K!8WxbbbdC>28$=d>y{z-?1AHm zp^gBpc5}zvjWp}}Q$Rwoa@PG;BieiTq(-i^-p+3sjB*x-6l9VT5juZs<&AgnbjM$JB4#)gg^DH0(9rRIT&N==kM`A^>_r(s;2;T1Qfe&arcbk0ZCn(&cG`;*++TyS>ddrKT5v%sAb#9T_M)Yg4K3QE7RnSrbR3UJNVh`91*27 zT)I;Z^i5DCZ@lEEBupy&tYf`?{J_ncrkd9UMebM(5gRARZ4blrAx4+=xEe;Ap-U@R z?@^-%2^hNq*xWo=+p0?N6)a-Xvf&7FzEY;ftCOdETWN$rh!!7T`%Bul>sU_7$r=qJ^b|(%w%OmFm^m)3N7k=N!r7ZQ; zwBKzlGc_2;Zd%;xUH3lGyf zYg6H@e1e>jox)Y21rA8PERX7o=1nqNg0~ztXMs-(+IGJMh}bkF0PVO-PCIdYYEzmUTp+o;VJD@wMM&+|9!&Nc0a+;=OO>*9I?78vy1+R_zew<<*N+qKWtb8&`4TW)r$wNXy1V_&0o*m@{iel=yuK65 z!EfKZ8;9V5ok)OnuNau8@g@PSS)gCcbu5;%#2g7WZ@ziIlwC6m-W`O3ChL!F09ELk zuk0EEyT@H{P5Hi162{e1zf=tAROU&fC_*L_-QXYp{QY2r1AyQDT%cZ{MI8})TNiAZ z$^Jw-h&cyZF}@z1QNh`+6dR^>N@cfAjDP7g6U^-3%eMm8m{z8Nr?b!JFUHDM$%Hxe zBg8N4cF^lyI=`}8MrZ;ewD!9YJPk)1Ub#B&;4;8>(T)jqM1U5=Q zjyska7EjW^QO)n*dY)8q4j!N-3_6ay>Ecb_`K{((~Dq=G3U1 zo~r()*+y!S>ZX%p3?)G2j^FEA5eK}Xmt-WZ4vmQ(=!%~*TQTw4+E}AH)+?6Qdmjz7 z^Q&%7m858AY!-bjDUr=<(XMQ8Wc4ecJc2{hyz_uBMAmtcAC0&nta%qx`8SLgP?d-E zl6_-EzFo%JCSf`&7m@*hDi)Ays`fmO6hyowNV}fJkyqJKq>6?+Gh1e#HsbCX5V#GZ zN*)7^e}l2~74sCEyolPxwAL|D-rXA0#xl9hltD(YXot+Ty7wyv*6od7-r O0vYNWU%_0mfAC))wQ%+T literal 0 HcmV?d00001 diff --git a/pygmt/tests/test_subplot.py b/pygmt/tests/test_subplot.py new file mode 100644 index 00000000000..241b2e23763 --- /dev/null +++ b/pygmt/tests/test_subplot.py @@ -0,0 +1,31 @@ +""" +Tests subplot +""" +import pytest + +from .. import Figure +from ..exceptions import GMTInvalidInput + + +@pytest.mark.mpl_image_compare +def test_subplot_basic(): + """ + Create a subplot figure with 1 row and 2 columns. + """ + fig = Figure() + fig.subplot(directive="begin", row=1, col=2, dimensions="f6c/3c") + fig.subplot(directive="set", row=0, col=0) + fig.basemap(region=[0, 3, 0, 3], frame=True) + fig.subplot(directive="set", row=0, col=1) + fig.basemap(region=[0, 3, 0, 3], frame=True) + fig.subplot(directive="end") + return fig + + +def test_subplot_incorrect_directive(): + """ + Check that subplot fails when an incorrect directive is used + """ + fig = Figure() + with pytest.raises(GMTInvalidInput): + fig.subplot(directive="start") From 78efa6cdddbd3f77acb1b718b9e17f1540c9da31 Mon Sep 17 00:00:00 2001 From: Wei Ji Date: Mon, 11 May 2020 11:21:50 +1200 Subject: [PATCH 02/12] Alias frame (B) for subplot Include test to check that map frame setting is applied to all subplot figures when using 'begin' subplot directive. --- pygmt/base_plotting.py | 2 +- pygmt/tests/baseline/test_subplot_frame.png | Bin 0 -> 9249 bytes pygmt/tests/test_subplot.py | 15 +++++++++++++++ 3 files changed, 16 insertions(+), 1 deletion(-) create mode 100644 pygmt/tests/baseline/test_subplot_frame.png diff --git a/pygmt/base_plotting.py b/pygmt/base_plotting.py index e2ec43459c7..5c3e5f56ed5 100644 --- a/pygmt/base_plotting.py +++ b/pygmt/base_plotting.py @@ -884,7 +884,7 @@ def legend(self, spec=None, position="JTR+jTR+o0.2c", box="+gwhite+p1p", **kwarg lib.call_module("legend", arg_str) @fmt_docstring - @use_alias(F="dimensions") + @use_alias(F="dimensions", B="frame") def subplot(self, directive: str, row: int = None, col: int = None, **kwargs): """ Manage modern mode figure subplot configuration and selection. diff --git a/pygmt/tests/baseline/test_subplot_frame.png b/pygmt/tests/baseline/test_subplot_frame.png new file mode 100644 index 0000000000000000000000000000000000000000..071133e186902fe31214d60f105fbe60bdf4a5e6 GIT binary patch literal 9249 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 D_KCoO literal 0 HcmV?d00001 diff --git a/pygmt/tests/test_subplot.py b/pygmt/tests/test_subplot.py index 241b2e23763..fa3630e70c8 100644 --- a/pygmt/tests/test_subplot.py +++ b/pygmt/tests/test_subplot.py @@ -22,6 +22,21 @@ def test_subplot_basic(): return fig +@pytest.mark.mpl_image_compare +def test_subplot_frame(): + """ + Check that map frame setting is applied to all subplot figures + """ + fig = Figure() + fig.subplot(directive="begin", row=1, col=2, dimensions="f6c/3c", frame="WSne") + fig.subplot(directive="set", row=0, col=0) + fig.basemap(region=[0, 3, 0, 3], frame="+tplot0") + fig.subplot(directive="set", row=0, col=1) + fig.basemap(region=[0, 3, 0, 3], frame="+tplot1") + fig.subplot(directive="end") + return fig + + def test_subplot_incorrect_directive(): """ Check that subplot fails when an incorrect directive is used From 5ed4745463b3428f998c61721a63c4249a87b2c9 Mon Sep 17 00:00:00 2001 From: Wei Ji Date: Sun, 20 Sep 2020 23:47:52 +1200 Subject: [PATCH 03/12] Initial subplot tutorial example Translated and adapted from https://github.com/gmt-china/GMT_Docs/blob/master/source/tutorial/subplot.rst. Covers basics of using pygmt subplot begin, set and end. Still need to handle gmt "set" using index instead of just row and col. --- doc/index.rst | 1 + examples/tutorials/subplots.py | 87 ++++++++++++++++++++++++++++++++++ pygmt/base_plotting.py | 2 + 3 files changed, 90 insertions(+) create mode 100644 examples/tutorials/subplots.py diff --git a/doc/index.rst b/doc/index.rst index b57c05e992d..04292c9aa88 100644 --- a/doc/index.rst +++ b/doc/index.rst @@ -32,6 +32,7 @@ projections/index.rst tutorials/coastlines.rst tutorials/plot.rst + tutorials/subplots.rst .. toctree:: :maxdepth: 2 diff --git a/examples/tutorials/subplots.py b/examples/tutorials/subplots.py new file mode 100644 index 00000000000..4b849f08ee1 --- /dev/null +++ b/examples/tutorials/subplots.py @@ -0,0 +1,87 @@ +""" +Subplots +======== + +When you're preparing a figure for a paper, there will often be times when +you'll need to put many individual plots into one large figure, and label them +'abcd'. These individual plots are called subplots. + +There are two main ways to handle subplots in GMT: + +- Use :meth:`pygmt.Figure.shift_origin` to manually move each individual plot + to the right position. +- Use :meth:`pygmt.Figure.subplot` to define the layout of the subplots. + +The first method is easier to use and should handle simple cases involving a +couple of subplots. For more advanced subplot layouts however, we recommend the +use of :meth:`pygmt.Figure.subplot` which offers finer grained control, and +this is what the tutorial below will cover. +""" + +############################################################################### +# Let's start by importing the PyGMT library and initiating a figure. + +import pygmt + +fig = pygmt.Figure() + +############################################################################### +# Define subplot layout +# --------------------- +# +# The ``fig.subplot(directive="begin")`` command is used to setup the layout, +# size, and other attributes of the figure. It divides the whole canvas into +# regular grid areas with n rows and m columns. Each grid area can contain an +# individual subplot. For example: + +fig.subplot(directive="begin", row=2, col=3, dimensions="s5c/3c", frame="lrtb") + +############################################################################### +# will define our figure to have a 2 row and 3 column grid layout. +# ``dimensions="s5c/3c"`` specifies that each 's'ubplot will have a width of +# 5cm and height of 3cm. Alternatively, you can set ``dimensions="f15c/6c"`` to +# define the overall size of the 'f'igure to be 15cm wide by 6cm high. Using +# ``frame="lrtb"`` allows us to customize the map frame for all subplots. The +# figure layout will look like the following: + +for index in range(2 * 3): + i = index // 3 # row + j = index % 3 # column + fig.subplot(directive="set", row=i, col=j) + fig.text( + x=0.5, y=0.5, text=f"index: {index}, row: {i}, col: {j}", region=[0, 1, 0, 1] + ) +fig.subplot(directive="end") +fig.show() + +############################################################################### +# The ``fig.subplot(directive="set")`` command activates a specified subplot, +# and all subsequent plotting commands will take place in that subplot. In +# order to specify a subplot, you will need to know the identifier for each +# subplot. This can be done by setting the ``row`` and ``col`` arguments. + +############################################################################### +# .. note:: +# +# The row and column numbering starts from 0. So for a subplot layout with +# N rows and M columns, row numbers will go from 0 to N-1, and column +# numbers will go from 0 to M-1. + +############################################################################### +# For example, to activate the subplot on the top right corner (index: 2) so +# that all subsequent plotting commands happen there, you can use the following +# command: + +############################################################################### +# .. code-block:: default +# +# fig.subplot(directive="set", row=0, col=2) + +############################################################################### +# Finally, remember to use ``fig.subplot(directive="end")`` to exit the subplot +# mode. + +############################################################################### +# .. code-block:: default +# +# fig.subplot(directive="end") diff --git a/pygmt/base_plotting.py b/pygmt/base_plotting.py index 5c3e5f56ed5..987813220af 100644 --- a/pygmt/base_plotting.py +++ b/pygmt/base_plotting.py @@ -911,6 +911,8 @@ def subplot(self, directive: str, row: int = None, col: int = None, **kwargs): The number of columns if using the 'begin' directive, or the column number if using the 'set' directive. First column is 0, not 1. dimensions : str + ``[f|s]width(s)/height(s)[+fwfracs/hfracs][+cdx/dy][+gfill][+ppen] + [+wpen]`` Specify the dimensions of the figure when using the 'begin' directive. There are two different ways to do this: (f) Specify overall figure dimensions or (s) specify the dimensions of a single From d4805e038ccabacc81d7c3d51321d34ee32345da Mon Sep 17 00:00:00 2001 From: Wei Ji Date: Wed, 13 May 2020 20:08:48 +1200 Subject: [PATCH 04/12] Experimental subplot implementation that mimics matplotlib's style Experimenting with a more matplotlib-like syntax for managing subplots, namely the `fig, axs = plt.subplots()` style of defining subplots! A high-level `subplots` function is defined in pygmtplots.py, that wraps around the new class SubPlot (inherited from the main Figure class to include subplot functionality). Re-aliased F to figsize (previously called dimensions). Also updated tests and tutorials to reference this new syntax. --- doc/api/index.rst | 2 +- examples/tutorials/subplots.py | 66 +++++++++++++++--------------- pygmt/__init__.py | 3 +- pygmt/base_plotting.py | 53 ------------------------ pygmt/figure.py | 75 ++++++++++++++++++++++++++++++++++ pygmt/pygmtplot.py | 39 ++++++++++++++++++ pygmt/tests/test_subplot.py | 30 ++++---------- 7 files changed, 158 insertions(+), 110 deletions(-) create mode 100644 pygmt/pygmtplot.py diff --git a/doc/api/index.rst b/doc/api/index.rst index d8c39a315c4..3ee226b9afc 100644 --- a/doc/api/index.rst +++ b/doc/api/index.rst @@ -16,6 +16,7 @@ All plotting is handled through the :class:`pygmt.Figure` class and its methods. :toctree: generated Figure + subplots Plotting data and laying out the map: @@ -34,7 +35,6 @@ Plotting data and laying out the map: Figure.logo Figure.image Figure.shift_origin - Figure.subplot Figure.text Figure.meca diff --git a/examples/tutorials/subplots.py b/examples/tutorials/subplots.py index 4b849f08ee1..0e1c5a218da 100644 --- a/examples/tutorials/subplots.py +++ b/examples/tutorials/subplots.py @@ -6,59 +6,58 @@ you'll need to put many individual plots into one large figure, and label them 'abcd'. These individual plots are called subplots. -There are two main ways to handle subplots in GMT: +There are two main ways to create subplots in GMT: - Use :meth:`pygmt.Figure.shift_origin` to manually move each individual plot to the right position. -- Use :meth:`pygmt.Figure.subplot` to define the layout of the subplots. +- Use :meth:`pygmt.subplots` to define the layout of the subplots. The first method is easier to use and should handle simple cases involving a couple of subplots. For more advanced subplot layouts however, we recommend the -use of :meth:`pygmt.Figure.subplot` which offers finer grained control, and -this is what the tutorial below will cover. +use of :meth:`pygmt.subplots` which offers finer grained control, and this is +what the tutorial below will cover. """ ############################################################################### -# Let's start by importing the PyGMT library and initiating a figure. +# Let's start by importing the PyGMT library import pygmt -fig = pygmt.Figure() - ############################################################################### # Define subplot layout # --------------------- # -# The ``fig.subplot(directive="begin")`` command is used to setup the layout, -# size, and other attributes of the figure. It divides the whole canvas into -# regular grid areas with n rows and m columns. Each grid area can contain an -# individual subplot. For example: +# The ``pygmt.subplots`` command is used to setup the layout, size, and other +# attributes of the figure. It divides the whole canvas into regular grid areas +# with n rows and m columns. Each grid area can contain an individual subplot. +# For example: -fig.subplot(directive="begin", row=2, col=3, dimensions="s5c/3c", frame="lrtb") +fig, axs = pygmt.subplots(nrows=2, ncols=3, figsize=("15c", "6c"), frame="lrtb") ############################################################################### # will define our figure to have a 2 row and 3 column grid layout. -# ``dimensions="s5c/3c"`` specifies that each 's'ubplot will have a width of -# 5cm and height of 3cm. Alternatively, you can set ``dimensions="f15c/6c"`` to -# define the overall size of the 'f'igure to be 15cm wide by 6cm high. Using -# ``frame="lrtb"`` allows us to customize the map frame for all subplots. The -# figure layout will look like the following: - -for index in range(2 * 3): - i = index // 3 # row - j = index % 3 # column - fig.subplot(directive="set", row=i, col=j) +# ``figsize=("15c", "6c")`` defines the overall size of the figure to be 15cm +# wide by 6cm high. Using ``frame="lrtb"`` allows us to customize the map frame +# for all subplots instead of setting them individually. The figure layout will +# look like the following: + +for index in axs.flatten(): + i = index // axs.shape[1] # row + j = index % axs.shape[1] # column + fig.sca(ax=axs[i, j]) # sets the current Axes fig.text( x=0.5, y=0.5, text=f"index: {index}, row: {i}, col: {j}", region=[0, 1, 0, 1] ) -fig.subplot(directive="end") +fig.end_subplot() fig.show() ############################################################################### -# The ``fig.subplot(directive="set")`` command activates a specified subplot, -# and all subsequent plotting commands will take place in that subplot. In -# order to specify a subplot, you will need to know the identifier for each -# subplot. This can be done by setting the ``row`` and ``col`` arguments. +# The ``fig.sca`` command activates a specified subplot, and all subsequent +# plotting commands will take place in that subplot. This is similar to +# matplotlib's ``plt.sca`` method. In order to specify a subplot, you will need +# to provide the identifier for that subplot via the ``ax`` argument. This can +# be found in the ``axs`` variable referenced by the ``row`` and ``col`` +# number. ############################################################################### # .. note:: @@ -68,20 +67,19 @@ # numbers will go from 0 to M-1. ############################################################################### -# For example, to activate the subplot on the top right corner (index: 2) so -# that all subsequent plotting commands happen there, you can use the following -# command: +# For example, to activate the subplot on the top right corner (index: 2) at +# ``row=0`` and ``col=2``, so that all subsequent plotting commands happen +# there, you can use the following command: ############################################################################### # .. code-block:: default # -# fig.subplot(directive="set", row=0, col=2) +# fig.sca(ax=axs[0, 2]) ############################################################################### -# Finally, remember to use ``fig.subplot(directive="end")`` to exit the subplot -# mode. +# Finally, remember to use ``fig.end_subplot()`` to exit the subplot mode. ############################################################################### # .. code-block:: default # -# fig.subplot(directive="end") +# fig.end_subplot() diff --git a/pygmt/__init__.py b/pygmt/__init__.py index 512865264b0..b07557ee8f6 100644 --- a/pygmt/__init__.py +++ b/pygmt/__init__.py @@ -13,12 +13,13 @@ # Import modules to make the high-level GMT Python API from .session_management import begin as _begin, end as _end -from .figure import Figure +from .figure import Figure, SubPlot from .filtering import blockmedian from .gridding import surface from .sampling import grdtrack from .mathops import makecpt from .modules import GMTDataArrayAccessor, config, info, grdinfo, which +from .pygmtplot import subplots from .gridops import grdcut from .x2sys import x2sys_init, x2sys_cross from . import datasets diff --git a/pygmt/base_plotting.py b/pygmt/base_plotting.py index 987813220af..6f7a4616ed2 100644 --- a/pygmt/base_plotting.py +++ b/pygmt/base_plotting.py @@ -883,59 +883,6 @@ def legend(self, spec=None, position="JTR+jTR+o0.2c", box="+gwhite+p1p", **kwarg arg_str = " ".join([specfile, build_arg_string(kwargs)]) lib.call_module("legend", arg_str) - @fmt_docstring - @use_alias(F="dimensions", B="frame") - def subplot(self, directive: str, row: int = None, col: int = None, **kwargs): - """ - Manage modern mode figure subplot configuration and selection. - - The subplot module is used to split the current figure into a - rectangular layout of subplots that each may contain a single - self-contained figure. A subplot setup is started with the begin - directive that defines the layout of the subplots, while positioning to - a particular subplot for plotting is done via the set directive. The - subplot process is completed via the end directive. - - Full option list at :gmt-docs:`subplot.html` - - {aliases} - - Parameters - ---------- - directive : str - Either 'begin', 'set' or 'end'. - row : int - The number of rows if using the 'begin' directive, or the row - number if using the 'set' directive. First row is 0, not 1. - col : int - The number of columns if using the 'begin' directive, or the column - number if using the 'set' directive. First column is 0, not 1. - dimensions : str - ``[f|s]width(s)/height(s)[+fwfracs/hfracs][+cdx/dy][+gfill][+ppen] - [+wpen]`` - Specify the dimensions of the figure when using the 'begin' - directive. There are two different ways to do this: (f) Specify - overall figure dimensions or (s) specify the dimensions of a single - subplot. - """ - if directive not in ("begin", "set", "end"): - raise GMTInvalidInput( - f"Unrecognized subplot directive '{directive}',\ - should be either 'begin', 'set', or 'end'" - ) - - with Session() as lib: - rowcol = "" # default is blank, e.g. when directive == "end" - if row is not None and col is not None: - if directive == "begin": - rowcol = f"{row}x{col}" - elif directive == "set": - rowcol = f"{row},{col}" - arg_str = " ".join( - a for a in [directive, rowcol, build_arg_string(kwargs)] if a - ) - lib.call_module(module="subplot", args=arg_str) - @fmt_docstring @use_alias(R="region", J="projection", B="frame") @use_alias( diff --git a/pygmt/figure.py b/pygmt/figure.py index d32588ae85d..91835601633 100644 --- a/pygmt/figure.py +++ b/pygmt/figure.py @@ -374,3 +374,78 @@ def _repr_html_(self): base64_png = base64.encodebytes(raw_png) html = '' return html.format(image=base64_png.decode("utf-8"), width=500) + + +class SubPlot(Figure): + """ + Manage modern mode figure subplot configuration and selection. + + The subplot module is used to split the current figure into a + rectangular layout of subplots that each may contain a single + self-contained figure. A subplot setup is started with the begin + directive that defines the layout of the subplots, while positioning to + a particular subplot for plotting is done via the set directive. The + subplot process is completed via the end directive. + + Full option list at :gmt-docs:`subplot.html` + """ + + def __init__(self, nrows, ncols, figsize, **kwargs): + super().__init__() + # Activate main Figure, and initiate subplot + self._activate_figure() + self.begin_subplot(row=nrows, col=ncols, figsize=figsize, **kwargs) + + @fmt_docstring + @use_alias(Ff="figsize", B="frame") + @kwargs_to_strings(Ff="sequence") + def begin_subplot(self, row=None, col=None, **kwargs): + """ + The begin directive of subplot defines the layout of the entire + multi-panel illustration. Several options are available to specify + the systematic layout, labeling, dimensions, and more for the + subplots. + + {aliases} + """ + arg_str = " ".join(["begin", f"{row}x{col}", build_arg_string(kwargs)]) + with Session() as lib: + lib.call_module(module="subplot", args=arg_str) + + @fmt_docstring + @use_alias(F="dimensions") + def sca(self, ax=None, **kwargs): + """ + Set the current Axes instance to *ax*. + + Before you start plotting you must first select the active subplot. + Note: If any projection (J) option is passed with ? as scale or + width when plotting subplots, then the dimensions of the map are + automatically determined by the subplot size and your region. For + Cartesian plots: If you want the scale to apply equally to both + dimensions then you must specify ``projection="x"`` [The default + ``projection="X"`` will fill the subplot by using unequal scales]. + + {aliases} + """ + arg_str = " ".join(["set", f"{ax}", build_arg_string(kwargs)]) + with Session() as lib: + lib.call_module(module=f"subplot", args=arg_str) + + @fmt_docstring + @use_alias(V="verbose") + def end_subplot(self, **kwargs): + """ + This command finalizes the current subplot, including any placement + of tags, and updates the gmt.history to reflect the dimensions and + linear projection required to draw the entire figure outline. This + allows subsequent commands, such as colorbar, to use + ``position="J"`` to place bars with reference to the complete + figure dimensions. We also reset the current plot location to where + it was prior to the subplot. + + {aliases} + """ + arg_str = " ".join(["end", build_arg_string(kwargs)]) + with Session() as lib: + lib.call_module(module="subplot", args=arg_str) diff --git a/pygmt/pygmtplot.py b/pygmt/pygmtplot.py new file mode 100644 index 00000000000..5fe8309ebe5 --- /dev/null +++ b/pygmt/pygmtplot.py @@ -0,0 +1,39 @@ +import numpy as np + +from .figure import SubPlot + + +def subplots(nrows=1, ncols=1, figsize=(6.4, 4.8), **kwargs): + """ + Create a figure with a set of subplots. + + Parameters + ---------- + nrows : int + Number of rows of the subplot grid. + + ncols : int + Number of columns of the subplot grid. + + figsize : tuple + Figure dimensions as ``(width, height)``. + + Returns + ------- + fig : :class:`pygmt.Figure` + A PyGMT Figure instance. + + axs : numpy.ndarray + Array of Axes objects. + """ + # Get PyGMT Figure with SubPlot initiated + fig = SubPlot(nrows=nrows, ncols=ncols, figsize=figsize, **kwargs) + + # Setup matplotlib-like Axes + axs = np.empty(shape=(nrows, ncols), dtype=object) + for index in range(nrows * ncols): + i = index // ncols # row + j = index % ncols # column + axs[i, j] = index + + return fig, axs diff --git a/pygmt/tests/test_subplot.py b/pygmt/tests/test_subplot.py index fa3630e70c8..7b8b1ee5504 100644 --- a/pygmt/tests/test_subplot.py +++ b/pygmt/tests/test_subplot.py @@ -3,8 +3,7 @@ """ import pytest -from .. import Figure -from ..exceptions import GMTInvalidInput +from ..pygmtplot import subplots @pytest.mark.mpl_image_compare @@ -12,13 +11,12 @@ def test_subplot_basic(): """ Create a subplot figure with 1 row and 2 columns. """ - fig = Figure() - fig.subplot(directive="begin", row=1, col=2, dimensions="f6c/3c") - fig.subplot(directive="set", row=0, col=0) + fig, axs = subplots(nrows=1, ncols=2, figsize=("6c", "3c")) + fig.sca(ax=axs[0, 0]) fig.basemap(region=[0, 3, 0, 3], frame=True) - fig.subplot(directive="set", row=0, col=1) + fig.sca(ax=axs[0, 1]) fig.basemap(region=[0, 3, 0, 3], frame=True) - fig.subplot(directive="end") + fig.end_subplot() return fig @@ -27,20 +25,10 @@ def test_subplot_frame(): """ Check that map frame setting is applied to all subplot figures """ - fig = Figure() - fig.subplot(directive="begin", row=1, col=2, dimensions="f6c/3c", frame="WSne") - fig.subplot(directive="set", row=0, col=0) + fig, axs = subplots(nrows=1, ncols=2, figsize=("6c", "3c"), frame="WSne") + fig.sca(ax=axs[0, 0]) fig.basemap(region=[0, 3, 0, 3], frame="+tplot0") - fig.subplot(directive="set", row=0, col=1) + fig.sca(ax=axs[0, 1]) fig.basemap(region=[0, 3, 0, 3], frame="+tplot1") - fig.subplot(directive="end") + fig.end_subplot() return fig - - -def test_subplot_incorrect_directive(): - """ - Check that subplot fails when an incorrect directive is used - """ - fig = Figure() - with pytest.raises(GMTInvalidInput): - fig.subplot(directive="start") From 18ab9d6b1d6f72b2a6336509a563bf8fc5f42577 Mon Sep 17 00:00:00 2001 From: Wei Ji Date: Sun, 20 Sep 2020 23:43:10 +1200 Subject: [PATCH 05/12] Alias ax (c) for subplot Used to advance to the selected subplot panel. Technically only allowed when in subplot mode. See https://docs.generic-mapping-tools.org/latest/gmt.html#c-full. --- pygmt/base_plotting.py | 23 +++++++++++-------- pygmt/figure.py | 2 +- pygmt/tests/baseline/test_subplot_direct.png | Bin 0 -> 11459 bytes pygmt/tests/test_subplot.py | 12 ++++++++++ 4 files changed, 26 insertions(+), 11 deletions(-) create mode 100644 pygmt/tests/baseline/test_subplot_direct.png diff --git a/pygmt/base_plotting.py b/pygmt/base_plotting.py index 6f7a4616ed2..a64bec8cbec 100644 --- a/pygmt/base_plotting.py +++ b/pygmt/base_plotting.py @@ -68,6 +68,7 @@ def _preprocess(self, **kwargs): # pylint: disable=no-self-use G="land", S="water", U="timestamp", + c="ax", t="transparency", ) @kwargs_to_strings(R="sequence") @@ -146,6 +147,7 @@ def coast(self, **kwargs): F="box", G="truncate", W="scale", + c="ax", t="transparency", ) @kwargs_to_strings(R="sequence", G="sequence") @@ -228,6 +230,7 @@ def colorbar(self, **kwargs): S="resample", U="timestamp", W="pen", + c="ax", l="label", t="transparency", ) @@ -309,6 +312,7 @@ def grdcontour(self, grid, **kwargs): B="frame", I="shading", C="cmap", + c="ax", t="transparency", ) @kwargs_to_strings(R="sequence") @@ -358,6 +362,7 @@ def grdimage(self, grid, **kwargs): Wf="facadepen", p="perspective", I="shading", + c="ax", t="transparency", ) @kwargs_to_strings(R="sequence", p="sequence") @@ -476,6 +481,7 @@ def grdview(self, grid, **kwargs): l="label", C="cmap", U="timestamp", + c="ax", t="transparency", ) @kwargs_to_strings(R="sequence", i="sequence_comma") @@ -597,6 +603,7 @@ def plot(self, x=None, y=None, data=None, sizes=None, direction=None, **kwargs): i="columns", l="label", C="levels", + c="ax", t="transparency", ) @kwargs_to_strings(R="sequence", i="sequence_comma") @@ -686,6 +693,7 @@ def contour(self, x=None, y=None, z=None, data=None, **kwargs): Td="rose", Tm="compass", U="timestamp", + c="ax", t="transparency", ) @kwargs_to_strings(R="sequence") @@ -736,6 +744,7 @@ def basemap(self, **kwargs): U="timestamp", D="position", F="box", + c="ax", t="transparency", ) @kwargs_to_strings(R="sequence") @@ -779,6 +788,7 @@ def logo(self, **kwargs): D="position", F="box", M="monochrome", + c="ax", t="transparency", ) @kwargs_to_strings(R="sequence") @@ -823,11 +833,7 @@ def image(self, imagefile, **kwargs): @fmt_docstring @use_alias( - R="region", - J="projection", - D="position", - F="box", - t="transparency", + R="region", J="projection", D="position", F="box", c="ax", t="transparency" ) @kwargs_to_strings(R="sequence") def legend(self, spec=None, position="JTR+jTR+o0.2c", box="+gwhite+p1p", **kwargs): @@ -893,6 +899,7 @@ def legend(self, spec=None, position="JTR+jTR+o0.2c", box="+gwhite+p1p", **kwarg D="offset", G="fill", W="pen", + c="ax", t="transparency", ) @kwargs_to_strings( @@ -1050,11 +1057,7 @@ def text( @fmt_docstring @use_alias( - R="region", - J="projection", - B="frame", - C="offset", - t="transparency", + R="region", J="projection", B="frame", C="offset", c="ax", t="transparency" ) @kwargs_to_strings(R="sequence") def meca( diff --git a/pygmt/figure.py b/pygmt/figure.py index 91835601633..885345df51f 100644 --- a/pygmt/figure.py +++ b/pygmt/figure.py @@ -430,7 +430,7 @@ def sca(self, ax=None, **kwargs): """ arg_str = " ".join(["set", f"{ax}", build_arg_string(kwargs)]) with Session() as lib: - lib.call_module(module=f"subplot", args=arg_str) + lib.call_module(module="subplot", args=arg_str) @fmt_docstring @use_alias(V="verbose") diff --git a/pygmt/tests/baseline/test_subplot_direct.png b/pygmt/tests/baseline/test_subplot_direct.png new file mode 100644 index 0000000000000000000000000000000000000000..4cedf3e8669e9cafe013f07ef8b5460fa1e608a0 GIT binary patch literal 11459 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 literal 0 HcmV?d00001 diff --git a/pygmt/tests/test_subplot.py b/pygmt/tests/test_subplot.py index 7b8b1ee5504..383866a124d 100644 --- a/pygmt/tests/test_subplot.py +++ b/pygmt/tests/test_subplot.py @@ -32,3 +32,15 @@ def test_subplot_frame(): fig.basemap(region=[0, 3, 0, 3], frame="+tplot1") fig.end_subplot() return fig + + +@pytest.mark.mpl_image_compare +def test_subplot_direct(): + """ + Plot map elements to subplots directly using ax argument + """ + fig, axs = subplots(nrows=2, ncols=1, figsize=("3c", "6c")) + fig.basemap(region=[0, 3, 0, 3], frame=True, ax=axs[0, 0]) + fig.basemap(region=[0, 3, 0, 3], frame=True, ax=axs[1, 0]) + fig.end_subplot() + return fig From aaa2059b0d2b54429a87fa99c93bd752b97bd2c6 Mon Sep 17 00:00:00 2001 From: Wei Ji Date: Thu, 15 Oct 2020 17:07:39 +1300 Subject: [PATCH 06/12] Use position="MC" to put text in the subplot tutorial and silence pylint --- .pylintrc | 1 + examples/tutorials/subplots.py | 2 +- pygmt/base_plotting.py | 1 - pygmt/figure.py | 9 ++++++--- pygmt/pygmtplot.py | 3 +++ 5 files changed, 11 insertions(+), 5 deletions(-) diff --git a/.pylintrc b/.pylintrc index 66b76a52b7c..9e16ab9da39 100644 --- a/.pylintrc +++ b/.pylintrc @@ -441,6 +441,7 @@ function-naming-style=snake_case good-names=i, j, k, + ax, ex, Run, _, diff --git a/examples/tutorials/subplots.py b/examples/tutorials/subplots.py index 0e1c5a218da..05911ff4bfe 100644 --- a/examples/tutorials/subplots.py +++ b/examples/tutorials/subplots.py @@ -46,7 +46,7 @@ j = index % axs.shape[1] # column fig.sca(ax=axs[i, j]) # sets the current Axes fig.text( - x=0.5, y=0.5, text=f"index: {index}, row: {i}, col: {j}", region=[0, 1, 0, 1] + position="MC", text=f"index: {index}, row: {i}, col: {j}", region=[0, 1, 0, 1] ) fig.end_subplot() fig.show() diff --git a/pygmt/base_plotting.py b/pygmt/base_plotting.py index 25b5d4193fc..c5b382a0943 100644 --- a/pygmt/base_plotting.py +++ b/pygmt/base_plotting.py @@ -1056,7 +1056,6 @@ def legend(self, spec=None, position="JTR+jTR+o0.2c", box="+gwhite+p1p", **kwarg lib.call_module("legend", arg_str) @fmt_docstring - @use_alias(R="region", J="projection", B="frame") @use_alias( R="region", J="projection", diff --git a/pygmt/figure.py b/pygmt/figure.py index 5ede3ab9e37..1018eb0681a 100644 --- a/pygmt/figure.py +++ b/pygmt/figure.py @@ -396,10 +396,11 @@ def __init__(self, nrows, ncols, figsize, **kwargs): self._activate_figure() self.begin_subplot(row=nrows, col=ncols, figsize=figsize, **kwargs) + @staticmethod @fmt_docstring @use_alias(Ff="figsize", B="frame") @kwargs_to_strings(Ff="sequence") - def begin_subplot(self, row=None, col=None, **kwargs): + def begin_subplot(row=None, col=None, **kwargs): """ The begin directive of subplot defines the layout of the entire multi-panel illustration. Several options are available to specify @@ -412,9 +413,10 @@ def begin_subplot(self, row=None, col=None, **kwargs): with Session() as lib: lib.call_module(module="subplot", args=arg_str) + @staticmethod @fmt_docstring @use_alias(F="dimensions") - def sca(self, ax=None, **kwargs): + def sca(ax=None, **kwargs): """ Set the current Axes instance to *ax*. @@ -432,9 +434,10 @@ def sca(self, ax=None, **kwargs): with Session() as lib: lib.call_module(module="subplot", args=arg_str) + @staticmethod @fmt_docstring @use_alias(V="verbose") - def end_subplot(self, **kwargs): + def end_subplot(**kwargs): """ This command finalizes the current subplot, including any placement of tags, and updates the gmt.history to reflect the dimensions and diff --git a/pygmt/pygmtplot.py b/pygmt/pygmtplot.py index 5fe8309ebe5..5e523135c41 100644 --- a/pygmt/pygmtplot.py +++ b/pygmt/pygmtplot.py @@ -1,3 +1,6 @@ +""" +High level functions for making subplots. +""" import numpy as np from .figure import SubPlot From 6f9771674c6f452e40e1487bc1cd53101dd409eb Mon Sep 17 00:00:00 2001 From: Wei Ji Date: Thu, 15 Oct 2020 17:25:02 +1300 Subject: [PATCH 07/12] Refactor to place every subplot related method in subplot.py --- pygmt/__init__.py | 4 +- pygmt/figure.py | 78 ----------------------- pygmt/pygmtplot.py | 42 ------------- pygmt/subplot.py | 122 ++++++++++++++++++++++++++++++++++++ pygmt/tests/test_subplot.py | 2 +- 5 files changed, 125 insertions(+), 123 deletions(-) delete mode 100644 pygmt/pygmtplot.py create mode 100644 pygmt/subplot.py diff --git a/pygmt/__init__.py b/pygmt/__init__.py index b07557ee8f6..ef7792824d2 100644 --- a/pygmt/__init__.py +++ b/pygmt/__init__.py @@ -13,13 +13,13 @@ # Import modules to make the high-level GMT Python API from .session_management import begin as _begin, end as _end -from .figure import Figure, SubPlot +from .figure import Figure from .filtering import blockmedian from .gridding import surface from .sampling import grdtrack from .mathops import makecpt from .modules import GMTDataArrayAccessor, config, info, grdinfo, which -from .pygmtplot import subplots +from .subplot import SubPlot, subplots from .gridops import grdcut from .x2sys import x2sys_init, x2sys_cross from . import datasets diff --git a/pygmt/figure.py b/pygmt/figure.py index 1018eb0681a..7f91492aad3 100644 --- a/pygmt/figure.py +++ b/pygmt/figure.py @@ -374,81 +374,3 @@ def _repr_html_(self): base64_png = base64.encodebytes(raw_png) html = '' return html.format(image=base64_png.decode("utf-8"), width=500) - - -class SubPlot(Figure): - """ - Manage modern mode figure subplot configuration and selection. - - The subplot module is used to split the current figure into a - rectangular layout of subplots that each may contain a single - self-contained figure. A subplot setup is started with the begin - directive that defines the layout of the subplots, while positioning to - a particular subplot for plotting is done via the set directive. The - subplot process is completed via the end directive. - - Full option list at :gmt-docs:`subplot.html` - """ - - def __init__(self, nrows, ncols, figsize, **kwargs): - super().__init__() - # Activate main Figure, and initiate subplot - self._activate_figure() - self.begin_subplot(row=nrows, col=ncols, figsize=figsize, **kwargs) - - @staticmethod - @fmt_docstring - @use_alias(Ff="figsize", B="frame") - @kwargs_to_strings(Ff="sequence") - def begin_subplot(row=None, col=None, **kwargs): - """ - The begin directive of subplot defines the layout of the entire - multi-panel illustration. Several options are available to specify - the systematic layout, labeling, dimensions, and more for the - subplots. - - {aliases} - """ - arg_str = " ".join(["begin", f"{row}x{col}", build_arg_string(kwargs)]) - with Session() as lib: - lib.call_module(module="subplot", args=arg_str) - - @staticmethod - @fmt_docstring - @use_alias(F="dimensions") - def sca(ax=None, **kwargs): - """ - Set the current Axes instance to *ax*. - - Before you start plotting you must first select the active subplot. - Note: If any projection (J) option is passed with ? as scale or - width when plotting subplots, then the dimensions of the map are - automatically determined by the subplot size and your region. For - Cartesian plots: If you want the scale to apply equally to both - dimensions then you must specify ``projection="x"`` [The default - ``projection="X"`` will fill the subplot by using unequal scales]. - - {aliases} - """ - arg_str = " ".join(["set", f"{ax}", build_arg_string(kwargs)]) - with Session() as lib: - lib.call_module(module="subplot", args=arg_str) - - @staticmethod - @fmt_docstring - @use_alias(V="verbose") - def end_subplot(**kwargs): - """ - This command finalizes the current subplot, including any placement - of tags, and updates the gmt.history to reflect the dimensions and - linear projection required to draw the entire figure outline. This - allows subsequent commands, such as colorbar, to use - ``position="J"`` to place bars with reference to the complete - figure dimensions. We also reset the current plot location to where - it was prior to the subplot. - - {aliases} - """ - arg_str = " ".join(["end", build_arg_string(kwargs)]) - with Session() as lib: - lib.call_module(module="subplot", args=arg_str) diff --git a/pygmt/pygmtplot.py b/pygmt/pygmtplot.py deleted file mode 100644 index 5e523135c41..00000000000 --- a/pygmt/pygmtplot.py +++ /dev/null @@ -1,42 +0,0 @@ -""" -High level functions for making subplots. -""" -import numpy as np - -from .figure import SubPlot - - -def subplots(nrows=1, ncols=1, figsize=(6.4, 4.8), **kwargs): - """ - Create a figure with a set of subplots. - - Parameters - ---------- - nrows : int - Number of rows of the subplot grid. - - ncols : int - Number of columns of the subplot grid. - - figsize : tuple - Figure dimensions as ``(width, height)``. - - Returns - ------- - fig : :class:`pygmt.Figure` - A PyGMT Figure instance. - - axs : numpy.ndarray - Array of Axes objects. - """ - # Get PyGMT Figure with SubPlot initiated - fig = SubPlot(nrows=nrows, ncols=ncols, figsize=figsize, **kwargs) - - # Setup matplotlib-like Axes - axs = np.empty(shape=(nrows, ncols), dtype=object) - for index in range(nrows * ncols): - i = index // ncols # row - j = index % ncols # column - axs[i, j] = index - - return fig, axs diff --git a/pygmt/subplot.py b/pygmt/subplot.py new file mode 100644 index 00000000000..b2713f12dc5 --- /dev/null +++ b/pygmt/subplot.py @@ -0,0 +1,122 @@ +""" +High level functions for making subplots. +""" +import numpy as np + +from .clib import Session +from .figure import Figure +from .helpers import build_arg_string, fmt_docstring, kwargs_to_strings, use_alias + + +class SubPlot(Figure): + """ + Manage modern mode figure subplot configuration and selection. + + The subplot module is used to split the current figure into a + rectangular layout of subplots that each may contain a single + self-contained figure. A subplot setup is started with the begin + directive that defines the layout of the subplots, while positioning to + a particular subplot for plotting is done via the set directive. The + subplot process is completed via the end directive. + + Full option list at :gmt-docs:`subplot.html` + """ + + def __init__(self, nrows, ncols, figsize, **kwargs): + super().__init__() + # Activate main Figure, and initiate subplot + self._activate_figure() + self.begin_subplot(row=nrows, col=ncols, figsize=figsize, **kwargs) + + @staticmethod + @fmt_docstring + @use_alias(Ff="figsize", B="frame") + @kwargs_to_strings(Ff="sequence") + def begin_subplot(row=None, col=None, **kwargs): + """ + The begin directive of subplot defines the layout of the entire + multi-panel illustration. Several options are available to specify + the systematic layout, labeling, dimensions, and more for the + subplots. + + {aliases} + """ + arg_str = " ".join(["begin", f"{row}x{col}", build_arg_string(kwargs)]) + with Session() as lib: + lib.call_module(module="subplot", args=arg_str) + + @staticmethod + @fmt_docstring + @use_alias(F="dimensions") + def sca(ax=None, **kwargs): + """ + Set the current Axes instance to *ax*. + + Before you start plotting you must first select the active subplot. + Note: If any projection (J) option is passed with ? as scale or + width when plotting subplots, then the dimensions of the map are + automatically determined by the subplot size and your region. For + Cartesian plots: If you want the scale to apply equally to both + dimensions then you must specify ``projection="x"`` [The default + ``projection="X"`` will fill the subplot by using unequal scales]. + + {aliases} + """ + arg_str = " ".join(["set", f"{ax}", build_arg_string(kwargs)]) + with Session() as lib: + lib.call_module(module="subplot", args=arg_str) + + @staticmethod + @fmt_docstring + @use_alias(V="verbose") + def end_subplot(**kwargs): + """ + This command finalizes the current subplot, including any placement + of tags, and updates the gmt.history to reflect the dimensions and + linear projection required to draw the entire figure outline. This + allows subsequent commands, such as colorbar, to use + ``position="J"`` to place bars with reference to the complete + figure dimensions. We also reset the current plot location to where + it was prior to the subplot. + + {aliases} + """ + arg_str = " ".join(["end", build_arg_string(kwargs)]) + with Session() as lib: + lib.call_module(module="subplot", args=arg_str) + + +def subplots(nrows=1, ncols=1, figsize=(6.4, 4.8), **kwargs): + """ + Create a figure with a set of subplots. + + Parameters + ---------- + nrows : int + Number of rows of the subplot grid. + + ncols : int + Number of columns of the subplot grid. + + figsize : tuple + Figure dimensions as ``(width, height)``. + + Returns + ------- + fig : :class:`pygmt.Figure` + A PyGMT Figure instance. + + axs : numpy.ndarray + Array of Axes objects. + """ + # Get PyGMT Figure with SubPlot initiated + fig = SubPlot(nrows=nrows, ncols=ncols, figsize=figsize, **kwargs) + + # Setup matplotlib-like Axes + axs = np.empty(shape=(nrows, ncols), dtype=object) + for index in range(nrows * ncols): + i = index // ncols # row + j = index % ncols # column + axs[i, j] = index + + return fig, axs diff --git a/pygmt/tests/test_subplot.py b/pygmt/tests/test_subplot.py index 383866a124d..a8c3467e502 100644 --- a/pygmt/tests/test_subplot.py +++ b/pygmt/tests/test_subplot.py @@ -3,7 +3,7 @@ """ import pytest -from ..pygmtplot import subplots +from ..subplot import subplots @pytest.mark.mpl_image_compare From 35f814eede790c9efc91236d732e3935a92b7873 Mon Sep 17 00:00:00 2001 From: Wei Ji Date: Thu, 15 Oct 2020 19:58:41 +1300 Subject: [PATCH 08/12] Alias autolabel (A), margins (M) and title (T) --- pygmt/subplot.py | 95 +++++++++++++++++++++++++++++-------- pygmt/tests/test_subplot.py | 25 +++++++++- 2 files changed, 100 insertions(+), 20 deletions(-) diff --git a/pygmt/subplot.py b/pygmt/subplot.py index b2713f12dc5..2a4fc3bb736 100644 --- a/pygmt/subplot.py +++ b/pygmt/subplot.py @@ -1,6 +1,4 @@ -""" -High level functions for making subplots. -""" +"""High level functions for making subplots.""" import numpy as np from .clib import Session @@ -30,14 +28,13 @@ def __init__(self, nrows, ncols, figsize, **kwargs): @staticmethod @fmt_docstring - @use_alias(Ff="figsize", B="frame") - @kwargs_to_strings(Ff="sequence") + @use_alias(Ff="figsize", A="autolabel", B="frame", M="margins", T="title") + @kwargs_to_strings(Ff="sequence", M="sequence") def begin_subplot(row=None, col=None, **kwargs): """ - The begin directive of subplot defines the layout of the entire - multi-panel illustration. Several options are available to specify - the systematic layout, labeling, dimensions, and more for the - subplots. + The begin directive of subplot defines the layout of the entire multi- + panel illustration. Several options are available to specify the + systematic layout, labeling, dimensions, and more for the subplots. {aliases} """ @@ -71,13 +68,12 @@ def sca(ax=None, **kwargs): @use_alias(V="verbose") def end_subplot(**kwargs): """ - This command finalizes the current subplot, including any placement - of tags, and updates the gmt.history to reflect the dimensions and - linear projection required to draw the entire figure outline. This - allows subsequent commands, such as colorbar, to use - ``position="J"`` to place bars with reference to the complete - figure dimensions. We also reset the current plot location to where - it was prior to the subplot. + This command finalizes the current subplot, including any placement of + tags, and updates the gmt.history to reflect the dimensions and linear + projection required to draw the entire figure outline. This allows + subsequent commands, such as colorbar, to use ``position="J"`` to place + bars with reference to the complete figure dimensions. We also reset + the current plot location to where it was prior to the subplot. {aliases} """ @@ -86,10 +82,20 @@ def end_subplot(**kwargs): lib.call_module(module="subplot", args=arg_str) -def subplots(nrows=1, ncols=1, figsize=(6.4, 4.8), **kwargs): +def subplots( + nrows=1, + ncols=1, + figsize=(6.4, 4.8), + autolabel=None, + margins=None, + title=None, + **kwargs, +): """ Create a figure with a set of subplots. + Full option list at :gmt-docs:`subplot.html#synopsis-begin-mode` + Parameters ---------- nrows : int @@ -99,7 +105,50 @@ def subplots(nrows=1, ncols=1, figsize=(6.4, 4.8), **kwargs): Number of columns of the subplot grid. figsize : tuple - Figure dimensions as ``(width, height)``. + Overall figure dimensions as ``(width, height)``. Default is (6.4, 4.8) + + autolabel : bool or str + ``[autolabel][+cdx[/dy]][+gfill][+j|Jrefpoint][+odx[/dy]][+ppen][+r|R] + [+v]``. + Specify automatic tagging of each subplot. Append either a number or + letter [a]. This sets the tag of the first, top-left subplot and others + follow sequentially. Surround the number or letter by parentheses on + any side if these should be typeset as part of the tag. Use + **+j|J**\\ *refpoint* to specify where the tag should be placed in the + subplot [TL]. Note: **+j** sets the justification of the tag to + *refpoint* (suitable for interior tags) while **+J** instead selects + the mirror opposite (suitable for exterior tags). Append + **+c**\\ *dx*[/*dy*] to set the clearance between the tag and a + surrounding text box requested via **+g** or **+p** [3p/3p, i.e., 15% + of the FONT_TAG size dimension]. Append **+g**\\ *fill* to paint the + tag's text box with *fill* [no painting]. Append + **+o**\\ *dx*\\ [/*dy*] to offset the tag's reference point in the + direction implied by the justification [4p/4p, i.e., 20% of the + FONT_TAG size]. Append **+p**\\ *pen* to draw the outline of the tag's + text box using selected *pen* [no outline]. Append **+r** to typeset + your tag numbers using lowercase Roman numerals; use **+R** for + uppercase Roman numerals [Arabic numerals]. Append **+v** to increase + tag numbers vertically down columns [horizontally across rows]. + + margins : tuple + This is margin space that is added between neighboring subplots (i.e., + the interior margins) in addition to the automatic space added for tick + marks, annotations, and labels. The margins can be specified as either: + + - a single value (for same margin on all sides). E.g. '5c'. + - a pair of values (for setting separate horizontal and vertical + margins). E.g. ['5c', '3c']. + - a set of four values (for setting separate left, right, bottom, and + top margins). E.g. ['1c', '2c', '3c', '4c']. + + The actual gap created is always a sum of the margins for the two + opposing sides (e.g., east plus west or south plus north margins) + [Default is half the primary annotation font size, giving the full + annotation font size as the default gap]. + + title : str + Overarching heading for the entire figure. Font is determined by + setting ``FONT_HEADING``. Returns ------- @@ -110,7 +159,15 @@ def subplots(nrows=1, ncols=1, figsize=(6.4, 4.8), **kwargs): Array of Axes objects. """ # Get PyGMT Figure with SubPlot initiated - fig = SubPlot(nrows=nrows, ncols=ncols, figsize=figsize, **kwargs) + fig = SubPlot( + nrows=nrows, + ncols=ncols, + figsize=figsize, + autolabel=autolabel, + margins=margins, + title=f'"{title}"' if title else None, + **kwargs, + ) # Setup matplotlib-like Axes axs = np.empty(shape=(nrows, ncols), dtype=object) diff --git a/pygmt/tests/test_subplot.py b/pygmt/tests/test_subplot.py index a8c3467e502..0e741fc8cbc 100644 --- a/pygmt/tests/test_subplot.py +++ b/pygmt/tests/test_subplot.py @@ -3,7 +3,8 @@ """ import pytest -from ..subplot import subplots +from ..helpers.testing import check_figures_equal +from ..subplot import SubPlot, subplots @pytest.mark.mpl_image_compare @@ -44,3 +45,25 @@ def test_subplot_direct(): fig.basemap(region=[0, 3, 0, 3], frame=True, ax=axs[1, 0]) fig.end_subplot() return fig + + +@check_figures_equal() +def test_subplot_autolabel_margins_title(): + """ + Make subplot figure with autolabels, setting some margins and a title. + """ + kwargs = dict(nrows=2, ncols=1, figsize=("15c", "6c")) + + fig_ref = SubPlot(A="(1)", M="0.3c/0.1c", T='"Subplot Title"', **kwargs) + fig_ref.basemap(region=[0, 1, 2, 3], frame="WSne", c="0,0") + fig_ref.basemap(region=[4, 5, 6, 7], frame="WSne", c="1,0") + fig_ref.end_subplot() + + fig_test, axs_test = subplots( + autolabel="(1)", margins=["0.3c", "0.1c"], title="Subplot Title", **kwargs + ) + fig_test.basemap(region=[0, 1, 2, 3], frame="WSne", ax=axs_test[0, 0]) + fig_test.basemap(region=[4, 5, 6, 7], frame="WSne", ax=axs_test[1, 0]) + fig_test.end_subplot() + + return fig_ref, fig_test From 0214c7cf3e84b4a1d8d4b3d4b046065616457680 Mon Sep 17 00:00:00 2001 From: Wei Ji Date: Thu, 15 Oct 2020 20:28:35 +1300 Subject: [PATCH 09/12] Expand subplot tutorial with subsection on "Making your first subplot" --- examples/tutorials/subplots.py | 48 +++++++++++++++++++++++++++++++--- 1 file changed, 44 insertions(+), 4 deletions(-) diff --git a/examples/tutorials/subplots.py b/examples/tutorials/subplots.py index 05911ff4bfe..587343c7410 100644 --- a/examples/tutorials/subplots.py +++ b/examples/tutorials/subplots.py @@ -27,10 +27,10 @@ # Define subplot layout # --------------------- # -# The ``pygmt.subplots`` command is used to setup the layout, size, and other -# attributes of the figure. It divides the whole canvas into regular grid areas -# with n rows and m columns. Each grid area can contain an individual subplot. -# For example: +# The :meth:`pygmt.subplots` command is used to setup the layout, size, and +# other attributes of the figure. It divides the whole canvas into regular grid +# areas with n rows and m columns. Each grid area can contain an individual +# subplot. For example: fig, axs = pygmt.subplots(nrows=2, ncols=3, figsize=("15c", "6c"), frame="lrtb") @@ -83,3 +83,43 @@ # .. code-block:: default # # fig.end_subplot() + +############################################################################### +# Making your first subplot +# ------------------------- +# Next, let's use what we learned above to make a 2 row by 2 column subplot +# figure. We'll also pick up on some new parameters to configure our subplot. +import pygmt + +fig, axs = pygmt.subplots( + nrows=2, + ncols=2, + figsize=("15c", "6c"), + autolabel=True, + margins=["0.3c", "0.1c"], + title="My Subplot Heading", +) +fig.basemap(region=[0, 10, 0, 10], projection="X?", frame=["af", "WSne"], ax=axs[0, 0]) +fig.basemap(region=[0, 20, 0, 10], projection="X?", frame=["af", "WSne"], ax=axs[0, 1]) +fig.basemap(region=[0, 10, 0, 20], projection="X?", frame=["af", "WSne"], ax=axs[1, 0]) +fig.basemap(region=[0, 20, 0, 20], projection="X?", frame=["af", "WSne"], ax=axs[1, 1]) +fig.end_subplot() +fig.show() + +############################################################################### +# In this example, we define a 2-row, 2-column (2x2) subplot layout using +# :meth:`pygmt.subplots`. The overall figure dimensions is set to be 15cm wide +# and 6cm high (``figsize=["15c", "6c"]``). In addition, we used some optional +# parameters to fine tune some details of the figure creation: +# +# - ``autolabel=True``: Each subplot is automatically labelled abcd +# - ``margins=["0.2c", "0.1c"]``: adjusts the space between adjacent subplots. +# In this case, it is set as 0.2 cm in the X direction and 0.1 cm in the Y +# direction. +# - ``title="My Subplot Heading"``: adds a title on top of the whole figure. +# +# Notice that each subplot was set to use a linear projection ``"X?"``. +# Usually, we need to specify the width and height of the map frame, but it is +# also possible to use a question mark ``"?"`` to let GMT decide automatically +# on what is the most appropriate width/height for the each subplot's map +# frame. From c9be38cba95df89d60dff3f20071910bf4026ce9 Mon Sep 17 00:00:00 2001 From: Wei Ji Date: Thu, 15 Oct 2020 21:49:37 +1300 Subject: [PATCH 10/12] Alias clearance (C) and layout (S) for subplot Also modified `basemap` to allow no frame (B) when ax (c) is used (i.e. with subplot). --- pygmt/base_plotting.py | 2 +- pygmt/subplot.py | 51 ++++++++++++++++++++++++++++++++++++- pygmt/tests/test_subplot.py | 21 +++++++++++++++ 3 files changed, 72 insertions(+), 2 deletions(-) diff --git a/pygmt/base_plotting.py b/pygmt/base_plotting.py index c5b382a0943..3f1720c9209 100644 --- a/pygmt/base_plotting.py +++ b/pygmt/base_plotting.py @@ -878,7 +878,7 @@ def basemap(self, **kwargs): """ kwargs = self._preprocess(**kwargs) - if not ("B" in kwargs or "L" in kwargs or "T" in kwargs): + if not ("B" in kwargs or "L" in kwargs or "T" in kwargs or "c" in kwargs): raise GMTInvalidInput("At least one of B, L, or T must be specified.") with Session() as lib: lib.call_module("basemap", build_arg_string(kwargs)) diff --git a/pygmt/subplot.py b/pygmt/subplot.py index 2a4fc3bb736..7c92232ea5c 100644 --- a/pygmt/subplot.py +++ b/pygmt/subplot.py @@ -28,7 +28,15 @@ def __init__(self, nrows, ncols, figsize, **kwargs): @staticmethod @fmt_docstring - @use_alias(Ff="figsize", A="autolabel", B="frame", M="margins", T="title") + @use_alias( + Ff="figsize", + A="autolabel", + B="frame", + C="clearance", + M="margins", + S="layout", + T="title", + ) @kwargs_to_strings(Ff="sequence", M="sequence") def begin_subplot(row=None, col=None, **kwargs): """ @@ -87,7 +95,9 @@ def subplots( ncols=1, figsize=(6.4, 4.8), autolabel=None, + clearance=None, margins=None, + layout=None, title=None, **kwargs, ): @@ -130,6 +140,20 @@ def subplots( uppercase Roman numerals [Arabic numerals]. Append **+v** to increase tag numbers vertically down columns [horizontally across rows]. + clearance : str + ``[side]clearance``. + Reserve a space of dimension *clearance* between the margin and the + subplot on the specified side, using *side* values from **w**, **e**, + **s**, or **n**, or **x** for both **w** and **e** or **y** for both + **s** and **n**. No *side* means all sides. The option is repeatable + to set aside space on more than one side. Such space will be left + untouched by the main map plotting but can be accessed by modules that + plot scales, bars, text, etc. Settings specified under **begin** + directive apply to all subplots, while settings under **set** only + apply to the selected (active) subplot. **Note**: Common options + **x_offset** and **y_offset* are not available during subplots; use + **clearance** instead. + margins : tuple This is margin space that is added between neighboring subplots (i.e., the interior margins) in addition to the automatic space added for tick @@ -146,6 +170,29 @@ def subplots( [Default is half the primary annotation font size, giving the full annotation font size as the default gap]. + layout : str or list + Set subplot layout for shared axes. May be set separately for rows + (**R**) and columns (**C**). E.g. ``layout=['Rl', 'Cb']``. + Considerations for **C**: Use when all subplots in a **C**\\ olumn + share a common *x*-range. The first (i.e., **t**\\ op) and the last + (i.e., **b**\\ ottom) rows will have *x* annotations; append **t** or + **b** to select only one of those two rows [both]. Append **+l** if + annotated *x*-axes should have a label [none]; optionally append the + label if it is the same for the entire subplot. Append **+t** to make + space for subplot titles for each row; use **+tc** for top row titles + only [no subplot titles]. Labels and titles that depends on which row + or column are specified as usual via a subplot's own **frame** setting. + Considerations for **R**: Use when all subplots in a **R**\\ ow share a + common *y*-range. The first (i.e., **l**\\ eft) and the last (i.e., + **r**\\ ight) columns will have *y*-annotations; append **l** or **r** + to select only one of those two columns [both]. Append **+l** if + annotated *y*-axes will have a label [none]; optionally, append the + label if it is the same for the entire subplot. Append **+p** to make + all annotations axis-parallel [horizontal]; if not used you may have to + set **clearance** to secure extra space for long horizontal + annotations. Append **+w** to draw horizontal and vertical lines + between interior panels using selected pen [no lines]. + title : str Overarching heading for the entire figure. Font is determined by setting ``FONT_HEADING``. @@ -164,7 +211,9 @@ def subplots( ncols=ncols, figsize=figsize, autolabel=autolabel, + clearance=clearance, margins=margins, + layout=layout, title=f'"{title}"' if title else None, **kwargs, ) diff --git a/pygmt/tests/test_subplot.py b/pygmt/tests/test_subplot.py index 0e741fc8cbc..50ee378d99f 100644 --- a/pygmt/tests/test_subplot.py +++ b/pygmt/tests/test_subplot.py @@ -67,3 +67,24 @@ def test_subplot_autolabel_margins_title(): fig_test.end_subplot() return fig_ref, fig_test + + +@check_figures_equal() +def test_subplot_clearance_and_shared_xy_axis_layout(): + """ + Ensure subplot clearance works, and that the layout can be set to use + shared X and Y axis labels across columns and rows. + """ + kwargs = dict(nrows=2, ncols=2, frame="WSrt", figsize=("5c", "5c")) + + fig_ref = SubPlot(C="y0.2", SR="l", SC="t", **kwargs) + fig_test, _ = subplots(clearance="y0.2", layout=["Rl", "Ct"], **kwargs) + + for fig in (fig_ref, fig_test): + fig.basemap(region=[0, 4, 0, 4], projection="X?", ax=True) + fig.basemap(region=[0, 8, 0, 4], projection="X?", ax=True) + fig.basemap(region=[0, 4, 0, 8], projection="X?", ax=True) + fig.basemap(region=[0, 8, 0, 8], projection="X?", ax=True) + fig.end_subplot() + + return fig_ref, fig_test From be964b303d1e04d23f937caa3d063cded1b51d5c Mon Sep 17 00:00:00 2001 From: Wei Ji Date: Thu, 15 Oct 2020 22:16:57 +1300 Subject: [PATCH 11/12] Expand subplot tutorial with subsection on "Shared X and Y axis labels" Also added in the `ax=True` tip for auto selection of next subplot, and fixed some typos in previous commit. --- examples/tutorials/subplots.py | 56 +++++++++++++++++++++++++++++++--- 1 file changed, 52 insertions(+), 4 deletions(-) diff --git a/examples/tutorials/subplots.py b/examples/tutorials/subplots.py index 587343c7410..35c818625e5 100644 --- a/examples/tutorials/subplots.py +++ b/examples/tutorials/subplots.py @@ -89,14 +89,13 @@ # ------------------------- # Next, let's use what we learned above to make a 2 row by 2 column subplot # figure. We'll also pick up on some new parameters to configure our subplot. -import pygmt fig, axs = pygmt.subplots( nrows=2, ncols=2, figsize=("15c", "6c"), autolabel=True, - margins=["0.3c", "0.1c"], + margins=["0.1c", "0.2c"], title="My Subplot Heading", ) fig.basemap(region=[0, 10, 0, 10], projection="X?", frame=["af", "WSne"], ax=axs[0, 0]) @@ -113,8 +112,8 @@ # parameters to fine tune some details of the figure creation: # # - ``autolabel=True``: Each subplot is automatically labelled abcd -# - ``margins=["0.2c", "0.1c"]``: adjusts the space between adjacent subplots. -# In this case, it is set as 0.2 cm in the X direction and 0.1 cm in the Y +# - ``margins=["0.1c", "0.2c"]``: adjusts the space between adjacent subplots. +# In this case, it is set as 0.1 cm in the X direction and 0.2 cm in the Y # direction. # - ``title="My Subplot Heading"``: adds a title on top of the whole figure. # @@ -123,3 +122,52 @@ # also possible to use a question mark ``"?"`` to let GMT decide automatically # on what is the most appropriate width/height for the each subplot's map # frame. + +############################################################################### +# .. tip:: +# +# In the above example, we used the following commands to activate the +# four subplots explicitly one after another:: +# +# fig.basemap(..., ax=axs[0, 0]) +# fig.basemap(..., ax=axs[0, 1]) +# fig.basemap(..., ax=axs[1, 0]) +# fig.basemap(..., ax=axs[1, 1]) +# +# In fact, we can just use ``fig.basemap(..., ax=True)`` without specifying +# any subplot index number, and GMT will automatically activate the next +# subplot. + +############################################################################### +# Shared X and Y axis labels +# -------------------------- +# In the example above with the four subplots, the two subplots for each row +# have the same Y-axis range, and the two subplots for each column have the +# same X-axis range. You can use the **layout** option to set a common X and/or +# Y axis between subplots. + +fig, axs = pygmt.subplots( + nrows=2, + ncols=2, + figsize=("15c", "6c"), + autolabel=True, + margins=["0.3c", "0.2c"], + title="My Subplot Heading", + layout=["Rl", "Cb"], + frame="WSrt", +) +fig.basemap(region=[0, 10, 0, 10], projection="X?", ax=True) +fig.basemap(region=[0, 20, 0, 10], projection="X?", ax=True) +fig.basemap(region=[0, 10, 0, 20], projection="X?", ax=True) +fig.basemap(region=[0, 20, 0, 20], projection="X?", ax=True) +fig.end_subplot() +fig.show() + +############################################################################### +# **Rl** indicates that subplots within a **R**\ ow will share the y-axis, and +# only the **l**\ eft axis is displayed. **Cb** indicates that subplots in +# a column will share the x-axis, and only the **b**\ ottom axis is displayed. +# +# Of course, instead of using the **layout** option, you can also set a +# different **frame** for each subplot to control the axis properties +# individually for each subplot. From 1e2fa38d15805b8f85a131d8ed025cf4a2060015 Mon Sep 17 00:00:00 2001 From: Wei Ji Date: Thu, 15 Oct 2020 22:53:30 +1300 Subject: [PATCH 12/12] Expand subplot tutorial with subsection on "Advanced subplot layouts" --- examples/tutorials/subplots.py | 51 ++++++++++++++++++++++++++++++++++ 1 file changed, 51 insertions(+) diff --git a/examples/tutorials/subplots.py b/examples/tutorials/subplots.py index 35c818625e5..a6fcef21abf 100644 --- a/examples/tutorials/subplots.py +++ b/examples/tutorials/subplots.py @@ -171,3 +171,54 @@ # Of course, instead of using the **layout** option, you can also set a # different **frame** for each subplot to control the axis properties # individually for each subplot. + +############################################################################### +# Advanced subplot layouts +# ------------------------ +# +# Nested subplot are currently not supported. If you want to create more +# complex subplot layouts, some manual adjustments are needed. +# +# The following example draws three subplots in a 2-row, 2-column layout, with +# the first subplot occupying the first row. + +fig, axs = pygmt.subplots(nrows=2, ncols=2, figsize=("15c", "6c"), autolabel=True) +fig.basemap( + region=[0, 10, 0, 10], projection="X15c/3c", frame=["af", "WSne"], ax=axs[0, 0] +) +fig.text(text="TEXT", x=5, y=5, projection="X15c/3c") +fig.basemap(region=[0, 5, 0, 5], projection="X?", frame=["af", "WSne"], ax=axs[1, 0]) +fig.basemap(region=[0, 5, 0, 5], projection="X?", frame=["af", "WSne"], ax=axs[1, 1]) +fig.end_subplot() +fig.show() + +############################################################################### +# +# When drawing the three basemaps, the last two basemaps use +# ``projection="X?"``, so GMT will automatically determine the size of the +# subplot according to the size of the subplot area. In order for the first +# subplot to fill up the entire top row space, we use manually adjusted the +# subplot width to 15cm using ``projection="X15c/3c"``. + +############################################################################### +# .. note:: +# +# There are bugs that have not been fixed in the above example. +# +# In subplot mode, the size of each subgraph is controlled by the +# ``figsize`` option of :meth:`pygmt.subplots`. Users can override this and +# use``projection`` to specify the size of an individual subplot, but this +# size will not be remembered. If the next command does not specify +# ``projection``, the default size of the subplot mode will be used, and +# the resulting plot will be inccorect. +# +# The current workaround is to use the same ``projection`` option in all +# commands for the subplot. For example, we forced subplot (a) to have a +# different size using ``projection="15c/3c``. The next command within the +# subplot (e.g. ``text``) must also use ``projection="x15c/3c"``, otherwise +# the placement will be wrong. + +############################################################################### +# Since we skipped the second subplot, the auto label function will name the +# three subplots as a, c and d, which is not what we want, so we have to use +# ``fig.sca(A=""(a)"`` to manually set the subplot label.