poi-tl之图表操作(基于模板)

poi-tl(poi template language)是Word模板引擎,使用Word模板和数据创建很棒的Word文档。
核心思想是在模板中放一个占位符,在代码中替换该占位符即可。
poi官网地址 点这里

基础工作

maven配置

<dependency>
    <groupId>com.deepoove</groupId>
    <artifactId>poi-tl</artifactId>
    <version>1.10.0</version> <!-- 替换为你想要使用的特定版本 -->
</dependency>

chart

chart分为三种类型,分别是单系列、多系列及组合。

  • 单系列图表指的是饼图(3D饼图)、圆环图等。
  • 多系列图表指的是条形图(3D条形图)、柱形图(3D柱形图)、面积图(3D面积图)、折线图(3D折线图)、雷达图、散点图等。
  • 组合图表指的是由多系列图表(柱形图、折线图、面积图)组合而成的图表。

单系列-饼图

  1. 代码如下
public static void main(String[] args) throws IOException {
      Map<String, Object> map = new HashMap<>();
      ChartSingleSeriesRenderData demo1 = Charts.ofPie3D("", new String[]{"第一季度", "第二季度", "第三季度", "第四季度"})
              .series("", new Number[]{0.3, 0.3, 0.2, 0.2})
              .create();
      map.put("demo1", demo1);
      XWPFTemplate.compile("C:\\Users\\huahua\\Desktop\\2.doc").render(map).writeToFile("C:\\Users\\huahua\\Desktop\\demo_output.doc");
}
  1. word模板

插入图表模板后,在图表空白区域右键选择设置图表区域格式->右侧属性选择文本选项->文本框->可选文字
,在标题中输入{{demo1}},其中{{}}为替换标签,必须满足该格式,demo作为属性值,可以修改,注意和代码中map.put("demo1", demo1);设置的键保持一致即可
在这里插入图片描述

此处模板我使用的是wps,wps和office不同,office直接右键查看可选文字即可,如下图所示
在这里插入图片描述

导出结果如下
在这里插入图片描述

多系列-柱状图

    public static void main(String[] args) throws IOException {
        Map<String, Object> map = new HashMap<>();
      
        ChartMultiSeriesRenderData demo2 = Charts.ofBar("", new String[]{"销售量"})
                .addSeries("第一季度", new Number[]{100})
                .addSeries("第二季度", new Number[]{200})
                .addSeries("第三季度", new Number[]{500})
                .addSeries("第四季度", new Number[]{1000})
                .create();
        map.put("demo2", demo2);
        XWPFTemplate.compile("C:\\Users\\huahua\\Desktop\\2.doc").render(map).writeToFile("C:\\Users\\huahua\\Desktop\\demo_output.doc");
    }

生成的图表如下
在这里插入图片描述

组合系列-柱状折线组合图

Map<String, Object> map = new HashMap<>();
ChartMultiSeriesRenderData demo3 = Charts.ofComboSeries("MyChart", new String[] { "中文", "English" })
        .addBarSeries("countries", new Double[] { 15.0, 6.0 })
        .addBarSeries("speakers", new Double[] { 223.0, 119.0 })
        .addBarSeries("NewBar", new Double[] { 223.0, 119.0 })
        .addLineSeries("youngs", new Double[] { 323.0, 89.0 })
        .addLineSeries("NewLine", new Double[] { 123.0, 59.0 }).create();
map.put("demo3", demo3);
XWPFTemplate.compile("C:\\Users\\huahua\\Desktop\\2.doc").render(map).writeToFile("C:\\Users\\huahua\\Desktop\\demo_output.doc");

在这里插入图片描述

小结

不同的chart配置都大同小异,基本都没什么难度,简单了解下就能上手

table

表格分为静态表格和动态表格,

静态表格

当表格的需求比较简单时,可以通过简单的模板替换及列表循环填充表格数据

模板替换

行循环

列循环

动态表格

