序言

备考某等级考试的时候,在教材中碰到了几个一直不太理解的、关于硬盘的概念:磁道、柱面号、扇区。然而教材没有配图,无法直观地了解这些概念的物理形态。维基百科的硬盘[1]词条页中倒是有一副不错的示意图,我截图搬运了过来

机械硬盘示意图

原图是一张 SVG 图片,本质上是一堆指令——也就是所谓的语绘啦。我是一个语绘爱好者,也想试试看能否用代码画一幅差不多的图出来。

在旧文《程序员特有的画图方式——语绘工具小入门》[2]中,我演示过几款写代码画图的工具,但它们都不适合用来绘制几何图形,所以这次它们没有用武之地。

本来我想试试用MetaPost[3]来画的,但鉴于“入门”了太多次,这次还是换点新花样吧。这一次,我用 LaTeX+TikZ 来画。

TikZ 是什么及光速入门

著名的压泡面神器、麻将桌脚垫《TAOCP》的作者发明了TeX[4],知名的Raft 竞品Paxos 算法的作者在此基础上创造了LaTeX[5],它们都是程序员简历论文排版的好帮手。而 TikZ 则是如虎添翼地在 LaTeX 中实现了简单易懂的绘图功能的一个红包宏包(macro package,TeX 的术语)。简而言之,TikZ 自定义了一套“语言”,可以在用 LaTeX 编写的文档中画出各种图形。

百闻不如一见,我演示一下如何用 TikZ 画一条线段、一个圆,以及一段圆弧。先将下列的代码保存到一个文件three_in_one.tex

\documentclass{standalone}\usepackage{tikz}\usetikzlibrary{shapes.geometric, arrows}\begin{document}\begin{tikzpicture}[scale=2]  %% 画一条从原点指向(1, 1)的线段  \draw (0, 0) -- (1, 1);  %% 画一个以(1, 1)为圆心,半径为2的圆。  \draw (1, 1) circle (2);  %% 画一段以原点为圆心,半径为1,张开角度为30度的圆弧。  \draw (1, 0) arc (0:30:1);\end{tikzpicture}\end{document}

再使用xelatex将其编译成 PDF 文件(xelatex可以通过安装 TeXLive 2020 获得)

xelatex three_in_one.tex

此时便得到了three_in_one.pdf文件。为了可以在文章中显示,我用 ImageMagick 将其转换为 PNG 文件

convert three_in_one.pdf /tmp/three_in_one.png

最终的图片如下

简单,就像画一匹马一样简单。

现在该来试试用 TikZ 复刻维基百科上的硬盘示意图了。

来点同心圆

在原图中最引人注目的,当属那十几个同心圆了。简单起见,我只画六个圆。这六个圆的半径相差1pt(pt是 TikZ 默认的长度单位),从3pt一直递增到8pt,它们的圆心都在坐标原点(0, 0)上。

%% 为了节省篇幅,只给出TikZ部分的代码。\begin{tikzpicture}  \draw (0, 0) circle (3);  \draw (0, 0) circle (4);  \draw (0, 0) circle (5);  \draw (0, 0) circle (6);  \draw (0, 0) circle (7);  \draw (0, 0) circle (8);\end{tikzpicture}

来点等分线

原图中有 12 根线段,将每一个圆等分成了全等的 12 份。从前一节的内容可知,要用\draw命令绘制线段,需要的是线段两端的坐标,那么这批坐标要怎么计算呢?尽管可以用三角函数计算出这些点的笛卡尔坐标,但在 TikZ 中可以用更方便的极坐标来指定这些点。

以原图中从 X 轴开始逆时针旋转遇到的第一条线段为例,它在半径为3pt的圆上的点的坐标为(30:3)(30 是极坐标中的角度,3 是半径长度),而在半径为8pt的圆上的点的坐标为(30:8),因此可以用\draw (30:3) -- (30:8)来画出这根线段。

通过调整其中的角度可以画出剩余的其它线段。

