• 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

  • 相关阅读:
    (转)使用Regex.Replace只替换字符串一次
    转:div超出范围显示省略号
    ibatis This SQL map does not contain a MappedStatement
    ibatis 多表关联
    ibatis 传入xml Hashtable的应用以及动态字段出错的解决方法
    前台学习过程
    转:C#动态循环生成button怎么分别写他们的事
    在oracle DB 中通过JOB 调用存储过程
    sybase与oracle存储过程的写法对比
    游标Oracle游标使用大全
  • 原文地址:https://www.cnblogs.com/dreamzhiya/p/5341995.html
Copyright © 2020-2023  润新知