4.4.2.3 用函数绘图

来源:互联网 发布:淘宝详情分割线 编辑:程序博客网 时间:2024/05/21 19:24

4.4.2.3 用函数绘图

 

DrawStep 函数的第一个参数是两个绘图函数中的一个,我们暂时先给这个绘图函数的类型命名为 DrawingFunc,以后再定义。在讨论其余的参数之前,我们先看一下这个函数的签名:

 

drawStep : (DrawingFunc * Graphics * float* (string * int) list) -> unit

 

我们再次使用元组语法来指定参数,因此,函数的参数为一个大元组;第二个参数是用来绘图的Graphics 对象,它会传递给绘图函数;接下来的两个参数描述绘图用的数据集,一个 float 值是所有数值的和,这样,就可以计算出饼图每个部分的角度,另一个 (string * int) list 类型的值,是我们熟悉的数据集,来自控制台应用程序,保存了绘图用的每一项的标签和值。

我们看一下 DrawingFunc 类型,它和清单 4.8 中的 drawPieSegment 函数有相同的签名;第二个绘图函数是 drawLabel,马上就会发现,它也有完全相同的签名。我们可以看看这个签名,声明的 DrawingFunc 类型,与这个两函数的类型完全相同:

 

drawPieSegment : (Graphics * string * int *int) -> unit

drawLabel : (Graphics * string * int * int)-> unit

type DrawingFunc = (Graphics * string * int* int) -> unit

 

最后一行是类型声明,声明了一个类型别名(type alias),这样,我们就为复杂类型指定一个名字,可以换一种方式来写。我们只在这个解释中使用 DrawingFunc 的名字,但实际可以在类型批注中使用,比如,我们想要引导类型推断,或都使代码更具可读性。

正如我们前面所说的,不需要在代码中写出这些类型,但是,定出来能帮助我们理解代码的功能。最重要的是,我们已知道 drawStep 函数第一个参数为绘图函数。清单 4.9 是 drawStep 函数的代码。

 

清单 4.9 使用指定的绘图函数绘制所有项 (F#)

let drawStep(drawingFunc, gr:Graphics, sum,data) =

  letrec drawStepUtil(data, angleSoFar) =   [1]

  matchdata with

  |[] –> ()      [2]

  |[title, value] –>  [3]

    letangle = 360 – angleSoFar  <-- 计算角度到 360 度

    drawingFunc(gr,title, angleSoFar, angle)

  |(title, value)::tail –>  [4]

    letangle = int(float(value) / sum * 360.0)

    drawingFunc(gr,title, angleSoFar, angle)

    drawStepUtil(tail,angleSoFar + angle)    <--  递归绘制其余部分

  drawStepUtil(data,0)    <-- 运行工具函数

 

为使代码更具可读性,我们把这个函数实现为嵌套函数[1],它遍历应该画在图表上的所有项。这些项保存在标准的 F# 列表中,因此,代码很像我们熟悉的列表处理模式。有一个显著的区别,因为这个列表匹配三种模式,而不是通常的两种情况的匹配,一个空列表和一个 cons cell。

模式匹配的第一个分支[2],匹配空列表,且不执行任何动作。我们已经知道,在 F# 中“什么也不做”用unit 值表示,因此,返回 unit 的代码,写作 ()。这是因为 F# 把每个构造都看作表达式,且表达式必须有返回值。如果空列表的分支为空,就不是有效的表达式。

第二个分支[3]的列表处理代码不同平常。可以看到,这个分支中所使用的模式是 [title, value]。这是一个由两个模式组成的嵌套模式,一个模式匹配只包含一个项目 [it] 的列表,另一个模式匹配包含两个元素(title, value) 的元组项,简写的语法为 [(title, value)],但它们意思相同。第一个模式使用通常的语法创建列表,因此,如果要写匹配有三项列表的模式,可以写成 [a; b; c]。我们包括了这种特殊情况,因为我们想要纠正舍入误差:即,处理列表最后一项时,要确保总角度正好是 360 度。在此分支中,我们只计算角度,然后调用 drawingFunc 函数时,把它作为参数值。

最后一个分支处理的列表不匹配前面的两种模式。在这里,模式的顺序非常重要,因为任何匹配第二种模式[3]的列表也能匹配最后一种模式[4],只是列表的尾为空而已。在代码中模式的顺序保证最后一项不会调用最后的分支。

最后分支的代码计算角度,并使用专门的绘图函数绘制饼图的一部分。这是递归处理列表的唯一分支,因为它一直使用到列表中只有最后一个元素为止,所以,代码的最后一行是递归调用。在递归过程中唯一改变的参数值,是列表中要绘制的剩余元素,和 angleSoFar,这是所有已处理的饼图部分占的角度。由于使用了局部函数,我们不需要传递不会改变的其它参数值。drawStep 函数本身只做一件事是:调用工具函数,用所有数据,把参数 angleSoFar 设置为 0。

 

绘制整个图表

 

0 0
原创粉丝点击