当需求中的表格更加复杂的时候,我们完全可以设计好那些固定的部分,将需要动态渲染的部分单元格交给自定义模板渲染策略。poi-tl提供了抽象表格策略 DynamicTableRenderPolicy 来实现这样的功能。

以官网模板为例
在这里插入图片描述

策略类

import com.deepoove.poi.data.RowRenderData;
import com.deepoove.poi.policy.DynamicTableRenderPolicy;
import com.deepoove.poi.policy.TableRenderPolicy;
import com.deepoove.poi.util.TableTools;
import org.apache.poi.xwpf.usermodel.XWPFTable;
import org.apache.poi.xwpf.usermodel.XWPFTableRow;

import java.util.List;

public class DetailTablePolicy extends DynamicTableRenderPolicy {
    // 货品填充数据所在行数
    int goodsStartRow = 2;
    // 人工费填充数据所在行数
    int laborsStartRow = 5;

    @Override
    public void render(XWPFTable table, Object data) throws Exception {
        if (null == data) return;
        DetailData detailData = (DetailData) data;

        // 人工费
        List<RowRenderData> labors = detailData.getLabors();
        if (null != labors) {
            table.removeRow(laborsStartRow);
            // 循环插入行
            for (int i = 0; i < labors.size(); i++) {
                XWPFTableRow insertNewTableRow = table.insertNewTableRow(laborsStartRow);
                for (int j = 0; j < 7; j++) insertNewTableRow.createCell();

                // 横合并单元格
                TableTools.mergeCellsHorizonal(table, laborsStartRow, 0, 3);
                // 单行渲染
                TableRenderPolicy.Helper.renderRow(table.getRow(laborsStartRow), labors.get(i));
            }
        }

        // 货物
        List<RowRenderData> goods = detailData.getGoods();
        if (null != goods) {
            table.removeRow(goodsStartRow);
            for (int i = 0; i < goods.size(); i++) {
                XWPFTableRow insertNewTableRow = table.insertNewTableRow(goodsStartRow);
                for (int j = 0; j < 7; j++) insertNewTableRow.createCell();
                TableRenderPolicy.Helper.renderRow(table.getRow(goodsStartRow), goods.get(i));
            }
        }
    }
}

实体类对象

PaymentData.java
import com.deepoove.poi.expression.Name;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import office.DetailData;

@Data
@AllArgsConstructor
@NoArgsConstructor
public class PaymentData {

    @Name("detail_table")
    private DetailData detailTable;

}
DetailData.java
import com.deepoove.poi.data.RowRenderData;
import lombok.Data;

import java.util.List;

@Data
public class DetailData {
    // 货品数据
    private List<RowRenderData> goods;

    // 人工费数据
    private List<RowRenderData> labors;
}

单元测试类

import com.deepoove.poi.XWPFTemplate;
import com.deepoove.poi.config.Configure;
import com.deepoove.poi.data.RowRenderData;
import com.deepoove.poi.data.Rows;
import excel.DetailTablePolicy;
import org.junit.jupiter.api.BeforeEach;

import java.util.Arrays;
import java.util.List;

public class Test {

    String resource = "C:\\Users\\huahua\\Desktop\\demo\\table.docx";
    PaymentData datas = new PaymentData();

    @BeforeEach
    public void init() {
        datas.setTotal("总共:7200");
        DetailData detailTable = new DetailData();
        RowRenderData rowRenderData = Rows.of("4", "墙纸", "书房+卧室", "1500", "/", "400", "1600").center().create();
        List<RowRenderData> goods = Arrays.asList(rowRenderData, rowRenderData, rowRenderData);

        RowRenderData labor = Rows.of("油漆工", "2", "200", "400").center().create();
        List<RowRenderData> labors = Arrays.asList(labor, labor, labor, labor);
        detailTable.setGoods(goods);
        detailTable.setLabors(labors);
        datas.setDetailTable(detailTable);
    }

    @org.junit.jupiter.api.Test
    public void testPaymentExample() throws Exception {
        Configure config = Configure.builder().bind("detail_table", new DetailTablePolicy()).build();
        XWPFTemplate template = XWPFTemplate.compile(resource, config).render(datas);
        template.writeToFile("C:\\Users\\huahua\\Desktop\\demo\\table_output.docx");
    }
}

