階:從subplot到Position屬性與tiledlayout的精確控制)
1. 項(xiàng)目概述為什么需要精細(xì)控制子圖在Matlab里畫圖subplot幾乎是每個(gè)使用者最早接觸的命令之一。一句subplot(2,2,1)就能輕松把畫布分成四宮格然后在第一個(gè)格子里畫圖這確實(shí)方便。但用久了你會(huì)發(fā)現(xiàn)它帶來的“方便”背后是深深的“不自由”。默認(rèn)的subplot布局是均分畫布每個(gè)子圖之間的間距固定圖與圖之間、圖與畫布邊緣之間都留著一圈空白。當(dāng)你需要并排對比兩組數(shù)據(jù)或者想把一個(gè)主圖配上幾個(gè)小的局部放大圖俗稱“插圖”或“Inset”時(shí)這種僵化的布局就完全不夠用了。你可能會(huì)覺得圖太小看不清或者插圖的位置怎么也對不齊非常別扭。這就是我們今天要徹底解決的問題如何像設(shè)計(jì)師一樣在Matlab中自由、精確地控制每一個(gè)子圖的位置和大小。這不僅僅是讓圖“好看”一點(diǎn)更是為了高效、清晰地傳達(dá)數(shù)據(jù)信息。無論是撰寫學(xué)術(shù)論文、制作項(xiàng)目報(bào)告還是進(jìn)行復(fù)雜的數(shù)據(jù)可視化分析對圖形元素的精確排版都是專業(yè)性的體現(xiàn)。通過手動(dòng)設(shè)置Position屬性你將完全掌控圖形布局實(shí)現(xiàn)諸如并排多圖無空白、復(fù)雜嵌套子圖、自定義插圖等高級(jí)效果。接下來我將從底層原理到實(shí)戰(zhàn)技巧帶你掌握這套核心技能。2. 核心原理理解Matlab的圖形對象與Position屬性要自由控制首先得知道控制的是什么。Matlab的圖形系統(tǒng)是面向?qū)ο蟮睦斫膺@幾個(gè)核心對象是關(guān)鍵。2.1 圖形對象層級(jí)當(dāng)你創(chuàng)建一個(gè)圖形時(shí)主要涉及三層對象圖形窗口Figure最頂層的容器就是你打開的那個(gè)窗口。它的Position屬性決定了這個(gè)窗口在屏幕上的位置和大小格式為[left, bottom, width, height]單位默認(rèn)是像素pixels但可以更改。坐標(biāo)軸Axes這是我們通常說的“子圖”或“繪圖區(qū)域”。它才是真正承載曲線、散點(diǎn)、柱狀圖等數(shù)據(jù)可視化的容器。一個(gè)Figure里可以包含多個(gè)Axes對象。我們所說的“調(diào)整子圖位置和大小”本質(zhì)上就是設(shè)置這些Axes對象的Position屬性。圖形對象Line, Text, Patch等這些是繪制在坐標(biāo)軸Axes內(nèi)的具體元素比如一條曲線、一個(gè)標(biāo)簽、一塊色塊。它們的位置通常相對于其父坐標(biāo)軸Axes來定義。2.2 Position屬性的含義與單位Position屬性是一個(gè)四元向量[left, bottom, width, height]。left子圖左下角相對于其父容器對于Axes來說父容器就是Figure左下角的水平距離。bottom子圖左下角相對于其父容器左下角的垂直距離。width子圖的寬度。height子圖的高度。關(guān)鍵在于理解“歸一化坐標(biāo)”Normalized。默認(rèn)情況下subplot創(chuàng)建的坐標(biāo)軸其Position使用的是“歸一化”單位。這意味著left,bottom,width,height的取值范圍都是[0, 1]。[0, 0]代表父容器的左下角[1, 1]代表右上角。使用歸一化單位的好處是與圖形窗口的絕對像素尺寸無關(guān)無論你如何縮放窗口子圖的相對位置和比例關(guān)系保持不變。例如一個(gè)Position為[0.1, 0.1, 0.8, 0.8]的坐標(biāo)軸意味著它在圖形窗口中居中四周各留了10%的邊距。2.3 subplot的局限性subplot(m, n, p)的工作原理可以理解為它先在心里把Figure劃分成一個(gè)m行n列的網(wǎng)格然后計(jì)算第p個(gè)格子對應(yīng)的歸一化位置和大小并創(chuàng)建一個(gè)使用該P(yáng)osition的坐標(biāo)軸。它為了確保格子之間不重疊會(huì)引入固定的間距SubplotGap并且會(huì)“擠占”或“對齊”已有的坐標(biāo)軸。當(dāng)你嘗試在同一個(gè)位置創(chuàng)建第二個(gè)坐標(biāo)軸時(shí)subplot默認(rèn)會(huì)刪除已存在的那個(gè)這就是為什么直接用subplot難以實(shí)現(xiàn)重疊或靈活布局的原因。注意subplot的Position計(jì)算包含了它自身的邊距邏輯這個(gè)邊距SubplotGap是內(nèi)部設(shè)定的不直接對應(yīng)歸一化坐標(biāo)中的空白。因此通過subplot創(chuàng)建后再用set(gca, ‘Position’, …)強(qiáng)行修改有時(shí)會(huì)與subplot的管理邏輯沖突導(dǎo)致意想不到的行為。更穩(wěn)健的做法是直接創(chuàng)建坐標(biāo)軸axes并指定位置。3. 實(shí)戰(zhàn)技巧四種方法精細(xì)控制子圖布局理解了原理我們來看具體怎么做。從最常用到最靈活我為你梳理了四種方法。3.1 方法一subplot創(chuàng)建后手動(dòng)調(diào)整Position這是最直接的入門方法。先用subplot快速搭建一個(gè)大致布局然后獲取每個(gè)坐標(biāo)軸的句柄再精細(xì)調(diào)整其Position。figure(‘Position‘, [100, 100, 1000, 500]); % 創(chuàng)建一個(gè)指定位置和大小的圖形窗口 % 創(chuàng)建2行2列的布局并激活第1個(gè)子圖 subplot(2,2,1); plot(randn(10,1)); title(‘子圖1 (默認(rèn)位置)‘); ax1 gca; % 獲取當(dāng)前坐標(biāo)軸句柄 % 創(chuàng)建并獲取其他子圖句柄 subplot(2,2,2); plot(randn(10,1)); title(‘子圖2‘); ax2 gca; subplot(2,2,3); plot(randn(10,1)); title(‘子圖3‘); ax3 gca; subplot(2,2,4); plot(randn(10,1)); title(‘子圖4‘); ax4 gca; % 現(xiàn)在開始手動(dòng)調(diào)整 % 讓左上角(ax1)的圖更寬 set(ax1, ‘Position‘, [0.05, 0.55, 0.5, 0.4]); % [左 下 寬 高] % 讓右上角(ax2)的圖變窄并靠右 set(ax2, ‘Position‘, [0.6, 0.55, 0.35, 0.4]); % 合并下方兩個(gè)子圖為一個(gè)寬圖 set(ax3, ‘Position‘, [0.05, 0.05, 0.9, 0.4]); % 隱藏或刪除第四個(gè)坐標(biāo)軸因?yàn)槲覀儼阉喜⒘?delete(ax4);實(shí)操心得使用gcaGet Current Axes或gcfGet Current Figure可以方便地獲取句柄。更規(guī)范的做法是在創(chuàng)建時(shí)保存句柄ax1 subplot(2,2,1);。調(diào)整一個(gè)坐標(biāo)軸的Position后其他坐標(biāo)軸不會(huì)自動(dòng)避讓可能會(huì)造成重疊。你需要通盤考慮所有坐標(biāo)軸的位置手動(dòng)計(jì)算。這種方法適合對已有subplot布局進(jìn)行微調(diào)。但對于復(fù)雜的新布局從零開始計(jì)算Position更清晰。3.2 方法二使用axes命令直接創(chuàng)建定位坐標(biāo)軸放棄subplot直接使用axes命令是獲得最大靈活性的方式。你可以像“擺放積木”一樣在畫布的任何位置創(chuàng)建任意大小的坐標(biāo)軸。figure(‘Position‘, [100, 100, 1200, 600]); % 創(chuàng)建主圖占據(jù)左側(cè)大部分區(qū)域 ax_main axes(‘Position‘, [0.1, 0.15, 0.6, 0.75]); % 左10%下15%寬60%高75% x linspace(0, 10, 100); y_main sin(x) 0.1*randn(size(x)); plot(ax_main, x, y_main, ‘b-‘, ‘LineWidth‘, 1.5); xlabel(ax_main, ‘時(shí)間 (s)‘); ylabel(ax_main, ‘幅值‘); title(ax_main, ‘主信號(hào)波形‘); grid(ax_main, ‘on‘); % 在右上角創(chuàng)建一個(gè)局部放大圖插圖 ax_inset axes(‘Position‘, [0.72, 0.65, 0.25, 0.25]); % 在右上角開辟一小塊區(qū)域 idx_zoom x 4 x 6; % 放大4到6秒的區(qū)域 plot(ax_inset, x(idx_zoom), y_main(idx_zoom), ‘r-‘, ‘LineWidth‘, 2); title(ax_inset, ‘局部放大 (4-6s)‘); ax_inset.Box ‘on‘; % 為插圖加上邊框 % 在右側(cè)創(chuàng)建一個(gè)統(tǒng)計(jì)直方圖 ax_hist axes(‘Position‘, [0.72, 0.25, 0.25, 0.3]); histogram(ax_hist, y_main, 20, ‘FaceColor‘, ‘green‘, ‘EdgeColor‘, ‘black‘); xlabel(ax_hist, ‘幅值分布‘); ylabel(ax_hist, ‘頻數(shù)‘); title(ax_hist, ‘幅值直方圖‘);注意事項(xiàng)axes(‘Position‘, pos)會(huì)創(chuàng)建一個(gè)新的坐標(biāo)軸并自動(dòng)將其設(shè)為當(dāng)前坐標(biāo)軸。后續(xù)的plot,xlabel等命令會(huì)作用在這個(gè)新坐標(biāo)軸上。為了避免混淆強(qiáng)烈建議像上面一樣在繪圖命令中顯式指定目標(biāo)坐標(biāo)軸句柄如plot(ax_main, …)。所有Position值必須在 [0, 1] 范圍內(nèi)且left width 1,bottom height 1否則坐標(biāo)軸會(huì)超出圖形窗口不可見。這種方法需要你事先規(guī)劃好每個(gè)“積木”的位置和大小進(jìn)行一些簡單的計(jì)算。對于規(guī)則布局可以結(jié)合循環(huán)來批量創(chuàng)建。3.3 方法三利用tiledlayout進(jìn)行更現(xiàn)代化的布局管理從Matlab R2019b開始引入了tiledlayout和nexttile。它比傳統(tǒng)的subplot更強(qiáng)大、更易用提供了統(tǒng)一的布局管理特別適合創(chuàng)建具有統(tǒng)一間距、標(biāo)題和標(biāo)簽的復(fù)雜網(wǎng)格。figure(‘Position‘, [100, 100, 1000, 700]); % 創(chuàng)建一個(gè)3x2的瓦片布局并指定緊湊的間距 t tiledlayout(3, 2, ‘TileSpacing‘, ‘compact‘, ‘Padding‘, ‘compact‘); % 使用nexttile在下一個(gè)可用位置創(chuàng)建坐標(biāo)軸并繪圖 nexttile; % 進(jìn)入第1個(gè)位置 plot(rand(10,1)); title(‘圖1‘); nexttile; % 進(jìn)入第2個(gè)位置 plot(rand(10,1)); title(‘圖2‘); % 讓第3個(gè)圖橫跨兩列 nexttile([1, 2]); % [行跨度 列跨度] plot(rand(10,2)); title(‘圖3 (跨兩列)‘); nexttile; plot(rand(10,1)); title(‘圖4‘); nexttile; plot(rand(10,1)); title(‘圖5‘); % 還可以創(chuàng)建獨(dú)立的、不遵循網(wǎng)格的坐標(biāo)軸類似于axes ax_extra axes(t); % 在tiledlayout t 中創(chuàng)建一個(gè)自由坐標(biāo)軸 ax_extra.Layout.Tile 6; % 指定它放置在第6個(gè)瓦片位置 ax_extra.Layout.TileSpan [1, 1]; % 占據(jù)1行1列 plot(ax_extra, rand(10,1)); title(ax_extra, ‘圖6 (通過axes創(chuàng)建)‘); % 為整個(gè)布局添加共享的標(biāo)題和標(biāo)簽 title(t, ‘使用Tiledlayout管理的復(fù)雜布局示例‘); xlabel(t, ‘公共X軸標(biāo)簽‘); ylabel(t, ‘公共Y軸標(biāo)簽‘);tiledlayout的優(yōu)勢自動(dòng)對齊所有子圖的刻度標(biāo)簽、坐標(biāo)軸框線會(huì)自動(dòng)對齊視覺效果更專業(yè)。靈活的跨度通過nexttile([rowspan, colspan])輕松實(shí)現(xiàn)子圖的合并。統(tǒng)一的邊距控制通過‘TileSpacing‘和‘Padding‘選項(xiàng)一次性控制所有子圖的內(nèi)外邊距非常方便。共享標(biāo)題和標(biāo)簽可以為整個(gè)布局添加一個(gè)總標(biāo)題和總坐標(biāo)軸標(biāo)簽這在論文插圖中非常有用。與直接設(shè)置Position的對比tiledlayout提供了更高層、更結(jié)構(gòu)化的管理。對于大多數(shù)網(wǎng)格化或半規(guī)則布局它是首選。但對于完全自由、任意位置擺放的布局比如多個(gè)重疊的插圖直接使用axes(‘Position‘, …)仍是更直接的選擇。3.4 方法四計(jì)算復(fù)雜布局的Position矩陣對于需要精確對齊的多個(gè)子圖例如并排的多個(gè)曲線圖要求Y軸刻度標(biāo)簽左對齊手動(dòng)計(jì)算每個(gè)Position向量很繁瑣。我們可以通過編程方式計(jì)算一個(gè)Position矩陣。假設(shè)我們需要?jiǎng)?chuàng)建3個(gè)并排的等寬子圖它們之間留有固定間距并且與圖形窗口邊緣也有間距。figure(‘Position‘, [100, 100, 1200, 400]); % 定義布局參數(shù) numPlots 3; % 3個(gè)子圖 leftMargin 0.08; % 左邊距 rightMargin 0.05; % 右邊距 bottomMargin 0.15; % 下邊距 topMargin 0.1; % 上邊距 horizontalGap 0.04; % 子圖之間的水平間隙 % 計(jì)算可用總寬度和每個(gè)子圖的寬度 availableWidth 1 - leftMargin - rightMargin - horizontalGap * (numPlots - 1); plotWidth availableWidth / numPlots; % 計(jì)算每個(gè)子圖的Position for i 1:numPlots left leftMargin (i-1) * (plotWidth horizontalGap); % 高度固定從下邊距延伸到上邊距 pos [left, bottomMargin, plotWidth, 1 - topMargin - bottomMargin]; ax axes(‘Position‘, pos); data randn(100,1) i; % 生成一些示例數(shù)據(jù) plot(ax, data); title(ax, [‘?dāng)?shù)據(jù)集 ‘, num2str(i)]); ylabel(ax, ‘測量值‘); if i 1 % 可以在這里為第一個(gè)圖添加更詳細(xì)的Y軸標(biāo)簽 end if i numPlots % 可以在這里為最后一個(gè)圖添加圖例 legend(ax, ‘顯示‘); end end這種方法的精髓將布局參數(shù)邊距、間隙定義為變量然后通過公式計(jì)算出每個(gè)子圖的確切位置。這保證了像素級(jí)的精確對齊修改一個(gè)參數(shù)如horizontalGap所有子圖會(huì)自動(dòng)更新位置非常適合需要批量生成或布局經(jīng)常變動(dòng)的場景。4. 高級(jí)應(yīng)用與常見問題排查掌握了基本方法我們來看看一些高級(jí)技巧和那些容易踩的“坑”。4.1 創(chuàng)建“圖中的圖”Inset Axes這是科研繪圖中非常常見的需求即在主圖內(nèi)部嵌入一個(gè)小的子圖來展示細(xì)節(jié)。figure; % 主圖 ax_main axes(‘Position‘, [0.15, 0.15, 0.7, 0.7]); x_main 0:0.01:10; y_main sin(x_main) 0.5*cos(2*x_main); plot(ax_main, x_main, y_main, ‘k-‘, ‘LineWidth‘, 1.5); xlabel(‘X‘); ylabel(‘Y‘); title(‘主函數(shù)與局部放大‘); % 在特定位置創(chuàng)建插圖 (使用歸一化坐標(biāo)相對于主圖ax_main!) % 我們希望插圖在主圖坐標(biāo)系內(nèi)的 [4, 6] 區(qū)間并放在主圖右上角 ax_inset axes(‘Position‘, [0.6, 0.6, 0.25, 0.25]); % 這個(gè)位置是相對于Figure的 % 但我們需要將插圖的數(shù)據(jù)范圍限定在局部 idx x_main 4 x_main 6; plot(ax_inset, x_main(idx), y_main(idx), ‘r-‘, ‘LineWidth‘, 2); title(ax_inset, ‘局部放大‘); grid(ax_inset, ‘on‘); ax_inset.Box ‘on‘; % 關(guān)鍵一步將插圖的坐標(biāo)軸范圍鎖定使其不隨主圖縮放而改變 % 否則當(dāng)你用鼠標(biāo)縮放或平移主圖時(shí)插圖可能會(huì)亂掉 ax_inset.XLim [4, 6]; ax_inset.YLim [min(y_main(idx)), max(y_main(idx))];重要提示創(chuàng)建插圖時(shí)其Position是相對于圖形窗口Figure的。你需要根據(jù)主圖的位置計(jì)算出插圖在窗口中的歸一化位置這需要一點(diǎn)幾何計(jì)算。一個(gè)更直觀但略復(fù)雜的方法是使用axes(‘Parent‘, ax_main, ‘Position‘, [0.6, 0.6, 0.3, 0.3])這樣插圖的Position就是相對于主圖坐標(biāo)軸的了但這種方法對坐標(biāo)軸刻度和標(biāo)簽的管理更復(fù)雜初學(xué)者建議先用相對Figure的絕對位置。4.2 保持坐標(biāo)軸比例Aspect Ratio有時(shí)你需要子圖保持特定的寬高比比如顯示一個(gè)正方形區(qū)域的地圖或圖像。figure; ax axes(‘Position‘, [0.1, 0.1, 0.8, 0.6]); % 先定義一個(gè)矩形區(qū)域 plot(ax, rand(10,1)); % 強(qiáng)制坐標(biāo)軸的數(shù)據(jù)單位在X和Y方向上等比例顯示 axis(ax, ‘equal‘); % 或者使用更靈活的方式手動(dòng)設(shè)置比例 % ax.DataAspectRatio [1, 1, 1]; % X:Y:Z 1:1:1 % 此時(shí)Matlab會(huì)根據(jù)DataAspectRatio和PlotBoxAspectRatio自動(dòng)調(diào)整坐標(biāo)軸在Position框內(nèi)的實(shí)際顯示區(qū)域可能會(huì)在兩側(cè)或上下留下空白。設(shè)置‘equal‘后Matlab會(huì)保證一個(gè)數(shù)據(jù)單位在X軸和Y軸上的物理長度相等。這時(shí)你最初設(shè)置的Position的寬高比可能不是1:1Matlab會(huì)在保持比例的前提下讓圖形在給定的Position矩形內(nèi)最大化顯示可能導(dǎo)致圖形不占滿整個(gè)指定的矩形區(qū)域。4.3 常見問題與解決方案速查表問題現(xiàn)象可能原因解決方案設(shè)置了Position但圖形沒變化或錯(cuò)位1. 句柄錯(cuò)誤修改了錯(cuò)誤的坐標(biāo)軸。2. Position值超出[0,1]范圍。3. 使用了subplot后又用axes創(chuàng)建句柄管理混亂。1. 繪圖后立即用gca或變量保存句柄。2. 檢查計(jì)算確保leftwidth1,bottomheight1。3. 對于復(fù)雜布局統(tǒng)一使用axes(‘Position‘, …)創(chuàng)建避免混用subplot。子圖內(nèi)容如標(biāo)簽被遮擋Position指定的繪圖區(qū)域過小無法容納坐標(biāo)軸標(biāo)簽、標(biāo)題等。1. 增大width和height。2. 調(diào)整left和bottom為標(biāo)簽留出空間。3. 使用tight_subplot等第三方函數(shù)自動(dòng)計(jì)算帶標(biāo)簽空間的Position。圖形保存后與窗口顯示不一致保存時(shí)如print或saveas使用的分辨率、渲染器或紙張尺寸與屏幕顯示不同。1. 在保存前使用set(gcf, ‘PaperPositionMode‘, ‘a(chǎn)uto‘)。這會(huì)讓保存的圖像使用屏幕上的尺寸。2. 顯式指定保存的尺寸和分辨率print(‘-dpng‘, ‘-r300‘, ‘myfigure.png‘)。使用tiledlayout時(shí)自定義Position的axes不聽話在tiledlayout中直接創(chuàng)建axes后沒有通過Layout屬性將其與瓦片布局關(guān)聯(lián)。使用ax axes(t);創(chuàng)建然后設(shè)置ax.Layout.Tile和ax.Layout.TileSpan來指定它在網(wǎng)格中的位置。多個(gè)坐標(biāo)軸重疊無法點(diǎn)擊或選中預(yù)期的圖坐標(biāo)軸的Position區(qū)域有重疊且Matlab的圖形對象拾取順序HitTest可能導(dǎo)致上層的圖攔截鼠標(biāo)事件。1. 調(diào)整Position避免重疊。2. 如果必須重疊如插圖可以設(shè)置上層坐標(biāo)軸的HitTest屬性為‘off‘set(ax_inset, ‘HitTest‘, ‘off‘)這樣鼠標(biāo)點(diǎn)擊會(huì)穿透它選中下層的坐標(biāo)軸。4.4 性能與最佳實(shí)踐批量操作在循環(huán)中創(chuàng)建和修改多個(gè)坐標(biāo)軸時(shí)可以考慮使用hold on在一個(gè)坐標(biāo)軸內(nèi)繪制多條線而不是創(chuàng)建多個(gè)坐標(biāo)軸對象這樣性能更高。但如果需要不同的坐標(biāo)軸尺度如雙Y軸、類型如線性-對數(shù)坐標(biāo)或完全獨(dú)立的區(qū)域則必須創(chuàng)建多個(gè)坐標(biāo)軸。關(guān)閉自動(dòng)重繪在復(fù)雜布局的創(chuàng)建過程中可以在開始前設(shè)置set(gcf, ‘Visible‘, ‘off‘)或使用set(gcf, ‘HandleVisibility‘, ‘off‘)來暫時(shí)隱藏圖形等所有設(shè)置完成后再顯示可以加速創(chuàng)建過程并避免閃爍。使用函數(shù)封裝如果你經(jīng)常使用某種特定的布局比如論文要求的雙欄并排圖可以將計(jì)算Position的代碼封裝成一個(gè)自定義函數(shù)例如function ax myCustomSubplot(m,n,p)這樣以后調(diào)用起來就和subplot一樣方便但布局完全自定義。通過徹底理解Position屬性并熟練運(yùn)用axes命令和tiledlayout你就能擺脫Matlab默認(rèn)布局的束縛創(chuàng)造出任何你想要的圖形排版效果。這不僅僅是美化更是讓數(shù)據(jù)講述更清晰、更有力故事的關(guān)鍵一步。