【程序设计】12.2 [图画师] 坐标系

 

12.2 坐标系

要画图,就要先建立坐标系的概念。我们在中学都学过一点平面解析几何和直角坐标系的知识,知道平面上的点就都可以用坐标来表示。计算机绘图也是这样,通常会使用直角坐标系来确定位置。

和我们习惯的坐标系不太一样的是,画布坐标系的原点在左上角,它的 Y 轴方向是从上向下的,整个画布都在它的第一象限中。

我们来用这个坐标系尝试画几个点:


A

B

1

[50,100,150,200,250]

2

=canvas()

3

=A2.plot("Dot","data1":300,"data2":50)

4

=A2.plot("Dot","data1":A1,"data2":A1)

5

=A2.draw(400,300)

=A2.draw(600,400)

画出这样的结果:

imagepng

显然,右上那个点是 A3 画的,左边的能构成斜线的一排点是 A4 画的。为什么这一排点会画出不同颜色的,我们以后再解释。

plot 函数可以一次画一个单点,也可以一次画多个点,只要把坐标值序列给出来就可以。Data1 和 Data2 就分别对应 X 轴和 Y 轴,注意如果是画多个点,两个坐标序列要一样长。

有兴趣的读者可以尝试用 plot 编辑界面改变一些外观,比如点的形状和颜色。

坐标值是以像素为单位的,所以在 A5 和 B5 画出来的结果是一样的,都在整个画布的左上部分,而且大小都一样。但是我们经常希望图形能随着尺寸变化(就像上一节的成品),用像素作为坐标单位就不合适了。

SPL 还提供了相对坐标。如果坐标值小于 1,则被认为是相对于画布的比例,也就是认为整个画布的坐标范围在 [0,1] 区间内。我们来试一下。


A

B

1

=10.((~-1)*0.1)

=A1.(~*~)

2

=canvas()


3

=A2.plot("Line","data1":A1,"data2":B1)

4

=A2.draw(400,300)

=A2.draw(600,400)

现在它能画出半截抛物线,而 B4 比 A4 的大了一圈,实现了按比例缩放。

这次用了线来绘制,图中的点被连接起来了,可以自己改变图元的各种外观属性,选择不画出点也可以。

理论上,有了这个坐标体系,我们就能画出任何图形了。通常,不是专业图形用途的程序设计语言能提供的绘图机制也确实就到此为止了(当然都还会有一些函数来完成复杂的组合动作)。不过,这显然还不太方便,有时候画图位置并不正好在 [0,1] 区间或画布的像素范围内,那就需自己做些变换才行,虽然 SPL 的计算能力超强,完成这些事不在话下,但仍然有点麻烦。

而且,前面的成品图中,我们在代码中向图元传递的参数直接就是地区名称和金额数值,它也画得挺好的,自己画图也能这样就方便多了。

使用像素坐标和相对坐标来画图时,我们使用的是画布本来就有的一套坐标系,称为物理坐标。SPL 允许编程人员用自己的数据作为坐标值来画图,这需要建立逻辑坐标系。

我们知道,直角坐标系总是有两个轴:横轴和纵轴。只要轴确定了,坐标系也就确定了。SPL 就是有这种办法来建立逻辑坐标系的。我们来看图元编辑对框右上那个图元列表,第一部分就是轴,有三种:

imagepng

我们选择第一种数值轴,看它的属性,重点是这几个:

imagepng

imagepng

左边的三个属性表明,它是个横轴,取值范围是 [-3,3](缺省值不是这个,这是可以修改的属性,包括上面两个勾选的条目,缺省是勾上的)。右边则是定义这个轴实际上的位置,因为定义轴的时候只有物理坐标,所以只能用物理坐标来刻画它的位置了,这些 0.1、0.9 和 0.5 就是前面说的相对坐标(这些也不是缺省值)。

用图元编辑界面在代码中增加一个图元,就用上面图中的属性值,未列出的属性都用缺省值。注意还要给这个轴起个名字(属性表的第一行),这里就叫 x 了。

类似地,再加一根纵轴,起名为 y,也把范围定成 [-3,3],并把范围上面两个勾选项目勾掉。轴的位置填成是 0.9、0.1、0.5。注意纵轴位置要填到右图下半部纵轴的属性中,而且起始是 0.9,终止是 0.1,从大到小,这样就可以把 y 轴做成从下向上的,符合我们的习惯。

生成的代码后再加一句 draw:


A

B

1

=canvas()


2

=A1.plot("NumericAxis","name":"x","autoCalcValueRange":false,"autoRangeFromZero":false,"maxValue":3,"minValue":-3,"xEnd":0.9,"xPosition":0.5)

3

=A1.plot("NumericAxis","name":"y","location":2,"autoCalcValueRange":false,"autoRangeFromZero":false,"maxValue":3,"minValue":-3,"yStart":0.9,"yEnd":0.1,"yPosition":0.5)

4

=A1.draw(600,400)


执行一下看看:

imagepng

果然画出两根轴。可以自己设置轴的各种属性控制是否显示刻度以及箭头等等。

