Mathematica Plot对象解析
引言
从一个简单的问题谈起。怎么样画这么一个图像?
恩,Rotate一下应该就可以了
Rotate[Plot[Sin[x], {x, 0, 4 \[Pi]}], 90 Degree]
但是仔细观察就会发现一个问题了,x轴是反的。怎么办呢?
想到的一个方法是Scale,把x轴Scale到-1倍就可以了。但列位试试,不论怎么把Scale作用到Plot上都是报错。以前也曾经试图把Plot对象和Circle,Point画在同一个Graphics里面,但是同样也失败了。这说明 Plot对象和Point, Line 这些 Graphics Primitives很不同, 因此我们有必要研究一下Plot对象的结构。
探索
首先使用TreeForm查看一个简单的 Plot[Sin[x],{x,-1,1}]
不难发现结果是以Graphics作为根的,这个很容易理解,毕竟Plot出来的结果既含有坐标轴,又含有标签,还有数据图像,肯定是个复杂的Graphics对象,在帮助里也说得很明确,“Plot normally returns Graphics[{Line[...],...}]. ”。因此,我们就不能够把Plot结果像Disk,Arrow一样插入到Graphics中,因为它自己本身就是Graphics对象。同时,针对graphics primitive的函数也都不能直接作用在Plot结果上,比如上面提到的Scale。
往下看,Graphics包含了两个List,前者包含着很多Primitive对象,后者是属性列表,这和我们平时生成Graphics的方法是一致的,因为它本来就是Graphics嘛。
如上图,我们只看第一个List。它本身也只包含一个List。再往下一层就可以看到我们的图线了,处于第三层的List包含了Hue和Line两个元素。我们试着同时绘制两条图线,TreeForm@Plot[{x, Sin[x]}, {x, -1, 1}],会发现新添加的图线也是处于第三层,第二层依然是只有一个孤零零的List。因此我们就可以知道,为了得到 Plot的图线数据,只需要把第3层的Line集合提取出来就行了。
Plot[{Sin[x], Cos[x]}, {x, 0, 2 \[Pi]}][[1, 1]] // Cases[#, Line[___], Infinity] &
使用 Cases, 我们可以提取出以Line开头的子表达式。
一鼓作气
至此,我们基本了解Plot结果的大致结构了,Plot可以看作是一种特殊的Graphics求值器,先根据函数式求出散点序列,转换为Line对象,然后填充到一个Graphics对象里。Plot的那些Axes,Epilog,PlotRange属性,其实是来自于Graphics而已。如果想要把Plot和Circle放到一起,就不能够想当然的把Plot放到Graphics里面了,因为Graphics是不支持嵌套的,可行方法有以下几种
Plot[Sin[x], {x, -1, 1}, Epilog -> Circle[{0, 0}, 0.1]]
Show[Plot[Sin[x], {x, -1, 1}], Graphics[Circle[{0, 0}, 0.1]]]
Graphics[{Circle[{0, 0}, 0.1], Inset[Plot[Sin[x], {x, -1, 1}], {0, 0}, {0, 0}, {1, 1}]}]
如果只是想获得Plot图形的数据,以便进一步处理,直接一层层剥皮即可
Plot[Sin[x], {x, 0, 4 \[Pi]}] // First // First // Last // Last
或者使用模式匹配
Plot[Sin[x], {x, 0, 4 \[Pi]}] // Cases[#, Line[___], Infinity] &
功德圆满
现在再来画文章开头展示的图片是不是就很简单了?
a = Plot[Sin[x], {x, -4 \[Pi], 4 \[Pi]}] // Cases[#, Line[___], Infinity] &; Graphics[{Blue, Thick, Rotate[a, 90 Degree, {0, 0}]}, Axes -> True, Ticks -> {{-\[Pi], \[Pi]}, Range[-4 \[Pi], 4 \[Pi], \[Pi]]}, PlotRange -> {{-\[Pi], \[Pi]}, Automatic}]