ExtJS 4后台保存Chart图片

前言


对于ExtJS 来说,在 Ext.chart.Chart 这个类直接有提供一个 save( [config] ) 的方法, 调用这个方法, 就可以在browser 下载当前这个chart 的对应格式的图形文件。

chart.save({  
     type: 'image/png'  
});  

这里使用的技术是把数据传递到服务器端, 由服务器端产生图再传到前端。
所以, 在调用save 这个方法的时候, 你会发现, 请求会访问http://svg.sencha.io/ 这个地址。
曾经, 大概在 2013 的年中的时候, 这个地址都可以访问, 当时不知何时, 这个地址的访问就会报 503 (Service Unavailable)的错误了。

这样的话, 原本可以work 的chart 导出功能, 现在就不行了。
网上搜索一下, 是说部分版本已经不提供这个服务了。
不管如何, 讲自己的服务放在别的地方, 总也不踏实, 是否可以开发出这种服务呢?

原理


首先看一看, chart.save 时,前端传递了那些参数:

参数有四个:
width 和 height 应该是图片的大小
type 应该是图的保存格式
svg 传递的就是xml方式的svg 格式的数据。
所以问题归结为一点: 在服务端如何解析这个svg 格式的数据并生成图片文件?

解决方法


看懂这些svg 数据, 并使用基本API生成一个图片不失为一种解法?
但是对Java 语言来说, 是否已经存在第三方包来帮我们直接做这种事呢?
答案之一就是:
Batik
Batik是使用svg格式图片来实现各种功能的应用程序以及Applet提供的一个基于java的工具包。
它是属于 Apache软件基金会 的一个项目, 应该是值得信赖的。
项目主页:http://xmlgraphics.apache.org/batik/
下载地址:http://mirrors.cnnic.cn/apache/xmlgraphics/batik/
开发步骤:
1. 下载batik-1.7.zip ( 目前的最新版) 并解压
2. 把根目录, lib 和 extensions 目录下的所有lib 包拷入项目的web lib 中。(可以选择)
3. 写一个servlet ImageExportService.java

/** 
* author:oscar999 
*/  
import java.io.IOException;  
import java.io.OutputStream;  
import java.io.StringReader;  
  
import javax.servlet.ServletException;  
import javax.servlet.http.HttpServlet;  
import javax.servlet.http.HttpServletRequest;  
import javax.servlet.http.HttpServletResponse;  
  
import org.apache.batik.transcoder.TranscoderException;  
import org.apache.batik.transcoder.TranscoderInput;  
import org.apache.batik.transcoder.TranscoderOutput;  
import org.apache.batik.transcoder.image.JPEGTranscoder;  
import org.apache.batik.transcoder.image.PNGTranscoder;  
  
public class ImageExportService extends HttpServlet {  
  
    /** 
     *  
     */  
    private static final long serialVersionUID = 1L;  
      
    /** 
     * Post File to Client 
     * Input Parameters:  
     *  type(image type, as png, jpeg),  
     *  svg(svg string, post by extjs),  
     *  filename(save file name) 
     */  
    @Override  
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException,  
            IOException {  
  
        String type = request.getParameter("type");       
        String svg = request.getParameter("svg");  
        String filename = "chart";  
        if( request.getParameter("filename")!=null&&request.getParameter("filename").length()>0)  
        {  
            filename = request.getParameter("filename");  
        }  
  
        String postfix;  
        if ("image/jpeg".equals(type)) {  
            response.setContentType("image/jpeg");  
            postfix = "jpg";  
        } else {  
            response.setContentType("image/png");  
            postfix = "png";  
        }  
        response.setHeader("Content-Disposition", "attachment; filename=" + filename + "." + postfix);  
  
        StringReader stringReader = new StringReader(svg);  
        OutputStream out = response.getOutputStream();  
  
        try {  
            TranscoderInput input = new TranscoderInput(stringReader);  
            TranscoderOutput output = new TranscoderOutput(out);  
  
            if ("image/jpeg".equals(type)) {  
                new JPEGTranscoder().transcode(input, output);  
            } else {  
                new PNGTranscoder().transcode(input, output);  
            }  
  
        } catch (TranscoderException e) {  
            throw new ServletException(e);  
        }  
    }  
  
}  

