最近忙着給部門開發一套交互式的報表系統,來替換原有的靜態報表系統。git
老系統是基於dotnetCHARTING開發的,dotnetCHARTING的優點是圖表類型豐富,接口調用簡單,使用時只需綁定數據源便可(指定鏈接字符和sql語句,簡單的配置一下就能出圖),支持生成靜態圖表圖片;缺點就是生成好的圖是圖片,傳到了前臺就失去了交互性(固然它還提供了一個jsCharting,不過感受交互性作的仍是不夠好),再有就是這東東是收費的呀,用的話須要折騰破解版本。github
我最終選擇了Highcharts(Interactive JavaScript charts for your webpage)來展示前臺圖表,經過Highcharts良好的交互性實現與服務端的數據交互,將數據可視化。web
dotnetCHARTING在數據加載的設計上作的仍是很不錯的,我在開發過程當中借鑑了其處理思想,本身實現了一套數據加載方案,可以很方便的把數據傳給Highcharts。這套數據加載方案,簡單的說就是指定好數據庫鏈接信息和sql查詢信息,服務端採用ADO.NET執行查詢生成DataSet,而後分析DataSet將數據轉換爲Highcharts可以直接使用的json格式。sql
報表的處理細節仍是蠻多的,這裏就不在一一討論了,如題,接下來重點跟你們分享一下,服務端生成圖表圖片那部分的處理細節。數據庫
生成圖片的數據流向卻是比較簡單,以下圖所示:json
根據上述生成圖片的步驟,核心其實就是對第二步的處理,也就是如何將SVG數據在服務端作處理,生成圖表圖片。瀏覽器
這樣的話,咱們的處理思路就很清晰了,直接在服務端把SVG處理爲圖片不就能夠了,這麼想,也就這麼作,恰好網上也有人這麼弄過,因而也就直接借鑑了其代碼,代碼不上了,介紹下用到的dll:app
在nuget中搜索svg,能夠找到一個SVG Rendering Library的包,能夠用這個包將SVG格式的數據保存爲圖片,用法也比較簡單,你們能夠到其官網查閱使用方法。asp.net
這個你們沒必要本身去實現,由於highcharts官網已經給出了第三方的ASP.NET導出圖表的模塊(他就是基於這個SVG Rendering Library實現的):less
https://github.com/imclem/Highcharts-export-module-asp.net
在使用SVG Rendering Library服務端生成圖表圖片的過程當中,發現一些問題:
先看一下圖片質量的問題,首先是Chrome中實際展示的圖表的截圖:
在來一張使用svg.dll在後臺生成的圖:
對比着兩張圖,能夠和明顯的看出生成的圖片中漢字發虛(尤爲是下面的月份)。正是這個緣由,促使我去尋找一個更好的方案來替代SVG Rendering Library,以確保服務端生成圖表圖片的質量。
心想highcharts在瀏覽器中的顯示效果已經不錯了,要不作截圖,可是截圖的話跟服務端也不要緊了呀,忽然想到了在服務端渲染截圖這麼個思路。可是具體怎麼作呢?先找找資料吧。
第一次接觸Phantomjs是半年前左右,當時正在開發web漏洞檢測工具,須要執行頁面上的js,進行分析,沒有經驗的我,各處找資料,看到PhantomJS後,心想,這貨不是已經有人作過了麼,幹嗎還重複造車輪子,後來隨着業務變動,也沒有深刻研究它。
此次搜索「服務端,截圖」這個關鍵字的時候,再次看到了PhantomJS,對它的印象不深了,先去官網看看介紹吧,PhantomJS: Headless WebKit with JavaScript API,哦,原來是個能夠執行js並集成了webkit的動動,只是沒有可視化的部分而已。
PhantomJS能幹啥呢?
對Phantomjs作過一番瞭解後,就肯定用它來處理服務端生成圖表圖片的問題了。我設計的處理流程以下:
畫的很挫,能看明白處理過程就好,接下來分享一下具體處理過程當中須要解決的問題。
圖表的其餘配置不須要修改,只需修改導出圖片的配置便可,導出的配置以下:
var chart = new Highcharts.Chart({ //... exporting: { url: '/Chart/Export', // 導出圖表的服務端處理地址 filename: 'chart_from_phantomjs' // 返回下載的文件名 }, //... });
咱們使用Chrome調試一下,看看下載圖片的時候,Highcharts都向服務端提交了哪些信息,截圖以下:
Highcharts向/Chart/Export發送了一個Post請求,提交的信息如上圖所示,在服務端,咱們須要根據type來生成不一樣的圖片格式,能夠經過svg獲取Highcharts提交的圖表數據。
首先直接將Highcharts傳遞的SVG數據保存爲本地文件,PhantomJS須要經過http://xxx/xxx.svg的形式請求SVG圖像,直接請求ASP.NET會以將svg數據以文件的形式返回,所以須要對svg的請求作單獨處理。代碼以下:
/// <summary> /// 處理Svg文件請求,避免直接返回文件 /// </summary> public class SvgHandler : IHttpHandler { public void ProcessRequest(HttpContext context) { var file = context.Server.MapPath(context.Request.Url.AbsolutePath); if (File.Exists(file)) { context.Response.ContentType = "image/svg+xml"; context.Response.WriteFile(file); } else { context.Response.Write("請求的文件不存在"); } } public bool IsReusable { get { return true; } } }
最後在Web.config中配置一下:
<httpHandlers> <add verb="*" path="*.svg" type="Highcharts.Exporting.Helper.SvgHandler, Highcharts.Exporting, Version=1.0.0.0, Culture=neutral"/> </httpHandlers>
因爲PhantomJS是個獨立的進程,這樣ASP.NET在與之交互的時候須要讓PhantomJS一直運行,否則每次啓動一個新的進程開銷也比較大。
PhantomJS支持js腳本調用,咱們能夠經過編寫腳本實現PhantomJS以服務的方式長期運行,代碼篇幅較長,下面會給出源碼。
PhantomJS中經過接收post請求,從請求信息中獲取url信息,url就是要渲染的SVG地址,將對應SVG渲染截圖,並返回BASE64編碼的數據處理,代碼以下:
page.open(req.post.url,function(status){ if(status !== "success"){ res.send(status); } else { setTimeout(function() { // 發送渲染後的圖片 var pic = page.renderBase64('png'); res.send(pic); }, req.post.timeout || 1000); } });
PhantomJS截圖服務腳本:點此下載。啓動方法:PhantomJS server.js [port]如不指定端口號,則默認使用8000端口:
ASP.NET須要將PhantomJS返回的BASE64數據反編碼,獲得PNG圖像數據,而後結合須要返回的圖片類型作格式轉換,並以文件的形式返回給客戶端瀏覽器,核心代碼以下:
// 提交SvgUrl到PhantomJS,讓其生成圖片 WebClient webClient = new WebClient(); NameValueCollection postValues = new NameValueCollection(); postValues.Add("url", siteUrl + svgFile); byte[] data = webClient.UploadValues(phantomJSUrl, postValues); // 從返回的Base64編碼中獲取圖片數據 string imageInfo = Encoding.UTF8.GetString(data); if (!String.IsNullOrEmpty(imageInfo)) { data = Convert.FromBase64String(imageInfo); MemoryStream ms = new MemoryStream(); ms.Write(data, 0, data.Length); image = Image.FromStream(ms); ms.Close(); }
返回Highcharts請求的圖片信息:
MemoryStream tStream = new MemoryStream(); var image = ImageHelper.SvgImageFromPhantomJs(tSvg); string tExt = "png"; string tTypeString = "-m image/png"; switch (tType) { case "image/png": tTypeString = "-m image/png"; tExt = "png"; break; case "image/jpeg": tTypeString = "-m image/jpeg"; tExt = "jpg"; break; case "application/pdf": tTypeString = "-m application/pdf"; tExt = "pdf"; break; case "image/svg+xml": tTypeString = "-m image/svg+xml"; tExt = "svg"; break; } if (tTypeString != "") {switch (tExt) { case "jpg": image.Save(tStream, ImageFormat.Jpeg); break; case "png": image.Save(tStream, ImageFormat.Png); break; case "pdf": PdfWriter tWriter = null; Document tDocumentPdf = null; try { image.Save(tStream, ImageFormat.Png); tDocumentPdf = new Document(new Rectangle(image.Width, image.Height)); tDocumentPdf.SetMargins(0.0f, 0.0f, 0.0f, 0.0f); iTextSharp.text.Image tGraph = iTextSharp.text.Image.GetInstance(tStream.ToArray()); tGraph.ScaleToFit(image.Width, image.Height); tStream = new MemoryStream(); tWriter = PdfWriter.GetInstance(tDocumentPdf, tStream); tDocumentPdf.Open(); tDocumentPdf.NewPage(); tDocumentPdf.Add(tGraph); tDocumentPdf.CloseDocument(); } catch (Exception ex) { throw ex; } finally { tDocumentPdf.Close(); tDocumentPdf.Dispose(); tWriter.Close(); tWriter.Dispose(); } break; case "svg": MemoryStream tData = new MemoryStream(Encoding.UTF8.GetBytes(tSvg)); tStream = tData; break; } } return tStream;
最後將tStream的圖像數據以文件的形式返回給前臺:
[HttpPost] [ValidateInput(false)] public ActionResult Export() { string siteUrl = String.Format("{0}://{1}:{2}/", Request.Url.Scheme, Request.Url.Host, Request.Url.Port); MemoryStream tStream = new MemoryStream(); string tType = Request.Form["type"]; string tSvg = Request.Form["svg"]; string tFileName = Request.Form["filename"]; if (String.IsNullOrEmpty(tFileName)) { tFileName = "chart"; } ChartHelper chartHelper = new ChartHelper(); tStream = chartHelper.GetSvgImageFromPhantomJs(siteUrl, tType, tSvg); return File(tStream.ToArray(), tType, tFileName); }
來一張效果圖,跟原來的對比一下:
可見漢字部分清晰了很多吧。
在服務端使用PhantomJS生成圖表圖片好處就是能將圖像渲染到最佳效果(直接使用WebKit內核渲染),缺點就是速度慢了些。
服務端生成Pdf圖表可使用iTextSharp生成。
附ASP.NET導出Highcharts的源碼:點此下載