概述
本 Demo 展示了如何自定义数据透视表的小计和总计设置,包括:设置值字段的计算类型、调整数值字段位置、配置小计显示位置、以及控制是否显示小计。Demo 提供了多个交互式控件,让用户可以实时查看不同配置对数据透视表的影响。
实现思路
创建数据源表格(包含销售数据的 117 行数据)
创建数据透视表并添加行字段(销售人员、车辆、数量)和列字段(季度)
添加两个值字段:"Sum of total"(总和)和 "Average of total"(平均值)
绑定多个下拉列表事件,允许用户交互式地修改小计类型、数据位置、小计位置等设置
实时刷新数据透视表以展示配置变化
代码解析
创建数据透视表并添加字段
这段代码创建了数据透视表,并添加了多个行字段和两个值字段。第二个值字段直接在 add 方法中指定了计算类型为平均值。
动态更改值字段的计算类型
当用户更改计算类型时,调用 subTotalType() 方法设置新的计算类型,然后更新字段名称以反映新的计算方式。getUniqueName() 函数确保字段名称的唯一性。
设置数据位置
dataPosition(positionType, positionIndex) 方法用于设置值字段在数据透视表中的位置:
positionType:0 表示行区域,1 表示列区域
positionIndex:值字段在行或列列表中的顺序
设置小计位置和显示
options.subtotalsPosition:设置数据透视表全局的小计位置(0=无,1=顶部,2=底部)
subtotalPosition(fieldName, position):设置特定字段的小计位置
subtotalVisible(fieldName, isVisible):控制特定字段是否显示小计
运行效果
页面左侧显示数据透视表,右侧显示配置面板
更改 "总计和" 字段的计算类型下拉列表,可以切换不同的计算方式(平均值、计数、最大值、最小值、求和等)
更改 "总计平均值" 字段的计算类型下拉列表,可以为第二个值字段设置不同的计算方式
通过 "位置类型" 和 "位置索引" 控件,可以将值字段移动到行区域或列区域,并调整显示顺序
通过 "数据透视表小计位置" 下拉列表,可以设置全局小计显示位置(无、顶部、底部)
通过 "车辆字段小计位置" 下拉列表,可以单独设置特定字段的小计位置
通过 "车辆字段小计可见性" 下拉列表,可以控制特定字段是否显示小计
所有修改都会实时反映在数据透视表中
API 参考
subTotalType() - 获取或设置字段的计算类型
fieldName:字段名称
type:计算类型枚举值(GC.Pivot.SubtotalType)
0: average(平均值)
1: count(计数)
2: countNums(数值计数)
3: max(最大值)
4: min(最小值)
5: product(乘积)
6: stdDev(总体标准偏差)
7: stdDevp(标准偏差)
8: sum(求和)
9: varr(总体方差)
10: varp(方差)
dataPosition() - 获取或设置值字段的位置
positionType:0=行区域,1=列区域
positionIndex:值字段在列表中的顺序索引
subtotalPosition() - 获取或设置字段的小计位置
fieldName:字段名称
position:GC.Spread.Pivot.SubtotalsPosition 枚举值
0: none(无)
1: top(顶部)
2: bottom(底部)
subtotalVisible() - 获取或设置字段是否显示小计
fieldName:字段名称
isVisible:布尔值,true 表示显示小计,false 表示隐藏小计
const SubtotalType = ["Average of total", "Count of total", "Count numbers of total", "Max of total", "Min of total",
"Product of total", "StdDev of total", "StdDevp of total", "Sum of total", "Varr of total", "Varp of total"];
var currentFieldName = "Sum of total";
window.onload = function () {
var spread = new GC.Spread.Sheets.Workbook(_getElementById('ss'), { sheetCount: 2 });
initSpread(spread);
var pivotLayoutSheet = spread.getSheet(0);
var pt = initPivotTable(pivotLayoutSheet);
bindEvent(spread, pt);
};
function initSpread(spread) {
spread.suspendPaint();
let sheet = spread.getSheet(1);
sheet.name("DataSource");
sheet.setRowCount(117);
sheet.setColumnWidth(0, 120);
sheet.getCell(-1, 0).formatter("YYYY-mm-DD");
sheet.getRange(-1,4,0,2).formatter("$ #,##0");
let table = sheet.tables.add('tableSales', 0, 0, 117, 6);
for(let i=2;i<=117;i++)
{
sheet.setFormula(i-1,5,'=D'+i+'*E'+i)
}
table.style(GC.Spread.Sheets.Tables.TableThemes["none"]);
sheet.setArray(0, 0, pivotSales);
var sheet0 = spread.getSheet(0);
sheet0.name("PivotLayout");
sheet0.setColumnCount(50);
spread.resumePaint();
}
function initPivotTable(sheet) {
let myPivotTable = sheet.pivotTables.add("myPivotTable", "tableSales", 1, 1, GC.Spread.Pivot.PivotTableLayoutType.outline, GC.Spread.Pivot.PivotTableThemes.light8);
myPivotTable.suspendLayout();
myPivotTable.options.showRowHeader = true;
myPivotTable.options.showColumnHeader = true;
myPivotTable.add("salesperson", "Salesperson", GC.Spread.Pivot.PivotTableFieldType.rowField);
myPivotTable.add("car", "Cars", GC.Spread.Pivot.PivotTableFieldType.rowField);
myPivotTable.add("quantity", "Quantity", GC.Spread.Pivot.PivotTableFieldType.rowField);
let groupInfo = { originFieldName: "date", dateGroups: [{ by: GC.Pivot.DateGroupType.quarters }] };
myPivotTable.group(groupInfo);
myPivotTable.add("季度 (date)", "季度 (date)",GC.Spread.Pivot.PivotTableFieldType.columnField);
myPivotTable.add("total", this.currentFieldName, GC.Spread.Pivot.PivotTableFieldType.valueField);
myPivotTable.subTotalType(this.currentFieldName, GC.Pivot.SubtotalType.sum);
myPivotTable.add("total", "Average of total", GC.Spread.Pivot.PivotTableFieldType.valueField, GC.Pivot.SubtotalType.average);
myPivotTable.resumeLayout();
myPivotTable.autoFitColumn();
return myPivotTable;
}
function getUniqueName(pt, name, index = 0) {
let realName = index === 0 ? name : name + (index + "");
if (pt.getField(realName)) {
return getUniqueName(pt, name, index + 1);
} else {
return realName;
}
}
function bindEvent (spread, pt) {
_getElementById("subtotalType").addEventListener("change", () => {
if (pt) {
var type = parseInt(_getElementById("subtotalType").value, 10);
var newFieldName = SubtotalType[type];
pt.subTotalType(currentFieldName, type);
newFieldName = getUniqueName(pt, newFieldName);
pt.updateFieldName(currentFieldName, newFieldName);
pt.autoFitColumn();
currentFieldName = newFieldName;
}
});
_getElementById("subtotalType2").addEventListener("change", () => {
if (pt) {
var type = parseInt(_getElementById("subtotalType2").value, 10);
pt.subTotalType("Average of total", type);
pt.autoFitColumn();
pt.refresh();
}
});
_getElementById("positionType").addEventListener("change", () => {
updateDataPosition(pt);
});
_getElementById("positionIndex").addEventListener("change", () => {
updateDataPosition(pt);
});
_getElementById("pt_subtotalsPosition").addEventListener("change", () => {
if (pt) {
var subtotalPosition = parseInt(_getElementById("pt_subtotalsPosition").value, 10);
pt.options.subtotalsPosition = subtotalPosition;
pt.autoFitColumn();
}
});
_getElementById("field_subtotalsPosition").addEventListener("change", () => {
if (pt) {
var subtotalPosition = parseInt(_getElementById("field_subtotalsPosition").value, 10);
pt.subtotalPosition("Cars", subtotalPosition);
pt.autoFitColumn();
}
});
_getElementById("field_subtotalsVisible").addEventListener("change", () => {
if (pt) {
var subtotalPosition = parseInt(_getElementById("field_subtotalsVisible").value, 10);
pt.subtotalVisible("Cars", !!subtotalPosition);
pt.autoFitColumn();
}
});
}
function updateDataPosition (pt) {
if (pt) {
var positionType = parseInt(_getElementById("positionType").value, 10);
var positionIndex = parseInt(_getElementById("positionIndex").value, 10);
pt.dataPosition(positionType, positionIndex);
}
}
function _getElementById(id) {
return document.getElementById(id);
}
<!doctype html>
<html style="height:100%;font-size:14px;">
<head>
<meta name="spreadjs culture" content="zh-cn" />
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<link rel="stylesheet" type="text/css"
href="$DEMOROOT$/zh/purejs/node_modules/@grapecity-software/spread-sheets/styles/gc.spread.sheets.excel2013white.css">
<script src="$DEMOROOT$/zh/purejs/node_modules/@grapecity-software/spread-sheets/dist/gc.spread.sheets.all.min.js"
type="text/javascript"></script>
<script
src="$DEMOROOT$/zh/purejs/node_modules/@grapecity-software/spread-sheets-shapes/dist/gc.spread.sheets.shapes.min.js"
type="text/javascript"></script>
<script
src="$DEMOROOT$/zh/purejs/node_modules/@grapecity-software/spread-sheets-pivot-addon/dist/gc.spread.pivot.pivottables.min.js"
type="text/javascript"></script>
<script src="$DEMOROOT$/spread/source/data/pivot-data.js" type="text/javascript"></script>
<script
src="$DEMOROOT$/zh/purejs/node_modules/@grapecity-software/spread-sheets-resources-zh/dist/gc.spread.sheets.resources.zh.min.js"
type="text/javascript"></script>
<script src="$DEMOROOT$/spread/source/js/license.js" type="text/javascript"></script>
<script src="app.js" type="text/javascript"></script>
<link rel="stylesheet" type="text/css" href="styles.css">
</head>
<body>
<div class="sample-tutorial">
<div id="ss" class="sample-spreadsheets"></div>
<div class="options-container">
<div class="option-row">
<label><b>设置</b>
</label>
</div>
<hr />
<div class="option-row">
更改 <b>总计和</b> 字段(D-G 列)的小计类型:
<select id="subtotalType">
<option value="0">平均值(Average)</option>
<option value="1">计数(Count)</option>
<option value="2">数值计数(CountNums)</option>
<option value="3">最大值(Maximum)</option>
<option value="4">最小值(Minimum)</option>
<option value="5">乘积(Product)</option>
<option value="6">标准偏差(StdDev)</option>
<option value="7">总体标准偏差(StdDevP)</option>
<option value="8" selected>求和(Sum)</option>
<option value="9">方差(Variance)</option>
<option value="10">总体方差(VarP)</option>
</select>
</div>
<div class="option-row">
更改 <b>总计平均值</b> 字段(H-M 列)的小计类型:
<select id="subtotalType2">
<option value="0" selected>平均值(Average)</option>
<option value="1">计数(Count)</option>
<option value="2">数值计数(CountNums)</option>
<option value="3">最大值(Maximum)</option>
<option value="4">最小值(Minimum)</option>
<option value="5">乘积(Product)</option>
<option value="6">标准偏差(StdDev)</option>
<option value="7">总体标准偏差(StdDevP)</option>
<option value="8">求和(Sum)</option>
<option value="9">方差(Variance)</option>
<option value="10">总体方差(VarP)</option>
</select>
</div>
<div class="option-row">
更改数据位置。
<div class="option-item">
位置类型:
<select id="positionType">
<option value="0">行</option>
<option value="1" selected>列</option>
</select>
</div>
<div class="option-item">
位置索引:
<input type="number" value="0" id="positionIndex" min="0" max="2" />
</div>
</div>
<div class="option-row">
更改数据透视表小计位置:
<div class="option-item">
小计位置:
<select name="" id="pt_subtotalsPosition" class="select-option-select">
<option value="0">无</option>
<option value="1">顶部</option>
<option value="2" selected>底部</option>
</select>
</div>
</div>
<div class="option-row">
更改 <b>车辆</b> 字段的小计位置:
<div class="option-item">
小计位置:
<select name="" id="field_subtotalsPosition" class="select-option-select">
<option value="1">顶部</option>
<option value="2" selected>底部</option>
</select>
</div>
</div>
<div class="option-row">
设置 <b>车辆</b> 字段是否显示小计:
<div class="option-item">
小计可见性:
<select name="" id="field_subtotalsVisible" class="select-option-select">
<option value="0">无</option>
<option value="1" selected>显示</option>
</select>
</div>
</div>
</div>
</div>
</body>
</html>
.sample-tutorial {
position: relative;
height: 100%;
}
.sample-spreadsheets {
width: calc(100% - 220px);
height: 100%;
overflow: hidden;
float: left;
}
.options-container {
float: right;
width: 220px;
padding: 12px;
height: 100%;
box-sizing: border-box;
background: #fbfbfb;
overflow: auto;
scrollbar-width: none;
-ms-overflow-style: none;
}
.options-container::-webkit-scrollbar {
display: none;
}
.option-row {
font-size: 14px;
padding: 5px;
margin-top: 10px;
}
.option-item {
margin-top: 5px;
}
.option-item #positionType {
float: right;
width: 80px;
}
.option-item #positionIndex {
float: right;
width: 72px;
}
body {
position: absolute;
top: 0;
bottom: 0;
left: 0;
right: 0;
}
#app {
height: 100%;
}