Highcharts结合PhantomJS在服务端生成高质量的图表图片
项目背景
最近忙着给部门开发一套交互式的报表系统,来替换原有的静态报表系统。
老系统是基于dotnetCHARTING开发的,dotnetCHARTING的优势是图表类型丰富,接口调用简单,使用时只需绑定数据源即可(指定连接字符和sql语句,简单的配置一下就能出图),支持生成静态图表图片;缺点就是生成好的图是图片,传到了前台就失去了交互性(当然它还提供了一个jsCharting,不过感觉交互性做的还是不够好),再有就是这东东是收费的呀,用的话需要折腾破解版本。
我最终选择了Highcharts(Interactive JavaScript charts for your webpage)来展现前台图表,通过Highcharts良好的交互性实现与服务端的数据交互,将数据可视化。
dotnetCHARTING在数据加载的设计上做的还是很不错的,我在开发过程中借鉴了其处理思想,自己实现了一套数据加载方案,能够很方便的把数据传给Highcharts。这套数据加载方案,简单的说就是指定好数据库连接信息和sql查询信息,服务端采用ADO.NET执行查询生成DataSet,然后分析DataSet将数据转换为Highcharts能够直接使用的json格式。
报表的处理细节还是蛮多的,这里就不在一一讨论了,如题,接下来重点跟大家分享一下,服务端生成图表图片那部分的处理细节。
Highcharts服务端生成图表图片流程简介
生成图片的数据流向倒是比较简单,如下图所示:
ASP.NET在服务端生成图表图片的方式
根据上述生成图片的步骤,核心其实就是对第二步的处理,也就是如何将SVG数据在服务端做处理,生成图表图片。
这样的话,我们的处理思路就很清晰了,直接在服务端把SVG处理为图片不就可以了,这么想,也就这么做,刚好网上也有人这么弄过,于是也就直接借鉴了其代码,代码不上了,介绍下用到的dll:
在nuget中搜索svg,可以找到一个SVG Rendering Library的包,可以用这个包将SVG格式的数据保存为图片,用法也比较简单,大家可以到其官网查阅使用方法。
这个大家不必自己去实现,因为highcharts官网已经给出了第三方的ASP.NET导出图表的模块(他就是基于这个SVG Rendering Library实现的):
https://github.com/imclem/Highcharts-export-module-asp.net
SVG Rendering Library的问题
在使用SVG Rendering Library服务端生成图表图片的过程中,发现一些问题:
- 生成的图片中文字体模糊发虚,整体图片质量差,跟实际在网页中显示的效果差别还真不小
- 图表上数据点的dataLabel无法显示(一开始以为是highcharts配置的问题,后来鉴定是SVG Rendering Library的问题,这个必须修改svg.dll才能解决)
先看一下图片质量的问题,首先是Chrome中实际展现的图表的截图:
在来一张使用svg.dll在后台生成的图:
对比着两张图,可以和明显的看出生成的图片中汉字发虚(尤其是下面的月份)。正是这个原因,促使我去寻找一个更好的方案来替代SVG Rendering Library,以确保服务端生成图表图片的质量。
心想highcharts在浏览器中的显示效果已经不错了,要不做截图,但是截图的话跟服务端也没关系了呀,突然想到了在服务端渲染截图这么个思路。但是具体怎么做呢?先找找资料吧。
神器PhantomJS华丽登场
第一次接触Phantomjs是半年前左右,当时正在开发web漏洞检测工具,需要执行页面上的js,进行分析,没有经验的我,各处找资料,看到PhantomJS后,心想,这货不是已经有人做过了么,干嘛还重复造车轮子,后来随着业务变更,也没有深入研究它。
这次搜索“服务端,截图”这个关键字的时候,再次看到了PhantomJS,对它的印象不深了,先去官网看看介绍吧,PhantomJS: Headless WebKit with JavaScript API,哦,原来是个可以执行js并集成了webkit的动动,只是没有可视化的部分而已。
PhantomJS能干啥呢?
- HEADLESS WEBSITE TESTING(非可视化的Web测试)
- SCREEN CAPTURE,Programmatically capture web contents, including SVG and Canvas.(截屏啊,支持SVG啊,吼吼,这不正是我想要的么)
- PAGE AUTOMATION(页面自动化,可以使用jQuery操作DOM)
- NETWORK MONITORING(监视页面加载,还可以结合Jenkins做自动化分析,流弊啊!)
对Phantomjs做过一番了解后,就确定用它来处理服务端生成图表图片的问题了。我设计的处理流程如下:
画的很挫,能看明白处理过程就好,接下来分享一下具体处理过程中需要解决的问题。
新方案的处理细节
Highcharts中导出图表的配置
图表的其他配置不需要修改,只需修改导出图片的配置即可,导出的配置如下:
var chart = new Highcharts.Chart({
//...
exporting: {
url: '/Chart/Export', // 导出图表的服务端处理地址
filename: 'chart_from_phantomjs' // 返回下载的文件名
},
//...
});
我们使用Chrome调试一下,看看下载图片的时候,Highcharts都向服务端提交了哪些信息,截图如下:
Highcharts向/Chart/Export发送了一个Post请求,提交的信息如上图所示,在服务端,我们需要根据type来生成不同的图片格式,可以通过svg获取Highcharts提交的图表数据。
ASP.NET中SVG的处理
首先直接将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>
ASP.NET与PhantomJS的交互处理
由于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返回的图像数据做处理
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生成的图表图片
来一张效果图,跟原来的对比一下:
可见汉字部分清晰了不少吧。
总结
在服务端使用PhantomJS生成图表图片好处就是能将图像渲染到最佳效果(直接使用WebKit内核渲染),缺点就是速度慢了些。
服务端生成Pdf图表可以使用iTextSharp生成。
附ASP.NET导出Highcharts的源码:点此下载
Highcharts结合PhantomJS在服务端生成高质量的图表图片的更多相关文章
- 从服务端生成Excel电子表格(Node.js+SpreadJS)
Node.js是一个基于Chrome V8引擎的JavaScript运行环境,通常用于创建网络应用程序.它可以同时处理多个连接,并且不像其他大多数模型那样依赖线程. 对于 Web 开发者来说,从数据库 ...
- 从服务端生成Excel电子表格(GcExcel + SpreadJS)
在服务端生成Excel电子表格,除了使用 Node.js + SpreadJS 外,葡萄城官方推荐使用 SpreadJS + GcExcel.该方案不仅能够解决批量绑定数据源并导出Excel.批量修改 ...
- C#放缩、截取、合并图片并生成高质量新图的类
原文:C#放缩.截取.合并图片并生成高质量新图的类 using System;using System.Drawing;using System.Drawing.Imaging;using Syste ...
- atitit.thumb生成高质量缩略图 php .net c++ java
atitit.java thumb生成高质量缩略图 php .net c++ 1. 图像缩放(image scaling)---平滑度(smoothness)和清晰度(sharpness) 1 2. ...
- 根据服务端生成的WSDL文件创建客户端支持代码的三种方式
第一种:使用wsimport是JDK自带的工具,来生成 生成java客户端代码常使用的命令参数说明: 参数 说明 -p 定义客户端生成类的包名称 -s 指定客户端执行类的源文件存放目录 -d 指定客户 ...
- eureka服务端的高可用
eureka client的高可用这个很简单,只需要向eureka服务端上多注册几个实例即可,那么eureka server端如何实现高可用呢?其实eureka server 端也是可以做为一个客户端 ...
- .Net之使用Jquery Ajax通过FormData对象异步提交图片文件到服务端保存并返回保存的图片路径
前言: 首先对于图片上传而言,在我们的项目开发中可以说出现的频率是相当的高的.这篇文章中,我将要描述的是在我们.Net中如何使用Jquery Ajax通过FormData对象异步提交图片文件到后台保存 ...
- ASP.NET MVC在服务端把异步上传的图片裁剪成不同尺寸分别保存,并设置上传目录的尺寸限制
我曾经试过使用JSAjaxFileUploader插件来把文件.照片以异步的方式上传,就像"MVC文件图片ajax上传轻量级解决方案,使用客户端JSAjaxFileUploader插件01- ...
- C#剪切生成高质量缩放图片
/// <summary> /// 高质量缩放图片 /// </summary> /// <param name="OriginFilePath"&g ...
随机推荐
- Win7 64位 VS2015环境编译cegui-0.8.5
首先是去官网下载源码与依赖库 http://cegui.org.uk/ 然后得提一下,编译DX11版本带Effects11框架的话会有问题,也就是默认情况编译有问题,这是因为VS2015升级后编译器对 ...
- SpringMVC常用注解實例詳解1:@Controller,@RequestMapping,@RequestParam,@PathVariable
我的開發環境 框架: springmvc+spring+freemarker 開發工具: springsource-tool-suite-2.9.0 JDK版本: 1.6.0_29 to ...
- Oracle学习笔记1
查看登录用户 show user; 启用scott用户 alter user scott account unlock; 操作表空间 select * from dba_tablespaces; se ...
- berkeley db 内存池 LRU算法
priority based lru in src/mp/mp_fget.c, __memp_fget(), 初始化 一个page buffer时, 设置其 priority: bhp->pri ...
- JS动态级联菜单
JS动态级联菜单是前端常用的一个功能,特此抽时间研究了下,附上代码 <html> <head> <meta charset="utf-8" /> ...
- 8.1 消息通信 EventBus
EventBus是一个事件发布和订阅的框架.EventBus是一款针对Android优化的发布/订阅事件总线.主要功能是替代Intent,Handler,BroadCast 在Fragment,Act ...
- [java基础]java中static关键字
1.static概念 static是静态修饰符,什么叫静态修饰符呢?大家都知道,在程序中任何变量或者代码都是在编译时由系统自动分配内存来存储的,而所谓静态就是指在编译后所分配的内存会一直存在,直到程序 ...
- listView后面加控件,防止被挤
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android=&quo ...
- 数据库Date类型和JavaDate类型的转换
问题: java.lang.ClassCastException : java.util.Date cannot be cast to java.sql.Date 1.若是想将字符串装换成sq ...
- Android 自定义View 三板斧之三——重写View来实现全新控件
通常情况下,Android实现自定义控件无非三种方式. Ⅰ.继承现有控件,对其控件的功能进行拓展. Ⅱ.将现有控件进行组合,实现功能更加强大控件. Ⅲ.重写View实现全新的控件 本文来讨论最难的一种 ...