5.5 透视图决策分析方案
透视图决策分析方案是统计图决策分析方案的综合应用,这里是通过JSP技术和JFreeChart技术联合实现的,JFreeChart技术负责绘制统计图,JSP技术负责实现透视图的效果。
1.方案分析
利用透视图开发决策分析模块,能够为用户提供灵活的操作空间,例如该案例,可以由用户控制统计的商品及时间,用户在操作透视图时,它的可控流程如所图5.46显示。

图5.46 透视图统计模块的可控流程
从图5.46可以看出,在开发透视图时,图例字段和分类字段是受页字段影响的,每次修改页字段,图例字段和分类字段都会随之变化,例如本案例,每修改页字段月份,图例字段都会发生变化,只提供当月有销售记录的商品。
本案例的透视图效果是通过一个完全独立的JSP页实现的,负责绘制统计图的JFreeChart代码也放在了该页中,目的是增强代码的可移植性,在需要透视图统计方式的地方调用该JSP就可以了,本案例的透视图效果及关键实施方案如图5.47所示。

图5.47 本案例的透视图效果及关键实施方案
2.实施过程
实例位置:光盘\mr\5\5.5\01
在开发图形统计模块时,还可以模仿Excel提供的透视图功能,利用JSP页开发透视图统计功能,透视图为人机对话提供了良好的操作页面,用户通过透视图,可以更好的分析数据,如图5.48所示,用户可以首先可以选择要查看的月份,然后还可以选择要查看的商品或时段。

