Introduction
This article describe how to use the value of row or column field as filter to filter raw data.
Pre-request
Pass Event to Other Component
http://ben-bai.blogspot.tw/2012/12/pass-event-to-other-component.html
ZK Pivottable: Display Data in ZK Pivottable
http://ben-bai.blogspot.tw/2012/07/zk-pivottable-display-data-in-zk.html
ZK Pivottable: Sync the Open Status of Pivotmodel
http://ben-bai.blogspot.tw/2013/02/zk-pivottable-sync-open-status-of.html
ZK Pivottable: Sync Structure of Pivot Model
http://ben-bai.blogspot.tw/2013/02/zk-pivottable-sync-structure-of-pivot.html
ZK Pivottable: Get Distinct Values of Field
http://ben-bai.blogspot.tw/2013/03/zk-pivottable-get-distinct-values-of.html
The Program
index.zul
A pivottable and a filter
<zk>
<!-- Tested with ZK 6.0.1 CE and ZK Pivottable 2.0.0 -->
<!-- window, apply a SelectorComposer -->
<window id="win" xmlns:w="client"
apply="test.TestComposer">
<!-- pivottable, get model from window's composer -->
<pivottable id="pivottable" model="${win$composer.pivotModel}" />
<div style="margin-top: 10px;">
Filter info:
<label id="lb" />
</div>
<!-- filter list -->
<div id="filter" />
</window>
</zk>
TestComposer.java
Provide model, listen to onPivotPopup to update filter list, listen to onFilterChanged to update pivottable with filtered data.
package test;
import java.util.List;
import org.zkoss.pivot.Pivottable;
import org.zkoss.pivot.event.PivotUIEvent;
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.Div;
import org.zkoss.zul.Label;
/**
* Tested with ZK 6.0.2 EE and ZK Pivottable 2.0.0
*
* @author benbai123
*/
@SuppressWarnings("rawtypes")
public class TestComposer extends SelectorComposer {
private static final long serialVersionUID = -8249566421884806620L;
@Wire
Pivottable pivottable;
@Wire
Div filter; // div that will contain filter list
@Wire
Label lb; // filter info
// pivot model with the 'whole' raw data
private TabularPivotModel _pivotModel;
// model provider, provide the columns, raw data and pivot model
private PivotModelProvider _modelProvider = new PivotModelProvider();
// handler to do the works of filter
// NOTE: Renew it if changed to a complete different model
private FilterHandler _filterHandler = new FilterHandler();
/**
* Get pivottable's model
* @return TabularPivotModel the pivottable's model
* @throws Exception
*/
public TabularPivotModel getPivotModel () throws Exception {
if (_pivotModel == null) {
_pivotModel = _modelProvider.getPivotModel();
}
return PVTUtils.cloneModelWithData(_pivotModel, getFilteredData());
}
/**
* update the selected field for filter
* @param e
*/
@Listen("onPivotPopup = #pivottable")
public void updateFilterIndex (PivotUIEvent e) {
if (e.getRowContext() != null
&& e.getColumnContext() == null) {
// clicked on row field
updateFilter(_pivotModel.getRowFields()[e.getRowContext().getNode().getDepth()-1]);
} else if (e.getRowContext() == null
&& e.getColumnContext() != null) {
// clicked on column field
updateFilter(_pivotModel.getColumnFields()[e.getColumnContext().getNode().getDepth()-1]);
}
}
/**
* called while filter is changed
* update filter value of selected field
* @param event
*/
@Listen("onFilterChanged = #pivottable")
public void updateLimit (FilterChangedEvent event) {
_filterHandler.updateLimit(event.getFieldName(), event.getValue(), event.isChecked());
updatePivottable();
}
/**
* update the filter list
* @param field the field to update filter list
*/
private void updateFilter (TabularPivotField field) {
_filterHandler.updateFilter(pivottable,
field,
_modelProvider.getColumns(),
getRawData(),
filter);
// update field info label
lb.setValue("field type: " + field.getType() + ", field name: " + field.getFieldName());
}
/**
* update pivottable with filtered pivot model
*/
private void updatePivottable () {
// store current structure at first
PVTUtils.syncModelStructure((TabularPivotModel)pivottable.getModel(), _pivotModel);
TabularPivotModel filteredModel = PVTUtils.cloneModelWithData(_pivotModel, getFilteredData());
pivottable.setModel(filteredModel);
}
/**
* get the filtered data
* @return
*/
private Iterable<List<Object>> getFilteredData () {
return _filterHandler.filterData(_pivotModel, _modelProvider.getColumns(), getRawData());
}
/**
* get complete raw data
* @return
*/
@SuppressWarnings("unchecked")
private Iterable<List<Object>> getRawData () {
return (Iterable<List<Object>>)_pivotModel.getRawData();
}
}
PivotModelProvider.java
Provide base model, raw data and columns.
package test;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Random;
import org.zkoss.pivot.PivotField;
import org.zkoss.pivot.impl.TabularPivotModel;
public class PivotModelProvider {
// pivot model with the 'whole' raw data
private TabularPivotModel _pivotModel;
/**
* Get pivottable's model
* @return TabularPivotModel the pivottable's model
* @throws Exception
*/
public TabularPivotModel getPivotModel () {
if (_pivotModel == null) {
_pivotModel = new TabularPivotModel(getData(), getColumns());
// assign rows, the order matches to the level of row node field
_pivotModel.setFieldType("Row_01", PivotField.Type.ROW);
_pivotModel.setFieldType("Row_02", PivotField.Type.ROW);
_pivotModel.setFieldType("Row_03", PivotField.Type.ROW);
// assign columns, the order matches to the level of column node field
_pivotModel.setFieldType("Column_01", PivotField.Type.COLUMN);
_pivotModel.setFieldType("Column_02", PivotField.Type.COLUMN);
// assign datas, the order matches to the order of data field
_pivotModel.setFieldType("Data_01", PivotField.Type.DATA);
_pivotModel.setFieldType("Data_02", PivotField.Type.DATA);
_pivotModel.setFieldType("Data_03", PivotField.Type.DATA);
}
return _pivotModel;
}
/**
* prepare columns name for pivottable's model
* @return
*/
public List<String> getColumns() {
return Arrays.asList(new String[]{
"Row_01", "Row_02", "Row_03",
"Column_01", "Column_02",
"Data_01", "Data_02", "Data_03"
});
}
/**
* prepare the data for pivottable's model
* The order of object put into data list matches
* the order of column name's order
* @return
* @throws Exception
*/
private List<List<Object>> getData() {
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("Row_01 - " + (r.nextInt(5) + 1));
data.add("Row_02 - " + (r.nextInt(5) + 1));
data.add("Row_03 - " + (r.nextInt(5) + 1));
data.add("Column_01 - " + (r.nextInt(5) + 1));
data.add("Column_02 - " + (r.nextInt(5) + 1));
data.add(r.nextInt(10000));
data.add(r.nextDouble() * 10000.0);
data.add(r.nextInt(100));
result.add(data);
}
return result;
}
}
FilterHandler.java
Handle filter task
package test;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import org.zkoss.pivot.Pivottable;
import org.zkoss.pivot.impl.TabularPivotField;
import org.zkoss.pivot.impl.TabularPivotModel;
import org.zkoss.pivot.util.PivotModels;
import org.zkoss.zk.ui.event.CheckEvent;
import org.zkoss.zk.ui.event.EventListener;
import org.zkoss.zk.ui.event.Event;
import org.zkoss.zk.ui.event.Events;
import org.zkoss.zul.Checkbox;
import org.zkoss.zul.Div;
import org.zkoss.zul.Listbox;
import org.zkoss.zul.Listcell;
import org.zkoss.zul.Listitem;
/**
* Class to handle tasks with respect to filter
*
* NOTE: A FilterHandler is rely on a specific set of fields and
* should be renew after the fields of pivot model are changed
*
* Tested with ZK 6.0.2 EE and ZK Pivottable 2.0.0
*
* @author benbai123
*/
public class FilterHandler {
/**
* map that contains all Limit object
* use field name as the key
*
*/
private Map<String, Limit> _fieldsLimitsMap = new HashMap<String, Limit>();
/**
* map that contains all field index
* use field name as the key
*/
private Map<String, Integer> _indexMap = new HashMap<String, Integer>();;
@SuppressWarnings("rawtypes")
public void updateFilter (Pivottable pivottable, TabularPivotField field, List<String> columns, Iterable<List<Object>> rawData, Div filter) {
List distinctValues = PVTUtils.getDistinctValues(rawData, columns, getFieldIndex(columns, field.getFieldName()));
updateFilterList(pivottable, distinctValues, field, filter);
}
/**
* update the limited values of a field
* @param fieldName the name of the field to update
* @param value the value to update
* @param accept whether accept the value above
*/
@SuppressWarnings({ "unchecked", "rawtypes" })
public void updateLimit (String fieldName, Object value, boolean accept) {
// try find Limit object
Limit limit = _fieldsLimitsMap.get(fieldName);
// create a new one if not found
if (limit == null) {
limit = new Limit(fieldName, new HashSet());
_fieldsLimitsMap.put(fieldName, limit);
}
// remove value from limited values if the value is accepted
// add value to limited values if the value is not accepted
if (accept) {
limit.getLimitedValues().remove(value);
} else {
limit.getLimitedValues().add(value);
}
}
/**
* filter data by limits
* @param model pivot model, get all row/column fields from it
* @param rawData raw data, to filter it
* @return the filtered raw data
*/
public Iterable<List<Object>> filterData (TabularPivotModel model, final List<String> columns, Iterable<List<Object>> rawData) {
// field name of row/column fields
final List<String> rcColumns = new ArrayList<String>();
// keep a final object of limit map so can be used in inner class
final Map<String, Limit> limits = _fieldsLimitsMap;
// add all row/column field names
for (TabularPivotField tpf : model.getRowFields()) {
rcColumns.add(tpf.getFieldName());
}
for (TabularPivotField tpf : model.getColumnFields()) {
rcColumns.add(tpf.getFieldName());
}
return PivotModels.filter(rawData, new PivotModels.Filter<List<Object>>() {
public boolean keep(List<Object> row) {
// for each row/column (field names)
for (String s : rcColumns) {
// find Limit object
Limit l = limits.get(s);
if (l != null) {
// find value index
int index = getFieldIndex(columns, s);
// get value
Object value = row.get(index);
// do not keep the value if the value is in limit
if (l.getLimitedValues().contains(value)) {
return false;
}
}
}
return true;
}
});
}
/**
* create the filter list
* @param pivottable pivottable, to fire onFilterChanged event as needed
* @param distinctValues all different values, used to construct the filter list
* @param field pivot field, used to get fieldName
* @param filter the div component specified in index.zul
*/
@SuppressWarnings({ "rawtypes", "unchecked" })
private void updateFilterList (final Pivottable pivottable, List distinctValues, final TabularPivotField field, Div filter) {
// clear old children
filter.getChildren().clear();
Listbox lb = new Listbox();
lb.setWidth("200px");
final String fieldName = field.getFieldName();
Limit limit = _fieldsLimitsMap.get(fieldName);
// for each value of this field
for (final Object value : distinctValues) {
Listitem li = new Listitem();
Listcell lc = new Listcell();
Checkbox cb = new Checkbox(value.toString());
// update checked status of checkbox according to
// whether this value is a limited value
if (limit != null && limit.getLimitedValues().contains(value)) {
cb.setChecked(false);
} else {
cb.setChecked(true);
}
cb.setParent(lc);
lc.setParent(li);
li.setParent(lb);
// add onCheck event listener to checkbox
cb.addEventListener("onCheck", new EventListener() {
public void onEvent (Event event) {
// fire event to pivottable with
// the information of changed filter attributes
Events.postEvent(new FilterChangedEvent(pivottable, fieldName, value, ((CheckEvent)event).isChecked() ));
}
});
}
// add listbox to div
lb.setParent(filter);
}
/**
* get the index in raw data of a field
* @param columns columns used in pivot model
* @param fieldName name of the field to search index
* @return int the found index
*/
private int getFieldIndex (List<String> columns, String fieldName) {
// search it from index map at first
if (!_indexMap.containsKey(fieldName)) {
int index = PVTUtils.getFieldIndex(columns, fieldName);
// store it to index map
_indexMap.put(fieldName, index);
}
return _indexMap.get(fieldName);
}
}
PVTUtils.java
Utils of pivottable
package test;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.zkoss.pivot.PivotField;
import org.zkoss.pivot.PivotHeaderNode;
import org.zkoss.pivot.impl.TabularPivotField;
import org.zkoss.pivot.impl.TabularPivotModel;
/**
* utilities for pivottable
*
* Tested with ZK 6.0.2 EE and ZK Pivottable 2.0.0
*
* @author benbai123
*/
public class PVTUtils {
/**
* Create a new pivot model based on
* current pivot model and new data
* @param model
* @param newData
* @return
*/
public static TabularPivotModel cloneModelWithData (TabularPivotModel model, Iterable<List<Object>>newData) {
TabularPivotField[] fields = model.getFields();
// get columns from old model
List<String> columns = new ArrayList<String>();
// set field
for (TabularPivotField tpf : fields) {
columns.add(tpf.getFieldName());
}
TabularPivotModel newModel = new TabularPivotModel(newData, columns);
PVTUtils.syncModelStructure(model, newModel);
return newModel;
}
/**
* get the index in raw data of a field
* @param columns columns used in pivot model
* @param fieldName name of the field to search index
* @return int the found index
*/
public static int getFieldIndex (List<String> columns, String fieldName) {
int index = -1;
// search field name in columns
for (int i = 0; i < columns.size(); i++) {
if (columns.get(i).equals(fieldName)) {
index = i;
break;
}
}
return index;
}
/**
* get all different values of a field
* @param rawData the raw data to get different values from
* @param columns all columns in pivot model
* @param index the index to get value from list
* @return
*/
@SuppressWarnings({ "rawtypes", "unchecked" })
public static List getDistinctValues (Iterable<List<Object>> rawData, List<String> columns, int index) {
// set used to hold distinct values
Set s = new HashSet();
// result list
List result = new ArrayList();
if (index == -1) return result;
// add all value to set directly
for (List<Object> data : rawData) {
s.add(data.get(index));
}
// copy to list then sort the list
for (Object o : s) {
result.add(o);
}
Collections.sort(result);
return result;
}
/**
* sync the structure of pivot model
*
* @param model the base pivot model
* @param modelTwo the pivot model to adjust its structure
*/
public static void syncModelStructure (TabularPivotModel model, TabularPivotModel modelTwo) {
syncFields(model.getRowFields(), modelTwo);
syncFields(model.getColumnFields(), modelTwo);
syncFields(model.getDataFields(), modelTwo);
syncFields(model.getFields(PivotField.Type.UNUSED), modelTwo);
syncOpenStatus(model.getRowHeaderTree().getRoot(), modelTwo.getRowHeaderTree().getRoot(), false);
syncOpenStatus(model.getColumnHeaderTree().getRoot(), modelTwo.getColumnHeaderTree().getRoot(), false);
}
/**
* sync pivot fields of pivot model
* @param fields the base fields
* @param model the pivot model to adjust its fields
*/
private static void syncFields (TabularPivotField[] fields, TabularPivotModel model) {
for (TabularPivotField f : fields) {
model.setFieldType(f.getFieldName(), f.getType());
PivotField field = model.getField(f.getFieldName());
model.setFieldSubtotals(field, f.getSubtotals());
model.setFieldKeyComparator(field, f.getComparator());
}
}
/**
* Synchronize the open status of two pivot header trees
*
* @param root the root of the base pivot header tree (or its sub trees)
* @param rootTwo the root of the pivot header tree (or its sub trees) to sync
* @param checkAll whether sync whole tree, <br>
* true: sync whole tree, put every node of base pivot header tree into open list to sync<br>
* false: sync only current view, only put the displayed node into open list to sync
*/
private static void syncOpenStatus (PivotHeaderNode root, PivotHeaderNode rootTwo, boolean checkAll) {
Map<Object, PivotHeaderNode> originalOpenMap = new HashMap<Object, PivotHeaderNode>();
// sync displayed node only if not checkAll
// so do not need to scan whole header tree
for (PivotHeaderNode node : root.getChildren()) {
// checkAll: sync each node
// !checkAll: sync displayed node
if (checkAll
|| (node.getDepth() == 1 || node.getParent().isOpen())) {
originalOpenMap.put(node.getKey(), node);
}
}
// for each node in children of rootTwo
for (PivotHeaderNode newNode : rootTwo.getChildren()) {
PivotHeaderNode node = originalOpenMap.get(newNode.getKey());
if (node != null) {
newNode.setOpen(node.isOpen());
// recursively sync sub trees
syncOpenStatus(node, newNode, checkAll);
}
}
}
}
Limit.java
Contains the restricted values of a pivot field
package test;
import java.util.HashSet;
import java.util.Set;
/**
* Class for hold limited values of a field
*
* Tested with ZK 6.0.2 EE and ZK Pivottable 2.0.0
*
* @author benbai123
*
*/
public class Limit {
// the name to represent a specific field
private String _fieldName;
// the limited values
private Set<Object> _limitedValues;
public Limit (String fieldName, Set<Object> limitedValues) {
_fieldName = fieldName;
_limitedValues = limitedValues;
if (_limitedValues == null) {
_limitedValues = new HashSet<Object>();
}
}
public String getFieldName () {
return _fieldName;
}
public Set<Object> getLimitedValues () {
return _limitedValues;
}
}
FilterChangedEvent.java
Event that will be fired when the filter is changed.
package test;
import org.zkoss.zk.ui.Component;
import org.zkoss.zk.ui.event.Event;
/**
* Event used to pass the information of updated filter
*
* Event name is "onFilterChanged"
*
* Tested with ZK 6.0.2 EE and ZK Pivottable 2.0.0
*
* @author benbai123
*
*/
public class FilterChangedEvent extends Event {
private static final long serialVersionUID = 5055917746546499563L;
/**
* whether a value is checked in filter list,
* true: checked (denotes this value is allowed)
* false: not checked (denotes this value is not allowed)
*/
private boolean _checked = true;
/**
* the updated filter value
*/
private Object _value;
/**
* the field related to the updated filter
*/
private String _fieldName;
// constructor,
public FilterChangedEvent (Component target, String fieldName, Object value, boolean checked) {
super("onFilterChanged", target);
_checked = checked;
_fieldName = fieldName;
_value = value;
}
public String getFieldName () {
return _fieldName;
}
public Object getValue () {
return _value;
}
public boolean isChecked () {
return _checked;
}
}
Reference
Filtering input data
http://books.zkoss.org/wiki/ZK_Pivottable_Essentials/Working_With_Pivottable/Prepare_Data#Filtering_input_data
The Result
View demo on line
http://screencast.com/t/XO77z12jf
Download
Full project at github
https://github.com/benbai123/ZK_Practice/tree/master/Components/projects/Addon_Practice/PivottableTest/FilterRowColumnHeader
Demo Flash
https://github.com/benbai123/ZK_Practice/blob/master/Components/demos/addon/pvt_filter_row_column_header.swf
No comments:
Post a Comment