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>

其次,这是我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[].Rows.Count <= )
{
context.Response.Write("[[" + DateTime.Parse(date).Subtract(new DateTime(, , )).TotalMilliseconds + ",0]]|[[" +
DateTime.Parse(date).Subtract(new DateTime(, , )).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[].Rows.Count == ) mapColumnName = "tempOne";
if (dsMap != null && dsMap.Tables[].Rows.Count > ) mapColumnName = dsMap.Tables[].Rows[]["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[].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(, , )).TotalMilliseconds);
builder.Append(",");
builder.Append(value);
builder.Append("],");
}
}
string builderStr = builder.ToString().Substring(, builder.ToString().Length - ) + "]";
return builderStr;
} private string ConstructSBuilder(StringBuilder builder, DataSet ds, string rowFilled)
{
builder.Append("[");
foreach (DataRow dr in ds.Tables[].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(, , )).TotalMilliseconds);
builder.Append(",");
builder.Append(result);
builder.Append("],");
}
}
string builderStr = builder.ToString().Substring(, builder.ToString().Length - ) + "]";
return builderStr;
}
}
}

首先,针对前台部分,我们需要注意几个东西,第一个是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文档组织好数据,就没有什么问题,下面看下效果图:

效果展示

基于Flot可放缩的折线图-LMLPHP

(图1,正常浏览)

基于Flot可放缩的折线图-LMLPHP

(图2,选择浏览范围)

基于Flot可放缩的折线图-LMLPHP

(图3,浏览范围选择完毕,自动放大)

基于Flot可放缩的折线图-LMLPHP

(图4,ToolTip效果展示)

当然了,如果你有多条线的情况下,你还可以通过选中或者是不选中CheckBox先展示一部分数据,只需要将代码修改成如下结构即可:

效果如下图:

基于Flot可放缩的折线图-LMLPHP

(图5,全部选中)

基于Flot可放缩的折线图-LMLPHP

(图5,只选中空气湿度和土壤温度)

代码下载

点击这里下载


 
 
 


04-15 19:45