Sunday, February 10, 2013

ZK Pivottable: Compare Pivotmodel Structure


Introduction

This article describe how to check whether two pivot model has the same structure (opened view or whole model).

Def: "The same structure" denotes they have the same row fields, the same column fields, the same amount of rows, the same amount of columns, the same calculators.

Pre-request

Display Data in Pivottable
http://ben-bai.blogspot.tw/2012/07/zk-pivottable-display-data-in-zk.html

The Program

index.zul

There are two pivottables, in the begining, they are structure-equivalent, click buttons to add something to second pivottable or open/close nodes to see the changed result.

<zk>
    <!-- Tested with ZK 6.0.2 EE and ZK Pivottable 2.0.0 -->
    <!-- window, apply a SelectorComposer -->
    <window id="win" xmlns:w="client"
        apply="test.PVTCompareStructureComposer">
        <vlayout>
            <!-- pivottable, get model from window's composer -->
            <button id="addRowBtn" label="add RowThree - 3 to second pivottable" />
            <button id="addCalBtn" label="add Min calculator to RowOne of second pivottable" />
            <button id="compareBtn" label="show compare result" />

            <pivottable id="pivottable" model="${win$composer.pivotModel}" />
            <pivottable id="pivottableTwo" model="${win$composer.pivotModelTwo}" />

             the current view of the two models are structure-equal? 
            <label id="lbOne" value="" />
             the whole structure of the two models are structure-equal? 
            <label id="lbTwo" value="" />
        </vlayout>
    </window>
</zk>


PVTCompareStructureComposer.java

package test;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Random;

import org.zkoss.pivot.Calculator;
import org.zkoss.pivot.PivotField;
import org.zkoss.pivot.PivotHeaderNode;
import org.zkoss.pivot.PivotHeaderTree;
import org.zkoss.pivot.Pivottable;
import org.zkoss.pivot.impl.StandardCalculator;
import org.zkoss.pivot.impl.TabularPivotField;
import org.zkoss.pivot.impl.TabularPivotModel;

import org.zkoss.zk.ui.select.SelectorComposer;
import org.zkoss.zk.ui.select.annotation.Listen;
import org.zkoss.zk.ui.select.annotation.Wire;
import org.zkoss.zul.Label;

/**
 * Tested with ZK 6.0.2 EE and ZK Pivottable 2.0.0
 *
 */
@SuppressWarnings("rawtypes")
public class PVTCompareStructureComposer extends SelectorComposer {
    /**
     * generated serial version UID
     */
    private static final long serialVersionUID = -2897873399288955635L;

    @Wire
    private Pivottable pivottable;
    @Wire
    private Pivottable pivottableTwo;
    @Wire
    private Label lbOne;
    @Wire
    private Label lbTwo;
    private TabularPivotModel _pivotModel;
    private TabularPivotModel _pivotModelTwo;

    public TabularPivotModel getPivotModel () throws Exception {
        if (_pivotModel == null) {
            _pivotModel = new TabularPivotModel(getData(), getColumns());

            // assign rows, the order matches to the level of row node field
            _pivotModel.setFieldType("RowOne", PivotField.Type.ROW);
            _pivotModel.setFieldType("RowTwo", PivotField.Type.ROW);
            _pivotModel.setFieldType("RowThree", PivotField.Type.ROW);

            // assign columns, the order matches to the level of column node field
            _pivotModel.setFieldType("ColumnOne", PivotField.Type.COLUMN);
            _pivotModel.setFieldType("ColumnTwo", PivotField.Type.COLUMN);

            // assign datas, the order matches to the order of data field
            _pivotModel.setFieldType("DataOne", PivotField.Type.DATA);

            PivotField field = _pivotModel.getField("RowOne");
            _pivotModel.setFieldSubtotals(field, new Calculator[] {StandardCalculator.SUM, StandardCalculator.MAX});

        }
        return _pivotModel;
    }
    public TabularPivotModel getPivotModelTwo () throws Exception {
        if (_pivotModelTwo == null) {
            _pivotModelTwo = new TabularPivotModel(getData(), getColumns());

            // assign rows, the order matches to the level of row node field
            _pivotModelTwo.setFieldType("RowOne", PivotField.Type.ROW);
            _pivotModelTwo.setFieldType("RowTwo", PivotField.Type.ROW);
            _pivotModelTwo.setFieldType("RowThree", PivotField.Type.ROW);

            // assign columns, the order matches to the level of column node field
            _pivotModelTwo.setFieldType("ColumnOne", PivotField.Type.COLUMN);
            _pivotModelTwo.setFieldType("ColumnTwo", PivotField.Type.COLUMN);

            // assign datas, the order matches to the order of data field
            _pivotModelTwo.setFieldType("DataOne", PivotField.Type.DATA);

            PivotField field = _pivotModelTwo.getField("RowOne");
            _pivotModelTwo.setFieldSubtotals(field, new Calculator[] {StandardCalculator.SUM, StandardCalculator.MAX});
        }
        return _pivotModelTwo;
    }

