;+ ; Name: ; multiplot ; ; Purpose: ; Create multiple plots with simple control over the gaps between plots. ; By default, the gap is zero but this can be set with the ; gap= keyword, or xgap=, ygap= for individual control over different axes. ; You can also place a single title along the x, y and top axes of the ; matrix of plots using the mtitle, mxtitle and mytitle keywords. ; ; It is good for data with one or two shared axes and retains all the ; versatility of the plot commands (e.g. all keywords and log scaling). ; The plots are connected with the shared axes, which saves space by ; omitting redundant ticklabels and titles. Multiplot does this by ; setting !p.position, !x.tickname and !y.tickname automatically. ; A call (multiplot,/reset) restores original values. ; ; CALLING SEQUENCE: ; multiplot, pmulti, ; gap=, xgap=, ygap=, ; /square, ; /doxaxis, /doyaxis, ; mTitle=, mTitSize=, mTitOffset=, ; mxTitle=, mxTitSize=, mxTitOffset=, ; myTitle=, myTitSize=, myTitOffset=, ; xtickformat=, ytickformat= ; /default, /reset, /rowmajor, /initialize ; ; INPUTS: ; pmulti: Optional input. [Nx,Ny] array describing the shape of the ; matrix of plots. This is equivalent to the 2nd and 3rd elements ; of !p.multi. Or you can send all 5 elements of the !p.multi. ; ; KEYWORD INPUTS: ; gap=: Set the gap between plots in normalized units. Default is 0. ; This input overrides the xgap and ygap inputs. ; xgap=: Gap between plots in the x direction. Default 0. To set both ; x and y gap to the same value just use the gap keyword. ; ygap=: Gap between plots in the y direction. Default 0. To set both ; x and y gap to the same value just use the gap keyword. ; ; mTitle: A single title to go across the top of the matrix of plots, ; as opposed to the plot over single plots you generate with the ; plot command for example. ; mTitSize: The font size of the top title. Default is 1.25*!p.charsize ; mTitOffset: Offset of the title in the y-direction. ; mxTitle, mxTitSize, mxTitOffset: same as above but for the x-axis title ; myTitle, myTitSize, myTitOffset: same as above but for the y-axis title ; ; xtickformat, ytickformat: Set the default tick formats when the ticks ; are plotted. This allows the user to avoid sending this to each ; plotting command which can have unexpected results if that axis ; was not to get tick labels in a given point in the matrix. ; ; KEYWORDS SWITCHES: ; /square: Force the axis ratio of each plot to be square. Note if ; xgap and ygap are set to different values, this axis ratio will ; not be preserved. It will be preserved if gap= is used. ; ; /doxaxis: Put axis labels, etc on the axis. Default is to place labels ; only on the left side and bottom sides of the plot matrix, but may ; be useful when some cells are empty; for example the x-axis of ; a 2x2 grid when only 3 total plots will be created. ; /doyaxis: Put axis labels, etc on the yxis. Default is to place labels ; only on the left side and bottom sides of the plot matrix, but may ; be useful when some cells are empty; for example the x-axis of ; a 2x2 grid when only 3 total plots will be created. ; ; /rowmajor: Like setting 5th element of !p.multi to 1. ; /reset: Set plotting parameters to their saved values from before ; multiplot was initially called. ; /default: Set plotting parameters to IDL defaults. This is useful ; when the saved parameters get in a whacky state. ; /initialize: Just do the initialization. This is what happends when ; you first call multiplot anyway. ; ; EXAMPLES: ; ; Make an array of plots [4,3] with a gap of 0.1 (in norm. coords.) ; ; and overall titles along the x and y axes as given. Force the ; ; plots to be square. ; ; erase & multiplot, [4,3], /square, gap=0.1, mXtitle='R', mYtitle='F(R)' ; for i=0,4*3-1 do begin ; plot, struct[i].x, struct[i].y, psym=4 ; multiplot ; endfor ; multiplot,/reset ; ; Side Effects: ; Multiplot sets a number of system variables: !p.position, !p.multi, ; !x.tickname, !y.tickname, !P.noerase---but all can be reset with ; the call: multiplot,/reset ; ; Things can get out of wack if your program crashes in the middle of ; making a matrix of plots, and often /reset will not fix it. In those ; cases, calling multiplot,/default will often fix the problem. ; ; Restrictions: ; 1. If you use !p.multi as the method of telling how many plots ; are present, you have to set !p.multi at the beginning each time you ; use multiplot or call multiplot with the /reset keyword. ; 2. There is no way to make plots of different sizes; each plot ; covers the same area on the screen or paper. ; ; Modification history: ; write, 21-23 Mar 94, Fred Knight (knight@ll.mit.edu) ; alter plot command that sets !x.window, etc. per suggestion of ; Mark Hadfield (hadfield@storm.greta.cri.nz), 7 Apr 94, FKK ; add a /default keyword restore IDL's default values of system vars, ; 7 Apr 94, FKK ; modify two more sys vars !x(y).tickformat to suppress user-formatted ; ticknames, per suggestion of Mark Hadfield (qv), 8 Apr 94, FKK ; Converted to IDL V5.0 W. Landsman September 1997 ; ; 2001-03-20 Added /square keyword ; Work in device coordinates so we can force aspect ratio to be square ; if requested. Erin Scott Sheldon UMichigan ; ; 2007-06-18 ; Can now place titles on the overall x and y axes, as well as a ; top title using these new keywords. ; mTitle=, mTitSize=, mTitOffset=, ; mxTitle=, mxTitSize=, mxTitOffset=, ; myTitle=, myTitSize=, myTitOffset=, ; Can also control overall tick formats. Useful because can just call ; multiplot initially and set this, while calling on each call to ; the plotting program will have unexpected results if the ticks ; are not to be labelled for that place in the matrix. ; xtickformat, ytickformat ; Erin Sheldon, NYU ; 2007-08-28: ; Can now add gaps between the plots with these keywords: ; gap=, xgap=, ygap= ; where the values are in normalized coordinates. Erin Sheldon, NYU ; ; ;- PRO multiplot, pmulti, help=help, $ initialize=initialize, reset=reset, default=default, $ rowmajor=rowmajor,verbose=verbose, square=square, $ gap=gap_in, xgap=xgap_in, ygap=ygap_in, $ doxaxis=doxaxis, doyaxis=doyaxis, $ $ xtickformat=xtickformat_in, ytickformat=ytickformat_in, $ mtitle=mtitle, mTitSize=mTitSize, mTitOffset=mTitOffset, $ mxTitle=mxTitle, mxTitSize=mxTitSize, mxTitOffset=mxTitOffset, $ myTitle=myTitle, myTitSize=myTitSize, myTitOffset=myTitOffset common multiplot $ ,nplots $ ; [# of plots along x, # of plots along y] ,nleft $ ; # of plots remaining---like the first element of !p.multi ,pdotmulti $ ; saved value of !p.multi ,margins $ ; calculated margins based on !p.multi or pmulti ,pposition $ ; saved value of !p.position ,colmajor $ ; flag for column major order ,noerase $ ; saved value of !p.noerase ,sqplot $ ; should be make it square? ,xtickname $ ; Original value ,ytickname $ ; Original value ,xtickformat_orig $ ; Original value ,ytickformat_orig $ ,xtickformat $ ; Value we will use ,ytickformat $ ,gap $ ,xgap $ ,ygap ; help message if keyword_set(help) then begin doc_library,'multiplot' return endif ; restore idl's default values (kill multiplot's influence) if keyword_set(default) then begin !p.position = 0 !x.tickname = '' !y.tickname = '' !x.tickformat = '' !y.tickformat = '' !p.multi = 0 !p.noerase = 0 nleft = 0 nplots = [1,1] pdotmulti = !p.multi margins = 0 sqplot=0 pposition = !p.position noerase = !p.noerase xtickname = !x.tickname ytickname = !y.tickname xtickformat = !x.tickformat ytickformat = !y.tickformat gap=0.0 xgap=0.0 ygap=0.0 if keyword_set(verbose) then begin message,/inform,$ 'Restore IDL''s defaults for affected system variables.' message,/inform,$ 'Reset multiplot''s common to IDL''s defaults.' endif return endif ; restore saved system variables if keyword_set(reset) then begin if n_elements(pposition) gt 0 then begin !p.position = pposition !x.tickname = xtickname !y.tickname = ytickname !x.tickformat = xtickformat_orig !y.tickformat = ytickformat_orig !p.multi = pdotmulti !p.noerase = noerase sqplot=0 endif nleft = 0 if keyword_set(verbose) then begin coords = '['+string(!p.position,form='(3(f4.2,","),f4.2)')+']' multi = '['+string(!p.multi,form='(4(i2,","),i2)')+']' message,/inform,'Reset. !p.position='+coords+', !p.multi='+multi endif gap=0.0 xgap=0.0 ygap=0.0 return endif ; ; Now the user inputs ; ; How big are the gaps between the plots? if n_elements(gap) eq 0 then begin ; initial set up of common block values xgap=0.0 ygap=0.0 gap=0.0 endif if n_elements(xgap_in) ne 0 then xgap=xgap_in if n_elements(ygap_in) ne 0 then ygap=ygap_in ; gap will override any previously set values if n_elements(gap_in) ne 0 then begin gap=gap_in xgap=gap ygap=gap endif ; ; Set up the plot layout ; ; Shall we force the individual plots to be square? if keyword_set(square) then sqplot=1 else begin if n_elements(sqplot) eq 0 then sqplot=0 endelse ; number of plots left in the grid if n_elements(nleft) eq 1 then init = (nleft eq 0) else init = 1 if (n_elements(pmulti) eq 2) or (n_elements(pmulti) eq 5) then init = 1 if (n_elements(!p.multi) eq 5) then begin if (!p.multi[1] gt 0) and (!p.multi[2] gt 0) then begin init = (!p.multi[0] eq 0) endif endif ; initialize if we are on the first plot if init or keyword_set(initialize) then begin case n_elements(pmulti) of 0:begin if n_elements(!p.multi) eq 1 then return ; NOTHING TO SET if n_elements(!p.multi) ne 5 then begin message,'Bogus !p.multi; aborting.' endif nplots = !p.multi[1:2] > 1 if keyword_set(rowmajor) then begin colmajor = 0 endif else begin colmajor = !p.multi[4] eq 0 endelse end 2:begin nplots = pmulti colmajor = not keyword_set(rowmajor) end 5:begin nplots = pmulti[1:2] if keyword_set(rowmajor) then begin colmajor = 0 endif else begin colmajor = pmulti[4] eq 0 endelse end else: message,'pmulti can only have 0, 2, or 5 elements.' endcase pposition = !p.position ; save sysvar to be altered xtickname = !x.tickname ytickname = !y.tickname ; keep original values for resetting xtickformat_orig = !x.tickformat ytickformat_orig = !y.tickformat ; what will we actually plot when ticks are exposed? if n_elements(xtickformat_in) ne 0 then begin xtickformat=xtickformat_in endif else begin xtickformat=xtickformat_orig endelse if n_elements(ytickformat_in) ne 0 then begin ytickformat=ytickformat_in endif else begin ytickformat=ytickformat_orig endelse pdotmulti = !p.multi nleft = nplots[0]*nplots[1] ; total # of plots !p.position = 0 ; reset !p.multi = 0 ; set window & region plot,/nodata,xstyle=4,ystyle=4,!x.range,!y.range,/noerase px = !x.window*!d.x_vsize py = !y.window*!d.y_vsize xsize = px[1] - px[0] ysize = py[1] - py[0] ; in normlized coordinates nmargins = [min(!x.window)-min(!x.region), $ min(!y.window)-min(!y.region), $ max(!x.region)-max(!x.window), $ max(!y.region)-max(!y.window)] ;in device coord margins = nmargins margins[0] = nmargins[0]*!d.x_vsize margins[2] = nmargins[2]*!d.x_vsize margins[1] = nmargins[1]*!d.y_vsize margins[3] = nmargins[3]*!d.y_vsize noerase = !p.noerase !p.noerase = 1 ; !p.multi does the same if keyword_set(verbose) then begin major = ['across then down (column major).',$ 'down then across (row major).'] if colmajor then index = 0 else index = 1 message,/inform,'Initialized for '+strtrim(nplots[0],2) $ +'x'+strtrim(nplots[1],2)+', plotted '+major[index] endif if keyword_set(initialize) then return endif ; ; Define the plot region without using !p.multi. ; cols = nplots[0] ; for convenience rows = nplots[1] nleft = nleft - 1 ; decrement plots remaining cur = cols*rows - nleft ; current plot #: 1 to cols*rows ; device coords per plot idx = [(!d.x_vsize-margins[0]-margins[2])/cols, $ (!d.y_vsize-margins[1]-margins[3])/rows] ;; force to be square if requested if sqplot then begin if idx[0] lt idx[1] then idx[1]=idx[0] else idx[0]=idx[1] endif if colmajor then begin ; location in matrix of plots col = cur mod cols if col eq 0 then col = cols row = (cur-1)/cols + 1 endif else begin ; here (1,2) is 1st col, 2nd row row = cur mod rows if row eq 0 then row = rows col = (cur-1)/rows + 1 endelse pos = $ [(col-1)*idx[0], (rows-row)*idx[1], $ col*idx[0], (rows-row+1)*idx[1]] $ + $ [margins[0], margins[1], $ margins[0], margins[1]] ; back to normalized coords pos[0] = pos[0]/!d.x_vsize pos[2] = pos[2]/!d.x_vsize pos[1] = pos[1]/!d.y_vsize pos[3] = pos[3]/!d.y_vsize ; add gaps pos[0] = pos[0] + xgap pos[2] = pos[2] - xgap pos[1] = pos[1] + ygap pos[3] = pos[3] - ygap ; ; Finally set the system variables; user shouldn't change them. ; !p.position = pos onbottom = (row eq rows) or (rows eq 1) onleft = (col eq 1) or (cols eq 1) IF keyword_set(doxaxis) THEN onbottom=1 IF keyword_set(doyaxis) THEN onleft=1 if onbottom then begin !x.tickname = xtickname endif else begin !x.tickname = replicate(' ',30) endelse if onleft then !y.tickname = ytickname else !y.tickname = replicate(' ',30) if onbottom then !x.tickformat = xtickformat else !x.tickformat = '' if onleft then !y.tickformat = ytickformat else !y.tickformat = '' if keyword_set(verbose) then begin coords = '['+string(pos,form='(3(f4.2,","),f4.2)')+']' plotno = 'Setup for plot ['+strtrim(col,2)+','+strtrim(row,2)+'] of ' $ +strtrim(cols,2)+'x'+strtrim(rows,2) message,/inform,plotno+' at '+coords endif ; Add titles to overall axes ; area covered by entire plot field in device coords allpos = $ [0, 0, cols*idx[0], rows*idx[1]] + $ [margins[0], margins[1], margins[0], margins[1]] ;; back to normalized coords allpos[0] = allpos[0]/!d.x_vsize allpos[2] = allpos[2]/!d.x_vsize allpos[1] = allpos[1]/!d.y_vsize allpos[3] = allpos[3]/!d.y_vsize xCharSizeNorm = float(!d.x_ch_size) / float(!d.x_size) yCharSizeNorm = float(!d.y_ch_size) / float(!d.y_size) ; top title if n_elements(mTitle) ne 0 then begin if n_elements(mTitSize) eq 0 then mTitSize = 1.0 if n_elements(mTitOffset) eq 0 then mTitOffset = 0.0 ; align middle of region in x xpos = (allpos[2] - allpos[0])/2.0 + nmargins[0] ; align relative to the top. Default is right there plus ; one character size. ypos = allpos[3] + (mTitOffset+1.0)*yCharSizeNorm ; correct for gaps ypos = ypos - ygap xyouts, $ xpos, $ ypos, $ mTitle, $ /normal, $ align = 0.5, $ charsize = 1.25 * mTitSize endif ; x title if n_elements(mxTitle) ne 0 then begin if n_elements(mxTitSize) eq 0 then mxTitSize = 1.0 if n_elements(mxTitOffset) eq 0 then mxTitOffset = 0.0 ; align middle of region in x xpos = (allpos[2] - allpos[0])/2.0 + nmargins[0] ; align middle of region in x ypos = allpos[1] - (mxTitOffset+3.0)*yCharSizeNorm ; correct for gaps ypos = ypos + ygap xyouts, $ xpos, $ ypos, $ mxTitle, $ /normal, $ align = 0.5, $ charsize = mxTitSize endif ; y title if n_elements(myTitle) ne 0 then begin if n_elements(myTitSize) eq 0 then myTitSize = 1.0 if n_elements(myTitOffset) eq 0 then myTitOffset = 0.0 ; align relative to the left side. Default is right there plus ; one character size. xpos = allpos[0] - (myTitOffset+6.0)*xCharSizeNorm ;xpos = allpos[0] - (myTitOffset+4.0)*xCharSizeNorm ; align middle of region in x ypos = (allpos[3] - allpos[1])/2.0 + nmargins[1] ; correct for gaps xpos = xpos + xgap xyouts, $ xpos, $ ypos, $ myTitle, $ /normal, $ align = 0.5, $ orientation = 90.0, $ charsize = myTitSize endif return end