我的需求是这样的:
有一大堆的供应商的库存数据要按产品按供应商按时间段进行数据比较,同时产生图表,就是
利用zedGraph实现不定曲线数曲线的生成
还是贴张图来说一下吧。
其它的实现方式,我会在这个项目完成后总结出来,困扰我的地方在于:
如何通过所选择的供应商数目及时间段进行绘制图表呢?这里是供应商数目不定,时间段不定。
关于:ZedGraph这东西我一直使用,可以要网上搜索到关于生成动态曲线(不定曲线数)的文章几乎没有。看来只能靠自己了。
返过头来看一下:要实现上面这玩艺,需要了解这些知识点
1.C# 交互数组的使用
(在最初的时候我选择了交互数组,但在中间过程中我又觉得自己错了,改回使用二维数组去实现,尽管数据是KO了,但就是不能画出图来,最后认真了解了一下C#数组的用法,还是走回原来的路使用了交互数组并达到想要的效果)
2.存储过程中相关的统计语句,主要是(行转列 ,Group by, 排序,动态SQL的构建)
gridview中的数据是通过存储过程去实现获取的,而这个存储过程是通过动态SQL去实现的。这个我稍后会整出来。
3.Split的用法,C#数组length的注意
事实证明到实现之前的时候,主要都是一些细枝末节的错误,看来我的基本功还不扎实啊!
现在我们来看一下实现的方式:
Step1.首先,我们通过一个页面进入供应商的比较
Step2.根据所选择的对象进行数据比较
这里的一个知识点在于,如何将选择的对象的ID值给传出去呢? 这就要用到Split的用法了。
int numOfChecked = 0; //计算选中的对象个数
for (int i = 0; i < this.gdvCompareObject.Rows.Count; i++)
{
bool isChecked = ((CheckBox)gdvCompareObject.Rows[i].FindControl("chkSelected")).Checked;
if (isChecked)
{
numOfChecked++;
}
} //这里是累加所选择的对象个数
if (numOfChecked < 2) //进行个数判断
{
Page.ClientScript.RegisterStartupScript(Page.GetType(), "", "<SCRIPT>alert('很抱歉,你至少要选择二个以上的对象才能进行比较.');</SCRIPT>");
}
else
{
string compareObjectList = string.Empty;
for (int i = 0; i < gdvCompareObject.Rows.Count; i++)
{
CheckBox chkSelectedAgentID = (CheckBox)gdvCompareObject.Rows[i].FindControl("chkSelected");
if (chkSelectedAgentID.Checked == true)
{
compareObjectList += gdvCompareObject.DataKeys[i]["Object_ID"] + ","; //获取要比较的对象ID值串成一个字付串
}
}
string URL = "SysSCompareBycompareObjectListAndDate.aspx?AID=" + compareObjectList.Substring(0, compareObjectList.Length - 1); //注意要去掉最后一个字符逗号
Response.Redirect(URL);
}
Step3.把值传了过来,就可以有文章可以做了。
这里就要用到动态SQL语句的构造了。这个存储过程是这样子的
Create procedure CompareStorageByObjectListAndCalculateDate
@compareObjectID varchar(1000),
@begindate datetime,
@endDate datetime
as
declare @sql varchar(8000)
set @sql = 'select Object_ID,Ojbect_Code '
select @sql = @sql + ' , max(case Convert(Varchar(10),calculateDate,120) when ''' + Convert(Varchar(10),calculateDate,120) + ''' then stockNum else 0 end) [' + Convert(Varchar(10),calculateDate,120) + ']'
from (select distinct calculateDate from Dezai_Storage where calculateDate between @begindate and @endDate ) as a
set @sql = @sql + ' from Dezai_Storage where Object_ID in ('+@compareObjectID+') group by Object_ID,Ojbect_Code'
exec(@sql)
GO
这里说明一点的是:我们如果用一般的sum(),group by 就可能不能获取下图中所要的样子了.(如果你把时间段都设成字段那另当别论,这里的时间是动态的)当然,所得到的数据可能是一样。这里就需要实现行转列的要求了。
推荐大家看这篇文章,至少我是受益非浅了
SQL行列转换大全
http://www.dezai.cn/article_show.asp?ArticleID=32432
Step4:生成数据及图表
Gridview数据的填充就不啰嗦了,这里要注意的是这个gridview里面的字段名及行数据都是动态生成的,因此要将其
属性AutoGenerateColumn设置为True,其它就不用管了。
如何得到传过来的供应商的值呢?
程序代码
获取代理商ID数组
string agentIDList = PSS.COMMON.Encrypt.Decode(Request.QueryString["AID"].ToString().Trim());
string[] agentID = agentIDList.Split(',');
int agentNum = agentID.Length; //要比较的代理商数量
我这里主要想说的是zedGraph,及如何可以动态获取单元格的值。
这里要获取值应该有两种方式,但获取值都不可避免要用到C#数组尤其是交互数组的操作.
对于C#数组的用法,可以看看下面这几篇文章
C#中二维数组
http://www.dezai.cn/article_show.asp?ArticleID=32403
二维数组简单例子(取得行数列数)
http://www.dezai.cn/article_show.asp?ArticleID=32383
C#数组教程
http://www.dezai.cn/article_show.asp?ArticleID=32433
(1)直接从业务层中获取数据
这种方法我回过头来看,应该也是可取的。我一开始采用此方法的时候,忽略了数组的长度(就是数组上标 下标)导致我误以为这样获取值比从gridview中获取取更加麻烦,这个方法我就没有实现下去
(2)从gridview中获取数据填充交互数组
这里关键就是如何把gridview中的数据填充到交互数组中去,因为zegraph绘制曲线图时不支持交互数组,所以用交互数组是实现的最好方式。
这里也有几个关键点,与大家分享一下:
Attention:
A.为什么要减2呢?
因为生成的gridview中的数据,前两列的数据是供应商的ID和供应商代码,所以它不是要比较的数据,换句话说他不是整数或具体要比较的数,所以去掉,让数组的长度能与所得到的数据进行比较
B. xrowCount ,yColumnCount是什么?
xrowCount是gridview的行数 yColumn是列数. xrowCount不包括表头,只是数据的行数
C.如何获取gridview中的各列的列名
我这里是X轴的刻度实现,也就是获取列名(除去前两列)
D.zedgraph的LineItem是否支持数组?
zedGraph的PointPairList LineItem均支持数组,你可以这样用
PointPairList[] ppl= new PointPairList [xrowCount]
LineItem[] line= new LineItem[xrowCount]
for (int j = 2; j < yColumnCount; j++)
{
x[j - 2] = (double)new XDate(DateTime.Parse(Convert.ToDateTime(gdvAnalysisData.HeaderRow.Cells[j].Text).ToShortDateString()));
//xi = gdvAnalysisData.HeaderRow.Cells[j].Text.ToString();
}
一开始我用的时二维数组,写法如下:
for (int i = 0; i < xRowCount; i++)
{
double[,] storage = new double[xRowCount,yColumnCount - 2]; //定义二维数组
for (int k = 2; k < yColumnCount; k++)
{
storage[i,k - 2] = Convert.ToDouble(gdvAnalysisData.Rows[i].Cells[k].Text.ToString()); //将单元格中的数据填充到二维数组中去 K-2是把前两列不是数据的列值去掉
Response.Write("x["+(k-2).ToString()+"]="+x[k-2].ToString()+" stroage["+i.ToString()+","+(k-2).ToString()+"]="+storage[i,k - 2].ToString() + "<br>"); //打印值检测数据
}
这段代码所获得的结果是KO的,就是把gridview的单元格的数据完整地填充到我的二维数组中去,可这zedgraph仍然不能通过其来绘制曲线图。所以,寻思了一段时间,我不得不重新用回交互数组来实现了。
因为交互数组就是数组的数组,数组中可以包含一个数组,这样我的值就可以直接给zedGraph使用了。
交互数组的实现:
for (int i = 0; i < xRowCount; i++)
{
double[][] storage = new double[xRowCount][]; //定义交互数组
storage[i] = new double[yColumnCount - 2]; //定义交互数组的数组的长度
for (int k = 2; k < yColumnCount; k++) //对gridview中的列进行for循环
{
storage[i][k - 2] = Convert.ToDouble(gdvAnalysisData.Rows[i].Cells[k].Text.ToString()); 将单元格的值填充到交互数据中所对应的I的数组中
// Response.Write("x[" + (k - 2).ToString() + "]=" + x[k - 2].ToString() + " storage[" + i.ToString() + "][" + (k - 2).ToString() + "]=" + storage[i][k - 2].ToString()+"<br>"); 打出数据来进行较正
}
myCurve[i] = myPane.AddCurve(objectID[i].ToString(), x, storage[i], Color.FromArgb(ra.Next(1, 255), ra.Next(1, 255), ra.Next(1, 255)), SymbolType.Circle); //将X和stroage[i]这两个数组的值填充到zedGraph中去,注意X轴是不变的,x轴的值就是时间段,也就是gridview中的列名
myCurve[i].Line.IsAntiAlias = true;
myCurve[i].Symbol.Fill = new Fill(Color.White);
myCurve[i].Symbol.Size = 7;
}
由上面的这段值就可以得到我想要的效果了,zedGraph不定曲线数不定列名的曲线就可以整出来了.
曲线实现的代码;
程序代码
private void OnRenderGraph(ZedGraphWeb zgw, Graphics g, MasterPane masterPane)
{
//图表相关属性设置
GraphPane myPane = masterPane[0];
// Set the title and axis labels
myPane.Title.Text = "总库存比较曲线图";
myPane.XAxis.Title.Text = "年-月-日";
myPane.YAxis.Title.Text = "库存量";
myPane.YAxis.Scale.MinAuto = true;
myPane.YAxis.Scale.MaxAuto = true;
myPane.YAxis.Scale.MajorStepAuto = true;
myPane.YAxis.Scale.IsUseTenPower = false;
//获取统计日期
DateTime beginDate = Convert.ToDateTime(txtAgentStorageBeginDate.Text.Trim());
DateTime endDate = Convert.ToDateTime(txtAgentStorageEndDate.Text.Trim());
//获取供应商ID数组
string ObjectIDList = PSS.COMMON.Encrypt.Decode(Request.QueryString["AID"].ToString().Trim());
string[] agentID = ObjectIDList.Split(',');
int agentNum = agentID.Length; //要比较的供应商数量
myPane.XAxis.Type = AxisType.DateAsOrdinal;
myPane.XAxis.Scale.Format = "yyyy-MM-dd";
LineItem[] myCurve = new LineItem[agentNum];
//获取GridView的列数,也就是要循环的数
PSS.BLL.Sys.Statistical bllStatistic = new PSS.BLL.Sys.Statistical();
int yColumnCount = bllStatistic.SysSumStoragesCompareList(ObjectIDList, beginDate, endDate).FieldCount; //列数
int xRowCount = gdvObjectStorage.Rows.Count; //行数
Random ra = new Random(1000);
//x轴
double[] x = new double[yColumnCount - 2];
for (int j = 2; j < yColumnCount; j++)
{
x[j - 2] = (double)new XDate(DateTime.Parse(Convert.ToDateTime(gdvObjectStorage.HeaderRow.Cells[j].Text).ToShortDateString()));
//string xi = gdvObjectStorage.HeaderRow.Cells[j].Text.ToString();
}
for (int i = 0; i < xRowCount; i++)
{
double[][] storage = new double[xRowCount][];
storage[i] = new double[yColumnCount - 2];
for (int k = 2; k < yColumnCount; k++)
{
storage[i][k - 2] = Convert.ToDouble(gdvObjectStorage.Rows[i].Cells[k].Text.ToString());
// Response.Write("x[" + (k - 2).ToString() + "]=" + x[k - 2].ToString() + " storage[" + i.ToString() + "][" + (k - 2).ToString() + "]=" + storage[i][k - 2].ToString()+"<br>"); 打出数据来进行较正
}
myCurve[i] = myPane.AddCurve(gdvObjectStorage.Rows[i].Cells[1].Text.ToString(), x, storage[i], Color.FromArgb(ra.Next(1, 255), ra.Next(1, 255), ra.Next(1, 255)), SymbolType.Diamond);
myCurve[i].Line.IsAntiAlias = true;
myCurve[i].Symbol.Fill = new Fill(Color.White);
myCurve[i].Symbol.Size = 7;
}
}
我的体会:
1.整个数据是一个数据挖掘的过程,如果利用数据挖掘或(OLAP)进行数据组织,实现其来应该是个很容易的事情,这就是一个小小的BI应用了。看来BI的市场大前钱途啊。
2.做统计报表分析,基本功还是在SQL,SQL统计就那个几个字段几个数据,得到的效果却可以千变万化,可万变不离其中,还是要掌握好SQL语句。
3.我所碰到的这个问题应该不是仅局限于如何传值到zedgraph里去,我觉得如何获取数据并按相应的规则去组织数据才是最重要的,思路最重要。
项目过程中的一点小知识及经验,与大家分享。
感谢在此问题中提供帮助的同仁同事们,欢迎大家指教。
利用zedGraph实现动态数据动态曲线(不定曲线数)的生成
http://www.dezai.cn/blog/article.asp?id=240