\begin{tikzpicture}  \draw (0, 0) circle (3);  \draw (0, 0) circle (4);  \draw (0, 0) circle (5);  \draw (0, 0) circle (6);  \draw (0, 0) circle (7);  \draw (0, 0) circle (8);

  \draw (0:3) -- (0:8);  \draw (30:3) -- (30:8);  \draw (60:3) -- (60:8);  \draw (90:3) -- (90:8);  \draw (120:3) -- (120:8);  \draw (150:3) -- (150:8);  \draw (180:3) -- (180:8);  \draw (210:3) -- (210:8);  \draw (240:3) -- (240:8);  \draw (270:3) -- (270:8);  \draw (300:3) -- (300:8);  \draw (330:3) -- (330:8);\end{tikzpicture}

来张色图

原图大致的骨架已经画完了,现在来尝试给它上色。在 TikZ 中,可以用\fill命令给一段封闭的曲线上色。比如用\fill[red] (0, 0) -- (1, 0) -- (1, 1) -- (0, 1) -- cycle可以将左下角在原点、边长为1pt的正方形涂成红色。

先给原图中的区域 B 上色。区域 B 是一个扇形,它由两根长度为8pt的半径和一段夹角为 30 度的圆弧构成。要描述这段封闭曲线,可以借助入门一节中介绍的arc命令。

\begin{tikzpicture}  %% 给区域B上色。  \fill[blue] (0, 0) -- (30:8) arc (30:60:8) -- cycle;

  \draw (0, 0) circle (3);  \draw (0, 0) circle (4);  \draw (0, 0) circle (5);  \draw (0, 0) circle (6);  \draw (0, 0) circle (7);  \draw (0, 0) circle (8);

  \draw (0:3) -- (0:8);  \draw (30:3) -- (30:8);  \draw (60:3) -- (60:8);  \draw (90:3) -- (90:8);  \draw (120:3) -- (120:8);  \draw (150:3) -- (150:8);  \draw (180:3) -- (180:8);  \draw (210:3) -- (210:8);  \draw (240:3) -- (240:8);  \draw (270:3) -- (270:8);  \draw (300:3) -- (300:8);  \draw (330:3) -- (330:8);\end{tikzpicture}

\fill命令那一行最后的cycle的意思,是让曲线回到起点组成一个封闭的形状。另外,\fill命令需要写在\draw命令之前,是为了避免蓝色颜料将区域内的圆弧给盖住了。

对于区域 C 和区域 D,方法是一样的,只是描述封闭曲线的坐标不同罢了。

\begin{tikzpicture}  %% 给区域B上色。  \fill[blue] (0, 0) -- (30:8) arc (30:60:8) -- cycle;  %% 给区域C上色。  \fill[purple] (30:4) -- (30:5) arc (30:60:5) -- (60:4) -- (60:4) arc (60:30:4);  %% 给区域D上色。  \fill[green] (240:6) -- (240:7) arc (240:330:7) -- (330:6) -- (330:6) arc (330:240:6);

  \draw (0, 0) circle (3);  \draw (0, 0) circle (4);  \draw (0, 0) circle (5);  \draw (0, 0) circle (6);  \draw (0, 0) circle (7);  \draw (0, 0) circle (8);

  \draw (0:3) -- (0:8);  \draw (30:3) -- (30:8);  \draw (60:3) -- (60:8);  \draw (90:3) -- (90:8);  \draw (120:3) -- (120:8);  \draw (150:3) -- (150:8);  \draw (180:3) -- (180:8);  \draw (210:3) -- (210:8);  \draw (240:3) -- (240:8);  \draw (270:3) -- (270:8);  \draw (300:3) -- (300:8);  \draw (330:3) -- (330:8);\end{tikzpicture}

给环形上色

聪明的读者也许已经发现了,区域 A 的环形没办法用这种方式来描述。不过没关系,只要将其视为上下半两部分,再分别上色即可。

\begin{tikzpicture}  %% 环的上半部分  \fill[red] (4, 0) -- (5, 0) arc (0:180:5) -- (-4, 0) -- (-4, 0) arc (180:0:4);  %% 环的下半部分  \fill[red] (4, 0) -- (5, 0) arc (360:180:5) -- (-4, 0) -- (-4, 0) arc (180:360:4);  %% 给区域B上色。  \fill[blue] (0, 0) -- (30:8) arc (30:60:8) -- cycle;  %% 给区域C上色。  \fill[purple] (30:4) -- (30:5) arc (30:60:5) -- (60:4) -- (60:4) arc (60:30:4);  %% 给区域D上色。  \fill[green] (240:6) -- (240:7) arc (240:330:7) -- (330:6) -- (330:6) arc (330:240:6);

  \draw (0, 0) circle (3);  \draw (0, 0) circle (4);  \draw (0, 0) circle (5);  \draw (0, 0) circle (6);  \draw (0, 0) circle (7);  \draw (0, 0) circle (8);

  \draw (0:3) -- (0:8);  \draw (30:3) -- (30:8);  \draw (60:3) -- (60:8);  \draw (90:3) -- (90:8);  \draw (120:3) -- (120:8);  \draw (150:3) -- (150:8);  \draw (180:3) -- (180:8);  \draw (210:3) -- (210:8);  \draw (240:3) -- (240:8);  \draw (270:3) -- (270:8);  \draw (300:3) -- (300:8);  \draw (330:3) -- (330:8);\end{tikzpicture}

