Stephen Wicklund
Stephen Wicklund

Reputation: 71

How to sort a TreeList with a different comparator for each column in a NatTable

I'm using NatTable to display a tree with multiple columns. The tree is flattened into a SortedList which is used to create TreeList.

EventList<Person> eventList = GlazedLists.eventList(perfStats.getFlattenedTree());
TransformedList<Person, Person> rowObjectsGlazedList = GlazedLists.threadSafeList(eventList);
SortedList<Person> sortedList = new SortedList<(rowObjectsGlazedList, null);
TreeList treeList = new TreeList(sortedList, treeFormat, TreeList.nodesStartCollapsed());

This works to display the tree. However, now my issue is how do I sort this properly?

The desired outcome would be for the roots to be properly sorted, then the children inside properly sorted independently and so on.

Right now, I'm using the GlazedListsSortModel and it sorts the flattened tree then builds the display from that which does not work.

Any help or just pointing me in the right direction would be appreciated!

Upvotes: 0

Views: 783

Answers (2)

floh.mueller
floh.mueller

Reputation: 83

I know this question is ooooold, but I just solved the problem for a TreeList I was working on...

I used a TreeList.Format which doubles as an ISortModel for my SortHeaderLayer.

The Comparator is directly applied to the SortedList while ISortModel's getComparatorsForColumnIndex(int) and getColumnComparator(int) may return null.
The Comparator always takes the whole path to a TreeNode into account and never returns 0 unless the TreeNodes are truly identical.

Setting SortDirectionEnum.NONE in ISortModel's sort(int, SortDirectionEnum, boolean) triggers a fallback to ascending order of column 0.

private class MyTreeFormat implements TreeList.Format<TreeNode>, ISortModel {

    private final IColumnPropertyAccessor<TreeNode> columnPropertyAccessor;

    public MyTreeFormat(final IColumnPropertyAccessor<TreeNode> columnPropertyAccessor) {
        this.columnPropertyAccessor = columnPropertyAccessor;
    }

    // never return 0 unless node1 == node2, else the list will be in arbitrary order and mess up the tree
    private final Comparator<TreeNode> comparator = new Comparator<TreeNode>() {
        @Override
        public int compare(final TreeNode node1, final TreeNode node2) {
            if (node1 == node2) return 0;

            // paths from root to node 1&2
            final Iterator<TreeNode> piter1 = node1.getPath().iterator();
            final Iterator<TreeNode> piter2 = node2.getPath().iterator();

            while (piter1.hasNext() && piter2.hasNext()) {
                final int result = doCompare(piter1.next(), piter2.next());
                if (result != 0) return result;
            }

            return 0;
        }

        private int doCompare(final TreeNode node1, final TreeNode node2) {
            if (node1 == node2) return 0;

            int result;
            if (sortedColumnIndex == 0) {
                result = SpecialComparatorForColumn0.getInstance().compare(node1.getId(), node2.getId());
            } else {
                final Object data1 = columnPropertyAccessor.getDataValue(node1, sortedColumnIndex);
                final Object data2 = columnPropertyAccessor.getDataValue(node2, sortedColumnIndex);
                final String s1, s2;
                
                // we have some Date columns while all other columns are Strings:
                if (data1 instanceof Date) {
                    s1 = MY_SIMPLE_DATE_FORMAT.format((Date) data1);
                    s2 = MY_SIMPLE_DATE_FORMAT.format((Date) data2);
                } else {
                    s1 = String.valueOf(data1);
                    s2 = String.valueOf(data2);
                }
                
                result = s1.compareToIgnoreCase(s2);
                if (result == 0) result = SpecialComparatorForColumn0.getInstance().compare(node1.getId(), node2.getId()); // ensures same order for equal values in sort column !!
            }
            return result * (sortDirection == SortDirectionEnum.ASC ? 1 : -1);
        }

    };

    @Override
    public void getPath(final List<TreeNode> path, final TreeNode element) {
        for (final TreeNode TreeNode : element.getPath()) {
            path.add(TreeNode);
        }
    }

    /**
     * Simply always return <code>true</code>.
     *
     * @return <code>true</code> if this element can have child elements, or
     *         <code>false</code> if it is always a leaf node.
     */
    @Override
    public boolean allowsChildren(final TreeNode element) {
        return true;
    }

    /**
     * Returns the comparator used to order path elements of the specified
     * depth. If enforcing order at this level is not intended, this method
     * should return <code>null</code>. We do a simple sorting of the last
     * names of the persons to show so the tree nodes are sorted in
     * alphabetical order.
     */
    @Override
    public Comparator<? super TreeNode> getComparator(final int depth) {
        return comparator;
    }

    /* ---------- ISortModel implementation START ---------- 
     * ISortModel for the SortHeaderLayer
     * sort only allowed by a single column at a time
     * key was to set the sorted list's comparator on sort(...) so the whole tree is reordered on a click on the column header
    */
    private int sortedColumnIndex = 0;
    private int sortedColumnIndexOld = 0;
    private SortDirectionEnum sortDirection = SortDirectionEnum.DESC;

    @Override
    public List<Integer> getSortedColumnIndexes() {
        return Collections.singletonList(sortedColumnIndex);
    }

    @Override
    public boolean isColumnIndexSorted(final int columnIndex) {
        return sortedColumnIndex == columnIndex;
    }

    @Override
    public SortDirectionEnum getSortDirection(final int columnIndex) {
        if (columnIndex == sortedColumnIndexOld) return sortDirection;
        else {
            sortedColumnIndexOld = columnIndex;
            return sortDirection = SortDirectionEnum.NONE;
        }
    }

    @Override
    public int getSortOrder(final int columnIndex) {
        return 0;
    }

    @SuppressWarnings("rawtypes")
    @Override
    public List<Comparator> getComparatorsForColumnIndex(final int columnIndex) {
        return null;
    }

    @Override
    public Comparator<?> getColumnComparator(final int columnIndex) {
        return null;
    }

    @Override
    public void sort(final int columnIndex, final SortDirectionEnum sortDirection, final boolean accumulate) {
        if (sortDirection == SortDirectionEnum.NONE) {
            this.sortDirection = SortDirectionEnum.ASC;
            this.sortedColumnIndex = 0;
        } else {
            this.sortDirection = sortDirection;
            this.sortedColumnIndex = columnIndex;
        }
        bodyLayerStack.getSortedList().setComparator(comparator);
    }

    @Override
    public void clear() {
        sort(0, SortDirectionEnum.ASC, false);
    }

}

Upvotes: 0

Dirk Fauth
Dirk Fauth

Reputation: 4231

When using a TreeList you pass in a Comparator for the tree structure via TreeList#Format. This is needed to ensure that the tree is created properly, as it is derivated from a List. So even if there is a sorting applied via SortedList, the Comparator of the TreeList#Format will win in the end.

To solve your requirement you therefore need to implement a TreeList#Format that takes the column sorting into account. This can be done by using the NatTable SortableTreeComparator for example. You can have a look at our TreeGridExample to get an idea of how this could look like.

A more extended version can be seen in the GroupByComparator that is used to support sorting by column with the GroupBy feature.

Upvotes: 0

Related Questions