星期一, 3月 24, 2008

用ZedGraph畫統計圖

Update: 沒想到這篇居然變成Google搜尋ZedGraph第一篇中文網頁,不過還是誠心建議用Windows上的C#先看一下免費的圖表元件:Microsoft Chart Controls,除非你非得用.Net 2.0(Windows 2000)或是用Mono。 BTW,我並不想成為微軟MVP,所以本Blog並不是有問必答的喲^_^

才剛貼完上一篇,馬上就有位朋友丟過來一個LGPL Open Source元件的網址:ZedGraph

參考:A flexible charting library for .NET




基本上,照著Use RenderMode.RawImage in a web page,應該可以做出來,只要注意把ZedGraph.dll和ZedGraph.Web.dll都複製到bin下即可。我是偷懶直接下載zedgraph_web_sample_v5.1.2.zip,用檔案系統網站開啟,再把那2個dll丟到bin下測試。



範例裏有三個目錄,我們只關注ImageTagCS和RawModeCS。原來以為ImageTagCS做成WebControl,只要一個檔案即時render比較好,但我發現錯了:會不斷產生暫存影像檔。
所以比較好的做法與ProEssentials一樣,必須用另一支aspx產生影像,也就是RawModeCS裏的做法。

從頭來做一次好了,先開一個新的Asp.Net檔案系統網站,再開bin目錄,將ZedGraph.dll和ZedGraph.Web.dll都複製到bin下。

接著加入一個新WebForm,叫zedgraph.aspx


在Default.aspx裏拉進一個button和一個Image物件,在button上double click,在form標籤內應該是
<asp:Button ID="Button1" runat="server" OnClick="Button1_Click" Text="查詢" />
<asp:Image ID="Image1" runat="server" ImageUrl="zedgraph.aspx" />


在Default.aspx.cs裏
protected void Button1_Click(object sender, EventArgs e)
{
PointPairList list1 = new PointPairList();
PointPairList list2 = new PointPairList();
PointPairList list3 = new PointPairList();
Random rand = new Random();

for ( double x=0; x<5; x+=1.0 )
{
double y = rand.NextDouble() * 1000;
double y2 = rand.NextDouble() * 1000;
double y3 = rand.NextDouble() * 1000;
list.Add( x, y );
list2.Add( x, y2 );
list3.Add( x, y3 );
}
Session["list1"] = list1;
Session["list2"] = list2;
Session["list3"] = list3;
//為避免cache,若使用AJAX一定要加
Image1.ImageUrl = "zedgraph.aspx?xxx="+DateTime.Now.Millisecond;
}

打開zedgraph.aspx,刪除原有內容,輸入
<%@ Page Language="c#" Inherits="ZG1.zedgraph" CodeFile="zedgraph.aspx.cs" %>
<%@ Register TagPrefix="zgw" Namespace="ZedGraph.Web" Assembly="ZedGraph.Web" %>
<zgw:zedgraphweb id="ZedGraphWeb1" runat="server" width="800" height="450" rendermode="RawImage" onrendergraph="OnRenderGraph"></zgw:zedgraphweb>

在zedgraph.aspx.cs裏加入
protected void OnRenderGraph(ZedGraphWeb zgw, Graphics g, MasterPane masterPane)
{
// Get the GraphPane so we can work with it
GraphPane myPane = masterPane[0];
myPane.Title.Text = "電腦統計圖";
myPane.XAxis.Title.Text = "部門";
myPane.YAxis.Title.Text = "台數";
// 這裏可視情況改成由DataTable讀入資料,
// 將DataTable從前一頁利用Session傳入
PointPairList list1 = (PointPairList)Session["list1"];
PointPairList list2 = (PointPairList)Session["list2"];
PointPairList list3 = (PointPairList)Session["list3"];

BarItem myCurve = myPane.AddBar("桌機", list1, Color.Blue);
myCurve.Bar.Fill = new Fill(Color.Blue, Color.White, Color.Blue);
BarItem myCurve2 = myPane.AddBar("筆電", list2, Color.Red);
myCurve2.Bar.Fill = new Fill(Color.Red, Color.White, Color.Red);
BarItem myCurve3 = myPane.AddBar("印表機", list3, Color.Green);
myCurve3.Bar.Fill = new Fill(Color.Green, Color.White, Color.Green);

myPane.XAxis.MajorTic.IsBetweenLabels = true;
string[] labels = { "行政部", "會計部", "資訊部", "業務部", "研發部" };
myPane.XAxis.Scale.TextLabels = labels;
myPane.XAxis.Type = AxisType.Text;
myPane.Fill = new Fill(Color.White, Color.FromArgb(200, 200, 255), 45.0f);
myPane.Chart.Fill = new Fill(Color.White, Color.LightGoldenrodYellow, 45.0f);

masterPane.AxisChange(g);
//產生每個Bar上的數量(Label),原範例沒有喲
BarItem.CreateBarLabels(myPane, false, "f0");
}

再執行 Default.aspx,執行結果:


重點在於把值利用Session傳入,以及zedgraph的事件


