基於Flot可放縮的折線圖


Flot初步

Flot是一個免費開源的圖標插件,可以用它開發出功能強大的圖表系統。下面着重講解在Asp.net中如何使用這個插件做出功能強大的圖表應用。

關於Flot,可以在這里查看現有的例子(或者是這里的例子),可以在這里查看現有的API。Flot的官方頁面在這里,在里面我們可以下載需要使用的插件。下面一段話是摘自Flot官網,藉此對Flot有個初步的映像:

Flot is a pure JavaScript plotting library for jQuery, with a focus on simple usage, attractive looks and interactive features.
Works with Internet Explorer 6+, Chrome, Firefox 2+, Safari 3+ and Opera 9.5+

下面就開始我們的講解吧。

項目講解

首先,這是我前台的HTML部分:

<%@ Page Language="C#" AutoEventWireup="true" CodeBehind="ChartDaemon.aspx.cs" Inherits="NXT_YMSYS.ChartDaemon" %>

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">

<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
    <title></title>
    <style type="text/css">
    body
    {
        background-color:#ECFBFE;
    }
    </style>
    <link href="Scripts/flot/jquery.jqplot.css" rel="stylesheet" type="text/css" />
    <script src="Scripts/flot/excanvas.min.js" type="text/javascript"></script>
    <script src="Scripts/flot/jquery.min.js" type="text/javascript"></script>
    <script src="Scripts/flot/jquery.flot.js" type="text/javascript"></script>
    <script src="Scripts/flot/jquery.flot.time.min.js" type="text/javascript"></script>
    <script src="Scripts/flot/jquery.flot.selection.min.js" type="text/javascript"></script>
    
    <script language="javascript" type="text/javascript">
        var line1, line2, line3, line4, line5, line6, line7, line8;
        var time="<%=time %>";
        var m_sDPID = "<%=m_sDPID %>";
        var min = "<%=min %>";
        var max = "<%=max %>";
        $(document).ready(function () {
            //alert(time + "---" + m_sDPID);
            if (time == "") time = "2013-10-08";
            if (m_sDPID == "") m_sDPID = "103";
            if (min == "") min = "0";
            if (max == "") max = "0";
            triggerAjaxForChart();
        });
        //時間改變
        function triggerAjaxForChart() {
            $.ajax({
                url: "Handler/FlotHandlerEx.ashx?date=" + time + "&msid=" + m_sDPID + "&min=" + min + "&max=" + max,
                success: function (data) {
                    var str = data;
                    //  line1 = eval(str);
                    var arrStr = str.split("|");
                    line1 = eval(arrStr[0]); //當前值
                    line2 = eval(arrStr[1]); //min value
                    line3 = eval(arrStr[2]); //max value
                    //數據
                    var dataDetail = [{
                        label: "當前溫度值",
                        data: line1,
                        color: "#1AC7F1",
                        lines:{ show: true, lineWidth: 3 },
                        shadowSize: 5,
                         points: { show: true, fillColor: "#fff" }
                    }, {
                        label: "最小溫度值",
                        data: line2,
                        color: "#b55e00",
                        lines: { show: true, lineWidth: 1 },
                        shadowSize: 0,
                        points: { show: false, fillColor: "#fff" }
                    }, {
                        label: "最大溫度值",
                        data: line3,
                        color: "#b55e01",
                        lines: { show: true, lineWidth:1 },
                        shadowSize: 0,
                        points: { show: false, fillColor: "#fff" }
                    }];
                    TriggerChart(dataDetail);
                },
                error: function (data) {
                }
            });
        }


        //Flot chart 的具體設置
        var TriggerChart = function (dataDetail) {
            //-------------------------------以下部分,不需要管理-----------------
            //要顯示的細節設置
            var detailOptions = {
                series: {
                    lines: { show: true, lineWidth: 2 },  
                    points: { show: false, fillColor: "#fff" }, 
                    shadowSize: 1
                },
                grid: {
                    hoverable: true,  
                    backgroundColor: { colors: ["#ECFBFE", "#ffffff"] }
                },
                yaxes: {
                    color: "#B8E2F8"
                },
                yaxis: {
                    min: -40,  //固定最小值
                    max: 40   //固定最大值
                },
                xaxis: {
                    mode: "time",  //x軸是時間格式
                    color: "#B8E2F8" 
                },
                selection: {
                    mode: "x"
                },
                legend:{
                labelBoxBorderColor:"#1AC7F1"
                }
            };

            //-----------------實現點選的部分------------------------------------------------
            var choiceContainer = $("#choices");
            $("#choices input").remove();  
            $("#choices label").remove();  
            plotAccordingToChoices(dataDetail, detailOptions);
            $("#detailContainer").UseTooltip();
        }

        var plotAccordingToChoices = function (dataDetail, detailOptions) {
            var data = [];
            data.push(dataDetail[0]);
            data.push(dataDetail[1]);
            data.push(dataDetail[2]);

            //data中保存了選中的線條
            if (data.length > 0) {
                //綁定選中的數據
                $.plot("#detailContainer", data, detailOptions);

                //綁定數據的縮放
                $("#detailContainer").bind("plotselected", function (event, ranges) {
                    plotDetail = $.plot($("#detailContainer"), data, $.extend(true, {}, detailOptions, { xaxis: { min: ranges.xaxis.from, max: ranges.xaxis.to} }));
                });

                //綁定右鍵恢復數據
                $("#detailContainer").live("mousedown", function (e) {
                    if (event.button == 2) {
                        var plotDetail = $.plot($("#detailContainer"), data, detailOptions);
                    }
                });
            }
        }


        var previousPoint = null, previousLabel = null;
        $.fn.UseTooltip = function () {
            $(this).bind("plothover", function (event, pos, item) {
                if (item) {
                    if ((previousLabel != item.series.label) || (previousPoint != item.dataIndex)) {
                        previousPoint = item.dataIndex;
                        previousLabel = item.series.label;
                        $("#tooltip").remove();

                        var x = item.datapoint[0];
                        var y = item.datapoint[1];
                        var date = new Date(x);
                        var color = item.series.color;

                        if (color == "#b55e00") {  //最小值
                            showTooltip(item.pageX, item.pageY, color,
                            "溫度最小值為: <strong>" + y + "</strong> (℃)");
                        }
                        else if (color == "#b55e01") {  //最大值
                            showTooltip(item.pageX, item.pageY, color,
                            "溫度最大值為: <strong>" + y + "</strong> (℃)");
                        }
                        else {
                            showTooltip(item.pageX, item.pageY, color,
                            "<strong>" + item.series.label + "</strong><br>" +
                            (date.getMonth() + 1) + "" + date.getDate() + "" + date.getUTCHours() + "" + date.getMinutes() + "" +
                            "   溫度: <strong>" + y + "</strong> (℃)");
                        }
                    }
                } else {
                    $("#tooltip").remove();
                    previousPoint = null;
                }
            });
        };

        function showTooltip(x, y, color, contents) {
            $('<div id="tooltip">' + contents + '</div>').css({
                position: 'absolute',
                display: 'none',
                top: y - 40,
                left: x - 120,
                border: '2px solid ' + color,
                padding: '3px',
                'font-size': '12px',
                'border-radius': '5px',
                'background-color': 'wheat',
                'font-family': '宋體,Verdana, Arial, Helvetica, Tahoma, sans-serif',
                opacity: 0.9
            }).appendTo("body").fadeIn(200);
        }
    </script>

