不同键的总和值(不分组)

时间:2016-09-16 18:00:09

标签: collections sum jasper-reports

背景

考虑以下data set

  Name   Mode   Tally
 ------ ------ -------
  N_1     M_1    1000
  N_2     M_3    4000
  N_3     M_2     500
  N_4     M_1    2000
  N_5     M_3    8000

模式的总计为:

  Mode   Total
 ------ -------
  M_1     3000
  M_2      500
  M_3    12000

数据按月分组,并按名称排序;它无法按模式分组。 模式的整个值集未知但有限(例如M_1M_2M_3M_xM_yM_z,等等。)

问题

模式总计必须在摘要频段中显示,该频段看起来像是使用JRDistinctCountIncrementer递增的变量的良好候选者(使用增量工厂类名JRDistinctCountIncrementerFactory)。部分问题是documentation缺乏。

示例输出

清楚说明预期用途:

Report Layout

请注意 Tx小计部分中的元素如何重用现有样式并与现有列对齐。只要Scriptlet可以公开可迭代数据,就可以使用Scriptlet将字符串值写入摘要频段。

方法1

必须在填写报告行之后检索每个不同元组的总计列表(例如,模式和计数)。然后该列表作为JRMapCollectionDataSource传递到子报表。该子报告位于主报告的摘要范围内。

为此,必须按以下方式创建变量:

  • 姓名: modes
  • 价值类名称: java.util.Map
  • 计算: No Calculation Function
  • 表达式: new AbstractMap.SimpleEntry( $F{mode}, $F{tally} )
  • 初始值表达式:
  • 增量类型: None
  • 增量工厂类名称:
  • 重置类型: Report

这将允许子报告的数据源表达式

new JRMapCollectionDataSource( $V{modes} )

方法2

创建MappedIncrementerFactoryMappedIncrementer,类似于JRDistinctCountIncrementerFactoryJRDistinctCountIncrementer

方法3

预先计算总计并使用数据对象模型传递它们。例如:

public class DataSetItem {
    public String getName() { ... }
    public String getMode() { ... }
    public Integer getTally() { ... }
}

public class DataSet {
    public List<DataSetItem> getDataSetItemList() { ... }
    public Map<String, Integer> getDataSetTotals() { ... }
}

public class DataSetFactory {
    /** Returns a single instance that has the list of items and totals. */
    public List<DataSet> createDataSetItemCollection() { ... }
}

方法4

使用Scriptlet并为值返回JRDataSource

问题

如何创建包含键/值对的类型集合变量(映射),其中每个值是与键名匹配的报表行的总和?

2 个答案:

答案 0 :(得分:3)

这将是方法4:使用JRScriplet

scriplet class

scriplet将汇总并存储数据到稍后(在所有细节之后)返回数据源。注意:不考虑Nullpointer或数据源的其他问题。

public class Scriplet extends JRDefaultScriptlet {

    private Map<String, TxSubTotal> subTotals; //Use map for quick access
    private int lastRecNr = 0; //Jasper has a bad habit of calling twice

    public Scriplet(){
        super();
        subTotals = new HashMap<>();
    }

    @Override
    public void afterDetailEval() throws JRScriptletException {
        int recNr = ((Integer)this.getVariableValue("REPORT_COUNT")); 
        if (lastRecNr==recNr){ //Check we only count once per record
            return;
        }
        lastRecNr=recNr;
        String key = (String)this.getFieldValue("Mode");
        int value = ((Integer)this.getFieldValue("Tally")).intValue();

        TxSubTotal tst = subTotals.get(key);
        if (tst == null){
            tst = new TxSubTotal(key);
            subTotals.put(key, tst);
        }
        tst.addTotal(value);
        super.afterDetailEval();
    }

    //This is our datasource
    public JRRewindableDataSource getSubTotalDataSource(){
        List<TxSubTotal> txList = new ArrayList<>();
        //add some sorting to show the power of scriplet
        txList.addAll(subTotals.values());
        Collections.sort(txList);
        return new JRBeanCollectionDataSource(txList);
    }
}

保存数据的Bean

这保留了数据并实现Comparable以进行一些不错的排序

public class TxSubTotal implements Comparable<TxSubTotal>{
    private String mode;
    private int total;
    public TxSubTotal(String key) {
        mode = key;
    }
    public String getMode() {
        return mode;
    }
    public void setMode(String mode) {
        this.mode = mode;
    }
    public int getTotal() {
        return total;
    }
    public void setTotal(int total) {
        this.total = total;
    }
    public void addTotal(int value){
        this.total+=value;
    }
    @Override
    public int compareTo(TxSubTotal o) {
        return mode.compareTo(o.getMode());
    }

