如何畫Flot折線圖

折線圖是最常被使用的圖形, 也是最容易繪製的圖表, 它可以顯示時間與趨勢的走向, 將各項數據圖表化, 讓人可以對這些數字與時間的相對關係一目了然, 因為這是Flot教學裡第一個圖表, 我們會完整的呈現所有細節及過程. 本章裡以2012年的黃金價格走勢做為範例, 我們將會以每月黃金的價格以及變動率做為資料來源

製作折線圖流程

本章以下圖折線圖為範例

加入所需要的Flot檔案

將繪製折線圖所會用到的檔案加到<head>裡, 第一個最重要的就是jQuery.js需要放在第一位, 再來就是Flot主要的檔案jquery.flot.js, 接下來因為我們繪製的是時間資料格式的圖表, 所以也需要把jquery.flot.time.js加進來

最後的2個檔案和jquery.flot.axislabels.js皆是Flot的外掛程式, Flot預設的資料點(data point)符號是圓型(circle), 因為我們有改變資料點符號, 所以需要加入jquery.flot.symbol.js, 另外, Flot本身並不支援軸標籤的設置, 所以需要透過加入jquery.flot.axislabels.js外掛程式來達成軸標籤的設置.

以下程式碼就是此範例所會用到的全部檔案
    
        <script type="text/javascript" src="/js/jquery-1.8.3.min.js"></script>       
         <!--[if lte IE 8]><script language="javascript" type="text/javascript" src="/js/flot/excanvas.min.js"></script><![endif]-->    
        <script type="text/javascript" src="/js/flot/jquery.flot.js"></script>
        <script type="text/javascript" src="/js/flot/jquery.flot.time.js"></script>    
        <script type="text/javascript" src="/js/flot/jquery.flot.symbol.js"></script>
        <script type="text/javascript" src="/js/flot/jquery.flot.axislabels.js"></script>
        

HTML5 canvas支援

如果你是使用Internet Explorer, 且版本為8以下的, 瀏覽有用Flot繪製圖表的網頁可能無法正常觀看, 因為這些瀏覽器不支援HTML5 canvas標籤, 不過別擔心, 只要加入ExplorerCanvas外掛程式(excanvas.min.js)就可以讓Internet Explorer也能正常顯示Flot圖表. 檔案可以到此下載.

加入定位點

準備好所需要的檔案後, 接下來就是決定Flot要繪圖的位置, 你可以自行決定Flot要在那裡出現, 只要在<body>加上下面的<div>標籤, 並給一個id, 後面要繪製時會用到此id. 另一個比較重要的是, 你必須設置定位點的長度和寬度, 如果沒有設置就有可能發生Uncaught Invalid dimensions for plot, width = 0, height = 0的script錯誤.
   
        <div id="flot-placeholder" style="width:300px;height:150px"></div>
        

準備折線圖所需資料

資料是繪圖過程中最重要的部份, 資料不正確繪製出來的圖就會不正確, 2012年的黃金價格及變動率資料如下
   
        //Gold Price
        var data1 = [
            [gd(2012, 0, 1), 1652.21], [gd(2012, 1, 1), 1742.14], [gd(2012, 2, 1), 1673.77], [gd(2012, 3, 1), 1649.69],
            [gd(2012, 4, 1), 1591.19], [gd(2012, 5, 1), 1598.76], [gd(2012, 6, 1), 1589.90], [gd(2012, 7, 1), 1630.31],
            [gd(2012, 8, 1), 1744.81], [gd(2012, 9, 1), 1746.58], [gd(2012, 10, 1), 1721.64], [gd(2012, 11, 1), 1684.76]
        ];

        //Change
        var data2 = [
            [gd(2012, 0, 1), 0.63], [gd(2012, 1, 1), 5.44], [gd(2012, 2, 1), -3.92], [gd(2012, 3, 1), -1.44],
            [gd(2012, 4, 1), -3.55], [gd(2012, 5, 1), 0.48], [gd(2012, 6, 1), -0.55], [gd(2012, 7, 1), 2.54],
            [gd(2012, 8, 1), 7.02], [gd(2012, 9, 1), 0.10], [gd(2012, 10, 1), -1.43], [gd(2012, 11, 1), -2.14]
        ];

        var dataset = [
            { label: "Gold Price", data: data1, points: { symbol: "triangle"} },
            { label: "Change", data: data2, yaxis: 2 }
        ];
        
因為有2組原如資料, 我們分別叫做data1(金價)和data2(變動率), 每組資料都是一個陣列, 裡面儲存了每個月的資料, 格式為[x, y], 因為x軸部份我們是用時間格式的, 而Flot的時間格式資料必須為數字格式, 且是用javascript timestamps格式, 也就是毫秒. 而y軸就是黃金價格.
data1格式如下
   
        [gd(2012, 0, 1), 1652.21]
        
