[]
        
(Showing Draft Content)

高级-游刃有余

前言

首先感谢 GcExcel 的使用者,分享宝贵的使用经验,让我们了解到他们以及您将来可能会如何使用 GcExcel。并据此分享给您可能涉及的功能及知识体系结构。本章节中,您将学习了解 GcExcel 在商业场景中所使用的一些功能及方案。

  • 功能

    • 模板语言

    • 公式引擎及优化

  • 场景

    • 服务端批量报表生成

    • 服务端公式计算服务

高级计划

模板语言

一般 Excel 类组件,通常会提供与 Excel 兼容的 API,如样式,条件格式,表格,图表等,开发者通过调用这些 API 即可满足 Excel 报表导出的需求。在批量导出的需求下,开发者可以通过提前预制设计Excel模板文件,通过替换占位符,将数据填充,生成报表来解决批量导出的需求。

但是,对于数据量动态变化的场景,只依靠 API 动态调整布局,则显得捉襟见肘。开发者需要针对每一个 Excel 模板文件,编写代码处理当数据量变化时,该如何增加或调整行列数保证样式和布局,亦或者同步表格,图表的数据引用,保证正确性。

为了解决动态调整布局的需求,GcExcel 提供了模板语言,帮助用户可以解决上述问题。

GcExcel 是如何通过模板生成报表的

GcExcel 模板文件是以 Excel 的 xlsx 格式保存的,这样最大的好处在于,您可以使用 Excel 设计模板文件。

在 GcExcel 中,与打开 Excel 文件一样,打开模板文件。

// 初始化工作簿 workbook
Workbook workbook = new Workbook();
// 加载模板文件 
workbook.open("template.xlsx");

准备并绑定数据源,通常您可能会将数据以数据库,CSV或者JSON的格式存储。您可以使用自定义对象,dataSet,dataTable的方式将模板与数据源绑定。比如:

BudgetVals dataSource = new BudgetVals();
{
    dataSource.Records = new ArrayList<BudgetRecord>();
}
BudgetRecord record1 = new BudgetRecord();
record1.SmartPhone = "Apple iPhone 11";
record1.Event = "Phone Launch";
record1.Budget = 1000;
dataSource.Records.add(record1);

BudgetRecord record2 = new BudgetRecord();
record2.SmartPhone = "Apple iPhone 11";
record2.Event = "CEO Meet";
record2.Budget = 2000;
dataSource.Records.add(record2);

workbook.addDataSource("ds", dataSource);

下来使用 ProcessTemplate 方法填充模板,并保存为 Excel 报表文件。

// 调用 processTemplate 来给模板填充数据。
workbook.processTemplate();
// 保存为Excel
workbook.save("Report.xlsx");

您可以参考使用模板创建Excel报表学习一个具体的示例。

模板配置

模板填充的主要过程是将数据填写到模板中,GcExcel 提供了模板字段(例如:{{ds.FieldName}})可以用作占位符,告知 GcExcel 如何将数据源中的字段与单元格进行关联。

模板属性

模板属性,是对模板字段的扩展,如:

  • 单元格扩展属性,可以描述字段是横向扩展还是纵向扩展。

  • 上下文属性,将不同的模板字段关联在一起,使用相同的上下文。

  • 分组属性,在填充数据时,对数据源中的数据进行分组(类似数据库中的Group)。

  • 排序属性,表示如何对字段进行排序。

模板中的 Excel 功能

除了上述的基础模板语言,您也可以在模板中使用Excel功能:

type=warning

更多模板的教程和示例请参考:模板文档模板示例

自定义公式

当您使用 GcExcel 时,难免有一些特定的需求,需要结合公式引擎,但希望自定义逻辑。这个时候,您可以使用 GcExcel 提供的自定义公式功能。

参考如下代码,了解如何使用 GcExcel 的自定义公式。

Workbook workbook = new Workbook();
Workbook.AddCustomFunction(new MyAddFunctionX());
IWorksheet worksheet = workbook.getActiveSheet();
worksheet.getRange("A1").setValue(1);
worksheet.getRange("B1").setValue(2);
worksheet.getRange("C1").setFormula("=MyAdd(A1, B1)");

// Range C1's value is 3
Object result = worksheet.getRange("C1").getValue();
worksheet.getRange("E1:F2").setValue(new Object[][]{
    {1, 3},
    {2, 4}
});

worksheet.getRange("G1:G2").setFormulaArray("=MyAdd(E1:E2, F1:F2)");

//Range G1's value is 4, Range G2's value is 6.
Object resultG1 = worksheet.getRange("G1").getValue();
Object resultG2 = worksheet.getRange("G2").getValue();