现在我们来用这两个轴确定的坐标系画图,继续在这个代码中写:


A

B

1

=canvas()

=60.((~-1)*6*pi()/180)

4

=B1.(2*cos(~)-cos(2*~))

=B1.(2*sin(~)-sin(2*~))

5

=A1.plot("Line","axis1":"x","data1":A4,"axis2":"y","data2":B4)

6

=A1.draw(600,400)

其中 A5 使用的是这几个属性:

imagepng

这表示,A5 画的图元用名叫 x 和 y 的这两个轴来定位,要画的点的坐标在 A4 和 B4 中。

我们成功地画出来这个:

imagepng

SPL 的图形主要是为了绘制针对结构化数据的统计图,这些图元的缺省属性并不单纯,比如点会有大小,能看得很明显。轴一般都会有刻度,画坐标系时缺省会有条纹。这些都可以通过修改属性来调整,但改成非缺省属性后,plot 函数的参数会更长,不方便列在书中,所以我们尽量使用缺省属性,但用来画函数图就略有点怪了。不过,这不影响理解知识点。

这些我们自己定义出来的轴称为逻辑轴,而画布原本的那两个轴称为物理轴。画图元时图两个轴就意味着将使用这两个轴建立的逻辑坐标系,然后就可以用我们更方便的数值作为坐标值了。如果没有选择逻辑轴,则意味着将使用物理坐标,像本节开始的那个图。

逻辑轴的本质是把我们熟悉的业务数值映射成物理轴的坐标值,把轴定义好之后,图元绘制时使用业务数值,由逻辑轴负责找到正确位置,修改逻辑轴和物理轴的映射关系后,图元绘制代码可以不必改动,图形也会以另一种方式画出来了。

比如,我们可以把 y 轴向上移一点,让它的起止位置改从 0.5 到 0.1,然后同时尝试一下让 SPL 自动寻找坐标轴的范围:


A

B

1

2

=A1.plot("NumericAxis","name":"x","autoRangeFromZero":false,"xEnd":0.9,"xPosition":0.5)

3

=A1.plot("NumericAxis","name":"y","location":2,"autoRangeFromZero":false,"yStart":0.5,"yPosition":0.5)

让 SPL 自动计算坐标轴范围,要把两个轴取值范围上面那两个勾选属性的第一个勾上,第二个仍然不要勾上,取值范围就可以不填了。

现在画成这样了,只在画布的上半部分有图形。

imagepng

SPL 提供的逻辑轴有三种:数值轴、枚举轴和日期轴。这是按业务数据的数据类型来划分的,业务数据是数值时就用数值轴,是序列成员时就用枚举轴,是日期类型时则用日期轴。

在前面的成品图中就使用了枚举轴,地区取值都在一个序列中,这时候我们可以要求 SPL 在横坐标为 East,纵坐标为 10 的位置上画一个柱子,至于 East 具体会对应画布的哪个位置,则由逻辑轴去解决,编写绘图代码时只要关心它的业务意义就行了。

我们来尝试用逻辑轴的办法重画前面的柱图。


A

B

1

[10,20,40,30,50]

[East,North,West,South,Center]

2

=canvas()

3

=A2.plot("EnumAxis","name":"area")

4

=A2.plot("NumericAxis","name":"amount","location":2)

5

=A2.plot("Column","axis1":"area","data1":B1,"axis2":"amount","data2":A1)

6

=A2.draw(600,400)

A3 创建了枚举轴作为横轴,起名 area,其它属性都用缺省的;A4 用了个常规的数值轴作为纵轴,起名 amount,也用缺省属性。然后 A5 用 A1 和 B1 的数据在这两个轴建立的逻辑坐标系中画图。

imagepng

还有个问题,现在这几个柱子从左到右的次序就是 B2 中数据的次序,如果我们希望换个次序呢,或者还有些地区并没有在业务数据出现,但仍然希望在图出有个位置,这是不是必须去修改 B1 中的业务数据?

不应该这样,如何找到物理位置应该是轴的任务,和业务数据无关。

代码改一下:


A

B

1

[10,20,40,30]

[East,North,West,South]

2

=canvas()

[East,North,Center,West,South]

3

=A2.plot("EnumAxis","name":"area","categories":B2)

我们故意把 A1 和 B1 的数据少填一个,但 B2 中有完整的地区序列,在 A3 建立枚举轴时把它的“分类”属性填成 B2,这样重新画一遍:

imagepng

如何找到物理位置确实是轴的任务,和用来绘图的数据没有关系,只是在没有填写轴的相应属性时,SPL 会自动给填一个。

总结一下,SPL 通过逻辑轴实现业务数据到物理位置的变换,明白了这个原理,日期轴也很容易就理解了(无非就是把日期转换成位置),再去研究这些逻辑轴图元的属性也很容易理解,比如数值轴上还可以做对数和指数变换。这些内容不再涉及知识点,只是具体的语法和编辑方案,需要时可参阅 SPL 资料。

【程序设计】 前言及目录

【程序设计】12.1 [图画师] 画布与图元

【程序设计】12.3 [图画师] 更多坐标系