Highcharts結合PhantomJS在服務端生成高質量的圖表圖片

項目背景

最近忙着給部門開發一套交互式的報表系統,來替換原有的靜態報表系統。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

報表的處理細節仍是蠻多的,這裏就不在一一討論了,如題,接下來重點跟你們分享一下,服務端生成圖表圖片那部分的處理細節。數據庫

Highcharts服務端生成圖表圖片流程簡介

生成圖片的數據流向卻是比較簡單,以下圖所示:json

ASP.NET在服務端生成圖表圖片的方式

根據上述生成圖片的步驟,核心其實就是對第二步的處理,也就是如何將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的問題

在使用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的源碼:點此下載

相關文章
相關標籤/搜索