Jasper Reports crosstab sorting with comparatorExp

2019-02-02 16:47发布

问题:

I'm trying to sort my dynamic columns in a cross tab according to some custom scheme.

In the docs I found mention of comparatorExpression: Crosstab group bucket comparator expression. The result of this expression is used to sort the buckets, in ascending or descending order. If no comparator expression is specified, the natural order will be used.

but I don't understand what the expression should look like. Can I somehow use a regular java comparator? Can someone share an example?

回答1:

I've had the same issue and did not find any examples or explanation but I found how to do it. I'll explain, so hopefully other people can use this solution.

I've used the code below for jasperreports 3.1.0 and iReport 3.1.4, but I guess it works for pretty much all versions.

First you'll have to make sure that you know wich class you have in the bucket expression for your row/column group. By default this is java.lang.String, but I have a custom class there. For this to work I needed to edit the xml like this for my column group:

<bucketExpression class="java.lang.String"><![CDATA[$F{customObj}]]></bucketExpression>

to

<bucketExpression class="com.project.CustomObj"><![CDATA[$F{customObj}]]></bucketExpression>

Obviously this customObj value is a field with the corresponding class, defined in the report itself.

Then you'll need to add a Comparator as a parameter, for example:

parameters.put("OVERRIDE_Comparator", new Comparator<CustomObj>() {
    public int compare(CustomObj c1, CustomObj c2) {
        //create your custom compare logic over here, this code works as if no custom Comparator is used
        return c1.compareTo(c2);
    }
});

Now add such a OVERRIDE_Comparator parameter in the jasperreport, using the java.util.Comparator Parameter Class.

Final step: Put $P{OVERRIDE_Comparator} as the Comparator Expression in the row/column group you needed.

When compiling such a report, the most likely compile error would be casting issues. Jasperreports defaults to java.lang.String, you might need to edit the xml of the report manually to get the correct class at each step.

(I found out this method from some asian site, thankfully the code itself was readable! :-) )



回答2:

There may be much easier and straight forward solutions:

I am using Jasper Reports Studio (Eclipse Plugin).

  1. did not finally work for me :-( ... (see 1. comment below my answer - may be a bug) you just can check [x] Data Pre Sorted in the Crosstab Properties. Of course this only works if you do not need another ordering somewhere else in the report based on the pre-sorted result set.

  2. using an invisible crosstab group header, which is rather tricky:

    1. create a new row/column group (e.g. via Outline view), leave the initial name Row Group1 or similar as is for now
    2. set its Total Position to None (we do not want to have a total column per row/column generated)
    3. move the group in the XML before the old one
    4. rename the group to some speaking name, e.g. name="invisible sort column ..."
      • (no further references to this group should exist in the XML anymore)
    5. if you are using group totals on your group (Total Position != None) then you have to basically move these totalling elements/settings to the first dummy sort group, because otherwise the totals will be no totals anymore (= per 2nd group totals) displayed after each group column/row, e.g. (here only shown with column group, but row group follows the same principle)

      a|b|c|sum      a|sum|b|sum|c|sum
      =========  =>  =================
      1|2|3|6        1|1  |2|2  |3|3
      
      • easiest may be to do this in the XML similar to this transformation (do not forget to move the totalPosition=... and columnTotalGroup=... attributes and to change the sum if applicable to your scenario $V{SomeSum_..._ALL}):

        ...
        <columnGroup name="OrderXDummy" height="0">
          ...
          <crosstabTotalColumnHeader>
            <cellContents/>
          </crosstabTotalColumnHeader>
        </columnGroup>
        ...
        <columnGroup name="X" ... totalPosition="End">
          ...
          <crosstabTotalColumnHeader>
            <cellContents ...>
              ...
            </cellContents>
          </crosstabTotalColumnHeader>
        </columnGroup>
        ...
        <crosstabCell ... columnTotalGroup="X">
          ...
           <textFieldExpression><![CDATA[$V{SomeSum_X_ALL}]]></textFieldExpression>
          ...
        </crosstabCell>
        

        =>

        ...
        <columnGroup name="OrderXDummy" height="0" totalPosition="End">
          ...
          <crosstabTotalColumnHeader>
            <cellContents ...>
              ...
            </cellContents>
          </crosstabTotalColumnHeader>
        </columnGroup>
        ...
        <columnGroup name="X" ... >
          ...
          <crosstabTotalColumnHeader>
            <cellContents/>
          </crosstabTotalColumnHeader>
          ...
        </columnGroup>
        ...
        <crosstabCell ... columnTotalGroup="OrderXDummy">
          ...
           <textFieldExpression><![CDATA[$V{SomeSum_OrderXDummy_ALL}]]></textFieldExpression>
          ...
        </crosstabCell>
        
    6. (may be skipped:) remove unnecessarily generated <crosstabCell ... column/rowTotalGroup="..."> cells

      • its maybe best to compare the report with a previous version to reliably and fast identify these spots in the XML
      • maybe this is not a crucial step, but looking at the existing XML is confusing enough ;-)
    7. add the (bucket) expression of your sort column, e.g. $F{ORDER_FOR_X}
      • don't forget to assign Value Class Name to java.lang.Integer or whatever fits for your values here (have a look at your dataset which type is assigned there if your are using it via some column)
    8. add some variable expression to the Order By Expression of the original group, e.g. $V{ORDER_FOR_X}

      • $V{...} is the trick, do not use $F{...}!
      • (the editor says it's invalid, but it will work)
      • meaning if you can provide some field that defines the sort and relates to your to-be-sorted column value, e.g. (Oracle SQL)

        select            1 as order_for_x,  'foo' as x,  'bla blu' as y  from dual
        union all select  2,                 'bar',       'ta tu'         from dual
        union all select  2,                 'bar',       'na na'         from dual
        union all select  1,                 'foo',       'check it'      from dual
        union all select  3,                 'queue',     'sap'           from dual
        
      • otherwise you can of course use something else here as well

      • if you should get some

        ...
        Caused by: java.lang.NullPointerException
        at org.apache.commons.collections.comparators.ComparableComparator.compare(ComparableComparator.java:92)
        at net.sf.jasperreports.crosstabs.fill.BucketExpressionOrderer.compareOrderValues(BucketExpressionOrderer.java:70)
        ...
        

        you can simply change the expression to $V{ORDER_FOR_X} == null ? 0 : $V{ORDER_FOR_X} which should do the trick

    9. set all high/width fields of the dummy group to 0 and the text fields Print When Expression to false

    10. (maybe check via compare with the previous report version that nothing else has changed to make sure you didn't mess up something else)
  3. using an order by based on measure totals (as described in the bottom link)

  4. using a custom Java Comparator class (as described in the answer from Pieter VN 2011-11-16)

further maybe helpful links I found:

  • http://community.jaspersoft.com/wiki/custom-crosstab-ordering-ireport-designer
  • http://community.jaspersoft.com/questions/506284/combined-crosstabs
  • http://jasperreports.sourceforge.net/schema.reference.html


回答3:

orderByExpression

The way to order crosstab columns normally is by using the orderByExpression

  1. Define a measure for what you like to order on (another field or and expression, use correct class definition in my case a Integer)
<measure name="orderByField_measure" class="java.lang.Integer">
     <measureExpression><![CDATA[$F{orderByField}]]></measureExpression>
</measure>
  1. Use the measure in orderByExpression in the bucket
<columnGroup name="myColumnGroup" height="10">
      <bucket class="java.lang.String">
        <bucketExpression><![CDATA[$F{MyField}]]></bucketExpression>
        <orderByExpression><![CDATA[$V{orderByField_measure}]]></orderByExpression>
    </bucket>
    .....
</columnGroup>

comparatorExpression

Set a Comparator (create a class in java es. MyCustomComparator that implements Comparator)

<bucket class="java.lang.String">
    <bucketExpression><![CDATA[$F{MyField}]]></bucketExpression>
    <comparatorExpression><![CDATA[new com.my.package.MyCustomComparator()]]></comparatorExpression>
</bucket>

Comparable

If you are displaying value from own object ($F{MyField} is a user defined object) you can simple implement Comparable to display the order as you like.