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


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM