工作中用到, 這里分享一下。 可以使用 amcharts 和 highcharts 在同一坐標中繪制多個對比曲線圖。 當然, 對圖形沒有過多裝飾, 可以參考 API 文檔:
highcharts: Highcharts API
amcharts: amcharts API
0. 說明
amcharts 與 highcharts 對於數據格式的要求是不一樣的。 amcharts 只需要一個 對象數組 [{'x': 1, 'y': 2, 'z': 3}, {'x':2, 'y': 4, 'z': 6}], 並指明 x ,y 軸的字段名,其它的就交給 amcharts 了; 而 highcharts 則需要對每個曲線圖定義好二維數組 , [[1,2], [2,4]] , [[1,3], [2,6]] ; 如果要使用對象數組返回格式, 就需要進行數據抽取和重組, 以符合 highchart 的要求。
由於 javascript 所使用的標准數據格式是 JSON, 因此, 可以非常方便地進行數據拼接和組合。 也就是說, 如果要在同一坐標中繪制多個曲線圖, 只需要定義一個數組, 將多個曲線圖的數據加入數組即可。
實現中, 有五點需要重點說明:
a. 時間字段。 由於不同的API 返回的JSON數據中,時間的字段名不一定相同(比如有的為 time, 有的為 timestamp), 並且時間的格式有所不同(比如有的為字符串, 有的為時間戳), 為了追求靈活性, 需要使用一個日期轉換函數 convertDate(chartData, timeStampFieldName, dateConvertFunc) 來統一處理。 這里統一轉換為 javascript timestamp 再轉化為 Date 類型, 可以兼容 Firefox 和 Chrome . 當然, 具體轉換為什么類型, 要視采取的庫支持決定。
b. 返回數據的格式, 主要以對象數組為主;
c. 由於要繪制任意多個曲線圖, 這里也需要考慮到靈活性, 利用了 JSON 格式的靈活性, 采用循環方式加入多個曲線的配置項來實現;
d. 在實現中,盡可能基於已有庫提供相同的使用接口, 這樣, 就可以在多個選擇中自由地切換;
e. 簡單的框架性處理: 通常顯示多圖時, 一般會顯示小圖, 然后點擊后顯示大圖。 這里, 點擊小圖后顯示大圖,最好做成框架中的處理, 客戶端使用不必操心這種事情,也避免了將類似的代碼分布在系統的各處。即盡量遵循“一個事實”的設計原則。
1. 返回數據格式
{"result":{"msg":null,"code":200,"data":[{"__source__":"10.201.20.35","__time__":"1380446527","blocked":"0","plist":"1517","runq":"0","ldavg_1":"2.34","ldavg_5":"1.56","ldavg_15":"1.43","time":"Sun Sep 29 17:22:07 2013"},{"__source__":"10.201.20.35","__time__":"1380446587","blocked":"0","plist":"1524","runq":"5","ldavg_1":"2.38","ldavg_5":"1.69","ldavg_15":"1.48","time":"Sun Sep 29 17:23:07 2013"},{"__source__":"10.201.20.35","__time__":"1380446647","blocked":"0","plist":"1523","runq":"3","ldavg_1":"1.51","ldavg_5":"1.56","ldavg_15":"1.45","time":"Sun Sep 29 17:24:07 2013"}],"success":true}}
2. HTML DIV
<body> <div id="perfcharts"> <div id="Loadperfchartdiv" style="width:100%; height:400px;" class="chartdiv"></div> </div> </body>
3. 使用 amcharts 繪制:
需要導入
<script src="../amcharts.js" type="text/javascript"></script> <script src="../jquery-1.10.1.min.js" type="text/javascript"></script> <script src="../drawchart.js" type="text/javascript"></script>
drawchart.js 是編寫的基於 amcharts 的在同一坐標中繪制多圖形的客戶端接口:
/** * 使用 amcharts 繪制時間趨勢曲線圖 * * generateChart: * chartDiv 繪圖所需要的 DIV 區域名稱; * chartData 繪圖所需要的數據 * chartConfig 繪圖的全局配置對象 * lineConfigArray 每個曲線圖的配置對象(配置Y軸) * */ var globalChart = null, globalChartData = null; function generateChart(chartDiv, chartData, chartConfig, lineConfigArray) { console.log('begin draw chart: ' + getNow()); // SERIAL CHART var chart = new AmCharts.AmSerialChart(); globalChart = chart; globalChartData = chartData; chart.pathToImages = "../resources/images/amcharts2/"; chart.zoomOutButton = { backgroundColor: '#000000', backgroundAlpha: 0.15 }; chart.dataProvider = chartData; chart.categoryField = "timeStamp"; // data updated event will be fired when chart is first displayed, // also when data will be updated. We'll use it to set some // initial zoom chart.addListener("dataUpdated", zoomChart); // AXES // Category var categoryAxis = chart.categoryAxis; categoryAxis.parseDates = true; // in order char to understand dates, we should set parseDates to true categoryAxis.minPeriod = "mm"; // as we have data with minute interval, we have to set "mm" here. categoryAxis.gridAlpha = 0.07; categoryAxis.axisColor = "#DADADA"; categoryAxis.labelFunction = function(valueText, date, categoryAxis) { var MM = date.getMonth()+1; var dd = date.getDate(); var hh = date.getHours(); if(hh<10) hh = '0' + hh; var mm = date.getMinutes(); if(mm<10) mm = '0' + mm; var ss = date.getSeconds(); return MM+'-'+dd+' '+hh+':'+mm; } // Value var valueAxis = new AmCharts.ValueAxis(); valueAxis.gridAlpha = 0.07; valueAxis.title = chartConfig.title; chart.addValueAxis(valueAxis); // GRAPH for (var i=0; i<lineConfigArray.length;i++) { var graph = new AmCharts.AmGraph(); graph.type = "line"; graph.title = lineConfigArray[i].title; graph.valueField = lineConfigArray[i].valueField; graph.lineAlpha = 1; graph.lineColor = lineConfigArray[i].lineColor; chart.addGraph(graph); } // CURSOR var chartCursor = new AmCharts.ChartCursor(); chartCursor.cursorPosition = "mouse"; chartCursor.categoryBalloonDateFormat = "MM DD JJ:NN"; chart.addChartCursor(chartCursor); // SCROLLBAR var chartScrollbar = new AmCharts.ChartScrollbar(); chart.addChartScrollbar(chartScrollbar); // LEGEND var legend = new AmCharts.AmLegend(); legend.marginLeft = 110; chart.addLegend(legend); // WRITE chart.write(chartDiv); console.log('end draw chart: ' + getNow()); } function convertDate(chartData, timeStampFieldName, dateConvertFunc) { for (var i=0; i<chartData.length;i++) { var timeStamp_i = chartData[i][timeStampFieldName == null ? "timeStamp": timeStampFieldName]; if (typeof dateConvertFunc === 'function') { chartData[i].timeStamp = dateConvertFunc(timeStamp_i); } else { chartData[i].timeStamp = new Date(timeStamp_i); } } return chartData; } function zoomChart() { globalChart.zoomToIndexes(0, globalChartData.length - 1); }
客戶端使用方法 :
AmCharts.ready(function () { var drawLoadperf = function() { $.ajax({ dataType: "json", url: httpPrefix + '/controllers/sls/obtainNcLoad', data: 'Category=load_log_index&' + params, success: function(data) { var chartData = convertDate(data.result.data, "time"); generateChart('Loadperfchartdiv', chartData, {'title': 'Load'}, [{'title':'load_1', 'valueField': 'ldavg_1', 'lineColor': '#ff0000'}, {'title':'load_5', 'valueField': 'ldavg_5', 'lineColor': '#00ff00'}, {'title':'load_15', 'valueField': 'ldavg_15', 'lineColor': '#0000ff'}]); } }); } }
amcharts 效果圖:

4. 使用 highcharts 繪制:
需要導入:
<script src="../jquery-1.10.1.min.js" type="text/javascript"></script>
<script src="../highcharts.js" type="text/javascript"></script>
<script src="../drawchart_highcharts.js" type="text/javascript"></script>
drawchart_highcharts.js 是基於 highcharts 的在同一坐標中繪制多個圖形的客戶端接口。點擊可以放大顯示,再點擊則還原到原來的視圖。
/** * 使用 highcharts 繪制時間趨勢曲線圖 * * generateChart: * chartDiv 繪圖所需要的 DIV 區域名稱; * chartData 繪圖所需要的數據 * chartConfig 繪圖的全局配置對象 * lineConfigArray 每個曲線圖的配置對象(配置Y軸) * */ Highcharts.setOptions({ global: { useUTC: false }, lang: { resetZoom: '還原視圖' } }); function generateChart(chartDiv, chartData, chartConfig, lineConfigArray) { var chartObj = { chart: { zoomType: 'x', events: { // 點擊圖表后在指定區域 zoomUpDiv 放大顯示 click: null } }, // 去掉 highcharts.com 鏈接 credits: { enabled: false, text: '' }, plotOptions: { series: { // 去掉點的marker, 使圖形更美觀 marker: { enabled: false, states: { hover: { enabled: true } } } } }, series: [], xAxis: { type: 'datetime', dateTimeLabelFormats: { hour: '%m-%d %H:%M' } }, yAxis: { title: { text: '' }, min: 0 }, tooltip: { crosshairs: true, shared: true, formatter: function() { // 當鼠標懸停圖表上時, 格式化提示信息 var tipText = '<b>' + Highcharts.dateFormat('%m-%d %H:%M', this.x) + '</b>'; $.each(this.points, function(i, point) { tipText += '<br/>'+ point.series.name +': '+ point.y; }); return tipText; } }, title: { // 不顯示圖表標題 text: null } }; // 在同一坐標中繪制多個曲線圖 for (var i=0; i<lineConfigArray.length;i++) { var subseries = { name: lineConfigArray[i].title, data: extract(chartData, lineConfigArray[i].valueField), color: lineConfigArray[i].lineColor }; chartObj.yAxis.title.text = chartConfig.title; chartObj.series.push(subseries); } // 點擊圖表后在指定區域 zoomUpDiv 放大顯示 chartObj.chart.events.click = function(event) { var zoomUpDiv = $('#zoomUpDiv'); if (zoomUpDiv != null) { $('#zoomUpDiv').css('display', 'block'); $('.chartdiv').css('display', 'none'); chartObj.chart.events.click = function(event) { $('#zoomUpDiv').css('display', 'none'); $('.chartdiv').css('display', 'block'); }; $('#zoomUpDiv').highcharts(chartObj); } }; $("#"+chartDiv).highcharts(chartObj); } /** * 指定后台返回的時間字段為 timeStampFieldName , 若不指定則默認為 timeStamp , * 並利用自定義回調函數 dateConvertFunc 統一為時間戳進行處理 */ function convertDate(chartData, timeStampFieldName, dateConvertFunc) { for (var i=0; i<chartData.length;i++) { var timeStamp_i = chartData[i][timeStampFieldName == null ? "timeStamp": timeStampFieldName]; if (typeof dateConvertFunc === 'function') { // 如果給定了自定義的時間轉換回調函數, 則使用該函數 chartData[i].timeStamp = dateConvertFunc(timeStamp_i); } else if (typeof timeStamp_i === 'number') { // 適用於時間戳 chartData[i].timeStamp = timeStamp_i; } else { // 適用於格式 "Sun Sep 29 17:24:07 2013", "2013-09-29 17:23:09" chartData[i].timeStamp = new Date(timeStamp_i); } } return chartData; } /** * 從 chartData 中抽取出 valueField 數據 */ function extract(chartData, valueField) { var valueData = []; var i=0, len = chartData.length; for (i=0; i<len; i++) { valueData.push([chartData[i].timeStamp, parseFloat(chartData[i][valueField])]); } return valueData; }
客戶端使用:
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd"> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=utf-8"> <title>宿主機性能狀態曲線圖</title> <script src="../jquery-1.10.1.min.js" type="text/javascript"></script> <script src="../highcharts.js" type="text/javascript"></script> <script src="../drawchart_highcharts.js" type="text/javascript"></script> <link rel="stylesheet" type="text/css" href="../resources/css/chart.css" /> <script type="text/javascript"> $(document).ready(function () { var iframeurl = parent.document.getElementById("ncperf").src var params = iframeurl.substring(iframeurl.lastIndexOf('?')+1); $(function () { var drawLoadperf = function() { $.ajax({ dataType: "json", url: httpPrefix + '/controllers/sls/obtainNcLoad', data: 'Category=load_log_index&' + params, success: function(data) { var chartData = convertDate(data.result.data, "time"); generateChart('Loadperfchartdiv', chartData, {'title': 'Load'}, [{'title':'load_1', 'valueField': 'ldavg_1', 'lineColor': '#f58220'}, {'title':'load_5', 'valueField': 'ldavg_5', 'lineColor': '#00ff00'}, {'title':'load_15', 'valueField': 'ldavg_15', 'lineColor': '#0000ff'}]); } }); } var drawCpuperf = function() { $.ajax({ dataType: "json", url: httpPrefix + '/controllers/sls/obtainNcCpuUsage', data: 'Category=dom0_log_index&' + params, success: function(data) { var chartData = convertDate(data.result.data, "time"); generateChart('CpuUsageperfchartdiv', chartData, {'title': 'Cpu 使用率(%)'}, [{'title':'usr', 'valueField': 'usr', 'lineColor': '#f58220'}, {'title':'sys', 'valueField': 'sys', 'lineColor': '#00ff00'}, {'title':'idle', 'valueField': 'idle', 'lineColor': '#0000ff'}, {'title':'iowait', 'valueField': 'iowait', 'lineColor': '#ffe600'}]); } }); } var drawNetworkperf = function() { $.ajax({ dataType: "json", url: httpPrefix + '/controllers/sls/obtainNcNetworkflow', data: 'Category=netflow_log_index&' + params, success: function(data) { var chartData = convertDate(data.result.data, "time"); generateChart('Networkflowchartdiv', chartData, {'title': '網絡流量 (KB/s)'}, [{'title': 'RECV_BYTES', 'valueField': 'rxkB', 'lineColor': '#f58220'}, {'title': 'TRAN_BYTES', 'valueField': 'txkB', 'lineColor': '#00ff00'}]); } }); } var drawIoperf = function() { $.ajax({ dataType: "json", url: httpPrefix + '/controllers/sls/obtainNcIoperf', data: 'Category=iostat_log_index&' + params, success: function(data) { var chartData = convertDate(data.result.data, "time"); generateChart('Iorwperfchartdiv', chartData, {'title': 'io讀寫 (KB/s)'}, [{'title': 'read_iops', 'valueField': 'rkB_s', 'lineColor': '#f58220'}, {'title': 'write_iops', 'valueField': 'wkB_s', 'lineColor': '#00ff00'}]); generateChart('Ioutilchartdiv', chartData, {'title': 'io利用率 (%)'}, [{'title': 'ioutil', 'valueField': 'util', 'lineColor': '#f58220'}]); } }); } // 間隔 一段時間 后發送下一個異步請求, 避免服務端線程競爭導致錯誤 setTimeout(drawCpuperf, 500); setTimeout(drawNetworkperf, 2000); setTimeout(drawIoperf, 3500); drawLoadperf(); }); }); // $document </script> </head> <body> <div id="zoomUpDiv"></div> <div id="perfcharts"> <div id="Loadperfchartdiv" class="chartdiv"></div> <div id="CpuUsageperfchartdiv" class="chartdiv"></div> <div id="Networkflowchartdiv" class="chartdiv"></div> <div id="Iorwperfchartdiv" class="chartdiv"></div> <div id="Ioutilchartdiv" class="chartdiv"></div> </div> </body> </html>
樣式文件: chart.css
* {font-family: 微軟雅黑, 宋體, san-serif!important} .chartdiv { float: left; margin: 5px 15px 5px 10px; width: 45%; height: 150px; } #zoomUpDiv { display: none; position: absolute; top: 20px; left: 10px; margin: 10px 15px 10px 10px; width: 95%; height: 80%; z-index: 5; }
highcharts 效果圖


