Pivot Set Node Value

SpreadJS PivotTables support editing values in the data area. The overwritten value can update the subtotal calculation.

Description
app.vue
index.html
styles.css
Copy to CodeMine

Pivot Node Info

To edit the value of a PivotTable, the first step is to describe the cell.
We defined an interface called PivotNodeInfo which can describe the field and value info of a pivot table node.

export interface GC.Spread.Pivot.IPivotNodeInfo {
    fieldInfos: GC.Spread.Pivot.IPivotItemInfo[];
    valueInfo: GC.Pivot.IValueInfo;
}

export interface GC.Pivot.IValueInfo {
    sourceName: string;
    subtotalType: GC.Pivot.SubtotalType;
}

// Sample code using IPivotNodeInfo
var nodeInfo = {
    fieldInfos: [
        {
            fieldName: 'Name',
            fieldItem: 'Jordan'
        }, {
            fieldName: 'Date', 
            fieldItem: 'Jan'
        }
    ],
    valueInfo: {
        sourceName: "Value",
        subtotalType: GC.Pivot.SubtotalType.sum
    }
};
pivotTable.setNodeValue(nodeInfo, 100);

Overwrite Value

Overwriting values in a pivot table will cause those values to be updated in the calculation.

This only supports [ sum, count, countNums, max, min ] types to include in calculation. Other types of subtotals will only change that subtotal value

If the overwritten value is a subtotal, its children nodes value will not change and its parent nodes will use its overwritten value to calculate directly, rather than calculating the result of its children nodes.

Updating the source will remove the values that would be set



API List

/**
 * Set overwrite value in the pivot cache.
 * @param {GC.Spread.Pivot.IPivotNodeInfo} nodeInfo the node info to be set.
 * @param {number} [value] the value set to the node info, if the value is null or undefined, this will remove the node info's value.
 */
setNodeValue(nodeInfo: GC.Spread.Pivot.IPivotNodeInfo,  value?: number): void;

/**
 * Compose overwrite info from the sheet row and column.
 * @param {number} row the sheet row of the cell.
 * @param {number} col the sheet column of the cell.
 * @returns {GC.Spread.Pivot.IPivotNodeInfo} the node info to get.
 */
getNodeInfo(row: number,  col: number): GC.Spread.Pivot.IPivotNodeInfo;

/**
 * Get value by the node info.
 * @param {GC.Spread.Pivot.IPivotNodeInfo} nodeInfo the node info to get.
 * @returns {number} the value of the node info.
 */
getNodeValue (nodeInfo: GC.Spread.Pivot.IPivotNodeInfo): number

/**
 * Clear overwrite info list.
 */
clearOverwriteList(): void;

/**
 * Get all overwrite info of the pivot cache.
 * @returns {GC.Spread.Pivot.IPivotOverwriteNodeInfo[]} overwrite info list.
 */
getOverwriteList(): GC.Spread.Pivot.IPivotOverwriteNodeInfo[];
Pivot Node Info To edit the value of a PivotTable, the first step is to describe the cell. We defined an interface called PivotNodeInfo which can describe the field and value info of a pivot table node. Overwrite Value Overwriting values in a pivot table will cause those values to be updated in the calculation. This only supports [ sum, count, countNums, max, min ] types to include in calculation. Other types of subtotals will only change that subtotal value If the overwritten value is a subtotal, its children nodes value will not change and its parent nodes will use its overwritten value to calculate directly, rather than calculating the result of its children nodes. Updating the source will remove the values that would be set API List
<template> <div class="sample-tutorial"> <div class="sjss"> <gc-spread-sheets class="sample-spreadsheets" @workbookInitialized="initSpread"></gc-spread-sheets> <gc-spread-sheets class="sample-spreadsheets-details" @workbookInitialized="initDetailsSpread"></gc-spread-sheets> </div> <div class="sample-panel"> <div id="config"> <h2>Overwrite Panel</h2> <div class="option node"> <label class="control-label"> <input id="enableDataValueEditing" type="checkbox" style="display: none" v-model="enableDataValueEditing" @change="changeOption()" /> <div class="check"></div> <div class="circle"></div> </label> <label for="enableDataValueEditing" class="label-text">Enable Edit Value in Data Area</label> </div> <br /> <div class="node"> <div class="textarea-block"> <span>Active Cell Node Info:</span> <textarea name="nodeInfo" id="nodeInfo" cols="20" rows="10" v-model="nodeInfo"></textarea> </div> <div class="label-block"> <span>Node Value:</span> <input type="number" name="nodeValue" id="nodeValue" placeholder="value" v-model="nodeValue" /> </div> <button id="setNodeValue" @click="setNodeValue()">SET</button> </div> <div class="sync-value"> <h3>Notice:</h3> <div>"Sync Value" will sync changing data back to the orginal data table by average.</div> <div>The PivotTable will update source and the overwrite info will be cleared.</div> <button id="syncValue" @click="syncValue()">Sync Value</button> </div> </div> </div> </div> </template> <script> import Vue from "vue"; import "@mescius/spread-sheets-vue"; import GC from "@mescius/spread-sheets"; import "@mescius/spread-sheets-shapes"; import "@mescius/spread-sheets-pivot-addon"; import "./styles.css"; let App = Vue.extend({ name: "app", data: function () { return { spread: null, detailsSpread: null, pivotTable: null, enableDataValueEditing: true, nodeValue: null, nodeInfo: "", }; }, methods: { initDetailsSpread: function (detailsSpread) { this.detailsSpread = detailsSpread; let spread = this.spread; spread.suspendPaint(); spread.setSheetCount(2); let sheet1 = spread.getSheet(0); let sheet2 = spread.getSheet(1); let tableName = this.getDataSource(sheet1, pivotSales); this.pivotTable = this.initPivotTable(sheet2, tableName); spread.setActiveSheet("PivotTable"); let detailsSheet = detailsSpread.getSheet(0); detailsSheet.name("PivotTable Details"); this.addEvent(sheet2, detailsSheet, this.pivotTable); spread.resumePaint(); }, initSpread: function (spread) { this.spread = spread; }, addEvent: function (sheet2, detailsSheet, pivotTable) { detailsSheet.setRowCount(700); detailsSheet.setColumnCount(10); detailsSheet.addSpan(0, 0, 2, 6); let style = new GC.Spread.Sheets.Style(); style.wordWrap = true; detailsSheet.setStyle(0, 0, style); detailsSheet.setValue( 0, 0, "In PivotTable of SpreadJS, pivot table show detail will find all merged data in data source" ); sheet2.addSpan(0, 0, 2, 5); sheet2.setStyle(0, 0, style); sheet2.setValue( 0, 0, "You Can Edit Cell Values in Data Area.\nuncheck the 'Enable Edit Value in Data Area' to close edit ablilty" ); sheet2.bind(GC.Spread.Sheets.Events.CellClick, (sender, args) => { this.updateDetailsSheet(detailsSheet, args.row, args.col, pivotTable); }); }, getPivotDetails: function (row, col, pivotTable) { let pivotInfo = pivotTable.getPivotInfo(row, col), detailsObj = []; if (!pivotInfo || pivotInfo.area !== 4) { return void 0; } let colInfo = pivotInfo.colInfos; let rowInfo = pivotInfo.rowInfos; this.setDetails(colInfo, detailsObj); this.setDetails(rowInfo, detailsObj); let dataSource = pivotTable.getPivotDetails(detailsObj); this.updateNodeInfo(pivotTable, row, col); return dataSource; }, updateNodeInfo(pivotTable, row, col) { let nodeInfo = pivotTable.getNodeInfo(row, col); if (!nodeInfo) { return; } this.nodeInfo = JSON.stringify(nodeInfo, null, 4); this.infoList = JSON.stringify(pivotTable.getOverwriteList(), null, 4) || ""; this.nodeValue = pivotTable.getNodeValue(nodeInfo) || ""; this.enableDataValueEditing = pivotTable.options.enableDataValueEditing; }, setDetails: function (rowOrColInfo, detailsObj) { if (rowOrColInfo && rowOrColInfo.length > 0) { for (let item of rowOrColInfo) { if (item.isGrandTotal) { break; } detailsObj.push({ fieldName: item.fieldName, fieldItem: item.itemName, }); } } }, getDataSource: function (sheet, tableSource) { 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("table", 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, tableSource); this.table = table; this.sourceSheet = sheet; return table.name(); }, changeOption() { this.pivotTable.enableDataValueEditing = !this.pivotTable.enableDataValueEditing; this.enableDataValueEditing = this.pivotTable.enableDataValueEditing; }, setNodeValue() { if (!this.nodeInfo) { return; } let nodeInfo = JSON.parse(this.nodeInfo); let nodeValue = parseInt(this.nodeValue, 10); if (nodeInfo && nodeValue !== undefined && nodeValue !== null && !isNaN(nodeValue)) { this.pivotTable.setNodeValue(nodeInfo, nodeValue); this.infoList = JSON.stringify( this.pivotTable.getOverwriteList(), null, 4 ) || ""; } }, updateDetailsSheet (detailsSheet, row, col, pivotTable) { detailsSheet.suspendPaint(); let dataSource = this.getPivotDetails( row, col, pivotTable ); detailsSheet.tables.remove("details"); if (dataSource) { let table = detailsSheet.tables.add( "details", 2, 0, dataSource.length, dataSource[0].length ); table.style(GC.Spread.Sheets.Tables.TableThemes["none"]); } detailsSheet.getCell(-1, 0).formatter("YYYY-mm-DD"); detailsSheet.getRange(-1, 4, 0, 2).formatter("$ #,##0"); detailsSheet.setColumnWidth(0, 120); detailsSheet.setArray(2, 0, dataSource); detailsSheet.resumePaint(); }, syncValue: function () { let pivotTable = this.pivotTable, table = this.table, sheet = this.sourceSheet; let list = pivotTable.getOverwriteList(); let tableRange = table.dataRange(); sheet.clearFormula( tableRange.row, 5, tableRange.rowCount, 1, function () { return true; } ); if (list && list.length > 0) { let columnNames = []; for ( let i = tableRange.col; i < tableRange.col + tableRange.colCount; i++ ) { columnNames[i] = table.getColumnName(i); } let filter = table.rowFilter(); let compareType = GC.Spread.Sheets.ConditionalFormatting.TextCompareType .equalsTo; list.forEach((info) => { let fieldInfos = info.fieldInfos; let details = pivotTable.getPivotDetails(fieldInfos); fieldInfos.forEach((fieldInfo) => { let expected = fieldInfo.fieldItem; let ptField = pivotTable.getField(fieldInfo.fieldName); let fieldType = ptField.dataType; let sourceName = ptField.sourceName; let columnIndex = columnNames.indexOf(sourceName); if (fieldType === GC.Pivot.PivotDataType.date) { for (let i = 1; i < details.length; i++) { let dateValue = GC.Spread.Sheets.CellTypes.Base.prototype.format( new Date(details[i][columnIndex]), "YYYY-mm-DD" ); let condition = new GC.Spread.Sheets.ConditionalFormatting.Condition( GC.Spread.Sheets.ConditionalFormatting.ConditionType.textCondition, { compareType: compareType, expected: dateValue, } ); filter.addFilterItem(columnIndex, condition); } } else { filter.addFilterItem( columnIndex, new GC.Spread.Sheets.ConditionalFormatting.Condition( GC.Spread.Sheets.ConditionalFormatting.ConditionType.textCondition, { compareType: compareType, expected: expected, } ) ); } }); filter.filter(); let valueColIndex = columnNames.indexOf( info.valueInfo.sourceName ); let leftRowIndexes = []; for ( let i = tableRange.row; i < tableRange.row + tableRange.rowCount; i++ ) { if (!filter.isRowFilteredOut(i)) { leftRowIndexes.push(i); } } let avgValue = info.value / leftRowIndexes.length; for (let i = 0; i < leftRowIndexes.length; i++) { sheet.setValue( leftRowIndexes[i], valueColIndex, avgValue ); } filter.reset(); }); } pivotTable.updateSource(); let detailsSheet = this.detailsSpread.getSheet(0), ptSheet = this.spread.getSheet(1); this.updateDetailsSheet(detailsSheet, ptSheet.getActiveRowIndex(), ptSheet.getActiveColumnIndex(), pivotTable); }, initPivotTable: function (sheet, tableName) { sheet.name("PivotTable"); sheet.setRowCount(1000); let option = { showRowHeader: true, showColumnHeader: true, bandRows: true, bandColumns: true, enableDataValueEditing: true, }; let pivotTable = sheet.pivotTables.add( "pivotTable", tableName, 2, 0, GC.Spread.Pivot.PivotTableLayoutType.outline, GC.Spread.Pivot.PivotTableThemes.medium8, option ); pivotTable.suspendLayout(); pivotTable.add( "salesperson", "Salesperson", GC.Spread.Pivot.PivotTableFieldType.rowField ); pivotTable.add( "car", "Cars", GC.Spread.Pivot.PivotTableFieldType.rowField ); let groupInfo = { originFieldName: "date", dateGroups: [{ by: GC.Pivot.DateGroupType.quarters }], }; pivotTable.group(groupInfo); pivotTable.add( "Quarters (date)", "Date", GC.Spread.Pivot.PivotTableFieldType.columnField ); pivotTable.add( "total", "Totals", GC.Spread.Pivot.PivotTableFieldType.valueField, GC.Pivot.SubtotalType.sum ); let style = new GC.Spread.Sheets.Style(); style.formatter = "$ #,##0"; pivotTable.setStyle({ dataOnly: true }, style); pivotTable.resumeLayout(); pivotTable.autoFitColumn(); return pivotTable; }, }, computed: { dataSource() { return getData(); }, }, }); new Vue({ render: (h) => h(App) }).$mount("#app"); </script>
<!doctype html> <html style="height:100%;font-size:14px;"> <head> <meta charset="utf-8" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <link rel="stylesheet" type="text/css" href="$DEMOROOT$/en/vue/node_modules/@mescius/spread-sheets/styles/gc.spread.sheets.excel2013white.css"> <script src="$DEMOROOT$/spread/source/data/pivot-data.js" type="text/javascript"></script> <!-- SystemJS --> <script src="$DEMOROOT$/en/vue/node_modules/systemjs/dist/system.src.js"></script> <script src="systemjs.config.js"></script> <script> System.import('./src/app.vue'); System.import('$DEMOROOT$/en/lib/vue/license.js'); </script> </head> <body> <div id="app"></div> </body> </html>
.sample-tutorial { position: relative; height: 100%; } .sjss { width: 70%; height: 100%; display: inline-block; } .sample-spreadsheets { width: 100%; height: 58%; overflow: hidden; } .sample-spreadsheets-details{ width: 100%; height: 40%; overflow: hidden; } body { position: absolute; top: 0; bottom: 0; left: 0; right: 0; overflow: hidden; } #config { width: 75%; margin-left: 10%; } .sample-panel { position: relative; float: right; width: 30%; padding: 12px; height: 100%; box-sizing: border-box; background: #fbfbfb; overflow-y: auto; overflow-x: hidden; } #app { height: 100%; } label{ position: relative; } .check{ width: 2rem; height: 1rem; border-radius: 100rem; border: 1px solid #a19b9b; transition: .3s; } .circle{ width: 1rem; height: 1rem; border-radius: 50%; background: #a19b9b; position: absolute; left: 1px; top: 1px; transform: translateX(0rem); transition: .3s; } input:checked ~.check{ background: #0099CC; transition: .3s; border-color: #0099CC; } input:checked ~ .circle{ transform: translateX(1rem); transition: .3s; background: #EEEEEE; } .control-label { display: inline-block; top: 3px; } #config h2 { width: 100%; text-align: center; } .label-text { user-select: none; line-height: 20px; white-space: nowrap; } .option { font-size: 15px; vertical-align: middle; white-space: nowrap; } .node { width: 100%; position: relative; } .textarea-block textarea { outline: none; resize: none; border: none; width: 100%; border-top: 1px solid #0099CC; font-family: 'Calibri', 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; background-color: transparent; } #config button { line-height: 18px; padding: 8px 4px; font-size: 13px; border: 1px solid #0099CC; cursor: pointer; background: transparent; color: #0099CC; text-align: center; -webkit-transition-duration: 0.3s; /* Safari */ transition-duration: 0.3s; text-decoration: none; text-transform: uppercase; } #config button:hover { background-color: #008CBA; color: white; } #setNodeValue { position: absolute; width: 30%; right: 0; } .label-block { position: absolute; left: 0; border: 1px solid #0099CC; padding: 8px 4px; color: #0099CC; width: 50%; white-space:nowrap; font-size: 13px; overflow: hidden; line-height: 18px; } .label-block input { display: inline-block; border: none; outline: none; width: 48%; } .label-block span { display: inline-block; width: 48%; } .textarea-block { display: block; margin-bottom: 10px; margin-top: 10px; padding: 8px 8px; color: #0e84ab; border: 1px solid #0099CC; } input[type=number]::-webkit-inner-spin-button, input[type=number]::-webkit-outer-spin-button { -webkit-appearance: none; margin: 0; } input[type=number] { -moz-appearance:textfield; } #syncValue { display: inline-block; margin-left: 25%; margin-top: 10px; width: 50%; } .sync-value { margin-top: 60px; }
(function (global) { System.config({ transpiler: 'plugin-babel', babelOptions: { es2015: true }, meta: { '*.css': { loader: 'css' }, '*.vue': { loader: 'vue-loader' } }, paths: { // paths serve as alias 'npm:': 'node_modules/' }, // map tells the System loader where to look for things map: { '@mescius/spread-sheets': 'npm:@mescius/spread-sheets/index.js', '@mescius/spread-sheets-shapes': 'npm:@mescius/spread-sheets-shapes/index.js', '@mescius/spread-sheets-pivot-addon': 'npm:@mescius/spread-sheets-pivot-addon/index.js', '@mescius/spread-sheets-vue': 'npm:@mescius/spread-sheets-vue/index.js', '@grapecity/jsob-test-dependency-package/react-components': 'npm:@grapecity/jsob-test-dependency-package/react-components/index.js', 'jszip': 'npm:jszip/dist/jszip.js', 'css': 'npm:systemjs-plugin-css/css.js', 'vue': 'npm:vue/dist/vue.min.js', 'vue-loader': 'npm:systemjs-vue-browser/index.js', 'tiny-emitter': 'npm:tiny-emitter/index.js', 'plugin-babel': 'npm:systemjs-plugin-babel/plugin-babel.js', 'systemjs-babel-build':'npm:systemjs-plugin-babel/systemjs-babel-browser.js' }, // packages tells the System loader how to load when no filename and/or no extension packages: { src: { defaultExtension: 'js' }, rxjs: { defaultExtension: 'js' }, "node_modules": { defaultExtension: 'js' } } }); })(this);