</head>
<body>
         <div id="example-section32" style="background-color:#EDFBFE;width:100%; float:left;"  >   
        <div id="choices" style="width:100%;float:left; text-align:left; font-size:10pt;font-weight:normal;margin-left:18px;"></div>
        <div id="detailContainer" style="height:240px;  width:600px;float:left;" oncontextmenu="return false"  ></div>
        </div>
</body>
</html>
View Code

其次,這是我Handler中的代碼部分:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Text;
using System.Data;
using System.Data.SqlClient;
using System.Configuration;

namespace NXT_YMSYS.Handler
{
    /// <summary>
    /// FlotHandlerEx 的摘要說明
    /// </summary>
    public class FlotHandlerEx : IHttpHandler
    {

        public bool IsReusable { get { return false; } }

        private string connStr = ConfigurationManager.AppSettings["ConnectionString"];

        public void ProcessRequest(HttpContext context)
        {
            string date = context.Request["date"];
            if (string.IsNullOrEmpty(date))
                date = "2012-12-10";

            string min = context.Request["min"];
            string max = context.Request["max"];

            //獲取對應的列
            DataSet dsMap = new DataSet();
            using (SqlConnection conn = new SqlConnection(connStr))
            {
                conn.Open();
                string querySQL = @"
                                                    select *,replace(
                                                                    P_data5,P_data5,case p_data5 when '一路溫度' then 'tempOne' 
                                                                                                 when '二路溫度' then 'tempTwo' 
                                                                                                 when '三路溫度' then 'tempThree' 
                                                                                                 when '四路溫度' then 'tempFour' 
                                                                                                 when '五路溫度' then 'tempFive' 
                                                                                                 when '六路溫度' then 'tempSix' 
                                                                                                 when '七路溫度' then 'tempSeven' 
                                                                                                 when '八路溫度' then 'tempEight' 
                                                                                                 end ) result
                                                        from TB05 where p_data1='" + context.Request["msid"] + "' ";
                SqlCommand cmd = new SqlCommand(querySQL, conn);
                SqlDataAdapter sda = new SqlDataAdapter(cmd);
                sda.Fill(dsMap);
            }

            //獲取對應的數據
            DataSet ds = null;
            using (SqlConnection conn = new SqlConnection(connStr))
            {
                conn.Open();
                ds = new DataSet();
                string sql = @"select   TB04.P_data1 as tempOne,
                                                    TB04.P_data2 as tempTwo,
                                                    TB04.P_data3 as tempThree,
                                                    TB04.P_data4 as tempFour,
                                                    TB04.P_data5 as tempFive,
                                                    TB04.P_data6 as tempSix,
                                                    TB04.P_data9 as tempSeven,
                                                    TB04.P_data10 as tempEight,
                                        (CONVERT(varchar(20), TB04.P_data7, 120)) as GetTime 
                                   from TB04 inner join TB05 on TB04.P_data8 = TB05.P_data2 
                                   where TB05.P_data1 = '" + context.Request["msid"] + @"' 
                                   and convert(date,TB04.P_data7)='" + date + @"' 
                                  -- and TB04.P_data1 not like '%F%'
                                   order by TB04.P_data7";

                SqlCommand cmd = new SqlCommand(sql, conn);
                SqlDataAdapter sda = new SqlDataAdapter(cmd);
                sda.Fill(ds);
            }

            if (ds.Tables[0].Rows.Count <= 0)
            {
                context.Response.Write("[[" + DateTime.Parse(date).Subtract(new DateTime(1970, 1, 1)).TotalMilliseconds + ",0]]|[[" +
                                             DateTime.Parse(date).Subtract(new DateTime(1970, 1, 1)).TotalMilliseconds + ",0]]");
                return;
            }

            //context.Response.Write("got it.");

            StringBuilder builder1 = new StringBuilder();
            StringBuilder builder2 = new StringBuilder();
            StringBuilder builder3 = new StringBuilder();
            StringBuilder builder4 = new StringBuilder();
            StringBuilder builder5 = new StringBuilder();
            StringBuilder builder6 = new StringBuilder();
            StringBuilder builder7 = new StringBuilder();
            StringBuilder builder8 = new StringBuilder();

            #region comment
            //builder1.Append("[");
            //builder2.Append("[");
            //foreach (DataRow dr in ds.Tables[0].Rows)
            //{
            //    builder1.Append("[");
            //    //builder1.Append("gd("+DateTime.Parse(dr["GetTime"].ToString()).ToString("yyyy,MM,dd,hh,mm,ss")+")");
            //    builder1.Append(DateTime.Parse(dr["GetTime"].ToString()).Subtract(new DateTime(1970, 1, 1)).TotalMilliseconds);
            //    builder1.Append(",");
            //    builder1.Append(dr["tempOne"].ToString());
            //    builder1.Append("],");

            //    builder2.Append("[");
            //    //builder2.Append("gd(" + DateTime.Parse(dr["GetTime"].ToString()).ToString("yyyy,MM,dd,hh,mm,ss") + ")");
            //    builder2.Append(DateTime.Parse(dr["GetTime"].ToString()).Subtract(new DateTime(1970, 1, 1)).TotalMilliseconds);
            //    builder2.Append(",");
            //    builder2.Append(dr["tempTwo"].ToString());
            //    builder2.Append("],");
            //}
            //string builder1Str = builder1.ToString().Substring(0, builder1.ToString().Length - 1) + "]";
            //string builder2Str = builder2.ToString().Substring(0, builder2.ToString().Length - 1) + "]";
            #endregion

            string mapColumnName = string.Empty;
            if (dsMap == null) mapColumnName = "tempOne";
            if (dsMap != null && dsMap.Tables[0].Rows.Count == 0) mapColumnName = "tempOne";
            if (dsMap != null && dsMap.Tables[0].Rows.Count > 0) mapColumnName = dsMap.Tables[0].Rows[0]["result"].ToString();

            string builder1Str = ConstructSBuilder(builder1, ds, mapColumnName);
            string buildMinStr = ConstructSBuilder(builder2, ds, mapColumnName, float.Parse(min).ToString("0.0"));
            string buildMaxStr = ConstructSBuilder(builder3, ds, mapColumnName, float.Parse(max).ToString("0.0"));
            //string builder2Str = ConstructSBuilder(builder2, ds, "tempTwo");
            //string builder3Str = ConstructSBuilder(builder3, ds, "tempThree");
            //string builder4Str = ConstructSBuilder(builder4, ds, "tempFour");
            //string builder5Str = ConstructSBuilder(builder5, ds, "tempFive");
            //string builder6Str = ConstructSBuilder(builder6, ds, "tempSix");
            //string builder7Str = ConstructSBuilder(builder7, ds, "tempSeven");
            //string builder8Str = ConstructSBuilder(builder8, ds, "tempEight");

            // context.Response.Write(builder1Str + "|" + builder2Str + "|" + builder3Str + "|" + builder4Str + "|" + builder5Str + "|" + builder6Str + "|" + builder7Str + "|" + builder8Str);
            context.Response.Write(builder1Str + "|" + buildMinStr + "|" + buildMaxStr);
        }