图5.48 透视图决策分析方案效果图
在图5.47中已经给出了透视图的效果及关键实施方案,下面将详细介绍透视图的实施过程,以及在编码过程中需要注意的事项,并且按照图5.46所示的可控流程进行讲解。
下面的代码负责实现页字段,页字段是单选的,并且提供了一个“全部”选项,当选中该选项时,代表选中所有的页字段选项,该选项为默认选项。这里为代表页字段的菜单控件添加了onchange事件,每当用户修改了该菜单控件的值,都会重新请求该页,并传入用户选中的值,具体代码如下:
例程5-73 代码位置:光盘\mr\5\5.5\01\StatChart\scenograph.jsp
<td class="otherTitle"><%=phasesTitle %>:
<select name="phasesSelected" onchange="requestCurrentJsp(this.value)">
<option value="All">全部</option>
<%
if(phasesSelected.equals("All")){
for(int i=0;i<phases.length;i++){
out.print("<option value='"+phases[i]+"'>"+phases[i]+"</option>");
}
}else{
for(int i=0;i<phases.length;i++){
if(phases[i].equals(phasesSelected))
out.print("<option selected alue='"+phases[i]+"'>"+phases[i]+"</option>");
else
out.print("<option value='"+phases[i]+"'>"+phases[i]+"</option>");
}
}
%>
</select> </td>
下面的代码负责实现图例字段,图例字段是多选的,也提供了一个“全部”选项,当选中该选项时,代表选中所有的图例字段选项,该选项为默认选项。用户订制完图例字段后,单击下方的“确定”按钮,也会重新请求该页,并传入用户选中的值,具体代码如下:
例程5-74 代码位置:光盘\mr\5\5.5\01\StatChart\scenograph.jsp
<tr align="center">
<td class="otherTitle"><%=cutlineTitle %></td>
</tr>
<tr align="center">
<td><select name="cutlineSelected" size="<%=multipleSize %>" multiple>
<option value="All">全部</option>
<%
if(cutlineSelected[0].equals("All")){
for(int i=0;i<cutline.length;i++){
out.print("<option value='"+cutline[i]+"'>"+cutline[i]+"</option>");
}
}else{
for(int m=0;m<cutline.length;m++){
boolean isSelected=false;
for(int n=0;n<cutlineSelected.length;n++){
if(cutline[m].equals(cutlineSelected[n])){
isSelected=true;
break;
}
}
if(isSelected){
out.print("<option selected value='"+cutline[m]+"'>"
+cutline[m]+"</option>");
}else{
out.print("<option value='"+cutline[m]+"'>"+cutline[m]+"</option>");
}
}
}
%>
</select></td>
</tr>
<tr align="center">
<td><input type="submit" name="Submit2" value="确定"></td>
</tr>
下面的代码负责实现分类字段,分类字段也是多选的,同样提供了一个“全部”选项,当选中该选项时,代表选中所有的分类字段选项,该选项为默认选项。用户订制完分类字段后,单击下方的“确定”按钮,也会重新请求该页,并传入用户选中的值,具体代码如下:
例程5-75 代码位置:光盘\mr\5\5.5\01\StatChart\scenograph.jsp
<tr align="center">
<td class="otherTitle"><%=categoryTitle %></td>
</tr>
<tr align="center">
<td><select name="categorySelected" size="<%=multipleSize %>" multiple>
<option value="All">全部</option>
<%
if(categorySelected[0].equals("All")){
for(int i=0;i<category.length;i++){
out.print("<option value='"+category[i]+"'>"+category[i]+"</option>");
}
}else{
for(int m=0;m<category.length;m++){
boolean isSelected=false;
for(int n=0;n<categorySelected.length;n++){
if(category[m].equals(categorySelected[n])){
isSelected=true;
break;
}
}
if(isSelected){
out.print("<option selected value='"+category[m]+"'>"
+category[m]+"</option>");
}else{
out.print("<option value='"+category[m]+"'>"+category[m]+"</option>");
}
}
}
%>
</select></td>
</tr>
<tr align="center">
<td><input type="submit" name="Submit" value="确定"></td>
</tr>
注意:代表图例字段的列表控件和代表分类字段的列表控件位于同一个form表单中,当需要对两个字段的值进行修改时,可以同时修改,然后单击任何一个“确定”按钮都可以!
下面的代码负责获得由用户选中的页字段、图例字段和分类字段的值,默认情况下均为选中“全部”选项。
例程5-76 代码位置:光盘\mr\5\5.5\01\StatChart\scenograph.jsp
String phasesSelected="All";
String[] cutlineSelected={"All"};
String[] categorySelected={"All"};
if(request.getParameter("phasesSelected")!=null){
phasesSelected=request.getParameter("phasesSelected");
if(request.getParameterValues("cutlineSelected")!=null){
cutlineSelected=request.getParameterValues("cutlineSelected");
}
if(request.getParameterValues("categorySelected")!=null){
categorySelected=request.getParameterValues("categorySelected");
}
}
在每次请求实现透视图的scenograph.jsp页时,都会执行下面的代码,从数据库获得页字段的选项,具体代码如下:
例程5-77 代码位置:光盘\mr\5\5.5\01\StatChart\scenograph.jsp
phases=dataStat.selectOneColumn("select (cast(datepart(yy,date) as char(4))+'-'
+cast(datepart(mm,date) as char(2))) as 'yy-mm' from tb_merchSale group by
(cast(datepart(yy,date) as char(4))+'-'+cast(datepart(mm,date) as char(2))),
datepart(yy,date),datepart(mm,date) order by datepart(yy,date) desc,
datepart(mm,date) desc");
下面的代码负责获得图例字段的选项,在组织用来检索图例字段选项的SQL语句时,需要依据页字段的值,代码如下:
例程5-78 代码位置:光盘\mr\5\5.5\01\StatChart\scenograph.jsp
String selectCutlineSql="select name+'('+model+')' as cutline from tb_merchInfo
where id in(select merchInfo_id from tb_merchSale";
if(!phasesSelected.equals("All"))
selectCutlineSql+=" where datepart(yy,date)="+phasesSelected.substring(0,4)+"
and datepart(mm,date)="+phasesSelected.substring(5);
selectCutlineSql+=" group by merchInfo_id) order by cutline desc,id desc";
cutline=dataStat.selectOneColumn(selectCutlineSql);
本案例的分类字段的选项不受页字段值的影响,是固定的,代码如下:
category=new String[]{"上旬","中旬","下旬"};
下面开始封装绘图数据,在封装绘图数据时分为两种情况,一种情况是分类字段的第一个值为“全部”,另一种情况是分类字段的值为非“全部”的任何值。任何一种情况,都要根据图例的选择情况,创建double型的实例,用来保存统计出的绘图数据。
首先看分类字段的第一个值为“全部”的情况。由于在这种情况下需要统计所有分类字段的数据,所以是按照图例进行封装数据的,具体代码如下:
例程5-79 代码位置:光盘\mr\5\5.5\01\StatChart\scenograph.jsp
if(cutlineSelected[0].equals("All"))
data=new double[cutline.length][category.length];
else
data=new double[cutlineSelected.length][category.length];
upData=dataStat.selectMoreColumn(upSql); // 统计上旬的数据
middleData=dataStat.selectMoreColumn(middleSql); // 统计中旬的数据
downData=dataStat.selectMoreColumn(downSql); // 统计下旬的数据
for(int m=0;m<cutlineId.length;m++){ // 按照图例进行封装
for(int n=0;n<upData.length;n++){
if((upData[n][0]+"").equals(cutlineId[m])){
data[m][0]=upData[n][1];
break;
}
}
for(int n=0;n<middleData.length;n++){
if((middleData[n][0]+"").equals(cutlineId[m])){
data[m][1]=middleData[n][1];
break;
}
}
for(int n=0;n<downData.length;n++){
if((downData[n][0]+"").equals(cutlineId[m])){
data[m][2]=downData[n][1];
break;
}
}
}
然后看分类字段的第一个值为非“全部”的任何值情况。由于在这种情况下并不需要统计出所有分类的数据,所以需要按照选择的分类值进行封装,具体代码如下:
例程5-80 代码位置:光盘\mr\5\5.5\01\StatChart\scenograph.jsp
if(cutlineSelected[0].equals("All"))
data=new double[cutline.length][categorySelected.length];
else
data=new double[cutlineSelected.length][categorySelected.length];
for(int i=0;i<categorySelected.length;i++){ // 按照选择分类值的个数进行循环
if(categorySelected[i].equals("上旬")){
upData=dataStat.selectMoreColumn(upSql);
for(int m=0;m<cutlineId.length;m++){
for(int n=0;n<upData.length;n++){
if((upData[n][0]+"").equals(cutlineId[m])){
data[m][i]=upData[n][1];
break;
}
}
}
}
if(categorySelected[i].equals("中旬")){
middleData=dataStat.selectMoreColumn(middleSql);
for(int m=0;m<cutlineId.length;m++){
for(int n=0;n<middleData.length;n++){
if((middleData[n][0]+"").equals(cutlineId[m])){
data[m][i]=middleData[n][1];
break;
}
}
}
}
if(categorySelected[i].equals("下旬")){
downData=dataStat.selectMoreColumn(downSql);
for(int m=0;m<cutlineId.length;m++){
for(int n=0;n<downData.length;n++){
if((downData[n][0]+"").equals(cutlineId[m])){
data[m][i]=downData[n][1];
break;
}
}
}
}
}
下面将通过上面处理得到的信息,定义图例、分类和JFreeChart的绘图数据集,具体代码如下:
例程5-81 代码位置:光盘\mr\5\5.5\01\StatChart\scenograph.jsp
DefaultCategoryDataset dataset = new DefaultCategoryDataset(); // 定义绘图数据集
String[] cutlineOver=cutlineSelected; // 定义图例
String[] categoryOver=categorySelected; // 定义分类
if(cutlineSelected[0].equals("All"))
cutlineOver=cutline;
if(categorySelected[0].equals("All"))
categoryOver=category;
for (int m = 0; m < cutlineOver.length; m++) {
for (int n = 0; n < categoryOver.length; n++) {
dataset.addValue(data[m][n], cutlineOver[m], categoryOver[n]);
}
}
现在就可以绘制统计图了,在绘制统计图时,将根据图例的多少分为两种情况,当图例较多时,如果绘制图例,将占用图片很大的空间,所以在这种情况下不绘制图例,而是对统计图提供热点标签,如图5.48所示;当图例较少时,则绘制图例,但是不提供热点标签,而是通过普通标签标注统计数据,如图5.49所示。

图5.49 图例较少时的透视图效果
为了实现上述效果,需要定义两个boolean型的属性,分别用来控制是否绘制图例和是否提供热点标签,默认为图例较少,接着利用这两个属性创建一个JFreeChart实例,具体代码如下:
例程5-82 代码位置:光盘\mr\5\5.5\01\StatChart\scenograph.jsp
boolean legend=true; // 默认绘制图例
boolean tooltips=false; // 默认不提供热点标签
if(cutlineOver.length>10){ // 当图例的个数大于10时,则相反
legend=false;
tooltips=true;
}
JFreeChart chart = ChartFactory.createBarChart3D(
"",
xTitle,
yTitle,
dataset,
PlotOrientation.VERTICAL,
legend,
tooltips,
false);
当legend属性的值为true时,将执行下面的代码,绘制用来标注统计数据的普通标签。
例程5-83 代码位置:光盘\mr\5\5.5\01\StatChart\scenograph.jsp
if(legend){
renderer.setItemLabelGenerator(new StandardCategoryItemLabelGenerator(
"{2}", new DecimalFormat("¥#,###.00"))); // 格式化普通标签
renderer.setItemLabelsVisible(true); // 设置普通标签为可见,JFreeChart默认为不可见
ItemLabelPosition p = new ItemLabelPosition( // 设置标签的显示位置
ItemLabelAnchor.CENTER, TextAnchor.CENTER_LEFT,
TextAnchor.CENTER_LEFT, -Math.PI / 2.0
);
renderer.setPositiveItemLabelPositionFallback(p);
}
当tooltips属性的值为true时,将执行下面的代码,对热点标签进行格式化。
例程5-84 代码位置:光盘\mr\5\5.5\01\StatChart\scenograph.jsp
if(tooltips)
renderer.setBaseToolTipGenerator(new StandardCategoryToolTipGenerator(
" [ {0},{1} ] = {2} ", new DecimalFormat("¥#,###.00")));
当tooltips属性的值为true时,还将执行下面的代码,作用是向JSP页输出MAP信息,供热点标签使用。
例程5-85 代码位置:光盘\mr\5\5.5\01\StatChart\scenograph.jsp
if(tooltips)
ChartUtilities.writeImageMap(new PrintWriter(out), "chartInfo", info, false);
至此,一个完整的透视图案例就实施完成了!
3.补充说明
在运用透视图方案开发统计模块时,当调用本案例中的scenograph.jsp时,如果在订制透视图时出现如图5.50所示效果,是因为在重新请求JSP页时编码错误导致的,解决的办法是在Web程序的欢迎页加入如下代码:
request.setCharacterEncoding("GBK");
上面代码的功能是用来设置request请求的编码方式,这里设置为GBK,例如本案例是在index.jsp页设置的。
注意:在Web程序的非欢迎页加入上述代码是不能解决问题的!

图5.50 图例较少时的透视图效果