如果像鳥毅一樣運氣太好,遇到browser cache圖怎麼辦?尤其是放在UpdatePanel內更容易發生,最簡單的方法就是在每次查詢(button click)事件處理內加上一條:
Image1.ImageUrl = "zedgraph.aspx?xxx="+DateTime.Now.Millisecond;
這樣應該就沒問題了。

19 則留言:

Vale 提到...

可以請較一下每個BAR的寬度是依圖形總寬度平均的嗎?
我現在想只作一個BAR 寬度約佔整個圖表的80%,可是我找不到怎麼去修改BarWidth的方法
方便的話可以指點迷津嗎? 先謝謝了~~~

鳥毅 提到...

我猜是 這個啦,我在家用Ubuntu,無開發環境,請見諒。

學習ZedGraph的人 提到...

不好意思,我最近使用這個控制項時發生了幾項問題
就是我利用大大的方法,但是抓不到資料

在default.aspx中
我是想利用點選一個按鈕後,將現存label.text的資料抓出來,然後
在default.aspx.vb中
將這些抓取道的資料寫到list1,2,3中

最後再在zedgraph.aspx中利用session
這邊的方法與大大的相同

但最後都抓不到資料值 所以圖都是空的
能否請大大指導一下?

鳥毅 提到...

1. 我今天大過了,感謝你的關心。
2. 看不到程式很難除錯耶...不過我建議你先把target指向另一支aspx程式,確定list 1, 2, 3都有值之後,才對zedgraph.aspx除錯。

學習zedgraph的人 提到...

妳好,我現在有點亂了
我剛剛又將程式寫了一下
可否請您幫我看一下呢..真的蠻困擾的

在Default.aspx內有兩個TextBox及兩個Label 有兩個Button分別取出TextBox的値到Label,以及產生圖形

在Default.aspx.vb內:

Protected Sub Button1_Click(ByVal sender As Object, ByVal e As System.EventArgs) Handles Button1.Click
Label1.Text = TextBox1.Text
Label2.Text = TextBox2.Text
End Sub

Protected Sub Button2_Click(ByVal sender As Object, ByVal e As System.EventArgs) Handles Button2.Click
Dim list1 As New PointPairList()
Dim list2 As New PointPairList()
Dim list3 As New PointPairList()
Dim i As Integer, rand As New Random()
Dim y As Integer, x1 As Integer, x2 As Integer, x3 As Integer
For i = 1 To 5
y = i
x1 = 100.0 + rand.NextDouble() * 100.0
x2 = 100.0 + rand.NextDouble() * 100.0
x3 = 100.0 + rand.NextDouble() * 100.0

list1.Add(x1, y)
list2.Add(x2, y)
list3.Add(x3, y)

Next i
End Sub


在z_graph.aspx.vb內:
Protected Sub OnRenderGraph1(ByVal zgw As ZedGraphWeb, ByVal g As System.Drawing.Graphics, _
ByVal masterPane As zedgraph.MasterPane) Handles ZedGraphWeb1.RenderGraph

' Get a reference to the GraphPane instance in the ZedGraphControl
Dim myPane As GraphPane = masterPane(0)
'Dim sessionData As New Sequence


' Set the title and axis labels
myPane.Title.Text = "Stacked Bars with Value Labels Inside Each Bar"
myPane.XAxis.Title.Text = "Position Number"
myPane.YAxis.Title.Text = "Some Random Thing"

Dim list1 As New PointPairList()
Dim list2 As New PointPairList()
Dim list3 As New PointPairList()

list1 = CType(Session("list1"), PointPairList)
list2 = CType(Session("list2"), PointPairList)
list3 = CType(Session("list3"), PointPairList)

' Create the three BarItems, change the fill properties so the angle is at 90
' degrees for horizontal bars
Dim bar1 As BarItem = myPane.AddBar("Bar 1", list1, Color.Red)
bar1.Bar.Fill = New Fill(Color.Red, Color.White, Color.Red, 90)
Dim bar2 As BarItem = myPane.AddBar("Bar 2", list2, Color.Blue)
bar2.Bar.Fill = New Fill(Color.Blue, Color.White, Color.Blue, 90)
Dim bar3 As BarItem = myPane.AddBar("Bar 3", list3, Color.Green)
bar3.Bar.Fill = New Fill(Color.Green, Color.White, Color.Green, 90)

' Set BarBase to the YAxis for horizontal bars
myPane.BarSettings.Base = BarBase.Y
' Make the bars stack instead of cluster
myPane.BarSettings.Type = BarType.Stack

' Fill the axis background with a color gradient
myPane.Chart.Fill = New Fill(Color.White, _
Color.FromArgb(255, 255, 166), 45.0F)

masterPane.AxisChange(g)
End Sub

現在的問題是似乎連隨機取的值都抓不到因此跑不出圖形.我是希望能讀取label的値進而產生圖

程式碼似乎過長,不知是否會過亂,真的非常抱歉,請教你這時該怎麼辦呢

鳥毅 提到...