也就等於是
   
        [1325347200000, 1652.21]
        
因為我們為了資料建立的方便, 所以自訂一個抓取日期的函式叫gd, 程式碼如下
   
        function gd(year, month, day) {
            return new Date(year, month, day).getTime();
        }
        
如此一來建立日期資料就方便許多, 日後要維護也比較容易 (如果你對Flot資料格式還不是很熟悉, 可以隨時回到Flot資料格式介紹去看). 而另一組的變動率的資料做法跟金價的做法是一樣的.

最後我們再把這2組原始資料結合成一個資料組(dataset), 再分別設定標籤(label)為"Gold Price"和"Change", 將data屬性分別設為data1和data2, 在dataset裡如果你不設定其它的屬性也可以, 唯一不可缺少的就是data屬性. 另外我們還設定了points屬性, 把其中的symbol設為triangle, 表示資料點符號會以三角形呈現.
   
        var dataset = [            
            { label: "Gold Price", data: data1, points: { symbol: "triangle"} },  //first data seires
            { label: "Change", data: data2, yaxis: 2 }                            //second data seires
        ];
        

自訂軸屬性

到這裡已經完成了80%的部份, 接下來這個部份就是美化你的圖表, Flot提供了相當多的屬性可以讓你自訂圖表的細節及外觀, 在這我們先就軸的部份說明. 因為我們所繪製的圖表是以x軸為時間格式, 當你用時間格式資料時, 就必須把軸屬性裡的mode設為"time", 設定之後Flot就會把該軸的資料當作時間來解讀.

另外還有刻度間隔大小(tickSize) tickSize能接受的值為數字或是陣列, 當你設定為2時, 刻度會變成顯示2, 4, 6, .., 設定為3時會變成顯示3, 6, 9, ...依此類推, 當你設定成陣列時, 如tickSize: [1, "month"], 刻度就會顯示成月份如Jan, Feb, Mar, ..., 除了"month"外也可設定成"day"、"year"、"hour"等.

因為Flot本身不支援軸標籤功能, 所以在加入檔案時也一併加入了jquery.flot.axislabels.js外掛程式讓Flot能支援,所謂的軸標籤就是在圖表的四周顯示的標籤文字,屬性axisLabel用來設定標籤顯示文字, axisLabelFontSizePixels可設定文字大小, axisLabelUseCanvas用來設定文字是否以Canvas方式繪製. 範例程式碼如下...
   
        xaxis: {
            mode: "time",
            tickSize: [1, "month"],
            tickLength: 0,
            axisLabel: "2012",
            axisLabelUseCanvas: true,
            axisLabelFontSizePixels: 12,
            axisLabelFontFamily: 'Verdana, Arial',
            axisLabelPadding: 10
        },
        yaxes: [{
                axisLabel: "Gold Price(USD)",
                axisLabelUseCanvas: true,
                axisLabelFontSizePixels: 12,
                axisLabelFontFamily: 'Verdana, Arial',
                axisLabelPadding: 3,
                tickFormatter: function (v, axis) {
                    return $.formatNumber(v, { format: "#,###", locale: "us" });
                }
            }, { 
                position: "right",
                axisLabel: "Change(%)",
                axisLabelUseCanvas: true,
                axisLabelFontSizePixels: 12,
                axisLabelFontFamily: 'Verdana, Arial',
                axisLabelPadding: 3
            }
        ]
        
因為此章用的是多軸圖, 所以原本用於設定軸屬性的原本是yaxis, 範例如下
   
        xaxis: {},
        yaxis: {}        
        
但因為有多個軸, 所以yaxis必須改為yaxes, 且必須使用陣列如下面範例
   
        xaxis: {},
        yaxes: [
            { 
                //first yaxis 
            },{ 
                //second yaxis 
            }
        ]        
        

呼叫$.plot函式

當你完成了以上所有的工作, 來到這最後的部份是最簡單的, 只需要呼叫$.plot並帶入參數即可. 如果你不熟悉$.plot函式可到繪製你的第一個圖表去回顧.

繪製Flot圖表其實沒想像中的難, 只是需要時間去熟悉, 而繪製折線圖是最好的開始, 後面的章節將會把其它的圖表如直條圖、橫條圖、區域圖等一一的介紹, 相信你會越來越駕輕就熟!
   
        $(document).ready(function () {
            $.plot($("#flot-placeholder"), dataset, options);            
        });
        