扩展

表格数据绑定

这里有个特别需要注意的地方,就是@Name("detail_table"),这个对象的值一定要和模板中的保持一致,否则在表格对象绑定的时候无法绑定到数据,从 XWPFTemplate template = XWPFTemplate.compile(resource, config).render(datas);绑定数据的时候传递到自定义测策略中的时候,数据会丢失,底层源码如下

  void visit(ElementTemplate eleTemplate) {
      RenderPolicy policy = eleTemplate.findPolicy(this.template.getConfig());
      Objects.requireNonNull(policy, "Cannot find render policy: [" + eleTemplate.getTagName() + "]");
      if (!(policy instanceof DocxRenderPolicy)) {
          logger.info("Start render Template {}, Sign:{}, policy:{}", new Object[]{eleTemplate, eleTemplate.getSign(), ClassUtils.getShortClassName(policy.getClass())});
          policy.render(eleTemplate, this.renderDataCompute.compute(eleTemplate.getTagName()), this.template);
      }
  }

this.renderDataCompute.compute(eleTemplate.getTagName(),其中renderDataCompute是我们单元测试类中传递的datas数据,eleTemplate.getTagName()是word模板中的{{detail_table}},从我们传递的数据中获取detail_table这个属性,并传递给render方法,这就是为什么如果属性名不匹配,render方法取不到值的原因。
至于total属性为什么不需要,后续再补充

单元格合并

横向单元格合并在行渲染的时候执行,纵向单元格合并在行渲染后执行,下面是一个行渲染的例子,每5行的第一列合并为一列

    public void render(XWPFTable table, Object data) throws Exception {
        if (null == data) return;
        List<DetailData2> dataList = (List<DetailData2>) data;
        if (dataList.size() == 0) return;

        //设置表格属性
        setTableType(table);
        // 循环插入行, 有多少条数据就有多少行
        //插入的时候是倒序插入的,所以循环遍历的时候倒序遍历
        for (int i = dataList.size()-1; i >=0; i--) {
            XWPFTableRow insertNewTableRow = table.insertNewTableRow(goodsStartRow);
            //创建单元格,即一行包含几列
            for (int j = 0; j < 11; j++) insertNewTableRow.createCell();
            // 单行渲染
            RowRenderData rowRenderData = this.getCellList(dataList.get(i));
            TableRenderPolicy.Helper.renderRow(table.getRow(goodsStartRow), rowRenderData);
        }

        //合并单元格
        int start = 3;
        for (int i = 0; i < dataList.size()/5; i++) {
        	//合并table对象从start-start+4行的第0列
            TableTools.mergeCellsVertically(table, 0, start, start+4);
            start += 5;
        }

    }

render方法中,行是从后向前渲染的,所以数据需要从后向前遍历,因为案例的数据都是一样的,所以没按照次做法。

渲染场景

在这里插入图片描述
支持渲染整个表格,渲染行和渲染单元格三种场景,表格暂时没用到,先不介绍。

渲染行

渲染行即模板只包含table的表头,下面的行数据需要手动渲染。模板文件可参考动态表格的第一个事例
核心代码如下

		for (int i = dataList.size()-1; i >=0; i--) {
            XWPFTableRow insertNewTableRow = table.insertNewTableRow(goodsStartRow);
            //创建单元格,即一行包含几列
            for (int j = 0; j < 11; j++) insertNewTableRow.createCell();
            // 单行渲染
            RowRenderData rowRenderData = this.getCellList(dataList.get(i));
            TableRenderPolicy.Helper.renderRow(table.getRow(goodsStartRow), rowRenderData);
        }

完整代码如下,其中包含了table及行和单元格样式的设置

package excel;

import com.deepoove.poi.data.*;
import com.deepoove.poi.data.style.*;
import com.deepoove.poi.policy.DynamicTableRenderPolicy;
import com.deepoove.poi.policy.TableRenderPolicy;
import com.deepoove.poi.util.TableTools;
import org.apache.poi.xwpf.usermodel.*;

import java.util.Arrays;
import java.util.List;

public class DetailTablePolicy2 extends DynamicTableRenderPolicy {
    // 货品填充数据所在行数
    int goodsStartRow = 3;

    @Override
    public void render(XWPFTable table, Object data) throws Exception {
        if (null == data) return;
        List<DetailData2> dataList = (List<DetailData2>) data;
        if (dataList.size() == 0) return;

        //设置表格属性
        setTableType(table);
        // 循环插入行, 有多少条数据就有多少行
        //插入的时候是倒序插入的,所以循环遍历的时候倒序遍历
        for (int i = dataList.size()-1; i >=0; i--) {
            XWPFTableRow insertNewTableRow = table.insertNewTableRow(goodsStartRow);
            //创建单元格,即一行包含几列
            for (int j = 0; j < 11; j++) insertNewTableRow.createCell();
            // 单行渲染
            RowRenderData rowRenderData = this.getCellList(dataList.get(i));
            TableRenderPolicy.Helper.renderRow(table.getRow(goodsStartRow), rowRenderData);
        }

        //合并单元格
        int start = 3;
        for (int i = 0; i < dataList.size()/5; i++) {
            TableTools.mergeCellsVertically(table, 0, start, start+4);
            start += 5;
        }

    }

    private void setTableType(XWPFTable table) {
        table.setTableAlignment(TableRowAlign.CENTER);
        table.setRightBorder(XWPFTable.XWPFBorderType.DOUBLE, 1, 0, "898989");
        table.setLeftBorder(XWPFTable.XWPFBorderType.THICK, 1, 0, "898989");
        table.setBottomBorder(XWPFTable.XWPFBorderType.THICK, 1, 0, "898989");
        table.setTopBorder(XWPFTable.XWPFBorderType.THICK, 1, 0, "898989");
        table.setInsideHBorder(XWPFTable.XWPFBorderType.THICK, 1, 0, "898989");
        table.setInsideVBorder(XWPFTable.XWPFBorderType.THICK, 1, 0, "898989");
    }

    public RowRenderData getCellList(DetailData2 detailData2) {
        RowRenderData rowRenderData = new RowRenderData();

        setRowType(rowRenderData);

        rowRenderData.addCell(createCell(detailData2.getName()));
        rowRenderData.addCell(createCell(detailData2.getRange()));
        operateValue(rowRenderData, detailData2.getNo1(),detailData2.getNo2(),detailData2.getNo3(),detailData2.getNo4(),detailData2.getNo5(),
                detailData2.getNo6(),detailData2.getNo7(),detailData2.getNo8(),detailData2.getNo9());
        return rowRenderData;
    }

    private void setRowType(RowRenderData rowRenderData) {
        //行样式
        RowStyle rowStyle = new RowStyle();
        rowRenderData.setRowStyle(rowStyle);

        //文本样式
        ParagraphStyle defaultParaStyle = new ParagraphStyle();
        defaultParaStyle.setAlign(ParagraphAlignment.CENTER);

        //单元格样式
        CellStyle defaultCellStyle = new CellStyle();
        defaultCellStyle.setVertAlign(XWPFTableCell.XWPFVertAlign.CENTER);
        defaultCellStyle.setDefaultParagraphStyle(defaultParaStyle);
        rowStyle.setDefaultCellStyle(defaultCellStyle);



    }

    private CellRenderData createCell(Object text) {
        return Cells.of(String.valueOf(text)).create();
    }

    private void operateValue(RowRenderData rowRenderData, int... values) {
        Arrays.stream(values).mapToObj((value) -> {
            CellRenderData cellRenderData = Cells.of(String.valueOf(value)).create();
            CellStyle cellStyle = new CellStyle();
            if (value > 50 ) {
                //单元格背景色
                cellStyle.setBackgroundColor("FFFF00");
                //文本样式
//                paragraphStyle.setDefaultTextStyle(new Style("FF7D7D"));
            } else if (value > 0) {
                cellStyle.setBackgroundColor("FF7D7D");
            } else {
                cellStyle.setBackgroundColor("92D050");
            }
            cellRenderData.setCellStyle(cellStyle);
            return cellRenderData;
        }).forEach(cellRenderData -> rowRenderData.addCell(cellRenderData));
    }
}
渲染单元格

渲染单元格同比渲染行简单一些,因为模板是完整的,我们只需要像渲染静态表格那样,替换单元格的内容及样式即可。这里为什么不使用静态表格,是因为静态表格只能修改文本及其样式,这里我们需要修改单元格的背景色。

		for (int i = dataList.size()-1; i >=0; i--) {
            int[] values = dataList.get(i);
            XWPFTableRow insertNewTableRow = table.getRow(goodsStartRow);
            // 单行渲染
            for (int j = 2; j < 11; j++) {
                CellRenderData cellRenderData = this.getCell(values[j-2]);
                TableRenderPolicy.Helper.renderCell(insertNewTableRow.getCell(j), cellRenderData, cellRenderData.getCellStyle(), defaultTextStyle);
            }
            goodsStartRow++;
        }

完整代码如下

package excel;

import com.deepoove.poi.data.CellRenderData;
import com.deepoove.poi.data.Cells;
import com.deepoove.poi.data.RowRenderData;
import com.deepoove.poi.data.style.CellStyle;
import com.deepoove.poi.data.style.ParagraphStyle;
import com.deepoove.poi.data.style.RowStyle;
import com.deepoove.poi.data.style.Style;
import com.deepoove.poi.policy.DynamicTableRenderPolicy;
import com.deepoove.poi.policy.TableRenderPolicy;
import com.deepoove.poi.util.TableTools;
import org.apache.poi.xwpf.usermodel.*;

import java.util.Arrays;
import java.util.List;

public class DetailTablePolicy3 extends DynamicTableRenderPolicy {
    // 货品填充数据所在行数
    int goodsStartRow = 3;

    @Override
    public void render(XWPFTable table, Object data) throws Exception {
        if (null == data) return;
        List<int[]> dataList = (List<int[]>) data;
        if (dataList.size() == 0) return;


        Style defaultTextStyle = new Style();


        // 循环插入行, 有多少条数据就有多少行
        //插入的时候是倒序插入的,所以循环遍历的时候倒序遍历
        for (int i = dataList.size()-1; i >=0; i--) {
            int[] values = dataList.get(i);
            XWPFTableRow insertNewTableRow = table.getRow(goodsStartRow);
            // 单行渲染
            for (int j = 2; j < 11; j++) {
                CellRenderData cellRenderData = this.getCell(values[j-2]);
                TableRenderPolicy.Helper.renderCell(insertNewTableRow.getCell(j), cellRenderData, cellRenderData.getCellStyle(), defaultTextStyle);
            }
            goodsStartRow++;
        }
    }

    private CellRenderData getCell(int value) {
        CellRenderData cellRenderData = Cells.of(String.valueOf(value)).create();

        CellStyle cellStyle = new CellStyle();
        cellStyle.setVertAlign(XWPFTableCell.XWPFVertAlign.CENTER);
        if (value > 50 ) {
            //单元格背景色
            cellStyle.setBackgroundColor("FFFF00");
        } else if (value > 0) {
            cellStyle.setBackgroundColor("FF7D7D");
        } else {
            cellStyle.setBackgroundColor("92D050");
        }
        cellRenderData.setCellStyle(cellStyle);

        ParagraphStyle defaultParagraphStyle = new ParagraphStyle();
        defaultParagraphStyle.setAlign(ParagraphAlignment.CENTER);
        cellStyle.setDefaultParagraphStyle(defaultParagraphStyle);
        return cellRenderData;
    }
}

模板文件参考如下表格,使用于这种表格行数固定及有几列固定值的场景:
在这里插入图片描述

  • 17
    点赞
  • 26
    收藏
    觉得还不错? 一键收藏
  • 4
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值