    @Override
    public boolean equals(Object obj) {
        if (obj instanceof TxSubTotal){
            return mode.equals(((TxSubTotal)obj).getMode());    
        }
        return false;
    }

    @Override
    public int hashCode() {
        return mode.hashCode();
    }
}

jrxml

将scriptlet设置为report

scriptletClass="Scriplet" 

此jrxml使用jr:table组件显示数据,但也可以使用子报告。关键是在汇总带中使用它或在表/子报表组件上使用evaluationTime="report"。数据源表达式为

<dataSourceExpression><![CDATA[$P{REPORT_SCRIPTLET}.getSubTotalDataSource()]]></dataSourceExpression>

完整的jrxml

<?xml version="1.0" encoding="UTF-8"?>
<jasperReport xmlns="http://jasperreports.sourceforge.net/jasperreports" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://jasperreports.sourceforge.net/jasperreports http://jasperreports.sourceforge.net/xsd/jasperreport.xsd" name="Jarvis" pageWidth="595" pageHeight="842" columnWidth="555" leftMargin="20" rightMargin="20" topMargin="20" bottomMargin="20" scriptletClass="Scriplet" uuid="d626fd77-14da-4974-8b4f-6368e913ac91">
    <style name="table">
        <box>
            <pen lineWidth="0.25" lineColor="#000000"/>
        </box>
    </style>
    <style name="table_TD" mode="Opaque" backcolor="#FFFFFF">
        <box>
            <pen lineWidth="0.5" lineColor="#000000"/>
        </box>
    </style>
    <subDataset name="subTotals" uuid="486941af-2c8f-4ebb-8b8a-7a1dfe2ac0ad">
        <field name="mode" class="java.lang.String"/>
        <field name="total" class="java.lang.Integer"/>
    </subDataset>
    <queryString>
        <![CDATA[]]>
    </queryString>
    <field name="Name" class="java.lang.String"/>
    <field name="Mode" class="java.lang.String"/>
    <field name="Tally" class="java.lang.Integer"/>
    <background>
        <band splitType="Stretch"/>
    </background>
    <columnHeader>
        <band height="20" splitType="Stretch">
            <staticText>
                <reportElement mode="Opaque" x="0" y="0" width="100" height="20" forecolor="#000000" backcolor="#CCCCCC" uuid="30cd16d8-6765-42f9-b5f9-22bc0625adca"/>
                <box leftPadding="2" rightPadding="2">
                    <pen lineWidth="0.25"/>
                    <topPen lineWidth="0.25"/>
                    <leftPen lineWidth="0.25"/>
                    <bottomPen lineWidth="0.25"/>
                    <rightPen lineWidth="0.25"/>
                </box>
                <textElement verticalAlignment="Middle"/>
                <text><![CDATA[Name]]></text>
            </staticText>
            <staticText>
                <reportElement mode="Opaque" x="100" y="0" width="100" height="20" forecolor="#000000" backcolor="#CCCCCC" uuid="88418f9a-5d2b-487e-b04d-e11bc57726d7"/>
                <box leftPadding="2" rightPadding="2">
                    <pen lineWidth="0.25"/>
                    <topPen lineWidth="0.25"/>
                    <leftPen lineWidth="0.25"/>
                    <bottomPen lineWidth="0.25"/>
                    <rightPen lineWidth="0.25"/>
                </box>
                <textElement verticalAlignment="Middle"/>
                <text><![CDATA[Mode]]></text>
            </staticText>
            <staticText>
                <reportElement mode="Opaque" x="200" y="0" width="100" height="20" forecolor="#000000" backcolor="#CCCCCC" uuid="dd2495bb-3a3d-4b0e-b986-055bf55cf19b"/>
                <box leftPadding="2" rightPadding="2">
                    <pen lineWidth="0.25"/>
                    <topPen lineWidth="0.25"/>
                    <leftPen lineWidth="0.25"/>
                    <bottomPen lineWidth="0.25"/>
                    <rightPen lineWidth="0.25"/>
                </box>
                <textElement textAlignment="Right" verticalAlignment="Middle"/>
                <text><![CDATA[Tally]]></text>
            </staticText>
        </band>
    </columnHeader>
    <detail>
        <band height="20" splitType="Stretch">
            <textField>
                <reportElement x="0" y="0" width="100" height="20" uuid="53931171-df80-43e7-9b9d-5d77ce4223dc"/>
                <box leftPadding="2" rightPadding="2">
                    <pen lineWidth="0.25"/>
                    <topPen lineWidth="0.25"/>
                    <leftPen lineWidth="0.25"/>
                    <bottomPen lineWidth="0.25"/>
                    <rightPen lineWidth="0.25"/>
                </box>
                <textElement verticalAlignment="Middle"/>
                <textFieldExpression><![CDATA[$F{Name}]]></textFieldExpression>
            </textField>
            <textField>
                <reportElement x="100" y="0" width="100" height="20" uuid="f809c675-c68b-4ad0-b662-23ba6e9dd70c"/>
                <box leftPadding="2" rightPadding="2">
                    <pen lineWidth="0.25"/>
                    <topPen lineWidth="0.25"/>
                    <leftPen lineWidth="0.25"/>
                    <bottomPen lineWidth="0.25"/>
                    <rightPen lineWidth="0.25"/>
                </box>
                <textElement verticalAlignment="Middle"/>
                <textFieldExpression><![CDATA[$F{Mode}]]></textFieldExpression>
            </textField>
            <textField>
                <reportElement x="200" y="0" width="100" height="20" uuid="0852ff01-aa61-4a7f-9c11-a481b78b56fd"/>
                <box leftPadding="2" rightPadding="2">
                    <pen lineWidth="0.25"/>
                    <topPen lineWidth="0.25"/>
                    <leftPen lineWidth="0.25"/>
                    <bottomPen lineWidth="0.25"/>
                    <rightPen lineWidth="0.25"/>
                </box>
                <textElement textAlignment="Right" verticalAlignment="Middle"/>
                <textFieldExpression><![CDATA[$F{Tally}]]></textFieldExpression>
            </textField>
        </band>
    </detail>
    <summary>
        <band height="75" splitType="Stretch">
            <componentElement>
                <reportElement key="table" style="table" x="120" y="25" width="180" height="50" uuid="5158e7c3-d87d-4877-a3ab-d6a6f9a57c5a"/>
                <jr:table xmlns:jr="http://jasperreports.sourceforge.net/jasperreports/components" xsi:schemaLocation="http://jasperreports.sourceforge.net/jasperreports/components http://jasperreports.sourceforge.net/xsd/components.xsd">
                    <datasetRun subDataset="subTotals" uuid="9b73e50e-4bdf-41c2-8d40-4d215fe6b6e9">
                        <dataSourceExpression><![CDATA[$P{REPORT_SCRIPTLET}.getSubTotalDataSource()]]></dataSourceExpression>
                    </datasetRun>
                    <jr:column width="90" uuid="cbb85338-16b3-487d-8045-6dd94c661fc0">
                        <jr:detailCell style="table_TD" height="30" rowSpan="1">
                            <textField>
                                <reportElement x="0" y="0" width="90" height="30" uuid="5c07b5af-9e28-4a14-a70a-3789bfc88087"/>
                                <box leftPadding="2" rightPadding="2"/>
                                <textElement verticalAlignment="Middle"/>
                                <textFieldExpression><![CDATA[$F{mode}]]></textFieldExpression>
                            </textField>
                        </jr:detailCell>
                    </jr:column>
                    <jr:column width="90" uuid="d69567f0-ec7c-4b83-bc22-5aae1cab7879">
                        <jr:detailCell style="table_TD" height="30" rowSpan="1">
                            <textField>
                                <reportElement x="0" y="0" width="90" height="30" uuid="2a3a2055-f9c0-46e3-b816-b6ee1c989f9c"/>
                                <box leftPadding="2" rightPadding="2"/>
                                <textElement textAlignment="Right" verticalAlignment="Middle"/>
                                <textFieldExpression><![CDATA[$F{total}]]></textFieldExpression>
                            </textField>
                        </jr:detailCell>
                    </jr:column>
                </jr:table>
            </componentElement>
            <staticText>
                <reportElement x="120" y="5" width="180" height="20" uuid="9ef22e85-34ce-47c9-afad-2813a8a0b863"/>
                <textElement verticalAlignment="Middle"/>
                <text><![CDATA[Tx SubTotals]]></text>
            </staticText>
        </band>
    </summary>