润色一下

用 macOS 的“数码测色计”看了一下原图中各个区域的颜色的 RGB 值,区域 A 大概是(236, 133, 130)、区域 B 大概是(122, 127, 237)、区域 C 大概是(131, 132, 139)、区域 D 大概是(0, 151, 27)。接下来我让 TikZ 以这四种指定的颜色填充图中的四个区域,先用 LaTeX 的\definecolor命令定义四个新的颜色的名字。

%% 下列四行代码置于document环境之前\definecolor{areaA}{RGB}{236,133,130}\definecolor{areaB}{RGB}{122,127,237}\definecolor{areaC}{RGB}{131,32,139}\definecolor{areaD}{RGB}{0,151,27}

再替换掉\fill命令中的颜色名即可

\begin{tikzpicture}  %% 环的上半部分  \fill[areaA] (4, 0) -- (5, 0) arc (0:180:5) -- (-4, 0) -- (-4, 0) arc (180:0:4);  %% 环的下半部分  \fill[areaA] (4, 0) -- (5, 0) arc (360:180:5) -- (-4, 0) -- (-4, 0) arc (180:360:4);  %% 给区域B上色。  \fill[areaB] (0, 0) -- (30:8) arc (30:60:8) -- cycle;  %% 给区域C上色。  \fill[areaC] (30:4) -- (30:5) arc (30:60:5) -- (60:4) -- (60:4) arc (60:30:4);  %% 给区域D上色。  \fill[areaD] (240:6) -- (240:7) arc (240:330:7) -- (330:6) -- (330:6) arc (330:240:6);

  \draw (0, 0) circle (3);  \draw (0, 0) circle (4);  \draw (0, 0) circle (5);  \draw (0, 0) circle (6);  \draw (0, 0) circle (7);  \draw (0, 0) circle (8);

  \draw (0:3) -- (0:8);  \draw (30:3) -- (30:8);  \draw (60:3) -- (60:8);  \draw (90:3) -- (90:8);  \draw (120:3) -- (120:8);  \draw (150:3) -- (150:8);  \draw (180:3) -- (180:8);  \draw (210:3) -- (210:8);  \draw (240:3) -- (240:8);  \draw (270:3) -- (270:8);  \draw (300:3) -- (300:8);  \draw (330:3) -- (330:8);\end{tikzpicture}

图文并茂

剩下的需要复刻的东西就是原图中的文字以及标注用的线了。线很容易画,只要规定了坐标后用\draw命令即可。比如说,我可以把四条线定义如下,其中的坐标和线段的长度纯粹是个人偏好

\draw (75:4.5) -- (75:9);\draw (40:7.5) -- (40:9);\draw (50:4.5) -- (50:9);\draw (285:6.5) -- (285:9);

线画完了,再到每一根线的“终点”标上文字说明,这需要用到 TikZ 的node功能。用法很简单,就是在需要标注文字的坐标后,紧跟着关键字node,以及一段用花括号包裹的文本即可

