PivotGrid with Sparklines

You can use the formatItem event to add custom content such as sparklines and sparkbars to grid cells. This example adds two extra fields to a PivotEngine and uses the formatItem event to add sparklines and sparkbars to the extra fields. The engine's getDetail method is also used retrieve the detail records for each cell and uses that data to build svg elements displayed in each cell.

import 'bootstrap.css'; import '@grapecity/wijmo.styles/wijmo.css'; import './styles.css'; // import * as wijmo from '@grapecity/wijmo'; import * as olap from '@grapecity/wijmo.olap'; import { getData } from './data'; // document.readyState === 'complete' ? init() : window.onload = init; // function init() { let a = scaleY(1, 0, 100); let b = scaleY(10, 0, 100); let c = scaleY(10, 0, 50); let d = scaleY(20, 0, 50); let e = scaleY(30, 0, 50); let f = scaleY(40, 0, 50); let g = scaleY(45, 0, 50); let h = scaleY(49, 0, 50); // create a PivotEngine with a custom view let ng = new olap.PivotEngine({ autoGenerateFields: false, itemsSource: getData(1000), showColumnTotals: 'Subtotals', showRowTotals: 'Subtotals', fields: [ { binding: 'product', header: 'Product', width: 100 }, { binding: 'date', header: 'Date', format: 'yyyy \"Q\"q', width: 100 }, { binding: 'sales', header: 'Sales', format: 'n0', width: 100 }, { binding: 'sales', header: 'Sparklines', width: 100 }, { binding: 'sales', header: 'Sparkbars', width: 100 } ], rowFields: ['Date'], columnFields: ['Product'], valueFields: ['Sales', 'Sparklines', 'Sparkbars'] }); // // show panel let pivotPanel = new olap.PivotPanel('#pivotPanel', { itemsSource: ng }); // // show summary let pivotGrid = new olap.PivotGrid('#pivotGrid', { isReadOnly: true, itemsSource: ng, formatItem: formatItem // customize the grid cells }); // // use formatItem to add sparklines and/or sparkbars let maxSparkLength = 25; function formatItem(s, e) { // we want the cells panel if (e.panel == s.cells) { // we want the 'Sparklines' and 'Sparkbars' value fields let ng = s.engine, field = ng.valueFields[e.col % ng.valueFields.length], item = s.rows[e.row].dataItem, binding = s.columns[e.col].binding, spark = field.header == 'Sparklines' || field.header == 'Sparkbars'; // // add/remove spark class wijmo.toggleClass(e.cell, 'spark', spark); // // add sparklines if (spark) { // if we have the data, show it if (item.sparkData) { let data = item.sparkData, delta = data[data.length - 1] - data[0]; // e.cell.innerHTML = field.header == 'Sparklines' ? getSparklines(item.sparkData) : getSparkbars(item.sparkData); wijmo.toggleClass(e.cell, 'spark-up', delta > 0); wijmo.toggleClass(e.cell, 'spark-down', delta < 0); } // // we dont have the data yet, so go get it if (!item.sparkData) { e.cell.innerHTML = ''; setTimeout(function () { let detail = s.engine.getDetail(item, binding), len = detail.length; // if (len > maxSparkLength) { detail = detail.slice(len - maxSparkLength); } // item.sparkData = detail.map(dataItem => dataItem.sales); // s.invalidate(); // invalidate to show the sparlines }); } } } } // // generate sparklines as SVG function getSparklines(data) { if (!data.length) { return ''; } // let svg = '<svg width="100%" height="100%">', min = Math.min.apply(Math, data), max = Math.max.apply(Math, data), x1 = 0, y1 = scaleY(data[0], min, max); // for (let i = 1; i < data.length; i++) { let x2 = Math.round((i) / (data.length - 1) * 100), y2 = scaleY(data[i], min, max); // svg += `<line x1="${x1}%" y1="${y1}%" x2="${x2}%" y2="${y2}%" />`; x1 = x2; y1 = y2; } // svg += '</svg>'; return svg; } // function getSparkbars(data) { if (!data.length) { return ''; } // let svg = '<svg width="100%" height="100%">', min = Math.min.apply(Math, data), max = Math.max.apply(Math, data), base = Math.min(max, Math.max(min, 0)), basey = scaleY(base, min, max), w = Math.round(100 / data.length) - 2; // for (let i = 0; i < data.length; i++) { let x = i * Math.round(100 / data.length) + 1, y = scaleY(data[i], min, max); svg += `<rect x="${x}%" width="${w}%" y="${Math.min(y, basey)}%" height="${Math.abs(y - basey)}%" />`; } // svg += `<rect x="0%" width="100%" height="1" y="${basey}%" opacity="0.5" />`; svg += '</svg>'; return svg; } // // min <= value <= max function scaleY(value, min, max) { if (min === max) { return 0; } // return 100 - Math.round((value - min) / (max - min) * 100); } } <!DOCTYPE html> <html lang="en"> <head> <meta charset="utf-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <title>Grapecity Wijmo OLAP Pivot Grid Sparkline</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 class="row"> <div class="col-xs-5"> <div id="pivotPanel"></div> </div> <div class="col-xs-7"> <div id="pivotGrid"></div> </div> </div> </div> </body> </html> // export function getData(cnt) { let year = new Date().getFullYear(), data = []; // for (let i = 0; i < cnt; i++) { data.push({ product: randomInt(0, 1) ? 'Wijmo' : 'Aoba', country: randomInt(0, 1) ? 'USA' : 'Japan', active: i % 2 == 0, date: new Date(year - randomInt(0, 2), randomInt(0, 11), randomInt(0, 27) + 1), sales: randomInt(10, 20), downloads: randomInt(10, 200) }); } // return data; } // function randomInt(min, max) { return Math.floor(Math.random() * (max - min + 1) + min); } .wj-pivotgrid { max-height: 400px; box-shadow: 0 10px 20px rgba(0,0,0,0.19), 0 6px 6px rgba(0,0,0,0.23); } .wj-pivotgrid .wj-cell.spark { padding: 8px; overflow: visible; } .wj-pivotgrid .wj-cell.spark svg { overflow: visible; stroke: currentColor; fill: currentColor; } .wj-pivotgrid .wj-cell.spark.spark-up svg { color: #009000; /* green for up */ } .wj-pivotgrid .wj-cell.spark.spark-down svg { color: #d00000; /* red for down */ } body { margin-bottom: 48pt; } 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 * as wjGrid from '@grapecity/wijmo.grid'; // import { Component, Inject, enableProdMode, NgModule, ViewChild } 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'; const maxSparkLength = 25; // @Component({ selector: 'app-component', templateUrl: 'src/app.component.html' }) export class AppComponent { @ViewChild('grid') pivotGrid: wjOlap.PivotGrid; ng: wjOlap.PivotEngine; // constructor(@Inject(DataService) private dataService: DataService) { this.ng = new wjOlap.PivotEngine({ autoGenerateFields: false, itemsSource: dataService.getData(1000), showColumnTotals: 'Subtotals', showRowTotals: 'Subtotals', fields: [ { binding: 'product', header: 'Product', width: 100 }, { binding: 'date', header: 'Date', format: 'yyyy \"Q\"q', width: 100 }, { binding: 'sales', header: 'Sales', format: 'n0', width: 100 }, { binding: 'sales', header: 'Sparklines', width: 100 }, { binding: 'sales', header: 'Sparkbars', width: 100 } ], rowFields: ['Date'], columnFields: ['Product'], valueFields: ['Sales', 'Sparklines', 'Sparkbars'] }); } // formatItem(e: wjGrid.FormatItemEventArgs) { // we want the cells panel let g = this.pivotGrid; if (e.panel == this.pivotGrid.cells) { // we want the 'Sparklines' and 'Sparkbars' value fields let ng = g.engine, field = ng.valueFields[e.col % ng.valueFields.length], item = g.rows[e.row].dataItem, binding = g.columns[e.col].binding, spark = field.header == 'Sparklines' || field.header == 'Sparkbars'; // // add/remove spark class wjCore.toggleClass(e.cell, 'spark', spark); // // add sparklines if (spark) { // // if we have the data, show it if (item.sparkData) { let data = item.sparkData, delta = data[data.length - 1] - data[0]; // e.cell.innerHTML = field.header == 'Sparklines' ? this._getSparklines(item.sparkData) : this._getSparkbars(item.sparkData); wjCore.toggleClass(e.cell, 'spark-up', delta > 0); wjCore.toggleClass(e.cell, 'spark-down', delta < 0); } // // we dont have the data yet, so go get it if (!item.sparkData) { e.cell.innerHTML = ''; setTimeout(function () { let detail: DataItem[] = g.engine.getDetail(item, binding), len = detail.length; // if (len > maxSparkLength) { detail = detail.slice(len - maxSparkLength); } item.sparkData = detail.map(dataItem => dataItem.sales); g.invalidate(); // invalidate to show the sparlines }); } } } } // // generate sparklines as SVG private _getSparklines(data: number[]) { if (!data.length) { return ''; } // let svg = '<svg width="100%" height="100%">', min = Math.min.apply(Math, data), max = Math.max.apply(Math, data), x1 = 0, y1 = this._scaleY(data[0], min, max); // for (let i = 1; i < data.length; i++) { let x2 = Math.round((i) / (data.length - 1) * 100), y2 = this._scaleY(data[i], min, max); // svg += `<line x1="${x1}%" y1="${y1}%" x2="${x2}%" y2="${y2}%" />`; x1 = x2; y1 = y2; } // svg += '</svg>'; return svg; } // private _getSparkbars(data: number[]) { if (!data.length) { return ''; } // let svg = '<svg width="100%" height="100%">', min = Math.min.apply(Math, data), max = Math.max.apply(Math, data), base = Math.min(max, Math.max(min, 0)), basey = this._scaleY(base, min, max), w = Math.round(100 / data.length) - 2; // for (let i = 0; i < data.length; i++) { let x = i * Math.round(100 / data.length) + 1, y = this._scaleY(data[i], min, max); svg += `<rect x="${x}%" width="${w}%" y="${Math.min(y, basey)}%" height="${Math.abs(y - basey)}%" />`; } // svg += `<rect x="0%" width="100%" height="1" y="${basey}%" opacity="0.5" />`; svg += '</svg>'; return svg; } // private _scaleY(value: number, min: number, max: number) { if (min === max) { return 0; } // return 100 - Math.round((value - min) / (max - min) * 100); } } // @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 Grid Sparkline</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 class="row"> <div class="col-xs-5"> <wj-pivot-panel [itemsSource]="ng"></wj-pivot-panel> </div> <div class="col-xs-7"> <wj-pivot-grid #grid [itemsSource]="ng" [isReadOnly]=true (formatItem)="formatItem($event)"></wj-pivot-grid> </div> </div> </div> import { Injectable } from '@angular/core'; // export interface DataItem { product: string; country: string; active: boolean; date: Date; sales: number; downloads: number; } // function randomInt(min: number, max: number): number { return Math.floor(Math.random() * (max - min + 1) + min); } // @Injectable() export class DataService { getData(cnt: number): DataItem[] { let year = new Date().getFullYear(), data = []; // for (let i = 0; i < cnt; i++) { data.push({ product: randomInt(0, 1) ? 'Wijmo' : 'Aoba', country: randomInt(0, 1) ? 'USA' : 'Japan', active: i % 2 == 0, date: new Date(year - randomInt(0, 2), randomInt(0, 11), randomInt(0, 27) + 1), sales: randomInt(10, 20), downloads: randomInt(10, 200) }); } // return data; } } .wj-pivotgrid { max-height: 400px; box-shadow: 0 10px 20px rgba(0,0,0,0.19), 0 6px 6px rgba(0,0,0,0.23); } .wj-pivotgrid .wj-cell.spark { padding: 8px; overflow: visible; } .wj-pivotgrid .wj-cell.spark svg { overflow: visible; stroke: currentColor; fill: currentColor; } .wj-pivotgrid .wj-cell.spark.spark-up svg { color: #009000; /* green for up */ } .wj-pivotgrid .wj-cell.spark.spark-down svg { color: #d00000; /* red for down */ } body { margin-bottom: 48pt; } <template> <div class="container-fluid"> <div class="row"> <div class="col-xs-5"> <wj-pivot-panel :itemsSource="ng"></wj-pivot-panel> </div> <div class="col-xs-7"> <wj-pivot-grid :itemsSource="ng" :isReadOnly=true :initialized="initializePivotGrid" :formatItem="formatItem"></wj-pivot-grid> </div> </div> </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 { getData } from './data' const maxSparkLength = 25; let App = Vue.extend({ name: "app", data: function() { return { ng: new wjcOlap.PivotEngine({ autoGenerateFields: false, itemsSource: getData(1000), showColumnTotals: 'Subtotals', showRowTotals: 'Subtotals', fields: [ { binding: 'product', header: 'Product', width: 100 }, { binding: 'date', header: 'Date', format: 'yyyy \"Q\"q', width: 100 }, { binding: 'sales', header: 'Sales', format: 'n0', width: 100 }, { binding: 'sales', header: 'Sparklines', width: 100 }, { binding: 'sales', header: 'Sparkbars', width: 100 } ], rowFields: ['Date'], columnFields: ['Product'], valueFields: ['Sales', 'Sparklines', 'Sparkbars'] }) }; }, methods: { initializePivotGrid(pivotGrid) { this.pivotGrid = pivotGrid; }, formatItem(s, e) { // we want the cells panel let pivotGrid = this.pivotGrid; if (e.panel == this.pivotGrid.cells) { // we want the 'Sparklines' and 'Sparkbars' value fields let ng = pivotGrid.engine, field = ng.valueFields[e.col % ng.valueFields.length], item = pivotGrid.rows[e.row].dataItem, binding = pivotGrid.columns[e.col].binding, spark = field.header == 'Sparklines' || field.header == 'Sparkbars'; // // add/remove spark class wjcCore.toggleClass(e.cell, 'spark', spark); // add sparklines if (spark) { // if we have the data, show it if (item.sparkData) { let data = item.sparkData, delta = data[data.length - 1] - data[0]; // e.cell.innerHTML = field.header == 'Sparklines' ? this._getSparklines(item.sparkData) : this._getSparkbars(item.sparkData); wjcCore.toggleClass(e.cell, 'spark-up', delta > 0); wjcCore.toggleClass(e.cell, 'spark-down', delta < 0); } // we dont have the data yet, so go get it if (!item.sparkData) { e.cell.innerHTML = ''; setTimeout(() => { let detail = pivotGrid.engine.getDetail(item, binding), len = detail.length; if (len > maxSparkLength) { detail = detail.slice(len - maxSparkLength); } item.sparkData = detail.map(dataItem => dataItem.sales); pivotGrid.invalidate(); // invalidate to show the sparlines }); } } } }, // generate sparklines as SVG _getSparklines(data) { if (!data.length) { return ''; } // let svg = '<svg width="100%" height="100%">', min = Math.min.apply(Math, data), max = Math.max.apply(Math, data), x1 = 0, y1 = this._scaleY(data[0], min, max); // for (let i = 1; i < data.length; i++) { let x2 = Math.round((i) / (data.length - 1) * 100), y2 = this._scaleY(data[i], min, max); // svg += `<line x1="${x1}%" y1="${y1}%" x2="${x2}%" y2="${y2}%" />`; x1 = x2; y1 = y2; } // svg += '</svg>'; return svg; }, _getSparkbars(data) { if (!data.length) { return ''; } // let svg = '<svg width="100%" height="100%">', min = Math.min.apply(Math, data), max = Math.max.apply(Math, data), base = Math.min(max, Math.max(min, 0)), basey = this._scaleY(base, min, max), w = Math.round(100 / data.length) - 2; // for (let i = 0; i < data.length; i++) { let x = i * Math.round(100 / data.length) + 1, y = this._scaleY(data[i], min, max); svg += `<rect x="${x}%" width="${w}%" y="${Math.min(y, basey)}%" height="${Math.abs(y - basey)}%" />`; } // svg += `<rect x="0%" width="100%" height="1" y="${basey}%" opacity="0.5" />`; svg += '</svg>'; return svg; }, _scaleY(value, min, max) { if (min === max) { return 0; } // return 100 - Math.round((value - min) / (max - min) * 100); } } }); new Vue({ render: h => h(App) }).$mount("#app"); </script> <style> .wj-pivotgrid { max-height: 400px; box-shadow: 0 10px 20px rgba(0,0,0,0.19), 0 6px 6px rgba(0,0,0,0.23); } .wj-pivotgrid .wj-cell.spark { padding: 8px; overflow: visible; } .wj-pivotgrid .wj-cell.spark svg { overflow: visible; stroke: currentColor; fill: currentColor; } .wj-pivotgrid .wj-cell.spark.spark-up svg { color: #009000; /* green for up */ } .wj-pivotgrid .wj-cell.spark.spark-down svg { color: #d00000; /* red for down */ } body { margin-bottom: 48pt; } </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 Grid Sparkline</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 getData(cnt) { let year = new Date().getFullYear(), data = []; // for (let i = 0; i < cnt; i++) { data.push({ product: randomInt(0, 1) ? 'Wijmo' : 'Aoba', country: randomInt(0, 1) ? 'USA' : 'Japan', active: i % 2 == 0, date: new Date(year - randomInt(0, 2), randomInt(0, 11), randomInt(0, 27) + 1), sales: randomInt(10, 20), downloads: randomInt(10, 200) }); } // return data; } // function randomInt(min, max) { return Math.floor(Math.random() * (max - min + 1) + min); } 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 { getData } from './data'; const maxSparkLength = 25; class App extends React.Component { constructor(props) { super(props); this.state = { ng: new wjcOlap.PivotEngine({ autoGenerateFields: false, itemsSource: getData(1000), showColumnTotals: 'Subtotals', showRowTotals: 'Subtotals', fields: [ { binding: 'product', header: 'Product', width: 100 }, { binding: 'date', header: 'Date', format: 'yyyy \"Q\"q', width: 100 }, { binding: 'sales', header: 'Sales', format: 'n0', width: 100 }, { binding: 'sales', header: 'Sparklines', width: 100 }, { binding: 'sales', header: 'Sparkbars', width: 100 } ], rowFields: ['Date'], columnFields: ['Product'], valueFields: ['Sales', 'Sparklines', 'Sparkbars'] }) }; } render() { return (<div className="container-fluid"> <div className="row"> <div className="col-xs-5"> <Olap.PivotPanel itemsSource={this.state.ng}></Olap.PivotPanel> </div> <div className="col-xs-7"> <Olap.PivotGrid itemsSource={this.state.ng} isReadOnly={true} initialized={this.initializePivotGrid.bind(this)} formatItem={this.formatItem.bind(this)}></Olap.PivotGrid> </div> </div> </div>); } initializePivotGrid(sender) { this._grid = sender; } formatItem(s, e) { // we want the cells panel let g = this._grid; if (e.panel == this._grid.cells) { // we want the 'Sparklines' and 'Sparkbars' value fields let ng = g.engine, field = ng.valueFields[e.col % ng.valueFields.length], item = g.rows[e.row].dataItem, binding = g.columns[e.col].binding, spark = field.header == 'Sparklines' || field.header == 'Sparkbars'; // // add/remove spark class wjcCore.toggleClass(e.cell, 'spark', spark); // add sparklines if (spark) { // if we have the data, show it if (item.sparkData) { let data = item.sparkData, delta = data[data.length - 1] - data[0]; // e.cell.innerHTML = field.header == 'Sparklines' ? this._getSparklines(item.sparkData) : this._getSparkbars(item.sparkData); wjcCore.toggleClass(e.cell, 'spark-up', delta > 0); wjcCore.toggleClass(e.cell, 'spark-down', delta < 0); } // we dont have the data yet, so go get it if (!item.sparkData) { e.cell.innerHTML = ''; setTimeout(() => { let detail = g.engine.getDetail(item, binding), len = detail.length; if (len > maxSparkLength) { detail = detail.slice(len - maxSparkLength); } item.sparkData = detail.map(dataItem => dataItem.sales); g.invalidate(); // invalidate to show the sparlines }); } } } } // generate sparklines as SVG _getSparklines(data) { if (!data.length) { return ''; } // let svg = '<svg width="100%" height="100%">', min = Math.min.apply(Math, data), max = Math.max.apply(Math, data), x1 = 0, y1 = this._scaleY(data[0], min, max); // for (let i = 1; i < data.length; i++) { let x2 = Math.round((i) / (data.length - 1) * 100), y2 = this._scaleY(data[i], min, max); // svg += `<line x1="${x1}%" y1="${y1}%" x2="${x2}%" y2="${y2}%" />`; x1 = x2; y1 = y2; } // svg += '</svg>'; return svg; } _getSparkbars(data) { if (!data.length) { return ''; } // let svg = '<svg width="100%" height="100%">', min = Math.min.apply(Math, data), max = Math.max.apply(Math, data), base = Math.min(max, Math.max(min, 0)), basey = this._scaleY(base, min, max), w = Math.round(100 / data.length) - 2; // for (let i = 0; i < data.length; i++) { let x = i * Math.round(100 / data.length) + 1, y = this._scaleY(data[i], min, max); svg += `<rect x="${x}%" width="${w}%" y="${Math.min(y, basey)}%" height="${Math.abs(y - basey)}%" />`; } // svg += `<rect x="0%" width="100%" height="1" y="${basey}%" opacity="0.5" />`; svg += '</svg>'; return svg; } _scaleY(value, min, max) { if (min === max) { return 0; } // return 100 - Math.round((value - min) / (max - min) * 100); } } 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> .wj-pivotgrid { max-height: 400px; box-shadow: 0 10px 20px rgba(0,0,0,0.19), 0 6px 6px rgba(0,0,0,0.23); } .wj-pivotgrid .wj-cell.spark { padding: 8px; overflow: visible; } .wj-pivotgrid .wj-cell.spark svg { overflow: visible; stroke: currentColor; fill: currentColor; } .wj-pivotgrid .wj-cell.spark.spark-up svg { color: #009000; /* green for up */ } .wj-pivotgrid .wj-cell.spark.spark-down svg { color: #d00000; /* red for down */ } body { margin-bottom: 48pt; } // export function getData(cnt) { let year = new Date().getFullYear(), data = []; // for (let i = 0; i < cnt; i++) { data.push({ product: randomInt(0, 1) ? 'Wijmo' : 'Aoba', country: randomInt(0, 1) ? 'USA' : 'Japan', active: i % 2 == 0, date: new Date(year - randomInt(0, 2), randomInt(0, 11), randomInt(0, 27) + 1), sales: randomInt(10, 20), downloads: randomInt(10, 200) }); } // return data; } // function randomInt(min, max) { return Math.floor(Math.random() * (max - min + 1) + min); }