最近忙着给部门开发一套交互式的报表系统,来替换原有的静态报表系统。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的源码:点此下载