PivotEngine Performance

The PivotEngine can summarize very large data sets. When the data is bound from the client-side, the summarized data is can be generated very fast. Data summaries are generated asynchronously, to improve user interaction. The sample below demonstrates performance by showing the time it takes to load various sized data sets (up to 1 million).

import 'bootstrap.css'; import '@grapecity/wijmo.styles/wijmo.css'; import './styles.css'; import * as wjOlap from '@grapecity/wijmo.olap'; import * as wjCore from '@grapecity/wijmo'; import { addData } from './data'; // document.readyState === 'complete' ? init() : window.onload = init; // function init() { // // initialize data sets var ds10 = addData([], 10e3), ds100 = [], ds500 = [], ds1M = [], result = document.getElementById('result'), start = 0; // // initialize pivot engine var ng = new wjOlap.PivotEngine({ autoGenerateFields: false, fields: [ { binding: 'date', header: 'Date', format: 'yyyy' }, { binding: 'buyer', header: 'Person' }, { binding: 'type', header: 'Category' }, { binding: 'amount', header: 'Amount', format: 'c0', aggregate: 'Sum' } ], itemsSource: ds10, showRowTotals: 'Subtotals', valueFields: ['Amount'], rowFields: ['Person', 'Category'], // // benchmark updatingView: function () { if (start == 0) { start = Date.now(); } }, updatedView: function (s) { var fmt = 'Summarized <b>{cnt:n0}</b> items in <b>{tm:n0}</b>ms'; result.innerHTML = wjCore.format(fmt, { cnt: s.itemsSource.length, tm: Date.now() - start }); start = 0; } }); // // show summary var pivotGrid = new wjOlap.PivotGrid('#pivotGrid', { itemsSource: ng }); // // handle click events to apply different data sources document.getElementById('buttons').addEventListener('click', function (e) { switch (e.target.id) { case '10k': ng.itemsSource = ds10; break; case '100k': ng.itemsSource = ds100; break; case '500k': ng.itemsSource = ds500; break; case '1M': ng.itemsSource = ds1M; break; } }); // // create large data asynchronously createDataAsync(100e3, function (result) { ds100 = result; enableButton('100k'); }); createDataAsync(500e3, function (result) { ds500 = result; enableButton('500k'); }); createDataAsync(1e6, function (result) { ds1M = result; enableButton('1M'); }); function enableButton(id) { document.getElementById(id).disabled = false; } // // // create data asynchronously function createDataAsync(cnt, callback) { var data = []; addDataAsync(data, cnt, function () { callback(data); }); } function addDataAsync(data, cnt, callback) { setTimeout(function () { addData(data, Math.min(cnt - data.length, 1000)); if (data.length == cnt) { callback(data); } else { addDataAsync(data, cnt, callback); } }); } } <!DOCTYPE html> <html lang="en"> <head> <meta charset="utf-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <title>Grapecity Wijmo OLAP Pivot Engine</title> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <!-- SystemJS --> <script src="node_modules/systemjs/dist/system.src.js"></script> <script src="systemjs.config.js"></script> <script> System.import('./src/app'); </script> </head> <body> <div class="container-fluid"> <div id="buttons"> <button id="10k" class="btn btn-primary"> 10k items </button> <button id="100k" class="btn btn-primary" disabled> 100k items </button> <button id="500k" class="btn btn-primary" disabled> 500k items </button> <button id="1M" class="btn btn-primary" disabled> One Million items </button> </div> <p id="result"> </p> <div class="output"> <div id="pivotGrid"></div> </div> <p> If you deal with massive data sets, with millions of records, you should consider using server-side OLAP providers like SSAS cubes or ComponentOne Data Services. The <b>PivotEngine</b> can connect to either. </p> </div> </body> </html> function randomItem(arr) { return arr[Math.floor(Math.random() * arr.length)]; } export function addData(data, cnt) { var today = Date.now(), buyers = 'Mom,Dad,Kelly,Sheldon'.split(','), types = 'Food,Clothes,Fuel,Books,Sports,Music'.split(','); for (var i = 0; i < cnt; i++) { data.push({ date: today - Math.random() * 365 * 3, buyer: randomItem(buyers), type: randomItem(types), amount: 20 + Math.random() * 1000 }); } return data; } #buttons { display: flex; margin-bottom: 6px; } #buttons .btn { margin: 6px; white-space: normal; } .output { display: flex; justify-content: center; margin: 6px; } .wj-pivotgrid { width: auto; max-height: 300px; } body { margin-bottom: 36pt; } import 'bootstrap.css'; import '@grapecity/wijmo.styles/wijmo.css'; import './styles.css'; import * as wjCore from '@grapecity/wijmo'; import * as wjOlap from '@grapecity/wijmo.olap'; // import { Component, Inject, enableProdMode, NgModule, AfterViewInit } from '@angular/core'; import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; import { BrowserModule } from '@angular/platform-browser'; import { WjOlapModule } from '@grapecity/wijmo.angular2.olap'; import { DataService, DataItem } from './app.data'; // @Component({ selector: 'app-component', templateUrl: 'src/app.component.html' }) export class AppComponent implements AfterViewInit { ng: wjOlap.PivotEngine; ds10: DataItem[]; ds100: DataItem[]; ds500: DataItem[]; ds1M: DataItem[]; start: number = 0; enable100K: boolean = true; enable500K: boolean = true; enable1M: boolean = true; result: string; // constructor(@Inject(DataService) private dataService: DataService) { var self = this; self.ds10 = dataService.addData([], 10e3); self.ng = new wjOlap.PivotEngine({ autoGenerateFields: false, fields: [ // specify the fields we want { binding: 'date', header: 'Date', format: 'yyyy' }, { binding: 'buyer', header: 'Person' }, { binding: 'type', header: 'Category' }, { binding: 'amount', header: 'Amount', format: 'c0', aggregate: 'Sum' } ], itemsSource: self.ds10, showRowTotals: 'Subtotals', valueFields: ['Amount'], rowFields: ['Person', 'Category'], updatingView: function () { if (self.start == 0) { self.start = Date.now(); } }, updatedView: function (s: wjOlap.PivotEngine) { var fmt = 'Summarized <b>{cnt:n0}</b> items in <b>{tm:n0}</b>ms'; self.result = wjCore.format(fmt, { cnt: s.itemsSource.length, tm: Date.now() - self.start }); self.start = 0; } }); } // ngAfterViewInit() { var self = this; self._createDataAsync(100e3, function (result: DataItem[]) { self.ds100 = result; self.enable100K = false; }); self._createDataAsync(500e3, function (result: DataItem[]) { self.ds500 = result; self.enable500K = false; }); self._createDataAsync(1e6, function (result: DataItem[]) { self.ds1M = result; self.enable1M = false; }); } // onButtonClick(e: MouseEvent) { switch ((e.target as HTMLElement).id) { case '10k': this.ng.itemsSource = this.ds10; break; case '100k': this.ng.itemsSource = this.ds100; break; case '500k': this.ng.itemsSource = this.ds500; break; case '1M': this.ng.itemsSource = this.ds1M; break; } } // _createDataAsync(cnt: number, callback: (result: DataItem[]) => void) { var data: DataItem[] = []; this._addDataAsync(data, cnt, function () { callback(data); }); } _addDataAsync(data: DataItem[], cnt: number, callback: (result: DataItem[]) => void) { var self = this; setTimeout(function () { self.dataService.addData(data, Math.min(cnt - data.length, 1000)); if (data.length == cnt) { callback(data); } else { self._addDataAsync(data, cnt, callback); } }); } _enableButton(id: string) { (document.getElementById(id) as HTMLButtonElement).disabled = false; } } // @NgModule({ imports: [WjOlapModule, BrowserModule], declarations: [AppComponent], providers: [DataService], bootstrap: [AppComponent] }) export class AppModule { } // enableProdMode(); // Bootstrap application with hash style navigation and global services. platformBrowserDynamic().bootstrapModule(AppModule); <!DOCTYPE html> <html lang="en"> <head> <meta charset="utf-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <title>Grapecity Wijmo OLAP Pivot Engine Performance</title> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <!-- Polyfills --> <script src="node_modules/core-js/client/shim.min.js"></script> <script src="node_modules/zone.js/dist/zone.min.js"></script> <!-- SystemJS --> <script src="node_modules/systemjs/dist/system.js"></script> <script src="systemjs.config.js"></script> <script> // workaround to load 'rxjs/operators' from the rxjs bundle System.import('rxjs').then(function (m) { System.set(SystemJS.resolveSync('rxjs/operators'), System.newModule(m.operators)); System.import('./src/app.component'); }); </script> </head> <body> <app-component></app-component> </body> </html> <div class="container-fluid"> <div id="buttons" (click)="onButtonClick($event)"> <button id="10k" class="btn btn-primary"> 10k items </button> <button id="100k" class="btn btn-primary" [disabled]="enable100K"> 100k items </button> <button id="500k" class="btn btn-primary" [disabled]="enable500K"> 500k items </button> <button id="1M" class="btn btn-primary" [disabled]="enable1M"> One Million items </button> </div> <p id="result" [innerHtml]="result"> </p> <div class="output"> <wj-pivot-grid [itemsSource]="ng"></wj-pivot-grid> </div> <p> If you deal with massive data sets, with millions of records, you should consider using server-side OLAP providers like SSAS cubes or ComponentOne Data Services. The <b>PivotEngine</b> can connect to either. </p> </div> import { Injectable } from '@angular/core'; export interface DataItem { date: number; buyer: string; type: string; amount: number; } function randomItem(arr: string[]): string { return arr[Math.floor(Math.random() * arr.length)]; } @Injectable() export class DataService { addData(data: DataItem[], cnt: number): DataItem[] { var today = Date.now(), buyers = 'Mom,Dad,Kelly,Sheldon'.split(','), types = 'Food,Clothes,Fuel,Books,Sports,Music'.split(','); for (var i = 0; i < cnt; i++) { data.push({ date: today - Math.random() * 365 * 3, buyer: randomItem(buyers), type: randomItem(types), amount: 20 + Math.random() * 1000 }); } return data; } } #buttons { display: flex; margin-bottom: 6px; } #buttons .btn { margin: 6px; white-space: normal; } .output { display: flex; justify-content: center; margin: 6px; } .wj-pivotgrid { width: auto; max-height: 300px; } body { margin-bottom: 36pt; } <template> <div class="container-fluid"> <div id="buttons" v-on:click="onButtonClick"> <button id="10k" class="btn btn-primary"> 10k items </button> <button id="100k" class="btn btn-primary" :disabled="enable100K"> 100k items </button> <button id="500k" class="btn btn-primary" :disabled="enable500K"> 500k items </button> <button id="1M" class="btn btn-primary" :disabled="enable1M"> One Million items </button> </div> <p id="result" v-html="result"> </p> <div class="output"> <wj-pivot-grid id="pivotGrid" :items-source="ng"></wj-pivot-grid> </div> <p> If you deal with massive data sets, with millions of records, you should consider using server-side OLAP providers like SSAS cubes or ComponentOne Data Services. The <b>PivotEngine</b> can connect to either. </p> </div> </template> <script> import '@grapecity/wijmo.styles/wijmo.css'; import 'bootstrap.css'; import Vue from 'vue'; import '@grapecity/wijmo.vue2.olap'; import * as wjcCore from '@grapecity/wijmo'; import * as wjcOlap from '@grapecity/wijmo.olap'; import { addData } from './data'; let App = Vue.extend({ name: "app", data: function() { return { ds10: addData([], 10e3), ds100: null, ds500: null, ds1M: null, start: 0, enable100K: true, enable500K: true, enable1M: true, result: '' }; }, created: function() { this.ng = new wjcOlap.PivotEngine({ autoGenerateFields: false, fields: [ // specify the fields we want { binding: 'date', header: 'Date', format: 'yyyy' }, { binding: 'buyer', header: 'Person' }, { binding: 'type', header: 'Category' }, { binding: 'amount', header: 'Amount', format: 'c0', aggregate: 'Sum' } ], itemsSource: addData([], 10e3), showRowTotals: 'Subtotals', valueFields: ['Amount'], rowFields: ['Person', 'Category'], updatingView: () => { if (this.start == 0) { this.start = Date.now(); } }, updatedView: (s) => { let fmt = 'Summarized <b>{cnt:n0}</b> items in <b>{tm:n0}</b>ms'; this.result = wjcCore.format(fmt, { cnt: s.itemsSource.length, tm: Date.now() - this.start }); this.start = 0; } }); }, mounted: function() { this._createDataAsync(100e3, (result) => { this.ds100 = result; this.enable100K = false; }); this._createDataAsync(500e3, (result) => { this.ds500 = result; this.enable500K = false; }); /*this._createDataAsync(1e6, (result) => { this.ds1M = result; this.enable1M = false; });*/ }, methods: { onButtonClick(e) { switch (e.target.id) { case '10k': this.ng.itemsSource = this.ds10; break; case '100k': this.ng.itemsSource = this.ds100; break; case '500k': this.ng.itemsSource = this.ds500; break; case '1M': this.ng.itemsSource = this.ds1M; break; } }, _createDataAsync(cnt, callback) { let data = []; this._addDataAsync(data, cnt, () => { callback(data); }); }, _addDataAsync (data, cnt, callback) { setTimeout(() => { addData(data, Math.min(cnt - data.length, 1000)); if (data.length == cnt) { callback(data); } else { this._addDataAsync(data, cnt, callback); } }); } } }); new Vue({ render: h => h(App) }).$mount("#app"); </script> <style> #buttons { display: flex; margin-bottom: 6px; } #buttons .btn { margin: 6px; white-space: normal; } .output { display: flex; justify-content: center; margin: 6px; } #pivotGrid { width: auto; max-height: 300px; } body { margin-bottom: 36pt; } </style> <!DOCTYPE html> <html lang="en"> <head> <meta charset="utf-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <title>Grapecity Wijmo OLAP Pivot Engine Performance</title> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <!-- SystemJS --> <script src="node_modules/systemjs/dist/system.src.js"></script> <script src="systemjs.config.js"></script> <script> System.import('./src/app.vue'); </script> </head> <body> <div id="app"> </div> </body> </html> export function addData(data, cnt) { var today = Date.now(), buyers = 'Mom,Dad,Kelly,Sheldon'.split(','), types = 'Food,Clothes,Fuel,Books,Sports,Music'.split(','); for (var i = 0; i < cnt; i++) { data.push({ date: today - Math.random() * 365 * 3, buyer: randomItem(buyers), type: randomItem(types), amount: 20 + Math.random() * 1000 }); } return data; } function randomItem(arr) { return arr[Math.floor(Math.random() * arr.length)]; } import './app.css'; import 'bootstrap.css'; import '@grapecity/wijmo.styles/wijmo.css'; // import * as React from 'react'; import * as ReactDOM from 'react-dom'; // import * as Olap from '@grapecity/wijmo.react.olap'; import * as wjcCore from '@grapecity/wijmo'; import * as wjcOlap from '@grapecity/wijmo.olap'; import { addData } from './data'; class App extends React.Component { constructor(props) { super(props); this._start = 0; this.state = { ds10: addData([], 10e3), ds100: null, ds500: null, ds1M: null, enable100K: true, enable500K: true, enable1M: true, ng: new wjcOlap.PivotEngine({ autoGenerateFields: false, fields: [ { binding: 'date', header: 'Date', format: 'yyyy' }, { binding: 'buyer', header: 'Person' }, { binding: 'type', header: 'Category' }, { binding: 'amount', header: 'Amount', format: 'c0', aggregate: 'Sum' } ], itemsSource: addData([], 10e3), showRowTotals: 'Subtotals', valueFields: ['Amount'], rowFields: ['Person', 'Category'], updatingView: () => { if (this._start == 0) { this._start = Date.now(); } }, updatedView: (s) => { let fmt = 'Summarized <b>{cnt:n0}</b> items in <b>{tm:n0}</b>ms'; let tm = Date.now() - this._start; let result = wjcCore.format(fmt, { cnt: s.itemsSource.length, tm }); this._result.innerHTML = result; this._start = 0; } }) }; } render() { return (<div className="container-fluid"> <div id="buttons" onClick={this.onButtonClick.bind(this)}> <button id="10k" className="btn btn-primary"> 10k items </button> <button id="100k" className="btn btn-primary" disabled={this.state.enable100K}> 100k items </button> <button id="500k" className="btn btn-primary" disabled={this.state.enable500K}> 500k items </button> <button id="1M" className="btn btn-primary" disabled={this.state.enable1M}> One Million items </button> </div> <p ref={el => this._result = el} id="result"> </p> <div className="output"> <Olap.PivotGrid id="pivotGrid" itemsSource={this.state.ng}></Olap.PivotGrid> </div> <p> If you deal with massive data sets, with millions of records, you should consider using server-side OLAP providers like SSAS cubes or ComponentOne Data Services. The <b>PivotEngine</b> can connect to either. </p> </div>); } onButtonClick(e) { switch (e.target.id) { case '10k': this.state.ng.itemsSource = this.state.ds10; break; case '100k': this.state.ng.itemsSource = this.state.ds100; break; case '500k': this.state.ng.itemsSource = this.state.ds500; break; case '1M': this.state.ng.itemsSource = this.state.ds1M; break; } this.setState({ ng: this.state.ng }); } _createDataAsync(cnt, callback) { let data = []; this._addDataAsync(data, cnt, () => { callback(data); }); } _addDataAsync(data, cnt, callback) { setTimeout(() => { addData(data, Math.min(cnt - data.length, 1000)); if (data.length == cnt) { callback(data); } else { this._addDataAsync(data, cnt, callback); } }); } componentDidMount() { this._createDataAsync(100e3, (result) => { this.setState({ ds100: result, enable100K: false }); }); this._createDataAsync(500e3, (result) => { this.setState({ ds500: result, enable500K: false }); }); /*this._createDataAsync(1e6, (result) => { this.setState({ ds1M: result, enable1M: false }); });*/ } } ReactDOM.render(<App />, document.getElementById('app')); <!DOCTYPE html> <html lang="en"> <head> <meta charset="utf-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <title>AutoComplete</title> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <!-- SystemJS --> <script src="node_modules/systemjs/dist/system.src.js"></script> <script src="systemjs.config.js"></script> <script> System.import('./src/app'); </script> </head> <body> <div id="app"></div> </body> </html> #buttons { display: flex; margin-bottom: 6px; } #buttons .btn { margin: 6px; white-space: normal; } .output { display: flex; justify-content: center; margin: 6px; } #pivotGrid { width: auto; max-height: 300px; } body { margin-bottom: 36pt; } export function addData(data, cnt) { var today = Date.now(), buyers = 'Mom,Dad,Kelly,Sheldon'.split(','), types = 'Food,Clothes,Fuel,Books,Sports,Music'.split(','); for (var i = 0; i < cnt; i++) { data.push({ date: today - Math.random() * 365 * 3, buyer: randomItem(buyers), type: randomItem(types), amount: 20 + Math.random() * 1000 }); } return data; } function randomItem(arr) { return arr[Math.floor(Math.random() * arr.length)]; }