    /**
     * prepare the data for pivottable's model
     * The order of object put into data list should match
     * the order of column name's
     * @return
     * @throws Exception
     */
    public List<List<Object>> getData() throws Exception {
        List<List<Object>> result = new ArrayList<List<Object>>();
        Random r = new Random();

        for (int i = 0; i < 100; i++) {
            List<Object> data = new ArrayList<Object>();
            data.add("RowOne - " + (r.nextInt(2) + 1));
            data.add("RowTwo - " + (r.nextInt(2) + 1));
            data.add("RowThree - " + (r.nextInt(2) + 1));
            data.add("ColumnOne - " + (r.nextInt(2) + 1));
            data.add("ColumnTwo - " + (r.nextInt(2) + 1));
            data.add(r.nextInt(10));
            result.add(data);
        }
        return result;
    }

    /**
     * prepare columns name for pivottable's model
     * @return
     */
    public List<String> getColumns() {
        return Arrays.asList(new String[]{
                "RowOne", "RowTwo", "RowThree",
                "ColumnOne", "ColumnTwo",
                "DataOne"
        });
    }

    @Listen ("onClick = #addRowBtn")
    public void addRow () {
        Random r = new Random();
        List<Object> data = new ArrayList<Object>();
        data.add("RowOne - 1");
        data.add("RowTwo - 1");
        data.add("RowThree - 3");
        data.add("ColumnOne - 1");
        data.add("ColumnTwo - 1");
        data.add(r.nextInt(10));
        ((List<List<Object>>)_pivotModelTwo.getRawData()).add(data);
        _pivotModelTwo.updateRawData();
        pivottableTwo.setModel(null);
        pivottableTwo.setModel(_pivotModelTwo);
    }

    @Listen ("onClick = #addCalBtn")
    public void addCalculator () {
        PivotField field = _pivotModelTwo.getField("RowOne");
        _pivotModelTwo.setFieldSubtotals(field, new Calculator[] {StandardCalculator.SUM, StandardCalculator.MAX, StandardCalculator.MIN});
    }

    @Listen ("onClick = #compareBtn")
    public void doCompare () {
        boolean isEqual = isStructureEqual(_pivotModel, _pivotModelTwo, true);
        lbOne.setValue(isEqual + "");
        lbOne.setStyle("font-size: 18px;"+(isEqual? "color: green;" : "color: red;"));
        isEqual = isStructureEqual(_pivotModel, _pivotModelTwo, false);
        lbTwo.setValue(isEqual + "");
        lbTwo.setStyle("font-size: 18px;"+(isEqual? "color: green;" : "color: red;"));
    }

