图表与主题如何设计扩展:基于 JQuick-Excel 现有工厂的工程化做法
tags: #JQuickExcel #JavaExcel #开源 #POI #Excel工具
简介
图表和主题往往是 Excel 导出最后才补上的能力,但它们最容易破坏可维护性:业务代码直接堆 POI API,颜色、字体、图表类型散落在多处;当报表类型增加,复制粘贴很快失控。JQuick-Excel 当前源码已经提供图表模型 JChartData、JSeriesData、图表工厂 JExcelChartFactory,以及主题接口 JExcelTheme、主题工厂 JExcelThemeFactory 和主题枚举 JExcelThemeType。
本文讨论如何围绕这些已存在的扩展点设计,而不虚构"自动图表 SPI"。我会把标准图表交给 JExcelChartFactory,把甜甜圈图这类特殊类型以现有 JExcelExtendsChartProvider 作为扩展契约,把全局视觉语言交给 JExcelTheme。
前言
我把"图表"和"主题"看作两个层次。图表回答数据怎样可视化:类别、系列、数值、位置和类型。主题回答整张工作簿怎样统一呈现:表头、奇偶数据行、标题、页脚、公式单元格分别使用什么样式。它们不应互相承担职责。
从 JExcelChartFactory 当前实现可以确认,它使用 Apache POI 的 XSSF/XDDF API,支持 BAR、BAR3D、COLUMN、LINE、PIE、AREA、AREA3D、SCATTER、RADAR、SURFACE 等枚举类型,写入类别和系列数据后创建图表。仓库还存在 JExcelExtendsChartProvider#createDonutChart(XSSFWorkbook, JChartData, String) 这一接口契约;JExcelDonutChart.java 当前为注释代码,不能视为可直接使用的实现。这个接口仍为标准枚举之外的甜甜圈图提供了明确的扩展边界。
主题方面,JExcelTheme 是可实现接口,定义主题编码、名称、描述及六类样式构建方法;JExcelThemeFactory 可按主题编码、枚举或默认值创建主题,并可返回全部内置主题和编码。README-CN 列出大量内置主题,并说明可在 JExcelExportModel 上调用 setTheme("jade")。
环境与依赖
图表工厂直接使用 org.apache.poi.xssf.usermodel.XSSFWorkbook 和 XDDF 图表 API,因此需要项目已声明的 poi-ooxml。主题同样构建 Apache POI 的 Workbook、CellStyle、Sheet 等对象。
本文演示使用仓库已有类型:
java
import com.github.paohaijiao.enums.JExcelChartType;
import com.github.paohaijiao.factory.JExcelChartFactory;
import com.github.paohaijiao.graph.model.JChartData;
import com.github.paohaijiao.graph.model.JSeriesData;
import com.github.paohaijiao.theme.JExcelTheme;
import com.github.paohaijiao.theme.factory.JExcelThemeFactory;
若由 XML 导出流程选择主题,继续使用 JQuickExcelExportXmlParseFactory(String theme, JContext context, List<JQuickRow> rows, OutputStream outputStream),主题编码作为第一个参数传入。图表是否从 XML GRAPH 规则生成,应以当前 DSL 和测试覆盖为准;对独立报表图表,直接调用 JExcelChartFactory 是可验证的 Java API。
代码示例
先给出一个 XML 导出规则,用 GRAPH 声明柱状图,并保留基础表格数据。README-CN 已确认 GRAPH 的表达形式包含 TYPE、TITLE、CATEGORIES 与 SERIES;这里使用最小的单系列数据。
xml
<excels namespace="demo.SalesExcelService">
<excel name="exportExcel" returnClass="void"><![CDATA[
EXPORT WITH SHEET="销售", HEADER=true,
MAPPING={"month":"月份","sales":"销售额"},
FORMAT={"sales":"0.00"},
GRAPH={
TYPE=COLUMN,
TITLE="月度销售额",
CATEGORIES=["1月","2月","3月"],
SERIES=[{NAME="销售额",DATA=[120.0,180.0,150.0]}]
}
]]></excel>
</excels>
下面是一个可独立运行的 Java 图表示例。它不依赖 XML 代理,直接构造 JChartData、JSeriesData 并调用工厂。JChartData 和 JSeriesData 的具体 setter 以当前仓库模型为准,示例采用常见 JavaBean 赋值方式;若你的版本模型使用构造器或 Lombok 链式 API,应按实际签名调整。
java
package demo;
import com.github.paohaijiao.enums.JExcelChartType;
import com.github.paohaijiao.factory.JExcelChartFactory;
import com.github.paohaijiao.graph.model.JChartData;
import com.github.paohaijiao.graph.model.JSeriesData;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
import java.io.FileOutputStream;
import java.util.Arrays;
import java.util.Collections;
public class ChartThemeDemo {
public static void main(String[] args) throws Exception {
JSeriesData series = new JSeriesData();
series.setName("销售额");
series.setData(Arrays.asList(120D, 180D, 150D));
JChartData chart = new JChartData();
chart.setTitle("月度销售额");
chart.setCategoryAxisTitle("月份");
chart.setValueAxisTitle("金额");
chart.setCategories(Arrays.asList("1月", "2月", "3月"));
chart.setSeries(Collections.singletonList(series));
try (XSSFWorkbook workbook =
JExcelChartFactory.createWorkbookWithChart(chart, JExcelChartType.COLUMN, "销售");
FileOutputStream output = new FileOutputStream("sales-chart.xlsx")) {
workbook.write(output);
}
}
}
主题选择可以独立验证:
java
JExcelTheme theme = JExcelThemeFactory.create("jade");
System.out.println(theme.getCode());
若通过 XML 导出带主题的工作簿,则传入主题编码:
java
JQuickParseHandler parser =
new JQuickExcelExportXmlParseFactory("jade", context, rows, output);
JQuickFactory factory = new JQuickXmlFactory(parser, "sales.xml");
factory.createApi(SalesExcelService.class).exportExcel("field", "value");
原理说明
JExcelChartFactory.createWorkbookWithChart 创建 XSSFWorkbook 后调用 createChart。后者创建指定工作表,将 JChartData 中的类别、系列名称和数值写入工作表,再用 POI 的绘图对象创建锚点与图表。类别取第 0 列,系列从第 1 列开始;每条系列的数值范围按它在 series 列表中的位置计算。因此模型中的类别数量和每条系列数据长度必须匹配,这是图表正确性的基础。
图表类型由 JExcelChartType 进入工厂分支。横向条形和纵向柱形都基于 POI 的 BAR 图表类型,但方向分别设置为 BAR 与 COL;折线、饼图、面积、散点、雷达、曲面则创建对应类型。对枚举未覆盖的图表,不应修改业务代码到处手写 POI,而应沿用已有 JExcelExtendsChartProvider 这一边界。甜甜圈图已有 createDonutChart 专用接口方法,正说明"扩展图表"可以有明确接口,而不是强行塞进标准枚举;使用前需要在应用或项目中提供实际实现。
主题的工作方式不同。JExcelTheme 要求实现 buildHeaderStyle、数据奇偶行、标题、页脚和公式单元格六类样式;默认 buildFooter 还会创建页脚单元格并合并到指定列。JExcelThemeFactory.create(code) 负责把编码解析为 JExcelThemeType 再创建实例;传入空编码会回退默认主题。这样,调用点只使用稳定编码,具体的颜色、字体和边框封装在主题实现中。
在设计自定义主题时,我会实现 JExcelTheme 或继承项目已有的 JAbstractExcelTheme,但只覆盖确有差异的样式方法。主题应保持一致性:表头与标题强调层级,奇偶行只做轻度区分,页脚和公式样式保持可读。不要把业务状态颜色、告警标识全塞进主题;那些属于局部 STYLE 或数据条件,而不是全局视觉语言。
注意事项
第一,JExcelChartFactory 的数据写入逻辑按类别和系列下标访问列表。每个 JSeriesData.getData() 的长度应至少覆盖类别数量,空列表或长度不一致会导致索引错误或错误图表。第二,工厂创建的是 XSSFWorkbook,适用于 xlsx;不要将它和旧版 xls 工作簿混用。
第三,示例中的 XML GRAPH 数据是固定数组,适合静态或模板演示。真实业务的动态图表应确认当前 DSL 是否支持从导出行聚合;若不支持,使用 JChartData 在 Java 中构造更清晰。第四,图表写入时会创建一个工作表并把图表源数据写到其中,选择工作表名称前要避免与已有名称冲突。
第五,主题编码要来自 JExcelThemeFactory.allCodes() 或 README 已列出的编码,如 jade、oceanBlue。未知编码的处理取决于 JExcelThemeType.codeOf,上线前应做显式校验。第六,主题是默认样式,不保证覆盖 XML STYLE 的局部指定;同一单元格同时受主题和局部样式影响时,应做生成文件断言,而不是依赖阅读顺序猜测优先级。
第七,JExcelExtendsChartProvider 当前只声明甜甜圈图能力。新增其他特殊图时,建议新增同样清晰的接口方法或实现,而不是将不相关参数塞进 createDonutChart。扩展接口稳定后,再决定是否需要更新枚举和 XML DSL。
运行步骤与工程化验证
我会先把图表构建单独做成一个可重复运行的 Java 测试,而不是在完整报表里第一次验证。构造 JChartData 后,必须调用 setCategories,并通过 setSeries 或 addSeries 放入至少一条 JSeriesData;每条系列先设置名称,再设置数值列表。随后调用 JExcelChartFactory.createWorkbookWithChart(chartData, JExcelChartType.COLUMN, "销售"),使用 try-with-resources 写出 xlsx。生成文件打开后,检查数据工作表、图表标题、坐标轴标题与系列是否齐全。因为工厂会按类别数量和系列数据下标取值,测试至少覆盖空类别、空系列、类别少于数据、类别多于数据这四类非法或边界输入,并由应用层在调用前拒绝不匹配的数据。
当前工厂创建的是 XSSFWorkbook,并在指定 sheet 中同时写入类别和数值作为图表数据源;工作表名称重复会由 POI 的创建过程暴露。我的业务装配层会在创建前规范化名称并检查冲突,也会避免把保密的原始明细仅为画图而写进最终交付工作簿。若图表源数据需要隐藏、脱敏或与主表分离,应先在应用层明确该工作簿的交付边界,再决定是否直接使用这个工厂。图表适合展示已经得到的聚合数据,不应把未经核对的大量原始记录直接塞进类别列表。
主题选择也需要独立验收。运行前用 JExcelThemeFactory.create("jade") 或 README-CN 的其他已列主题编码确认能创建主题;再让同一份小数据分别导出默认主题和目标主题,对比表头、奇偶行、标题、页脚和公式单元格的显示。主题是整表默认视觉,STYLE 是局部规则,两者都出现时必须用真实工作簿验证最终效果。不要把主题编码当成业务状态开关,更不要把新增颜色直接散落在各个 XML 中;需要长期复用的视觉规则才适合通过 JExcelTheme 或现有抽象主题组织。
特殊图表必须承认版本边界。JExcelExtendsChartProvider 当前明确了 createDonutChart(XSSFWorkbook, JChartData, String) 的甜甜圈图契约,而 JExcelDonutChart 处于注释状态,不能作为已可运行实现。我的做法是在应用中提供实际实现、针对目标 POI 版本做文件验证,并把该实现与标准枚举图表分开部署。不要把任意未支持类型伪装成 JExcelChartType,也不要只根据生成过程未抛异常就认定所有 Excel 客户端都能正确渲染。
总结
图表和主题的可扩展性来自清晰分层。JChartData、JSeriesData 描述数据,JExcelChartFactory 负责标准 XSSF 图表,JExcelExtendsChartProvider 承接甜甜圈图等特殊能力;JExcelTheme 描述整表默认视觉,JExcelThemeFactory 通过稳定编码提供主题实例。
我会先用 JExcelChartFactory 验证数据长度、图表类型和生成结果,再把稳定的颜色与样式沉淀成主题,把个别报表要求留在 XML STYLE。这样新增图表或换一套视觉风格时,影响范围始终可预测。