图表与主题如何设计扩展:基于 JQuick-Excel 现有工厂的工程化做法

图表与主题如何设计扩展:基于 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。这样新增图表或换一套视觉风格时,影响范围始终可预测。

相关推荐
积流成海1 小时前
CSV与JSON转换:RFC 4180 转义规则与 Excel 中文乱码
json·excel·盘子工具站
小麦项目管理指南3 小时前
弱电工程管理,用 Excel 还是项目管理软件?5 个该换系统的信号
excel
Sadie_d14 小时前
Excel管理研发项目有哪些问题?什么情况要升级项目管理系统?
excel·项目管理·研发管理
Metaphor69217 小时前
如何快速拆分超大 Excel 文件【Python 教程】
python·excel
工具派19 小时前
PDF里没有表格,PDF转Excel行列是靠什么对上的?
pdf·excel
泡海椒21 小时前
JQuickExcel 大文件排障与调优:从实际路径定位 OOM、临时盘和慢任务
开发语言·数据库·缓存·oracle·excel
Data-Miner1 天前
用 Excel 做「高级」数据统计分析的实操指南
excel
2501_930707781 天前
使用C#代码设置 Excel 单元格的数字格式
excel
我的愿望是成为富婆1 天前
2027校招:大数据管理与应用专业,如何用数据能力匹配电商运营与供应链管培生
人工智能·sql·excel