    /**
     * Check whether two pivot models are structure-equal
     * @param modelOne the first pivot model
     * @param modelTwo the second pivot model
     * @param openedOnly whether only check the opened node and leaf
     * @return boolean
     */
    private boolean isStructureEqual (TabularPivotModel modelOne, TabularPivotModel modelTwo, boolean openedOnly) {
        boolean equal = true;

        List<PivotHeaderNode> rows = getNodeList(modelOne.getRowHeaderTree(), openedOnly);
        List<PivotHeaderNode> columns = getNodeList(modelOne.getColumnHeaderTree(), openedOnly);
        List<PivotHeaderNode> rowsTwo = getNodeList(modelTwo.getRowHeaderTree(), openedOnly);
        List<PivotHeaderNode> columnsTwo = getNodeList(modelTwo.getColumnHeaderTree(), openedOnly);
        TabularPivotField[] dataFields = modelOne.getDataFields();
        TabularPivotField[] dataFieldsTwo = modelTwo.getDataFields();

        if (rows.size() != rowsTwo.size()
            || columns.size() != columnsTwo.size()
            || dataFields.length != dataFieldsTwo.length) {
            equal = false;
        } else {
            equal = compareNodeList(rows, rowsTwo, openedOnly) && compareNodeList(columns, columnsTwo, openedOnly);
        }
        return equal;
    }
    /**
     * Get pivot nodes in a pivot header tree
     * @param headerTree the pivot header tree to get pivot nodes
     * @param openedOnly whether get only the opened nodes and leaf
     * @return
     */
    @SuppressWarnings("unchecked")
    public List<PivotHeaderNode> getNodeList (PivotHeaderTree headerTree, boolean openedOnly) {
        PivotHeaderNode root = headerTree.getRoot();
        List<PivotHeaderNode> all = new ArrayList<PivotHeaderNode>();
        List<PivotHeaderNode> nodes = new ArrayList<PivotHeaderNode>();
        List<PivotHeaderNode> tmp = new ArrayList<PivotHeaderNode>();
        nodes = (List<PivotHeaderNode>)root.getChildren();

        // all: all target nodes
        // nodes: the node list to loop through
        // tmp: temp store the children while loop through nodes
        boolean foundAllNodes = false;
        while (!foundAllNodes) {
            foundAllNodes = true;
            for (PivotHeaderNode phn : nodes) {
                // get only onened and leaf nodes
                // if opened only
                if (phn.isOpen() || !openedOnly) {
                    List<PivotHeaderNode> children = (List<PivotHeaderNode>)phn.getChildren();
                    if (children != null && children.size() > 0) {
                        tmp.addAll(children);
                        foundAllNodes = false;
                    } else {
                        tmp.add(phn);
                    }
                } else {
                    tmp.add(phn);
                }
            }
            all.addAll(nodes);
            nodes = tmp;
            tmp = new ArrayList<PivotHeaderNode>();
        }
        return all;
    }

    /**
     * Compare the nodes under two node lists one by one
     * @param list the first node list
     * @param listTwo the second node list
     * @param openedOnly whether compare calculator according to open status
     * @return
     */
    private boolean compareNodeList (List<PivotHeaderNode> list, List<PivotHeaderNode> listTwo, boolean openedOnly) {
        boolean equal = true;
        int i, j;
        // compare nodes
        for (i = 0; i < list.size() && equal; i++) {
            PivotHeaderNode node = list.get(i);
            PivotHeaderNode nodeTwo = listTwo.get(i);

            // key should be equal
            // depth should be equal
            // subtotal count should be equal
            //
            // openedOnly: get subtotal count according to the open stats of node
            // !openedOnly: get subtotal count as the node is opened
            if (!node.getKey().equals(nodeTwo.getKey())
                || node.getDepth() != nodeTwo.getDepth()
                || node.getSubtotalCount(node.isOpen() || !openedOnly) != nodeTwo.getSubtotalCount(nodeTwo.isOpen() || !openedOnly)) {
                equal = false;
                break;
            }

            // check calculators if any
            // openedOnly: get subtotal count according to the open stats of node
            // !openedOnly: get subtotal count as the node is opened
            if (node.getSubtotalCount(node.isOpen() || !openedOnly) > 0) {
                Calculator[] cals = node.getField().getSubtotals();
                Calculator[] calsTwo = nodeTwo.getField().getSubtotals();
                for (j = 0; j < cals.length; j++) {
                    Calculator cal = cals[j];
                    Calculator calTwo = calsTwo[j];

                    // label and label key should be euqal
                    if (!cal.getLabel().equals(calTwo.getLabel())
                        || !cal.getLabelKey().equals(calTwo.getLabelKey())) {
                        equal = false;
                        break;
                    }
                }
            }
        }
        return equal;
    }
}


The Result

View demo on line
http://screencast.com/t/z6v4RAvkcv

Reference

The start point of a bunch of APIs
http://www.zkoss.org/javadoc/latest/zkpvt/org/zkoss/pivot/impl/TabularPivotModel.html


Download

Full project at github
https://github.com/benbai123/ZK_Practice/tree/master/Components/projects/Addon_Practice/PivottableTest/CompareModelStructure

Demo flash
https://github.com/benbai123/ZK_Practice/blob/master/Components/demos/addon/Compare_Pivotmodel_Structure.swf

No comments:

Post a Comment