        private string ConstructSBuilder(StringBuilder builder, DataSet ds, string rowFilled,string value)
        {
            builder.Append("[");
            foreach (DataRow dr in ds.Tables[0].Rows)
            {
                string result = dr[rowFilled].ToString();
                if (!result.ToUpper().Equals("FF.F"))
                {
                    builder.Append("[");
                    //builder1.Append("gd("+DateTime.Parse(dr["GetTime"].ToString()).ToString("yyyy,MM,dd,hh,mm,ss")+")");
                    builder.Append(DateTime.Parse(dr["GetTime"].ToString()).Subtract(new DateTime(1970, 1, 1)).TotalMilliseconds);
                    builder.Append(",");
                    builder.Append(value);
                    builder.Append("],");
                }
            }
            string builderStr = builder.ToString().Substring(0, builder.ToString().Length - 1) + "]";
            return builderStr;
        }

        private string ConstructSBuilder(StringBuilder builder, DataSet ds, string rowFilled)
        {
            builder.Append("[");
            foreach (DataRow dr in ds.Tables[0].Rows)
            {
                string result = dr[rowFilled].ToString();
                if (!result.ToUpper().Equals("FF.F"))
                {
                    builder.Append("[");
                    //builder1.Append("gd("+DateTime.Parse(dr["GetTime"].ToString()).ToString("yyyy,MM,dd,hh,mm,ss")+")");
                    builder.Append(DateTime.Parse(dr["GetTime"].ToString()).Subtract(new DateTime(1970, 1, 1)).TotalMilliseconds);
                    builder.Append(",");
                    builder.Append(result);
                    builder.Append("],");
                }
            }
            string builderStr = builder.ToString().Substring(0, builder.ToString().Length - 1) + "]";
            return builderStr;
        }
    }
}
View Code

