Highcharts图表导出为pdf的JavaWeb实践

写给读者的话^_^:

  众所周知,基于Highcharts插件生成的svg图片组(注意这里鄙人指的组是若干图有序组合,并非一张图片,具有业务意义)导出为PDF文档是有难度滴。鄙人也曾“异想天开”用前端技术拍个快照然后转换为pdf文件导出,后来因为能力有限未能完美实现。因此,参照互联网已有的经验和做法,创造出一套较为有操作性的方案,详情见下文。

  

---------------------------------------------------说正事儿分割线----------------------------------------------------

 

假设需求如下:

  1. 如图所示的复杂图表报告
  2. 对其进行PDF导出(demo中所有数据为伪造,并无任何价值)
  3. 此图仅作为demo展示,不涉及商业数据,所有数据均为构造假数据

    那么问题来了,肿么导出哩,先看下导出后的效果,卖个关子,如下图:

  4. 当然,不可否认的是图像质量会打折。但是效果终究实现了。接下来我们去看看前端怎么写,然后提交到后台又如何处理返回一个文档输出流。

    1. 前端html自定义元素属性,如下:
      <div class="timeFenBuPic" id="timeFenBuPic">
                          <div class="timeFenBuOne" id="timeFenBuOne" softOrHard="hard" position="center" getSvg="true" h4="VR眼镜使用饱和度">
                          </div>
                      </div>

      例如:其中position咱们可以定义给它三个值属性:left,center,right代表了在文档中,每一组svg图的相对位置,其余几个属性自己结合后台程序使用即可。

  5. 前端js脚本获取并且组织svg图像元素并提交给服务端(这里我们用的服务端时Java写的struts2作为控制器层的服务端接口),js写法如下:
    function PDFExecute(){
        //循环拿到各个绘图区域id
        $("#svgPDF").empty();
        $.each($("[getSvg='true']"),function(index,ele){
            //根据每个绘图区域的id获取svg,position,softOrHard等属性
            var svg = $(this).highcharts();
            if(typeof(svg)=='undefined'||svg==null){
                svg = 'noData';
            }else{
                svg = svg.getSVG();
            }
            $("#svgPDF").append("<input id='SVG"+$(this).attr("id")+"' name='svg' type='hidden' value='' />");
            $("#SVG"+$(this).attr("id")).val(
                    $(this).attr("id")+
                    "___"+$(this).attr("position")+
                    "___"+encodeURI($(this).attr("h4")+getSvgUnit($(this).parents('li').children('ul').children('li .curr').text()))+
                    "___"+$(this).attr("softOrHard")+
                    "___"+svg);
        });
        $("#svgPDF").append("<input name='logoT' type='hidden' value='"+encodeURI($(".logoT").text())+"' />");
        //处理文本锚点异常错误
    //    $('[text-anchor="undefined"]').attr('text-anchor','');
        $("#svgPDF").submit();
    
    }
  6. 服务端处理
  7. 服务端处理采用itext作为pdf生成第三方工具包,然后返回一个输出流到前端
    1. pdf导出接口

          /**
           * PDF导出入口方法
           * 参数要求:
           * 1.一个页面的title(encode后的)
           * 2.所有highcharts的svg
           * 3.页面所有查询参数(用于表格类型的数据查询,因为表格类型前端无法传给后台)
           * 4.svg详述:
           *         svg为一个数组
           *         svg的每个数组元素为字符串,且包含多个信息,以三个连续英文半角的下划线___做split操作,得到数组,具体内容如下:
           *      页面每个hicharts图的绘制id___此图在水平方向的相对位置(left还是right)___encode后的每两个图组成的title标题
           *      (例如xx投放趋势)___此图为软广还是硬广(soft还是hard)___svg字符串用来转换图片输出流
           *      因此 svg.split("___")结果为:
           *      ["charts图id","left/right","xx趋势图","soft/hard","<svg.../>"]
           * 5.使用时修改ByteArrayOutputStream方法下参数及布局规则
           */
          public String svgPDF(){
              try {
                      request.setCharacterEncoding("utf-8");
                      response.setCharacterEncoding("utf-8");
                      Map<String,Object> map = new HashMap<String,Object>();
                      String logoT = request.getParameter("logoT");
                      if(StringUtils.isNotEmpty(logoT)){
                          logoT = URLDecoder.decode(logoT,"utf-8");
                      }
                      
                      downloadFileName= URLEncoder.encode(logoT,"utf-8")+".pdf";
                      String[] svg = request.getParameterValues("svg");
                      map.put("svg", svg);
                      map.put("logoT", logoT);
                      
                      //实例化文档绘制工具类
                      ComprehensivePdfUtil cpu = new ComprehensivePdfUtil();
                      
                      ByteArrayOutputStream buff = cpu.getPDFStream(request,response,map);
                      inputStream = new ByteArrayInputStream(buff.toByteArray());
                      buff.close();
                  return "success";
              } catch (IOException e) {
                  e.printStackTrace();
                  return null;
              }
          }

      此接口响应来自客户端的http请求并返回输出流

    2. PDF文档绘制工具类
      package com.demo.utils;
      
      import java.io.ByteArrayOutputStream;
      import java.io.IOException;
      import java.io.StringReader;
      import java.io.UnsupportedEncodingException;
      import java.net.MalformedURLException;
      import java.net.URLDecoder;
      import java.util.ArrayList;
      import java.util.List;
      import java.util.Map;
      
      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.PNGTranscoder;
      
      import com.itextpdf.text.BadElementException;
      import com.itextpdf.text.BaseColor;
      import com.itextpdf.text.Document;
      import com.itextpdf.text.DocumentException;
      import com.itextpdf.text.Element;
      import com.itextpdf.text.Font;
      import com.itextpdf.text.Image;
      import com.itextpdf.text.Paragraph;
      import com.itextpdf.text.Phrase;
      import com.itextpdf.text.Rectangle;
      import com.itextpdf.text.pdf.BaseFont;
      import com.itextpdf.text.pdf.PdfPCell;
      import com.itextpdf.text.pdf.PdfPRow;
      import com.itextpdf.text.pdf.PdfPTable;
      import com.itextpdf.text.pdf.PdfWriter;
      
      
      
      /**
       * @Description XXX分析页面PDF导出工具方法
       */
      public class ComprehensivePdfUtil {
          /**
           * 获得PDF字节输出流及pdf布局业务逻辑
           * @param request
           * @param response
           * @param resultMap 包含参数:svg(绘图svg参数及hicharts图布局参数) logoT(页面总标题)
           * @param list 页面包含植入栏目排行表格图,该list存储绘制表格所用的数据
           * @param tableTh 页面包含植入栏目排行表格图,该字符串作为表格表头
           * @param tableTd 页面包含植入栏目排行表格图,该字符串作为表格内容填充时,实体类反射值所用的方法名(必须与实体方法严格一致)
           * @return
           */
          public ByteArrayOutputStream getPDFStream(HttpServletRequest request,
                  HttpServletResponse response,
                  Map<String,Object> resultMap){
              try {
                  //图片变量定义
                  String noData = "/style/images/noData.png";//无数据左右图
                  String noDataCenter = "/style/images/noDataCenter.png";//无数据中间图
                  String waterMark = "/style/images/PDFSHUIYIN.png";//PDF导出文件水印图片
                  String [] svgName = (String[]) resultMap.get("svg");//导出PDF页面所有svg图像
                  Document document = new Document();
      
                  ByteArrayOutputStream buffer = new ByteArrayOutputStream();
                  PdfWriter pdfWriter = PdfWriter.getInstance(document, buffer);
                  
                  //设置页面大小
                  int pageHeight = 2000;
                  Rectangle rect = new Rectangle(0,0,1200,pageHeight);
                  rect.setBackgroundColor(new BaseColor(248,248,248));//页面背景色
                  document.setPageSize(rect);//页面参数
                  
                  //页边空白  
                  document.setMargins(20, 20, 30, 20);
                  document.open();
                  
                  //设置页头信息
                  if(null!=resultMap.get("logoT")){
                      BaseFont bfChinese = BaseFont.createFont("STSong-Light", "UniGB-UCS2-H", BaseFont.NOT_EMBEDDED);
                      Font FontChinese = new Font(bfChinese,20, Font.BOLD); 
                      Paragraph paragraph = new Paragraph((String)resultMap.get("logoT"),FontChinese);
                      paragraph.setAlignment(Element.ALIGN_CENTER);
                      document.add(paragraph);
                  }
                  
                  PdfPTable table = null;
                  String path = request.getContextPath();
                  String basePath = request.getScheme()+"://"+request.getServerName()+":"+request.getServerPort()+path+"/";
                  
                  //开始循环写入svg图像到pdf文档对象
                  for(String str:svgName){
                      //////////////////////////////////////////////////////////////////////////////////////////////////////////
                      //positionAndSvg数组中元素说明:
                      //positionAndSvg[0]表示svg图像所在页面的div的id
                      //positionAndSvg[1]表示svg图像在水平方向的相对位置:    
                      //                    1.left(水平方向两张图,居左且占比50%) 
                      //                    2.right(水平方向两张图,居右且占比50%)
                      //                    3.center(水平方向一张图,居中且占比100%)
                      //positionAndSvg[2]表示svg图像模块的标题如:xxx走势图
                      //positionAndSvg[3]表示soft/hard即软广图或者硬广图,当无数据时为无数据提示效果图提供判断依据
                      //positionAndSvg[4]表示svg图像元素,形如<svg...../>
                      //////////////////////////////////////////////////////////////////////////////////////////////////////////
                      String[] positionAndSvg = str.split("___");
                      
                      Image image1 = null;
                      boolean havaData = true;
                      
                      if("noData".equals(positionAndSvg[4])){//无数据时
                          image1 = Image.getInstance(basePath+noData);
                          havaData = false;
                      }else{//有数据
                          image1 = Image.getInstance(highcharts(request,response,positionAndSvg[4]).toByteArray());
                          havaData = true;
                      }
                      
                      if("left".equals(positionAndSvg[1])){
                          String title1 = URLDecoder.decode(positionAndSvg[2],"utf-8");
                          setTitleByCharts(document,30,title1,"",0,87,55,Element.ALIGN_LEFT,headfont);
                          if(!"cooperateProporOne".equals(positionAndSvg[0])){
                              setTitleByCharts(document,0,"左图","右图",248,248,248,Element.ALIGN_CENTER,blackTextFont);
                          }else{
                              setTitleByCharts(document,0,"","",248,248,248,Element.ALIGN_CENTER,blackTextFont);
                          }
                          table = new PdfPTable(2);
                          
                          float[] wid ={0.50f,0.50f}; //列宽度的比例
                          table.setWidths(wid); 
                          table = PdfPTableImage(table,image1,80f);
                      }else if("right".equals(positionAndSvg[1])){
                          table = PdfPTableImage(table,image1,80f);
                          table.setSpacingBefore(10);
                          table=setTableHeightWeight(table,360f,1000);
                          document.add(table);
                          table = null;
                      }else if("center".equals(positionAndSvg[1])){//总览全局
                          String title1 = URLDecoder.decode(positionAndSvg[2],"utf-8");
                          setTitleByCharts(document,30,title1,"",0,87,55,Element.ALIGN_LEFT,headfont);
                          setTitleByCharts(document,0,"","",248,248,248,Element.ALIGN_CENTER,blackTextFont);
                          table = new PdfPTable(1);
                          float[] wid ={1.00f}; //列宽度的比例
                          table.setWidths(wid); 
                          if(havaData){
                              table = PdfPTableImageTable(table,image1,1000f,600f);
                          }else{
                              table = PdfPTableImageTable(table,Image.getInstance(basePath+noDataCenter),1000f,600f);
                          }
                          table=setTableHeightWeight(table,400f,1000);
                          document.add(table);
                          table=null;
                      }
                  }
                  
                  //添加水印Start---------------------------------------------------------------------------------------------
                  PdfFileExportUtil pdfFileExportUtil = new PdfFileExportUtil();
                  pdfWriter.setPageEvent(pdfFileExportUtil.new PictureWaterMarkPdfPageEvent(basePath+waterMark));
      //            pdfWriter.setPageEvent(pdfFileExportUtil.new TextWaterMarkPdfPageEvent("xxx科技"));
                  //添加水印End-----------------------------------------------------------------------------------------------
                  document.close();
                  return buffer;
              } catch (BadElementException e) {
                  e.printStackTrace();
                  return null;
              } catch (MalformedURLException e) {
                  e.printStackTrace();
                  return null;
              } catch (DocumentException e) {
                  e.printStackTrace();
                  return null;
              } catch (IOException e) {
                  e.printStackTrace();
                  return null;
              } catch (Exception e) {
                  e.printStackTrace();
                  return null;
              }
          }
          
      
          
          /**
           * 设置图片类型Cell属性
           * @param table
           * @param image1
           * @param imgPercent
           * @return
           * @throws Exception
           */
          private PdfPTable PdfPTableImage(PdfPTable table,Image image1,float imgPercent){
              table = useTable(table,Element.ALIGN_CENTER);
              PdfPCell cellzr = createCellImage(image1,imgPercent);
              cellzr.setBorder(0);
              cellzr.setBackgroundColor(new BaseColor(248,248,248));  
              table.addCell(cellzr); 
              return table;
          }
          /**
           * 设置图片类型Table的Cell属性
           * @param table
           * @param image1
           * @param imgPercentWidth
           * @param imgPercentHeight
           * @return
           * @throws Exception
           */
          private PdfPTable PdfPTableImageTable(PdfPTable table,Image image1,float imgPercentWidth,float imgPercentHeight){
              table = useTable(table,Element.ALIGN_CENTER);
              PdfPCell cellzr = createCellImageTable(image1,imgPercentWidth,imgPercentHeight);
              cellzr.setBorder(0);
              cellzr.setBackgroundColor(new BaseColor(248,248,248));  
              table.addCell(cellzr); 
              return table;
          }
          
          /**
           * 设置表头
           * @param document
           * @param SpacingBefore
           * @param title1
           * @param title2
           * @param r1
           * @param r2
           * @param r3
           * @param ele
           * @param font
           * @throws Exception
           */
          private void setTitleByCharts(Document document,int SpacingBefore,String title1,String title2,int r1,int r2,int r3,int ele,Font font){
              try {
                  float[] titlewidthsLeft = {0.50f,0.50f};
                  PdfPTable zrfbtitleTable = createTable(titlewidthsLeft);
                  PdfPCell cellzr = createCellLeft(title1,font,ele);  
                  cellzr.setBorder(0);  
                  cellzr.setBackgroundColor(new BaseColor(r1,r2,r3));  
                  zrfbtitleTable.addCell(cellzr); 
                  
                  PdfPCell cellzr1 = createCellLeft(title2,font,ele);  
                  cellzr1.setBorder(0);  
                  cellzr1.setBackgroundColor(new BaseColor(r1,r2,r3));  
                  zrfbtitleTable.addCell(cellzr1); 
                  zrfbtitleTable.setSpacingBefore(SpacingBefore);
                  zrfbtitleTable=setTableHeightWeight(zrfbtitleTable,30f,1000);
                  
                  document.add(zrfbtitleTable);
              } catch (DocumentException e) {
                  e.printStackTrace();
              }
          }
      
          /**
           * 导出Pdf所用字体静态变量
           */
          private static Font headfont ;// title字体
          private static Font blackTextFont ;// 黑色字体
          private static Font colorfont;
          int maxWidth = 500;
          static{
              BaseFont bfChinese;
              try {
                    bfChinese = BaseFont.createFont("STSong-Light", "UniGB-UCS2-H", BaseFont.NOT_EMBEDDED);
                    headfont = new Font(bfChinese, 15, Font.BOLD);// 设置字体大小
                    headfont.setColor(BaseColor.WHITE);
                    blackTextFont = new Font(bfChinese, 11, Font.BOLD);// 设置字体大小
                    blackTextFont.setColor(BaseColor.BLACK);
                    colorfont = new Font(bfChinese, 11, Font.NORMAL);// 设置字体大小
                    colorfont.setColor(BaseColor.RED);
              } catch (Exception e) {            
                  e.printStackTrace();
              } 
          }
          
           /**
            * 创建指定内容背景色的Table元素Cell
            * @param value
            * @param font
            * @param c1
            * @param c2
            * @param c3
            * @return
            */
           public PdfPCell createCell(String value,Font font,int c1,int c2, int c3){
               PdfPCell cell = new PdfPCell();
               cell.setVerticalAlignment(Element.ALIGN_MIDDLE);
               cell.setHorizontalAlignment(Element.ALIGN_CENTER);    
               cell.setPhrase(new Phrase(value,font));
               cell.setBackgroundColor(new BaseColor(c1,c2,c3));
               cell.setFixedHeight(33.33f);
               cell.setBorder(0);
              return cell;
          }
           /**
            * 创建指定位置的Table元素Cell
            * @param value
            * @param font
            * @param ele
            * @return
            */
           public PdfPCell createCellLeft(String value,Font font,int ele){
               PdfPCell cell = new PdfPCell();
               cell.setVerticalAlignment(Element.ALIGN_MIDDLE);
               cell.setHorizontalAlignment(ele);    
               cell.setPaddingLeft(10);
               cell.setPhrase(new Phrase(value,font));
              return cell;
          }
           /**
            * 创建内容为Image的Table元素Cell
            * @param image
            * @param imgPercent
            * @return
            */
           public PdfPCell createCellImage(Image image,float imgPercent){
               image.scalePercent(imgPercent);
               PdfPCell cell = new PdfPCell(image,false);
               cell.setUseAscender(true);
               cell.setUseDescender(true);
               cell.setVerticalAlignment(Element.ALIGN_MIDDLE);
               cell.setHorizontalAlignment(Element.ALIGN_CENTER);    
               cell.setPaddingLeft(10);
               return cell;
           }
           /**
            * 创建table元素cell
            * @param image
            * @param imgPercentWidth
            * @param imgPercentHeight
            * @return
            */
           public PdfPCell createCellImageTable(Image image,float imgPercentWidth,float imgPercentHeight){
               image.scaleAbsoluteWidth(imgPercentWidth);
               if(imgPercentHeight==410f){
                   image.scaleAbsoluteHeight(imgPercentHeight);     
               }
               
               PdfPCell cell = new PdfPCell(image,false);
               cell.setVerticalAlignment(Element.ALIGN_MIDDLE);
               cell.setHorizontalAlignment(Element.ALIGN_CENTER);
               return cell;
           }
      
           /**
            * 创建Table
            * @param widths 列宽比例
            * @return
            */
           public PdfPTable createTable(float[] widths){
                   for(int i=0;i<widths.length;i++){
                       widths[i] = widths[i]*maxWidth;
                   }
                  PdfPTable table = new PdfPTable(widths);
                  try{
                      table.setTotalWidth(maxWidth);
                      table.setLockedWidth(true);
                      table.setHorizontalAlignment(Element.ALIGN_CENTER);        
                      table.getDefaultCell().setBorder(1);
                  }catch(Exception e){
                      e.printStackTrace();
                  }
                  return table;
              }
           /**
            * 设置table参数
            * @param table
            * @param position
            * @return
            */
           public PdfPTable useTable(PdfPTable table,int position){
               try{
                   table.setTotalWidth(maxWidth);
                   table.setLockedWidth(true);
                   table.setHorizontalAlignment(position);        
                   table.getDefaultCell().setBorder(0);
               }catch(Exception e){
                   e.printStackTrace();
               }
               return table;
           }
      
           /**
            * 设置PdfTable行高
            * @param table
            * @param maxHeight
            * @param maxWidth
            * @return
            */
           public PdfPTable setTableHeightWeight(PdfPTable table,float maxHeight,float maxWidth){
               table.setTotalWidth(maxWidth);
               List<PdfPRow> list=new ArrayList<PdfPRow>();
               list=table.getRows();
               for(PdfPRow pr:list){
                   pr.setMaxHeights(maxHeight);
               }
               return table;
           }
      
          /**
           * 根据SVG字符串得到一个输出流
           * @param request
           * @param response
           * @param svg
           * @return
           * @throws Exception
           */
          public ByteArrayOutputStream highcharts(HttpServletRequest request,HttpServletResponse response,String svg){
              try {
                  request.setCharacterEncoding("utf-8");// 注意编码
                  //转码防止乱码
                  byte[] arrayStr = svg.getBytes("utf-8");
                  svg = new String(arrayStr, "UTF-8");
                  
                  ByteArrayOutputStream stream = new ByteArrayOutputStream();
                          
                   try {
                       stream=this.transcode(stream, svg);
                  } catch (Exception e) {
                      e.printStackTrace();
                  }
                  return  stream;
              } catch (UnsupportedEncodingException e) {
                  e.printStackTrace();
                  return null;
              }         
          }
          
          /**
           * 对svg进行转码
           * @param stream
           * @param svg
           * @return
           * @throws Exception
           */
          public synchronized ByteArrayOutputStream transcode(ByteArrayOutputStream stream, String svg){
              try {
                  TranscoderInput input = new TranscoderInput(new StringReader(svg));
                  
                  TranscoderOutput transOutput = new TranscoderOutput(stream);
                  
                  PNGTranscoder  transcoder = new PNGTranscoder();
                  
                  
                  transcoder.transcode(input, transOutput);
                  return stream;
              } catch (TranscoderException e) {
                  e.printStackTrace();
                  return null;
              }
          }
      
      }
      PDF文档绘制工具类

      此工具类可以根据前端传来的svg信息,前文中提到的自定义position等属性,布局完成所要输出的PDF文档,因时间有限,不再一一赘述,有想研究的可以下载demo,我已做了一个demo供各位交流学习,下载地址:http://yun.baidu.com/share/link?shareid=2976350494&uk=657798452

posted @ 2016-03-31 17:34  阿尔法钻石狗  阅读(5853)  评论(2编辑  收藏  举报