本章完整程式碼

   
<!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>
    <title></title>
    <script src="/js/lib/jquery-1.8.3.min.js" type='text/javascript'></script>  
    <!--[if lte IE 8]><script language="javascript" type="text/javascript" src="/js/flot/excanvas.min.js"></script><![endif]-->
    
    <script type="text/javascript" src="/js/flot/jquery.flot.min.js"></script>
    <script type="text/javascript" src="/js/flot/jquery.flot.time.js"></script>    
    <script type="text/javascript" src="/js/flot/jquery.flot.symbol.js"></script>
    <script type="text/javascript" src="/js/flot/jquery.flot.axislabels.js"></script>
    <script type="text/javascript" src="/js/flot/jshashtable-2.1.js"></script>    
    <script type="text/javascript" src="/js/flot/jquery.numberformatter-1.2.3.min.js"></script> 
    
   

    <script>
        //******* 2012 Gold Price Chart
        var data1 = [
            [gd(2012, 0, 1), 1652.21], [gd(2012, 1, 1), 1742.14], [gd(2012, 2, 1), 1673.77], [gd(2012, 3, 1), 1649.69],
            [gd(2012, 4, 1), 1591.19], [gd(2012, 5, 1), 1598.76], [gd(2012, 6, 1), 1589.90], [gd(2012, 7, 1), 1630.31],
            [gd(2012, 8, 1), 1744.81], [gd(2012, 9, 1), 1746.58], [gd(2012, 10, 1), 1721.64], [gd(2012, 11, 2), 1684.76]
        ];

        var data2 = [
            [gd(2012, 0, 1), 0.63], [gd(2012, 1, 1), 5.44], [gd(2012, 2, 1), -3.92], [gd(2012, 3, 1), -1.44],
            [gd(2012, 4, 1), -3.55], [gd(2012, 5, 1), 0.48], [gd(2012, 6, 1), -0.55], [gd(2012, 7, 1), 2.54],
            [gd(2012, 8, 1), 7.02], [gd(2012, 9, 1), 0.10], [gd(2012, 10, 1), -1.43], [gd(2012, 11, 2), -2.14]
        ];
        var dataset = [
            { label: "Gold Price", data: data1, points: { symbol: "triangle"} },
            { label: "Change", data: data2, yaxis: 2 }
        ];

        var options = {
            series: {
                lines: {
                    show: true
                },
                points: {
                    radius: 3,
                    fill: true,
                    show: true
                }
            },
            xaxis: {
                mode: "time",
                tickSize: [1, "month"],
                tickLength: 0,
                axisLabel: "2012",
                axisLabelUseCanvas: true,
                axisLabelFontSizePixels: 12,
                axisLabelFontFamily: 'Verdana, Arial',
                axisLabelPadding: 10
            },
            yaxes: [{
                axisLabel: "Gold Price(USD)",
                axisLabelUseCanvas: true,
                axisLabelFontSizePixels: 12,
                axisLabelFontFamily: 'Verdana, Arial',
                axisLabelPadding: 3,
                tickFormatter: function (v, axis) {
                    return $.formatNumber(v, { format: "#,###", locale: "us" });
                }
            }, {
                position: "right",
                axisLabel: "Change(%)",
                axisLabelUseCanvas: true,
                axisLabelFontSizePixels: 12,
                axisLabelFontFamily: 'Verdana, Arial',
                axisLabelPadding: 3
            }
          ],
            legend: {
                noColumns: 0,
                labelBoxBorderColor: "#000000",
                position: "nw"
            },
            grid: {
                hoverable: true,
                borderWidth: 2,
                borderColor: "#633200",
                backgroundColor: { colors: ["#ffffff", "#EDF5FF"] }
            },
            colors: ["#FF0000", "#0022FF"]
        };

        $(document).ready(function () {
            $.plot($("#flot-placeholder1"), dataset, options);
            $("#flot-placeholder1").UseTooltip();
        });




        function gd(year, month, day) {
            return new Date(year, month, day).getTime();
        }

        var previousPoint = null, previousLabel = null;
        var monthNames = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"];

        $.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 color = item.series.color;
                        var month = new Date(x).getMonth();

                        //console.log(item);

                        if (item.seriesIndex == 0) {
                            showTooltip(item.pageX,
                            item.pageY,
                            color,
                            "<strong>" + item.series.label + "</strong><br>" + monthNames[month] + " : <strong>" + y + "</strong>(USD)");
                        } else {
                            showTooltip(item.pageX,
                            item.pageY,
                            color,
                            "<strong>" + item.series.label + "</strong><br>" + monthNames[month] + " : <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': '9px',
                'border-radius': '5px',
                'background-color': '#fff',
                'font-family': 'Verdana, Arial, Helvetica, Tahoma, sans-serif',
                opacity: 0.9
            }).appendTo("body").fadeIn(200);
        }
    </script>
</head>
<body>
    <div style="width:450px;height:300px;text-align:center;margin:10px">        
        <div id="flot-placeholder1" style="width:100%;height:100%;"></div>        
    </div>
</body>
</html>
        


練習

本章的完整範例程式碼可以在這裡找到並做線上練習 Go to Example Tester