首先,針對前台部分,我們需要注意幾個東西,第一個是Flot數據的組織方式;其次是折線如何配置並顯示;再者就是如何實現放大縮小;最后就是如何顯示ToolTip

 

數據的組織方式,通過查看API文檔,我們發現,Flot線條的數據組織方式如下[[x軸顯示的值,Y軸顯示的值],[x軸顯示的值,Y軸顯示的值]…..],也就就是

[[x1,y1],[x2,y2],...]的組織方式,所以在后台,我也是用這種方式來組織數據的。需要說明下,如果x軸是時間軸的話,我們需要計算從現在時間到1970年1月1日所有微秒的和,用C#表示就是:

builder.Append(DateTime.Parse(dr["GetTime"].ToString()).Subtract(new DateTime(1970, 1, 1)).TotalMilliseconds);

 

折線如何配置並顯示呢?這個通過API文檔,我們發現數據是這樣組織的:

  {
    color: color or number  //線條顏色
    data: rawdata    //你從后台拼接的數據,例如剛剛說的[[x1,y1],[x2,y2]...]
    label: string    //標簽文本,描述你的線條的名稱
    lines: specific lines options  //設置線條屬性,比如lines:{ show: true, lineWidth: 3 }
    bars: specific bars options //同上
    points: specific points options  //每個數據點的屬性,比如points: { show: true, fillColor: "#fff" }
    xaxis: number   //x軸屬性
    yaxis: number   //y軸屬性
    clickable: boolean  //是否可點擊
    hoverable: boolean  //是否可懸停
    shadowSize: number  //陰影效果
  }