</jasperReport>

从包含有问题数据的cvs文件生成pdf导出的主要方法

public static void main(String[] args) throws JRException, FileNotFoundException {
    JasperReport report = JasperCompileManager.compileReport("myReport.jrxml");

    JRCsvDataSource datasource = new JRCsvDataSource(new File("data/datasource.csv"));
    datasource.setFieldDelimiter(';');
    datasource.setUseFirstRowAsHeader(true);
    datasource.setLocale(Locale.US);

    JasperPrint jasperPrint = JasperFillManager.fillReport(report, new HashMap<String, Object>(), datasource);

    JRPdfExporter exporter = new JRPdfExporter();       
    exporter.setExporterInput(new SimpleExporterInput(jasperPrint));
    exporter.setExporterOutput(new SimpleOutputStreamExporterOutput("pdf/example.pdf"));
    SimplePdfExporterConfiguration configuration = new SimplePdfExporterConfiguration();
    exporter.setConfiguration(configuration);
    exporter.exportReport();
}

结果

Result

答案 1 :(得分:2)

一般解决方案如下。键和值列元组的名称在子报表中设置为参数。主报告包含scriptlet,子报告和总计摘要页面。这在有许多几乎相同的子报表的情况下很有用,但只有部分子报表需要基于列元组的总计。

