1. 从“能画”到“画好”:初阶绘图的进阶门槛
上次我们聊了MATLAB绘图最基础的那几板斧:plot、figure、hold on/off、xlabel/ylabel/title和legend。掌握了这些,你确实已经能“画出”东西了。但如果你把用默认参数画出来的图直接贴到报告或者论文里,大概率会被导师或同事打回来,原因很简单:太丑、不专业、信息表达不清晰。
默认的细蓝线、默认的坐标轴范围、挤在一起的图例、难以区分的多条曲线……这些都是新手绘图最常见的“车祸现场”。绘图的核心目的不是展示“我会用MATLAB画图”,而是清晰、准确、美观地传达数据背后的信息。从“能画”到“画好”,中间隔着的就是对这些图形属性进行精细化控制的技能。这就像拍照,自动模式能拍,但想拍出好照片,你必须懂得调整光圈、快门、ISO和白平衡。
网络热词里频繁出现的LineStyle(线型)、LineWidth(线宽)、Marker(数据点标记),正是我们攻克这一进阶门槛的核心武器。它们不再是可有可无的修饰,而是提升图表可读性、区分多组数据、满足出版级要求的必备工具。今天,我们就深入这些属性的世界,让你的图表摆脱“新手感”。
2. 线条的“形”与“神”:LineStyle与LineWidth详解
当我们用plot(x, y)画出一条线时,MATLAB默认给我们的是宽度为0.5磅的实线。在屏幕上看或许还行,但一旦打印出来,或者插入到文档中经过缩放,这条细线很容易变得模糊不清,特别是在有多条曲线需要对比时,缺乏视觉分量。
2.1LineWidth:赋予线条视觉分量
LineWidth属性控制线条的粗细,单位是“点”(points)。这是一个非常直观的属性:数值越大,线越粗。
x = 0:0.1:2*pi; y1 = sin(x); y2 = cos(x); figure('Position', [100, 100, 800, 400]) % 设置图形窗口位置和大小 subplot(1,2,1) plot(x, y1, 'b-', 'LineWidth', 0.5); % 默认细线 hold on plot(x, y2, 'r-', 'LineWidth', 0.5); title('默认线宽 (0.5)') legend('sin(x)', 'cos(x)') grid on subplot(1,2,2) plot(x, y1, 'b-', 'LineWidth', 2.0); % 加粗线条 hold on plot(x, y2, 'r-', 'LineWidth', 2.0); title('加粗线宽 (2.0)') legend('sin(x)', 'cos(x)') grid on运行这段代码,你会立刻看到右侧子图的曲线更加清晰、醒目。在学术图表中,1.5到2.5的线宽是常见的选择,它能保证在黑白打印或缩小后依然清晰可辨。但也不是越粗越好,过粗的线条(比如超过3)可能会掩盖细节,并让图表显得笨重。
注意:
LineWidth影响的是线条的视觉权重。当你需要突出某条关键曲线时,将其线宽设置为其他曲线的1.5-2倍,是一个立竿见影的方法。
2.2LineStyle:区分线条的逻辑
当有多条曲线出现在同一坐标系中时,仅靠颜色区分有时是不够的(例如黑白打印或色盲读者)。这时,LineStyle(线型)就至关重要了。MATLAB提供了几种基础的线型:
'-':实线(solid)。最常用,用于表示主要、连续的趋势或数据。'--':虚线(dashed)。常用于表示理论值、参考线、对比基线或次要数据。':':点线(dotted)。常用于表示辅助线、假设线或间隔较小的数据。'-.':点划线(dash-dot)。一种折中的选择,用于需要与实线和虚线都区分开的情况。
让我们结合颜色和线型来优化一个多曲线图表:
x = linspace(0, 10, 100); y1 = exp(-0.2*x) .* sin(x); % 衰减正弦波 y2 = 0.5 * cos(0.5*x); % 余弦波 y3 = 0.3 * x - 1; % 线性趋势 y4 = zeros(size(x)) + 0.2; % 水平参考线 figure('Color', 'white'); plot(x, y1, 'b-', 'LineWidth', 2.0, 'DisplayName', '衰减振荡'); % 实线, 主数据 hold on plot(x, y2, 'r--', 'LineWidth', 1.5, 'DisplayName', '余弦分量'); % 虚线, 对比数据 plot(x, y3, 'g:', 'LineWidth', 2.0, 'DisplayName', '线性趋势'); % 点线, 趋势线 plot(x, y4, 'k-.', 'LineWidth', 1.0, 'DisplayName', '参考阈值'); % 点划线, 参考线 xlabel('时间 (s)') ylabel('幅值') title('多类型信号对比 (优化线型与线宽)') legend('Location', 'best') % 'best'让MATLAB自动选择图例最佳位置 grid on box on % 给图形加上边框,更美观在这段代码中,我们做了几件事:
- 主次分明:主要分析的“衰减振荡”信号用了最粗的蓝色实线。
- 清晰区分:四条曲线分别用了四种不同的线型,即使打印成黑白也一目了然。
- 语义化:参考阈值使用了细一点的点划线,视觉上不抢镜但又能被注意到。
实操心得:为线型赋予“语义”是一个好习惯。在你的研究领域内,可以固定一套规则,比如:实验数据用实线,仿真结果用虚线,理论边界用点线。这样你和你团队的读者都能快速理解图表含义。
3. 标记数据点:Marker的妙用
plot函数不仅可以画线,还可以在数据点上放置标记(Marker)。这对于以下场景特别有用:
- 数据点稀疏,连线不能很好反映趋势。
- 需要强调某些特定的数据样本位置。
- 在散点图或需要同时显示线型和数据点的图中。
3.1 常用标记符号
MATLAB提供了丰富的标记类型,以下是最常用的一些:
| 符号 | 描述 | 示例 | 适用场景 |
|---|---|---|---|
'.' | 点 | 大量数据点,避免遮挡 | |
'o' | 圆圈 | ○ | 最常用,清晰不填充 |
's' | 正方形 | □ | |
'd' | 菱形 | ◇ | |
'^' | 上三角 | △ | 表示增长、正向 |
'v' | 下三角 | ▽ | 表示下降、负向 |
'>' | 右三角 | ▷ | |
'<' | 左三角 | ◁ | |
'*' | 星号 | ★ | 突出关键点 |
'+' | 加号 | + | |
'x' | 叉号 | × |
3.2 综合应用:线型、颜色与标记
在plot函数中,你可以用一个字符串同时指定颜色、线型和标记。格式是:'颜色线型标记'。例如:
'ro-':红色、圆圈标记、实线连接。'b*:':蓝色、星号标记、点线连接。'ks--':黑色、正方形标记、虚线连接。
如果只想画标记不连线,则省略线型部分,如'ro'。
x = 1:10; y_measured = [2.1, 2.9, 4.2, 4.8, 6.1, 7.0, 8.2, 8.9, 10.1, 11.0]; % 实测数据 y_theoretical = x; % 理论值 y=x figure; % 绘制理论线:黑色虚线,无标记 plot(x, y_theoretical, 'k--', 'LineWidth', 1.5, 'DisplayName', '理论值 (y=x)'); hold on % 绘制实测数据:红色圆圈标记,用细实线连接,标记边缘黑色,内部白色填充 plot(x, y_measured, 'ro-', 'LineWidth', 1.0, ... 'MarkerSize', 8, ... % 标记大小 'MarkerEdgeColor', 'k', ... % 标记边缘颜色 'MarkerFaceColor', 'w', ... % 标记填充颜色 'DisplayName', '实测数据'); xlabel('输入值') ylabel('输出值') title('实测数据与理论对比') legend('Location', 'northwest') grid on axis tight % 让坐标轴范围紧贴数据范围这段代码展示了更高级的属性设置:
'MarkerSize':控制标记的大小。'MarkerEdgeColor':控制标记边缘的颜色。'MarkerFaceColor':控制标记内部的填充颜色。设置为'w'(白色)可以让红色圆圈看起来是空心的,这在黑白印刷时比纯色圆圈更具可读性。
踩坑提醒:当数据点非常密集时,使用过大的标记或复杂的标记符号(如星号、菱形)会导致图形重叠、混乱不堪。此时应优先使用小圆点
'.'或不加标记,仅用线条表示。
4. 属性设置的两种范式:plot字符串 vs. set函数
到目前为止,我们都是在调用plot时直接使用字符串(如'ro--')或名称-值对(如'LineWidth', 2)来设置属性。这是一种非常方便的方式,尤其适用于创建图形时。
4.1 使用plot函数直接设置
这是最常用、最直观的方法,适用于已知所有属性的情况。
h = plot(x, y, 'b-s', 'LineWidth', 2, 'MarkerSize', 10, 'MarkerFaceColor', 'y');这行代码做了三件事:1)绘制图形;2)返回一个“句柄”h;3)设置了线条的颜色、线型、标记以及相关属性。
4.2 使用图形句柄与set函数进行后期修改
图形句柄(Handle)是MATLAB中指向某个图形对象(如一条线、一个坐标轴、一个文本标签)的引用。获取句柄后,我们可以在图形创建后的任意时刻,使用set函数修改其属性。这在交互式调整图形或批量修改时极其强大。
% 先以默认样式绘图 x = 0:pi/20:2*pi; y1 = sin(x); y2 = cos(x); figure; h1 = plot(x, y1); % 获取第一条线的句柄,保存到h1 hold on h2 = plot(x, y2); % 获取第二条线的句柄,保存到h2 title('初始绘图') % ... 运行后觉得图形不好看,在命令行或脚本后续部分进行修改 set(h1, 'LineStyle', '--', 'Color', [0, 0.5, 0], 'LineWidth', 2.5); % 将h1改为绿色虚线 set(h2, 'Marker', 'o', 'MarkerSize', 6, 'MarkerFaceColor', 'r', 'LineWidth', 1.5); % 将h2改为带红色实心圆标记的线 % 也可以使用更简洁的“点表示法”(推荐,更现代) % h1.LineStyle = ':'; % h1.Color = 'm'; % h2.Marker = 's';使用set或点表示法的最大优势在于灵活性。你可以先快速把图画出来看看效果,然后再像精修照片一样,慢慢调整每一条线的样式,直到满意为止。这在处理复杂图表时是必不可少的技能。
进阶技巧:如何知道一个图形对象有哪些属性可以设置?使用
get函数。在命令行输入get(h1),MATLAB会列出h1这条线对象的所有属性和当前值。这是探索和学习图形属性的最佳方式。
5. 多子图绘制进阶:subplot的灵活布局与美化
subplot(m, n, p)是创建多子图的标准方法,它将图形窗口划分为m行n列的网格,并激活第p个位置进行绘图。但默认的子图间距往往很窄,标题和标签可能重叠。
5.1 调整子图间距:subplot的局限与tiledlayout的登场
传统的subplot对间距控制不友好。MATLAB从R2019b开始引入了更强大的tiledlayout和nexttile函数,它们提供了对子图间距和布局的精细化控制,是现在更推荐的方式。
% 使用 tiledlayout 创建 2x2 的布局,并指定紧凑的间距 figure; t = tiledlayout(2, 2, 'TileSpacing', 'compact', 'Padding', 'compact'); % 创建布局对象 % 在第一个位置绘图 nexttile; x = linspace(0, 2*pi, 50); plot(x, sin(x), 'b-', 'LineWidth', 1.5); title('正弦函数 Sin(x)') grid on % 在第二个位置绘图 nexttile; plot(x, cos(x), 'r--', 'LineWidth', 1.5); title('余弦函数 Cos(x)') grid on % 在第三个位置绘图,这次用散点图 nexttile; scatter(x, sin(x) + 0.1*randn(size(x)), 40, 'filled', 'MarkerFaceColor', 'g'); % 带噪声的散点 title('带噪声的散点图') grid on % 在第四个位置绘图,使用阶梯图 nexttile; stairs(x, sin(x), 'k-', 'LineWidth', 1.5); title('阶梯图 Stairs') grid on % 为整个图形窗口添加总标题和坐标轴标签 title(t, '常用函数图形展示合集') % 总标题 xlabel(t, '公共X轴:角度 (rad)') % 公共X轴标签 ylabel(t, '函数值 Y') % 公共Y轴标签tiledlayout的优点在于:
- 统一控制:可以轻松地为所有子图设置统一的标题、坐标轴标签。
- 灵活间距:通过
'TileSpacing'和'Padding'参数精确控制子图间以及子图与边框的间距。 - 复杂布局:支持创建非均匀的网格布局,这是
subplot难以实现的。
5.2 为每个子图精细化设置坐标轴
即使有了好的布局,每个子图内部的坐标轴也可能需要调整,比如设置相同的Y轴范围以方便比较。
% 接上例,假设我们需要将第一行两个子图的Y轴范围都设为[-1.2, 1.2] ax1 = nexttile(1); % nexttile返回坐标轴句柄 ax2 = nexttile(2); ylim([ax1, ax2], [-1.2, 1.2]); % 同时设置两个坐标轴的Y轴范围 % 或者单独设置 % ax1.YLim = [-1.2, 1.2]; % ax2.YLim = [-1.2, 1.2];通过获取坐标轴句柄,你可以修改任何属性:XLim,YLim,XTick,XTickLabel,FontSize,FontName等等。这是实现出版级图表美化的关键。
6. 坐标轴与网格的精细化控制
默认的坐标轴和网格线往往是为了通用性而设计的,在特定场景下需要调整。
6.1 坐标轴范围、刻度与标签
xlim/ylim:手动设置坐标轴范围。使用axis tight可以自动使范围紧贴数据。xticks/yticks:手动设置刻度线的位置。xticklabels/yticklabels:手动设置刻度标签的文字。这常用于将数字刻度改为分类标签(如‘周一’,‘周二’)。
x = 0:0.5:10; y = x.^2; figure; plot(x, y, 'bd-', 'LineWidth', 1.5, 'MarkerSize', 6, 'MarkerFaceColor', 'c'); grid on box on % 1. 设置坐标轴范围 xlim([0, 12]); ylim([-5, 120]); % Y轴从-5开始,可以更清晰地看到曲线与X轴的交点(原点) % 2. 设置刻度 xticks(0:2:12); % X轴每2一个刻度 yticks([0, 25, 50, 75, 100]); % Y轴指定几个刻度 % 3. 设置刻度标签(示例:将X轴数字转换为时间概念) % 假设x是时间(小时),我们想显示为“0h", "2h", ... "12h” xticklabels({'0h', '2h', '4h', '6h', '8h', '10h', '12h'}); xlabel('时间') ylabel('性能指标') title('坐标轴刻度与标签自定义示例')6.2 网格线与边框
grid on/off:打开/关闭网格线。grid minor:打开次要网格线(位于主刻度之间)。box on/off:控制是否显示坐标轴区域的边框。
% 创建一个对比图 figure; subplot(1,2,1) plot(x, y, 'r-'); grid on % 只有主网格 title('仅主网格') box on subplot(1,2,2) plot(x, y, 'b-'); grid on grid minor % 同时打开主网格和次要网格 title('主网格+次要网格') box on次要网格线能提供更精细的视觉参考,尤其在数据密度高、需要精确读数时非常有用。
7. 添加文本、箭头与图例美化
7.1 添加文本注释:text与annotation
text(x, y, '字符串')在图形窗口的数据坐标(x, y)处添加文本。这对于在曲线上标注特定点非常有用。
annotation('textarrow', x, y, 'String', '注释文本')可以添加带箭头的文本注释。这里的x和y是归一化的图形坐标(从0到1),(0,0)是图形左下角,(1,1)是右上角。这需要一点转换思维。
x = 0:0.1:10; y = sin(x) + 0.5*x; figure; plot(x, y, 'LineWidth', 2); grid on xlabel('X') ylabel('Y') title('使用 text 和 annotation 添加注释') % 找到最大值点 [ymax, idx] = max(y); xmax = x(idx); % 方法1:使用 text 在数据点旁添加标签 text(xmax, ymax, sprintf(' 最大值 (%.2f, %.2f)', xmax, ymax), ... 'VerticalAlignment', 'bottom', ... % 文本底部对齐到点 'HorizontalAlignment', 'left', ... % 文本左对齐到点 'FontSize', 10, ... 'BackgroundColor', 'y', ... 'EdgeColor', 'k'); % 方法2:使用 annotation 添加带箭头的注释(使用图形坐标) % 首先将数据坐标 (xmax, ymax) 转换为归一化坐标 ax = gca; % 获取当前坐标轴句柄 pos = ax.Position; % 坐标轴在图形中的位置和大小 [left, bottom, width, height] x_range = ax.XLim; % 坐标轴的X轴范围 y_range = ax.YLim; % 坐标轴的Y轴范围 % 转换公式: norm_x = pos(1) + pos(3) * (x_data - x_range(1)) / diff(x_range) norm_x = pos(1) + pos(3) * (xmax - x_range(1)) / diff(x_range); norm_y = pos(2) + pos(4) * (ymax - y_range(1)) / diff(y_range); % 创建箭头注释,指向最大值点,起点在图形中间偏右上的位置 annotation('textarrow', [0.7, norm_x], [0.8, norm_y], ... 'String', '这是全局最大值点', ... 'FontSize', 9, ... 'TextBackgroundColor', [0.9, 0.9, 0.5]);7.2 图例 (legend) 的高级用法
我们已经用过legend('sin','cos')这种基本形式。图例也可以进行大量美化:
'Location': 控制图例位置。如'north','south','east','west','northeast'(默认),'best'(自动选择最佳位置)等。'Orientation': 方向,'vertical'(默认,垂直)或'horizontal'(水平)。当图例项很多时,水平排列可以节省空间。'NumColumns': 设置图例的列数(R2018a以上)。对于大量图例项,分多列显示比单列更紧凑。'Box': 是否显示图例边框,'on'(默认)或'off'。
% 创建一个需要复杂图例的图 x = linspace(0, 5, 20); figure; hold on plot(x, sin(x), 'ro-', 'LineWidth', 1.5, 'MarkerSize', 8, 'MarkerFaceColor', 'r', 'DisplayName', '实验组 A (高频)'); plot(x, cos(x), 'bs--', 'LineWidth', 2, 'MarkerSize', 10, 'MarkerFaceColor', 'c', 'DisplayName', '对照组 B'); plot(x, exp(-x), 'g^:', 'LineWidth', 1, 'MarkerSize', 6, 'MarkerFaceColor', 'y', 'DisplayName', '衰减模型 C'); plot(x, 0.5*x, 'kd-', 'LineWidth', 1.2, 'MarkerSize', 0, 'DisplayName', '线性参考 D'); % MarkerSize=0 不显示标记 % 美化图例 lgd = legend('Location', 'southoutside', ... % 放在图形下方外部 'Orientation', 'horizontal', ... % 水平排列 'NumColumns', 2, ... % 分两列显示 'Box', 'off'); % 去掉边框 lgd.FontSize = 9; title('多组数据对比与图例美化示例') grid on hold off8. 综合案例:绘制一张可直接用于报告的图表
让我们把今天学到的所有知识融合起来,绘制一张从数据到样式都经过精心设计的、可以直接放入学术报告或论文中的图表。
场景:比较三种不同算法在迭代过程中的收敛速度(误差下降曲线)。
%% 1. 模拟数据 iterations = 1:100; % 算法A: 快速收敛,但后期有波动 error_A = 10 * exp(-0.05*iterations) + 0.1*randn(size(iterations)); error_A(error_A < 0.01) = 0.01; % 设置一个下限 % 算法B: 平稳收敛 error_B = 8 * exp(-0.03*iterations) + 0.05*randn(size(iterations)); % 算法C: 收敛较慢 error_C = 12 * exp(-0.01*iterations) + 0.15*randn(size(iterations)); %% 2. 创建图形窗口,设置大小和背景色(适合嵌入文档的宽度) figure('Units', 'centimeters', 'Position', [5, 5, 18, 12], 'Color', 'w'); % 18cm宽,12cm高,白色背景 %% 3. 绘制三条曲线,仔细设置每条线的属性以区分 hA = plot(iterations, error_A, 'b-o', ... % 蓝色圆圈实线 'LineWidth', 1.8, ... 'MarkerSize', 5, ... 'MarkerFaceColor', 'b', ... 'MarkerEdgeColor', 'w', ... % 白色边缘使蓝色更突出 'DisplayName', '算法A (Proposed)'); hold on hB = plot(iterations, error_B, 'r--s', ... % 红色虚线方块 'LineWidth', 1.5, ... 'MarkerSize', 6, ... 'MarkerFaceColor', 'none', ... % 空心方块 'MarkerEdgeColor', 'r', ... 'DisplayName', '算法B (Baseline 1)'); hC = plot(iterations, error_C, 'k:d', ... % 黑色点线菱形 'LineWidth', 1.5, ... 'MarkerSize', 7, ... 'MarkerFaceColor', [0.8, 0.8, 0.8], ... % 浅灰色填充 'MarkerEdgeColor', 'k', ... 'DisplayName', '算法C (Baseline 2)'); %% 4. 添加一条表示收敛阈值的水平参考线 threshold = 1.0; yline(threshold, '--', 'Color', [0.5, 0.5, 0.5], 'LineWidth', 1.2, ... 'DisplayName', sprintf('收敛阈值 (%.1f)', threshold)); % yline是R2018b引入的便捷函数 % 对于更早版本,可以用: plot([iterations(1), iterations(end)], [threshold, threshold], '--', ...) %% 5. 精细化设置坐标轴 ax = gca; % 获取当前坐标轴句柄 ax.Box = 'on'; % 显示边框 ax.LineWidth = 1.0; % 坐标轴线宽 ax.FontSize = 11; % 坐标轴字体大小 ax.FontName = 'Arial'; % 设置字体,Arial是论文常用字体 ax.TickDir = 'out'; % 刻度朝外,更美观 % 设置坐标轴范围 xlim([0, 100]); ylim([0, 12]); % 设置刻度 xticks(0:20:100); yticks(0:2:12); % 设置坐标轴标签 xlabel('迭代次数', 'FontSize', 12, 'FontWeight', 'bold'); ylabel('误差 (Log Scale)', 'FontSize', 12, 'FontWeight', 'bold'); % 将Y轴设置为对数坐标,以便更清晰地观察误差下降(这在收敛性图中很常见) set(ax, 'YScale', 'log'); % 重新设置对数坐标下的Y轴刻度标签,使其更易读 yticks([0.01, 0.1, 1, 10]); yticklabels({'0.01', '0.1', '1', '10'}); ylim([0.008, 15]); % 调整对数坐标下的范围 %% 6. 添加网格(次要网格) grid on grid minor ax.MinorGridLineStyle = ':'; % 次要网格用点线 ax.MinorGridAlpha = 0.3; % 设置次要网格透明度 ax.GridAlpha = 0.5; % 设置主网格透明度 %% 7. 添加图例,并放置在不遮挡曲线的位置 lgd = legend('Location', 'northeast'); lgd.FontSize = 10; lgd.Box = 'on'; %% 8. 添加标题 title('三种优化算法的收敛性能对比', 'FontSize', 14, 'FontWeight', 'bold') %% 9. 高亮关键区域:标注算法A首次低于阈值的位置 idx_cross = find(error_A < threshold, 1, 'first'); if ~isempty(idx_cross) x_cross = iterations(idx_cross); y_cross = error_A(idx_cross); % 在交叉点画一个大的星号 plot(x_cross, y_cross, 'p', 'MarkerSize', 15, 'MarkerEdgeColor', 'm', ... 'MarkerFaceColor', 'm', 'HandleVisibility', 'off'); % 'HandleVisibility', 'off' 使其不出现在图例中 % 添加文本标注 text(x_cross+2, y_cross*1.5, sprintf('A在%d次迭代后收敛', x_cross), ... 'FontSize', 9, 'Color', 'm', 'BackgroundColor', [1, 0.9, 1]); end %% 10. 最后,调整图形输出设置,便于保存为高质量图片 % 设置图形渲染器,对于包含透明度和复杂标记的图,'painters' 或 'opengl' 各有利弊 % set(gcf, 'Renderer', 'painters'); hold off % 保存图形(取消注释以保存) % print('Algorithm_Comparison.png', '-dpng', '-r600'); % 保存为600DPI的PNG % saveas(gcf, 'Algorithm_Comparison.fig'); % 保存为MATLAB FIG文件,便于后续修改这张图几乎用到了我们讨论的所有技巧:自定义线型、标记、颜色、线宽;设置了坐标轴范围、刻度、标签和字体;添加了网格、参考线和图例;使用了文本注释;甚至设置了图形窗口的物理尺寸和保存选项。它不再是简单的数据展示,而是一个完整、专业的信息传达载体。
走到这一步,你的MATLAB图表已经具备了“专业”的雏形。但这仅仅是初阶绘图的终点,也是中高阶绘图的起点。接下来,你将面对更复杂的挑战:如何绘制三维曲面、如何制作动画、如何自定义颜色映射、如何处理大规模数据可视化,以及如何将多个图形元素组合成复杂的仪表板。这些我们将在后续的篇章中继续探索。记住,好的图表是思考和设计的产物,而不仅仅是代码运行的結果。