看完這個,你再看看我HTML代碼中的設置,是不是恍然大悟呢?

 $.ajax({
                url: "Handler/FlotHandlerEx.ashx?date=" + time + "&msid=" + m_sDPID + "&min=" + min + "&max=" + max,
                success: function (data) {
                    var str = data;
                    //  line1 = eval(str);
                    var arrStr = str.split("|");
                    line1 = eval(arrStr[0]); //當前值
                    line2 = eval(arrStr[1]); //min value
                    line3 = eval(arrStr[2]); //max value
                    //Flot數據,注意其數據格式
                    var dataDetail = [{
                        label: "當前溫度值",
                        data: line1,
                        color: "#1AC7F1",
                        lines:{ show: true, lineWidth: 3 },
                        shadowSize: 5,
                         points: { show: true, fillColor: "#fff" }
                    }, {
                        label: "最小溫度值",
                        data: line2,
                        color: "#b55e00",
                        lines: { show: true, lineWidth: 1 },
                        shadowSize: 0,
                        points: { show: false, fillColor: "#fff" }
                    }, {
                        label: "最大溫度值",
                        data: line3,
                        color: "#b55e01",
                        lines: { show: true, lineWidth:1 },
                        shadowSize: 0,
                        points: { show: false, fillColor: "#fff" }
                    }];
                    TriggerChart(dataDetail);
                },
                error: function (data) {
                }
            });

