highChart导出PDF

写给读者的话^_^:

  众所周知,基于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. 服务端处理服务端处理采用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

Back in 2003, when I wanted to implement charts for my home page, Flash-based charting solutions were totally dominating the market. I resented the idea of meeting my nontechnical readers with a prompt to install a browser plugin just to view my content, so I went looking for other solutions. There were server-side libraries that produced a static chart image, but they didn't provide any form of interactivity. So I built a chart based on an image created dynamically on the server, overlaid with tool tips created in JavaScript. This still runs on my website and has survived the coming of the touch age without modification. But I still had an idea of something simpler. By 2006 all major browsers had support for vector graphics through either SVG or VML, so this seemed the way to go. I started working on Highcharts on weekends and vacations, and released it in 2009. It was an instant success. Today, three years later, it has grown to become the preferred web charting engine by many, perhaps most, developers. Our bootstrapper company has nine persons working full time on developing, marketing, and selling Highcharts, and we have sold more than 22,000 licenses. Our clients include more than half of the 100 greatest companies in the world. I was thrilled when Packt Publishing contacted me for reviewing this book. I soon realized that the author, Joe Kuan, has a tight grip on Highcharts, jQuery, and general JavaScript. He also does what I love the most to see from Highcharts users—he bends, tweaks, and configures the library, and creates charts that surpass what we even thought possible with our tool. All done step by step in increasingly complex examples. I can't wait to recommend this book to our users. Torstein Hønsi CTO, Founder Highsoft Solutions
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值