4. 在 web.xml 中配置此servlet 的请求路径

  <servlet>  
    <servlet-name>ImageExportService</servlet-name>  
    <servlet-class>com.XXX.ImageExportService</servlet-class>  
  </servlet>    
  <servlet-mapping>  
      <servlet-name>ImageExportService</servlet-name>  
      <url-pattern>/ImageExportService</url-pattern>  
  </servlet-mapping>  

5. 让ExtJS 的Chart.save 切换到新的服务器。

Ext.draw.engine.ImageExporter.defaultUrl = 'ImageExportService'; 

(注意, 这部分是写在web端的, 可以放在 Ext.onReady 里面)

6. 全部完成, 写个例子测试一下

//author: oscar999  
<script type="text/javascript">  
var chart;  
var panel1;  
Ext.require(['*']);  
Ext.onReady(function() {  
  
    Ext.draw.engine.ImageExporter.defaultUrl = '/'+WEB_PROJECT_NAME+'/ImageExportService';  
      
    var store = Ext.create('Ext.data.JsonStore', {  
        fields: ['name', 'data'],  
        data: [  
            { 'name': 'metric one',   'data':100000 },  
            { 'name': 'metric two',   'data': 7 },  
            { 'name': 'metric three', 'data': 5 },  
            { 'name': 'metric four',  'data': 2 },  
            { 'name': 'metric five',  'data':27 }  
        ]  
    });  
  
    var chart = Ext.create('Ext.chart.Chart', {  
        renderTo: Ext.getBody(),  
        width: 500,  
        height: 300,  
        animate: true,  
        store: store,  
        axes: [{  
            type: 'Numeric',  
            position: 'bottom',  
            fields: ['data'],  
            label: {  
                renderer: Ext.util.Format.numberRenderer('0,0')  
            },  
            title: 'Sample Values',  
            grid: true,  
            minimum: 0  
        }, {  
            type: 'Category',  
            position: 'left',  
            fields: ['name'],  
            title: 'Sample Metrics'  
        }],  
        series: [{  
            type: 'bar',  
            axis: 'bottom',  
            highlight: true,  
            tips: {  
              trackMouse: true,  
              width: 140,  
              height: 28,  
              renderer: function(storeItem, item) {  
                this.setTitle(storeItem.get('name') + ': ' + storeItem.get('data') + ' views');  
              }  
            },  
            label: {  
              display: 'insideEnd',  
                field: 'data',  
                renderer: Ext.util.Format.numberRenderer('0'),  
                orientation: 'horizontal',  
                color: '#333',  
                'text-anchor': 'middle'  
            },  
            xField: 'name',  
            yField: 'data'  
        }]  
    });  
  
    Ext.MessageBox.confirm('Confirm Download', 'Would you like to download the chart as an image?', function(choice){  
        if(choice == 'yes'){  
            chart.save({  
                type: 'image/png',  
                filename:'testfile'  
            });  
        }  
    });  
      
});  
</script>  

跨浏览器处理


可能想到会有一个问题, IE 的低版本还只是支持vml 绘图, 那在这些版本的IE中, 导出图是否就不行了呢?
其实不用担心, Extjs 已经帮我们处理好了, 即使在这些版本的IE中, 传递到服务器的也是 svg 格式的数据。
(另外发现的一点是, 如果要传入自己定义的参数, 比如filename 等, 直接从save 中可能不行,可以考虑从呼叫url 传入)

作者: oscar999
原文: http://blog.csdn.net/oscar999/article/details/17910843