第十三章 简易家庭帐本——年度收支汇总
在上一章,我们利用表格的画线和写字功能,实现了动态数据表格的功能,可以用表格显示动态数据、表格翻页以及选中表格中的数据项,等等。在这一章,我们仍然利用画布组件的画线及写字功能,实现家庭帐本的另一个功能——收支信息的统计汇总。
什么是统计汇总?汇总是一种对数据的宏观描述,是对总量的计算,与统计汇总相关的因素包括时间以及分类。与查询功能中的时间因素不同(可以随意设定起止日期),为了让统计结果具有可比性,这里的时间通常为某个固定的时间段,比如,以自然年或自然月为基准,统计收入及支出的总额。帐本应用中采用了年度及月度两种时间尺度进行统计汇总。统计汇总中的分类因素包括对数据的各种分类方式,在帐本应用中,参与统计汇总的分类包括收入类别、支出一级分类、收入者以及支出专项。将以上时间及分类因素进行排列组合,帐本应用将实现以下统计汇总功能,如表13- 1所示。本章的目标是实现“年度收支汇总”功能,其余功能将在下一章实现。
表13- 1 组合时间与分类因素,定义统计汇总功能
第一节 功能描述
虽然本章只实现一项功能,但考虑到各项功能之间的相关性,这里依然完整地描述全部七项功能:
- 入口:用户在导航菜单页选择“收支汇总”项,进入收支汇总页面,并打开汇总项目列表,其中包含了7个选项,如图13- 1所示;
- 出口:用户点击“返回主菜单”按钮,关闭当前页面,返回导航菜单页;
- 选择汇总项目:在汇总项目列表中选择一项;
- 收支年度汇总:
- 按月汇总:用户选择汇总年份后,以表格方式逐月汇总并显示该年度的收入、支出及盈余合计,每个月份占一行,共12行;
- 显示年度合计:在表格的底部(表脚)汇总并显示年度收入、支出及盈余的合计值;
- 汇总信息的默认显示方式为表格,用户也可以查看汇总信息的图表显示方式,年度汇总信息的图表为两条折线,分别为收入线及支出线;
- 年度收入分类汇总
- 用户选择年份后,默认以表格方式显示该年度各类收入的合计以及年度收入的总和;
- 用户可以选择以图表方式显示汇总结果,分类汇总的图表为饼状图;
- 年度个人收入汇总
- 用户选择年份后,以表格方式显示该年度每个家庭成员的收入合计(收入为0的不予显示)以及全体成员年度收入的总和;
- 用户可以选择以图表方式显示汇总结果,分类汇总的图表为饼状图;
- 年度支出分类汇总
- 用户选择年份后,以表格方式显示该年度各个一级分类的支出合计以及年度支出的总和;
- 用户可以选择以图表方式显示汇总结果,分类汇总的图表为饼状图;
- 年度专项支出汇总:用户选择年份后,以表格方式显示该年度各个专项的支出合计;
- 月度收入分类汇总:用户选择年份及月份后,以表格方式显示该月各类收入的合计以及月收入的总和;
- 月度支出分类汇总:用户选择年份及月份后,以表格方式显示该月各一级分类支出的合计以及月度支出的总和。
第二节 数据模型
一、表格数据
如图13- 2所示,年度收支汇总的绘图数据为二级列表,第1级列表中包含14项,第1项为表头,中间12项为月收入、支出及盈余合计,第14项为年度收入、支出及盈余的总计,对应于表格中的“表脚”(或表尾);第2级列表中包含4个列表项,分别为月份、收入合计、支出合计以及盈余合计。
二、折线图数据
我们这里先假设绘制折线图的画布高度为240像素,宽度为320像素,为了绘制年度收支汇总数据的折线图,我们需要对真实的数据进行成比例的缩放,并根据数据中的最大值,来设置坐标轴的单位。
首先来看一组假想的数据,如表13- 2所示,按照表中数据绘制的折线图如图13- 3所示。
表13- 2 一组假想的年度收支数据
首先我们要找到所有数值中的最大值,然后判断最大值的最高位(数值N)所在的区间;这个值要么位于0~5区间(0<N<5),要么位于5~10区间(5≤N≤9),表13- 2中的最大值为9900,最高位为千位,数值为9,位于5~10区间。如果最大值位于0~5区间,则y坐标的最大值为510n-1(n为最大值的位数,下同);如果最大值位于5~10区间,则y坐标的最大值Y=110n。对于例子中的数据来说,y轴坐标的最大值为10000;假设数据中的最大值为4999,则y轴坐标的最大值Y=5000。这样设置的目的是为了最大限度地利用画布的绘图空间,同时便于对数据的缩放处理。
接下来,考虑绘制坐标轴、参考线以及标注数字。如图13- 3所示,图中给出了坐标轴距离画布边界的距离、原点坐标以及标注文字的坐标计算方法(图中的比例并非严格与数据匹配),这些数据将以列表的方式保存在全局变量中。
最后,我们来确定绘图点的坐标(x,y)。每个绘图点的x坐标与月份有关,x=50+(月份-1)20,当月份为1时,x=50。Y坐标与表格中的实际值(v)有关,根据实际值可以求出绘图点高度h(绘图点距离x轴的垂直距离),公式为h=v200/Y,以表格中1月份收入值3500为例,绘图点高度h=3500*200/10000,计算结果为h=70;再将h换算成y坐标,公式为y=画布高度-50-h,这里假设画布高度为240,因此y= 240 - 40 – 70 =200 – 70 = 130。表13- 2中的数据经过折算后的结果如表13- 3所示。
表13- 3 将实际值折算成坐标值——折线图绘制数据
以上关于绘制折线图的说明,是基于一组假想的数据,在实际程序运行过程中,数据来自于数据库。
三、全局变量
1、固定数值类
- 行高:绘制表格时每一行的高度,单位为像素;
- 文字基准Y:在画布上写字时,文字的基准点——文字底部距离行顶边的距离;
- 绘图区高度:在画布上绘制折线图时,折线所覆盖的最大高度;
2、动态数值类(无)
3、静态列表类
- X坐标:绘制数据表格时,每一列文字的中心点坐标,列表长度为4(对应4列数据);
- 行颜色:键值对列表,长度为4,绘制数据表格时,表头背景、奇数行背景、偶数行背景及表头文字的颜色;
- 图表颜色:键值对列表,列表长度为4,绘制折线图时,坐标轴、收入线、支出线及标注文字的颜色;
- 折线图参数:键值对列表,列表长度为11,包含4级列表,是绘制折线图坐标轴及标注文字的坐标或坐标对。
4、动态类表类
- 收入全集:从数据库中读取的全部收入记录;
- 支出全集:从数据库中读出的全部支出记录;
- 年度收支汇总:绘制年度收支数据表格及折线图的绘图数据,为二级列表,包含14个列表项,分别为表头、表脚及12个月的收支及盈余数据;
第三节 技术准备——绘制折线图
我们将继续利用上一节中的假想数据,来讲解折线图的绘制方法。为了减少正式帐本项目中的代码量,我们新建一个临时项目——折线图,其中有一个画布组件,宽为充满,高为240像素;另有两个按钮组件,一个用于绘制图表,另一个用于清空画布,如图13- 4所示。
切换到编程视图,创建一个全局变量——年度收支汇总,将上一节中的假想数据用列表的方式组织起来,如图13- 5所示(注意这组数据没有表头及表脚)。
利用上述组件及数据,我们来绘制折线图,首先绘制坐标轴、参考线以及标注文字,然后再绘制表现数据的折线。
一、绘制坐标轴
我们将图13- 3中标注的坐标值保存到全局变量“折线图参数”中,如图13- 6所示,该变量为键值对列表,其中的键为线的名称(第1~9项)或坐标的说明(最后两项),值为所绘线段的起点及终点坐标(第1~9项)或文字基准点的坐标(最后两项)。对于固定大小的画布来说,这些坐标值是固定不变的。
在绘制折线图时,需要使用不同的画笔颜色来画线或写字,我们将颜色保存到全局变量“颜色”中,以便在绘图过程中使用,如图13- 7所示。
根据以上数据,创建一个“画线”过程,如图13- 8所示,该过程有一个 “名称”参数,这个名称指的是键值对列表“折线图参数”中的“键”,通过“键”来读取对应线段的起点及终点坐标。
再创建一个“绘制坐标轴”过程,通过调用“画线”过程,来绘制坐标轴以及标记线——五条水平标记线以及x轴上的12个垂直标记线,代码如图13- 9所示。
在屏幕初始化程序中调用“绘制坐标轴”,代码如图13- 10所示,测试结果如图13- 11所示。
二、绘制图例
图例中包括两个线条及两组文字,线条的颜色与绘制折线图的颜色相一致,即,收入线条为蓝色,支出线条为暗红色(如图13- 7所示),两组文字分别为“收入”及“支出”。我们定义一个“绘制图例”过程来实现画线及写字操作,代码如图13- 12所示。
注意到在绘制坐标轴及绘制图例时,使用了不同的画笔线宽:前者线宽为1像素,后者为3像素,稍后测试时,你将看到这两者的差异。
三、写标注文字
标注文字指的是x轴及y轴上单位标记处的文字,其中x轴的标注文字很简单,就是代表月份的12个数字,y轴的标注文字的内容需要根据实际数据来确定。我们着重解决y轴标注文字的问题。
y轴标注文字取决于实际数据中的最大值,其中包含两个因素:
- 最大值的位数,用n表示,如9012的位数为4位,即n=4;
- 最大值的最高位的值,简称“高位值”,用m表示,如9012的最高位是千位,它的值是9,即,m=9。
如前所述,高位值m要么位于0~5之间,要么位于5~10之间,根据高位值m可以确定y轴标注数字中的最大值:
- 当高位值位于0~5区间时,严格地说,当0<m<5时,最大标注数字的最高位为5;
- 当高位值位于5~10区间时,即,当5≤m≤9时,最大标注数字的最高为为1。
实际数据中的最大值的位数n将决定最大标注数字的位数,其公式为:
- 当0<m<5时,最大标注数字=5*10n-1;
- 当5≤m≤9时,最大标注数字=1*10n。
根据以上分析,我们创建一个有返回值的过程——“高位与位数”过程,求收支汇总数据中的最大值,并返回最大值的高位值及位数,代码如图13- 13所示。
现在创建“写标注文字”过程,来写x、y轴的标注文字,代码如图13- 14所示。
注意上图中第二个循环语句的循环初值,不是1而是0;最后一行代码可以简写为“205-30*数”,但是为了说明这个值的计算方法,代码中保留了完整的运算公式。在屏幕初始化时调用上述过程,来绘制完整的坐标,如图13- 15所示,测试结果如图13- 16所示。
四、绘制折线图
绘制折线图的第一步就是要将收支数据的实际值(用v表示)换算成最终的画布坐标(像素值)。App Inventor的画布坐标不同于数学中的坐标,在实际值与画布坐标之间,还有一个过渡的量——高度(用h表示),这是绘图点到x轴的垂直距离,也就是数学概念上的y坐标。我们首先要将实际值转换为高度,然后再由高度转换为画布坐标,其中涉及两个换算公式:
- 高度h = 实际值v ×(画布绘图区高度÷y轴的最大标注值)
- y坐标 = 200 – 高度h
所谓绘图区高度指的是x轴到y轴最大标注值之间的像素数,以上两个公式是我们做数值转换的基础,我们先来求y轴的最大标注值,创建有返回值的过程——y轴最大标注,代码如图13- 17所示。
读者可以自己用一个实际的数(例如4999及9999)带到上述过程里,计算一下结果,看结果是否正确。
根据上面的公式(1),我们创建一个有返回值的过程——数值转高度,代码如图13- 18所示。这里我们声明了一个全局变量——绘图区高度,并设其值为150(像素),也许在开发过程中我们会修改绘图区的高度,因此,我们在代码中不直接使用具体数值,而是用变量代替数值,以便于对程序的修改。
下面将实际值转换为绘图坐标——画布坐标,创建有返回值的过程——绘图y坐标,该过程返回一个二级列表,其中包含2个列表项,第一个列表项为收入数据对应的y坐标,第2项为支出数据的y坐标。代码如图13- 19所示。
根据坐标列表,我们可以绘制两条折线,由于两条折线的数据格式及绘图方法相同,因此我们创建一个过程——绘制折线,该过程有一个 “数据列表”参数,它可以是图13- 19中的收入坐标列表,也可以是支出坐标列表。代码如图13- 20所示。
利用上述过程,创建“绘制折线图”过程,实现完整地绘图功能,包括清除画布、绘制坐标轴、写标注文字、绘制图例以及绘制两条折线,代码如图13- 21所示;然后在绘图按钮的点击事件中调用该过程。代码的测试结果如图13- 22所示。
以上是绘制折线图的相关程序,目前绘图功能并不完备,缺少对最低数值的判断,例如,上图中2000以下没有数据,我们应该让2000的标记线与x轴重合,这样绘图空间加大,有利于增加图表的表现力。有兴趣的读者可以自行修改程序,增加对最低数值的判断及处理。
此外,折线图中没有对数据点进行标记,像样例图中那样,用菱形或方形来突出数据点的位置。
第四节 用户界面设计
用户界面设计更准确地说应该是“组件设计”,因为屏幕中还有若干个非可视组件。设计视图中的收支汇总屏幕(SUMMARY)如图13- 23所示,组件的命名及属性设置如表13- 4所示。
表13- 4 组件的命名及属性设置
注意表中的月份选框,默认情况下不显示该组件,只有用户选择了月度收入/支出分类汇总时,才设置改下拉框为可见。
第五节 页面逻辑
一、屏幕初始化
收支汇总屏幕中共有三个列表类组件——汇总项目选框、年份选框及月份选框,在屏幕初始化时,需要设置这三个组件的列表属性:
1、数据绑定
(1) 汇总项目选框:如图13- 24所示;
(2) 年份选框:如果当前年度为2016年,则列表中包含2项——2015、2016,否则包含从2016到当前年的选项,即,如果当前年为2018年,则列表中包含2016、2017及2018三项;
(3) 月份选框:包含从1至12的12个选项。
2、加载数据全集
从数据库中读取收入记录与支出记录,分别保存到全局变量“收入全集”与“支出全集”中。
3、打开汇总项目选框
打开汇总项目选框,用户可以直接从中选择将要统计汇总的项目。
二、选中汇总项目
当用户从汇总项目选框中选择了某一项时,设汇总项目选框的显示文本属性为该选框的选中项;根据选框的选中项索引值,设置月份选框的允许显示属性:当索引值小于等于5时,隐藏月份选框,否则,显示月份选框。
三、数据筛选与汇总
当用户点击汇总按钮时,如果按钮的显示文本为“汇总”,则根据汇总条件,对数据进行筛选及汇总,并将汇总后的数据用表格的方式显示出来,并设汇总按钮的显示文本为“显示图表”;如果按钮的显示文本为“显示图表”,则根据不同的汇总项目,显示对应的折线图或饼状图,并设汇总按钮的显示文本为“显示数据”。
四、返回主菜单
当用户点击返回按钮时,关闭当前屏幕,应用将返回到导航菜单页。
第六节 编写程序——创建过程
一、可以充当变量的过程——有返回值过程
当屏幕初始化时,需要为列表类组件设置列表属性,这些属性值可以从下列过程的返回值中获得,它们分别是汇总项、年份及月份。
1、汇总项
如图13- 25所示,该过程的返回值为列表,其中包含7个列表项,这些内容将要显示在汇总项目选框中。
2、年份
如图13- 26所示,年份过程的返回值为列表,由于应用的创建时间是2016年,因此我们假设用户输入的数据从2016年开始,到当前年份为止。
3、月份
如图13- 27所示,月份的返回值为列表,其中包含12个列表项,分别为从1至12的数字,与月份相对应。
4、年、月
在收入及支出记录中,日期数据是以毫秒的方式保存的,为了方便对数据的筛选,我们创建两个有返回值的过程——年、月,从日期的毫秒数中提取年、月信息,代码如图13- 28所示。
5、年度记录
如图13- 29所示,年度记录过程返回一个二级列表,其中一级列表中包含12个列表项,分别为1~12月份的收入或支出记录;二级列表中包含所有选中年某月的全部收入或支出记录。年度记录过程的返回值(列表)是我们计算年度汇总数据的依据。过程中的“选中年”参数等于年份选框的选中项,“全集”参数为收入全集或支出全集。
6、月累计
在年度记录中,每个列表项都是某一个月的全部收入或支出记录,我们创建一个“月累计”过程,对月数据中的金额一项进行累加,代码如图13- 30所示,该过程的返回值为收入或支出的月合计值。
6、绘图数据_收支汇总
如图13- 31所示,用循环语句对年度收入、支出记录进行遍历,通过调用“月累计”过程,对年度数据逐月求和,并按照图13- 2中的顺序将计算结果按月添加到局部变量“年度汇总”列表中;在对月份的遍历完成之后,再将年度的总收入、总支出及总盈余添加到年度汇总列表中,最后,返回年度汇总列表。
以上是与年度收支汇总相关的有返回值过程,这些过程实现了对数据的整理,下面的过程与表格及图表的绘制有关。
二、与绘图相关的过程——无返回值过程
我们需要借用上一章中绘制表格的思路,对其中的部分过程进行改造,如绘制单行背景、写单行文字等,另外还须添加一些过程,如x坐标、绘制表脚等。
统计汇总功能中的表格采用统一的格式,均为四列表格,表格行数依数据项而定,行高26,背景宽25,表格占满画布的宽度。
1、表头字x坐标
我们将屏幕宽度等分为8份,则第1、3、5、7个等分点处,就是文字的中心点,将这几个点的x坐标保存在列表中,返回给调用者,过程代码如图13- 32所示。
2、绘制表头
绘制表头包括绘制表头背景及写表头文字,表头文字由过程的参数提供,该参数包含4个列表项。代码如图13- 33所示。
3、绘制单行背景
与上一章不同的是,本章的绘图数据中包含表头,因此数据行“行号”的起始值为2,因此y坐标的计算公式中,将上一章的“行号”改为“行号-1”,过程代码如图13- 34所示。
4、写单行文字
与绘制单行背景过程一样,对y坐标的计算需要将上一章的“行号”改为“行号-1”,同时,表格文字的来源由参数“文字列表”提供,该列表包含4个列表项,过程代码如图13- 35所示。
5、绘制表脚
与绘制表头过程相类似,采用表头的颜色绘制背景,表脚文字由参数“文字列表”提供,过程代码如图13- 36所示。细心的读者可以发现,其实绘制表头及绘制表脚这两个过程完全可以合并为一个过程,行号=1时的绘制表脚过程,与绘制表头过程完全相同。读者可以自行归并这两个过程。
6、绘制数据行
如图13- 37所示,绘制数据行过程只是简单地调用“绘制单行背景”与“写单行文字”两个过程,这样的过程仅仅是出于程序结构的考虑,并不能提高代码的复用性。
7、绘制表格
如图13- 38所示,绘制表格过程是一个集大成者,参数“制表数据”为二级列表,不同的汇总功能对应的列表长度不尽相同,对于年度收支汇总功能来说,第1级列表中包含14个列表项;无论哪一种汇总功能,其第2级列表中都包含4个列表项。
以上为绘制表格的相关过程,下面是绘制折线图的相关过程,有些过程已经在“技术准备”一节中介绍过,这里只显示它们的折叠状态。
8、绘制图表相关的变量及过程
我们需要打开“折线图”项目,将图13- 39中的代码放到代码背包中,再回到帐本项目中,从代码背包中取出这些代码。由于折线图项目中的画布名称为“画布1”,而帐本项目中画布的名称为“画布”,因此复制过来的代码与画布相关的代码块会显示红色三角形“警告”,只要将“画布1”改为“画布”,警告信息就消失了。下述变量及过程的代码介于图13- 6至图13- 20之间(图13- 10、图13- 11、图13- 15及图13- 16除外),在此不再显示详细内容。
上述代码大部分都可以直接使用,但是需要关注与数据源相关的代码,因为在“技术准备”一节中,使用的数据源是一组假想的数据,虽然数据格式是相同的,但应用中的数据添加了表头及表脚,因此这里需要对部分程序进行改写,它们是“高位与位数”及“绘图y坐标”两个过程,修改后的代码如图13- 40及图13- 41所示。
第七节 编写程序——事件处理程序
一、屏幕初始化
如图13- 42所示,在屏幕初始化程序中,首先为列表类组件设置列表属性,然后设置X坐标列表,并从数据库中读取收入及支出记录,最后打开汇总项目选框。
二、汇总按钮点击程序
汇总按钮的显示文本属性具有“标记”作用,它的默认值为“汇总”,当用户点击“汇总”按钮时,应用将根据汇总项及汇总时段对数据进行筛选,并绘制表格,同时,将“汇总”改为“显示图表”;当用户点击“显示图表”时,应用将清空画布上的表格,并绘制根据数据的筛选结果绘制相应的图表(折线图或饼状图),同时,将“显示图表”修改为“显示数据”。为此,在汇总按钮的点击程序中,用到了条件语句,代码如图13- 43所示。
三、汇总项选择程序
当用户从“汇总项目选框”中选择一项后,设置该列表选择框的显示文本属性为选中项,并根据其选中项索引值,设置月份选框的允许显示属性,代码如图13- 44所示。这样的设置可以提示用户当前正在执行的统计汇总类型。
四、返回按钮点击程序
与此前几章相同,点击返回按钮后,应用将关闭当前屏幕,回到导航菜单页,代码如图13- 45所示。
第八节 测试与改进
一、测试
首先从汇总项目选框中选择第1项——年度收支汇总,然后从年份选框中选择2016,并点击“汇总”按钮,测试结果如图13- 46所示。
图中的数据来源于我们在收支查询(QUERY)屏幕中用程序生成(收入)或加载(支出)的数据,显然,收入与支出数据的差别太大——收入远远大于支出,这导致图中收入的折线图几乎紧贴坐标轴,这样的图表既不真实,也不美观。我们来修改一下收入数据,将收入发生的频次从每天一次降低到每6天一次,代码图13- 47图。重新生成的数据测试结果如图13- 48所示。
我们可以观察一下测试结果中的数据和图表,看它们之间是否一致。
二、改进
与图13- 3中的样例相比,我们的折线图似乎少了点什么,是的,在数据点处缺少一个突出的标记,样例图中用菱形和方向来标记数据点,我们试着用圆形和方形来标记,为此需要添加一个“绘制数据标记”过程,代码如图13- 49所示。
利用画布组件画圆及画线功能,在数据点处分别绘制半径为4的实心圆以及高为6、宽为4的矩形,作为数据点的标记。注意,在画完矩形之后,将画布的画笔线宽恢复到3,以保持绘制折线时画笔的宽度不变。上述过程的参数“收入线”的值为逻辑类型,绘制收入折线时,其值设为“真”,否则设为“假”。
在“绘制折线”过程里添加一个同样的逻辑类型参数“收入线”,并调用“绘制数据标记”过程,代码如图13- 50所示。注意,折线图中共有11条线段,但有12个数据点,因此,当占位变量“数”为12时,还需为最后一个数据点绘制标记。
最后,在“绘制折线图”过程里,为新增的“收入线”参数提供相应的值(真或假),代码如图13- 51所示。
上述改进的测试结果如图所示。
本章到这里结束,下一章讲解收支汇总的其余功能。