數據組織好以后,利用Flot自帶的函數  $.plot("#detailContainer", data, detailOptions);  就可以將指定的數據綁定到界面了。需要說明的是detailOptions是指整個圖標的全局設置,包括y軸最大值,最小值,圖表背景色等等,可以參閱HTML源碼部分。

 

然后就是實現放大縮小功能了。由於在例子中,已經提供了說明,所以,我打開了例子的源碼,研究了之后,發現要實現放大縮小還是很簡單的。利用其提供的plotselected方法即可。

//綁定數據的縮放
$("#detailContainer").bind("plotselected", function (event, ranges) {
   plotDetail = $.plot($("#detailContainer"), data, $.extend(true, {}, detailOptions, { xaxis: { min: ranges.xaxis.from, max: ranges.xaxis.to} }));
});

其中,$.extend(true, {}, detailOptions, { xaxis: { min: ranges.xaxis.from, max: ranges.xaxis.to} })是關鍵點。

 

最后就是顯示ToolTip(注意,由於本人美工較差,ToolTip外形效果來自網上)功能了,因為Flot中提供了當前鼠標懸停的點的位置,所以我們可以通過編寫函數來實現懸停效果:

 var previousPoint = null, previousLabel = null;
        $.fn.UseTooltip = function () {
            $(this).bind("plothover", function (event, pos, item) {
                if (item) {
                    if ((previousLabel != item.series.label) || (previousPoint != item.dataIndex)) {
                        previousPoint = item.dataIndex;
                        previousLabel = item.series.label;
                        $("#tooltip").remove();
 
                        var x = item.datapoint[0];
                        var y = item.datapoint[1];
                        var date = new Date(x);
                        var color = item.series.color;
 
                        if (color == "#b55e00") {  //最小值
                            showTooltip(item.pageX, item.pageY, color,
                            "溫度最小值為: <strong>" + y + "</strong> (℃)");
                        }
                        else if (color == "#b55e01") {  //最大值
                            showTooltip(item.pageX, item.pageY, color,
                            "溫度最大值為: <strong>" + y + "</strong> (℃)");
                        }
                        else {
                            showTooltip(item.pageX, item.pageY, color,
                            "<strong>" + item.series.label + "</strong><br>" +
                            (date.getMonth() + 1) + "月" + date.getDate() + "日" + date.getUTCHours() + "時" + date.getMinutes() + "分" +
                            "   溫度: <strong>" + y + "</strong> (℃)");
                        }
                    }
                } else {
                    $("#tooltip").remove();
                    previousPoint = null;
                }
            });
        };
 
        function showTooltip(x, y, color, contents) {
            $('<div id="tooltip">' + contents + '</div>').css({
                position: 'absolute',
                display: 'none',
                top: y - 40,
                left: x - 120,
                border: '2px solid ' + color,
                padding: '3px',
                'font-size': '12px',
                'border-radius': '5px',
                'background-color': 'wheat',
                'font-family': '宋體,Verdana, Arial, Helvetica, Tahoma, sans-serif',
                opacity: 0.9
            }).appendTo("body").fadeIn(200);
        }

后台代碼我就不講解了,沒什么實際意義,只要按照API文檔組織好數據,就沒有什么問題,下面看下效果圖:

效果展示

QQ截圖20131031152813

(圖1,正常瀏覽)

QQ截圖20131031152835

(圖2,選擇瀏覽范圍)

QQ截圖20131031152847 

(圖3,瀏覽范圍選擇完畢,自動放大)

QQ截圖20131031152904

(圖4,ToolTip效果展示) 

 

當然了,如果你有多條線的情況下,你還可以通過選中或者是不選中CheckBox先展示一部分數據,只需要將代碼修改成如下結構即可:

 

效果如下圖:

QQ截圖20131031153720

(圖5,全部選中)

QQ截圖20131031153740

(圖5,只選中空氣濕度和土壤溫度)

代碼下載

點擊這里下載

 

 
 
 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM