I've just set installed a live demo running the current SNAPSHOT version of the project.
http://demo.desarrolloagil.es/olap-faces/test.jsf
Version 6: Hierarchy Expansion, let’s drill-up
Wednesday, February 22, 2012
You can download this version of the sample, and an improved version of the component library from here.
This version includes the capability to expand/collapse a hierarchy in the cellset table. An expanded hierarchy initially shows all of its members, and the user can drill-up to hide details. A collapsed hierarchy initially shows only its root members, and the user can drill-down to show further detail.
I've modified the queryCellSet composite component (and QueryCellSetBean, its associated managed bean) to include a hierarchy expansion button in the header of both cellset axes, allowing the user toggling the expand state for a hierarchy.
This functionality is supported by three methods in the QueryAxis class:
QueryHierarchy Expression Generation
The strategy to MDX generation for an expanded hierarchy is as follows:
Refactoring Axis Set Expression Generation
/**
* Recursively generates the set expression for a Query Hierarchy.
*
* @param current
* currently visited node
* @param expander
* helper object to execute drills/expansions
* @param drillList
* members to drill/undrill
* @param expression
* generated expression
*/
@SuppressWarnings("unused")
private void toOlap4jQueryDidactic(VisitingInfo current,
HierarchyExpander expander, List<Member> drillList,
AxisExpression expression) {
boolean isMemberIncluded =
current.getEffectiveSign(Operator.MEMBER) == Sign.INCLUDE;
boolean areChildrenIncluded =
current.getEffectiveSign(Operator.CHILDREN) == Sign.INCLUDE;
boolean areDescendantsIncluded =
current.getEffectiveSign(Operator.DESCENDANTS) == Sign.INCLUDE;
// Processes current member, including or excluding it from the
// expression as necessary.
Member currentMember = current.getMember();
if (isMemberIncluded) {
expression.include(currentMember);
if (expander.isDrilled(currentMember, drillList)) {
// Current member is included in the hierarchy but collapsed.
// Ends this visit as we should not include any descendant.
return;
}
}
// Recursively calls toOlap4jQueryDidactic on overrided children
List<Member> overridedChildren = new ArrayList<Member>();
for (SelectionTree overridedChild : current.getNode()
.getOverridingChildren()) {
overridedChildren.add(overridedChild.getMember());
VisitingInfo childVisit = current.visitChild(overridedChild);
toOlap4jQueryDidactic(
childVisit,
expander,
drillList,
expression);
}
if (areDescendantsIncluded) {
// Expand/undrill descendants
MemberSet expansionBase;
if (areChildrenIncluded) {
expansionBase = new ChildrenMemberSet(currentMember,
overridedChildren);
} else {
expansionBase = new GrandchildrenSet(currentMember,
overridedChildren);
}
expander.expand(expansionBase, drillList, expression);
} else {
// Include children if necessary
if (areChildrenIncluded) {
MemberSet nonOverridingChildren
= new ChildrenMemberSet(currentMember, overridedChildren);
expression.include(nonOverridingChildren.getMdx());
}
}
}
}
This version includes the capability to expand/collapse a hierarchy in the cellset table. An expanded hierarchy initially shows all of its members, and the user can drill-up to hide details. A collapsed hierarchy initially shows only its root members, and the user can drill-down to show further detail.
Expanded Hierarchy: Store Type hierarchy expanded with some undrilled members |
This functionality is supported by three methods in the QueryAxis class:
- void expandHierarchy(QueryHierarchy h)Expands the hierarchy, invalidating any previous drill/undrill operation on every appearance of h in this axis
- void collapseHierarchy(QueryHierarchy h)
Collapses the hierarchy, invalidating any previous drill/undrill operation on every appearance of h in this axis - boolean isExpanded(QueryHierarchy h)
Tests if the provided hierarchy is expanded in this axis.
QueryHierarchy Expression Generation
The strategy to MDX generation for an expanded hierarchy is as follows:
- A fully expanded hierarchy generates this MDX:
DESCENDANTS(<root members set>, 0, SELF_AND_AFTER) - Member undrill is implemented by an external EXCEPT:
EXCEPT(
DESCENDANTS(<root members set>, 0, SELF_AND_AFTER),
DESCENDANTS(<undrilled members set>, 0, AFTER)
)
Refactoring Axis Set Expression Generation
Previously, axis MDX expression generation was driven by the drill tree. I've refactored this logic to let the axis expression generation be driven by the QueryHierarchy inclusion/exclusion tree.
This is a simplified version of the function to generate an axis set expression for a QueryHierarchy/**
* Recursively generates the set expression for a Query Hierarchy.
*
* @param current
* currently visited node
* @param expander
* helper object to execute drills/expansions
* @param drillList
* members to drill/undrill
* @param expression
* generated expression
*/
@SuppressWarnings("unused")
private void toOlap4jQueryDidactic(VisitingInfo current,
HierarchyExpander expander, List<Member> drillList,
AxisExpression expression) {
boolean isMemberIncluded =
current.getEffectiveSign(Operator.MEMBER) == Sign.INCLUDE;
boolean areChildrenIncluded =
current.getEffectiveSign(Operator.CHILDREN) == Sign.INCLUDE;
boolean areDescendantsIncluded =
current.getEffectiveSign(Operator.DESCENDANTS) == Sign.INCLUDE;
// Processes current member, including or excluding it from the
// expression as necessary.
Member currentMember = current.getMember();
if (isMemberIncluded) {
expression.include(currentMember);
if (expander.isDrilled(currentMember, drillList)) {
// Current member is included in the hierarchy but collapsed.
// Ends this visit as we should not include any descendant.
return;
}
}
// Recursively calls toOlap4jQueryDidactic on overrided children
List<Member> overridedChildren = new ArrayList<Member>();
for (SelectionTree overridedChild : current.getNode()
.getOverridingChildren()) {
overridedChildren.add(overridedChild.getMember());
VisitingInfo childVisit = current.visitChild(overridedChild);
toOlap4jQueryDidactic(
childVisit,
expander,
drillList,
expression);
}
if (areDescendantsIncluded) {
// Expand/undrill descendants
MemberSet expansionBase;
if (areChildrenIncluded) {
expansionBase = new ChildrenMemberSet(currentMember,
overridedChildren);
} else {
expansionBase = new GrandchildrenSet(currentMember,
overridedChildren);
}
expander.expand(expansionBase, drillList, expression);
} else {
// Include children if necessary
if (areChildrenIncluded) {
MemberSet nonOverridingChildren
= new ChildrenMemberSet(currentMember, overridedChildren);
expression.include(nonOverridingChildren.getMdx());
}
}
}
}
Version 5: Refining Hierarchy Selection UI and Query Axis Editing
Thursday, February 02, 2012
You can download this version of the sample, and an improved version of the component library from here.
Sample page including the <olap:queryAxisEditor> component |
- Displays the current cube's hierarchies organized by the axis where they are used. Unused hierarchies are displayed in an "Unused"
axis. - Allows for hierarchy sorting (within the ROWS and COLUMN axes).
- Allows hierarchy removal from an axis.
- Allows moving used hierarchies to other query axes.
- Keeps a selected hierarchy (in the sample page this selected hierarchy is the one displayed by the query hierarchy editor)
- Includes a button to swap rows and columns axes.
- Allows selecting of the non-empty state for ROWS and COLUMNS axes.
<olap:queryAxisEditor
id="axisEditor"
query="#{queryBean}"
value="#{queryBean.selectedHierarchyName}">
id="axisEditor"
query="#{queryBean}"
value="#{queryBean.selectedHierarchyName}">
<f:ajax
event="change"
execute=":form:axisEditor"
render=":form:axisEditor :form:hierarchyEditor"/>
event="change"
execute=":form:axisEditor"
render=":form:axisEditor :form:hierarchyEditor"/>
<f:ajax
event="edit"
render="@all"/>
event="edit"
render="@all"/>
</olap:queryAxisEditor>
<olap:queryAxisEditor> Composite Component Internals
The queryAxisEditor composite component takes the following parameters:
- query: AbstractQueryBean derived managed bean keeping the query state
- value: backing bean property used to keep the current selected hierarchy name
- change: when the user changes the currently selected hierarchy
- edit: when the user clicks on any action button (remove, add to rows, add to columns, add to filter, push up or push down) changing the current query.
<composite:interface>
<composite:attribute
name="query"
/>
name="query"
/>
<composite:attribute
name="value"
/>
name="value"
/>
<composite:attribute
name="styleClass"
/>
name="styleClass"
/>
<composite:attribute
name="headerClass"
/>
name="headerClass"
/>
<composite:attribute
name="rowClasses"
/>
name="rowClasses"
/>
<composite:clientBehavior
name="change"
default="true"
event="click"
targets="columns rows filter unused"
/>
name="change"
default="true"
event="click"
targets="columns rows filter unused"
/>
<composite:clientBehavior
name="edit"
event="edit"
targets="columns rows filter unused"
/>
name="edit"
event="edit"
targets="columns rows filter unused"
/>
<composite:clientBehavior
name="edit"
targets="swap"
/>
name="edit"
targets="swap"
/>
</composite:interface>
<composite:implementation>
<div
id="#{cc.attrs.clientId}">
id="#{cc.attrs.clientId}">
<h:commandButton
id="swap"
styleClass="cgaofHierarchyListSwapBtn"
value="Swap Axes"
action="#{queryAxisEditorBean.swapAxes(cc.attrs.query)}"/>
id="swap"
styleClass="cgaofHierarchyListSwapBtn"
value="Swap Axes"
action="#{queryAxisEditorBean.swapAxes(cc.attrs.query)}"/>
<h:inputHidden
id="selected"
value="#{cc.attrs.value}"/>
id="selected"
value="#{cc.attrs.value}"/>
<olap:queryAxisEditorBase
query="#{cc.attrs.query}"
value="#{cc.attrs.value}"
valueControl="#{cc.attrs.clientId}:selected"
axisName="COLUMNS"
id="columns"
query="#{cc.attrs.query}"
value="#{cc.attrs.value}"
valueControl="#{cc.attrs.clientId}:selected"
axisName="COLUMNS"
id="columns"
styleClass="#{cc.attrs.styleClass}"
headerClass="#{cc.attrs.headerClass}"
rowClasses="#{cc.attrs.rowClasses}"/>
headerClass="#{cc.attrs.headerClass}"
rowClasses="#{cc.attrs.rowClasses}"/>
<olap:queryAxisEditorBase
query="#{cc.attrs.query}"
value="#{cc.attrs.value}"
valueControl="#{cc.attrs.clientId}:selected"
axisName="ROWS"
id="rows"
query="#{cc.attrs.query}"
value="#{cc.attrs.value}"
valueControl="#{cc.attrs.clientId}:selected"
axisName="ROWS"
id="rows"
styleClass="#{cc.attrs.styleClass}"
headerClass="#{cc.attrs.headerClass}"
rowClasses="#{cc.attrs.rowClasses}"/>
headerClass="#{cc.attrs.headerClass}"
rowClasses="#{cc.attrs.rowClasses}"/>
<olap:queryAxisEditorBase
query="#{cc.attrs.query}"
value="#{cc.attrs.value}"
valueControl="#{cc.attrs.clientId}:selected"
axisName="FILTER"
id="filter"
query="#{cc.attrs.query}"
value="#{cc.attrs.value}"
valueControl="#{cc.attrs.clientId}:selected"
axisName="FILTER"
id="filter"
rendered="#{cc.attrs.query.slicerHierarchies.size() > 0}"
styleClass="#{cc.attrs.styleClass}"
styleClass="#{cc.attrs.styleClass}"
headerClass="#{cc.attrs.headerClass}"
rowClasses="#{cc.attrs.rowClasses}"/>
rowClasses="#{cc.attrs.rowClasses}"/>
<olap:queryAxisEditorBase
query="#{cc.attrs.query}"
value="#{cc.attrs.value}"
valueControl="#{cc.attrs.clientId}:selected"
id="unused"
query="#{cc.attrs.query}"
value="#{cc.attrs.value}"
valueControl="#{cc.attrs.clientId}:selected"
id="unused"
styleClass="#{cc.attrs.styleClass}"
headerClass="#{cc.attrs.headerClass}"
rowClasses="#{cc.attrs.rowClasses}"/>
headerClass="#{cc.attrs.headerClass}"
rowClasses="#{cc.attrs.rowClasses}"/>
</div>
</composite:implementation>
- The axis name and the non-empty toggle button are displayed within the table's header.
- Each hierarchy within the axis is displayed in a row with three columns:
- A column for the remove button.
- A column for the hierarchy's caption. The caption includes javascript to change the selected hierarchy on clicking.
- A column for the other action buttons: rows, columns, filter, push up and push down.
Required Query Package Improvements
The improvements implemented in the query package have been straightforward and all of them consisted in implementing methods already defined by the org.olap4j.query package:- Query.swapAxes to allow axes swapping
- QueryHierarchy.isNonEmpty(), QueryHierarchy.setNonEmpty() to check/set the flag controlling the generation of NON EMPTY axis.
- QueryHierarchy.pushUp(), QueryHierarchy.pushDown()to allow moving a hierarchy within an axis.
Subscribe to:
Posts
(
Atom
)