這位大哥:
首先要向您道歉,小弟在貼code時,因為html碼不吃角刮號,所以某些code被blogger的編輯器吃掉了:P
問題很簡單呀,您在Button2_Click那裏並沒有把list1,2,3加到Session(就是我被吃掉的code,等會就補上),所以在next i與end sub中間加上
Session("list1") = list1 等3行,就傳得到 z_graph.aspx

另一點,您在z_graph.aspx.vb裏的是OnRenderGraph1而不是OnRenderGraph,請確認aspx裏呼叫的事件名稱是否一致。

學習ZedGraph 的人 提到...

大哥 非常感謝你

我已經成功的讀到label的資料了

在大哥您的最後一行有說到

遇到brower cache圖的問題是什麼意思呢?
是說遇到讀取到的圖是舊的嗎?
我有大概爬了一下文
是否是Browser快取(Cache)起來,除非主動清除這些快取資料,或人工將頁面重新整理才能讀到新的資料的意思。

希望大哥能夠為小弟稍稍講解一番
還有就是解決方法,能否向小弟講解一下,
因為不太了解C#,非常感謝你
Image1.ImageUrl = "zedgraph.aspx?xxx="+DateTime.Now.Millisecond;

xxx是固定的xxx還是某個變數呢?
希望大哥能為小弟稍稍講解,讓小弟可以順利的轉成VB

鳥毅 提到...

關於browser cache,可以google "IE6 cache problem",大致上就是你說的意思,圖不會更新。

因此,最簡單的方法就是改變url,所以我那行用VB.Net寫就是
Image1.ImageUrl = "zedgraph.aspx?xxx=" & DateTime.Now.Millisecond
其中 xxx 只要你爽寫成什麼都可以:P

學習ZedGraph的人 提到...

非常感謝你 大哥^^
最後再請教一下
Image1.ImageUrl = "zedgraph.aspx?xxx=" & DateTime.Now.Millisecond
是寫到Button1_Click內對吧

非常感謝大哥解決我的問題,因為ZedGraph的資料真的不多,除了官方網站..

感謝!! :D

鳥毅 提到...

Read the sample code again, I've updated it.

vincentwin 提到...

在zedgraph.aspx中,刪除原有內容,輸入.....
CodeFile="zedgraph.aspx.cs"
上面這一句害我不淺,我的project編譯發佈到遠端後,就只剩dll跟aspx檔,CodeFile好像是asp.net 1.1的屬性,發佈到遠端後,iis就一直找不到"zedgraph.aspx.cs",除非我手動copy過去,在vs2005中測試倒是正常得很,因為cs檔就在身邊,看一看官方範例就是寫成CodeFile,範例中竟然還寫(This file uses the newer syntax for the .Net 2.0 version.) = =。後來把CodeFile改成2.0的CodeBehind,好像就解決這個問題了,整整找了兩天,希望不要有人在遇到這問題了,不用vs2005自帶的開發網頁伺服器,叫vs2005用iis運行,好像也會遇到。

鳥毅 提到...

vincentwin,你搞錯了,CodeFile才是2.0版的寫法,1.1是叫CodeBehind。

要刮別人的鬍子前,先刮自己的鬍子。

vincentwin 提到...

好吧,確實是搞錯了。查了一下之間的不同。http://charlesbc.blogspot.com/2007/08/codefile-codebehind.html

leslie1027 提到...

請問一下大大,我要列出出每個Bar的標題
要怎麼寫?? 上面標示每個Bar的字型不知道可以改嗎?? 因為我覺得滿小的....

因為我做的是動態的Bar,我需要在每個Bar上註明數量,不過因為動態的原因,那些數字都會留下之前顯示果的數量,找了好久都不知道怎麼解決...

可以請大大幫幫忙嗎?? 謝謝喔!!

鳥毅 提到...

leslie1027,我不寫code已經半年了,最近連公司的桌機也換成Ubuntu,手邊已經沒有.Net的開發環境。

而ZedGraph我也只有稍微玩一下,請再加油或找高手協助吧!我不過是個肉腳半吊子兩光網管呀!

匿名 提到...

先感謝鳥毅大大的詳細說明,
不知道原因,本人照用回大大的code卻出下以問題。

----------------------------------
編譯器錯誤訊息: CS0246: 找不到型別或命名空間名稱 'PointPairList' (您是否遺漏 using 指示詞或組件參考?)


{
行 17: PointPairList list1 = new PointPairList();
行 18: PointPairList list2 = new PointPairList();
行 19: PointPairList list3 = new PointPairList();

----------------------------------

感謝大大的解答~!!

鳥毅 提到...

匿名:
請下載文中的 sample project zip file。遺漏 using 指示詞或組件參考表示你沒有引用,完全不懂asp.net引用組件的方式;恕我直言,請從基礎開始學。

ZedGraph不適合你,請看免費的圖表元件:Microsoft Chart Controls

俊海 提到...

請問 如果我要畫成類似excel函數中(NORMDIST)常態分配圖,請問 哪裡可以讓你選擇你要畫圖的總類??謝謝

鳥毅 提到...

俊海:你說的是 這些 圖形種類嗎?Normal Distribution應該只要描點,所以看這個就可以了。

目前ZedGraph沒什發展了,你可以考慮Microsoft Chart Controls