\documentclass{standalone}\usepackage{tikz}\usepackage{xeCJK}\setCJKmainfont{Songti TC}\usetikzlibrary{shapes.geometric, arrows}\definecolor{areaA}{RGB}{236,133,130}\definecolor{areaB}{RGB}{122,127,237}\definecolor{areaC}{RGB}{131,32,139}\definecolor{areaD}{RGB}{0,151,27}\begin{document}\begin{tikzpicture}  %% 环的上半部分  \fill[areaA] (4, 0) -- (5, 0) arc (0:180:5) -- (-4, 0) -- (-4, 0) arc (180:0:4);  %% 环的下半部分  \fill[areaA] (4, 0) -- (5, 0) arc (360:180:5) -- (-4, 0) -- (-4, 0) arc (180:360:4);  %% 给区域B上色。  \fill[areaB] (0, 0) -- (30:8) arc (30:60:8) -- cycle;  %% 给区域C上色。  \fill[areaC] (30:4) -- (30:5) arc (30:60:5) -- (60:4) -- (60:4) arc (60:30:4);  %% 给区域D上色。  \fill[areaD] (240:6) -- (240:7) arc (240:330:7) -- (330:6) -- (330:6) arc (330:240:6);

  \draw (0, 0) circle (3);  \draw (0, 0) circle (4);  \draw (0, 0) circle (5);  \draw (0, 0) circle (6);  \draw (0, 0) circle (7);  \draw (0, 0) circle (8);

  \draw (0:3) -- (0:8);  \draw (30:3) -- (30:8);  \draw (60:3) -- (60:8);  \draw (90:3) -- (90:8);  \draw (120:3) -- (120:8);  \draw (150:3) -- (150:8);  \draw (180:3) -- (180:8);  \draw (210:3) -- (210:8);  \draw (240:3) -- (240:8);  \draw (270:3) -- (270:8);  \draw (300:3) -- (300:8);  \draw (330:3) -- (330:8);

  \draw (75:4.5) -- (75:9) node {磁道};  \draw (40:7.5) -- (40:9) node {扇面};  \draw (50:4.5) -- (50:9) node {扇区};  \draw (285:6.5) -- (285:9) node {簇};\end{tikzpicture}\end{document}

需要留意的是,我在源代码开头的位置,引入了xeCJK宏包(\usepackage{xeCJK}),并且指定了中文内容用的字体为宋体(\setCJKmainfont{Songti TC}),这样才能成功编译。

至此,复刻算是完成了。

后记

本文只是管中窥豹,TikZ 还可以画出其它更复杂更美轮美奂的图形,有兴趣的读者可以移步这里[6]观赏。此外,TikZ 也可以“编程”,比如下面的两行代码便足矣画出上文中 12 行代码才完成的等分线

\foreach \x in {0,30,60,90,120,150,180,210,240,270,300,330}\draw (\x:3) -- (\x:8);

TikZ 的更多潜力和乐趣,就由各位读者自己探索吧。

如果你想要和我交流,欢迎点击阅读原文到我的博客上发表评论。

参考资料

[1]

硬盘: https://zh.wikipedia.org/wiki/%E7%A1%AC%E7%9B%98

[2]

《程序员特有的画图方式——语绘工具小入门》: https://liutos.github.io/2020/05/07/%E7%A8%8B%E5%BA%8F%E5%91%98%E7%89%B9%E6%9C%89%E7%9A%84%E7%94%BB%E5%9B%BE%E6%96%B9%E5%BC%8F%E2%80%94%E2%80%94%E8%AF%AD%E7%BB%98%E5%B7%A5%E5%85%B7%E5%B0%8F%E5%85%A5%E9%97%A8/

[3]

MetaPost: https://zh.wikipedia.org/wiki/MetaPost

[4]

TeX: https://zh.wikipedia.org/wiki/TeX

[5]

LaTeX: https://zh.wikipedia.org/wiki/LaTeX

[6]

这里: https://texample.net/tikz/examples/

点击进入留言区

