# Perspective Report Crosstab Column Sorting

**URL:** <https://forum.inductiveautomation.com/t/perspective-report-crosstab-column-sorting/117663>\
**Category:** Ignition\
**Tags:** perspective, reporting\
**Created:** [October 29, 2026, 2:32pm UTC](https://forum.inductiveautomation.com/t/perspective-report-crosstab-column-sorting/117663 "2026-10-29T14:32:24Z")\
**Posts on this page:** 1\
**Showing post:** 5

<div class="post-metadata">

**Author:** ![paul-griffith](https://sea1.discourse-cdn.com/inductiveautomation/user_avatar/forum.inductiveautomation.com/paul-griffith/32/37249_2.png) [@paul-griffith](https://forum.inductiveautomation.com/u/paul-griffith)\
**Post date:** [October 29, 2026, 10:26pm UTC](https://forum.inductiveautomation.com/t/perspective-report-crosstab-column-sorting/117663/5 "2026-10-29T22:26:07Z")

</div>

It's not possible\[1\] to set explicit sorting for cross tabs. I'm really surprised no one has noticed this before.

That said, along the lines of [previous nerd sniping](https://forum.inductiveautomation.com/t/pdf-report-linking/18860/2), it is at least _technically_ possible. I would not advise anyone actually do this.

> ****
>
> Given crosstab data like this:
> 
> ```python
> # Faux row-level data for a Reporting Cross Tab.
> headers = ['Region', 'Product', 'Quarter', 'Sales', 'Units']
> rows = [
> ['North', 'Widgets', 'Q1', 12500.00, 118],
> ['North', 'Widgets', 'Q2', 14750.00, 136],
> ['North', 'Widgets', 'Q3', 13925.00, 129],
> ['North', 'Widgets', 'Q4', 16200.00, 151],
> ['North', 'Gadgets', 'Q1', 9800.00, 92],
> ['North', 'Gadgets', 'Q2', 11125.00, 105],
> ['North', 'Gadgets', 'Q3', 10875.00, 101],
> ['North', 'Gadgets', 'Q4', 12400.00, 116],
> ['South', 'Widgets', 'Q1', 10350.00, 99],
> ['South', 'Widgets', 'Q2', 11900.00, 112],
> ['South', 'Widgets', 'Q3', 13250.00, 126],
> ['South', 'Widgets', 'Q4', 14100.00, 134],
> ['South', 'Gadgets', 'Q1', 8700.00, 83],
> ['South', 'Gadgets', 'Q2', 9400.00, 89],
> ['South', 'Gadgets', 'Q3', 10150.00, 96],
> ['South', 'Gadgets', 'Q4', 11325.00, 108],
> ['East', 'Widgets', 'Q1', 15100.00, 143],
> ['East', 'Widgets', 'Q2', 16850.00, 159],
> ['East', 'Widgets', 'Q3', 17400.00, 165],
> ['East', 'Widgets', 'Q4', 18950.00, 180],
> ['East', 'Gadgets', 'Q1', 10725.00, 102],
> ['East', 'Gadgets', 'Q2', 12100.00, 114],
> ['East', 'Gadgets', 'Q3', 11650.00, 110],
> ['East', 'Gadgets', 'Q4', 13575.00, 128],
> ['West', 'Widgets', 'Q1', 9300.00, 88],
> ['West', 'Widgets', 'Q2', 10450.00, 99],
> ['West', 'Widgets', 'Q3', 11225.00, 106],
> ['West', 'Widgets', 'Q4', 12750.00, 121],
> ['West', 'Gadgets', 'Q1', 7600.00, 73],
> ['West', 'Gadgets', 'Q2', 8350.00, 80],
> ['West', 'Gadgets', 'Q3', 9100.00, 87],
> ['West', 'Gadgets', 'Q4', 9950.00, 95]
> ]
> 
> data['crossTabData'] = system.dataset.toDataset(headers, rows)
> 
> ```
> 
> And a script like this in the script console:
> 
> ```python
> # grouping key -> list of (sort key, "ascend" | "descend")
> SORTS = {
> "Quarter": [("total.Sales", "descend")],
> "Product": [("Product", "ascend")],
> }
> 
> from java.awt import Toolkit
> from java.awt.datatransfer import DataFlavor, Transferable, UnsupportedFlavorException
> from java.io import ByteArrayInputStream
> from java.lang import String as JString
> import xml.etree.ElementTree as ET
> 
> RM_FLAVOR = DataFlavor("application/reportmill", "ReportMill Shape Data")
> 
> class ShapeTransferable(Transferable):
> def __init__ (self, xmlString):
> self.bytes = JString(xmlString).getBytes("UTF-8")
> 
> def getTransferDataFlavors(self):
> return [RM_FLAVOR, DataFlavor.stringFlavor]
> 
> def isDataFlavorSupported(self, flavor):
> return flavor in self.getTransferDataFlavors()
> 
> def getTransferData(self, flavor):
> if flavor.equals(RM_FLAVOR):
> return ByteArrayInputStream(self.bytes)
> if flavor.equals(DataFlavor.stringFlavor):
> return JString(self.bytes, "UTF-8")
> raise UnsupportedFlavorException(flavor)
> 
> clipboard = Toolkit.getDefaultToolkit().getSystemClipboard()
> root = ET.fromstring(clipboard.getData(DataFlavor.stringFlavor).encode("utf-8"))
> 
> for grouping in root.iter("grouping"):
> sorts = SORTS.get(grouping.get("key"))
> if sorts is None:
> continue
> for existing in grouping.findall("sort"):
> grouping.remove(existing)
> for key, order in sorts:
> attrs = {"key": key}
> if order == "descend":
> attrs["order"] = "descend"
> ET.SubElement(grouping, "sort", attrs)
> print "Set sorts on grouping %r: %s" % (grouping.get("key"), sorts)
> 
> clipboard.setContents(ShapeTransferable(ET.tostring(root)), None)
> 
> ```
> 
> If you:
> 
> 1. Create your own crosstab
> 2. Update the script in the console with appropriate keys and sorting decisions
> 3. Copy the cross tab element directly in the report designer
> 4. Execute the code in the script console
> 5. Paste the updated crosstab back in to the report
> 
> You will, in fact, get a "sorted" crosstab:
> 
> ![Screenshot 2026-10-29 at 3.25.30 PM](https://us1.discourse-cdn.com/inductiveautomation/original/3X/8/7/8783924918bba66e5a5ad809e7c282b1a26867ec.png)

* * *

1. or at least not practical

---

_[View the full topic](https://forum.inductiveautomation.com/t/perspective-report-crosstab-column-sorting/117663)._