每个具有总元组总数的子报表必须为关键参数定义一个值,并为值参数定义值:SCRIPTLET_KEY_COLUMN_NAMESCRIPTLET_VALUE_COLUMN_NAME,分别。如果未在子报表中设置key参数,则isSubreport()将返回false,并且不会执行汇总。

的scriptlet

主报告运行以下scriptlet,通过将 Scriptlet类设置为com.company.jasper.TupleSumScriptlet来配置。

package com.company.jasper;

import java.util.*;
import net.sf.jasperreports.engine.*;
import net.sf.jasperreports.engine.data.JRBeanCollectionDataSource;

public class TupleSumScriplet extends JRDefaultScriptlet {
    private final static String REPORT_KEY_COLUMN_NAME
            = "SCRIPTLET_KEY_COLUMN_NAME";
    private final static String REPORT_VALUE_COLUMN_NAME
            = "SCRIPTLET_VALUE_COLUMN_NAME";

    private final Map<String, Integer> sums = new HashMap<>();

    public TupleSumScriplet() { }

    @Override
    public void afterDetailEval() throws JRScriptletException {
        if (isSubreport()) {
            final String keyColumnName = getKeyColumnName();
            final String key = (String) getFieldValue(keyColumnName);

            final String valueColumnName = getValueColumnName();
            final int value = (Integer) getFieldValue(valueColumnName);

            final Map<String, Integer> totals = getSums();
            final int sum = totals.containsKey(key) ? totals.get(key) : 0;

            totals.put(key, sum + value);
        }
    }

    public JRDataSource getDataSource() {
        return new JRBeanCollectionDataSource(sort(getSums()));
    }

    public Map<String, Integer> getSums() {
        return this.sums;
    }

    private String getKeyColumnName() throws JRScriptletException {
        return (String) getParameterValue(REPORT_KEY_COLUMN_NAME);
    }

    protected String getValueColumnName() throws JRScriptletException {
        return (String) getParameterValue(REPORT_VALUE_COLUMN_NAME);
    }

    private boolean isSubreport() {
        boolean result;

        try {
            result = true;
            final String unused = getKeyColumnName();

        } catch (JRScriptletException e) {
            result = false;
        }

        return result;
    }

    public static
            <K extends Comparable<? super K>, V> Collection<Map.Entry<K, V>>
            sort(Map<K, V> map) {
        final List<Map.Entry<K, V>> list = new LinkedList<>(map.entrySet());

        Collections.sort(list, new Comparator<Map.Entry<K, V>>() {
            @Override
            public int compare(Map.Entry<K, V> o1, Map.Entry<K, V> o2) {
                return (o1.getKey()).compareTo(o2.getKey());
            }
        });

        return list;
    }
}

主报告

主报告的JRXML包括:

  • 详细信息带子报告
  • 摘要频段 Grand Totals子报告

详细信息区子报告元素必须使用主报告中的REPORT_SCRIPLET传入$P{REPORT_SCRIPTLET}参数。

摘要频段子报表元素(Grand Totals)非常简单,因为它使用Map.EntrySet<K, V>定义的API,它公开了getKeygetValue方法。这些方法直接映射到在子报表中定义和使用的字段 - 分别为keyvalue。该元素还必须将数据源表达式设置为:

$P{REPORT_SCRIPTLET}.getDataSource()

Grand Totals子报告的相关JRXML如下:

<field name="key" class="java.lang.String">
    <fieldDescription><![CDATA[key]]></fieldDescription>
</field>
<field name="value" class="java.lang.Integer">
    <fieldDescription><![CDATA[value]]></fieldDescription>
</field>
<detail>
    <band height="15" splitType="Stretch">
        <property name="com.jaspersoft.studio.unit.height" value="pixel"/>
        <textField isBlankWhenNull="true">
            <reportElement x="0" y="0" width="75" height="15" isRemoveLineWhenBlank="true"/>
            <textFieldExpression><![CDATA[$F{key}]]></textFieldExpression>
        </textField>
        <textField isBlankWhenNull="true">
            <reportElement x="75" y="0" width="150" height="15" isRemoveLineWhenBlank="true"/>
            <textFieldExpression><![CDATA[$F{value}]]></textFieldExpression>
        </textField>
    </band>
</detail>

示例输出

这会产生所需的结果,并且在元组的列名称发生更改时不需要修改Java源代码。

Example Filled Report