msdn画圆弧函数_精确之美——用TikZ画硬盘示意图相关推荐

  1. msdn画圆弧函数_画直线不简单!python-matplotlib告诉你为什么

    1 说明: ====== 1.1 python的matplotlib画直线,看似简单,其实很难,从简单到复杂,逐步深入,小白秒懂. 1.2 内容:画直线,画圆,画圆点,动画的单摆和圆套圆,好东西在后面 ...

  2. msdn画圆弧函数_三角函数常识2020

    速成零基础数学三角函数(旋转坐标系角度相关基础知识) 三角函数基础知识 函数是指在某个变化过程中,有两个互相依赖的变量x和y,如果x取某值,y则依照确定的关系取相应值,这时,y是x的函数.例如,骑自行 ...

  3. msdn画圆弧函数_复变函数与积分变换 简明笔记(八):保形映射(共形映射)

    在第一部分中我们就引入了复变函数的概念,但由于复变函数是二维点集之间的映射,所以作出复变函数的图像并不简单.事实上,研究复变函数的图像性质,主要是观察它将 平面上的平面图形映成 平面( )上的什么图形 ...

  4. 画分段函数_秃头节:“函数”段子已出炉高中数学题型分析

    高中数学函数题型整理解析版 函数图像 有关函数图象识别问题的常见题型及解题思路(1)由函数的定义域,判断图象左右的位置,由函数的值域,判断图象的上下位置:②由函数的单调性,判断图象的变化趋势:③由函数 ...

  5. python绘制反比例函数_怎样在几何画板中画可变的反比例函数

    原标题:怎样在几何画板中画可变的反比例函数 反比例函数是中学时代必学的一种函数,其图像是是双曲线,是用平滑的曲线把一些特殊的点连接起来的,掌握反比例函数的图像是中学数学的重点和难点.作为好用的绘图工具 ...

  6. cad多段线画圆弧方向_(cad多段线画圆弧方向)在cad中如何使用excel画样条曲线

    在cad中如何使用excel画样条曲线 AutoCAD本身没有提供函数曲线的绘制功能,不能直接利AutoCAD绘制函数曲线,但借助其他工具也能绘制,常用的方法有3种,第一种,借助其他能绘制函数曲线的C ...

  7. 怎样使用python画复杂函数_在python中绘制复杂的函数?

    我试图在python中绘制一个带有可变参数的复杂函数,并且发现我无法解释的差异.我的代码如下所示:import matplotlib.pyplot as plt from numpy import p ...

  8. python画反比例函数_反比例光滑支撑向量机

    1. 引言 2001年Lee等人通过对支撑向量机的深入研究引入光滑的概念,使用了sigmoid积分函数p(x,α)对无约束的支撑向量机模型SVM [1] 进行光滑化,得出了分类性能较好的光滑支撑向量机 ...

  9. python正态分布函数_数学之美_正态分布(Python代码)

    1 在概率统计中,我们针对某个事件当中各个样本发生的概率的频率进行统计,用一个函数的形式写出的这个概率的频率函数就叫做分布函数. 2 分布函数顾名思义,就是某个连续事件发生频率的汇总表示.再直白一点儿 ...

最新文章

  1. win7 64位操作系统中 Oracle 11g 安装教程(图解)
  2. POJ 2870 求矩阵的加法
  3. 友盟QQ好友或者空间分享成功,但回调失败或取消
  4. vector动态数组
  5. java 高并发第三阶段实战_Java 高并发第三阶段实战---Java并发包深入解析与使用详解...
  6. 注入代码oracle
  7. 如何获取Agile PLM Business Object 对应Agile对象的属性?
  8. 201501006-构建之法:现代软件工程-阅读笔记
  9. membercache java_Java开发中的Memcache原理及实现
  10. 身份证号码(最后一位)计算。(使用 Excel 公式,计算 身份证最后一位)
  11. 【leetcode】523. Continuous Subarray Sum
  12. leetcode/力扣 二叉树题目大总结,BAT程序员完整学习手册PDF开放下载!
  13. JavaScript - 理解面向对象编程
  14. Inno Setup 系列之自定义卸载文件名称的脚本
  15. win10计算机丢失msvcr,Win10计算机丢失MSVCR120.dll怎么解决
  16. ArcGIS影像多种裁剪方法
  17. django 使用mixins时报HTTP方法patch method not allow
  18. foxmail 163企业邮箱配置
  19. IDEA一直在indexing的解决方案
  20. WEB 主机安全防护(Fail2ban + firewalld)_防止渗透猜解

热门文章

  1. ARM的UART实验
  2. java用栈处理四则运算_Java 用栈处理四则运算
  3. 不卷学术了,这次卷一波NLP实战落地经验
  4. 论文浅尝 | 一种可解释的语义匹配复值网络
  5. MongoDB:SpringBoot有关@Document(collection = )与@Document(collation= )注解的区别
  6. WeStrom自定义设置修改快捷键
  7. Codeforces Round #462 (Div. 2), problem: (C) A Twisty Movement (求可以转一次区间的不递增子序列元素只有1,2)...
  8. js构造函数内存在的闭包
  9. 性能测试小总结(四) 结果分析(未完成)
  10. redis 字符串数据(string)