// 额外实现 继承自 CustomFunction 的 MyAddFunctionX 类
class MyAddFunctionX extends CustomFunction {
    public MyAddFunctionX() {
        super("MyAdd", FunctionValueType.Number, new Parameter[]{
            new Parameter(FunctionValueType.Number), 
            new Parameter(FunctionValueType.Number)
            });
    }

    @Override
    public Object evaluate(Object[] arguments, ICalcContext context) {
       return (double) arguments[0] + (double) arguments[1];
    }
}

更多的自定义函数资料请参考自定义函数Demo示例

公式引擎及优化

通过初级与中级的学习计划,相信您已经掌握如何使用 GcExcel 进行公式计算。而在本章节中,将对您更多的诠释 GcExcel 中公式的运行机制,已了解如何优化公式运算的性能。

公式计算的时机

GcExcel 以性能为目标,公式仅在需要时才会被计算。例如,当您获取某个单元格公式的计算结果。

此时,GcExcel 的公式引擎会分析公式依赖的单元格,对依赖链上的每一个单元格进行计算。

公式结果的缓存

为了提升性能,GcExcel 会对每个单元格公式的结果进行缓存。仅当公式链上的某个单元格产生变化,才会重算当前单元格的公式。

例如,您在 A1 单元格中有一个公式 = A2 + A3。那么在第一次计算后 A1 的值将被缓存,只有 A2 和 A3 的值变化后,A1的值才会在下次需要时冲算。

那 GcExcel 是如何实现上述的缓存机制呢?在 GcExcel 内部,维护了一个缓存,并且维护了一个状态,是否需要冲算。在上面的例子中,当 A2 被修改时,GcExcel 会给 A1 添加一个状态,需要重算。我们管这种状态为 Dirty (标脏),认为 A1 单元格缓存的结果已经为脏数据,不可再用。这样下次在计算时,GcExcel 在处理依赖链上的单元格时,会优先从缓存中取值,如果发现单元格已标脏,才会重新计算。

性能优化

在绝大多数情况下,我们认为您无需考虑公式的性能优化,因为公式链的分析是一个非常快的过程。但是在实际的适用场景中,有的行业需要借助 Excel 强大的公式计算能力,对业务数据进行测算。例如地产,金融行业。

在这种场景下,公式计算会有一个非常长的依赖链,当修改这个链路上的任何单元格时,都会导致链路下游的单元格标记为脏。因此如果反复的修改单元格的值,会导致相同公式链分析的时间增加,从而产生性能问题。

在这种情况下您有两种方法可以优化计算性能。

  1. 挂起引擎

  2. 延迟计算

挂起引擎

挂起公式引擎可以暂停公式引擎对于公式链单元格的维护,当您修改完所有单元格,需要进行公式计算之前。恢复公式引擎即可,在这种情况下,公式引擎会将所有的单元格标记为脏。如果您的操作,会影响绝大多数的公式单元格时,该方法会很有效。

具体的做法,您可以参考失效和重新计算

延迟计算

或许您已经注意到,恢复引擎时,会导致所有的单元格都被标脏,这是因为引擎被挂起时,GcExcel 无法知道您修改了哪些单元格。

但是所有的单元格都被标脏,对于下面的场景则显得很笨拙:

  • Excel文件中,公式复杂且非常多。

  • 计算时仅利用到其中一小部分公式,但公式依赖的参数很多。

对于这种场景,因为参数的修改,公式引擎反复对同一个公式链标脏。为了优化该场景,您可以使用延迟标脏 setDeferUpdateDirtyState

延迟标脏,会将公式引擎的标脏暂停,同时记录所有修改的单元格。在下一次公式计算前,公式引擎会将记录的单元格,进行公式链分析,标脏。来保证公式计算的正确。

type=warning

请注意,延迟标脏并不会暂停公式引擎。相对应的,您需要尽可能将公式计算的时机延后,来合并公式链分析的逻辑。否则,延迟标脏并不能有效的提升性能。

与SpreadJS结合,前后端解决方案

GcExcel 做为基于 Java 的 Excel 组件,常在 B/S 架构中的服务器上使用。常用于批量操作、大数据量或者给前端提供服务的场景。通过结合 SpreadJS,您可以实现类 Excel 的全栈解决方案,例如:

  • 服务端批量报表生成

  • 服务端公式计算服务

服务端批量报表生成

在批量报表生成的场景中,您可能会使用到模板功能,在服务端批量生成报表。结合 SpreadJS,您可以让您的用户在浏览器中编辑或者预览模板。

同时也可以将生成的报告返回到前端,使用 SpreadJS 进行预览。

服务端公式计算服务

在公式计算服务的场景中,您可能会将复杂的Excel文件,作为公式引擎,存储在服务端。通过配合 SpreadJS,您可以让用户在前端填写公式计算所需要的参数,并将公式计算的结果显示在前端,给您的客户提供更加易用的体验。