피벗 그리드(Pivot Grid) 컴포넌트는 대용량 데이터에 대한 신속한 요약을 가능하게 한다. 이 컴포넌트는 다양한 데이터 포인트를 좀더 명확하도록 동향과 개요를 갖는 형식으로 요약하는 손쉬운 방법을 제공한다. 고전적인 예는 영업 데이터로서 회사는 특정 기간에 대한 모든 영업 레코드를 발표한다고 가정하자. 이 데이터는 아마도 수천개의 데이터 행을 초과하는 일이 비일비재할 것이다. 피벗 그리드는 이 데이터를 각 영업사원의 실적에 따라, 가장 이익을 많이 낸 도시에 따라, 도시 별로 판매된 제품에 따라 보여줄 수 있다.
주의
: 이 장에서는 Ext JS 6.x 이전 버전에서 다운로드 하여 사용할 수 있는 stand-alone PivotGrid와 Ext JS 6 SDK의 프리미엄 버전에 번들 되는 PivotGrid 패키지를 둘 다 살펴볼 것이다. 아래에 기술되는 내용은 Ext JS 6의 PivotGrid 패키지를 사용하는 것으로 가정하고 작성되었다. stand-alone 버전의 다운로드를 사용할 경우에는 다음과 같이 해당 내용을 대체하도록 하자.
모든 "Mz" 네임스페이스 클래스들은 Ext JS 6의 "Ext" 네임스페이스에 속한다.
"Mz.aggregate.
" 클래스들은 Ext JS 6의 "Ext.pivot.
"가 된다.
"mzgrandtotal"의 Mz.aggregate.matrix.Abstract.grandTotalKey는 Ext JS 6에서 "grandtotal"의 Ext.pivot.matrix.Abstract.grandTotalKey가 된다.
다음과 같은 그리드 패널이 있다고 생각해보자.
다음과 같은 질문에 대한 답을 어떻게 클라이언트-사이드에서 쉽게 구현하려면 어떻게 해야 할까?
John Doe의 총 주문 금액은 얼마인가?
나라별 총 주문 금액은 얼마인가?
특정 연도에 영업 사원들의 실적은 어떠한가?
이러한 요구사항들은 피벗 그리드가 해결할 수 있는 일부의 과제에 지나지 않는다. 위 요구에 대한 답이 어떻게 구현되었는지 한번 살펴보자.
피벗 그리드는 Ext JS 프레임워크에 번들되지 않지만 어플리케이션에서 손쉽게 require 할 수 있다. Sencha Cmd를 사용해서 생성한 어플리케이션이거나 혹은 자체적으로 디자인한 구조의 어플리케이션이라고 할지라도 피벗 그리드 코드를 추가하고 스타일링 하는데에 필요한 단계는 많지 않다.
Ext JS
Sencha Pivot Grid는 Sencha Ext JS 6에서 동작한다.
Sencha Cmd
Sencha Cmd는 피벗 그리드를 활요함에 있어 필요 사항은 아니다. 그러나 Cmd를 사용하면 어플리케이션의 app.json 파일을 통해 피벗 그리드 패키지를 손쉽게 include 할 수 있다.
Sencha Cmd를 통한 Pivot Grid 사용
피벗 그리드는 어플리케이션의 packages 폴더에 쉽게 배포될 수 있도록 패키지화 되어 있으며 전체 소스 코드와 함께 제공된다.
피벗 그리드를 include 하기 위해 어플리케이션 루트 디렉토리의 app.json 파일에 피벗 그리드 패키지를 require 하도록 추가하자.
"name": "YourApp",
"requires": [
"pivot"
"id": "391a5ff6-2fd8-4e10-84d3-9114e1980e2d"
Sencha Cmd 없이 Pivot Gird 사용
SDK는 Sencha Cmd를 사용하지 않을 경우에 활용할 수 있도록 컴파일된 버전의 피벗 그리드 코드를 포함하고 있다. index 페이지에 아래의 파일을 링크해서 피벗 그리드를 추가하도록 하자.
{pivotFolder}/packages/pivot/build/pivot.js
{pivotFolder}/packages/pivot/build/{themeName}/resources/pivot-all.css
어플리케이션 디렉토리에서 다음을 추가하자.
Pivot Grid 사용
피벗 그리드는 네이티브 그리드 패널을 상속한 Axis와 Aggregation 두 가지에 의존하고 있다. Axis는 aggregation이 그룹핑 연산을 관리하는 것에 대비하여 행(row)와 열(column)의 배치를 판단할 수 있도록 한다.
축(Axis)
앞서 설명한 피벗 그리드의 사용 예로 다시 돌아가 보자. 일단 우선, 데이터셋(dataset)을 영업사원(salespeople)과 년도(year)로 분할 해야 한다. 이러한 설정은 top과 left axis의 설정을 통해 가능하다.
leftAxis: [{
width: 80,
dataIndex: 'salesperson',
header: 'Salesperson'
topAxis: [{
dataIndex: 'year',
header: 'Year',
direction: 'ASC'
이 결과는 뷰에서 아래와 같을 것이다.
집합(Aggregation)
다음 단계로 셀 값들이 적당하게 계산 되도록 집합(aggregation)을 설정할 것이다. 피벗 그리드는 다음과 같이 뛰어나고도 다양한 aggregation 메서드를 제공한다.
average
count
groupSumPercentage - 만약 Johe이 US와 Germany에서 제품을 판매했다면 전체 판매액에서 US에 판매한 금액의 퍼센트를 보고 싶을 수 있다. groupSumPercentage 메서드는 현재 항목 합계가 상위 항목 합계에서 차지하는 비율을 계산한다.
groupCountPercentage - groupSumPercentage와 같지만 sum 대신 count를 사용한다.
variance
varianceP
stdDev
stdDevP
또한 사용자 정의 처리를 위해 고유한 자신만의 aggregation 함수를 사용할 수도 있다. 다음의 예제는 aggregate 배열에서 각각의 aggregator 설정을 통해 어떻게 다중 필드(sales와 quantity)를 묶어 내는지를 보여준다. 코드에서 볼 수 있는 바와 같이 Quantity 계산을 위해 사용자 집합 함수(custom aggregator)를 사용했다.
aggregate: [{
measure: 'amount',
header: 'Sales',
aggregator: 'sum',
align: 'right',
width: 85,
renderer: Ext.util.Format.numberRenderer('0,000.00')
measure: 'orderid',
header: 'Qnt',
aggregator: function(records, measure, matrix, rowGroupKey, colGroupKey) {
// custom aggregator logic
return records.length;
align: 'right',
width: 85,
renderer: Ext.util.Format.numberRenderer('0,000.00')
위 예제는 aggregate에서 무엇이 가능하지를 보여주는 단순한 예제이다. 만약 자체적인 aggregation 메서드를 작성해야 한다면, 최선의 선택은 Ext.pivot.Aggregators를 오버라이드 하는 것이다. 오버라이드를 통해 사용자 함수를 추가하고, aggregator config name으로 추가한 함수 이름을 사용할 수 있다.
설정을 정의할 때 결과 정렬을 제어할 수 있는 다음 옵션을 사용할 수 있다.
sortable : True로 설정하면 결과 정렬이 가능하다. (기본 값은 true)
direction : 정렬 시 오름차순(ASC)으로 할지 내림차순(DESC)로 할지 지정 할 수 있다. (기본 값은 ASC)
caseSensitiveSort : true로 설정하면 필터링 시 대소문자를 구분한다. (기본 값은 true)
sorterFn : 사용자 sorter 함수
sortIndex : 레코드의 다른 값을 사용해 결과를 정렬해야 할 경우에 사용. "month-name"이라는 필드에 Jan, Feb, ... 값이 있다고 하고, 다른 필드 "month-value"에는 1,2,3... 값이 있다고 가정하자. "month-name"으로 얻은 결과(dataIndex)를 "month-value"로 정렬(sortIndex) 할 수도 있을 것이다. 이럴 때 sortIndex를 사용한다.
다중 총합계(Multiple Grand Totals)
기본적으로 피벗그리드는 행 기반 총합계를 계산한다. 필요에 따라 총합계를 표시 할지 말지 선택할 수 있으며, 행(Row)에 여러개의 총합계를 표시하는 방법은 다음 두가지 방법으로 가능하다.
피벗 그리드에서 fire하는 pivotbuildtotals 이벤트를 리스닝할 수 있다. 이벤트 핸들러에게는 총합계 값의 배열과 기본 값이 포함된 파라미터가 전달된다. 이 배열의 각 객체는 전체 레코드 생성 시 사용될 "title"과 "values" 값을 갖고 있다.
matrix 클래스 중 하나를 상속 후 onBuildTotals 템플릿 메서드를 사용하여 위에서 설명한 로직처럼 수행한다.
새로운 총합계는 피벗 그리드에서 기본 총합계처럼 스타일링 될 것이다.
listeners: {
pivotbuildtotals : function(matrix, totals) {
var dataAvg = {},
dataMax = {};
Ext.Array.each(matrix.model, function(field) {
var result, agg;
if (field.col && field.agg) {
agg = matrix.aggregate.getByKey(field.agg);
result = matrix.results.get(matrix.grandTotalKey, field.col);
if(result && agg) {
dataAvg[field.name] = result.calculateByFn(
'totalavg',
agg.dataIndex,
Ext.pivot.Aggregators.avg);
dataMax[field.name] = result.calculateByFn(
'totalmax',
agg.dataIndex,
Ext.pivot.Aggregators.max);
totals.push({
title: 'Grand total (avg)',
values : dataAvg
title: 'Grand total (max)',
values : dataMax
//...
범위 그룹핑(Range Grouping)
만약 한개의 축에 연도별 결과를 갖고 있으며, 80년대, 90년대 등과 같이 그 결과를 그룹으로 묶고 싶다면 어떻게 해야할까? 다음과 같이 두 가지 방법을 통해 가능하다.
"grouperFn"을 정의하고 레코드를 전달하면 그룹 값을 리턴한다. (예를 들어 80년대, 90년대)
소스 모델에 새로운 필드를 정의하여 위와 같은 동일한 결과를 수행하도록 "convert" 함수를 추가한다.
grouperFn 예제는 다음과 같다.
leftAxis : [{
//...
grouperFn: function(record){
var dataIndex = this.dataIndex,
recIndex = record.get(dataIndex);
if (recIndex >= 1980 && recIndex < 1990) return "80'";
if (recIndex >= 1990 && recIndex < 2000) return "90'";
return 'Rest';
//...
필터링(Filtering)
피벗 그리드는 다음과 같은 두 가지 타입의 필터를 제공한다.
Label 필터
라벨 필터는 top 또는 left axis 결과에 대한 값을 평가하여 결과를 필터링 한다. (예를 들어 ~로 시작하는 begins with, ~로 시작하지 않는 does not begin with, ~로 끝나는 ends with 등등)
Value 필터
값 필터는 각 top/left axis 쌍에 대해 계산된 값을 평가하여 결과를 필터링 한다. (같은 equals to, 보다 큰 greater than, 상위 10개 항목 top 10 items, 상위 10% top 10 percent, 상위 10항목 합계 top 10 sum, 등)
필터는 left 또는 top 축의 dimension item에 설정한다.
leftAxis : [{
//...
filter: {
type : 'value',
operator : 'top10',
topOrder : 'top',
topType: 'sum',
value : 9500,
dimensionId : 'agg2'
//...
좀 더 자세한 사항은 'Ext.pivot.filter.*' 클래스를 참조하라.
버퍼드 랜더링(Buffered Rendering)
피벗 그리드의 계산의 끝난 피벗 스토어에는 수천 개의 레코드들이 있을 수 있다. DOM의 과도한 부하 방지를 위해 BufferedRenderer 플러그인을 사용할 수 있는데 BufferedRenderer는 Ext JS 4.2.0 이후 버전부터 제공되고 있다. 버전 5+의 BufferedRenderer는 그리드 패널에서 기본으로 사용되고 있다.
BufferedRenderer는 사용자에게 수천개의 레코드를 스크롤 할 때 화면에 보여지는 양만큼의 레코드만 랜더링 함으로서 성능 저하 방지한다.
합계 위치(Totals Positioning)
총합계(grand total)의 위치는 left와 top 축 양쪽에 모두 설정 가능하다.설정 가능한 값은 first, last 또는 none이다.
그룹 총계은 그룹이 접혀 있을 때면 항상 보여진다. 이는 합계 설정이 어떤 값으로 되어 있는지와 상관 없이 어떤 축에 대해 여러 개의 dimension을 갖을 때 적용된다. 그룹이 펼쳐져 있을 때에도 역시 동일한 설정을 사용할 수 있다.
랜더러(Renderer)
랜더러는 셀 스타일이나 결과 값의 포맷을 설정할 때 사용한다. 셀 스타일링을 위해서는 aggreate dimension에 반드시 renderer 함수를 정의해야 한다. 예를 들면
//...
aggregator : 'sum',
renderer : function (value, meta, record) {
if (value > 40000) {
meta.style = "background-color: yellow;";
return Ext.util.Format.number(value, '0,000.00');
//...
위와 같은 랜더러를 사용하면 그리드의 출력은 다음과 같게 된다.
renderer 함수에는 좀더 복잡한 로직을 정의할 수도 있다. 예를 들면, 아래의 랜더러는 합계가 다른 일부 필드의 평균보다 작을 때 셀 스타일을 다르게 설정한다.
//...
aggregator : 'sum',
renderer: function(value, meta, record, recordIndex, columnIndex, store, view) {
var grid = view.up('pivotgrid'),
topItem = grid.getTopAxisItem(meta.column),
leftItem = grid.getLeftAxisItem(record),
result;
if(topItem && leftItem) {
result = grid.getMatrix().results.get(leftItem.key, topItem.key);
//만약 다른 dimension에 이미 avg를 설정했었다면 단순히 결과만 fetch
//result.getValue('avg');
//아니라면 result를 계산
result.calculateByFn('avg', 'some-other-field', Ext.pivot.Aggregators.avg);
if(value < result.getValue('avg')) {
meta.style = "background-color : yellow;";
return Ext.util.Format.number(value, '0,000.00');
//...
Stateful
stateful : true와 stateId를 사용하면 피벗 그리드를 추적가능한 상태로 설정할 수 있다. 피벗 그리드의 상태는 모든 dimension 즉 left, top, aggregate 설정을 저장한다. 저장된 상태를 이용해 그룹의 펼침 또는 접힘 상태를 재호출(recall) 할 수 있다.
잠긴 그리드
left 축을 "lock" 상태로 설정하려면 피벗 그리드의 설정을 enableLocking: true로 지정하면 된다. 이렇게 하면 생성된 열의 top 축에서만 스크롤 할 수 있다.
Range Editor
이 플러그인은 피벗 그리드 결과를 사용자에게 편집할 수 있도록 한다. 사용자는 피벗 그리드를 더블 클릭하면 열리는 윈도우에서 필드 값을 편집할 수 있다. 다음과 같은 형태의 편집이 가능하다.
percentage : 해당 셀 뒤로 모든 레코드에 있는 값을 입력된 퍼센트 값만큼 증가 시킨다.
increment : 각 레코드에 입력된 값 만큼 더한다.
overwrite : 각 레코드의 기존 값을 입력 값으로 대체한다.
uniformly : 모든 레코드의 값을 입력 값으로 균일하게 분포시킨다.
플러그인은 또한 편집 전/후 처리를 위해 onBeforeRecordUpdate와 onAfterRecordUpdate 두가지 템플릿 메서드를 제공한다.
요약 수준 변경(Drill Down)
드릴 다운 플러그인은 피벗 그리드 결과의 요약 수준을 필요에 따라 변경할 때 사용한다. 피벗 그리드의 셀을 더블 클릭하면 셀의 소스로 참고하고 있는 상세 데이터가 별도 윈도우 형태로 팝업된다.
드릴 다운 윈도우에 포함된 상세 데이터 그리드는 플러그인의 columns 설정에 따라 사용자 지정이 가능하며, 보통의 Ext JS grid와 사용법은 동일하다.
내보내기(Exporter)
내보내기 플러그인은 피벗 그리드의 결과를 클래스 시스템에 속한 모든 Exporter type으로 내보낸다. 이 플러그인은 엑셀 XML 내보내기를 포함하는 Exporter 패키지를 사용한다.
내보내기 플러그인을 사용하게 되면 다음과 같은 두가지 새로운 메서드를 피벗 그리드 컴포넌트에 추가한다.
saveDocumentAs : 브라우저가 지원할 경우 이 함수는 내보내기 파일을 저장한다.
getDocumentData : 내보내기 문서의 내용을 리턴한다.
두 메서드 모두 파라미터로 설정 객체를 받을 수 있다.
type : exporter 타입 (기본 값은 excel)
onlyExpandedNodes : true로 설정하면 펼쳐진 그룹만 내보낸다. (기본 값은 false)
showSummary : false로 설정하면 내보내기 값에서 합계는 제외된다. (기본 값은 true)
title : 내보내진 문서에서 컬럼 헤더 이에 보여질 제목을 설정
fileName : 저장될 파일 이름
각 Exporter는 자체적으로 고유한 추가 설정을 사용할 수 있다. 이러한 추가 설정은 위의 설정 객체에 대해 위 함수 들 중 하나가 호출될 때 제공된다.
만약 saveDocumentAs 메서드가 브라우저에서 작동하지 않는다면 XML 데이터를 서버로 보낸 다음 서버로 하여금 파일 다운로드 가능한 적절한 헤더로 하는 편이 좋다.
서버 전송을 위해 피벗 그리드에서의 함수 구현은 아래 예를 참고하라.
//...
onExport : function() {
var me = this,
form = Ext.DomHelper.createDom({
tag : 'form',
action : 'url' //xml 컨텐츠를 수신할 서버 url
method : 'POST',
children: [{
tag : 'input',
type : 'hidden',
name : 'filename',
value : me.title
tag : 'input',
type : 'hidden',
name : 'content',
value : encodeURIComponent(me.getDocumentData({type:'excel'}))
form.submit();
// ...
서버 side에서 PHP를 사용 한다면 전송된 데이터를 이용해 브라우저에서 다운로드할 수 있도록 다음과 같이 할 수 있을 것이다.
//...
$fileName = $_POST['filename'].'.xml';
$fileContent = urldecode($_POST['content']);
header('Content-Length: '.strlen($fileContent));
header("Content-Type: application/vnd.ms-excel; charset=utf-8");
header("Content-Disposition: attachment; filename=\"$filename\"");
header("Expires: 0");
header("Cache-Control: must-revalidate, post-check=0, pre-check=0");
header("Cache-Control: private", false);
echo $fileContent;
//...
설정자(Configurator)
설정자 플러그인은 드래그 & 드롭 기능으로 피벗 그리드를 손쉽게 설정하도록 한다.
플러그인에서 제공하는 설정은 다음과 같다.
dock : configurator 패널이 피벗 그리드의 top, right, bottom 및 left에 도킹될 수 있도록 한다. (기본 값은 right)
collapsible : configurator 패널 접기 가능여부 (기본 값은 true)
fields : dimension 상에 드래그 & 드롭 할 수 있는 필드의 배열. fields cofig가 지정되지 않으면 플러그인은 Local matrix를 사용해서 자동으로 스토어 모델에서 전체 필드를 추출한다.
만약 사용자 필드가 있을 경우 각 필드에 대해 다음을 설정할 필요가 있다.
dataIndex : 필드 값을 제공하는 모델 상의 필드 네임
header : 뷰에서 보여질 필드 제목
그외 기타 dimension item config
Matrix 클래스
matrix 클래스들에 의해 fire되는 대부분의 이벤트들은 pivot prefix가 붙어서 피벗 그리드로 전달된다.
Base matrix
기반(base) matrix 클래스는 top과 left axis 아이템을 빌드한다. 이 클래스는 각 left/ top 아이템 쌍에 대한 결과를 계산한다.
Local matrix
local로 matrix 타입을 설정하면 데이터 계산이 브라우저에서 수행되도록 한다. Local matrix는 소규모에서 중간 용량의 데이터 셋일 경우 이상적이다.
Ext.create('Ext.pivot.Grid', {
//...
matrixConfig: {
type : 'local', //기본 값
store : 'YourStore',
recordsPerJob : 1000,
timeBetweenJobs : 2
//...
Local matrix는 중간 용량의 데이터 셋을 처리할 때 브라우저의 과부하 방지를 위해 스토어 레코드들을 여러 개의 job으로 처리한다. 이에 대해 선택적으로 얼마나 많은 수의 레코드가 job별로 처리 되도록 할 것인지, 각 job 사이의 대기 시간은 얼마나 길게 할것인지 설정할 수 있다.
Remote matrix
대용량의 데이터 셋을 브라우저에서 처리하도록 하면 프로세싱에 많은 시간이 소요될 수 있고 브라우저가 응답 없음 상태가 되는 원인이 될 수 있다. 만약 대용량 데이터 셋을 처리해야 한다면 반드시 Remote matrix를 사용해야 한다. Remote matrix는 모든 설정을 serialize해서 서버로 전송하기 때문에 계산을 원격으로 처리할 수 있게 된다.
Ext.create('Ext.pivot.Grid', {
//...
matrixConfig: {
type : 'remote',
url : 'http://your-backend-url',
timeout : 3000 //Ajax call에 의해 사용될 optional config
// ...
서버로 전송되는 것은 무엇인가?
leftAxis, topAxis 및 aggregate 들이 dimension item 객체의 배열로 전달된다. 이들 배열은 serialize 되어진 후 Ajax request에서 파라미터로 전단된다.
params = {
leftAxis: leftAxis,
topAxis : topAxis,
aggregate : aggregate,
grandTotalKey : 'grandtotal', //matrix에서 grandTotalKey로 설정된 값
keysSeparator : '#_#' //matrix에서 keysSeparator로 설정된 값
//...
Ext.Ajax.request({
// ...
url : me.url,
timeout : me.timeout,
jsonData : params,
// ...
Ajax 호출이 실행되기 전에 추가적인 파라미터들은 beforerequest 이벤트(실제로는 pivotbeforerequest로 피벗 그리드에 중계 됨) 또는 onBeforeReques 템플릿 메서드에 의해 수집된다.
JSON 응답은 어떻게 되어야 하나?
피벗 그리드는 특정 포맷의 JSON 응답을 수신한다. 응답은 반드시 leftAxis와 topAxis에 대한 아이템 배열이어야 하며, 모든 결과는 left/ top 아이템 쌍이어야 한다.
주의: "id" 속성은 매우 중요하다. 별도 지정하지 않으면 생성된 값이 id에 지정된다.
success: true,
leftAxis: [{
key: '-1276511163', // 자체적인 고유 키를 지정하되 keysSeparator와 충돌하지 않도록 주의할 것
value: 'Macromedia',
// 다음은 dimension의 id로서 지정했거나 또는 생성된 것임
dimensionId: 'leftAxis1'
// 여러개의 dimension이 제공 될 경우, result key는
// 반드시 아래와 같은 형식을 갖춰야 함
key: '-1276511163#_#243243', // 서버로 전송되었던 keysSeparator 사용
value: 'Steve',
// 다음은 dimension의 id로서 지정했거나 또는 생성된 것임
dimensionId: 'leftAxis2'
topAxis: [{
// leftAxis와 같은 형식
results: [{
leftKey: '-1276511163',
topKey: '34535345435',
values: {
agg1: 4345.34, // agg1은 aggregate dimension의 id
agg2: 244 // agg2는 정의된 두번쨰 aggregate dimension
"grandtotal"을 호출하는 key는 총 합계 계산에 관한 matrix에 대해 생성된다. matrix.grandtotalKey의 값을 수정하면 이에 대한 네이밍 스키마를 수정할 수 있다.
만약 leftKey와 topKey를 둘 다 사용하고 있다면 이는 두 축에 대한 총 합계에 대응할 것이다.
만약 topKey를 사용 한다면 각 leftKey가 제공된 행의 총 합계에 대응할 것이다.
서버에서 계산하려면 어떻게 해야 하나?
examples 폴더가 포함하고 있는 PHP 예제는 서버 사이드 계산을 어떻게 해야하는지를 설명한다. 이 예제를 활용하면 위에서 설명한 JSON 객체를 리턴할 수 있기만 하다면 어떤 서버 사이드 언어에 대해서도 자체적인 구현이 가능할 것이다.
에러 핸들링
만약 서버 측에서 계산이 실패하면 success: false와 함께 피벗 그리드에서 처리하려고 하는 추가적인 메타 데이터 정보를 수신하게 될 것이다. Remote matrix 클래스는 호출 결과의 실퍠를 인식하고 "requestexception" 이벤트를 fire하여 피벗 그리드로 "pivotrequestexception"으로 전달한다.
Sencha 피벗 그리드는 대용량 데이터 셋으로부터 수집한 여러 측면의 합계를 도출해 내기 위한 이상적인 솔루션을 제공한다. 데이터 셋에서 지정된 특정 필드는 사용자의 여러 관점에서 데이터 셋을 시각화 할 수 있도록 합계와 그 원래의 데이터를 필요에 따라 피벗 그리드 형태로 표시된다.
피벗 그리드 설정에 대한 더 많은 예제는 다운로드한 SDK의 example 폴더를 참고하라.
2015년 8월 31일 최종 갱신